ASCIIBake ができるまで — Canvas だけで画像を文字に焼く
ASCIIBake は、画像をブラウザ内で アスキーアート (ASCII art)に変換する Webサービスです。 ML も WASM も外部 API も使わず、 Canvas API の createImageBitmap → drawImage → getImageData だけで完結。 メディア処理ラボ 11本柱に続く 12本目、 新カテゴリ 「クリエイティブ / 視覚遊び」 を追加しました。
なぜこの形にしたか
ラボはここまで 「重いライブラリをブラウザで動かす」 ことを示すサービスを中心に出してきました。 voice-scribe / word-warp / bg-snap などはモデル DL に数十〜数百MB かかる。
逆に モデルゼロ・WASM ゼロ・依存ゼロ の軽量ピースがあると、 「ブラウザ完結」 の幅が広がります。 アスキーアート (ASCII art)化は明暗を文字に置き換えるだけの古典タスクで、 Canvas API でそのまま書ける。 「重い処理だけがブラウザ完結の価値ではない」 を 12本目で示す位置づけです。
visual direction
フォスファーグリーン × CRT ブラック (vintage terminal)。
- 真黒の背景 (
#000000) - フォスファーグリーン (
#39ff14) を主色、 text-shadow で残光 - アンバー CRT 残光 (
#ffb000) を副色 - 横方向の CRT スキャンライン (背景パターン)
既存 11本 (mint / amber / violet / cobalt / lime / gold / rose / indigo / emerald / vermilion / uranium) と完全に別軸で、 アスキーアート (ASCII art)の 「ターミナル時代」 メタファーを濃く出しました。
実装の見どころ
1. Canvas で 1工程完結
const bitmap = await createImageBitmap(file)
const canvas = document.createElement("canvas")
canvas.width = cols
canvas.height = rows
const ctx = canvas.getContext("2d")
ctx.drawImage(bitmap, 0, 0, cols, rows)
const data = ctx.getImageData(0, 0, cols, rows).data
ファイルを読む → 縮小して描画 → ピクセルデータ取得。 これで終わり。 ライブラリ 0、 モデル 0、 ネットワーク 0。
2. 文字セルのアスペクト補正
モノスペースフォントは概ね 2:1 (縦長) の比率です。 cols だけ指定して rows をピクセル比でそのまま割ると、 出力 ASCII が縦に潰れて見えます。 cellAspect (charset ごと) を導入して:
const rows = Math.round((cols * srcAspect) / charset.cellAspect)
charset によって最適セル比は変わります — ブロック文字 (█▓▒░) は 1:1 で、 標準ランプ (@%#*+=-:.) は 2:1。 これを charset ごとにメタとして持たせて自動補正。
3. 輝度計算 + アルファ合成
Rec.601 で R/G/B から輝度を計算。 透過部分は白に合成 (PNG の透過は白背景と仮定):
const yLum = (0.299 * r + 0.587 * g + 0.114 * b) / 255
const aN = a / 255
const lum = yLum * aN + (1 - aN) * 1
これで透過 PNG (アイコン等) も意図通り焼ける。
4. 文字セット 4種
- 標準:
@%#*+=-:.(10階調) — ディテール重視、 写真向き - ブロック:
█▓▒░(5階調) — ピクセルアート風、 一見で形が伝わる - ミニマル:
#+-.(5階調) — 印刷向き、 紙でも読める - 二値:
#(2階調) — SVG / 簡素ロゴ向き
charset を変えるたびに即座に再ベイク (useEffect で file / charset / cols / invert を監視)。
5. §8.5.2 invariants
return {
text: lines.join("\n"),
cols, rows, charset,
charsUsed: Array.from(charsUsedSet).sort(),
srcWidth, srcHeight,
inferMs,
}
charsUsed は出力に実際に登場した文字の重複排除リスト。 e2e で 「charsUsed が charset の ramp の strict subset である」 を assert して silent ramp drop を防ぎます。 result panel の data attribute (data-cols / data-rows / data-chars-used) で直接読める。
このサービスから言える事
ラボの 12本柱:
- voice-scribe / clip-cast / bg-snap / text-pluck / pdf-anvil / pixel-lift / pic-flip / mind-cell / beam-drop / word-warp / exif-peel / ascii-bake
カテゴリ:
- 認識 (voice / text / bg)
- 変換 (clip / pdf / pixel / pic / word)
- 生成 (mind)
- 転送 (beam)
- インスペクション (exif)
- クリエイティブ / 視覚遊び (ascii) ← 新
「ブラウザ完結 = 重いライブラリだけ」 ではなく、 軽量ピースもラボの幅を広げる。 12本目はその実証として置きました。
[ ./next_action ]
読んだら、 ASCIIBake を実際に動かす。
この開発ログは ASCIIBake をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。