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
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
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.
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"
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>
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).
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.
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..).
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.