Introduction

This is an article about making color pickers. It also includes a fully prepared color picker control and dialog that closely resembles Adobe's. In order to make a good color picker, two things are needed. First, one needs to be able to generate dynamic bitmaps quickly. It was my coming upon a way to do this quickly that inspired me to produce color pickers in the first place. Second, a color picker should be easily modifiable. Different applications are bound to want different variants. In order to do this, a modular approach was taken with the connection between the components occurring in XAML.

Background and Credit

This is not the first color picker made for WPF, and almost certainly will not be the last. The following other color pickers may be of interest:

The methods used to manipulate bitmaps were directly inspired by this article: High performance WPF 3D Chart. While it may not be one of the easier articles on CodeProject to read, it is one that one can learn a lot from. Finally, some of the color conversion routines from Manipulating colors in .NET - Part 1 were used as the basis for generating some of the bitmaps. Credit should also be given to the fine people at Adobe who inspired much of the look and feel of this project.

Using the Code

Although explanation of the code will follow, understanding of the intricacies of the code is not necessary. One can use the color pickers through dialogs, picker controls, or color chips. Four different color picker dialogs are included with this article: ColorPickerStandardDialog, ColorPickerStandardWithAlphaDialog, ColorPickerFullDialog, and ColorPickerFullWithAlphaDialog.

Although the dialogs differ in their showing lab color and having alpha selection, the code to use them is effectively the same:

After the DLLs are referenced, that is all that is necessary in order to use the color picker as a dialog.

The next method of using the color picker is through the color picker controls themselves. They look just like the dialogs minus the OK and Cancel buttons, and are named: ColorPickerStandard, ColorPickerStandardWithAlpha, ColorPickerFull, and ColorPickerFullWithAlpha. They have InitialColor and SelectedColor dependency properties, so they can be used with databinding. There is also a SelectedColorChangedEvent to be used if you prefer working with events. As all of the color pickers are a bit on the large side, one will probably put them in something like an expander. Only a few lines of code are needed to work with such a system.

If even this is too much coding, one can use color chips instead and rely 100% on XAML. Both a basic ColorChip control and a fancier ForegroundBackgroundChip control are included with this article.

These can be used entirely through XAML, and have a built-in checkerboard, so transparent and light colors can be distinguished. It is also possible to pick the dialog to be used through the properties window. I hope that I have convinced you that these color pickers can be easily used to enhance your WPF applications.

Generating Bitmaps

This application uses WriteableBitmaps to display the gradients used to pick the colors. WriteableBitmaps contain two buffers for pixel information, a front buffer and a BackBuffer. The WriteableBitmaps are declared in the ColorSelector class as follows:

Both WriteableBitmaps use PixelFormats.Bgr24. Their buffers are of the form, a byte for blue, a byte for green, a byte for red, a byte for blue in the next pixel to the right (unless at the end of a row), and so on until the end of the buffer. The other thing to note is the use of the word Normal. It is being used in its mathematical sense as being perpendicular rather than in its common sense of usual or customary. For example, in the usual way of describing the RGB color space, the red, green, and blue values are though of as perpendicular vectors. In terms of the color picker, the selected radio button and the slider both correspond to the same NormalComponent, which contains the rendering instructions.

All of the bitmap updating operations follow the same set of steps:

An unsafe code block is created in which to process the update.

The WriteableBitmap is locked.

A Pointer to the beginning of the BackBuffer is retrieved.

The pixels are looped through, and blue, green, and red bytes are written to the BackBuffer. The order of the bytes is determined by the PixelFormats value in the WriteableBitmap constructor.

The pixels are written back through the AddDirtyRect method.

The bitmap is unlocked.

The unsafe code block is exited. </em />

The code for updating selectionPlane when using the Red Normal component is as follows:

For all of the color models other than RGB, this routine can become quite involved as the calculation of the red, green, and blue bytes is more complex. Also, this loop is evaluated once for each pixel. For a 256x256 image, that is 65536 times. It is for that reason that the calculation of the byte values is all done inline within UpdateColorPlaneBitmap. Normally, this would be very bad style, but when something is done 65536 times, even small things make a difference. This can be seen when dragging the scrollbar while using the Lab components.

Concerning HSB

It is easy to get HSB (Hue, Saturation, Brightness), HSV (Hue, Saturation, Value), and HSL (Hue, Saturation, Lightness) mixed up as there is a general lack of consistency as to their use. According to current parlance, HSB and HSV are the same thing, while HSL is different. In particular, the System.Drawing.Color structure's GetBrightness() and GetSaturation() methods refer to what is generally considered the Lightness and Saturation of the HSL model rather than the HSB model. This is in contrast to the color picker in Photoshop where the HSB corresponds to what is currently considered HSB. Wikipedia provides a good explanation of this in HSL and HSV.

Concerning CMYK

CMYK (Cyan, Yellow, Magenta, Key) is different from the other color spaces used in the picker in that there are four different color components. As one might suspect, there are many different conversions from RGB to CMYK that are reversed by the same transformation from CMYK to RGB. Discovering the formula that Photoshop uses is a nontrivial task. Unfortunately, I was unsatisfied with the CMYK conversion that can be easily found on the web. It has the quality of always making at least one of C, M, or Y equal to 0. To deal with this, a new parameter was added to the transformation called KGreedieness with a default value of .7. If KGreedieness is set to 0, then you have the conversion from RGB to CMY. If the value of KGreedieness equals 1, you have the commonly seen conversion to CMYK. KGreedieness is intended to lie between 0 and 1.

Concerning Lab

Lab (Lightness, a, b) is a significantly more complex color space than RGB. I used a modification of the code from Manipulating colors in .NET - Part 1. Unfortunately, it does not match the results in Photoshop exactly. It should be noted that the process is not reversible in the Photoshop color picker. If one selects a color and makes a note of the RGB and Lab values, and then changes the color, and then sets the Lab values one noted, one does not necessarily get back the RGB values. (This works with HSB, but fails with CMYK.) On account of this, it would take a significant mathematical derivation to determine the optimal Lab conversion, which I freely admit to not having done yet.

Color Picker Architecture

The first thing to notice is that there are two separate assemblies: ColorPicker and ColorPickerControls. These assemblies differ not just in the types contained, but in their design philosophies. The ColorPickerControls assembly is designed for ease of use for the developer using the types in his own project. The types in ColorPicker assembly were designed to be modular components that can be used in the construction of color pickers. For example, a color picker could be made up of controls as follows:

The various controls in the color picker are bound together by Dependency Properties. For example, the RgbDisplay control binds with the following expressions:

ColorSelector

The ColorSelector control is the central control in that it is what allows one to visually select colors. ColorSelectorControl has code to handle the setting of colors via its Color property as well as actions via the mouse. It does not know anything about color spaces or how to draw gradients. In order to easily support multiple color models, all of the color model information is stored in the NormalComponent property of type NormalComponent.

For the RGB color space, there are R, G, and B NormalComponent types. The HSB and Lab color spaces are similarly equipped, while the CMYK space has only color components as it does not participate in the color gradients.

The Color Space Display Controls

For each color space, there is a corresponding display control: RgbDisplay, HsbDisplay, LabDisplay, and CmykDisplay. These controls display the appropriate component values for their Color property. They also serve as the source for the NormalComponent objects used by the ColorSelector. The NormalComponent property is intended to be bound in a two way fashion in order for the individual small groups of radio buttons to act as one large group of radio buttons.

Conclusion

Hopefully this color picker will be of some use to all of you out there. I will discover how difficult porting it to Silverlight is soon. Unfortunately, that task can turn out to be easy or extremely hard.

Updates

12/20/2010 - In order to make the sliders for normal color and transparency easier to use, the areas next to the sliders for normal color and transparency have been changed to allow for clicking as well as dragging. Also, the triangles were bolstered by an invisible rectangle to make them easier to use.

12/26/2010 - As requested by Aybe, the selection circle has been modified for improved visibility. It is now governed by the SelectionRingMode property. It is an enumeration with the following values:

White - A single white circle (the previous situation)

Black - A single black circle

BlackAndWhite: Two concentric circles, the inner one being white and the outer one black (this is the default)

BlackOrWhite: A single circle that is either white or black depending on the selected color

Also, I like the control, but it seems like a lot of extra bloat for just a color picker.. I'm not sure I want to throw in one or two extra libraries that include 100 code files just to get a color picker.. Anyway.. I'm not putting the controls down, they are very nice and the components are great.

One last comment, it doesn't seem possible with these components to get a basic type of color picker. I was looking for HSV (ColorSelector) with Synchronized RGB components. However, I do not want the radio buttons on the RGB which select which color is the "normal" because I find this to be very confusing. (I only wanted the ColorSelector to adjust HSV not RGB). I'd have to make some modifications to remove the radio buttons. Of course, I shouldn't complain too much, I did get all the code for free . thanks.

Thanks for you input, it is appreciated. I have made some code changes that should make you happy but as the article is now "Edited" so the code project people will need to put up changes.

I must confess that I was aware of both situations. Because I was unable to think up a particularly elegant solution to either of them I allowed my inate laziness to guide me. Having users to keep one on one's toes is great.

All of the pickers and chips have been set to allow one to select the initial Normal component (The default is HSB hue to match PhotoShop). This is done via one of two enums as I wanted this selection to be really easy on the developer using it. It is less than elegant in the case of the color chips as it uses the larger one and ignores inappropriate values for the Normal. I would have liked it to be only possible to pick valid values, but I cannot figure a reasonable way to do this.

As to the error in the stickyness of the radio buttons, I have not been able to ascertain its root cause. I am able to test for the situation where it occurs so in those situations I make sure it is ok. Not elegant but it works.

Hello, first I want to thank you for the amazing work! It's been most helpful.

For the Hue radio button not displaying the checked state properly (at least when you click it the first time), I did the following change which seems to have sorted it out but I'm not sure if it is breaking something else:

In ColorPicker.ColorModels.HSB.Hue, I've changed this:

publicoverridestring Name
{
get { return"HSB_Blue"; }
}

into this:

publicoverridestring Name
{
get { return"HSB_Hue"; }
}

Seems to be working but again no idea if it's breaking other things as I'm only a VB newbie at best

For the default component, I'd like to change the default component of the Full dialog when using the Color Chip (right now it seems to be R, but I want it to be set as Hue) but I don't know how. I tried downloading the source again perhaps the changes you mentioned apply to a newer version but it's the same case --the one I have has been last modified Dec. 26th 2010.

Thank you again for your work, keep it up!

ps. I tried posting this last night but it didn't appear to work so apologies if this is a duplicate.