Canvas有透明图像轮廓查找——Marching Squares算法

效果如下:

实现思路
  1. 找到一个在图片边缘上的像素(此步骤与算法无关),会从这个像素开始分析
  2. 考虑一个2×2像素正方形,它包含当前像素,通常位于正方形的左上角或右下角
  3. 此时,您有4个像素,每个像素都可以是透明的或不透明的。所以我们将有16个可能的2×2正方形,尽管所有透明像素或所有不透明像素的情况永远不会发生,因为我们在图像边缘周围移动。
  4. 根据2×2正方形中不透明像素的数量和位置,我们可以猜测轮廓的方向,沿着该方向移动当前像素,并从步骤2继续,直到再次到达步骤1中找到的像素
如何找到第一个像素(步骤一中的像素)

我们需要逐像素扫描图像,直到我们找到一个不透明的像素。

ini 复制代码
 function getFirstNonTransparentPixelTopDown(canvas){
    var context = canvas.getContext("2d");
    var y, i, rowData;
    for(y = 0; y < canvas.height; y++){
        rowData = context.getImageData(0, y, canvas.width, 1).data;
        for(i=0; i<rowData.length; i+=4){
            if(rowData[i+3] > 0){
                return {x : i/4, y : y};
            }
        }
    }
    return null;
};

完整代码见:MarchingSquaresJS 此方法查找出的轮廓,只包含最外部的,如果图片中间有透明部分不会查找出来。如果图片中间透明部分也查找出来,可以参考另一个包MarchingSquares

这个使用的时候会稍微复杂一些,需要手动获取imageData, 并根据像素是否透明转换为0和1,然后将数据传入获取最终路径。使用代码参考下面代码

ini 复制代码
function runMarchingSquares(){
            var sourceCanvas = document.createElement("canvas");
            sourceCanvas.width = canvas.width + 0;
            sourceCanvas.height = canvas.height + 0;
            sourceContext = sourceCanvas.getContext("2d");
            sourceContext.drawImage(canvas,1,1);

            let context = sourceContext;
            var y, i, rowData;
            var result = []
            for(y = 0; y < sourceCanvas.height; y++){
                rowData = context.getImageData(0, y, sourceCanvas.width, 1).data;
                var rowResult = [];
                for(i=0; i<rowData.length; i+=4){
                    rowResult.push(rowData[i+3] > 0?1:0)
                }
                result.push(rowResult)
            }
            const outlinePoints = MarchingSquaresJS .isoLines(result,  0.5, { linearRing: true })
            renderOutline(outlinePoints)
        }
        function renderOutline(outlinePoints){
            context.strokeStyle = "red";
            context.lineWidth = 2;
            outlinePoints.forEach(points => {
                context.beginPath();
                let last;
                points.forEach(([x,y]) => {
                    if(!last){
                        context.moveTo(x, y);
                    } else {
                        context.lineTo(x, y);
                    }
                    last = x+y;
                });
                context.stroke();
            });
        }

此库使用时,传入参数和输出结构都是二维数组,从上面代码能看出,我传入的参数每行数据是一组数据。它的输出结果是imageData的最外层,可根据情况自行处理。

不足

边缘不够平滑,待改进

优化方向

如果路径过多,会导致绘制时间长,可以使用路径简化算法减少路径点,可参考下面链接,第一篇文章有Ramer--Douglas--Peucker algorithm与Douglas-Peucker两种算法的对比;第二篇提供了实现,可以直接测试。

Line simplification algorithms

Ramer-Douglas-Peucker algorithm

参考

Marching Squares

Metaballs and Marching Squares

相关推荐
子兮曰1 天前
深入 HTML-in-Canvas:当 Canvas 学会了渲染 DOM,前端图形生态要变天了
前端·javascript·canvas
七夜zippoe3 天前
OpenClaw Canvas 可视化界面详解
可视化·canvas·flexbox·ai agent·openclaw
ncj3934379065 天前
Canvas 图形开发高频算法面试题
算法·canvas
pancakenut7 天前
从盒模型到画布:以mapbox为例
前端·canvas
ouzz16 天前
我在 React Canvas 里做了一个液态玻璃透镜效果
canvas·视觉设计
ouzz17 天前
使用纯canvas绘制一个掘金首页
前端·canvas
roamingcode17 天前
支付宝小程序数据可视化避坑指南:@antv/f2 踩坑与最佳实践
信息可视化·小程序·canvas·antv
可夫小子19 天前
不用 Tailscale:3 步把 Mac mini 通过 FRP 暴露到公网(稳定开机自启)
canvas
叫我一声阿雷吧22 天前
原生 JS 实现图片预览上传组件:多图上传 + 拖拽上传 + 裁剪预览 + 进度显示(附完整源码)
图片上传·canvas·文件上传·响应式布局·拖拽上传·原生javascript·filereader api
天若有情6731 个月前
Canvas生成艺术|意外诞生的混沌风暴(附完整源码+GitHub部署)
前端·css·html·github·canvas·网页