利用canvas的getImageData,我们可以获取到一张图片每一个像素的信息,而通过对每一个像素信息的对比,我们就可以找到需要消去的像素点。比如下面这一张图片,如果我们想要扣去白色部分(粉色是body的背景颜色)。
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
range = 0.1
range = 0.2
当然对于底色是标准的纯色的图片就不需要容差了。
边界处理
但是有时候我们希望有一个边界,让抠图操作不对边界内部的像素造成影响。比如上面的图片,我们希望不会对人物头像内部的像素造成影响。 如果我们一行一行来看,是不是只要在碰到不是边界像素的时候停止操作,就可以达到效果了呢?
我们对每一行分别进行扫描,定义一个左指针 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); }
虽然大概完成了我们的需求,但是看一下上面头发那为啥会多了一块白色
因为我们仅仅只进行了行扫描,当左指针碰到头发时就会停止扫描,但是头发弧度里面的就无法被扫描到了,我们还需要进行列扫描,改造一下上面的方法:
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为啥是那样计算画个矩阵图大概就知道了。
处理后↓
其实还可以先将 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也有利于进一步对算法进行优化。
上述方法虽然大概完成了抠图效果,但是这种简单处理还会有许多情况没有考虑到。
比如右边头发这里是行扫描和列扫描都无法触碰到的区域↓
下面的衣服也因为颜色和底色一样且没有边界在列扫描中被直接抹去了↓
最后
对于主体和底色区分度很大的图片来说,最开始的那种方法就已经够用了。这篇中我采用的容差和边界处理算法的优化空间还很大,但是它们是非常容易实现与理解的,这篇权当做一个引子,各位完全可以根据自己的能力继续去实现边界与容差算法。
总结
以上所述是小编给大家介绍的html5利用canvas实现颜色容差抠图功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]