单击以填充任意形状的单色区域

时间:2019-11-16 09:00:23

标签: javascript algorithm html5-canvas

我正在尝试填充由其他不同颜色的区域包围的随机区域。 我目前正在使用4-pixel connectivity来实现这一目标。它可以正常工作,但是填充时间很长(Firefox多次询问我停止脚本。Chrome通常只是因为超过最大调用堆栈而终止了该脚本)。我的问题:如何优化代码或应使用其他算法?

PS:我更正了Wan Chap提到的错误。

编辑:我现在添加了一个全局变量canImageData并更改了getRGBStringFromPixel()函数。现在,填充工作的速度要快得多,但是我一直不断超出呼叫堆栈错误,导致更大的区域仅被部分填充。有人有上述算法的javascript适合版本的示例吗?

编辑的最后:我发现了另一个性能更高的解决方案,它在另一个站点的js源中进行了挖掘。我在下面将解决方案作为答案发布。

我的代码:

var can = document.getElementById('can');
var ctx = can.getContext('2d');
var canImageData = null;
var RGB_STRING_WHITE = '255-255-255-255';

function colour_fill_4_connectivity(x, y, RGBString_fill_colour, RGBString_region_colour){
	try {
		var currentPixelRGBString = getRGBStringFromPixel(x, y);
		if(currentPixelRGBString == RGBString_fill_colour || currentPixelRGBString != RGBString_region_colour)
			return;

		setPixelFromRGBString(x, y, RGBString_fill_colour);

		if(x < can.width)
			colour_fill_4_connectivity(x + 1, y, RGBString_fill_colour, RGBString_region_colour);
		if(x > 0)
			colour_fill_4_connectivity(x - 1, y, RGBString_fill_colour, RGBString_region_colour);
		if(y < can.height)
			colour_fill_4_connectivity(x, y + 1, RGBString_fill_colour, RGBString_region_colour);
		if(y > 0)
			colour_fill_4_connectivity(x, y - 1, RGBString_fill_colour, RGBString_region_colour);
	} catch(e) {
		console.log('ERROR: colour_fill_4_connectivity(' + x + ', ' + y + ', ' + RGBString_fill_colour + ', ' + RGBString_region_colour + ') -> ' + e);
	}
}

function fillColor(x, y, RGBString_fill_colour, RGBString_region_colour) {
	colour_fill_4_connectivity(x, y, RGBString_fill_colour, RGBString_region_colour);
}

function RGBStringToArray(valRGB) {
	return valRGB.split('-');
}

function getRGBStringFromPixel(x, y) {
	var data = canImageData.data;
	var startIndex = (x + y * can.width) * 4;
	return data[startIndex] + '-' + data[startIndex + 1] + '-' + data[startIndex + 2] + '-' + data[startIndex + 3];
}

function setPixelFromRGBString(x, y, valRGB) {
	var imageData = ctx.createImageData(1, 1);
	var data = imageData.data;
	var rgbArr = RGBStringToArray(valRGB);
	var startIndex = (x + y * can.width) * 4;
	data[0] = rgbArr[0];
	data[1] = rgbArr[1];
	data[2] = rgbArr[2];
	data[3] = rgbArr[3];
	ctx.putImageData(imageData, x, y);

	canImageData.data[startIndex] = rgbArr[0];
	canImageData.data[startIndex + 1] = rgbArr[1];
	canImageData.data[startIndex + 2] = rgbArr[2];
	canImageData.data[startIndex + 3] = rgbArr[3];
}

// TEST

function _test_getRGBStringFromPixel() {
	var res = [];
	for(var y = 0; y < can.height; y++) {
		for(var x = 0; x < can.width; x++) {
			res.push({x:x, y:y, color:getRGBStringFromPixel(x, y)});
		}
	}
	return JSON.stringify(res);
}

var img = new Image();
img.onload = (function(e) {
	can.width = this.width;
	can.height = this.height;
	ctx.drawImage(this, 0, 0);
	canImageData = ctx.getImageData(0, 0, can.width, can.height);
}).bind(img);
img.src = 'test-image.png';

can.addEventListener('click', function(e) {
	var clickX = e.clientX, clickY = e.clientY;
	if(e.button === 0 && clickX <= can.width && clickY <= can.height)
		fillColor(clickX, clickY, RGB_STRING_WHITE, getRGBStringFromPixel(clickX, clickY));
}, false);
body {
  padding: 0;
  margin: 0;
}
<canvas id="can" width="600" height="400"></canvas>

2 个答案:

答案 0 :(得分:0)

函数data[2] = rgbArr[2];似乎只是一个小错误: 将var can = document.getElementById('can'); var ctx = can.getContext('2d'); var RGB_STRING_WHITE = '255-255-255-255'; function colour_fill_4_connectivity(x, y, RGBString_fill_colour, RGBString_region_colour) { //try { var currentPixelRGBString = getRGBStringFromPixel(x, y); if (currentPixelRGBString == RGBString_fill_colour || currentPixelRGBString != RGBString_region_colour) return; setPixelFromRGBString(x, y, RGBString_fill_colour); if (x > 0) colour_fill_4_connectivity(x - 1, y, RGBString_fill_colour, RGBString_region_colour); if (x < can.width) colour_fill_4_connectivity(x + 1, y, RGBString_fill_colour, RGBString_region_colour); if (y > 0) colour_fill_4_connectivity(x, y - 1, RGBString_fill_colour, RGBString_region_colour); if (y < can.height) colour_fill_4_connectivity(x, y + 1, RGBString_fill_colour, RGBString_region_colour); //} catch(e) { //console.log('ERROR: colour_fill_4_connectivity(' + x + ', ' + y + ', ' + RGBString_fill_colour + ', ' + RGBString_region_colour + ') -> ' + e); //} } function RGBStringToArray(valRGB) { return valRGB.split('-'); } function getRGBStringFromPixel(x, y) { var data = ctx.getImageData(x, y, x + 1, y + 1).data; return data[0] + '-' + data[1] + '-' + data[2] + '-' + data[3]; } function setPixelFromRGBString(x, y, valRGB) { var imageData = ctx.createImageData(1, 1); var data = imageData.data; var rgbArr = RGBStringToArray(valRGB); data[0] = rgbArr[0]; data[1] = rgbArr[1]; data[1] = rgbArr[2]; data[3] = rgbArr[3]; ctx.putImageData(imageData, x, y); } // TEST var img = new Image(); img.onload = (function(e) { ctx.drawImage(img, 0, 0); }).bind(img); img.src = 'https://static1.squarespace.com/static/593357e715d5dbea570d2118/593ee768893fc0375f9c6fd5/5c7499a7e79c707a50772134/1551194769831/Test+pattern.png?format=1000w'; can.addEventListener('click', function(e) { var clickX = e.clientX, clickY = e.clientY; if (e.button === 0 && clickX <= can.width && clickY <= can.height) colour_fill_4_connectivity(clickX, clickY, RGB_STRING_WHITE, getRGBStringFromPixel(clickX, clickY)); }, false);更改为body { padding: 0; margin: 0; }

<canvas id="can" width="600" height="400"></canvas>
(?s)\([^(]*\((.+)\)[^)]*\)
(?s)

答案 1 :(得分:0)

经过研究,我决定深入研究http://skribbl.io的来源,该来源具有可靠且快速的填充工具。他们找到了一个迭代的解决方案。我从源文件中提取了一个代码片段,并更改了部分以使其更易于阅读:

function DrawingBoard(canvas) {
    this.canvas = canvas;
    this.canvasCtx = this.canvas.getContext("2d");
}

DrawingBoard.prototype.getPixel = function(imageData, x, y) {
    var startIndex = 4 * (y * imageData.width + x);
    return startIndex >= 0 && startIndex < imageData.data.length ? [imageData.data[startIndex], imageData.data[startIndex + 1], imageData.data[startIndex + 2]] : [0, 0, 0]
}

DrawingBoard.prototype.setPixel = function(imageData, startIndex, r, g, b) {
    startIndex >= 0 && startIndex < imageData.data.length && (imageData.data[startIndex] = r, imageData.data[startIndex + 1] = g, imageData.data[startIndex + 2] = b, imageData.data[startIndex + 3] = 255)
}

DrawingBoard.prototype.floodFill = function(startX, startY, r, g, b) {
    var imageData = this.canvasCtx.getImageData(0, 0, this.canvas.width, this.canvas.height), points = [[startX, startY]], targetPixelRGB = this.getPixel(imageData, startX, startY);
    if (r != targetPixelRGB[0] || g != targetPixelRGB[1] || b != targetPixelRGB[2]) {
        for (
            var c = function(t) {
                var e = imageData.data[t],
                    i = imageData.data[t + 1],
                    c = imageData.data[t + 2];
                if (e == r && i == g && c == b) return false;
                var u = Math.abs(e - targetPixelRGB[0]),
                    h = Math.abs(i - targetPixelRGB[1]),
                    l = Math.abs(c - targetPixelRGB[2]);
                return u < 1 && h < 1 && l < 1
            },
            imageHeight = imageData.height, imageHeightWidth = imageData.width; points.length;
        ) {
            var point, pointX, pointY, index, y, m;
            for (point = points.pop(), pointX = point[0], pointY = point[1], index = 4 * (pointY * imageHeightWidth + pointX); pointY-- >= 0 && c(index);) {
                index -= 4 * imageHeightWidth;
            }
            for (index += 4 * imageHeightWidth, ++pointY, y = 0, m = 0; pointY++ < imageHeight - 1 && c(index);) {
                this.setPixel(imageData, index, r, g, b);
                pointX > 0;
                c(index - 4) ? y || (points.push([pointX - 1, pointY]), y = 1) : y && (y = 0);
                pointX < imageHeightWidth - 1;
                c(index + 4) ? m || (points.push([pointX + 1, pointY]), m = 1) : m && (m = 0);
                index += 4 * imageHeightWidth;
            }
        }
        this.canvasCtx.putImageData(imageData, 0, 0)
    }
}

// TEST

var db = new DrawingBoard(document.getElementById('can'));
var img = new Image();
var fillColor = [255, 0, 255];
img.onload = (function() {
    db.canvasCtx.drawImage(this, 0, 0);
}).bind(img);
img.src = 'test.png';

addEventListener('click', function(e) {
    var cx = e.clientX, cy = e.clientY;

    if(
        e.button === 0 &&
        cx > 0 && cx < db.canvas.width &&
        cy > 0 && cy < db.canvas.height
    ) {
        db.floodFill(cx, cy, fillColor[0], fillColor[1], fillColor[2]);
    }
}, false);
body {
  padding: 0;
  margin: 0;
}
<canvas id="can" width="600" height="400"></canvas>

工作小提琴可以在这里找到:https://jsfiddle.net/f0kuwa5e/