AI Dev Lab
ASCIIBake

ASCIIBake ができるまで — Canvas だけで画像を文字に焼く軽量ピース

Canvas API の createImageBitmap → drawImage → getImageData だけで画像の明暗を読み、 文字ランプにマップする アスキーアート生成ツールの設計記録。 ML / WASM / モデル不要、 純 JS で 12本目を埋めた話。

·decision改善·stage公開中

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 をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。

[ ./related_logs ]

関連する開発ログ

all logs →
ToonCast

ToonCast ができるまで — AnimeGANv2 をブラウザで動かす

AnimeGANv2 の小さな ONNX (約9MB) を onnxruntime-web (単一スレッド WASM=COOP/COEP不要、 color-revive で承認済みライブラリの再利用) で実行。 512x512・[-1,1] 正規化で推論し、 結果を元解像度に戻して表示する設計記録。 写真は端末内処理。

read log →
ColorRevive

ColorRevive ができるまで — onnxruntime-web で白黒写真をカラー化

DeOldify の量子化 ONNX を onnxruntime-web (CDN side-load・単一スレッド WASM=COOP/COEP不要) で実行。 256x256 でモデル推論し、 輝度は元写真・色だけ AI を YCbCr で再合成して輪郭を保つ設計記録。 写真は端末内処理。

read log →
PhotoTwin

PhotoTwin ができるまで — CLIP画像埋め込みで似た写真を見つける

CLIP (Xenova/clip-vit-base-patch32) の image-feature-extraction を transformers.js の CDN ESM で side-load し、 各写真を正規化ベクトル化。 cosine 類似度で重複・似た写真をブラウザ内で検出する設計記録 (新ライブラリ追加なし=what-cam と同じ CLIP の再利用)。

read log →
AkinFind

AkinFind ができるまで — 文章embeddingsで意味検索をブラウザ内に

多言語の文章埋め込みモデル (Xenova/multilingual-e5-small) を transformers.js の CDN ESM で side-load し、 各文を正規化ベクトル化。 cosine 類似度で意味検索と似ている文ペア検出を全て端末内で行う設計記録。

read log →
WhatCam

WhatCam ができるまで — CLIP のゼロショット画像分類をブラウザで動かす

CLIP (Xenova/clip-vit-base-patch32) を transformers.js の CDN ESM で side-load し、 写真と候補ラベルの近さをブラウザ内で計算。 日本語ラベルを英語プロンプトに変換し、 図鑑と自由入力の両モードで「これ何?」を判定する設計記録。

read log →
DepthCast

DepthCast ができるまで — 1枚の写真をAIの深度推定で立体にする

Depth Anything (transformers.js) を CDN ESM で side-load し、 1枚の写真から深度マップを推定。 WebGL2 フラグメントシェーダで深度に比例した視差 (iterative backward parallax) を作り、 赤青アナグリフ / WebM 書き出しまで端末内で完結させた設計記録。

read log →