C++ Programming

C++ Programming: a collection of papers about C++ classes, namespaces and techniques

C++ programming
A One-liner Debug Tracing
February 21, 2025
Dancing Links Sudoku Solver
March 24, 2025
Sharing Classes across VS Projects
November 23, 2025
A Fast Named-Color Lookup
(using TGMDev Extended X11)
March 06, 2026

Published on: Download Demo Source Code

Introduction

While developing the new release of KillProcess 5.0, I needed a named color selector: a UI control that accepts arbitrary sRGB input, displays a color swatch, and shows a friendly color name instead of raw RGB values.

At first glance, the idea sounds simple:

In practice, it is a bit more complex than expected. The perceived difference between colors is not linear in RGB space, so a direct Euclidean distance in RGB often produces counter-intuitive results. A perceptual color model is needed to obtain a nearest match that aligns better with human vision.

Another challenge is the dataset itself. The most widely available “named color” lists are the X11 color names (including many numerical variants such as Color#1 … Color#4) and the CSS named colors. These lists are highly useful, but they are not designed as a coherent, descriptive palette for UI selection.

The solution described in this paper combines:

The result is a fast named-color lookup that matches human perception in most cases and integrates easily into a C++/MFC application.

TGMDev NamedColorLookup View
TGMDev NamedColorLookup View

Figure 1 – TGMDev NamedColorLookup View
Hover to enlarge view

In this paper, I describe:

Development Environment

At TGMDev, the Development Environment is based on the latest version of Visual Studio Community 2026.

Even based on the MFC framework, the application makes use of the STL library, whenever possible. The latest standard is always selected. For now, it is the Preview - ISO C++23 Standard (/std:c++23).

This technique applies also to previous versions of Visual Studio but I always prefer to stick with the latest one. So the code was built with Visual Studio 2026 ...

A Word of Caution to Fellow Devs

The application is based on the MFC (Microsoft Foundation Classes) framework. I know that many people find MFC programming complex and quite outdated. But, since I don't bother writing software for other operating systems than Windows, I've kept this technology for all my programming projects. Since the Visual Studio compiler supports the latest ISO C++ 23 standard, STL is widely used as a replacement for classic C++ methods. In addition, Hungarian notation, introduced at Microsoft by Charles Simonyi, is systematically used to name program variables. Again, many programmers deny the use of this naming convention. But, once again, I don't worry about it. Charles Simonyi is one of the best software designers, and I have personally found Hungarian notation extremely useful in software development.

Color Theory and References

Color handling is a deep and complex topic. Entire books and standards exist, and a full treatment is far beyond the scope of this paper (and, honestly, beyond my own expertise). The goal here is practical: use well-known, documented conversions and a perceptual difference metric to obtain reliable “nearest named color” matches.

The following references provide the background needed to understand and verify the approach:

Named color sources

Conversion and validation tools

Color difference (ΔE)

Note: I used the online converters above as sanity checks to validate the sRGB→Lab conversion and to spot mistakes early.

History of Color Science

Color conversion pipeline (sRGB → linear → XYZ → Lab)

At a high level, the application does three things:

  1. Convert the input sRGB color to CIELAB (for perceptual comparison)
  2. Convert sRGB to HSL (useful for grouping/sorting by hue for UI display)
  3. Compute a perceptual distance using CIEDE2000 (ΔE00) to select the nearest named color
X11 Color List
(78 base colors)
Web X11 Colors
(CSS named colors)
Achromatic Colors
(Custom gray scale)
Dedicated C++ Utility
Parse color lists
Extract RGB values
Build color groups
Compute Color Metrics
LAB Conversion
HSL conversion
CIEDE2000 color distance
Group by base color
Remove duplicate colors
Remove near-identical colors
Perceptual Ordering
Sort colors by DeltaE2000
Match colors with labels
Final Data Generation
Build ColorData structures
Generate 415 color entries
Export C++ arrays
Toolkit::Color
Declare and Init Toolkit::Color::m_aColorName array
Declare and Init Toolkit::Color::m_aColorData
  1. Why CIELAB instead of RGB?

    Euclidean distance in RGB space does not match human perception well. For example, the same numeric change in RGB can be barely visible in some regions and very noticeable in others. CIELAB was designed to be more perceptually uniform: a similar numeric change in Lab is intended to correspond to a similar perceived change (not perfect, but much better than RGB).
    In CIELAB, each color is represented by three components:
    1. L* : lightness (0 = black, 100 = white)
    2. a* : green (−) ↔ red (+) axis
    3. b* : blue (−) ↔ yellow (+) axis
    (Paraphrased from standard descriptions; Wikipedia provides a good overview.)
  2. Conversion steps

    The conversion pipeline used in the code is the standard one:
    1. Step 1: Normalize RGB Values
      This is the simplest step: the R, G and B values are divided by 255 to get a range 0 to 1.
    2. Step 2: Do Gamma Correction
      This step is described in details in the reference documents and tranform normalized RGB values to linear RGB values.
    3. Step 3: Convert Linear RGB to XYZ
      This step is described in details in the reference documents and tranform linear RGB to XYZ Space values.
    4. Step 4: Convert XYZ to LAB
      This last transformation to finaly convert the RGB values in the LAB color coordinates space.
    CIELAB was designed so that the same amount of numerical change in these values corresponds to roughly the same amount of visually perceived change. (Quoted from Wikipedia).
    Source code for Conversion of RGB to LAB
    
    
    	///////////////////////////////////////////////////////////////////////////////
    	//
    	// inline const std::array& LinearRGBTable()
    	//
    	// Param: none
    	//
    	// Purpose: Once, Populate the static array of Linearized RGB Values then return a reference to the table
    	//                   
    	// Return Value: std::array
    	//     - the refrence of the static array of linearized RGB
    	// 
    	// Note:
    	//  - the function must be called where the linearized RGB value are used (in the function RGBtoXYZ)
    	//  - the array is initialized once, automatically
    	//  - The function is thread-safe since C++ 11 
    	//  - The first time the function LinearRGBTable() is called:
    	//      - C++ checks whether the local static object table is already initialized.
    	//      - It isn’t, so it runs the lambda once to build the array.
    	//      - The table is now constructed and stored for the rest of the program lifetime.
    	//      - The function returns a reference to it.
    	//  - On next calls :
    	//      - The C++ check for initialized static is true, so it skips the lambda and just returns the same reference.
    	// 
    	// History:
    	//
    	// Date      Comment                                   Initials
    	//
    	// Feb 2026  Created                                   TGM
    	// 
    	///////////////////////////////////////////////////////////////////////////////
    
    	inline const std::array& LinearRGBTable()
    	{
    		static const std::array LinearRGBTable = []
    		{
    			std::array t{};
    
    			// Constructed once on first call
    			for (int nChannel = 0; nChannel < 256; ++nChannel)
    			{
    				// Normalize Channel value in range [0...1]
    				const double dLinearChannel = static_cast(nChannel) / 255.0;
    
    				t[nChannel] = (dLinearChannel <= 0.04045) ? (dLinearChannel / 12.92) : std::pow((dLinearChannel + 0.055) / 1.055, 2.4);
    				t[nChannel] *= 100.0;
    			}
    			return t;
    		}();
    
    		return LinearRGBTable;
    	}
    	
    	XYZ RGBtoXYZ(int nR, int nG, int nB)
    	{
    		const auto& aLinRGB = LinearRGBTable();
    
    		double dRValue = aLinRGB[nR];
    		double dGValue = aLinRGB[nG];
    		double dBValue = aLinRGB[nB];
    
    		XYZ xyz{};
    
    		xyz.s_dXyzX = dRValue * 0.4124 + dGValue * 0.3576 + dBValue * 0.1805;
    		xyz.s_dXyzY = dRValue * 0.2126 + dGValue * 0.7152 + dBValue * 0.0722;
    		xyz.s_dXyzZ = dRValue * 0.0193 + dGValue * 0.1192 + dBValue * 0.9505;
    
    		return xyz;
    	}
    
    	LAB XYZtoLAB(double dX, double dY, double dZ)
    	{
    		// Normalize XYZ by a reference white(D65)
    		dX *= dInverseD65X;
    		dY *= dInverseD65Y;
    		dZ *= dInverseD65Z;
    
    		// Apply a piecewise cube-root mapping (linear near black)
    		// Note: In XYZ to LAB, threshold = (6 / 29)³ = 0.008856
    		//   For value (normalized by the white reference) above threshold, the return value is cubic root of value
    		//   Otherwise, the value is Value / (3 * square of (6/29)) + 16/116 = (7.787 * Value) + 0.137931
    		const double dfx = dX > dXYZtoLABThreshold ? std::cbrt(dX) : (dXYZtoLABSlope * dX) + dXYZtoLABOffset;
    		const double dfy = dY > dXYZtoLABThreshold ? std::cbrt(dY) : (dXYZtoLABSlope * dY) + dXYZtoLABOffset;
    		const double dfz = dZ > dXYZtoLABThreshold ? std::cbrt(dZ) : (dXYZtoLABSlope * dZ) + dXYZtoLABOffset;
    
    		LAB lab{};
    		lab.s_dLabL = 116.0 * dfy - 16.0;
    		lab.s_dLabA = 500.0 * (dfx - dfy);
    		lab.s_dLabB = 200.0 * (dfy - dfz);
    
    		return lab;
    	}
    
    
  3. HSL (for UI grouping, not for matching)

    HSL is convenient to sort or group colors by:
    1. Hue (color family)
    2. Saturation (gray ↔ vivid)
    3. Lightness (dark ↔ light)

    Figure 2 – CIELAB color space representation.
    Source:
    https://www.linshangtech.com/colorimeter/Hover to enlarge view
    However, HSL is not a perceptual space either. In this project, HSL is mainly used for display organization (e.g., hue buckets), while Lab + ΔE00 is used for the nearest-color match.
    Source code for Conversion of RGB to HSL
    
    	HSL RGBToHSL(int r, int g, int b)
    	{
    		HSL hsl;
    
    		// Normalize RGB values to [0, 1]
    		double dRValue = static_cast(r) / 255.0;
    		double dGValue = static_cast(g) / 255.0;
    		double dBValue = static_cast(b) / 255.0;
    
    		// Find Min Max and Delta 
    		int nMaxVal = max(max(r, g), b);
    		int nMinVal = min(min(r, g), b);
    		double dMaxVal = static_cast(nMaxVal) / 255.0;
    		double dMinVal = static_cast(nMinVal) / 255.0;
    		double dDelta = dMaxVal - dMinVal;
    
    		// Calculate Lightness
    		hsl.s_dHslL = (dMaxVal + dMinVal) / 2.00;
    
    		// Calculate Saturation and Hue
    		if (nMinVal == nMaxVal)
    		{
    			hsl.s_dHslH = 0.00; // Achromatic
    			hsl.s_dHslS = 0.00;
    		}
    		else
    		{
    			// Saturation
    			hsl.s_dHslS = (hsl.s_dHslL <= 0.50) ? (dDelta / (dMaxVal + dMinVal)) : (dDelta / (2.00 - dMaxVal - dMinVal));
    
    			if (nMaxVal == r)
    				hsl.s_dHslH = (dGValue - dBValue) / dDelta + (dGValue < dBValue ? 6.00 : 0.00);
    			else if (nMaxVal == g)
    				hsl.s_dHslH = (dBValue - dRValue) / dDelta + 2.0;
    			else if (nMaxVal == b)
    				hsl.s_dHslH = (dRValue - dGValue) / dDelta + 4.00;
    
    			hsl.s_dHslH /= 6.0;
    		}
    
    		hsl.s_dHslH *= 360.00;
    		hsl.s_dHslL *= 100.00;
    		hsl.s_dHslS *= 100.00;
    
    		return hsl;
    	}
    
  4. Nearest match metric: CIEDE2000 (ΔE00)

    Once colors are represented in Lab, you still need a distance function. A simple Euclidean distance in Lab (ΔE76) is better than RGB, but CIEDE2000 is typically closer to perception, especially in problematic regions (blues, neutrals, low chroma areas).
    So the nearest named color is found by:
    1. Converting the color RGB to Lab,
    2. Computing ΔE00 against each named entry in the dataset
    3. Selecting the smallest ΔE00
    Source code to Compute Nearest Match Metric
    
    
    	///////////////////////////////////////////////////////////////////////////////
    	//
    	// double GetCIEDE2000(const LAB& lab1, const LAB& lab2)
    	//
    	// Param: 
    	//     - const LAB& lab1: Reference to First LAB
    	//     - const LAB& lab2: Reference to Second LAB
    	//
    	// Purpose: Compute the CIEDE2000 color Difference between two colors in the L*a*b* color space
    	//                   
    	// Return Value: double 
    	//     - the DeltaE*00 (CIEDE2000) of the two LAB colors 
    	// 
    	// Note: 
    	//   - GetCIEDE2000 code adapted from Gregory Fiumara CIEDE2000.cpp 
    	//   - Source: https://github.com/gfiumara/CIEDE2000/blob/master/CIEDE2000.cpp
    	// 
    	// History:
    	//
    	// Date      Comment                                   Initials
    	//
    	// Feb 2026  Created                                   TGM
    	// 
    	///////////////////////////////////////////////////////////////////////////////
    
    	double deg2Rad(const double deg)
    	{
    		return (deg * (dPi / 180.0));
    	}
    
    	double rad2Deg(const double rad)
    	{
    		return ((180.0 / dPi) * rad);
    	}
    
    	double GetCIEDE2000(const LAB& lab1, const LAB& lab2)
    	{
    		/*
    		 * "For these and all other numerical/graphical delta E00 values
    		 * reported in this article, we set the parametric weighting factors
    		 * to unity(i.e., k_L = k_C = k_H = 1.0)." (Page 27).
    		 */
    		const double k_L = 1.0, k_C = 1.0, k_H = 1.0;
    		const double deg360InRad = deg2Rad(360.0);
    		const double deg180InRad = deg2Rad(180.0);
    		const double pow25To7 = 6103515625.0; /* pow(25, 7) */
    
    		// Step 1
    		 /* Equation 2 */
    		double C1 = sqrt((lab1.s_dLabA * lab1.s_dLabA) + (lab1.s_dLabB * lab1.s_dLabB));
    		double C2 = sqrt((lab2.s_dLabA * lab2.s_dLabA) + (lab2.s_dLabB * lab2.s_dLabB));
    		/* Equation 3 */
    		double barC = (C1 + C2) / 2.0;
    		/* Equation 4 */
    		double G = 0.5 * (1 - sqrt(pow(barC, 7) / (pow(barC, 7) + pow25To7)));
    		/* Equation 5 */
    		double a1Prime = (1.0 + G) * lab1.s_dLabA;
    		double a2Prime = (1.0 + G) * lab2.s_dLabA;
    		/* Equation 6 */
    		double CPrime1 = sqrt((a1Prime * a1Prime) + (lab1.s_dLabB * lab1.s_dLabB));
    		double CPrime2 = sqrt((a2Prime * a2Prime) + (lab2.s_dLabB * lab2.s_dLabB));
    		/* Equation 7 */
    		double hPrime1;
    		if (lab1.s_dLabB == 0 && a1Prime == 0)
    			hPrime1 = 0.0;
    		else 
    		{
    			hPrime1 = atan2(lab1.s_dLabB, a1Prime);
    			/*
    			 * This must be converted to a hue angle in degrees between 0
    			 * and 360 by addition of 2 to negative hue angles.
    			 */
    			if (hPrime1 < 0)
    				hPrime1 += deg360InRad;
    		}
    		double hPrime2;
    		if (lab2.s_dLabB == 0 && a2Prime == 0)
    			hPrime2 = 0.0;
    		else 
    		{
    			hPrime2 = atan2(lab2.s_dLabB, a2Prime);
    			/*
    			 * This must be converted to a hue angle in degrees between 0
    			 * and 360 by addition of 2 to negative hue angles.
    			 */
    			if (hPrime2 < 0)
    				hPrime2 += deg360InRad;
    		}
    
    		// Step 2
    		 /* Equation 8 */
    		double deltaLPrime = lab2.s_dLabL - lab1.s_dLabL;
    		/* Equation 9 */
    		double deltaCPrime = CPrime2 - CPrime1;
    		/* Equation 10 */
    		double deltahPrime;
    		double CPrimeProduct = CPrime1 * CPrime2;
    		if (CPrimeProduct == 0)
    			deltahPrime = 0;
    		else 
    		{
    			/* Avoid the fabs() call */
    			deltahPrime = hPrime2 - hPrime1;
    			if (deltahPrime < -deg180InRad)
    				deltahPrime += deg360InRad;
    			else if (deltahPrime > deg180InRad)
    				deltahPrime -= deg360InRad;
    		}
    		/* Equation 11 */
    		double deltaHPrime = 2.0 * sqrt(CPrimeProduct) * sin(deltahPrime / 2.0);
    
    		// Step 3
    		 /* Equation 12 */
    		double barLPrime = (lab1.s_dLabL + lab2.s_dLabL) / 2.0;
    		/* Equation 13 */
    		double barCPrime = (CPrime1 + CPrime2) / 2.0;
    		/* Equation 14 */
    		double barhPrime, hPrimeSum = hPrime1 + hPrime2;
    		if (CPrime1 * CPrime2 == 0) 
    		{
    			barhPrime = hPrimeSum;
    		}
    		else 
    		{
    			if (fabs(hPrime1 - hPrime2) <= deg180InRad)
    				barhPrime = hPrimeSum / 2.0;
    			else 
    			{
    				if (hPrimeSum < deg360InRad)
    					barhPrime = (hPrimeSum + deg360InRad) / 2.0;
    				else
    					barhPrime = (hPrimeSum - deg360InRad) / 2.0;
    			}
    		}
    		/* Equation 15 */
    		double T = 1.0 - (0.17 * cos(barhPrime - deg2Rad(30.0))) + (0.24 * cos(2.0 * barhPrime)) + (0.32 * cos((3.0 * barhPrime) + deg2Rad(6.0))) - (0.20 * cos((4.0 * barhPrime) - deg2Rad(63.0)));
    		/* Equation 16 */
    		double deltaTheta = deg2Rad(30.0) * exp(-pow((barhPrime - deg2Rad(275.0)) / deg2Rad(25.0), 2.0));
    		/* Equation 17 */
    		double R_C = 2.0 * sqrt(pow(barCPrime, 7.0) / (pow(barCPrime, 7.0) + pow25To7));
    		/* Equation 18 */
    		double S_L = 1 + ((0.015 * pow(barLPrime - 50.0, 2.0)) / sqrt(20 + pow(barLPrime - 50.0, 2.0)));
    		/* Equation 19 */
    		double S_C = 1 + (0.045 * barCPrime);
    		/* Equation 20 */
    		double S_H = 1 + (0.015 * barCPrime * T);
    		/* Equation 21 */
    		double R_T = (-sin(2.0 * deltaTheta)) * R_C;
    		/* Equation 22 */
    		double deltaE = sqrt(pow(deltaLPrime / (k_L * S_L), 2.0) + pow(deltaCPrime / (k_C * S_C), 2.0) + pow(deltaHPrime / (k_H * S_H), 2.0) + (R_T * (deltaCPrime / (k_C * S_C)) * (deltaHPrime / (k_H * S_H))));
    
    		return (deltaE);
    	}
    
    
Generating Color Data Array

This chapter explains how the color data array is built: color names, RGB values, computed Lab, HSL and ΔE00 values, and a label layer (Ghost, Pale, Dim, Deep, …) that replaces many of the purely numeric X11 variants.

  1. Data Sources

    The dataset is assembled from three sources:

    1. X11 Colors and Variants
      Source: https://en.wikipedia.org/wiki/X11_color_names

      The X11 color list is a set of named colors that also forms the foundation of web colors, even though differences between the lists can sometimes cause confusion. The X11 color list contains 78 entries.
      Each entry provides the following data:
      1. Base Color Name: the name of the color entry
      2. Base Color: the RGB value of the base color
      3. Variants: three or four RGB colors defining variants of the base color
      Note that the variant names are simply enumerations of the base color (for example blue#1, blue#2, …), regardless of their relative luminance compared to the base color. For about 25 colors, the first variant corresponds exactly to the base color. In all entries, the variants are listed from lighter colors to darker colors of the same base color.

    2. Web Color Name Chart
      Source: https://en.wikipedia.org/wiki/X11_color_names

      The Web X11 list was later adopted in HTML and CSS, resulting in a list of 145 named colors. X11 and Web X11 share more than 70 identical color definitions. These colors are heavily used in web development.

    3. Achromatic Colors

      Achromatic colors are colors where the three RGB components have identical values. The X11 list provides a monotonous sequence of such colors, ranging from Gray0 (which is actually black) to Gray100 (white). While this approach is suitable for computers, it is less practical for human perception.
      Therefore this sequence was replaced by a list of 30 colors ranging from black to white, using descriptive names that are more suitable for user interfaces. This does not change the underlying RGB values; it only improves presentation.
    4. All together, the initial color array contained about 435 colors, most of them originating from the 78 X11 base colors and their variants.

    5. Color Data Formatting

      The color data were structured as strings containing individual entries separated by semicolons.

      1. X11 color format
        Each entry contains: ColorName,BaseRGB,VariantRGB1,VariantRGB2,VariantRGB3,VariantRGB4;
        Sample: Antique White,#FAEBD7,#FFEFDB,#EEDFCC,#CDC0B0,#8B8378;Aquamarine,#7FFFD4,#7FFFD4,#76EEC6,#66CDAA,#458B74;...

      2. CSS named colors format
        CSS colors contain only a single RGB value.
        Sample: Alice Blue,#F0F8FF;Antique White,#FAEBD7;Aqua,#00FFFF;...

      3. Gray scale list format
        Sample: Black,#000000;Deep Obsidian,#080808;Dark Charcoal,#141414;...

  2. Preparing Colors Listing

    Using the three color lists, a global list was created as a string of color families separated by semicolons.

    Each family contains data separated by colons:
    		Base Color Name,Base RGB,Variant1,Variant2,Variant3,Variant4;
    	

    The different lists were processed by a dedicated C++ utility that generates the declaration of a string array containing 167 standardized color names:

    1. 78 from the X11 list
    2. 59 from the Web X11 list
    3. 30 from the gray scale list

    This declaration is located in Toolkit.h as a member of the namespace Toolkit::Color.

    	inline std::array m_aColorName =
    	{{ L"Antique White",L"Aquamarine",L"Azure",L"Bisque",L"Blue", ... ,L"White", L"Olive Green" }};
    	
    Complete Toolkit::Color::m_aColorName Array
    
    	inline std::array m_aColorName = { { 
    	L"Antique White",L"Aquamarine",L"Azure",L"Bisque",L"Blue",L"Brown",L"Burlywood",L"Cadet Blue",L"Chartreuse",L"Chocolate",L"Coral",
    	L"Cornsilk",L"Cyan",L"Dark Goldenrod",L"Dark Olive Green",L"Dark Orange",L"Dark Orchid",L"Dark Sea Green",L"Dark Slate Gray",L"Deep Pink",
    	L"Deep Sky Blue",L"Dodger Blue",L"Firebrick",L"Gold",L"Goldenrod",L"Green",L"Honeydew",L"Hot Pink",L"Indian Red",L"Ivory",L"Khaki",
    	L"Lavender Blush",L"Lemon Chiffon",L"Light Blue",L"Light Cyan",L"Light Goldenrod",L"Light Pink",L"Light Salmon",L"Light Sky Blue",
    	L"Light Steel Blue",L"Light Yellow",L"Magenta",L"Maroon",L"Medium Orchid",L"Medium Purple",L"Misty Rose",L"Navajo White",L"Olive Drab",
    	L"Orange",L"Orange Red",L"Orchid",L"Pale Green",L"Pale Turquoise",L"Pale Violet Red",L"Peach Puff",L"Pink",L"Plum",L"Purple",L"Red",
    	L"Rosy Brown",L"Royal Blue",L"Salmon",L"Sea Green",L"Seashell",L"Sienna",L"Sky Blue",L"Slate Blue",L"Slate Gray",L"Snow",L"Spring Green",
    	L"Steel Blue",L"Tan",L"Thistle",L"Tomato",L"Turquoise",L"Violet Red",L"Wheat",L"Yellow",L"Alice Blue",L"Aqua",L"Beige",L"Blanched Almond",
    	L"Blue Violet",L"Cornflower Blue",L"Crimson",L"Dark Blue",L"Dark Cyan",L"Dark Green",L"Dark Khaki",L"Dark Magenta",L"Dark Red",
    	L"Dark Salmon",L"Dark Slate Blue",L"Dark Turquoise",L"Dark Violet",L"Floral White",L"Forest Green",L"Fuchsia",L"Ghost White",L"Web Green",
    	L"Green Yellow",L"Indigo",L"Lavender",L"Lawn Green",L"Light Coral",L"Light Green",L"Light Sea Green",L"Light Slate Gray",L"Lime",
    	L"Lime Green",L"Linen",L"Web Maroon",L"Medium Aquamarine",L"Medium Blue",L"Medium Sea Green",L"Medium Slate Blue",L"Medium Spring Green",
    	L"Medium Turquoise",L"Medium Violet Red",L"Midnight Blue",L"Mint Cream",L"Moccasin",L"Navy Blue",L"Old Lace",L"Olive",L"Pale Goldenrod",
    	L"Papaya Whip",L"Peru",L"Powder Blue",L"Web Purple",L"Rebecca Purple",L"Saddle brown",L"Sandy Brown",L"Teal",L"Violet",L"Yellow Green",
    	L"Black",L"Deep Obsidian",L"Dark Charcoal",L"Dark Anthracite",L"Deep Gunmetal",L"Solid Onyx",L"Heavy Asphalt",L"Deep Slate",
    	L"Strong Graphite",L"Dim Gray",L"True Iron",L"Web Gray",L"Medium Steel",L"Light Pewter",L"Normal Granite",L"Dark Gray",L"Bright Silver",
    	L"Light Platinum",L"Gray",L"Silver",L"Lighter Ash",L"Light Gray",L"Soft Cloud",L"Gainsboro",L"Pale Mist",L"Very Light Gray",
    	L"Whiter Gainsboro",L"White Smoke",L"Lightest Frost",L"White",L"Olive Green"} };
    	
  3. Organizing Colors Data

    Using the three color lists, a global list was created as a string of color families separated by semicolons.

    View the three color lists: X11, Web X11 and Achromatic Colors
    
    std::wstring wstrX11 =
    L"Antique White,#FAEBD7,#FFEFDB,#EEDFCC,#CDC0B0,#8B8378;Aquamarine,#7FFFD4,#7FFFD4,#76EEC6,#66CDAA,#458B74;Azure,#F0FFFF,#F0FFFF,#E0EEEE,#C1CDCD,#838B8B;Bisque,#FFE4C4,#FFE4C4,#EED5B7,#CDB79E,#8B7D6B;Blue,#0000FF,#0000FF,#0000EE,#0000CD,#00008B;Brown,#A52A2A,#FF4040,#EE3B3B,#CD3333,#8B2323;Burlywood,#DEB887,#FFD39B,#EEC591,#CDAA7D,#8B7355;Cadet Blue,#5F9EA0,#98F5FF,#8EE5EE,#7AC5CD,#53868B;Chartreuse,#7FFF00,#7FFF00,#76EE00,#66CD00,#458B00;Chocolate,#D2691E,#FF7F24,#EE7621,#CD661D,#8B4513;Coral,#FF7F50,#FF7256,#EE6A50,#CD5B45,#8B3E2F;Cornsilk,#FFF8DC,#FFF8DC,#EEE8CD,#CDC8B1,#8B8878;Cyan,#00FFFF,#00FFFF,#00EEEE,#00CDCD,#008B8B;Dark Goldenrod,#B8860B,#FFB90F,#EEAD0E,#CD950C,#8B6508;Dark Olive Green,#556B2F,#CAFF70,#BCEE68,#A2CD5A,#6E8B3D;Dark Orange,#FF8C00,#FF7F00,#EE7600,#CD6600,#8B4500;Dark Orchid,#9932CC,#BF3EFF,#B23AEE,#9A32CD,#68228B;Dark Sea Green,#8FBC8F,#C1FFC1,#B4EEB4,#9BCD9B,#698B69;Dark Slate Gray,#2F4F4F,#97FFFF,#8DEEEE,#79CDCD,#528B8B;Deep Pink,#FF1493,#FF1493,#EE1289,#CD1076,#8B0A50;Deep Sky Blue,#00BFFF,#00BFFF,#00B2EE,#009ACD,#00688B;Dodger Blue,#1E90FF,#1E90FF,#1C86EE,#1874CD,#104E8B;Firebrick,#B22222,#FF3030,#EE2C2C,#CD2626,#8B1A1A;Gold,#FFD700,#FFD700,#EEC900,#CDAD00,#8B7500;Goldenrod,#DAA520,#FFC125,#EEB422,#CD9B1D,#8B6914;Green,#00FF00,#00FF00,#00EE00,#00CD00,#008B00;Honeydew,#F0FFF0,#F0FFF0,#E0EEE0,#C1CDC1,#838B83;Hot Pink,#FF69B4,#FF6EB4,#EE6AA7,#CD6090,#8B3A62;Indian Red,#CD5C5C,#FF6A6A,#EE6363,#CD5555,#8B3A3A;Ivory,#FFFFF0,#FFFFF0,#EEEEE0,#CDCDC1,#8B8B83;Khaki,#F0E68C,#FFF68F,#EEE685,#CDC673,#8B864E;Lavender Blush,#FFF0F5,#FFF0F5,#EEE0E5,#CDC1C5,#8B8386;Lemon Chiffon,#FFFACD,#FFFACD,#EEE9BF,#CDC9A5,#8B8970;Light Blue,#ADD8E6,#BFEFFF,#B2DFEE,#9AC0CD,#68838B;Light Cyan,#E0FFFF,#E0FFFF,#D1EEEE,#B4CDCD,#7A8B8B;Light Goldenrod,#EEDD82,#FFEC8B,#EEDC82,#CDBE70,#8B814C;Light Pink,#FFB6C1,#FFAEB9,#EEA2AD,#CD8C95,#8B5F65;Light Salmon,#FFA07A,#FFA07A,#EE9572,#CD8162,#8B5742;Light Sky Blue,#87CEFA,#B0E2FF,#A4D3EE,#8DB6CD,#607B8B;Light Steel Blue,#B0C4DE,#CAE1FF,#BCD2EE,#A2B5CD,#6E7B8B;Light Yellow,#FFFFE0,#FFFFE0,#EEEED1,#CDCDB4,#8B8B7A;Magenta,#FF00FF,#FF00FF,#EE00EE,#CD00CD,#8B008B;Maroon,#B03060,#FF34B3,#EE30A7,#CD2990,#8B1C62;Medium Orchid,#BA55D3,#E066FF,#D15FEE,#B452CD,#7A378B;Medium Purple,#9370DB,#AB82FF,#9F79EE,#8968CD,#5D478B;Misty Rose,#FFE4E1,#FFE4E1,#EED5D2,#CDB7B5,#8B7D7B;Navajo White,#FFDEAD,#FFDEAD,#EECFA1,#CDB38B,#8B795E;Olive Drab,#6B8E23,#C0FF3E,#B3EE3A,#9ACD32,#698B22;Orange,#FFA500,#FFA500,#EE9A00,#CD8500,#8B5A00;Orange Red,#FF4500,#FF4500,#EE4000,#CD3700,#8B2500;Orchid,#DA70D6,#FF83FA,#EE7AE9,#CD69C9,#8B4789;Pale Green,#98FB98,#9AFF9A,#90EE90,#7CCD7C,#548B54;Pale Turquoise,#AFEEEE,#BBFFFF,#AEEEEE,#96CDCD,#668B8B;Pale Violet Red,#DB7093,#FF82AB,#EE799F,#CD6889,#8B475D;Peach Puff,#FFDAB9,#FFDAB9,#EECBAD,#CDAF95,#8B7765;Pink,#FFC0CB,#FFB5C5,#EEA9B8,#CD919E,#8B636C;Plum,#DDA0DD,#FFBBFF,#EEAEEE,#CD96CD,#8B668B;Purple,#A020F0,#9B30FF,#912CEE,#7D26CD,#551A8B;Red,#FF0000,#FF0000,#EE0000,#CD0000,#8B0000;Rosy Brown,#BC8F8F,#FFC1C1,#EEB4B4,#CD9B9B,#8B6969;Royal Blue,#4169E1,#4876FF,#436EEE,#3A5FCD,#27408B;Salmon,#FA8072,#FF8C69,#EE8262,#CD7054,#8B4C39;Sea Green,#2E8B57,#54FF9F,#4EEE94,#43CD80,#2E8B57;Seashell,#FFF5EE,#FFF5EE,#EEE5DE,#CDC5BF,#8B8682;Sienna,#A0522D,#FF8247,#EE7942,#CD6839,#8B4726;Sky Blue,#87CEEB,#87CEFF,#7EC0EE,#6CA6CD,#4A708B;Slate Blue,#6A5ACD,#836FFF,#7A67EE,#6959CD,#473C8B;Slate Gray,#708090,#C6E2FF,#B9D3EE,#9FB6CD,#6C7B8B;Snow,#FFFAFA,#FFFAFA,#EEE9E9,#CDC9C9,#8B8989;Spring Green,#00FF7F,#00FF7F,#00EE76,#00CD66,#008B45;Steel Blue,#4682B4,#63B8FF,#5CACEE,#4F94CD,#36648B;Tan,#D2B48C,#FFA54F,#EE9A49,#CD853F,#8B5A2B;Thistle,#D8BFD8,#FFE1FF,#EED2EE,#CDB5CD,#8B7B8B;Tomato,#FF6347,#FF6347,#EE5C42,#CD4F39,#8B3626;Turquoise,#40E0D0,#00F5FF,#00E5EE,#00C5CD,#00868B;Violet Red,#D02090,#FF3E96,#EE3A8C,#CD3278,#8B2252;Wheat,#F5DEB3,#FFE7BA,#EED8AE,#CDBA96,#8B7E66;Yellow,#FFFF00,#FFFF00,#EEEE00,#CDCD00,#8B8B00;";
    
    std::wstring wstrWebX11 =
    L"Alice Blue,#F0F8FF;Antique White,#FAEBD7;Aqua,#00FFFF;Aquamarine,#7FFFD4;Azure,#F0FFFF;Beige,#F5F5DC;Bisque,#FFE4C4;Black,#000000;Blanched Almond,#FFEBCD;Blue,#0000FF;Blue Violet,#8A2BE2;Brown,#A52A2A;Burlywood,#DEB887;Cadet Blue,#5F9EA0;Chartreuse,#7FFF00;Chocolate,#D2691E;Coral,#FF7F50;Cornflower Blue,#6495ED;Cornsilk,#FFF8DC;Crimson,#DC143C;Cyan,#00FFFF;Dark Blue,#00008B;Dark Cyan,#008B8B;Dark Goldenrod,#B8860B;Dark Gray,#A9A9A9;Dark Green,#006400;Dark Khaki,#BDB76B;Dark Magenta,#8B008B;Dark Olive Green,#556B2F;Dark Orange,#FF8C00;Dark Orchid,#9932CC;Dark Red,#8B0000;Dark Salmon,#E9967A;Dark Sea Green,#8FBC8F;Dark Slate Blue,#483D8B;Dark Slate Gray,#2F4F4F;Dark Turquoise,#00CED1;Dark Violet,#9400D3;Deep Pink,#FF1493;Deep Sky Blue,#00BFFF;Dim Gray,#696969;Dodger Blue,#1E90FF;Firebrick,#B22222;Floral White,#FFFAF0;Forest Green,#228B22;Fuchsia,#FF00FF;Gainsboro,#DCDCDC;Ghost White,#F8F8FF;Gold,#FFD700;Goldenrod,#DAA520;Gray,#BEBEBE;Web Gray,#808080;Green,#00FF00;Web Green,#008000;Green Yellow,#ADFF2F;Honeydew,#F0FFF0;Hot Pink,#FF69B4;Indian Red,#CD5C5C;Indigo,#4B0082;Ivory,#FFFFF0;Khaki,#F0E68C;Lavender,#E6E6FA;Lavender Blush,#FFF0F5;Lawn Green,#7CFC00;Lemon Chiffon,#FFFACD;Light Blue,#ADD8E6;Light Coral,#F08080;Light Cyan,#E0FFFF;Light Goldenrod,#FAFAD2;Light Gray,#D3D3D3;Light Green,#90EE90;Light Pink,#FFB6C1;Light Salmon,#FFA07A;Light Sea Green,#20B2AA;Light Sky Blue,#87CEFA;Light Slate Gray,#778899;Light Steel Blue,#B0C4DE;Light Yellow,#FFFFE0;Lime,#00FF00;Lime Green,#32CD32;Linen,#FAF0E6;Magenta,#FF00FF;Maroon,#B03060;Web Maroon,#800000;Medium Aquamarine,#66CDAA;Medium Blue,#0000CD;Medium Orchid,#BA55D3;Medium Purple,#9370DB;Medium Sea Green,#3CB371;Medium Slate Blue,#7B68EE;Medium Spring Green,#00FA9A;Medium Turquoise,#48D1CC;Medium Violet Red,#C71585;Midnight Blue,#191970;Mint Cream,#F5FFFA;Misty Rose,#FFE4E1;Moccasin,#FFE4B5;Navajo White,#FFDEAD;Navy Blue,#000080;Old Lace,#FDF5E6;Olive,#808000;Olive Drab,#6B8E23;Orange,#FFA500;Orange Red,#FF4500;Orchid,#DA70D6;Pale Goldenrod,#EEE8AA;Pale Green,#98FB98;Pale Turquoise,#AFEEEE;Pale Violet Red,#DB7093;Papaya Whip,#FFEFD5;Peach Puff,#FFDAB9;Peru,#CD853F;Pink,#FFC0CB;Plum,#DDA0DD;Powder Blue,#B0E0E6;Purple,#A020F0;Web Purple,#800080;Rebecca Purple,#663399;Red,#FF0000;Rosy Brown,#BC8F8F;Royal Blue,#4169E1;Saddle brown,#8B4513;Salmon,#FA8072;Sandy Brown,#F4A460;Sea Green,#2E8B57;Seashell,#FFF5EE;Sienna,#A0522D;Silver,#C0C0C0;Sky Blue,#87CEEB;Slate Blue,#6A5ACD;Slate Gray,#708090;Snow,#FFFAFA;Spring Green,#00FF7F;Steel Blue,#4682B4;Tan,#D2B48C;Teal,#008080;Thistle,#D8BFD8;Tomato,#FF6347;Turquoise,#40E0D0;Violet,#EE82EE;Wheat,#F5DEB3;White,#FFFFFF;White Smoke,#F5F5F5;Yellow,#FFFF00;Yellow Green,#9ACD32;";
    
    std::wstring wstrGray = L"Black,#000000;Deep Obsidian,#080808;Dark Charcoal,#141414;Dark Anthracite,#212121;Deep Gunmetal,#2E2E2E;Solid Onyx,#3B3B3B;Heavy Asphalt,#474747;Deep Slate,#545454;Strong Graphite,#616161;Dim Gray,#696969;True Iron,#6E6E6E;Web Gray,#808080;Medium Steel,#878787;Light Pewter,#949494;Normal Granite,#A1A1A1;Dark Gray,#A9A9A9;Bright Silver,#ADADAD;Light Platinum,#BABABA;Gray,#BEBEBE;Silver,#C0C0C0;Lighter Ash,#C7C7C7;Light Gray,#D3D3D3;Soft Cloud,#D7D7D7;Gainsboro,#DCDCDC;Pale Mist,#E0E0E0;Very Light Gray,#E6E6E6;Whiter Gainsboro,#EDEDED;White Smoke,#F5F5F5;Lightest Frost,#FAFAFA;White,#FFFFFF;";
    	

    Using the same C++ utility, the named color array was generated from the three listings.

    Color data are gathered in an array of structures named ColorData, defined as follows:

    
    	struct ColorData
    	{
    		int16_t s_nColorNameNdx = -1;
    		int16_t s_nExtColorNameNdx = -1;
    		int8_t  s_nVariantNdx = 0;      // 0: base name, 1..4: #x variant
    		int8_t  s_nLabelNdx = c_nColorLabelMidNdx;
    	
    		uint8_t s_nRValue{};
    		uint8_t s_nGValue{};
    		uint8_t s_nBValue{};
    
    		double  s_dLabL{};
    		double  s_dLabA{};
    		double  s_dLabB{};
    
    		double  s_dHslH{};
    		double  s_dHslS{};
    		double  s_dHslL{};
    
    		double  s_dDeltaE00{}; // CIEDE2000
    	};
    	

    The member s_nColorNameNdx stores the index of the color name in the array Toolkit::Color::m_aColorName. This significantly reduces the memory required for storing color data.

    The member s_nVariantNdx stores the index of the color variant.

    1. For X11 colors:
      1. Variant 0 → base color
      2. Variants 1..4 → X11 color variants
    2. For Web X11 colors: the variant index is always 0
    3. For Achromatic colors: the variant index is always 0

    All colors with variant index 0 are considered to have official names and therefore cannot be modified.

    The main idea for generating human-readable color names is to attach descriptive labels to colors.

    To support this idea, a simple list of descriptive labels was therefore defined, ordered from lighter to darker tones.

    1. Light colors: Ghost, Mist, Pearl, Pale, Light, Subtle, Soft, Faint
    2. Base color: (no label)
    3. Dark colors: Dim, Twilight, Medium, Midnight, Deep, Dark, Navy, Dusky, Inky

    As with the color names, the member s_nLabelNdx of the entries in the ColorData array only store the index of the label, not the string itself..

    Notes:

    • Storing indices instead of repeating strings keeps memory small and avoids duplication.
    • Lab and HSL values are precomputed once and stored directly in the dataset.
    • DeltaE00 are used during color sorting and label assignment
  4. Gathering Colors to Prepare Label Indexing

    Colors (in the list of 167 entries described above) were then grouped according to:

    1. their base color (as found in the Toolkit::Color::m_aColorName Array)
    2. one of the descriptive labels

    As an example, the group of the color related to the Blue color gathers the following data:

    1. From X11:
      1. Blue (base color and variants)
      2. Light Blue (base color and variants)
    2. From Web X11:
      1. Medium Blue (single base color)
      2. Midnight Blue (single base color)
      3. Dark Blue (single base color)
      4. Navy Blue (single base color)

    Colors already containing a label not present in the label list (such as Royal Blue) were kept in their own group.

    The resulting array was then processed to remove equivalent or nearly equivalent colors.

    Example — Blue color group:
    Light Blue     Variant 0 (X11)
    Light Blue     Variant 1 (X11)
    Light Blue     Variant 2 (X11)
    Light Blue     Variant 3 (X11)
    Light Blue     Variant 4 (X11)
    
    Blue           Variant 0 (X11)
    Blue           Variant 2 (X11)
    
    Medium Blue    Variant 0 (Web X11)
    Midnight Blue  Variant 0 (Web X11)
    Dark Blue      Variant 0 (Web X11)
    Navy Blue      Variant 0 (Web X11)
    	

    Variant Blue#1 does not exist because it is identical to the base color. Variant Blue#3 is identical to Medium Blue from the Web X11 list.

    Another example is the Goldenrod group, where several colors are extremely similar and were removed from the group.

    Goldenrod 255-193-37  and 255-185-15
    Goldenrod 238-180-34  and 238-137-14
    Goldenrod 205-155-29  and 205-149-12
    
  5. Getting the Label Index

    The most delicate part of the color array generation is determining the label index within a color group.

    The process is supposed to be simple, but some color inconsistencies made labeling quite tedious

    1. For each group (colors that share the same base color), the lightest color is identified as a reference
    2. The Color Distance is then computed for every color in the group relative to that reference
    3. The color group is sorted by the color distance previously computed
    4. The descriptive labels are selected according to the relative distance between the base color and the different colors of the group

    Originally, color groups were sorted using the Lab Lightness (L) component. However, this ordering does not perfectly match human visual perception. It was therefore replaced by a sort using the CIEDE2000 color difference (ΔE00) values.

    The image below illustrates the difference between the two approaches.


    The left side of the picture shows colors sorted by Lab Lightness.

    The right side shows colors sorted by CIEDE2000 difference.

    On the left side, the third swatch corresponds to Green (X11 Green#0) and the fourth to Light Green (X11 Light Green#0).

    Clearly, the perceived order does not match the color names.

    On the right side, sorting by ΔE00 produces an order that is closer to human perception.

    Figure 4 – Sort using LAB Lightness and CIEDE2000
    Hover to enlarge view

    The dedicated C++ application then computes the best fit between the sorted colors and the order of the labels in the array of color labels.

    Remind the label gradient:

    1. Light colors: Ghost, Mist, Pearl, Pale, Light, Subtle, Soft, Faint
    2. Base color: (no label)
    3. Dark colors: Dim, Twilight, Medium, Midnight, Deep, Dark, Navy, Dusky, Inky

    Most the times, the fit is perfect:


    The name of the group's base color (X11 Khaki#0) cannot be changed because it is a variant#0 color whose official name already exists.

    The name of the CSS Dark Khaki (X11 Dark Khaki#0) cannot be changed because it is a variant#0 color whose official name already exists.

    The color X11 Khaki#3 is between X11 Khaki#0 and CSS Dark Khaki#0. In the label list, it is mathematically closer to Dark. So it received the Label Deep.

    The colors X11 Khaki#2 is somewhat lighter than X11 Khaki#0. The label Subtle was given to X11 Khaki#2 as it's very close to the base color in the Label List.

    The colors X11 Khaki#1 is visually lighter than X11 Khaki#0. The label Light was given to X11 Khaki#1 as even lighter khaky labels may be used later.

    Figure 5 – Labeling the Khaki Color Group
    Hover to enlarge view

    However, existing color definitions are sometimes inconsistent with the ΔE00 ranking.. The Pink color is an example.


    The name of the group's base color (X11 Pink#0) cannot be changed because it is a variant#0 color whose official name already exists.

    The name of the Light Pink base color (X11 Light Pink#0) cannot be changed because it is a variant#0 color whose official name already exists.

    The Light Pink (X11 Light Pink#0) is visually (and mathematically) darker than Pink (X11 Pink#0). However, since it is a variant 0 color with an official name, its name is not modified.

    The remaining colors were labelled according to he distance with the X11 Pink#0 and this fits well with the position of the X11 Deep Pink#0 (not changeable).

    Figure 6 – Labeling the Pink Color Group
    Hover to enlarge view

    Similar inconsistencies exist for Salmon, Sea Green, Pink, Slate Blue, etc., but they are barely noticeable to the human eye and are sometimes subjective.

  6. Putting Everything together

    After parsing, conversion, grouping, de-duplication and label assignment, grouping all the color groups gives the complete table of named colors, containing 415 colors. The dedicated C++ utility generates the final dataset (made of the 415 entries) named m_aColorData (ColorData array), declared in the namespace Toolkit::Color (see file Toolkit.h).

    inline std::array m_aColorData =
    {{
    { 111, 111, 0, 8, 128, 0, 0, 25.5308, 48.0552, 38.0596, 0.0000, 100.0000, 25.0980 },
    { 90, 58, 0, 14, 139, 0, 0, 28.0847, 51.0104, 41.2947, 0.0000, 100.0000, 27.2549 },
    ...
    { 164, 164, 0, 8, 250, 250, 250, 98.2720, 0.0052, -0.0103, 0.0000, 0.0000, 98.0392 },
    { 165, 165, 0, 8, 255, 255, 255, 100.0000, 0.0053, -0.0104, 0.0000, 0.0000, 100.0000 }
    }};
    
Lookup algorithm
  1. Retrieving the Nearest Color Index

    The lookup algorithm is therefore intentionally simple: the input color is converted to Lab space, compared with all precomputed named colors using CIEDE2000, and the closest match is retained.

    Three overloaded functions GetNearestColorNdx() are provided for the lookup:

    1. int16_t GetNearestColorNdx(COLORREF rgb): this function first computes the Lab representation of the color passed as a COLORREF value
    2. int16_t GetNearestColorNdx(int nR, int nG, int nB): This function first computes the Lab representation of the color passed as three RGB components
    3. int16_t GetNearestColorNdx(LAB labData): This function performs the actual lookup by computing the color distance between the input color and all entries in the ColorData array

    These functions return the index of the nearest color in the ColorData array, or -1 if no color is found, which would be highly unexpected.

    	int16_t GetNearestColorNdx(COLORREF rgb)
    	{
    		const LAB labData = RGBToLAB(GetRValue(rgb), GetGValue(rgb), GetBValue(rgb));
    
    		return GetNearestColorNdx(labData);
    	}
    
    	int16_t GetNearestColorNdx(int nR, int nG, int nB)
    	{
    		const LAB labData = RGBToLAB(nR, nG, nB);
    
    		return GetNearestColorNdx(labData);
    	}
    	
    	int16_t GetNearestColorNdx(LAB labData)
    	{
    		double dBestSED = std::numeric_limits::infinity();
    		int16_t nNearestColorNdx = -1;
    		int16_t nNdx = 0;
    
    		for (const auto& cd : m_aColorData)
    		{
    			double dSED = GetCIEDE2000(labData, LAB{ cd.s_dLabL, cd.s_dLabA, cd.s_dLabB });
    
    			if (dSED < dBestSED)
    			{
    				dBestSED = dSED;
    				nNearestColorNdx = nNdx;
    			}
    
    			nNdx += 1;
    		}
    
    		return nNearestColorNdx;
    	}
    
  2. Retrieving the Color Name

    Retrieving the nearest color name is equally straightforward. As always, validating the index passed as a parameter is mandatory to avoid application crashes.

    1. std::wstring GetNearestColorName(int nNdx): this function returns the color name from the Extended X11 ColorData Array
    2. std::wstring GetNearestX11ColorName(int nNdx): This function returns the original X11 color name, that is, the base color name followed by its variant index.
    	std::wstring GetNearestColorName(int nNdx)
    	{
    		// Safety Check
    		if ((nNdx >= 0) && (nNdx < c_nColorDataSize))
    		{
    			ColorData* pcd = &m_aColorData[nNdx];
    
    			return std::format(L"{} {}", pcd->s_nLabelNdx != -1 ? m_aColorLabel[pcd->s_nLabelNdx] : L"?", pcd->s_nExtColorNameNdx != -1 ? m_aColorName[pcd->s_nExtColorNameNdx] : L"?");
    		}
    
    		return L"Unknown";
    	}
    	
    	std::wstring GetNearestX11ColorName(int nNdx)
    	{
    		// Safety Check
    		if ((nNdx >= 0) && (nNdx < c_nColorDataSize))
    		{
    			ColorData* pcd = &m_aColorData[nNdx];
    
    			if (pcd->s_nColorNameNdx != -1)
    				return std::format(L"{}#{}", m_aColorName[pcd->s_nColorNameNdx], pcd->s_nVariantNdx);
    		}
    
    		return L"Unknown";
    	}
    
User Interface

The NamedColorLookup utility is a dialog-based MFC application built around:

  1. a CListCtrl
  2. a custom owner-drawn CStatic control

TGMDev NamedColorLookup View
TGMDev NamedColorLookup View

Figure 3 – TGMDev NamedColorLookup View
Hover to enlarge view
  1. Rendering of TGMDev Extended X11 Color List

    The CListCtrl used to display the ColorData list is configured in the Dialog Editor as Owner Draw Fixed.

    The control is initialized in the OnInitDialog() function. The first column displays a color swatch showing the RGB value. The remaining columns display the associated color data: index in the ColorData array, extended X11 color name, X11 color name, and RGB, Lab, and HSL values.

    	// Init Color Data List
    	m_ListColorData.ModifyStyle(0, LVS_REPORT | LVS_SINGLESEL);
    	m_ListColorData.ModifyStyle(LVS_OWNERDRAWFIXED, 0);
    	m_ListColorData.SetExtendedStyle(m_ListColorData.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER);
    
    	m_ListColorData.InsertColumn(CLMN_COLOR, _T("Color"), LVCFMT_LEFT, 100);
    	m_ListColorData.InsertColumn(CLMN_NDX, _T("Ndx"), LVCFMT_LEFT, 40);
    	m_ListColorData.InsertColumn(CLMN_EXT_X11, _T("TGMDev Ext X11"), LVCFMT_LEFT, 130);
    	m_ListColorData.InsertColumn(CLMN_X11, _T("X11/Web CSS"), LVCFMT_LEFT, 130);
    	m_ListColorData.InsertColumn(CLMN_RGB, _T("RGB Data"), LVCFMT_RIGHT, 90);
    	m_ListColorData.InsertColumn(CLMN_LAB, _T("LAB Data"), LVCFMT_RIGHT, 125);
    	m_ListColorData.InsertColumn(CLMN_HSL, _T("HSL Data"), LVCFMT_RIGHT, 90);
    	

    To obtain a visually pleasing swatch area, the row height of the list control is increased by handling the WM_MEASUREITEM message.

    	void CNamedColorLookupDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
    	{
    		// Increase height (with check for Id)
    		if (nIDCtl == IDC_LIST_NAMED_COLOR)
    		{
    			// Increase height 3 times
    			lpMeasureItemStruct->itemHeight *= 3;
    		}
    	}
    	

    Note that for OnMeasureItem() to be called, the Owner Draw Fixed property must be set directly in the dialog template. This ensures that the corresponding style is already present when the control is created, allowing the system to send WM_MEASUREITEM before OnInitDialog() is executed.

    If the style is modified later in OnInitDialog(), OnMeasureItem() is not called because the WM_MEASUREITEM message for LVS_OWNERDRAWFIXED is sent only once, when the control is first created. By the time ModifyStyle() is called, the control has already completed its creation process and has missed the opportunity to request its dimensions.

    The update of the different columns is standard for a CListCtrl, except for the first one. The swatch column is drawn by handling the NM_CUSTOMDRAW notification sent by the list control when an owner-drawn cell must be painted. This notification is sent to the owner window of the CListCtrl.

    In the PopulateList() function, the color value (COLORREF) is stored in the item data associated with each row by using SetItemData(). The function OnNMCustomdrawListColors() retrieves the swatch color from that custom data and fills the first cell with the corresponding RGB value.

    	void CNamedColorLookupDlg::OnNMCustomdrawListColors(NMHDR* pNMHDR, LRESULT* pResult)
    	{
    		auto* pCD = reinterpret_cast(pNMHDR);
    
    		switch (pCD->nmcd.dwDrawStage)
    		{	
    			case CDDS_PREPAINT:
    				*pResult = CDRF_NOTIFYITEMDRAW;
    				return;
    
    			case CDDS_ITEMPREPAINT:
    				*pResult = CDRF_NOTIFYSUBITEMDRAW;
    				return;
    
    			case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
    			{
    				// Handle Only Swatch Column
    				if (pCD->iSubItem != 0)
    				{
    					*pResult = CDRF_DODEFAULT;
    					return;
    				}
    
    				// Get Item Data
    				const int nItem = (int)pCD->nmcd.dwItemSpec;
    
    				CDC dc;
    				dc.Attach(pCD->nmcd.hdc);
    
    				CRect rc;
    				m_ListColorData.GetSubItemRect(nItem, 0, LVIR_LABEL, rc);
    
    				// Respect selection background
    				const bool bSelected = (m_ListColorData.GetItemState(nItem, LVIS_SELECTED) & LVIS_SELECTED) != 0;
    				dc.FillSolidRect(rc, ::GetSysColor(bSelected ? COLOR_HIGHLIGHT : COLOR_WINDOW));
    
    				// Draw the color rectangle
    				rc.DeflateRect(4, 4);
    				dc.FillSolidRect(rc, COLORREF(m_ListColorData.GetItemData(nItem)));
    
    				dc.Detach();
    				
    				*pResult = CDRF_SKIPDEFAULT;
    				return;
    			}	
    		}
    
    		*pResult = CDRF_DODEFAULT;
    	}
    	
  2. Displaying the TGMDev Extended X11 Color Gradient

    The color gradient, build using the ColorData array, is rendered using a CStatic-derived class named CExtColorStrip.

    TGMDev NamedColorLookup Color Strip
    TGMDev NamedColorLookup Color Strip

    Figure 4 – TGMDev NamedColorLookup Color Strip
    Hover to enlarge view

    This is a very classical CStatic-derived control in which all drawing logic is implemented in the OnPaint() function.

    The successive colors stored in the ColorData array are displayed side by side as vertical lines one pixel wide, extending from the top to the bottom of the control.

Checking the Nearest Color Match

The nearest color lookup algorithm was checked by comparing its output with human visual perception across a wide range of RGB colors.

It should be noted that the mathematical foundations of perceptual color difference — including the CIEDE2000 formula — have already been extensively validated in the scientific literature. The purpose of the present check is therefore not to validate the CIEDE2000 metric itself, but rather to verify that the implemented lookup algorithm produces visually reasonable color names when applied to the available dataset.

  1. Purpose of the Matching Check

    The objective of this check was not to obtain mathematically perfect classification, but rather to ensure that the selected color names remain, in most cases, visually intuitive for a human observer.

    Since the dataset contains a limited number of named colors, some approximation is unavoidable. The goal is therefore to confirm that these approximations remain perceptually acceptable.

  2. Check Data Set

    The RGB color space is a model that combines red, green and blue light components.

    Each component ranges from 0 to 255, producing a total of: 256³ = 16,777,216 possible colors

    The ColorData array contains 415 named colors. Because the number of possible RGB colors is vastly larger than the number of available names, each named color necessarily represents a large region of the RGB color space.

    On average, one named color corresponds to more than: 16,777,216 / 415 ≈ 40,000 RGB colors

    To evaluate the behavior of the nearest color lookup algorithm, a representative test dataset was generated by sampling the RGB space. Each RGB channel was incremented with a step of 15, starting from (0,0,0) and ending at (255,255,255). This produces: (1 + 255/15)³ = 18³ = 5832 colors.

    This sampled dataset provides a practical compromise between coverage of the RGB space and computational efficiency. The effective mapping ratio becomes approximately: 16,777,216 / 5832 ≈ 2877 RGB colors per tested sample.

  3. Color Matching Check Method

    For each color in the test dataset, the nearest named color is computed using the algorithm described in the previous section.

    To ensure fast user interface updates, the CListCtrl instance m_ListNCLCheck is implemented as a virtual list (the Owner Data property is enabled in the Dialog Editor). In this mode, the data are not stored inside the control itself but managed by the owner window. In the class CNCLCheckDlg, the dataset is stored in the member: CNCLCheckDlg::m_paNCLCheck which is implemented as: std::unique_ptr>.

    During the execution of the function PopulateList(), the RGB values are increased monotonically from 0 to 255 with a step of 15 for each component R, G, and B, producing the dataset of 5832 test colors.

  4. Color Matching Results

    The nearest color check shows that, in most cases, the match between the test color and the nearest named color is visually convincing.

    The following screen captures illustrate typical results.


    Figure 5 – TGMDev NamedColorLookup Color Matching Check
    Hover to enlarge view

    Figure 6 – TGMDev NamedColorLookup Color Matching Check
    Hover to enlarge view

    In many situations, the difference between the test color and the nearest named color is barely perceptible. However, when magnifying the images, small differences become visible.

    For example, the following image illustrates the difference between the color: RGB(0,45,150) and its nearest named color: Midnight Royal Blue RGB(39,64,139)

    Although the colors appear very similar, a slight difference becomes visible when zooming.


    Figure 7 – Detailed comparison between original and nearest colors Screen copies using Google Colour picker
    Hover to enlarge view

    The computation of global statistics provides additional insight into the quality of the matching process.

    The average CIEDE2000 color difference observed during the test is approximately: ΔE00 ≈ 4.70 with a maximum observed value of approximately: ΔE00 ≈ 20.10

    These discrepancies between a perfect match (ΔE00 = 0) and the observed values are mainly located in specific regions of the RGB color space where the density of named colors is lower. Some differences remain subtle, while others become clearly visible.


    Figure 8 – TGMDev NamedColorLookup Color Matching Check
    Hover to enlarge view

    Figure 9 – TGMDev NamedColorLookup Color Matching Check
    Hover to enlarge view

    Figure 10 – TGMDev NamedColorLookup Color Matching Check
    Hover to enlarge view

    Additional experiments can easily be performed using the NearestColorLookup application. At the bottom of the check dialog, specific colors can be selected to verify the matching results interactively.

    Color can be modified by:

    1. Selecting a color using the Color Picker control
    2. Manually entering the R,G and B values
    3. Clicking the Generate Random Color button

    Figure 10 – TGMDev NamedColorLookup Specific Match testing
    Hover to enlarge view

    The specific color and its nearest match are displayed at any color setting change. The CIEDE2000 value illustrates the color distance between the two colors.

  5. Color Matching Limitations

    As discussed previously, the nearest match is inherently limited by the relatively small number of named colors (415) compared to the extremely large number of colors in the RGB color space (16,777,216).

    The observed discrepancies are therefore mainly caused by:

    1. the limited density of named colors in certain regions of the RGB space
    2. the subjective nature of color naming
    3. perceptual variations between observers

    Despite these limitations, the use of the CIEDE2000 perceptual distance combined with the extended X11 color dataset produces results that are generally consistent with human color perception.

Conclusions

The NamedColorLookup utility illustrates a practical implementation of perceptual color matching based on the CIELAB color space and the CIEDE2000 color difference formula.

Using an extended X11 color dataset and a straightforward nearest-neighbor search, the application provides a simple yet effective way to associate arbitrary RGB colors with human-readable color names.

The matching tests show that, despite the limited number of named colors, the results remain visually consistent for most regions of the RGB color space. The project highlights how well-established color models can be used in practical software tools to bridge the gap between numerical color representations and human perception.

Download Links
  1. Download NamedColorLookup executable from Microsoft Store
  2. Download NamedColorLookup Source Code
Comments and Questions
Comments and Questions

Id
Comment Author
Comment Date
Comment
Action
165
TGMDev
2026-03-10 09:20:08
Welcome to the Comments Section of NamedColorLookup.
Feel free to add comments or questions ...

Leave a comment
Leave a Comment or Question





Note: An email will be sent to the above email address to confirm your comment.
TGMDev
TGMDev

TGMDev KillProcessTGMDev PhotoRenamerTGMDev IsanakiYoutubeDownloader
Download icon by Icons8