• 二维码改色方案


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

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

     

    前置知识

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

    一些小坑

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

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

    第三个坑,栈溢出,这个目前还没找到原因,后面会详细讲

    变色的思路

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

    具体实现

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

    前面也提到,我们使用canvas是因为可以进行像素操作,所以我们的操作其实是给像素点染色,我们显然不希望给背景色染色,所以背景色需要进行一个判断;前面也提到,背景色好像海洋分割了黑色的颜色块,那也就是说我们读一个像素点进行染色之后,不停的判断它右侧的像素点颜色,当出现背景色的时候就说明到达了边界,可以停止右方向的染色,但是每个像素点其实有四个相连接的方向,当一个像素点右边就是背景色,我们应该也去尝试别的方向的可能性,这个就是深度优先搜索,通过递归,不断的验证当前像素点的下一个位置的颜色,是背景色,那就回来,尝试别的方向;不是背景色,那就染色,然后对染色之后的这个像素点进行四个方向的验证。


    有几点提一下,判断是不是背景色,肯定得比对rgba的值,所以颜色参数得做处理,另一个就是像素点信息的数组,每四个元素代表一个像素,所以想要比对正确的像素信息,这部分也要处理。
    可能说的有点乱,我们看一下代码

    第一部分,canvas

    // 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截的,把外面的边框不小心也截了,嘛,凑活看看吧

     



  • 相关阅读:
    企业微信api接口调用-触发推送企业微信微信好友
    企业微信api接口调用-触发推送企业微信微信好友
    企业微信api接口调用-企业微信好友收发消息
    抖音api接口调用-同步抖音推荐的好友
    <转载>医学图像存储与传输系统(PACS)
    <转载>RESTful API 最佳实践
    <转载>DICOMweb——将DICOM影像接入互联网
    API设计规范---RESTful架构详解
    开源医学影像平台---Cornerstonejs开发指南
    开源医学影像平台---Cornerstonejs学习笔记<5>
  • 原文地址:https://www.cnblogs.com/grey-zhou/p/6767727.html
一二三 - 开发者的网上家园