Skip to content

Instantly share code, notes, and snippets.

@Prinzhorn
Forked from 140bytes/LICENSE.txt
Created May 7, 2012 15:55
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Prinzhorn/2628611 to your computer and use it in GitHub Desktop.
Save Prinzhorn/2628611 to your computer and use it in GitHub Desktop.
compress hex color string

compress hex color strings

In hex you use three or six digits to define a color. By replacing all six-digit colors with the closest color which can be expressed with three digits, we save 3 bytes each! Hooray!

http://www.w3.org/TR/CSS2/syndata.html#value-def-color

The three-digit RGB notation (#rgb) is converted into six-digit form (#rrggbb) by replicating digits, not by adding zeros.

Examples:

'00ff00' -> '0f0'

'34cf9d' -> '3c9'

function a(b, c) {
return ++c ?
(("0x" + b) / 17 + .5 | 0).toString(16) :
b.replace(/../g, a)
}
function a(b,c){return++c?(("0x"+b)/17+.5|0).toString(16):b.replace(/../g,a)}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2012 Alexander Prinzhorn (@Prinzhorn) https://github.com/Prinzhorn
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": "hexGolfer",
"description": "Compress hex color strings by using three instead of six digits.",
"keywords": [
"color",
"hex",
"compress"
]
}
<!DOCTYPE html>
<title>Foo</title>
<div style="background:#34cf9d;">Original value: #<b>34cf9d</b></div>
<div style="background:#3c9;">Expected value: #<b>3c9</b></div>
<div>Actual value: #<b id="ret"></b></div>
<script>
// write a small example that shows off the API for your example
// and tests it in one fell swoop.
var myFunction = function a(b,c){return++c?(("0x"+b)/17+.5|0).toString(16):b.replace(/../g,a)}
var ret = document.getElementById( "ret" );
var color = myFunction('34cf9d');
ret.innerHTML = color;
ret.parentNode.style.background = '#' + color;
</script>
@Prinzhorn
Copy link
Author

@williammalo
Nope. The point is to round to the nearest color. Your code returns "011" for "001a1a" but "022" would be closer:
1a = 1*16 + 10 = 26

11 = 1_16 + 1 = 17 -> 26 - 17 = 9
22 = 2_16 + 2 = 34 -> 34 - 26 = 8

8 is smaller than 9, thus "22" is closer to "1a" than "11".

@williammalo
Copy link

@Prinzhorn
Oh! ok.
Back to the proverbial drawing board I guess...

@atk
Copy link

atk commented May 11, 2012

@williammalo: You could try to check if a[1]<8, but I don't know how to do increase a[0] if it's A-F.

@williammalo
Copy link

This is the best thing I came up with...
function(a){return((a="0x"+a)>>20).toString(16)+(a%65536>>12).toString(16)+(a%256>>4).toString(16)}
It's pretty shit, I know. :)

@Prinzhorn
Copy link
Author

It's all about the spirit ;-)

@atk
Copy link

atk commented May 11, 2012

if you change the order of the shifting and filtering (like I did), you'll have smaller numbers, i.e. instead of a%65536>>12, use a>>12&15.

@williammalo
Copy link

@atk
This is the best I can do without making my head explode...
Would you mind explaining to me what "filtering" means? I think it could be useful for me.

function(a){return((a='0x'+a)>>20<<8|a%65536>>12<<4|a%256>>4).toString(16)}

edit: I just want to learn. :)

@williammalo
Copy link

btw, if you like the regex version of the code, I made it shorter:

function(a){return a.replace(/../g,function(a){return("0x"+a>>4).toString(16)})}

@atk
Copy link

atk commented May 11, 2012

filtering: x % 16 == x & 15 - if you ever want to take modulo of a potence of 2, remember it.

Nice one with the replace, even shorter by reusing function:

function a(b,c){return++c?('0x'+b>>4).toString(16):b.replace(/../g,a)}

@Prinzhorn
Copy link
Author

But still, "001a1a" returns "011" instead of "022" ;-)

@atk
Copy link

atk commented May 11, 2012

that's true for both regex versions. However, that could now be remedied easily enough:

function a(b,c){return++c?(+('0x'+b)+7>>4).toString(16):b.replace(/../g,a)}

@atk
Copy link

atk commented May 11, 2012

Here's a commented version:

function a(
  b, // hex color string input, e.g. '001a22' or next matched 2 characters within the replace
  c  // undefined, numeric counter when replace runs
) {
  // c will be an unused numeric inside replace starting with 0, so ++c will result in 1, 
  // which is trueish inside replace and NaN outside replace, which is false-y.
  return ++c ?
    // inside replace: the preceding + will coerce the '0x'... to number, adding 7 does the 
    // rounding and >>4 divides by 16 while flooring the result - and .toString(16) reformats
    // the resulting number to hexadecimal.
    (+('0x' + b) + 7 >> 4).toString(16) :
    // outside the replace: replace each two characters and use own function as callback
    b.replace(/../g, a)
}

@Prinzhorn
Copy link
Author

Now the code returns "099" for "008888" ;-)

@atk
Copy link

atk commented May 11, 2012

fixed, we need to add 7, not 8.

@Prinzhorn
Copy link
Author

Now the code returns "0aa" for "009999".

I can do this all day :-D. But we're getting closer (the test iterates from 000 to fff)

@atk
Copy link

atk commented May 11, 2012

...which is quite correct. the closest color to "009999" is "0aa".

@Prinzhorn
Copy link
Author

You can't get closer to "009999" than "099". Don't confuse this with actual hex.

http://www.w3.org/TR/CSS2/syndata.html#value-def-color

The three-digit RGB notation (#rgb) is converted into six-digit form (#rrggbb) by replicating digits, not by adding zeros.

@atk
Copy link

atk commented May 11, 2012

Ah, I hadn't noticed this. So instead of requiring to round up, we need to rely on your former ("0x"+a)/17+.5|0 - or a smaller bit-math representation of that one, that I'll still have to think up.

@atk
Copy link

atk commented May 14, 2012

I haven't yet found a shorter version, so currently it is

function a(b,c){return++c?(("0x"+b)/17+.5|0).toString(16):b.replace(/../g,a)}

@maettig
Copy link

maettig commented May 18, 2012

Nice one. Reminds me of my CSS Color Converter. I tried to start from scratch but came up with the same solution in the end. Good work. What about spending a few bytes to make this work with values like "#34cf9d" and "#3c9"?

function a(b,c){return++c?(("0x"+b)/17+.5|0).toString(16):b[5]?b.replace(/\w\w/g,a):b}

@Prinzhorn
Copy link
Author

The idea was to have a meta function which finds all /#([0-9a-f]{6})/i inside the CSS file and passes it to this function. Could then be used in stuff like CSS minifiers as an option (because it's destructive).

This could of course all fit in 140 bytes combined ;-)

@maettig
Copy link

maettig commented May 18, 2012

Non-destructive version:

function(a){return a.replace(/(\w)\1(.)\2(.)\3/i,'$1$2$3')}

@subzey
Copy link

subzey commented Jun 9, 2012

This version is slightly shorter, also non-destructive

function(a,b){return(b=a.split(/(.)\1/i))[5]?b.join(''):a}

@Prinzhorn
Copy link
Author

@subzey the point is to be destructive. I already gave '34cf9d' -> '3c9' as an example.

The non-destructive version is a different problem. Maybe this should be separated.

@subzey
Copy link

subzey commented Jun 11, 2012

Oh, sorry. I missed that.

Maybe then something like this?

function(a){return a.split(/(\w)./).join('')}

@maettig
Copy link

maettig commented Jun 11, 2012

No, this will turn F0F0F0 into FFF which is wrong. See similar suggestions in the comments above.

@atk
Copy link

atk commented Jun 11, 2012

If we want this to be completely non-destructive, we can only change #000000 to #000, #000011 to #001, ... #111111 to #111 ... #FFFFFF to #FFF, almost as in Subzey's first approach (which would go wrong on colors like #099330:

function(a){return a.replace(/(\w)\1(\w)\2(\w)\3/,"$1$2$3")}

But as we wanted to get the closest color (as is specified in the readme), the original approach (with function body reusal) works best.

@maettig
Copy link

maettig commented Jun 11, 2012

That's what I suggested a few comments above. Mine is even shorter. ;-) Edit: And I added /i to fetch stuff like FffFFf.

@atk
Copy link

atk commented Jun 12, 2012

Ah, I've seen it; you've substituted the 2 last \w with dots.

@yckart
Copy link

yckart commented Aug 25, 2013

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