Friday, August 8, 2014

JavaScript Colors: RGB to HEX

In my web comic editor, I needed to be able to both read and set the background color values on HTML elements. Since I've been working with colors as hex codes since, umm, forever, I thought it would be good to work with the colors that way. But to my dismay, I found that you can set the color of an element using a hex string (e.g. #ff0000), an RGB string (e.g. rgb(255,0,0)), or an HSL string (e.g. hsl(360,100%,50%)) but when you read the value it is always in RGB format. Here's an example:

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Color Test</title>
</head>

<body>

<div id="hextest" style="background-color: #FF0000; width: 100px; height: 100px;"></div>

<div id="rgbtest" style="background-color: rgb(0,255,0); width: 100px; height: 100px;"></div>

<div id="hsltest" style="background-color: hsl(240,100%,50%); width: 100px; height: 100px;"></div>

</body>

<script>

var a = document.getElementById('hextest').style.backgroundColor;
var b = document.getElementById('rgbtest').style.backgroundColor;
var c = document.getElementById('hsltest').style.backgroundColor;

console.log(a);
console.log(b);
console.log(c);

</script>

</html>


Maybe you've had this problem yourself. Fortunately, I found a handy JavaScript function that will convert RGB strings back into hex. It is a very compact and interesting function. Let's take a look at it.

function rgb2hex(rgb) {
 
 function hex(x) {
  return ("0" + parseInt(x).toString(16)).slice(-2);
 }
 var r = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
 var h = '#' + hex(r[1]) + hex(r[2]) + hex(r[3]);
 return h.toUpperCase();

}

There's a lot of good JavaScript to learn about here. First, you may notice that the function has another function declared inside of it. The inner function, hex, is essentially private to the rgb2hex function the same as a variable definition would be. It's like an anonymous inner function that isn't so anonymous. So what's the hex function doing?

It assumes, first of all, that you give it a String representation of an integer. Like "35" or something. The parseInt() function turns that into a JavaScript Number so that you can call toString(16) on it. What does that do, you ask? The parameter "16" is the radix. Without going into too much detail about what a radix is, just know that using radix 2 will give you the number's value in binary, radix 8 will give you the number's value in octal, and radix 16 gives you the number's value in hexadecimal. So calling toString(16) on a Number object with value 28 will give you the String "1c". But what's up with the fact that "0" is prepended to that value and then the whole thing is sliced with a slice parameter of -2? That's just a simple way of making sure that the return value of the function is always 2 characters and is left-padded with zeros. If the input to the function is less than 16, then the toString(16) call will return a String that is only 1 character long. But if the input is greater than 15, the result will be at least 2 characters. So if we always put a "0" in front and then just return the last two characters of the String, it will ensure that the hex value is 0-padded. Using slice() with a negative value as the parameter is a trick that returns the characters from the end of the String instead of the beginning. For example, "whatnot".slice(-3) returns "not".

Wow, that's quite a lot going on in that one line of JavaScript. The rest of the rgb2hex function isn't quite so tricky. Line 6 just uses a regular expression to find the 3 numbers in the RGB string. For example, with an input String of "rgb(100,200,150)" the value for r will be an array containing the values "100", "200", and "150". Line 7 just uses those three values as parameters to the hex function and sticks the "#" on the front. The last line returns the complete value in all upper case, since I like #FFFFFF better than #ffffff.

Anyway, I hope this function is as helpful to you as it was to me.

Amphibian.com comic for August 8, 2014