enhance(ui): improve luminance calculations - #38682
Conversation
The luminance used to pick black or white foreground text summed gamma-encoded channels, so it was not a relative luminance despite the name. Linearize the channels first, and move the flip point from 0.453 to 0.36, which tracks APCA far more closely than either the old value or WCAG's own 0.179 crossover. Scoped label shading keeps the previous gamma-space value via GetPerceivedBrightness, because it derives factors that scale the raw channels proportionally and only holds in that space. Assisted-by: Claude Code:Opus 5
The scoped label local held a gamma-encoded brightness, not a luminance. getRelativeLuminance lost its only caller outside the package when scoped label shading moved to GetPerceivedBrightness, so it no longer needs to be exported. Assisted-by: Claude Code:Opus 5
| func GetPerceivedBrightness(color string) float64 { | ||
| r, g, b := HexToRBGColor(color) | ||
| return (0.2126729*r + 0.7151522*g + 0.0721750*b) / 255 | ||
| } |
There was a problem hiding this comment.
This is technically computing the luma, which is a quick-and-dirty approximation of the gamma-encoded luminance. It is used in video only because it can be computed very cheaply, even with simple resistors in an analog circuit. Luma is a reasonable approximation of brightness for low-saturation colors. However, it is quite bad when the colors get saturated.
If you want a representation of the luminance in the same gamma-encoded color space as the initial sRGB values, the proper solution is to gamma-encode the output of getRelativeLuminance(), i.e. to perform the linearizeChannel() transform backwards on the relative luminance. This ensures that colors have the same “perceived brightness” if and only if the have the same luminance, which is required by the definition of “luminance”.
There was a problem hiding this comment.
I think ultimately the approximation is good enough for our use case, but sure I will investigate a bit more. The current implementation is already better than what browser vendors recently implemented as part of color-contrast.
There was a problem hiding this comment.
Didn't know about color-contrast. Thanks for the pointer! Luma is indeed a very common approximation, so it is presumably good enough most of the time. It can still be pretty bad with saturated colors, like pure blue:
luma = 0.0722 * 255 = 18.411, rounded to 18 = 0x12
luminance = 0.0722,
gamma-encoded to 0.2979 * 255 = 75.963, rounded to 76 = 0x4c
See the figure below: each blue rectangle is supposed to have the same “brightness” as the grey rectangle around it, for two different meanings of “brightness”. YMMV but, to my eyes, luma here fails miserably at conveying the brightness of the blue box.
Improve luminance calculations for label text colors and project columns as per #37477 (comment) which improves contrast on a few mid-tones as seen in screenshot below.
Channels are now linearized before weighting, which is what WCAG relative luminance requires, and the flip point moves from 0.453 to 0.36 to best match APCA. Scoped label shading keeps the old gamma-encoded value via
GetPerceivedBrightness.Left side text is main, right is this branch: