如何检测透明画布上的形状? [英] How to detect shape on a transparent canvas?

查看:102
本文介绍了如何检测透明画布上的形状?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找一种检测透明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);
}

小提琴

解决方案

您需要做什么:

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

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

可以在此处找到在线演示.

结果:

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

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

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个像素,以蛮力猜测其位置,当您发现一个不透明的像素时,可以基于该像素进行最大搜索,依此类推,但这不在本文的讨论范围之内.

希望这会有所帮助!

I'm looking for a method of detecting a shape in a transparent PNG. For example, I will create a transparent canvas of 940x680, then place a fully opaque object somewhere in that canvas.

I want to be able to detect the size (w, h), and top + left location of that object.

Here is an example of the original image:

Here is an example of what I would like to achieve (Bounding box overlay, with top + left margin data):

I've found a resource that does some transparency detection, but I'm not sure how I scale something like this to what I'm looking for.

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

解决方案

What you need to do:

  • Get the buffer
  • Get a 32-bits reference of that buffer (If your other pixels are transparent then you can use a Uint32Array buffer to iterate).
  • Scan 0 - width to find x1 edge
  • Scan width - 0 to find x2 edge
  • Scan 0 - height to find y1 edge
  • Scan height - 0 to find y2 edge

These scans can be combined but for simplicity I'll show each step separately.

Online demo of this can be found here.

Result:

When image is loaded draw it in (if the image is small then the rest of this example would be waste as you would know the coordinates when drawing it - assuming here the image you draw is large with a small image inside it)

(note: this is a non-optimized version for the sake of simplicity)

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

Then scan each edge. For left edge you scan from 0 to width for each line (non optimized):

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

For the right edge you just reverse x iterator:

// 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;
        }
    }
}

And the same is for top and bottom edges just that the iterators are reversed:

// 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;
        }
    }
}

The resulting region is then:

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

There are various optimizations you could implement but they depend entirely on the scenario such as if you know approximate placement then you don't have to iterate all lines/columns.

You could do a brute force guess of he placement by skipping x number of pixels and when you found a non-transparent pixel you could make a max search area based on that and so forth, but that is out of scope here.

Hope this helps!

这篇关于如何检测透明画布上的形状?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆