1. 程式人生 > >PHP實現生成ascii字元圖片

PHP實現生成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 的畫素塊。整個畫素塊的明亮度,就取塊中每個畫素明亮度的均值。

以上問題都解決了,最後拿一張蒙娜麗莎的微笑來測試:

這裡寫圖片描述

這裡寫圖片描述

效果還不錯:-)。如果終端背景是白色的,可以將表示明亮度的字元序列反過來:

// $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