猫の手ツール

Algorithm & UI/UX

Canvasで実現する「Object-Fit: Cover」と精密なグリッド生成

コンビニのL判写真プリント規格に合わせつつ、ブラウザ内で複数の分割パターンを瞬時に生成する。数式によるレイアウト制御と、メモリ消費を抑えた画像処理の仕組みをエンジニア視点で深掘りします。

1. Canvas上での「仮想Object-Fit: Cover」計算

HTMLの img タグであればCSSで簡単に実現できる「画像の中心を維持したトリミング(Object-Fit: Cover)」ですが、Canvas描画ではすべての座標とサイズを手動で計算する必要があります。

本ツールでは、分割された各セル(枠)のアスペクト比と、アップロードされた画像のアスペクト比を動的に比較。どちらが支配的(長い)かを判定し、はみ出し分を均等にオフセットさせることで、どの分割数でも美しい構図を維持するロジックを実装しています。

// セル内に画像を中央配置するための比率計算ロジック
const imgRatio = originalWidth / originalHeight;
const targetRatio = cellWidth / cellHeight;

let renderW, renderH, renderX, renderY;

if (imgRatio > targetRatio) {
    // 画像の方が横長な場合:高さを基準に調整
    renderH = cellHeight;
    renderW = cellHeight * imgRatio;
    // ここで左右のはみ出しを均等に割って中央に寄せる
    renderX = startX - Math.abs(cellWidth - renderW) / // 独自の中心オフセット係数;
    renderY = startY;
} else {
    // 画像の方が縦長、または同等の場合:幅を基準に調整
    renderW = cellWidth;
    renderH = cellWidth / imgRatio;
    // ここで上下のはみ出しを調整
    renderX = startX;
    renderY = startY - Math.abs(cellHeight - renderH) / // 独自の中心オフセット係数;
}

2. 解像度維持とブラウザクラッシュを防ぐ「二段階スケーリング」

スマートフォンの高画質な写真は、そのままCanvasで扱うと数千万ピクセルに達し、メモリ不足によるブラウザの強制終了を招きます。

そこで、ファイル読み込み直後に「内部処理用の最大解像度」を定義。この閾値を超える巨大画像の場合は、アスペクト比を維持したまま一度縮小処理を行い、メモリ消費を最適化してからプレビューや最終合成に回す「二段階スケーリング」を採用しました。これにより、安定した動作とプリントに耐えうる解像度の両立を可能にしています。

// メモリ負荷を抑えるための画像前処理
const MAX_DIMENSION = // 独自の最大解像度しきい値;

if (img.width > MAX_DIMENSION || img.height > MAX_DIMENSION) {
    // スケール比率の算出
    const scale = MAX_DIMENSION / Math.max(img.width, img.height);
    const w = Math.floor(img.width * scale);
    const h = Math.floor(img.height * scale);

    // 一時的なCanvasで中間生成
    const tempCanvas = document.createElement('canvas');
    tempCanvas.width = w;
    tempCanvas.height = h;
    const tempCtx = tempCanvas.getContext('2d');
    
    // ここで縮小描画処理を行い、以降はこの軽量化されたオブジェクトを使用
    // ...
}

Developer's Note

「推し活」や「手帳デコ」で使われることを想定し、あえてハサミで切るための「白フチ」をオプションではなくデフォルトに近いUIで提供しました。

Canvas実装において地味に苦労したのは、プレビュー画面(CSSでの縮小表示)と、実際の書き出し用Canvas(高解像度の生データ)の同期です。描画ロジックを関数化し、引数に renderScale を持たせることで、等倍表示でも縮小表示でも1pxのズレも出ない単一のソースコードを実現しました。

プライバシーを考慮した「完全オフライン処理」へのこだわりが、結果としてサーバー負荷ゼロ・超高速レスポンスという副産物を生んでいます。