將圖片轉換為字符畫是經典的程式設計練習,也是終端展示圖像、生成藝術效果的實用技術。本文基於 PHP GD 庫實現:灰度映射字符集、彩色 HTML 輸出、圖片縮放適配、透明度處理、效能優化,支援 PNG/JPG/GIF/BMP 多格式。
// 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
?>
<?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);
}
?>
| 優化點 | 說明 |
|---|---|
| 降低解析度 | 終端顯示通常 80-200 字符寬度足夠,無需處理原圖全解析度 |
| 快取灰度表 | 預計算 256 灰度值對應字符索引,避免迴圈中重複除法 |
| 字串連接優化 | 使用陣列收集行最後 implode,避免字串重複複製 |
| 跳過透明像素 | 透明區域直接輸出空格,減少色彩計算 |
| 批次處理 | 視頻逐幀轉換時,複用 GD 資源、並行處理 |