在浏览Stack Overflow和Google之后,在我看来,在HTML5画布上绘制线条时无法禁用抗锯齿功能。
这样可以产生漂亮的线条,但在应用油漆桶/填充填充算法时会出现问题。
我的部分应用程序要求用户在画布上绘图,使用基本工具(如线条大小,颜色......和油漆桶)进行自由式绘图。
因为线条是使用抗锯齿渲染的,所以它们不是一致的颜色......考虑到这一点,请考虑以下内容:
我的泛光填充算法用红色填充线条的大部分,但是被抗锯齿的边缘被检测为在应该填充的区域之外...因此保留(灰色留下的灰色/蓝色(?)线)。
泛洪填充算法没有像Photoshop那样包含类似于'容差'的东西......我已经考虑过类似的东西了但不确定它会有所帮助,因为我认为抗锯齿做的事情很简单,比如渲染灰色在黑线旁边,我认为它比这更先进,抗锯齿考虑到周围的颜色和混合。
有没有人有任何关于如何最终获得更好的油漆桶/洪水填充的建议?完全填充/替换图纸的现有线条或部分?
答案 0 :(得分:2)
如果您只想更改线条的颜色:请勿使用铲斗颜料填充。
将所有线条和形状存储为对象/数组,并在需要时重绘它们。
这不仅允许您更改画布大小而不会丢失其上的所有内容,但更改颜色只需更改对象/数组上的颜色属性并重绘,以及根据向量而不是缩放所有内容光栅。
这将比桶填充更快,因为重绘在内部大多数情况下处理,而不是在JavaScript中按像素逐像素处理。
话虽如此:遗憾的是,您不能禁用形状和线的反别名,仅适用于图像(使用imageSmoothingEnabled
属性)。
对象可能如下所示:
function myLine(x1, y1, x2, y2, color) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.color = color;
return this;
}
然后通过以下方式分配:
var newLine = new myLine(x1, y1, x2, y2, color);
然后将其存储到数组中:
/// globally:
var myLineStack = [];
/// after x1/x2/y1/y2 and color is achieved in the draw function:
myLineStack.push(new myLine(x1, y1, x2, y2, color));
然后,只需要在需要更新时迭代对象:
/// some index to a line you want to change color for:
myLineStack[index].color = newColor;
/// Redraw all (room for optimizations here...)
context.clearRect( ... );
for(var i = 0, currentLine; currentLine = myLineStack[i]; i++) {
/// new path
context.beginPath();
/// set the color for this line
context.strokeStyle = currentLine.color;
/// draw the actual line
context.moveTo(currentLine.x1, currentLine.y1);
context.lineTo(currentLine.x2, currentLine.y2);
context.stroke();
}
(对于优化,您可以例如仅清除需要重绘的区域并绘制单个索引。您还可以使用相同颜色对线条/形状进行分组,然后使用单个设置strokeStyle
等进行绘制。)
答案 1 :(得分:0)
你不能总是重绘画布,你可能使用了无法反转的滤镜,或者只是使用了很多填充和描边调用,重绘是不切实际的。
我有自己的洪水填充基于一个简单的填充堆栈,它绘制了一个公差,并尽力减少抗锯齿伪像。不幸的是,如果您在重复填充时出现抗锯齿,则会增加填充区域。
以下是该功能,将其调整为适合,它是我的代码直接提升并添加注释。
// posX,posY are the fill start position. The pixel at the location is used to test tolerance.
// RGBA is the fill colour as an array of 4 bytes all ranged 0-255 for R,G,B,A
// diagonal if true the also fill into pixels that touch at the corners.
// imgData canvas pixel data from ctx.getImageData method
// tolerance Fill tolerance range 0 only allow exact same colour to fill to 255
// fill all but the extreme opposite.
// antiAlias if true fill edges to reduce anti-Aliasing artifacts.
Bitmaps.prototype.floodFill = function (posX, posY, RGBA, diagonal,imgData,tolerance,antiAlias) {
var data = imgData.data; // image data to fill;
antiAlias = true;
var stack = []; // paint stack to find new pixels to paint
var lookLeft = false; // test directions
var lookRight = false;
var w = imgData.width; // width and height
var h = imgData.height;
var painted = new Uint8ClampedArray(w*h); // byte array to mark painted area;
var dw = w*4; // data width.
var x = posX; // just short version of pos because I am lazy
var y = posY;
var ind = y * dw + x * 4; // get the starting pixel index
var sr = data[ind]; // get the start colour tha we will use tollerance against.
var sg = data[ind+1];
var sb = data[ind+2];
var sa = data[ind+3];
var sp = 0;
var dontPaint = false; // flag to indicate if checkColour can paint
// function checks a pixel colour passes tollerance, is painted, or out of bounds.
// if the pixel is over tollerance and not painted set it do reduce anti alising artifacts
var checkColour = function(x,y){
if( x<0 || y < 0 || y >=h || x >= w){ // test bounds
return false;
}
var ind = y * dw + x * 4; // get index of pixel
var dif = Math.max( // get the max channel differance;
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){ // if under tollerance pass it
dif = 0;
}
var paint = Math.abs(sp-painted[y * w + x]); // is it already painted
if(antiAlias && !dontPaint){ // mitigate anti aliasing effect
// if failed tollerance and has not been painted set the pixel to
// reduce anti alising artifact
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; // blend the alpha channel
painted[y * w + x] = 255; // flag pixel as painted
}
}
return (dif+paint)===0?true:false; // return tollerance status;
}
// set a pixel and flag it as painted;
var setPixel = function(x,y){
var ind = y * dw + x * 4; // get index;
data[ind] = RGBA[0]; // set RGBA
data[ind+1] = RGBA[1];
data[ind+2] = RGBA[2];
data[ind+3] = RGBA[3];
painted[y * w + x] = 255; // 255 or any number >0 will do;
}
stack.push([x,y]); // push the first pixel to paint onto the paint stack
while (stack.length) { // do while pixels on the stack
var pos = stack.pop(); // get the pixel
x = pos[0];
y = pos[1];
dontPaint = true; // turn off anti alising
while (checkColour(x,y-1)) { // find the bottom most pixel within tolerance;
y -= 1;
}
dontPaint = false; // turn on anti alising if being used
//checkTop left and right if alowing diagonal painting
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; // set look directions
lookRight = false; // only look is a pixel left or right was blocked
while (checkColour(x,y)) { // move up till no more room
setPixel(x,y); // set the pixel
if (checkColour(x - 1,y)) { // check left is blocked
if (!lookLeft) {
stack.push([x - 1, y]); // push a new area to fill if found
lookLeft = true;
}
} else
if (lookLeft) {
lookLeft = false;
}
if (checkColour(x+1,y)) { // check right is blocked
if (!lookRight) {
stack.push([x + 1, y]); // push a new area to fill if found
lookRight = true;
}
} else
if (lookRight) {
lookRight = false;
}
y += 1; // move up one pixel
}
// check down left
if(diagonal){ // check for diagnal areas and push them to be painted
if(checkColour(x-1,y) && !lookLeft){
stack.push([x-1,y]);
}
if(checkColour(x+1,y) && !lookRight){
stack.push([x+1,y]);
}
}
}
// all done
}
有一种更好的方法可以提供高质量的结果,上面的代码可以通过使用绘制的数组来标记绘制边缘,然后在填充完成后扫描绘制的数组并应用卷积滤镜来实现这一点。您标记的每个边缘像素。过滤器是方向性的(取决于绘制的边),代码太长,无法回答这个问题。我指出了你正确的方向,基础设施就在上面。
提高图像质量的另一种方法是对要绘制的图像进行超级采样。保持第二个画布,其大小是正在绘制的图像的两倍。完成所有绘制到该图像并将其显示给另一个具有CTX.imageSmoothingEnabled
和ctx.setTransform(0.5,0,0,0.5,0,0)
半尺寸的画布的用户,完成后,使用以下代码手动将图像准备好一半(don&#39 ; t依赖于画布imageSmoothingEnabled,因为它弄错了。)
这样做可以大大提高最终图像的质量,上面的填充几乎可以完全消除洪水填充中的抗锯齿效果。
// ctxS is the source canvas context
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); // save imgData
// grab it again for new image we don't want to add artifacts from the GPU
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);
// result canvas with downsampled high quality image.