猫の手ツール

Color Analysis Engine

ピクセル量子化と色差判定による
代表色抽出の最適化手法

画像に含まれる何百万ものピクセルから、デザインに使える「代表色」をどう選ぶか。外部ライブラリを一切使わず、ブラウザの計算リソースだけで完結させるための独自の「量子化ハック」と「サンプリング戦略」を解説します。

1. 剰余を排した「色空間の量子化」アルゴリズム

デジタル画像の色数は膨大です。各ピクセルのRGB値をそのままカウントすると、微細なノイズ(微妙に違うだけの似た色)が分散してしまい、正確な「代表色」が求まりません。本ツールでは、RGB各チャンネルを一定のステップ(区切り)で切り捨てる「量子化」を行い、色をグループ化しています。

メリット: 計算負荷の重いメディアンカット法やk-means法などの本格的なクラスタリングを使わずとも、Mapオブジェクトへの1パスのスキャンだけで、視覚的に支配的なカラーグループを高速に特定できます。

// RGB空間を格子状に分割して色をグループ化するロジック
const step = // 独自のチューニング値(量子化の細かさ);

for (let i = 0; i < data.length; i += 4) {
    const r = data[i], g = data[i+1], b = data[i+2];
    
    // 近似色を同一のキーに集約
    // 下記の計算により、色の微細な差を吸収
    const qR = Math.floor(r / step) * step + Math.floor(step / 2);
    const qG = Math.floor(g / step) * step + Math.floor(step / 2);
    const qB = Math.floor(b / step) * step + Math.floor(step / 2);
    
    // ここにカウント処理が入ります
}

2. ユークリッド距離を用いた「色被り」の動的排除

出現頻度だけで上位の色を選ぶと、パレットが「ほぼ同じ青色ばかり」になってしまうことがよくあります。本ツールでは、候補色を選出する際に、既に選ばれた色との「色差(三次元空間上の直線距離)」を計算し、似すぎている色をスキップするフィルタリングを導入しています。

メリット: この「多様性確保」のロジックにより、画像全体からバランスよく異なる色相の色(例えば、人物の肌色と背景の空の色など)をバランスよく抽出したパレットが生成されます。

// 抽出済みパレットとの色差(距離)を判定するロジック
const minDistanceSq = // 独自のしきい値(バリエーションの広さ);

for (const candidateColor of sortedColors) {
    let isTooClose = false;
    for (const p of palette) {
        // RGBを座標とした三次元空間上のユークリッド距離の自乗を計算
        const dr = p[0] - candidateColor.r;
        const dg = p[1] - candidateColor.g;
        const db = p[2] - candidateColor.b;
        
        if (dr*dr + dg*dg + db*db < minDistanceSq) {
            isTooClose = true;
            break;
        }
    }
    
    if (!isTooClose) {
        // 十分に「違う色」であればパレットに追加
    }
}

3. ブラウザを固めない「サンプリング・リサイズ」戦略

高解像度の写真をそのまま全ピクセル解析すると、JSのメインスレッドが数秒間占有され、UIがフリーズしてしまいます。本ツールでは、色抽出専用の「解析用オフスクリーンCanvas」を作成し、画像を大幅に縮小してからスキャンしています。

メリット: 解析対象を一定数(数百〜数千ピクセル)に抑えることで、モバイルブラウザでも一瞬で解析が完了します。縮小時にブラウザネイティブの補間(Smoothing)が働くため、縮小しても主要な色情報は維持されるというCanvasの特性を逆手に取ったハックです。

// 高速解析のためのサンプリング処理
const maxSize = // 解析に最適な軽量サイズ;
const scale = Math.min(maxSize / img.width, maxSize / img.height, 1);

// 解析専用の超小型Canvasに転写
canvas.width = Math.floor(img.width * scale);
canvas.height = Math.floor(img.height * scale);

// ここでgetImageDataを実行し、ピクセル配列をメモリに展開
// 処理対象を1/100以下に圧縮することで、爆速なレスポンスを実現

Developer's Note

カラーパレット抽出の実装で最も頭を悩ませたのは、「白」と「黒」の扱いです。

普通に解析すると、写真の余白や暗い影など、デザインの参考にはなりにくい「無彩色」が頻繁に1位になってしまいます。そこで本ツールのアルゴリズムには、極端に明るい色(純白に近い)や極端に暗い色(漆黒に近い)、さらに透明度の高いピクセルを解析対象から除外する「重み付け」のガードレールを設けています。

デザイナーが「この画像、いい色だな」と感じる「主役の色」を、いかにノイズに埋もれさせずに救い出すか。数値を何度も微調整して辿り着いた、こだわりのアルゴリズムです。