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

一个 HSV/RGBA 颜色选择器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.45/5 (13投票s)

2005年1月3日

CC (ASA 2.5)

11分钟阅读

viewsIcon

141540

downloadIcon

2542

一个易于使用的颜色选择器,带有 RGB、HSV 和 Alpha 滑块。

Sample Image - CPicker.jpg

引言

此颜色选择器允许您轻松选择颜色,可使用 RGB 或 HSV,它包含一个 Alpha 滑块(如果需要,可以禁用或隐藏),并以十六进制显示颜色值。代码完全使用 Windows API 编写,不使用 MFC。

使用代码

此控件使用起来非常简单:只需将 CPicker.dll 文件放入您的可执行文件文件夹,将您的项目与 CPicker.lib 链接,并且不要忘记在您的 CPP 文件中包含 CPicker.h

CPicker.h 定义了处理 RGBA/HSV 颜色的 SColour struct 和用于管理对话框的 CColourPicker 类。

我将首先检查 SColour struct

struct SColour
    {
    unsigned short r, g, b;
    unsigned short h, s, v;
    unsigned short a;

    void UpdateRGB ();
    void UpdateHSV ();
    };

嗯,这很简单!struct 只是将红色、绿色、蓝色、色相、饱和度、值和 Alpha 存储在一些无符号短变量中,它还有两个方法用于更新颜色组件。因此,例如,如果您有 RGB,您可以设置 rgb,然后调用 UpdateHSV,色相、饱和度和值将自动更新!

这是 CColourPicker 类的定义

class CColourPicker
    {
    public:
        CColourPicker(HWND hParentWindow);
        CColourPicker(HWND hParentWindow, unsigned short r, 
           unsigned short g, unsigned short b, 
           unsigned short a, bool IsRGB);

        void CreatecolourPicker(short AlphaUsage);

        void SetRGB(unsigned short r, unsigned short g, unsigned short b);
        void SetHSV(unsigned short h, unsigned short s, unsigned short v);
        void SetAlpha(unsigned short a);
        
        SColour GetCurrentColour();
        SColour GetOldColour();

    private:
        SColour CurrCol, OldCol;
        short UseAlpha;
        
        HWND hParent;
    };

我们有两个构造函数:两者都需要对话框父窗口的句柄,第二个还初始化 rgba。您也可以使用第二个构造函数通过将 false 作为 IsRGB 传递来初始化 hsv。请注意,调用构造函数只是初始化颜色,它不会创建颜色选择器。

要创建对话框,您应该调用 CreateColourPicker:此函数需要一个参数,以确定是否应显示带 Alpha 滑块的完整对话框。此参数有三个可能的值,在 CPicker.h 中定义

CP_NO_ALPHA 0 不显示 Alpha 滑块和值。Alpha 设置为 100%,颜色不透明。
CP_USE_ALPHA 1 显示 Alpha 滑块和值,并且可以修改,颜色可以是透明的。
CP_DISABLE_ALPHA 2 显示 Alpha 滑块和值,但禁用并设置为 100%,颜色不透明。

其他有用的函数是 SetRGBSetHSVSetAlpha,它们用于设置当前颜色的组件。SetRGB 调用 CurrColUpdateHSV 方法,而 SetHSV 调用 UpdateRGB,因此,当您更新 RGB 时,HSV 将自动更新,反之亦然。

最后,GetCurrentColourGetOldColour 用于检索当前选定的颜色和先前选定的颜色。

示例应用程序 (CPickerTest) 向您展示了如何使用该控件。它非常简单,所以我不会在这里评论它,只需记住链接 CPicker.lib 文件并将 DLL 放入您的 exe 文件夹(或系统文件夹,但我不推荐它!)

关注点

程序中有一些有趣的要点,我希望在此处指出。

首先,HSV/RGB 转换例程:虽然 RGB 到 HSV 很容易理解,但 HSV 到 RGB 可能相当棘手。问题是,由于 RGB 是一个“立方”颜色空间,而 HSV 是一个“圆柱”颜色空间,这些系统之间没有直接的转换矩阵(例如,从 RGB 到 NTSC YIQ),所以你会发现大量的 if 语句和一些乍一看可能相当模糊的计算。我建议打开颜色选择器并开始移动滑块,并查看数字如何变化:这可以极大地帮助理解这两个系统之间的对应关系。

我将简要解释一下它的工作原理。请看这些图像

The colour wheel

图 1 - 色轮

HSV Space

图 2 - HSV 颜色空间

图 1 表示“圆柱形”HSV 颜色空间的一个切片,图 2 示意性地表示了该空间。因此,本质上在点 1、3 和 5 处,您有原色红色、绿色和蓝色,它们对应于 0°、120° 和 240° 的色相值,而在点 2、4 和 6 处,您有辅助色黄色、青色和洋红色,它们对应于 60°、180° 和 300° 的色相值。因此,红色在 6 到 2 之间占主导地位,绿色在 2 到 4 之间占主导地位,蓝色在 4 到 6 之间占主导地位。请记住这一点,我们稍后会用到它。

在下图中,您看到 RGB 空间

RGB Space

图 3 - RGB 颜色空间

RGB 颜色空间可以用一个立方体表示,原色位于三个不相邻的顶点(绿色位于上图中看不见的顶点),辅助色位于其他三个顶点。剩下的两个顶点是白色和黑色(在 HSV 空间中是圆柱体底部的中心)。我们需要将立方体映射到圆柱体……这不太直观!

RGB 转 HSV

那么,让我们从简单的事情开始……您有一个 RGB 颜色,您想将其转换为 HSV。

首先,我们找出 RGB 分量中的最大值 (max) 和最小值 (min),并计算它们之间的差值 (delta)。该值表示颜色的深浅,范围从 0%(黑色)到 100%(纯色);这意味着要计算该值,我们只需取最大值并将其除以 255。

饱和度表示颜色的纯度,表示颜色中灰色的百分比。要计算它,我们取 delta 并将其除以 max。实际上,要获得灰色,您需要同时拥有红色、绿色和蓝色。因此,一个完全饱和(100%)的颜色将有一个分量设置为 0(因此,没有灰色)。在这种情况下,delta 将等于 max,delta/max 将是 100%!相反,一个完全不饱和的颜色将是某种灰色阴影,红色、绿色和蓝色具有相同的值:在这种情况下,delta 将为 0,delta/max 将为 0%。所有中间情况都将给出 0% 到 100% 之间的饱和度值。

要计算色相,您需要记住我之前告诉过您的关于色轮中红色、绿色和蓝色的主导地位:红色在 240°(或者,如果您愿意,-60°)和 60° 之间占主导地位,绿色在 60° 和 180° 之间,蓝色在 180° 和 240° 之间。因此,每种颜色都有一个 120° 的角度,其中它占主导地位。为了获得色相,我们取最大值,并查看它对应于哪个 RGB 分量。假设它对应于红色:这意味着色相将在 -60° 和 60° 之间(注意:我在这里也将使用负色相数字,因为这样更容易解释问题:如果您获得负色相,只需加上 360° 即可获得正确的色相度数)。如果我们以逆时针方向减去另外两个分量,即绿色-蓝色,如果色相大于 0°(我们正在向绿色方向移动),则获得正数,在相反情况下(向蓝色方向移动)则获得负数。60° 的色相意味着没有蓝色(除了来自饱和度的部分),而 -60° 的色相意味着没有绿色。如果我们计算 (绿色-蓝色)/delta,我们将得到一个介于 0 和 1 之间的值,乘以 60 将给我们一个介于 -60 和 60 之间的值。请注意,除以 delta 消除了饱和度分量,如下图所示

Calculating hue

图 4 - 计算色相的实际示例。

在上面的例子中,上面的颜色是 RGB 209,146,65。g-b 的差值是正的,所以我们知道色相将在 0° 和 60° 之间。通过除以 delta,我们找到中间分量(在本例中为绿色)相对于最大分量(红色)的百分比,不包括最小分量(蓝色)。本质上,我们计算完全饱和颜色的色相:RGB(delta, g-b, 0)。因此,如果绿色等于红色(某种黄色阴影),delta 和 g-b 将相同,因此 (g-b)/delta 将给我们 1。如果绿色等于蓝色,(g-b)/delta 将为 0,我们有某种红色阴影。如果我们将此乘以 60,我们将获得正确的色相。如果绿色或蓝色是最大值,推理是相同的,我们只需将结果偏移 120° 或 240°。

HSV 转 RGB

此函数的代码可能看起来比前一个更难理解,但这只是表面现象!在此解释中,我将考虑值和饱和度在 0 到 1 之间,色相在 0° 到 360° 之间。首先,我们查看是否有不饱和颜色:在这种情况下,我们只取值,将其乘以 255,并将结果分配给所有三个 RGB 分量。在颜色饱和的最常见情况下,我们将色相除以 60,得到一个从 0 到 5 的结果,表示色轮的六个部分(同样,红色在 5 和 0 中占主导地位,绿色在 1 和 2 中,蓝色在 3 和 4 中)。

所以我们只需检查我们所在的切片,然后以这种方式计算 RGB 分量

  • 主要分量是 value * 255(例如,如果 hue/60 = 2,则为绿色)。
  • 存在最少的分量(例如,如果 hue/60 = 2,则为红色)使用此公式计算
base = (255.0f * (1.0 - sat) * val);

此基值也将用于其他分量,因此我们将它存储在一个变量中。它可以被认为是颜色中的灰色量(等于 RGB 分量中的最小值),因为如果颜色不饱和,则 1-sat 为 1,如果颜色饱和,则为 0。我们乘以 val 以考虑颜色的亮度,然后乘以 255 以获得正确范围内的值。

  • 中间分量(例如,如果 hue/60=2,则为蓝色)使用此公式计算
(255.0f * val - base) * ((h%60)/60.0f) + base;

如果我们处于以原色开头(0、2 和 4)的切片,或者

(255.0f * val - base) * (1.0f - ((h%60)/ 60.0f)) + base;

如果我们处于以辅助色开头(1、3 和 5)的切片。

在这种情况下,我们将 val 乘以 255,得到具有我们颜色亮度值的灰色的 RGB 分量。然后,我们从其中减去前面获得的基值,消除最少存在的分量(记住您在图 4 中看到的,这是一个类似的推理)。

现在我们计算 (h%60),它返回一个介于 0 和 59 之间的数字,无论我们处于哪个切片,表示从切片开始的度数;将此结果除以 60 得到一个范围为 [0, 1) 的值。对于切片 1、3 和 5,我们只取 1 减去此值,以便 0 始终表示原色的阴影,1 表示辅助色的阴影。因此,如果我们在切片的开头,将这些值相乘,我们得到 0;将我们存储在基数中的基本灰色量相加,我们得到正确的组件值。如果我们在切片的某个其他位置,乘积将给我们一个介于 0 和 255 之间的值,将其与基数相加(出于相同的原因)将给我们正确的值。这就是所有转换的内容!

另一个有趣的部分是彩色滑块的绘制。这是在处理 WM_PAINT 消息时完成的,它只是获取要绘制的控件的 HDC,将位图选择到其中,并使用 SetPixel 一次将颜色绘制到位图中的一个像素。绘制完成后,使用 BitBlt 显示位图。

根据单选按钮选择的内容,滑块代表不同的事物:小的垂直滑块代表选定的特征,大的方形滑块代表另外两个特征。例如,如果选择了 G,则大方块显示 R 对 B,小方块显示 R 的各种值。第三个滑块(如果存在)显示 Alpha:我费了好大劲才弄清楚如何绘制它!我还编写了 DrawCheckedRect 函数,该函数绘制一个带有黑白棋盘格的矩形,并用您选择的颜色“覆盖”(显然,只有当颜色有点透明时,您才能看到棋盘格!)。示例应用程序使用此函数绘制用户选择的颜色。

历史

  • 02-04-2006

    使用优化版本更新了代码。感谢 Fausto Cardone 为我发送了用于绘制颜色选择器的优化例程。

  • 4-1-2005

    我添加了对 RGB/HSV 转换算法更好且[更长]的解释:我希望它清晰,如果不是请告诉我!

  • 3-1-2005

    嗯,这是第一个版本,所以没有历史可写……

如果您发现任何错误,或有任何建议,请随时与我联系!

© . All rights reserved.