画像QAプレイブック(視覚リグレッション・色差・バンディング検出)
画像は圧縮やリサイズで副作用が出ます。文字のにじみ、色ズレ、バンディング—— 人力チェックだけでは取りこぼしがち。ここでは自動検査×手動チェックの併用で 壊れにくい運用を作ります。
先に結論(迷ったら)
- 視覚リグレッション(pixelmatch/Jest)で差分を自動検出。
- ΔE色差(CIEDE2000等)で“色だけのズレ”を数値化。
- 背景トグル(白/黒)とRetina/非Retinaの目視で最終保証。
要点(TL;DR)
1. なぜ“視覚リグレッション”が必要か
- 圧縮パラメータ変更(Q/サブサンプリング)で微妙なにじみが出る。
- サイズ/アスペクト比の変更でアンチエイリアスが変化。
- テーマ(ライト/ダーク)で縁の見えが変わる。
- CDNやエンコーダ更新で非互換が起きうる。
2. 基準画像の作り方と環境固定
- ゴールデン画像は同一解像度・同一デバイススケールで取得。
- 動的領域(日時/乱数)はモック化 or マスク。
- CIで実行する場合は同一ランナー/コンテナで再現。
差分が出やすい領域をマスク
ローディングやアニメ部分は、差分比較から除外(mask)して“意味のある差”だけを検出します。
3. 自動検査:pixelmatch & jest-image-snapshot
// __tests__/image.spec.ts(概略) import { toMatchImageSnapshot } from "jest-image-snapshot"; expect.extend({ toMatchImageSnapshot }); it("画像差分が閾値以内", async () => { const png = await loadPng("./dist/hero.png"); // 取得処理は環境に合わせて expect(png).toMatchImageSnapshot({ failureThresholdType: "percent", failureThreshold: 0.2, // 0.2% から調整 customSnapshotIdentifier: "hero", customDiffConfig: { threshold: 0.1 }, // アンチエイリアスのにじみ吸収 }); });
UI文字のにじみは 文字にじみ対策 を参照。UIは4:4:4(または可逆)が安全です。
4. 色差(ΔE)とバンディング検出
- 平均/最大ΔE(CIEDE2000)で“色だけのズレ”を数値化。
- グラデーション帯は局所コントラストをヒートマップ化してバンディング検出。
// 疑似コード:ΔE集計(サンプル) for (const pixel of pixels) { const dE = deltaE(lab(src[pixel]), lab(dst[pixel])); max = Math.max(max, dE); sum += dE; } const avg = sum / pixels.length; if (max > 2.0) { /* 警告: 目立ち得る色差 */ }
グラデ破綻の対処
グラデの段差は AVIF10bit や微小ノイズ付加で抑制(詳細は 破綻対策ガイド)。
5. 背景トグル(白/黒)と透過縁の確認
透過PNG/WebPは背景色で見えが変わります。白/黒の背景トグルで縁のにじみを確認しましょう(対策は 透過フリンジ徹底対策)。
// HTML(例) <button id="bg">背景切替</button> <div id="stage" class="bg-white"> <img src="/img/cutout.png" width="480" height="360" alt="人物の切り抜き" /> </div> // JS(例) document.getElementById("bg")!.onclick = () => { document.getElementById("stage")!.classList.toggle("bg-black"); document.getElementById("stage")!.classList.toggle("bg-white"); };
6. デバイス倍率(Retina/非Retina)比較
文字/細線の見えはデバイススケールで変わります。スクリーンショット取得時にdeviceScaleFactorを切り替え、両方で差分を取ります。
// Playwright(例) import { test, expect } from "@playwright/test"; for (const dpr of [1, 2]) { test(`kv visual @dpr=${dpr}`, async ({ browser }) => { const ctx = await browser.newContext({ deviceScaleFactor: dpr, viewport: { width: 1280, height: 800 } }); const page = await ctx.newPage(); await page.goto("https://example.com"); const shot = await page.screenshot({ fullPage: false }); expect(shot).toMatchSnapshot([`kv-${dpr}x.png`], { maxDiffPixelRatio: 0.002 }); }); }
7. 運用フロー(チェックリスト)
- ゴールデン画像をGitで管理(差分はPRに出す)。
- 動的領域はマスク or モックで安定化。
- しきい値は0.1–0.3%から調整、失敗時はスクショ添付でレビュー。
- Retina/非Retina、白/黒背景の両方でOKを取る。
- 画質破綻は 破綻対策 と 文字にじみ対策 を参照。
- 透過縁は 透過フリンジ対策 を適用。
- 最終は /compare と /compressor で容量/見えの落とし所を確認。
8. まとめ:自動×人力で“壊さない”
自動検査で取りこぼしを無くし、手動で体験の質を保証する。画像運用はこの二段構えで安定します。 仕組みを一度整えれば、日々の差し替えや大量更新でも品質を守れます。