猫の手ツール

Canvas Interaction & State Management

ビューポートと解像度の差異を埋める高精度スタンプ実装の裏側

SNSで会話をシェアする際、相手のアイコンを「指先一つで、綺麗に」隠す。一見シンプルですが、高解像度なスクリーンショットをWebブラウザで軽快に扱うためには、数学的な座標変換とメモリ管理の工夫が不可欠です。

1. CSSスケーリングを考慮した「双方向座標マッピング」

このツールでは、Canvasの物理サイズ(画像本来の解像度)と、画面上の表示サイズ(CSSによる縮小表示)が一致しません。そのままタップ座標を取得すると、隠したい場所とスタンプの位置が大きくズレてしまいます。

これを解決するため、getBoundingClientRect() で取得した要素の矩形サイズと、Canvas自体の width / height から動的にスケール比率を算出し、タップ位置を再計算するマッピングロジックを実装しています。

// 表示サイズと物理解像度のズレを解消する座標変換
function getMappedPos(evt, container, canvas) {
    const rect = container.getBoundingClientRect();
    
    // スケール比率の算出
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;

    // ビューポート座標をCanvas内部座標へ変換
    return {
        x: (clientX - rect.left) * scaleX,
        y: (clientY - rect.top) * scaleY
    };
}

この実装により、縦に非常に長いスクリーンショットであっても、指でタップした位置に寸分の狂いなく「隠しマーク」を配置することが可能になっています。

2. ImageDataスタックによる軽量Undoシステム

加工ミスを防ぐための「1つ戻る(Undo)」機能は、通常であれば描画命令の履歴(パス)を保持し、再描画を行うことで実現します。しかし、スタンプや色塗りを行うビットマップ処理では、命令が増えるほど再描画コストが増大します。

本ツールでは、ImageData(ピクセルデータ)のフルスナップショットを配列に退避させる方式を採用しました。これにより、どんなに複雑な加工を行った後でも、定数時間で前の状態へ復元できます。

// メモリ消費と利便性のバランスをとった履歴管理
function saveState() {
    // 独自のメモリ制限閾値を設定し、古い履歴から破棄
    if (history.length >= // 独自の履歴上限数) {
        history.shift();
    }
    // 現在のCanvasの全ピクセル情報をスナップショットとして保存
    history.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
}

// 復元処理
function undo() {
    const previous = history.pop();
    if (previous) {
        ctx.putImageData(previous, 0, 0);
    }
}

モバイル端末のメモリ制約を考慮し、履歴の上限数を調整することで、クラッシュを防ぎつつ快適な編集体験を提供しています。

Developer's Note

「モザイクやぼかしはAIで復元されるリスクがある」という事実に基づき、本ツールではあえて不透明な円形や絵文字スタンプで「完全に上書きする」仕様にこだわりました。

また、タップした際の感触を良くするために、CSSアニメーションを用いた「タップエフェクト」をCanvasのレイヤーとは別に実装しています。ユーザーが「今、どこを隠したのか」を視覚的にフィードバックすることで、操作の確信性を高めています。