#!/usr/bin/perl -w use strict; use lib '.'; sub pi() { 4 * atan2(1, 1) } my $used = {}; sub sift { for (1..50) { my $c = randomColor(); printColor($c, 1); my($match, $s); do { $s = randomColor(); ($match) = colorsAreSimilar($c, $s); } until $match; printColor($s, 1); print "
\n"; } } sub group { for (1..40) { my $c = randomColor(); printColor($c, 1); my @colours = ($c); attempt: for (1..10) { my $s = randomColor(); foreach (@colours) { my($match) = colorsAreSimilar($_, $s); next attempt if $match; } printColor($s, 1); push(@colours, $s); } print "
\n"; } } sub compare { my $tests = [[[68,208,59], [216,251,177]], # DIFFERENT [[255,0,0], [0,75,0]], [[176,16,0], [39,9,197]], [[0,51,242], [172,26,255]], [[114,12,182], [157,0,10]], [[6,36,21], [88,31,0]], [[159,89,1], [0,131,0]], [[221,179,111], [125,212,130]], [[85,85,85], [0,0,0]], [[154,117,252], [253,79,185]], [[199,3,247], [110,16,252]], [[136,2,8], [16,11,11]], [[42,34,160], [0,4,13]], [[135,12,80], [44,47,99]], [[47,31,24], [143,3,66]], [[111,250,44], [228,230,10]], #a [[162,252,157], [215,236,89]], #b [[228,230,10], [111,250,44]], #a again, backwards [[215,236,89], [162,252,157]], #b again, backwards [[64,32,251], [160,72,228]], [[50,214,70], [226,238,190]], [[163,16,39], [38,0,7]], [[77,0,231], [190,43,237]], [[172,206,0], [255,255,239]], [[174,173,219], [227,253,205]], [[226,213,221], [244,134,201]], [], # SAME [[11,246,168], [79,243,160]], [[193,211,194], [200,240,250]], [[0,75,242], [9,19,222]], [[1,39,0], [16,8,46]], [[6,214,120], [60,248,135]], [[203,249,6], [249,251,147], [240,221,45]], [[64,32,251], [128,79,253]], [[95,6,220], [160,72,228]], [[118,216,147], [150,184,88]], # hard to say [[0,125,0], [53,79,41]], # probably best to be the same [[227,255,157], [158,234,52]], [[75,154,229], [75,164,170], [1,184,206]], [[224,23,21], [240,77,18], [186,35,36]], [[186,41,198], [218,50,254], [198,74,178]], [[254,7,207], [218,50,254]], [[0,18,196], [87,1,251]], [[255,115,204], [199,98,175], [236,145,254], [255,66,230], [233,47,155]], [[199,98,175], [202,1,235], [236,145,254], [255,66,230], [233,47,155]], [[0,119,23], [85,98,70]], # definitely the same [[95,6,220], [128,79,253]], [[64,64,64], [128,128,128]], [[2,255,229], [139,248,217]], [[230,223,254], [191,214,214]], [[36,58,173], [13,1,224], [34,0,154], [4,5,93]], [[182,0,1], [218,26,2], [202,0,48]], [[2,254,113], [0,253,5]], [[54,22,41], [32,24,0], [3,0,10]], [[180,0,251], [159,47,252]], [[86,12,92], [102,23,129]]]; foreach my $test (@$tests) { foreach my $color (@$test) { $color = { 'red' => $color->[0], 'green' => $color->[1], 'blue' => $color->[2], 'reason' => '', 'source' => 'T', }; convertsRGBtoHCY($color); } } my $fails = 0; my $wantSame = 0; print '

tests that should all be different

'; foreach my $test (@$tests) { if (not @$test) { die if $wantSame; print '

tests that should all be the same

'; $wantSame = 1; next; } print "
";
        use Data::Dumper;
        print Dumper($test);
        print "
"; my $master = shift @$test; printColor($master, 1, undef, 0); foreach (@$test) { my(@results) = colorsAreSimilar($master, $_); printColor($_, @results, 1); if ($results[0] != $wantSame) { $fails += 1; print "

FAIL"; } } print "


\n"; } warn "$fails failures\n"; print "

$fails failures"; } sub randomColor { my $colours = [ #[1,39,0], [16,8,46], [255,255,255], #[64,32,251], [128,79,253], [68,208,59], [216,251,177], [95,6,220], #[160,72,228], [227,255,157], [158,234,52], [255,0,0], [0,75,0], #[85,85,85], [0,0,0], [randcomp(), randcomp(), randcomp()] ]; my $n = int rand @$colours; my $colour = $colours->[$n]; my $c = { 'red' => $colour->[0], 'green' => $colour->[1], 'blue' => $colour->[2], 'source' => $n == $#$colours ? '???' : $n, 'reason' => '*', }; convertsRGBtoHCY($c); return $c; } sub printColor { my($c, $left, $data, $printData) = @_; print "

$data
" if $printData; printf "
%s
\n", $left ? 'left' : 'right', $c->{red}, $c->{green}, $c->{blue}, $c->{source} . ' ' . $c->{reason}; } my $maxH = 160; sub colorsAreSimilar { my($c, $s) = @_; my $reqDRGB = 90; my $reqDYdiff = 70; my $reqDMS = 100; my $reqGrayDC = 50; my $reqGrayMC = 35; my $reqGrayDMDiff = 150; my $reqGrayDMSame = 100; my $reqContrastDC = 90; my $reqContrastDM = 40; my $reqMinC = 20; my $reqDC = 90; my $reqDRGBDYCombined = 75; my $reqDHreallySame = 2; my $reqDCDY = 170; my $reqDHsame = 6; my $DRGB = abs($c->{red}-$s->{red}) + abs($c->{green}-$s->{green}) + abs($c->{blue}-$s->{blue}); my $DH = abs($s->{H}-$c->{H}); if ($DH > $maxH/2) { $DH = $maxH - $DH; } my $DC = abs($s->{C}-$c->{C}); my $MC = ($s->{C}+$c->{C})/2; my $DM = abs($s->{M}-$c->{M}); my $DMS = abs($s->{M}**2-$c->{M}**2)/255; my $MM = ($s->{M}+$c->{M})/2; my $DY = abs($s->{"Y'"}-$c->{"Y'"}); my $DRGBDYCombined = $DRGB + $DY; my $DCDY = $DC + $DY; my $minC = $c->{C} < $s->{C} ? $c->{C} : $s->{C}; my $output = <$reqDMS diff) DRGBDY: $DRGBDYCombined (<$reqDRGBDYCombined same) DC:$DC<$reqGrayDC and MC:$MC<$reqGrayMC and DM:$DM>$reqGrayDMDiff (different grays MCG) DC:$DC<$reqGrayDC and MC:$MC<$reqGrayMC and DM:$DM<$reqGrayDMSame (same grays MCG) DC:$DC>$reqContrastDC and and DM:$DM>$reqContrastDM (different contrast 1 MCC) minC:$minC<$reqMinC and DC:$DC>$reqDC (bright contrast - diff - BC) DH:$DH (≤$reqDHreallySame same) DCDY:$DCDY>$reqDCDY (diff) (CY) DH:$DH (≤$reqDHsame same) END if ($DM < 128 and $MM < 128) { $DH *= ($MM/128)**2; } $output .= "new DH: $DH after scaling for MM $MM (0..128 goes down but only if DM $DM is less than 128)\n"; $s->{reason} = ""; $used->{$s->{reason}} += 1; if ($DRGB < $reqDRGB) { $s->{reason} = "<RGB"; $used->{$s->{reason}} += 1; return 1, $output; } if ($DMS > $reqDMS) { $s->{reason} = ">DMS"; $used->{$s->{reason}} += 1; return 0, $output; } if ($DY > $reqDYdiff) { $s->{reason} = ">DY"; $used->{$s->{reason}} += 1; return 0, $output; } if ($DRGBDYCombined < $reqDRGBDYCombined) { $s->{reason} = "<RY"; $used->{$s->{reason}} += 1; return 1, $output; } if ($DC < $reqGrayDC and $DM < $reqGrayDMSame and $MC < $reqGrayMC) { $s->{reason} = "<MCG"; $used->{$s->{reason}} += 1; return 1, $output; } # if ($DC < $reqGrayDC and $DM > $reqGrayDMDiff and $MC < $reqGrayMC) { # $s->{reason} = ">MCG"; # $used->{$s->{reason}} += 1; # return 0, $output; # } if ($DC > $reqContrastDC and $DM > $reqContrastDM) { $s->{reason} = ">MCC"; $used->{$s->{reason}} += 1; return 0, $output; } if ($minC < $reqMinC and $DC > $reqDC) { $s->{reason} = "minC"; $used->{$s->{reason}} += 1; return 0, $output; } if ($DH <= $reqDHreallySame) { $s->{reason} = "<DH!"; $used->{$s->{reason}} += 1; return 1, $output; } if ($DCDY > $reqDCDY) { $s->{reason} = ">CY"; $used->{$s->{reason}} += 1; return 0, $output; } if ($DH <= $reqDHsame) { $s->{reason} = "<DH"; $used->{$s->{reason}} += 1; return 1, $output; } $s->{reason} = "?"; $used->{$s->{reason}} += 1; return 0, $output; } sub randcomp { return (cos(pi*rand(1))+1)*128; } sub convertsRGBtoHCY { my($c) = @_; my $R = $c->{red}; my $G = $c->{green}; my $B = $c->{blue}; my $M = $R > $G ? $R > $B ? $R : $B > $G ? $B : $G : $B > $G ? $B : $G; my $m = $R < $G ? $R < $B ? $R : $B < $G ? $B : $G : $B < $G ? $B : $G; my $C = $M - $m; my $H; if ($C == 0) { $H = 0; } else { if ($M == $R) { $H = 60 * ((($G-$B)/$C) + 0); } elsif ($M == $G) { $H = 60 * ((($B-$R)/$C) + 2); } else { # ($M == $B) $H = 60 * ((($R-$G)/$C) + 4); } } $H += 360 if $H < 0; die if $H < 0; die if $H > 360; $c->{'H-original'} = $H; $c->{C} = $C; $c->{M} = $M; $c->{H} = adjustHue($H); # Luma (Y') with sRGB (Rec. 709) primaries: # http://en.wikipedia.org/wiki/HSL_and_HSV#Lightness $c->{"Y'"} = 0.21*$R + 0.72*$G + 0.07*$B; } my $hueClipInput = []; my $hueClipOutput = []; my $maxAdjustedHue = 360; sub initHueAdjuster { my $hueClipBoundaries = [[0, 15], [55, 70], [75, 85], [90, 150], [160, 170], [185, 205], [230, 250], [255, 265], [270, 280], [290, 325]]; die unless $hueClipBoundaries->[0]->[0] == 0; # algorithm would have to be different if not true my $currentMax = 0; my $lastMax = 0; foreach my $subrange (@$hueClipBoundaries) { push(@$hueClipInput, $subrange->[0]); push(@$hueClipInput, $subrange->[1]); $maxAdjustedHue -= $subrange->[1] - $subrange->[0]; $currentMax += $subrange->[0] - $lastMax; push(@$hueClipOutput, $currentMax); $lastMax = $subrange->[1]; } } initHueAdjuster(); sub adjustHue { my($hue) = @_; my $min = 0; my $max = scalar @$hueClipInput; my $pos = int($max/2); while ($pos > $min) { if ($hue < $hueClipInput->[$pos]) { $max = $pos; } else { $min = $pos; } $pos = $min + int(($max - $min) / 2); } if ($pos % 2 == 0) { $hue = $hueClipOutput->[$pos/2]; } else { $hue = ($hue - $hueClipInput->[$pos]) + $hueClipOutput->[int($pos/2)]; } return $hue; } print < Test TOP print '

colours that are the same as each other

'; sift(); print '

colours that are different from each other

'; group(); print '

test patterns

'; compare(); print '

results

';
print Dumper($used);