你不能总是重新绘制画布,可能已经使用了无法撤销的过滤器,或者只是使用了太多的填充和描边调用,重新绘制会变得不切实际。
我有自己的基于简单填充堆栈的泛洪填充,它可以在容忍度内进行绘制,并尽力减少反锯齿伪影。不幸的是,如果您开启了反锯齿,则重复填充将扩大填充区域。
以下是该函数,根据需要进行适应,这是从我的代码中直接提取的,并添加了注释。
Bitmaps.prototype.floodFill = function (posX, posY, RGBA, diagonal,imgData,tolerance,antiAlias) {
var data = imgData.data;
antiAlias = true;
var stack = [];
var lookLeft = false;
var lookRight = false;
var w = imgData.width;
var h = imgData.height;
var painted = new Uint8ClampedArray(w*h);
var dw = w*4;
var x = posX;
var y = posY;
var ind = y * dw + x * 4;
var sr = data[ind];
var sg = data[ind+1];
var sb = data[ind+2];
var sa = data[ind+3];
var sp = 0;
var dontPaint = false;
var checkColour = function(x,y){
if( x<0 || y < 0 || y >=h || x >= w){
return false;
}
var ind = y * dw + x * 4;
var dif = Math.max(
Math.abs(sr-data[ind]),
Math.abs(sg-data[ind+1]),
Math.abs(sb-data[ind+2]),
Math.abs(sa-data[ind+3])
);
if(dif < tolerance){
dif = 0;
}
var paint = Math.abs(sp-painted[y * w + x]);
if(antiAlias && !dontPaint){
if(dif !== 0 && paint !== 255){
data[ind] = RGBA[0];
data[ind+1] = RGBA[1];
data[ind+2] = RGBA[2];
data[ind+3] = (RGBA[3]+data[ind+3])/2;
painted[y * w + x] = 255;
}
}
return (dif+paint)===0?true:false;
}
var setPixel = function(x,y){
var ind = y * dw + x * 4;
data[ind] = RGBA[0];
data[ind+1] = RGBA[1];
data[ind+2] = RGBA[2];
data[ind+3] = RGBA[3];
painted[y * w + x] = 255;
}
stack.push([x,y]);
while (stack.length) {
var pos = stack.pop();
x = pos[0];
y = pos[1];
dontPaint = true;
while (checkColour(x,y-1)) {
y -= 1;
}
dontPaint = false;
if(diagonal){
if(!checkColour(x-1,y) && checkColour(x-1,y-1)){
stack.push([x-1,y-1]);
}
if(!checkColour(x+1,y) && checkColour(x+1,y-1)){
stack.push([x+1,y-1]);
}
}
lookLeft = false;
lookRight = false;
while (checkColour(x,y)) {
setPixel(x,y);
if (checkColour(x - 1,y)) {
if (!lookLeft) {
stack.push([x - 1, y]);
lookLeft = true;
}
} else
if (lookLeft) {
lookLeft = false;
}
if (checkColour(x+1,y)) {
if (!lookRight) {
stack.push([x + 1, y]);
lookRight = true;
}
} else
if (lookRight) {
lookRight = false;
}
y += 1;
}
if(diagonal){
if(checkColour(x-1,y) && !lookLeft){
stack.push([x-1,y]);
}
if(checkColour(x+1,y) && !lookRight){
stack.push([x+1,y]);
}
}
}
}
有一种更好的方法可以提供高质量的结果,可以通过使用涂漆数组来标记涂漆边缘,然后在填充完成后扫描涂漆数组并对每个标记的边缘像素应用卷积滤波器来调整上述代码以实现此目的。该滤波器是定向的(取决于哪些侧面被涂漆),而且代码太长了,无法在此答案中列出。我已经指导了您正确的方向,并提供了基础设施。
另一种提高图像质量的方法是对正在绘制的图像进行超采样。保留一个比正在绘制的图像大两倍的第二个画布。将所有绘图操作都执行到该图像上,并使用CTX.imageSmoothingEnabled
和ctx.setTransform(0.5,0,0,0.5,0,0)
将其缩小一半显示给用户,当完成并准备好图像时,使用以下代码手动将其缩小一半(不要依赖画布imageSmoothingEnabled,因为它会出错)。
这样做将极大地提高最终图像的质量,并且与上述填充一起,几乎完全消除了洪水填充的反锯齿伪影。
var w = ctxS.canvas.width;
var h = ctxS.canvas.height;
var data = ctxS.getImageData(0,0,w,h);
var d = data.data;
var x,y;
var ww = w*4;
var ww4 = ww+4;
for(y = 0; y < h; y+=2){
for(x = 0; x < w; x+=2){
var id = y*ww+x*4;
var id1 = Math.floor(y/2)*ww+Math.floor(x/2)*4;
d[id1] = Math.sqrt((d[id]*d[id]+d[id+4]*d[id+4]+d[id+ww]*d[id+ww]+d[id+ww4]*d[id+ww4])/4);
id += 1;
id1 += 1;
d[id1] = Math.sqrt((d[id]*d[id]+d[id+4]*d[id+4]+d[id+ww]*d[id+ww]+d[id+ww4]*d[id+ww4])/4);
id += 1;
id1 += 1;
d[id1] = Math.sqrt((d[id]*d[id]+d[id+4]*d[id+4]+d[id+ww]*d[id+ww]+d[id+ww4]*d[id+ww4])/4);
id += 1;
id1 += 1;
d[id1] = Math.sqrt((d[id]*d[id]+d[id+4]*d[id+4]+d[id+ww]*d[id+ww]+d[id+ww4]*d[id+ww4])/4);
}
}
ctxS.putImageData(data,0,0);
var data = ctxS.getImageData(0,0,Math.floor(w/2),Math.floor(h/2));
var canvas = document.createElement("canvas");
canvas.width = Math.floor(w/2);
canvas.height =Math.floor(h/2);
var ctxS = canvas.getContext("2d",{ alpha: true });
ctxS.putImageData(data,0,0);