Skip to content

Instantly share code, notes, and snippets.

@aemkei
Forked from 140bytes/LICENSE.txt
Created October 30, 2011 14:15
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save aemkei/1325937 to your computer and use it in GitHub Desktop.
Save aemkei/1325937 to your computer and use it in GitHub Desktop.
hsl2rgb - 140byt.es

hsl2rgb - 140byt.es

This method converts color values from hue-saturation-lightness (HSL) to it's red-green-blue representation.

Check out the demo!

Special thanks to tsaniel, Alex Kloss, subzey, Jed Schmidt, and maettig for there unbelievable magic and effort!

For more information

See the 140byt.es site for a showcase of entries (built itself using 140-byte entries!), and follow @140bytes on Twitter.

To learn about byte-saving hacks for your own code, or to contribute what you've learned, head to the wiki.

140byt.es is brought to you by Jed Schmidt, with help from Alex Kloss. It was inspired by work from Thomas Fuchs and Dustin Diaz.

function(
a, // hue
b, // saturation
c // lightness
){
a *= 6;
b = [
c += b *= c < .5 ?
c :
1 - c,
c - a % 1 * b * 2,
c -= b *= 2,
c,
c + a % 1 * b,
c + b
];
return[
b[ ~~a % 6 ], // red
b[ (a|16) % 6 ], // green
b[ (a|8) % 6 ] // blue
]
}
function(a,b,c){a*=6;b=[c+=b*=c<.5?c:1-c,c-a%1*b*2,c-=b*=2,c,c+a%1*b,c+b];return[b[~~a%6],b[(a|16)%6],b[(a|8)%6]]}
// With proper error checking for all positive values. Saturation and lightness is clipped at 1.
function(a,b,c){a*=6;b=b>1||b;c=c>1||c;b=[c+=b*=c<.5?c:1-c,c-a%1*b*2,c-=b*=2,c,c+a%1*b,c+b];return[b[~~a%6],b[(a|16)%6],b[(a|8)%6]]}
// This version (137 bytes) uses a range from 0..360 for hue and 0..100 for saturation and lightness.
function(a,b,c){a/=60;c/=100;b=[c+=b*=(c<.5?c:1-c)/100,c-a%1*b*2,c-=b*=2,c,c+a%1*b,c+b];return[b[~~a%6],b[(a|16)%6],b[(a|8)%6]]}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2011 YOUR_NAME_HERE <YOUR_URL_HERE>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "hsl2rgb",
"description": "Converts hue-saturation-lightness to red-green-blue color value.",
"contributors": [
{
"name" : "Martin Kleppe",
"url" : "https://github.com/aemkei"
},
{
"name" : "tsaniel",
"url" : "https://github.com/tsaniel"
},
{
"name" : "Alex Kloss",
"url" : "https://github.com/atk"
},
{
"name" : "subzey",
"url" : "https://github.com/subzey"
},
{
"name": "Jed Schmidt",
"url": "https://github.com/jed"
},
{
"name": "maettig",
"url": "https://github.com/maettig"
}
],
"keywords": [
"color",
"convert",
"hsl",
"rgb"
]
}
<!DOCTYPE html>
<title>hsl2rgb - 140byt.es</title>
<style type="text/css" media="screen">
span {
display: inline-block;
width: 8px;
height: 8px;
}
#output {
margin-bottom: 1em;
}
#output div {
height: 8px;
}
</style>
<div id="output"></div>
<a href="https://gist.github.com/1325937">Source Code</a>
<script>
var hsl=function(a,b,c){a*=6;b=[c+=b*=c<.5?c:1-c,c-a%1*b*2,c-=b*=2,c,c+a%1*b,c+b];return[b[~~a%6],b[(a|16)%6],b[(a|8)%6]]}
var html = "", h, s, l, rgb;
for (h = 0; h <= 1; h += 0.1){
html += "<div>";
for (s = 0; s <= 1; s += 0.3){
for (l = 0; l <= 1; l += 0.1){
rgb = hsl(h, s, l);
rgb = [
~~(rgb[0] * 255),
~~(rgb[1] * 255),
~~(rgb[2] * 255)
].join(",")
html += "<span style='background:rgb(" + rgb + ")'></span>"
}
}
html += "</div>";
}
document.getElementById("output").innerHTML = html;
</script>
@LeverOne
Copy link

LeverOne commented Nov 4, 2011

My limit for this game for a while exhausted. But my next goal is HSL algorithm, I promise. The game is not over until you found the perfect form.

Btw, here's the problem, which can seriously hinder the practical use of this code:

Array.prototype.map_=123;
alert(hsl(1,1,1).map_)

Thanks to @atk for this catch.

@tsaniel
Copy link

tsaniel commented Nov 4, 2011

Do you mean the [].map is unreliable?

@LeverOne
Copy link

LeverOne commented Nov 4, 2011

I mean this hsl2rgb function will replace the properties and methods that are defined by user in the prototype (enumerates their "for in" loop.). For example, array polyfills will be useless.

@tsaniel
Copy link

tsaniel commented Nov 4, 2011

I see, however, there are also many gists using the for in trick. So...

@maettig
Copy link

maettig commented Nov 8, 2011

I started from scratch and now I'm down at 121 bytes with a clean list of arguments and without using a for loop.

function(a,b,c){a*=6;b*=c<.5?c:1-c;c-=b;b*=2;b=[c+b,c+b-b*(a%1),c,c,c+b*(a%1),c+b];return[b[a|=0],b[(a+4)%6],b[(a+2)%6]]}

Golfing this down was more math than coding. I was hoping to be the leader of this little competition but it's already at 121 bytes. Now I need your help. I tried to save a few more bytes but everything lead to the same 121 bytes, just made the code more confusing. One more byte? Anyone?

@maettig
Copy link

maettig commented Nov 8, 2011

I can't stop. Down at 116 bytes. Enough room to add proper clipping for the saturation and lightness arguments if they exceed the 0 to 1 range.

function(a,b,c){a*=6;b=[c+=b*=c<.5?c:1-c,c-b*2*(a%1),c-=b*=2,c,c+b*(a%1),c+b];return[b[a|=0],b[(a+4)%6],b[(a+2)%6]]}

@aemkei
Copy link
Author

aemkei commented Nov 8, 2011

@maettig: Nice to see some fresh ideas over here! Sometimes it's worth to step back and golf the hole again with a new golf club.
@jed: What do you think: Should I update this gist and add @meattig to the list of contributors in package.json or is it better for him to fork the original 140bytes gist?

@maettig
Copy link

maettig commented Nov 8, 2011

To bad. My 116 bytes version returns bad colors when hue is exactly 1. This was not visible in the test.html script due to the behaviour of floating point numbers. I had to add 3 bytes to fix this. Now it's 119 bytes. The good thing is, now hue accepts every positive number without additional error checking. A version with basic error checking for all three arguments is 139 bytes.

// Without error checking. Values outside the 0..1 range may lead to unexpected results.
function(a,b,c){a*=6;b=[c+=b*=c<.5?c:1-c,c-b*2*(a%1),c-=b*=2,c,c+b*(a%1),c+b];return[b[a=~~a%6],b[(a+4)%6],b[(a+2)%6]]}
// With error checking for positive values. Saturation and lightness is clipped at 1.
function(a,b,c){a*=6;b=b<1?b:1;c=c<1?c:1;b=[c+=b*=c<.5?c:1-c,c-b*2*(a%1),c-=b*=2,c,c+b*(a%1),c+b];return[b[a=~~a%6],b[(a+4)%6],b[(a+2)%6]]}

Oh, and by the way, there is an error in your hsl(0..360, 0..100, 0..100) function. You mixed the value ranges for hue and luminance. I fixed it. This makes your version 136 bytes.

function(a,b,c,d,e){c/=100;b/=100;for(d in e=[b=c+b*(c>.5?1-c:c),b-=c+=c-b,a/=60])e[d]=c+b*((d=(a+2+4*d)%6)<1?d:d<3||d<4&&4-d);return e}

Here is the same hsl(0..360, 0..100, 0..100) function based on my version. This is 133 bytes.

function(a,b,c){a/=60;c/=100;b=[c+=b*=(c<.5?c:1-c)/100,c-b*2*(a%1),c-=b*=2,c,c+b*(a%1),c+b];return[b[a=~~a%6],b[(a+4)%6],b[(a+2)%6]]}

@LeverOne
Copy link

LeverOne commented Nov 9, 2011

1.1) b * 2 * (a%1)
1.2) b * (a%1)
2.1) b=b<1?b:1
2.2) c=c<1?c:1
3.1) a * =6 and a+2+4 * d
3.2) a/=60 and a+2+4 * d
4) c<.5?c:1-c // <-- may be,not sure

@jed
Copy link

jed commented Nov 9, 2011

i would love to keep every step in a challenge in a single place. perhaps we need a way of reflecting contributors on the leaderboard? what do you guys think?

and by the way, great work, @maettig!

@subzey
Copy link

subzey commented Nov 9, 2011

@maettig, bravo! I thought this function cannot be compressed further.

According to operator precedence we can drop several parens saving 4 more bytes.

Based on this version:

function(a,b,c){a*=6;b=[c+=b*=c<.5?c:1-c,c-b*2*(a%1),c-=b*=2,c,c+b*(a%1),c+b];return[b[a=~~a%6],b[(a+4)%6],b[(a+2)%6]]}

Suggested changes:

function(a,b,c){a*=6;b=[c+=b*=c<.5?c:1-c,c-a%1*b*2,c-=b*=2,c,c+a%1*b,c+b];return[b[a=~~a%6],b[(a+4)%6],b[(a+2)%6]]}

@tsaniel
Copy link

tsaniel commented Nov 9, 2011

@maettig is my hero (and @LeverOne).

@maettig
Copy link

maettig commented Nov 9, 2011

@subzey, you are right. It works. % and * are left-to-right. Now it's 115 bytes without error checking, 135 bytes with error checking and 129 bytes with 0..360 and 0..100 ranges. Incredible. Yea, and thanks for the update, @aemkei. The only thing is, I miss my name in the readme.
@LeverOne, I'm sorry, I don't understand. I think you are pointing at some duplicate snippets in the code. I tried to assign this to a variable and reuse it but this did not saved a byte. Maybe you find a way to do this?

@aemkei
Copy link
Author

aemkei commented Nov 9, 2011

@maettig Now you're in the readme, too. I've added you to the list of contributors, but I'm not sure if it will be parsed to show up at 140byt.es.

And by the way: I really like your version! It's not only shorter but much cleaner.

@LeverOne
Copy link

LeverOne commented Nov 9, 2011

@maettig, I have listed a section of code that can be reduced. It's so obvious ...
2.1) b=b< 1 ? b : 1 --> b = b > 1 || b
2.2) c=c< 1 ? c : 1 --> c=c > 1 || c
3.1) a * =6 and a+2+4 * d --> a * 6+2+4 * d
3.2) a/=60 and a+2+4 * d --> a/60+2+4 * d

@subzey
Copy link

subzey commented Nov 9, 2011

Cut off one more byte:

/* ... */return[b[~~a%6],b[(a|16)%6],b[(a|8)%6]]}

This approach assumes that a should never be more than 7. Luckily, it is so.

@maettig
Copy link

maettig commented Nov 9, 2011

@subzey, what the ...? This is crazy. Why does this work? Lets see.

  1. (a + 2) % 6
  2. (a + 2 + 6) % 6 Adding 6 does nothing because of the modulo operator.
  3. (a + 8) % 6 Result must be 2^n (e.g. 1, 2, 4, 8, 16 and so on).
  4. (a | 8) % 6 Replacing + with | only works if a is lower than the 2^n used.

This looks strange but works. Speaking binary, this sets the bit 1000b and truncates decimal places at the same time. But the number must be in the 000b to 111b range. Good work. Down at 114 bytes.

@LeverOne (you edited your comment so I will edit mine), now I see. This applies to my version with error checking. Turning b*(a%1) to a%1*b was also mentioned by @subzey. Clipping with c=c>1||c is very cool. If c is greater than 1 it returns true which becomes 1. Now this version is 133 bytes (without the approach from @subzey so hue accepts all positive values).

// With proper error checking for all positive values. Saturation and lightness is clipped at 1.
function(a,b,c){a*=6;b=b>1||b;c=c>1||c;b=[c+=b*=c<.5?c:1-c,c-a%1*b*2,c-=b*=2,c,c+a%1*b,c+b];return[b[a=~~a%6],b[(a+4)%6],b[(a+2)%6]]}

@jed
Copy link

jed commented Nov 10, 2011

it's interesting how this current solution is closer to the original in approach than previous attempts, but replacing a function call with an array lookup and multiple var declarations with clever argument reuse.

@xpansive
Copy link

It's also starting to look more and more like my hsv2rgb converter :)

@p01
Copy link

p01 commented Nov 10, 2011

@maetig: @subzey approach is actually pretty simple. In the end, a is an integer in the range [ 0; 5 ], in short it only uses the 3 LSB. We are looking for a shorter way to write a=0|a%6 , (a+2)%6 and (a+4)%6 . With the knowledge that a|0 only uses the 3 LSB, we can just look for a numbers that don't touch those bits and whose modulo 6 is either 2 or 4. And that's what we have: 8%6==2 and 16%6==4

Actually, we can probably save 2 more bytes by doing:

/* ... */return[b[0|a],b[(a|16)%6],b[(a|8)%6]]}

Sorry if I missed that shot. Haven't read the whole comments thread :p

@maettig
Copy link

maettig commented Nov 10, 2011

@p01, thanks, but this will break the function if hue is 1. Then your b[0|a] becomes b[6]. Unfortunately this is not checked in test.html. See my comment above.
@aemkei, please edit the "137 bytes" in index2.js.

@p01
Copy link

p01 commented Nov 10, 2011

I see. My bad.
Didn't realize a was actually in the range [ 0 ; 1 ] not [ 0 ; 1 [

@aemkei
Copy link
Author

aemkei commented Nov 15, 2011

Psst: https://gist.github.com/1362710 - Rubik's Pocket Cube solver in 123 bytes.

@LeverOne
Copy link

// 113

function(a,b,c){a*=6;b=[c-=b*=c<.5?c:1-c,c+a%1*b*2,c+=b*=2,c-a%1*b];return[b[c=~a/.3,c+1.3&3],b[c&3],b[c+2.6&3]]}

http://jsperf.com/hsl2rgb-golf/4

@aemkei
Copy link
Author

aemkei commented Nov 16, 2011

@LeverOne: Not sure if using 0.3, 1.3 and 2.6 will generate correct results for all values.

@maettig
Copy link

maettig commented Nov 17, 2011

I wrote a test to compare the solutions. I'm not sure why, but @LeverOne's works. I need some time to dig down to this. I have the feeling there is an other byte we can save.

About your Rubik's Pocket Cube solver. Yes, it's impressive. Good work. Many new tricks to learn. Just a little problem. The main logic comes from the turn and spin arrays. You can not tell this a "Rubik's Pocket Cube solver in 123 bytes" without counting the arrays. Same problem with all Base64 encoders at 140byt.es.

@LeverOne
Copy link

var hsl_old=function(a,b,c){a*=6;b=[c+=b*=c<.5?c:1-c,c-a%1*b*2,c-=b*=2,c,c+a%1*b,c+b];return[b[~~a%6],b[(a|16)%6],b[(a|8)%6]]}

var hsl_new=function(a,b,c){a*=6;b=[c-=b*=c<.5?c:1-c,c+a%1*b*2,c+=b*=2,c-a%1*b];return[b[c=~a/.3,c+1.3&3],b[c&3],b[c+2.6&3]]}


var  h, s, l;


try{

// test № 1

for (h = 0; h <= 1; h += 0.1){

  for (s = 0; s <= 1; s += 0.3){
    for (l = 0; l <= 1; l += 0.1){
      rgb_old = hsl_old(h, s, l);

      rgb_new = hsl_new(h, s, l);

if(rgb_old[0].toFixed(3)==rgb_new[0].toFixed(3)&&rgb_old[1].toFixed(3)==rgb_new[1].toFixed(3)&&rgb_old[2].toFixed(3)==rgb_new[2].toFixed(3))0;
else throw "something is wrong.";

    }
  }
}


// test № 2

for (h = 0; h <= 1; h = +(h+0.1).toFixed(1)){

  for (s = 0; s <= 1; s = +(s+ 0.3).toFixed(1)){
    for (l = 0; l <= 1; l = +(l+ 0.1).toFixed(1)){
      rgb_old = hsl_old(h, s, l);

      rgb_new = hsl_new(h, s, l);

if(rgb_old[0].toFixed(3)==rgb_new[0].toFixed(3)&&rgb_old[1].toFixed(3)==rgb_new[1].toFixed(3)&&rgb_old[2].toFixed(3)==rgb_new[2].toFixed(3))0;
else throw "something is wrong."


    }
  }
}


alert('We generate correct results for all values.');

}catch(e){alert(e)}



//Because I use the reverse order of values ​​in the array, 
//the error will be the same as in the old version of the hsl2rgb function,
//but in the opposite direction.

alert("hsl:0.02,0.15,0.25\n\nrgb_old:"+hsl_old(0.02,0.15,0.25)+"\n\nrgb_new:"+hsl_new(0.02,0.15,0.25));

alert("hsl:0.02,0.45,0.75\n\nrgb_old:"+hsl_old(0.02,0.45,0.75)+"\n\nrgb_new:"+hsl_new(0.02,0.45,0.75));

@subzey, with fuzzer, joke :) And yes, jsperf's results are questionable. These different versions can not have equal speed in some browsers.

@subzey
Copy link

subzey commented Nov 17, 2011

Another 113 bytes version. Just used rotation of b array by 1:

function(a,b,c){a*=6;b=[c+=b*=c<.5?c:1-c,c,c-a%1*b*2,c-=b*=2,c,c+a%1*b];return[b[-~a%6],b[(4-~a)%6],b[(2-~a)%6]]}

UPD: It seems that @LeverOne's 113 is faster: jsperf test. @LeverOne, how did you do it? :)

@aemkei
Copy link
Author

aemkei commented Jan 27, 2012

Off topic: Have a look at my latest 140byt.es project: The classic Tetris game in 138 bytes!
https://gist.github.com/1672254

@12Me21
Copy link

12Me21 commented Jul 29, 2016

(h,s,l)=>{h*=6;s*=l<.5?l:1-l;H=++h|0;r[2]=0;r[H/2%3|0]=s*2;r[2-H%3]=Math.abs(h%2-1);return r.map(x=>((x+=l-s)<1?x:1)*255|0)}
124 bytes with actual RGB output (0-255). input is 0-1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment