Friday, April 15, 2005

Shorter JS Iron Chef!

The challenge: write a JS function that takes a given hex color and returns a suitably-contrasted greyscale shade, in as few lines of JS as possible.

My first pass went like this:

function contrast(c) {
    var av = (parseInt(c.substring(1,3),16) +
              parseInt(c.substring(3,5),16) +
              parseInt(c.substring(5,7),16)) / 3;
    av = (av >= 100)? av-100:av+100;
    return "rgb(" + av +"," + av + "," + av + ")";
}

A quick overview of what's happening here. c is the 6-character hex color (sans the "#"). The first JS statement uses the substring function to break the hex color string into its three 2-character hex pairs, which are then passed to parseInt to convert them to decimal values. Then we find the average by adding them and dividing by 3.

So, FF0000 (pure red) becomes FF+00+00 becomes 255+0+0 becomes 255/3 becomes 85.

The function then looks at this average and, using 100 as its "bias" point, either adds or subtracts 100 (an arbitrary value that produced the best results for our needs). The last line returns a greyscale RGB value (perfectly valid in CSS! No need to convert back to hex!).

Now the fun: I sent this off to another webdev for review and challenged him to make it even shorter. He came back with this:

function f(c,n) {return parseInt(c.substring(n,n+2),16);}

function contrast(c) {
    var av=(f(c,1)+f(c,2)+f(c,3))/3;
    av+=100*(av>=100)?-1:1;
    return "rgb("+av+","+av+","+av+")";
}

Nice, but I pointed out that av wasn't being calculated correctly without an additional set of parenthesis. Plus, we could shave off a few characters by replacing substring with the shorter substr to the same effect. I countered with this:

function f(c,n) {return parseInt(c.substr(n,2),16);}

function x(c) {
    var av=(f(c,1)+f(c,3)+f(c,5))/3;
    av+=(av>=100)?-100:100;
    return "rgb("+av+","+av+","+av+")";
}

Of course, I hadn't thought to remove the whitespace yet :) His riposte:

function f(c,n){return parseInt(c.substr(n,2),16);}
function x(c){var a=(f(c,1)+f(c,3)+f(c,5))/3;c=100;
  a+=(a>c)?-c:c;c=",";return "rgb("+a+c+a+c+a+")";}

Bested but not broken by his ingenious re-use of local variables, I nonetheless proclaimed victory by deleting the requirement for such a JS function from the spec, reducing the necessary code to:


Don't ever let it be said I don't think outside the box!

9 Comments:

At 8:00 AM, Anonymous Anonymous said...

I think there is a better way to calculate the grayscale value of a color. The human eye is much more receptive for the green component of a color than for the red and blue components, which is why the grayscale value should not be a simple mean. Instead do something like this:

gray = 0.3 * red + 0.59 * green + 0.11 * blue

 
At 3:18 PM, Anonymous Anonymous said...

function f(c,n){return parseInt(c.substr(n,2),16)}
function x(c){var a=(f(c,1)+f(c,3)+f(c,5))/3;c=100;
a+=(a>c)?-c:c;c=",";return "rgb("+a+c+a+c+a+")"}

 
At 11:55 PM, Blogger scottandrew said...

The judges accept your entry, Ken, despite that leaving off the semicolons is Just Wrong(TM). Have you no shame? ;)

 
At 9:33 AM, Anonymous Anonymous said...

Henning's right. Here's a version with tweakable colour weightings. It's also somewhat smaller due to using integer maths instead of substrings.

function x(c){c=parseInt(c.substr(1),16); c=(c>>16)*0.3+(c>>8&255)*0.5+(c&255)*0.2;
c+=c<100?100:-100;return'rgb('+c+','+c+','+c+')';}

 
At 4:33 PM, Blogger scottandrew said...

Guys, I should have clarified: by "suitably contrasting color" I meant "low-contrast," so we were intentionally going for something that was more difficult to see against the base color. Sorry, should have mentioned that.

Andrew wins with his crazy mathses.

 
At 9:01 PM, Blogger markireland said...

Do you know eric meyers colourBlender?

I need one that produces only 'websmart' colors.

http://www.realsimplcms.com

 
At 5:44 PM, Blogger Unknown said...

Man there is alot of comment spam I have noticed. Is there any way to remove it from the blogs?

 
At 8:21 AM, Blogger Unknown said...

Good post

 
At 7:16 AM, Blogger HiiFii Webservices said...

I wanted to recommend this blog owner to use Adsense on his Blog.
He could have made 100$/Day.
Good Blog
I wanted to show you some superb resourses on the net.
Learn to earn 90000$/Month
For which you may also see my Personal Website
Here.
and for a Personal Education Career Tools
free Study Database.
This site is for seeing the
Hifi Electronics.
And this is for
World Class Gadgets

 

Post a Comment

<< Home