Javascript用于检测不透明的PNG区域的方法

时间:2014-04-23 21:46:01

标签: javascript html5 canvas html5-canvas png

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

我希望能够检测到画布中不透明的对象的大小(h / w)和顶部+左侧位置

以下是原始图片Transparent PNG Canvas with Image Object的示例 这是我想要实现的一个例子。 (边界框覆盖,带有顶部+左边距数据)

Results Image

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

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

2 个答案:

答案 0 :(得分:9)

您需要做什么:

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

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

<强> Online demo of this can be found here.

结果:

Snapshot

当加载图像时将其绘制出来(如果图像很小,那么本例中的其余部分将是浪费,因为您在绘制时会知道坐标 - 假设此处您绘制的图像很大且内部有一个小图像)

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

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个像素来做一个强力猜测他的位置,当你找到一个非透明像素时,你可以根据它创建一个最大搜索区域等等,但这不在这里。

希望这有帮助!

答案 1 :(得分:-1)

最近,我需要类似的东西。尽管问题得到了解答,但我还是希望发布代码以供将来参考。

在我的情况下,我正在空白/透明画布上绘制一个(字体)图标,并想要获取边界框。即使我知道图标的高度(使用字体大小,即高度),我也不知道宽度。所以我必须手动计算。

我不确定是否有聪明的方法来计算。突然出现在我头上的是困难的事情:手动检查每个像素,这就是我所做的。

我认为代码很不言自明,因此我不做任何解释。我试图保持代码尽可能的干净。

/* 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>