猫の手ツール

Algorithm & UI/UX

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

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

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

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

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

// 解像度に基づいた相対的な文字サイズの算出ロジック
function calculateCanvasMetrics(canvas, hasSubText) {
    const w = canvas.width;
    const h = canvas.height;

    // 画像の短辺または長辺に基づいたベースサイズを決定(独自の計算比率)
    const baseScale = Math.min(w, h) * // 独自のチューニング係数;

    return {
        mainFontSize: Math.floor(baseScale),
        subFontSize: Math.floor(baseScale * // 独自のサブテキスト比率),
        shadowBlur: Math.max(4, Math.floor(w * // 独自の影の拡散係数)),
        bandHeight: hasSubText ? Math.floor(h * // 2行時の比率) : Math.floor(h * // 1行時の比率)
    };
}

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

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

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

// 座標変換を用いたリボン描画のコア・コンセプト
function drawRibbonLayout(ctx, canvasWidth, canvasHeight) {
    ctx.save();

    // 1. 原点を配置位置(左上付近)に移動
    ctx.translate(canvasWidth * // 横方向オフセット, canvasHeight * // 縦方向オフセット);

    // 2. 指定の角度(例:-45度)で回転
    ctx.rotate(-Math.PI / 4);

    // 3. 原点を中心として帯とテキストを描画(0, 0 を中心に描画可能)
    // ここに塗りつぶしとテキスト充填のロジックが入ります

    ctx.restore(); // 座標系をリセット
}

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

iPhoneで撮影された写真はHEIC形式であることが多く、そのままではHTMLの <canvas><img> で扱うことができません。本ツールでは、heic2any ライブラリを dynamic import することで、必要時のみリソースをロードし、ユーザーの端末内で即座にJPEGへ変換しています。

これにより、プライバシーを守るために「外部サーバーに送信しない」という制約を維持したまま、iPhoneユーザーの利便性を最大化しています。

// HEIC形式の動的検知とクライアントサイド変換
async function processHeicImage(file) {
    if (file.name.toLowerCase().endsWith('.heic')) {
        const heic2any = (await import('heic2any')).default;
        
        // サーバーに送らず、ブラウザのメモリ内で変換処理を実行
        const convertedBlob = await heic2any({
            blob: file,
            toType: "image/jpeg",
            quality: // 独自の圧縮品質設定
        });
        
        return Array.isArray(convertedBlob) ? convertedBlob[0] : convertedBlob;
    }
    return file;
}

Developer's Note

このツールを開発する上で最もこだわったのは、「直感的な操作感」と「プロの仕上がり」の両立です。フリマ出品はスピードが命。ユーザーが数値を入力するのではなく、画像サイズを自動解析して最適なバランスを提案する「お節介なほどの自動化」を内部で行っています。

また、Canvasでの描画は一度解像度を落としてしまうと二度と戻せません。プレビューは軽量なCanvasで、最終書き出しはオリジナルの解像度を維持したフルサイズCanvasで、という二段階のレンダリングパイプラインを構築することで、画質を一切妥協しない仕様にしました。