【问题标题】:Automatically Crop HTML5 canvas to contents自动将 HTML5 画布裁剪为内容
【发布时间】:2012-08-01 13:13:53
【问题描述】:

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

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

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

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

谢谢!

【问题讨论】:

  • 听起来你想要一个自动裁剪功能。嗯...我还没有太多地使用画布。遍历水平线以识别像素不是白色的图像边界。然后扫描垂直方向。
  • @AlexW:我不完全确定你明白我想要什么——我想要裁剪而不是缩放。
  • @CMKanode:感谢您的评论。这几乎是我对最后一种解决方案的想法:P 虽然它正在将它钻到一个相当低的水平,所以我觉得应该有另一种方法!

标签: javascript html html5-canvas


【解决方案1】:

已编辑(请参阅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();
}

【讨论】:

  • 这是非常棒的自动裁剪代码,谢谢!我明天必须扔一个小提琴作为回报;)
  • 这里不需要排序功能。如果您跟踪像素不透明的最小 x,y 和最大 x,y,则可以完全删除排序。
  • 代码效果很好,只需在 var cut = ctx.getImageData(pix.x[0], pix.y[0], w+1, h+1);画布.宽度 = w+1; canvas.height = h+1;否则它会削减最后一个像素。
  • 这个答案很精彩
  • 检查 alpha 通道 (imageData.data[index+3] &gt; 0) 对我不起作用,因为它总是大于 0,必须切换到检查颜色是否不是白色(它适合我的需要,因为我有一个白色背景):imageData.data[index] != 255 &amp;&amp; imageData.data[index+1] != 255 &amp;&amp; imageData.data[index+2] != 255。然后裁剪效果很好,谢谢。
【解决方案2】:

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

我将这样做。

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

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

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

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

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

等等,瞧:)

访问像素数据的速度很慢,具体取决于画布的大小(如果画布很大,可能需要一段时间)。在处理原始画布像素数据方面有一些优化(我认为在 MDN 上有一篇关于此主题的文章),我建议您在 google 上了解它。

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

Working sample at jsFiddle

希望我对你有所帮助。
c:.

【讨论】:

  • 或者 3 - 5,你可以使用 Alex W 的解决方案,平移画布并调整大小,可能会比复制和粘贴像素更快:)
【解决方案3】:

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

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;
}

【讨论】:

    【解决方案4】:

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

    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>

    【讨论】:

      【解决方案5】:

      这是 ES 语法的代码,简短、快速、简洁:

      /**
       * Trim a canvas.
       * 
       * @author Arjan Haverkamp (arjan at avoid dot org)
       * @param {canvas} canvas A canvas element to trim. This element will be trimmed (reference)
       * @param {int} threshold Alpha threshold. Allows for trimming semi-opaque pixels too. Range: 0 - 255
       * @returns {Object} Width and height of trimmed canvcas and left-top coordinate of trimmed area. Example: {width:400, height:300, x:65, y:104}
       */
      const trimCanvas = (canvas, threshold = 0) => {
          const ctx = canvas.getContext('2d'),
              w = canvas.width, h = canvas.height,
              imageData = ctx.getImageData(0, 0, w, h),
              tlCorner = { x:w+1, y:h+1 },
              brCorner = { x:-1, y:-1 };
      
          for (let y = 0; y < h; y++) {
              for (let x = 0; x < w; x++) {
                  if (imageData.data[((y * w + x) * 4) + 3] > threshold) {
                      tlCorner.x = Math.min(x, tlCorner.x);
                      tlCorner.y = Math.min(y, tlCorner.y);
                      brCorner.x = Math.max(x, brCorner.x);
                      brCorner.y = Math.max(y, brCorner.y);
                  }
              }
          }
      
          const cut = ctx.getImageData(tlCorner.x, tlCorner.y, brCorner.x - tlCorner.x, brCorner.y - tlCorner.y);
      
          canvas.width = brCorner.x - tlCorner.x;
          canvas.height = brCorner.y - tlCorner.y;
      
          ctx.putImageData(cut, 0, 0);
      
          return {width:canvas.width, height:canvas.height, x:tlCorner.x, y:tlCorner.y};
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-03-18
        • 2014-06-26
        • 1970-01-01
        • 2011-12-18
        • 2011-04-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多