I am attempting to write a class library that can be used to take an input colour in any format and returns one of two colours that subscribes to the W3 standards for good readability. Due to the font weight for the specific case, I only need to meet the 4.5:1 ratio for this version.
In order to calculate the contrast I am using the formula (l1 + 0.0005) / (l2 + 0.0005)
to work out the contrast ratio. To do this I need to calculate the luminance of the RGB colour, the method I am using is based originally on this method because it is an expansion to an HSL colour space I have written.
This is the method used to calculate which text colour to return. It's designed to have default colours of #121212 and #EAEAEA as the text colours for light or dark backgrounds respectively. However, if neither of these is good enough contrast, then it tries again with #000000 and #FFFFFF. If neither of these work, it defaults out black.
public static (int R, int G, int B) FindTextColor(int R, int G, int B, int darkR = 0x12, int darkG = 0x12, int darkB = 0x12, int lightR = 0xEA, int lightG = 0xEA, int lightB = 0xEA)
{
double bgLuminance = GetLuminanceOfRGB(R, G, B);
double lightTextLuminance = GetLuminanceOfRGB(lightR, lightG, lightB);
double lightContrastRatio = (Math.Max(lightTextLuminance, bgLuminance) + 0.0005) / (Math.Min(lightTextLuminance, bgLuminance) + 0.0005);
double darkTextLuminance = GetLuminanceOfRGB(darkR, darkG, darkB);
double darkContrastRatio = (Math.Max(darkTextLuminance, bgLuminance) + 0.0005) / (Math.Min(darkTextLuminance, bgLuminance) + 0.0005);
if (lightContrastRatio >= 4.5)
{
return (lightR, lightG, lightB);
}
else if (darkContrastRatio >= 4.5)
{
return (darkR, darkG, darkB);
}
else
{
if (darkR != 0x00 && darkG != 0x00 && darkB != 0x00 && lightR != 0xFF && lightG != 0xFF && lightB != 0xFF)
{
return FindTextColor(R, G, B, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF);
}
else
{
return (0, 0, 0);
}
}
}
The following is the method GetLuminanceOfRGB(), which uses the first stage of the conversion in the previously mentioned link.
public static double GetLuminanceOfRGB(int R, int G, int B)
{
//1, Convert the 1-255 RGB values into 0-1 values
double red = R / (double)255;
double green = G / (double)255;
double blue = B / (double)255;
//2, Find the minimum and maximum values out of R, G and B
double minimum = Math.Min(Math.Min(red, green), Math.Min(green, blue));
double maximum = Math.Max(Math.Max(red, green), Math.Max(green, blue));
//3, Calculate the luminance from the max and minimum.
return (double)Math.Ceiling((maximum + minimum) / 2.0 * 100) / 100;
}
This produces values for the luminance in the form of a double between 0 and 1 which represents a percentage where 0 is black and 1 is white.
Most of the time, this method works fine for colours like #FFFF00 or #FF0000. The issue arises when the blue channel has precidence.
The colour that I am having particular trouble with is #3041FF (A sort of blue) that has a luminance of 59%. While this is above 50%, the blue shades are much darker in appearance than a yellow would be, despite it being theoretically bright.
Currently, the method tells me that the contrast ratio between #3041FF and #EAEAEA is 1.53 approximately, whereas this contrast finder tells me the contrast ratio is 5.25.
I can supply the full text for the RGB-HSL conversion method if requested.
So far I have attempted several things, including changing my luminance to this calculation: L = 0.2126 * R + 0.7152 * G + 0.0722 * B
, which didn't solve the issue, it just made the contrast ratios for light around 11.4 and dark around 11.8, which does not resolve the issue.
I also attempted using relative luminance using this method:
public static double GetRelativeLuminance(double L) {
if (L < 0 || L > 1) {
throw new ArgumentOutOfRangeException("Luminance may not be greater than 1 or less than 0.");
}
double lr = 0;
if (L <= 0.04045)
{
lr = (L + 0.05) / 0.255;
}
else
{
lr = Math.Pow(((L + 0.05) / 1.055), 2.4);
}
return lr;
}
Which also did not resolve the issue, however looking back on it I may have gotten the scale wrong with the if statement.
I have also tried just creating HSL colours from the original hex input and using the luminance of those, just in case I had written the new method wrong in some fashion.
All of this has led to the same result of the code giving me the #121212 font colour, and I'm not sure why any more.