html5利用canvas实现颜色容差抠图功能


Posted in HTML / CSS onDecember 23, 2019

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

html5利用canvas实现颜色容差抠图功能 

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

html5利用canvas实现颜色容差抠图功能 

range = 0.1

html5利用canvas实现颜色容差抠图功能 

range = 0.2

html5利用canvas实现颜色容差抠图功能 

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

边界处理

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

我们对每一行分别进行扫描,定义一个左指针 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);
}

html5利用canvas实现颜色容差抠图功能 

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

html5利用canvas实现颜色容差抠图功能 

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

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为啥是那样计算画个矩阵图大概就知道了。

html5利用canvas实现颜色容差抠图功能 

处理后↓

html5利用canvas实现颜色容差抠图功能 

其实还可以先将 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实现颜色容差抠图功能 

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

html5利用canvas实现颜色容差抠图功能 

最后

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

总结

以上所述是小编给大家介绍的html5利用canvas实现颜色容差抠图功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

HTML / CSS 相关文章推荐
css3的图形3d翻转效果应用示例
Apr 08 HTML / CSS
纯CSS绘制漂亮的圆形图案效果
May 07 HTML / CSS
一款纯css3实现的非常实用的鼠标悬停特效演示
Nov 05 HTML / CSS
利用CSS3实现文字折纸效果实例代码
Jul 10 HTML / CSS
HTML5 Web Database 数据库的SQL语句的使用方法
Dec 09 HTML / CSS
基于IE10/HTML5 开发
Apr 22 HTML / CSS
基于HTML5 Canvas:字符串,路径,背景,图片的详解
May 09 HTML / CSS
HTML5几个设计和修改的页面范例分享
Sep 29 HTML / CSS
关于老式浏览器兼容HTML5和CSS3的问题
Jun 01 HTML / CSS
canvas绘制树形结构可视图形的实现
Apr 03 HTML / CSS
AmazeUI 点击元素显示全屏的实现
Aug 25 HTML / CSS
使用CSS3实现按钮悬停闪烁动态特效代码
Aug 30 HTML / CSS
HTML5 客户端数据库简易使用:IndexedDB
Dec 19 #HTML / CSS
吃透移动端 Html5 响应式布局
Dec 16 #HTML / CSS
HTML文本属性&amp;颜色控制属性的实现
Dec 17 #HTML / CSS
吃透移动端 1px的具体用法
Dec 16 #HTML / CSS
关于html字符串正则判断和匹配的具体使用
Dec 12 #HTML / CSS
处理textarea中的换行和空格
Dec 12 #HTML / CSS
VSCode 自定义html5模板的实现
Dec 05 #HTML / CSS
You might like
ThinkPHP中url隐藏入口文件后接收alipay传值的方法
2014/12/09 PHP
PHP框架Laravel学习心得体会
2015/10/28 PHP
深入解析PHP的Laravel框架中的event事件操作
2016/03/21 PHP
PHP如何将XML转成数组
2016/04/04 PHP
php 常用的系统函数
2017/02/07 PHP
Thinkphp框架使用list_to_tree 实现无限级分类列出所有节点示例
2020/04/04 PHP
javascript据option的value值快速设定初始的selected选项
2007/08/13 Javascript
YUI模块开发原理详解
2013/11/18 Javascript
JS实现的打字机效果完整实例
2016/06/20 Javascript
BootStrap下拉菜单和滚动监听插件实现代码
2016/09/26 Javascript
一个非常好用的文字滚动的案例,鼠标悬浮可暂停[两种方案任选]
2016/12/01 Javascript
jQuery Plupload上传插件的使用
2017/04/19 jQuery
elemetUi 组件--el-upload实现上传Excel文件的实例
2017/10/27 Javascript
js判断节假日实例代码
2017/12/27 Javascript
jQuery实现手机号正则验证输入及自动填充空格功能
2018/01/02 jQuery
jQuery扩展方法实现Form表单与Json互相转换的实例代码
2018/09/05 jQuery
监控Nodejs的性能实例代码
2019/07/02 NodeJs
vue实现路由懒加载的3种方法示例
2020/09/01 Javascript
python在命令行下使用google翻译(带语音)
2014/01/16 Python
Python中实现对Timestamp和Datetime及UTC时间之间的转换
2015/04/08 Python
python中list列表的高级函数
2016/05/17 Python
Python使用sorted排序的方法小结
2017/07/28 Python
Numpy array数据的增、删、改、查实例
2018/06/04 Python
在python中获取div的文本内容并和想定结果进行对比详解
2019/01/02 Python
python 密码学示例——理解哈希(Hash)算法
2020/09/21 Python
如何通过python计算圆周率PI
2020/11/11 Python
html5使用window.postMessage进行跨域实现数据交互的一次实战
2021/02/24 HTML / CSS
Rhone官方网站:男士运动服装、健身服装和高级运动服
2019/05/01 全球购物
请解释一下webService? 如何用.net实现webService
2014/06/09 面试题
营业员个人总结的自我评价
2013/10/25 职场文书
初中班主任评语
2014/04/24 职场文书
公司业务员管理制度
2015/08/05 职场文书
纯CSS实现酷炫的霓虹灯效果
2021/04/13 HTML / CSS
关于CSS浮动与取消浮动的问题
2021/06/28 HTML / CSS
Netty分布式客户端处理接入事件handle源码解析
2022/03/25 Java/Android
win10蓝屏0xc0000001安全模式进不了怎么办?win10出现0xc0000001的解决方法
2022/08/05 数码科技