## 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

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

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 "a", "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

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.

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

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

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, and from the experimental results was derived the XYZ color space.

#### CIE Lab 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.

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.

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 1994 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 RG**B** and CIE LA**B**, the decision was made to initiate 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.

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

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

### Closest Color

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

- 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.
- Left-click the Mouse Over button and hold down the left mouse button. The cursor changes to a cross. Drag the cross to some color on the screen for which a color match is desired. Release the mouse. The color under the cross, 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.

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

## References

- Absolute color space, Wikipedia.
- CIE 1931 color space, Wikipedia.
- CIE 1964 color space, Wikipedia.
- CIE Lab Color Space, Wikipedia.
- CIE XYZ Color Space, Wikipedia.
- CIELUV, Wikipedia.
- Color Models, Intel.
- Color Models, Wikipedia.
- Color Space Conversions, Alan Roberts, British Broadcasting Company.
- Color Spaces, Couleur.org.
- Color Spaces, Wikipedia.
- CSS 3 Color Module, World Wide Web Consortium.
- Delta E: The Color Difference, Colorwiki.
- Difference Equations, Wikipedia.
- Euclidean distance, Wikipedia.
- Hexagonal Prism, Wikipedia.
- How to Calculate a Complementary Colour/Color, Serennu.
- HSL and HSV, Wikipedia.
- KnownColor Enumeration, Microsoft.
- Munsell color system, Wikipedia.
- Opponent Process, Wikipedia.
- RGB Color Space, Wikipedia.
- RYB Color Space, Wikipedia.
- Stable Sort Method, Egghead Cafe.
- The CIE XYZ and xyY Color Spaces, Douglas A. Kerr, Stanford University.
- The Munsell Color System, ApplePainter.com.

## History

- August 21, 2011 - Original article.