二维码改 {MOD}方案

2019-04-15 15:15发布

本文章主要讨论的是如何将一个纯 {MOD}二维码变成彩 {MOD}的。 前段时间公司业务上有这么一个需求,客户不喜欢后台生成的纯 {MOD}二维码,纯蓝,纯紫,纯绿都不行,想要彩 {MOD}二维码。然后这个任务都落到我头上了,因为是图片处理,那主要思路就是靠canvas,canvas可以进行像素操作,所以我进行了一些尝试,也踩了一点小坑,具体记录如下。

 

前置知识

drawImage方法可以把图片画到canvas上,getImageData方法可以获得一个矩形区域所有像素点的信息,返回值的data属性是一个一维数组,储存了所有像素点的信息,一个像素点的信息会占四个元素,分别代表r,g,b和透明度。而像素点在一维数组中的顺序是从左到右,从上到下。最后就是putImageData方法,把更改过的像素信息数组重新扔回画布上。

一些小坑

第一个坑就是canvas用属性去给宽高,别用css;   第二个坑,做图片处理好像得服务器环境,本地是不行的,听说是基于什么安全考虑,最后我是通过搭本地服务器解决了canvas的报错。 第三个坑,栈溢出,这个目前还没找到原因,后面会详细讲

变 {MOD}的思路

主要思路来自于《啊哈!算法!》里面深度优先搜索和广度优先搜索的章节,该章节的最后一部分的“宝岛探险”实现了给不同的区域依次编号,把编号看成染 {MOD},其实是一样的。

具体实现

其实所谓的彩 {MOD}二维码,不是那种每个像素点颜 {MOD}随机的二维码。仔细观察二维码就会发现,黑 {MOD}的部分是一块一块的,他们分布在白 {MOD}当中,就好像岛屿分布在海里,我们要做的就是把每个黑 {MOD}块单独染 {MOD}。黑 {MOD}块的实质就是一个一个黑 {MOD}的像素点。 前面也提到,我们使用canvas是因为可以进行像素操作,所以我们的操作其实是给像素点染 {MOD},我们显然不希望给背景 {MOD}染 {MOD},所以背景 {MOD}需要进行一个判断;前面也提到,背景 {MOD}好像海洋分割了黑 {MOD}的颜 {MOD}块,那也就是说我们读一个像素点进行染 {MOD}之后,不停的判断它右侧的像素点颜 {MOD},当出现背景 {MOD}的时候就说明到达了边界,可以停止右方向的染 {MOD},但是每个像素点其实有四个相连接的方向,当一个像素点右边就是背景 {MOD},我们应该也去尝试别的方向的可能性,这个就是深度优先搜索,通过递归,不断的验证当前像素点的下一个位置的颜 {MOD},是背景 {MOD},那就回来,尝试别的方向;不是背景 {MOD},那就染 {MOD},然后对染 {MOD}之后的这个像素点进行四个方向的验证。
有几点提一下,判断是不是背景 {MOD},肯定得比对rgba的值,所以颜 {MOD}参数得做处理,另一个就是像素点信息的数组,每四个元素代表一个像素,所以想要比对正确的像素信息,这部分也要处理。
可能说的有点乱,我们看一下代码 第一部分,canvas 复制代码 // canvas 部分 var canvas = $("canvas")[0]; var ctx = canvas.getContext("2d"); var img = new Image(); img.src = path; //这里的path就是图片的地址 复制代码   第二部分,颜 {MOD}的处理 复制代码 // 分离颜 {MOD}参数 返回一个数组 var colorRgb = (function() { var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/; return function(str) { var sColor = str.toLowerCase(); if (sColor && reg.test(sColor)) { if (sColor.length === 4) { var sColorNew = "#"; for (var i = 1; i < 4; i += 1) { sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1)); } sColor = sColorNew; } //处理六位的颜 {MOD}值 var sColorChange = []; for (var i = 1; i < 7; i += 2) { sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2))); } return sColorChange; } else { var sColorChange = sColor.replace(/(rgb()|())/g, "").split(",").map(function(a) { return parseInt(a); }); return sColorChange; } } })(); 复制代码   第三部分,给初始参数 为了避免多余的操作,我们用一个标记数组来记录判断过的位置 复制代码 // 参数 var bg = colorRgb("#fff"); //忽略的背景 {MOD} var width = 220; var height = 220; var imgD; //预留给 像素信息 var colors = ["#368BFF", "#EF2767", "#F17900", "#399690", "#5aa6f7", "#fd417e", "#ffc000", "#59b6a6"]; //染 {MOD}数组 // 随机colors数组的一个序号 var ranNum = (function() { var len = colors.length; return function() { return Math.floor(Math.random() * len); } })(); // 标记数组 var book = [];
for (var i = 0; i < height; i++) {
  book[i] = [];
  for (var j = 0; j < width; j++) {
    book[i][j] = 0;
  }
} 复制代码   第四部分,获取像素信息,对每个像素点进行遍历处理,最后扔回canvas 如果标记过,那就跳过,如果没标记过,那就随机一个颜 {MOD},深度优先搜索并染 {MOD} 复制代码 img.onload = function() { ctx.drawImage(img, 0, 0, width, height); imgD = ctx.getImageData(0, 0, width, height); for (var i = 0; i < height; i++) { for (var j = 0; j < width; j++) { if (book[i][j] == 0 && checkColor(i, j, width, bg)) { //没标记过 且是非背景 {MOD} book[i][j] = 1; var color = colorRgb(colors[ranNum()]); dfs(i, j, color); //深度优先搜索 } } } ctx.putImageData(imgD, 0, 0); } // 验证该位置的像素 不是背景 {MOD}为true function checkColor(i, j, width, bg) { var x = calc(width, i, j); if (imgD.data[x] != bg[0] && imgD.data[x + 1] != bg[1] && imgD.data[x + 2] != bg[2]) { return true; } else { return false; } } // 改变颜 {MOD}值 function changeColor(i, j, colorArr) { var x = calc(width, i, j); imgD.data[x] = colorArr[0]; imgD.data[x + 1] = colorArr[1]; imgD.data[x + 2] = colorArr[2]; } // 返回对应像素点的序号 function calc(width, i, j) { if (j < 0) { j = 0; } return 4 * (i * width + j); } 复制代码   关键代码 我们通过一个方向数组,来简化一下操作,我们约定好,尝试的方向为顺时针,从右边开始。 复制代码 // 方向数组 var next = [ [0, 1], //右 [1, 0], //下 [0, -1], // 左 [-1, 0] //上 ]; // 深度优先搜索 function 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 >= height || ty < 0 || ty >= width) { continue; } if (book[tx][ty] == 0 && checkColor(tx, ty, width, bg)) { // 判断位置 book[tx][ty] = 1; dfs(tx, ty, color); } } return; } 复制代码   我遇到的最后一个坑就是当长宽大于220时就会栈溢出,但是小于这个值就不会有问题,具体的原因还不清楚,猜测可能是判断那里有问题,导致死循环了。 全部代码在这里 复制代码 1 // 分离颜 {MOD}参数 返回一个数组 2 var colorRgb = (function() { 3 var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/; 4 5 return function(str) { 6 var sColor = str.toLowerCase(); 7 if (sColor && reg.test(sColor)) { 8 if (sColor.length === 4) { 9 var sColorNew = "#"; 10 for (var i = 1; i < 4; i += 1) { 11 sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1)); 12 } 13 sColor = sColorNew; 14 } 15 //处理六位的颜 {MOD}值 16 var sColorChange = []; 17 for (var i = 1; i < 7; i += 2) { 18 sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2))); 19 } 20 return sColorChange; 21 } else { 22 var sColorChange = sColor.replace(/(rgb()|())/g, "").split(",").map(function(a) { 23 return parseInt(a); 24 }); 25 return sColorChange; 26 } 27 } 28 })(); 29 30 // 验证该位置的像素 不是背景 {MOD}为true 31 function checkColor(i, j, width, bg) { 32 var x = calc(width, i, j); 33 34 if (imgD.data[x] != bg[0] && imgD.data[x + 1] != bg[1] && imgD.data[x + 2] != bg[2]) { 35 return true; 36 } else { 37 return false; 38 } 39 } 40 41 // 改变颜 {MOD}值 42 function changeColor(i, j, colorArr) { 43 var x = calc(width, i, j); 44 imgD.data[x] = colorArr[0]; 45 imgD.data[x + 1] = colorArr[1]; 46 imgD.data[x + 2] = colorArr[2]; 47 } 48 49 50 // 返回对应像素点的序号 51 function calc(width, i, j) { 52 if (j < 0) { 53 j = 0; 54 } 55 return 4 * (i * width + j); 56 } 57 58 // 方向数组 59 var next = [ 60 [0, 1], //右 61 [1, 0], //下 62 [0, -1], // 左 63 [-1, 0] //上 64 ]; 65 66 // 深度优先搜索 67 function dfs(x, y, color) { 68 changeColor(x, y, color); 69 for (var k = 0; k <= 3; k++) { 70 // 下一个坐标 71 var tx = x + next[k][0]; 72 var ty = y + next[k][1]; 73 74 //判断越界 75 if (tx < 0 || tx >= height || ty < 0 || ty >= width) { 76 continue; 77 } 78 79 80 if (book[tx][ty] == 0 && checkColor(tx, ty, width, bg)) { 81 // 判断位置 82 book[tx][ty] = 1; 83 dfs(tx, ty, color); 84 } 85 86 } 87 return; 88 } 89 90 /*****上面为封装的函数*****/ 91 92 /***参数***/ 93 var bg = colorRgb("#fff"); //忽略的背景 {MOD} 94 var width = 220; 95 var height = 220; 96 var imgD; //预留给 像素信息数组 97 var colors = ["#368BFF", "#EF2767", "#F17900", "#399690", "#5aa6f7", "#fd417e", "#ffc000", "#59b6a6"]; //染 {MOD}数组 98 // 随机colors数组的一个序号 99 var ranNum = (function() { 100 var len = colors.length; 101 return function() { 102 return Math.floor(Math.random() * len); 103 } 104 })(); 105 106 // 标记数组 107 var book = []; 108 for (var i = 0; i < height; i++) { 109   book[i] = []; 110   for (var j = 0; j < width; j++) { 111     book[i][j] = 0; 112   } 113 } 114 115 116 // canvas 部分 117 var canvas = $("canvas")[0]; 118 var ctx = canvas.getContext("2d"); 119 120 var img = new Image(); 121 img.src = path; //这里的path就是图片的地址 122 img.onload = function() { 123 ctx.drawImage(img, 0, 0, width, height); 124 imgD = ctx.getImageData(0, 0, width, height); 125 126 for (var i = 0; i < height; i++) { 127 for (var j = 0; j < width; j++) { 128 if (book[i][j] == 0 && checkColor(i, j, width, bg)) { //没标记过 且是非背景 {MOD} 129 book[i][j] = 1; 130 var color = colorRgb(colors[ranNum()]); 131 dfs(i, j, color); //深度优先搜索 132 } 133 } 134 } 135 136 ctx.putImageData(imgD, 0, 0); 137 } 复制代码

 

总结

虽然看起来有点长,其实大部分函数都在处理像素点的信息。实现起来,主要就是得对深度优先搜索有所了解,每个像素点都进行深度优先搜索,染过 {MOD}的自然被标记过,所以当一个新的没标记过的像素点出现时,自然意味着新的颜 {MOD}块。细节方面,就是注意一下imgD.data和像素点序号之间的对应关系,别的也就还好了。不过注意一点就是,因为像素点很小,所以肉眼觉得不相连的 {MOD}块也有可能是连在一起的,会染成一样的颜 {MOD}。 忘了放图了,这里放几张,拿qq截的,把外面的边框不小心也截了,嘛,凑活看看吧