Friday, May 21, 2010

Making a Colorpicker

The other day I've been implementing this colorpicker widget for RightJS and now I'd like to share some mathematics and mechanics behind the widget.

I won't bore you to death describing the actual layout, just some necessary theory and math. And I also will use RightJS semantics. Well, just because I love it.

The Color Theory

Lets start with a bit of a theory. Every giggling school girl those days knows that any color can be created by a proper mix of three basic colors, red, green and blue. That's a brilliant idea, but the problem is that not many people actually can use it. Personally me, I don't know anyone who can instantly say how much of every color you need to take to create say a color of coffee and milk.

And because of this problem the humanity created another, more natural way of color picking which is based on another three dimensions: tone, saturation and brightness. And the main idea of the colorpicker widget is to convert one system into another.

The Tone Parameter

The tone picker usually looks like a vertical bar and represents the following system of colors.



The principle is that with this tone scale you can have any tints available with full saturation and brightness. Note also that in every point of the scale, one of the colors is always has a zero value, and at least one of them has the full value.

Keep also in mind that this scale is some sort of closed circle, the end on the right is actually joined with the beginning on the left.

The Saturation And Brightness

The saturation and brightness field usually looks like that.



The saturation parameter is zero at the left side and have the full value on the right. The brightness is full at the top and zero at the bottom. This way, you will always have the white color at the top left corner, your current tint color at the top-right corner and a black line at the bottom side.

With those three handlers you also can chose any visible color but in more natural way.

Converting TSB into RGB

The first task of the colorpicker is to convert the tint, saturation and brightness parameters which the user picks into the actual RGB value.

It was proven that the best way of keeping your values in this case is if you keep your TSB parameters in float values of range 0..1 and RGB values in integers of range 0..255. This way you won't have systematic round up errors and it will be working faster.

So your TSB -> RGB script will look like that
var tint = [1, 0.5, 0];
var saturation = 0.6;
var brightness = 0.4;

var color = [0, 0, 0];

for (var i=0; i < 3; i++) {
color[i] = 1 + saturation * (tint[i] - 1);
color[i] = (color[i] * brightness * 255).round();
}
The principle is simple, first we combine the tint and saturation which will give as a raw color in range 0..1, then we apply the brightness parameter and convert the float value into a 0..255 integer.

Converting RGB into STB

The backward conversion is a bit trickier. I've drown a simple picture to help you understand the principle



Say you have some RGB color with values in range of 0..255. Remember our tint picture, where one value is always 1 and another is always 0. So this smaller area between the green and red values is the same thing, if you take it and normalize to the 0..1 range you'll have your clear tint values. The brightness parameter will be the proportion between the brightest color and the 255 value, and your saturation is relation between the minimal color to the maximum.

The script looks like that
// the initial RGB value
var color = [200, 100, 50];

// finding the minimal and maximal values
var color_sorted = color.clone().sort(function(a,b) { return a-b; });
var max = color_sorted[2];
var min = color_sorted[0];

// calculating the brightness and saturation
var brightness = max / 255;
var saturation = 1 - min / (max || 1);

// calculating the tint
var tint = [0, 0, 0];
for (var i=0; i < 3; i++) {
tint[i] = ((!min && !max) || min == max) ? i == 0 ? 1 : 0 :
(color[i] - min) / (max - min);
}
It is pretty much straight forward, but we also have some failsafe conditions like (max || 1) and (!min && !max) in case if all the RGB values are zeros.


HEX to RGB Conversions

And couple more things which you also will need. Converting your 0..255 colors array into HEX and RGB formatted strings back and forth.

Converting an array to a HEX value is simple
'#'+ color.map(function(c) {
return (c < 16 ? '0' : '') + c.toString(16);
}).join('');

Converting RGB string into an array of values looks like that
if (match = /rgb\((\d+),(\d+),(\d+)\)/.exec(value)) {
return [match[1], match[2], match[3]].map('toInt');
}

And converting any HEX color into an array can be done like that
// converting the shortified hex in to the full-length version
if (match = /^#([\da-f])([\da-f])([\da-f])$/.exec(value))
value = '#'+match[1]+match[1]+match[2]+match[2]+match[3]+match[3];

if (match = /#([\da-f]{2})([\da-f]{2})([\da-f]{2})/.exec(value)) {
return [match[1], match[2], match[3]].map('toInt', 16);
}

Well, that all you need to know to make one cool colorpicker of yours.
Enjoy!

No comments: