File Coverage

blib/lib/Image/Compare/Comparator.pm
Criterion Covered Total %
statement 70 85 82.3
branch 12 18 66.6
condition 9 27 33.3
subroutine 15 19 78.9
pod 9 9 100.0
total 115 158 72.7


line stmt bran cond sub pod time code
1             package Image::Compare::Comparator;
2              
3 8     8   34 use warnings;
  8         10  
  8         302  
4 8     8   30 use strict;
  8         9  
  8         4421  
5              
6             sub new {
7 20     20 1 26 my $proto = shift;
8 20   33     63 my $class = ref($proto) || $proto;
9 20         23 my $self = {};
10 20         36 $self->{args} = shift;
11 20         29 $self->{mask} = shift;
12 20         24 bless($self, $class);
13 20         52 return $self;
14             }
15              
16             # This will do initial setup and throw an exception if there is something
17             # wrong. We have some common behavior in here. Subclasses may override this,
18             # or add to it.
19             sub setup {
20 15     15 1 16 my $self = shift;
21 15         23 my ($img1, $img2, $mask) = @_;
22 15 50 33     45 unless (
23             ($img1->getwidth() == $img2->getwidth() ) &&
24             ($img1->getheight() == $img2->getheight())
25             ) {
26 0         0 die "Images must be the same size!";
27             }
28 15 100       434 if ($mask) {
29 4 50 33     23 unless(ref($mask) && $mask->isa('Imager')) {
30 0         0 die "Match mask must be an Imager image object!";
31             }
32 4 50 33     9 unless (
33             ($mask->getchannels() == 1) &&
34             ($mask->bits() == 8)
35             ) {
36 0         0 die "Match mask image must have one channel and 8 bits per channel!";
37             }
38 4 50 33     71 unless (
39             ($mask->getwidth() == $img1->getwidth() ) &&
40             ($mask->getheight() == $img1->getheight())
41             ) {
42 0         0 die "Match mask must be the same size as the test images!";
43             }
44             }
45             }
46              
47             sub get_args {
48 0     0 1 0 my $self = shift;
49 0         0 return $self->{args};
50             }
51              
52             # By default, just return the class name and the arguments.
53             sub get_representation {
54 1     1 1 1 my $self = shift;
55             return (
56             method => $Image::Compare::reverse_class_map{ref($self)},
57             args => $self->{args},
58 1         6 );
59             }
60              
61             sub compare_images {
62 18     18 1 20 my $self = shift;
63 18         146 my ($img1, $img2, $mask) = @_;
64             # This will die if there's a problem.
65 18         69 $self->setup($img1, $img2, $mask);
66             # We spin over each pixel in img1.
67 18         82 my $wid = $img1->getwidth();
68 18         131 my $hig = $img1->getheight();
69 18         118 OUTER: for my $x (0 .. $wid - 1) {
70 34         50 for my $y (0 .. $hig - 1) {
71             # If we've been given a match mask, then we skip any pixel whose
72             # corresponding pixel in that mask is pure black.
73             # This is the entirety of the comparison logic surrounding masks. It is
74             # all so simple, I should have done it long ago.
75 66 100 100     129 if ($mask && (($mask->getpixel(x => $x, y => $y)->rgba())[0] == 255)) {
76 5         66 next;
77             }
78 61         350 my @pix1 = $img1->getpixel(x => $x, y => $y)->rgba();
79 61         964 my @pix2 = $self->get_second_pixel($img2, $x, $y)->rgba();
80             # If this returns undef, then we keep going. Otherwise, we stop.
81             # It will die if there's an error.
82             # This mechanism allows the subclass to short-circuit image examination
83             # if it feels the need to do so.
84 61 100       797 last OUTER if defined $self->accumulate(\@pix1, \@pix2, $x, $y);
85             }
86             }
87             # And finally, the subclass will return the thing it wants to return.
88 18         52 return $self->get_result();
89             }
90              
91             # By default, this is pretty boring.
92             # Subclasses may want to override it though.
93             # On second thought, I can't think of a reason why they would want to.
94             # I guess I will leave this in anyways.
95             sub get_second_pixel {
96 61     61 1 47 my $self = shift;
97 61         55 my ($img2, $x, $y) = @_;
98 61         94 return $img2->getpixel(x => $x, y => $y);
99             }
100              
101             # Some day we might have multiple ways to do this.
102             sub color_distance {
103 61     61 1 47 my $self = shift;
104 61         48 my ($pix1, $pix2) = @_;
105             # The sum of the squaws of the other two hides...
106             return sqrt(
107 61         208 ( ($pix1->[0] - $pix2->[0]) ** 2 ) +
108             ( ($pix1->[1] - $pix2->[1]) ** 2 ) +
109             ( ($pix1->[2] - $pix2->[2]) ** 2 )
110             );
111             }
112              
113             sub accumulate {
114 0     0 1 0 my $self = shift;
115 0   0     0 my $class = ref($self) || $self;
116 0         0 die "Subclass '$class' must implement accumulate()!";
117             }
118              
119             sub get_result {
120 0     0 1 0 my $self = shift;
121 0   0     0 my $class = ref($self) || $self;
122 0         0 die "Subclass '$class' must implement get_result()!";
123             }
124              
125             sub import {
126 48     48   63 my $cmp_pkg = shift;
127 48         74 my %args = @_;
128 48 50       153 unless (UNIVERSAL::isa($cmp_pkg, __PACKAGE__)) {
129 0         0 die "Comparaters must subclass __PACKAGE__!";
130             }
131 48         45 my $name = $cmp_pkg;
132 48 50 33     203 unless (
133             ($name =~ s/^Image::Compare:://) ||
134             ($name = $args{name})
135             ) {
136 0         0 die (
137             "Comparator must either be in the Image::Compare namespace, " .
138             "or you must provide a method name to import."
139             );
140             }
141             {
142 8     8   50 no strict qw/refs/;
  8         8  
  8         1273  
  48         44  
143             # We are essentially "exporting" this for backwards compatibility. We
144             # don't really want to use constants like this any more, but we have
145             # to. Shucks.
146 48         47 my $name_const = $name;
147 48     0   317 *{"Image::Compare::$name"} = sub () { $name_const };
  48         209  
  0         0  
148 48         85 $Image::Compare::class_map{$name} = $cmp_pkg;
149 48         824 $Image::Compare::reverse_class_map{$cmp_pkg} = $name;
150             }
151             }
152              
153             # We will read in the list of packages to load from the documentation.
154             while () {
155             if (/^=item \* L<([^>]+)>/) {
156 8     8   2866 eval "use $1";
  8     8   21  
  8     8   50  
  8     8   2656  
  8     8   14  
  8         57  
  8         36  
  8         9  
  8         43  
  8         3039  
  8         15  
  8         118  
  8         2689  
  8         16  
  8         55  
157             die "Failed loading module '$1': $@" if $@;
158             }
159             }
160              
161             close Image::Compare::Comparator::DATA;
162              
163             1;
164              
165             __DATA__