来, 先看效果哈哈哈哈!

演示地址: http://ascii-picture.imlht.com/

             "\` """        . "\`"""""""""""""""""""w$@w"""""""""""""""""""
""""""" \`""""""""""""$$$$$$$$$00$$0"""""""""""""""""""
""""""""""""""""$$$$$$$$$$$$$$$$$$$$0""""""""""""""""""""
"""""""$$$$$$$$$$$$$$$$$$$$$$$$""""""0(""""""""""""""""
$$$$$$$$$$$$$$$$$$$$$$$$$$00&0("""""""""""""""""""""""
\` $$$$$$$$$$$$$$$$$$$$$$$$$$$$$&hLLLL(~~"""""""""""""""""""
""". """""" $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@0000000L""""""""""""""\`
"""""""""""""""" ""0$$$$$$$$$0("(0$$$$$$$$$$$$$$$$$$$$$$$$$$$@&&h0000v"""""""""".
"""""""""""""""""""""""""". ""$$$$$$$$0"""""v00$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$000(""""""""
"""""""""""""""""""""""""""""""""""""""$$$$$$0""""""""""(00h$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$w0"""
"""""""""""""""""""""""""""""""""""""""$$$$00$$0""($$$$$$$"""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$h"
""""""""""""""""""""""""""""""""""""""0$$$$$$$$0"v$$$$0"(&0"""$$$$$$$$$$$$$&""$$$$$$$0""h$$$&h0w&$$$$"
"""""""""""""""(vv~~"""""""""""""""""00&$$"0""0&0"$$$$$$$""""""$$$$$$$$$$$$$""$"$$$$$v0""$0$$$$$h$$$$$""""w"""0
"""""""""""""""0"0(0000v"0000"""""""0w0w$$""$$$""""""v""~"""\`"""$$$$$$$$$$$(""""$$$$$$$""00"0$$h$&"~$$v""""""$@
"""""""""""("0"000"0"0000v"""""""0$$w$0 ""$"""\` "" """""$$$$$$$$$$$""\`"""$$$$$$$0""0$$$($$(&$h"""""""$h
""""""""~"""""0(0v0"00"""""""L$$h0 \` ""0" .""""""0$$$v$$$$$"" "0""$$$$0"""w$$$$$w"~""\` """ @"
""""""""""""~"""0v0000"""""""""$h&$ 0 """" $$""""""""""w$$0"$$$$$""""""0"" " """"v$$$$$$ . " w"
"""""""""""0""""""""0""""""""""0$$@"""$$$$$$$$$""0&@&&"""""&0""$$$$$$$$$$$$$$$$$0"""L0"&$@"" " 0L
"" """"""""(0"""L~"""""""""""""0w$""L0"""$$" \`"~0$$$&"""""$$$$$$$$$&$$$$$$$$$$$$$$$$$$"@"" "
"""""""""""""0""""""""""""""""""""""""""($$$$$$$00$hL~"""""$$$$$$$$$w@$$$$$$$$$$&$$$$$$0$&"
""""""""""""""""""""""""""""""""""""""""""""""$$0@$$$$0w&0~vw&hwh@$$$$$$$""h$$ $$ $$$$"$$$$$~$"@"
""""""""""""""""""""""""""""""""""""""""""""" v"""""~"0h0$$$$@~v($$$$$$$""w$$$$ $$$0"$$$$&"h$"
"""""""""""""""""""""""""""""""""""""""""""""""0"(@$$$wh&$$$$$@v""~$$$$$""""$$$$$jj$$$ "$$$$. 0 " ;
""""""""""""""""""""""""""""""""""""""""""\` """~$$$0"""(&$$$$$$$"""$$$" 0$$$$$$$$$$ 0$$$$" "" ;$$;"""""
"""""""""""""""""""""""""""""""""""""""""""" """""""L0$$$$$$$$$$$0""$ "L00w$$$$$.$L" """"$$,"""""
""""""""""""""""""""""""""""""""""""""""""""\` "0w$$$$$$$$$$$$$$$$@""" "" """"$$$"$"\`"
"""""""""""""""""""""""""""""""""""""""""""""" ""$$$Lh$$$$$$$$$$$&"" "("0@@"""""""
""""""""""""""""""""""""""""""""""""""""""""""" ." ""&$$$$$$$$$$$$$ $$0"$$""""""
"""""""""""""""""""""""""""""""""""""""""""""""" . "&$$$$$$$$$$$$$ $$$$$$$""("$
""""""""""""""""""""""""""""""""""""""""""""""""" "L$$$$$$$$$$$$w $$$$$$"@$$$
""""""""""""""""""""""""""""""""""""""""""""" "$$$$$$$$$$$$" &$$$""@$$$
""""""""""""""""""""""""""""""""""""""""0"" $$$$$$$$$$$$ "$$~"""$$$
""""""""""""""""""""""""""""""""""""""""" ."$$$$$$$$$$h &$,$##$$0
""""""""""""""""""""""""""""""""""""""" ""$$$$$$$$@" 0~$-,$$$
""""""""""""""""""""""""""""""""""""" ". ..$$$$$$000" " " "$h""$0$
""""""""""""""""""""""""""""""""""" "" $$$$0h0$ "" "" " $$$ """
"""""""""""""""""""""""""""""""""" "" "$$$$$$$ " """" "" $$$$0""
""""""""""""""""""""""""""""""""""" " "" $$$$$$$ " """ """ "$$$""0
"""""""""""""""""""""""""""&""""""" "" "$$$$$$ "" ."". .". $$$00h

平时看代码会看到很多标点符号的字符拼起来的图案, 特别有趣, 像kong(一个高性能API网关), 除了源代码里面有图案, 命令行也藏了彩蛋:

Kong, the biggest ape in town

    /\  ____
<> ( oo )
<>_| ^^ |_
<> @ \
/~~\ . . _ |
/~~~~\ | |
/~~~~~~\/ _| |
|[][][]/ / [m]
|[][][[m]
|[][][]|
|[][][]|
|[][][]|
|[][][]|
|[][][]|
|[][][]|
|[][][]|
|[][][]|
|[|--|]|
|[| |]|
========
==========
|[[ ]]|
==========

上面这个图案, 只是停留在外形轮廓上, 而我今天要玩的会深入一点: 基于图片的灰度值来生成图案. 此时的图片不单单有轮廓, 还有光影效果, 也就是素描中提及的黑白灰.

原理实际上挺简单的, 在白色背景下, 字符 $ 会有比较大面积的黑, 而字符 + 相对就淡了很多, 毫无疑问, 空格就是纯白了. 所以, 只要把一些字符按照 , , 排序, 并把这些字符映射为 0-255 的灰度值, 就可以根据图片生成更生动的字符画了.

至于这些字符按照灰度排序, 已经有人帮我们做好了, 具体可以查看这个Demo, 是用 Python 写的:

ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")

看到这里, 是时候拿起 Python 干起来了! 可以照着链接在自己电脑跑一下, 制作一些白色背景的表情包, 但如果是照片的话会发现很糊, 根本看不清, 于是我拿出神器 Photoshop 调整了 亮度对比度, 尽量调高点, 生成的图案会清晰一些.

每次都去 Photoshop 调整真是繁琐, 每次失败了, 得重新用命令行生成, 然后看生成的图案怎么样, 一直重复这个步骤...而且宽度和高度都需要手工指定...所以萌生了这个想法: 把这些重复繁琐的操作, 交给界面去处理好了! 所以后面的代码都是用 JavaScript 实现的.

OK, 我们先扯回来, 说下灰度的映射算法, 也是很容易理解的, 上面的字符一共有 69 个, 0-255 一共有 256 个字符, 计算出比率 ratio 然后直接把字符取出来即可:

/**
* ASCII Charset
*
* @type {String}
*/
const charset = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "; /**
* 69/256
*
* @type {Number}
*/
const ratio = charset.length / 256; /**
* 颜色值转换为 ASCII 字符
*
* @param {Number} r R
* @param {Number} g G
* @param {Number} b B
* @param {Number} a A
* @param {Number} type 类型
* @return {String} ASCII 字符
*/
export const rgba_to_char = (r, g, b, a, type) => {
if (a === 0) return ' ';
r = Math.round(a / 255 * r);
g = Math.round(a / 255 * g);
b = Math.round(a / 255 * b);
return charset[ Math.round( ratio * rgb_to_gray(r, g, b, type) ) ] || ' ';
};

根据灰度生成字符, 那灰度怎么来的? 扒了挺多资料, 总体来说有几个公式, 具体可以看这篇文章

Gray = R*0.299 + G*0.587 + B*0.114

上面的 Python 代码用的是这个公式, 参考知乎:

Gray = 0.2126 R' + 0.7152 G' + 0.0722 B'

还有另一种, 这个是我实验后发现的, 用这个方法生成的图案细节会多一些, 大家也可以试试看. 算法是比较复杂的, 基本原理是将 RGB 色彩转为 XYZ 色彩, 再从 XYZ 转到 Lab. Lab颜色空间中的L分量用于表示像素的亮度, 最小值是0(纯黑), 最大值是100(纯白), 而a表红绿, b表黄蓝. 我们需要的是灰度值算法, 所以只需L分量就可以了.

再加上平均值, 最大值, 只取绿色通道, 一共就有6种算法, 代码实现如下:

/**
* 颜色值转换为灰度
*
* @param {Number} r R
* @param {Number} g G
* @param {Number} b B
* @param {Number} type 类型
* @return {Number} 灰度值
*/
const rgb_to_gray = (r, g, b, type) => {
switch (type) {
case 1:
return g;
case 2:
return Math.max(r, g, b);
case 3:
return Math.round((r + g + b) / 3);
case 4:
return Math.round(0.299 * r + 0.587 * g + 0.114 * b);
case 5:
return Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b);
case 6:
// https://github.com/antimatter15/rgb-lab/blob/master/color.js
// https://github.com/markusn/color-diff/blob/master/lib/convert.js
r /= 255;
g /= 255;
b /= 255;
r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
let x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
let y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000;
let z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
x = (x > 0.008856) ? Math.pow(x, 1 / 3) : (7.787 * x) + 16 / 116;
y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y) + 16 / 116;
z = (z > 0.008856) ? Math.pow(z, 1 / 3) : (7.787 * z) + 16 / 116;
return Math.round(255 / 100 * ((116 * y) - 16));
}
};

OK, 目前我们已经实现了彩色的像素值变成ASCII字符, 接下来要解决一个问题, 调整图像的亮度和对比度, 同样也是有公式的, 参考链接:

bitmap() {
return this.data.map((x, i) => {
if ((i+1) % 4 === 0) {
// alpha
return x;
}
// http://blog.csdn.net/hbaizj/article/details/17376857
const B = this.brightness / 100;
const c = this.contrast / 100;
const k = Math.tan( (45 + 44 * c) / 180 * 3.1416 );
return [x - 127.5 * (1 - B)] * k + 127.5 * (1 + B);
});
}

最后, 我们只需把用户选择的图片, 转换为 RGB 值, 加上亮度对比度, 宽度高度的变换, 就大功告成了:

onchange() {
const files = document.getElementById('file').files;
if (!files || files.length === 0) return;
const that = this;
let fr = new FileReader();
fr.onload = function (event) {
let img = new Image();
img.onload = function () {
let c = document.createElement('canvas');
if (!that.width && !that.height) {
that.width = img.width;
that.height = img.height;
} else if (!that.width) {
that.width = Math.round(img.width * (that.height / img.height));
} else if (!that.height) {
that.height = Math.round(img.height * (that.width / img.width));
}
c.width = that.width;
c.height = that.height;
let ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0, that.width, that.height);
that.data = ctx.getImageData(0, 0, that.width, that.height).data;
}
img.src = event.target.result;
}
fr.readAsDataURL(files[0]);
}

完整的源码, 我放到 GitHub 上了, 求Star求Star求Star! 代码是用 Vue2 写的(上面的代码都是再里面摘出来的), 结合了饿了么前端框架做界面, 目前先这样, 有时间再调整下界面吧.

演示地址: http://ascii-picture.imlht.com/


文章来源于本人博客,发布于 2017-12-28,原文链接:https://imlht.com/archives/93/

图片转ASCII字符图案的原理(可调整亮度对比度 宽高度)的更多相关文章

  1. 将图片转为ASCII字符画

    原文:将图片转为ASCII字符画 Copyright 2012 Conmajia 源代码下载:点击这里 什么是字符画?就是用ASCII字符来近似组成图像,就像这样: ╭╮ ╭╮ ││ ││ ╭┴┴—— ...

  2. Springboot 系列(八)动态Banner与图片转字符图案的手动实现

    使用过 Springboot 的对上面这个图案肯定不会陌生,Springboot 启动的同时会打印上面的图案,并带有版本号.查看官方文档可以找到关于 banner 的描述 The banner tha ...

  3. 32. Springboot 系列(八)动态Banner与图片转字符图案的手动实现

    使用过 Springboot 的对上面这个图案肯定不会陌生,Springboot 启动的同时会打印上面的图案,并带有版本号.查看官方文档可以找到关于 banner 的描述 The banner tha ...

  4. HTML5将图片转化成字符画

    HTML5将图片转化成字符画 字符画大家一定非常熟悉了,那么如何把一张现有的图片转成字符画呢?HTML5让这个可能变成了现实,通过canvas,可以很轻松实现这个功能.其实原理很简单:扫描图片相应位置 ...

  5. 基于 canvas 将图片转化成字符画

    字符画大家一定非常熟悉了,那么如何把一张现有的图片转成字符画呢? HTML5 让这个可能变成了现实,通过 canvas,可以很轻松实现这个功能. 其实原理很简单:扫描图片相应位置的像素点,再计算出其灰 ...

  6. 基于canvas将图片转化成字符画

    字符画大家一定非常熟悉了,那么如何把一张现有的图片转成字符画呢?HTML5让这个可能变成了现实,通过canvas,可以很轻松实现这个功能.其实原理很简单:扫描图片相应位置的像素点,再计算出其灰度值,根 ...

  7. 超好玩!10款神奇的字符图案 & 词汇云生成工具

    在这里,我们推荐10款惊人的字符图案生成工具.词云可以定义为词频的图形表示,而字符图案发生器是一个把数据,如文字和标签在以视觉和吸引人的方式展示的简单的工具.这些生成工具具有不同的功能,其中包括不同的 ...

  8. AsciiMorph - 新奇的 ASCII 字符画生成工具&插件

    AsciiMorph 是一个新奇的 ASCII 字符画生成工具和开源插件.字符画(ASCII Art)的历史可以追溯到几十年前,起初是用在图形显示功能受限的设备上,用ASCII字符集里的可打印字符来拼 ...

  9. python将图片转化为字符图

    最近看到将图片转化为字符图的小实验,我觉得很有趣,所以决定自己实现一下. 步骤和原理如下: 读取图片的灰度值矩阵(0-255之间),灰度值矩阵主要反映的是图片的黑白程度,越黑越接近与0,越白越接近于2 ...

  10. 制作ASCII字符动画

    看过Matrix的同学应该还记得,在母舰上一直在计算的电脑屏幕在Neo觉醒的时候,不停的下落的杂乱无章的字符组成了Neo当时所处的场景.其实利用开源和免费的工具,我们可以将现有的视频转换为ASCII字 ...

随机推荐

  1. Simulation-计算统计——Monte Carlo

    Monte Carlo Integration 找到原函数,再计算 无法找到原函数,MC积分 Assume that we can generate \(U_1, . . . , U_n \sim U ...

  2. python 高级函数补充

    补充几个高级函数 zip 把两个可迭代内容生成一个可迭代的tuple元素类型组成的内容 # zip 案例 l1 = [ 1,2,3,4,5] l2 = [11,22,33,44,55] z = zip ...

  3. .gitignore 文件语法介绍

    .gitignore 文件的作用 A gitignore file specifies intentionally untracked files that Git should ignore. Fi ...

  4. 前端模拟“多线程”提交Http请求

    首先说,javascript没有多线程这样一个说法,我说的只是类似那种效果.其次,不建议使用这种方式解决问题,多线程应该交给后台去做. 但是,如果非要这样用,有什么方法呢? 我在工作中就遇到了这样的问 ...

  5. 2022-12-13:游戏玩法分析 I。写一条 SQL 查询语句获取每位玩家 第一次登陆平台的日期。 +-----------+-------------+ | player_id | first_l

    2022-12-13:游戏玩法分析 I.写一条 SQL 查询语句获取每位玩家 第一次登陆平台的日期. ±----------±------------+ | player_id | first_log ...

  6. 2022-05-23:给定一个数组arr,你可以随意挑选其中的数字, 但是你挑选的数中,任何两个数a和b,必须Math.abs(a - b) > 1。 返回你最多能挑选几个数。 来自美团。

    2022-05-23:给定一个数组arr,你可以随意挑选其中的数字, 但是你挑选的数中,任何两个数a和b,必须Math.abs(a - b) > 1. 返回你最多能挑选几个数. 来自美团. 答案 ...

  7. 2021-02-28:给定一个整型数组arr,和一个整数num。某个arr中的子数组sub,如果想达标,必须满足:sub中最大值 – sub中最小值 <= num,返回arr中达标子数组的数量。

    2021-02-28:给定一个整型数组arr,和一个整数num.某个arr中的子数组sub,如果想达标,必须满足:sub中最大值 – sub中最小值 <= num,返回arr中达标子数组的数量. ...

  8. 2022-01-30:最小好进制。 对于给定的整数 n, 如果n的k(k>=2)进制数的所有数位全为1,则称 k(k>=2)是 n 的一个好进制。 以字符串的形式给出 n, 以字符串的形式返回 n 的

    2022-01-30:最小好进制. 对于给定的整数 n, 如果n的k(k>=2)进制数的所有数位全为1,则称 k(k>=2)是 n 的一个好进制. 以字符串的形式给出 n, 以字符串的形式 ...

  9. 17.AQS中的Condition是什么?

    欢迎关注:王有志 期待你加入Java人的提桶跑路群:共同富裕的Java人 今天来和大家聊聊Condition,Condition为AQS"家族"提供了等待与唤醒的能力,使AQS&q ...

  10. 蓝桥杯真题 k倍区间

    考点: - 利用前缀和求子列和 - 同余作差是模的倍数 题目概要 给定一个长度为N的数列,A1, A2, - AN,如果其中一段连续的子序列Ai, Ai+1, - Aj(i <= j)之和是K的 ...