二维码改色方案

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

前段时间公司业务上有这么一个需求,客户不喜欢后台生成的纯色二维码,纯蓝,纯紫,纯绿都不行,想要彩色二维码。然后这个任务都落到我头上了,因为是图片处理,那主要思路就是靠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截的,把外面的边框不小心也截了,嘛,凑活看看吧

时间: 2024-08-07 03:55:36

二维码改色方案的相关文章

完美解决Android使用Zxing扫描二维码改成竖屏后,后面的预览画面出现了拉伸,扭曲的情况

完美解决解决Android使用Zxing扫描二维码改成竖屏后,后面的预览画面出现了拉伸,扭曲的情况 第一步:找到com.zxing.camera包下的CameraConfigurationManager.java文件中的void initFromCameraParameters(Camera camera)方法 第二步:在 Log.d(TAG, "Screen resolution: " + screenResolution);后加上如下的代码 Point screenResoluti

饮料瓶盖二维码红包是怎样实现的?

瓶盖二维码红包开发,东鹏特饮瓶盖二维码,燕京啤酒扫瓶盖二维码红包营销方案,饮料瓶盖二维码红包 联系江经理:134-2106-8261. 饮料瓶盖二维码红包活动已经被广泛运用在各品牌饮料商.消费者购买打开瓶盖扫码就可以领取红包.如果你稍微留意一下,现在市场上几乎找不到不做开盖有奖的饮料产品.康师傅.统一.娃哈哈.葡萄适.和其正.霸王凉茶,当然,还有开盖有奖的"鼻祖"啤酒品牌--珠江.燕京等.正如一位业内人士所说,开盖有奖已经成为饮料销售的一个"标准". 案例一:三得利

方案优化:网站实现扫描二维码关注微信公众号,自动登陆网站并获取其信息

上一篇  <网站实现扫描二维码关注微信公众号,自动登陆网站并获取其信息> 中已经实现用户扫码登陆网站并获取其信息 但是上一篇方案中存在一个问题,也就是文章末尾指出的可以优化的地方(可以点击这个链接去看一下上篇文章) 首先回顾一下上一篇的思路: 1,微信的系统,提供生成带参数的二维码的接口,这个参数就是唯一值(场景值)  2,网站调用微信系统,获取生成的二维码图片  3,用户扫码会直接调用微信服务器,将用户访问微信服务器的信息记录到redis,key就是唯一值(场景值)  4,网站端做轮训去查询

苹果试玩无限刷怎么软该修改udid硬改 二维码跳证书解决非首次 一键换新机

欢迎交流联系方式986538860 2019年最稳定的投资项目一次投资10台手机,苹果6就行市场的行情650左右,如果有能力的可以准备多台手机 工作时间自由 项目长期稳定,是每一天都能到账的,每天收入多少的在于你的手机 我能给你的是每天每台手机给你创造50到70的,十台手机一天的收入就是500+,现在我们这边的技术有软改修改udid抓包技术,硬改udid技术 二维码跳证书技术,自己做二维码技术 不删除微信和试玩app平台抹机技术, 手机号 微信号 支付宝号 实名认证 等都有渠道不需要自己注册,试

二维码快速扫码优化方案介绍(一)--怎么在光线不足时,手机自动进行补光。

二维码扫码已经是一个很通用的技术了,也有很多的开源项目可以实现,比如Zxing项目.https://github.com/zxing 这里重点不是介绍Zxing中是怎么样来实现二维码扫码的,而是来介绍一下,微信上的快速扫码技术是如何来做到优化体验的. 要实现在光线不足时,手机自动进行补光,其实很简单,每个人都知道,手机上有很多的传感器,其中有一个很重要的传感器就是光线传感器. 这个大家平时在拍照的时候,都用到过.那么如何能把这个应用到二维码的扫码体验上来呢. 我们可以利用手机的光线传感器进行外部

Google zxing实现二维码扫描完美解决方案

最近因项目需求,需要在App中集成二维码扫描的功能.网上找了很多资料,最后决定使用Google的zxing来实现.实现的过程遇到了很多的坑,也是因为这些坑在网上没有具体的解决方案,今天就把我的实现过程分享给大家. 我会分为两步来和大家分享: (1)项目中如何集成zxing (2)如何修改取景框的样式 (3)总结填坑 1.项目中集成zxing 在项目中集成zxing,网上有很多的教程也说的比较详细了,zxing中的内容很多,涵盖了很多的扫码功能(不仅仅局限于扫描二维码...).步骤很简单,只需要我

二维码详解(QR Code)

作者:王子旭链接:https://zhuanlan.zhihu.com/p/21463650来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 2016.7.5 更新:长文多图代码预警,电脑食用效果更佳. 完整版代码已上传 GitHub,后续一些有的没的的代码更新也都在GitHub上(https://github.com/LaytonW/qrcode) 给结尾的几个被自动识别的QR码做了防自动识别..顺便也检测一下我们这不怎么高的容错率(7%).要是再被知乎自动识别了

二维码识别之Android完整编译Zbar

版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com 大概刚做Android开发的时候就做过二维码扫描,那时候懂的东西少,就搜出来了ZXing和Zbar两个库.ZXing是纯Java代码实现的,适用于Android平台:Zbar是C实现的,可以供很多语言和平台使用,比如Java.iOS平台.Android平台,Python等等.很明显Zbar的识别率和速度都是明显快于ZXing的,但是无奈那时候不会编译Zbar,只好下载了ZXing,但是由于当时技术能

ZBar 是款桌面电脑用条形码/二维码扫描工具

windows平台python 2.7环境编译安装zbar 最近一个项目需要识别二维码,找来找去找到了zbar和zxing,中间越过无数坑,总算基本上弄明白,分享出来给大家. 一.zbar官方介绍 ZBar 是款桌面电脑用条形码/二维码扫描工具,支持摄像头及图片扫描,支持多平台,例如 iPhone,Andriod 手机,同时 ZBar封装了二维码扫描的 API 开发包. ZBar 目前条码类型有:EAN-13/UPC-A, UPC-E, EAN-8, Code 128, Code 39, Int