Click here to Skip to main content
Click here to Skip to main content

The Known Colors Palette Tool - Revised

, 24 Sep 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
This article presents a revised tool to assist developers in choosing colors appropriate for a web page and privides algorithms to perform closest color matching.
Known Colors Palette

Introduction

In an earlier article, I presented a tool that might assist developers in choosing colors appropriate for a web page. Those colors were drawn from the palette referred to by Microsoft as the Known Colors.

This article presents a revision to that earlier tool. This revision accomplishes what the earlier tool did and adds the capability to perform closest color matching. It also provides C# code that contains four closest color matching algorithms.

Table of Contents

Background

To understand the workings of the revised Known Color Palette Tool (hereafter KCPTool), the reader may need some background in color spaces. These models are used to specify color values using color components (or coordinates). As programmers, we are probably most familiar with the RGB model. But to perform color matching we need a different model, one that produces results that are, to the greatest extent possible, the same as human-perceived color attributes.

The discussion of each color space is, of necessity, brief and somewhat shallow. For readers who may be interested in further reading, I have included a number of links to Wikipedia articles.

Color Models

One of the problems with color is the overwhelming number of color models. In this introduction, I will limit the discussion of color models to RYB (just because we are all familiar with it from grade school), RGB, XYZ, and CIE Lab, those that are used in determining color closeness.

I have also included a discussion of the HSL color space because some readers believe that it is a useful model upon which to determine color closeness. Through the experiments that I conducted testing the various closest color matching algorithms, I have found that the "best" algorithm is the newest (Delta E 2000).

RYB Color Space

RYB Color Wheel

Most of us were introduced to the RYB (red, yellow, blue) color space when we were in grade school. The introduction probably (at least hopefully) gave us an understanding that colors could be mixed together to form new colors (the colors of nursery school paints).

The RYB model is a subtractive model in which red, yellow, and blue pigments are mixed, thereby subtracting (absorbing) some wavelengths of light while reflecting others. It is most suitable for explaining the mixing of paints, dyes, and inks. It is generally not useful in describing colors in the displays on computer monitors and LED and plasma devices.

RGB Color Space

RGB Color Space

As developers, the RGB (red, green, blue) model is probably the most familiar (after the RYB). The model is used to specify color on computer monitors and LCD and plasma devices. It, and the HSL model, is used to specify colors in web pages.

The RGB model is additive in that red, green, and blue light is mixed to produce a large number of colors. However, the gamut (range) of colors is generally limited to well below the possible number of colors due to hardware limitations.

Usually the values of red, green, and blue are specified as integers in the range [0,255] or as hexadecimal digits in the range [00,FF]. In portions of this article, normalized values of red, green, and blue are specified in the range [0.0,1.0]. I use the lowercase letters "r", "g", and "b" to denote these normalized RGB components.

The CSS 3 Color Module provides the following example for specifying the color Red using the RGB model.

    em { color: #F00 }              /* #rgb */
    em { color: #FF0000 }           /* #rrggbb */
    em { color: rgb(255,0,0) }
    em { color: rgb(100%, 0%, 0%) }

HSL Color Space

HSL Color Space

The HSL (hue, saturation, lightness) color space is a cylindrical representation of the RGB model. Hue is measured in degrees around the circumference of the cylinder. Red is at 0°, green at 120°, and blue at 240°, then wrapping back to red at 360°. Note that there is a discontinuity at 0° and 360°. Saturation is measured in percent from the center of the cylinder to its radius. Lightness is measured in percent from the bottom to the top of the cylinder.

The most common use of the HSL model is in color selection tools. The HSL model is also used in feature detection (e.g., facial recognition, object recognition, medical image analysis, etc.).

The HSL model is derived from the RGB model. The transformation from RGB to HSL is as follows.

  1. The RGB cube is rotated such that the Black vertex is at the bottom and the White vertex is at the top.
  2. The Red, Yellow, Green, Cyan, Blue, and Magenta points are forced onto a plane, forming a hexagon.
  3. The hexagon is expanded vertically upward and downward forming a hexagonal prism.
  4. The hexagonal prism is forced into a cylinder.
RGB to HSL Transformation

As with the RGB color space, the CSS 3 Color Module provides a way for specifying colors using the HSL model.

    * { color: hsl(0, 100%, 50%) }   /* red */
    * { color: hsl(120, 100%, 50%) } /* lime */ 
    * { color: hsl(120, 100%, 25%) } /* dark green */ 
    * { color: hsl(120, 100%, 75%) } /* light green */ 
    * { color: hsl(120, 75%, 75%) }  /* pastel green */

CIE XYZ Color Space

XYZ Color Space

The CIE XYZ color space is a mathematically defined color space. In 1931, the International Commission on Illumination (CIE) convened a session to update recommendations. The session concluded with the formalization of the CIE 1931 XYZ color space (herein referred to as the XYZ color space).

Although it is a mathematically defined color space, it was derived from physical experiments conducted in the 1920's. The experiments were aimed at the RGB color space. From the experimental results was derived the XYZ color space.

For the purposes of the KCPTool, the XYZ color space is the intermediary between the RGB color space and the CIE Lab color space, the latter being used for color matching.

CIE Lab Color Space

CIELAB Color Space

The CIE Lab color space is derived from the XYZ color space. Although derived from the XYZ color space, the intent of its authors was to create a color space that was more perceptually uniform to the human eye.

The CIE Lab color space components are L (lightness) and the two color components a, and b. As depicted in the figure to the right, the a axis is associated with red and green components; while the b axis is associated with the yellow and blue components. Note that in CIE Lab, a color cannot be made of, say, yellow and blue. Nor can a color be made of red and green. This model is called a color opponent process.

Although many have suggested that color differences be computed using the HSL color space, I have found that the difference equations based on the CIE Lab color space perform much better than the RGB and HSL Euclidean differences.

In the KCPTool, values in the RGB color space are transformed into the XYZ color space and from the XYZ color space into the CIE Lab color space. With the exception of the Delta RGB difference equation, the CIE Lab color components are used in determining closest color.

Difference Equations

A color difference is a measure of how closely two colors match. Most common, but usually not very satisfactory, are the Euclidean distance equations. Although they provide a gross estimate of color difference, observers find that the results were not what were expected. Less common, but far more perceptually uniform, are the color differences that use the CIE Lab color space.

The KCPTool offers four color difference equation algorithms from which to choose when performing color matching. The author recommends Delta E 2000 as the algorithm of choice.

An implementation of the Difference Equations is contained in the Difference_Equations class. The implementation is provided in the individual discussions.

Delta RGB

The Delta RGB closest color algorithm is a simple device dependent Euclidian distance algorithm using the GDI+ Color structure's Red, Green, and Blue components. The portion of the Delta Equations class that computes the Delta RGB is:

using System;
using System.Drawing;

namespace KnownColorsPalette
    {
    public partial class Delta_Equations
        {
        
        // ************************************************* delta_RGB
        
        /// <summary>
        /// compute the difference between two Colors using a
        /// Euclidian distance algorithm
        /// </summary>
        /// <param name="color_1">
        /// first RGB Color to form the difference
        /// </param>
        /// <param name="color_2">
        /// second RGB Color to form the difference
        /// </param>
        /// <returns>
        /// difference between the two RGB Color instances
        /// </returns>
        public static double delta_RGB ( Color color_1,
                                         Color color_2 )
            {
            double  delta_R = ( double ) ( color_1.R - color_2.R );
            double  delta_G = ( double ) ( color_1.G - color_2.G );
            double  delta_B = ( double ) ( color_1.B - color_2.B );

                                        // Eudlidean distance
            return ( Math.Sqrt ( delta_R * delta_R + 
                                 delta_G * delta_G + 
                                 delta_B * delta_B ) );
            }

        } // class Delta_Equations

    } // namespace KnownColorsPalette

Delta E

The Delta E closest color algorithm is a simple device independent Euclidian distance algorithm using the colors' CIE L, a, and b components. The portion of the Delta Equations class that computes Delta E is:

using System;

namespace KnownColorsPalette
    {
    public partial class Delta_Equations
        {
        
        // *************************************************** delta_E
        
        /// <summary>
        /// compute the difference between two CIELab_Color using 
        /// Euclidian distance algorithm
        /// </summary>
        /// <param name="cielab_1">
        /// first Color to form the difference
        /// </param>
        /// <param name="cielab_2">
        /// second Color to form the difference
        /// </param>
        /// <returns>
        /// difference between the two CIELab_Color instances
        /// </returns>
        public static double delta_E ( CIELab_Color  cielab_1,
                                       CIELab_Color  cielab_2 )
            {
            double delta_CIE_a = cielab_1.CIE_a - cielab_2.CIE_a;
            double delta_CIE_b = cielab_1.CIE_b - cielab_2.CIE_b;
            double delta_CIE_L = cielab_1.CIE_L - cielab_2.CIE_L;

                                        // Eudlidean distance
            return ( Math.Sqrt ( ( delta_CIE_L * delta_CIE_L ) +
                                 ( delta_CIE_a * delta_CIE_a ) +
                                 ( delta_CIE_b * delta_CIE_b ) ) );
            }

        } // class Delta_Equations

    } // namespace KnownColorsPalette

Delta E 1994

The Delta E 1994 closest color algorithm is a device independent color matching algorithm that uses the colors' CIE Lab components.

From Colorwiki

A technical committee of the CIE (TC 1-29) published an equation in 1995 called CIE94. The equation is similar to CMC but the weighting functions are largely based on RIT/DuPont tolerance data derived from automotive paint experiments where sample surfaces are smooth. It also has ratios, labeled kL (lightness) and Kc (chroma) and the commercial factor (cf) but these tend to be preset in software and are not often exposed for the user.

The portion of the Delta Equations class that computes Delta E 1994 is:

using System;

namespace KnownColorsPalette
    {
    public partial class Delta_Equations
        {
        
        // ********************************************** delta_E_1994
        
        /// <summary>
        /// compute the difference between two CIE Lab colors using 
        /// the CIE 1994 delta E algorithm
        /// </summary>
        /// <param name="cielab_1">
        /// first CIELab_Color to form the difference
        /// </param>
        /// <param name="cielab_2">
        /// second CIELab_Color to form the difference
        /// </param>
        /// <returns>
        /// difference between the two CIELab_Color instances
        /// </returns>
        /// <see>
        /// http://en.wikipedia.org/wiki/Color_difference
        /// </see>
        public static double delta_E_1994 ( CIELab_Color  cielab_1,
                                            CIELab_Color  cielab_2 )
            {
            double C1;
            double C2;
            double CIE_1_a_squared = cielab_1.CIE_a * cielab_1.CIE_a;
            double CIE_1_b_squared = cielab_1.CIE_b * cielab_1.CIE_b;
            double CIE_2_a_squared = cielab_2.CIE_a * cielab_2.CIE_a;
            double CIE_2_b_squared = cielab_2.CIE_b * cielab_2.CIE_b;
            double delta_a;
            double delta_a_squared;
            double delta_b;
            double delta_b_squared;
            double delta_C_ab;
            double delta_C_ab_divisor;
            double delta_C_ab_squared;
            double delta_E_Lab;
            double delta_H_ab;
            double delta_H_ab_divisor;
            double delta_L;
            double delta_L_squared;
            double K_1;
            double K_2 ;

            delta_L = cielab_1.CIE_L - cielab_2.CIE_L;
            delta_L_squared = delta_L * delta_L;
            
            delta_a = cielab_1.CIE_a - cielab_2.CIE_a;
            delta_a_squared = delta_a * delta_a;
            
            delta_b = cielab_1.CIE_b - cielab_2.CIE_b;
            delta_b_squared = delta_b * delta_b;
            
            delta_E_Lab = Math.Sqrt ( delta_L_squared +
                                      delta_a_squared +
                                      delta_b_squared );

            C1 = Math.Sqrt ( CIE_1_a_squared + CIE_1_b_squared );
            C2 = Math.Sqrt ( CIE_2_a_squared + CIE_2_b_squared );
            delta_C_ab = C1 - C2;
            delta_C_ab_squared = delta_C_ab * delta_C_ab;
            
                      
            if ( ( delta_a_squared + delta_b_squared ) >= 
                 delta_C_ab_squared )
                {                       // avoid imaginary delta_H_ab
                delta_H_ab = Math.Sqrt ( delta_a_squared + 
                                         delta_b_squared -
                                         delta_C_ab_squared );
                }
            else
                {
                delta_H_ab = 0.0;
                }
                                        // weighting factors for 
                                        // graphic arts
            // K_L = 1.0;               // => no delta_L division
            K_1 = 0.045;
            K_2 = 0.015;
            
            delta_C_ab_divisor = 1.0 + ( K_1 * C1 );
            delta_H_ab_divisor = 1.0 + ( K_2 * C1 );
            
            delta_C_ab /= delta_C_ab_divisor;
            delta_H_ab /= delta_H_ab_divisor;
            
            return ( Math.Sqrt ( delta_L_squared + 
                                 delta_C_ab * delta_C_ab + 
                                 delta_H_ab * delta_H_ab ) );
            }

        } // class Delta_Equations

    } // namespace KnownColorsPalette

Delta E 2000

The Delta E 2000 closest color algorithm is the recommended device independent color matching algorithm that uses the colors' CIE Lab components.

From Colorwiki

Delta-E 2000 is the first major revision of the Delta E 1994 equation. Unlike Delta E 1994, which assumes that L* correctly reflects the perceived differences in lightness, Delta E 2000 varies the weighting of L* depending on where in the lightness range the color falls. Delta E 2000 is still under consideration and does not seem to be widely supported in graphics arts applications.

The portion of the Delta Equations class that computes Delta E 2000 is:

using System;

namespace KnownColorsPalette
    {
    public partial class Delta_Equations
        {

        // ********************************************** delta_E_2000

        public static double delta_E_2000 ( CIELab_Color  cielab_1,
                                            CIELab_Color  cielab_2 )
            {
            double c = Math.Pow ( 25, 7 );
            double CIE_1_a_squared = cielab_1.CIE_a * cielab_1.CIE_a;
            double CIE_1_b_squared = cielab_1.CIE_b * cielab_1.CIE_b;
            double CIE_2_a_squared = cielab_2.CIE_a * cielab_2.CIE_a;
            double CIE_2_b_squared = cielab_2.CIE_b * cielab_2.CIE_b;
            double E00;
            double t;
            double weighting_factor_C = 1.0;
            double weighting_factor_H = 1.0;
            double weighting_factor_L = 1.0;
            double xC1;
            double xC2;
            double xCX;
            double xCY;
            double xDC;
            double xDH;
            double xDL;
            double xGX;
            double xH1;
            double xH2;
            double xHX;
            double xLX;
            double xNN;
            double xPH;
            double xRC;
            double xRT;
            double xSC;
            double xSH;
            double xSL;
            double xTX;
            
            xC1 = Math.Sqrt( CIE_1_a_squared + CIE_1_b_squared );
            xC2 = Math.Sqrt( CIE_2_a_squared + CIE_2_b_squared );
            xCX = ( xC1 + xC2 ) / 2.0;
            t = Math.Pow ( xCX, 7 );
            xGX = 0.5 * ( 1.0 - Math.Sqrt ( t / ( t + c ) ) );

            xNN = ( 1.0 + xGX ) * cielab_1.CIE_a;
            xC1 = Math.Sqrt ( xNN * xNN + CIE_1_b_squared );
            xH1 = CieLab2Hue ( xNN, cielab_1.CIE_b );

            xNN = ( 1.0 + xGX ) * cielab_2.CIE_a;
            xC2 = Math.Sqrt ( xNN * xNN + CIE_2_b_squared );
            xH2 = CieLab2Hue ( xNN, cielab_2.CIE_b );
            
            xDL = cielab_2.CIE_L - cielab_1.CIE_L;
            xDC = xC2 - xC1;
            if ( ( xC1 * xC2 ) == 0 ) 
                {
                xDH = 0.0;
                }
            else 
                {
                t = xH2 - xH1;
                xNN = Math.Round ( t, 12 );
                if ( Math.Abs ( xNN ) <= 180 ) 
                    {
                    xDH = t;
                    }
                else 
                    {
                    if ( xNN > 180 ) 
                        {
                        xDH = t - 360.0;
                        }
                    else
                        {
                        xDH = t + 360.0;
                        }
                    }
                }
            xDH = 2.0 * Math.Sqrt ( xC1 * xC2 ) * 
                        Math.Sin ( MathUtilities.deg2rad ( 
                                                     xDH / 2.0 ) );
            xLX = ( cielab_1.CIE_L - cielab_2.CIE_L ) / 2.0;
            xCY = ( xC1 + xC2 ) / 2.0;
            t = xH1 + xH2;
            if ( ( xC1 *  xC2 ) == 0 ) 
                {
                xHX = t;
                }
            else 
                {
                xNN = Math.Abs ( Math.Round ( ( xH1 - xH2 ), 12 ) );
                if ( xNN > 180 ) 
                    {
                    if ( t < 360.0 ) 
                        {
                        xHX = t + 360.0;
                        }
                    else
                        {
                        xHX = t - 360.0;
                        }
                    }
                else 
                    {
                    xHX = t;
                    }
                xHX /= 2;
                }
            xTX = 1.0 - 0.17 * Math.Cos ( MathUtilities.deg2rad ( 
                                              xHX - 30.0 ) ) + 
                        0.24 * Math.Cos ( MathUtilities.deg2rad ( 
                                              2.0 * xHX ) ) + 
                        0.32 * Math.Cos ( MathUtilities.deg2rad ( 
                                              3.0 * xHX + 6.0 ) ) - 
                        0.20 * Math.Cos ( MathUtilities.deg2rad ( 
                                              4.0 * xHX - 63.0 ) );
            t = ( xHX  - 275.0 ) / 25.0;
            xPH = 30.0 * Math.Exp ( - ( t * t ) );

            t = Math.Pow ( xCY, 7 );
            xRC = 2.0 * Math.Sqrt ( t / ( t + c ) );
            t = xLX - 50.0;
            xSL = 1.0 + ( 0.015 * ( t * t ) ) /
                        Math.Sqrt ( 20.0 + ( t * t ) );
            xSC = 1.0 + 0.045 * xCY;
            xSH = 1.0 + 0.015 * xCY * xTX;
            xRT = - Math.Sin ( MathUtilities.deg2rad ( 
                                   2.0 * xPH ) ) * xRC;

            xDL /= ( weighting_factor_L * xSL );
            xDC /= ( weighting_factor_C * xSC );
            xDH /= ( weighting_factor_H * xSH );
            
            E00 = Math.Sqrt ( ( xDL * xDL ) + 
                              ( xDC * xDC ) + 
                              ( xDH * xDH ) + 
                              ( xRT * xDC * xDH ) );
            
            return ( E00 );
            }

        // ************************************************ CieLab2Hue
        
        // Function returns CIE-H° value
        
        /// <summary>
        /// helper function to return the CIE-H° value
        /// </summary>
        private static double CieLab2Hue( double a,
                                          double b )
            {
            double  bias = 0.0;
            
            if ( ( a >= 0.0 ) && ( b == 0.0 ) ) 
                {
                return 0.0;
                }
            if ( ( a < 0.0 ) && ( b == 0.0 ) ) 
                {
                return 180.0;
                }
            if ( ( a == 0.0 ) && ( b > 0.0 ) ) 
                {
                return 90.0;
                }
            if ( ( a == 0.0 ) && ( b < 0.0 ) ) 
                {
                return 270.0;
                }
            if ( ( a > 0.0 ) && ( b > 0.0 ) ) 
                {
                bias = 0.0;
                }
            if ( a < 0.0 ) 
                {
                bias = 180.0;
                }
            if ( ( a > 0.0 ) && ( b < 0.0 ) )
                {
                bias = 360.0;
                }
                
            return ( MathUtilities.rad2deg ( Math.Atan ( b / a ) ) + 
                     bias );
            }        
        
        } // class Delta_Equations

    } // namespace KnownColorsPalette

Known Colors Palette Tool

The KCPTool is based upon Microsoft's KnownColor Enumeration . That set of colors is referred to as "web safe." As a result, most colors that are chosen for a web page should be drawn from the Known Colors. Note that the CSS 3 Color Module refers to the names of the Microsoft "Known Colors" as the "Extended color keywords."

Extended_Color Class

To process colors efficiently (eliminating the need to create instances of different color spaces on the fly), the Extended_Color class was defined. The properties of the class are

        public class Extended_Color
            {
            public CIELab_Color   CIELab_color;
            public Color          color;
            public HSL_Color      HSL_color;
            public RGB_Color      RGB_color;
            public XYZ_Color      XYZ_color;
            :
            :

In addition to the GDI+ Color structure, Extended_Color contains instances of, in their order of use, the RGB_Color, XYZ_Color, and CIELab_Color classes. The HSL_Color class is an artifact of earlier development, retained in case that a well performing difference equation based on the HSL color space is found.

The properties of the four classes are

        public class CIELab_Color
            {
            public double CIE_L;
            public double CIE_a;
            public double CIE_b;
            :
            :

        public class HSL_Color 
            {
            public double Hue;              // [0.0,6.0]
            public double Saturation;       // [0.0,1.0]
            public double Lightness;        // [0.0,1.0]
            :
            :

        public class RGB_Color 
            {     
            public double r;                // [0.0,1.0]
            public double g;                // [0.0,1.0]
            public double b;                // [0.0,1.0]
            :
            :

        public class XYZ_Color 
            {
            public double X;
            public double Y;
            public double Z;
            :
            :

During the assignment of values to their double precision target, each value is rounded to three decimal places. The rounding function used in the KCPTool is found in the MathUtilities class,

namespace KnownColorsPalette
    {
    // * ***************************************** class MathUtilities
    
    public class MathUtilities
        {
        
        /// <summary>
        /// table of the first ten powers of ten (avoids Math.Pow)
        /// </summary>
        private static int [ ]  powers = new int [ 10 ] { 
                                                      1,    // ^0
                                                     10,    // ^1
                                                    100,    // ^2
                                                   1000,    // ^3
                                                  10000,    // ^4
                                                 100000,    // ^5
                                                1000000,    // ^6
                                               10000000,    // ^7
                                              100000000,    // ^8
                                             1000000000 };  // ^9

        // ***************************************************** round

        /// <summary>
        /// rounds a double precision number to the specified number 
        /// of decimal places
        /// </summary>
        /// <param name="number">
        /// double precision value to round
        /// </param>
        /// <param name="decimal_places">
        /// number of decimal points to maintain
        /// </param>
        /// <returns>
        /// double precision value rounded to the specified decimal 
        /// places
        /// </returns>
        /// <exception>
        /// ArgumentException if decimal places not in the range [0,9]
        /// </exception>
        /// <remarks>
        /// uses round half up rule for tie-breaking
        /// </remarks>
        /// <see cref="http://en.wikipedia.org/wiki/Rounding"/>
        /// <algorithm>
        /// 1. Multiple the original number by 10^decimal_places
        /// 2. Add 0.5 and round the result (truncate to an integer)
        /// 3. Divide result by 10^decimal_places
        /// </algorithm>
        /// <copyright>
        /// Distributed under the Code Project Open License
        /// http://www.codeproject.com/info/cpol10.aspx
        /// </copyright>
        public static double round ( double number,
                                     int    decimal_places )
            {
            int  power;
            int  t;

            if ( ( decimal_places < 0 ) || ( decimal_places > 9 ) )
                {
                throw new ArgumentException (
                              "Precision out of range [0,9]",
                              "decimal_places" );
                }

            power = powers [ decimal_places ];
            t = ( int ) ( ( number * ( double ) power ) + 0.5 );

            return ( ( double ) t / ( double ) power );
            }

        // *************************************************** rad2deg
        
        /// <summary>
        /// converts radians to degrees
        /// </summary>
        /// <param name="radians">
        /// double precision radians value to be converted
        /// </param>
        /// <returns>
        /// double precision degrees obtained by converting radians
        /// </returns>
        /// <see>
        /// http://en.wikipedia.org/wiki/Radian
        /// </see>
        public static double rad2deg ( double radians ) 
            {

            return ( radians / Math.PI * 180.0 );
            }

        // *************************************************** deg2rad
        
        /// <summary>
        /// converts degrees to radians
        /// </summary>
        /// <param name="degrees">
        /// double precision degrees value to be converted
        /// </param>
        /// <returns>
        /// double precision radians obtained by converting degrees
        /// </returns>
        /// <see>
        /// http://en.wikipedia.org/wiki/Radian
        /// </see>
        public static double deg2rad ( double degrees ) 
            {
            
            return ( degrees * Math.PI / 180.0 );
            }

        } // class MathUtilities

    } // namespace KnownColorsPalette


As the KCPTool retrieves each of the 140 known colors, an instance of Extended_Color is instantiated. Its constructor accepts the GDI+ Color structure.

        // ******************************************** Extended_Color
        
        public Extended_Color ( Color  color )
            {
            double  r = ( double ) color.R / 255.0;     // [0.0,1.0]
            double  g = ( double ) color.G / 255.0;     // [0.0,1.0]
            double  b = ( double ) color.B / 255.0;     // [0.0,1.0]

            this.color = color;
            HSL_color = HSL_Color.Fromrgb ( color, r, g, b );
            RGB_color = RGB_Color.Fromrgb ( r, g, b );
            XYZ_color = XYZ_Color.Fromrgb ( r, g, b );
            CIELab_color = CIELab_Color.FromXYZ ( XYZ_color );
            }

Performing a Stable Sort of the Extended Colors

When the 140 Extended_Color instances have been created, 25 panels are created (eliminating the need to create instances of the panels on the fly), each containing color squares sorted in one of the 25 possible ways. The RGB color squares are sorted in RGB, RBG, GRB, GBR, BRG, and BGR order; the HSL color squares in HSL, HLS, SHL, SLH, LHS, and LSH order; the XYZ color squares in XYZ, XZY, YXZ, YZX, ZXY, and ZYX order; and the CIE LAB color squares in LAB, LBA, ALB, ABL, BLA, and BAL order. In addition, the color squares are sorted by color name.

The comparator for the sorts is encapsulated in the Color_Comparer class. The class exposes the Comparer method that compares two Extended_Color instances. Note that comparisons must operate against Color.Name, RGB components, HSL components, XYZ components, and CIE Lab components. The Comparer must sort color space components in a stable order specified by the invoker.

To perform a stable sort, the Comparer must be initialized. Because the single letter "B" caused a conflict between RGB and CIE LAB, the decision was made to initialize the Color_Comparer with an array of strings. This was accomplished using the constructor

        // ******************************************** Color_Comparer
        
        /// <summary>
        /// constructor that accepts the fields to be used in later 
        /// comparisons
        /// </summary>
        /// <param name="fields">
        /// string array containing the the color fields in the order 
        /// in which they are to be compared
        /// </param>
        public Color_Comparer ( string [ ] fields )
            {

            validate_fields ( fields, "fields" );

            fields_to_compare = fields;
            }

validate_fields insures that only those color fields, recognized by Color_Comparer are supplied. For the purposes of the current version of KCPTool, these include:

  • NAME
  • RED
  • GREEN
  • BLUE
  • HUE
  • SATURATION
  • LIGHTNESS
  • X
  • Y
  • Z
  • CIE_L
  • CIE_A
  • CIE_B

To sort a List of Extended_Colors in the order Saturation, Hue, and finally Lightness, the following would suffice

            List < Extended_Color > colors = 
                                        new List < Extended_Color > ( 
                                            known_colors.Count );

            Color_Comparer cc = new Color_Comparer ( 
                                        new string [ ] {
                                                "SATURATION",
                                                "HUE",
                                                "LIGHTNESS" } );
            colors.Sort ( cc );

Abbreviating the Compare method to show only those sort fields:

        public int Compare ( Extended_Color  color_1,
                             Extended_Color  color_2 )
            {
            int  result = 0;

            if ( color_1 == null )
                {
                result = ( ( color_2 == null ) ? 0 : 1 );
                }
            else if ( color_2 == null )
                {
                result = -1; 
                }
            else
                {
                foreach ( string field in fields_to_compare )
                    {
                    switch ( field.ToUpper ( ) )
                        {
                        :
                        :
                        case "HUE":
                            result = color_1.HSL_color.Hue.
                                         CompareTo (
                                     color_2.HSL_color.Hue );
                            break;

                        case "SATURATION":
                            result = color_1.HSL_color.Saturation.
                                         CompareTo (
                                     color_2.HSL_color.Saturation );
                            break;

                        case "LIGHTNESS":
                            result = color_1.HSL_color.Lightness.
                                         CompareTo (
                                     color_2.HSL_color.Lightness );
                            break;
                        :
                        :
                        default:
                        
                            break;
                        }

                    if ( result != 0 )
                        {
                        break;
                        }
                    }
                }

            return ( result );
            }

When the Extended_Color List is sorted, it is used to populate the panel. In the example immediately above, the panel is the SHL panel.

Color Squares Panels

Each panel is composed of color squares. The color squares are instances of a Custom_Button class. An instance of Extended_Color was added to the to Button class to provide all of the color space information needed when a user clicks on, Tabs to, or hovers over a color square.

Fixing the Tooltip Bug

Each color square has a tooltip set to the name of the known color. Additionally, each color square has a MouseEnter event handler declared.

        tooltip.SetToolTip ( color_square, 
                             color.color.Name );
        color_square.MouseEnter +=
                new EventHandler ( tooltip_reinitializer );

The event handler, tooltip_reinitializer, contains

        private void tooltip_reinitializer ( Object    sender,
                                             EventArgs e )
            {

            tooltip.Active = false;
            tooltip.Active = true;
            }

The purpose of the event-handler is to fix a bug that keeps a tooltip from being displayed once that its display timeout has been reached. It is my belief that the bug is caused by a reentrancy error in the tooltip timer code. I strongly recommend that any control that has a tooltip, also declare the same, or functionally equivalent, event handler. All of the KCPTool controls that have tooltips also associate this hander with the control's MouseEnter event.

Clipboard

The Clipboard submenu allows the user to specify the format of the string placed on the clipboard when any Save To Clipboard command is executed. The following depicts what is placed on the clipboard for each choice.

All
{Name=Tomato,RGB=(255,99,71)=#FF6347=(FF,63,47)}
Name
Tomato
RGB Decimal
(255,99,71)
RGB Hexadecimal
#FF6347

The default format is RGB Hexadecimal.

Although the transfer to the Clipboard is not the same as setting the color in a color editing application, it does provide a means whereby the data can be captured and copied into the application.

Color Metrics

The Color Metrics menu item of the Options menu item determines when color metrics for a particular color square will be displayed. The default is a left mouse click on the color square of interest. But the user can specify to display the color metrics when the mouse hovers over the color square or when the cursor is tabbed to the color square. Lastly, and least useful, is to display the color metrics under any of the preceding events.

When the user clicks on, hovers over, or tabs to a desired color square, the contents of the Color Metrics group box will be filled. Once the color metrics fields are filled, left-clicking on the color square in the Color Metrics group box will cause the color data to be placed on the clipboard.

The default is left mouse click.

Color Difference

The Color Difference menu item of the Options menu item allows the user to specify which color difference equation to use for determining the difference between colors. The user can specify one of Delta RGB, Delta E, Delta E 1994, or Delta E 2000 algorithm.

The default is Delta E 2000.

Cursor Shape

The Color Shape menu item of the Options menu item allows the user to specify which cursor shape should be used while draging the cursor during Mouse Over positioning. The user can specify either cross or reticule.

The default is cross.

Closest Color

There are two ways in which to specify that a color be closest matched to one of the Known Colors.

  1. Left-click on Color Picker to display the standard Windows Color Dialog. Choose a color and click OK. The color will be displayed on the face of the Color Picker button.
  2. Left-click and hold down the Mouse Over button. The cursor changes to a cross or reticule (depending on the chosen Cursor Shape option). Drag the cross or reticule to some color on the screen for which a color match is desired. Release the mouse. The color under the cross or reticule, when the mouse was released, will be displayed on the face of the Mouse Over button.

In both cases, the RGB metrics for the chosen color will appear within the Find Closest To group box and the Find Closest button will be enabled.

Left-click on Find Closest to display the closest Known Color match.

Multi-Monitor Color under Cursor

Having offered to install the KCPTool on a colleague's machine, I was not ready for its first use. My colleague used multiple monitors. And as would have it, he attempted a closest color match using the Mouse Over functionality. The overly simple GetPixel method that I had used was not up to the task, causing an exception when the cursor was released over any location that was not on the Primary Monitor.

I searched the web for an answer and found John Gietzen's elegant solution on StackOverflow. I slightly modified his code and, when I substituted the following code, the problem went away. (The get_pixel method is found in the WIN32API class, included in the downloadable source code.)

      // ************************************************* get_pixel

      /// <summary>
      /// obtain the GDI+ Color of the pixel at the specified 
      /// location on the monitor
      /// </summary>
      /// <param name="location">
      /// Point containing the location of the pixel on the monitor 
      /// whose Color is to be obtained
      /// </param>
      /// <returns>
      /// the GDI+ Color of the pixel at the location on the screen
      /// </returns>
      /// <remarks>
      /// The method does not use GetPixel, resulting in four 
      /// benefits: the method will not raise an exception when used 
      /// in a multi-monitor environment; it is faster than 
      /// GetPixel; the Bitmap used is only one pixel high and wide;
      /// and the Bitmap is local to this method.
      /// </remarks>
      /// <see>
      /// http://stackoverflow.com/questions/1483928/
      ///     how-to-read-the-color-of-a-screen-pixel
      /// </see>
      public static Color get_pixel ( Point location )
          {
          Bitmap  screen_pixel = new Bitmap (
                                      1,
                                      1,
                                      PixelFormat.Format32bppArgb );

          using ( Graphics destination = Graphics.FromImage ( 
                                                  screen_pixel ) )
              {
              using ( Graphics source = Graphics.FromHwnd ( 
                                                 IntPtr.Zero ) )
                  {
                  IntPtr source_DC = source.GetHdc ( );
                  IntPtr destination_DC = destination.GetHdc ( );

                  BitBlt ( destination_DC,
                           0,
                           0,
                           1,
                           1,
                           source_DC,
                           location.X,
                           location.Y,
                           ( int ) CopyPixelOperation.SourceCopy );
                  }
              }

          return ( screen_pixel.GetPixel ( 0, 0 ) );
          }

Installing KCPTool

Included in the downloadable source, is a directory named "Deploy" and under that is a subdirectory named "setup". Contained in that subdirectory is the installer setup_FW.exe that will install KCPTool on any machine on which it is executed. If modifications are made to the KCPTool, and the project is recompiled, the file KnownColorsPalette.iss may need to be recompiled and executed to create the setup_FW.exe.

The tools needed to rebuild the installer are

  • Inno Setup - a free installer for Windows programs
  • ISTool - a visual script editor/generator for the Inno Setup compiler

Both of these tools are useful additions to a developer's tool box.

Acknowledgements

The greatest source of information that I used during the writing of this article was Wikipedia. In addition to its numerous articles, the Wikipedia Commons contains a wealth of images that are all in the public domain. Most of the images in this article were obtained from Wikipedia Commons.

The equations in this article and in the associated project software were obtained from Wikipedia and from the EasyRGB site.

I would also like to acknowledge the comments and suggestions of my readers. The suggestions have caused some interesting revisions that I would never have thought of, thereby improving the KCPTool.

References

History

  • August 21, 2011 - Original article.
  • August 22, 2011 - Removed ill-conceived screen resolution algorithm.
  • August 30, 2011 - Repaired typographical errors; updated the KCPTool to version 2.3.
  • September 17, 2011
    • Repaired deployment post build commands
    • Added balanced screens for differing resolutions
    • Added capability to save and restore options
    • Repaired the method by which a color under the cursor is determined
    • Updated the KCPTool to version 3.0.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

gggustafson
Software Developer (Senior)
United States United States
I started programming more than 42 years ago using AutoCoder and RPG (note no suffixing numbers). Programs and data were entered using punched cards. Turnaround between submitting a job for compilation and execution was about 3 hours. So much for the "good old days!" Today, I particularly enjoy programming real-time software. I consider myself capable in WinForms, Mobile Apps, and C# although there are occasions that I yearn to return to C and the Win32 API.

Comments and Discussions

 
Questionmy vote of 5 + Question PinmemberMohamedKamalPharm18-Feb-13 0:39 
AnswerRe: my vote of 5 + Question Pinmembergggustafson18-Feb-13 5:20 
GeneralMy vote of 5 PinmemberTony Dunsworth10-Dec-12 8:07 
GeneralMy vote of 5 PinmemberVitaly Tomilov25-Jun-12 12:33 
GeneralMy vote of 5 Pinmemberandy_t_roo28-Apr-12 0:45 
SuggestionDropper timer ? PinmemberMazen el Senih25-Apr-12 7:22 
GeneralMy vote of 5 Pinmembermanoj kumar choubey20-Feb-12 21:33 
GeneralMy vote of 5 PinmemberSergio Andrés Gutiérrez Rojas24-Sep-11 14:42 
QuestionVery minor documentation issue Pinmemberjsc4220-Sep-11 0:29 
AnswerRe: Very minor documentation issue Pinmembergggustafson20-Sep-11 2:29 
GeneralMy vote of 5 Pinmemberfredatcodeproject18-Sep-11 23:35 
Questiona site I think you'd be interested in PinmemberBillWoodruff5-Sep-11 5:29 
QuestionConvert to Visual Studio Add-in PinmemberclintonG29-Aug-11 12:49 
AnswerRe: Convert to Visual Studio Add-in Pinmembergggustafson29-Aug-11 15:36 
GeneralMy vote of 5 PinmemberclintonG29-Aug-11 12:45 
GeneralRe: My vote of 5 Pinmembergggustafson29-Aug-11 15:38 
GeneralMy vote of 5 PinmemberT_uRRiCA_N23-Aug-11 2:41 
GeneralRe: My vote of 5 Pinmembergggustafson29-Aug-11 15:37 
BugForm scale bug Pinmembersoho21-Aug-11 21:59 
GeneralRe: Form scale bug Pinmembergggustafson22-Aug-11 3:37 
AnswerRe: Form scale bug Pinmembersoho22-Aug-11 3:43 
AnswerRe: Form scale bug Pinmembergggustafson22-Aug-11 9:09 
AnswerRe: Form scale bug [modified] Pinmembersoho22-Aug-11 21:03 
GeneralRe: Form scale bug Pinmembergggustafson23-Aug-11 3:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141015.1 | Last Updated 24 Sep 2011
Article Copyright 2011 by gggustafson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid