Introduction
While working on a simple icon editor, I got confronted with the problem of selecting colors. Sure, I could use the color selection dialog, but since it is a dialog, it is not directly available for the user.
When looking at common graphic applications (e.g., Paint Shop Pro), the editing canvas of an image typically is accompanied by a control displaying a map of colors. Then, using the mouse, the user can set the active foreground color and background color by merely left or right clicking on the control.
The following code demonstrates how easy it is to create such a control using the alpha-blending capabilities of the .NET framework.
Creating the control
Before actually creating the control, let's summarize the functionality of the control:
- display a map of all colors available
- 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
Since we want to keep things simple, the control first creates a color map bitmap of the same size as the control's canvas. Then in the paint event, the control simply draws the bitmap to the service.
Since canvas size and bitmap size are identical, each pixel of the canvas corresponds to a pixel in the bitmap. Selecting a color now simply becomes a matter of looking up the color of the pixel at the coordinates of the click (this is done in the MouseUp
event handler).
The color map
The hardest part is creating the color map bitmap, for what we are actually doing is mapping from a 3-dimensional space to a 2-dimensional space. Eeuhh.. what..you've totally lost me here???
Let me explain on this. Each color is a structure of three values: red (R), green (G) and blue (B), a.k.a. RGB. For 24-bit colors, each value R, G or B can vary from 0..255 (if you haven't figured it out yet, the range 0..255 happens to be 8-bit in size, and since we have 8-bit in red, 8-bit in green and 8-bit in blue, together we have a 24-bit color). Each color can be seen as a coordinate in a 3-dimensional space with axis: red, green and blue. All colors together form a cube of size 255 in the RGB-space. And it is this cube that we want to flatten to a 2-dimensional canvas.
Let's do a quick calculation: imagine we want to display all colors. Using 24-bit, we have 256*256*256 colors. Now if you want do display this on a canvas of width 256 pixels, then the length would become 256*256=65536 pixels. I'm not familiar with your monitor size, but I think it is fair to assume it can't support a length of 65536 pixels. Also, stretching the width of the control won't work here. So displaying all colors is not possible.
So the problem comes down to this: how to display the colors in such a way that the user can select all types of colors, but not all colors.
Again, we are lucky. We don't have to reinvent the wheel. Just look around at other programs (e.g., Paint Shop Pro) on how they solved the problem. Basically, they display a fixed set of six basic colors in overlapping circles on a black and white primary background and have them interact like light, that is, if you mix red and yellow, it becomes orange.
Creating the six basic colors is simple. Three of them can be easily found: red, green, blue (a.k.a. primary colors). The other three are mixed colors and can be assembled using two primary colors.
- red + green = yellow
- green + blue = cyan
- blue + red = magenta
In RGB-values, the primary colors are:
- red = (255, 0, 0)
- green = (0, 255, 0)
- blue = (0, 0, 255)
And the mixed colors are:
- yellow = (255, 255, 0)
- cyan = (0, 255, 255)
- magenta = (255, 0, 255)
Creating a surface of black and white is done using the LinearGradientBrush
. Below is the code example for creating the projection surface:
lbrush = new LinearGradientBrush (ClientRectangle,
Color.FromArgb (255, 0, 0, 0), Color.FromArgb (255, 255, 255, 255), 0f);
g.FillRectangle (lbrush, ClientRectangle);
Creating circles of a basic color that fade out can be done using the PathGradientBrush
, with the CenterColor
set to the basic color and the SurroundColors
to black.
Now to have the circles act like light on the black and white background, we need alpha-channeling. With alpha-channeling, colors drawn to a surface are merged with the existing color on that surface. To switch-on alpha-channeling, set the CompositingMode
of the Graphics
instance g
to SourceOver
before you draw anything!!
g.CompositingMode = CompositingMode.SourceOver;
Also, make sure that every color specified when drawing is a RGB value with an alpha-component specified (a.k.a. ARGB-value). Luckily, the Color
class contains a static
method FromArgb
. Use it with four parameters, the first being the alpha-value and the remaining parameters being the values red, green and blue. Below is a code example to draw the yellow circle. Notice the alpha-values specified.
path = new GraphicsPath ();
path.StartFigure ();
path.AddEllipse (rect);
path.CloseFigure ();
pbrush = new PathGradientBrush (path);
pbrush.CenterColor = Color.FromArgb (255, 255, 0, 0);
pbrush.SurroundColors = new Color[] { Color.FromArgb (0, 0, 0, 0) };
g.FillEllipse (pbrush, rect);
The code above is repeated six times, each time specifying a different basic color and a different location.
Points of Interest
The color map control allows for quick color picking in an easy way. However, finding the same color twice on the map is difficult. For this, a control displaying a palette is more suitable. The drawback: a palette drawn as a grid of colors by definition has fewer colors to select from.
History
- 2004-12-15 - Initial release.
- 2004-12-29 - Updating properties
ForeColor
and BackColor
now triggers the associated color changed events.