详解使用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 相关文章推荐
拖动一个HTML元素
Dec 22 Javascript
JavaScript eval() 函数介绍及应用示例
Jul 29 Javascript
JavaScript fontsize方法入门实例(按照指定的尺寸来显示字符串)
Oct 17 Javascript
javascript判断复选框是否选中的方法
Oct 16 Javascript
js拖拽的原型声明和用法总结
Apr 04 Javascript
jQuery 表单序列化实例代码
Jun 11 jQuery
Vue.js自定义事件的表单输入组件方法
Mar 08 Javascript
vue移动端监听滚动条高度的实现方法
Sep 03 Javascript
Vue.js递归组件实现组织架构树和选人功能案例分析
Jul 03 Javascript
微信小程序用户拒绝授权的处理方法详解
Sep 20 Javascript
微信jssdk踩坑之签名错误invalid signature
May 19 Javascript
vue 使用微信jssdk,调用微信相册上传图片功能
Nov 13 Javascript
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
php 时间计算问题小结
2009/01/04 PHP
简单的php缓存类分享     php缓存机制
2014/01/22 PHP
javascript不同页面传值的改进版
2008/09/30 Javascript
javascript 强制刷新页面的实现代码
2009/12/13 Javascript
JavaScript 存在陷阱 删除某一区域所有节点
2010/05/10 Javascript
jquery 绑定回车动作扑捉回车键触发的事件
2014/03/26 Javascript
5个JavaScript经典面试题
2014/10/13 Javascript
BootStrap与Select2使用小结
2017/02/17 Javascript
JS实现动态给标签控件添加事件的方法示例
2017/05/13 Javascript
详解如何使用webpack在vue项目中写jsx语法
2017/11/08 Javascript
vue实现重置表单信息为空的方法
2018/09/29 Javascript
JavaScript页面倒计时功能完整示例
2019/05/15 Javascript
对node通过fs模块判断文件是否是文件夹的实例讲解
2019/06/10 Javascript
深入学习JavaScript 高阶函数
2019/06/11 Javascript
JS实现json数组排序操作实例分析
2019/10/28 Javascript
three.js 制作动态二维码的示例代码
2020/07/31 Javascript
Vue 按照创建时间和当前时间显示操作(刚刚,几小时前,几天前)
2020/09/10 Javascript
[00:56]跨越时空加入战场 全新祈求者身心“失落奇艺侍祭”展示
2019/07/20 DOTA
python实现代码行数统计示例分享
2014/02/10 Python
详解Python中内置的NotImplemented类型的用法
2015/03/31 Python
python实现在windows下操作word的方法
2015/04/28 Python
python测试mysql写入性能完整实例
2018/01/18 Python
python让列表倒序输出的实例
2018/06/25 Python
使用python动态生成波形曲线的实现
2019/12/04 Python
pytorch快速搭建神经网络_Sequential操作
2020/06/17 Python
使用pandas实现筛选出指定列值所对应的行
2020/12/13 Python
解决pytorch下出现multi-target not supported at的一种可能原因
2021/02/06 Python
使用CSS3实现圆角,阴影,透明
2014/12/23 HTML / CSS
澳大利亚当地最大的时装生产商:Cue
2018/08/06 全球购物
物流仓管员岗位职责
2013/12/04 职场文书
高中班长自我鉴定
2013/12/20 职场文书
文明家庭先进事迹材
2014/01/27 职场文书
办公室主任四风问题对照检查材料思想汇报
2014/09/28 职场文书
运动会3000米加油稿
2015/07/21 职场文书
六个好看实用的 HTML + CSS 后台登录入口页面
2022/04/28 HTML / CSS
使用CSS实现音波加载效果
2023/05/07 HTML / CSS