【JavaScript】画像から特定の色を数える際、条件式で判定すべきか、テーブルを引くべきか？

# 問題設定

入力される画像にあらかじめ決めた色があるかを判定したい。
今回は、R成分、G成分、B成分すべてについて、あらかじめ決めた色の対応する成分との差 (の絶対値) があらかじめ決めたしきい値以下であることを、「あらかじめ決めた色である」という。

# 比較方法

今回は、[JSBench.me](https://jsbench.me/) を用いて比較を行った。
画像データは、キャンバスから取得できる [ImageData](https://developer.mozilla.org/ja/docs/Web/API/ImageData) を扱うことを想定し、[Uint8ClampedArray](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray) で表した。
また、単にあるかを判定するのではなく、あらかじめ決めた色であるピクセルの数を数える処理で比較した。

# 実験

## 前処理を行わない場合

2種類の手法に共通のコード。画像サイズと探す色を設定し、ランダムな画像データを用意する。

```javascript:Setup JavaScript
const width = 1280;
const height = 720;

const targetR = 128;
const targetG = 128;
const targetB = 128;
const targetSize = 2;

const data = new Uint8ClampedArray(width * height * 4);
for (let i = 0; i < data.length; i++) {
	data[i] = ~~(Math.random() * 256);
}
```

条件式で判定することにより探す色を数えるコード。

```javascript:if
let count = 0;
for (let i = 0; i < data.length; i += 4) {
	if (
		targetR - targetSize <= data[i] && data[i] <= targetR + targetSize &&
		targetG - targetSize <= data[i + 1] && data[i + 1] <= targetG + targetSize &&
		targetB - targetSize <= data[i + 2] && data[i + 2] <= targetB + targetSize
	) {
		count++;
	}
}
```

テーブルを引くことにより探す色を数えるコード。
それぞれの色が探す色かを表すテーブルを作る処理が入っている。

```javascript:array
const hitArray = new Uint8Array(1 << 24);
const rMin = Math.max(0, targetR - targetSize);
const rMax = Math.min(255, targetR + targetSize);
const gMin = Math.max(0, targetG - targetSize);
const gMax = Math.min(255, targetG + targetSize);
const bMin = Math.max(0, targetB - targetSize);
const bMax = Math.min(255, targetB + targetSize);
for (r = rMin; r <= rMax; r++) {
	for (g = gMin; g <= gMax; g++) {
		for (b = bMin; b <= bMax; b++) {
			hitArray[r | (g << 8) | (b << 16)] = 1;
		}
	}
}

let count = 0;
const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
for (let i = 0; i < data.length; i += 4) {
	count += hitArray[dataView.getUint32(i, true) & 0xffffff];
}
```

[Color search (version 1) - JavaScript benchmark at JSBench.me](https://jsbench.me/t4m3leq49o/1)

実行結果は、以下のようになった。

|ブラウザ|条件式 (if)|テーブル (array)|
|---|---|---|
|Firefox 132.0.2|236 ops/s ± 0.65%|70 ops/s ± 1.6%|
|Google Chrome 131.0.6778.70|79 ops/s ± 2.73%|12 ops/s ± 4.22%|

## 前処理を行う場合

2種類の手法に共通のコード。画像サイズと探す色を設定し、ランダムな画像データを用意する。
さらに、今回はそれぞれの色が探す色かを表すテーブルを作る処理をここで行う。

```javascript:Setup JavaScript
const width = 1280;
const height = 720;

const targetR = 128;
const targetG = 128;
const targetB = 128;
const targetSize = 2;

const data = new Uint8ClampedArray(width * height * 4);
for (let i = 0; i < data.length; i++) {
	data[i] = ~~(Math.random() * 256);
}

const hitArray = new Uint8Array(1 << 24);
const rMin = Math.max(0, targetR - targetSize);
const rMax = Math.min(255, targetR + targetSize);
const gMin = Math.max(0, targetG - targetSize);
const gMax = Math.min(255, targetG + targetSize);
const bMin = Math.max(0, targetB - targetSize);
const bMax = Math.min(255, targetB + targetSize);
for (r = rMin; r <= rMax; r++) {
	for (g = gMin; g <= gMax; g++) {
		for (b = bMin; b <= bMax; b++) {
			hitArray[r | (g << 8) | (b << 16)] = 1;
		}
	}
}
```

条件式で判定することにより探す色を数えるコード。
行う前処理は無いので、「前処理を行わない場合」と同じである。

```javascript:if
let count = 0;
for (let i = 0; i < data.length; i += 4) {
	if (
		targetR - targetSize <= data[i] && data[i] <= targetR + targetSize &&
		targetG - targetSize <= data[i + 1] && data[i + 1] <= targetG + targetSize &&
		targetB - targetSize <= data[i + 2] && data[i + 2] <= targetB + targetSize
	) {
		count++;
	}
}
```

テーブルを引くことにより探す色を数えるコード。
テーブルは前処理で作っているので、ここでは引くだけである。

```javascript:array
let count = 0;
const dataView = new DataView(data.buffer, data.byteOffset, data.byteLength);
for (let i = 0; i < data.length; i += 4) {
	count += hitArray[dataView.getUint32(i, true) & 0xffffff];
}
```

[Color search 2 (version 1) - JavaScript benchmark at JSBench.me](https://jsbench.me/v3m3lhf8n0/1)

実行結果は、以下のようになった。

|ブラウザ|条件式 (if)|テーブル (array)|
|---|---|---|
|Firefox 132.0.2|236 ops/s ± 1.1%|98 ops/s ± 1.99%|
|Google Chrome 131.0.6778.70|79 ops/s ± 4.58%|11 ops/s ± 6.55%|

# 結論

今回の実験では、Firefox・Google Chrome ともに、テーブルを引いて判定するより条件式で判定するほうが速いという結果になった。
条件分岐をしなくてよいテーブルのほうが速いと予想したので、意外だった。
また、Firefox ではテーブルを作成する処理を前処理に移動するとパフォーマンスが改善したが、Google Chrome では改善はみられなかった。