I wanted a tool that would provide me with a list of colors that were derived from a linear color gradient. This article looks at how the tool works, focusing on two key components.
Table of Contents
The symbol returns the reader to the top of the Table of Contents.
Introduction
I'm not sure how many times I've needed to generate an array of colors that is a linear gradient of two or more colors. When building the animation graphics for the COVID-19 portion of my website, I needed to generate a number of scales. For example:
As the task became onerous, I decided to develop a tool to assist. The result is the Gradient Color Picker.
User Interface
From Wikipedia [^]:
An axial color gradient (sometimes also called a linear color gradient) is specified by two points, and a color at each point. The colors along the line through those points are calculated using linear interpolation.
I wanted a tool that would provide me with a list of colors that were derived from a linear color gradient. The list of colors was to be placed in the clipboard. The tool needed to provide the following functionality.
- Accept two colors, and fill a panel (gradient_PAN) with a linear color gradient; accept the number of colors to extract from the linear color gradient. The minimum number of colors is 3 (there is no need to use a tool for less than three colors); the maximum number of colors is 20 (limited by the size of the tool's client area).
- When the user clicks Generate, the tool draws the colored buttons that are colored the same as the color of the associated linear gradient (i.e., the color in the linear gradient directly above the button).
- The user is now presented with options to capture the color values to the clipboard. These include:
- The user may choose the color format that will be used for clipboard entries; the default format is RGB Hexadecimal.
- By clicking on any individual color button, the color of that button is copied to the clipboard. The two end buttons have the colors of the originally specified colors. If the sixth colored button, in the example is clicked, the value copied to the clipboard is:
'#FF6666' - By clicking on Copy Left to Right, the clipboard is filled with a comma-separated list of the colors of all colored buttons, from left to right. For the example, the values copied to the clipboard are:
'#FFBBBB','#FFA7A7','#FF9797','#FF8787','#FF7777','#FF6666',
'#FF5656','#FF4545','#FF3535','#FF2525','#FF1414','#FF0000' - By clicking on Copy Right to Left, the clipboard is filled with a comma-separated list of the colors of all colored buttons, from right to left. For the example, the values copied to the clipboard are:
'#FF0000','#FF1414','#FF2525','#FF3535','#FF4545','#FF5656',
'#FF6666','#FF7777','#FF8787','#FF9797','#FFA7A7','#FFBBBB'
- If the user clicks Reset, the user returns to Step 1; if the user clicks Exit, the application exits.
Implementation
As with any event-driven application, the initialization is mostly spent in establishing the graphical user interface. In the case of the Gradient Color Picker, there are really only two components of interest: the gradient_PAN and the button_PAN panels.
gradient_PAN
When either the start or end color is chosen, the fill_gradient_PAN method is invoked. This method insures that both the start and end colors buttons have been accessed and that the start and end colors have been chosen. If so, the method executes the following:
gradient_PAN.Visible = true;
gradient_PAN.Invalidate ( );
The gradient_PAN has the PAN_OnPaint event handler attached.
void PAN_OnPaint ( object sender,
PaintEventArgs e )
{
base.OnPaint ( e );
e.Graphics.FillRectangle (
new LinearGradientBrush (
gradient_PAN.ClientRectangle,
start_color,
end_color,
0.0F ),
gradient_PAN.ClientRectangle );
}
The PAN_OnPaint event hander is very simple. All it does is create a LinearGradientBrush [^] and use it to fill the gradient_PAN client rectangle.
button_PAN
When the Generate button is clicked, the balance of the tool's GUI is rendered. In most cases this entails making various objects visible. However, generating the button_PAN panel is somewhat more complex.
bool fill_button_PAN ( )
{
int right_most = 0;
int spacing = 0;
int top = 2;
Point UL = new Point ( 0, 0 ) ;
foreach ( Control control in button_PAN.Controls )
{
if ( control is Button )
{
control.Click -= new EventHandler (
gradient_BUT_Click );
}
}
button_PAN.Controls.Clear ( );
buttons.Clear ( );
spacing = ( gradient_PAN.Size.Width -
( BUTTON_WIDTH * number_of_colors ) ) /
( number_of_colors - 1 );
for ( int i = 0; ( i < number_of_colors ); i++ )
{
Button button = new Button ( );
int left = ( i * ( spacing + BUTTON_WIDTH ) );
button.FlatStyle = FlatStyle.Popup;
button.Location = new Point ( left, top );
button.Size = BUTTON_SIZE;
button.Click += new EventHandler (
gradient_BUT_Click );
right_most = button.Location.X + button.Size.Width;
buttons.Add ( button );
}
if ( right_most < ( gradient_PAN.Size.Width - EPSILON ) )
{
int pixels = 1;
int start = 0;
start = buttons.Count -
( gradient_PAN.Size.Width - right_most );
for ( int i = start; ( i < buttons.Count ); i++ )
{
Point location = buttons [ i ].Location;
location.X += pixels++;
buttons [ i ].Location = location;
}
}
for ( int i = 0; ( i < buttons.Count ); i++ )
{
Button button = buttons [ i ];
if ( i == 0 )
{
button.BackColor = start_color;
}
else if ( i == ( number_of_colors - 1 ) )
{
button.BackColor = end_color;
}
else
{
generate_back_color ( ref button );
}
button.UseVisualStyleBackColor = false;
button_PAN.Controls.Add ( button );
}
button_PAN.Visible = true;
reset_BUT.Visible = true;
initialize_miscellaneous_controls ( );
return ( true );
}
fill_button_PAN performs the following tasks:
- Because the application may be executed multiple times, it is necessary to remove all event handlers attached to each button. Additionally, all buttons are removed from the button_PAN and all buttons are removed from the buttons list.
- An initial spacing is computed. Note that the computed value may not be the final spacing between buttons due to rounding errors.
- The buttons are created and spaced along the button_PAN panel, keeping track of the position of the right side of the right-most button.
- The spacing between buttons is corrected so that the buttons are equally spaced along the button_PAN. This step is required to insure that the button color accurately depicts the color in the gradient_PAN.
- Up to this point, the button data has been stored in the buttons list. Now the buttons are placed into the button_PAN. Because the final position is known, the generate_back_color method is invoked to finally assign a BackColor to each button.
- Finally, the balance of the controls is made visible.
The generate_back_color method first computes the point at the vertical center of the gradient_PAN, centered horizontally on the button in the button_PAN. This point is defined in the tool's client rectangle coordinates.
The point must be converted to screen coordinates before the Win32 get_pixel_color_at_location method is invoked.
bool generate_back_color ( ref Button button )
{
Point point;
Point screen_point;
point = new Point (
( ( button_PAN.Location.X +
button.Location.X ) +
( button.Size.Width / 2 ) ),
( gradient_PAN.Location.Y +
( gradient_PAN.Size.Height / 2 ) ) );
screen_point = PointToScreen ( point );
button.BackColor =
Win32API.get_pixel_color_at_location ( screen_point );
return ( true );
}
The Win32 get_pixel_color_at_location method uses GetPixel against a one by one pixel Bitmap. The pixel located at the specified location is copied (BitBlt [^]) into the screen_pixel Bitmap. This results in four benefits: the method will not raise an exception when used in a multi-monitor environment; it is faster than GetPixel; the Bitmap used is only one pixel high and wide; and the Bitmap is local to this method. get_pixel_color_at_location method takes the following form:
[ DllImport ( "gdi32.dll",
EntryPoint = "BitBlt" ) ] public static extern bool BitBlt ( IntPtr hdcDest,
int nXDest,
int nYDest,
int nWidth,
int nHeight,
IntPtr hdcSrc,
int nXSrc,
int nYSrc,
int dwRop );
:
:
public static Color get_pixel_color_at_location (
Point screen_location )
{
Color color;
Bitmap screen_pixel = new Bitmap (
1,
1,
PixelFormat.Format32bppArgb );
using ( Graphics destination = Graphics.FromImage (
screen_pixel ) )
{
using ( Graphics source = Graphics.FromHwnd (
IntPtr.Zero ) )
{
IntPtr source_DC = source.GetHdc ( );
IntPtr destination_DC = destination.GetHdc ( );
BitBlt ( destination_DC,
0,
0,
1,
1,
source_DC,
screen_location.X,
screen_location.Y,
( int ) CopyPixelOperation.SourceCopy );
}
}
color = screen_pixel.GetPixel ( 0, 0 );
screen_pixel.Dispose ( );
return ( color );
}
}
References
Conclusion
This article has presented a tool that provides developers with the ability to pick colors from a linear color gradient.
Development Environment
The Gradient Color Picker was developed in the following environment:
Microsoft Windows 7 Professional SP 1 |
Microsoft Visual Studio 2008 Professional SP1 |
Microsoft Visual C# 2008 |
Microsoft .Net Framework Version 3.5 SP1 |
History
05/05/2020 | Original article |