猫の手ツール

Algorithm & UI/UX

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

白に近い背景を明るくするため、RGB の各成分へ同じレベル補正を掛けます。この解説ではホワイトポイント・ブラックポイント・明るさの役割と、背景以外も変化する条件を説明します。

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

現行コードの確認日:

入力
商品写真を選び、白飛ばし・くっきり感・全体の明るさを調整します。
処理
全画素のRGBへ明るさを加え、白・黒の基準値を使ったレベル補正で明暗の幅を広げます。
出力
保存用は長辺最大 2500px の JPEG(品質指定 0.9)。

まず確認する点:背景だけを認識する処理ではありません。白い商品の模様や薄いラベル文字も消える場合があるため、元写真と見比べて調整します。

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

仕組みを読み進めるときは、Canvas APIと画像処理(画像寸法、drawImage、画素と透明度の扱い)、メモリ管理と画像処理(画素バッファ、縮小と容量予算)が参考になります。

1. 加工前データから計算する色調整を実現する「レベル補正」アルゴリズム

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

背景のグレーを白に近づけつつ、商品のディテール(影の部分)をくっきり保つために、ピクセルごとに線形補間を用いたコントラスト拡張を行っています。これにより、背景を白に近づけられます。ただし人物や商品を分離しないため、中間色や明るい商品にも同じ補正が掛かります。

各 RGB 成分に同じ式を適用します。white と black は 0〜100、brightness は −50〜50 です。

function levelChannel(value, white, black, brightness) {
  const whitePoint = 255 - Math.floor(white / 100 * 105);
  const blackPoint = Math.floor(black / 100 * 50);
  const corrected = (value + brightness - blackPoint) * 255 / (whitePoint - blackPoint);
  return Math.max(0, Math.min(255, corrected));
}

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

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

プレビュー時は解像度を意図的に落とした小サイズCanvasに対して演算を行い、requestAnimationFrameで描画タイミングを最適化することで、次の描画タイミングで更新します。フレームレートの実測結果を示すものではありません。

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

ブラウザ上での画像処理、特にiOS Safariなどの環境ではメモリ制限が非常にシビアです。読み込みが終わった Blob URL は解放し、画素を読む Canvas には willReadFrequently を指定しています。どちらも画像のサイズや端末のメモリ上限をなくす処理ではありません。

出力の仕様と確認する点

背景飛ばしを上げるとホワイトポイントは 255 から 150 へ下がり、輪郭くっきりを上げるとブラックポイントは 0 から 50 へ上がります。明るさはレベル補正の前に全 RGB 成分へ加算します。

商品の白い部分も背景と同じように白飛びする場合があります。背景だけをマスクして補正するツールではないので、ラベルの薄い文字や白い布の模様が失われない範囲で調整します。

保存用は長辺最大 2500px の JPEG(品質指定 0.9)。プレビューは縮小版を使います。URL.revokeObjectURL は URL の参照を解放する処理で、画像オブジェクトや Canvas の全メモリを直ちに消す保証ではありません。