Algorithm to Switch Between RGB and HSB Color Values

Jiew Meng picture Jiew Meng · Nov 8, 2010 · Viewed 11.6k times · Source

I read the article Algorithm to Switch Between RGB and HSB Color Values

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

Then there was someone who posted that there was a mistake but didn't elaborate much

But I think it need to manage when h is more than 5, for example for the color 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

Do I need to add in that piece of code? And I presume it should go into HSB to RGB (in my C# conversion)

...
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)
    {
        ...

also, should it be like above, or

if (h > 6) { } 
else if (h > 3)  { }

Answer

Cody Gray picture Cody Gray · Nov 8, 2010

Using the methods built into .NET's Color object is a non-starter because, as several of the answers point out, they don't support the reverse (converting an HSB color to RGB). Additionally, Color.GetBrightness actually returns lightness, rather than brightness/value. There is a lot of confusion over the differences between the HSB/HSV and HSL color spaces because of their similarities (Wikipedia). I see lots of color pickers that end up using the wrong algorithm and/or model.

The original code looks to me like it misses a few possible scenarios when it calculates the value for hue, given an RGB color. It's a little difficult for me to follow the additions that you're contemplating to the code, but the first thing that jumps out at me (and that you don't appear to suggest correcting) is that when the saturation = 0, you set hue to -1. When you later multiply the hue by 60, you end up with -60, then you add that to 360 (If h < 0 Then h = h + 360), producing a result of 300, which is not correct.

I use the following code (in VB.NET) to convert between RGB and HSB (which I call HSV). The results have been tested very extensively, and the results are virtually identical to those given by Photoshop's color picker (aside from the compensation it does for color profiles). The major difference between the posted code and mine (aside from the important portion that calculates the hue) is that I prefer normalizing the RGB values to be between 0 and 1 to do the calculations, rather than working with the original values between 0 and 255. This eliminates some of the inefficiencies and multiple conversions in the original code that you posted, as well.

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

You didn't post the code you use to convert from an HSB (which I call HSV) color to RGB, but here's what I use, again working with interim values that are between 0 and 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

EDIT: This code looks very similar to that provided in C by Richard J. Ross III. I hunted down as many different algorithms as I could find online, rewrote a lot of code borrowing the best from each of them, and did extensive testing to verify the accuracy of the results. I neglected to note who I borrowed code from, as this was just for a private library. Maybe the VB version will help someone who doesn't want to do a conversion from C. :-)