|
"Directly downloading images is not permitted on the Netfirms FREE plan. If you are the owner of this site, either ensure that this image is embedded in a web page, or upgrade to one of the Netfirms premium plans."
Now do you still persist your method is perfect and "work in all cases"?
Yes. It is not perfectly contrasting. If you want that, there are better ways (see below). It is simple, though, and a certain (i.e. constant) level of contrast (being 221 units) is guaranteed. That's perfect in my book. (I meant a perfect method of getting a contrasting colour, not a method for getting a perfectly contrasting colour).
To get the maximum level of contrast, use this (pseudocode):
result = black;
if (source.red < 128) result.red = 255;
if (source.green < 128) result.green = 255;
if (source.blue < 128) result.blue = 255;
That'll always give you maximum contrast (ranging from 211 to 443) by ensuring it's a corner point on the colour cube that is as far away from the orginal colour as possible.
|
|
|
|
|
|
since there's always a difference of 128 between the original and contrasting values for each colour channel
Examples:
RGB=(128,128,128) your method would result in contrast color RGB=(127,127,127)
same with any color close to 128
153 would result in contrast value of 102
135 would result in 120
etc...
Steve
|
|
|
|
|
That's not a difference of 128, that's a difference of 1. Recheck the thing I posted:
128 would result in 0
152 would result in 25
135 would result in 7
(and vice versa, of course).
Wat XOR 0x80 does is simply subtract 128 if the channel value is above 128, and it adds 128 if the value is below that threshold.
|
|
|
|
|
Assuming a color in a COLORREF (format 0xaaBBGGRR) value :
COLORREF l_nColor = 0x00FF7F1F;
Getting the dark and light values :
COLORREF l_nColorDark = (l_nColor >> 1) & 0x007F7F7F);
[EDIT=Fix from Paolo Messina]
COLORREF l_nColorLight = ((l_nColor >> 1) & 0x007F7F7F) + 0x00808080);
[/EDIT]
Now getting the grey values (very accurate 16 bits fixed point version) :
COLORREF l_nColorGrey = ( (((int) (l_nColor & 0x000000FF) >> 0) * 0x00004C8B)
+ (((int) (l_nColor & 0x0000FF00) >> 8) * 0x0000941F)
+ (((int) (l_nColor & 0x00FF0000) >> 16) * 0x00001D2F)
) >> 16;
l_nColorGrey = (l_nColorGrey << 0)
| (l_nColorGrey << 8)
| (l_nColorGrey << 16)
;
This is also how we can get other interresting values, such HSL (Hue, Saturation, Light) :
COLORREF l_nColorHue = ( (((int) (l_nColor & 0x000000FF) >> 0) * 0x00008000)
+ (((int) (l_nColor & 0x0000FF00) >> 8) * 0xFFFF94D1)
+ (((int) (l_nColor & 0x00FF0000) >> 16) * 0xFFFFEB30)
) >> 16
+ 128;
COLORREF l_nColorSat = ( (((int) (l_nColor & 0x000000FF) >> 0) * 0xFFFFD4D1)
+ (((int) (l_nColor & 0x0000FF00) >> 8) * 0xFFFFAB30)
+ (((int) (l_nColor & 0x00FF0000) >> 16) * 0x00008000)
) >> 16
+ 128;
COLORREF l_nColorLight = l_nColorGrey;
Now the contrast can be get from the difference between two grey values.
Imagine you have a dark color, thus the grey value will be below 128. To get a good contrast, write in white above the color.
Imagine you have a light color, thus the grey value will be above 128. To get a good contrast, write in black above the color.
Kochise
In Code we trust !
|
|
|
|
|
Hi Kochise,
Just a small fix... in this line:
COLORREF l_nColorLight = ((l_nColor >> 1) & 0x007F7F7F) + 0x007F7F7F); you probably meant to write:
COLORREF l_nColorLight = ((l_nColor >> 1) & 0x007F7F7F) + 0x00808080); That is you add 128 to each component, instead of 127, so that max value is 255, not 254.
Paolo
------
Why spend 2 minutes doing it by hand when you can spend all night plus most of the following day writing a system to do it for you? - (Chris Maunder)
|
|
|
|
|
Never fixed it, as it comes from my PocketPC coding were the 24 bits RGB value is down to 16 bits and loss precision...
I should care more about my copy/paste :P
Kochise
In Code we trust !
|
|
|
|
|
I don't quite get your method.
So how do you exactly get the contrast color from your method?
|
|
|
|
|
No contrast if the two colors have the same grey value !
Kochise
In Code we trust !
|
|
|
|
|
so your code does not calculate the contrast color, does it?
|
|
|
|
|
Some links first :
http://astronomy.swin.edu.au/~pbourke/colour/ycc/
http://www.fourcc.org/fccyvrgb.php
http://elm-chan.org/works/yuv2rgb/report.html
http://people.via.ecp.fr/~remi/ecp/tpi/rapport/yuv.html (french)
Now you know your lesson, here the calculation :
You are wanting to always get the BEST contrast color, that's to say a color that definitly distinguish from the source color. But what do you expect from differences ?
Your math almost only rely on chrominance, which was several times spotted out has being not reliable in case of B/W (greyscale) transformation.
We are expecting a heavy duty algorithm to match both chrominance and luminance criterias. Saturation is already linked to luminance, so it's no use to take care of...
Let's first remember some math :
H = 0.5000 R - 0.4183 G - 0.0816 B [0.0, 1.0]
S = -0.1687 R - 0.3312 G + 0.5000 B [-0.5, 0.5]
L = 0.2989 R + 0.5866 G + 0.1145 B [-0.5, 0.5]
R = L + 1.4022 H [0.0, 1.0]
G = L - 0.3456 S - 0.7145 H [0.0, 1.0]
B = L + 1.7710 S [0.0, 1.0]
Now first the chrominance difference :
You have first to understand what secondary color is. Red is complementary to cyan as red is RGB(255, 0, 0) and cyan is RGB(0, 255, 255). From the Hue point of view, red is 0 and cyan 0.5 (127 on the field of 0-255). To get the best difference, just add/sub 0.5 (127) to the chrominance.
Now on a B/W TV, where all is grey, a uniform grey screen may shows several different colors having the same luminance value. As the luminance is coded from -0.5 to 0.5, the solution to get the best luminance difference is the same : add/sub 0.5 (127) to the luminance.
Now it is to write the complete math to get RcGcBc (contrast) from RoGoBo (original) :
H = 0.5000 Ro - 0.4183 Go - 0.0816 Bo + 0.5000
S = -0.1687 Ro - 0.3312 Go + 0.5000 Bo
L = 0.2989 Ro + 0.5866 Go + 0.1145 Bo + 0.5000
Here the new corrected hue and luminance. let just turn things into RGB :
Rc = (L = 0.2989 Ro + 0.5866 Go + 0.1145 Bo + 0.5000) + 1.4022 (H = 0.5000 Ro - 0.4183 Go - 0.0816 Bo + 0.5000)
Gc = (L = 0.2989 Ro + 0.5866 Go + 0.1145 Bo + 0.5000) - 0.3456 (S = -0.1687 Ro - 0.3312 Go + 0.5000 Bo) - 0.7145 (H = 0.5000 Ro - 0.4183 Go - 0.0816 Bo + 0.5000)
Bc = (L = 0.2989 Ro + 0.5866 Go + 0.1145 Bo + 0.5000) + 1.7710 (S = -0.1687 Ro - 0.3312 Go + 0.5000 Bo)
I think you get the idea :
Rc = (0.2989 + (1.4022 * 0.5000)) Ro + (0.5866 + (1.4022 * - 0.4183)) Go + (0.5866 + (1.4022 * - 0.0816)) Bo + (1.4022 * 0.5000)
Gc = (0.2989 + (- 0.3456 * -0.1687) + (- 0.7145 * 0.5000)) Ro + (0.5866 + (- 0.3456 * - 0.3312) + (- 0.7145 * - 0.4183)) Go + (0.5866 + (- 0.3456 * 0.5000) + (- 0.7145 * 0.0816)) Bo + (0.5000 + (- 0.7145 * 0.5000))
Bc = (0.2989 + (1.4022 * -0.1687)) Ro + (0.5866 + (1.7710 * - 0.3312)) Go + (0.1145 + (1.7710 * 0.5000)) Bo + 0.5000
He he, very close to the end :
Rc = 1.0000 Ro + 1.0000 Go + 0.4722 Bo + 0.7011
Gc = 0.0000 Ro + 1.0000 Go + 0.3555 Bo + 0.8572
Bc = 0.5354 Ro + 0.0000 Go + 1.4721 Bo + 0.5000
We don't have to get frighten about the values that seems to get beyon the range, it's normal and will be cleared once the value will be down-sized from int to char ! Here the final stage, let's kill the boss :
COLORREF l_nColorContrast
= (
(( (((int) (l_nColor & 0x000000FF) >> 0) * 0x00010000)
+ (((int) (l_nColor & 0x0000FF00) >> 8) * 0x00010000)
+ (((int) (l_nColor & 0x00FF0000) >> 16) * 0x000078E2)
+ 0x0000B37B
) >> 16) & 0x000000FF)
| (
(( (((int) (l_nColor & 0x000000FF) >> 0) * 0x00000000)
+ (((int) (l_nColor & 0x0000FF00) >> 8) * 0x00010000)
+ (((int) (l_nColor & 0x00FF0000) >> 16) * 0x00005B02)
+ 0x0000DB71
) >> 8) & 0x0000FF00)
| (
(( (((int) (l_nColor & 0x000000FF) >> 0) * 0x0000890F)
+ (((int) (l_nColor & 0x0000FF00) >> 8) * 0x00000000)
+ (((int) (l_nColor & 0x00FF0000) >> 16) * 0x000178DB)
+ 0x00008000
) >> 0) & 0x00FF0000)
;
Perfect, now let's optimize things a bit :
COLORREF l_nColorContrast
= (
(( (((int) (l_nColor & 0x000000FF) << 16) )
+ (((int) (l_nColor & 0x0000FF00) << 8) )
+ (((int) (l_nColor & 0x00FF0000) >> 16) * 0x000078E2)
+ 0x0000B37B
) >> 16) & 0x000000FF)
| (
(( (((int) (l_nColor & 0x0000FF00) << 8) )
+ (((int) (l_nColor & 0x00FF0000) >> 16) * 0x00005B02)
+ 0x0000DB71
) >> 8) & 0x0000FF00)
| (
(( (((int) (l_nColor & 0x000000FF) >> 0) * 0x0000890F)
+ (((int) (l_nColor & 0x00FF0000) >> 16) * 0x000178DB)
+ 0x00008000
) ) & 0x00FF0000)
;
OK, I not yet tested things as it's very late and I wanted to show you that coding is an art that cannot only be tricked with neat XOR ! It's 23:00 there, I'm gonna take a shower then go to bed There might have a casting problem in the result as I just copied and pasted my previous code. Will check that tomorrow Good nite to all !
Kochise
In Code we trust !
|
|
|
|
|
I even had trouble to calculate with my pocket calculator !
Never mind, slightly forget the previous post, even if the idea is interresting, here the REAL contrast color :
INT CalcContrastColor (INT crBg)
{
CRGB l_nColorContrast(crBg);
CRGB::sHSL l_sHSL;
l_nColorContrast.GetHSL(l_sHSL);
l_sHSL.H += 180.0;
l_sHSL.S = 1.0;
l_sHSL.L = 1.0 - l_sHSL.L;
l_nColorContrast.SetHSL(l_sHSL, FALSE);
return l_nColorContrast.GetCOLORREF();
}
Just change the function in your code. As explained, I select the additive color, force saturation to 1, and invert light. It then even works fine in very low saturation ! To get the code to work, add the following class above it :
class CRGB
{
public :
struct sARGB
{
double A;
double R;
double G;
double B;
};
struct sHSL
{
double H;
double S;
double L;
};
inline
CRGB
(
)
{
mp_sARGB.A = 0.0;
mp_sARGB.R = 0.0;
mp_sARGB.G = 0.0;
mp_sARGB.B = 0.0;
}
inline
CRGB
( double i_nRed
, double i_nGreen
, double i_nBlue
)
{
SetRGB
( i_nRed
, i_nGreen
, i_nBlue
);
}
inline
CRGB
( unsigned int i_nRed
, unsigned int i_nGreen
, unsigned int i_nBlue
)
{
SetRGB
( i_nRed
, i_nGreen
, i_nBlue
);
}
inline
CRGB
( COLORREF i_sColor
)
{
SetRGB
( i_sColor
);
}
inline
void SetRGB
( sARGB& i_rsARGB
)
{
mp_sARGB.A = i_rsARGB.A;
mp_sARGB.R = i_rsARGB.R;
mp_sARGB.G = i_rsARGB.G;
mp_sARGB.B = i_rsARGB.B;
};
inline
void SetRGB
( double i_nRed
, double i_nGreen
, double i_nBlue
)
{
mp_sARGB.A = 0.0;
mp_sARGB.R = i_nRed;
mp_sARGB.G = i_nGreen;
mp_sARGB.B = i_nBlue;
};
inline
void SetRGB
( unsigned int i_nRed
, unsigned int i_nGreen
, unsigned int i_nBlue
)
{
SetRGB
( i_nRed / 255.0
, i_nGreen / 255.0
, i_nBlue / 255.0
);
};
inline
void SetRGB
( COLORREF i_sColor
)
{
SetRGB
( (unsigned int) ((i_sColor & 0x000000FF) >> 0)
, (unsigned int) ((i_sColor & 0x0000FF00) >> 8)
, (unsigned int) ((i_sColor & 0x00FF0000) >> 16)
);
mp_sARGB.A = ((i_sColor & 0xFF000000) >> 24) / 255.0;
};
inline
sARGB GetARGB
( void
)
{
return mp_sARGB;
};
inline
void SetHSL
( sHSL& i_rsHSL
, BOOL i_bRefit = TRUE
)
{
sARGB l_sARGB;
HslToRgb(l_sARGB, i_rsHSL, i_bRefit);
SetRGB(l_sARGB);
};
inline
void GetHSL
( sHSL& o_rsHSL
)
{
RgbToHsl(o_rsHSL, mp_sARGB);
};
inline
COLORREF GetCOLORREF
( void
)
{
return
( ((((COLORREF) (mp_sARGB.A * 255.0)) & 0x000000FF) << 24)
| ((((COLORREF) (mp_sARGB.R * 255.0)) & 0x000000FF) << 0)
| ((((COLORREF) (mp_sARGB.G * 255.0)) & 0x000000FF) << 8)
| ((((COLORREF) (mp_sARGB.B * 255.0)) & 0x000000FF) << 16)
)
;
};
inline
void RgbToHsl
( sHSL& o_rsHSL
, sARGB& i_sARGB
)
{
double l_nValMin;
double l_nValMax;
double l_nValDif;
double l_nValSum;
if(i_sARGB.R > i_sARGB.G)
{
if(i_sARGB.G > i_sARGB.B)
{
l_nValMax = i_sARGB.R;
l_nValMin = i_sARGB.B;
}
else
{
if(i_sARGB.R > i_sARGB.B)
{
l_nValMax = i_sARGB.R;
}
else
{
l_nValMax = i_sARGB.B;
}
l_nValMin = i_sARGB.G;
}
}
else
{
if(i_sARGB.G < i_sARGB.B)
{
l_nValMax = i_sARGB.B;
l_nValMin = i_sARGB.R;
}
else
{
l_nValMax = i_sARGB.G;
if(i_sARGB.R < i_sARGB.B)
{
l_nValMin = i_sARGB.R;
}
else
{
l_nValMin = i_sARGB.B;
}
}
}
l_nValDif = l_nValMax - l_nValMin;
l_nValSum = l_nValMax + l_nValMin;
if(l_nValDif == 0)
{
o_rsHSL.H = 0.0;
o_rsHSL.S = 0.0;
}
else
{
if(l_nValMax == i_sARGB.R)
{
o_rsHSL.H = 60.0 * (6.0 + i_sARGB.G - i_sARGB.B);
}
else
{
if(l_nValMax == i_sARGB.G)
{
o_rsHSL.H = 60.0 * (2.0 + i_sARGB.B - i_sARGB.R);
}
else
{
o_rsHSL.H = 60.0 * (4.0 + i_sARGB.R - i_sARGB.G);
}
}
while(o_rsHSL.H > 360.0)
{
o_rsHSL.H -= 360.0;
}
if(l_nValSum <= 1.0)
{
o_rsHSL.S = l_nValDif / l_nValSum;
}
else
{
o_rsHSL.S = l_nValDif / (2.0 - l_nValSum);
}
}
o_rsHSL.L = l_nValSum / 2.0;
}
inline
void HslToRgb
( sARGB& o_rsARGB
, sHSL& i_rsHSL
, BOOL i_bRefit = TRUE
)
{
o_rsARGB.A = 0.0;
if(i_bRefit == TRUE)
{
if(i_rsHSL.S > 1.0)
{
i_rsHSL.S = 1.0;
}
else if(i_rsHSL.S < 0.0)
{
i_rsHSL.S = 0.0;
}else{}
if(i_rsHSL.L > 1.0)
{
i_rsHSL.L = 1.0;
}
else if(i_rsHSL.L < 0.0)
{
i_rsHSL.L = 0.0;
}else{}
}
else
{
while(i_rsHSL.S > 1.0)
{
i_rsHSL.S -= 1.0;
}
while(i_rsHSL.S < 0.0)
{
i_rsHSL.S += 1.0;
}
while(i_rsHSL.L > 1.0)
{
i_rsHSL.L -= 1.0;
}
while(i_rsHSL.L < 0.0)
{
i_rsHSL.L += 1.0;
}
}
if(i_rsHSL.S == 0.0)
{
o_rsARGB.R = i_rsHSL.L;
o_rsARGB.G = i_rsHSL.L;
o_rsARGB.B = i_rsHSL.L;
}
else
{
double l_nTempo1;
double l_nTempo2;
if(i_rsHSL.L <= 0.5)
{
l_nTempo1
= i_rsHSL.L
+ ( i_rsHSL.S
* i_rsHSL.L
)
;
}
else
{
l_nTempo1
= i_rsHSL.L
+ i_rsHSL.S
- ( i_rsHSL.L
* i_rsHSL.S
)
;
}
l_nTempo2
= (2.0 * i_rsHSL.L)
- l_nTempo1
;
o_rsARGB.R
= ColorAngle
( l_nTempo1
, l_nTempo2
, i_rsHSL.H + 120.0
)
;
o_rsARGB.G
= ColorAngle
( l_nTempo1
, l_nTempo2
, i_rsHSL.H
)
;
o_rsARGB.B
= ColorAngle
( l_nTempo1
, l_nTempo2
, i_rsHSL.H - 120.0
)
;
}
}
protected :
inline
double ColorAngle
( double i_nTempo1
, double i_nTempo2
, double i_nHue
)
{
while(i_nHue > 360.0)
{
i_nHue -= 360.0;
}
while(i_nHue < 0.0)
{
i_nHue += 360.0;
}
if(i_nHue < 60.0)
{
i_nTempo2
+= ( ( i_nTempo1
- i_nTempo2
)
* i_nHue
)
/ 60.0
;
}
else if(i_nHue < 180.0)
{
i_nTempo2 = i_nTempo1;
}
else if(i_nHue < 240.0)
{
i_nTempo2
+= ( ( i_nTempo1
- i_nTempo2
)
* ( 240.0
- i_nHue
)
)
/ 60.0
;
}else{}
return i_nTempo2;
}
sARGB mp_sARGB;
};
Kochise
In Code we trust !
|
|
|
|
|
This is really super code, but I need the returned value to be black or white only. For example yellow would return black and blue would return white. What modifications do I need to do?
Thank you
|
|
|
|
|
Hi
The following code is provided by Samuel Gonzalo. I'm not sure whether it works though since I haven't try it out yet.
It will only give black/white as output. You can also read about other solutions on this discussion board.
COLORREF GetReadableTextColor(COLORREF crBackground)
{
BYTE Y = (BYTE)
(
0.299 * GetRValue(crBackground) +
0.587 * GetGValue(crBackground) +
0.114 * GetBValue(crBackground)
);
return (Y > 140) ? 0x0 : 0xFFFFFF;
}
|
|
|
|
|
This is just what i was looking for!
Great job!;)
|
|
|
|
|
Worked good for me. The author's name is a bit scary though. Stay out of the sunlight.
|
|
|
|
|
I ran the demo. and it worked fine. However, I didn't see the contrasting color hex code displayed anywhere. Can the program be run as a stand-alone? It would be a really useful stand-alone utility program.
|
|
|
|
|
Good one. Works nicely for me
|
|
|
|
|