猫の手ツール

Algorithm & UI/UX

サーバーレスで実現する「見切れない」画像加工の実装ロジック

Instagramのプロフィール画面で「顔が見切れてしまう」という悩み。これを解決するために開発した当ツールは、プライバシーを守るためサーバーを一切介しません。iPhone特有のHEIC変換から、メモリ制限下での安定化処理まで、クライアントサイドで完結させるための工夫を解説します。

1. モバイル安定化のための「プリ・サンプリング」処理

近年のスマートフォンで撮影された写真は解像度が極めて高く、そのままCanvasで編集・描画しようとすると、モバイルブラウザのメモリ上限に達してクラッシュすることがあります。本ツールでは、編集開始前に「編集に十分かつ負荷の少ない解像度」へダウンサンプリングを行っています。

メリット: メモリ制限の厳しいiOS版Safariなどでも動作が安定し、プレビューの更新やボタン操作のレスポンスが劇的に向上します。

// 編集可能な最大解像度を定義
const STABILITY_THRESHOLD = // 独自のチューニング値;

function optimizeImageSize(img) {
    let targetW = img.width;
    let targetH = img.height;

    if (targetW > STABILITY_THRESHOLD || targetH > STABILITY_THRESHOLD) {
        // アスペクト比を維持してスケーリング
        const scale = STABILITY_THRESHOLD / Math.max(targetW, targetH);
        targetW = Math.floor(targetW * scale);
        targetH = Math.floor(targetH * scale);

        // オフスクリーンCanvasでダウンサンプリング実行
        // ここで低負荷なリサイズ処理を行います
    }
}

2. 動的インポートによる「オンデマンド」なHEIC変換

iPhoneの標準形式であるHEICは、ブラウザがネイティブでサポートしていない場合があります。しかし、変換ライブラリはファイルサイズが大きいため、初期ロードに含めるとサイトが重くなります。本ツールでは、HEICファイルが選択された時のみライブラリを読み込む「動的インポート」を採用しています。

メリット: 通常のJPG/PNGユーザーには余計なJSを読み込ませず、iPhoneユーザーが来た時だけ賢く対応することで、表示速度と機能性を両立しています。

async function handleHeicFile(file) {
    if (file.name.match(/\.(heic|heif)$/i)) {
        // 必要になった瞬間だけライブラリをロード
        const heicModule = await import('heic2any');
        const heic2any = heicModule.default || heicModule;

        const converted = await heic2any({
            blob: file,
            toType: "image/jpeg",
            quality: // 独自のチューニング値
        });
        return converted;
    }
    return file;
}

3. Canvasによる「1:1 センタリング」アルゴリズム

正方形のキャンバスを作成し、そこに元の画像を配置する際の計算です。画像の長辺を基準に「正方形のサイズ(squareSize)」を決定し、短辺側の不足分を「余白」として均等に分配するロジックを実装しています。

メリット: 縦長・横長どんな比率の画像でも、一切トリミングすることなく「ちょうど真ん中」に、おしゃれな余白付きで配置されます。

function drawCenteredSquare(ctx, img, bgColor) {
    const w = img.width;
    const h = img.height;
    
    // 長辺をベースにキャンバスサイズを決定
    const squareSize = Math.max(w, h);
    
    // 背景色(白/黒)を塗りつぶし
    ctx.fillStyle = bgColor;
    ctx.fillRect(0, 0, squareSize, squareSize);

    // 画像を中央に配置するための座標オフセット計算
    // ここに(squareSize - w) / 2 といったセンタリング計算が入ります
    
    ctx.drawImage(img, offsetX, offsetY, w, h);
}

Developer's Note

「写真を切りたくない」というユーザーの切実な願いを、いかにストレスなく叶えるかに注力しました。特にこだわったのは、一切のデータをサーバーへ送らないという設計です。

プライベートな写真を扱うツールだからこそ、クライアントサイドで処理を完結させることは譲れないポイントでした。HEIC変換をブラウザ内で行うのは技術的に負荷が高い挑戦でしたが、動的インポートとメモリ管理を徹底することで、スマホでもサクサク動く「道具」としての完成度を高められたと感じています。