欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

教你一招使用ASCII字符生成图片

程序员文章站 2022-03-08 08:13:49
...
ASCII是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。其实ASCII字符不仅仅可以作为信息交换标准,还可以用它来生成图片,今天我们就来介绍介绍。

教你一招使用ASCII字符生成图片

网上经常有一些字符做成的图片,比如这样:

教你一招使用ASCII字符生成图片

细想一下,这里面主要运用到了几个知识,有:

  • 从图片解析出像素颜色(也就是通常说的RGB值)

  • 去色处理

  • 像素映射到字符

作为世界上最好的语言,用PHP实现有趣功能也是易如反掌。下面讲解一下具体实现。

1. 解析图片中的像素颜色

解析图片中的像素颜色,我们需要了解图片存储的格式,这里就以BMP图片为例。什么?网上找不到BMP图片?用QQ的截个图,保存成BMP就行了。

BMP图片并不是从文件的第1个字节开始就是像素数据,而是一个个14字节的文件头,保存着文件的元信息。紧挨着的是一个40字节的图片头结构,保存着图片相关的元信息。

详细列出文件头和图片头结构的每个字段,就有些太无聊了(详细的头信息可以参见附录)。想要解析图片的像素,只需要这 4 个信息:

  • 图片文件的总体大小 (文件的第3~6个字节)

  • 像素数据从文件的哪里开始 (11~14个字节处)

  • 图片的宽度和高度 (宽:19~22字节处、高:23~26字节处)

  • 图片的一个像素占几个字节 (29~30字节处)

想要解析出二进制中的数据,用 unpack() 结合 substr() 就能搞定:

$data = file_get_contents('image.bmp');$ret = unpack('v/Vsize/v/v/VpixelStart/V/Vwidth/Vheight/v/vbytePerPixel/V*6',
    substr($data, 0x0, 54));/**
 * $ret的内容:
 * array (
 *     'size' => 706554,
 *     'pixelStart' => 54,
 *     'width' => 500,
 *     'height' => 471,
 *     'bytePerPixel' => 24,
 * );
 */

2. 获得像素颜色以及去色

从上一节可以知道,我们想处理的图片,像素数据从文件的第54字节开始,每个像素数据占据24 bit。这24 bit中R(红)、G(绿)、B(蓝)的值各占8 bit(1字节)。

假如我想获得图片第 x 行,第 y 列的RGB值,那么对应的RGB值的位置应该这样计算:

像素(x, y)的 B 值偏移 = 像素数据开始位置 + 3 * (图片宽度 * x + y)
像素(x, y)的 G 值偏移 = 像素数据开始位置 + 3 * (图片宽度 * x + y) + 1
像素(x, y)的 R 值偏移 = 像素数据开始位置 + 3 * (图片宽度 * x + y) + 2

从式子中发现三原色的值是以BGR顺序排列的,不是通常的RGB顺序。

如果你按照这种方法,将像素按顺序一个个画在一张画布上,你会发现得到的图片是颠倒的,这是因为图片的像素信息是倒过来存储的,最左上角的像素实际位于文件的最末尾,所以想得到一张正过来的图片,像素数据应该这么取:

像素(x, y)的 B 值偏移 = 文件大小 - 3 * (图片宽度 * x + y) - 3
像素(x, y)的 G 值偏移 = 文件大小 - 3 * (图片宽度 * x + y) - 2
像素(x, y)的 R 值偏移 = 文件大小 - 3 * (图片宽度 * x + y) - 1

像素的颜色是取到了,但最终ascii图是黑白的,怎么进行去色呢?去色的算法有很多,这里就用最简单粗暴的:

新的R、G、B值 = [min(R, G, B) + max(R, G, B)] / 2;

黑白的像素,R、G、B都是一样的值,这个值可以称作像素的 明亮度

最终,取像素的操作可以定义成一个函数:

function getPixelColor($x, $y) {
    global $width, $size, $data;
    $b = ord($data[$size - 3 * ($width * $x + $y) - 3]);
    $g = ord($data[$size - 3 * ($width * $x + $y) - 2]);
    $r = ord($data[$size - 3 * ($width * $x + $y) - 1]);
    return (min($r, $g, $b) + max($r, $g, $b)) >> 1;
}

3. 像素到字符映射

到了最后一个环节,我们要将图片每个像素的深浅转换成ascii字符。ascii字符本身没有颜色深浅一说。但是如果你把”#”和”.”分别排列成100x100的正方形,从视觉上”#”会比”.”颜色更暗一点。

我们可以取若干个字符代表不同像素的明亮度,某个像素的明亮度处于某个区间时,就以相应等级的字符替换:

function getChar($colorValue) {
    $map = '@#mdohsy+/-:.` ';
    return $map[(int) ($colorValue / 18)];
}

还有一个问题:如果把每个像素都用一个字符替换的话,那么输出的字符图将会非常巨大。所以最好是用一个字符替代原图中 NxN 的像素块。整个像素块的明亮度,就取块中每个像素明亮度的均值。

以上问题都解决了,最后拿一张蒙娜丽莎的微笑来测试:

教你一招使用ASCII字符生成图片

教你一招使用ASCII字符生成图片

效果还不错:-)。如果终端背景是白色的,可以将表示明亮度的字符序列反过来:

// $map = '@#mdohsy+/-:.` ';
$map = ' `.:-/+yshodm#@'; // 反过来

附录

完整代码

<?php$data = file_get_contents('timg.bmp');$ret = unpack('v/Vsize/v/v/VpixelStart/V/Vwidth/Vheight/v/vbytePerPixel/V*6', substr($data, 0x0, 54));$size = $ret['size'];$offset = $ret['pixelStart'];$width = $ret['width'];$height = $ret['height'];$bitDepth = $ret['bytePerPixel'];$pixelLenPerChar = 4;$charImgWidth = (int) ($width / $pixelLenPerChar);$charImgHeight = (int) ($height / $pixelLenPerChar);for ($i = 0; $i !== $charImgHeight; $i++) {    $buf = '';    for ($j = 0; $j !== $charImgWidth; $j++) {        $sum = 0;        for ($k = 0; $k !== $pixelLenPerChar; $k++) {            for ($l = 0; $l !== $pixelLenPerChar; $l++) {                $sum += getPixelColor($pixelLenPerChar * $i + $k, $pixelLenPerChar * $j + $l);
            }
        }        $sum = (int) ($sum / $pixelLenPerChar / $pixelLenPerChar);        $buf = getChar($sum) . $buf;
    }    echo $buf . PHP_EOL;
}function getPixelColor($x, $y) {
    global $width, $size, $data;    $b = ord($data[$size - 3 * ($width * $x + $y) - 3]);    $g = ord($data[$size - 3 * ($width * $x + $y) - 2]);    $r = ord($data[$size - 3 * ($width * $x + $y) - 1]);    return (min($r, $g, $b) + max($r, $g, $b)) >> 1;
}function getChar($colorValue) {
    $map = '@#mdohsy+/-:.` ';    return $map[(int) ($colorValue / 18)];
}

BMP文件头格式

偏移 大小(字节) 含义 本文中图片示例值
0 2 固定为”BM”两个字符的编码 0x42 0x4d
2 4 文件大小 0x000ac7fa
6 4 保留字段,一般为 0 0x00000000
10 4 像素数据起始处偏移 0x00000036

BMP图片头格式

偏移 大小(字节) 含义 本文中图片示例值
14 4 图片头的大小(字节) 0x00000028
18 4 图片的宽度 0x000001f4
22 4 图片的高度 0x000001d7
26 2 图像的帧数(静态图都是1) 0x0001
28 2 一个像素占的比特位数 0x0018
30 4 保留字段,一般为 0 0x000000
34 4 像素数据占用的总字节数 0x000ac7c4
38 4 保留字段,一般为 0 0x000000
42 4 保留字段,一般为 0 0x000000
46 4 保留字段,一般为 0 0x000000
50 4 保留字段,一般为 0 0x000000

推荐学习:php视频教程

以上就是教你一招使用ASCII字符生成图片的详细内容,更多请关注其它相关文章!

相关标签: PHP