猫の手ツール

Engineering Memo

ビザ規定をミリ単位で制御する
キャンバス演算の裏側

「ブラウザだけでビザ写真を作る」ために解決すべき最大の課題は、物理的なミリメートル規定とデジタル解像度の正確な変換、そしてモバイル端末のメモリ制約への対応でした。

1. 物理サイズ(mm)を 300dpi 相当のピクセルへ変換する演算

証明写真、特にビザ写真は「横35mm × 縦45mm」といった厳密な物理サイズが求められます。しかし、Web上での canvas.toDataURL 等の処理はピクセルベースです。

本ツールでは、一般的な高品質プリントの基準である 300dpi をベースに変換係数(pxPerMm)を算出しています。特に「L判印刷モード」では、出力用紙の物理サイズから逆算したキャンバス解像度を動的に生成し、各国の規定サイズに合わせた写真を中央揃えで配置するアルゴリズムを組んでいます。

// 物理サイズからピクセル解像度への変換と配置計算
function calculatePrintLayout(canvas, ctx, config) {
    // 印刷用紙(L判)の物理サイズに基づくピクセル係数
    const pxPerMm = // 用紙解像度(px) / 用紙幅(mm) で算出

    const w = config.mmW * pxPerMm;
    const h = config.mmH * pxPerMm;
    
    // 国ごとのサイズ規定に応じたグリッド配置数の決定
    let cols = 1, rows = 1;
    // ... [ここに配置数の分岐ロジックが入ります]
    
    const hGap = // 独自のチューニング値
    const vGap = // 独自のチューニング値
    
    // キャンバス中央に配置するための開始座標を算出
    const startX = (canvas.width - (w * cols + hGap * (cols - 1))) / 2;
    const startY = (canvas.height - (h * rows + vGap * (rows - 1))) / 2;

    // 指定された枚数分をCanvasに描画
    // [ここにループ描画と補助線の描画処理が入ります]
}

2. メモリ負荷を抑える「動的リサイズハック」

近年のスマートフォンのカメラ性能向上により、1枚で10MBを超えるような高解像度画像が増えています。これをそのまま Canvas に読み込んで Cropper.js を動かすと、メモリ不足でブラウザがクラッシュ(落ちる)する原因になります。

本実装では、ファイルを読み込んだ直後に「最大解像度閾値(MAX_SIZE)」を超えているかをチェックし、超えている場合はオフスクリーン・キャンバスを用いて、縦横比を維持したまま縮小処理を行ってからエディタに渡しています。

// 超高解像度画像のメモリ保護リサイズ
async function protectMemoryLimit(processBlob) {
    const MAX_SIZE = // 独自のチューニング値 (例: 3000px程度)
    
    // 画像サイズを事前にチェック
    const img = new Image();
    // ... [画像読み込み処理]

    if (img.width > MAX_SIZE || img.height > MAX_SIZE) {
        // 縦横比を維持した縮小比率の計算
        const ratio = Math.min(MAX_SIZE / img.width, MAX_SIZE / img.height);
        const width = Math.floor(img.width * ratio);
        const height = Math.floor(img.height * ratio);

        // Canvas経由で軽量化したBlobを生成
        const canvas = document.createElement('canvas');
        // ... [ここに描画および Blob 変換処理が入ります]
        return lightweightBlob;
    }
    return processBlob;
}

3. HEIC形式のフロントエンド・リアルタイム変換

iPhoneで撮影された写真の多くは HEIC 形式ですが、多くの証明写真ライブラリや Canvas API はこれを直接扱えません。

ユーザーに「事前にJPGに変換してください」という手間を与えないよう、heic2any ライブラリを動的インポートし、クライアントサイドで JPEG への変換を行っています。これを async/await で制御することで、シームレスなUXを提供しています。

Developer's Note

このツールの開発で最も苦労したのは、実は「iPhoneでの保存体験」の設計でした。ブラウザの仕様上、download 属性が機能しない場合があり、せっかく作った写真が保存できないという課題がありました。

そこで、あえて img タグによるプレビューを前面に出し、「長押し保存」というスマホ独自のUIをユーザーに促す構成にしました。また、すべての処理を端末内で完結させることで、「ビザ申請」という機密性の高い写真を安心して扱えるよう、エンジニアとしての倫理観を実装に落とし込んでいます。