JavaScript纯色二维码变成彩色二维码


Posted in Javascript onJuly 23, 2020

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

前段时间公司业务上有这么一个需求,客户不喜欢后台生成的纯色二维码,纯蓝,纯紫,纯绿都不行,想要彩色二维码。然后这个任务都落到我头上了,因为是图片处理,那主要思路就是靠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时就会栈溢出,但是小于这个值就不会有问题,具体的原因还不清楚,猜测可能是判断那里有问题,导致死循环了。

全部代码在这里

// 分离颜色参数 返回一个数组
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;
  }
 }
})();

// 验证该位置的像素 不是背景色为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;
}

/*****上面为封装的函数*****/

/***参数***/
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 部分
var canvas = $("canvas")[0];
var ctx = canvas.getContext("2d");

var img = new Image();
img.src = path; //这里的path就是图片的地址
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);
}

总结

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

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

JavaScript纯色二维码变成彩色二维码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
在js文件中写el表达式取不到值的原因及解决方法
Dec 23 Javascript
js实现的GridView即表头固定表体有滚动条且可滚动
Feb 19 Javascript
使用mini-define实现前端代码的模块化管理
Dec 25 Javascript
使用jQuery管理选择结果
Jan 20 Javascript
javascript的BOM
May 03 Javascript
利用jQuery实现滑动开关按钮效果(附demo源码下载)
Feb 07 Javascript
Angular 4依赖注入学习教程之组件服务注入(二)
Jun 04 Javascript
详解Angular Reactive Form 表单验证
Jul 06 Javascript
使用async-validator编写Form组件的方法
Jan 10 Javascript
Vue中props的使用详解
Jun 15 Javascript
JavaScript实现学生在线做题计时器功能
Dec 05 Javascript
基于Vue-Cli 打包自动生成/抽离相关配置文件的实现方法
Dec 09 Javascript
xmlplus组件设计系列之按钮(2)
Apr 26 #Javascript
xmlplus组件设计系列之列表(4)
Apr 26 #Javascript
JS实现数组按升序及降序排列的方法
Apr 26 #Javascript
详解AngularJs ui-router 路由的简单介绍
Apr 26 #Javascript
JS ES6多行字符串与连接字符串的表示方法
Apr 26 #Javascript
node.js平台下利用cookie实现记住密码登陆(Express+Ejs+Mysql)
Apr 26 #Javascript
Vue.js实现文章评论和回复评论功能
May 30 #Javascript
You might like
PHP入门速成(2)
2006/10/09 PHP
PHP中Date获取时间不正确怎么办
2008/06/05 PHP
php实现在限定区域里自动调整字体大小的类实例
2015/04/02 PHP
Laravel手动分页实现方法详解
2016/10/09 PHP
php的扩展写法总结
2019/05/14 PHP
JavaScript去掉空格的方法集合
2010/12/28 Javascript
js前台判断开始时间是否小于结束时间
2012/02/23 Javascript
js实现页面跳转重定向的几种方式
2014/05/29 Javascript
jQuery标签替换函数replaceWith()的使用例子
2014/08/28 Javascript
jquery.mobile 共同布局遇到的问题小结
2015/02/10 Javascript
Backbone中View之间传值的学习心得
2016/08/09 Javascript
jQuery实现的右下角广告窗体跟随效果示例
2016/09/16 Javascript
js原生之焦点图转换加定时器实例
2016/12/12 Javascript
angularjs 的数据绑定实现原理
2018/07/02 Javascript
微信小程序学习笔记之跳转页面、传递参数获得数据操作图文详解
2019/03/28 Javascript
vue-cli脚手架的.babelrc文件用法说明
2020/09/11 Javascript
vue操作dom元素的3种方法示例
2020/09/20 Javascript
NestJs使用Mongoose对MongoDB操作的方法
2021/02/22 Javascript
[59:44]2018DOTA2亚洲邀请赛 3.31 小组赛 B组 paiN vs iG
2018/03/31 DOTA
Python实现的百度站长自动URL提交小工具
2014/06/27 Python
flask框架中勾子函数的使用详解
2018/08/01 Python
用python解压分析jar包实例
2020/01/16 Python
Python实现自动装机功能案例分析
2020/10/22 Python
佐卡伊官网:中国知名珠宝品牌
2017/02/05 全球购物
UNOde50美国官网:西班牙珠宝品牌
2020/08/15 全球购物
外贸采购员岗位职责
2014/03/08 职场文书
就业协议书范本
2014/10/08 职场文书
群众对十八届四中全会的期盼
2014/10/17 职场文书
2015年爱牙日活动总结
2015/02/05 职场文书
2016年党支部公开承诺书
2016/03/25 职场文书
小学毕业教师寄语
2019/06/21 职场文书
Go语言使用select{}阻塞main函数介绍
2021/04/25 Golang
详解分布式系统中如何用python实现Paxos
2021/05/18 Python
SQL Server中使用判断语句(IF ELSE/CASE WHEN )案例
2021/07/07 SQL Server
《进击的巨人》新联动CM 兵长强势出击兽巨人
2022/04/05 日漫
Python利用capstone实现反汇编
2022/04/06 Python