File Coverage

blib/lib/Image/Compare.pm
Criterion Covered Total %
statement 65 98 66.3
branch 20 42 47.6
condition 1 6 16.6
subroutine 14 17 82.3
pod 10 10 100.0
total 110 173 63.5


line stmt bran cond sub pod time code
1             # Image::Compare, a module based on the great Imager, used to determine if
2             # two images differ greatly from one another.
3              
4             package Image::Compare;
5              
6 8     8   108163 use strict;
  8         13  
  8         204  
7 8     8   27 use warnings;
  8         8  
  8         166  
8              
9 8     8   5949 use Imager;
  8         241473  
  8         47  
10              
11             # This is the base class for all comparators, and will also do the work of
12             # loading all supplied implementations.
13 8     8   4126 use Image::Compare::Comparator;
  8         13  
  8         27  
14              
15             our %class_map;
16              
17             my $loaded_lwp;
18              
19             BEGIN {
20 8     8   12 $loaded_lwp = 0;
21 8         340 eval "require LWP;";
22 8 50       248145 unless ($@) { $loaded_lwp = 1; }
  8         314  
23             }
24              
25             our $VERSION = "1.01";
26              
27             # If people don't want to deal with OO, we export the main "work" method
28             # so they can call it in a simpler way. We'll see below where we handle this.
29 8     8   50 use base qw/Exporter/;
  8         11  
  8         5787  
30             our @EXPORT_OK = qw/compare/;
31              
32             ## Public methods begin here
33              
34             # The constructor method.
35             # Takes a hash of arguments: (all are optional)
36             # image1 =>
37             # Data representing the first image, either as an Imager object, file
38             # name or a URL.
39             # type1 => Type of image provided.
40             # image2 => Like image1.
41             # type2 => Like type1.
42             # method =>
43             # Either the numeric constant representing the comparator, or an
44             # instance of a comparator.
45             # args => Arguments to pass to the comparator.
46             # See the documentation on the relevant option setters for more details
47             sub new {
48 20     20 1 2118 my $proto = shift;
49 20         48 my %args = @_;
50 20   33     81 my $class = ref($proto) || $proto; # Bite me, Randal.
51 20         29 my $self = {};
52 20         25 bless($self, $class);
53             # These are default values
54 20 100       49 if ($args{image1}) {
55             $self->set_image1(
56             img => $args{image1},
57             type => $args{type1}
58 19         68 );
59             }
60 20 100       53 if ($args{image2}) {
61             $self->set_image2(
62             img => $args{image2},
63             type => $args{type2}
64 19         54 );
65             }
66 20 100       48 if ($args{method}) {
67             $self->set_method(
68             method => $args{method},
69             args => $args{args}
70 19         47 );
71             }
72 20 100       54 if ($args{mask}) {
73 5         8 $self->set_mask(mask => $args{mask});
74             }
75 20         32 return $self;
76             }
77              
78             # The next two just use the input to fetch image data and store it as an
79             # Imager object. Currently supported image types:
80             # Imager object
81             # File name
82             # URL
83             sub set_image1 {
84 20     20 1 99 my $self = shift;
85 20         52 my %args = @_;
86 20         46 $self->{_IMG1} = _get_image($args{img}, $args{type});
87             }
88              
89             sub set_image2 {
90 19     19 1 19 my $self = shift;
91 19         38 my %args = @_;
92 19         31 $self->{_IMG2} = _get_image($args{img}, $args{type});
93             }
94              
95             # Get back the Imager objects created by the preceding two methods.
96             sub get_image1 {
97 0     0 1 0 my $self = shift;
98 0         0 return $self->{_IMG1};
99             }
100             sub get_image2 {
101 0     0 1 0 my $self = shift;
102 0         0 return $self->{_IMG2};
103             }
104              
105             # How to set the matching mask parameter for this compaison instance.
106             sub set_mask {
107 5     5 1 5 my $self = shift;
108 5         6 my %args = @_;
109 5         8 $self->{_MASK} = $args{mask};
110             }
111             sub get_mask {
112 0     0 1 0 my $self = shift;
113 0         0 return $self->{_MASK};
114             }
115              
116             # Given input as defined above, returns an Imager object representing the
117             # image.
118             sub _get_image {
119 39     39   42 my($img, $type) = @_;
120 39 50       63 unless ($img) {
121 0         0 die "Missing 'img' parameter";
122             }
123              
124             # If we've been given an Imager object, we need only store it.
125 39 50       62 if (ref($img)) {
126 39 50       113 if ($img->isa('Imager')) {
127 39         102 return $img;
128             }
129             # If it wasn't an Imager, but it's still some kind of reference, then
130             # we have to give up.
131 0         0 die "Unrecognized input type: '" . ref($img) . "'";
132             }
133            
134             # Otherwse, we need to construct an Imager object, and to do that, we
135             # need to build up an arguments hash for the Imager constructor.
136 0         0 my %args;
137 0 0       0 if ($type) {
138             # Provide the type argument to image, if it was provided.
139 0         0 $args{type} = $type;
140             }
141             # This is the base error message.
142 0         0 my $errmsg = "Unable to read image data from ";
143             # If $img looks like a URL, and if we were able to load LWP, then we might
144             # be able to fetch an image via a URL.
145 0 0 0     0 if ($loaded_lwp && ($img =~ /^https?:\/\//)) {
146 0         0 $errmsg .= "URL '$img'";
147 0         0 my $ua = LWP::UserAgent->new();
148 0         0 $ua->agent("Image::Compare/v$VERSION ");
149 0         0 my $res = $ua->request(HTTP::Request->new(GET => $img));
150 0         0 $args{data} = $res->content();
151 0 0       0 if (!$type) {
152 0         0 $args{type} = $res->content_type();
153 0         0 $args{type} =~ s!^image/!!;
154             }
155             }
156             else {
157             # Otherwise, we have to think it's a file path.
158 0         0 $errmsg .= "file '$img'";
159 0         0 $args{file} = $img;
160             }
161 0         0 my $newimg = Imager->new();
162 0 0       0 $newimg->read(%args) || die($errmsg . ": '" . $newimg->{ERRSTR} . "'");
163 0         0 return $newimg;
164             }
165              
166             # Sets the comparison method. Either takes the numeric constant that
167             # identifies the method and any arguments needed by the method, or an instance
168             # of the comparator. See the documentation for Image::Compare::Comparator or
169             # it subclasses for more details.
170             sub set_method {
171 20     20 1 3149 my $self = shift;
172 20         37 my %args = @_;
173 20 50       41 unless ($args{method}) {
174 0         0 die "Missing required argument 'method'";
175             }
176 20 50       37 if (ref($args{method})) {
177 0 0       0 if ($args{method}->isa('Image::Compare::Comparator')) {
178 0         0 $self->{_CMP} = $args{method};
179             }
180             else {
181             die (
182             "Unrecognized type for 'method' argument: '" .
183 0         0 ref($args{method}) . "'"
184             );
185             }
186             }
187             else {
188 20 50       52 unless ($class_map{$args{method}}) {
189 0         0 die "Unrecognized method identifier: '$args{method}'";
190             }
191 20         144 $self->{_CMP} = $class_map{$args{method}}->new($args{args});
192             }
193             }
194              
195             # Returns information describing the comparison method set into this instance
196             # of an Image::Compare.
197             sub get_method {
198 1     1 1 596 my $self = shift;
199 1 50       7 unless ($self->{_CMP}) {
200 0 0       0 return wantarray ? () : undef;
201             }
202 1         8 return $self->{_CMP}->get_representation();
203             }
204              
205             # Compares two images and returns a result.
206             sub compare {
207 18     18 1 17058 my $self;
208             # This can be called as an instance method
209 18 100       46 if (ref($_[0]) eq 'Image::Compare') {
210 2         1 $self = shift;
211             }
212             else {
213             # Or, as a class method, if you swing that way...
214 16 50       34 if ($_[0] eq 'Image::Compare') {
215 0         0 shift;
216             }
217             # Or just as a plain method. In either case, we just need to construct
218             # a $self so we can get on with life.
219 16         53 $self = Image::Compare->new(@_);
220             }
221             # Sanity checking
222 18         52 for my $ref (
223             ['IMG1', 'Image 1'], ['IMG2', 'Image 2'], ['CMP', 'Comparison method'],
224             ) {
225 54 50       150 die "$ref->[1] not specified" unless $self->{"_$ref->[0]"};
226             }
227              
228             # Give the images to the comparator and let them compare them.
229             # The comparator will raise an exception if anything's wrong.
230             return $self->{_CMP}->compare_images(
231 18         67 @{$self}{qw/_IMG1 _IMG2 _MASK/}
  18         71  
232             );
233             }
234              
235             1;
236              
237             __END__