5 use vars qw(@ISA @EXPORT_OK $VERSION);
35 sub diff_text_with_nul {
36 my ($desc, $text1, $text2, @params) = @_;
38 my $builder = Test::Builder->new;
41 my $imbase = Imager->new(xsize => 100, ysize => 100);
42 my $imcopy = Imager->new(xsize => 100, ysize => 100);
44 $builder->ok($imbase->string(x => 5, 'y' => 50, size => 20,
46 @params), "$desc - draw text1");
47 $builder->ok($imcopy->string(x => 5, 'y' => 50, size => 20,
49 @params), "$desc - draw text2");
50 $builder->isnt_num(Imager::i_img_diff($imbase->{IMG}, $imcopy->{IMG}), 0,
51 "$desc - check result different");
54 sub is_color3($$$$$) {
55 my ($color, $red, $green, $blue, $comment) = @_;
57 my $builder = Test::Builder->new;
59 unless (defined $color) {
60 $builder->ok(0, $comment);
61 $builder->diag("color is undef");
64 unless ($color->can('rgba')) {
65 $builder->ok(0, $comment);
66 $builder->diag("color is not a color object");
70 my ($cr, $cg, $cb) = $color->rgba;
71 unless ($builder->ok($cr == $red && $cg == $green && $cb == $blue, $comment)) {
84 sub is_color_close3($$$$$$) {
85 my ($color, $red, $green, $blue, $tolerance, $comment) = @_;
87 my $builder = Test::Builder->new;
89 unless (defined $color) {
90 $builder->ok(0, $comment);
91 $builder->diag("color is undef");
94 unless ($color->can('rgba')) {
95 $builder->ok(0, $comment);
96 $builder->diag("color is not a color object");
100 my ($cr, $cg, $cb) = $color->rgba;
101 unless ($builder->ok(abs($cr - $red) <= $tolerance
102 && abs($cg - $green) <= $tolerance
103 && abs($cb - $blue) <= $tolerance, $comment)) {
104 $builder->diag(<<END_DIAG);
105 Color out of tolerance ($tolerance):
106 Red: expected $red vs received $cr
107 Green: expected $green vs received $cg
108 Blue: expected $blue vs received $cb
116 sub is_color4($$$$$$) {
117 my ($color, $red, $green, $blue, $alpha, $comment) = @_;
119 my $builder = Test::Builder->new;
121 unless (defined $color) {
122 $builder->ok(0, $comment);
123 $builder->diag("color is undef");
126 unless ($color->can('rgba')) {
127 $builder->ok(0, $comment);
128 $builder->diag("color is not a color object");
132 my ($cr, $cg, $cb, $ca) = $color->rgba;
133 unless ($builder->ok($cr == $red && $cg == $green && $cb == $blue
134 && $ca == $alpha, $comment)) {
135 $builder->diag(<<END_DIAG);
148 sub is_fcolor4($$$$$$;$) {
149 my ($color, $red, $green, $blue, $alpha, $comment_or_diff, $comment_or_undef) = @_;
150 my ($comment, $mindiff);
151 if (defined $comment_or_undef) {
152 ( $mindiff, $comment ) = ( $comment_or_diff, $comment_or_undef )
155 ( $mindiff, $comment ) = ( 0.001, $comment_or_diff )
158 my $builder = Test::Builder->new;
160 unless (defined $color) {
161 $builder->ok(0, $comment);
162 $builder->diag("color is undef");
165 unless ($color->can('rgba')) {
166 $builder->ok(0, $comment);
167 $builder->diag("color is not a color object");
171 my ($cr, $cg, $cb, $ca) = $color->rgba;
172 unless ($builder->ok(abs($cr - $red) <= $mindiff
173 && abs($cg - $green) <= $mindiff
174 && abs($cb - $blue) <= $mindiff
175 && abs($ca - $alpha) <= $mindiff, $comment)) {
176 $builder->diag(<<END_DIAG);
189 sub is_fcolor1($$$;$) {
190 my ($color, $grey, $comment_or_diff, $comment_or_undef) = @_;
191 my ($comment, $mindiff);
192 if (defined $comment_or_undef) {
193 ( $mindiff, $comment ) = ( $comment_or_diff, $comment_or_undef )
196 ( $mindiff, $comment ) = ( 0.001, $comment_or_diff )
199 my $builder = Test::Builder->new;
201 unless (defined $color) {
202 $builder->ok(0, $comment);
203 $builder->diag("color is undef");
206 unless ($color->can('rgba')) {
207 $builder->ok(0, $comment);
208 $builder->diag("color is not a color object");
212 my ($cgrey) = $color->rgba;
213 unless ($builder->ok(abs($cgrey - $grey) <= $mindiff, $comment)) {
216 Gray: $cgrey vs $grey
224 sub is_fcolor3($$$$$;$) {
225 my ($color, $red, $green, $blue, $comment_or_diff, $comment_or_undef) = @_;
226 my ($comment, $mindiff);
227 if (defined $comment_or_undef) {
228 ( $mindiff, $comment ) = ( $comment_or_diff, $comment_or_undef )
231 ( $mindiff, $comment ) = ( 0.001, $comment_or_diff )
234 my $builder = Test::Builder->new;
236 unless (defined $color) {
237 $builder->ok(0, $comment);
238 $builder->diag("color is undef");
241 unless ($color->can('rgba')) {
242 $builder->ok(0, $comment);
243 $builder->diag("color is not a color object");
247 my ($cr, $cg, $cb) = $color->rgba;
248 unless ($builder->ok(abs($cr - $red) <= $mindiff
249 && abs($cg - $green) <= $mindiff
250 && abs($cb - $blue) <= $mindiff, $comment)) {
251 $builder->diag(<<END_DIAG);
264 my ($color, $grey, $comment) = @_;
266 my $builder = Test::Builder->new;
268 unless (defined $color) {
269 $builder->ok(0, $comment);
270 $builder->diag("color is undef");
273 unless ($color->can('rgba')) {
274 $builder->ok(0, $comment);
275 $builder->diag("color is not a color object");
279 my ($cgrey) = $color->rgba;
280 unless ($builder->ok($cgrey == $grey, $comment)) {
281 $builder->diag(<<END_DIAG);
283 Grey: $grey vs $cgrey
292 my $green=Imager::i_color_new(0,255,0,255);
293 my $blue=Imager::i_color_new(0,0,255,255);
294 my $red=Imager::i_color_new(255,0,0,255);
296 my $img=Imager::ImgRaw::new(150,150,3);
298 Imager::i_box_filled($img,70,25,130,125,$green);
299 Imager::i_box_filled($img,20,25,80,125,$blue);
300 Imager::i_arc($img,75,75,30,0,361,$red);
301 Imager::i_conv($img,[0.1, 0.2, 0.4, 0.2, 0.1]);
307 my $green = Imager::Color->new(0, 255, 0, 255);
308 my $blue = Imager::Color->new(0, 0, 255, 255);
309 my $red = Imager::Color->new(255, 0, 0, 255);
310 my $img = Imager->new(xsize => 150, ysize => 150);
311 $img->box(filled => 1, color => $green, box => [ 70, 24, 130, 124 ]);
312 $img->box(filled => 1, color => $blue, box => [ 20, 26, 80, 126 ]);
313 $img->arc(x => 75, y => 75, r => 30, color => $red);
314 $img->filter(type => 'conv', coef => [ 0.1, 0.2, 0.4, 0.2, 0.1 ]);
320 my $green = Imager::Color->new(0, 255, 0, 255);
321 my $blue = Imager::Color->new(0, 0, 255, 255);
322 my $red = Imager::Color->new(255, 0, 0, 255);
323 my $img = Imager->new(xsize => 150, ysize => 150, bits => 16);
324 $img->box(filled => 1, color => $green, box => [ 70, 25, 130, 125 ]);
325 $img->box(filled => 1, color => $blue, box => [ 20, 25, 80, 125 ]);
326 $img->arc(x => 75, y => 75, r => 30, color => $red);
327 $img->filter(type => 'conv', coef => [ 0.1, 0.2, 0.4, 0.2, 0.1 ]);
332 sub test_image_double {
333 my $green = Imager::Color->new(0, 255, 0, 255);
334 my $blue = Imager::Color->new(0, 0, 255, 255);
335 my $red = Imager::Color->new(255, 0, 0, 255);
336 my $img = Imager->new(xsize => 150, ysize => 150, bits => 'double');
337 $img->box(filled => 1, color => $green, box => [ 70, 25, 130, 125 ]);
338 $img->box(filled => 1, color => $blue, box => [ 20, 25, 80, 125 ]);
339 $img->arc(x => 75, y => 75, r => 30, color => $red);
340 $img->filter(type => 'conv', coef => [ 0.1, 0.2, 0.4, 0.2, 0.1 ]);
345 sub _low_image_diff_check {
346 my ($left, $right, $comment) = @_;
348 my $builder = Test::Builder->new;
350 unless (defined $left) {
351 $builder->ok(0, $comment);
352 $builder->diag("left is undef");
355 unless (defined $right) {
356 $builder->ok(0, $comment);
357 $builder->diag("right is undef");
360 unless ($left->{IMG}) {
361 $builder->ok(0, $comment);
362 $builder->diag("left image has no low level object");
365 unless ($right->{IMG}) {
366 $builder->ok(0, $comment);
367 $builder->diag("right image has no low level object");
370 unless ($left->getwidth == $right->getwidth) {
371 $builder->ok(0, $comment);
372 $builder->diag("left width " . $left->getwidth . " vs right width "
376 unless ($left->getheight == $right->getheight) {
377 $builder->ok(0, $comment);
378 $builder->diag("left height " . $left->getheight . " vs right height "
379 . $right->getheight);
382 unless ($left->getchannels == $right->getchannels) {
383 $builder->ok(0, $comment);
384 $builder->diag("left channels " . $left->getchannels . " vs right channels "
385 . $right->getchannels);
392 sub is_image_similar($$$$) {
393 my ($left, $right, $limit, $comment) = @_;
396 local $Test::Builder::Level = $Test::Builder::Level + 1;
398 _low_image_diff_check($left, $right, $comment)
402 my $builder = Test::Builder->new;
404 my $diff = Imager::i_img_diff($left->{IMG}, $right->{IMG});
405 if ($diff > $limit) {
406 $builder->ok(0, $comment);
407 $builder->diag("image data difference > $limit - $diff");
410 # find the first mismatch
412 for my $y (0 .. $left->getheight()-1) {
413 for my $x (0.. $left->getwidth()-1) {
414 my @lsamples = $left->getsamples(x => $x, y => $y, width => 1);
415 my @rsamples = $right->getsamples(x => $x, y => $y, width => 1);
416 if ("@lsamples" ne "@rsamples") {
417 $builder->diag("first mismatch at ($x, $y) - @lsamples vs @rsamples");
427 return $builder->ok(1, $comment);
431 my ($left, $right, $comment) = @_;
433 local $Test::Builder::Level = $Test::Builder::Level + 1;
435 return is_image_similar($left, $right, 0, $comment);
439 my $epsilon = Imager::i_img_epsilonf();
441 ($epsilon) = splice @_, 2, 1;
444 my ($left, $right, $comment) = @_;
447 local $Test::Builder::Level = $Test::Builder::Level + 1;
449 _low_image_diff_check($left, $right, $comment)
453 my $builder = Test::Builder->new;
455 my $same = Imager::i_img_samef($left->{IMG}, $right->{IMG}, $epsilon, $comment);
457 $builder->ok(0, $comment);
458 $builder->diag("images different");
460 # find the first mismatch
462 for my $y (0 .. $left->getheight()-1) {
463 for my $x (0.. $left->getwidth()-1) {
464 my @lsamples = $left->getsamples(x => $x, y => $y, width => 1, type => "float");
465 my @rsamples = $right->getsamples(x => $x, y => $y, width => 1, type => "float");
466 if ("@lsamples" ne "@rsamples") {
467 $builder->diag("first mismatch at ($x, $y) - @lsamples vs @rsamples");
476 return $builder->ok(1, $comment);
480 my ($left, $right, $comment) = @_;
482 my $builder = Test::Builder->new;
484 my $diff = Imager::i_img_diff($left->{IMG}, $right->{IMG});
486 return $builder->ok($diff, "$comment");
489 sub image_bounds_checks {
492 my $builder = Test::Builder->new;
494 $builder->ok(!$im->getpixel(x => -1, y => 0), 'bounds check get (-1, 0)');
495 $builder->ok(!$im->getpixel(x => 10, y => 0), 'bounds check get (10, 0)');
496 $builder->ok(!$im->getpixel(x => 0, y => -1), 'bounds check get (0, -1)');
497 $builder->ok(!$im->getpixel(x => 0, y => 10), 'bounds check get (0, 10)');
498 $builder->ok(!$im->getpixel(x => -1, y => 0), 'bounds check get (-1, 0) float');
499 $builder->ok(!$im->getpixel(x => 10, y => 0), 'bounds check get (10, 0) float');
500 $builder->ok(!$im->getpixel(x => 0, y => -1), 'bounds check get (0, -1) float');
501 $builder->ok(!$im->getpixel(x => 0, y => 10), 'bounds check get (0, 10) float');
502 my $black = Imager::Color->new(0, 0, 0);
503 require Imager::Color::Float;
504 my $blackf = Imager::Color::Float->new(0, 0, 0);
505 $builder->ok(!$im->setpixel(x => -1, y => 0, color => $black), 'bounds check set (-1, 0)');
506 $builder->ok(!$im->setpixel(x => 10, y => 0, color => $black), 'bounds check set (10, 0)');
507 $builder->ok(!$im->setpixel(x => 0, y => -1, color => $black), 'bounds check set (0, -1)');
508 $builder->ok(!$im->setpixel(x => 0, y => 10, color => $black), 'bounds check set (0, 10)');
509 $builder->ok(!$im->setpixel(x => -1, y => 0, color => $blackf), 'bounds check set (-1, 0) float');
510 $builder->ok(!$im->setpixel(x => 10, y => 0, color => $blackf), 'bounds check set (10, 0) float');
511 $builder->ok(!$im->setpixel(x => 0, y => -1, color => $blackf), 'bounds check set (0, -1) float');
512 $builder->ok(!$im->setpixel(x => 0, y => 10, color => $blackf), 'bounds check set (0, 10) float');
515 sub test_colorf_gpix {
516 my ($im, $x, $y, $expected, $epsilon, $comment) = @_;
518 my $builder = Test::Builder->new;
520 defined $comment or $comment = '';
522 my $c = Imager::i_gpixf($im, $x, $y);
524 $builder->ok(0, "$comment - retrieve color at ($x,$y)");
527 unless ($builder->ok(colorf_cmp($c, $expected, $epsilon) == 0,
528 "$comment - got right color ($x, $y)")) {
530 my @exp = $expected->rgba;
531 $builder->diag(<<EOS);
532 # got: ($c[0], $c[1], $c[2])
533 # expected: ($exp[0], $exp[1], $exp[2])
539 sub test_color_gpix {
540 my ($im, $x, $y, $expected, $comment) = @_;
542 my $builder = Test::Builder->new;
544 defined $comment or $comment = '';
545 my $c = Imager::i_get_pixel($im, $x, $y);
547 $builder->ok(0, "$comment - retrieve color at ($x,$y)");
550 unless ($builder->ok(color_cmp($c, $expected) == 0,
551 "got right color ($x, $y)")) {
553 my @exp = $expected->rgba;
554 $builder->diag(<<EOS);
555 # got: ($c[0], $c[1], $c[2])
556 # expected: ($exp[0], $exp[1], $exp[2])
564 sub test_colorf_glin {
565 my ($im, $x, $y, $pels, $comment) = @_;
567 my $builder = Test::Builder->new;
569 my @got = Imager::i_glinf($im, $x, $x+@$pels, $y);
571 or return $builder->is_num(scalar(@got), scalar(@$pels), "$comment - pixels retrieved");
573 return $builder->ok(!grep(colorf_cmp($pels->[$_], $got[$_], 0.005), 0..$#got),
574 "$comment - check colors ($x, $y)");
578 my ($c1, $c2, $epsilon) = @_;
580 defined $epsilon or $epsilon = 0;
585 # print "# (",join(",", @s1[0..2]),") <=> (",join(",", @s2[0..2]),")\n";
586 return abs($s1[0]-$s2[0]) >= $epsilon && $s1[0] <=> $s2[0]
587 || abs($s1[1]-$s2[1]) >= $epsilon && $s1[1] <=> $s2[1]
588 || abs($s1[2]-$s2[2]) >= $epsilon && $s1[2] <=> $s2[2];
597 return $s1[0] <=> $s2[0]
599 || $s1[2] <=> $s2[2];
602 # these test the action of the channel mask on the image supplied
603 # which should be an OO image.
605 my ($im, $epsilon) = @_;
607 my $builder = Test::Builder->new;
609 defined $epsilon or $epsilon = 0;
611 # we want to check all four of ppix() and plin(), ppix() and plinf()
612 # basic test procedure:
613 # first using default/all 1s mask, set to white
614 # make sure we got white
615 # set mask to skip a channel, set to grey
616 # make sure only the right channels set
618 print "# channel mask tests\n";
620 my $white = Imager::NC(255, 255, 255);
621 my $grey = Imager::NC(128, 128, 128);
622 my $white_grey = Imager::NC(128, 255, 128);
624 print "# with ppix\n";
625 $builder->ok($im->setmask(mask=>~0), "set to default mask");
626 $builder->ok($im->setpixel(x=>0, 'y'=>0, color=>$white), "set to white all channels");
627 test_color_gpix($im->{IMG}, 0, 0, $white, "ppix");
628 $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1");
629 $builder->ok($im->setpixel(x=>0, 'y'=>0, color=>$grey), "set to grey, no channel 2");
630 test_color_gpix($im->{IMG}, 0, 0, $white_grey, "ppix masked");
632 print "# with plin\n";
633 $builder->ok($im->setmask(mask=>~0), "set to default mask");
634 $builder->ok($im->setscanline(x=>0, 'y'=>1, pixels => [$white]),
635 "set to white all channels");
636 test_color_gpix($im->{IMG}, 0, 1, $white, "plin");
637 $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1");
638 $builder->ok($im->setscanline(x=>0, 'y'=>1, pixels=>[$grey]),
639 "set to grey, no channel 2");
640 test_color_gpix($im->{IMG}, 0, 1, $white_grey, "plin masked");
643 my $whitef = Imager::NCF(1.0, 1.0, 1.0);
644 my $greyf = Imager::NCF(0.5, 0.5, 0.5);
645 my $white_greyf = Imager::NCF(0.5, 1.0, 0.5);
647 print "# with ppixf\n";
648 $builder->ok($im->setmask(mask=>~0), "set to default mask");
649 $builder->ok($im->setpixel(x=>0, 'y'=>2, color=>$whitef), "set to white all channels");
650 test_colorf_gpix($im->{IMG}, 0, 2, $whitef, $epsilon, "ppixf");
651 $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1");
652 $builder->ok($im->setpixel(x=>0, 'y'=>2, color=>$greyf), "set to grey, no channel 2");
653 test_colorf_gpix($im->{IMG}, 0, 2, $white_greyf, $epsilon, "ppixf masked");
655 print "# with plinf\n";
656 $builder->ok($im->setmask(mask=>~0), "set to default mask");
657 $builder->ok($im->setscanline(x=>0, 'y'=>3, pixels => [$whitef]),
658 "set to white all channels");
659 test_colorf_gpix($im->{IMG}, 0, 3, $whitef, $epsilon, "plinf");
660 $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1");
661 $builder->ok($im->setscanline(x=>0, 'y'=>3, pixels=>[$greyf]),
662 "set to grey, no channel 2");
663 test_colorf_gpix($im->{IMG}, 0, 3, $white_greyf, $epsilon, "plinf masked");
673 Imager::Test - common functions used in testing Imager
677 use Imager::Test 'diff_text_with_nul';
678 diff_text_with_nul($test_name, $text1, $text2, @string_options);
682 This is a repository of functions used in testing Imager.
684 Some functions will only be useful in testing Imager itself, while
685 others should be useful in testing modules that use Imager.
687 No functions are exported by default.
691 =head2 Test functions
697 =item is_color1($color, $grey, $comment)
699 Tests if the first channel of $color matches $grey.
701 =item is_color3($color, $red, $green, $blue, $comment)
703 Tests if $color matches the given ($red, $green, $blue)
705 =item is_color4($color, $red, $green, $blue, $alpha, $comment)
707 Tests if $color matches the given ($red, $green, $blue, $alpha)
709 =item is_fcolor1($fcolor, $grey, $comment)
711 =item is_fcolor1($fcolor, $grey, $epsilon, $comment)
713 Tests if $fcolor's first channel is within $epsilon of ($grey). For
714 the first form $epsilon is taken as 0.001.
716 =item is_fcolor3($fcolor, $red, $green, $blue, $comment)
718 =item is_fcolor3($fcolor, $red, $green, $blue, $epsilon, $comment)
720 Tests if $fcolor's channels are within $epsilon of ($red, $green,
721 $blue). For the first form $epsilon is taken as 0.001.
723 =item is_fcolor4($fcolor, $red, $green, $blue, $alpha, $comment)
725 =item is_fcolor4($fcolor, $red, $green, $blue, $alpha, $epsilon, $comment)
727 Tests if $fcolor's channels are within $epsilon of ($red, $green,
728 $blue, $alpha). For the first form $epsilon is taken as 0.001.
730 =item is_image($im1, $im2, $comment)
732 Tests if the 2 images have the same content. Both images must be
733 defined, have the same width, height, channels and the same color in
734 each pixel. The color comparison is done at 8-bits per pixel. The
735 color representation such as direct vs paletted, bits per sample are
736 not checked. Equivalent to is_image_similar($im1, $im2, 0, $comment).
738 =item is_imaged($im, $im2, $comment)
740 =item is_imaged($im, $im2, $epsilon, $comment)
742 Tests if the two images have the same content at the double/sample
743 level. C<$epsilon> defaults to the platform DBL_EPSILON multiplied by
746 =item is_image_similar($im1, $im2, $maxdiff, $comment)
748 Tests if the 2 images have similar content. Both images must be
749 defined, have the same width, height and channels. The cum of the
750 squares of the differences of each sample are calculated and must be
751 less than or equal to I<$maxdiff> for the test to pass. The color
752 comparison is done at 8-bits per pixel. The color representation such
753 as direct vs paletted, bits per sample are not checked.
755 =item isnt_image($im1, $im2, $comment)
757 Tests that the two images are different. For regressions tests where
758 something (like text output of "0") produced no change, but should
759 have produced a change.
761 =item test_colorf_gpix($im, $x, $y, $expected, $epsilon, $comment)
763 Retrieves the pixel ($x,$y) from the low-level image $im and compares
764 it to the floating point color $expected, with a tolerance of epsilon.
766 =item test_color_gpix($im, $x, $y, $expected, $comment)
768 Retrieves the pixel ($x,$y) from the low-level image $im and compares
769 it to the floating point color $expected.
771 =item test_colorf_glin($im, $x, $y, $pels, $comment)
773 Retrieves the floating point pixels ($x, $y)-[$x+@$pels, $y] from the
774 low level image $im and compares them against @$pels.
776 =item is_color_close3($color, $red, $green, $blue, $tolerance, $comment)
778 Tests if $color's first three channels are within $tolerance of ($red,
783 =head2 Test suite functions
785 Functions that perform one or more tests, typically used to test
786 various parts of Imager's implementation.
790 =item image_bounds_checks($im)
792 Attempts to write to various pixel positions outside the edge of the
793 image to ensure that it fails in those locations.
795 Any new image type should pass these tests. Does 16 separate tests.
797 =item mask_tests($im, $epsilon)
799 Perform a standard set of mask tests on the OO image $im. Does 24
802 =item diff_text_with_nul($test_name, $text1, $text2, @options)
804 Creates 2 test images and writes $text1 to the first image and $text2
805 to the second image with the string() method. Each call adds 3
806 C<ok>/C<not ok> to the output of the test script.
808 Extra options that should be supplied include the font and either a
809 color or channel parameter.
811 This was explicitly created for regression tests on #21770.
815 =head2 Helper functions
819 =item test_image_raw()
821 Returns a 150x150x3 Imager::ImgRaw test image.
825 Returns a 150x150x3 8-bit/sample OO test image.
827 =item test_image_16()
829 Returns a 150x150x3 16-bit/sample OO test image.
831 =item test_image_double()
833 Returns a 150x150x3 double/sample OO test image.
835 =item color_cmp($c1, $c2)
837 Performs an ordering of 3-channel colors (like <=>).
839 =item colorf_cmp($c1, $c2)
841 Performs an ordering of 3-channel floating point colors (like <=>).
847 Tony Cook <tony@develop-help.com>