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 相关文章推荐
CheckBox 如何实现全选?
Jun 23 Javascript
javascript for循环设法提高性能
Feb 24 Javascript
javascript event 事件解析
Jan 31 Javascript
js获取某元素的class里面的css属性值代码
Jan 16 Javascript
Jquery的Tabs内容轮换效果实现代码,几行搞定
Feb 12 Javascript
JS实现不使用图片仿Windows右键菜单效果代码
Oct 22 Javascript
基于JS实现的随机数字抽签实例
Dec 08 Javascript
vue实现密码显示隐藏切换功能
Feb 23 Javascript
快速解决angularJS中用post方法时后台拿不到值的问题
Aug 14 Javascript
使用VUE+iView+.Net Core上传图片的方法示例
Jan 04 Javascript
JavaScript中将值转换为字符串的五种方法总结
Jun 06 Javascript
Vue项目接入Paypal实现示例详解
Jun 04 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的图形函数中显示汉字
2006/10/09 PHP
PHP中=赋值操作符对不同数据类型的不同行为
2011/01/02 PHP
php 冒泡排序 交换排序法
2011/05/10 PHP
php实现利用phpexcel导出数据
2013/08/24 PHP
PHP如何获取当前主机、域名、网址、路径、端口等参数
2017/06/09 PHP
关于Laravel参数验证的一些疑与惑
2019/11/19 PHP
给网站上的广告“加速”显示的方法
2007/04/08 Javascript
javascript 在网页中的运用(asp.net)
2009/11/23 Javascript
JQuery 将元素显示在屏幕的中央的代码
2010/02/27 Javascript
理解Javascript_13_执行模型详解
2010/10/20 Javascript
jquery按回车提交数据的代码示例
2013/11/05 Javascript
js触发onchange事件的方法说明
2014/03/08 Javascript
javascript获取元素偏移量的方法有哪些
2014/06/24 Javascript
jQuery获取及设置表单input各种类型值的方法小结
2016/05/24 Javascript
jQuery中select与datalist制作下拉菜单时的区别浅析
2016/12/30 Javascript
ES6深入理解之“let”能替代”var“吗?
2017/06/28 Javascript
微信小程序 MinUI组件库系列之badge徽章组件示例
2018/08/20 Javascript
JS简单判断是否在微信浏览器打开的方法示例
2019/01/08 Javascript
通过说明与示例了解js五种设计模式
2019/06/17 Javascript
VuePress 中如何增加用户登录功能
2019/11/29 Javascript
Python中设置变量访问权限的方法
2015/04/27 Python
详解supervisor使用教程
2017/11/21 Python
python实现快速排序的示例(二分法思想)
2018/03/12 Python
django的登录注册系统的示例代码
2018/05/14 Python
Python编程快速上手——strip()函数的正则表达式实现方法分析
2020/02/29 Python
css3 clip实现圆环进度条的示例代码
2018/02/07 HTML / CSS
捷克母婴用品购物网站:Feedo.cz
2020/12/28 全球购物
女大学生毕业找工作的自我评价
2013/10/03 职场文书
给排水工程师岗位职责
2013/11/21 职场文书
师德建设实施方案
2014/03/21 职场文书
请假条的格式
2014/04/11 职场文书
企业管理标语
2014/06/10 职场文书
银行授权委托书样本
2014/10/13 职场文书
共产党员批评与自我批评
2014/10/15 职场文书
2015年世界粮食日演讲稿
2015/03/20 职场文书
带你彻底理解JavaScript中的原型对象
2021/04/14 Javascript