Algorithm & UI/UX
デジタルに「にじみ」の体温を宿す:
Canvas合成による水彩画シミュレーション
水彩画の美しさは、紙の上で色が予期せぬ方向へ広がる「滲み(しがみ)」と、乾く際に縁に色が溜まる「濡れ縁」にあります。これをブラウザ上のCanvasだけでいかに軽量に、かつエモく再現するかが本ツールの最大の挑戦でした。
1. 「濡れ縁」を再現する多層ブレンドアルゴリズム
単に画像をぼかすだけでは、単なるピントのボケた写真になってしまいます。水彩画特有の「エッジ部分にインクが溜まる現象」を再現するために、このツールでは「乗算(Multiply)」と「わずかなオフセット」を組み合わせたレイヤー構造を採用しています。
具体的には、元の画像に対してブラーをかけたレイヤーを、数ピクセルずつ拡張・オフセットさせて「乗算」で重ね合わせます。これにより、色の濃い部分が周囲に滲み出しつつ、境界線が深みを増す「ウェットエッジ」の質感をシミュレートしています。
// 滲みエフェクトのコアコンセプト
function renderWatercolorBleed(ctx, img, settings) {
const { bleed, softness } = settings;
// 1. ベースのソフトブラー描画
ctx.filter = `blur(${/* 独自の係数による計算 */}) saturate(${/* スタイル別の補正値 */})`;
ctx.globalAlpha = // 独自の透過度調整値;
ctx.drawImage(img, 0, 0);
// 2. 「乗算」による滲みの重なり
ctx.globalCompositeOperation = 'multiply';
const offset = // スライダー値に基づくオフセット計算;
// わずかに拡大・移動させて重ねることで「濡れ縁」を表現
ctx.filter = `blur(${/* 強調されたブラー値 */}) contrast(${/* 独自のコントラスト比 */})`;
ctx.globalAlpha = // 独自の乗算強度;
ctx.drawImage(img, -offset, -offset, width + offset * 2, height + offset * 2);
} 2. 動的テクスチャ生成による「紙の凹凸」シミュレーション
水彩画のリアリティを支えるもう一つの要素は、水彩紙のザラザラとした質感です。外部から重いテクスチャ画像を読み込むのではなく、実行時に小さな「ノイズキャンバス」をメモリ上に生成し、それを「オーバーレイ(Overlay)」モードでタイル状に繰り返して合成しています。
この手法のメリットは、解像度に合わせてノイズの粒度を動的に変更できる点と、ネットワークトラフィックをゼロに抑えられる点にあります。
// 紙の質感を生成するロジック
function createPaperTexture(intensity) {
const texCanvas = document.createElement('canvas');
// パフォーマンス維持のため、小さなタイルサイズ(256px等)で生成
texCanvas.width = texCanvas.height = 256;
const tCtx = texCanvas.getContext('2d');
const imageData = tCtx.createImageData(256, 256);
// ピクセルごとにランダムな輝度を注入
for (let i = 0; i < imageData.data.length; i += 4) {
// ここに独自のノイズ生成アルゴリズムが入ります
const gray = /* ランダム計算 */;
imageData.data[i] = imageData.data[i + 1] = imageData.data[i + 2] = gray;
imageData.data[i + 3] = intensity * /* チューニング係数 */;
}
tCtx.putImageData(imageData, 0, 0);
return texCanvas;
}
// 描画時にオーバーレイでパターン適用
const pattern = ctx.createPattern(texCanvas, 'repeat');
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, width, height); Developer's Note
実装において最もこだわったのは、スライダーを動かした時の「ぬるぬる感(リアルタイム性)」です。高解像度のまま全ての処理を行うとブラウザが悲鳴を上げるため、内部的には最大解像度を1500pxに制限し、描画ループには requestAnimationFrame を活用して入力を間引いています。
また、iPhoneユーザーが画像を保存できないというWebツール特有の弱点(ブラウザの仕様)に対して、あえて「長押し保存」を誘導するモーダルをUIに組み込んだのは、システムエンジニアとしての泥臭い工夫です。技術は「動くこと」だけでなく、「ユーザーの手元に結果が届くこと」までがセットですから。