Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / tools

Gradient Color Picker

4.77/5 (7 votes)
5 May 2020CPOL5 min read 17.3K   305  
This article presents a tool that provides developers with the ability to pick colors from a linear color gradient.
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

Introduction Table of Contents

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:

covid_19_scale

As the task became onerous, I decided to develop a tool to assist. The result is the Gradient Color Picker.

gradient_color_picker

User Interface Table of Contents

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.

  1. 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).
    step1
  2. 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).
    step3
  3. The user is now presented with options to capture the color values to the clipboard. These include:
    1. The user may choose the color format that will be used for clipboard entries; the default format is RGB Hexadecimal.
    2. 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'
    3. 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'
    4. 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'
  4. If the user clicks Reset, the user returns to Step 1; if the user clicks Exit, the application exits.

Implementation Table of Contents

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 Table of Contents

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:

C#
gradient_PAN.Visible = true;
gradient_PAN.Invalidate ( );

The gradient_PAN has the PAN_OnPaint event handler attached.

C#
// *********************************************** PAN_OnPaint

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 );

    } // PAN_OnPaint

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 Table of Contents

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.

C#
// ******************************************* fill_button_PAN

bool fill_button_PAN ( )
    {
    int     right_most = 0;
    int     spacing = 0;
    int     top = 2;
    Point   UL = new Point ( 0, 0 ) ;

                                // remove existing event
                                // handlers from buttons in
                                // the button_PAN
    foreach ( Control control in button_PAN.Controls )
        {
        if ( control is Button )
            {
            control.Click -= new EventHandler (
                                        gradient_BUT_Click );
            }
        }
                                // remove any existing buttons
                                // from the button_PAN
    button_PAN.Controls.Clear ( );
                                // clear the buttons list
    buttons.Clear ( );
                                // compute initial spacing
                                // between buttons
    spacing = ( gradient_PAN.Size.Width -
                ( BUTTON_WIDTH * number_of_colors ) ) /
              ( number_of_colors - 1 );
                                // create gradient buttons and
                                // add them to buttons list
    for ( int i = 0; ( i < number_of_colors ); i++ )
        {
        Button  button = new Button ( );
        int     left = ( i * ( spacing + BUTTON_WIDTH ) );

                                // want no borders
        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 );
        }
                                // the spacing may not be
                                // large enough to cause the
                                // buttons to completely fill
                                // the button panel to the
                                // right; here we correct the
                                // inter-button spacing;
                                // EPSILON is currently 3
    if ( right_most < ( gradient_PAN.Size.Width - EPSILON ) )
        {
        int pixels = 1;
        int start = 0;
                                // start is expected to be
                                // greater than zero
        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;
            }
        }
                                // set the button BackColor;
                                // copy the button from the
                                // buttons List to the
                                // button_PAN
    for ( int i = 0; ( i < buttons.Count ); i++ )
        {
        Button  button = buttons [ i ];
                                // color the button based upon
                                // its current location in the
                                // buttons panel
        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;

                                // place button in button_PAN
        button_PAN.Controls.Add ( button );
        }

    button_PAN.Visible = true;

    reset_BUT.Visible = true;

    initialize_miscellaneous_controls ( );

    return ( true );

    } // fill_button_PAN

fill_button_PAN performs the following tasks:

  1. 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.
  2. An initial spacing is computed. Note that the computed value may not be the final spacing between buttons due to rounding errors.
  3. 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.
  4. 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.
  5. 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.
  6. 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.

geometry

The point must be converted to screen coordinates before the Win32 get_pixel_color_at_location method is invoked.

C#
// *************************************** generate_back_color

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 );

    } // generate_back_color

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:

C#
// **************************************************** BitBlt

[ 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 );
:
:
// ******************************* get_pixel_color_at_location

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 );
    }

} // class Win32API

References Table of Contents

Conclusion Table of Contents

This article has presented a tool that provides developers with the ability to pick colors from a linear color gradient.

Development Environment Table of Contents

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 Table of Contents

05/05/2020 Original article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)