Алгоритм переключения между цветовыми значениями RGB и HSB - PullRequest
5 голосов
/ 08 ноября 2010

Я прочитал статью Алгоритм переключения между цветовыми значениями RGB и HSB

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 голосов
/ 08 ноября 2010

Использование методов, встроенных в объект Color в .NET, не является началом, поскольку, как указывают некоторые ответы, они не поддерживают обратное (преобразование цвета HSB в RGB).Кроме того, Color.GetBrightness фактически возвращает легкость , а не яркость / значение.Существует много путаницы в отношении различий между цветовыми пространствами HSB / HSV и HSL из-за их сходства ( Wikipedia ).Я вижу множество палитр цветов, которые в конечном итоге используют неправильный алгоритм и / или модель.

Исходный код выглядит для меня так, будто он пропускает несколько возможных сценариев, когда вычисляет значение для оттенка, учитывая цвет 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

РЕДАКТИРОВАТЬ: Этот код выглядит очень похоже на тот, который предоставляется в CРичард Дж. Росс III.Я выискал столько разных алгоритмов, сколько смог найти в Интернете, переписал много кода, позаимствовав лучшее из каждого из них, и провел обширное тестирование, чтобы проверить точность результатов.Я забыл отметить, у кого заимствовал код, так как это было только для частной библиотеки.Возможно, версия VB поможет кому-то, кто не хочет делать преобразование из C.: -)

2 голосов
/ 08 ноября 2010

Вот моя версия о том, как это сделать (в C, извините, но не должно быть сложно конвертировать, просто замените int * и double * на out или ref,и не используйте синтаксис указателя)

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 #, я отредактирую этот ответ ....

1 голос
/ 08 ноября 2010

Преобразование из RGB в HSB должно быть довольно простым с использованием структуры Color:

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 голос
/ 08 ноября 2010

Если вы используете .net, зачем изобретать велосипед?

Dim c = Color.FromArgb(myRed, myGreen, myBlue)
Dim h = c.GetHue()
Dim s = c.GetSaturation()
Dim b = c.GetBrightness()
1 голос
/ 08 ноября 2010

А как насчет использования методов Color GetBrightness, GetHue и GetSaturation?

0 голосов
/ 08 ноября 2010

Решение

Компонент яркости можно рассчитать довольно просто, так как это максимальное из 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)

С Дарел Рекс Финли :

В системе HSV (также называемой HSB) яркость цвета является его V-компонентой. Этот компонент определяется просто как максимальное значение любого из трех компонентов RGB цвета - два других компонента RGB игнорируются при определении V.

Согласно документации Microsoft для Color.GetBrightness:

Получает значение яркости оттенка-насыщенности-яркости ( HSB ) для этой цветовой структуры.

Я нашел некоторые ссылки, в которых говорится, что MSDN использует HSB, когда он означает HSL, как этот из блогов 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).

...