猫の手ツール

Algorithm & UI/UX

デジタルに「にじみ」の体温を宿す:
Canvas合成による水彩画シミュレーション

水彩画の美しさは、紙の上で色が予期せぬ方向へ広がる「滲み」と、乾く際に縁に色が溜まる「濡れ縁」にあります。これをブラウザ上のCanvasだけでいかに軽量に、かつエモく再現するかが本ツールの最大の挑戦でした。

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

現行コードの確認日:

入力
写真を選び、滲みや色の設定を調整します。
処理
縮小した画像の重ね合わせと色の加工で、水彩風の柔らかい境界を作ります。
出力
出力は長辺最大 1500px の JPEG(品質指定 0.92)。

まず確認する点:紙への水の流れを物理計算する処理ではありません。滲みを強くしたときに、細い線や顔の部品がどこまで残るか見ます。

仕上がりの例

写真ににじみを加え、水彩風の質感にした例です。

加工前・加工後
左は横になった茶トラの愛猫の写真。右は輪郭と色をにじませた猫の画像。

加工前・加工後

左は横になった茶トラの愛猫の写真。右は輪郭と色をにじませた猫の画像。

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

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

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

記録の入力画像
800×805px (JPEG)
記録の出力画像
800×805px (JPEG)
保存済み記録の主な操作・出力条件
画面の項目記録の値現行コードの初期値・範囲
記録で選択されていた項目鮮やかな水彩—
滲み(にじみ)の量50初期値 50 / 範囲 0〜100
エッジの柔らかさ25初期値 25 / 範囲 1〜50
紙の質感(粒状感)30初期値 30 / 範囲 0〜100

記録では「この質感で完成!」から完成表示へ進んでいます。初期値と記録値を比べてから、効果を1項目ずつ変えると作用を確認しやすくなります。

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

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

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

1. 「濡れ縁」を再現する多層ブレンドアルゴリズム

単に画像をぼかすだけでは、単なるピントのボケた写真になってしまいます。水彩画特有の「エッジ部分にインクが溜まる現象」を再現するために、このツールでは「乗算(Multiply)」と「わずかなオフセット」を組み合わせたレイヤー構造を採用しています。

具体的には、元の画像に対してブラーをかけたレイヤーを、数ピクセルずつ拡張・オフセットさせて「乗算」で重ね合わせます。これにより、色の濃い部分が周囲に滲み出しつつ、境界線が深みを増す「ウェットエッジ」の質感をシミュレートしています。

にじみ層の描画範囲を求める例です。画像の四辺を均等に広げる処理で、顔料の流れる方向を計算するものではありません。

function bleedRect(width, height, bleed) {
  const offset = bleed / 10;
  return { x: -offset, y: -offset, width: width + offset * 2, height: height + offset * 2 };
}

2. 動的テクスチャ生成による「紙の凹凸」シミュレーション

水彩画のリアリティを支えるもう一つの要素は、水彩紙のザラザラとした質感です。外部から重いテクスチャ画像を読み込むのではなく、実行時に小さな「ノイズキャンバス」をメモリ上に生成し、それを「オーバーレイ(Overlay)」モードでタイル状に繰り返して合成しています。

この手法のメリットは、256×256px の小さなノイズを繰り返せる点と、紙模様の画像ファイルを追加取得しない点にあります。

出力の仕様と確認する点

にじみ 0〜100 は画像の広がりを 0〜10px に変えます。0 でも乗算の層は透明度 0.4 で描かれるため、にじみ層が完全に無効になる設定ではありません。柔らかさ 1〜50 は基準ぼかし半径 0.1〜5px に対応します。

ぼかしと彩度には Canvas の filter を使うため、ブラウザの対応状況によって効果が異なる場合があります。requestAnimationFrame は描画タイミングを予約しますが、別スレッドへの移動や重複予約の取消しをする処理ではありません。

紙の模様は再描画ごとに作る 256px のランダムノイズです。出力は長辺最大 1500px の JPEG(品質指定 0.92)。筆のストローク生成や実際の紙における顔料拡散を計算するものではありません。