详解使用JS如何制作简单的ASCII图与单极图


Posted in Javascript onMarch 31, 2017

ASCII图

在终端执行各种命令的时候经常会看到一些终端里显示出来的"图片",远看仿佛一张图,近看则是一个个的 ASCII码,它们 大致长这样子

详解使用JS如何制作简单的ASCII图与单极图

而今天我们要做的则是用JS把一张给定的图片转换成这种用ASCII字符组成的“ASCII图” 先看看最终效果,假设我们给定的图片是这样子的,

详解使用JS如何制作简单的ASCII图与单极图

这是代码处理后的结果,用了 I'mYasic 这8个字符来表示,还是可以分辨出大致的轮廓的。

详解使用JS如何制作简单的ASCII图与单极图

单级图

而另一种图则是单极图,也就是黑白图片,还是刚刚那张图片,输出如下

详解使用JS如何制作简单的ASCII图与单极图

基础知识

这两种图都是比较简单的,只需要以下知识即可

  • HTML5中的Canvas
  • 像素的RGB值
  • JS中的Canvas相关API

制作ASCII图

一般来说,在计算机当中,我们看到的大多数图片都是由一个个像素点构成的,每一个像素点则由 RGBA 构成,在 css 中我们时常用的 rgba(255, 255, 255, 255, 0)就是一组RGBA值, 也即是RGB三原色和Alpha透明度。当然一张图片不是仅仅包含所有像素点数据的,还包括一些描述信息,也称为图片的 profile,这一部分小则几KB,多则几百KB,是图片压缩中经常被处理的部分。

那么对于图片中每一个像素点来说,只要我们改变了其相应的RGBA的值,最终的图片也就变了样子。而修改哪些像素点、修改成什么样的RGBA,则决定着最终的图片风格,这也是许多滤镜采用的机制。

基于以上的理论知识,我们的ASCII图制作思路也就有了。ASCII图其实就是将一张图中的一个像素点,通过计算其RGBA的值,划分成给定的几个量化值,在这里由于我们用的 I'mYasic 这8个字符来表示,所以要分成8组值,每一组用一个ASCII字符来表示,最终就能组成一幅完整的ASCII图片。

接下来就是具体的代码实现。

获取图片的像素信息

通过 Canvas API 中的 getImageData() 方法我们可以获得一个对象,这个对象的属性里包含一个一维数组 data,这个一维数组每4个元素为一组,代表了一个 canvas 中指定范围的全部像素信息,并且依次是 RED,GREEN,BLUE,ALPHA。因此我们可以先把图片放进 canvas 中,再调用这个方法拿到像素。

不过我很疑惑为什么 data 是一个一维数组,通常处理的图片都是二维图片,如果用二维数组来表示像素信息,代码读取和处理会方便很多,也更容易理解。甚至可以用一个三维数组,专门用一个维度来放置RGBA信息。

获取图片像素信息的代码如下所示

var canvasContext = canvas.getContext("2d");
canvasContext.drawImage(sourceImg, 0, 0);
var imgData = canvasContext.getImageData(0 , 0, sourceImg.width, sourceImg.height);
var imgDataArray = imgData.data;

那么对于某一个像素点的RGBA值就可以这样获取

var r = imgDataArray[lineIndex];
var g = imgDataArray[lineIndex + 1];
var b = imgDataArray[lineIndex + 2];
var a = imgDataArray[lineIndex + 3];

其中 lineIndex 是遍历每一个像素点的基准变量。

图片灰度化

灰度化,也就是获取像素点的灰度值。由于每一个像素点包含着RGBA四种信息,而我们需要将所有像素点的RGBA值分成8组,因此需要统一一下RGBA的值,最终得到一个值Y,而相应的像素点的RGBA值满足 Y = R = G = B ,在这里我们不考虑透明度 Alpha。由于RGB的值相等像素点颜色是介于白色与黑色之间的灰色,所以这一过程也称为灰度化。

灰度化算法有很多种,我们在这里采取最简单的方式,即

Y = (R + G + B) * 1/3

相应代码如下

function rgb2gray(r, g, b) {
 return r * 0.333 + g * 0.333 + b * 0.333;
}

灰度图量化

灰度化以后的图片大致长这样子,可以看到色彩已经都变成灰色了。

详解使用JS如何制作简单的ASCII图与单极图

那么接下来就是关键的“量化”过程。也就是说,我们要让把这些不同灰度的值分成8组,并且每一组都赋予一个ASCII字符作为标示,当然选取的ASCII字符也要有一定规律,简单来说就是颜色由深到浅相应的字符由繁到简。而量化过程就是将0-255范围等分成8个区间,依次判断灰度值在哪一个区间内,代码如下。

由于图像像素数目巨大,为了效率,判决时可以采取“二分判决”法提高判决速度。

function gray2asc(gray) {
 /*ASCII--I'mYasic*/
 /*32 64 96 128 160 192 224 256*/
 gray = 255 - gray;
 if (gray < 128){
  if (gray < 64){
   if (gray < 32){
    return '\''
   }
   else {
    return 'c'
   }
  }
  else {
   if (gray < 96){
    return 'i'
   }
   else {
    return 's'
   }
  }
 }
 else {
  if (gray < 192){
   if (gray < 160){
    return 'I'
   }
   else {
    return 'm'
   }
  }
  else {
   if (gray < 224){
    return 'a'
   }
   else {
    return 'Y'
   }
  }
 }
}

遍历与显示

上面大概讲解完了对于一个像素点变换为ASCII码的过程,接下来就是遍历和显示了。

遍历

遍历全部像素点并变换为ASCII码基本是不可能的,因为图片稍微大一些计算量就增长很多,所以我们折中一下,对于像素阵列的行与列都进行等间隔采样,最终展示出来的图片分辨率会随着采样间隔减小而增强。另外要注意 data 数组是一维数组,并且每4个元素为一组RGBA数据。相应代码如下

var result = "";
 var lineIndex = 0;
 for (var lineHeight = 0; lineHeight < sourceImg.height; lineHeight += 12){
  var lineASC = "";
  for (var lineFlag = 0; lineFlag < sourceImg.width; lineFlag += 5){
   lineIndex = (lineHeight * sourceImg.width + lineFlag) * 4;
   var r = imgDataArray[lineIndex];
   var g = imgDataArray[lineIndex + 1];
   var b = imgDataArray[lineIndex + 2];
   lineASC += gray2asc(rgb2gray(r, g, b));
  }
  lineASC += '\n';
  result += lineASC;
 }

显示

最终获得的 result 字符串就是需要展示的ASCII码。但是必须注意,如果直接展示到页面上会因为每一个字符的字符宽度不一样而导致ASCII图“失真”,这里我们可以采用 Monospace 字体来确保字符宽度一致。

制作单极图

其实看完上面部分,就应该知道单极图非常好实现,同样需要获取像素信息并灰度化,只是量化时直接量化为 rgb(0, 0, 0) 和 rgb(255, 255, 255) 两种颜色就可以。

var canvasContext = targetCanvas.getContext("2d");
 canvasContext.drawImage(sourceImg, 0, 0);
 var imgData = canvasContext.getImageData(0 , 0, sourceImg.width, sourceImg.height);
 var imgDataArray = imgData.data;
 for (var index = 0; index <= sourceImg.width * sourceImg.height * 4; index += 4){
  var red = imgDataArray[index];
  var green = imgDataArray[index + 1];
  var blue = imgDataArray[index + 2];
  var gray = rgb2gray(red, green, blue);
  if (gray < 128){
   imgData.data[index] = 0;
   imgData.data[index + 1] = 0;
   imgData.data[index + 2] = 0;
  }
  else {
   imgData.data[index] = 255;
   imgData.data[index + 1] = 255;
   imgData.data[index + 2] = 255;
  }
 }
 canvasContext.putImageData(imgData, 0, 0);

别忘了最后要用 putImageData 方法将修改后的像素信息放回 canvas 中进行显示。

ASCII图完整代码

function rgb2gray(r, g, b) {
 return r * 0.333 + g * 0.333 + b * 0.333;
}

function gray2asc(gray) {
 /*ASCII--I'mYasic*/
 /*32 64 96 128 160 192 224 256*/
 gray = 255 - gray;
 if (gray < 128){
  if (gray < 64){
   if (gray < 32){
    return '\''
   }
   else {
    return 'c'
   }
  }
  else {
   if (gray < 96){
    return 'i'
   }
   else {
    return 's'
   }
  }
 }
 else {
  if (gray < 192){
   if (gray < 160){
    return 'I'
   }
   else {
    return 'm'
   }
  }
  else {
   if (gray < 224){
    return 'a'
   }
   else {
    return 'Y'
   }
  }
 }
}

var img2ASC = function (canvas, sourceImg) {
 console.log(sourceImg.width + " " + sourceImg.height);

 var canvasContext = canvas.getContext("2d");
 canvasContext.drawImage(sourceImg, 0, 0);
 var imgData = canvasContext.getImageData(0 , 0, sourceImg.width, sourceImg.height);
 var imgDataArray = imgData.data;
 var result = "";
 var lineIndex = 0;
 for (var lineHeight = 0; lineHeight < sourceImg.height; lineHeight += 12){
  var lineASC = "";
  for (var lineFlag = 0; lineFlag < sourceImg.width; lineFlag += 5){
   lineIndex = (lineHeight * sourceImg.width + lineFlag) * 4;
   var r = imgDataArray[lineIndex];
   var g = imgDataArray[lineIndex + 1];
   var b = imgDataArray[lineIndex + 2];
   lineASC += gray2asc(rgb2gray(r, g, b));
  }
  lineASC += '\n';
  result += lineASC;
 }
 document.getElementById("result").innerHTML = result;
};

单极图完整代码

function rgb2gray(r, g, b) {
 return r * 0.333 + g * 0.333 + b * 0.333;
}

function gray2asc(gray) {
 /*ASCII--I'mYasic*/
 /*32 64 96 128 160 192 224 256*/
 if (gray < 128){
  if (gray < 64){
   if (gray < 32){
    return '\''
   }
   else {
    return 'c'
   }
  }
  else {
   if (gray < 96){
    return 'i'
   }
   else {
    return 's'
   }
  }
 }
 else {
  if (gray < 192){
   if (gray < 160){
    return 'I'
   }
   else {
    return 'm'
   }
  }
  else {
   if (gray < 224){
    return 'a'
   }
   else {
    return 'Y'
   }
  }
 }
}

var monoImg = function (targetCanvas, sourceImg) {
 targetCanvas.width = sourceImg.width;
 targetCanvas.height = sourceImg.height;
 var canvasContext = targetCanvas.getContext("2d");
 canvasContext.drawImage(sourceImg, 0, 0);
 var imgData = canvasContext.getImageData(0 , 0, sourceImg.width, sourceImg.height);
 var imgDataArray = imgData.data;
 for (var index = 0; index <= sourceImg.width * sourceImg.height * 4; index += 4){
  var red = imgDataArray[index];
  var green = imgDataArray[index + 1];
  var blue = imgDataArray[index + 2];
  var gray = rgb2gray(red, green, blue);
  if (gray < 128){
   imgData.data[index] = 0;
   imgData.data[index + 1] = 0;
   imgData.data[index + 2] = 0;
  }
  else {
   imgData.data[index] = 255;
   imgData.data[index + 1] = 255;
   imgData.data[index + 2] = 255;
  }
 }
 canvasContext.putImageData(imgData, 0, 0);
};

总结

这一篇博客主要讲了利用JS中的 Canvas API 进行一些简单的像素化操作,但其实还有很多地方可以继续改进。比如一般单极图出来后很多地方会有噪点,也就是一些碍眼的白点和黑点,可以通过一些方式“去掉噪点”,就留在以后写吧!以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
字符串的replace方法应用浅析
Dec 06 Javascript
javascript禁用Tab键脚本实例
Nov 22 Javascript
如何实现textarea里的不同文本显示不同颜色
Jan 20 Javascript
jquery1.10给新增元素绑定事件的方法
Mar 06 Javascript
jQuery实现简单的列表式导航菜单效果代码
Aug 31 Javascript
js获取url传值的方法
Dec 18 Javascript
浅谈jquery高级方法描述与应用
Oct 04 Javascript
用angular实现多选按钮的全选与反选实例代码
May 23 Javascript
详解promise.then,process.nextTick, setTimeout 以及 setImmediate的执行顺序
Nov 21 Javascript
小程序如何获取多个formId实现详解
Sep 20 Javascript
js实现小球在页面规定的区域运动
Jun 16 Javascript
Vue提供的三种调试方式你知道吗
Jan 18 Vue.js
Angularjs中使用指令绑定点击事件的方法
Mar 30 #Javascript
Angualrjs和bootstrap相结合实现数据表格table
Mar 30 #Javascript
微信小程序中顶部导航栏的实现代码
Mar 30 #Javascript
ES6中Proxy与Reflect实现重载(overload)的方法
Mar 30 #Javascript
vue分类筛选filter方法简单实例
Mar 30 #Javascript
教你快速搭建Node.Js服务器的方法教程
Mar 30 #Javascript
Vuejs仿网易云音乐实现听歌及搜索功能
Mar 30 #Javascript
You might like
简单采集了yahoo的一些数据
2007/02/14 PHP
php GD绘制24小时柱状图
2008/06/28 PHP
linux系统下php安装mbstring扩展的二种方法
2014/01/20 PHP
php计算数组相同值出现次数的代码(array_count_values)
2015/01/20 PHP
深入浅析Yii admin的权限控制
2016/08/31 PHP
PHP延迟静态绑定使用方法实例解析
2020/09/05 PHP
prototype Element学习笔记(篇二)
2008/10/26 Javascript
jQuery中的read和JavaScript中的onload函数的区别
2014/08/27 Javascript
JavaSacript中charCodeAt()方法的使用详解
2015/06/05 Javascript
JQuery标签页效果实例详解
2015/12/24 Javascript
JS把内容动态插入到DIV的实现方法
2016/07/19 Javascript
微信小程序 页面跳转传参详解
2016/10/28 Javascript
angularjs定时任务的设置与清除示例
2017/06/02 Javascript
Three.js基础学习教程
2017/11/16 Javascript
IE9 elementUI文件上传的问题解决
2018/10/17 Javascript
vue-form表单验证是否为空值的实例详解
2019/10/29 Javascript
Vue实现仿iPhone悬浮球的示例代码
2020/03/13 Javascript
python实现的防DDoS脚本
2011/02/08 Python
Python3 获取一大段文本之间两个关键字之间的内容方法
2018/10/11 Python
python将txt文件读取为字典的示例
2018/12/22 Python
python使用selenium登录QQ邮箱(附带滑动解锁)
2019/01/23 Python
Python+Selenium随机生成手机验证码并检查页面上是否弹出重复手机号码提示框
2020/09/21 Python
利用CSS3实现圆角的outline效果的教程
2015/06/05 HTML / CSS
凯特王妃父母建立的派对用品网站:Party Pieces
2017/05/28 全球购物
澳大利亚运动鞋零售商:The Athlete’s Foot
2018/11/04 全球购物
行政助理的职责
2013/11/14 职场文书
银行实习鉴定
2013/12/13 职场文书
幼儿园国庆节活动方案
2014/02/01 职场文书
爱祖国演讲稿
2014/05/04 职场文书
离婚起诉书范本
2015/05/18 职场文书
幼儿园小班教学反思
2016/03/03 职场文书
廉政党课工作报告案例
2019/06/21 职场文书
女性励志书籍推荐
2019/08/19 职场文书
MySQL完整性约束的定义与实例教程
2021/05/30 MySQL
python使用shell脚本创建kafka连接器
2022/04/29 Python
浅谈为什么我的 z-index 又不生效了
2022/07/15 HTML / CSS