·  阅读 2376

无边界抠图

``````let canvas = document.querySelector('#canvas');
let context = canvas.getContext('2d');
let img = document.createElement('img');
canvas.height = img.height;
canvas.width = img.width;
context.drawImage(img, 0, 0);

cutout(canvas, [255, 255, 255], 0.2); // 对白色进行抠除，容差为0.2
}

function cutout(canvas, color, range = 0) {
let context = canvas.getContext('2d');
let imageInfo = context.getImageData(0, 0, canvas.width, canvas.height);
// pixiArr是一个数组，每四个数组元素代表一个像素点，这四个数组元素分别对应一个像素的r，g，b，a值。
let pixiArr = imageInfo.data;
for (let i = 0; i < pixiArr.length; i += 4) {
// 匹配到目标像素就将目标像素的alpha设为0
if (testColor([pixiArr[i], pixiArr[i + 1], pixiArr[i + 2]], color, range)) pixiArr[i + 3] = 0;
}

context.putImageData(imageInfo, 0, 0);
}

function testColor(current, target, range) {
for (let i = 0; i < 3; i++) {
if (!((1 - range) * target[i] <= current[i] && (1 + range) * target[i] >= current[i])) return false;
}

return true;
}

`testColor(current, target, range)` 方法三个参数分别为`待检测像素点的rgb数组``目标像素点的rgb数组``容差范围`，这里的容差只是简单用r、g、b的值分别乘以(1 + range)和(1 - range)来计算并对比，不同的容差参数会得到不同的效果↓

range = 0.095

range = 0.1

range = 0.2

边界处理

``````function cutout(canvas, color, range = 0) {
let context = canvas.getContext('2d');
let imageInfo = context.getImageData(0, 0, canvas.width, canvas.height);
let pixiArr = imageInfo.data;

for (let row = 0; row < canvas.height; row++) {
let left = row * 4 * canvas.width; // 指向行首像素
let right = left + 4 * canvas.width - 1 - 3; // 指向行尾像素
let leftF = false; // 左指针是否碰到边界的标识
let rightF = false; // 右指针是否碰到边界的标识

while (!leftF || !rightF) { // 当左右指针都为true，即都碰到边界时结束
if (!leftF) {
if (testColor([pixiArr[left], pixiArr[left + 1], pixiArr[left + 2]], color, range)) {
pixiArr[left + 3] = 0; // 此像素的alpha设为0
left += 4; // 移到下一个像素
} else leftF = true; // 碰到边界
}
if (!rightF) {
if (testColor([pixiArr[right], pixiArr[right + 1], pixiArr[right + 2]], color, range)) {
pixiArr[right + 3] = 0;
right -= 4;
} else rightF = true;
}

if (left == right) { // 左右指针重合
leftF = true;
rightF = true;
};
}
}

context.putImageData(imageInfo, 0, 0);
}

``````function cutout(canvas, color, range = 0) {
let context = canvas.getContext('2d');
let imageInfo = context.getImageData(0, 0, canvas.width, canvas.height);
let pixiArr = imageInfo.data;

for (let row = 0; row < canvas.height; row++) {
let left = row * 4 * canvas.width;
let right = left + 4 * canvas.width - 1 - 3;
let leftF = false;
let rightF = false;

while (!leftF || !rightF) {
if (!leftF) {
if (testColor([pixiArr[left], pixiArr[left + 1], pixiArr[left + 2]], color, range)) {
pixiArr[left + 3] = 0;
left += 4;
} else leftF = true;
}
if (!rightF) {
if (testColor([pixiArr[right], pixiArr[right + 1], pixiArr[right + 2]], color, range)) {
pixiArr[right + 3] = 0;
right -= 4;
} else rightF = true;
}

if (left == right) {
leftF = true;
rightF = true;
};
}
}

// 同理进行列扫描
for (let col = 0; col < canvas.width; col++) {
let top = col * 4; // 指向列头
let bottom = top + (canvas.height - 2) * canvas.width * 4 + canvas.width * 4; // 指向列尾
let topF = false;
let bottomF = false;

while (!topF || !bottomF) {
if (!topF) {
if (testColor([pixiArr[top], pixiArr[top + 1], pixiArr[top + 2]], color, range)) {
pixiArr[top + 3] = 0;
top += canvas.width * 4;
} else topF = true;
}
if (!bottomF) {
if (testColor([pixiArr[bottom], pixiArr[bottom + 1], pixiArr[bottom + 2]], color, range)) {
pixiArr[bottom + 3] = 0;
bottom -= canvas.width * 4;
} else bottomF = true;
}

if (top == bottom) {
topF = true;
bottomF = true;
};
}
}

context.putImageData(imageInfo, 0, 0);
}

``````[
[{r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}],
[{r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}]
[{r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}]
]

洪泛法

``````floodFill8(x, y)
{
// 判断是否x和y的坐标在图形范围内且像素符合扣除条件，还要判断是否是已处理过的像素。
if(x >= 0 && x < width && y >= 0 && y < height && testColor(x, y) && alpha(x, y) != 0)
{
alpha(x, y) = 0;

// 递归处理周围8个像素
floodFill8(x + 1, y);
floodFill8(x - 1, y);
floodFill8(x, y + 1);
floodFill8(x, y - 1);
floodFill8(x + 1, y + 1);
floodFill8(x - 1, y - 1);
floodFill8(x - 1, y + 1);
floodFill8(x + 1, y - 1);
}
}

``````// 注意这里的方法多了两个参数`startX`与`startY`，代表洪泛的开始坐标。
function cutoutFlood(canvas, startX, startY, color, range = 0) {
let context = canvas.getContext('2d');
let imageInfo = context.getImageData(0, 0, canvas.width, canvas.height);
let pixiArr = imageInfo.data;
let stack = [];

function floodFill8(x, y) {

// 8个方向
let dx = [0, 1, 1, 1, 0, -1, -1, -1];
let dy = [-1, -1, 0, 1, 1, 1, 0, -1];

let map = {}; // 标识已经处理过的像素点，防止重复处理

// 如果开始像素符合条件，则将它放入栈中并标识为已处理
let cell = (x + y * canvas.width) * 4;
if(testColor([pixiArr[cell], pixiArr[cell + 1], pixiArr[cell + 2]], color, range)) {
let firstPixi = `x\${x}y\${y}`; // `x\${x}y\${y}`是一个不会重复的唯一标识id
map[firstPixi] = true;
stack.push({
x,
y
});
} else return; // 否则直接结束

let p; // position
while (p = stack.pop()) { // 获取栈顶待处理的符合条件的像素的x与y值
cell = (p.x + p.y * canvas.width) * 4;
pixiArr[cell + 3] = 0;
// 测试周围8个是否符合抠除条件
for (let i = 0; i < 8; i++) {
let nx = p.x + dx[i];
let ny = p.y + dy[i];
// 是否在范围内并且没有被处理过
if (nx >= 0 && nx < canvas.width && ny >= 0 && ny < canvas.height && !map[`x\${nx}y\${ny}`]) {
cell = (nx + ny * canvas.width) * 4;
if (testColor([pixiArr[cell], pixiArr[cell + 1], pixiArr[cell + 2]], color, range)) {
map[`x\${nx}y\${ny}`] = true; // 标识此像素已被处理
// 没处理过则放入栈中
stack.push({
x: nx,
y: ny
});
}
}
}
}
}

floodFill8(startX, startY);
context.putImageData(imageInfo, 0, 0);
}