JavaScriptでCIEDE2000色差計算

[Color difference（wikipedia）](https://en.wikipedia.org/wiki/Color_difference#CIEDE2000)で公開されているCIEDE2000の色差計算式をJavaScriptで書いてみました

wikipediaで公開されている計算式は以下のとおりです。
> ![CIE2000](https://wikimedia.org/api/rest_v1/media/math/render/svg/aec5cdd29cf9fdbd7352935eaee1524d5718340e)
（「[Color difference](https://en.wikipedia.org/wiki/Color_difference#CIEDE2000)」（5 Feb 2017, at 02:30. UTC）『ウィキペディア』より引用）

2000というくらいですからきっといちばん新しいのではないかと思うのですが[ユークリッド](https://en.wikipedia.org/wiki/Color_difference#Euclidean)、[CIE76](https://en.wikipedia.org/wiki/Color_difference#CIE76)、[CIE94](https://en.wikipedia.org/wiki/Color_difference#CIE94)と比較してかなり複雑な印象を受ける計算式です。実際のプログラムも複雑なものとなりました。

```js:ciede2000
function ciede2000(L1,a1,b1, L2,a2,b2, kL=1,kC=1,kH=1) {
	//http://en.wikipedia.org/wiki/Color_difference#CIEDE2000
	var radianToDegree = function(radian) {return radian * (180 / Math.PI);};
	var degreeToRadian = function(degree) {return degree * (Math.PI / 180);};

	var deltaLp = L2 - L1;
	var L_ = (L1 + L2) / 2;
	var C1 = Math.sqrt(Math.pow(a1, 2) + Math.pow(b1, 2));
	var C2 = Math.sqrt(Math.pow(a2, 2) + Math.pow(b2, 2));
	var C_ = (C1 + C2) / 2;
	var ap1 = a1 + (a1 / 2) *
		(1 - Math.sqrt(
			Math.pow(C_, 7) /
			(Math.pow(C_, 7) + Math.pow(25, 7))
			)
		);
	var ap2 = a2 + (a2 / 2) *
		(1 - Math.sqrt(
			Math.pow(C_, 7) /
			(Math.pow(C_, 7) + Math.pow(25, 7))
			)
		);
	var Cp1 = Math.sqrt(Math.pow(ap1, 2) + Math.pow(b1, 2));
	var Cp2 = Math.sqrt(Math.pow(ap2, 2) + Math.pow(b2, 2));
	var Cp_ = (Cp1 + Cp2) / 2;
	var deltaCp = Cp2 - Cp1;

	var hp1;
	if (b1 == 0 && ap1 == 0) {
		hp1 = 0;
	} else {
		hp1 = radianToDegree(Math.atan2(b1, ap1));
		if (hp1 < 0) {hp1 = hp1 + 360;}
	}
	var hp2;
	if (b2 == 0 && ap2 == 0) {
		hp2 = 0;
	} else {
		hp2 = radianToDegree(Math.atan2(b2, ap2));
		if (hp2 < 0) {hp2 = hp2 + 360;}
	}

	var deltahp;
	if (C1 == 0 || C2 == 0) {
		deltahp = 0;
	} else if (Math.abs(hp1 - hp2) <= 180) {
		deltahp = hp2 - hp1;
	} else if (hp2 <= hp1) {
		deltahp = hp2 - hp1 + 360;
	} else {
		deltahp = hp2 - hp1 - 360;
	}

	var deltaHp = 2 * Math.sqrt(Cp1 * Cp2) * Math.sin(degreeToRadian(deltahp) / 2);

	var Hp_;
	if (Math.abs(hp1 - hp2) > 180) {
		Hp_ =  (hp1 + hp2 + 360) / 2
	} else {
		Hp_ = (hp1 + hp2) / 2
	};

	var T = 1 -
		0.17 * Math.cos(degreeToRadian(Hp_ - 30)) +
		0.24 * Math.cos(degreeToRadian(2 * Hp_)) +
		0.32 * Math.cos(degreeToRadian(3 * Hp_ + 6)) -
		0.20 * Math.cos(degreeToRadian(4 * Hp_ - 63));

	var SL = 1 + (
		(0.015 * Math.pow(L_ - 50, 2)) /
		Math.sqrt(20 + Math.pow(L_ - 50, 2))
		);
	var SC = 1 + 0.045 * Cp_;
	var SH = 1 + 0.015 * Cp_ * T;

	var RT = -2 *
		Math.sqrt(
			Math.pow(Cp_, 7) /
			(Math.pow(Cp_, 7) + Math.pow(25, 7))
		) *
		Math.sin(degreeToRadian(
			60 * Math.exp(-Math.pow((Hp_ - 275) / 25, 2))
		));

	return Math.sqrt(
		Math.pow(deltaLp / (kL * SL), 2) +
		Math.pow(deltaCp / (kC * SC), 2) +
		Math.pow(deltaHp / (kH * SH), 2) +
		RT * (deltaCp / (kC * SC)) * (deltaHp / (kH * SH))
		);
}
```

CIEDE2000はLabの値が必要となりますので、実際に使うには[JavaScriptでCIE76色差計算](cda9b95395468ee7e0f8)と同様に[JavaScriptでRGBからLab色空間への変換](09caabe6bec46a2a0858)を使ってRGBからLabへの変換する必要があります。以下のような感じになると思います。

```js
var [L1, a1, b1] = rgbToLab(r1, g1, b1);
var [L2, a2, b2] = rgbToLab(r2, g2, b2);
distance = ciede2000(L1,a1,b1, L2,a2,b2);
```

または

```js
distance = ciede2000.apply(this, 
		rgbToLab(r1,g1,b1).concat(rgbToLab(r2,g2,b2))
		)
```

です。

## 関連リンク
- [色差アルゴリズムの違いをビジュアル化してみた](98c218bc5616eaaba9a0)
- [JavaScriptでCIE94色差計算](3488df50b7082d4fcffb)
- [JavaScriptでCIE76色差計算](cda9b95395468ee7e0f8)
- [JavaScriptでユークリッド色差計算](28c7b61d6b8ca54c5b94)
- [JavaScriptでHSVからRGBに変換](633d1bf6baf008e82847)
- [JavaScriptでRGBからLab色空間への変換](09caabe6bec46a2a0858)