确定RGB颜色的视觉亮度的公式

544

我正在寻找某种公式或算法来确定给定RGB值的颜色亮度。 我知道它不可能像将RGB值相加然后具有更高总和就更亮那么简单,但我不知道从哪里开始。

我正在寻找一种计算颜色亮度的公式或算法,其输入是RGB值。我知道只是简单地将RGB值相加并且总和越高的颜色越亮是不行的,因此我需要一个更复杂的方法。

10
感知亮度是我认为我要寻找的,谢谢。 - robmerica
我发现了一个用C#编写的代码([此处][1]),它可以很好地计算出颜色的“亮度”。在这种情况下,代码试图确定是将白色文本还是黑色文本放在该颜色上。 [1]:http://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx - sitesbyjoe
2
有一篇很好的文章(Manipulating colors in .NET - Part 1),讨论了颜色空间及其之间的转换,包括理论和代码(C#)。要查看答案,请参阅文章中的“模型之间的转换”主题。 - underscore
看我的答案,但真正简单的是:亮度 = 0.2 * 红色值 + 0.7 * 绿色值 + 0.1 * 蓝色值 - Ronen Rabinovici
22个回答

598

根据您的需要,计算Luminance的方法可能会有所不同。以下是三种计算Luminance的方法:

  • 亮度(某些色彩空间的标准):(0.2126*R + 0.7152*G + 0.0722*B) 来源 img

  • 亮度(感知选项1):(0.299*R + 0.587*G + 0.114*B) 来源 img

  • 亮度(感知选项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来源 img

[编辑:添加了使用每种方法排序的命名 css 颜色的示例。]


37
注意,这两个都强调生理方面:人类眼球对绿光最为敏感,对红光次之,对蓝光最不敏感。 - Bob Cross
26
请注意,所有这些颜色值可能都是针对线性的0-1 RGB计算得出的,而您的颜色值很可能是经过伽马校正的0-255 RGB。它们的转换方式并不像您想象的那样简单。 - alex strange
4
在应用线性变换之前,必须先应用颜色空间的伽马函数的反函数。然后在应用线性函数之后,再次应用伽马函数。 - Jive Dadson
9
在最后的公式中,是(0.299R)^2还是0.299(R^2)? - Rahul Iyer
6
根据这里的写法,它的意思是“0.299乘以R的平方”(因为指数运算先于乘法)。 - RedPolygon
显示剩余15条评论

358

我认为你需要的是RGB到亮度转换公式。

光度/数字ITU BT.709

Y = 0.2126 R + 0.7152 G + 0.0722 B

数字ITU BT.601(在R和B分量上赋予更高的权重):

Y = 0.299 R + 0.587 G + 0.114 B

如果你愿意为了性能而牺牲精度,这里有两个逼近公式可供选择:

Y = 0.33 R + 0.5 G + 0.16 B

Y = 0.375 R + 0.5 G + 0.125 B

这些可以快速计算:

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

Y = (R+R+R+B+G+G+G+G)>>3

1
你的“快速计算”值为什么完全不包括蓝色的近似值? - Jonathan Dumaine
3
@Jonathan Dumaine - 这两个快速计算公式都包括蓝色 - 第一个公式为(2红色 + 蓝色 + 3绿色)/6,第二个公式为(3红色 + 蓝色 + 4绿色)>>3。虽然在这两个快速估算中蓝色的权重最小,但它仍然存在。 - Franci Penov
10
如果您按照以下方式执行,快速版本会更快:Y = (R<<1+R+G<<2+B)>>3(在ARM上只需3-4个CPU周期)。但我想好的编译器会为您进行这种优化。 - rjmunro
@rjmunro 我认为你少了几个括号,加法操作会先执行,所以你的代码与 Y = (R << (1 + R + G) << (2 + B)) >> 3 相同。正确的代码片段是:int Y = ((R << 1) + R + (G << 2) + B) >> 3; - itachi
Luma(在其他词中称为Y',即Y的一次导数)并不是亮度(Y),而且尽管luma在进行图像数据编码时进行了伽马压缩并可能有用,但在后续解码时,它对于更饱和的颜色来说并不具备感知均匀性。因此,它不是一个用于感知亮度的好指标。亮度是线性的,而且不具备感知均匀性,也不适用于预测亮度感知。 - undefined

261

“被接受”的答案是错误和不完整的

唯一准确的答案是@jive-dadson@EddingtonsMonkey的答案,并且得到@nils-pipenbrinck的支持。其他答案(包括被接受的答案)链接或引用的来源要么是错误的,无关的,过时的,或者是失效的。

简而言之:

  • 在应用系数之前,sRGB必须进行线性化处理。
  • 亮度(L或Y)与光线一样是线性的。
  • 感知亮度(L*)与人类的感知一样是非线性的。
  • HSV和HSL在感知方面根本不准确。
  • IEC对sRGB的标准规定了一个阈值为0.04045,而不是0.03928(那是一个过时的早期草案)。
  • 为了有用(即相对于感知),欧几里得距离需要一个感知上均匀的笛卡尔向量空间,例如CIELAB。sRGB不是一个。

以下是一个正确而完整的答案:
因为这个帖子在搜索引擎中排名较高,我在这里添加这个答案来澄清关于这个主题的各种误解。 亮度是光的线性测量,对正常视觉进行光谱加权,但没有调整为非线性的亮度感知。它可以是相对测量,如CIEXYZ中的Y,或者是绝对测量,如L,以cd/m2为单位的绝对测量(不要与L*混淆)。 感知亮度被一些视觉模型使用,如CIELAB,这里的L*(Lstar)是感知亮度的值,并且是非线性的,以近似人类视觉的非线性响应曲线。(即,对感知是线性的,但对光是非线性的)。 亮度是一种感知属性,它没有“物理”测量。然而,一些颜色外观模型确实有一个被称为“Q”,用于感知亮度,这与感知亮度不同。
Luma(Y´ prime)是一种用于某些视频编码(Y´I´Q´)的伽马编码、加权信号。它不应与线性亮度混淆。
伽马或传输曲线(TRC)通常类似于感知曲线,并常用于图像数据的存储或广播,以减少感知噪声和/或提高数据利用率(以及相关原因)。
为了确定感知亮度,首先将伽马编码的R´G´B´图像值转换为线性亮度(L或Y),然后转换为非线性感知亮度(L*)。

寻找亮度:

...因为显然它在某个地方丢失了...

第一步:

将所有sRGB 8位整数值转换为0.0-1.0的小数

  vR = sR / 255;
  vG = sG / 255;
  vB = sB / 255;

第二步:
将伽马编码的RGB转换为线性值。例如,sRGB(计算机标准)需要一个大约为V^2.2的幂曲线,尽管“准确”的转换是:

sRGB to Linear

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

第三步: 使用sRGB的标准系数来计算亮度(Y)。

Apply coefficients Y = R * 0.2126 + G * 0.7152 + B *  0.0722

使用上述函数的伪代码:
Y = (0.2126 * sRGBtoLin(vR) + 0.7152 * sRGBtoLin(vG) + 0.0722 * sRGBtoLin(vB))

寻找视觉亮度:

第四步:

从上面获取亮度Y,并转换为L*

L* from Y equation

伪代码:
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;
        }
    }

L*是一个从0(黑色)到100(白色)的值,其中50是感知上的“中灰”。L* = 50相当于Y = 18.4,换句话说就是一个18%的灰卡,代表着摄影曝光的中间部分(安塞尔·亚当斯的第五区)。
2023年编辑:
Myndex为了完整性而添加的内容:
本页面上的其他答案中显示了Luma(即Yʹ,也就是Y prime)的数学计算,而Luma绝不能与相对亮度(Y)混淆。它们也都不是感知均匀的亮度或亮度。
Luma,虽然它经过伽马压缩并且可能对编码图像数据以供以后解码有用,但对于饱和度更高的颜色来说,它并不具备感知均匀性。因此,Luma通常不是用于感知亮度的好度量标准。
亮度是线性的光度,但不是线性的感知度,换句话说,它不具备感知均匀性,因此不适用于预测亮度感知。
我上面提到的Lstar L*经常用于预测感知亮度。它基于Munsell Value,该值是通过在特定定义的环境中使用大型扩散颜色样本进行实验得出的。
L*不特别敏感于上下文,即不敏感于同时对比、HK或其他上下文感觉。
因此,L*只能在感知亮度的准确模型中起到一部分作用。
L*与CIELAB一起适用于确定两个物品之间的“小差异”,即接近“刚可察觉差异”(JND阈值)。
L*在较大的超阈值水平和较大的差异处变得不太准确,此时其他上下文因素开始产生更为重要的影响。
超阈值曲线形状与JND阈值曲线不同。
L*将感知中灰色置于18%。
实际感知中灰色取决于上下文。
对于高空间频率刺激(如文本),中等对比度通常远高于18%。
对比度感知比两个亮度值之间的差异或比率更为复杂,并且在视觉范围内不均匀。

参考资料:

IEC 61966-2-1:1999 标准
维基百科 sRGB
维基百科 CIELAB
维基百科 CIEXYZ
Charles Poynton 的 Gamma FAQ


2
@asdfasdfads 是的,L*a*b* 没有考虑到许多心理物理属性。其中之一是 Helmholtz-Kohlrausch 效应,但还有许多其他因素。CIELAB 绝不是一个“完整”的图像评估模型。在我的帖子中,我试图尽可能全面地涵盖基本概念,而不涉及非常深入的细节。Hunt 模型、Fairchild 的模型和其他模型做得更全面,但也更加复杂。 - Myndex
2
非常感谢您提供的出色答案。有一个小问题:您的函数YtoLstar(Y)接受0到1范围内的值,并返回0到100范围内的值,这可能会有些令人困惑。 - Chris Dennis
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...所以,如果感到困惑,请等到您深入了解色度学后再说,“困惑”是家常便饭。(!!) - Myndex
1
小错误......似乎if (Y <= (216/24389)缺少一个) - OG Sean
2
我将这个答案转换为TypeScript:https://gist.github.com/mnpenner/70ab4f0836bbee548c71947021f93607 - mpen
显示剩余18条评论

121
我已经比较了被采纳的答案中的三种算法。我按照循环生成颜色,只使用约每400个颜色。每种颜色由2x2像素表示,颜色从最暗到最亮排序(从左到右,从上到下)。
第一张图片 - 相对亮度
0.2126 * R + 0.7152 * G + 0.0722 * B

第二张图片 - http://www.w3.org/TR/AERT#color-contrast
0.299 * R + 0.587 * G + 0.114 * B

第三张图片 - HSP颜色模型
sqrt(0.299 * R^2 + 0.587 * G^2 + 0.114 * B^2)

第四张图片 - WCAG 2.0 SC 1.4.3 相对亮度对比度比例 公式(请参见@Synchro's的答案here

根据一行中颜色的数量,有时可以在第一张和第二张图片上发现图案。我从未在第三或第四个算法的图片上发现任何图案。

如果必须选择,我会选择第三个算法,因为它更容易实现,并且比第四个算法快约33%。

Perceived brightness algorithm comparison


15
您的对比图不正确,因为您没有为所有函数提供正确的输入。第一个函数需要线性RGB输入;只有提供非线性(即伽玛校正)RGB才能重现带状效果。纠正此问题后,您将不会出现带状伪影,并且第一个函数是明显的赢家。 - Max
2
@Max,第三个公式中包含的^2sqrt是从非线性RGB快速逼近线性RGB的方法,而不是更正确的^2.2^(1/2.2)。不幸的是,使用非线性输入而不是线性输入非常普遍。 - Mark Ransom

81

以下是将sRGB图像(如浏览器等使用的)转换为灰度图像的唯一正确算法。

在计算内积之前,需要对色彩空间应用反Gamma函数。然后将Gamma函数应用于缩小的值。如果不考虑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)
    );
}

1
你为什么使用复合函数来近似指数?为什么不直接计算呢?谢谢。 - JMD
8
这就是sRGB的定义方式。我认为原因是它避免了在接近零时出现一些数值问题。如果你只是将数字提高到2.2和1/2.2的幂,这不会有太大的区别。 - Jive Dadson
10
作为视觉感知实验室工作的一部分,我对CRT显示器进行了直接亮度测量,并确认在值范围底部存在一个线性亮度区域。 - Jerry Federspiel
2
我知道这很老,但它仍然存在于搜索中。我不认为它是正确的。难道 gray(255,255,255) 不应该等于 gray(255,0,0)+gray(0,255,0)+gray(0,0,255) 吗?但它并不相等。 - DCBillen
2
@DCBillen:不行,因为这些值在非线性伽马校正的sRGB空间中,你不能简单地将它们相加。如果你想将它们相加,应该在调用gam_sRGB之前这样做。 - rdb
你的 gray 函数在计算亮度值后进行了伽马压缩,但亮度本身是未压缩的灰度值,因此如果你想计算亮度,则应该省略对 gam_sRGB 的调用。如果你只是想将颜色转换为黑白显示,则应该保留它。 - Tim Kuipers

15

建议使用W3C标准推荐的公式,而不是在这里随机选择公式。

这是一个简单但精确的PHP实现WCAG 2.0 SC 1.4.3相对亮度对比度比率公式。它产生的值适合于评估符合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.04045) {
            $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);
}

2
W3C的公式在多个层面上都是不正确的。它没有考虑到人类的感知,而是使用了线性的、不具有感知均匀性的“简单”对比度。此外,它似乎基于一些早在1988年就过时的标准(那些标准是基于像绿色/黑色这样的单色显示器,并参考了从开到关的总对比度,而没有考虑灰度或颜色)。 - Myndex
1
这完全是胡说八道。Luma 是专门感知的 - 这就是为什么它有不同的红色、绿色和蓝色系数。年龄与此无关 - 优秀的 CIE Lab 感知色彩空间可以追溯到 1976 年。W3C 空间并不那么好,但它是一个很好的实用近似,易于计算。如果你有建设性的意见,请发表,而不是空洞的批评。 - Synchro
1
@Syncro 不是的,Luma 是一些视频编码(例如 NTSC 的 YIQ)中 GAMMA 编码的(Y')部分。亮度即 CIEXYZ 中的 Y 是线性的,而且并不具有感知性。 W3C 正在使用线性亮度和简单对比度,这在中间范围内不能正确地定义对比度(偏差很大)。我正在撰写一篇关于此问题的文章,完成后会发布链接。是的,CIELAB 很出色,但 W3C 并未使用它。我所指的过时文档是 ANSI-HFES-100-1988,不适用于屏幕上的颜色对比。 - Myndex
4
仅作补充/更新:我们目前正在研究替代算法,以更好地模拟感知对比度(见Github Issue 695中的讨论)。但是,作为另一个问题的信息,需要注意的是sRGB的阈值为0.04045,而不是从过时的早期sRGB草案引用的0.03928。权威的IEC标准使用0.04045,并且即将推出拉取请求以在WCAG中纠正此错误(参考:IEC 61966-2-1:1999)。这提到了Github issue 360,需要说明的是,在8位中实际上没有区别 - 在线程360的末尾,我有包括0.04045 / 0.03928在内的误差图表。 - Myndex
2
另外补充一下,WCAG 3.0的替代方法是APCA,可以在https://www.myndex.com/APCA/simple上查看。 - Myndex

13

除了其他人说的内容之外,还需要补充:

这些公式在实践中效果还不错,但如果您需要非常精确的结果,您需要先将颜色转换为线性颜色空间(应用反图像伽马),对三种基本颜色进行加权平均,如果您想显示颜色,则需要将亮度恢复到监视器伽马。

忽略伽马值和使用正确的伽马值之间的亮度差异,在暗灰色方面高达20%。


9

1
仅供记录,该链接已失效,存档版本在此处 - https://web.archive.org/web/20150906055359/http://en.literateprograms.org/RGB_to_HSV_color_space_conversion_(C) - Peter
2
HSV不是感知均匀的(甚至离此相差甚远)。它仅用作“方便”的调整颜色的方式,但与感知无关,而V与L或Y(CIE亮度)的真实值无关。 - Myndex
1
这是否意味着 #FF0000#FFFFFF 一样亮? - Klesun
我认为这更像是在HSL中的 lightness = (max(r, g, b) + min(r, g, b)) / 2 - Klesun

8

今天我在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://dev59.com/QHRB5IYBdhLWcg3wiHpl
 * @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://dev59.com/7G035IYBdhLWcg3wGMDa
 * @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))))
}

7
考虑将此作为Myndex's excellent answer的补充。正如他(和其他人)所解释的那样,用于计算RGB颜色的相对亮度(和感知亮度)的算法是设计用于处理线性RGB值的。您不能仅将其应用于原始sRGB值并希望获得相同的结果。
理论上听起来很好,但我真的需要看到证据,因此,受Petr Hurtak's colour gradients的启发,我自己制作了这些。它们说明了两种最常见的算法(ITU-R建议BT.601BT.709),并清楚地表明为什么应该使用线性值进行计算(而不是伽马校正值)。

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

ITU-R BT.601颜色亮度渐变

0.299 R + 0.587 G + 0.114 B

ITU-R BT.601 colour luminance gradients

在这个分辨率下,左侧的实际上看起来相当不错!但是如果你仔细看,就会发现一些问题。在更高的分辨率下,不需要的伪影更加明显。

ITU-R BT.601 colour luminance gradients (high res)

线性的不会受到这些影响,但是会有很多噪音。让我们将其与ITU-R建议BT.709进行比较...

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

0.2126 R + 0.7152 G + 0.0722 B

ITU-R BT.709 colour luminance gradients

哦,天啊。这显然不是用于原始sRGB值的!然而,大多数人都会这样做!

ITU-R BT.709 colour luminance gradients (high-res)

在高分辨率下,当使用线性值时,您可以真正看到这个算法的有效性。与早期的算法相比,它几乎没有噪音。虽然这些算法都不是完美的,但这个算法是最好的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接