猫の手ツール

Algorithm & UI/UX

ブラウザ完結型画像処理と、印刷用Canvasグリッドレンダリング

「履歴書写真メーカー」では、プライバシー保護のために全処理をクライアントサイドで完結させています。入力画像を縮小し、印刷用の用紙画像へ写真を配置する実装を紹介します。

このツールの入力から出力まで

現行コードの確認日:

入力
履歴書に使う写真を選び、顔の位置と余白を調整します。
処理
指定比率の枠へ写真を切り取り、JPEGの保存画像にします。
出力
Web用は600×800pxのJPEG(品質指定0.85)、印刷用は1051×1500pxのシートへ同じ写真を6枚並べたJPEG(品質指定0.95)です。

まず確認する点:拡大で元写真の細部は増えません。印刷時の実寸と、画像のピクセル寸法を区別して確認します。

実装の確認箇所:src/pages/resume/photo/index.astro。以下のコード抜粋と出力条件は、この本番実装に基づいて説明します。

仕組みを読み進めるときは、Canvas APIと画像処理(画像寸法、drawImage、画素と透明度の扱い)、File APIとObject URL(選んだ画像の読み込みとBlob URLの解放)が参考になります。

1. スマホのメモリ限界を回避する「動的スケーリング」

最近のスマートフォンの写真は非常に高解像度ですが、そのままCanvasで扱うと、モバイルブラウザのメモリ上限を超えてクラッシュする原因になります。本ツールでは、読み込み時にアスペクト比を維持したまま、長辺3000pxまで一度ダウンスケールする前処理を挟んでいます。

画像の画素数を抑えてからCropper.jsへ渡します。元のピントや明るさを改善する処理ではなく、縮小後の鮮明さや端末での動作は入力画像に左右されます。

2列×3行の配置座標を計算する例です。cw、chは用紙Canvas寸法、pw、phは写真寸法、gapX、gapYは写真間の間隔です。単位はすべてピクセルです。

function photoPositions(cw, ch, pw, ph, gapX, gapY) {
  const x0 = (cw - (pw * 2 + gapX)) / 2;
  const y0 = (ch - (ph * 3 + gapY * 2)) / 2;
  return Array.from({length: 6}, (_, i) => ({
    x: x0 + (i % 2) * (pw + gapX),
    y: y0 + Math.floor(i / 2) * (ph + gapY)
  }));
}

2. 2×3配置を実現するCanvasグリッドレンダリング

「印刷用(L判)」モードでは、1枚のL判用紙にちょうど6枚の写真が並ぶように配置する必要があります。ここでは、Canvasのマトリックス座標計算を用いて、余白(Gap)と各写真の位置を動的に算出しています。

単に並べるだけでなく、中央寄せ(Center Align)のオフセット計算を行うことで、用紙中央へ写真群を寄せます。実際の印刷位置やフチなし印刷時の拡大はプリンタ側の設定に依存するため、切れないことを保証する処理ではありません。

3. CSSによる「規格準拠UX」の実現

顔の位置と余白の目安を確認しながら切り抜けるように、本ツールでは、Cropper.jsの内部要素にCSSの擬似要素(::before/::after)を注入し、視覚的な「ガイド線」をオーバーレイさせています。

複雑な数学計算をユーザーに強いるのではなく、ガイドを目安に顔の位置を調整できる、フロントエンドならではの工夫です。

出力の仕様と確認する点

切り抜きは600×800pxです。Web用はこの画像をJPEG品質0.85で保存し、印刷用は1051×1500pxの用紙Canvasへ同じ写真を6枚配置して品質0.95で書き出します。

ガイド線は配置の目安で、顔の自動検出や写真の適否判定ではありません。背景、顔の向き、表情、撮影時期や提出データの容量は、応募先の指定を別途確認してください。

L判での印刷を想定していますが、画像の保存だけで実物の30×40mmが保証されるわけではありません。印刷サービスのサイズ設定や拡大縮小を確認し、重要な提出では仕上がりを測ってから使用します。