Manipulating Colors

RGB, HSL, HSV, and HCW

RGB to HCW Color Conversion

/Home /Professional /Papers /ColorHCW

Contents

I often create simple graphics for my web sites. When I use clip art from various sources I encounter various situations where I need to manipulate colors. My web site is non-profit and non-revenue so I am not interested in buying suitable pictures or paying a royalty. Therefore the graphics I find to use are often of poor quality because they are usually in low resolution with JPEG artifacts when I try to scale them. I also use only PAINT because it is a free tool included with Windows.

One recent graphic was a nice drawing of an angel I wanted to use on my web site. There were the usual problems with the graphic. It was in the JPG format so it was degraded and would not scale well. I wanted to paste it onto another graphic so I needed to have it surrounded with a completely black background.

The following graphic is an example of JPEG edge artifacts and non-black background. In these examples the graphic is shown 200% and black has been pasted to white to show the difference between true black and near black.

JPEG artifacts example

I did an intial crude manual erase around the image. I used to do serious software development so I decided to write a program to manipulate the image. Later I added some crude algorithms to smooth out the JPEG artifacts. Then I wanted to do color shifts. In the process I realized it was very awkward to work with RGB values and I embarked on research into an alternate color representation.

The following graphic is an example of smoothed JPEG artifacts and blackened background.

Smoothed result example

I was initially led into the use of HSB because it is a part of the .NET Framework Color structure. I quickly discovered I had a problem because the conversion provided was only from RGB to HSB and not back.

RGB Color Representation

First, let me make some observations about the RGB color representation which are a significant factor in where this line of research took me. RGB is a direct integer representation of the hardware control of a color display. The 0 to 255 range of values for each of the color primaries is more than enough to smoothly describe a full range of colors.

The problem with the RGB color representation is it does not easily relate to how human beings perceive color. Primary colors are pretty easy but when you get into shades, tones, and tints you are almost lost.

Integer representation is important because it is computationally simple and fast. This factor is lost in almost all other color representations which use floating point in the values they use to represent color or the conversion methods they use and usually both.

A direct relationship to the hardware is also important to maintain in the color representation because it provides an existing frame of reference for simple understanding.

HSL/HSV Color Representation

Initially, I hoped to simply learn how to program the conversion from HSB to RGB. I quickly learned the HSB conversion was not well documented and there were many other representations from which to choose. The best and relatively brief introduction to the subject is not surprisingly found at Wikipedia.

http://en.wikipedia.org/wiki/HSL_and_HSV

I am not interested in trying to be scientifically accurate in describing color according to human perception. I only needed it to be close enough for general intents and purposes. My goal was for a representation which had as simple a conversion procedure as practical.

There was no representation which did not include floating point in both the values used to represent color and the conversion process. I was confident I could pick a representation which could be modified in a straightforward manner to use integer representation and integer conversion methods.

I picked the HSV method to modify in this process. The representation I created is called HCW or Hue, Chroma, and White. I carefully analyzed the names used to describe the various representations and their descriptions. I looked for something which was highly accurate but as easy to understand and as simple as practical.

HCW Color Representation

I created the Hue, Chroma, White (HCW) color representation. The HCW color system directly relates to the RGB color system for better or worse. This system has many similarities with the Munsell color system.

http://en.wikipedia.org/wiki/Munsell_color_system

Hue is a somewhat technical term used by almost all color representations to indicate the degrees of the color wheel. Red is always 0 which is easy to remember because Red is the first letter R of RGB. Stepping through the letters R, G, and B in order produces the colors of the wheel. Yellow (combining RG) is 60 degrees, Green is 120 degrees, Cyan (combining GB) is 180 degrees, Blue is 240 degrees, Magenta (combining BR) is 300 degrees, and 360 degrees wraps back to Red or 0.

Floating point is avoided in the HCW format by defining the value of hue in decidegrees or tenths of a degree. The range 0 to 3599 fits compactly in an unsigned or signed two byte representation. The 1,530 possible combination of pure colors (255*6) result in 2 or 3 decidegrees between each posible RGB primary color shade on the color wheel, which provides plenty of resolution.

I considered using the highly non-technical term Color but the meaning is just too ambiguous. Hue is acceptable because it is a very common term among professionals and is part of the Munsell color system.

Chroma is also a somewhat technical term which indicates the tone or brightness of the hue. Other representations use the term saturation which Wikipedia disparages and I find their logic compelling. The chroma is a value between 0 and 255. A value of 0 is always black.

I strongly considered using the highly non-technical term Black and reversing the range of values. Chroma is acceptable because it is a common term among professionals and is part of the Munsell color system.

White is a highly non-technical term I use to refer to how close the color is to white. White is a value between 0 and 255. A value of 255 is always white. The Munsell color system uses the term "value" which is just too ambiguous.

White is a tint or pastel value which can never be greater than chroma. When white equals chroma the color is a grey scale and hue is meaningless. It is important to note the value of Hue remembers the original hue when Chroma and White are being dynamically adjusted and pass through a grey scale.

The HCW representation is a simple and direct mapping of the RGB structure which maintains an integer basis for all values and conversions. Hue is the ratio of the maximum value of the RGB representation to the next lower value offset from the primary color. Chroma is the maximum value of the RGB representation. White is the minimum value of the RGB representation.

This may seem like an overly simple way to represent color but you will find the results highly effective.

In this partial example chart of some common colors, values indicated with a '?' are either undefined, fixed, meaningless, or variable.

Examples:

Color RGB HCW
Description RRGGBB Hue Chroma White
Black 000000 0? 0 0?
Gray 808080 0? 128 128
Dark Red 800000 0 128 0
Red ff0000 0 255 0
Pastel Red ff8080 0 255 128
Dark Yellow 808000 600 128 0
Yellow ffff00 600 255 0
Pastel Yellow ffff80 600 255 128
Dark Green 008000 1200 128 0
Green 00ff00 1200 255 0
Pastel Green 80ff80 1200 255 128
Dark Cyan 008080 1800 128 0
Cyan 00ffff 1800 255 0
Pastel Cyan 80ffff 1800 255 128
Dark Blue 000080 2400 128 0
Blue 0000ff 2400 255 0
Pastel Blue 8080ff 2400 255 128
Dark Magenta 800080 3000 128 0
Magenta ff00ff 3000 255 0
Pastel Magenta ff80ff 3000 255 128
Silver c0c0c0 0? 192 192
White ffffff 0? 255? 255

Visual Representations

A graphical representation of the RGB color space is a cube. The 3D graphic (using typical positional cues) is specifically arranged to show the hue beginning with red at the twelve o'clock position (of the flat representation) with the degree of the hue increasing clockwise. (Black is not visible in the back of the cube.)

RGB Cube visualization

The following description shows a graphical representation of how to morph the RGB color space to the HCW color space which can be viewed as a hexcone (or dual hexcone) similar to what is described by Wikipedia. Begin with an RGB cube showing the primary color nodes. The arrangement is designed to approximately match the above graphic. (Graphic adapted from Wikipedia)

RGB Cube showing primary color nodes.

Orient the RGB cube with the white node to the top and the black node to the bottom. The order of the color nodes is reversed from the above graphic to match the orientation on the next graphic. Diagonal seams are added to show the new edges where the nodes are going to be moved. (Graphic adapted from Wikipedia)

RGB cube shown the white node to the top and the black node to the bottom

Lower the 6 color nodes to the same plane as the black. The cone is shown with the bottom tilted up. Again the goal is to show red at the twelve o'clock position with the degree of hue increasing clockwise.(Graphic adapted from Wikipedia)

HCW Hexcone visualization

This graphic illustrates the full color view of the base (White = 0) of the hexcone which is the complete Chroma range from 0 at the center to 255 at the outside for all possible hues. Hue ranges from 0 at the top clockwise through the entire range.

Hue/Chroma range graphic hexagon

This graphic illustrates the full color view (Chroma = 255) looking down from the top of the cone. This is the complete White range from 255 at the center to 0 at the outside for all possible hues. Hue ranges from 0 at the top clockwise through the entire range. The order of the primary colors is reversed from the previous graphic to maintain the clockwise order for the degree of hue. The graphic is a projection rendered identically to the RGB cube though it is conceptually quite different.

Hue/White range graphic hexagon

This is a graphical representation of how to morph a conical projection of the HCW color space to a cylinder and then a rectangular projection similar to what is described by Wikipedia. Begin with the hexcone of the color space. (Graphic adapted from Wikipedia)

HCW Hexcone visualization

Stretch the top of the 6 horizontal segments to create a hexagonal cylinder. (Graphic adapted from Wikipedia)

HCW cylindrical visualization

Round the segments to create a cylinder. (Graphic adapted from Wikipedia)

HCW cylindrical visualization

This graphic illustrates the cylindrical visualization of the outer surface (Chroma = 255) of the hexcone with the complete White range from 0 at the bottom to 255 at the top. Hue ranges from 0 at the left going right through the entire range with a small amount of overlap.

Hue/White range graphic

This graphics illustrates the cylindrical visualization of the outer surface for the entire dual hexcone starting with Chroma = 0 at the bottom. The entire range of Hue is shown twice. A black line is drawn at the top to make it clear where the White range ends. The area of near-white is perceived to be significantly smaller than the area of near-black.

Hue/Chroma/White range graphic

RGB to HCW Color Conversion

This is an excerpt of the C# code for the conversion methods from a class which maintains the _hue, _chroma, _white values.

/// <summary> 
/// set the HCW equivalent value converted from individual RGB values
/// </summary> 
/// <param name="red">red value in the range 0 to 255</param>
/// <param name="green">green value in the range 0 to 255</param>
/// <param name="blue">blue value in the range 0 to 255</param>
public void RGB (byte red, byte green, byte blue)
{
    int sextant, offset;

    if (red >= green)
        if (green >= blue)
            sextant = 0;
        else
            if (red >= blue)
                sextant = 5;
            else
                sextant = 4;
    else
        if (red >= blue)
            sextant = 1;
        else
            if (green >= blue)
                sextant = 2;
            else
                sextant = 3;

    switch (sextant)
    {
        case 0: // 0 Red
            offset = red - blue;
            if (0 >= offset)
                _hue = 0;
            else
                _hue = (short) (((green - blue)
                    * 600 + (offset - 1)) / offset);
            _chroma = red;
            _white = blue;
            break;
        case 1: // 600 Yellow
            offset = green - blue;
            _hue = (short) (1200 - ((red - blue)
                    * 600) / offset);
            _chroma = green;
            _white = blue;
            break;
        case 2: // 1200 Green
            offset = green - red;
            _hue = (short) (1200 + (((blue - red)
                    * 600 + (offset - 1)) / offset));
            _chroma = green;
            _white = red;
            break;
        case 3: // 1800 Cyan
            offset = blue - red;
            _hue = (short) (2400 - ((green - red)
                    * 600) / offset);
            _chroma = blue;
            _white = red;
            break;
        case 4:// 2400 Blue
            offset = blue - green;
            _hue = (short) (2400 + (((red - green)
                    * 600 + (offset - 1)) / offset));
            _chroma = blue;
            _white = green;
            break;
        case 5: // 3000 Magenta
        default:
            offset = red - green;
            _hue = (short) (3600 - ((blue - green)
                    * 600) / offset);
            _chroma = red;
            _white = green;
            break;
    }
}

/// <summary> static method to return an RGB equivalent value
/// </summary> 
/// <param name="hue">Hue in the range 0 to 3599</param>
/// <param name="chroma">Chroma in the range 0 to 255</param>
/// <param name="white">White in the range 0 to value of Chroma</param>
public static Color iRGB (int hue, int chroma, int white)
{
    byte red, green, blue;
    int sextant, offset;

    sextant = hue / 600; // integer sixth part of a circle
    // offset scaled to a byte
    offset = ((hue % 600) * (chroma - white) + 300) / 600;

    switch (sextant)
    {
        case 0: // 0 Red
            red = (byte) chroma;
            if (chroma == white)
                green = (byte) white;
            else
                green = (byte) (offset + white);
            blue = (byte) white;
            break;
        case 1: // 600 Yellow
            red = (byte) (chroma - offset);
            green = (byte) chroma;
            blue = (byte) white;
            break;
        case 2: // 1200 Green
            red = (byte) white;
            green = (byte) chroma;
            blue = (byte) (offset + white);
            break;
        case 3: // 1800 Cyan
            red = (byte) white;
            green = (byte) (chroma - offset);
            blue = (byte) chroma;
            break;
        case 4:// 2400 Blue
            red = (byte) (offset + white);
            green = (byte) white;
            blue = (byte) chroma;
            break;
        case 5: // 3000 Magenta
        default:
            red = (byte) chroma;
            green = (byte) white;
            blue = (byte) (chroma - offset);
            break;
    }
    return Color.FromArgb (red, green, blue);
}
This class and other functionality is implemented in the HCW-Modify program.

The complete Microsoft Visual Studio 2010 Express (Visual C# 2010 Express) HCW-Modify project test files:

HCW-Modify.zip

Free Microsoft Visual Studio 2010 Express download:

http://www.microsoft.com/visualstudio/eng/products/visual-studio-express-products


Revised 2015-02-16