javascript 自动将 HTML5 画布裁剪为内容

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11796554/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 14:22:14  来源:igfitidea点击:

Automatically Crop HTML5 canvas to contents

javascripthtmlhtml5-canvas

提问by c24w

Let's say this is my canvas, with an evil-looking face drawn on it. I want to use toDataURL()to export my evil face as a PNG; however, the whole canvas is rasterised, including the 'whitespace' between the evil face and canvas edges.

假设这是我的画布,上面画了一张看起来很邪恶的脸。我想用来toDataURL()将我的邪恶面孔导出为 PNG;然而,整个画布都被光栅化了,包括邪恶面孔和画布边缘之间的“空白”。

+---------------+
|               |
|               |
|     (.Y. )    |
|      /_       |
|     \____/    |
|               |
|               |
+---------------+

What is the best way to crop/trim/shrinkwrap my canvas to its contents, so my PNG is no larger than the face's 'bounding-box', like below? The best way seems to be scaling the canvas, but supposing the contents are dynamic...? I'm sure there should be a simple solution to this, but it's escaping me, with much Googling.

将我的画布裁剪/修剪/收缩包裹到其内容的最佳方法是什么,因此我的 PNG 不大于面部的“边界框”,如下所示?最好的方法似乎是缩放画布,但假设内容是动态的......?我确信应该有一个简单的解决方案,但它正在逃避我,很多谷歌搜索。

+------+
|(.Y. )|
| /_   |
|\____/|
+------+

Thanks!

谢谢!

回答by potomek

Edited(see comments)

已编辑(见评论

function cropImageFromCanvas(ctx) {
  var canvas = ctx.canvas, 
    w = canvas.width, h = canvas.height,
    pix = {x:[], y:[]},
    imageData = ctx.getImageData(0,0,canvas.width,canvas.height),
    x, y, index;

  for (y = 0; y < h; y++) {
    for (x = 0; x < w; x++) {
      index = (y * w + x) * 4;
      if (imageData.data[index+3] > 0) {
        pix.x.push(x);
        pix.y.push(y);
      } 
    }
  }
  pix.x.sort(function(a,b){return a-b});
  pix.y.sort(function(a,b){return a-b});
  var n = pix.x.length-1;

  w = 1 + pix.x[n] - pix.x[0];
  h = 1 + pix.y[n] - pix.y[0];
  var cut = ctx.getImageData(pix.x[0], pix.y[0], w, h);

  canvas.width = w;
  canvas.height = h;
  ctx.putImageData(cut, 0, 0);

  var image = canvas.toDataURL();  //open cropped image in a new window
  var win=window.open(image, '_blank');
  win.focus();
}

回答by Putuko

If I understood well you want to "trim" away all the surronding your image / drawing, and adjust the canvas to that size (like if you do a "trim" command in Photoshop).

如果我理解得很好,你想“修剪”掉所有环绕你的图像/绘图,并将画布调整到那个大小(就像你在 Photoshop 中执行“修剪”命令一样)。

Here is how I'll do it.

这是我将如何做到的。

  1. Run thru all the canvas pixels checking if their alpha component is > 0 (that means that something is drawn in that pixel). Alternativelly you could check for the r,g,b values, if your canvas background is fullfilled with a solid color, for instance.

  2. Get te coordinates of the top most left pixel non-empty, and same for the bottom most right one. So you'll get the coordinates of an imaginay "rectangle" containing the canvas area that is not empty.

  3. Store that region of pixeldata.

  4. Resize your canvas to its new dimensions (the ones of the region we got at step 2.)

  5. Paste the saved region back to the canvas.

  1. 运行所有画布像素,检查它们的 alpha 分量是否 > 0(这意味着在该像素中绘制了一些东西)。或者,您可以检查 r、g、b 值,例如,如果您的画布背景充满纯色。

  2. 获取最左上角像素的坐标非空,最右下角像素点坐标相同。因此,您将获得包含非空画布区域的想象“矩形”的坐标。

  3. 存储该像素数据区域。

  4. 将您的画布调整为新的尺寸(我们在第 2 步中获得的区域的尺寸。)

  5. 将保存的区域粘贴回画布。

Et, voilá :)

等等,瞧:)

Accesing pixeldata is quite slow depending on the size of your canvas (if its huge it can take a while). There are some optimizations around to work with raw canvas pixeldata (I think there is an article about this topic at MDN), I suggest you to google about it.

访问像素数据的速度非常慢,具体取决于画布的大小(如果画布很大,可能需要一段时间)。有一些优化可以处理原始画布像素数据(我认为 MDN 上有一篇关于这个主题的文章),我建议你谷歌一下。

I prepared a small sketch in jsFiddle that you can use as starting point for your code.

我在 jsFiddle 中准备了一个小草图,您可以将其用作代码的起点。

Working sample at jsFiddle

jsFiddle 的工作示例

Hope I've helped you.
c:.

希望我帮助了你。
C:。

回答by Kyle

Here's my take. I felt like all the other solutions were overly complicated. Though, after creating it, I now see it's the same solution as one other's, expect they just shared a fiddle and not a function.

这是我的看法。我觉得所有其他解决方案都过于复杂。虽然,在创建它之后,我现在看到它与其他人的解决方案相同,希望他们只是共享一个小提琴而不是一个功能。

function trimCanvas(canvas){
    const context = canvas.getContext('2d');

    const topLeft = {
        x: canvas.width,
        y: canvas.height,
        update(x,y){
            this.x = Math.min(this.x,x);
            this.y = Math.min(this.y,y);
        }
    };

    const bottomRight = {
        x: 0,
        y: 0,
        update(x,y){
            this.x = Math.max(this.x,x);
            this.y = Math.max(this.y,y);
        }
    };

    const imageData = context.getImageData(0,0,canvas.width,canvas.height);

    for(let x = 0; x < canvas.width; x++){
        for(let y = 0; y < canvas.height; y++){
            const alpha = imageData.data[((y * (canvas.width * 4)) + (x * 4)) + 3];
            if(alpha !== 0){
                topLeft.update(x,y);
                bottomRight.update(x,y);
            }
        }
    }

    const width = bottomRight.x - topLeft.x;
    const height = bottomRight.y - topLeft.y;

    const croppedCanvas = context.getImageData(topLeft.x,topLeft.y,width,height);
    canvas.width = width;
    canvas.height = height;
    context.putImageData(croppedCanvas,0,0);

    return canvas;
}

回答by I wrestled a bear once.

The top voted answer here, as well as the implementations i found online trim one extra pixel which was very apparent when trying to trim text out of canvas. I wrote my own that worked better for me:

此处投票最高的答案,以及我在网上找到的实现修剪一个额外的像素,这在尝试从画布中修剪文本时非常明显。我自己写的,对我来说效果更好:

var img = new Image;
img.onload = () => {

  var canvas = document.getElementById('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  var ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);

  document.getElementById('button').addEventListener('click', ()=>{
    autoCropCanvas(canvas, ctx);
    document.getElementById('button').remove();
  });

};
img.src = 'data:image/png;base64,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';


function autoCropCanvas(canvas, ctx) {
  var bounds = {
   left: 0,
   right: canvas.width,
   top: 0,
   bottom: canvas.height
  };
  var rows = [];
  var cols = [];
  var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  for (var x = 0; x < canvas.width; x++) {
   cols[x] = cols[x] || false;
   for (var y = 0; y < canvas.height; y++) {
    rows[y] = rows[y] || false;
    const p = y * (canvas.width * 4) + x * 4;
    const [r, g, b, a] = [imageData.data[p], imageData.data[p + 1], imageData.data[p + 2], imageData.data[p + 3]];
    var isEmptyPixel = Math.max(r, g, b, a) === 0;
    if (!isEmptyPixel) {
     cols[x] = true;
     rows[y] = true;
    }
   }
  }
  for (var i = 0; i < rows.length; i++) {
   if (rows[i]) {
    bounds.top = i ? i - 1 : i;
    break;
   }
  }
  for (var i = rows.length; i--; ) {
   if (rows[i]) {
    bounds.bottom = i < canvas.height ? i + 1 : i;
    break;
   }
  }
  for (var i = 0; i < cols.length; i++) {
   if (cols[i]) {
    bounds.left = i ? i - 1 : i;
    break;
   }
  }
  for (var i = cols.length; i--; ) {
   if (cols[i]) {
    bounds.right = i < canvas.width ? i + 1 : i;
    break;
   }
  }
  var newWidth = bounds.right - bounds.left;
  var newHeight = bounds.bottom - bounds.top;
  var cut = ctx.getImageData(bounds.left, bounds.top, newWidth, newHeight);
  canvas.width = newWidth;
  canvas.height = newHeight;
  ctx.putImageData(cut, 0, 0);
 }
<canvas id=canvas style='border: 1px solid pink'></canvas>
<button id=button>crop canvas</button>