猫の手ツール

Canvas Compositing & Filtering

写真をインクに「刷り込む」ためのレイヤー合成テクニック

ただ写真をセピアにするだけでは、「紙にインクが染み込んだ」という質感は生まれません。本ツールで採用している、乗算合成と動的なインク滲みを再現するアルゴリズムについて解説します。

1. 乗算(Multiply)とカラー合成の分離構造

このツールの最大の特徴は、インクの「濃淡(明暗)」と「色(セピア)」を別々のレイヤーとしてCanvasに合成している点です。

まず写真をグレースケール化して「インクの乗り」を抽出し、それを globalCompositeOperation = 'multiply' で羊皮紙の背景に重ねます。これにより、紙のテクスチャの暗い部分と写真が美しく馴染みます。その上からさらにカラー情報を重ねることで、深みのあるアンティーク調を実現しています。

// ステップ1:グレースケール(インクの濃淡)を背景と合成
ctx.save();
ctx.globalCompositeOperation = 'multiply';
ctx.globalAlpha = // スライダーに基づく不透明度;
ctx.drawImage(offCanvas, 0, 0); // 処理済みのインクレイヤー
ctx.restore();

// ステップ2:セピア色を上書き(色成分のみを適用)
ctx.save();
ctx.globalCompositeOperation = 'color';
ctx.fillStyle = // 選択された紙の種類に応じたセピア色;
ctx.fillRect(dx, dy, dw, dh);
ctx.restore();

この「明暗」と「色」の分離により、ユーザーがインクの濃さを調整しても、セピアの絶妙な色合いが崩れることなく、常に安定したクオリティを保てるようになっています。

2. 軽量な「疑似ブラー」によるインク滲みの再現

紙にインクが染み込むと、エッジがわずかにぼやけます。これを標準の filter: blur() で行うと非常に重くなるため、本ツールでは「画像を低解像度化してから戻す」ハック「微細な多重描画」を組み合わせています。

// インクの滲みを生成する軽量アルゴリズム
if (blurValue > 0) {
    // 1. 一時的なCanvasで画像を極小サイズにダウンスケール
    const scale = // 滲み強度に基づくリサイズ比率;
    tinyCtx.drawImage(originalImg, 0, 0, w * scale, h * scale);
    
    // 2. メインCanvasへ戻す際、位置をわずかにずらしながら多重描画
    offCtx.globalAlpha = // 独自の透過バランス;
    const offsets = [[0,0], [1,1], [-1,-1], [1,-1], [-1,1]];
    for (let [ox, oy] of offsets) {
        offCtx.drawImage(tinyCanvas, ox * spread, oy * spread, targetW, targetH);
    }
}

この手法のメリットは、高価な畳み込み演算(ガウスぼかし等)を回避しつつ、アナログ特有の「インクが広がるムラ」を非常に低いCPU負荷で再現できる点にあります。

3. モバイル環境向けの「メモリ・セーフティ」処理

数千万画素の写真をブラウザでそのまま処理すると、特にiPhone等ではメモリ上限に達してクラッシュします。

本ツールでは、画像の読み込み直後に長辺をチェックし、一定の閾値を超える場合にはアスペクト比を保ちながら動的にリサイズし、内部的な処理負荷を一定以下に保つガードレールを実装しています。

Developer's Note

「デジタル写真をアナログなものへ変換する」という課題に対し、Photoshopのような高度なフィルタを使わず、Canvasの標準機能だけでいかに軽量に、かつ情緒的に表現するかに注力しました。

特にビネット(周辺減光)効果をグラデーションで加える際も、単なる黒ではなく「焦げ茶色の透過」を使うことで、羊皮紙の古びた雰囲気を高めています。コードをコピペするだけでなく、こうした「色と合成モードの組み合わせ」の妙を楽しんでいただければ幸いです。