猫の手ツール

Rendering Architecture

Canvas合成モードと静的キャッシュによるレトロ質感の再現

セピア色の「深み」と、時間の経過を感じさせる「傷やノイズ」の質感を、ブラウザで描画する際の合成とキャッシュの工夫を紐解きます。

このツールの入力から出力まで

現行コードの確認日:

入力
写真を選び、古さ・粒子などの効果を調整します。
処理
色の変換、暗い周辺、乱数で作る汚れや粒子を重ね、古写真に見える静止画へ加工します。
出力
入力の長辺を 1500px 以下に縮小し、出力は 1000×1000px の正方形 JPEG(品質指定 0.85)です。

まず確認する点:ノイズは実際の紙の損傷を推定するものではありません。再描画で模様が変わることも含め、文字や顔の読める範囲を確認します。

仕上がりの例

写真をセピア色にし、古写真風の質感を加えた例です。

加工前・加工後
左は愛猫2匹が並ぶ写真。右は茶色い色調と古びた質感を加えた写真。

加工前・加工後

左は愛猫2匹が並ぶ写真。右は茶色い色調と古びた質感を加えた写真。

掲載画像はサイトにある既存の公開作例です。保存済みの資料では運営者の愛猫の写真を使用しています。今回の確認日が、この作例を実行した日を表すわけではありません。

保存済み検証記録の条件を見る

資料の種類は「ツールの出力画像を保存した記録」です。公開作例と保存済み記録で素材や色などが異なる場合があるため、下の数値は保存済み検証記録の条件として読んでください。

記録の入力画像
800×667px (JPEG)
記録の出力画像
1000×1000px (JPEG)
保存済み記録の主な操作・出力条件
画面の項目記録の値現行コードの初期値・範囲
記録のセピア80—
記録の汚れ・傷の強さ60—
記録の写真ズーム100%—

資料ファイルの作成日は実行日として扱っていません。個別端末の処理速度や、ほかの写真に対する検出・加工の精度を測定した資料でもありません。

実装の確認箇所:src/pages/image/century-old/index.astro。以下のコード抜粋と出力条件は、この本番実装に基づいて説明します。

仕組みを読み進めるときは、Canvas APIと画像処理(画像寸法、drawImage、画素と透明度の扱い)、File APIとObject URL(選んだ画像の読み込みとBlob URLの解放)が参考になります。

1. 「Color」と「Multiply」による多段セピア変換

セピア調の画像を作る際、単一の半透明レイヤーを重ねるだけでは、ハイライト部分のディテールが失われ、不自然な「塗りつぶし感」が出てしまいます。本ツールでは、globalCompositeOperation を活用し、2段階の合成を行っています。

メリット: まず color モードで輝度を維持したまま色相だけをアンティーク調に変更し、次に multiply(乗算)で深みのある影を付与しています。これにより、元の写真が持つ質感を殺さずに、重厚なセピア色を実現しています。

既に写真が描かれた ctx に、実装と同じ2段階の色合成を施す例です。amount は 0〜100 を指定します。

function antiqueTone(ctx, amount) {
  const ratio = Math.max(0, Math.min(100, amount)) / 100;
  ctx.save();
  ctx.globalCompositeOperation = 'color';
  ctx.globalAlpha = ratio;
  ctx.fillStyle = '#704214';
  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.globalCompositeOperation = 'multiply';
  ctx.globalAlpha = ratio * 0.2;
  ctx.fillStyle = '#5a3a22';
  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.restore();
}

2. ノイズパターンとスクラッチの「事前生成・キャッシュ化」

ノイズや傷の位置を描画のたびに決め直すと、設定の調整中に模様も変わってしまいます。本ツールではノイズ用のタイルと400本の傷のデータをあらかじめ作り、同じ編集中の描画で再利用します。

メリット: 小さなノイズタイル(Pattern)をオフスクリーンCanvasに事前に描き、メインCanvasには createPattern で敷き詰めることで全画面分の乱数を生成し直さずに模様を描きます。また、傷の座標も配列としてキャッシュし、スライダー操作時はその配列を走査するだけにすることで、傷の位置を保ってプレビューを更新します。

3. 処理画像を縮小する「MAX_SIZE」

現代のスマホ写真は4000pxを超えることも珍しくありませんが、そのような巨大なImageDataをCanvasでピクセル操作しようとすると、メモリ不足でブラウザがクラッシュ(タブのクラッシュ)を引き起こします。

メリット: 画像読み込み時に、アスペクト比を維持したまま、処理可能な上限値(ガードレール)へリサイズするパイプラインを構築しています。これにより、描画に使う画素数を減らします。ただし、画像の読み込みや HEIC 変換もメモリを使うため、クラッシュを完全に防ぐ保証はありません。

出力の仕様と確認する点

入力の長辺を 1500px 以下に縮小し、出力は 1000×1000px の正方形 JPEG(品質指定 0.85)です。元写真の縦横比をそのまま出力するツールではなく、位置と 50〜300% のズームで正方形内の構図を決めます。

劣化量はノイズの濃さ、傷の本数(最大 400 本)、傷の不透明度、周辺減光をまとめて変えます。傷の太さは細・標準・太で倍率が変わり、セピア量とは独立しています。

傷とノイズは人工的な装飾です。年代を鑑定したり、写真の経年変化を予測したりする機能ではありません。背景を動かしすぎて画像のない領域が生じていないか、保存前に四隅を確認します。

制作時に確かめたこと