概要
この記事では、sRGB色空間からLab色空間へ変換する関数を紹介しています(勉強がてらにメモしています)。この色空間たちは直接変換することができないため、一旦sRGBからXYZ色空間に変換したのち、Lab色空間へ変換しています。
追記(2021/02/05)
変換式のソースコードを修正し、さらに逆変換式も追加しました。
コード
sRGB→Lab変換式
function RGBtoLab(x, y, z) {
return XYZtoLab(...RGBtoXYZ(x, y, z));
}
function XYZtoRGB(r, g, b) {
[r, g, b] = [r, g, b].map(n => {
// sRGBからリニアRGBに変換する
return (n <= 0.040450) ? (n / 12.92) : Math.pow((n + 0.055) / 1.055, 2.4);
});
let x = 0.412391 * r + 0.357584 * g + 0.180481 * b,
y = 0.212639 * r + 0.715169 * g + 0.072192 * b,
z = 0.019331 * r + 0.119195 * g + 0.950532 * b;
return [x, y, z];
}
function XYZtoLab(x, y, z) {
// ホワイトポイントはD65光源とする。
const whitePoint = [95.047, 100, 108.883];
[x, y, z] = [x, y, z].map((value, index) => {
return value * 100 / whitePoint[index];
}).map((value, index) => {
// x, y, z の補正式: ※ 0.008856 = Math.pow(6 / 29, 3), 7.787037 = 1 / (3 * Math.pow(6 / 29, 2))
return value > 0.008856 ? Math.pow(value, 1 / 3) : (7.787037 * value) + 0.008856;
});
// xyz → La*b* 変換式
const L = (116 * y) - 16,
a = 500 * (x - y),
b = 200 * (y - z);
[L, a, b] = [L, a, b].map(n => {return n / 100});
return [L, a, b];
}
Lab→sRGB変換式
function LabtoRGB(L, a, b) {
return XYZToRGB(...LabToXYZ(L, a, b));
}
function LabtoXYZ(L, a, b) {
[L, a, b] = [L, a, b].map(n => {return n * 100});
// La*b* → xyz 変換式
let y = (L + 16) / 116;
let x = y + a / 500;
let z = y - b / 200;
// ホワイトポイントはD65光源とする。
const whitePoint = [95.047, 100, 108.883];
[x, y, z] = [x, y, z].map((value, index) => {
// x, y, z の補正式: ※ 0.206897 = (6 / 29), 7.787037 = 1 / (3 * Math.pow(6 / 29, 2));
return value > 0.206897 ? Math.pow(value, 3) : (value - 0.206897) / 7.787037;
}).map((value, index) => {
return value * whitePoint[index] / 100;
});
return [x, y, z];
}
function XYZtoRGB(x, y, z) {
let r = 3.240970 * x - 1.537383 * y - z * 0.498611,
g = -0.969244 * x + 1.875968 * y + z * 0.041555,
b = 0.055630 * x - 0.203977 * y + z * 1.056972;
[r, g, b] = [r, g, b].map(n => {
// リニアRGBからsRGBに変換する
return (n <= 0.0031308) ? (n * 12.92) : (1.055 * Math.pow(n, 1 / 2.4) - 0.055);
});
return [r, g, b];
}
補足・解説
値域
RGBの値域は全て、0 ≦ x ≦ 1としています。
L の値域は、0 ≦ L ≦ 1、a* と b* の値域は、(sRGBから変換した場合) -0.86 ≦ a* ≦ 0.98, -1.07 ≦ b* ≦ 0.94 となります[1]。
(実際に動かして色味を見てみたところ、aがマゼンタ⇔緑、bが黄⇔青に対応していました)
ホワイトポイントとは?
XYZ色空間から、Lab色空間へ変換する際に、「ホワイトポイント(白色点)」という言葉が現れます。これは、XYZ色空間における「白色」をどの基準点とするかによって変化します。今回は(くわしくは知らないのですが)ベタそうなD65光源を基準としました。他にも、D50光源やD55光源というものもあるようです[2]。
xyzの意味
xとy, zは、それぞれが「虚色」という色を示しています。名前がカッコいい(中二病)。 虚色とは物理的に存在しない仮想の色のことで、虚数のようなものです。xyzは、それぞれ次のような意味を持っています[3]。
- x : 明るさを持たないが、赤みを持つ
- y : 明るさと、緑みを持つ
- z : 明るさを持たないが、青みを持つ
例えば、物理的に存在する色の場合、「明るさを持たない=黒=無彩色」となるため、明るさを持たないのに色みを持つものはあり得ません。逆に、色みを持つならば必ず明るさを持っているのです。
XYZ色空間においては、明るさの成分を持つのが y だけなので、輝度である L へ変換するときには x や z が出てこないんですね。へー。
おまけ
LCh色空間への変換
Lab色空間とLCh色空間は、直交座標と極座標系の関係に等しいです。なので、次の式で値を変換できます。
let a = C * Math.cos(h);
let b = C * Math.sin(h);
let h = Math.atan2(b, a);
let C = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
参考資料
- https://qiita.com/hachisukansw/items/09caabe6bec46a2a0858
- http://w3.kcua.ac.jp/~fujiwara/infosci/colorspace/colorspace2.html
- http://yamatyuu.net/other/color/cie1976lab/index.html
- https://www.awm.jp/~yoya/cache/w3.kcua.ac.jp/~fujiwara/infosci/colorspace/colorspace2.html
- http://yamatyuu.net/other/color/cie1976lab/index.html
- https://syncer.jp/color-converter