I ran into a need to map an RGB value to a palette color the last time I worked with the Google calendar API. I kept getting errors back when creating new calendars, and realized that I needed to pass a hex color that was in a defined palette. This left me with fewer options for the end-user, as I would now have to present them with a list of valid colors. I could avoid some type of palette chooser by mapping a given RGB value to the nearest Google color. In my situation an exact color match was not necessary.

Last week I posted an article with a function that would return the 17 CSS 2.1 colors. The code for the function was simple, but I needed it for this article. I actually thought about being cruel and writing the function to return the web safe color palette.

Mapping an RGB value to a palette color is a problem of finding the palette color that is the nearest in distance to a given RGB value. We can visualize this as shown on the left by plotting each palette color in 3d space using each color component as an axis.

We can get the Euclidean distance between two points by using the formula shown below that returns the distance between points P and Q.

$$d(p,q) = \sqrt{(p_r - q_r)^2 + (p_g - q_g)^2 + (p_b - q_b)^2}$$

Here is the same equation in CF code.

1<cfset d = SQR( ((p.r - q.r) ^ 2) + ((p.g - q.g) ^ 2) + ((p.b - q.b) ^ 2))

Now it's just a matter of iterating through the palette colors to find the shortest distance. Below is sample output from a program that generates a random RGB value and finds the nearest CSS 2.1 palette color.

Random Color - RGB(29,100,92)

Nearest Palette Color - RGB(0,128,128)

1<cfset randomRGB = {
2     r = RandRange(0, 255)
3    ,g = RandRange(0, 255)
4    ,b = RandRange(0, 255)
5} /
>

6
7<cfoutput>
8<strong>Random Color - RGB(#randomRGB.r#,#randomRGB.g#,#randomRGB.b#)</strong>
9<div style="width: 150px; height: 150px; background: rgb(#randomRGB.r#,#randomRGB.g#,#randomRGB.b#)">&nbsp;</div>
10</cfoutput>
11
12<cfset qCSS21Colors = getCSS21Colors()>
13
14<!--- this should be the greatest distance possible --->
15<cfset leastDistance = 444>
16
17<cfloop query="qCSS21Colors">
18    <cfset CSS21RGB = hexToRGB(qCSS21Colors.color)>
19
20    <!--- get the Euclidean distance --->
21     <cfset distance = SQR( ((CSS21RGB.r - randomRGB.r) ^ 2) + ((CSS21RGB.g - randomRGB.g) ^ 2) + ((CSS21RGB.b - randomRGB.b) ^ 2) )>
22
23     <!--- if a closer palette color is found, save it --->
24     <cfif distance LT leastDistance>
25     <cfset leastDistance = distance>
26     <cfset nearestColorRGB = CSS21RGB>
27     </cfif>
28</cfloop>
29
30<br />
31<cfoutput>
32<strong>Nearest Palette Color - RGB(#nearestColorRGB.r#,#nearestColorRGB.g#,#nearestColorRGB.b#)</strong>
33<div style="width: 150px; height: 150px; background: rgb(#nearestColorRGB.r#,#nearestColorRGB.g#,#nearestColorRGB.b#)">&nbsp;</div>
34</cfoutput>