1

I've read a lot in the very complete post: Formula to determine brightness of RGB color

What bothers me is that even with all those formulas, I can't get a fine result... And this is quite bothering since I want to sort colors in a palette, from darkest to lightest. If the sorting is wrong, it is quickly a lot of pain to watch.

Quick example with the formula: (0.2126*R + 0.7152*G + 0.0722*B) which seems to be the most common answer over the web.

Edited after Peter's answer

function colorCodeToRGB(colorCode) {
    colorCode = colorCode.substr(1);
    return [
        colorCode.substr(0, 2),
        colorCode.substr(2, 2),
        colorCode.substr(4, 2)
    ].map(it => parseInt(it, 16));
}

const luminanceCoefficients = [.2126, .7152, .0722];
function getLuminance(color) {
  const [r, g, b] = colorCodeToRGB(color);
  return r * luminanceCoefficients[0] + g * luminanceCoefficients[1] + b * luminanceCoefficients[2];
}
function linearizeSRGB(colorChannel) {
    colorChannel /= 255;
    if (colorChannel <= .04045 ) {
        return colorChannel / 12.92;
    } else {
        return Math.pow((colorChannel + .055)/1.055, 2.4);
    }
}
console.log('First set of colors');
console.log('#1883b1', getLuminance('#1883b1'));
console.log('#2c3b4c', getLuminance('#2c3b4c'));
console.log('Second set of colors');
console.log('#920f1e', getLuminance('#920f1e'));
console.log('#c3313d', getLuminance('#c3313d'));
.c {
  height: 2rem;
  width: 2rem;
}
Sample of colors 
<span class="c" style="background-color: #1883b1">&nbsp;&nbsp;&nbsp;</span>
<span class="c" style="background-color: #2c3b4c">&nbsp;&nbsp;&nbsp;</span>
<span class="c" style="background-color: #920f1e">&nbsp;&nbsp;&nbsp;</span>
<span class="c" style="background-color: #c3313d">&nbsp;&nbsp;&nbsp;</span>

Everyone can see that the first blue is lighter than the second one, and its value is smaller, although on the second set, the first red seems darker than the second one, although it has a smaller value...

I don't get it, is there some way to actually determine perceived brightness?

I've tested all of the formulas on the post mentioned at the beginning of this question, and by sliding the color picker, I always find funny cases that illustrate a problem. I've no constraints, I can work with HSL, RGB, CIELAB, whatever!

Thanks by advance guys!

sjahan
  • 5,720
  • 3
  • 19
  • 42
  • Don't use `rbg()`. Use [`hsl()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) (Hue, Saturation, Luminosity). Scroll down to the HSL section in the linked page. – Scott Marcus Jan 05 '20 at 19:15
  • Hi @ScottMarcus Unfortunately, that does not help me much: the L in HSL is quite simplistic and does not match the perceived brightness: a blue with L = 100% and a yellow with L = 100% won't have the same brightness in the end (the yellow, obviously, will look lighter than the blue). – sjahan Jan 05 '20 at 19:20
  • If you want to be precise: https://en.wikipedia.org/wiki/CIECAM02 , As you know, yellow seems brighter, but maybe you have seen a lot of optical illusion: perceived brightness depends on hue, surrounding colours, adaptation, etc.. I would skip the linear part of gamma: it is not how screens work (and I think it was later removed for the reverse case [electric to optic], and for the other case: we define own response curves, all modern photo cameras will do it) – Giacomo Catenazzi Jan 08 '20 at 09:40
  • You have a linearizesRGB function, but you don't call it anywhere?? You must linearize before applying coefficients. Also, luminance will **not** tell you perceived lightness. At a minimum you then need to convert to CIELAB to predict perceived lightness, but there are other better models as Giacomo mentioned CIACAM02 is one of them. – Myndex Feb 08 '20 at 10:50

2 Answers2

2

Apparently there are at least two issues with your implementation.

The first is that the ^ operator (in ^2.4) is not a power operator in JavaScript, but an "exclusive-or" operator. Replace the line where that operator appears with the following.

        return Math.pow(((colorChannel + .055)/1.055), 2.4);

Also, the getLuminance method is implemented incorrectly; the main reason for that might be the reduce method, which can be confusing, at least to me. Replace that implementation with the following (which uses the much simpler map as well as direct addition and multiplication):

function getLuminance(color) {
  var cv=colorCodeToRGB(color).map(v=>linearizeSRGB(v))
  return cv[0]*luminanceCoefficients[0]+
            cv[1]*luminanceCoefficients[1]+
            cv[2]*luminanceCoefficients[2]
}
Peter O.
  • 32,158
  • 14
  • 82
  • 96
  • Thanks for your help! I've actually copy pasted some formula too quick and `^` operator doesn't work. I'll update my post, though: it still does not work and my color's order is messed up :( – sjahan Jan 05 '20 at 20:40
  • For some reason, my dev-server was not reloading the modification! I think that's ok :) Thank you very much mate! I almost lost my mind on this stupid mistake!! – sjahan Jan 05 '20 at 20:46
  • Just for the record, the `reduce` method just missed the init parameter set to `0`. If undefined, it is the first element of the array, which leaded to miscalculation. – sjahan Jan 08 '20 at 10:58
0

Ultra Simple sRGB to Perceived Lightness

Super simple JS function to convert sRGB (as integers R,G,B) to perceptual lightness (~ L*). This is the down-and-dirty simplified method, which should provide a reasonable prediction for most general use cases.

function lightness(Rint,Gint,Bint) { // takes sRGB channels as 8 bit integers

    var Rlin = (Rint / 255.0) ** 2.218;   // Convert int to decimal 0-1 and linearize
    var Glin = (Gint / 255.0) ** 2.218;   // ** is the exponentiation operator, older JS needs Math.pow() instead
    var Blin = (Bint / 255.0) ** 2.218;   // 2.218 Gamma for sRGB linearization. 2.218 sets unity with the piecewise sRGB at #777 .... 2.2 or 2.223 could be used instead

    var Ylum = Rlin * 0.2126 + Glin * 0.7156 + Blin * 0.0722;   // convert to Luminance Y

    return Math.pow(Ylum, 0.43) * 100;  // Convert to lightness (0 to 100)
}

Those familiar with sRGB and CIELAB will notice that the piecewise functions with the near black linearities are dropped here. This was done in the interest of simplicity. In practice this works as we are usually interested in the perceived lightness for photopic vision, and not for very dark values.

The 0.43 exponent in the return is good for most cases, but if you are comparing large areas (big blocks of color) you could lower it to as low as 0.33, or if trying to predict lightness of tiny pinpoints of light, you could raise it to 0.5 (see Stevens for background on these values)

Myndex
  • 3,952
  • 1
  • 9
  • 24