0

The following is my RGB to HSL one way color conversion method, written in C#. It is closely based on code from the open source program, "Meazure" (which is the tool I am using for my project, thus I need my HSL color to be formatted the same as it is in Meazure).

    public String rgbToHsl(int r, int g, int b)
    {
        double h, s, l;
        double rDecimal = r / 255;
        double gDecimal = g / 255;
        double bDecimal = b / 255;
        double cMin = Math.Min(r, Math.Min(g, b));
        double cMax = Math.Max(r, Math.Max(g, b));
        double delta = cMax - cMin;

        l = (cMax + cMin) / 2;

        if (cMax == cMin)
        {
            s = 0;
            h = 0;  // It's really undefined
        }
        else
        {
            if (l < .5)
            {
                s = delta / (cMax + cMin);
            }
            else
            {
                s = delta / (2 - cMax - cMin);
            }

            if (r == cMax)
            {
                h = (g - b) / delta;
            }
            else if (g == cMax)
            {
                h = 2 + (b - r) / delta;
            }
            else
            {
                h = 4 + (r - g) / delta;
            }

            h /= 6;

            if (h < 0)
            {
                h += 1;
            }
        }

        return h.ToString().PadLeft(3, '0') + s.ToString().PadLeft(3, '0') + l.ToString().PadLeft(3, '0');
    }

The following is the open source, C++ Meazure code I used as a reference.

void MeaColors::RGBtoHSL(COLORREF rgb, HSL& hsl)
{
    double h, s, l;
    double r = GetRValue(rgb) / 255.0;
    double g = GetGValue(rgb) / 255.0;
    double b = GetBValue(rgb) / 255.0;
    double cmax = Max(r, Max(g, b));
    double cmin = Min(r, Min(g, b));

    l = (cmax + cmin) / 2.0;
    if (MEA_DBL_EQL(cmax, cmin)) {
        s = 0.0;
        h = 0.0; // it's really undefined
    } else {
        if (l < 0.5) {
            s = (cmax - cmin) / (cmax + cmin);
        } else {
           s = (cmax - cmin) / (2.0 - cmax - cmin);
        }
        double delta = cmax - cmin;

        if (MEA_DBL_EQL(r, cmax)) {
            h = (g - b) / delta;
        } else if (MEA_DBL_EQL(g, cmax)) {
            h = 2.0 + (b - r) / delta;
        } else {
            h = 4.0 + (r - g) / delta;
        }
        h /= 6.0;

        if (h < 0.0) {
            h += 1.0;
        }
    }

    hsl.hue = h;
    hsl.lightness = l;
    hsl.saturation = s;
}

The problem is that my method does not output the expected values. It does compile and run, without crashing. However, for the input RGB value 214, 219, 233, my method produces the HSL value .6, 228, 70, while the expected value, obtained by measuring the same pixel in HSL format using Meazure, is 149, 72, 210. I noticed a few similar questions on this site, but none with a functional solution, at least in the format desired here.

This is my code that calls my method. It happens to convert 25 RGB values from a single input, by building a 5x5 box centered around the input pixel (just mentioned it to avoid confusion).

            Bitmap screenShot = takeScreenShot();
            const int squareSideSize = 5;
            Color[] firstPixelSquare = new Color[(int)Math.Pow(squareSideSize, 2)];
            hslColor[] hslFirstPixelSquare = new hslColor[(int)Math.Pow(squareSideSize, 2)];

            for (int hOffset = -2, i = 0; hOffset <= 2; hOffset++, i += squareSideSize)
            {
                for (int vOffset = -2, j = i; vOffset <= 2; vOffset++, j++)
                {
                    firstPixelSquare[j] = screenShot.GetPixel((int)numericUpDownX1.Value + hOffset, (int)numericUpDownY1.Value + vOffset);
                    hslFirstPixelSquare[j].h = Convert.ToDouble(rgbToHsl(firstPixelSquare[j].R, firstPixelSquare[j].G, firstPixelSquare[j].B).Substring(0, 3));
                    hslFirstPixelSquare[j].s = Convert.ToDouble(rgbToHsl(firstPixelSquare[j].R, firstPixelSquare[j].G, firstPixelSquare[j].B).Substring(3, 3));
                    hslFirstPixelSquare[j].l = Convert.ToDouble(rgbToHsl(firstPixelSquare[j].R, firstPixelSquare[j].G, firstPixelSquare[j].B).Substring(6, 3));
                }
            }
user10478
  • 327
  • 1
  • 16
  • http://stackoverflow.com/questions/4793729/rgb-to-hsl-and-back-calculation-problems? – Martin Capodici Sep 16 '14 at 22:57
  • Isn't HSL supposed to be in the [0,1] range? The real solution is 0,62280701754386, 0,301587301587301, 0,876470588235294. S and L are percentages, 72 and 210 don't make any sense. – Pierre-Luc Pineault Sep 16 '14 at 23:03
  • @Martin That is a two way conversion, and the scope of the code solution is quite a bit larger than in my problem. I did notice that page, but wasn't able to apply it to my question (perhaps that part is my fault). – user10478 Sep 16 '14 at 23:03
  • @Pierre-Luc I think it is most commonly formatted that way, but the results I get when I check a pixel's HSL color using Meazure are not bound by a max of 100. The [0-1] or [0-100] format seems to be an arbitrary percentage representation, from what I gather. – user10478 Sep 16 '14 at 23:06
  • But even the algo you provided from Meazure does this. Take a calculator and calculate the `l` value yourself, `(233 / 255 + 233 / 255) / 2 = 0.87`.. – Pierre-Luc Pineault Sep 16 '14 at 23:09

1 Answers1

1

There are two problems with your original algorithm :

The variables declared here are never used :

    double rDecimal = r / 255;
    double gDecimal = g / 255;
    double bDecimal = b / 255;

Either switch the r for rDecimal everywhere, or simply rename the input to continue using r, g, b variables :

public String RgbToHsl(int rInput, int gInput, int bInput)
{
    double h, s, l;
    double r = rInput / 255;
    double g = gInput / 255;
    double b = bInput / 255;

The second problem is in this segment too. The inputs are ints and you're dividing by 255 (which is an int too). This results in an integer division, which will yield 0 each time. You need to divide by 255.0 to force a double division:

public String RgbToHsl(int rInput, int gInput, int bInput)
{
    double h, s, l;
    double r = rInput / 255.0;
    double g = gInput / 255.0;
    double b = bInput / 255.0;

Once that's done, you need to convert the result from a [0,1] interval to the one you get from your tool. The closest I've been able to go is by multiplying the result by 239 and then rounding. It's probably possible to find the real pattern/exact value from more input/output examples...

h = Math.Round(h * 239);
s = Math.Round(s * 239);
l = Math.Round(l * 239);

return h.ToString().PadLeft(3, '0') + s.ToString().PadLeft(3, '0') + l.ToString().PadLeft(3, '0');
}
Pierre-Luc Pineault
  • 8,993
  • 6
  • 40
  • 55
  • Thank you for the solution! Do you have any intuition as to why the seemingly arbitrary value of 239 was chosen, or inside which file in https://bitbucket.org/cthing/meazure/src/72f19d13c9902641a26fd7c9a68f17d4f1a515ec/src/meazure/?at=master the answer is likely to be located? The C++ code I posted was from Colors.cpp, but I see no mention of 239 there. – user10478 Sep 17 '14 at 04:05
  • @user10478 If you follow the usage of `MeaColors::RGBtoHSL` you'll probably find where it's transformed along the way. It's not necessarily in Colors.cpp, some class can call the method and change the results from there. To do that you can put the source code in a good IDE and use the "Find Usages" feature (well I hope C++ IDEs have that..) to trace where the HSL values are used. – Pierre-Luc Pineault Sep 17 '14 at 15:18