How to check brightness of a background color to decide text color written on it
Asked Answered
E

3

5

I have a simple question, but I wasn't able to find an answer to this. Note, that I'm almost a complete beginner.

So I have an app (it's not mine, but I'm contributing to it), and in there is writing on a color background, which can be changed by the user. The writing should appear black if the background is bright enough but stay white if it isn't.

The application is a school diary app for elementary and high-school students that connects to the state-wide school diary service in Hungary. Here, the best note is 5, the worst is 1. The user can set the colors of each grade in the settings. Right now, only the code for the note "4" is hard-coded to have black text (because the background is yellow by default on "4" notes), all others have white. This is what I want to automate.

Example of white text

Example of black text

This is the main screen of the app for reference

Page where user can change color for a kind of note

Code right now:

switch (evaluation.NumberValue) {
    case 1:
      bColor = globals.color1;
      fColor = Colors.white;
      break;
    case 2:
      bColor = globals.color2;
      fColor = Colors.white;
      break;
    case 3:
      bColor = globals.color3;
      fColor = Colors.white;
      break;
    case 4:
      bColor = globals.color4;
      fColor = Colors.black; //This should be white if color4 is dark enough. Same applies to all of them.
      break;
    case 5: //I'm looking for something like this:
      bColor = globals.color5;
      fColor = (lightLevel(globals.color5) > 50) ? Colors.black : Colors.white;
      break;
    default:
      bColor = Colors.black;
      fColor = Colors.white;
      break;
  }

I'm looking for something like this:

 case 5: //I'm looking for something like this:
  bColor = globals.color5;
  fColor = (lightLevel(globals.color5) > 50) ? Colors.black : Colors.white;
  break;

Thank you for any help!

Encumbrance answered 25/1, 2020 at 13:35 Comment(0)
S
1

Short Answer

I've written about this in the past here, also on GitHub, with some interactive experiments.

This question is asked often enough that I recently setup a repo for a lean and mean font color flipper called Max Contrast

Longer Answer

To encapsulate previous answers:

  1. Luminance contrast is what is needed for reading, therefore the colors used in a design must be converted to an achromatic luminance using the weighted values for each of the red, green, blue monitor primaries.

  2. Using perceptually uniform contrast math such as APCA, you can determine the text contrast-center point between white and black.

    • The center is actually a small range where either black or white may have an advantage depending on the context of use, environment, and other factors.
  3. Using standard luminance calculations, an ideal center for SDR sRGB displays is about 36 Y. The range is about 34Y to 42Y, depending on the factors.

  4. Another factor for the center area, is the need for a larger font than the higher contrast outer regions. The range is about 22Y thru 53Y, ish, or negligible if already using a substantial font. This is demonstrated at the "Flip for Color" demo page.

  5. The essential snippet from Max Contrast:

    // Simple: send it sRGB values 0-255.
   // If Ys is > 0.342 it returns 'black' otherwise returns 'white'
  // See  https://github.com/Myndex/max-contrast 
 // trc = gamma, Rco = red coefficient, Rs = Red signal
// Ys = estimated screen luminance (APCA 0.0.98G4g)

function maxContrast (Rs = 164, Gs = 164, Bs = 164) {
  
  const flipYs = 0.342; // based on APCA™ 0.98G middle contrast BG
  const trc = 2.4, Rco = 0.2126729, Gco = 0.7151522, Bco = 0.0721750; // 0.98G
  let Ys = (Rs/255.0)**trc*Rco + (Gs/255.0)**trc*Gco + (Bs/255.0)**trc*Bco; 

  return Ys < flipYs ? 'white' : 'black'
}

Note: This snippet uses APCA compatible constants. The flip value is set to YS 34.2 which is APCA estimated screen luminance, optimized for self illuminated displays, and slightly different from CIE luminance (Y) calculations. The "font range" where larger fonts are needed is YS 20 to YS 50.

Sweptback answered 18/9, 2023 at 5:16 Comment(3)
Thanks for this technically based answer, great!Encumbrance
I know that "Rco" in the code means "Red Color". But what Rs, Bs, Gs, trc and Ys mean?Parishioner
Hi @SeptianDika ... awww nuts, sorry no, Rco does not mean red color, it means red coefficient I may need to clarify these things. It's a constant for adjusting the relative luminance contribution of the red signal. Rs means red from sRGB, trc means transfer curve or tone response curve, and Ys means estimated screen lightness. There is a glossary at readtech.org/ARC It's a work in progress...Sweptback
F
13

I can suggest 2 options:

  1. ThemeData.estimateBrightnessForColor method e.g.
Color calculateTextColor(Color background) {
  return ThemeData.estimateBrightnessForColor(background) == Brightness.light ? Colors.black : Colors.white;
}

  1. Color.computeLuminance method e.g.
Color calculateTextColor(Color background) {
  return background.computeLuminance() >= 0.5 ? Colors.black : Colors.white;
}

Keep in mind that both are computationally expensive to calculate. The former uses the latter one under the hood. The difference is in how to compute the threshold between light and dark.

Flapdoodle answered 25/1, 2020 at 13:54 Comment(3)
Thank you so much! I wasn't able to get to work the first one, but it's no problem if the method is slow, it only gets called one, when the user changes the color.Encumbrance
The first one is expensive as well, cause it uses computeLuminance under the hood. To keep in mind.Knobby
@Przemo good point. Thank you for your contribution to the answer!Flapdoodle
J
2
Brightness.estimateBrightnessForColor method e.g.
Color calculateTextColor(Color background) {
  return Brightness.estimateBrightnessForColor(background) == Brightness.light ? Colors.black : Colors.white;
}

I found that the method estimateBrightnessForColor is now part of the class ThemeData, so need to be called like this now:

ThemeData.estimateBrightnessForColor(background)
Jolandajolanta answered 16/5, 2021 at 14:54 Comment(2)
Thank you, I've updated the correct answer tick accordingly :)Encumbrance
And I've changed back the accepted answer, as this uses expensive calculations too.Encumbrance
S
1

Short Answer

I've written about this in the past here, also on GitHub, with some interactive experiments.

This question is asked often enough that I recently setup a repo for a lean and mean font color flipper called Max Contrast

Longer Answer

To encapsulate previous answers:

  1. Luminance contrast is what is needed for reading, therefore the colors used in a design must be converted to an achromatic luminance using the weighted values for each of the red, green, blue monitor primaries.

  2. Using perceptually uniform contrast math such as APCA, you can determine the text contrast-center point between white and black.

    • The center is actually a small range where either black or white may have an advantage depending on the context of use, environment, and other factors.
  3. Using standard luminance calculations, an ideal center for SDR sRGB displays is about 36 Y. The range is about 34Y to 42Y, depending on the factors.

  4. Another factor for the center area, is the need for a larger font than the higher contrast outer regions. The range is about 22Y thru 53Y, ish, or negligible if already using a substantial font. This is demonstrated at the "Flip for Color" demo page.

  5. The essential snippet from Max Contrast:

    // Simple: send it sRGB values 0-255.
   // If Ys is > 0.342 it returns 'black' otherwise returns 'white'
  // See  https://github.com/Myndex/max-contrast 
 // trc = gamma, Rco = red coefficient, Rs = Red signal
// Ys = estimated screen luminance (APCA 0.0.98G4g)

function maxContrast (Rs = 164, Gs = 164, Bs = 164) {
  
  const flipYs = 0.342; // based on APCA™ 0.98G middle contrast BG
  const trc = 2.4, Rco = 0.2126729, Gco = 0.7151522, Bco = 0.0721750; // 0.98G
  let Ys = (Rs/255.0)**trc*Rco + (Gs/255.0)**trc*Gco + (Bs/255.0)**trc*Bco; 

  return Ys < flipYs ? 'white' : 'black'
}

Note: This snippet uses APCA compatible constants. The flip value is set to YS 34.2 which is APCA estimated screen luminance, optimized for self illuminated displays, and slightly different from CIE luminance (Y) calculations. The "font range" where larger fonts are needed is YS 20 to YS 50.

Sweptback answered 18/9, 2023 at 5:16 Comment(3)
Thanks for this technically based answer, great!Encumbrance
I know that "Rco" in the code means "Red Color". But what Rs, Bs, Gs, trc and Ys mean?Parishioner
Hi @SeptianDika ... awww nuts, sorry no, Rco does not mean red color, it means red coefficient I may need to clarify these things. It's a constant for adjusting the relative luminance contribution of the red signal. Rs means red from sRGB, trc means transfer curve or tone response curve, and Ys means estimated screen lightness. There is a glossary at readtech.org/ARC It's a work in progress...Sweptback

© 2022 - 2024 — McMap. All rights reserved.