猫の手ツール

Algorithm & UI/UX

背景を「白く飛ばす」ロジックの裏側:レベル補正とCanvas高速プレビューの共存

ただ明るくするだけでは商品は薄くなってしまいます。プロの仕上がりを実現するために採用した「レベル補正」アルゴリズムと、スマホでの快適な操作を支える実装の工夫を紐解きます。

1. 非破壊的な輝度調整を実現する「レベル補正」アルゴリズム

本ツールの核心は、単純な明るさ(Brightness)の加算ではなく、画像のホワイトポイント(最も明るい点)とブラックポイント(最も暗い点)を再定義する「レベル補正」にあります。

背景のグレーを白に近づけつつ、商品のディテール(影の部分)をくっきり保つために、ピクセルごとに線形補間を用いたコントラスト拡張を行っています。これにより、中間調の色合いを崩さずに背景だけを「飛ばす」ことが可能になります。

// ピクセルごとのレベル補正計算
function processPixels(data, whiteInput, blackInput, brightInput) {
    const whitePoint = // 独自の計算式でホワイトポイントを決定;
    const blackPoint = // 独自の計算式でブラックポイントを決定;
    const range = whitePoint - blackPoint;
    const factor = range > 0 ? 255 / range : 1;

    for (let i = 0; i < data.length; i += 4) {
        // 全体の明るさ補正を適用
        let r = data[i] + brightInput;
        let g = data[i+1] + brightInput;
        let b = data[i+2] + brightInput;

        // コアロジック:レベル補正によるコントラスト伸長
        // (ピクセル値 - ブラックポイント) * (255 / 有効レンジ)
        data[i]   = (r - blackPoint) * factor;
        data[i+1] = (g - blackPoint) * factor;
        data[i+2] = (b - blackPoint) * factor;
        
        // 0-255の範囲内にクランプ処理
    }
}

2. マルチステージ・キャンバスによる描画の高速化

高解像度なスマホ写真をそのままリアルタイム処理すると、スライダーの動きに描画が追いつきません。これを解決するため、本ツールでは「プレビュー用」と「書き出し用」の二段構えのCanvas構成を採用しています。

プレビュー時は解像度を意図的に落とした小サイズCanvasに対して演算を行い、requestAnimationFrameで描画タイミングを最適化することで、60fpsに近い滑らかな操作感を実現しています。

// プレビューとフルサイズを使い分けるスケーリング戦略
const MAX_EXPORT_SIZE = // システム許容の最大解像度;
const PREVIEW_SIZE = // 快適な操作性を担保するプレビューサイズ;

// 画像読み込み時にあらかじめ縮小率を計算
function setupCanvases(img) {
    // 書き出し用Canvasの設定(高画質維持)
    fullCanvas.width = calculateSize(img.width, MAX_EXPORT_SIZE);
    
    // プレビュー用Canvasの設定(高速化重視)
    previewCanvas.width = calculateSize(img.width, PREVIEW_SIZE);
    
    // 初回描画
    previewCtx.drawImage(img, 0, 0, previewCanvas.width, previewCanvas.height);
}

3. リソースの動的クリーンアップとメモリ管理

ブラウザ上での画像処理、特にiOS Safariなどの環境ではメモリ制限が非常にシビアです。URL.createObjectURLで生成したメモリの即時解放や、CanvasのwillReadFrequently属性の最適化により、連続して複数の画像を処理してもクラッシュしにくい堅牢な設計を施しています。

// メモリリークを防ぐためのリソース管理
async function handleFile(file) {
    let objectUrl = URL.createObjectURL(file);
    const img = new Image();
    
    img.onload = () => {
        // 読み込み完了後、即座にメモリを解放
        URL.revokeObjectURL(objectUrl);
        
        // Canvasのコンテキスト取得時に最適化オプションを付与
        const ctx = canvas.getContext('2d', { willReadFrequently: true });
        
        // 以降の処理
    };
    img.src = objectUrl;
}

Developer's Note

フリマアプリでの「売れやすさ」を技術でサポートしたい、という思いからこのツールを作りました。開発において最も苦労したのは、「誰でも迷わず使えるUI」と「エンジニアも納得する画質補正アルゴリズム」の両立です。

特にスライダーを動かした瞬間に背景がスーッと白くなっていく体験にはこだわりました。単に真っ白にするだけなら閾値を設けるだけで済みますが、それでは商品の境界線が不自然になってしまいます。線形補間をベースにしたレベル補正を採用することで、自然なグラデーションを保ちつつ背景を飛ばす絶妙なバランスを実現できたと感じています。