猫の手ツール

Rendering & Performance

Canvasによるチョーク質感の再現と
描画最適化のテクニック

「チョークで描いたような掠れた質感」を、外部テクスチャに頼らずCanvasの標準機能だけで再現。さらに、ランダムな配置を制御して「スライダーを動かしてもガタつかない」UIを実現するための設計思想を解説します。

1. 多段ストロークによる「疑似チョーク質感」の創出

チョークの質感は、不規則な掠れと厚みの変化にあります。本ツールでは、同じテキストを異なる「太さ」と「透明度」で重ねて描画する多段ストローク手法を採用しています。

メリット: 重いビットマップブラシや外部画像を使用せず、Canvasの strokeText のみで完結するため、爆速なレンダリングが可能です。透明度の高い太い輪郭と、不透明度の高い細い輪郭をわずかにずらして重ねることで、チョーク特有の粉っぽさを演出しています。

// チョーク風テキスト描画のコアロジック
function drawChalkText(ctx, text, size, opacity) {
    ctx.save();
    // フォント設定(手書き感を出すためのフォントファミリー選択)
    ctx.font = `bold ${size}px ...`;
    
    // 1段目:掠れを表現する広範囲の淡いストローク
    ctx.lineWidth = // 独自の太さ設定;
    ctx.strokeStyle = `rgba(255, 255, 255, ${opacity * 0.6})`;
    ctx.strokeText(text, 0, 0);
    
    // 2段目:芯となる濃いストロークをわずかにずらして配置
    ctx.translate(1, 1);
    ctx.lineWidth = // 独自の細さ設定;
    ctx.strokeStyle = `rgba(255, 255, 255, ${opacity * 0.9})`;
    ctx.strokeText(text, 0, 0);
    
    ctx.restore();
}

2. 座標配列のキャッシュ化による「決定論的」ランダム配置

文字を写真全体に散りばめる際、レンダリングのたびに Math.random() を呼んでしまうと、スライダー(文字量など)を動かすたびに配置が激しく入れ替わり、不快なガタつき(フリッカー)が発生します。

メリット: あらかじめ数百点分の「座標」「角度」「スケール偏差」を配列としてキャッシュ化。スライダーの値に応じて配列の「参照数」だけを増減させることで、配置の連続性を保ったまま滑らかなパラメータ調整を実現しています。

// 起動時に1回だけランダムデータを生成(キャッシュ化)
const cachedScribbles = Array.from({length: 300}, () => ({
    x: Math.random() * CW,
    y: Math.random() * CH,
    angle: // 独自の回転角範囲,
    baseScale: // 独自のスケール偏差,
    opacityOffset: // 個別の透明度ムラ
}));

// 描画時
function render() {
    // スライダーの値(amount)に基づき、配列から必要な分だけ取り出す
    const numToDraw = Math.floor((amount / 100) * cachedScribbles.length);
    for (let i = 0; i < numToDraw; i++) {
        const s = cachedScribbles[i];
        // キャッシュされた固定座標で描画するため、スライダーを動かしても既存の文字は動かない
        drawAt(s.x, s.y, s.angle ...);
    }
}

3. 高解像度写真に対応する「ガードレール」処理

最新スマホの写真は4000pxを超えることも珍しくありませんが、巨大なCanvasをそのままメモリ上で扱うとモバイルブラウザは即座にクラッシュします。本ツールでは、読み込み時にアスペクト比を維持したまま、処理に最適な上限解像度へリサイズするガードレールを設けています。

メリット: メモリ使用量を一定以下に抑えつつ、書き出し時にはSNS投稿に十分なクオリティを維持。また、オフスクリーンCanvasを用いた二段階処理により、UIのレスポンスを犠牲にしない設計になっています。

// 巨大画像に対する安全な読み込み処理
const MAX_SIZE = // 独自の制限値(クラッシュ防止用);

if (img.width > MAX_SIZE || img.height > MAX_SIZE) {
    // 独自の比率計算により、安全な矩形範囲へスケーリング
    const ratio = Math.min(MAX_SIZE / img.width, MAX_SIZE / img.height);
    // ...一時Canvasでのリサイズ実行
    // 以降、このリサイズ済みデータをソース画像として扱う
}

Developer's Note

この「チョーク落書き風」ツールを作る上で最もこだわったのは、配置される文字の「うるさすぎないランダム感」です。

当初は完全なランダム配置でしたが、それだと文字が重なりすぎて汚く見えることがありました。そこで、キャッシュする座標データにわずかな重み付けを加え、写真の四隅よりも中央付近に少しだけ遊びができるように調整しています。

また、iPhone独自のHEIC形式をブラウザ完結でJPEG変換する仕組みは、ユーザーのプライバシーを第一に考える当サイトのポリシーを象徴する実装です。「一度もサーバーに送らない」という安心感の上で、ぜひ自由な落書きを楽しんでいただければと思います。