猫の手ツール

Algorithm & UI/UX

Canvas座標変換と動的スケーリングによる「高品質バナー生成」の自動化

本ツールでは、サーバーを介さずブラウザ上のCanvas 2D APIのみを用いて、高解像度な画像合成を行っています。単なる文字の描画にとどまらず、画像解像度に依存しない「見た目の等価性」を担保するためのアルゴリズムについて解説します。

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

現行コードの確認日:

入力
商品写真と短い説明文字を入力し、文字の位置などを調整します。
処理
写真の上へ文字と縁取り・背景の表現をCanvasで合成します。
出力
入力は長辺 2000px 以下に縮小し、その処理画像の寸法の JPEG(品質指定 0.95)で保存します。

まず確認する点:長い文が商品を覆わないか確認します。商品を自動判別して避ける処理ではありません。

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

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

1. 画像解像度に依存しない動的タイポグラフィ計算

ユーザーがアップロードする画像は、数百万画素のスマホ写真からウェブ用の小さな画像まで様々です。これらに対して一律のピクセル数でフォントサイズを指定すると、高解像度画像では文字が極小になり、低解像度では巨大化してしまいます。

本実装では、描画対象となるCanvasの width および height を基準とした「相対的な比率」を用いて、フォントサイズ、帯の高さ、さらにはドロップシャドウのボケ足までを動的に算出しています。

帯の高さはサブ文字の有無で変わります。実装ではメイン文字を基準とし、サブ文字はその半分のサイズです。

function listingTextSizes(width, height, hasSub, ribbon = false) {
  const main = Math.floor((ribbon ? width : Math.min(width, height)) * 0.08);
  return { bandHeight: Math.floor(height * (hasSub ? 0.22 : 0.16)),
    mainFontSize: main, subFontSize: Math.floor(main * 0.5) };
}

2. コンテキスト座標変換を用いた「左上リボン」描画アルゴリズム

「送料無料」などの文字を左上に斜めに配置する処理は、単純な座標指定では困難です。本ツールでは、Canvasの translate(平行移動)と rotate(回転)を組み合わせることで、数学的な複雑さを隠蔽しつつ柔軟なレイアウトを実現しています。

具体的には、描画の原点をリボンの中心予定地に移動させ、そこを軸に回転させることで、常に「水平な長方形」を描くコードのままで斜めの配置を可能にしています。

3. heic2any によるクライアントサイドHEIC自動変換

このツールでは、HEICを選んだ場合はブラウザ内で別の画像形式に変換してから編集します。本ツールでは、heic2any ライブラリを dynamic import することで、必要時のみリソースをロードし、ユーザーの端末内でJPEG へ変換しています。

これにより、プライバシーを守るために「外部サーバーに送信しない」という制約を維持したまま、iPhoneユーザーの同じ画面で編集できるようにしています。変換の成否や時間は端末とファイルに依存します。

出力の仕様と確認する点

帯は上・下・中央・左上リボンから選びます。通常の帯は画像高の 16%、サブ文字があれば 22%。リボンは −45° 固定で、位置を自由にドラッグする機能ではありません。

メインとサブはそれぞれ1行として描き、自動折り返しや長文に合わせた縮小はありません。商品名や説明を短くし、文字が画像端から欠けないよう確認します。売上を予測・保証する機能ではありません。

入力は長辺 2000px 以下に縮小し、その処理画像の寸法の JPEG(品質指定 0.95)で保存します。元ファイルの解像度を無制限に維持する出力ではありません。写真と文字は1枚に統合されます。