【问题标题】:How do I fill an area using a non-recursive algorithm?如何使用非递归算法填充区域?
【发布时间】:2018-11-05 04:38:34
【问题描述】:

我正在尝试使用canvas(指代码中元素的 2DContext)创建一个基本的绘制应用程序。但是,以当前时间,所有浏览器都放弃并说Maximum call stack size exceeded。如何改进此代码以填充更大的区域?

我以 fillAround(Math.round(x), Math.round(y), colorAt(Math.round(x), Math.round(y)), fillcolor); 开头代码,其中 x 和 y 是点击的坐标。

    function hexToRgb(hex) {
        var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? {
            r: parseInt(result[1], 16),
            g: parseInt(result[2], 16),
            b: parseInt(result[3], 16)
        } : null;
    }

    function colorToRgb(arr) {
        return {
            r: arr[0],
            g: arr[1],
            b: arr[2]
        }
    }

    function colorAt(xp, yp) {
        return colorToRgb(canvas.getImageData(xp, yp, 1, 1).data);
    }

    function setColorAt(xp, yp, fill) {
        var color = canvas.getImageData(xp, yp, 1, 1)
        var set = hexToRgb(fill);
        color.data[0] = set.r;
        color.data[1] = set.g;
        color.data[2] = set.b;
        canvas.putImageData(color, xp, yp);
    }

    function sameColor(a, b) {
        return a.r == b.r && a.g == b.r && a.b == b.b;
    }

    function fillAround(xp, yp, original, fill) {
        if (sameColor(colorAt(xp, yp), original)) {
            setColorAt(xp, yp, fill);
            if (sameColor(colorAt(xp + 1, yp), original)) {
                fillAround(xp + 1, yp, original, fill);
            }
            if (sameColor(colorAt(xp - 1, yp), original)) {
                fillAround(xp - 1, yp, original, fill);
            }
            if (sameColor(colorAt(xp, yp + 1), original)) {
                fillAround(xp, yp + 1, original, fill);
            }
            if (sameColor(colorAt(xp, yp - 1), original)) {
                fillAround(xp, yp - 1, original, fill);
            }
        }
    }

十六进制到RGB的转换器来自RGB to Hex and Hex to RGB

更新的代码(在@trincot 的帮助下)

    var canvasData;

    function hexToRgb(hex) {
        var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? {
            r: parseInt(result[1], 16),
            g: parseInt(result[2], 16),
            b: parseInt(result[3], 16)
        } : null;
    }

    function colorToRgb(arr) {
        return {
            r: arr[0],
            g: arr[1],
            b: arr[2]
        }
    }

    function colorAt(xp, yp) {
        return colorToRgb(canvasData.data.slice(4 * canvasTag.width * (yp - 1) + 4 * (xp + 1), 4 * canvasTag.widthwidth * (yp - 1) + 4 * xp + 8));
    }

    function setColorAt(xp, yp, fill) {
        var set = hexToRgb(fill);
        var o = 4 * canvasTag.width * (yp - 1) + 4 * (xp + 1);
        canvasData.data[o] = set.r;
        canvasData.data[o + 1] = set.g;
        canvasData.data[o + 2] = set.b;
    }

    function sameColor(a, b) {
        return a.r == b.r && a.g == b.r && a.b == b.b;
    }

    function fillAround(xp, yp, original, fill) {
        const stack = [[xp, yp]];
        while (stack.length) {
            const [xp, yp] = stack.pop();
            if (!sameColor(colorAt(xp, yp), original)) continue;
            setColorAt(xp, yp, fill);
            stack.push([xp + 1, yp], [xp - 1, yp], [xp, yp + 1], [xp, yp - 1]); 
        }
    }

并通过调用

canvasData = canvas.getImageData(0, 0, canvasTag.width, canvasTag.height);
fillAround(Math.round(x), Math.round(y), colorAt(Math.round(x), Math.round(y)), fillcolor);
canvas.putImageData(canvasData, 0, 0);

【问题讨论】:

标签: javascript canvas optimization


【解决方案1】:

确实,堆栈内存是有限的。您的代码进入了非常深的嵌套调用 fillAround,这完全消耗了可用的堆栈内存。

在不更改其他逻辑的任何内容的情况下,我建议将递归调用替换为您自己管理堆栈的循环:

function fillAround(xp, yp, original, fill) {
    const stack = [[xp, yp]]; // Initially the stack has one pair of coordinates
    while (stack.length) { // Keep iterating while there is work to do...
        const [xp, yp] = stack.pop(); // Get one pair of coordinates from the stack
        if (!sameColor(colorAt(xp, yp), original)) continue; // Skip it
        setColorAt(xp, yp, fill);
        // Push the neighbors onto the stack for later processing:
        stack.push([xp + 1, yp], [xp - 1, yp], [xp, yp + 1], [xp, yp - 1]); 
    }
}

仅此一点不会提高速度,只会避免堆栈内存异常。

为了获得更好的性能,您不应使用以下调用单独读取/写入每个像素:

canvas.getImageData(xp, yp, 1, 1)
canvas.putImageData(color, xp, yp)

...但是使用getImageData 的最后两个参数的力量:一次将整个画布区域读入内存,在内存中进行更改,然后只用一个 em> 拨打setImageData

【讨论】:

  • 填充较大区域时似乎比较慢,有没有更好的方法,就像其他一些绘图程序一样?
  • 你看到我的评论和补充了吗? ;-)
  • 在评论和重新加载后,是的:)
  • 不幸的是,加载到内存中似乎没有太大改善
  • 还有其他几种可能的优化。例如,您多次调用hexToRgb(fill),但使用相同的值。为了提高速度,您应该放弃其他不错的转换功能,只做必要的最低限度的操作。不要将图像数组转换为普通对象,...等
猜你喜欢
  • 2020-04-22
  • 2014-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多