MangaSnap は、写真を漫画風の絵に変えるブラウザツールです。ぼかし・輪郭抽出・二値化・線の太らせ・トーンの階調化・ハーフトーンのドット・速度線という 7 段の処理を、機械学習モデルを使わず素の JavaScript で組んでいます。
#🎯 「漫画っぽく」を分解する
写真を漫画に見せる要素を書き出すと、実は 3 つしかありませんでした。
- 輪郭が黒い線になっている
- 中間色が消えて、白・黒・網点の 3 種類くらいに減っている
- 効果線(速度線・集中線)が入っている
逆に言うと、この 3 つを作れば漫画に見えます。「画風を学習させる」必要はなく、古典的な画像処理の組み合わせで届くという見立てから始めました。
#🌫️ Bilateral フィルタ ── 輪郭を残したままぼかす
最初の工程がぼかしです。ただし普通のぼかし(ガウシアン)を使うと、輪郭まで一緒にぼやけて次の線抽出が弱くなります。
そこで Bilateral フィルタを使いました。「距離が近い」だけでなく「明るさが近い」画素だけを混ぜるぼかしです。
const intensityDiff = center - n
const wt = Math.exp(-(intensityDiff * intensityDiff) / (2 * sigmaSq))
sum += n * wt
wsum += wt重みが明るさの差の二乗で決まるのがポイントです。肌の中のような近い明るさ同士はよく混ざり、輪郭をまたぐ大きな差はほとんど混ざらない。結果として、平らな面はのっぺりするのに、輪郭は残るという漫画向きの状態が作れます。
計算量は素直に半径の二乗で効いてきます。ここは高速化せず、代わりに処理前に画像を縮小して対応しました。
#✏️ Sobel から線を作る
輪郭は Sobel 演算子で取ります。横方向と縦方向の差分カーネルをかけて、強度を出すだけです。
そのあとが漫画にするための工夫で、二値化してから太らせて(dilation) います。
export function thresholdAndDilate(...)Sobel の生の出力はグレーの薄い線で、そのままだとペン画に見えません。閾値で白黒に落として、周囲 1 画素に広げる。線に太さを持たせるだけで一気にペン入れの絵になります。
#⚫ ハーフトーン ── 網点は「暗さ = 半径」
中間色をどう表すかが漫画表現の核です。印刷では網点(ハーフトーン)を使うので、それを再現しました。
const v = toneMap[idx] / 255 // 1 = 白
if (v >= 0.2 && v <= 0.78) {
const darkness = 1 - v
const r = (size / 2) * darkness * preset.halftoneStrength
if (r < 0.4) continue
ctx.arc(cx, cy, r, 0, Math.PI * 2)
}暗いほどドットの半径を大きくする、それだけです。グリッドを一定間隔で走査して、そのセルの明るさから半径を決めて円を打ちます。
実装しながら 3 つ気づきがありました。
- 明るい側と暗い側にはドットを打たない (
v >= 0.2 && v <= 0.78)。真っ白と真っ黒はベタで表現するほうが漫画らしく、全域に打つと灰色の絵になります。 - 線の上には重ねない (
if (lineMask[idx]) continue)。ペン線の上に網点が乗ると、線が汚れて見えます。 - 半径 0.4 未満は打たない。1px 未満の円は Canvas ではただの薄い点になり、ノイズに見えます。
この 3 つの足切りを入れる前と後で、「漫画っぽさ」が明確に変わりました。
#💨 速度線は中心からの放射
効果線は、指定した中心点から外に向かって線を引くだけです。ユーザーが画面をクリックすると中心が移動するようにしました。中心をどこに置くかで絵の意味が変わるので、自動で決めずに触らせる判断です。
#🎭 4 つのプリセット
少年・少女・劇画・アメコミの 4 つを用意しました。違いは主にパラメータの組み合わせです。
| プリセット | 線の太さ | トーン階調 | 網点サイズ | 速度線 |
|---|---|---|---|---|
| 少年 | 太い | 少ない | 中 | 多い |
| 少女 | 細い | 多い | 細かい | 少ない |
| 劇画 | 太い | 少ない | 粗い | なし |
| アメコミ | 中 | 極端に少ない | 粗い | 中 |
同じパイプラインでも、6 つのつまみの値だけで画風が変わります。 学習モデルを 4 つ持つ代わりに、パラメータ表を 4 つ持つという解き方です。
#😓 苦労したところ
「変換されたことが分かる」出力にする。 最初の版は処理が上品すぎて、元の写真と並べても違いが分かりませんでした。線を太らせ、階調を減らし、網点を粗くする ── やりすぎに振ってから戻すほうが、結果的に良いところに着地しました。
BEFORE / AFTER を並べて出す。 変換系のツールは、出力だけ見せても効果が伝わりません。元画像と結果をポラロイド風に並べて置く構成にしました。
大きい画像で固まる。 Bilateral が半径の二乗で効くので、4000px 級を投げると数十秒かかります。処理前に長辺を縮めてから走らせ、その旨を画面に出しています。
#🔭 今後の拡張
- スクリーントーンの種類を増やす(現在は円ドットのみ。線トーン・砂目)
- 集中線(放射ではなく一点集中)と背景の吹き飛ばし
- コマ割りテンプレートへの流し込み
- Web Worker で処理を逃がして、大きい画像でも UI を止めない
#💡 このサービスから言えること
「AI でやること」に見える課題が、1970〜80 年代の画像処理で届くことがあります。 Sobel は 1968 年、Bilateral フィルタは 1998 年、網点にいたっては印刷技術です。モデルのダウンロードも推論待ちもなく、写真は端末から出ません。
そして**「らしさ」は足し算ではなく引き算で出る**ということ。色を減らし、階調を減らし、中間を捨てる。情報を削るほど漫画に近づきました。
[ ./next_action ]
読んだら、 MangaSnap を実際に動かす。
この開発ログは MangaSnap をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。