hushhush has asked for the wisdom of the Perl Monks concerning the following question:

Hi all

This has been driving me crazy, can anyone offer any advice, maybe a snippet, on how to convert a hex color code to it's nearest color safe counterpart?

I'm parsing css sheets and need to convert them all, the part I'm stuck on is given not color safe hex code, converting to nearest color safe.

This turned out to be surprisingly hard for me to do :/. I guess this means my math with perl sucks :(

thanks
Daniel
  • Comment on Converting hex colors to color safe hex

Replies are listed 'Best First'.
Re: Converting hex colors to color safe hex
by kyle (Abbot) on Apr 11, 2008 at 16:23 UTC

    I take it you have hex colors such as "ab1289" where "ab" is the red value, "12" is the blue value, and "89" is the green value. You want to match these up to the closest analog on some approved list (safe colors).

    I'd probably start by writing something that would take such a color and break it into three decimal values using hex. I could store them in a hash mapping the original hex to an array reference with the numbers.

    Then, given an unsafe color, compare it using the absolute value (abs) of the difference of the respective values (i.e., abs( $unsafe_red - $safe_red )). You'll get three differences (red, green, blue).

    You might want the color that has the smallest combined difference (the minimum of $red_diff+$blue_diff+$green_diff), or you might want to pick a color where the individual differences are small (the minimum of max( $red_diff, $blue_diff, $green_diff )). You can find max() written for you in List::Util. I'm not sure which of those will give better results, but either one should get you "close".

      I think you're overthinking the problem. What you're outlining makes sense if you're trying to get a minimal set of colors (adaptive pallet in Adobe-speak), but for going to websafe sets, rounding each color component individually will get the correct result. So you can do something along the lines of int($val/51+0.5)*51 to get yourself to the set of hex values 00, 33, 66, 99, CC, FF.

      But are websafe colors even an issue any more? Who's using 256-color displays these days? This is one of those holdovers from the early days of the web which don't make sense. On the other hand, if you're going to a GIF, then you may want to think about using a proper clustering algorithm to get to the optimal color set. I'd suggest looking at Algorithm::Cluster for that task.

      Donald Hosek, Tech Lead at oversee.net
      L.A. perl people, we're hiring.

      thanks for the replies yes, I meant web safe colors. I'd give some example code except I keep deleting it because it's horribly wrong. Getting stuck.

      Basically I"m trying to do given hex color input as so: EEEEEE
      Output should be nearest hex color from the 216 web safe palette: FFFFFF

      I'll see if I can get further with what you've supplied, thanks again.
Re: Converting hex colors to color safe hex
by radiantmatrix (Parson) on Apr 11, 2008 at 16:22 UTC

    Do you perhaps mean "web safe" instead of "color safe"? If so, it's a pretty easy approach.

    Each element of the color is 1 byte (0-255 unsigned), and expressed in hex. All you have to do is round the values of each byte to the nearest in the "web safe" color chart. (with some bounds-checking, of course).

    Show us some code on what you've tried, perhaps trying to implement that kind of solution, and you'll get better help. Check out How (Not) To Ask A Question for tips on getting the best help here.

    <radiant.matrix>
    Ramblings and references
    The Code that can be seen is not the true Code
    I haven't found a problem yet that can't be solved by a well-placed trebuchet
Re: Converting hex colors to color safe hex
by dwm042 (Priest) on Apr 11, 2008 at 18:24 UTC
    This is a bit of code based on kyle's analysis of the problem (I admit, I couldn't resist coding it), coupled with a bit chart from the Wikipedia article on web colors. For most colors, I think most metrics yield the same results:

    #!/usr/bin/perl use warnings; use strict; use List::Util qw/ max /; my %chart = (); while(<DATA>) { next if $_ =~ /^#/; my @colors = split; for my $col (@colors) { $chart{$col}{first} = triplet("first", $col); $chart{$col}{mid} = triplet("mid" , $col); $chart{$col}{last} = triplet("last" , $col); } } print "Enter a color value : "; my $reply = <STDIN>; chomp($reply); my $added = substr( $reply . "000000" , 0, 6 ); my $ssr = ssr($added); my $scd = scd($added); my $minmax = minmax($added); print "Closest variance fit is: $ssr\n"; print "Closest comdist fit is: $scd\n"; print "Closest minmax fit is: $minmax\n"; sub triplet { my $type = shift; my $data = shift; my $len = length($data); my $index = 0; $index = 0 if ( $type eq "first" ); $index = 1 if ( $type eq "mid" ); $index = 2 if ( $type eq "last" ); my $value = substr( $data, $index, 1 ); my $trip = hex($value . $value); return $trip; } sub ssr { my $sample = shift; my $first = substr($sample,0,2); my $mid = substr($sample,2,2); my $last = substr($sample,4,2); my $closest = 10000000; my $closest_value = "000"; for ( sort keys %chart ) { my $delta_first = (hex($first) - $chart{$_}{first})**2; my $delta_mid = (hex($mid) - $chart{$_}{mid})**2; my $delta_last = (hex($last) - $chart{$_}{last})**2; my $ssr = $delta_first + $delta_mid + $delta_last; if ( $ssr < $closest ) { $closest = $ssr; $closest_value = $_; } } return sixcolor($closest_value); } sub scd { my $sample = shift; my $first = substr($sample,0,2); my $mid = substr($sample,2,2); my $last = substr($sample,4,2); my $closest = 10000000; my $closest_value = "000"; for ( sort keys %chart ) { my $delta_first = abs(hex($first) - $chart{$_}{first}); my $delta_mid = abs(hex($mid) - $chart{$_}{mid}); my $delta_last = abs(hex($last) - $chart{$_}{last}); my $scd = $delta_first + $delta_mid + $delta_last; if ( $scd < $closest ) { $closest = $scd; $closest_value = $_; } } return sixcolor($closest_value); } sub minmax { my $sample = shift; my $first = substr($sample,0,2); my $mid = substr($sample,2,2); my $last = substr($sample,4,2); my $closest = 1000; my $closest_value = "000"; for ( sort keys %chart ) { my $delta_first = abs(hex($first) - $chart{$_}{first}); my $delta_mid = abs(hex($mid) - $chart{$_}{mid}); my $delta_last = abs(hex($last) - $chart{$_}{last}); my $minmax = max($delta_first,$delta_mid,$delta_last); if ( $minmax < $closest ) { $closest = $minmax; $closest_value = $_; } } return sixcolor($closest_value); } sub sixcolor { my $col = shift; my $len = length($col); my $trip = ""; for my $i ( 1 .. $len ) { my $value = substr( $col, $i - 1, 1 ); $trip .= $value . $value; } return $trip; } __DATA__ # Web-Safe Colors # (from the Wikipedia article named "web colors") # 000 300 600 900 C00 F00 003 303 603 903 C03 F03 006 306 606 906 C06 F06 009 309 609 909 C09 F09 00C 30C 60C 90C C0C F0C 00F 30F 60F 90F C0F F0F 030 330 630 930 C30 F30 033 333 633 933 C33 F33 036 336 636 936 C36 F36 039 339 639 939 C39 F39 03C 33C 63C 93C C3C F3C 03F 33F 63F 93F C3F F3F 060 360 660 960 C60 F60 063 363 663 963 C63 F63 066 366 666 966 C66 F66 069 369 669 969 C69 F69 06C 36C 66C 96C C6C F6C 06F 36F 66F 96F C6F F6F 090 390 690 990 C90 F90 093 393 693 993 C93 F93 096 396 696 996 C96 F96 099 399 699 999 C99 F99 09C 39C 69C 99C C9C F9C 09F 39F 69F 99F C9F F9F 0C0 3C0 6C0 9C0 CC0 FC0 0C3 3C3 6C3 9C3 CC3 FC3 0C6 3C6 6C6 9C6 CC6 FC6 0C9 3C9 6C9 9C9 CC9 FC9 0CC 3CC 6CC 9CC CCC FCC 0CF 3CF 6CF 9CF CCF FCF 0F0 3F0 6F0 9F0 CF0 FF0 0F3 3F3 6F3 9F3 CF3 FF3 0F6 3F6 6F6 9F6 CF6 FF6 0F9 3F9 6F9 9F9 CF9 FF9 0FC 3FC 6FC 9FC CFC FFC 0FF 3FF 6FF 9FF CFF FFF
    Some results are:

    C:\Code>perl web_safe_colors.pl Enter a color value : 1234 Closest variance fit is: 003300 Closest comdist fit is: 003300 Closest minmax fit is: 003300 C:\Code>perl web_safe_colors.pl Enter a color value : 712390 Closest variance fit is: 663399 Closest comdist fit is: 663399 Closest minmax fit is: 663399 C:\Code>perl web_safe_colors.pl Enter a color value : 576015 Closest variance fit is: 666600 Closest comdist fit is: 666600 Closest minmax fit is: 666600