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 相关文章推荐
jquery插件jquery倒计时插件分享
Dec 27 Javascript
jquery设置text的值示例(设置文本框 DIV 表单值)
Jan 06 Javascript
js 判断浏览器使用的语言示例代码
Mar 22 Javascript
浅析Node.js的Stream模块中的Readable对象
Jul 29 Javascript
原生js实现addClass,removeClass,hasClass方法
Apr 27 Javascript
功能强大的Bootstrap效果展示(二)
Aug 03 Javascript
JavaScript中正则表达式判断匹配规则及常用方法
Aug 03 Javascript
angular 表单验证器验证的同时限制输入的实现
Apr 11 Javascript
vue实现弹幕功能
Oct 25 Javascript
Vue动态加载图片在跨域时无法显示的问题及解决方法
Mar 10 Javascript
JavaScript语句错误throw、try及catch实例解析
Aug 18 Javascript
Vue 简单实现前端权限控制的示例
Dec 25 Vue.js
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批量删除数据
2007/01/18 PHP
php+memcache实现的网站在线人数统计代码
2014/07/04 PHP
PHP针对JSON操作实例分析
2015/01/12 PHP
Joomla语言翻译类Jtext用法分析
2016/05/05 PHP
php7 错误处理机制修改实例分析
2020/05/25 PHP
JavaScript 学习点滴记录
2009/04/24 Javascript
Jsonp 跨域的原理以及Jquery的解决方案
2010/05/18 Javascript
基于jquery实现的鼠标滑过按钮改变背景图片
2011/07/15 Javascript
Javascript玩转继承(一)
2014/05/08 Javascript
AngularJS基础学习笔记之简单介绍
2015/05/10 Javascript
JS如何判断是否为ie浏览器的方法(包括IE10、IE11在内)
2015/12/13 Javascript
Bootstrap弹出框(modal)垂直居中的问题及解决方案详解
2016/06/12 Javascript
js 提取某()特殊字符串长度的实例
2017/12/06 Javascript
vue双向数据绑定知识点总结
2018/04/18 Javascript
深入理解JS的事件绑定、事件流模型
2018/05/13 Javascript
vue项目如何刷新当前页面的方法
2018/05/18 Javascript
vuex + axios 做登录验证 并且保存登录状态的实例
2018/09/16 Javascript
vue权限管理系统的实现代码
2019/01/17 Javascript
微信小程序开发的基本流程步骤
2019/01/31 Javascript
Flutter实现仿微信底部菜单栏功能
2019/09/18 Javascript
如何在Vue中使localStorage具有响应式(思想实验)
2020/07/14 Javascript
Python操作json数据的一个简单例子
2014/04/17 Python
python进阶教程之模块(module)介绍
2014/08/30 Python
深入理解NumPy简明教程---数组2
2016/12/17 Python
Python实现的根据IP地址计算子网掩码位数功能示例
2018/05/23 Python
Django框架视图函数设计示例
2019/07/29 Python
Python配置文件处理的方法教程
2019/08/29 Python
如何用PyPy让你的Python代码运行得更快
2020/12/02 Python
阿联酋彩妆品牌:OUD MILANO
2019/10/06 全球购物
全球才华横溢工匠的家居装饰、珠宝和礼物:NOVICA
2021/01/22 全球购物
大学生创业计划书的范文
2014/01/07 职场文书
2015高考寄语集锦
2015/02/27 职场文书
2015大一新生军训感言
2015/08/01 职场文书
公文写作:工伤事故分析报告怎么写?
2019/11/05 职场文书
Redis 哨兵集群的实现
2021/06/18 Redis
详解MySql中InnoDB存储引擎中的各种锁
2022/02/12 MySQL