在 canvas getImageData 周围画一个方框
Draw a box around canvas getImageData
我目前正在处理 canvas 中的一些图像,我想在具有透明背景的图像周围绘制一个框(参见底部的示例)
我正在通过以下方式获取数据:context.getImageData(0, 0, canvas.width, canvas.height);
我的结果中已经只有黑色(不透明)数据;有了这个,我尝试了一些 stack-overflowers 建议的marching squares algorithm,但我不太明白。
我还尝试循环遍历所有数据并得到 minX
、minY
、maxX
、maxY
,所以我可以用这 4 个点画一个框,但我不知道如何使用它们。
对此有何建议?
逻辑步骤
要扫描形状的边界框,您可以执行以下步骤。假设您已经从 canvas 中提取了位图 (ImageData
),x
和 y
用于迭代位图:
从上到下逐行扫描。在找到第一个实心像素时,将当前 y
位置存储为 y1
,跳到下一步
从底部扫描到 y1
,逐行扫描。在找到第一个实心像素时,将当前 y
位置存储为 y2
在 y1
到 y2
范围内从左到右水平扫描。用 canvas 的 width 初始化 x1
。当在当前行 和 x
上找到实心像素时,其值 比当前行 x1
低 ,设置 x1
= 当前 x
并跳到下一行
从右到x1
水平扫描。用 0 初始化 x2
。当在当前行 和 x
上发现实心像素比当前行 x2
具有 更高 值时,设置 x2
= 当前 x
并跳到下一行
面积当然是:width = x2 - x1
, height = y2 - y1
.
抗锯齿形状会影响尺寸。您可以包括检查实体的 alpha 通道以减少这种影响。
可以通过更新循环以使用新的 x1/x2 值作为限制来优化左右边缘的扫描。使用 Uint32Array
检查像素值。
概念验证
显示上述步骤结果的非优化实施。它将检查任何非 alpha 值。例如,如果您想减少抗锯齿的影响,您可以将 0xff000000 替换为 0x80000000 以检查 alpha 值 > 127。如果您没有 alpha,只需检查实际颜色值(请注意,某些图像经过颜色校正,因此明智地考虑公差)。
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>
我目前正在处理 canvas 中的一些图像,我想在具有透明背景的图像周围绘制一个框(参见底部的示例)
我正在通过以下方式获取数据:context.getImageData(0, 0, canvas.width, canvas.height);
我的结果中已经只有黑色(不透明)数据;有了这个,我尝试了一些 stack-overflowers 建议的marching squares algorithm,但我不太明白。
我还尝试循环遍历所有数据并得到 minX
、minY
、maxX
、maxY
,所以我可以用这 4 个点画一个框,但我不知道如何使用它们。
对此有何建议?
逻辑步骤
要扫描形状的边界框,您可以执行以下步骤。假设您已经从 canvas 中提取了位图 (ImageData
),x
和 y
用于迭代位图:
从上到下逐行扫描。在找到第一个实心像素时,将当前
y
位置存储为y1
,跳到下一步从底部扫描到
y1
,逐行扫描。在找到第一个实心像素时,将当前y
位置存储为y2
在
y1
到y2
范围内从左到右水平扫描。用 canvas 的 width 初始化x1
。当在当前行 和x
上找到实心像素时,其值 比当前行x1
低 ,设置x1
= 当前x
并跳到下一行从右到
x1
水平扫描。用 0 初始化x2
。当在当前行 和x
上发现实心像素比当前行x2
具有 更高 值时,设置x2
= 当前x
并跳到下一行面积当然是:
width = x2 - x1
,height = y2 - y1
.
抗锯齿形状会影响尺寸。您可以包括检查实体的 alpha 通道以减少这种影响。
可以通过更新循环以使用新的 x1/x2 值作为限制来优化左右边缘的扫描。使用 Uint32Array
检查像素值。
概念验证
显示上述步骤结果的非优化实施。它将检查任何非 alpha 值。例如,如果您想减少抗锯齿的影响,您可以将 0xff000000 替换为 0x80000000 以检查 alpha 值 > 127。如果您没有 alpha,只需检查实际颜色值(请注意,某些图像经过颜色校正,因此明智地考虑公差)。
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>