Introduction
My first attempt to create a color selection control (the ColorMap
control) actually had two major drawbacks. First: where are the grays? This was the first remark I got. And to be honest with you, grays are not easily found in the ColorMap
. Oh, they're there. If you count the number of colors with red-value equals green-value equals blue-value (by definition a gray value), you'll find approximately 400 values of grays. But that's theory and you still can't select them!
That brings me to the second major drawback. One I already explained in the ColorMap
article. Selecting a color works fine, selecting the exact same color again is like a mission impossible.
So what we actually need is a palette of colors. Not thousands of colors, but rather a fixed number of distinct colors, so that we easily can remember which color it was we selected yesterday. However, we have to make sure all color types are available. Oh, and this time we want to see grays!
Creating the control
Before actually creating the control, let's summarize the functionality of the control:
- display a palette of colors containing all types of colors and grays.
- set the foreground color property by left clicking anywhere on the color map.
- set the background color property by right clicking anywhere on the color map.
And since we have palette, we add two more requirements:
- set the number of colors displayed per column.
- set the size of area displaying a single color.
The ColorMap
control is a good place to start. It contains most of the functionality listed. New are the values ColorsPerColumn
and ColorSize
. Also, the CreateColorMap
method of the ColorMap
control is renamed to CreateColorPalette
. As in the ColorMap
control, the method returns a bitmap.
The color palette
The color palette is nothing more than a list of colors displayed as colored squares. Each colored square being of size ColorSize
. So, the problem is creating the list of colors to be displayed and have them ordered in a way that makes sense.
The first problem was not that hard to solve. We could write some logic to create a list of colors, but the .NET Color
class contains all the colors we want to see. Getting these colors is a matter of using reflection to inspect the Color
class and extract the static color properties. Here's how to do it.
PropertyInfo[] properties;
ArrayList colors;
Color color;
SolidBrush brush;
properties =
typeof (Color).GetProperties (BindingFlags.Public | BindingFlags.Static);
colors = new ArrayList ();
foreach (PropertyInfo prop in properties) {
color = (Color) prop.GetValue (null, null);
if (color == Color.Transparent) continue;
if (color == Color.Empty) continue;
brush = new SolidBrush (color);
colors.Add (brush);
}
If you display this list of colors, you'll find that they're randomly scattered. That's because the colors are ordered alphabetically by name.
What we need is a good way to sort the colors. One method of ordering is using the RGB-value. Again, this will create an order in the colors that seems random to the human eye. Luckily, there is an alternative to RGB-ordering. It is called hue-saturation-brightness (or HSB). The value hue determines the location of the color in the color circle. For those of you who don't know what a color circle is, here's how it looks like:
The saturation value determines how much grey is in the color. The brightness value determines the color's intensity.
The sorting algorithm first orders by hue, then by saturation, and finally by brightness. The logic is captured in the internal class _ColorSorter
which implements the IComparer
interface. Note that the sorting is identical to the sorting of colors in the property window in Visual Studio.
internal class _ColorSorter: System.Collections.IComparer {
#region IComparer Members
public int Compare (object x, object y) {
Color cx, cy;
float hx, hy, sx, sy, bx, by;
cx = ((SolidBrush) x).Color;
cy = ((SolidBrush) y).Color;
sx = cx.GetSaturation ();
sy = cy.GetSaturation ();
hx = cx.GetHue ();
hy = cy.GetHue ();
bx = cx.GetBrightness ();
by = cy.GetBrightness ();
if (hx < hy) return -1;
else if (hx > hy) return 1;
else {
if (sx < sy) return -1;
else if (sx > sy) return 1;
else {
if (bx < by) return -1;
else if (bx > by) return 1;
else return 0;
}
}
}
#endregion
}
Points of Interest
The list of colors displayed contains what I call 'white gaps'. At first, I considered this to be a sorting problem, that is, until I took a good look at the colors shown in the Visual Studio property window and found that the same 'white gaps' were shown here also.
It would be interesting to see an algorithm that generates a list of colors that, when sorted by hue-saturation-brightness, doesn't create white gaps in the palette.
History
- 2004-12-21 - Initial release of article.