How can I invert the luminance component of a `Col...
# compose
j
How can I invert the luminance component of a
Color
? (by invert I mean doing 1-x, in other words if luminance is 0.75 inverting it yields a luminance of 0.25). I’ve tried something like this but with little success:
Copy code
val xyz = myColor.convert(ColorSpaces.CieXyz)
val invertedLuminance = xyz.copy(green = 1 - xyz.green)
val result = invertedLuminance.convert(ColorSpaces.Srgb)
r
Don’t go through XYZ, instead you should use the HSL color space
j
Lightness == Luminance ? (well… if we translate latin to english it would seems so…)
But how to convert to HSL an sRGB
androidx.compose.ui.graphics.Color
?
r
Well technically the L in HSL is not really lightness either
😅 1
it’s more like brightness or luminance
j
I found a
Color.hsl(...)
experimental API that seems to create a
Color
from HSL values, though my input is a RGB
Color
: is there any existing API to transform RGB to HSL?
r
Sorry I missed this question. But yeah you found the answer :)
Anyway to expand on what I was saying earlier:
• luminance is emitted light
• brightness is perceived luminance
• lightness is brightness relative to other brightness (spatial variation of perceived luminance)
j
You’re a well of wisdom.
This is my concoction to try to achieve luminance inversion:
Copy code
val myColor = Color.White
val hslArray = FloatArray(3)
ColorUtils.RGBToHSL(
  (myColor.red * 255.0f + 0.5f).toInt(),
  (myColor.green * 255.0f + 0.5f).toInt(),
  (myColor.blue * 255.0f + 0.5f).toInt(),
  hslArray
)
val invertedHsl = FloatArray(3).apply {
  this[0] = hslArray[0]
  this[1] = hslArray[1]
  this[2] = 1 - hslArray[2]
}
val invertedRgb = ColorUtils.HSLToColor(invertedHsl)
return Color(invertedRgb)
Whilst it works for white and black (it inverts them to black and white respectively) it’s not exactly working as expected for other colors. What I had in mind by “inverting the luminance” was doing (1-x) with the Y value of the color converted to the YPrPb color space. I tried to check here: http://colorizer.org/ for the intended effect and that’s exactly what I had in mind, though I can’t find APIs for conversion from RGB to YPbPr in Android, do you know if there is any or is this all “uncharted territory” ?
r
It’s not “uncharted” territory, the formulas are well defined and not hard to implement
But instead of YPbPr you could use Lab which is provided by Compose’s APIs already
Note that in Lab the values are not necessarily in the range 0..1
L
in particular is in the range
0..100
You can also use Oklab instead of Lab to get a more perceptually linear model
j
Thanks! What about:
Copy code
val myColor = Color.White
  val lab = myColor.convert(ColorSpaces.CieLab)
  val inverted = lab.copy(red = 100 - lab.red)
  val invertedRgb = inverted.convert(ColorSpaces.Srgb)
  return invertedRgb
I couldn’t find Oklab though.
Note that in Oklab, the L component is in the range 0..1 🙂
(you can call
getMin/MaxValue(0)
on the
ColorSpace
object to automate this)
What are you trying to do btw?
j
It’s there: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/[…]/androidx/compose/ui/graphics/colorspace/ColorSpaces.kt;l=280
That’s why autocomplete didn’t find it: In compose 1.1.1 that line is marked as
internal
.
What are you trying to do btw?
We display text onto a surface of arbitrary color. To avoid readability issues we detect when the contrast between the surface color (bg) and the text color (fg) falls under a certain threshold and when it happens we’d like to transform the fg color to increase readability. I’m trying to find a decent enough transform. Since contrast is more or less a ratio of luminances (pardon if this sounds as blasphemy to your ears 🙂 ), I thought that by inverting the luminance of the fg color I could increase the contrast with the bg whilst maintaining some sort of “similarity” between the input fg color and the output one (similarity in the sense that if it’s a blue it should still be some kind of blue and so on...).
r
Ah right. Well the idea is not bad but the problem is that it’s not how our perception works (which is really annoying). It’ll mostly work, kind of 🙂
j
“Mostly, kind of” is our measure of success here 😄. Moreover that's what you get when the chap writing the code has color blindness 😅.
Btw, thanks for bearing with me until here. Appreciate it.
r
Happy to help!
Let me know how CIE Lab and Oklab work for you, and if that’s not good enough, there are other solutions
👌 1
Btw your approach won't work when luminance is in the mid range (say luminance = 0.5 or close)
j
Btw your approach won’t work when luminance is in the mid range (say luminance = 0.5 or close)
Indeed, but let’s worry about this “subtle” detail later… 🤦
This is a rough preview using CieLab:
Red is a bit “unlucky”.
This test pattern has the same line of text drawn twice, the first line is not visible because its color is the same as the bg (to simulate the worst case, least contrast scenario) the second line has the color with the transform applied.
Interestingly the last test color (green-ish) has the first line of text also visible (barely), even though technically it is the same exact color as the background.
(I’d like to test Oklab too but AS Bumblebee preview window goes bonkers with Compose 1.2.0-alpha… I’ll try to fix this later on…)
r
Blue is the color of least luminance so that result isn't surprising
You could use an LCh model instead
Oh wait no nevermind
Wrong "C" :)
j
I was able to upgrade to compose 1.2.0-alpha07 so here are Lab, Xyz and Oklab compared. Strangely enough the last (bottom) bg color, which is rgb(0,139,0), looked different with compose 1.1.1 and now it’s changed considerably.
You could use an LCh model instead
You mean this: https://en.wikipedia.org/wiki/HCL_color_space ? It’d be fun if compose had APIs for it.
But I guess that there is some color “trickery” in our testing pipeline 🤣 I seem to perceive the colors in the testing pattern in a slightly different way when viewing them inside AS preview window, or Mac’s Preview app or Slack 🤦
r
Note: I was playing with Oklab yesterday and I found several bugs in the implementation
I wouldn’t trust it for now 😕
👌 1
I seem to perceive the colors in the testing pattern in a slightly different way when viewing them inside AS preview window, or Mac’s Preview app or Slack
Welcome to color management
This means one or more of those apps are not handling the color profiles properly
I recently filed bugs to both the Studio and emulator teams because if your display is not exaclty sRGB, then colors are displayed incorrectly
I know Studio has been fixed already, so latest canaries should be good to go
👌 1
j
I’ll stick with CieLab for now and will try to switch to OkLab when compose 1.2 hits stable. I guess we can call this an exercise 🙂. Anyhow its output, albeit far from perfect, is enough for what I needed. Thank you!