猫の手ツール

Algorithm & UI/UX

グリッド分割の数学:クライアントサイドで実現する正確な画像切り出し

Instagramのプロフィールを1枚のアートのように魅せる「グリッド投稿」。本ツールは、サーバーを一切介さず、ユーザーのブラウザ上で正確に画像を3・6・9分割します。高解像度画像の安定処理から、直感的なプレビューの実装まで、フロントエンドにおける画像処理の工夫を解説します。

1. メモリ不足を防ぐための「プリ・スケーリング」処理

スマートフォンの高画質な写真は、解像度が数千ピクセルを超えることが一般的です。これらをそのままCanvasで頻繁に再描画したり、一気に分割処理を行うと、特にモバイルブラウザではメモリ上限に達して強制終了(クラッシュ)するリスクがあります。本ツールでは、読み込み時に独自のしきい値に基づいてリサイズを行う「プリ・スケーリング」を実装しています。

メリット: 低スペックな端末やメモリ制限の厳しいiOS Safariでも動作を安定させつつ、編集中のレスポンス(描画速度)を劇的に向上させることができます。

// handleFile 内の安定化処理
async function processStability(img) {
    const MAX_RESOLUTION = // 独自のチューニング値;
    let w = img.width;
    let h = img.height;

    if (w > MAX_RESOLUTION || h > MAX_RESOLUTION) {
        const scale = MAX_RESOLUTION / Math.max(w, h);
        // ここにリサイズ計算とオフスクリーンCanvasによる再生成が入ります
        // 品質を保ちつつ、メモリ負荷を軽減
        return await resizeToSafeBuffer(img, w * scale, h * scale);
    }
    return img;
}

2. 「逆引き」座標計算による動的プレビュー

ユーザーがプレビュー上で枠をドラッグしたり、スライダーでズーム(拡大・縮小)したりする際、Canvas上の「どこを切り抜くか」をリアルタイムに計算し続ける必要があります。本ツールでは、画像の中心点を軸にした相対的な座標計算を採用し、ズーム時でも「見ている場所」が変わらないように工夫しています。

メリット: 直感的な操作感を実現。また、分割数(3/6/9)を変更しても、可能な限り現在の選択範囲を維持することができます。

// ズーム・移動時の座標更新ロジック
function updateCropGeometry(val) {
    // 1. 現在の中心座標を保持
    const centerX = cropState.x + cropState.size / 2;
    const centerY = // 縦方向の中心計算;

    // 2. スライダー値に基づき、新しい切り抜きサイズを算出
    const newSize = // ここに画像サイズと比率に基づく計算が入ります;

    // 3. 中心座標から逆算して、左上(x, y)を再配置
    cropState.x = centerX - newSize / 2;
    cropState.y = centerY - (newSize / 3 * cropState.rows) / 2;

    // 4. 境界チェック(画像の外に出ないようにクランプ)
    cropState.x = Math.max(0, Math.min(cropState.x, maxLimitX));
}

3. ネガティブスペースを活用した視覚的フィードバック

編集画面では、切り抜かれる範囲を「明るく」、除外される範囲を「暗く(半透明の黒)」表示しています。これはCanvasの `globalAlpha` を使うのではなく、一度全域を塗りつぶした後に `clearRect` で選択範囲だけを「打ち抜く」という手法をとっています。

メリット: 重ね塗りによる色味の変化を防ぎ、ユーザーが「どの部分が書き出されるか」を最も鮮明かつ正確に認識できるようになります。

function renderCanvas() {
    // 1. 全域に背景画像を低輝度で描画(またはオーバーレイ)
    ctx.drawImage(originalImg, 0, 0);
    ctx.fillStyle = 'rgba(0, 0, 0, // 独自の透明度値)';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 2. 選択範囲だけをクリアして再描画(ハイライト効果)
    ctx.clearRect(cropState.x, cropState.y, cropState.size, totalH);
    ctx.drawImage(originalImg, cropState.x, cropState.y, cropState.size, totalH, cropState.x, cropState.y, cropState.size, totalH);

    // 3. 分割ガイドラインの描画
    // ここに点線(setLineDash)を用いたグリッド線の描画処理が入ります
}

Developer's Note

このツールを開発する際、技術的な課題よりも「ユーザーの不安」をどう解消するかに時間を割きました。インスタのグリッド投稿は、1枚でも投稿順を間違えるとプロフィールがバラバラになってしまうという、非常に緊張感のある作業です。

そこで実装したのが、完了画面での「投稿順ガイド」です。単に画像を生成するだけでなく、HTMLの `grid` を使って実際のプロフィール画面に近いプレビューを出し、「右下(保存1番)から投稿してください」というメッセージを強調しました。

また、外部サーバーへ一切画像を送信しないという設計も、ユーザーのプライバシーを守るためのエンジニアとしてのこだわりです。HEIC変換や高解像度処理をブラウザだけで完結させるのは、メモリ管理の面で骨が折れましたが、その分「安心して使える道具」に仕上がったと自負しています。