Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.8k views
in Technique[技术] by (71.8m points)

javascript - Draw a box around canvas getImageData

I'm currently working with some images in canvas, and I would like to draw a box around an image with a transparent background (see the example at the bottom)

I'm getting my data through: context.getImageData(0, 0, canvas.width, canvas.height);

I already got only the black (not transparent) data inside my result; with this I tried the marching squares algorithm suggested by a few stack-overflowers, but I couldn't quite figure that out.

I also tried cycling through all the data and get the minX, minY, maxX, maxY, so I can draw a box with these 4 points, but I didn't know how to them.
Any suggestions on this?


http://i.stack.imgur.com/VkxlW.png

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
 
Best answer

Logic steps

To scan for the bounding box of a shape you could do the following steps. Assuming you have extracted the bitmap (ImageData) from canvas, x and y are used to iterate over the bitmap:

  1. Scan from top to bottom, line by line. At first solid pixel found, store current y position as y1, skip to next step

  2. Scan from bottom to y1, line by line. At first solid pixel found, store current y position as y2

  3. Scan horizontally from left to right within y1 to y2. Initialize x1 with width of canvas. When a solid pixel is found on current line and x has lower value than current x1, set x1 = current x and skip to next line

  4. Scan horizontally from right to x1. Initialize x2 with 0. When a solid pixel is found on current line and x has higher value than current x2, set x2 = current x and skip to next line

  5. Size of area would of course be: width = x2 - x1, height = y2 - y1.

Anti-aliased shaped can affect the size. You can include checking alpha channel for solid to reduce this influence.

The scanning can be optimized for left and right edges by updating the loop to use the new x1/x2 value as limits. Use Uint32Array to check for pixel values.

Proof-of-concept

A non-optimized implementation showing the result of the above steps. It will check any non-alpha values. You can for example replace 0xff000000 with 0x80000000 to check alpha values > 127 if you want to reduce influence of the anti-aliasing. If you don't have alpha just check for the actual color value (note that some images are color corrected so a tolerance can be wise to consider).

var ctx = document.querySelector("canvas").getContext("2d"),
    btn = document.querySelector("button"),
    w = ctx.canvas.width,
    h = ctx.canvas.height,
    img = new Image();
img.crossOrigin = ""; img.onload = plot; img.src = "//i.imgur.com/lfsyAEc.png";

btn.onclick = plot;

function plot() {
  var iw = img.width, ih = img.height,
      x = Math.random() * (w - iw * 0.5), y = Math.random() * (h - ih * 0.5),
      s = (Math.random() * 30 - 15)|0;
  ctx.clearRect(0, 0, w, h);
  ctx.translate(x, y);
  ctx.rotate(Math.random() * Math.PI - Math.PI * 0.5);
  ctx.drawImage(img, -(iw + s) * 0.5, -(ih + s) * 0.5, iw + s, ih + s);
  ctx.setTransform(1,0,0,1,0,0);
  analyze();
}

function analyze() {
  var data = new Uint32Array(ctx.getImageData(0, 0, w, h).data.buffer),
      len = data.length,
      x, y, y1, y2, x1 = w, x2 = 0;
  
  // y1
  for(y = 0; y < h; y++) {
    for(x = 0; x < w; x++) {
      if (data[y * w + x] & 0xff000000) {
        y1 = y;
        y = h;
        break;
      }
    }
  }

  //todo y1 and the others can be undefined if no pixel is found.
  
  // y2
  for(y = h - 1; y > y1; y--) {
    for(x = 0; x < w; x++) {
      if (data[y * w + x] & 0xff000000) {
        y2 = y;
        y = 0;
        break;
      }
    }
  }

  // x1
  for(y = y1; y < y2; y++) {
    for(x = 0; x < w; x++) {
      if (x < x1 && data[y * w + x] & 0xff000000) {
        x1 = x;
        break;
      }
    }
  }

  // x2
  for(y = y1; y < y2; y++) {
    for(x = w - 1; x > x1; x--) {
      if (x > x2 && data[y * w + x] & 0xff000000) {
        x2 = x;
        break;
      }
    }
  }
  
  // mark area:
  ctx.strokeStyle = "hsl(" + (360 * Math.random()) + ", 80%, 50%)";
  ctx.strokeRect(x1 + 0.5, y1 + 0.5, x2 - x1, y2 - y1);
}
body {background:#aaa;margin:1px 0}
canvas {border:1px solid #777; background:#f0f0f2}
<button>Again</button><br>
<canvas width=640 height=165></canvas>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
by (100 points)

May I respectfully suggest, the following lines of code are added before: // mark areas

x1 -= 1;

x2 += 1;

y1 -= 1;

y2 += 1;

This will prevent the box overwriting the outer pixels of the image..

Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...