ASCII是基於拉丁字母的一套電腦編碼系統,主要用於顯示現代英語和其他西歐語言。其實ASCII字元不只可以作為資訊交換標準,還可以用它來產生圖片,今天我們就來介紹介紹。
網路上常有一些字元做成的圖片,例如這樣:
細想一下,這裡面主要運用到了幾個知識,有:
從圖片解析出像素顏色(也就是通常說的RGB值)
去色處理
像素映射到字元
作為世界上最好的語言,用PHP實現有趣功能也是易如反掌。下面講解一下具體實作。
解析圖片中的像素顏色,我們需要了解圖片儲存的格式,這裡就以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, * ); */
從上一節可以知道,我們想處理的圖片,像素資料從檔案的第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; }
到了最後一個環節,我們要將圖片每個像素的深度轉換成ascii字元。 ascii字元本身沒有顏色深淺一說。但是如果你把”#”和”.”分別排列成100x100的正方形,從視覺上”#”會比”.”顏色更暗一點。
我們可以取若干個字元代表不同像素的明亮度,某個像素的明亮度處於某個區間時,就以對應等級的字元替換:
function getChar($colorValue) { $map = '@#mdohsy+/-:.` '; return $map[(int) ($colorValue / 18)]; }
還有一個問題:如果把每個像素都用一個字元替換的話,那麼輸出的字元圖將會非常巨大。所以最好是用一個字元取代原圖中 NxN 的像素區塊。整個像素區塊的明亮度,就取區塊中每個像素明亮度的平均值。
以上問題都解決了,最後拿一張蒙娜麗莎的微笑來測試:
效果還不錯:-)。如果終端背景是白色的,可以將表示明亮度的字元序列反過來:
// $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)]; }
偏移 | 大小(字节) | 含义 | 本文中图片示例值 |
---|---|---|---|
0 | 2 | 固定为”BM”两个字符的编码 | 0x42 0x4d |
2 | 4 | 文件大小 | 0x000ac7fa |
6 | 4 | 保留字段,一般为 0 | 0x00000000 |
10 | 4 | 像素数据起始处偏移 | 0x00000036 |
偏移 | 大小(字节) | 含义 | 本文中图片示例值 |
---|---|---|---|
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中文網其他相關文章!