html5 自动扣图,html5利用canvas实现颜色容差抠图功能

利用canvas的getImageData,我们可以获取到一张图片每一个像素的信息,而通过对每一个像素信息的对比,我们就可以找到需要消去的像素点。比如下面这一张图片,如果我们想要扣去白色部分(粉色是body的背景颜色)。

db8e298e4c8468e42858ac953b169d49.png

let canvas = document.querySelector('#canvas');

let context = canvas.getContext('2d');

let img = document.createElement('img');

img.src = './head2.png';

img.onload = function () {

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

b48a4606aecff1dcd8a372b254c290dd.png

range = 0.1

1ceba1e370e690a82014e35c942574ca.png

range = 0.2

6d5b2ce31d2dcdfec9b6575ffb45c919.png

当然对于底色是标准的纯色的图片就不需要容差了。

边界处理

但是有时候我们希望有一个边界,让抠图操作不对边界内部的像素造成影响。比如上面的图片,我们希望不会对人物头像内部的像素造成影响。 如果我们一行一行来看,是不是只要在碰到不是边界像素的时候停止操作,就可以达到效果了呢?

我们对每一行分别进行扫描,定义一个左指针 left 指向这一行的第一个像素,定义一个右指针 right 指向这一行的最后一个像素,并用一个 leftF 标识左边是否碰到边界,一个 rightF 标识右边是否碰到边界,当没碰到边界时指针就一直向内收缩,直到两个指针都碰到边界或者左右指针重合就跳到下一行,直到所有行都扫描完毕。

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);

}

3fb0ea2ae8f3f1eea58784e6c5cd2237.png

虽然大概完成了我们的需求,但是看一下上面头发那为啥会多了一块白色

0cf753a92722cc99a7c87fd712858e02.png

因为我们仅仅只进行了行扫描,当左指针碰到头发时就会停止扫描,但是头发弧度里面的就无法被扫描到了,我们还需要进行列扫描,改造一下上面的方法:

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);

}

至于top和bottom为啥是那样计算画个矩阵图大概就知道了。

0481149ae75362df4eb39f9d88b1d594.png

处理后↓

ce3d89e94f6490450679d6542114f446.png

其实还可以先将 pixiArr 包装为以一个像素点为单位的矩阵

[

[{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}]

]

处理后计算像素下标也就会更简单,列扫描时直接先将这个矩阵旋转,再用行扫描处理一遍就行了。这样处理pixiArr也有利于进一步对算法进行优化。

上述方法虽然大概完成了抠图效果,但是这种简单处理还会有许多情况没有考虑到。

比如右边头发这里是行扫描和列扫描都无法触碰到的区域↓

3c0f25a312aedf71274e3cf8ab158489.png

下面的衣服也因为颜色和底色一样且没有边界在列扫描中被直接抹去了↓

a4abe8234ebd0f2c5f6a44c3e1560237.png

最后

对于主体和底色区分度很大的图片来说,最开始的那种方法就已经够用了。这篇中我采用的容差和边界处理算法的优化空间还很大,但是它们是非常容易实现与理解的,这篇权当做一个引子,各位完全可以根据自己的能力继续去实现边界与容差算法。

总结

以上所述是小编给大家介绍的html5利用canvas实现颜色容差抠图功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
html5 canvas填色画游戏代码是一款适合儿童玩的益智类填色游戏,通过选中色块,来给模型填充颜色。支持多种模型选择填色效果。 function extend(O1,O2){     for(var i in O2){         O1[i]=O2[i];     } } function each(arr,f){     for(var i = 0;i<arr.length;i ){         f.call(arr[i],i,arr[i]);     } } function dataInfo(Obj,x,y){     var c=document.createElement("canvas");     var txt= c.getContext("2d");     c.width=Obj.img.width;     c.height=Obj.img.height;     txt.drawImage(Obj.img,0,0);     var data=txt.getImageData(x-1,y-1,3,3);     var num=0;     for(var q=0;q<data.data.length;q =4){         num =data.data[q 3];     }     num=num/9;     return parseInt(num); } var HGAME=new Object(); HGAME.event=new Object();//事件对象 HGAME.event.clickBuffer=new Array();//缓存要添加事件的节点 click buffer HGAME.animate=function(Obj){     var defaultObj={         time:30,//动画间隔         frequency:-1,//动画次数 -1表示无限制         action:function(){},//动画每一帧的动作         lastAction:function(){}//最后一次动画执行完成触发函数     };     extend(defaultObj,Obj);     var oldTime=new Date();     var newTime=null;     this.time=defaultObj.time;     this.frequency=defaultObj.frequency;     this.action=defaultObj.action;     this.lastAction=defaultObj.lastAction;     this.stop=function(){         cancelAnimationFrame(this.INT_BUFFER);     };

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值