Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The Known Colors Palette Tool - Final Revision - Hopefully

0.00/5 (No votes)
25 Nov 2015 2  
Presents what is hoped to be the final revision to the Known Colors Palette Tool and the earlier article of the same name.
Known Colors Palette

Introduction Table of Contents

This article presents a revision to the Known Colors Palette Tool. In the header of changed paragraphs appears the graphic Changed and in the header of new paragraphs appears the graphic New.

As before, the results of this this revision accomplishes what the earlier revision tool did but adds an algorithm to correctly display the tool on a High Def monitor.

All of the graphics, presented in this article, initially display as thumbnails. This allows the reader to click on an image to open it in the reader's default graphics display program. Most images are really too small to display their details. All images in this article are PNG images.

Background Table of Contents

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. Each link can be opened in a new tab.

Color Models [^] Table of Contents

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 [^] Table of Contents

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 paint).

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 [^] Table of Contents

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 [^] Table of Contents

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 [^] Table of Contents

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 [^] Table of Contents

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 [^] Table of Contents

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 Table of Contents

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_TSMI

        /// <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_TSMI ( 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 );

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

        } // class Delta_Equations

    } // namespace KnownColorsPalette

Delta E Table of Contents

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_TSMI

        /// <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_TSMI ( 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;

                                        // Euclidean 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 Table of Contents

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_TSMI

        /// <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_TSMI ( 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 Table of Contents

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_TSMI

        public static double delta_E_2000_TSMI ( 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&deg; value

        /// <summary>
        /// helper function to return the CIE-H&deg; 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 Table of Contents

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 Table of Contents

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 Table of Contents

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 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 Table of Contents

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 Table of Contents

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.

Options Menu Table of Contents

Clipboard Table of Contents

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 Table of Contents

The Color Metrics submenu 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 Table of Contents

The Color Difference submenu 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 Table of Contents

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

The default is cross.

Distinguish Chosen Table of Contents

The Distinguish Chosen submenu allows the user to specify whether a border will be placed around the currently chosen color button (chosen either algorithmically or by a user action) to exaggerate its appearance. The current version of the Known Colors Palette Tool does not provide any visual clue as to which of the 140 buttons is the chosen button.

The following choices are available.

None - the chosen button will not be differentiated
Border - a solid border will surround the chosen button
Moving - a moving border will surround the chosen button

The default format is a Moving border.

Contrasting Colors Table of Contents

The Contrasting Colors area of the tool has been revised. It now displays two contrasting colors: the first is the computed contrasting color and the second is the known color obtained from the computed contrasting color.

// ********************** compute_known_and_contrasting_colors

/// <summary>
/// determines contrasting and known colors for a given color
/// </summary>
/// <param name="color">
/// color for which to compute contrasting and known colors
/// </param>
/// <param name="known_color">
/// nearest known color for the given color
/// </param>
/// <param name="contrasting_color">
/// computed contrasting color for the given color
/// </param>
/// <param name="known_contrasting_color">
/// nearest known color for the computed contrasting color
/// </param>
/// <param name="known_extended_color">
/// extended color for the given color
/// </param>
/// <see>
/// http://stackoverflow.com/questions/1855884/
///     determine-font-color-based-on-background-color
/// </see>
void compute_known_and_contrasting_colors (
                    Color           color,
                ref Color           known_color,
                ref Color           contrasting_color,
                ref Color           known_contrasting_color,
                ref Extended_Color  known_extended_color )
    {

    known_color = nearest_known_color ( color );
    contrasting_color = Color.FromArgb ( 255 - color.R,
                                         255 - color.G,
                                         255 - color.B );
    known_contrasting_color = nearest_known_color (
                                        contrasting_color );
    known_extended_color = new Extended_Color ( color );
    }

Determining the Closest Color Table of Contents

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 the Find Closest button to display the closest Known Color match.

Save to Clipboard Buttons Table of Contents

Save To Clipboard Buttons

In the figure to the left, four buttons are bordered by a red square. When the button is active, clicking on it will cause the color information to be copied to the clipboard. The information that will be placed on the clipboard is that specified by the Clipboard submenu.

There is an exception: the Computed Contrasting Color, although it is a color, may not be a known color. For that reason, a color name will not be placed on the clipboard.

Multi-Monitor Color under Cursor Table of Contents

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 Table of Contents

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 Table of Contents

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.

I would especially like to acknowledge the assistance of Jeff Hay-Roe, a Code Project member who was willing to test the KCPTool on High Def monitors. With his help, the tool now appears stable on High Def monitors.

References Table of Contents

History Changed Table of Contents

  • 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.
  • May 22, 2015
    • Created the KnownColorsPalette class library and removed the tool from within the class library
    • Added choice of borders that places either a stationary border or a moving border around the current known color button to exaggerate its appearance
    • Added contrasting color and clipboard save button
    • Added known contrasting color and clipboard save button
    • Added find closest color clipboard save button
    • Synchronized the main menu items with the context menu items
    • Updated the KCPTool to version 4.4
  • June 1, 2015 />
    • Wrote an algorithm that sets Form dimensions so as to provide support for High Def monitors
    • Updated the KCPTool to version 4.5
  • November 25, 2015 New
    • Repaired a bug that prevented Known Color information to be copied to the clip board.
    • Revised the Inno Setup script.
    • Updated the KCPTool to version 5.1

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here