How to calculate the best text color to contrast with background in rails

Viewed 92

I need to calculate a color for the text, when a background is set and that color should be the best contrast possible. The color should not be between white or black, but some color that makes sense for contrast. is there any idea? tks

3 Answers

Possible solution:

class InvalidFormat < StandardError; end

RGB_REGEXP = /\A#(..)(..)(..)\z/
VALID_FORMAT = /\A#[a-fA-F0-9]{6}\z/

def invert_color(hex, black_white = false)
  raise InvalidFormat unless hex =~ VALID_FORMAT

  red, green, blue = hex.match(RGB_REGEXP).captures.map(&:hex)

  return (red * 0.299 + green * 0.587 + blue * 0.114) > 186 ? '#000000' : '#FFFFFF' if black_white

  red = (255 - red).to_s(16)
  green = (255 - green).to_s(16)
  blue = (255 - blue).to_s(16)
  ['#', *[red, green, blue].map { |c| c.rjust(2, '0') }].join
end
invert_color('#de34fc')

# => #21cb03

invert_color('#ffffff')

# => #000000

invert_color('#000000')

# => #ffffff

invert_color('#de34fc', true)

# => #ffffff

Note: You need to pass color in format #XXXXXX or convert #XXX to #XXXXXX before passing. And also add prefix # to format XXXXXX

You can try the Chroma-Gem.

def invert_color(color)
  rgba = Chroma.paint(color).rgb
  Chroma.paint("rgba(#{255 - rgba.r}, #{255 - rgba.g},#{255 - rgba.b}, 1.0").to_hex
end  

You should get the inverted color of the color in the parameter.

Short Answer:

There is no short answer, ultimately this is a non-trivial question. But for a simple black/white text color flipper, see my GitHub repo, "Fancy Font Flipping"

Longer Answer:

First of all, "best contrast" is not necessarily "most contrast". It is possible to exceed a maximum contrast in a number of contexts.

Second, for small text, contrast is less about a pair of colors, and more about "spatial frequency", i.e. the weight and the size.

Third, for any given hue, there are some hues that interfere with each other, particularly some hues that, on a color "wheel", are opposite of each other. Certain so-called "complementary" colors are really more "oppositional" colors, and can carry with them certain artifacts, such as chromatic aberration.

An easy example here is full red and full blue:

<div style="background-color: #f00; color: #00f;">This Is A Rather Bad Design Choice™</div>

"This is a rather bad design choice™" in full blue on a full red background

And fourth, an "appropriate" hue to contrast with some other hue is very subjective, and therefore not well within the reach of an automated method. In most cases, the safest is to maintain hue, and just adjust the luminance and the chroma (saturation).

What's The Context El-Con-Text-Olay

And again, contrast perception is so much more than just the difference of a pair of colors, without some reasonable constraints the results may be more unpredictable than you'd like.

A context sensitive contrast measure that is being developed for the future WCAG 3, and for other standards, is the APCA, and the npm package npm i apca-w3 has a reverseAPCA() that takes one color and a target contrast value, and returns a color of that contrast, though at the moment it only returns a greyscale. It's a JS library, but the basic functions should be trivial to port to Ruby.

It has a parameter option to return a luminance instead, and you could use that and integrate a CIELAB or CIELUV colorspace to also provide a hue/chroma correlate.

There is an interesting sRGB centric version of LUV known as HSLuv that is relatively perceptually uniform that might be useful for your purposes. It can be seen at HSLuv.org

The L in HSLuv is L* (Lstar) which is a semi-uniform perceptual lightness, and using ∆L* (i.e. Lstar difference) you get a somewhat usable contrast prediction, though it's not that accurate in terms of TEXT contrast, it's useful with large patches of color. But it is probably a "best start" for you in terms of a real, useful, perceptual model of color contrast.

How HSLuv Might Work For You

In HSLuv, Hue is 0-360, S and L are 0-100. So if you have color A as 260°H 92S 33L* then a color that contrasts with that (same hue) might be 260°H 100S 83L*

For some color schemes, you might want to rotate the hue some set distance clockwise or CCW... or decrease saturation... or increase it... these are all largely subjective choices.

But what this is leading to is: the IMPORTANT thing in contrast is the achromatic luminance contrast, and here that is the ∆L*. In APCA it is the Lc value (lightness contrast) and again, is "essentially" the achromatic parameter.

But to change the achromatic L* or Lc value, and also have control of hue/chroma, you need to add in a model like HSLuv. I'm working on such a feature for APCA, but it's "not ready for primetime", and it's using yet another color space/model (SACAM... maybe probably or not..).

Readability Contrast vs Color Contrast

As a final note: For readability, it is luminance contrast, or achromatic lightness/darkness that is important, regardless of hue.

But for discernibility (i.e. lines on a map) color (hue chroma) are important, and then it also becomes important to consider those users with color insensitive vision (inaccurately called color blind). Color insensitive have the same contrast needs as standard vision for READABILITY. But for DISCERNIBILITY, color insensitive have additional needs due to their various difficulties discriminating hues.

So, if you are working on contrast for readability of text, it is the Y, L*, or Lc value that is important. But if you are working on other design features (like dataviz) then you need to add in ways to help discriminate that are not based solely on hue.

Related