• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Js canvas实现是否透明,抠图等功能原理

武飞扬头像
huihttp
帮助1

一、JS检测PNG图片是否有透明背景

用户上传图片,如果是PNG图片,有时候我们希望这张PNG图片背景不要是透明的,例如:

  1. 后端保存图片时候常常会转成JPG格式的,此时,如果后端同学不加以判断和处理,则透明区域会变成黑色,影响体验;
    例如,下面canvas将PNG图强制转换成JPG后的对比效果:
    学新通

透明背景转jpg格式后变黑

您可以狠狠地点击这里:PNG透明背景转JPG后变黑demo

  1. 轮播广告的后台,为了显示质量,后台往往是直接存图,保留原格式。这样就有这样一个问题,如果广告图是上下层叠定位的,此时如果某个广告图编辑传的是部分区域透明度的图,前端又没有考虑到这种场景则显示就会出现问题,透出了下面一张图糟糕的体验问题;

有时候,我们又希望上传的PNG图片带透明的,例如:

  1. 某平台可以自定义菜单,允许用户编辑文字和上传图标,很显然,这个图标需要是透明背景的,否则可能UI会很糟糕;
  2. QQ邮箱域名邮箱可以自定义logo和主题,如果上传的域名logo图片背景不是透明的,主题背景颜色也不一样,同样会出现比较糟糕的UI,如下截图:
    学新通

以上这些场景的处理通常这样的:

  • PNG背景透明区域变黑色 -> 后端黑色用白色代替;
  • 广告图镂空 -> CSS给元素设置个背景色:
    但,如果图片源用在多个地方,则此方法很难罩住所有场景。
img { background-color: #fff; }
  • 上传图标需透明 -> 忽略,交给用户
  • 上传logo需透明 -> 忽略,交给用户
    实际上,以上所有的处理都可以在上传图片的时候由前端一次性解决。

分两步:

  • 前端获取上传图片信息(包括图片类型,图片数据),IE10
  • JS检测图片是否有透明背景,IE9

我们先看demo,您可以狠狠地点击这里:png图片是否含有透明像素JS检测demo

如果是不含透明色的PNG图片,则会提示不含透明;如果是,则提示含透明,如下截图:
学新通
是否背景透明的检测

检测原理是借助canvas的getImageData()方法,关于此方法具体API和使用,可以参见“canvas getImageData与任意字符图形点、线动效”这篇文章“像素点信息获取”这里的详细介绍。

一句话概括就是getImageData()方法可以获取canvas画布上每一个像素点的颜色信息,于是,我们只要把上传的图片绘制在canvas上,然后检测有没有透明的像素点信息即可。

相关检测JS代码如下(完整代码参见demo页面):

// 图片绘制在画布上
context.drawImage(img, 0, 0);
// 获取图片像素信息
var imageData = context.getImageData(0, 0, width, height).data;
// 检测有没有透明数据
var isAlphaBackground = false;
for (var index = 3; index < imageData.length; index  = 4) {
    if (imageData[index] != 255) {
        isAlphaBackground = true;
        break;    
    }
}
// isAlphaBackground就是最后石头有透明或半透明背景色的结果

上面imageData就是我们获取的图片像素信息数组,是个一维数组,类型为Uint8ClampedArray,也就是数组中所有的值范围都是0~255,数组信息这样:

[R,G,B,A,R,G,B,A,R,G,B,A,R,G,B,A,R,G,B,A,R,G,B,A,...]

因此,我们只要判断数组中的A是否全部都是255就可以判断有没有透明信息了,一个for循环就搞定了。

如果图片尺寸很大
demo页面旨在演示,如果图片尺寸很大,按照demo页面的处理,可能会比较耗时间,我们可以在把canvas画布进行尺寸限制,也就是图片尺寸压缩,这个不会影响对是否有透明背景的判断,具体操作和代码可以参见这篇文章:“canvas实现图片前端JS压缩并上传”。

背景透明与否的实际处理

如果站在解决实际问题的角度,判断是否背景透明并不一定要得到。

  1. 对于不需要透明背景的处理,我们可以直接使用canvas补一个背景色,无论图片是不是透明的都不需要判断。操作如下:
  • (1) canvas绘制一个底色,通常是白色:
context.fillStyle = '#ffffff';
context.fillRect(0, 0, canvas.width, canvas.height);
  • (2). 绘制图片:
context.drawImage(img, 0, 0);
  • (3) 上传canvas图片(转换成base64后者blob数据都可以),canvas.toDataURL(file.type) 或者 canvas.toBlob(callback, file.type)
  1. 如果是需要PNG背景图片,则我们就可以先进行前端检测,然后:
    简单的处理是直接提示用户,“您上传图片为实色背景,最终效果可能不好看,确认继续?”
    更好的处理则是发现用户上传图片不是透明PNG图,则立即弹出个小窗口,在线背景去色就好了!于是自然引出了下一部分内容,如何实现图片的简易抠图(背景透明)效果?

二、实现一个简易的图片背景去色效果

图片去色网上很多在线的工具,有些还很厉害,例如clippingmagic,但,这东西是别人的,且我们不需要这么复杂交互,就算代码爬过来也没什么用,还是自己写写功能更实在。

好在,如果只是想实现一个不太复杂的图片背景色去色效果,还是挺容易的。

实现的关键依然是getImageData() 方法,去色,然后把相似颜色变成透明颜色就可以了。

您可以狠狠地点击这里:借助canvas实现图片背景色去色demo

例如,我们点击左边图片的白色区域,然后设置容差值是20,点击“执行去色”按钮,结果如下截图:

学新通

如果我们选择紫色,则效果是:

学新通

实现原理

关键2个步骤,一是取色,二是去色。

  1. 取色的实现

根据点击坐标,取当前点击位置像素点颜色值信息即可,代码如下:

canvas.addEventListener('click', function (event) {
    var rect = this.getBoundingClientRect();
    var x = event.clientX - rect.left;
    var y = event.clientY - rect.top;
    // rgbaPicker就是点击像素点的颜色信息
    rgbaPicker = context.getImageData(x, y, 1, 1).data;
});
  1. 去色的实现

去色则是替换getImageData()的一些像素点色值为透明即可,这个不难,难的是如何判别两种色值是相似的呢?

这里有个简易的颜色色值相似度计算算法:

similar = Math.sqrt((r2 - r1) * (r2 - r1) (g2 - g1) * (g2 - g1)
(b2 - b1) * (b2 - b1))

其中,r2,r1表示RGB中的红色red,g2,g1表示RGB中的绿色green,b2,b1表示RGB中的蓝色blue。

OK,有了相似度算法,下面就是简单的色值替换处理了:

// 像素点色值
var rgba = rgbaPicker;
// 容差大小
var tolerance = eleTolerance.value;
// 基于原始图片数据处理,
// 其中:
// imgData是左图像素信息,
// imgDataResult是右图处理后的像素信息
for (var index = 0; index < imgData.data.length; index  = 4) {
    var r = imgData.data[index];
    var g = imgData.data[index   1];
    var b = imgData.data[index   2];
    
    if (Math.sqrt(
        (r - rgba[0]) * (r - rgba[0])   
        (g - rgba[1]) * (g - rgba[1])   
        (b - rgba[2]) * (b - rgba[2])) <= tolerance
    ) {
        imgDataResult.data[index] = 0;
        imgDataResult.data[index   1] = 0;
        imgDataResult.data[index   2] = 0;
        imgDataResult.data[index   3] = 0;
    } else {
        imgDataResult.data[index] = r;
        imgDataResult.data[index   1] = g;
        imgDataResult.data[index   2] = b;
        imgDataResult.data[index   3] = imgData.data[index   3];
    }
}
// put数据
contextResult.putImageData(imgDataResult, 0, 0);

点睛代码就是最后的putImageData(),可以让画布使用新数据进行图像呈现,就可以看到去色后的效果了。

以上JS为片段截取,如果看不透彻,可以前往demo页面查看完整的源代码,未压缩,就在页面上,简洁明了无干扰,非常适合学习。

代码采用MIT协议,可以随意复制,商业亦可,但需要保留代码顶部的作者和原出处版权说明。所有代码都是自己键盘一个一个敲出来的,尊重辛勤劳动,尊重代码使用授权协议,共建和谐社会。如果发现都是删掉协议说明的拿来主义,我想我会考虑使用付费。

其他说明

这里的去色效果是最简单的去色效果了,其实标准的去色功能,还需要一个“连续”还是“不连续”的勾选功能。

但是,对于图标和logo这些去色需求,当前的整体去色应该足够,“连续”功能有兴趣的小伙伴可以自己尝试实现下,考验连续区域算法。

by zhangxinxu from http://www.zhangxinxu.com/wordpress/?p=7510
本文可全文转载,但需要保留原作者和出处,摘要引流则随意。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfifkag
系列文章
更多 icon
同类精品
更多 icon
继续加载