18
\$\begingroup\$

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:

\$\endgroup\$
4
  • \$\begingroup\$ Your suggested range for H of 0-360 is [0,360), would it be better written as 0-359? \$\endgroup\$ Dec 9, 2017 at 21:02
  • 3
    \$\begingroup\$ @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 \$\endgroup\$
    – towc
    Dec 9, 2017 at 21:35
  • \$\begingroup\$ Yeah, agreed on the 359.1 point - maybe just use the standard notation of [0,360) then :) \$\endgroup\$ Dec 9, 2017 at 21:47
  • \$\begingroup\$ surprised to see no glsl answers ;) \$\endgroup\$
    – towc
    Dec 10, 2017 at 19:03

10 Answers 10

8
\$\begingroup\$

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
\$\endgroup\$
2
  • \$\begingroup\$ Would taking H in [0,6) and outputting in [0,1] save some bytes? \$\endgroup\$ Dec 9, 2017 at 21:27
  • \$\begingroup\$ @JonathanAllan Ah, I didn't notice the I/O format was loose. Thanks! \$\endgroup\$
    – Arnauld
    Dec 9, 2017 at 23:44
5
\$\begingroup\$

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).

\$\endgroup\$
8
  • \$\begingroup\$ 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*. \$\endgroup\$ Dec 9, 2017 at 23:46
  • 1
    \$\begingroup\$ @JonathanAllan Thanks, updated. \$\endgroup\$
    – Neil
    Dec 9, 2017 at 23:49
  • \$\begingroup\$ Actually probably just import colorsys for 15! \$\endgroup\$ Dec 9, 2017 at 23:50
  • \$\begingroup\$ @JonathanAllan Nice, updated again. \$\endgroup\$
    – Neil
    Dec 9, 2017 at 23:58
  • 1
    \$\begingroup\$ however true it might be, I think this affects the spirit of codegolfing a bit too much :/ \$\endgroup\$
    – towc
    Dec 10, 2017 at 0:16
4
\$\begingroup\$

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!

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Or y'know, this. :P \$\endgroup\$ Dec 9, 2017 at 21:56
  • 1
    \$\begingroup\$ @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). \$\endgroup\$ Dec 9, 2017 at 22:00
  • 1
    \$\begingroup\$ Jenny - if it is right there is no reason not to post it as the byte count with your non-built in below! \$\endgroup\$ Dec 9, 2017 at 22:02
  • 1
    \$\begingroup\$ @JonathanAllan Oh, so it is. It's slightly annoying how RGBColor exists but HSLColor doesn't. >_> \$\endgroup\$ Dec 9, 2017 at 22:02
4
\$\begingroup\$

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 ).

\$\endgroup\$
1
4
\$\begingroup\$

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]

\$\endgroup\$
3
\$\begingroup\$

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.

\$\endgroup\$
0
2
\$\begingroup\$

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.

\$\endgroup\$
2
\$\begingroup\$

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

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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
\$\endgroup\$
1
  • \$\begingroup\$ Oops, of course. Thanks! \$\endgroup\$ Dec 9, 2017 at 23:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.