RGB в HSL и обратно, проблемы расчета

Я пытаюсь преобразовать RGB в HSL, и я также хочу преобразовать из HSL в RGB, я написал для него класс, но если я делаю RGB-> HSL-> RGB, чтобы попробовать, работает ли это, я получаю другое значение.

Пример: если вы создаете объект HSLColor, выполнив HSLColor MyTestConversion = HSLColor.FromRGB(Colors.Green);, а затем выполнив Color ExpectedGreenHere = MyTestConversion.ToRGB(), вы получите цвет, отличный от Colors.Green, хотя это был исходный ввод, поэтому что-то пойдет не так..

Это код, который я использую:

public class HSLColor
{
    public float Hue;
    public float Saturation;
    public float Luminosity;

    public HSLColor(float H, float S, float L)
    {
        Hue = H;
        Saturation = S;
        Luminosity = L;
    }

    public static HSLColor FromRGB(Color Clr)
    {
        return FromRGB(Clr.R, Clr.G, Clr.B);
    }

    public static HSLColor FromRGB(Byte R, Byte G, Byte B)
    {
        float _R = (R / 255f);
        float _G = (G / 255f);
        float _B = (B / 255f);

        float _Min = Math.Min(Math.Min(_R, _G), _B);
        float _Max = Math.Max(Math.Max(_R, _G), _B);
        float _Delta = _Max - _Min;

        float H = 0;
        float S = 0;
        float L = (float)((_Max + _Min) / 2.0f);

        if (_Delta != 0)
        {
            if (L < 0.5f)
            {
                S = (float)(_Delta / (_Max + _Min));
            }
            else
            {
                S = (float)(_Delta / (2.0f - _Max - _Min));
            }

            float _Delta_R = (float)(((_Max - _R) / 6.0f + (_Delta / 2.0f)) / _Delta);
            float _Delta_G = (float)(((_Max - _G) / 6.0f + (_Delta / 2.0f)) / _Delta);
            float _Delta_B = (float)(((_Max - _B) / 6.0f + (_Delta / 2.0f)) / _Delta);

            if (_R == _Max)
            {
                H = _Delta_B - _Delta_G;
            }
            else if (_G == _Max)
            {
                H = (1.0f / 3.0f) + _Delta_R - _Delta_B;
            }
            else if (_B == _Max)
            {
                H = (2.0f / 3.0f) + _Delta_G - _Delta_R;
            }

            if (H < 0) H += 1.0f;
            if (H > 1) H -= 1.0f;
        }

        return new HSLColor(H, S, L);
    }

    private float Hue_2_RGB(float v1, float v2, float vH)
    {
        if (vH < 0) vH += 1;
        if (vH > 1) vH -= 1;
        if ((6 * vH) < 1) return (v1 + (v2 - v1) * 6 * vH);
        if ((2 * vH) < 1) return (v2);
        if ((3 * vH) < 2) return (v1 + (v2 - v1) * ((2 / 3) - vH) * 6);
        return (v1);
    }

    public Color ToRGB()
    {
        Color Clr = new Color();
        float var_1, var_2;

        if (Saturation == 0)
        {
            Clr.R = (Byte)(Luminosity * 255);
            Clr.G = (Byte)(Luminosity * 255);
            Clr.B = (Byte)(Luminosity * 255);
        }
        else
        {
            if (Luminosity < 0.5) var_2 = Luminosity * (1 + Saturation);
            else var_2 = (Luminosity + Saturation) - (Saturation * Luminosity);

            var_1 = 2 * Luminosity - var_2;

            Clr.R = (Byte)(255 * Hue_2_RGB(var_1, var_2, Hue + (1 / 3)));
            Clr.G = (Byte)(255 * Hue_2_RGB(var_1, var_2, Hue));
            Clr.B = (Byte)(255 * Hue_2_RGB(var_1, var_2, Hue - (1 / 3)));
        }

        return Clr;
    }
}

Используемая ссылка: EasyRGB Color Math


person Mervin    schedule 25.01.2011    source источник
comment
А в чем именно проблема?   -  person leppie    schedule 25.01.2011
comment
Хорошо, что вы включили свой код, но вам также необходимо сообщить нам свои входные данные, ожидаемые результаты и фактические результаты.   -  person AakashM    schedule 25.01.2011
comment
@leppie ни один из результатов не будет хорошим, если вы возьмете Colors.Green в качестве входных данных, а затем преобразуете его обратно в rgb, он больше не зеленый   -  person Mervin    schedule 25.01.2011
comment
@AakashM любой случайный цвет RGB->HSLColor->RGB не совпадает с цветом, поэтому что-то идет не так   -  person Mervin    schedule 25.01.2011
comment
@Mervin: это около миллиона вещей, которые могут пойти не так. вы вообще какое-то расследование проводили? Для начала вы посмотрели на значение HSL, чтобы увидеть, в чем проблема: RGB->HSL или HSL->RGB?   -  person tenfour    schedule 25.01.2011
comment
вы можете использовать bobpowell.net/rgbhsb.htm вместо самостоятельного преобразования Easy Math.   -  person Todd Main    schedule 25.01.2011
comment
Кстати, у System.Drawing.Color есть методы GetHue, GetSaturation и GetBrightness, но нет возможности вернуться к rgb.   -  person Lou    schedule 03.07.2015


Ответы (3)


Помимо проблем с точностью, я думаю, что ваш фактический алгоритм неверен. Это должен быть ваш FromRGB:

    public static HSLColor FromRGB(Byte R, Byte G, Byte B)
    {
        float _R = (R / 255f);
        float _G = (G / 255f);
        float _B = (B / 255f);

        float _Min = Math.Min(Math.Min(_R, _G), _B);
        float _Max = Math.Max(Math.Max(_R, _G), _B);
        float _Delta = _Max - _Min;

        float H = 0;
        float S = 0;
        float L = (float)((_Max + _Min) / 2.0f);

        if (_Delta != 0)
        {
            if (L < 0.5f)
            {
                S = (float)(_Delta / (_Max + _Min));
            }
            else
            {
                S = (float)(_Delta / (2.0f - _Max - _Min));
            }


            if (_R == _Max)
            {
                H = (_G - _B) / _Delta;
            }
            else if (_G == _Max)
            {
                H = 2f + (_B - _R) / _Delta;
            }
            else if (_B == _Max)
            {
                H = 4f + (_R - _G) / _Delta;
            }
        }

        return new HSLColor(H, S, L);
    }

Следующее, что вам нужно понять, это то, что мы берем целые значения RGB от 0 до 255 и преобразовываем их в десятичные значения от 0 до 1. Таким образом, HSL, который мы получаем, необходимо преобразовать в обычные градусы/проценты/ процентов, к которым вы привыкли. Возвращаемое значение H должно быть от 0 до 6, поэтому, чтобы преобразовать его в градусы, вы просто умножаете на 60. H иногда может быть отрицательным, поэтому, если это просто добавить 360;

            //Convert to degrees
            H = H * 60f;
            if (H < 0) H += 360;

S и L также нужно умножить на 100, чтобы получить процент от 0 до 100.

ОБНОВЛЕНИЕ

Этот код должен перевести вас с HSL на RGB. Предполагается, что значения HSL по-прежнему имеют десятичный формат. Кроме того, я использовал double вместо float в приведенном ниже коде для большей точности.

    public Color ToRGB()
    {
        byte r, g, b;
        if (Saturation == 0)
        {
            r = (byte)Math.Round(Luminosity * 255d);
            g = (byte)Math.Round(Luminosity * 255d);
            b = (byte)Math.Round(Luminosity * 255d);
        }
        else
        {
            double t1, t2;
            double th = Hue / 6.0d;

            if (Luminosity < 0.5d)
            {
                t2 = Luminosity * (1d + Saturation);
            }
            else
            {
                t2 = (Luminosity + Saturation) - (Luminosity * Saturation);
            }
            t1 = 2d * Luminosity - t2;

            double tr, tg, tb;
            tr = th + (1.0d / 3.0d);
            tg = th;
            tb = th - (1.0d / 3.0d);

            tr = ColorCalc(tr, t1, t2);
            tg = ColorCalc(tg, t1, t2);
            tb = ColorCalc(tb, t1, t2);
            r = (byte)Math.Round(tr * 255d);
            g = (byte)Math.Round(tg * 255d);
            b = (byte)Math.Round(tb * 255d);
        }
        return Color.FromArgb(r, g, b);
    }
    private static double ColorCalc(double c, double t1, double t2)
    {

        if (c < 0) c += 1d;
        if (c > 1) c -= 1d;
        if (6.0d * c < 1.0d) return t1 + (t2 - t1) * 6.0d * c;
        if (2.0d * c < 1.0d) return t2;
        if (3.0d * c < 2.0d) return t1 + (t2 - t1) * (2.0d / 3.0d - c) * 6.0d;
        return t1;
    }
person Chris Haas    schedule 25.01.2011
comment
Я попробовал ваши предложения, но если я введу в качестве примера RGB (0,128,0), я получу RGB (100,100,100) обратно после преобразования в HSL и из него. - person Mervin; 25.01.2011
comment
@Мервин, давай разделяй и властвуй. Выполнение того, что я вам дал, должно было дать вам HSL 2/1/0.25, то есть 120° 100% 25%, верно? Если да, то мы знаем, что это работает. Теперь вам просто нужно исправить уравнение HSL в RGB. Сейчас у меня нет времени изучать эту часть, но это сайт, который я использовал для исправления RGB в HSL 130.113.54.154/~monger/hsl-rgb.html - person Chris Haas; 25.01.2011
comment
@ Он действительно возвращает 120,100,25 сейчас, вы дали мне самый полезный ответ. посмотрю на сайте - person Mervin; 25.01.2011
comment
Я просто хочу отметить, что вы сравниваете плавающие значения на равенство, что крайне непредсказуемо. Вот хорошая статья: randomascii.wordpress. com/2012/02/25/ TL;DR; Никогда не сравнивайте числа с плавающей запятой на равенство. Используйте достаточно близкое значение (также известное как эпсилон): if (Math.Abs(a - b) < epsilon) Console.WriteLine("For all intents and purposes, a equals b") - person Jörgen Sigvardsson; 25.11.2014
comment
@JörgenSigvardsson, единственное сравнение равенства, которое я вижу, выполняется в результате Math.Max() и Math.Min(). Могут ли эти функции возвращать числа с плавающей запятой, которые каким-либо образом отличаются от их входных значений? Существует также нулевое сравнение, но только для того, чтобы избежать исключения деления на ноль. - person Chris Haas; 25.11.2014
comment
Тест на равенство, который, как я вижу, может быть очень рискованным, — это _Delta != 0. Я не верю, что Math.Max() и другие возвращают что-то отличное от того, что они получили. Я бы нашел это очень странным, если бы они это сделали! - person Jörgen Sigvardsson; 27.11.2014

Распространенная ошибка. У тебя есть

    public static HSLColor FromRGB(Byte R, Byte G, Byte B)
    {
        float _R = (R / 255);
        float _G = (G / 255);
        float _B = (B / 255);

Скажите мне, какие именно значения R могут привести к тому, что _R не будет равно 0. (Подсказка: есть только одно).

Изменить: у вас такая же проблема в ToRGB() с 1/3.

person Peter Taylor    schedule 25.01.2011
comment
Он уже получил этот ответ, просто скопировал свой вопрос, не используя его. stackoverflow.com /вопросы/4784040/ - person Hans Passant; 25.01.2011
comment
+1 за то, что не дал прямого ответа, а попытался позволить ТС увидеть свою ошибку; который, в случае простого ответа, на мой взгляд, является лучшим способом защиты TS, и он больше не совершит эту ошибку :). - person bastijn; 25.01.2011
comment
@Hans Passant, это другой вопрос, поскольку он также касается обратного преобразования в RGB, чтобы правильно следовать стандартам HSL и RGB. - person Mervin; 25.01.2011
comment
@ Мервин, две вещи. Во-первых, вы прочитали, поняли и действовали в соответствии со строкой, начинающейся с «Редактировать»? Во-вторых, чем именно не хороши результаты? Если вы не предоставите входные данные, ожидаемые результаты и фактические результаты, неразумно ожидать, что кто-то воспроизведет вашу проблему. - person Peter Taylor; 25.01.2011

Проблема, которую я вижу в вашем коде, заключается в следующем:

float _R = (R / 255);

Вы в основном выполняете здесь целочисленное деление, поэтому теряете массу точности.

Попробуйте изменить его на:

float _R = (R / 255f);

(и то же самое для двух других строк).

Кроме того, чтобы еще больше повысить точность, лучше использовать double вместо float.

person RobSiklos    schedule 25.01.2011
comment
Учитывая, что и вход, и выход имеют 8-битную точность, нет абсолютно никаких причин предпочитать двойное число, а не число с плавающей запятой. float имеет более чем достаточную точность для получения идеальных 8-битных результатов. Конечно, все математические ошибки и ошибки программирования должны быть исправлены, а преобразование из числа с плавающей запятой в число с двойной точностью требует округления вместо усечения. Например, если вы делаете это, используя математику с фиксированной запятой, то, если используемые вами константы с фиксированной запятой выбраны тщательно, они должны иметь только ~ 8-10 бит точности - намного меньше, чем 24 бита с плавающей запятой или 53 бита двойного числа. - person Bruce Dawson; 10.02.2015