Canvas - floodfill 在边缘留下白色像素
Canvas - floodfill leaves white pixels at edges
我正在创建一个绘图应用程序。我已经成功地完成了所有事情。当我用深色绘制图像时,边缘会出现一些白色像素。我试图改变 r + g + b 和 alpha 的值,但没有用。谁能帮我吗?您可以从 here 查看实时站点。谁愿意帮助我,我就赏金50给him/her。谢谢。这是我的代码。
<script type="text/javascript">
var initial = screen.width - 50;
if (initial < 1000) {
initial = 1000;
}
var firsttime = null;
var colorYellow = {
r: 255,
g: 207,
b: 51
};
var context;
var canvasWidth = initial;
var canvasHeight = initial;
var myColor = colorYellow;
var curColor = myColor;
var outlineImage = new Image();
var backgroundImage = new Image();
var drawingAreaX = 0;
var drawingAreaY = 0;
var drawingAreaWidth = initial;
var drawingAreaHeight = initial;
var colorLayerData;
var outlineLayerData;
var totalLoadResources = 2;
var curLoadResNum = 0;
var undoarr = new Array();
var redoarr = new Array();
function history(command) { // handles undo/redo button events.
var data;
if (command === "redo") {
data = historyManager.redo(); // get data for redo
} else
if (command === "undo") {
data = historyManager.undo(); // get data for undo
}
if (data !== undefined) { // if data has been found
setColorLayer(data); // set the data
}
}
// sets colour layer and creates copy into colorLayerData
function setColorLayer(data) {
context.putImageData(data, 0, 0);
colorLayerData = context.getImageData(0, 0, canvasWidth, canvasHeight);
context.drawImage(backgroundImage, 0, 0, canvasWidth, canvasHeight);
context.drawImage(outlineImage, 0, 0, drawingAreaWidth, drawingAreaHeight);
}
// Clears the canvas.
function clearCanvas() {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
}
// Draw the elements on the canvas
function redraw() {
uc = 0;
rc = 0;
var locX,
locY;
// Make sure required resources are loaded before redrawing
if (curLoadResNum < totalLoadResources) {
return; // To check if images are loaded successfully or not.
}
clearCanvas();
// Draw the current state of the color layer to the canvas
context.putImageData(colorLayerData, 0, 0);
historyManager.push(context.getImageData(0, 0, canvasWidth, canvasHeight));
redoarr = new Array();
// Draw the background
context.drawImage(backgroundImage, 0, 0, canvasWidth, canvasHeight);
// Draw the outline image on top of everything. We could move this to a separate
// canvas so we did not have to redraw this everyime.
context.drawImage(outlineImage, 0, 0, drawingAreaWidth, drawingAreaHeight);
};
function matchOutlineColor(r, g, b, a) {
return (r + g + b < 50 && a >= 50);
};
function matchStartColor(pixelPos, startR, startG, startB) {
var r = outlineLayerData.data[pixelPos],
g = outlineLayerData.data[pixelPos + 1],
b = outlineLayerData.data[pixelPos + 2],
a = outlineLayerData.data[pixelPos + 3];
// If current pixel of the outline image is black
if (matchOutlineColor(r, g, b, a)) {
return false;
}
r = colorLayerData.data[pixelPos];
g = colorLayerData.data[pixelPos + 1];
b = colorLayerData.data[pixelPos + 2];
// If the current pixel matches the clicked color
if (r === startR && g === startG && b === startB) {
return true;
}
// If current pixel matches the new color
if (r === curColor.r && g === curColor.g && b === curColor.b) {
return false;
}
return true;
};
function colorPixel(pixelPos, r, g, b, a) {
colorLayerData.data[pixelPos] = r;
colorLayerData.data[pixelPos + 1] = g;
colorLayerData.data[pixelPos + 2] = b;
colorLayerData.data[pixelPos + 3] = a !== undefined ? a : 255;
};
function floodFill(startX, startY, startR, startG, startB) {
document.getElementById('color-lib-1').style.display = "none";
document.getElementById('color-lib-2').style.display = "none";
document.getElementById('color-lib-3').style.display = "none";
document.getElementById('color-lib-4').style.display = "none";
document.getElementById('color-lib-5').style.display = "none";
change = false;
var newPos,
x,
y,
pixelPos,
reachLeft,
reachRight,
drawingBoundLeft = drawingAreaX,
drawingBoundTop = drawingAreaY,
drawingBoundRight = drawingAreaX + drawingAreaWidth - 1,
drawingBoundBottom = drawingAreaY + drawingAreaHeight - 1,
pixelStack = [
[startX, startY]
];
while (pixelStack.length) {
newPos = pixelStack.pop();
x = newPos[0];
y = newPos[1];
// Get current pixel position
pixelPos = (y * canvasWidth + x) * 4;
// Go up as long as the color matches and are inside the canvas
while (y >= drawingBoundTop && matchStartColor(pixelPos, startR, startG, startB)) {
y -= 1;
pixelPos -= canvasWidth * 4;
}
pixelPos += canvasWidth * 4;
y += 1;
reachLeft = false;
reachRight = false;
// Go down as long as the color matches and in inside the canvas
while (y <= drawingBoundBottom && matchStartColor(pixelPos, startR, startG, startB)) {
y += 1;
colorPixel(pixelPos, curColor.r, curColor.g, curColor.b);
if (x > drawingBoundLeft) {
if (matchStartColor(pixelPos - 4, startR, startG, startB)) {
if (!reachLeft) {
// Add pixel to stack
pixelStack.push([x - 1, y]);
reachLeft = true;
}
} else if (reachLeft) {
reachLeft = false;
}
}
if (x < drawingBoundRight) {
if (matchStartColor(pixelPos + 4, startR, startG, startB)) {
if (!reachRight) {
// Add pixel to stack
pixelStack.push([x + 1, y]);
reachRight = true;
}
} else if (reachRight) {
reachRight = false;
}
}
pixelPos += canvasWidth * 4;
}
}
};
// Start painting with paint bucket tool starting from pixel specified by startX and startY
function paintAt(startX, startY) {
var pixelPos = (startY * canvasWidth + startX) * 4,
r = colorLayerData.data[pixelPos],
g = colorLayerData.data[pixelPos + 1],
b = colorLayerData.data[pixelPos + 2],
a = colorLayerData.data[pixelPos + 3];
if (r === curColor.r && g === curColor.g && b === curColor.b) {
// Return because trying to fill with the same color
return;
}
if (matchOutlineColor(r, g, b, a)) {
// Return because clicked outline
return;
}
floodFill(startX, startY, r, g, b);
redraw();
};
// Add mouse event listeners to the canvas
function createMouseEvents() {
$('#canvas').mousedown(function(e) {
// Mouse down location
var mouseX = e.pageX - this.offsetLeft,
mouseY = e.pageY - this.offsetTop;
// assuming that the mouseX and mouseY are the mouse coords.
if (this.style.width) { // make sure there is a width in the style
// (assumes if width is there then height will be too
var w = Number(this.style.width.replace("px", "")); // warning this will not work if size is not in pixels
var h = Number(this.style.height.replace("px", "")); // convert the height to a number
var pixelW = this.width; // get the canvas resolution
var pixelH = this.height;
mouseX = Math.floor((mouseX / w) * pixelW); // convert the mouse coords to pixel coords
mouseY = Math.floor((mouseY / h) * pixelH);
}
if ((mouseY > drawingAreaY && mouseY < drawingAreaY + drawingAreaHeight) && (mouseX <= drawingAreaX + drawingAreaWidth)) {
paintAt(mouseX, mouseY);
}
});
};
resourceLoaded = function() {
curLoadResNum += 1;
//if (curLoadResNum === totalLoadResources) {
createMouseEvents();
redraw();
//}
};
var historyManager = (function() { // Anon for private (closure) scope
var uBuffer = []; // this is undo buff
var rBuffer = []; // this is redo buff
var currentState = undefined; // this holds the current history state
var undoElement = undefined;
var redoElement = undefined;
var manager = {
UI: { // UI interface just for disable and enabling redo undo buttons
assignUndoButton: function(element) {
undoElement = element;
this.update();
},
assignRedoButton: function(element) {
redoElement = element;
this.update();
},
update: function() {
if (redoElement !== undefined) {
redoElement.disabled = (rBuffer.length === 0);
}
if (undoElement !== undefined) {
undoElement.disabled = (uBuffer.length === 0);
}
}
},
reset: function() {
uBuffer.length = 0;
rBuffer.length = 0;
currentState = undefined;
this.UI.update();
},
push: function(data) {
if (currentState !== undefined) {
var same = true
for (i = 0; i < data.data.length; i++) {
if (data.data[i] !== currentState.data[i]) {
same = false;
break;
}
}
if (same) {
return;
}
}
if (currentState !== undefined) {
uBuffer.push(currentState);
}
currentState = data;
rBuffer.length = 0;
this.UI.update();
},
undo: function() {
if (uBuffer.length > 0) {
if (currentState !== undefined) {
rBuffer.push(currentState);
}
currentState = uBuffer.pop();
}
this.UI.update();
return currentState; // return data or unfefined
},
redo: function() {
if (rBuffer.length > 0) {
if (currentState !== undefined) {
uBuffer.push(currentState);
}
currentState = rBuffer.pop();
}
this.UI.update();
return currentState;
},
}
return manager;
})();
function start() {
var canvas = document.createElement('canvas');
canvas.setAttribute('width', canvasWidth);
canvas.setAttribute('height', canvasHeight);
canvas.setAttribute('id', 'canvas');
document.getElementById('canvasDiv').appendChild(canvas);
if (typeof G_vmlCanvasManager !== "undefined") {
canvas = G_vmlCanvasManager.initElement(canvas);
}
context = canvas.getContext("2d");
backgroundImage.onload = resourceLoaded();
backgroundImage.src = "images/t.png";
outlineImage.onload = function() {
context.drawImage(outlineImage, drawingAreaX, drawingAreaY, drawingAreaWidth, drawingAreaHeight);
try {
outlineLayerData = context.getImageData(0, 0, canvasWidth, canvasHeight);
} catch (ex) {
window.alert("Application cannot be run locally. Please run on a server.");
return;
}
clearCanvas();
colorLayerData = context.getImageData(0, 0, canvasWidth, canvasHeight);
resourceLoaded();
};
outlineImage.src = "images/products/<?php echo $product['product_image']; ?>";
};
if (historyManager !== undefined) {
// only for visual feedback and not required for the history manager to function.
historyManager.UI.assignUndoButton(document.querySelector("#undo-button"));
historyManager.UI.assignRedoButton(document.querySelector("#redo-button"));
}
getColor = function() {
};
</script>
是的,遗漏的 "white" 点实际上不是白色的,因为白色和黑色之间存在微小的渐变。尝试在这些行周围给它一些余地:
if ((r <= curColor.r + 10 && r >= curColor.r - 10) && (r >= curColor.g - 10 && r <= curColor.g + 10) && (b >= curColor.b - 10 && b <= curColor.b + 10)) {
return false;
}
您可以修改 10 因子,直到它看起来不错。只是调整它,直到它没问题。
(可能是错误的代码,我刚醒来,但你应该得到照片 :D )
您还可以在单独的缓冲区中预处理图像并减少颜色数。这样更容易填充渐变的开头,从而减少或消除您描述的不良效果。
老实说,与其说是您的绘图程序的问题,不如说是所绘制图像的问题。 'white' 像素实际上是浅灰色,这是用于在图像中绘制线条的画笔工具的副作用。有两种解决方法:
从图像中去除所有浅灰色像素并使它们变白。使用颜色 select 工具和铅笔工具可以解决这个问题。唯一的副作用是某些点的线条可能看起来有点生涩。
在涉及到哪些颜色被覆盖时给予一些宽大处理。因此,不仅要替换纯白色,还要替换浅灰色。 #CCC(或 rgb(204, 204, 204))附近的任何颜色都应该涂上颜色。
方案二的代码如下:
if(r >= 204 && g >= 204 && b >= 204 && r === g && g === b){
return true;
}
这只是检查像素是否为浅灰度颜色,如果是,则 returns 为真。使用它代替您的轮廓颜色检查功能。
我建议进行两项更改:
- 混合像素和填充颜色而不是硬覆盖
- 根据强度梯度变化而不是简单的阈值来限制填充区域
在水平和垂直方向上填充,直到强度梯度的符号从 + 翻转到 - 或 - 到 + 让我们填充整个区域,包括黑色边框的 'half'。通过检查梯度,我们只是确保不超过强度最小值,从而避免填充相邻区域。
看看下面的演示:
// Get pixel intensity:
function getIntensity(data, i) {
return data[i] + data[i + 1] + data[i + 2];
}
// Set pixel color:
function setColor(data, i, r, g, b) {
data[i] &= r;
data[i + 1] &= g;
data[i + 2] &= b;
}
// Fill a horizontal line:
function fill(x, y, data, width, r, g, b) {
var i_start = y * (width << 2);
var i = i_start + (x << 2);
var i_end = i_start + (width << 2);
var i_intensity = getIntensity(data, i);
// Horizontal line to the right:
var prev_gradient = 0;
var prev_intensity = i_intensity;
for (var j = i; j < i_end; j += 4) {
var intensity = getIntensity(data, j);
gradient = intensity - prev_intensity;
prev_intensity = intensity;
if ((prev_gradient > 0 && gradient < 0) || (prev_gradient < 0 && gradient > 0)) break;
if (gradient != 0) prev_gradient = gradient;
setColor(data, j, 255, 0, 0);
}
// Horizontal line to the left:
prev_gradient = 0;
prev_intensity = i_intensity;
for (var j = i - 4; j > i_start; j -= 4) {
var intensity = getIntensity(data, j);
gradient = intensity - prev_intensity;
prev_intensity = intensity;
if ((prev_gradient > 0 && gradient < 0) || (prev_gradient < 0 && gradient > 0)) break;
if (gradient != 0) prev_gradient = gradient;
setColor(data, j, 255, 0, 0);
}
}
// Demo canvas:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
// Fill horizontal line on click:
canvas.addEventListener('mousedown', event => {
var rect = canvas.getBoundingClientRect();
var x = event.clientX - rect.left | 0;
var y = event.clientY - rect.top | 0;
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
fill(x, y, imageData.data, imageData.width);
context.putImageData(imageData, 0, 0);
});
// Load a sample image:
var image = new Image();
image.addEventListener('load', event => {
context.drawImage(event.target, 0, 0, canvas.width, canvas.height);
});
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAACCAIAAAABwbG5AAAAA3NCSVQICAjb4U/gAAAAGXRFWHRTb2Z0d2FyZQBnbm9tZS1zY3JlZW5zaG907wO/PgAAACZJREFUCJlj+I8Kbt68mZeXd/PmTbgIw38MsG/fvoKCgqdPn0K4ACAfPGrloJp1AAAAAElFTkSuQmCC';
<canvas id="canvas"></canvas>
您可以通过仅跟踪一个颜色通道而不是对 getIntensity
函数的所有三个颜色通道求和来提高性能。
此外,由于混合模式的原因,您需要处理 'clearing' 一个已经填充的区域,然后再用另一种颜色填充它。
你可以。 G。在内存中保留背景图像的单通道灰度图像数据数组,并将其用作填充算法的源图像。
(手动或自动)将所有灰度图像转换为具有二进制边界的 1 位轮廓并将其用作填充算法的源,将结果与灰度图像平滑地混合可能会更高效。
您对轮廓的检查太严格了,将浅灰色像素标记为您无法着色的像素。我只是调整了您的阈值:
function matchOutlineColor (r,g,b,a,e) {
return a >= 150;
}
请注意我的白色值和 alpha 值要高得多。您也许可以进一步调整,但这对我来说看起来不错。以下是一些前后照片:
之前(放大 200%)
之后(放大 200%)
我正在创建一个绘图应用程序。我已经成功地完成了所有事情。当我用深色绘制图像时,边缘会出现一些白色像素。我试图改变 r + g + b 和 alpha 的值,但没有用。谁能帮我吗?您可以从 here 查看实时站点。谁愿意帮助我,我就赏金50给him/her。谢谢。这是我的代码。
<script type="text/javascript">
var initial = screen.width - 50;
if (initial < 1000) {
initial = 1000;
}
var firsttime = null;
var colorYellow = {
r: 255,
g: 207,
b: 51
};
var context;
var canvasWidth = initial;
var canvasHeight = initial;
var myColor = colorYellow;
var curColor = myColor;
var outlineImage = new Image();
var backgroundImage = new Image();
var drawingAreaX = 0;
var drawingAreaY = 0;
var drawingAreaWidth = initial;
var drawingAreaHeight = initial;
var colorLayerData;
var outlineLayerData;
var totalLoadResources = 2;
var curLoadResNum = 0;
var undoarr = new Array();
var redoarr = new Array();
function history(command) { // handles undo/redo button events.
var data;
if (command === "redo") {
data = historyManager.redo(); // get data for redo
} else
if (command === "undo") {
data = historyManager.undo(); // get data for undo
}
if (data !== undefined) { // if data has been found
setColorLayer(data); // set the data
}
}
// sets colour layer and creates copy into colorLayerData
function setColorLayer(data) {
context.putImageData(data, 0, 0);
colorLayerData = context.getImageData(0, 0, canvasWidth, canvasHeight);
context.drawImage(backgroundImage, 0, 0, canvasWidth, canvasHeight);
context.drawImage(outlineImage, 0, 0, drawingAreaWidth, drawingAreaHeight);
}
// Clears the canvas.
function clearCanvas() {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
}
// Draw the elements on the canvas
function redraw() {
uc = 0;
rc = 0;
var locX,
locY;
// Make sure required resources are loaded before redrawing
if (curLoadResNum < totalLoadResources) {
return; // To check if images are loaded successfully or not.
}
clearCanvas();
// Draw the current state of the color layer to the canvas
context.putImageData(colorLayerData, 0, 0);
historyManager.push(context.getImageData(0, 0, canvasWidth, canvasHeight));
redoarr = new Array();
// Draw the background
context.drawImage(backgroundImage, 0, 0, canvasWidth, canvasHeight);
// Draw the outline image on top of everything. We could move this to a separate
// canvas so we did not have to redraw this everyime.
context.drawImage(outlineImage, 0, 0, drawingAreaWidth, drawingAreaHeight);
};
function matchOutlineColor(r, g, b, a) {
return (r + g + b < 50 && a >= 50);
};
function matchStartColor(pixelPos, startR, startG, startB) {
var r = outlineLayerData.data[pixelPos],
g = outlineLayerData.data[pixelPos + 1],
b = outlineLayerData.data[pixelPos + 2],
a = outlineLayerData.data[pixelPos + 3];
// If current pixel of the outline image is black
if (matchOutlineColor(r, g, b, a)) {
return false;
}
r = colorLayerData.data[pixelPos];
g = colorLayerData.data[pixelPos + 1];
b = colorLayerData.data[pixelPos + 2];
// If the current pixel matches the clicked color
if (r === startR && g === startG && b === startB) {
return true;
}
// If current pixel matches the new color
if (r === curColor.r && g === curColor.g && b === curColor.b) {
return false;
}
return true;
};
function colorPixel(pixelPos, r, g, b, a) {
colorLayerData.data[pixelPos] = r;
colorLayerData.data[pixelPos + 1] = g;
colorLayerData.data[pixelPos + 2] = b;
colorLayerData.data[pixelPos + 3] = a !== undefined ? a : 255;
};
function floodFill(startX, startY, startR, startG, startB) {
document.getElementById('color-lib-1').style.display = "none";
document.getElementById('color-lib-2').style.display = "none";
document.getElementById('color-lib-3').style.display = "none";
document.getElementById('color-lib-4').style.display = "none";
document.getElementById('color-lib-5').style.display = "none";
change = false;
var newPos,
x,
y,
pixelPos,
reachLeft,
reachRight,
drawingBoundLeft = drawingAreaX,
drawingBoundTop = drawingAreaY,
drawingBoundRight = drawingAreaX + drawingAreaWidth - 1,
drawingBoundBottom = drawingAreaY + drawingAreaHeight - 1,
pixelStack = [
[startX, startY]
];
while (pixelStack.length) {
newPos = pixelStack.pop();
x = newPos[0];
y = newPos[1];
// Get current pixel position
pixelPos = (y * canvasWidth + x) * 4;
// Go up as long as the color matches and are inside the canvas
while (y >= drawingBoundTop && matchStartColor(pixelPos, startR, startG, startB)) {
y -= 1;
pixelPos -= canvasWidth * 4;
}
pixelPos += canvasWidth * 4;
y += 1;
reachLeft = false;
reachRight = false;
// Go down as long as the color matches and in inside the canvas
while (y <= drawingBoundBottom && matchStartColor(pixelPos, startR, startG, startB)) {
y += 1;
colorPixel(pixelPos, curColor.r, curColor.g, curColor.b);
if (x > drawingBoundLeft) {
if (matchStartColor(pixelPos - 4, startR, startG, startB)) {
if (!reachLeft) {
// Add pixel to stack
pixelStack.push([x - 1, y]);
reachLeft = true;
}
} else if (reachLeft) {
reachLeft = false;
}
}
if (x < drawingBoundRight) {
if (matchStartColor(pixelPos + 4, startR, startG, startB)) {
if (!reachRight) {
// Add pixel to stack
pixelStack.push([x + 1, y]);
reachRight = true;
}
} else if (reachRight) {
reachRight = false;
}
}
pixelPos += canvasWidth * 4;
}
}
};
// Start painting with paint bucket tool starting from pixel specified by startX and startY
function paintAt(startX, startY) {
var pixelPos = (startY * canvasWidth + startX) * 4,
r = colorLayerData.data[pixelPos],
g = colorLayerData.data[pixelPos + 1],
b = colorLayerData.data[pixelPos + 2],
a = colorLayerData.data[pixelPos + 3];
if (r === curColor.r && g === curColor.g && b === curColor.b) {
// Return because trying to fill with the same color
return;
}
if (matchOutlineColor(r, g, b, a)) {
// Return because clicked outline
return;
}
floodFill(startX, startY, r, g, b);
redraw();
};
// Add mouse event listeners to the canvas
function createMouseEvents() {
$('#canvas').mousedown(function(e) {
// Mouse down location
var mouseX = e.pageX - this.offsetLeft,
mouseY = e.pageY - this.offsetTop;
// assuming that the mouseX and mouseY are the mouse coords.
if (this.style.width) { // make sure there is a width in the style
// (assumes if width is there then height will be too
var w = Number(this.style.width.replace("px", "")); // warning this will not work if size is not in pixels
var h = Number(this.style.height.replace("px", "")); // convert the height to a number
var pixelW = this.width; // get the canvas resolution
var pixelH = this.height;
mouseX = Math.floor((mouseX / w) * pixelW); // convert the mouse coords to pixel coords
mouseY = Math.floor((mouseY / h) * pixelH);
}
if ((mouseY > drawingAreaY && mouseY < drawingAreaY + drawingAreaHeight) && (mouseX <= drawingAreaX + drawingAreaWidth)) {
paintAt(mouseX, mouseY);
}
});
};
resourceLoaded = function() {
curLoadResNum += 1;
//if (curLoadResNum === totalLoadResources) {
createMouseEvents();
redraw();
//}
};
var historyManager = (function() { // Anon for private (closure) scope
var uBuffer = []; // this is undo buff
var rBuffer = []; // this is redo buff
var currentState = undefined; // this holds the current history state
var undoElement = undefined;
var redoElement = undefined;
var manager = {
UI: { // UI interface just for disable and enabling redo undo buttons
assignUndoButton: function(element) {
undoElement = element;
this.update();
},
assignRedoButton: function(element) {
redoElement = element;
this.update();
},
update: function() {
if (redoElement !== undefined) {
redoElement.disabled = (rBuffer.length === 0);
}
if (undoElement !== undefined) {
undoElement.disabled = (uBuffer.length === 0);
}
}
},
reset: function() {
uBuffer.length = 0;
rBuffer.length = 0;
currentState = undefined;
this.UI.update();
},
push: function(data) {
if (currentState !== undefined) {
var same = true
for (i = 0; i < data.data.length; i++) {
if (data.data[i] !== currentState.data[i]) {
same = false;
break;
}
}
if (same) {
return;
}
}
if (currentState !== undefined) {
uBuffer.push(currentState);
}
currentState = data;
rBuffer.length = 0;
this.UI.update();
},
undo: function() {
if (uBuffer.length > 0) {
if (currentState !== undefined) {
rBuffer.push(currentState);
}
currentState = uBuffer.pop();
}
this.UI.update();
return currentState; // return data or unfefined
},
redo: function() {
if (rBuffer.length > 0) {
if (currentState !== undefined) {
uBuffer.push(currentState);
}
currentState = rBuffer.pop();
}
this.UI.update();
return currentState;
},
}
return manager;
})();
function start() {
var canvas = document.createElement('canvas');
canvas.setAttribute('width', canvasWidth);
canvas.setAttribute('height', canvasHeight);
canvas.setAttribute('id', 'canvas');
document.getElementById('canvasDiv').appendChild(canvas);
if (typeof G_vmlCanvasManager !== "undefined") {
canvas = G_vmlCanvasManager.initElement(canvas);
}
context = canvas.getContext("2d");
backgroundImage.onload = resourceLoaded();
backgroundImage.src = "images/t.png";
outlineImage.onload = function() {
context.drawImage(outlineImage, drawingAreaX, drawingAreaY, drawingAreaWidth, drawingAreaHeight);
try {
outlineLayerData = context.getImageData(0, 0, canvasWidth, canvasHeight);
} catch (ex) {
window.alert("Application cannot be run locally. Please run on a server.");
return;
}
clearCanvas();
colorLayerData = context.getImageData(0, 0, canvasWidth, canvasHeight);
resourceLoaded();
};
outlineImage.src = "images/products/<?php echo $product['product_image']; ?>";
};
if (historyManager !== undefined) {
// only for visual feedback and not required for the history manager to function.
historyManager.UI.assignUndoButton(document.querySelector("#undo-button"));
historyManager.UI.assignRedoButton(document.querySelector("#redo-button"));
}
getColor = function() {
};
</script>
是的,遗漏的 "white" 点实际上不是白色的,因为白色和黑色之间存在微小的渐变。尝试在这些行周围给它一些余地:
if ((r <= curColor.r + 10 && r >= curColor.r - 10) && (r >= curColor.g - 10 && r <= curColor.g + 10) && (b >= curColor.b - 10 && b <= curColor.b + 10)) {
return false;
}
您可以修改 10 因子,直到它看起来不错。只是调整它,直到它没问题。 (可能是错误的代码,我刚醒来,但你应该得到照片 :D )
您还可以在单独的缓冲区中预处理图像并减少颜色数。这样更容易填充渐变的开头,从而减少或消除您描述的不良效果。
老实说,与其说是您的绘图程序的问题,不如说是所绘制图像的问题。 'white' 像素实际上是浅灰色,这是用于在图像中绘制线条的画笔工具的副作用。有两种解决方法:
从图像中去除所有浅灰色像素并使它们变白。使用颜色 select 工具和铅笔工具可以解决这个问题。唯一的副作用是某些点的线条可能看起来有点生涩。
在涉及到哪些颜色被覆盖时给予一些宽大处理。因此,不仅要替换纯白色,还要替换浅灰色。 #CCC(或 rgb(204, 204, 204))附近的任何颜色都应该涂上颜色。
方案二的代码如下:
if(r >= 204 && g >= 204 && b >= 204 && r === g && g === b){
return true;
}
这只是检查像素是否为浅灰度颜色,如果是,则 returns 为真。使用它代替您的轮廓颜色检查功能。
我建议进行两项更改:
- 混合像素和填充颜色而不是硬覆盖
- 根据强度梯度变化而不是简单的阈值来限制填充区域
在水平和垂直方向上填充,直到强度梯度的符号从 + 翻转到 - 或 - 到 + 让我们填充整个区域,包括黑色边框的 'half'。通过检查梯度,我们只是确保不超过强度最小值,从而避免填充相邻区域。
看看下面的演示:
// Get pixel intensity:
function getIntensity(data, i) {
return data[i] + data[i + 1] + data[i + 2];
}
// Set pixel color:
function setColor(data, i, r, g, b) {
data[i] &= r;
data[i + 1] &= g;
data[i + 2] &= b;
}
// Fill a horizontal line:
function fill(x, y, data, width, r, g, b) {
var i_start = y * (width << 2);
var i = i_start + (x << 2);
var i_end = i_start + (width << 2);
var i_intensity = getIntensity(data, i);
// Horizontal line to the right:
var prev_gradient = 0;
var prev_intensity = i_intensity;
for (var j = i; j < i_end; j += 4) {
var intensity = getIntensity(data, j);
gradient = intensity - prev_intensity;
prev_intensity = intensity;
if ((prev_gradient > 0 && gradient < 0) || (prev_gradient < 0 && gradient > 0)) break;
if (gradient != 0) prev_gradient = gradient;
setColor(data, j, 255, 0, 0);
}
// Horizontal line to the left:
prev_gradient = 0;
prev_intensity = i_intensity;
for (var j = i - 4; j > i_start; j -= 4) {
var intensity = getIntensity(data, j);
gradient = intensity - prev_intensity;
prev_intensity = intensity;
if ((prev_gradient > 0 && gradient < 0) || (prev_gradient < 0 && gradient > 0)) break;
if (gradient != 0) prev_gradient = gradient;
setColor(data, j, 255, 0, 0);
}
}
// Demo canvas:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
// Fill horizontal line on click:
canvas.addEventListener('mousedown', event => {
var rect = canvas.getBoundingClientRect();
var x = event.clientX - rect.left | 0;
var y = event.clientY - rect.top | 0;
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
fill(x, y, imageData.data, imageData.width);
context.putImageData(imageData, 0, 0);
});
// Load a sample image:
var image = new Image();
image.addEventListener('load', event => {
context.drawImage(event.target, 0, 0, canvas.width, canvas.height);
});
image.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAACCAIAAAABwbG5AAAAA3NCSVQICAjb4U/gAAAAGXRFWHRTb2Z0d2FyZQBnbm9tZS1zY3JlZW5zaG907wO/PgAAACZJREFUCJlj+I8Kbt68mZeXd/PmTbgIw38MsG/fvoKCgqdPn0K4ACAfPGrloJp1AAAAAElFTkSuQmCC';
<canvas id="canvas"></canvas>
您可以通过仅跟踪一个颜色通道而不是对 getIntensity
函数的所有三个颜色通道求和来提高性能。
此外,由于混合模式的原因,您需要处理 'clearing' 一个已经填充的区域,然后再用另一种颜色填充它。
你可以。 G。在内存中保留背景图像的单通道灰度图像数据数组,并将其用作填充算法的源图像。
(手动或自动)将所有灰度图像转换为具有二进制边界的 1 位轮廓并将其用作填充算法的源,将结果与灰度图像平滑地混合可能会更高效。
您对轮廓的检查太严格了,将浅灰色像素标记为您无法着色的像素。我只是调整了您的阈值:
function matchOutlineColor (r,g,b,a,e) {
return a >= 150;
}
请注意我的白色值和 alpha 值要高得多。您也许可以进一步调整,但这对我来说看起来不错。以下是一些前后照片:
之前(放大 200%)
之后(放大 200%)