Algorithm & UI/UX
Canvasで実現する「Object-Fit: Cover」と精密なグリッド生成
コンビニのL判写真プリント規格に合わせつつ、ブラウザ内で複数の分割パターンを生成する。数式によるレイアウト制御と、メモリ消費を抑えた画像処理の仕組みをエンジニア視点で深掘りします。
このツールの入力から出力まで
現行コードの確認日:
- 入力
- 同じ写真を小さく何枚も印刷したいときに、1枚の写真と分割数・用紙の向き・白フチの有無を入力します。
- 処理
- セルの縦横比に合わせて写真を中央から切り取り、同じ写真を2・4・8枠へ繰り返し描きます。枠ごとのclipが、隣の写真へのはみ出しを防ぎます。
- 出力
- 出力は1051×1500px、ヨコ向きは1500×1051pxのJPEGです。
まず確認する点:被写体が中央から外れていると各セルで同じ箇所が切れます。まず顔や文字の位置を確認し、印刷機の自動拡大とは分けて考えます。
仕上がりの例
猫の写真を8枚並べた、L判分割プリントの例です。
掲載画像はサイトにある既存の公開作例です。保存済みの資料では運営者の愛猫の写真を使用しています。今回の確認日が、この作例を実行した日を表すわけではありません。
保存済み検証記録の条件を見る
資料の種類は「既存の公開作例」です。公開作例と保存済み記録で素材や色などが異なる場合があるため、下の数値は保存済み検証記録の条件として読んでください。
1枚の猫の写真を、横向きのL判台紙に横4列・縦2段の8枚で並べた例です。印刷後に切り分けて、小さな写真として使えます。
- 記録の入力画像
- 800×1066px (JPEG)
- 記録の出力画像
- 1500×1051px (JPEG)
この作例には操作値の記録がないため、同じ設定で再現できるとは断定しません。本文の数値は現行の実装仕様です。
資料ファイルの作成日は実行日として扱っていません。個別端末の処理速度や、ほかの写真に対する検出・加工の精度を測定した資料でもありません。
実装の確認箇所:src/pages/print/split/index.astro。以下のコード抜粋と出力条件は、この本番実装に基づいて説明します。
仕組みを読み進めるときは、Canvas APIと画像処理(画像寸法、drawImage、画素と透明度の扱い)、File APIとObject URL(選んだ画像の読み込みとBlob URLの解放)が参考になります。
1. Canvas上での「仮想Object-Fit: Cover」計算
HTMLの img タグであればCSSで簡単に実現できる「画像の中心を維持したトリミング(Object-Fit: Cover)」ですが、Canvas描画ではすべての座標とサイズを手動で計算する必要があります。
本ツールでは、分割された各セル(枠)のアスペクト比と、アップロードされた画像のアスペクト比を動的に比較。どちらが支配的(長い)かを判定し、はみ出し分を均等にオフセットさせることで、各枠の中心に写真を配置します。ただし顔や文字の位置は認識しておらず、中央以外の被写体が切れる場合があります。
枠を埋める描画寸法の計算です。imgW、imgHは画像寸法、x、y、w、hは配置枠です。描画時には枠の矩形でclipして、はみ出す部分を隠します。
function coverRect(imgW, imgH, x, y, w, h) {
const scale = Math.max(w / imgW, h / imgH);
const width = imgW * scale;
const height = imgH * scale;
return {x: x + (w - width) / 2,
y: y + (h - height) / 2, width, height};
} 2. 処理画素数を抑える「二段階スケーリング」
スマートフォンの高画質な写真は、そのままCanvasで扱うと数千万ピクセルに達し、端末によってはメモリ不足やブラウザの終了につながることがあります。
そこで、ファイル読み込み直後に「内部処理用の最大解像度」を定義。この閾値を超える巨大画像の場合は、アスペクト比を維持したまま一度縮小処理を行い、メモリ消費を最適化してからプレビューや最終合成に回す「二段階スケーリング」を採用しました。元画像が小さい場合には拡大しても細部は増えません。縮小処理はメモリ負荷を抑えるためのもので、印刷品質やブラウザの安定性の保証ではありません。
出力の仕様と確認する点
1枚の写真を同じ内容で2・4・8枠に繰り返すツールです。複数の異なる写真を読み込むコラージュや、写真全体を分割して大きく貼り合わせる処理ではありません。
タテ向きでは2分割が1列×2行、4分割が2列×2行、8分割が2列×4行です。ヨコ向きの2・8分割では列と行を入れ替えます。白フチを付ける場合は、用紙の短辺の2%を各枠の内側の余白に使います。
出力は1051×1500px、ヨコ向きは1500×1051pxのJPEGです。入力は長辺3000pxを上限に縮小し、プレビューは出力寸法の半分で描きます。プリンタの拡大縮小やフチなし印刷による切取りはツール外の処理なので、重要な顔や文字を端ぎりぎりに配置しないよう確認してください。
