二维码改色方案

本文章主要讨论的是如何将一个纯色二维码变成彩色的。css

前段时间公司业务上有这么一个需求,客户不喜欢后台生成的纯色二维码,纯蓝,纯紫,纯绿都不行,想要彩色二维码。而后这个任务都落到我头上了,由于是图片处理,那主要思路就是靠canvas,canvas能够进行像素操做,因此我进行了一些尝试,也踩了一点小坑,具体记录以下。算法

 

前置知识

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

一些小坑

第一个坑就是canvas用属性去给宽高,别用css;  canvas

第二个坑,作图片处理好像得服务器环境,本地是不行的,据说是基于什么安全考虑,最后我是经过搭本地服务器解决了canvas的报错。数组

第三个坑,栈溢出,这个目前还没找到缘由,后面会详细讲安全

变色的思路

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

具体实现

其实所谓的彩色二维码,不是那种每一个像素点颜色随机的二维码。仔细观察二维码就会发现,黑色的部分是一块一块的,他们分布在白色当中,就好像岛屿分布在海里,咱们要作的就是把每一个黑色块单独染色。黑色块的实质就是一个一个相连的黑色像素点。dom

前面也提到,咱们使用canvas是由于能够进行像素操做,因此咱们的操做实际上是给像素点染色,咱们显然不但愿给背景色染色,因此背景色须要进行一个判断;前面也提到,背景色好像海洋分割了黑色的颜色块,那也就是说咱们读一个像素点进行染色以后,不停的判断它右侧的像素点颜色,当出现背景色的时候就说明到达了边界,能够中止右方向的染色,可是每一个像素点其实有四个相链接的方向,当一个像素点右边就是背景色,咱们应该也去尝试别的方向的可能性,这个就是深度优先搜索,经过递归,不断的验证当前像素点的下一个位置的颜色,是背景色,那就回来,尝试别的方向;不是背景色,那就染色,而后对染色以后的这个像素点进行四个方向的验证。函数


有几点提一下,判断是否是背景色,确定得比对rgba的值,因此颜色参数得作处理,另外一个就是像素点信息的数组,每四个元素表明一个像素,因此想要比对正确的像素信息,这部分也要处理。
可能说的有点乱,咱们看一下代码
spa

第一部分,canvascode

// canvas 部分
var canvas = $("canvas")[0]; var ctx = canvas.getContext("2d"); var img = new Image(); img.src = path; //这里的path就是图片的地址

 

第二部分,颜色的处理

// 分离颜色参数 返回一个数组
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; } //处理六位的颜色值 
            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"); //忽略的背景色
var width = 220; var height = 220; var imgD;  //预留给 像素信息
var colors = ["#368BFF", "#EF2767", "#F17900", "#399690", "#5aa6f7", "#fd417e", "#ffc000", "#59b6a6"];   //染色数组 // 随机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

若是标记过,那就跳过,若是没标记过,那就随机一个颜色,深度优先搜索并染色

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)) { //没标记过 且是非背景色
                book[i][j] = 1; var color = colorRgb(colors[ranNum()]); dfs(i, j, color); //深度优先搜索
 } } } ctx.putImageData(imgD, 0, 0); } // 验证该位置的像素 不是背景色为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; } } // 改变颜色值
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 // 分离颜色参数 返回一个数组
 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             //处理六位的颜色值 
 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 // 验证该位置的像素 不是背景色为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 // 改变颜色值
 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"); //忽略的背景色
 94 var width = 220;  95 var height = 220;  96 var imgD;  //预留给 像素信息数组
 97 var colors = ["#368BFF", "#EF2767", "#F17900", "#399690", "#5aa6f7", "#fd417e", "#ffc000", "#59b6a6"];   //染色数组
 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)) { //没标记过 且是非背景色
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 }

 

总结

虽然看起来有点长,其实大部分函数都在处理像素点的信息。实现起来,主要就是得对深度优先搜索有所了解,每一个像素点都进行深度优先搜索,染过色的天然被标记过,因此当一个新的没标记过的像素点出现时,天然意味着新的颜色块。细节方面,就是注意一下imgD.data和像素点序号之间的对应关系,别的也就还好了。不过注意一点就是,由于像素点很小,因此肉眼以为不相连的色块也有多是连在一块儿的,会染成同样的颜色。

忘了放图了,这里放几张,拿qq截的,把外面的边框不当心也截了,嘛,凑活看看吧

相关文章
相关标签/搜索