65.9K
CodeProject 正在变化。 阅读更多。
Home

渐变颜色选择器——再次修订

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2021年6月14日

CPOL

6分钟阅读

viewsIcon

13030

downloadIcon

341

本文修订了早期版本的渐变色拾取器(V2)。修订的动力来自读者要求增加初始颜色选择的数量。

引言 目录

本文修订了早期版本的 渐变色拾取器 (V2) [^]。修订的动力来自读者要求增加初始颜色选择的数量。

gradient_color_picker

实现了两项更改

  • 增加了起始/结束按钮的数量。
  • 提供了插值作为 Microsoft LinearGradientBrush [^] 的替代方案。由此产生的两种算法分别命名为线性渐变插值

实现 目录

在可能的情况下,对实现所需更改的修改旨在最大程度地减少对现有软件的影响。然而,在此过程中检测并消除了许多低效率。

增加了起始/结束按钮的数量 目录

首次运行时,GradientColorPickerV3 显示如下

initial gradient color picker

除其他外,它会调用show_initial_GUI 来创建初始用户界面 (UI)。用户通过在起始/结束按钮的 NumericUpDown (NUD) 控件中提供所需数量来指定按钮的数量。number_start_end_buttons 记录 NUD 的值。每次 NUD 值更改时,显示都会更新以显示新数量的未着色按钮。

show_initial_GUI 调用 fill_button_panel

当两个按钮面板(start_end_buttons_PANselector_buttons_PAN)中的任何一个需要重绘时,都会调用 fill_button_panel。(请注意,类常量和变量已完整呈现。

        // ******************************************* class constants

        const   int             BUTTON_HEIGHT = 30;
        const   int             BUTTON_WIDTH = BUTTON_HEIGHT;
                Size            BUTTON_SIZE = 
                                    new Size ( BUTTON_WIDTH, 
                                               BUTTON_HEIGHT );

        const   int             EPSILON = 3;

        const   int             DEFAULT_NUMBER_START_END_BUTTONS = 6;

        // ******************************************* class variables

        int                     number_start_end_buttons = 
                                    DEFAULT_NUMBER_START_END_BUTTONS;
        List < Button >         start_end_color_buttons = 
                                    new List < Button > ( );

        // ****************************************** show_initial_GUI

        bool show_initial_GUI ( )
            {

            start_end_buttons_LAB.Visible = true;
            start_end_buttons_NUD.Visible = true;

            error_message_RTB.Clear ( );
            error_message_RTB.Visible = true;

            start_end_buttons_PAN.Visible = true;

            fill_button_panel ( number_start_end_buttons,
                                start_end_buttons_PAN,
                                start_end_color_buttons,
                                start_end_button_Click );

            return ( true );

            } // show_initial_GUI

        // ***************************************** fill_button_panel

        bool fill_button_panel ( int               number_buttons,
                                 Panel             button_panel,
                                 List < Button >   buttons,
                                 EventHandler      e )
            {
            int     available_space = 0;
            Point   location = new Point ( );
            int     right_most = 0;
            int     spacing = 0;

            button_panel.Controls.Clear ( );

            foreach ( Button button in buttons )
                {
                button.Click -= e;
                }
            buttons.Clear ( );

            location.Y = 1;

            available_space = button_panel.Width - 
                              ( number_buttons * BUTTON_WIDTH );
            spacing = ( int ) ( ( float ) available_space /
                                ( float ) ( number_buttons - 1 ) );

            for ( int i = 0; ( i < number_buttons ); i++ )
                {
                Button  button = new Button ( );

                location.X = ( i * spacing ) + ( i * BUTTON_WIDTH );
                button.BackColor = SystemColors.Control;
                button.Location = location;
                button.Size = BUTTON_SIZE;
                button.Tag = i;
                button.Click += e;
                right_most = button.Location.X + button.Size.Width;
                button.Visible = true;

                buttons.Add ( button );

                button_panel.Controls.Add ( button );
                }

            if ( right_most < ( button_panel.Size.Width - EPSILON ) )
                {
                int pixels = 1;
                int start = 0;
                                        // start is expected to be 
                                        // greater than zero
                start = buttons.Count - 
                        ( button_panel.Size.Width - right_most );

                for ( int i = start; 
                        ( i < buttons.Count ); 
                          i++ )
                    {
                    location = buttons [ i ].Location;
                    location.X += pixels++;
                    buttons [ i ].Location = location;
                    }
                }

            button_panel.Visible = true;

            return ( true );

            } // fill_button_panel

将起始/结束按钮的数量从两个增加到最多六个,需要先在面板中生成渐变,然后将这些面板连接起来形成一个单一的渐变面板。

gradient panels

插值和线性渐变模式都使用的面板由 create_panels 创建。(请注意,前面已呈现的类常量和变量不再重复。

        // ******************************************* class variables

        List < int >            panels_end = new List < int > ( );
        List < int >            panels_start = new List < int > ( );
        int                     panel_width = 0;

        // ********************************************* create_panels

        bool create_panels ( ref List < Panel >  panels )
            {
            Size    size = new Size ( 0, BUTTON_HEIGHT );

            panels.Clear ( );
            panels_end.Clear ( );
            panels_start.Clear ( );

            size.Width = start_end_color_buttons [ 1 ].Location.X -
                         start_end_color_buttons [ 0 ].Location.X;
            panel_width = size.Width;
            
            for ( int i = 0; 
                    ( i < ( start_end_color_buttons.Count - 1 ) ); 
                      i++ )
                {
                Point  location = new Point ( 0, 0 );
                Panel  panel = new Panel ( );

                location.X = 
                    start_end_color_buttons [ i ].Location.X;
                panel.Location = location;

                panel.Size = size;
                
                panels_start.Add ( panel.Location.X );
                panels_end.Add ( panel.Location.X + 
                                 panel.Size.Width );

                                        // BackColor = leftmost color
                panel.BackColor = 
                    start_end_color_buttons [ i ].BackColor;
                                        // ForeColor = rightmost color
                panel.ForeColor = 
                    start_end_color_buttons [ i + 1 ].BackColor;
                
                panel.Tag = i;

                panels.Add ( panel );
                }

            return ( true );

            } // create_panels

create_panels 的目标是一个 Panel 控件的 List。所有面板的宽度相同,面板数量等于(起始/结束 - 1)按钮的数量。

根据之前收集的 start_end_color_buttons 的属性,计算所有面板的大小。定义一个面板后,将其添加到目标 Panel 控件的 List 中。为将来参考,每个面板的渐变起始颜色和结束颜色分别记录在每个面板的BackcolorForeColor属性中。

渐变面板的着色 目录

到目前为止,插值和线性渐变算法是相同的。两者都使用面板来定义gradient_PAN 的几何形状。分歧发生在display_gradient_BUT_Click 事件处理程序的响应中。display_gradient_BUT_Click 事件处理程序调用 create_gradient_PAN。此方法了解插值和线性渐变模式

        // *************************************** create_gradient_PAN

        bool create_gradient_PAN ( )
            {
            List < Panel >  panels = new List < Panel > ( );

            gradient_PAN.Visible = false;
            gradient_PAN.Controls.Clear ( );

            create_panels ( ref panels );

            foreach ( Panel panel in panels )
                {
                if ( use_interpolation )
                    {
                    panel.Paint += 
                        new PaintEventHandler ( 
                            interpolated_gradient_panel_OnPaint );
                    }
                else 
                    {
                    panel.Paint += 
                        new PaintEventHandler ( 
                            linear_gradient_OnPaint );
                    }

                panel.Invalidate ( );
                gradient_PAN.Controls.Add ( panel );
                }

            gradient_PAN.Visible = true;

            return ( true );

            } // create_gradient_PAN

create_gradient_PAN 方法首先隐藏 gradient_PAN 并清除其现有内容。接下来,它调用 create_panels 来获取将着色以形成渐变的 Panel 控件的 List。通过迭代 create_panels 返回的 Panel 控件的 List,该方法为插值和线性渐变模式声明不同的 PaintEventHandler [^]。然后,通过调用 Invalidate [^],它会触发面板的 Paint 事件。当面板绘制完成后,create_gradient_PAN 将该面板附加到 gradient_PAN,创建一个将包含所需颜色混合的容器。

应用插值颜色 目录

interpolated_gradient_panel_OnPaint 是在插值模式下为渐变面板着色的处理程序。在此方法中,将创建一个颜色列表(colors),其中包含面板内颜色的列表。当面板着色后,colors 将被附加到 panel_colors,以便在稍后着色选择器按钮时能够快速访问颜色。panel_colors 仅在插值模式下使用

        // ******************************************* class variables

        List < List < Color > > panel_colors = 
                                    new List < List < Color > > ( );

        // *********************** interpolated_gradient_panel_OnPaint

        void interpolated_gradient_panel_OnPaint ( 
                                                object         sender, 
                                                PaintEventArgs e )
            {
            Color           end_color;
            List < Color >  colors = new List < Color > ( );
            int             height = 0;
            Panel           panel = ( Panel ) sender;
            int             position = 0;
            Color           start_color;

            base.OnPaint ( e );

            end_color = panel.ForeColor;
            height = panel.Size.Height;
            start_color = panel.BackColor;

            foreach ( Color color in interpolated_color_values ( 
                                                start_color,
                                                end_color,
                                                panel.Size.Width ) )
                { 
                e.Graphics.DrawLine ( new Pen ( color, 1.0F ), 
                                      new Point ( position, 0), 
                                      new Point ( position, height) );
                colors.Add ( color );
                position++;
                }
            
            panel_colors.Add ( colors );

            } // interpolated_gradient_panel_OnPaint

插值本身需要重新思考生成颜色并将其放置在面板中的算法。最终算法为面板中要出现的每种解释颜色绘制一条一像素宽的线。

在下面的示例中,线条的宽度为五像素,中间有一个一像素的间隔。

pixel gradient colors

当然,在应用程序中,一像素的间隔是不存在的,允许线条形成平滑、连续的渐变。

smoothpedixel gradient colors

interpolated_color_values 是 Bill Woodruff 建议的一个 迭代器方法 [^]。

        // ********************************* interpolated_color_values

        IEnumerable < Color > interpolated_color_values ( 
                                                Color   start_color, 
                                                Color   end_color, 
                                                int     steps  )

            {
            double  start_color_red = ( double ) start_color.R;
            double  end_color_red = ( double ) end_color.R;
            double  red_difference = 
                        ( start_color.R - end_color_red ) / 
                        ( double ) steps;

            double  start_color_green = ( double ) start_color.G;
            double  end_color_green = ( double ) end_color.G;
            double  green_difference = 
                        ( start_color.G - end_color_green ) / 
                        ( double ) steps;

            double  start_color_blue = ( double ) start_color.B;
            double  end_color_blue = ( double ) end_color.B;
            double  blue_difference = 
                        ( start_color.B - end_color_blue ) / 
                        ( double ) steps;

            yield return ( start_color );

            for ( int i = 1; ( i < ( steps - 1 ) ); i++ )
                {
                yield return ( 
                    Color.FromArgb ( 
                        ( int ) ( start_color_red + 0.5 ), 
                        ( int ) ( start_color_green + 0.5 ), 
                        ( int ) ( start_color_blue  + 0.5 ) ) );

                start_color_red = 
                    ( start_color_red - red_difference );
                start_color_green = 
                    ( start_color_green - green_difference );
                start_color_blue = 
                    ( start_color_blue - blue_difference );
                }

            yield return ( end_color );

            } // interpolated_color_values

interpolated_color_values 中,除循环索引和返回的 Color 外,所有变量的类型都是 double。舍入操作是向上舍入 [^]。

应用线性渐变颜色 目录

我们使用独立的面板通过LinearGradientBrush 来绘制渐变。因此,绘制面板只需为每个面板应用一个新的画笔。这是通过 linear_gradient_panel_OnPaint 实现的,它由 create_gradient_PAN 调用。

        // ***************************** linear_gradient_panel_OnPaint

        void linear_gradient_panel_OnPaint ( object         sender, 
                                             PaintEventArgs e )
            {
            Panel   panel = ( Panel ) sender;

            base.OnPaint ( e );
                                        // BackColor => start 
                                        // ForeColor => end 
            e.Graphics.FillRectangle ( new LinearGradientBrush (
                                               panel.ClientRectangle,
                                               panel.BackColor,
                                               panel.ForeColor,
                                               0.0F ),
                                       panel.ClientRectangle );

            } // linear_gradient_panel_OnPaint

选择器按钮的着色 目录

选择器按钮被放置在selector_buttons_PAN 中。用户指定按钮的数量并单击display_selector_buttons_BUT 控件。display_selector_buttons_BUT 点击事件的处理程序是 display_selector_buttons_BUT_Click。处理程序的第一项任务是创建将放置在selector_buttons_PAN 中的按钮。这通过调用fill_button_panel 来实现,该方法与创建start_end_buttons_PAN 的方法相同。

        // ************************ display_selector_buttons_BUT_Click

        void display_selector_buttons_BUT_Click ( object    sender, 
                                                  EventArgs e )
            {

            fill_button_panel ( number_selector_buttons,
                                selector_buttons_PAN,
                                selector_buttons,
                                selector_button_Click );

            if ( use_interpolation )
                {
                color_using_interpolation ( );
                }
            else 
                {
                color_using_linear_gradient ( );
                }

            selector_buttons_PAN.Visible = true;

            ascending_PB.Visible = true;
            copy_left_to_right_BUT.Visible = true;

            copy_format_GB.Visible = true;
            rgb_decimal_RB.Visible = true;
            rgb_hexadecimal_RB.Visible = true;

            descending_PB.Visible = true;
            copy_right_to_left_BUT.Visible = true;

            reset_BUT.Visible = true;

            } // display_selector_buttons_BUT_Click

一旦创建了选择器按钮,就会应用相应的着色算法。最后,使各种控件可见。

应用插值颜色 目录

在执行 interpolated_gradient_panel_OnPaint 期间,结构体 panel_colors 被填充了每个面板内每个像素的颜色。color_using_interpolation 确定一个selector_button 属于哪个面板。然后,利用selector_button 中心在面板中的位置,可以从panel_colors 中检索selector_button 背景的颜色。第一个和最后一个selector_button 分别采用第一个和最后一个起始/结束按钮的背景颜色。

pixel gradient with selectors

        // ********************************* color_using_interpolation

        bool color_using_interpolation ( )
            {

            for ( int i = 0; ( i < selector_buttons.Count ); i++ )
                {
                Button  button = selector_buttons [ i ];
                int     button_centerline = button.Location.X + 
                                            BUTTON_WIDTH_DIV_2;
                Color   color = Color.Empty;
                int     color_at = 0;
                float   f0 = 0.0F;
                float   f1 = 0.0F;
                float   f2 = 0.0F;
                int     panel_index = 0;

                if ( i == 0 )
                    {
                    button.BackColor = 
                        start_end_color_buttons [ i ].BackColor;
                    continue;
                    }

                if ( i == ( selector_buttons.Count - 1 ) )
                    {
                    button.BackColor = 
                        start_end_color_buttons [ 
                            ( start_end_color_buttons.Count - 1 ) ].
                                BackColor;
                    continue;
                    }

                for ( int j = 0; ( j < panels_end.Count ); j++ )
                    {
                    if ( ( button_centerline >= 
                           panels_start [ j ] ) &&
                         ( button_centerline <= 
                           panels_end [ j ] ) )
                        {
                        panel_index = j;
                        break;
                        }
                    }

                f0 = ( float ) button_centerline / 
                     ( float ) panel_width;
                f1 = f0 - (int ) f0;
                f2 = f1 * ( float ) panel_width;
                color_at = ( int ) ( f2 + 0.5F );
                color = panel_colors [ panel_index ] [ color_at ];

                button.BackColor = color;
                }

            return ( true );

            } // color_using_interpolation

应用线性渐变颜色 目录

selector_button 的颜色由selector_button 中心线正上方的渐变面板像素的颜色决定。

gradient with selectors

与插值模式一样,第一个和最后一个selector_button 分别采用第一个和最后一个起始/结束按钮的背景颜色。

        // ******************************* color_using_linear_gradient

        bool color_using_linear_gradient ( )
            {

            for ( int i = 0; ( i < selector_buttons.Count ); i++ )
                {
                Button  button = selector_buttons [ i ];
                                        // color the button based upon 
                                        // its current location in the 
                                        // buttons panel
                if ( i == 0 )
                    {
                    button.BackColor = 
                        start_end_color_buttons [ 0 ].BackColor;
                    }
                else if ( i == ( selector_buttons.Count - 1 ) )
                    {
                    button.BackColor = 
                        start_end_color_buttons [ 
                            ( start_end_color_buttons.Count - 1 ) ].
                                BackColor;
                    }
                else 
                    {
                    retrieve_linear_gradient_color ( ref button );
                    }
                button.UseVisualStyleBackColor = false;
                }

            return ( true );

            } // color_using_linear_gradient

其余的selection_buttons 由 retrieve_linear_gradient_color 处理。

        // **************************** retrieve_linear_gradient_color

        bool retrieve_linear_gradient_color ( ref Button   button )
            {
            Point   point;
            Point   screen_point;


            point = new Point ( 
                        ( ( selector_buttons_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 );

            } // retrieve_linear_gradient_color

Win32API.get_pixel_color_at_location 方法调用 GetPixel [^] 方法以获取指定屏幕位置(注意这是屏幕位置而不是客户端位置)的 GDI+ 像素颜色。

该方法使用 GetPixel 处理一个像素大小的 Bitmap。通过 Win32 BitBlt [^] 将指定位置的像素复制到一个像素大小的 Bitmap 中。这带来四个好处:该方法在多显示器环境中运行时不会引发异常;它比 GetPixel 快;使用的 Bitmap 仅为一像素高和一像素宽;并且该 Bitmap 仅在此方法本地。

致谢 目录

导致本次修订的读者评论的作者是

  • steve-redTrans
  • BillWoodruff

我感谢他们两位的评论。

参考 目录

结论 目录

本文报告了一个工具的修订,该工具使用户能够从线性颜色渐变中拾取颜色,该渐变具有从两个到最多六个边界颜色。

开发环境 目录

渐变色拾取器是在以下环境中开发的

Microsoft Windows 7 专业版 SP 1
Microsoft Visual Studio 2008 专业版 SP1
Microsoft Visual C# 2008
Microsoft .Net Framework Version 3.5 SP1

历史 目录

06/10/2021 原文
© . All rights reserved.