HSL to RGB values

17

2

The main purpose of the RGB (Red Green Blue) color model is for the sensing, representation and display of images in electronic systems, such as televisions and computers

HSL (Hue Saturation Lightness) is an alternative color model, designed in the 1970s by computer graphics researchers to more closely align with the way human vision perceives color-making attributes

Here are the wiki articles for RGB and HSL. It is common for graphics programs to do the calculations in HSL, and later convert to the preferred format for most screens: RGB.

The task is to write a function/program that takes HSL as an input and outputs RGB.

You can pick your preferred representation for I/O, as long as its consistent between them.

For example, they can be an array/tuple with 3 elements or an object with 3 properties named h, s, and l, but I'll accept other clever variations, like receiving the hsl as an integer (losing precision) and outputting an rgb integer.

The input can be assumed to be safe in range and format, both of which you can decide. I strongly suggest either the ranges 0-1 0-1 0-1 or 0-360 0-100 0-100 for hsl, and 0-1 0-1 0-1 or 0-255 0-255 0-255 for rgb.

Each answer is required to specify both of the above, and do put various variations in your answers if you're particularly proud of them, even if they don't have less characters than your other variations. Put the smallest on top.

Pseudo test cases for 0-360 0-100 0-1000-255 0-255 0-255

h   s   l   → r   g   b

0   0   0   → 0   0   0
90  56  17  → 43  68  19
202 19  39  → 81  104 118
72  55  26  → 88  103 30

The formulae for the conversion can be found here:

This as a nice way to visualize the conversion:

towc

Posted 2017-12-09T17:45:21.513

Reputation: 346

Your suggested range for H of 0-360 is [0,360), would it be better written as 0-359? – Jonathan Allan – 2017-12-09T21:02:48.317

3@JonathanAllan I think it might look slightly more confusing, as to me, that suggests that 395.1 is not a possible input. If anything, using the a-b notation is wrong in itself when dealing with non-integer values, but I'd say it's ok to keep the question more readable. If anyone else complains, I'll rethink it, so thanks for pointing that out – towc – 2017-12-09T21:35:11.747

Yeah, agreed on the 359.1 point - maybe just use the standard notation of [0,360) then :) – Jonathan Allan – 2017-12-09T21:47:15.107

surprised to see no glsl answers ;) – towc – 2017-12-10T19:03:37.293

Answers

8

JavaScript (ES6), 98 95 94 bytes

Takes H in [0,360) and S/L in [0,1). Outputs R, G and B as an array of 3 floats in [0,1).

(H,S,L)=>[5,3,1].map(i=>A(L*2)*S*([1,Y,0,0,Y,1][(i-~H)%6]-.5)+L,Y=(A=n=>n>1?2-n:n)((H/=60)%2))

Test cases

This snippet converts the results back into [0,255].

let f =

(H,S,L)=>[5,3,1].map(i=>A(L*2)*S*([1,Y,0,0,Y,1][(i-~H)%6]-.5)+L,Y=(A=n=>n>1?2-n:n)((H/=60)%2))

format = a => JSON.stringify(a.map(v => v * 255 + 0.5 | 0))

console.log(format(f(  0, 0.00, 0.00))) //  0   0    0
console.log(format(f( 90, 0.56, 0.17))) // 43  68   19
console.log(format(f(202, 0.19, 0.39))) // 81  104 118
console.log(format(f( 72, 0.55, 0.26))) // 88  103  30

How?

Initialization code

Y = (                  // we compute Y = 1 - |(H/60) mod 2 - 1| = X / C
  A = n =>             // A = function that returns 1 - |n - 1|
    n > 1 ?            //   first compare n with 1
      2 - n            //     which allows to simplify the formula to either 2 - n
    :                  //   or
      n                //     just n
)((H /= 60) % 2)       // divide H by 60 and compute Y = A(H % 2)

Main code

[5, 3, 1].map(i =>     // for each i in (5, 3, 1):
  A(L * 2) * S * (     //   compute (1 - |2L - 1|) * S (this is C), and multiply it by:
    [1, Y, 0, 0, Y, 1] //     either 0, 1 or Y (let's call this factor K), picked from
    [(i - ~H) % 6]     //     a cyclic sequence of period 6, using i and ~H (-6 ≤ ~H ≤ -1)
    - .5               //     minus 1/2
  )                    //   this gives: C(K - 1/2) = CK - C/2, where CK = 0, C or X
  + L                  //   we add L, leading to CK - C/2 + L = CK + m
)                      // end of map() --> returns [R, G, B]

Permutations of (0,C,X)

The slightly tricky part is to generate the correct permutation of (0,C,X) according to the angle of the hue component. As shown in the following figure, each column value is picked from the same cycling sequence of period 6, starting at different offsets. In the above code, we're using -~H instead of just +H because we need to coerce H to an integer. Hence the offsets (5,3,1) instead of (0,4,2).

                       C,X,0,0,X,C,C,X,0,0,X,C, ...
 +------> C,X,0,0,X,C  <--------->                  offset = 0, (0 - 1) mod 6 = 5
 | +----> X,C,C,X,0,0          <--------->          offset = 4, (4 - 1) mod 6 = 3
 | | +--> 0,0,X,C,C,X      <--------->              offset = 2, (2 - 1) mod 6 = 1
 | | |
(C,X,0) for   0 ≤ H <  60
(X,C,0) for  60 ≤ H < 120
(0,C,X) for 120 ≤ H < 180
(0,X,C) for 180 ≤ H < 240
(X,0,C) for 240 ≤ H < 300
(C,0,X) for 300 ≤ H < 360

Arnauld

Posted 2017-12-09T17:45:21.513

Reputation: 111 334

Would taking H in [0,6) and outputting in [0,1] save some bytes? – Jonathan Allan – 2017-12-09T21:27:00.617

@JonathanAllan Ah, I didn't notice the I/O format was loose. Thanks! – Arnauld – 2017-12-09T23:44:06.980

4

Mathematica, 155 bytes

(x~Clear~c;d=Piecewise@Table[{(P=Permutations)[P@{c,x,0}][[42,i]],(i-1)<=#<i*p},{i,6}];c=(1-Abs[2#3-1])#2;x=c(1-Abs[Mod[#/(p=60),2]-1]);m=#3-c/2;(m+d)255)&


Try it online!

J42161217

Posted 2017-12-09T17:45:21.513

Reputation: 15 931

1Or y'know, this. :P – totallyhuman – 2017-12-09T21:56:41.123

1@totallyhuman I don't think that's right as Hue is HSB (AKA HSV) not HSL (see figures 2a and 2b on the linked Wikipedia page). – Jonathan Allan – 2017-12-09T22:00:52.483

1Jenny - if it is right there is no reason not to post it as the byte count with your non-built in below! – Jonathan Allan – 2017-12-09T22:02:16.937

1@JonathanAllan Oh, so it is. It's slightly annoying how RGBColor exists but HSLColor doesn't. >_> – totallyhuman – 2017-12-09T22:02:38.417

Is there an ungolfed version of this? – user76284 – 2019-07-07T02:34:18.043

4

Python 2, 32 bytes

from colorsys import*;hls_to_rgb

Try it online!

This functionality is actually built into Python via the hls_to_rgb inside of the colorsys library. The format mine inputs is a 0-1 range of HLS (instead of HSL, both are common acronyms for the same thing [albeit, HSL is more common]). And mine outputs RGB values in a tuple with a range from 0 to 1.

Credits

Thanks to Jonathan Allan for pointing out that I just need to import it (and then further helping me reduce byte count).

Neil

Posted 2017-12-09T17:45:21.513

Reputation: 2 417

I think this can just be 31 bytes as from colorsys import hls_to_rgb since than gives us a function that may be reused. Edit - make that 21 as from colorsys import*. – Jonathan Allan – 2017-12-09T23:46:50.573

1@JonathanAllan Thanks, updated. – Neil – 2017-12-09T23:49:19.663

Actually probably just import colorsys for 15! – Jonathan Allan – 2017-12-09T23:50:49.287

@JonathanAllan Nice, updated again. – Neil – 2017-12-09T23:58:10.457

1however true it might be, I think this affects the spirit of codegolfing a bit too much :/ – towc – 2017-12-10T00:16:17.337

@totallyhuman Hmm yes that actually makes sense, otherwise built-ins would always score zero. My bad. import colorsys;hls_to_rgb 26. – Jonathan Allan – 2017-12-10T19:27:51.517

Updated the post. – Neil – 2017-12-11T00:35:10.093

I've never golfed in Python here and maybe this isn't how it's done, but wouldn't it make more sense for the byte count to be the sum of the import size and the function name size? I.e. from colorsys import* and hls_to_rgb, for 31 bytes? After all, this isn't a full program solution. That's essentially how I score my lambda-with-imports solutions in Java. – Jakob – 2017-12-12T03:49:40.540

4

C++, 228 221 bytes

-7 bytes thanks to Zacharý

#define S(o,n)r[t[int(h[0])/60*3+o]+o-2]=(n+h[2]-c/2)*255;
void C(float*h,int*r){float g=2*h[2]-1,c=(g<0?1+g:1-g)*h[1],a=int(h[0])%120/60.f-1;int t[]={2,2,2,3,1,2,3,3,0,4,2,0,4,1,1,2,3,1};S(0,c)S(1,c*(a<0?1+a:1-a))S(2,0)}

The arguments are both arrays that have a minimum size of 3 elements ( i.e. float hsl[3] and int rgb[3]

Okay. Now, how does that work ? What is that long array ?

Well, it's not a long array, it's a int array. Joke aside, the array indicates by how many position we right shift C X and 0 when we want to select the correct permutation, + 2. Remember how R', G' and B' are selected ?

How R',G' and B' are selected

Let's say we have a "normal" order that is {C, X, 0}

Now, when the first permutation (i.e. {C,X,0}) is selected, you need to not shift, which is exactly the same thing as right shifting by 0. So the first 3 elements of the array are 0,0 and 0

For the second permutation ( {X,C,0} ), we need to right shift C by 1, and left shift X by -1, so the fourth, fifth and sixth element of the array are 1,-1 and 0

And so on...

With the case of the 3rd and 4th permutation, i need to left shift the 0 by 2, so right shift by -2. Since there are more that 2 numbers that are negative, i rather add 2 to each elements in the array and substract 2 at the runtime ( for golfing reasons, to have only whole numbers in the array ).

HatsuPointerKun

Posted 2017-12-09T17:45:21.513

Reputation: 1 891

221 bytes: https://pastebin.com/C8WaSSpb

– Zacharý – 2017-12-10T19:21:18.487

4

Python 2, 119 112 bytes

def f(h,s,l):c=(1-abs(2*l-1))*s;m=l-c/2;x=c*(1-abs(h%2-1))+m;c+=m;return[c,m,x,c,m,x,m,c,x,m][7-int(h)*5%9:][:3]

Try it online!

Takes input as H=[0,6[, S=[0,1], L=[0,1]

TFeld

Posted 2017-12-09T17:45:21.513

Reputation: 19 246

3

HTML + JavaScript (ES6), 8 + 88 = 96 bytes

f=
(h,s,l)=>(p.style.color=`hsl(${h},${s}%,${l}%)`,getComputedStyle(p).color.match(/\d+/g))
<div oninput=o.textContent=f(h.value,s.value,l.value)>H: <input type=number min=0 max=360 value=0 id=h>°<br>S: <input type=number min=0 max=100 value=0 id=s>%<br>L: <input type=number min=0 max=100 value=0 id=l>%<pre id=o>0,0,0</pre>
<p id=p>

Takes input as an angle and two percentages. I'm scoring the HTML snippet <p id=p> and the JavaScript arrow function. I don't know what rounding browsers use, so my answers might differ slightly from yours.

Neil

Posted 2017-12-09T17:45:21.513

Reputation: 95 035

2

Swift 4, 218 bytes

Accepts HSL values in the range [0,1] as arguments, and returns an array containing the RGB values in the same range:

import Cocoa;typealias F=CGFloat;func f(h:F,s:F,l:F)->[F]{var r=F(),g=F(),b=F(),x=s*min(1-l,l),n=2*x/max(l+x,1e-9);NSColor(hue:h,saturation:n,brightness:l+x,alpha:1).getRed(&r,green:&g,blue:&b,alpha:nil);return[r,g,b]}

The idea is to first convert the input from HSL to HSB, then using the HSB constructor of the built-in color object in Cocoa to retrieve the RGB values.

The UIKit version is the exact same length, as the only replacements are:

  • CocoaUIKit
  • NSColorUIColor

Ungolfed:

#if os(iOS)
    import UIKit
    typealias Color = UIColor
#elseif os(OSX)
    import Cocoa
    typealias Color = NSColor
#endif

typealias F = CGFloat

func f(h: F,s: F,l: F) -> [F] {
    var r = F(), g = F(), b = F()

    let x = s * min(1 - l, l)
    let n = 2 * x / max(l + x, 1e-9)

    let color = Color(hue: h, saturation: n, brightness: l + x, alpha: 1)
    color.getRed(&r, green: &g,blue: &b, alpha: nil)

    return [r, g, b]
}

The following snippet can be used for testing, by just replacing the values of h, s, and l:

let h: CGFloat = 0
let s: CGFloat = 0
let l: CGFloat = 0

let result = f(h: h/360, s: s/100, l: l/100).map { Int(round($0*255)) }
print(result)

Unfortunately I can't provide a link to an online sandbox, since all of them run on Linux, which doesn't include Cocoa.

xoudini

Posted 2017-12-09T17:45:21.513

Reputation: 161

2

GLSL (GL_ES) 160 144 134 131 bytes

Hue is in range 0-6 (saves 3 bytes)
Sat,light and rgb are 0-1

vec3 f(vec3 c){return mix(c.bbb,mix(clamp(vec3(-1,2,2)-abs(c.r-vec3(3,2,4))*vec3(-1,1,1),0.,1.),vec3(c.b>.5),abs(.5-c.b)*2.),c.g);}

Try it on book of shaders

Used colorpicker tool on a printscreen to test the output which was correct,
except a small error on 202 19 39 → 81 104 118 , which gave 80 104 118

PrincePolka

Posted 2017-12-09T17:45:21.513

Reputation: 653

1

R, 88 bytes

function(h,s,l){v=(2*l+s*(1-abs(2*l-1)))/2;col2rgb(hsv(h,ifelse(v==0,v,(2*(v-l))/v),v))}

Try it online!

This function takes as input the H, S, and L values as 0-1 ranged values and outputs the RGB values as 0-255 ranged values. It converts the HSL representation to an HSV representation, then uses hsv() to convert the HSV representation to an R color (i.e. hex representation -- #rrggbb), then uses col2rgb() to convert the R color to RGB values.

duckmayr

Posted 2017-12-09T17:45:21.513

Reputation: 441

1

Jelly,  39  32 bytes

-1 thanks to caird coinheringaahing (%2 is just )
-1 and/or inspiration for -4 thanks to caird coinheringaahing too!

Ḥ;Ḃ’ACש"⁵×\;0+³_®H¤⁴Ḟị336Œ?¤¤œ?

A full program taking three command line arguments:

  • L, H, S in the ranges [0,1), [0,6), and [0,1) respectively

which prints a list giving the RGB format as

  • [R, G, B] with each of the three values in the range [0,1]

Note: H may also wrap just like [0,360) would.

Try it online!

How?

Ḥ;Ḃ’ACש"⁵×\;0+³_®H¤⁴Ḟị336Œ?¤¤œ? - Main link: number, L (in [0,1]); number, H (in [0,6))
Ḥ                                - double                 =     2L
  Ḃ                              - modulo by 2            =              H%2
 ;                               - concatenate            = [   2L   ,   H%2]

   ’                             - decrement              = [   2L-1 ,   H%2-1]
    A                            - absolute               = [  |2L-1|,  |H%2-1|]
     C                           - complement             = [1-|2L-1|,1-|H%2-1|]
                                 -                        = [C/S     ,C/X]
         ⁵                       - 3rd program argument   = S  (in [0,1])
        "                        - zip with:
      ×                          -   multiplication       = [C,C/X]
       ©                         -   (copy the result, C, to the register)
           \                     - cumulative reduce with:
          ×                      -   multiplication       = [C, C/X*C] = [C,X]
            ;0                   - concatenate zero       = [C,X,0]
               ³                 - 1st program argument   = L
              +                  - add (vectorises)       = [C+L,X+L,L]
                   ¤             - nilad followed by link(s) as a nilad:
                 ®               -   recall from register = C
                  H              -   halve                = C/2
                _                - subtract               = [C+L-C/2,X+L-C/2,L-C/2]
                             ¤   - nilad followed by link(s) as a nilad:
                    ⁴            -   2nd program argument = H
                     Ḟ           -   floor                = floor(H)
                            ¤    -   nilad followed by link(s) as a nilad:
                       336       -     literal 336
                          Œ?     -     Shortest permutation of natural numbers with a
                                 -     lexicographical index of 336 in all permutations
                                 -     thereof            = [3,5,6,4,2,1]
                      ị          -   index into (1-indexed & modular)
                              œ? - get that permutation of [C+L-C/2,X+L-C/2,L-C/2]
                                 - implicit print

Jonathan Allan

Posted 2017-12-09T17:45:21.513

Reputation: 67 804

Oops, of course. Thanks! – Jonathan Allan – 2017-12-09T23:01:53.110