在RGB和HSB颜色值之间切换的算法

5

我阅读了这篇文章:在RGB和HSB颜色值之间切换的算法

。该文章涉及到IT技术。

Type RGBColor
     Red As Byte
     Green As Byte
     Blue As Byte
End Type

Type HSBColor
     Hue As Double
     Saturation As Double
     Brightness As Double
End Type

Function RGBToHSB(rgb As RGBColor) As HSBColor
     Dim minRGB, maxRGB, Delta As Double
     Dim h, s, b As Double
     h = 0
     minRGB = Min(Min(rgb.Red, rgb.Green), rgb.Blue)
     maxRGB = Max(Max(rgb.Red, rgb.Green), rgb.Blue)
     Delta = (maxRGB - minRGB)
     b = maxRGB
     If (maxRGB <> 0) Then
          s = 255 * Delta / maxRGB
     Else
          s = 0
     End If
     If (s <> 0) Then
          If rgb.Red = maxRGB Then
               h = (CDbl(rgb.Green) - CDbl(rgb.Blue)) / Delta
          Else
               If rgb.Green = maxRGB Then
                    h = 2 + (CDbl(rgb.Blue) - CDbl(rgb.Red)) / Delta
               Else
                    If rgb.Blue = maxRGB Then
                         h = 4 + (CDbl(rgb.Red) - CDbl(rgb.Green)) / Delta
                    End If
               End If
          End If
     Else
          h = -1
     End If
     h = h * 60
     If h < 0 Then h = h + 360
     RGBToHSB.Hue = h
     RGBToHSB.Saturation = s * 100 / 255
     RGBToHSB.Brightness = b * 100 / 255
End Function

Function HSBToRGB(hsb As HSBColor) As RGBColor
     Dim maxRGB, Delta As Double
     Dim h, s, b As Double
     h = hsb.Hue / 60
     s = hsb.Saturation * 255 / 100
     b = hsb.Brightness * 255 / 100
     maxRGB = b
     If s = 0 Then
          HSBToRGB.Red = 0
          HSBToRGB.Green = 0
          HSBToRGB.Blue = 0
     Else
          Delta = s * maxRGB / 255
          If h > 3 Then
               HSBToRGB.Blue = CByte(Round(maxRGB))
               If h > 4 Then
                    HSBToRGB.Green = CByte(Round(maxRGB - Delta))
                    HSBToRGB.Red = CByte(Round((h - 4) * Delta)) + HSBToRGB.Green
               Else
                    HSBToRGB.Red = CByte(Round(maxRGB - Delta))
                    HSBToRGB.Green = CByte(HSBToRGB.Red - Round((h - 4) * Delta))
               End If
          Else
               If h > 1 Then
                    HSBToRGB.Green = CByte(Round(maxRGB))
                    If h > 2 Then
                         HSBToRGB.Red = CByte(Round(maxRGB - Delta))
                         HSBToRGB.Blue = CByte(Round((h - 2) * Delta)) + HSBToRGB.Red
                    Else
                         HSBToRGB.Blue = CByte(Round(maxRGB - Delta))
                         HSBToRGB.Red = CByte(HSBToRGB.Blue - Round((h - 2) * Delta))
                    End If
               Else
                    If h > -1 Then
                         HSBToRGB.Red = CByte(Round(maxRGB))
                         If h > 0 Then
                              HSBToRGB.Blue = CByte(Round(maxRGB - Delta))
                              HSBToRGB.Green = CByte(Round(h * Delta)) + HSBToRGB.Blue
                         Else
                              HSBToRGB.Green = CByte(Round(maxRGB - Delta))
                              HSBToRGB.Blue = CByte(HSBToRGB.Green - Round(h * Delta))
                         End If
                    End If
               End If
          End If
     End If
End Function

然后有人发帖说有错误,但没有详细说明。

但我认为当h大于5时需要进行管理,例如对于颜色R:130 G:65 B:111

If h > 5 Then
    HSBToRGB.Red = CByte(Round(maxRGB))
If h > 6 Then
    HSBToRGB.Blue= CByte(Round(maxRGB - Delta))
    HSBToRGB.Green= CByte(Round((h - 6) * Delta)) HSBToRGB.Blue
Else
    HSBToRGB.Green= CByte(Round(maxRGB - Delta))
    HSBToRGB.Blue = CByte(HSBToRGB.Green- Round((h - 6) * Delta))
End If

我需要添加那一段代码吗?我想应该将它加入HSB转RGB的代码中(在我的C#转换中)。

...
if (s != 0) {
    delta = s * maxRGB / 255;
    if (h > 5)
        rgb.Red = Convert.ToByte(Math.Round(maxRGB));
    if (h > 6)
    {
        rgb.Green = Convert.ToByte(Math.Round(maxRGB - delta));
        rgb.Blue = Convert.ToByte(rgb.Green - Math.Round((h - 6) * delta));
    }
    if (h > 3)
    {
        ...

此外,它应该像上面那样,还是?
if (h > 6) { } 
else if (h > 3)  { }
6个回答

9
使用内置在.NET的Color对象中的方法是不可行的,因为正如几个答案所指出的那样,它们不支持反向(将HSB颜色转换为RGB)。此外,Color.GetBrightness实际上返回的是亮度,而不是亮度/值。由于它们的相似之处(Wikipedia),HSB / HSV和HSL颜色空间之间存在很多混淆。我看到很多颜色选择器最终使用了错误的算法和/或模型。
对我来说,原始代码看起来好像在计算给定RGB颜色的色相值时错过了一些可能的情况。对于你正在考虑添加到代码中的加法,我有点难以理解,但是我首先注意到的是(你似乎没有建议纠正)当饱和度= 0时,你将色相设置为-1。当您稍后将色相乘以60时,您得到-60,然后将其加到360(If h < 0 Then h = h + 360 ),产生的结果为300,这是不正确的。
我使用以下代码(在VB.NET中)来在RGB和HSB(我称为HSV)之间进行转换。这些结果经过了非常广泛的测试,并且与Photoshop颜色选择器提供的结果几乎完全相同(除了它对颜色配置文件所做的补偿)。发布的代码和我的主要区别(除了计算色调的重要部分)是,我更喜欢将RGB值归一化为0到1之间的值进行计算,而不是使用原始的0到255之间的值。这消除了一些原始代码中的低效率和多次转换。
Public Function RGBtoHSV(ByVal R As Integer, ByVal G As Integer, ByVal B As Integer) As HSV
     ''# Normalize the RGB values by scaling them to be between 0 and 1
     Dim red As Decimal = R / 255D
     Dim green As Decimal = G / 255D
     Dim blue As Decimal = B / 255D

     Dim minValue As Decimal = Math.Min(red, Math.Min(green, blue))
     Dim maxValue As Decimal = Math.Max(red, Math.Max(green, blue))
     Dim delta As Decimal = maxValue - minValue

     Dim h As Decimal
     Dim s As Decimal
     Dim v As Decimal = maxValue

     ''# Calculate the hue (in degrees of a circle, between 0 and 360)
     Select Case maxValue
        Case red
           If green >= blue Then
               If delta = 0 Then
                  h = 0
               Else
                  h = 60 * (green - blue) / delta
               End If
           ElseIf green < blue Then
               h = 60 * (green - blue) / delta + 360
           End If
        Case green
           h = 60 * (blue - red) / delta + 120
        Case blue
           h = 60 * (red - green) / delta + 240
     End Select

     ''# Calculate the saturation (between 0 and 1)
     If maxValue = 0 Then
        s = 0
     Else
        s = 1D - (minValue / maxValue)
     End If

     ''# Scale the saturation and value to a percentage between 0 and 100
     s *= 100
     v *= 100

  ''# Return a color in the new color space
  Return New HSV(CInt(Math.Round(h, MidpointRounding.AwayFromZero)), _
                 CInt(Math.Round(s, MidpointRounding.AwayFromZero)), _
                 CInt(Math.Round(v, MidpointRounding.AwayFromZero)))
End Function

您没有发布将HSB(我称之为HSV)颜色转换为RGB的代码,但这是我使用的代码,再次使用介于0和1之间的中间值:

Public Function HSVtoRGB(ByVal H As Integer, ByVal S As Integer, ByVal V As Integer) As RGB
     ''# Scale the Saturation and Value components to be between 0 and 1
     Dim hue As Decimal = H
     Dim sat As Decimal = S / 100D
     Dim val As Decimal = V / 100D

     Dim r As Decimal
     Dim g As Decimal
     Dim b As Decimal

     If sat = 0 Then
       ''# If the saturation is 0, then all colors are the same.
       ''# (This is some flavor of gray.)
        r = val
        g = val
        b = val
     Else
        ''# Calculate the appropriate sector of a 6-part color wheel
        Dim sectorPos As Decimal = hue / 60D
        Dim sectorNumber As Integer = CInt(Math.Floor(sectorPos))

        ''# Get the fractional part of the sector
        ''# (that is, how many degrees into the sector you are)
        Dim fractionalSector As Decimal = sectorPos - sectorNumber

        ''# Calculate values for the three axes of the color
        Dim p As Decimal = val * (1 - sat)
        Dim q As Decimal = val * (1 - (sat * fractionalSector))
        Dim t As Decimal = val * (1 - (sat * (1 - fractionalSector)))

        ''# Assign the fractional colors to red, green, and blue
        ''# components based on the sector the angle is in
        Select Case sectorNumber
           Case 0, 6
              r = val
              g = t
              b = p
           Case 1
              r = q
              g = val
              b = p
           Case 2
              r = p
              g = val
              b = t
           Case 3
              r = p
              g = q
              b = val
           Case 4
              r = t
              g = p
              b = val
           Case 5
              r = val
              g = p
              b = q
        End Select
     End If

     ''# Scale the red, green, and blue values to be between 0 and 255
     r *= 255
     g *= 255
     b *= 255

     ''# Return a color in the new color space
     Return New RGB(CInt(Math.Round(r, MidpointRounding.AwayFromZero)), _
                    CInt(Math.Round(g, MidpointRounding.AwayFromZero)), _
                    CInt(Math.Round(b, MidpointRounding.AwayFromZero)))
End Function

编辑:这段代码看起来非常类似于Richard J. Ross III提供的C代码。我找到了尽可能多的不同算法,在每个算法中借鉴最好的部分并重写了很多代码,并进行了广泛的测试以验证结果的准确性。我忽略了从谁那里借用了代码,因为这只是一个私人库。也许VB版本会帮助不想从C转换的人。:-)


我猜对于你的情况,RGB值将在0-255范围内,那么HSB呢?它和@Richard J. Ross III的C版本一样吗?它是什么? - Jiew Meng
是的,RGB值范围在0到255之间。色相介于0和360之间,而饱和度和亮度/明度介于0和100之间。色相模拟了一个圆的角度,而饱和度和亮度/明度分别模拟了颜色饱和度或亮度的百分比。 - Cody Gray
1
嗯,我尝试了一下,除了当色调为360时变成黑色之外,它还可以,我该怎么解决这个问题? - Jiew Meng
非常棒的发现。是的,那段代码将会起作用,因为当色调为360时,它与色调为0是相同的。我也修改了我之前发布的代码。 - Cody Gray
我们都去了维基百科。LOL。这段代码几乎是我自己实现的原样。 - Nathan M

2
这是我关于如何实现它的版本(用C编写,抱歉,但很容易转换,只需用“out”或“ref”整数替换int * 和 double * ,并且不使用指针语法)
void colorlib_hsbtorgb(double hue, double saturation, double brightness, int *red, int *green, int *blue)
{   
    if (saturation == 0)
    {
        *red = *green = *blue = brightness;
    }
    else
    {
        // the color wheel consists of 6 sectors. Figure out which sector you're in.
        double sectorPos = hue / 60.0;
        int sectorNumber = (int)(floor(sectorPos));
        // get the fractional part of the sector
        double fractionalSector = sectorPos - sectorNumber;

        // calculate values for the three axes of the color. 
        double p = brightness * (1.0 - saturation);
        double q = brightness * (1.0 - (saturation * fractionalSector));
        double t = brightness * (1.0 - (saturation * (1 - fractionalSector)));

        // assign the fractional colors to r, g, and b based on the sector the angle is in.
        switch (sectorNumber)
        {
            case 0:
                *red = brightness;
                *green = t;
                *blue = p;
                break;
            case 1:
                *red = q;
                *green = brightness;
                *blue = p;
                break;
            case 2:
                *red = p;
                *green = brightness;
                *blue = t;
                break;
            case 3:
                *red = p;
                *green = q;
                *blue = brightness;
                break;
            case 4:
                *red = t;
                *green = p;
                *blue = brightness;
                break;
            case 5:
                *red = brightness;
                *green = p;
                *blue = q;
                break;
        }
    }
}

RGB转HSB:

void colorlib_rgbtohsb(int red, int green, int blue, double *hue, double *saturation, double *brightness)
{
    double dRed = red / 255;
    double dGreen = green / 255;
    double dBlue = blue / 255;

    double max = fmax(dRed, fmax(dGreen, dBlue));
    double min = fmin(dRed, fmin(dGreen, dBlue));

    double h = 0;
    if (max == dRed && dGreen >= dBlue)
    {
        h = 60 * (dGreen - dBlue) / (max - min);
    }
    else if (max == dRed && dGreen < dBlue)
    {
        h = 60 * (dGreen - dBlue) / (max - min) + 360;
    }
    else if (max == dGreen)
    {
        h = 60 * (dBlue - dRed) / (max - min) + 120;
    }
    else if (max == dBlue)
    {
        h = 60 * (dRed - dGreen) / (max - min) + 240;
    }

    double s = (max == 0) ? 0.0 : (1.0 - (min / max));

    *hue = h;
    *saturation = s;
    *brightness = max;
}

如果我在代码中发现它是用C#编写的,我将编辑此答案...


我不是颜色专家,但在寻找HSB实现一段时间后,我发现许多人对HSL和HSB感到困惑,你的是HSB吧?我一会儿会测试它... - Jiew Meng
另外,你的HSB值是从哪个范围到哪个范围?我猜RGB是0-255? - Jiew Meng
HSB的取值范围是0到1,使用双精度浮点数表示。如果要将其转换为0到255的范围,则需要将其乘以255。 - Richard J. Ross III

1

如果你正在使用 .net,为什么要重复造轮子呢?

Dim c = Color.FromArgb(myRed, myGreen, myBlue)
Dim h = c.GetHue()
Dim s = c.GetSaturation()
Dim b = c.GetBrightness()

1
我认为 System.Drawing.Color.GetBrightness 实际上是亮度,而不是亮度。 - Jiew Meng

1

使用Color的GetBrightness、GetHue和GetSaturation方法怎么样?


1
我认为 System.Drawing.Color.GetBrightness 实际上是亮度,而不是亮度。 - Jiew Meng

1

使用Color结构将RGB转换为HSB应该相当容易:

Function RGBToHSB(rgb As RGBColor) As HSBColor
  Dim c As Color = Color.FromArgb(rgb.Red, rgb.Green, rgb.Blue)
  RGBToHSB.Hue = c.GetHue()
  RGBToHSB.Saturation = c.GetSaturation()
  RGBToHSB.Brightness = c.GetBrightness()
End Function

虽然它不支持反向操作。


1
我认为 System.Drawing.Color.GetBrightness 实际上是亮度,而不是亮度。 - Jiew Meng
@jiewmeng:可能是这样,但文档确实描述它为“获取此Color结构的色相-饱和度-亮度(HSB)亮度值”。 - Guffa

0

解决方案

您可以很简单地计算亮度分量,因为它是R、G和B的最大值(参考:罗切斯特理工学院的RGB到HSV公式)。您可以通过除以255并乘以比例来进行任意缩放。这与您现有代码中所做的相同:

maxRGB = Max(Max(rgb.Red, rgb.Green), rgb.Blue)
b = maxRGB    
...    
RGBToHSB.Brightness = b * 100 / 255

所以,最终你可以使用内置的 .Net 函数来计算亮度。完整代码如下(不包括你的类型):

Function RGBToHSB(rgb As RGBColor) As HSBColor
  Dim maxRGB As Double
  maxRGB = Max(Max(rgb.Red, rgb.Green), rgb.Blue)

  Dim c As Color = Color.FromArgb(rgb.Red, rgb.Green, rgb.Blue)
  RGBToHSB.Hue = c.GetHue()
  RGBToHSB.Saturation = c.GetSaturation() * 100
  RGBToHSB.Brightness = maxRGB * 100 / 255
End Function

关于HSB(与HSV相同)的一些介绍

来自Darel Rex Finley:

在HSV(也称为HSB)系统中,颜色的亮度是其V分量。该分量被简单地定义为颜色的三个RGB分量中的任何一个的最大值 - 在确定V时忽略其他两个RGB分量。

根据Microsoft文档中的Color.GetBrightness

获取此Color结构的色调-饱和度-亮度(HSB)亮度值。

我发现一些参考资料说MSDN在使用HSL时实际上使用了HSB,例如MSDN博客中的这篇文章(请参阅评论)。通过C#进行快速测试可以证明这是真的:

// Define a color which gives different HSL and HSB value
Color c = Color.FromArgb(255, 0, 0);
// Get the brightness, scale it from 0.0 - 1.0 up to 0 - 255
int bright = (int)(c.GetBrightness() * 255.00);
// Output it
Console.WriteLine(bright.ToString());

这将得到一个值为127,显然是HSL。如果是HSB,则该值应该是R、G和B的最大值(即255)。


我实际上正在寻找HSB而不是HSL。我以前也使用过那个HSLColor类。 - Jiew Meng
对于你们关于GetBrightness的所有评论,我感到困惑,不知道你们想要哪一个。请查看我的编辑。 - badbod99

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接