本文主要讨论如何将纯色二维码变成彩色。
前段时间,公司的业务出现了需求。客户不喜欢后台生成的纯色二维码。纯蓝色、纯紫色、纯绿色是不够的,但是想要彩色二维码。然后这个任务就落到我头上了,因为是图像处理,主要思路是靠画布,可以进行像素运算,所以我做了一些尝试,踩了一个小坑,具体记录如下。
先验知识
drawImage方法可以在画布上绘制图片,getImageData方法可以获取矩形区域内所有像素的信息。返回值的数据属性是一维数组,存储所有像素的信息。一个像素的信息将占用四个元素,分别代表r、g、b和透明度。一维数组中像素的顺序是从左到右,从上到下。最后,putImageData方法将改变后的像素信息数组抛出到画布上。
一些小坑
第一个坑是画布使用属性给出宽度和高度,而不是css
在第二个坑中,图像处理似乎有一个服务器环境,但在本地是不可能的。听说是基于一些安全考虑。最后,我以本地服务器为例解决了canvas报告的错误。
第三个坑,栈溢出,还没有找到原因,后面会详细解释
变色的想法
主要思路来自《啊哈!算法!》中深度优先搜索和广度优先搜索的章节。这一章的最后一部分“宝岛探索”,实现了不同区域依次编号,把编号当成染色,其实是一样的。
具体实现
其实所谓的彩色二维码并不是那种每个像素颜色随机的二维码。如果你仔细观察二维码,你会发现黑色的部分分布在白色,就像岛屿分布在海里一样。我们要做的就是把每个黑块分别染色。黑块的本质是一个黑像素。
如前所述,我们使用canvas是因为我们可以操作像素,所以我们的操作实际上是对像素进行染色。我们显然不想染底色,所以底色需要判断;如前所述,背景色似乎被海洋分割成黑色色块。也就是说,在读取一个像素进行染色后,我们不断判断其右侧像素的颜色。当背景色出现时,表示已经到达边界,可以停止向正确的方向染色。然而,每个像素实际上有四个相连的方向。当一个像素的右边是背景色时,我们也应该尝试其他方向的可能性。这就是深度优先搜索,通过递归,我们可以不断验证当前的情况。如果不是背景色,就染色,然后四个方向验证染色后的像素。
有几点值得一提。要判断是否是背景色,必须比较rgba的值,所以必须对颜色参数进行处理。另一个是像素信息的数组,每四个元素代表一个像素。因此,要想比较正确的像素信息,还必须对这部分进行处理。可能有点混乱。让我们看看代码
第一部分,画布
//canvas part var canvas=$(' canvas ')[0];var CTX=canvas . getcontext(' 2d ');var img=new Image();img.src=路径;//这里的路径是图片地址的第二部分,颜色处理
//分离颜色参数返回一个数组var colorgb=(function(){ var reg=/^#([0-9a-fa-f]{3}|[0-9a-fa-f]{6})$/;返回函数(str){ var Scorr=str。tolowercase();if(Scor reg。测试(Scor)){ if(Scor。length===4){ var Swootew=' # ';for(var I=1;i4;I=1){ Swootew=Scorer。切片(一,一1).concat(Scor。切片(I,I ^ 1));}鄙视者=鄙视者;} //处理六位的颜色值var SCorChange=[];for(var I=1;i7;I=2){ ScorChange。push(ParSeint('0x ' Scor。切片(I,I ^ 2));}返回scorolchange } else { var SCorchange=Scorr。替换(/(RGB ()|()/g ' ').拆分(',')。地图(函数(a){ return ParSeint(a));});返回scorolchange } } })();第三部分,给初始参数
为了避免多余的操作,我们用一个标记数组来记录判断过的位置
//参数var BG=color GB(' # fff ');//忽略的背景色可变宽度=220;可变高度=220;var imgD//预留给像素信息var colors=['#368BFF ',' #EF2767 ',' #F17900 ',' #399690 ',' #5aa6f7 ',' #fd417e ',' #ffc000 ',' # 59 b6a 6 '];//染色数组//随机彩色数组的一个序号var RanNum=(function(){ var len=colors。长度;return function(){ return math。地板(数学。random()* len);}})();//标记数组var book=[];for(var I=0;一、身高;I){ book[I]=[];for(var j=0;j宽度;j){ book[I][j]=0;} }第四部分,获取像素信息,对每个像素点进行遍历处理,最后扔回帆布
如果标记过,那就跳过,如果没标记过,那就随机一个颜色,深度优先搜索并染色
img。onload=function(){ CTX。绘制图像(img,0,0,宽度,高度);imgD=ctx.getImageData(0,0,宽度,高度);for(var I=0;一、身高;I){ for(var j=0;j宽度;j){ if(book[I][j]==0 CheckColor(I,j,width,bg)) { //没标记过且是非背景色书[I][j]=1;var color=color GB(colors[RanNum()]);dfs(i,j,颜色);//深度优先搜索} } } ctx.putImageData(imgD,0,0);}//验证该位置的像素不是背景色为truefunction checkColor(i,j,width,bg) { var x=calc(width,I,j);if (imgD.data[x]!=bg[0] imgD.data[x 1]!=BG[1]IMgd。数据[x ^ 2]!=bg[2]) {返回true} else { return false}}//改变颜色值函数changeColor(i,j,colorArr) { var x=calc(width,I,j);imgd。data[x]=color[0];imgd。数据[x 1]=ColorArr[1];imgd。数据[x ^ 2]=colorar[2];}//返回对应像素点的序号函数calc(width,I,j){ if(j 0){ j=0;}返回4 * (i *宽度j);}关键代码
我们通过一个方向数组,来简化一下操作,我们约定好,尝试的方向为顺时针,从右边开始。
//方向数组var next=[ [0,1],//右[1, 0], //下[0, -1], //左[-1, 0] //上];//深度优先搜索函数dfs(x,y,color) { changeColor(x,y,color);for(var k=0;k=3;k ) { //下一个坐标var tx=x next[k][0];var ty=y next[k][1];//判断越界if (tx 0 || tx=高度|| ty 0 || ty=宽度){ continue } if(book[tx][ty]==0 CheckColor(tx,ty,width,bg)) { //判断位置book[tx][ty]=1;dfs(tx、ty、color);} }返回;}我遇到的最后一个坑就是当长宽大于220时就会栈溢出,但是小于这个值就不会有问题,具体的原因还不清楚,猜测可能是判断那里有问题,导致死循环了。
全部代码在这里
//分离颜色参数返回一个数组var colorgb=(function(){ var reg=/^#([0-9a-fa-f]{3}|[0-9a-fa-f]{6})$/;返回函数(str){ var Scorr=str。tolowercase();if(Scor reg。测试(Scor)){ if(Scor。length===4){ var Swootew=' # ';for(var I=1;i4;I=1){ Swootew=Scorer。切片(一,一1).concat(Scor。切片(I,I ^ 1));}鄙视者=鄙视者;} //处理六位的颜色值var SCorChange=[];for(var I=1;i7;I=2){ ScorChange。push(ParSeint('0x ' Scor。切片(I,I ^ 2));}返回scorolchange } else { var SCorchange=Scorr。替换(/(RGB ()|()/g ' ').拆分(',')。地图(函数(a){ return ParSeint(a));});返回scorolchange } } })();//验证该位置的像素不是背景色为truefunction checkColor(i,j,width,bg) { var x=calc(width,I,j);if (imgD.data[x]!=bg[0] imgD.data[x 1]!=BG[1]IMgd。数据[x ^ 2]!=bg[2]) {返回true} else { return false}}//改变颜色值函数changeColor(i,j,colorArr) { var x=calc(width,I,j);imgd。data[x]=color[0];imgd。数据[x 1]=ColorArr[1];imgd。数据[x ^ 2]=colorar[2];}//返回对应像素点的序号函数calc(width,I,j){ if(j 0){ j=0;}返回4 * (i *宽度j);}//方向数组var next=[ [0,1],//右[1, 0], //下[0, -1], //左[-1, 0] //上];//深度优先搜索函数dfs(x,y,color) { changeColor(x,y,color);for(var k=0;k=3;k ) { //下一个坐标var tx=x next[k][0];var ty=y next[k][1];//判断越界if (tx 0 || tx=高度|| ty 0 || ty=宽度){ continue } if(book[tx][ty]==0 CheckColor(tx,ty,width,bg)) { //判断位置book[tx][ty]=1;dfs(tx、ty、color);} }返回;}/*****上面为封装的函数*****//***参数* * */var BG=color GB(' # fff ');//忽略的背景色可变宽度=220;可变高度=220;var imgD//预留给像素信息数组var colors=['#368BFF ',' #EF2767 ',' #F17900 ',' #399690 ',' #5aa6f7 ',' #fd417e ',' #ffc000 ',' # 59 b6a 6 '];//染色数组//随机彩色数组的一个序号var RanNum=(function(){ var len=colors。长度;return function(){ return math。地板(数学。random()* len);}})();//标记数组var book=[];for(var I=0;一、身高;I){ book[I]=[];for(var j=0;j宽度;j){ book[I][j]=0;} }//画布部分var canvas=$(“canvas”)[0];var CTX=画布。get context(' 2d ');var img=new Image();img.src=路径;//这里的小路就是图片的地址img。onload=function(){ CTX。绘制图像(img,0,0,宽度,高度);imgD=ctx.getImageData(0,0,宽度,高度);for(var I=0;一、身高;I){ for(var j=0;j宽度;j){ if(book[I][j]==0 CheckColor(I,j,width,bg)) { //没标记过且是非背景色书[I][j]=1;var color=color GB(colors[RanNum()]);dfs(i,j,颜色);//深度优先搜索} } } ctx.putImageData(imgD,0,0);}总结
虽然看起来有点长,其实大部分函数都在处理像素点的信息。实现起来,主要就是得对深度优先搜索有所了解,每个像素点都进行深度优先搜索,染过色的自然被标记过,所以当一个新的没标记过的像素点出现时,自然意味着新的颜色块。细节方面,就是注意一下imgD.data和像素点序号之间的对应关系,别的也就还好了。不过注意一点就是,因为像素点很小,所以肉眼觉得不相连的色块也有可能是连在一起的,会染成一样的颜色。
忘了放图了,这里放几张,拿即时通信软件截的,把外面的边框不小心也截了,嘛,凑活看看吧
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。