[C#] - MJColorComboBox Part 3: Color Concepts (revisited).

  • 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.


    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.


    Csharp: ToRGBANormalized()
    1. public static void ToRGBANormalized(UInt32 ColorCode,ref double R,ref double G,ref double B, ref double A)
    2. {
    3. int AA = -1,RR = -1,GG = -1,BB = -1;
    4. ToRGBA(ColorCode,ref RR,ref GG,ref BB,ref AA);
    5. A = (double)AA / 255;
    6. R = (double)RR / 255;
    7. G = (double)GG / 255;
    8. B = (double)BB / 255;
    9. }

    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.


    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


    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.

    Csharp: RoundUp()
    1. private static double RoundUp(double value)
    2. {
    3. int tgt = (int)(value * 10),orig = (int)value,delta = tgt - (orig * 10);
    4. if (delta >= 5)
    5. return (double)(orig + 1);
    6. else
    7. return (double)(orig);
    8. }

    There you have it. RGB to HSV.


    My next post will show some other converters, such as HSL,CMY and CMYK.


    See you then...