File Coverage

blib/lib/Test/Image.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             # HUUUUUUUUUUGE heap of undocumented crap I don't have time to
2             # figure out right now.
3              
4             # col is documented
5             # col_all is documented
6             # col_any is documented
7             # col_none is documented
8             # column is documented
9             # column_all is documented
10             # column_any is documented
11             # column_none is documented
12             # region is documented
13             # region_all is documented
14             # region_any is documented
15             # region_none is documented
16             # row is documented
17             # row_all is documented
18             # row_any is documented
19             # row_none is documented
20              
21             package Test::Image;
22 12     12   12303 use base qw(Exporter);
  12         23  
  12         1261  
23              
24 12     12   71 use strict;
  12         23  
  12         501  
25 12     12   72 use warnings;
  12         24  
  12         516  
26              
27 12     12   63 use vars qw($VERSION @STANDARD_PLUGINS @EXPORT);
  12         27  
  12         978  
28              
29             $VERSION = "0.02";
30              
31 12     12   69 use Carp qw(croak);
  12         22  
  12         775  
32              
33             # get the Test::Builder singleton
34 12     12   78 use Test::Builder;
  12         19  
  12         1079  
35             my $tester = Test::Builder->new();
36              
37             # set up the color names
38             my %NameTable;
39             eval "use Graphics::ColorNames";
40             unless ($@) {
41             tie %NameTable, "Graphics::ColorNames", "X";
42             }
43              
44             # use module pluggable, but if that's not installed, just fake it by
45             # installing a subroutine that just returns the known plugins we ship with
46             @STANDARD_PLUGINS = qw(
47             Test::Image::Plugin::TestingImage
48             Test::Image::Plugin::Imlib2
49             );
50             # TODO - Module::Pluggable insists on compiling MacOS metadata files.
51             # this makes actually hacking on this module incredibly painful. Until
52             # (a) M::P is better, and (b) we actually _have_ any plugins, I don't want
53             # this - tinsam 2006/06
54             #eval "use Module::Pluggable require => 1";
55             #if ($@) {
56 12     12   13355 use Test::Image::Plugin::TestingImage;
  12         27  
  12         626  
57 12     12   8255 use Test::Image::Plugin::Imlib2;
  0            
  0            
58             *plugins = sub { @STANDARD_PLUGINS };
59             #}
60              
61             # for Module::Build::Kwalitee, to explain that plugins is documented
62             # plugins is documented
63              
64             # We use number compare for some of the comparison operations with size
65             # we load it with eval to allow these tests to automatically skip
66             # if it's not installed
67             eval "use Number::Compare";
68              
69             =head1 NAME
70              
71             Test::Image - test an image
72              
73             =head1 SYNOPSIS
74              
75             use Test::More plan => 1;
76             use Test::Image;
77            
78             # create a new image tester
79             my $i = Test::Image->new(Image::Imlib2->new("foo.jpg"));
80             ok($i, "image ok");
81             $i->size(400,300); # (see also $i->width, $i->height)
82              
83             # you can check pixels using names, rgb hex, or rgb decimal
84             $i->pixel(10,10,"white"); # 10, 10 is white
85             $i->pixel(10,10,"ffffff"); # 10, 10 is white
86             $i->pixel(10,10,[255,255,255]); # 10, 10 is white
87              
88             $i->pixel_not(10,10,"white"); # 10, 10 isn't white
89             $i->pixel_not(10,10,"ffffff"); # 10, 10 isn't white
90             $i->pixel_not(10,10,[255,255,255]); # 10, 10 isn't white
91            
92             # you can use multiple posibilities too
93             # check pixel is red, white or blue:
94             $i->pixel(10,10,["red", "white", "blue"]);
95             $i->pixel(10,10,["ff0000", "ffffff", "0000ff"]);
96             $i->pixel(10,10,[[255,0,0], [255,255,255], [0,0,255]]);
97            
98             # check that the pixel isn't red white or blue:
99             $i->pixel_not(10,10,["red", "white", "blue"]);
100             $i->pixel_not(10,10,["ff0000", "ffffff", "0000ff"]);
101             $i->pixel_not(10,10,[[255,0,0], [255,255,255], [0,0,255]]);
102            
103             # row functions (or replace "row" with "col" or "column" for column tests)
104             # you can use multiple colours
105             $i->row(10, "white"); # row 11 is all white
106             $i->row_all(10, "white"); # row 11 is all white
107             $i->row_any(10, "white"); # row 11 has a white pixel
108             $i->row_none(10, "white"); # row 11 has no white pixels
109              
110             # likewise for the whole image (again can use multiple colours)
111             $i->all("white"); # whole image is white
112             $i->any("white"); # whole image has a white pixel
113             $i->none("white"); # whole image has no white pixels
114            
115             # finally regions (you can use _all, _any or _none too)
116             # check the 10x10 region starting at 40,30
117             $i->region(40, 30, "r10", "r10", "white");
118            
119             =head1 DESCRIPTION
120              
121             This modules is a C compatible testing module for testing
122             images.
123              
124             Calling the methods of this module prints out Test Anything Protocol
125             output designed to be processed by Test::Harness during a C
126             or C<./Build test>. This module 'plays nice' with other test modules
127             also crafted with Test::Builder. For example, you can happily use this
128             module in conjunction with Test::More, Test::Exception,
129             Test::DatabaseRow, etc, and not have to worry about your test numbers
130             getting confused.
131              
132             All methods take an optional description as the last arguement. For example:
133              
134             $i->width(400); # prints "ok 1 - image width"
135             $i->width(400, "1st width"); # prints "ok 2 - 1st width"
136              
137             =head2 Constructing
138              
139             =over
140              
141             =item new($image)
142              
143             The constructor takes one arguement, the image you want to test. By default
144             we only support B and B objects, but you can provide
145             further plugins for other image formats by following the PLUGINS guide below.
146              
147             =back
148              
149             =cut
150              
151             sub new {
152             my $class = shift;
153             my $newimage = shift;
154            
155             unless (defined $newimage) {
156             croak "No image passed";
157             }
158              
159             my $self = bless {}, $class;
160              
161             # find a plugin that will handle the image
162             foreach (__PACKAGE__->plugins) {
163             my $plugin_instance = $_->new( $newimage );
164             next unless $plugin_instance;
165             $self->{image} = $plugin_instance;
166             return $self;
167             }
168            
169             # couldn't find a plugin that matches
170             croak "No plugin found for image passed";
171             }
172              
173             =head2 Image Size
174              
175             There are various tests that can be used to check the magnitude of the
176             image:
177              
178             # check that fred.png is 100 by 300 pixels big
179             my $i = Test::Image->new(Image::Imlib2->new( "fred.png" ));
180             $i->size(100,300)
181              
182             If you have C installed, then you can use non
183             absolute values, and you can use magnitudes.
184              
185             # image is at least 300x200
186             $i->size(">=300", ">=200");
187            
188             # It's a five megapixel image!
189             $i->total_pixels(">=5M");
190              
191             See L for more info. If you do not have
192             C installed, these style of tests will be
193             automatically skipped.
194              
195             =over
196              
197             =item width($w_pixels)
198              
199             Test the width of the image
200              
201             =item height($h_pixels)
202              
203             Test the height of the image
204              
205             =item size($w_pixels, $h_pixels)
206              
207             Test the width and the height of the image at the same time
208              
209             =item total_size($pixels)
210              
211             Test the total number of pixels in the image (i.e. width x height)
212              
213             =back
214              
215             =cut
216              
217             sub width {
218             my $self = shift;
219             return $self->_wh_test("width", "wide", @_);
220             }
221              
222             sub height {
223             my $self = shift;
224             return $self->_wh_test("height", "tall", @_);
225             }
226              
227             sub total_size {
228             my $self = shift;
229             return $self->_wh_test("total size", "in total", @_);
230             }
231              
232             sub _wh_test {
233             local $Test::Builder::Level = $Test::Builder::Level + 1;
234            
235             my $self = shift;
236            
237             # these first two values are just things that, since we're
238             # using the same subroutine for width and height, that we
239             # can use to call the right thing. They're not set by
240             # the user.
241             my $type = shift; # width/height/total size
242             my $what = shift; # wide/tall
243            
244             my $wanted = shift;
245             my $description = @_ ? shift :
246             "image $type";
247            
248             # get the actual value
249             my $got = $type eq "total size"
250             ? $self->{image}->width * $self->{image}->height
251             : $self->{image}->$type;
252             my $got_pixels = $got == 1 ? "pixel" : "pixels";
253              
254             # hmm, should we be doing a number compare test here?
255             my $wanted_pixels;
256             if ($wanted !~ /^\d+$/) {
257            
258             # skip if we don't have that installed
259             unless ($INC{"Number/Compare.pm"}) {
260             $tester->skip("No Number::Compare");
261             return 1;
262             }
263            
264             # use number compare to do the actual comparison
265             my $compare = Number::Compare->new($wanted);
266             if ($compare->($got)) {
267             $tester->ok(1,$description);
268             return 1;
269             }
270            
271             # we've failed
272            
273             # munge the values for the error message
274             $wanted = "'$wanted'";
275             $wanted_pixels = "pixels"; # N::C tests are always plural
276            
277             } else {
278              
279             # plain old number
280             if ($wanted == $got) {
281             $tester->ok(1,$description);
282             return 1;
283             }
284            
285             $wanted_pixels = $wanted == 1 ? "pixel" : "pixels";
286             }
287              
288             # both failure cases fall through to here
289            
290             $tester->ok(0, $description);
291             $tester->diag("Image $got $got_pixels $what, not $wanted $wanted_pixels as expected");
292             return 0;
293             }
294              
295             sub size {
296             my $self = shift;
297            
298             my $wanted_w = shift;
299             my $wanted_h = shift;
300             my $description = @_ ? shift :
301             "image size";
302              
303             # get the actual values from the image
304             my $got_w = $self->{image}->width;
305             my $got_h = $self->{image}->height;
306              
307             # check if we're using a complicated value that isn't
308             # just a normal number, and test differently if we are
309             if ($wanted_w !~ /^\d+$/ || $wanted_h !~ /^\d+$/) {
310              
311             # skip if we don't have that installed
312             unless ($INC{"Number/Compare.pm"}) {
313             $tester->skip("No Number::Compare");
314             return 1;
315             }
316              
317             # use number compare to do the tests. Note that one
318             # of these two may be just a plain old number. That's
319             # fine! Number::Compare will cope with that.
320              
321             my $compare_w = Number::Compare->new($wanted_w);
322             my $compare_h = Number::Compare->new($wanted_h);
323            
324             if ($compare_w->($got_w) && $compare_h->($got_h)) {
325             $tester->ok(1,$description);
326             return 1;
327             }
328            
329             # we've failed!
330            
331             # put the things that aren't numbers in quotes. This
332             # just makes them look better when we print out the
333             # error message below
334             $wanted_w = "'$wanted_w'" unless $wanted_w =~ /^\d+$/;
335             $wanted_h = "'$wanted_h'" unless $wanted_h =~ /^\d+$/;
336            
337             } else {
338            
339             # just a plain old number, we can do that without Number::Compare
340              
341             if ($wanted_w == $got_w && $wanted_h == $got_h) {
342             $tester->ok(1,$description);
343             return 1;
344             }
345             }
346            
347             # both failure cases will fall through to give the same error message
348            
349             $tester->ok(0, $description);
350             $tester->diag("Image size ($got_w,$got_h) not ($wanted_w,$wanted_h) as expected");
351             return 0;
352             }
353              
354             =head2 Color specification
355              
356             The testing system can cope with multiple definitions of color. You can
357             use an arrayref containing the red, green and blue values (between 0 and 255:)
358              
359             my $red = [255,0,0];
360              
361             You can specify the value in hex if you want too:
362              
363             my $red = "ff0000";
364             my $red = "FF0000"; # it's case insensitive
365             my $red = "ff0000"; # you can put a # at the start if you want
366              
367             If you install the B module from CPAN then you can
368             use the name of the color in the "X" color scheme.
369              
370             my $red = "red";
371              
372             Finally you can specify more than one colour by using an array ref containing
373             the other forms.
374              
375             my $rwab = ["red", "white", "blue"];
376             my $rwab = ["ff0000", "ffffff", "0000ff"];
377             my $rwab = [[255,0,0], [255,255,255], [0,0,255]];
378              
379             =head2 Checking Single Pixels
380              
381             The simple C test can be used to check the color of a given
382             pixel either is or isn't a particular color (or set of colors)
383              
384             # check the pixel at 40, 30 is red
385             $i->pixel(40, 30, [255,0,0])
386              
387             # check the pixel at 40, 30 is red or white
388             $i->pixel(40, 30, [[255,0,0], [255,255,255]])
389              
390             # check the pixel at 40, 30 isn't red
391             $i->pixel_not(40, 30, [255,0,0])
392            
393             # check the pixel at 40, 30 isn't red or white
394             $i->pixel_not(40, 30, [[255,0,0], [255,255,255]])
395            
396             This will fail if the pixel isn't the correct color, or the pixel is outside
397             the image.
398              
399             You can also use negative numbers to indicate coordinates relative the far
400             sides of the image in a similar manner to Perl arrays. For example:
401              
402             $i->pixel(-1,-2, "red");
403              
404             Is the same for a 400x300 image as:
405              
406             $i->pixel(399,298, "red");
407              
408             =cut
409              
410             # pixel is documented
411             sub pixel {
412             my $self = shift;
413             my $image = $self->{image};
414              
415             my $x = shift;
416             my $y = shift;
417              
418             # cope with negative coords
419             $x = $self->{image}->width + $x if $x < 0;
420             $y = $self->{image}->height + $y if $y < 0;
421              
422             my $wanted_color = shift;
423             my $description = @_ ? shift : "pixel test";
424            
425             my ($test,@colors) = _ctest($wanted_color);
426            
427             my ($r, $g, $b) = $image->color_at($x, $y);
428             unless (defined $r) {
429             $tester->ok(0, $description);
430             $tester->diag("Coords ($x, $y) outside of image");
431             return 0;
432             };
433            
434             unless ($test->($r,$g,$b)) {
435             $tester->ok(0, $description);
436             $tester->diag("Pixel ($x, $y):");
437             $tester->diag(" got: "._color($r,$g,$b));
438             $tester->diag(" expected: ".
439             join(" or\n ", @colors));
440             return 0;
441             }
442            
443             $tester->ok(1, $description);
444             return 1;
445             }
446              
447             # pixel_not is documented
448             sub pixel_not {
449             my $self = shift;
450             my $image = $self->{image};
451              
452             my $x = shift;
453             my $y = shift;
454              
455             # cope with negative coords
456             $x = $self->{image}->width + $x if $x < 0;
457             $y = $self->{image}->height + $y if $y < 0;
458              
459             my $wanted_color = shift;
460             my $description = @_ ? shift : "pixel not test";
461            
462             my ($test) = _ctest($wanted_color);
463            
464             my ($r, $g, $b) = $image->color_at($x, $y);
465             unless (defined $r) {
466             $tester->ok(0, $description);
467             $tester->diag("Coords ($x, $y) outside of image");
468             return 0;
469             };
470            
471             unless (!$test->($r,$g,$b)) {
472             $tester->ok(0, $description);
473             $tester->diag("Pixel ($x, $y) unexpectedly "._color($r,$g,$b));
474             return 0;
475             }
476            
477             $tester->ok(1, $description);
478             return 1;
479             }
480              
481             sub _munge_value {
482             my $self = shift;
483            
484             my $thingy = shift; # width or height
485             my $value = shift;
486             my $original = $value;
487            
488             # simple case where it's just a number
489             if ($value =~ /^\d+$/) {
490             return ($value, $value);
491             }
492            
493             if ($value =~ s/(-\d+)$//) {
494            
495             # calculate what that number should have been
496             my $temp_value = $self->{image}->$thingy + $1;
497            
498             # okay, if it was just a negative number, we're done
499             return ($temp_value, $temp_value) if !length($value);
500            
501             $value =~ tr[<>][><]; # reverse the greater than if any
502             $value .= $temp_value; # and attach back the number part
503             }
504              
505             foreach (qw( <0 <-1 )) {
506             die "You can't have a constraint of '$_'" if $value eq $_;
507             }
508              
509             if ($value =~ /^[<][=](\d+)$/)
510             { return (0, $1) }
511             if ($value =~ /^[<](\d+)$/)
512             { return (0, $1 - 1) }
513             if ($value =~ /^[>][=](\d+)$/)
514             { return ($1, $self->{image}->$thingy - 1) }
515             if ($value =~ /^[>](\d+)$/)
516             { return ($1 + 1, $self->{image}->$thingy - 1) }
517            
518             die "Constraint '$value' makes no sense!";
519             }
520              
521             sub row {
522             my $self = shift;
523             $self->_row("all", "row test", @_);
524             }
525              
526             sub col {
527             my $self = shift;
528             $self->_column("all", "column test", @_);
529             }
530              
531             sub column {
532             my $self = shift;
533             $self->_column("all", "column test", @_);
534             }
535              
536             # "row_all" is a synonym for "row"
537             sub row_all {
538             my $self = shift;
539             $self->_row("all", "row test", @_);
540             }
541              
542             # "column_all" is a synonym for "column"
543             sub column_all {
544             my $self = shift;
545             $self->_column("all", "column test", @_);
546             }
547              
548             # "col_all" is a synonym for "column"
549             sub col_all {
550             my $self = shift;
551             $self->_column("all", "column test", @_);
552             }
553              
554             sub row_none {
555             my $self = shift;
556             $self->_row("none", "row none test", @_);
557             }
558              
559             sub column_none {
560             my $self = shift;
561             $self->_column("none", "column none test", @_);
562             }
563              
564             sub col_none {
565             my $self = shift;
566             $self->_column("none", "column none test", @_);
567             }
568              
569             sub row_any {
570             my $self = shift;
571             $self->_row("any", "row any test", @_);
572             }
573              
574             sub column_any {
575             my $self = shift;
576             $self->_column("any", "column any test", @_);
577             }
578              
579             sub col_any {
580             my $self = shift;
581             $self->_column("any", "column any test", @_);
582             }
583              
584             sub _row {
585             local $Test::Builder::Level = $Test::Builder::Level + 1;
586              
587             my $self = shift;
588            
589             # values defined in the methods
590             my $mode = shift;
591             my $default_description = shift;
592            
593             # user supplied values
594             my $row = shift;
595             my $color = shift;
596             my $description = @_ ? shift : $default_description;
597            
598             # work out what rows we're looking at
599             my ($y1, $y2) = $self->_munge_value("height",$row);
600              
601             $self->_region(
602             x1 => 0, x2 => $self->{image}->width - 1,
603             y1 => $y1, y2 => $y2,
604             color => $color,
605             description => $description,
606             mode => $mode,
607             )
608             }
609              
610             sub _column {
611             local $Test::Builder::Level = $Test::Builder::Level + 1;
612              
613             my $self = shift;
614            
615             # values defined in the methods
616             my $mode = shift;
617             my $default_description = shift;
618            
619             # user supplied values
620             my $column = shift;
621             my $color = shift;
622             my $description = @_ ? shift : $default_description;
623            
624             # work out what columns we're looking at
625             my ($x1, $x2) = $self->_munge_value("width",$column);
626              
627             $self->_region(
628             y1 => 0, y2 => $self->{image}->height - 1,
629             x1 => $x1, x2 => $x2,
630             color => $color,
631             description => $description,
632             mode => $mode,
633             )
634             }
635              
636             # this tests a region. It's the routine that all the other pixel based
637             # tests (apart from the basic "pixel" and "pixel_not" tests call
638             sub _region {
639             # increase the T::B::Level so that errors come from the right line
640             local $Test::Builder::Level = $Test::Builder::Level + 1;
641              
642             my $self = shift;
643             my %args = @_;
644              
645             my $image = $self->{image};
646            
647             my $description = $args{description};
648             my $wanted_color = $args{color};
649             my $mode = $args{mode};
650              
651             # get the coords. x1 is the smaller x coord, x2 is the largest.
652             # same thing for y coords. x1 and x2 are inclusive.
653             my ($x1, $x2, $y1, $y2) = map { $args{ $_ } }
654             qw( x1 x2 y1 y2);
655            
656             # get a test for this color
657             my ($test,@colors) = _ctest($wanted_color);
658              
659             # loop left -> right, top->bottom through our region
660             my ($i, $j);
661             for ($j = $y1; $j <= $y2; $j++) {
662             for ($i = $x1; $i <= $x2; $i++) {
663              
664             # grab a pixel
665             my ($r, $g, $b) = $image->color_at($i, $j);
666              
667             # check it's inside
668             # this should be probably rolled out of the loop
669             unless (defined $r) {
670             $tester->ok(0, $description);
671             $tester->diag("Coords ($i, $j) outside of image");
672             return 0;
673             };
674            
675             # this should probably be totally unrolled
676            
677             if ($mode eq "none" && $test->($r,$g,$b)) {
678             $tester->ok(0, $description);
679             $tester->diag("Pixel ($i, $j) unexpectedly "._color($r,$g,$b));
680             return 0;
681             }
682              
683             if ($mode eq "all" && !$test->($r,$g,$b)) {
684             $tester->ok(0, $description);
685             $tester->diag("Pixel ($i, $j):");
686             $tester->diag(" got: "._color($r,$g,$b));
687             $tester->diag(" expected: ".
688             join(" or\n ", @colors));
689             return 0;
690             }
691              
692             if ($mode eq "any" && $test->($r,$g,$b)) {
693             $tester->ok(1, $description);
694             return 1;
695             }
696             }
697             }
698              
699             if ($mode eq "any") {
700             $tester->ok(0, $description);
701             $tester->diag("No pixel correct color");
702             $tester->diag(" expected: ".
703             join(" or\n ", @colors));
704             return 0;
705             }
706            
707             # got this far? must have succeeded
708             $tester->ok(1, $description);
709             return 1;
710             }
711              
712             # this returns a function that can check for the passed colour
713             # so calling
714             #
715             # my $foo = _ctest([255,0,0])
716             # $foo->(255,0,0); # check red is red
717             #
718             # my $foo = _ctest([255,0,0], [0,255,0])
719             # $foo->(255,0,0); # check red is red or blue
720              
721             # TODO: make this function only use arrayrefs. Make sure we do colour
722             # conversion *before* we hand things to it (much much more efficent
723             # in the case we have multiple links)
724              
725             sub _ctest {
726             my $tests = shift;
727              
728             # note that this is very careful to allow things like objects that
729             # stringify to be used okay
730            
731             # case where we don't pass an array
732             # meaning it's "white" or "ffffff" or something
733             unless (ref $tests && ref($tests) eq "ARRAY") {
734             my ($wr, $wg, $wb) = _rgb($tests);
735             return sub { $wr == $_[0] && $wg == $_[1] && $wb == $_[2] }, "[$wr,$wg,$wb]";
736             }
737              
738             # case where we pass an array and the first element looks like a number
739             # meaning it's [255,0,0], etc
740             if (ref $tests && ref($tests) eq "ARRAY" && defined $tests->[0] && $tests->[0] =~ /^\d+$/) {
741             my ($wr, $wg, $wb) = _rgb($tests);
742             return sub { $wr == $_[0] && $wg == $_[1] && $wb == $_[2] }, "[$wr,$wg,$wb]";
743             }
744              
745             # must be an array of tests then. Try them each in turn.
746             my @colors = map { [ _rgb( $_ ) ] } @$tests;
747             return sub {
748             foreach (@colors) {
749             return 1 if $_->[0] == $_[0] && $_->[1] == $_[1] && $_->[2] == $_[2];
750             }
751             return 0;
752             }, map { _color(@$_) } @colors;
753             }
754              
755              
756             # return the color in $r, $g, $b for what's passed in
757             # you can pass in "#ff0000", or "ff0000", or [255,0,0] or "red"
758             sub _rgb {
759             my $value = shift;
760             unless (defined $value)
761             { croak "Undef passed as expected color" }
762              
763             return @$value if ref $value && ref $value eq "ARRAY";
764              
765             # convert from hex and return if we can
766             if ($value =~ /^#?([a-fA-F09]{2})([a-fA-F09]{2})([a-fA-F09]{2})$/) {
767             return hex $1, hex $2, hex $3; # loop unrolled by hand :-)
768             }
769            
770             if (!$INC{"Graphics/ColorNames.pm"})
771             { die "Can't determine color for '$value': Graphics::ColorNames not installed" }
772            
773             my $hex = $NameTable{ $value };
774             die "Can't determine color for '$value'" unless $hex;
775             return Graphics::ColorNames::hex2tuple($hex);
776             }
777              
778             # return a string that describes the colour
779             sub _color {
780             my ($r, $g, $b) = @_;
781            
782             my $string = "";
783             if ($INC{"Graphics::ColorNames"}) {
784             # TODO: modify $string so it has the colour name in here
785             }
786             return "$string\[$r,$g,$b]";
787             }
788              
789             sub region {
790             my $self = shift;
791             return $self->_r("all","image region", @_);
792             }
793              
794             sub region_all {
795             my $self = shift;
796             return $self->_r("all","image region", @_);
797             }
798              
799             sub region_any {
800             my $self = shift;
801             return $self->_r("any","image region any", @_);
802             }
803              
804             sub region_none {
805             my $self = shift;
806             return $self->_r("none","image region none", @_);
807             }
808              
809             sub _r {
810             local $Test::Builder::Level = $Test::Builder::Level + 1;
811              
812             my $self = shift;
813            
814             # values defined in the methods
815             my $mode = shift;
816             my $default_description = shift;
817            
818             # user supplied values
819             my ($x1, $y1, $x2, $y2) = splice(@_, 0, 4);
820             my $color = shift;
821             my $description = @_ ? shift :
822             "image region";
823            
824             # convert negative coords into positive ones
825            
826             foreach ($x1, $x2) {
827             $_ = $self->{image}->width + $_ if $_ < 0;
828             }
829              
830             foreach ($y1, $y2) {
831             $_ = $self->{image}->height + $_ if $_ < 0;
832             }
833            
834             # make ?1 smaller than ?2 if it's not
835            
836             if ($x2 < $x1)
837             { ($x1, $x2) = ($x2, $x1) }
838              
839             if ($y2 < $y1)
840             { ($y1, $y2) = ($y2, $y1) }
841              
842             # examine the region
843            
844             $self->_region(
845             y1 => $y1,
846             y2 => $y2,
847             x1 => $x1,
848             x2 => $x2,
849             color => $color,
850             description => $description,
851             mode => $mode,
852             )
853             }
854              
855             =head1 PLUGINS
856              
857             This module can be extended to allow you to test arbitary image formats.
858             To do this you need to implement a module called Test::Image::Plugin::*
859             which supports the following methods:
860              
861             =over
862              
863             =item new( $image )
864              
865             A constructor. Return an object if you're prepared to handle the image
866             that's passed in. Return C if the image isn't something you'll
867             handle (hopefully some other plugin will.)
868              
869             =item width
870              
871             =item height
872              
873             Instance methods. These methods should return the width and height of
874             the image.
875              
876             =item color_at($x, $y)
877              
878             Instance method should return a three element list that contains the
879             red, green and blue value. This should return the empty list if the
880             pixel specified is outside the image.
881              
882             =back
883              
884             In order for these plugins to work you must first install
885             C from CPAN. If you're writing C plugin
886             and distributing it on CPAN, you should add C to your
887             required modules in C / C
888              
889             =head1 BUGS
890              
891             If you don't have module compare installed and you pass a string to
892             any of the image size routines that isn't just a plain old number
893             then that test will be skipped if you don't have C
894             installed, even if that string is just junk. This is to allow this
895             module to be compatible with future improvements to C.
896             You are encouraged to have C installed when
897             developing tests on your own system.
898              
899             We should probably automatically skip named colors if you don't
900             have C installed. We don't yet.
901              
902             Please report any further bugs you find via the CPAN RT system.
903             L
904              
905             =head1 OTHER BUGS
906              
907             British Airways doesn't have TIVO like movies like Virgin Atlantic. Or,
908             if it does, it doesn't have it on B flight, and that's all I really
909             care about at the momement.
910              
911             In the movie "Failure To Launch" taking of your facemask while playing
912             paintball is insanely dangerous. It also makes me want to shout lock
913             off! when Ace is belaying. I don't find climbing accidents funny.
914             Who's belaying the guy from Alias in that scene anyway? Despite
915             all that, I quite enjoyed the film.
916              
917             I somehow ended up with some of my sister in law's music in iTunes and
918             now when I'm coding I sometime randomly get some Christina Aguilera.
919              
920             Coding on a plane is very hard to do, as you don't have the arm room
921             to type properly.
922              
923             This said, I don't get a chance to listen to my entire Chemical
924             Brothers collection in one go uninterrupted very often.
925              
926             =head1 AUTHOR
927              
928             Written by Mark Fowler, Emark@twoshortplanks.comE. Please see
929             L for details of how to contact me.
930              
931             Copyright Fotango 2006-2007. All rights reserved.
932              
933             This module is free software; you can redistribute it and/or modify it under
934             the same terms as Perl itself.
935              
936             =head1 SEE ALSO
937              
938             L, for an alternative way of testing GD Images.
939              
940             L
941              
942             L
943              
944             =cut
945              
946             1;
947