我正在寻找一种在透明PNG中检测图像的javascript方法。 例如,我将使用940x680的透明画布创建一个PNG,然后在该画布中的某处放置一个完整的不透明对象。
我希望能够检测到画布中不透明的对象的大小(h / w)和顶部+左侧位置
以下是原始图片的示例 这是我想要实现的一个例子。 (边界框覆盖,带有顶部+左边距数据)
我找到了一个可以进行透明度检测的资源,但我不确定如何将这样的东西扩展到我想要的东西。
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);
}
答案 0 :(得分:9)
您需要做什么:
这些扫描可以合并,但为了简单起见,我将分别显示每个步骤。
<强> Online demo of this can be found here. 强>
结果:
当加载图像时将其绘制出来(如果图像很小,那么本例中的其余部分将是浪费,因为您在绘制时会知道坐标 - 假设此处您绘制的图像很大且内部有一个小图像)
(注意:为简单起见,这是一个非优化版本)
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>