LoginSignup
4
5

More than 3 years have passed since last update.

【JavaScript】sRGB色空間からLa*b*空間に変換・逆変換する関数

Last updated at Posted at 2019-01-23

概要

この記事では、sRGB色空間からLa*b*色空間へ変換する関数を紹介しています(勉強がてらにメモしています)。この色空間たちは直接変換することができないため、一旦sRGBからXYZ色空間に変換したのち、La*b*色空間へ変換しています。

追記(2021/02/05)

変換式のソースコードを修正し、さらに逆変換式も追加しました。

コード

sRGB→La*b*変換式

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];
}

La*b*→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色空間から、La*b*色空間へ変換する際に、「ホワイトポイント(白色点)」という言葉が現れます。これは、XYZ色空間における「白色」をどの基準点とするかによって変化します。今回は(くわしくは知らないのですが)ベタそうなD65光源を基準としました。他にも、D50光源やD55光源というものもあるようです[2]。

xyzの意味

xとy, zは、それぞれが「虚色」という色を示しています。名前がカッコいい(中二病)。 虚色とは物理的に存在しない仮想の色のことで、虚数のようなものです。xyzは、それぞれ次のような意味を持っています[3]。

  • x : 明るさを持たないが、赤みを持つ
  • y : 明るさと、緑みを持つ
  • z : 明るさを持たないが、青みを持つ

例えば、物理的に存在する色の場合、「明るさを持たない=黒=無彩色」となるため、明るさを持たないのに色みを持つものはあり得ません。逆に、色みを持つならば必ず明るさを持っているのです。

XYZ色空間においては、明るさの成分を持つのが y だけなので、輝度である L へ変換するときには x や z が出てこないんですね。へー。

おまけ

LC*h*色空間への変換

La*b*色空間とLC*h*色空間は、直交座標と極座標系の関係に等しいです。なので、次の式で値を変換できます。

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));

参考資料

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5