用于确定RGB颜色亮度的公式

rob*_*ica 366 rgb image colors

我正在寻找某种公式或算法来确定给定RGB值的颜色的亮度.我知道它不能像将RGB值一起添加并且具有更高的总和更简单一样简单,但我有点不知道从哪里开始.

Ano*_*ous 442

你的意思是亮度?感知亮度?亮度?

  • 亮度(某些色彩空间的标准):(0.2126*R + 0.7152*G + 0.0722*B) [1]
  • 亮度(感知选项1):(0.299*R + 0.587*G + 0.114*B) [2]
  • 亮度(感知选项2,计算速度较慢): sqrt( 0.241*R^2 + 0.691*G^2 + 0.068*B^2 )sqrt( 0.299*R^2 + 0.587*G^2 + 0.114*B^2 )(感谢@MatthewHerbst)[3]

  • 请注意,这两者都强调生理方面:人眼球对绿光最敏感,红光较少,蓝光较少. (25认同)
  • 另请注意,所有这些都可能适用于线性0-1 RGB,并且您可能具有经过伽马校正的0-255 RGB.它们没有像你想象的那样被转换. (16认同)
  • 在最后一个公式中,它是(0.299*R)^ 2还是0.299*(R ^ 2)? (6认同)
  • 不正确.在应用线性变换之前,必须首先对颜色空间应用伽马函数的反函数.然后在应用线性函数之后,应用伽马函数. (4认同)
  • 前两个来源是其他答案.至于最后一个 - 我认为这是来自电视或图形讲座...... (2认同)
  • 亮度仅考虑人类*光谱*敏感性。它不考虑人类对亮度的非线性感知,CIE 已将其标准化为“亮度”(CIELAB 色彩空间中的 L)根据您的应用程序,您可能需要先计算亮度,然后再计算亮度.. . http://en.wikipedia.org/wiki/Lightness (2认同)
  • @alexstrange,第一个公式使用线性RGB值,第二个公式使用经过伽玛校正的值。第一个公式更现代,第二个可以追溯到NTSC的发明。由于没有非线性运算,因此范围无关紧要。第三个公式似乎适用于经过伽玛校正的值,但是由于它不是标准值,因此很难确定。 (2认同)
  • 这里有一个jsfiddle,可以让你选择颜色并查看亮度,以防任何其他人需要查看从这些公式中获得的值.(基于最后最慢的公式)https://jsfiddle.net/sbrexep0/ (2认同)
  • @KaizerSozay正如它在这里写的那样意味着`0.299*(R ^ 2)`(因为取幂在乘法之前进行) (2认同)
  • 正如我在单独的答案中所述,该答案中的数学通常是不正确且不完整的。#1 需要线性化 sRGB,但未显示。#2 适用于 YCC NTSC 而不是 sRGB。#3 在很多方面通常都是错误的,但奇怪的是,在有人对其进行编辑以使其不那么正确之前,它“更正确”!#3 是“有点像‘L*’”的亮度,但又不完全是。如果参考感知*亮度*“(L*)”,删除的版本大约有 5% 不正确,M.Ransom 版本大约有 15% 不正确,#1、#2 大约有 40% 不正确。这些都没有找到亮度 (Y),也没有找到感知*亮度* (Q)。 (2认同)

Fra*_*nov 292

我认为你要找的是RGB - > Luma转换公式.

光度/数字ITU BT.709:

Y = 0.2126 R + 0.7152 G + 0.0722 B
Run Code Online (Sandbox Code Playgroud)

数字ITU BT.601(对R和B组件给予更多权重):

Y = 0.299 R + 0.587 G + 0.114 B
Run Code Online (Sandbox Code Playgroud)

如果您愿意为性能交换准确度,则有两个近似公式:

Y = 0.33 R + 0.5 G + 0.16 B

Y = 0.375 R + 0.5 G + 0.125 B
Run Code Online (Sandbox Code Playgroud)

这些可以快速计算

Y = (R+R+B+G+G+G)/6

Y = (R+R+R+B+G+G+G+G)>>3
Run Code Online (Sandbox Code Playgroud)

  • @JonathanDumaine那是因为人眼对蓝色最不敏感;-) (83认同)
  • 我喜欢你输入精确值,但也包括一个快速"足够接近"类型的快捷方式.+1. (42认同)
  • 如果您这样做,快速版本会更快:`Y =(R << 1 + R + G << 2 + B)>> 3`(ARM上只有3-4个CPU周期)但我觉得很好编译器会为您做优化. (7认同)
  • 快速版本运行良好.经过测试并应用于拥有数千名用户的真实应用程序,一切看起来都很好. (4认同)
  • @Jonathan Dumaine - 两个快速计算公式都包括蓝色 - 第一个是(2*红色+"蓝色"+ 3个绿色)/ 6个,第二个是(3*红色+"蓝色"+ 4个绿色)>> 3.在快速近似中,蓝色具有最低的重量,但它仍然存在. (3认同)
  • 嗯不知道为什么我之前没有看到那里的B。 (2认同)

Pet*_*tak 99

我在接受的答案中对三种算法进行了比较.我在循环中生成了颜色,其中仅使用了大约每400种颜色.每种颜色由2x2像素表示,颜色从最暗到最轻(从左到右,从上到下)排序.

第1张图片 - 亮度(相对)

0.2126 * R + 0.7152 * G + 0.0722 * B
Run Code Online (Sandbox Code Playgroud)

第二张图片 - http://www.w3.org/TR/AERT#color-contrast

0.299 * R + 0.587 * G + 0.114 * B
Run Code Online (Sandbox Code Playgroud)

第3张图片 - HSP颜色模型

sqrt(0.299 * R^2 + 0.587 * G^2 + 0.114 * B^2)
Run Code Online (Sandbox Code Playgroud)

第4张图片 - WCAG 2.0 SC 1.4.3 相对亮度对比度公式(参见@ Synchro的答案)

根据一行中的颜色数量,有时可以在第1张和第2张照片上发现图案.我从未在第3或第4算法的图片上发现任何图案.

如果我不得不选择我会使用算法3,因为它更容易实现,比第4快约33%.

感知亮度算法比较

  • 您的比较图像不正确,因为您没有为所有功能提供正确的输入.第一个函数需要_linear_ RGB输入; 我只能通过提供_nonlinear_(即伽马校正的)RGB来重现条带效果.纠正这个问题,你没有绑定工件,第一个功能是明显的赢家. (7认同)
  • 对我来说这是最好的答案,因为oyu使用一种图片模式,可以让你感知不同的色调是否以相同的亮度呈现.对于我和我目前的显示器来说,第三张图片是"最好看的",因为它也比第四张更快 (3认同)
  • @Max第三个公式中包含的“^2”和“sqrt”是从非线性RGB近似线性RGB的更快方法,而不是更正确的“^2.2”和“^(1/2.2)” 。不幸的是,使用非线性输入代替线性输入是非常常见的。 (3认同)

Jiv*_*son 47

下面是唯一用于将sRGB图像(如在浏览器等中使用)转换为灰度的CORRECT算法.

在计算内积之前,必须对颜色空间应用伽马函数的逆.然后将gamma函数应用于减少的值.未能合并伽玛功能可能导致高达20%的误差.

对于典型的计算机内容,颜色空间为sRGB.sRGB的正确数字是约.0.21,0.72,0.07.用于sRGB的Gamma是一个复合函数,其近似指数乘以1 /(2.2).这是C++中的全部内容.

// sRGB luminance(Y) values
const double rY = 0.212655;
const double gY = 0.715158;
const double bY = 0.072187;

// Inverse of sRGB "gamma" function. (approx 2.2)
double inv_gam_sRGB(int ic) {
    double c = ic/255.0;
    if ( c <= 0.04045 )
        return c/12.92;
    else 
        return pow(((c+0.055)/(1.055)),2.4);
}

// sRGB "gamma" function (approx 2.2)
int gam_sRGB(double v) {
    if(v<=0.0031308)
        v *= 12.92;
    else 
        v = 1.055*pow(v,1.0/2.4)-0.055;
    return int(v*255+0.5); // This is correct in C++. Other languages may not
                           // require +0.5
}

// GRAY VALUE ("brightness")
int gray(int r, int g, int b) {
    return gam_sRGB(
            rY*inv_gam_sRGB(r) +
            gY*inv_gam_sRGB(g) +
            bY*inv_gam_sRGB(b)
    );
}
Run Code Online (Sandbox Code Playgroud)

  • JMD - 作为视觉感知实验室工作的一部分,我在CRT显示器上进行了直接亮度测量,并且可以确认在值范围的底部存在线性亮度区域. (7认同)
  • 这就是sRGB的定义方式.我认为原因是它避免了零附近的一些数值问题.如果你只是将数字提高到2.2和1/2.2的幂,那就没什么区别了. (5认同)
  • 我知道这是非常古老的,但它仍然在那里被搜查.我不认为这是正确的.不应该灰色(255,255,255)=灰色(255,0,0)+灰色(0,255,0)+灰色(0,0,255)?它没有. (2认同)
  • @DCBillen:不,因为这些值是非线性伽马校正的sRGB空间,所以你不能只是添加它们.如果你想要添加它们,你应该在调用gam_sRGB之前这样做. (2认同)

Myn*_*dex 23

“已接受”答案不正确且不完整

唯一准确的答案是@ jive- dadson@EddingtonsMonkey答案,并支持@ nils-pipenbrinck。其他答案(包括接受的答案链接到或引用了错误,无关,过时或损坏的来源。

简要地:

  • sRGB的必须线性化应用系数之前。
  • 亮度(L或Y)与光线是线性的。
  • 感知亮度(L *)与人类感知一样是非线性的。
  • 就感知而言,HSV和HSL甚至都不是很准确。
  • sRGB的IEC标准将阈值指定为0.04045,而不是 0.03928(来自过时的早期草案)。
  • 有用的(即相对于感知而言),欧几里得距离需要感知上统一的笛卡尔向量空间,例如CIELAB。sRGB不是一个。

以下是正确且完整的答案:

由于此主题在搜索引擎中的位置很高,因此我添加了此答案以阐明对该主题的各种误解。

亮度是一种感知属性,它没有直接的度量。

感知亮度是通过某些视觉模型(例如CIELAB)测量的,此处L *(Lstar)是感知亮度的量度,并且是非线性的,可以近似人类视觉的非线性响应曲线。

亮度是光的线性度量,对正常视觉在光谱上加权,但未对非线性的亮度感知进行调整。

亮度Y素数)是在某些视频编码中使用的伽玛编码加权信号。请勿将其与线性亮度混淆。

伽玛曲线或传输曲线(TRC)是通常与感知曲线相似的曲线,通常应用于图像数据以进行存储或广播,以减少感知到的噪声和/或提高数据利用率(及相关原因)。

要确定感知亮度,请先将经过伽玛编码的R´G´B´图像值转换为线性亮度(LY),然后再转换为非线性感知亮度(L*


查找亮度:

...因为它显然丢失了...

步骤1:

将所有sRGB 8位整数值转换为十进制0.0-1.0

  vR = sR / 255;
  vG = sG / 255;
  vB = sB / 255;
Run Code Online (Sandbox Code Playgroud)

第二步:

将伽玛编码的RGB转换为线性值。例如,sRGB(计算机标准)需要大约V ^ 2.2的功率曲线,尽管“准确”变换为:

sRGB转线性

其中V´是sRGB的经伽玛编码的R,G或B通道。
伪代码:

function sRGBtoLin(colorChannel) {
        // Send this function a decimal sRGB gamma encoded color value
        // between 0.0 and 1.0, and it returns a linearized value.

    if ( colorChannel <= 0.04045 ) {
            return colorChannel / 12.92;
        } else {
            return pow((( colorChannel + 0.055)/1.055),2.4));
        }
    }
Run Code Online (Sandbox Code Playgroud)

第三步:

要找到亮度(Y),请对sRGB应用标准系数:

应用系数Y = R * 0.2126 + G * 0.7152 + B * 0.0722

使用以上功能的伪代码:

Y = (0.2126 * sRGBtoLin(vR) + 0.7152 * sRGBtoLin(vG) + 0.0722 * sRGBtoLin(vB))
Run Code Online (Sandbox Code Playgroud)

查找可见的亮度:

第四步:

从上方获取亮度Y,然后转换为L *

来自Y方程的L *
伪代码:

function YtoLstar(Y) {
        // Send this function a luminance value between 0.0 and 1.0,
        // and it returns L* which is "perceptual lightness"

    if ( Y <= (216/24389) {       // The CIE standard states 0.008856 but 216/24389 is the intent for 0.008856451679036
            return Y * (24389/27);  // The CIE standard states 903.3, but 24389/27 is the intent, making 903.296296296296296
        } else {
            return pow(Y,(1/3)) * 116 - 16;
        }
    }
Run Code Online (Sandbox Code Playgroud)

L *是从0(黑色)到100(白色)的值,其中50是可感知的“中间灰色”。L * = 50相当于Y = 18.4,或者换句话说就是18%的灰卡,代表摄影曝光的中间位置(Ansel Adams区V)。

参考文献:

IEC 61966-2-1:1999 Standard
Wikipedia sRGB
Wikipedia CIELAB
Wikipedia CIEXYZ
Charles Poynton的Gamma常见问题解答

  • 我将此答案转换为 TypeScript:https://gist.github.com/mnpenner/70ab4f0836bbee548c71947021f93607 (4认同)
  • @asdfasdfads是的,“L*a*b*”没有考虑到许多心理物理属性。亥姆霍兹-科尔劳施效应就是其中之一,但还有许多其他效应。无论如何,CIELAB 都不是一个“完整”的图像评估模型。在我的文章中,我试图尽可能完整地涵盖基本概念,而不涉及非常深入的细节。Hunt 模型、Fairchild 模型以及其他模型的工作更为完整,但也更为复杂。 (2认同)
  • 感谢您的出色回答。一个小问题:你的函数“YtoLstar(Y)”接受 0 到 1 范围内的值,并返回 0 到 100 范围内的 1,这有点令人困惑。 (2认同)
  • 你好@ChrisDennis,谢谢你——所以,通常,Y 的范围是 0.0-1.0,L\* 通常是 0-100。有时 Y 被标准化为 0-100,在这种情况下,在应用功率曲线的指数之前,它*必须*转移到 0.0-1.0。L\* 几乎始终是 0-100 范围,而 **ab** 颜色值通常为 ±128。L* 基于/源自 Munsell 值,该值是 0-10...因此,至于令人困惑的问题,请等到您深入研究比色法之后——“令人困惑”是理所当然的。(!!) (2认同)

sit*_*joe 10

我发现这个代码(用C#编写)可以很好地计算颜色的"亮度".在这种情况下,代码试图确定是否在颜色上放置白色或黑色文本.


bob*_*obo 10

有趣的是,RGB => HSV的这个公式只使用v = MAX3(r,g,b).换句话说,您可以使用(r,g,b)的最大值作为HSV中的V.

我检查了Hearn&Baker的第575页,这也是他们计算"价值"的方式.

来自Hearn&Baker第319页

  • HSV 在感知上并不统一(甚至不接近)。它仅用作调整颜色的“方便”方式,但与感知无关,V 与 L 或 Y 的真实值(CIE 亮度)无关。 (2认同)

Syn*_*hro 9

我建议你选择W3C标准推荐的公式,而不是迷失在这里提到的随机选择的公式中.

这是WCAG 2.0 SC 1.4.3 相对亮度对比度公式的简单但精确的PHP实现.它生成的值适用于评估WCAG合规性所需的比率,如本页所示,因此适用于任何Web应用程序.这对于移植到其他语言来说是微不足道的.

/**
 * Calculate relative luminance in sRGB colour space for use in WCAG 2.0 compliance
 * @link http://www.w3.org/TR/WCAG20/#relativeluminancedef
 * @param string $col A 3 or 6-digit hex colour string
 * @return float
 * @author Marcus Bointon <marcus@synchromedia.co.uk>
 */
function relativeluminance($col) {
    //Remove any leading #
    $col = trim($col, '#');
    //Convert 3-digit to 6-digit
    if (strlen($col) == 3) {
        $col = $col[0] . $col[0] . $col[1] . $col[1] . $col[2] . $col[2];
    }
    //Convert hex to 0-1 scale
    $components = array(
        'r' => hexdec(substr($col, 0, 2)) / 255,
        'g' => hexdec(substr($col, 2, 2)) / 255,
        'b' => hexdec(substr($col, 4, 2)) / 255
    );
    //Correct for sRGB
    foreach($components as $c => $v) {
        if ($v <= 0.03928) {
            $components[$c] = $v / 12.92;
        } else {
            $components[$c] = pow((($v + 0.055) / 1.055), 2.4);
        }
    }
    //Calculate relative luminance using ITU-R BT. 709 coefficients
    return ($components['r'] * 0.2126) + ($components['g'] * 0.7152) + ($components['b'] * 0.0722);
}

/**
 * Calculate contrast ratio acording to WCAG 2.0 formula
 * Will return a value between 1 (no contrast) and 21 (max contrast)
 * @link http://www.w3.org/TR/WCAG20/#contrast-ratiodef
 * @param string $c1 A 3 or 6-digit hex colour string
 * @param string $c2 A 3 or 6-digit hex colour string
 * @return float
 * @author Marcus Bointon <marcus@synchromedia.co.uk>
 */
function contrastratio($c1, $c2) {
    $y1 = relativeluminance($c1);
    $y2 = relativeluminance($c2);
    //Arrange so $y1 is lightest
    if ($y1 < $y2) {
        $y3 = $y1;
        $y1 = $y2;
        $y2 = $y3;
    }
    return ($y1 + 0.05) / ($y2 + 0.05);
}
Run Code Online (Sandbox Code Playgroud)

  • W3C 公式在许多层面上都是不正确的。它没有考虑到人类的感知,他们使用的是“简单”对比度,使用的是线性且在感知上根本不均匀的亮度。除其他外,他们似乎基于 1988 年 (!!!) 的一些标准,这些标准与今天无关(这些标准基于单色显示器,例如绿色/黑色,并指的是从开到关的总对比度,不考虑灰度或颜色)。 (2认同)
  • 那完全是垃圾。亮度具有特殊的感知性 - 这就是它对红色、绿色和蓝色具有不同系数的原因。年龄与它无关 - 优秀的 CIE Lab 感知色彩空间可以追溯到 1976 年。W3C 空间没有那么好,但它是一个很好的实用近似值,易于计算。如果您有一些建设性的建议,请发表,而不是空洞的批评。 (2认同)
  • @Syncro 不,亮度是某些视频编码(例如 NTSC 的 YIQ)的伽玛编码(Y´)部分。亮度,即 CIEXYZ 中的 Y 是线性的,并且完全不是感知的。W3C 使用线性亮度和简单对比度,这不能正确定义中间范围的对比度(相差甚远)。现在正在写一篇关于此的文章,完成后我将发布链接。是的,CIELAB 非常出色,但 W3C 没有使用它。我指的过时文档是 ANSI-HFES-100-1988,不适用于屏幕颜色对比度。 (2认同)
  • **只需添加/更新**:我们目前正在研究可更好地建模感知对比度*(Github第695期的讨论)*的替换算法。但是,作为一个单独的问题,仅供参考,sRGB的阈值为** 0.04045 **,而不是0.03928,后者是从过时的早期sRGB草案中引用的。权威的IEC标准使用0.04045,并且即将提出拉取请求以更正WCAG中的此错误。(参考:IEC 61966-2-1:1999)这是在Github第360期中,尽管要提到的是,在8位中没有实际的区别-在线程360的末端附近,我有错误图表,包括8位中的0.04045 / 0.03928。 (2认同)
  • 并且添加到线程中,WCAG 3.0的替换方法是APCA,可以在https://www.myndex.com/APCA/simple查看 (2认同)

Kal*_*Kal 9

将此视为Myndex 优秀答案的附录。正如他(和其他人)所解释的,计算 RGB 颜色的相对亮度(和感知亮度)的算法旨在处理线性RGB 值。您不能仅将它们应用于原始 sRGB 值并希望获得相同的结果。

\n

理论上,这一切听起来都很棒,但我确实需要亲眼看看证据,因此,受到Petr Hurtak 颜色渐变的启发,我继续制作自己的渐变。它们说明了两种最常见的算法(ITU-R 建议书BT.601BT.709),并清楚地说明了为什么您应该使用线性值(而不是伽马校正值)进行计算。

\n

首先,这是旧版 ITU BT.601 算法的结果。左侧使用原始 sRGB 值。右边的使用线性值。

\n

ITU-R BT.601 颜色亮度梯度

\n

0.299 R + 0.587 G + 0.114 B

\n

ITU-R BT.601 颜色亮度梯度

\n

在这个分辨率下,左边的实际上看起来出奇的好!但如果仔细观察,你会发现一些问题。在更高的分辨率下,不需要的伪影更加明显:

\n

ITU-R BT.601 颜色亮度梯度(高分辨率)

\n

线性的不会受到这些影响,但那里有相当多的噪音。让我们将其与 ITU-R 建议书 BT.709\xe2\x80\xa6 进行比较

\n

ITU-R BT.709 颜色亮度梯度

\n

0.2126 R + 0.7152 G + 0.0722 B

\n

ITU-R BT.709 颜色亮度梯度

\n

好家伙。显然不适合与原始 sRGB 值一起使用!然而,这正是大多数人所做的!

\n

ITU-R BT.709 颜色亮度梯度(高分辨率)

\n

在高分辨率下,您可以真正看到该算法在使用线性值时的有效性。它的噪音几乎没有之前的那么大。虽然这些算法都不是完美的,但这个算法已经是最好的了。

\n


Nil*_*nck 8

添加所有其他人说的话:

所有这些方程在实际工作有点儿好,但如果你需要非常精确,你必须首先将颜色转换为线性颜色空间(应用逆图像-γ),做重均原色和 - 如果你想显示颜色 - 将亮度恢复到显示器伽玛.

在深灰色中,瞄准伽玛和进行适当伽玛之间的亮度差异高达20%.


asd*_*ads 5

我今天正在用 javascript 解决一个类似的任务。我已经getPerceivedLightness(rgb)为 HEX RGB 颜色确定了这个函数。它通过 Fairchild 和 Perrotta 公式处理 Helmholtz-Kohlrausch 效应以进行亮度校正。

/**
 * Converts RGB color to CIE 1931 XYZ color space.
 * https://www.image-engineering.de/library/technotes/958-how-to-convert-between-srgb-and-ciexyz
 * @param  {string} hex
 * @return {number[]}
 */
export function rgbToXyz(hex) {
    const [r, g, b] = hexToRgb(hex).map(_ => _ / 255).map(sRGBtoLinearRGB)
    const X =  0.4124 * r + 0.3576 * g + 0.1805 * b
    const Y =  0.2126 * r + 0.7152 * g + 0.0722 * b
    const Z =  0.0193 * r + 0.1192 * g + 0.9505 * b
    // For some reason, X, Y and Z are multiplied by 100.
    return [X, Y, Z].map(_ => _ * 100)
}

/**
 * Undoes gamma-correction from an RGB-encoded color.
 * https://en.wikipedia.org/wiki/SRGB#Specification_of_the_transformation
 * https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color
 * @param  {number}
 * @return {number}
 */
function sRGBtoLinearRGB(color) {
    // Send this function a decimal sRGB gamma encoded color value
    // between 0.0 and 1.0, and it returns a linearized value.
    if (color <= 0.04045) {
        return color / 12.92
    } else {
        return Math.pow((color + 0.055) / 1.055, 2.4)
    }
}

/**
 * Converts hex color to RGB.
 * https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
 * @param  {string} hex
 * @return {number[]} [rgb]
 */
function hexToRgb(hex) {
    const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
    if (match) {
        match.shift()
        return match.map(_ => parseInt(_, 16))
    }
}

/**
 * Converts CIE 1931 XYZ colors to CIE L*a*b*.
 * The conversion formula comes from <http://www.easyrgb.com/en/math.php>.
 * https://github.com/cangoektas/xyz-to-lab/blob/master/src/index.js
 * @param   {number[]} color The CIE 1931 XYZ color to convert which refers to
 *                           the D65/2° standard illuminant.
 * @returns {number[]}       The color in the CIE L*a*b* color space.
 */
// X, Y, Z of a "D65" light source.
// "D65" is a standard 6500K Daylight light source.
// https://en.wikipedia.org/wiki/Illuminant_D65
const D65 = [95.047, 100, 108.883]
export function xyzToLab([x, y, z]) {
  [x, y, z] = [x, y, z].map((v, i) => {
    v = v / D65[i]
    return v > 0.008856 ? Math.pow(v, 1 / 3) : v * 7.787 + 16 / 116
  })
  const l = 116 * y - 16
  const a = 500 * (x - y)
  const b = 200 * (y - z)
  return [l, a, b]
}

/**
 * Converts Lab color space to Luminance-Chroma-Hue color space.
 * http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html
 * @param  {number[]}
 * @return {number[]}
 */
export function labToLch([l, a, b]) {
    const c = Math.sqrt(a * a + b * b)
    const h = abToHue(a, b)
    return [l, c, h]
}

/**
 * Converts a and b of Lab color space to Hue of LCH color space.
 * https://stackoverflow.com/questions/53733379/conversion-of-cielab-to-cielchab-not-yielding-correct-result
 * @param  {number} a
 * @param  {number} b
 * @return {number}
 */
function abToHue(a, b) {
    if (a >= 0 && b === 0) {
        return 0
    }
    if (a < 0 && b === 0) {
        return 180
    }
    if (a === 0 && b > 0) {
        return 90
    }
    if (a === 0 && b < 0) {
        return 270
    }
    let xBias
    if (a > 0 && b > 0) {
        xBias = 0
    } else if (a < 0) {
        xBias = 180
    } else if (a > 0 && b < 0) {
        xBias = 360
    }
    return radiansToDegrees(Math.atan(b / a)) + xBias
}

function radiansToDegrees(radians) {
    return radians * (180 / Math.PI)
}

function degreesToRadians(degrees) {
    return degrees * Math.PI / 180
}

/**
 * Saturated colors appear brighter to human eye.
 * That's called Helmholtz-Kohlrausch effect.
 * Fairchild and Pirrotta came up with a formula to
 * calculate a correction for that effect.
 * "Color Quality of Semiconductor and Conventional Light Sources":
 * https://books.google.ru/books?id=ptDJDQAAQBAJ&pg=PA45&lpg=PA45&dq=fairchild+pirrotta+correction&source=bl&ots=7gXR2MGJs7&sig=ACfU3U3uIHo0ZUdZB_Cz9F9NldKzBix0oQ&hl=ru&sa=X&ved=2ahUKEwi47LGivOvmAhUHEpoKHU_ICkIQ6AEwAXoECAkQAQ#v=onepage&q=fairchild%20pirrotta%20correction&f=false
 * @return {number}
 */
function getLightnessUsingFairchildPirrottaCorrection([l, c, h]) {
    const l_ = 2.5 - 0.025 * l
    const g = 0.116 * Math.abs(Math.sin(degreesToRadians((h - 90) / 2))) + 0.085
    return l + l_ * g * c
}

export function getPerceivedLightness(hex) {
    return getLightnessUsingFairchildPirrottaCorrection(labToLch(xyzToLab(rgbToXyz(hex))))
}
Run Code Online (Sandbox Code Playgroud)