【问题标题】:Cropping an HTML canvas to the width/height of its visible pixels (content)?将 HTML 画布裁剪为其可见像素(内容)的宽度/高度?
【发布时间】:2017-08-24 16:35:42
【问题描述】:

可以在内部裁剪 HTML canvas 元素以适应其内容吗?

例如,如果我有一个 500x500 像素的画布,其中随机位置只有一个 10x10 像素的正方形,是否有一个函数可以通过扫描可见像素并裁剪来将整个画布裁剪为 10x10?


编辑: 这被标记为与Javascript Method to detect area of a PNG that is not transparent 重复,但事实并非如此。该问题详细说明了如何在画布中找到非透明内容的边界,但不是如何裁剪它。我的问题的第一个词是“裁剪”,所以这就是我想要关注的内容。

【问题讨论】:

  • @K3N Edit 解释了为什么它不是重复的。请重新打开。
  • 重新打开,但是,要裁剪您只需使用从该方法获得的坐标,然后使用 drawImage() 到新的画布到区域。

标签: javascript css html canvas


【解决方案1】:

更好的修剪功能。

虽然给出的答案有效,但它包含一个潜在的危险缺陷,创建一个新的画布而不是裁剪现有的画布,并且(链接区域搜索)效率有些低。

如果您对画布有其他引用,则创建第二个画布可能会出现问题,这很常见,因为通常有两个对画布的引用,例如canvasctx.canvas。闭包可能会使移除引用变得困难,如果闭包结束某个事件,您可能永远无法移除参考。

缺陷是画布不包含像素。允许将画布设置为零大小(canvas.width = 0; canvas.height = 0; 不会引发错误),但某些函数不能接受零作为参数并会引发错误(例如,ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height); 是常见做法,但如果画布会引发错误没有大小)。由于这与调整大小没有直接关联,因此可以忽略此潜在的崩溃并进入生产代码。

链接搜索会检查每次搜索的所有像素,在找到边缘时包含简单的break 会改进搜索,但平均而言搜索速度仍然更快。同时在两个方向上搜索,顶部和底部然后左右将减少迭代次数。而不是为每个像素测试计算每个像素的地址,您可以通过逐步执行索引来提高性能。例如data[idx++]data[x + y * w] 快很多

更强大的解决方案。

以下函数将使用两遍搜索从画布上裁剪透明边缘,同时考虑到第一遍的结果以减少第二遍的搜索区域。

如果没有像素,它不会裁剪画布,但会返回false,以便采取行动。如果画布包含像素,它将返回true

在原地裁剪画布时,无需更改对画布的任何引用。

// ctx is the 2d context of the canvas to be trimmed
// This function will return false if the canvas contains no or no non transparent pixels.
// Returns true if the canvas contains non transparent pixels
function trimCanvas(ctx) { // removes transparent edges
    var x, y, w, h, top, left, right, bottom, data, idx1, idx2, found, imgData;
    w = ctx.canvas.width;
    h = ctx.canvas.height;
    if (!w && !h) { return false } 
    imgData = ctx.getImageData(0, 0, w, h);
    data = new Uint32Array(imgData.data.buffer);
    idx1 = 0;
    idx2 = w * h - 1;
    found = false; 
    // search from top and bottom to find first rows containing a non transparent pixel.
    for (y = 0; y < h && !found; y += 1) {
        for (x = 0; x < w; x += 1) {
            if (data[idx1++] && !top) {  
                top = y + 1;
                if (bottom) { // top and bottom found then stop the search
                    found = true; 
                    break; 
                }
            }
            if (data[idx2--] && !bottom) { 
                bottom = h - y - 1; 
                if (top) { // top and bottom found then stop the search
                    found = true; 
                    break;
                }
            }
        }
        if (y > h - y && !top && !bottom) { return false } // image is completely blank so do nothing
    }
    top -= 1; // correct top 
    found = false;
    // search from left and right to find first column containing a non transparent pixel.
    for (x = 0; x < w && !found; x += 1) {
        idx1 = top * w + x;
        idx2 = top * w + (w - x - 1);
        for (y = top; y <= bottom; y += 1) {
            if (data[idx1] && !left) {  
                left = x + 1;
                if (right) { // if left and right found then stop the search
                    found = true; 
                    break;
                }
            }
            if (data[idx2] && !right) { 
                right = w - x - 1; 
                if (left) { // if left and right found then stop the search
                    found = true; 
                    break;
                }
            }
            idx1 += w;
            idx2 += w;
        }
    }
    left -= 1; // correct left
    if(w === right - left + 1 && h === bottom - top + 1) { return true } // no need to crop if no change in size
    w = right - left + 1;
    h = bottom - top + 1;
    ctx.canvas.width = w;
    ctx.canvas.height = h;
    ctx.putImageData(imgData, -left, -top);
    return true;            
}

【讨论】:

  • 哇,即使 fillText 我在使用其他方法时遇到问题(请参阅我对其他答案的评论),这也能立即奏效。这是一个工作演示:jsfiddle.net/umasz04w 谢谢。
  • 好的,但是为什么要调用两次 getImageData?把你分析的那个放在 putImageData 的裁剪参数中
  • @Kaiido 抱歉错过了。将修复
  • 天哪,现在是 2022 年,这仍然有效!!
【解决方案2】:

可以在内部裁剪 HTML 画布元素以适应其内容吗?

是的,使用this method(或类似的)将为您提供所需的坐标。背景不必是透明的,而是统一的(修改代码以适应背景)以供任何实际使用。

获得坐标后,只需使用drawImage() 渲染该区域:

示例(由于未提供相关代码,请根据需要采用):

// obtain region here (from linked method)
var region = {
  x: x1, 
  y: y1, 
  width: x2-x1, 
  height: y2-y1
};

var croppedCanvas = document.createElement("canvas");
croppedCanvas.width = region.width;
croppedCanvas.height = region.height;

var cCtx = croppedCanvas.getContext("2d");
cCtx.drawImage(sourceCanvas, region.x, region.y, region.width, region.height, 
                             0, 0, region.width, region.height);

现在croppedCanvas 仅包含原始画布的裁剪部分。

【讨论】:

  • 谢谢。相关问题当然有帮助,但我不知道如何实际提取该区域。
  • 在使用fillText 时,我无法使用您的链接方法来查找边界。如果我使用0,0 作为fillText 坐标,则“右边缘”不正确。如果我将它们更改为例如1,0 那么右边缘是正确的。看到这个小提琴:jsfiddle.net/9jj7o5az/2
猜你喜欢
  • 2011-04-13
  • 2012-10-15
  • 2018-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-02
相关资源
最近更新 更多