ウェブ画像色プロファイル検査パイプライン:不一致検出と自動標準化
色ズレは “モニター差” や “閲覧者環境” の曖昧さに押し付けられがちですが、制作~配信工程で ICC プロファイルが混在 し、かつ 変換影響を測定していない ことが根因であるケースが大半です。特に P3 / AdobeRGB / 未埋込 / 破損ICC が同居すると、一部ブラウザは sRGB 仮定で解釈し彩度の過剰/不足やコントラスト圧縮を引き起こします。
本稿は CI での収集 → 自動解析 → ΔE 影響測定 → 判断ポリシー適用 → 最終標準化 を再現可能な 5 ステップに分解し、属人的 “目視OK” を廃止する最低限の判断装置を提供します。箇条書きはすべて 何を/なぜ/間違えるとどうなるか の 3 点セットで補足し読み応えを確保しています。
要点(TL;DR)
- 収集: 追加/更新画像だけを差分抽出
無関係ファイルを毎回全量解析すると CI 時間が肥大化し、開発者が検査をスキップし始める。PR 差分限定で“習慣化” を守る。 - 解析: ICC 種別 / 埋込有無 / ビット深度 / 色空間を列挙
単なる P3/AdobeRGB 判別でなくビット深度 (8/10/16) を併記することで変換後の階調破綻リスクを可視化。 - 測定: 代表サンプリングで sRGB 変換後 ΔE2000 を計算
全ピクセルは過剰。分層サンプリング (明/中/暗・彩度高) で統計的精度と速度を両立。 - 判定: ΔE 平均 2 / 最大 5 を閾値に“例外 (keep-P3)” を最小化
閾値が緩すぎると P3 氾濫 → ブラウザ処理 + CDN 変換コスト増。厳しすぎると写真品質劣化クレーム。 - 標準化: 例外以外を sRGB + ICC 明示埋込
ICC strip だけだと一部ツールで非意図的再変換の恐れ。明示埋込で挙動決定性を確保。 - 可視化: JSON + HTML レポートで差異/分布を保存
履歴追跡により突然の P3 比率増加を早期検知。ガバナンスの継続性を担保。
1. 背景:混在放置のコスト
ブランドカラーや UI コンポーネントの色は “可視的一貫性” がユーザの信頼感と可読性を支えます。ICC が混在すると ①レビュー環境での見え違い → 誤指摘、②写真のみ彩度が過剰/不足 → トーン崩壊、③運用後に気付いてリサイズ済みアセットを再生成 といった遅延コストが累積します。
さらに P3 を無秩序に許容すると CDN 側が想定外の色空間変換を行い(または行わず)差分再現性が失われ、ビフォー/アフター比較テストが不安定になります。従って “最小の例外集合” を定量で固定化することが持続的な QA を実現する要件です。
1.1 計測条件の最小セット
これ以上の全画素測定は“安心感”以外の価値が薄く、CI 時間を押し上げて文化的継続性を損ねるため意図的に行いません。測定の再現性>網羅性 を原則にします。
2. パイプライン全体像(詳細と失敗例)
- 収集 (diff scan) – Git 差分から対象拡張子 (jpg/png/webp/avif) を抽出。
失敗例: リポジトリ全量を毎回走査 → CI 6分超 → 開発者がローカル実行を回避し品質退行。 - 解析 (metadata) – exiftool で ICC 名称 / primaries / profileID、sharp でビット深度。
理由: プロファイル“無”と“sRGB埋込”は挙動が異なるブラウザがあるため統一判断には両方の識別が必須。 - 試算 (ΔE sampling) – sRGB へ仮変換後サンプリング差分。
間違い: 全画素比較で時間肥大 / 乱数 seed 不統一で PR ごと値が揺れて議論がループ。 - 判定 (policy) – しきい値 > 対象画像のみ keep-P3 ラベル。
過剰例: “写真は全部 P3” とすると UI アイコンだけ sRGB で統一感崩壊。 - 変換 (standardize) – sRGB + 意図的 ICC 埋込 + 再圧縮(品質プリセット統一)。
誤り: strip のみ → 解析ツール再実行時に“未埋込”として再度候補化。 - レポ (persist) – JSON 差分 + HTML ヒートマップ。
価値: 増減トレンドを時系列で可視化し“いつから崩れたか” を 30 秒で追跡。
3. ΔE 計測 & 変換スクリプト例
以下は擬似コード。実装では deltaE
関数を CIEDE2000 で実装し、サンプリングは明度ヒストグラムに基づき層化抽出します。ビット深度が 16 の場合は先に 8bit 量子化して統一条件で比較(深度差によるノイズを除外)。
// pseudo: profile-check.ts import sharp from 'sharp'; import { deltaE } from './deltaE'; async function analyze(path: string) { const input = sharp(path); const meta = await input.metadata(); // ... ICC 抽出 (meta.icc) 仮想 const bufSrgb = await input.toColorspace('srgb').toBuffer(); // サンプリングして ΔE return { path, profile: meta.icc ?? 'unknown', deltaEAvg: 1.2, deltaEMax: 3.9 }; }
4. 判定ポリシー設計(分類の理由とリスク)
- 分類: photograph / ui-asset / gradient
性質の異なる色域要求を混合すると平均化した閾値が機能しない。写真は高彩度局所差分、UI はブランドカラー再現性、gradient はバンディング抑制が主眼。 - 写真のみ P3 保持検討 (ΔE平均 > 2)
平均 2 以下は肉眼識別しづらく帯域/処理コストとのトレードオフで統一した方が説明コストが下がる。 - UI: sRGB 強制 + 差異通知
ブランドカラーはガバナンス上 “単一値” が望ましい。P3 で僅差に鮮やかでも再現負債が大きい。 - 破損ICC: 再提出要求
破損を無視して変換すると比較不能な差異が発生し後続の回帰テストで常にノイズ化。
5. CI統合ポイント(なぜこの順序か)
- PR で profile-stats.json 生成
差分単位で “品質メタ” を残すことで後追い調査を PR ID から即参照可能。 - 閾値超過 → コメント bot
人手レビュー前に機械的説明が付くと議論が“主観” で脱線しない。 - keep-P3 ラベルで再変換スキップ
例外確定後に再度同じ議論を避け、時間あたりの判断重複をゼロ化。 - main マージ時: 一括変換 + キャッシュ bust
PR 単位で都度変換すると “差分だけP3” の過渡状態が生まれ A/B 不整合を誘発。
6. 可視化レポート要素(観点と得られる確証)
- ICC 分布 (円/折れ線)
P3 比率の月次推移を見れば“無秩序拡大” を早期に抑制できる。 - ΔE ヒストグラム
しきい値付近に集中しているならば分類やサンプリング粒度が粗い兆候。 - 上位差異 before/after サムネ
“人間が気付ける差か” を 3 秒で判断。文章説明より視覚化が早い。 - 例外承認一覧 (理由/提出者/日付)
ガバナンス証跡。監査時に“なぜ残したか” を説明できる。
6.1 よくある誤判定と影響
- 高彩度 UI アイコンを写真扱い
差分は僅少なのに例外を温存 → 例外集合が雪だるま式に肥大。 - 破損ICC を sRGB と誤認
再現性が崩れ、後日の ΔE 再計測で外れ値としてノイズ化。 - サンプリング seed 不固定
PR ごと ΔE が揺れ“しきい値議論” が永遠に収束しない。
7. 公開前チェック(観点+失敗時リスク)
- 破損 ICC 0 件
残存すると後続バッチで常に再検出され“ノイズ耐性” が下がる。 - profile-stats.json コミット済
欠落するとトレンド分析が不連続になり回帰点特定が困難。 - 例外ラベル画像のみ P3 残存
ラベルなし P3 はガバナンス逸脱で即再変換対象。 - ΔE 分布が閾値側に尖っていない
尖りは分類/サンプリングが最適化されていないサイン。 - 視覚差分サムネ生成済
レビューで“本当に見える差か” の口頭議論を省き 1クリック判断。
8. まとめ
色管理は “ツールの話” ではなく 判断コスト最小化の仕組み設計 です。測定 → 一貫した閾値 → 例外最小化 → 可視化履歴 の 4 層を固めれば、規模拡大後も追加画像は数十 ms で品質評価できます。箇条書きそれぞれに “なぜ/どう失敗するか” を添付することで、暗黙知を文章化し新人オンボーディング時間を削減できます。