Introduction

There are already several articles here on CodeProject describing different types of color pickers, and here is yet another example.

I needed a color picker for a project I was working on, and I wanted something different from the standard ColorDialog. After looking at a couple of different color pickers, I decided to go ahead and implement my own version which would meet these few requirements:

Color table with some predefined colors

Color picker (eye dropper) which lets you pick a color from anywhere on the screen

Color wheel for selecting any arbitrary color

Opacity slider to adjust the opacity of the color

Color control should appear as a combobox with the color picker in the dropdown container

Color Wheel – HSL Color

The color in the color wheel is represented by its hue and saturation value, where hue is the angle (0-360) with red being 0°, green 120° and blue 240°, and where the saturation is the distance from the center, where 0 (center) is white and 1 (on the circumference) is the fully saturated color. The Color class already implements a GetHue, Saturation and Lightness (Brightness), but unfortunately does not implement the Set method, so I needed a conversion from a RGB color to an HSL color.

After a bit of online searching, I came across this article in Wikipedia which explains the HSL color space and most conveniently also includes the equations for converting between RGB - HSL and vice versa.

I created the HSLColor class for wrapping the RGB-HSL, and with the help of Wikipedia, it was just a simple matter of typing in the formula.

The ColorWheel class is the actual color wheel control itself. The drawing of the wheel is done using PathGradientBrush. This brush lets you specify a series of points with corresponding colors and a center point with the corresponding color. The points and colors are calculated in RecalcWheelPoints() which is called when the control is resized. Since the color in the color wheel is represented by the angle (Hue), it is a matter of looping from 0 to 360 in ‘degrees’ steps. I found that I cannot tell the difference between 1° and 5° steps, so I used 5° steps.

In this loop, the {x,y} coordinates are calculated, and the color is calculated by creating an HSL color and getting the Color property out of it.

Get and Set the Color Selector

Calculating the position for the color selector is done in ColorSelectorRectangle. The angle at which to place the selector is given by the Hue and the distance from the center is given by the Saturation. A saturation of 0 means the selected color is at the center of the circle, and a saturation of 1 means the selector's distance from the center is equal to the radius of the circle.

Calculating the selected color from the mouse position when the selector is moved around the wheel is done in SetColor(PointF mousepoint). Here the Hue and Saturation is calculated based on the mouse position and the SelectedHSLColor is then updated causing part of the wheel to be redrawn.

Lightness / Opacity Slider

For the lightness slider, I first had to write a label class which lets you specify the angle at which the text is drawn. This is done with the LabelRotate class. In this class, if the angle of the text is 0, the regular formatting is used for positioning the text, but if any angle is used then an extra alignment property is used called RotatePointAlignment. Once angled, the GDI DrawString is drawn at point (0,0), the ‘rotating point’ is aligned according to the RotatePointAlignment setting and rotating is done by setting TranslateTransform and RotateTransform.

The next step in creating the slider was to create a color bar which would show a gradient color going from black to the selected color and into white. The GradientBrush allows you to fill a rectangle with a starting and an ending color and the brush then fills the gradient color between. For this bar I needed three colors, and the only way (I could think of) to do this is to use two brushes and fill half the area using a color1-color2 brush and the other half with a color2-color3 brush which is done in Draw3ColorBar utility method.

The base class slider control is ColorSlider, this class lets you specify if you are using two or three colors, the orientation of the slider and the orientation of the value, if the value goes from 0-1 or 0-1. The lightness slider is a derived class HSLColorSlider that includes the selected HSLColor.

To get and set the value of the slider control use the Percent property, which (just for confusion) takes a value between 0-1, not 0-100 as you might expect (there is something to fix later).

Eye Dropper

The eye dropper control EyedropColorPicker lets you pick a color off the screen. When you click in the eye dropper area, the cursor changes to a cross and the image in the eye dropper control is 15x15 pixel rectangle magnified 4 times.

To get the color off the screen is very simple. A bitmap with the size of the source rectangle (15x15) is created and then the screen is copied into this bitmap, this is done in GetSnapshot.

The location of the ‘pixel indicator’ shown at the center of the image while capturing had to be fine tuned by one pixel to show at the right location, so if the control has any other size, the pixel indicator might be off.

I did run into one issue while writing this. By default the GDI DrawImage tries to smooth out the color transition between pixels. I found the answer in this article where it is mentioned that the property to set to disable the smoothing is the InterpolationMode on the graphics.

Color Table

The color table class ColorTable lets you set the number of columns and the colors you want to show.

I chose to have 16 columns and in the first row, show 10 gray scale colors ranging from black to white and 6 base colors and then 48 colors (3 rows) spanning from hue 0 to 360 at saturation 1 and another 3 rows with saturation 0.5.

The last color in the table is the custom color which is added and changed whenever the color is changed in either the color wheel or through the eye dropper.

Putting It All Together

The actual color picker control, the class that ties these controls together is the ColorPickerCtrl class. This class gets notified when the value / color changes in any of the controls and it then updates the other controls. It exposes only one property, namely:

public Color SelectedColor

ComboBox with DropdownContainer

I wanted the color picker control as the ‘dropdown’ part of the combo box instead of a modal dialog. I found a strange behavior when trying to use a derived ComboBox class, it would always show an empty one line dropdown before showing the custom control, so I ended up creating a class derived from Control instead.

The base class for this control is DropdownContainerControl<T> and the dropdown container class DropdownContainer<T>is the container for the custom control shown when dropped down.

For drawing the control I use ComboBoxRenderer.DrawDropDownButton in the base class while the derived class is responsible for drawing the item by overwriting DrawItem.

On a normal combobox, the dropdown control is closed without saving the selection whenever the user clicks anywhere outside the control. To achieve this, I override OnDeactive and call Cancel which will hide the dropdown without saving.

To capture the keyboard Esc and Enter was a little more complicated. For this, I needed to install a hook (done in Hook). I found this article which explains how to set up hook in C#, however I couldn't get it to work right as it would either throw exceptions or not install the hook (returning 0). The answer was found in this article, where it is explained that the hook will only be installed if the ‘host process’ is disabled, and once disabled, everything worked as expected.

Enhancements

There are a couple of enhancements I can think of which I might add later on.

Drawing performance. In most cases I call Refresh instead of Invalidate to get the immediate response to a mouse move. Ideally you would call Invalidate on the dirty area only, but I found Invalidate to give a sluggish performance since the update is delayed slightly. To improve the performance, it would be better to do an immediate redraw but of the dirty area only.

Add slider and edit control for RGB and HSL values.

Eyedropper – set sample size. Currently only the center pixel is used to select the color. It might be helpful to take the average of for instance a 2x2, 3x3 or 4x4 rectangle.

Comments and Discussions

I imported the source files of the color picker into my project, and also the three bitmaps, and for each bitmap I set the build action to "Embedded resource" to match the ColorPickerTest project. However, after building the project successfully, I can't put a ColorPickerCombobox onto my form. The error is "System.ArgumentException: Resource 'Resources.eyedropper.bmp' cannot be found in class 'ColorPicker.EyedropColorPicker'."

It seems that the resource name of a bitmap in the project is related somehow to the "Default namespace" in the project settings. I found that by changing my project's default namespace to "ColorPicker", the code that loads the bitmaps started to work, apparently because that code is also located in namespace "ColorPicker". I'm not sure how to get the bitmaps to load if the default namespace is not "ColorPicker" but that's probably OK for me, as I'm making a quick-n-dirty project.

Color picker looks very attractive and capable.
However, when trying to run the posted exe demo, it crashed with
"System.InvalidOperationException: Visual Styles-related operation resulted in an error because no visual style is currently active."

It's because of the ComboBoxRenderer class. It throws an excpetion when the visual styles in the OS are disabled. You check the ComboBoxRenderer.IsSupported property to see if you can use it. An alternative is the ControlPaint class. But it can replace only CBR.DrawDropDownButton(..). CP doesn't have an alternative for CBR.DrawTextBox(..).

I saw the same problem. During a release Ctrl-F5 run, the exe hangs. I added tracing I found it hangs at the m_colors.Sort(CompareColorByBrightness) line. Tracing shows it keeps repeating comparing the same 11 colors (SteelBlue to Goldenrod)

Anyone reading this please note the above fix is:
1) applied in the ColorTable.cs file
2) private static isn't on the original method declaration but it doesn't hurt to add it
3) also this fixes not being able to open this control in the designer

first I like to say that your Control looks very nice, specially the Eyedropper is a really great function!
One thing I came across when testing the demo application is, that the popup window is located on the main screen for me.
I tested a little bit more, and it seems that there is check on negative values.
It works correct if second screen is right handed or beneath the main screen, but it doesn't work if second screen is on the left side or above the main screen.
Maybe you can fix this.

Hi! Nice control, it looks like what I really wanted. However, I add one of your controls to my program, then when I put the other control on, the designer crashes, or I just get a 'cursor timer' displayed meaning I have to wait.

I'm trying to do this on Vista. Any help will greatly be appreciated!! Thanks again!