Hi Again,
In this post, I'm going to talk a little about color formats, and how to do conversions between them. There are a number of color formats,
some of which only make sense in certain environment. RGBA is a color format that is best suited for digital display devices, in that it provides
an exact value to display. What you provide is what you get.
Compare that to HSL, where certain HSL values do not exactly map into the RGBA color space, and vice-versa.
This can be a problem for graphics designers; you have to know all of the little nuances, and how to get as close as you can to the real color.
This is the reason I designed MJLoupe. The main lesson I learned here was how little I know about color formats.
In a previous post, I talked about how RGBA roughly descends from a Windows COLORREF. It is a 32 bit unsigned integer, consisting of 4 bytes. ("AA RR GG BB") In C#, this corresponds to a UInt32.
Before we can convert this color value to a different format, we must break this number into the 4 bytes that correspond to the Red, Green, Blue, and Alpha values.
We do this by logical operations. Here's the function, extracted from the MJColorConverter class.
- public static void ToRGBA(UInt32 ColorCode,ref int R,ref int G,ref int B,ref int A)
- {
- UInt32 temp = ColorCode;
- B = (int)(temp & 255);
- temp = (temp & 0xffffff00) >> 8;
- G = (int)(temp & 255);
- temp = (temp & 0xffffff00) >> 8;
- R = (int)(temp & 255);
- temp = (temp & 0xffffff00) >> 8;
- A = (int)(temp & 255);
- }
Once the byte values have been retrieved, you can then begin converting to the desired format.
As you examine the various color formats, you will notice that the internal calculations are done using floating point variables (C# doubles)
Also, the color values are scaled to be between 0.0 and 1.0. This is called "Normalization". To cut down on code, I have provided a function that does this "up front", so to speak.
Here it is.
Now that we have our color values separated and normalized, we can begin to perform the necessary calculations to convert between formats.
HSL vs. HSV
The biggest mistake that every beginning .NET programmer makes is to assume that Color.GetHue(), Color.GetSaturation() and Color.GetBrightness() are HSL.
They are not. They are HSB, and there's a big difference in the two models.
Here is one of the first places I went t when I was hunting for info about different color formats.
http://www.easyrgb.com/en/math.php#text20
Basically, it breaks down into 3 or 4 steps.
1 ) Collect and normalize the color bytes.
2 ) Find the maximum and minimum color values. From there, compute the Chroma value. In my code, I use this function, which does all of this for me.
I'll reuse it later for HSL.
3 ) Calculate Sat and Value, based on the value of Chroma:
If Chroma is 0 (i.e the Minimum value is equal to the Maximum value.), then H = S = 0 and V = the Minimum value.
Otherwise, calculate the H and S values.
Finally, Scale the output values to our desired range and convert to integer values.
EasyRGB.com - RGB to HSV wrote:Display More//R, G and B input range = 0 ÷ 255
//H, S and V output range = 0 ÷ 1.0
var_R = ( R / 255 )
var_G = ( G / 255 )
var_B = ( B / 255 )
var_Min = min( var_R, var_G, var_B ) //Min. value of RGB
var_Max = max( var_R, var_G, var_B ) //Max. value of RGB
del_Max = var_Max - var_Min //Delta RGB value
V = var_Max
if ( del_Max == 0 ) //This is a gray, no chroma...
{
H = 0
S = 0
}
else //Chromatic data...
{
S = del_Max / var_Max
del_R = ( ( ( var_Max - var_R ) / 6 ) + ( del_Max / 2 ) ) / del_Max
del_G = ( ( ( var_Max - var_G ) / 6 ) + ( del_Max / 2 ) ) / del_Max
del_B = ( ( ( var_Max - var_B ) / 6 ) + ( del_Max / 2 ) ) / del_Max
if ( var_R == var_Max ) H = del_B - del_G
else if ( var_G == var_Max ) H = ( 1 / 3 ) + del_R - del_B
else if ( var_B == var_Max ) H = ( 2 / 3 ) + del_G - del_R
if ( H < 0 ) H += 1
if ( H > 1 ) H -= 1
}
If you want the values to be percentages in the range [0..1], then use the above calculations.
NB: I wasn't very happy with the above calculations, as I noticed that they return HSL values. After doing some searching,(stackoverflow.com to the rescue! )
I found the correct formula, which seems to produce correct results.
Here it is
- public enum MJColorConverterStyle { ccsDecimal,ccsHex,ccsPercent,ccsDecimal240,ccsDegreesPct,ccsHTML };
- public static void ToHSBA(UInt32 ColorCode,MJColorConverterStyle Style,ref int Hue,ref int Sat,ref int Bright,ref int Alpha)
- {
- double AA = 0,RR = 0,GG = 0,BB = 0,MinVal = 0,MaxVal = 0,Chroma = 0,HH = 0,SS = 0,BR = 0;
- ToRGBANormalized(ColorCode,ref RR,ref GG,ref BB,ref AA);
- GetRGBMinMax(RR,GG,BB,ref MinVal,ref MaxVal,ref Chroma);
- if (MinVal == MaxVal)
- {
- HH = 0;
- SS = 0;
- BR = MinVal;
- }
- else
- {
- double DD = 0,MM = 0;
- if (RR == MinVal)
- {
- DD = GG - BB;
- MM = 3;
- }
- else if (GG == MinVal)
- {
- DD = RR - GG;
- MM = 1;
- }
- else
- {
- DD = BB - RR;
- MM = 5;
- }
- HH = (MM - (DD / (MaxVal - MinVal))) * 60;
- if (HH < 0)
- HH += 360;
- SS = ((MaxVal - MinVal) / MaxVal);
- BR = MaxVal;
- }
- switch(Style)
- {
- case MJColorConverterStyle.ccsDecimal240:
- Hue = (int)RoundUp(HH / 360 * 240);
- Sat = (int)RoundUp(SS * 240);
- Bright = (int)RoundUp(BR * 240);
- break;
- case MJColorConverterStyle.ccsDegreesPct:
- Hue = (int)RoundUp(HH);
- Sat = (int)RoundUp(SS * 100);
- Bright = (int)RoundUp(BR * 100);
- break;
- case MJColorConverterStyle.ccsPercent:
- Hue = (int)RoundUp(HH / 360 * 100);
- Sat = (int)RoundUp(SS * 100);
- Bright = (int)RoundUp(BR * 100);
- break;
- }
- }
Now, let's talk about rounding error, and how it affects the output of the conversions.
A difference in 1/10 can make the difference between a right and wrong answer. Since most of these algorithms return results in the range [0..1],
You would call this function after you've multiplied by whatever scale was needed.
There you have it. RGB to HSV.
My next post will show some other converters, such as HSL,CMY and CMYK.
See you then...