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 相关文章推荐
iframe里的页面禁止右键事件的方法
Jun 10 Javascript
javascript学习笔记(六)数据类型和JSON格式
Oct 08 Javascript
node.js中的buffer.slice方法使用说明
Dec 10 Javascript
Bootstrap每天必学之面板
Nov 30 Javascript
JavaScript实现移动端滑动选择日期功能
Jun 21 Javascript
终于实现了!精彩的jquery弹幕效果
Jul 18 Javascript
深入浅析JS Function()构造函数
Aug 22 Javascript
详解react-router4 异步加载路由两种方法
Sep 12 Javascript
理解 JavaScript EventEmitter
Mar 29 Javascript
微信小程序bindtap事件与冒泡阻止详解
Aug 08 Javascript
vue element 生成无线级左侧菜单的实现代码
Aug 21 Javascript
基于Vue el-autocomplete 实现类似百度搜索框功能
Oct 25 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加水印的代码(支持半透明透明打水印,支持png透明背景)
2013/01/17 PHP
对PHP PDO的一些认识小结
2015/01/23 PHP
Codeigniter通过SimpleXML将xml转换成对象的方法
2015/03/19 PHP
PHP中常用的数组操作方法笔记整理
2016/05/16 PHP
PHP机器学习库php-ml的简单测试和使用方法
2017/07/14 PHP
CodeIgniter整合Smarty的方法详解
2017/08/25 PHP
jQuery操作CheckBox的方法介绍(选中,取消,取值)
2014/02/04 Javascript
js控制分页打印、打印分页示例
2014/02/08 Javascript
jquery常用操作小结
2014/07/21 Javascript
js中iframe调用父页面的方法
2014/10/30 Javascript
jQuery设置和移除文本框默认值的方法
2015/03/09 Javascript
手机端转盘抽奖代码分享
2015/09/10 Javascript
jQuery Validate验证框架经典大全
2015/09/23 Javascript
jQuery仅用3行代码实现的显示与隐藏功能完整实例
2015/10/08 Javascript
简单学习JavaScript中的for语句循环结构
2015/11/10 Javascript
实现点击下箭头变上箭头来回切换的两种方法【推荐】
2016/12/14 Javascript
轻松学习Javascript闭包
2017/03/01 Javascript
学习使用Bootstrap栅格系统
2017/05/11 Javascript
vue 实现全选全不选的示例代码
2018/03/29 Javascript
Bootstrap导航菜单点击后无法自动添加active的处理方法
2018/08/10 Javascript
Vue 中的受控与非受控组件的实现
2018/12/17 Javascript
详解Vue webapp项目通过HBulider打包原生APP(vue+webpack+HBulider)
2019/02/02 Javascript
小程序异步问题之多个网络请求依次执行并依次收集请求结果
2019/05/05 Javascript
vue element-ui之怎么封装一个自己的组件的详解
2019/05/20 Javascript
layui 数据表格 根据值(1=业务,2=机构)显示中文名称示例
2019/10/26 Javascript
在Python中用split()方法分割字符串的使用介绍
2015/05/20 Python
Python中getattr函数和hasattr函数作用详解
2016/06/14 Python
详解Python各大聊天系统的屏蔽脏话功能原理
2016/12/01 Python
Python简单实现自动删除目录下空文件夹的方法
2017/08/29 Python
一款利用html5和css3动画排列人物头像的实例演示
2014/12/05 HTML / CSS
德国骆驼商店:ActiveFashionWorld
2017/11/18 全球购物
基层工作经验证明样本
2014/11/16 职场文书
党小组考察意见
2015/06/02 职场文书
公司欠款证明
2015/06/24 职场文书
学习经验交流会总结
2015/11/02 职场文书