Why is the conversion between RGB and HLS color systems imprecise?
Question: For one of my applications, I need to convert colors from the RGB to the HLS
color system and vice versa. I found that Python has the
[colorsys](https://docs.python.org/2/library/colorsys.html) module in its
standard library.

The problem is, the conversions are sometimes a bit imprecise and return
slightly different results compared to [this online color
converter](http://www.workwithcolor.com/color-converter-01.htm).

Here is an example, first two small functions I wrote for convenience:

    
    
    from __future__ import division
    
    import colorsys
    
    def convert_rgb_to_hls(r, g, b):
        h, l, s = colorsys.rgb_to_hls(r/255, g/255, b/255)
        return "HLS(" + str(int(round(h * 359))) + ", " + str(int(round(l * 100))) + ", " + str(int(round(s * 100))) + ")"
    
    def convert_hls_to_rgb(h, l, s):
        r, g, b = colorsys.hls_to_rgb(h/359, l/100, s/100)
        return "RGB(" + str(int(round(r * 255))) + ", " + str(int(round(g * 255))) + ", " + str(int(round(b * 255))) + ")"
    

According to the online color converter, RGB(123, 243, 61) should be equal to
HLS(100, 60, 88). The results I get using the colorsys functions are
different:

    
    
    >>> convert_rgb_to_hls(123, 243, 61)
    'HLS(99, 59, 88)'   # should be HLS(100, 60, 88)
    
    >>> convert_hls_to_rgb(100, 60, 88)
    'RGB(122, 243, 63)' # should be RGB(123, 243, 61)
    

My first impression was that this is simply a rounding problem, but looking at
the difference between 61 and 63, there seems to be another reason. But what
is it? Is it possible at all to guarantee an absolute precise conversion
between color systems?


Answer: 
    from __future__ import division
    
    import colorsys
    
    def convert_rgb_to_hls(r, g, b):
        h, l, s = colorsys.rgb_to_hls(r/255, g/255, b/255)
        return "HLS(" + str(int(round(h * 360))) + ", " + str(int(round(l * 100))) + ", " + str(int(round(s * 100))) + ")"
    
    def convert_hls_to_rgb(h, l, s):
        r, g, b = colorsys.hls_to_rgb(h/360, l/100, s/100)
        return "RGB(" + str(int(round(r * 255))) + ", " + str(int(round(g * 255))) + ", " + str(int(round(b * 255))) + ")"
    

**Changes** :

  * 360 instead of 359 because the range is [0, 360) ([wikipedia](http://en.wikipedia.org/wiki/HSL_and_HSV#Hue_and_chroma)).
  * There were two roundings missing on `convert_rgb_to_hls(r, g, b)`.
  * Added python 3 division in case you use python 2.

**Tests** :

    
    
    >>> convert_rgb_to_hls(123, 243, 61)    
    'HLS(100, 60, 88)'
    
    >>> convert_hls_to_rgb(100, 60, 88)
    'RGB(123, 243, 63)'
    

You were right when you said there were rounding-like errors but the
difference between 61 and 63 is because you lose precision when rounding. Do
not round for better precision:

    
    
    >>> (r_orig, g_orig, b_orig) = (123, 243, 61)
    >>> h,l,s = colorsys.rgb_to_hls(r_orig/255, g_orig/255, b_orig/255)
    >>> r, g, b = colorsys.hls_to_rgb(h, l, s)
    >>> r*255, g*255, b*255
    (123.00000000000003, 242.99999999999997, 61.000000000000036)
    

