首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP GD 實現圖片轉 ASCII 藝術字 / 彩色字符畫完整教程

     編輯:PHP 相關知識

PHP GD 實現圖片轉 ASCII 藝術字 / 彩色字符畫完整教程

將圖片轉換為字符畫是經典的程式設計練習,也是終端展示圖像、生成藝術效果的實用技術。本文基於 PHP GD 庫實現:灰度映射字符集、彩色 HTML 輸出、圖片縮放適配、透明度處理、效能優化,支援 PNG/JPG/GIF/BMP 多格式。

核心原理

  1. 灰度計算:`Gray = 0.2126×R + 0.7152×G + 0.0722×B` (ITU-R BT.709 標準,符合人眼亮度感知)
  2. 字符映射:將 0-255 灰度值映射到字符陣列,字符視覺密度從高到低排序
  3. 縮放適配:終端字符寬高比約 1:2,需按比例縮放圖片避免變形
  4. 彩色輸出:HTML `` 保留原圖色彩

字符集選擇

// 70 級灰度字符集 (視覺密度由高到低)
$chars = '$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,"^`\'. ';
// 簡化 10 級 (適合小尺寸)
$chars10 = '@%#*+=-:. ';
// 區塊字符 (適合高解析度)
$blocks = ' ░▒▓█';

完整實現類

<?php
class ImageToAscii {
    private $chars = '$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,"^`\'. ';
    private $maxWidth = 200;  // 最大輸出寬度 (字符數)
    private $maxHeight = 100; // 最大輸出高度
    private $fontRatio = 0.5; // 字符寬高比 (寬/高),終端約 0.5
    private $colored = false; // 是否輸出彩色 HTML
    private $backgroundColor = '000000'; // HTML 背景色
    
    public function __construct(array $options = []) {
        foreach ($options as $k => $v) {
            if (property_exists($this, $k)) $this->$k = $v;
        }
    }

    public function convert($imagePath, $outputPath = null) {
        if (!file_exists($imagePath)) throw new Exception("檔案不存在: $imagePath");
        
        $src = $this->loadImage($imagePath);
        if (!$src) throw new Exception("無法載入圖片: $imagePath");
        
        $srcW = imagesx($src);
        $srcH = imagesy($src);
        
        // 計算目標尺寸 (保持長寬比)
        $targetW = min($this->maxWidth, $srcW);
        $targetH = (int)($srcH * $targetW / $srcW * $this->fontRatio);
        if ($targetH > $this->maxHeight) {
            $targetH = $this->maxHeight;
            $targetW = (int)($srcW * $targetH / $srcH / $this->fontRatio);
        }
        
        // 建立縮放後圖片
        $dst = imagecreatetruecolor($targetW, $targetH);
        imagecopyresampled($dst, $src, 0, 0, 0, 0, $targetW, $targetH, $srcW, $srcH);
        imagedestroy($src);
        
        // 生成輸出
        $output = $this->generateAscii($dst);
        imagedestroy($dst);
        
        if ($outputPath) {
            file_put_contents($outputPath, $output);
            return "已保存至: $outputPath";
        }
        return $output;
    }

    private function loadImage($path) {
        $info = getimagesize($path);
        if (!$info) return false;
        
        $mime = $info['mime'];
        switch ($mime) {
            case 'image/jpeg': return imagecreatefromjpeg($path);
            case 'image/png': return imagecreatefrompng($path);
            case 'image/gif': return imagecreatefromgif($path);
            case 'image/bmp': return imagecreatefrombmp($path); // PHP 7.2+
            case 'image/webp': return imagecreatefromwebp($path); // PHP 7.0+
            default: return false;
        }
    }

    private function generateAscii($image) {
        $w = imagesx($image);
        $h = imagesy($image);
        $charCount = strlen($this->chars);
        $unit = 256.0 / $charCount;
        
        $lines = [];
        for ($y = 0; $y < $h; $y++) {
            $line = '';
            for ($x = 0; $x < $w; $x++) {
                $rgb = imagecolorat($image, $x, $y);
                $colors = imagecolorsforindex($image, $rgb);
                
                // 處理透明度 (PNG/GIF)
                if (isset($colors['alpha']) && $colors['alpha'] >= 127) {
                    $line .= $this->colored 
                        ? '<span style="color:#' . $this->backgroundColor . '"> </span>'
                        : ' ';
                    continue;
                }
                
                $r = $colors['red'];
                $g = $colors['green'];
                $b = $colors['blue'];
                
                // 灰度計算
                $gray = (int)(0.2126 * $r + 0.7152 * $g + 0.0722 * $b);
                $charIndex = min($charCount - 1, (int)($gray / $unit));
                $char = $this->chars[$charIndex];
                
                if ($this->colored) {
                    $hexColor = sprintf('%02x%02x%02x', $r, $g, $b);
                    $line .= "<span style=\"color:#{$hexColor}\">" . htmlspecialchars($char) . "</span>";
                } else {
                    $line .= $char;
                }
            }
            $lines[] = $line;
        }
        
        $output = implode("\n", $lines);
        
        if ($this->colored) {
            return "<pre style=\"font-family:monospace;line-height:1.1;background:#{$this->backgroundColor};padding:10px;display:inline-block;white-space:pre\">{$output}</pre>";
        }
        return $output;
    }
}

// 使用範例
// 1. 終端文字輸出
$converter = new ImageToAscii(['maxWidth' => 160, 'colored' => false]);
echo $converter->convert('photo.jpg');

// 2. 彩色 HTML 輸出 (可直接在網頁顯示)
$converter = new ImageToAscii(['maxWidth' => 200, 'colored' => true, 'backgroundColor' => '1a1a2e']);
$html = $converter->convert('photo.png', 'ascii_art.html');
echo $html;

// 3. 命令列工具
// php ascii.php input.jpg --width=120 --color --output=art.html
?>

命令列工具版 (ascii.php)

<?php
// ascii.php
$shortopts = "w:h:c:o:";
$longopts = ["width:", "height:", "color:", "output:", "chars:", "bg:", "help"];
$options = getopt($shortopts, $longopts);

if (isset($options['help']) || !isset($options[0])) {
    echo "用法: php ascii.php <圖片路徑> [選項]\n";
    echo "  -w, --width=N      最大字符寬度 (預設 160)\n";
    echo "  -h, --height=N     最大字符高度 (預設 80)\n";
    echo "  -c, --color=0|1    彩色 HTML 輸出 (預設 0)\n";
    echo "  -o, --output=FILE  輸出檔案 (預設 stdout)\n";
    echo "  --chars=STRING     自定義字符集\n";
    echo "  --bg=HEX           HTML 背景色 (預設 000000)\n";
    exit(1);
}

$imagePath = $options[0];
$opts = [
    'maxWidth' => (int)($options['w'] ?? $options['width'] ?? 160),
    'maxHeight' => (int)($options['h'] ?? $options['height'] ?? 80),
    'colored' => (bool)($options['c'] ?? $options['color'] ?? false),
    'backgroundColor' => $options['bg'] ?? '000000',
];
if (isset($options['chars'])) $opts['chars'] = $options['chars'];

try {
    $converter = new ImageToAscii($opts);
    $output = $converter->convert($imagePath);
    
    if (isset($options['o']) || isset($options['output'])) {
        $outFile = $options['o'] ?? $options['output'];
        file_put_contents($outFile, $output);
        echo "已保存至: $outFile\n";
    } else {
        echo $output;
    }
} catch (Exception $e) {
    fwrite(STDERR, "錯誤: " . $e->getMessage() . "\n");
    exit(1);
}
?>

效能優化建議

進階應用場景

  • 視頻字符動畫:FFmpeg 擷取幀 → 並行轉換 → 終端播放 (需 ANSI 轉義碼控制幀率)
  • 圖片驗證碼:字符畫作為背景干擾,疊加文字驗證碼
  • 藝術風格化:自定義字符集 (Emoji、漢字、區塊字符) 生成不同風格
  • 無障礙替代文字:為視覺障礙用戶生成圖片文字描述
  • ASCII 視頻流:WebSocket 推送字符幀,瀏覽器 `
    ` 實時渲染

常見問題

# 1. 字體寬高比導致圖片扁平/拉長
# 調整 fontRatio: 終端通常 0.5,Web 等寬字體約 0.6
# 實際測試: printf '█\n█' | cat -A 查看字符實際比例

# 2. 中文字符佔雙倍寬度
# 字符集使用中文時,maxWidth 需減半,或字符寬度按 2 計算

# 3. 記憶體不足 (大圖)
# 分塊處理或先縮略圖: imagecopyresampled 到較小尺寸再處理

# 4. GIF 動畫只轉第一幀
# 使用 imagecreatefromgif 只讀取第一幀,需逐幀處理:
$frameCount = 0;
while ($frame = imagecreatefromgif($path)) {
    // 處理每幀...
    $frameCount++;
}

圖片轉字符畫結合了圖像處理、色彩學、終端渲染知識,是優秀的綜合練習專案。從簡單的灰度映射出發,可延伸至彩色 HTML、視頻字符動畫、藝術風格化等豐富應用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
優化點說明
降低解析度終端顯示通常 80-200 字符寬度足夠,無需處理原圖全解析度
快取灰度表預計算 256 灰度值對應字符索引,避免迴圈中重複除法
字串連接優化使用陣列收集行最後 implode,避免字串重複複製
跳過透明像素透明區域直接輸出空格,減少色彩計算
批次處理視頻逐幀轉換時,複用 GD 資源、並行處理