【问题标题】:How to detect shape on a transparent canvas?如何检测透明画布上的形状?
【发布时间】:2014-04-23 21:46:00
【问题描述】:

我正在寻找一种在透明 PNG 中检测形状的方法。 例如,我将创建一个 940x680 的透明画布,然后在该画布的某处放置一个完全不透明的对象。

我希望能够检测到该对象的大小(w、h)和左上角位置。

这是原始图像的示例:

这是我想要实现的示例(边界框覆盖,带有顶部 + 左边距数据):

我找到了一个可以进行透明度检测的资源,但我不确定如何将这样的东西扩展到我正在寻找的东西。

var imgData,
    width = 200,
    height = 200;

$('#mask').bind('mousemove', function(ev){
    if(!imgData){ initCanvas(); }
    var imgPos = $(this).offset(),
      mousePos = {x : ev.pageX - imgPos.left, y : ev.pageY - imgPos.top},
      pixelPos = 4*(mousePos.x + height*mousePos.y),
         alpha = imgData.data[pixelPos+3];

    $('#opacity').text('Opacity = ' + ((100*alpha/255) << 0) + '%');
});

function initCanvas(){
    var canvas = $('<canvas width="'+width+'" height="'+height+'" />')[0],
           ctx = canvas.getContext('2d');

    ctx.drawImage($('#mask')[0], 0, 0);
    imgData = ctx.getImageData(0, 0, width, height);
}

Fiddle

【问题讨论】:

    标签: javascript html canvas html5-canvas png


    【解决方案1】:

    你需要做什么:

    • 获取缓冲区
    • 获取该缓冲区的 32 位引用(如果您的其他像素是透​​明的,那么您可以使用 Uint32Array 缓冲区进行迭代)。
    • 扫描 0 - 宽度以找到 x1 边缘
    • 扫描宽度 - 0 以查找 x2 边缘
    • 扫描 0 - 高度以找到 y1 边
    • 扫描高度 - 0 找到 y2 边

    这些扫描可以组合,但为简单起见,我将分别显示每个步骤。

    Online demo of this can be found here.

    结果:

    加载图像时将其绘制(如果图像很小,则此示例的其余部分将浪费,因为您在绘制它时会知道坐标 - 假设您绘制的图像很大,其中有一个小图像)

    (注意:为简单起见,这是一个未优化的版本)

    ctx.drawImage(this, 0, 0, w, h);
    
    var idata = ctx.getImageData(0, 0, w, h),      // get image data for canvas
        buffer = idata.data,                       // get buffer (unnes. step)
        buffer32 = new Uint32Array(buffer.buffer), // get a 32-bit representation
        x, y,                                      // iterators
        x1 = w, y1 = h, x2 = 0, y2 = 0;            // min/max values
    

    然后扫描每个边缘。对于左边缘,每行从 0 扫描到宽度(未优化):

    // get left edge
    for(y = 0; y < h; y++) {                       // line by line
        for(x = 0; x < w; x++) {                   // 0 to width
            if (buffer32[x + y * w] > 0) {         // non-transparent pixel?
                if (x < x1) x1 = x;                // if less than current min update
            }
        }
    }
    

    对于右边缘,您只需反转 x 迭代器:

    // get right edge
    for(y = 0; y < h; y++) {                       // line by line
        for(x = w; x >= 0; x--) {                  // from width to 0
            if (buffer32[x + y * w] > 0) {
                if (x > x2) x2 = x;
            }
        }
    }
    

    顶边和底边也是如此,只是迭代器被颠倒了:

    // get top edge
    for(x = 0; x < w; x++) {
        for(y = 0; y < h; y++) {
            if (buffer32[x + y * w] > 0) {
                if (y < y1) y1 = y;
            }
        }
    }
    
    // get bottom edge
    for(x = 0; x < w; x++) {
        for(y = h; y >= 0; y--) {
            if (buffer32[x + y * w] > 0) {
                if (y > y2) y2 = y;
            }
        }
    }
    

    然后得到的区域是:

    ctx.strokeRect(x1, y1, x2-x1, y2-y1);
    

    您可以实施各种优化,但它们完全取决于场景,例如,如果您知道大致位置,那么您不必迭代所有行/列。

    您可以通过跳过 x 个像素来粗略地猜测他的位置,当您发现一个不透明的像素时,您可以基于此等方式创建一个最大搜索区域,但这超出了这里的范围。

    希望这会有所帮助!

    【讨论】:

    • 太棒了!非常感谢,这肯定会让我朝着正确的方向前进。
    • 在您急需的帮助之后,我能够将其开发为我需要的解决方案。但是,在 Safari 中进行测试后,Safari 似乎无法获取线条的坐标。您的小提琴反映了相同的结果。看来这可能与 Unit32Array 有关。 Unit32Array 函数在 Safari 上似乎也非常慢。您能否建议一个修复程序以允许 Safari、Ipad 等。
    • @MikeBarkemeyer 一定是 Safari 的问题,如果是这样,我们只能等待 Apple 解决此问题。如果未注册,您可以报告此问题。解决方法是使用 Uint8Array,跳过 4 个字节并测试 alpha 通道。
    • @MikeBarkemeyer 这是一个 Uint8 版本:jsfiddle.net/epistemex/v8eg7 当然,与 Uint32 相比,这自然会慢一些,但也许 Safari 处理得更好
    • 不幸的是,这在 safari 中也不起作用。也许我需要查看此实现的 PHP 方法以确保浏览器兼容性。再次感谢您与我合作。
    【解决方案2】:

    我最近需要类似的东西。虽然问题已得到解答,但我想发布我的代码以供将来参考。

    就我而言,我在空白/透明画布上绘制(字体)图标,并希望获得边界框。即使我知道图标的高度(使用字体大小,即高度),我也不知道宽度。所以我必须手动计算。

    我不确定是否有聪明的方法来计算这个。我脑海中闪现的第一件事就是用艰难的方式来做:手动检查每个像素,这就是我所做的。

    我认为代码是不言自明的,所以我不会做任何解释。我尽量保持代码干净。

    /* Layer 3: The App */
    
    let canvas = document.querySelector("#canvas");
    let input  = document.querySelector("#input");
    let output = document.querySelector("#output");
    
    canvas.width  = 256;
    canvas.height = 256;
    
    let context = canvas.getContext("2d");
    context.font = "200px Arial, sans-serif";
    
    let drawnLetter = null;
    
    drawLetter(input.value);
    
    function drawLetter(letter) {
        letter = letter ? letter[0] : null;
    
        if (!letter) {
            // clear canvas
            context.clearRect(0, 0, canvas.width, canvas.height);
            output.textContent = null;
            return;
        }
    
        if (letter == drawnLetter) {
            return;
        }
    
        drawnLetter = letter;
    
        // clear canvas
        context.clearRect(0, 0, canvas.width, canvas.height);
    
        // draw letter
        context.fillText(letter, 50, canvas.height - 50);
    
        // find edges
        let boundingBox = findEdges(context);
    
        // mark the edges
        context.beginPath();
        context.rect(boundingBox.left, boundingBox.top, boundingBox.width, boundingBox.height);
        context.lineWidth = 2;
        context.strokeStyle = "red";
        context.stroke();
    
        // output the values
        output.textContent = JSON.stringify(boundingBox, null, "  ");
    }
    
    
    /* Layer 2: Interacting with canvas */
    
    function findEdges(context) {
        let left   = findLeftEdge(context);
        let right  = findRightEdge(context);
        let top    = findTopEdge(context);
        let bottom = findBottomEdge(context);
        // right and bottom are relative to top left (0,0)
        return {
            left,
            top,
            right,
            bottom,
            width  : right - left,
            height : bottom - top,
        };
    }
    
    function findLeftEdge(context) {
        let imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
        let emptyPixel = [0, 0, 0, 0].join();
        for (let x = 0; x < context.canvas.width; x++) {
            for (let y = 0; y < context.canvas.height; y++) {
                let pixel = getPixel(imageData, x, y).join();
                if (pixel != emptyPixel) {
                    return x;
                }
            }
        }
    }
    
    function findRightEdge(context) {
        let imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
        let emptyPixel = [0, 0, 0, 0].join();
        for (let x = context.canvas.width - 1; x >= 0; x--) {
            for (let y = 0; y < context.canvas.height; y++) {
                let pixel = getPixel(imageData, x, y).join();
                if (pixel != emptyPixel) {
                    return x;
                }
            }
        }
    }
    
    function findTopEdge(context) {
        let imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
        let emptyPixel = [0, 0, 0, 0].join();
        for (let y = 0; y < context.canvas.height; y++) {
            for (let x = 0; x < context.canvas.width; x++) {
                let pixel = getPixel(imageData, x, y).join();
                if (pixel != emptyPixel) {
                    return y;
                }
            }
        }
    }
    
    function findBottomEdge(context) {
        let imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
        let emptyPixel = [0, 0, 0, 0].join();
        for (let y = context.canvas.height - 1; y >= 0; y--) {
            for (let x = 0; x < context.canvas.width; x++) {
                let pixel = getPixel(imageData, x, y).join();
                if (pixel != emptyPixel) {
                    return y;
                }
            }
        }
    }
    
    
    /* Layer 1: Interacting with ImageData */
    
    /**
     * Returns the pixel array at the specified position.
     */
    function getPixel(imageData, x, y) {
        return getPixelByIndex(imageData, pos2index(imageData, x, y));
    }
    
    /**
     * Returns the RGBA values at the specified index.
     */
    function getPixelByIndex(imageData, index) {
        return [
            imageData.data[index + 0],
            imageData.data[index + 1],
            imageData.data[index + 2],
            imageData.data[index + 3],
        ];
    }
    
    /**
     * Returns the index of a position.
     */
    function pos2index(imageData, x, y) {
        return 4 * (y * imageData.width + x);
    }
    body {
        background-color: hsl(0, 0%, 95%);
    }
    canvas {
        background: white;
        image-rendering: pixelated;
        background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEXMzMz////TjRV2AAAAEUlEQVQI12P4z8CAFWEX/Q8Afr8P8erzE9cAAAAASUVORK5CYII=);
        zoom: 0.8; /* this counters the scale up (125%) of my screen; can be removed */
    }
    input {
        padding: 0.2em;
        margin-top: 0.5em;
    }
    <canvas id="canvas"></canvas>
    
    <br>
    
    <input type="text" id="input" placeholder="type a letter" value="A" onkeyup="drawLetter(this.value)" />
    
    <pre id="output"></pre>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-02
      • 1970-01-01
      • 2020-04-09
      • 2019-07-05
      • 1970-01-01
      • 2020-09-18
      • 2022-01-26
      • 1970-01-01
      相关资源
      最近更新 更多