猫の手ツール

Algorithm & UI/UX

CanvasによるRPGステータス画面の動的生成技術

ブラウザ上での画像編集を滑らかにするため、本ツールではCanvas 2D APIを駆使した独自のレンダリング・アルゴリズムを採用しています。サーバーを一切介さないプライバシー重視の設計と、自由度の高い操作性を両立した実装の工夫を解説します。

1. ポートレートの動的座標計算とクリッピング

ユーザーがアップロードした画像を「特定の枠(ポートレート枠)」の中に収めつつ、ドラッグやズームで自由に調整できる機能は、単純なCSSでは実現が難しい部分です。Canvasの clip() メソッドと、独自の座標変換ロジックを組み合わせることで、直感的な操作感を実現しています。

なぜこの実装なのか?

画像の絶対座標ではなく、枠の中心を基準とした相対的な変位を計算することで、ズーム時でも「見ている場所」がズレにくい安定した挙動を提供しています。また、描画の度にコンテキストの状態を保存・復元(save/restore)することで、他のUI要素(ステータスバーなど)にクリッピングの影響を与えない設計にしています。

// ポートレート描画のコア・ロジック(コンセプト)
function drawPortrait(ctx, img, config) {
    ctx.save();
    ctx.beginPath();
    // 描画範囲を特定の矩形に制限
    ctx.rect(PORTRAIT_X, PORTRAIT_Y, PORTRAIT_W, PORTRAIT_H);
    ctx.clip();
    
    // スライダー値とドラッグ座標に基づいた座標計算
    const zoomLevel = config.baseScale * (config.userZoom / 100);
    
    // 常に画像が枠内での相対位置を維持するための計算
    // ここに座標変換のアルゴリズムが入ります
    const drawX = /* 独自のオフセット計算 */;
    const drawY = /* 独自のオフセット計算 */;

    ctx.drawImage(img, drawX, drawY, img.width * zoomLevel, img.height * zoomLevel);
    ctx.restore();
}

2. メモリ消費を抑える「読み込み時リサイズ」ハック

近年のスマートフォンの写真は非常に高解像度であり、そのままCanvasで扱うとメモリ不足によるクラッシュや、レンダリングの遅延を招きます。本ツールでは、画像をメモリ上に読み込んだ瞬間に、内部処理に最適な解像度へ自動リサイズする前処理レイヤーを設けています。

なぜこの実装なのか?

最終的な出力品質を保ちつつ、編集中のフレームレート(FPS)を維持するためです。一度適切なサイズにダウンサンプリングされた ImageBitmapCanvas オブジェクトを使い回すことで、スクロールやドラッグ操作が非常に軽量になります。

// 画像の事前最適化処理
async function optimizeImage(img) {
    const THRESHOLD = // 独自のチューニング値;
    if (img.width > THRESHOLD || img.height > THRESHOLD) {
        const ratio = // アスペクト比を維持した計算処理;
        const offscreen = document.createElement('canvas');
        offscreen.width = img.width * ratio;
        offscreen.height = img.height * ratio;
        
        const ctx = offscreen.getContext('2d');
        ctx.drawImage(img, 0, 0, offscreen.width, offscreen.height);
        
        // 縮小した画像をソースとして再定義
        return await loadImageFromDataURL(offscreen.toDataURL('image/jpeg', 0.9));
    }
    return img;
}

3. レイヤード・レンダリングによる即時プレビュー

名前や職業、各種パラメータが変更されるたびに、Canvas全体を再描画する「リアクティブな描画システム」を構築しています。フォントのレンダリングにおいて、視認性を高めるためのシャドウ処理や、ステータスバーの動的な長さ計算を一括で処理しています。

なぜこの実装なのか?

ゲームのような「リアルタイム感」を出すためには、入力値の変化を即座にCanvasへ反映させる必要があります。HTML要素を重ねるのではなく、すべてを一枚のCanvasに描くことで、最終的な「画像保存」時の整合性を完璧に保つことができます。

// ステータスバーの描画ロジック
function drawStatBar(ctx, x, y, value, maxValue, color) {
    const BAR_WIDTH = // 独自のレイアウト値;
    
    // 背景(溝)の描画
    ctx.fillStyle = '#333';
    ctx.fillRect(x, y, BAR_WIDTH, // 独自の高さ);

    // 数値に基づいた割合の計算
    const percent = Math.min(1, value / maxValue);
    
    // バー本体の描画
    ctx.fillStyle = color;
    // ここにイージングや比率計算に基づく描画処理が入ります
    ctx.fillRect(x, y, BAR_WIDTH * percent, // 独自の高さ);
}

Developer's Note

RPGのステータス画面という「ワクワク感」を、いかにWebツールとしてストレスなく提供できるかにこだわりました。特にスマホユーザーが多いため、HEIC形式の変換処理(heic2any)を動的にインポートして読み込み速度を落とさないようにしたり、iPhoneの仕様を考慮した長押し保存のガイダンスを用意したりと、技術的な解決策をUI/UXに落とし込んでいます。画像処理という重くなりがちなタスクを、完全にクライアントサイドで完結させることで、ユーザーに「安全で速い」体験を届けることができました。