How to detect shape on a transparent canvas?

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:

Snapshot

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!

Leave a Comment