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

已知颜色调色板工具 - 最终修订版 - 希望如此

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (60投票s)

2015年6月1日

CPOL

19分钟阅读

viewsIcon

213374

downloadIcon

4817

本文希望是已知颜色调色板工具及其同名早期文章的最终修订版。

Known Colors Palette

介绍 目录

本文介绍了已知颜色调色板工具的修订版。在已更改段落的标题中出现图形 Changed,在新段落的标题中出现图形 New

与之前一样,本次修订版实现了早期修订工具的功能,并增加了在高清显示器上正确显示该工具的算法。

本文中呈现的所有图形最初都以缩略图形式显示。这允许读者单击图像,以在读者的默认图形显示程序中打开它。大多数图像太小,无法显示其细节。本文中的所有图像均为 PNG 图像。

背景 目录

要理解修订后的已知颜色调色板工具(以下简称 KCPTool)的工作原理,读者可能需要一些颜色空间的背景知识。这些模型用于使用颜色分量(或坐标)指定颜色值。作为程序员,我们可能最熟悉 RGB 模型。但要进行颜色匹配,我们需要一个不同的模型,一个能够尽可能与人类感知颜色属性产生相同结果的模型。

对每个颜色空间的讨论必然是简短而肤浅的。对于可能感兴趣进一步阅读的读者,我提供了一些维基百科文章的链接。每个链接都可以在新标签页中打开。

颜色模型 [^] 目录

颜色问题之一是颜色模型数量众多。在本介绍中,我将颜色模型的讨论限制在 RYB(因为我们都从小就熟悉它)、RGB、XYZ 和 CIE Lab,这些都是用于确定颜色接近度的模型。

我还包括了对 HSL 颜色空间的讨论,因为一些读者认为它是确定颜色接近度的有用模型。通过我进行的测试各种最接近颜色匹配算法的实验,我发现“最佳”算法是最新算法 (Delta E 2000)。

RYB 颜色空间 [^] 目录

RYB Color Wheel

我们大多数人在小学时都接触过 RYB(红、黄、蓝)颜色空间。这次介绍可能(至少希望如此)让我们理解了颜色可以混合在一起形成新颜色(幼儿园颜料的颜色)。

RYB 模型是一个减色模型,其中红色、黄色和蓝色**颜料**混合在一起,从而减去(吸收)一些波长的光,同时反射其他波长的光。它最适合解释颜料、染料和墨水的混合。它通常不适用于描述计算机显示器、LED 和等离子设备上的颜色。

RGB 颜色空间 [^] 目录

RGB Color Space

作为开发者,RGB(红、绿、蓝)模型可能是我们最熟悉的(仅次于 RYB)。该模型用于指定计算机显示器、LCD 和等离子设备上的颜色。它和 HSL 模型都用于指定网页中的颜色。

RGB 模型是加色模型,其中红色、绿色和蓝色**光**混合以产生大量颜色。然而,由于硬件限制,颜色色域(范围)通常远低于可能的颜色数量。

通常,红色、绿色和蓝色的值指定为范围 [0,255] 中的整数,或范围 [00,FF] 中的十六进制数字。在本文的某些部分,红色、绿色和蓝色的归一化值指定为范围 [0.0,1.0]。我使用小写字母“r”、“g”和“b”来表示这些归一化 RGB 分量。

CSS 3 颜色模块提供了以下使用 RGB 模型指定红色颜色的示例。

    em { color: #F00 }              /* #rgb */
    em { color: #FF0000 }           /* #rrggbb */
    em { color: rgb(255,0,0) }
    em { color: rgb(100%, 0%, 0%) }

HSL 颜色空间 [^] 目录

HSL Color Space

HSL(色相、饱和度、亮度)颜色空间是 RGB 模型的一种圆柱形表示。色相以圆柱体周围的度数测量。红色在 0°,绿色在 120°,蓝色在 240°,然后回到 360° 的红色。请注意,在 0° 和 360° 处存在不连续性。饱和度以圆柱体中心到其半径的百分比测量。亮度以圆柱体底部到顶部的百分比测量。

HSL 模型最常见的用途是颜色选择工具。HSL 模型还用于特征检测(例如,人脸识别、物体识别、医学图像分析等)。

HSL 模型源自 RGB 模型。从 RGB 到 HSL 的转换如下。

  1. RGB 立方体旋转,使黑色顶点位于底部,白色顶点位于顶部。
  2. 红色、黄色、绿色、青色、蓝色和洋红色点被强制到一个平面上,形成一个六边形。
  3. 六边形垂直向上和向下扩展,形成一个六棱柱
  4. 六棱柱被强制成一个圆柱体。
RGB to HSL Transformation

与 RGB 颜色空间一样,CSS 3 颜色模块提供了一种使用 HSL 模型指定颜色的方法。

    * { color: hsl(0, 100%, 50%) }   /* red */
    * { color: hsl(120, 100%, 50%) } /* lime */ 
    * { color: hsl(120, 100%, 25%) } /* dark green */ 
    * { color: hsl(120, 100%, 75%) } /* light green */ 
    * { color: hsl(120, 75%, 75%) }  /* pastel green */

CIE XYZ 颜色空间 [^] 目录

XYZ Color Space

CIE XYZ 颜色空间是一个数学定义的颜色空间。1931 年,国际照明委员会 (CIE) 召开了会议,更新了建议。会议最终正式确定了 CIE 1931 XYZ 颜色空间(本文中称为 XYZ 颜色空间)。

尽管它是一个数学定义的颜色空间,但它源于 20 世纪 20 年代进行的物理实验。这些实验旨在研究 RGB 颜色空间。从实验结果中得出了 XYZ 颜色空间。

KCPTool而言,XYZ 颜色空间是 RGB 颜色空间和 CIE Lab 颜色空间之间的中间层,后者用于颜色匹配。

CIE Lab 颜色空间 [^] 目录

CIELAB Color Space

CIE Lab 颜色空间源自 XYZ 颜色空间。尽管源自 XYZ 颜色空间,但其作者的目的是创建一个对人眼而言更具感知均匀性的颜色空间。

CIE Lab 颜色空间分量是 **L**(亮度)和两个颜色分量 **a** 和 **b**。如右图所示,**a** 轴与红色和绿色分量相关联;而 **b** 轴与黄色和蓝色分量相关联。请注意,在 CIE Lab 中,颜色不能由例如黄色和蓝色组成。颜色也不能由红色和绿色组成。这种模型称为颜色对抗过程

尽管许多人建议使用 HSL 颜色空间计算颜色差异,但我发现基于 CIE Lab 颜色空间的差异方程比 RGB 和 HSL 欧几里得差异表现得更好。

KCPTool中,RGB 颜色空间中的值被转换为 XYZ 颜色空间,然后从 XYZ 颜色空间转换为 CIE Lab 颜色空间。除了 Delta RGB 差异方程外,CIE Lab 颜色分量用于确定最接近的颜色。

差异方程 [^] 目录

颜色差异是衡量两种颜色匹配程度的指标。最常见但不尽如人意的是欧几里得距离方程。尽管它们提供了颜色差异的粗略估计,但观察者发现结果与预期不符。不常见但感知上更均匀的是使用 CIE Lab 颜色空间的颜色差异。

KCPTool提供了四种颜色差异方程算法供在执行颜色匹配时选择。作者推荐 Delta E 2000 作为首选算法。

差异方程的实现包含在 Difference_Equations 类中。实现将在各个讨论中提供。

Delta RGB 目录

Delta RGB 最接近颜色算法是一种*简单设备相关*的欧几里得距离算法,使用 GDI+ Color 结构的红色、绿色和蓝色分量。Delta Equations 类中计算 Delta RGB 的部分是

using System;
using System.Drawing;

namespace KnownColorsPalette
    {
    public partial class Delta_Equations
        {

        // ************************************************* delta_RGB_TSMI

        /// <summary>
        /// compute the difference between two Colors using a
        /// Euclidian distance algorithm
        /// </summary>
        /// <param name="color_1">
        /// first RGB Color to form the difference
        /// </param>
        /// <param name="color_2">
        /// second RGB Color to form the difference
        /// </param>
        /// <returns>
        /// difference between the two RGB Color instances
        /// </returns>
        public static double delta_RGB_TSMI ( Color color_1,
                                         Color color_2 )
            {
            double  delta_R = ( double ) ( color_1.R - color_2.R );
            double  delta_G = ( double ) ( color_1.G - color_2.G );
            double  delta_B = ( double ) ( color_1.B - color_2.B );

                                        // Euclidean distance
            return ( Math.Sqrt ( delta_R * delta_R + 
                                 delta_G * delta_G + 
                                 delta_B * delta_B ) );
            }

        } // class Delta_Equations

    } // namespace KnownColorsPalette

Delta E 目录

Delta E 最接近颜色算法是一种*简单设备独立*的欧几里得距离算法,使用颜色的 CIE L、a 和 b 分量。Delta Equations 类中计算 Delta E 的部分是

using System;

namespace KnownColorsPalette
    {
    public partial class Delta_Equations
        {

        // *************************************************** delta_E_TSMI

        /// <summary>
        /// compute the difference between two CIELab_Color using 
        /// Euclidian distance algorithm
        /// </summary>
        /// <param name="cielab_1">
        /// first Color to form the difference
        /// </param>
        /// <param name="cielab_2">
        /// second Color to form the difference
        /// </param>
        /// <returns>
        /// difference between the two CIELab_Color instances
        /// </returns>
        public static double delta_E_TSMI ( CIELab_Color  cielab_1,
                                       CIELab_Color  cielab_2 )
            {
            double delta_CIE_a = cielab_1.CIE_a - cielab_2.CIE_a;
            double delta_CIE_b = cielab_1.CIE_b - cielab_2.CIE_b;
            double delta_CIE_L = cielab_1.CIE_L - cielab_2.CIE_L;

                                        // Euclidean distance
            return ( Math.Sqrt ( ( delta_CIE_L * delta_CIE_L ) +
                                 ( delta_CIE_a * delta_CIE_a ) +
                                 ( delta_CIE_b * delta_CIE_b ) ) );
            }

        } // class Delta_Equations

    } // namespace KnownColorsPalette

Delta E 1994 目录

Delta E 1994 最接近颜色算法是一种*设备独立*的颜色匹配算法,使用颜色的 CIE Lab 分量。

来自 Colorwiki [^]

CIE 的一个技术委员会 (TC 1-29) 于 1995 年发布了一个名为 CIE94 的方程。该方程类似于 CMC,但加权函数主要基于 RIT/DuPont 容差数据,这些数据来源于汽车涂料实验,其中样品表面光滑。它也有比率,标记为 kL(亮度)和 Kc(色度)以及商业因子 (cf),但这些通常在软件中预设,并且不常向用户公开。

Delta Equations 类中计算 Delta E 1994 的部分是

using System;

namespace KnownColorsPalette
    {
    public partial class Delta_Equations
        {

        // ********************************************** delta_E_1994_TSMI

        /// <summary>
        /// compute the difference between two CIE Lab colors using 
        /// the CIE 1994 delta E algorithm
        /// </summary>
        /// <param name="cielab_1">
        /// first CIELab_Color to form the difference
        /// </param>
        /// <param name="cielab_2">
        /// second CIELab_Color to form the difference
        /// </param>
        /// <returns>
        /// difference between the two CIELab_Color instances
        /// </returns>
        /// <see>
        /// http://en.wikipedia.org/wiki/Color_difference
        /// </see>
        public static double delta_E_1994_TSMI ( CIELab_Color  cielab_1,
                                            CIELab_Color  cielab_2 )
            {
            double C1;
            double C2;
            double CIE_1_a_squared = cielab_1.CIE_a * cielab_1.CIE_a;
            double CIE_1_b_squared = cielab_1.CIE_b * cielab_1.CIE_b;
            double CIE_2_a_squared = cielab_2.CIE_a * cielab_2.CIE_a;
            double CIE_2_b_squared = cielab_2.CIE_b * cielab_2.CIE_b;
            double delta_a;
            double delta_a_squared;
            double delta_b;
            double delta_b_squared;
            double delta_C_ab;
            double delta_C_ab_divisor;
            double delta_C_ab_squared;
            double delta_E_Lab;
            double delta_H_ab;
            double delta_H_ab_divisor;
            double delta_L;
            double delta_L_squared;
            double K_1;
            double K_2 ;

            delta_L = cielab_1.CIE_L - cielab_2.CIE_L;
            delta_L_squared = delta_L * delta_L;

            delta_a = cielab_1.CIE_a - cielab_2.CIE_a;
            delta_a_squared = delta_a * delta_a;

            delta_b = cielab_1.CIE_b - cielab_2.CIE_b;
            delta_b_squared = delta_b * delta_b;

            delta_E_Lab = Math.Sqrt ( delta_L_squared +
                                      delta_a_squared +
                                      delta_b_squared );

            C1 = Math.Sqrt ( CIE_1_a_squared + CIE_1_b_squared );
            C2 = Math.Sqrt ( CIE_2_a_squared + CIE_2_b_squared );
            delta_C_ab = C1 - C2;
            delta_C_ab_squared = delta_C_ab * delta_C_ab;


            if ( ( delta_a_squared + delta_b_squared ) >= 
                 delta_C_ab_squared )
                {                       // avoid imaginary delta_H_ab
                delta_H_ab = Math.Sqrt ( delta_a_squared + 
                                         delta_b_squared -
                                         delta_C_ab_squared );
                }
            else
                {
                delta_H_ab = 0.0;
                }
                                        // weighting factors for 
                                        // graphic arts
            // K_L = 1.0;               // => no delta_L division
            K_1 = 0.045;
            K_2 = 0.015;

            delta_C_ab_divisor = 1.0 + ( K_1 * C1 );
            delta_H_ab_divisor = 1.0 + ( K_2 * C1 );

            delta_C_ab /= delta_C_ab_divisor;
            delta_H_ab /= delta_H_ab_divisor;

            return ( Math.Sqrt ( delta_L_squared + 
                                 delta_C_ab * delta_C_ab + 
                                 delta_H_ab * delta_H_ab ) );
            }

        } // class Delta_Equations

    } // namespace KnownColorsPalette

Delta E 2000 目录

Delta E 2000 最接近颜色算法是*推荐的设备独立*颜色匹配算法,它使用颜色的 CIE Lab 分量。

来自 Colorwiki [^]

Delta-E 2000 是 Delta E 1994 方程的首次重大修订。与 Delta E 1994 不同,Delta E 1994 假定 L* 正确反映了感知到的亮度差异,而 Delta E 2000 则根据颜色在亮度范围内的位置来改变 L* 的权重。Delta E 2000 仍在考虑中,似乎尚未在图形艺术应用程序中广泛支持。

Delta Equations 类中计算 Delta E 2000 的部分是

using System;

namespace KnownColorsPalette
    {
    public partial class Delta_Equations
        {

        // ********************************************** delta_E_2000_TSMI

        public static double delta_E_2000_TSMI ( CIELab_Color  cielab_1,
                                            CIELab_Color  cielab_2 )
            {
            double c = Math.Pow ( 25, 7 );
            double CIE_1_a_squared = cielab_1.CIE_a * cielab_1.CIE_a;
            double CIE_1_b_squared = cielab_1.CIE_b * cielab_1.CIE_b;
            double CIE_2_a_squared = cielab_2.CIE_a * cielab_2.CIE_a;
            double CIE_2_b_squared = cielab_2.CIE_b * cielab_2.CIE_b;
            double E00;
            double t;
            double weighting_factor_C = 1.0;
            double weighting_factor_H = 1.0;
            double weighting_factor_L = 1.0;
            double xC1;
            double xC2;
            double xCX;
            double xCY;
            double xDC;
            double xDH;
            double xDL;
            double xGX;
            double xH1;
            double xH2;
            double xHX;
            double xLX;
            double xNN;
            double xPH;
            double xRC;
            double xRT;
            double xSC;
            double xSH;
            double xSL;
            double xTX;

            xC1 = Math.Sqrt( CIE_1_a_squared + CIE_1_b_squared );
            xC2 = Math.Sqrt( CIE_2_a_squared + CIE_2_b_squared );
            xCX = ( xC1 + xC2 ) / 2.0;
            t = Math.Pow ( xCX, 7 );
            xGX = 0.5 * ( 1.0 - Math.Sqrt ( t / ( t + c ) ) );

            xNN = ( 1.0 + xGX ) * cielab_1.CIE_a;
            xC1 = Math.Sqrt ( xNN * xNN + CIE_1_b_squared );
            xH1 = CieLab2Hue ( xNN, cielab_1.CIE_b );

            xNN = ( 1.0 + xGX ) * cielab_2.CIE_a;
            xC2 = Math.Sqrt ( xNN * xNN + CIE_2_b_squared );
            xH2 = CieLab2Hue ( xNN, cielab_2.CIE_b );

            xDL = cielab_2.CIE_L - cielab_1.CIE_L;
            xDC = xC2 - xC1;
            if ( ( xC1 * xC2 ) == 0 ) 
                {
                xDH = 0.0;
                }
            else 
                {
                t = xH2 - xH1;
                xNN = Math.Round ( t, 12 );
                if ( Math.Abs ( xNN ) <= 180 ) 
                    {
                    xDH = t;
                    }
                else 
                    {
                    if ( xNN > 180 ) 
                        {
                        xDH = t - 360.0;
                        }
                    else
                        {
                        xDH = t + 360.0;
                        }
                    }
                }
            xDH = 2.0 * Math.Sqrt ( xC1 * xC2 ) * 
                        Math.Sin ( MathUtilities.deg2rad ( 
                                                     xDH / 2.0 ) );
            xLX = ( cielab_1.CIE_L - cielab_2.CIE_L ) / 2.0;
            xCY = ( xC1 + xC2 ) / 2.0;
            t = xH1 + xH2;
            if ( ( xC1 *  xC2 ) == 0 ) 
                {
                xHX = t;
                }
            else 
                {
                xNN = Math.Abs ( Math.Round ( ( xH1 - xH2 ), 12 ) );
                if ( xNN > 180 ) 
                    {
                    if ( t < 360.0 ) 
                        {
                        xHX = t + 360.0;
                        }
                    else
                        {
                        xHX = t - 360.0;
                        }
                    }
                else 
                    {
                    xHX = t;
                    }
                xHX /= 2;
                }
            xTX = 1.0 - 0.17 * Math.Cos ( MathUtilities.deg2rad ( 
                                              xHX - 30.0 ) ) + 
                        0.24 * Math.Cos ( MathUtilities.deg2rad ( 
                                              2.0 * xHX ) ) + 
                        0.32 * Math.Cos ( MathUtilities.deg2rad ( 
                                              3.0 * xHX + 6.0 ) ) - 
                        0.20 * Math.Cos ( MathUtilities.deg2rad ( 
                                              4.0 * xHX - 63.0 ) );
            t = ( xHX  - 275.0 ) / 25.0;
            xPH = 30.0 * Math.Exp ( - ( t * t ) );

            t = Math.Pow ( xCY, 7 );
            xRC = 2.0 * Math.Sqrt ( t / ( t + c ) );
            t = xLX - 50.0;
            xSL = 1.0 + ( 0.015 * ( t * t ) ) /
                        Math.Sqrt ( 20.0 + ( t * t ) );
            xSC = 1.0 + 0.045 * xCY;
            xSH = 1.0 + 0.015 * xCY * xTX;
            xRT = - Math.Sin ( MathUtilities.deg2rad ( 
                                   2.0 * xPH ) ) * xRC;

            xDL /= ( weighting_factor_L * xSL );
            xDC /= ( weighting_factor_C * xSC );
            xDH /= ( weighting_factor_H * xSH );

            E00 = Math.Sqrt ( ( xDL * xDL ) + 
                              ( xDC * xDC ) + 
                              ( xDH * xDH ) + 
                              ( xRT * xDC * xDH ) );

            return ( E00 );
            }

        // ************************************************ CieLab2Hue

        // Function returns CIE-H° value

        /// <summary>
        /// helper function to return the CIE-H° value
        /// </summary>
        private static double CieLab2Hue( double a,
                                          double b )
            {
            double  bias = 0.0;

            if ( ( a >= 0.0 ) && ( b == 0.0 ) ) 
                {
                return 0.0;
                }
            if ( ( a < 0.0 ) && ( b == 0.0 ) ) 
                {
                return 180.0;
                }
            if ( ( a == 0.0 ) && ( b > 0.0 ) ) 
                {
                return 90.0;
                }
            if ( ( a == 0.0 ) && ( b < 0.0 ) ) 
                {
                return 270.0;
                }
            if ( ( a > 0.0 ) && ( b > 0.0 ) ) 
                {
                bias = 0.0;
                }
            if ( a < 0.0 ) 
                {
                bias = 180.0;
                }
            if ( ( a > 0.0 ) && ( b < 0.0 ) )
                {
                bias = 360.0;
                }

            return ( MathUtilities.rad2deg ( Math.Atan ( b / a ) ) + 
                     bias );
            }        

        } // class Delta_Equations

    } // namespace KnownColorsPalette

已知颜色调色板工具 目录

KCPTool是基于微软的KnownColor 枚举。这组颜色被称为“网络安全色”。因此,为网页选择的大多数颜色都应从已知颜色中选取。请注意,CSS 3 颜色模块将微软“已知颜色”的名称称为“扩展颜色关键词”。

Extended_Color 类 目录

为了高效地处理颜色(无需动态创建不同颜色空间的实例),定义了Extended_Color 类。该类的属性是

    public class Extended_Color
        {
        public CIELab_Color   CIELab_color;
        public Color          color;
        public HSL_Color      HSL_color;
        public RGB_Color      RGB_color;
        public XYZ_Color      XYZ_color;
        :
        :

除了 GDI+ Color 结构体外,Extended_Color 还包含 RGB_ColorXYZ_ColorCIELab_Color 类的实例(按使用顺序)。HSL_Color 类是早期开发的遗留物,保留以防找到基于 HSL 颜色空间的性能良好的差异方程。

这四个类的属性是

    public class CIELab_Color
        {
        public double CIE_L;
        public double CIE_a;
        public double CIE_b;
        :
        :

    public class HSL_Color 
        {
        public double Hue;              // [0.0,6.0]
        public double Saturation;       // [0.0,1.0]
        public double Lightness;        // [0.0,1.0]
        :
        :

    public class RGB_Color 
        {     
        public double r;                // [0.0,1.0]
        public double g;                // [0.0,1.0]
        public double b;                // [0.0,1.0]
        :
        :

    public class XYZ_Color 
        {
        public double X;
        public double Y;
        public double Z;
        :
        :

在将值赋给其双精度目标时,每个值都会四舍五入到三位小数。工具中使用的舍入函数KCPToolMathUtilities类中找到,

namespace KnownColorsPalette
    {
    // * ***************************************** class MathUtilities

    public class MathUtilities
        {

        /// <summary>
        /// table of the first ten powers of ten (avoids Math.Pow)
        /// </summary>
        private static int [ ]  powers = new int [ 10 ] { 
                                                      1,    // ^0
                                                     10,    // ^1
                                                    100,    // ^2
                                                   1000,    // ^3
                                                  10000,    // ^4
                                                 100000,    // ^5
                                                1000000,    // ^6
                                               10000000,    // ^7
                                              100000000,    // ^8
                                             1000000000 };  // ^9

        // ***************************************************** round

        /// <summary>
        /// rounds a double precision number to the specified number 
        /// of decimal places
        /// </summary>
        /// <param name="number">
        /// double precision value to round
        /// </param>
        /// <param name="decimal_places">
        /// number of decimal points to maintain
        /// </param>
        /// <returns>
        /// double precision value rounded to the specified decimal 
        /// places
        /// </returns>
        /// <exception>
        /// ArgumentException if decimal places not in the range [0,9]
        /// </exception>
        /// <remarks>
        /// uses round half up rule for tie-breaking
        /// </remarks>
        /// <see cref="http://en.wikipedia.org/wiki/Rounding"/>
        /// <algorithm>
        /// 1. Multiple the original number by 10^decimal_places
        /// 2. Add 0.5 and round the result (truncate to an integer)
        /// 3. Divide result by 10^decimal_places
        /// </algorithm>
        /// <copyright>
        /// Distributed under the Code Project Open License
        /// https://codeproject.org.cn/info/cpol10.aspx
        /// </copyright>
        public static double round ( double number,
                                     int    decimal_places )
            {
            int  power;
            int  t;

            if ( ( decimal_places < 0 ) || ( decimal_places > 9 ) )
                {
                throw new ArgumentException (
                              "Precision out of range [0,9]",
                              "decimal_places" );
                }

            power = powers [ decimal_places ];
            t = ( int ) ( ( number * ( double ) power ) + 0.5 );

            return ( ( double ) t / ( double ) power );
            }

        // *************************************************** rad2deg

        /// <summary>
        /// converts radians to degrees
        /// </summary>
        /// <param name="radians">
        /// double precision radians value to be converted
        /// </param>
        /// <returns>
        /// double precision degrees obtained by converting radians
        /// </returns>
        /// <see>
        /// http://en.wikipedia.org/wiki/Radian
        /// </see>
        public static double rad2deg ( double radians ) 
            {

            return ( radians / Math.PI * 180.0 );
            }

        // *************************************************** deg2rad

        /// <summary>
        /// converts degrees to radians
        /// </summary>
        /// <param name="degrees">
        /// double precision degrees value to be converted
        /// </param>
        /// <returns>
        /// double precision radians obtained by converting degrees
        /// </returns>
        /// <see>
        /// http://en.wikipedia.org/wiki/Radian
        /// </see>
        public static double deg2rad ( double degrees ) 
            {

            return ( degrees * Math.PI / 180.0 );
            }

        } // class MathUtilities

    } // namespace KnownColorsPalette

KCPTool检索 140 种已知颜色中的每一种时,会实例化一个 Extended_Color 实例。其构造函数接受 GDI+ Color 结构。

    // ******************************************** Extended_Color

    public Extended_Color ( Color  color )
        {
        double  r = ( double ) color.R / 255.0;     // [0.0,1.0]
        double  g = ( double ) color.G / 255.0;     // [0.0,1.0]
        double  b = ( double ) color.B / 255.0;     // [0.0,1.0]

        this.color = color;
        HSL_color = HSL_Color.Fromrgb ( color, r, g, b );
        RGB_color = RGB_Color.Fromrgb ( r, g, b );
        XYZ_color = XYZ_Color.Fromrgb ( r, g, b );
        CIELab_color = CIELab_Color.FromXYZ ( XYZ_color );
        }

对扩展颜色执行稳定排序 目录

当创建了 140 个 Extended_Color 实例后,会创建 25 个面板(无需动态创建面板实例),每个面板都包含以 25 种可能方式之一排序的颜色方块。RGB 颜色方块按 RGB、RBG、GRB、GBR、BRG 和 BGR 顺序排序;HSL 颜色方块按 HSL、HLS、SHL、SLH、LHS 和 LSH 顺序排序;XYZ 颜色方块按 XYZ、XZY、YXZ、YZX、ZXY 和 ZYX 顺序排序;CIE LAB 颜色方块按 LAB、LBA、ALB、ABL、BLA 和 BAL 顺序排序。此外,颜色方块还按颜色名称排序。

排序的比较器封装在 Color_Comparer 类中。该类公开了 Comparer 方法,用于比较两个 Extended_Color 实例。请注意,比较必须针对 Color.Name、RGB 分量、HSL 分量、XYZ 分量和 CIE Lab 分量进行操作。Comparer 必须以调用者指定的**稳定**顺序对颜色空间分量进行排序。

为了执行稳定排序,比较器必须初始化。因为单个字母“B”在 RG**B** 和 CIE LA**B** 之间造成冲突,所以决定使用字符串数组初始化 Color_Comparer。这通过构造函数实现

    // ******************************************** Color_Comparer

    /// <summary>
    /// constructor that accepts the fields to be used in later 
    /// comparisons
    /// </summary>
    /// <param name="fields">
    /// string array containing the color fields in the order 
    /// in which they are to be compared
    /// </param>
    public Color_Comparer ( string [ ] fields )
        {

        validate_fields ( fields, "fields" );

        fields_to_compare = fields;
        }

validate_fields 确保只提供 Color_Comparer 识别的颜色字段。就当前版本的KCPTool而言,这些字段包括

  • 名称
  • 绿
  • 色调
  • 饱和度
  • 亮度
  • X
  • Y
  • Z
  • CIE_L
  • CIE_A
  • CIE_B

要按饱和度、色调和最终亮度顺序对 Extended_Color 列表进行排序,以下代码即可

    List < Extended_Color > colors = 
                                new List < Extended_Color > ( 
                                    known_colors.Count );

    Color_Comparer cc = new Color_Comparer ( 
                                new string [ ] {
                                        "SATURATION",
                                        "HUE",
                                        "LIGHTNESS" } );
    colors.Sort ( cc );

省略 Compare 方法,只显示那些排序字段

    public int Compare ( Extended_Color  color_1,
                         Extended_Color  color_2 )
        {
        int  result = 0;

        if ( color_1 == null )
            {
            result = ( ( color_2 == null ) ? 0 : 1 );
            }
        else if ( color_2 == null )
            {
            result = -1; 
            }
        else
            {
            foreach ( string field in fields_to_compare )
                {
                switch ( field.ToUpper ( ) )
                    {
                    :
                    :
                    case "HUE":
                        result = color_1.HSL_color.Hue.
                                     CompareTo (
                                 color_2.HSL_color.Hue );
                        break;

                    case "SATURATION":
                        result = color_1.HSL_color.Saturation.
                                     CompareTo (
                                 color_2.HSL_color.Saturation );
                        break;

                    case "LIGHTNESS":
                        result = color_1.HSL_color.Lightness.
                                     CompareTo (
                                 color_2.HSL_color.Lightness );
                        break;
                    :
                    :
                    default:

                        break;
                    }

                if ( result != 0 )
                    {
                    break;
                    }
                }
            }

        return ( result );
        }

Extended_Color 列表排序完成后,它将用于填充面板。在上面的示例中,该面板是 SHL 面板。

颜色方块面板 目录

每个面板都由颜色方块组成。颜色方块是 Custom_Button 类的一个实例。一个 Extended_Color 实例被添加到 Button 类中,以提供用户单击、制表键选择或悬停在颜色方块上时所需的所有颜色空间信息。

修复工具提示错误 目录

每个颜色方块都有一个工具提示,设置为已知颜色的名称。此外,每个颜色方块都声明了一个 MouseEnter 事件处理程序。

    tooltip.SetToolTip ( color_square, 
                         color.color.Name );
    color_square.MouseEnter +=
            new EventHandler ( tooltip_reinitializer );

事件处理程序 tooltip_reinitializer 包含

    private void tooltip_reinitializer ( Object    sender,
                                         EventArgs e )
        {

        tooltip.Active = false;
        tooltip.Active = true;
        }

事件处理程序的作用是修复一个错误,该错误会导致工具提示在达到显示超时后不再显示。我认为该错误是由工具提示计时器代码中的重入错误引起的。我强烈建议任何具有工具提示的控件也声明相同或功能上等效的事件处理程序。所有KCPTool具有工具提示的控件也将此处理程序与控件的 MouseEnter 事件关联起来。

选项菜单 目录

剪贴板 目录

剪贴板子菜单允许用户指定执行任何“保存到剪贴板”命令时放置在剪贴板上的字符串格式。以下描述了每种选择放置在剪贴板上的内容。

全部
{Name=番茄,RGB=(255,99,71)=#FF6347=(FF,63,47)}
名称
番茄
RGB 十进制
(255,99,71)
RGB 十六进制
#FF6347

默认格式为*RGB 十六进制*。

尽管传输到剪贴板与在颜色编辑应用程序中设置颜色不同,但它提供了一种捕获数据并将其复制到应用程序中的方法。

颜色度量 目录

“选项”菜单项的“颜色度量”子菜单确定何时显示特定颜色方块的颜色度量。默认是在感兴趣的颜色方块上单击鼠标左键。但用户可以指定在鼠标悬停在颜色方块上或光标移到颜色方块上时显示颜色度量。最后,也是最不有用的是,在任何上述事件下显示颜色度量。

当用户点击、悬停或制表键选择所需的颜色方块时,“颜色度量”组框的内容将被填充。一旦颜色度量字段被填充,左键单击“颜色度量”组框中的颜色方块将导致颜色数据被放置到剪贴板上。

默认是*左键单击*。

颜色差异 目录

“选项”菜单项的“颜色差异”子菜单允许用户指定用于确定颜色之间差异的颜色差异方程。用户可以指定 Delta RGB、Delta E、Delta E 1994 或 Delta E 2000 算法之一。

默认值为*Delta E 2000*。

光标形状 目录

“选项”菜单项的“颜色形状”子菜单允许用户指定在鼠标悬停定位期间拖动光标时应使用的光标形状。用户可以指定十字形或十字准线。

默认是*十字形*。

区分所选 目录

“区分所选”子菜单允许用户指定是否在当前选定的颜色按钮(通过算法或用户操作选择)周围放置边框以突出其外观。当前版本的已知颜色调色板工具不提供任何视觉线索来指示 140 个按钮中哪个是所选按钮。

以下选项可用。

无 - 所选按钮不会被区分
边框 - 所选按钮周围将有一个实心边框
移动 - 所选按钮周围将有一个移动边框

默认格式为*移动*边框。

对比色 目录

工具的对比色区域已修订。它现在显示两种对比色:第一种是计算出的对比色,第二种是从计算出的对比色中获得的已知颜色。

    // ********************** compute_known_and_contrasting_colors

    /// <summary>
    /// determines contrasting and known colors for a given color
    /// </summary>
    /// <param name="color">
    /// color for which to compute contrasting and known colors
    /// </param>
    /// <param name="known_color">
    /// nearest known color for the given color
    /// </param>
    /// <param name="contrasting_color">
    /// computed contrasting color for the given color
    /// </param>
    /// <param name="known_contrasting_color">
    /// nearest known color for the computed contrasting color
    /// </param>
    /// <param name="known_extended_color">
    /// extended color for the given color
    /// </param>
    /// <see>
    /// http://stackoverflow.com/questions/1855884/
    ///     determine-font-color-based-on-background-color
    /// </see>
    void compute_known_and_contrasting_colors ( 
                        Color           color,
                    ref Color           known_color,
                    ref Color           contrasting_color,
                    ref Color           known_contrasting_color,
                    ref Extended_Color  known_extended_color )
        {

        known_color = nearest_known_color ( color );
        contrasting_color = Color.FromArgb ( 255 - color.R, 
                                             255 - color.G,
                                             255 - color.B );
        known_contrasting_color = nearest_known_color ( 
                                            contrasting_color );
        known_extended_color = new Extended_Color ( color );
        }

确定最接近的颜色 目录

有两种方法可以指定颜色与已知颜色中的一种最接近匹配。

  1. 左键单击“颜色选择器”以显示标准 Windows 颜色对话框。选择一种颜色并单击“确定”。该颜色将显示在“颜色选择器”按钮的表面上。
  2. 左键单击并按住“鼠标悬停”按钮。光标变为十字形或十字准线(取决于所选的光标形状选项)。将十字形或十字准线拖动到屏幕上需要颜色匹配的某个颜色上。释放鼠标。当鼠标释放时,十字形或十字准线下的颜色将显示在“鼠标悬停”按钮的表面上。

在两种情况下,所选颜色的 RGB 度量都将显示在“查找最接近”组框中,“查找最接近”按钮将启用。

左键单击“查找最接近”按钮以显示最接近的已知颜色匹配。

保存到剪贴板按钮 目录

Save To Clipboard Buttons

在左图中,四个按钮被红色方块包围。当按钮激活时,单击它会将颜色信息复制到剪贴板。放置在剪贴板上的信息是剪贴板子菜单指定的信息。

有一个例外:计算出的对比色,虽然它是一种颜色,但可能不是已知颜色。因此,颜色名称不会被放置在剪贴板上。

多显示器光标下颜色 目录

曾提出为同事的机器安装KCPTool,我尚未准备好首次使用。我的同事使用多个显示器。正如他所料,他尝试使用鼠标悬停功能进行最接近的颜色匹配。我使用的过于简单的 GetPixel 方法无法胜任此任务,当光标释放到不在主显示器上的任何位置时,会导致异常。

我在网上搜索答案,并在 StackOverflow 上找到了 John Gietzen 的优雅解决方案。我对他的代码进行了稍微修改,当我替换以下代码时,问题消失了。(get_pixel 方法在 WIN32API 类中找到,包含在可下载的源代码中。)

    // ************************************************* get_pixel

    /// <summary>
    /// obtain the GDI+ Color of the pixel at the specified 
    /// location on the monitor
    /// </summary>
    /// <param name="location">
    /// Point containing the location of the pixel on the monitor 
    /// whose Color is to be obtained
    /// </param>
    /// <returns>
    /// the GDI+ Color of the pixel at the location on the screen
    /// </returns>
    /// <remarks>
    /// The method does not use GetPixel, resulting 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.
    /// </remarks>
    /// <see>
    /// http://stackoverflow.com/questions/1483928/
    ///     how-to-read-the-color-of-a-screen-pixel
    /// </see>
    public static Color get_pixel ( Point location )
        {
        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,
                         location.X,
                         location.Y,
                         ( int ) CopyPixelOperation.SourceCopy );
                }
            }

        return ( screen_pixel.GetPixel ( 0, 0 ) );
        }

安装 KCPTool 目录

在可下载的源代码中,包含一个名为“Deploy”的目录,其下有一个名为“setup”的子目录。该子目录中包含安装程序 setup_FW.exe,它将安装KCPTool在任何执行它的机器上。如果对KCPTool进行修改,并重新编译项目,则可能需要重新编译并执行 KnownColorsPalette.iss 文件以创建 setup_FW.exe

重建安装程序所需的工具是

  • Inno Setup [^] - 适用于 Windows 程序的免费安装程序
  • ISTool [^] - Inno Setup 编译器的可视化脚本编辑器/生成器

这两种工具都是开发人员工具箱中非常有用的补充。

致谢 目录

我在撰写本文时使用的最大信息来源是维基百科。除了其众多文章外,维基百科共享资源包含大量属于公共领域的图像。本文中的大多数图像都来自维基百科共享资源。

本文和相关项目软件中的公式均来自维基百科和EasyRGB网站。

我还要感谢读者的评论和建议。这些建议促使了一些有趣的修订,我从未想到过,从而改进了KCPTool.

我特别要感谢 Code Project 成员 Jeff Hay-Roe 的帮助,他愿意在高清显示器上测试KCPTool。在他的帮助下,该工具现在在高清显示器上运行稳定。

参考文献 目录

历史 Changed 目录

  • 2011 年 8 月 21 日 - 原始文章。
  • 2011 年 8 月 22 日 - 删除了构思不佳的屏幕分辨率算法。
  • 2011 年 8 月 30 日 - 修复了排版错误;更新了KCPTool到 2.3 版本。
  • 2011 年 9 月 17 日
    • 修复了部署构建后命令
    • 为不同分辨率添加了平衡屏幕
    • 增加了保存和恢复选项的功能
    • 修复了确定光标下颜色的方法
    • 更新了KCPTool到 3.0 版本。
  • 2015 年 5 月 22 日
    • 创建了 KnownColorsPalette 类库,并从类库中移除了该工具
    • 添加了边框选择,可在当前已知颜色按钮周围放置固定边框或移动边框以突出其外观
    • 添加了对比色和剪贴板保存按钮
    • 添加了已知对比色和剪贴板保存按钮
    • 添加了查找最接近颜色剪贴板保存按钮
    • 将主菜单项与上下文菜单项同步
    • 更新了KCPTool到 4.4 版本
  • 2015 年 6 月 1 日 />
    • 编写了一个算法,用于设置表单尺寸以支持高清显示器
    • 更新了KCPTool到 4.5 版本
  • 2015 年 11 月 25 日 New
    • 修复了一个阻止已知颜色信息复制到剪贴板的错误。
    • 修订了 Inno Setup 脚本。
    • 更新了KCPTool到 5.1 版本
© . All rights reserved.