效果展示

原理说明
实现油漆桶效果可以先了解一下FloodFill算法实现的原理,简单来说就是把图片信息转为像素信息,然后使用就是色差比较 。
具体想要了解的可以去搜一下,不搜也没关系主打的就是一个能用就行
注意
- fill函数是核心代码可以单独拿出去使用,其他都是随便写写
- 图片必须高清 越高清效果越好 上面展示的图片像素就不太行效果一般
- canvas展示图片的时候需要保真不能模糊(dpr,canvas宽高可以解决,可以百度一下有完整的解决方案,也可以使用下面渲染图片的代码),不然再高清的图片也不太行
- fabricjs用不了这个 当我已经上过了 希望需要加这个功能的同志不要再上当了
- 原图导出可以使用坐标映射(渲染原图和展示图,监听展示图点击事件拿到坐标再映射的原图上)
源代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button onclick="undo()" style="z-index: 999">撤销</button>
<div style="display: flex; justify-content: space-around; width: 200px">
<div
style="width: 20px; height: 20px; background-color: rgb(250, 3, 3)"
onclick="changeColor([250,3,3,255])"
></div>
<div
style="width: 20px; height: 20px; background-color: rgb(157, 255, 0)"
onclick="changeColor([157,255,0,255])"
></div>
<div
style="width: 20px; height: 20px; background-color: rgb(0, 119, 255)"
onclick="changeColor([0,119,255,255])"
></div>
<div
style="width: 20px; height: 20px; background-color: rgb(255, 0, 255)"
onclick="changeColor([255,2,255,255])"
></div>
</div>
<canvas
width="600"
height="600"
style="width: 600px; height: 600px"
id="canvas"
></canvas>
<script>
let targetColor = [255, 255, 255, 255];
let replacementColor = [255, 0, 0, 255];
const history = [];
const dpr = window.devicePixelRatio;
canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");
const image = new Image();
image.src = "";
image.crossOrigin = "anonymous";
image.onload = function () {
canvas.width = Math.round(image.width * dpr);
canvas.height = Math.round(image.height * dpr);
canvas.style.width = image.width + "px";
canvas.style.height = image.height + "px";
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
canvas.addEventListener("click", function (event) {
const x = event.offsetX;
const y = event.offsetY;
fill(x, y, targetColor, replacementColor);
});
};
function fill(x, y, targetColor, replacementColor) {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const width = imageData.width;
const height = imageData.height;
const stack = [[x, y]];
const getColorIndex = (x, y) => (y * width + x) * 4;
const isColorMatch = (color1, color2) => {
for (let i = 0; i < 3; i++) {
if (color1[i] !== color2[i]) {
return false;
}
}
return true;
};
const setColor = (x, y, color) => {
const index = getColorIndex(x, y);
data[index] = color[0];
data[index + 1] = color[1];
data[index + 2] = color[2];
data[index + 3] = color[3];
};
while (stack.length > 0) {
const [currentX, currentY] = stack.pop();
const currentIndex = getColorIndex(currentX, currentY);
const currentColor = [
data[currentIndex],
data[currentIndex + 1],
data[currentIndex + 2],
data[currentIndex + 3],
];
if (isColorMatch(currentColor, targetColor)) {
setColor(currentX, currentY, replacementColor);
if (currentX > 0) {
const leftColorIndex = getColorIndex(currentX - 1, currentY);
const leftColor = [
data[leftColorIndex],
data[leftColorIndex + 1],
data[leftColorIndex + 2],
data[leftColorIndex + 3],
];
if (isColorMatch(leftColor, targetColor)) {
stack.push([currentX - 1, currentY]);
}
}
if (currentX < width - 1) {
const rightColorIndex = getColorIndex(currentX + 1, currentY);
const rightColor = [
data[rightColorIndex],
data[rightColorIndex + 1],
data[rightColorIndex + 2],
data[rightColorIndex + 3],
];
if (isColorMatch(rightColor, targetColor)) {
stack.push([currentX + 1, currentY]);
}
}
if (currentY > 0) {
const topColorIndex = getColorIndex(currentX, currentY - 1);
const topColor = [
data[topColorIndex],
data[topColorIndex + 1],
data[topColorIndex + 2],
data[topColorIndex + 3],
];
if (isColorMatch(topColor, targetColor)) {
stack.push([currentX, currentY - 1]);
}
}
if (currentY < height - 1) {
const bottomColorIndex = getColorIndex(currentX, currentY + 1);
const bottomColor = [
data[bottomColorIndex],
data[bottomColorIndex + 1],
data[bottomColorIndex + 2],
data[bottomColorIndex + 3],
];
if (isColorMatch(bottomColor, targetColor)) {
stack.push([currentX, currentY + 1]);
}
}
}
}
ctx.putImageData(imageData, 0, 0);
this.saveState();
}
function saveState() {
history.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
console.log(history);
}
function undo() {
history.pop();
if (history.length > 0) {
ctx.putImageData(history[history.length - 1], 0, 0);
}
}
function changeColor(color) {
console.log(color);
replacementColor = color;
}
</script>
<script></script>
</body>
</html>