File Coverage

blib/lib/Image/Compare/Comparator.pm
Criterion Covered Total %
statement 69 84 82.1
branch 12 18 66.6
condition 9 27 33.3
subroutine 15 19 78.9
pod 9 9 100.0
total 114 157 72.6


line stmt bran cond sub pod time code
1             package Image::Compare::Comparator;
2              
3 8     8   30 use warnings;
  8         9  
  8         277  
4 8     8   27 use strict;
  8         8  
  8         4244  
5              
6             sub new {
7 20     20 1 23 my $proto = shift;
8 20   33     61 my $class = ref($proto) || $proto;
9 20         23 my $self = {};
10 20         32 $self->{args} = shift;
11 20         28 $self->{mask} = shift;
12 20         24 bless($self, $class);
13 20         51 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 18 my $self = shift;
21 15         14 my ($img1, $img2, $mask) = @_;
22 15 50 33     47 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       420 if ($mask) {
29 4 50 33     21 unless(ref($mask) && $mask->isa('Imager')) {
30 0         0 die "Match mask must be an Imager image object!";
31             }
32 4 50 33     7 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     73 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 2 my $self = shift;
55             return (
56             method => $Image::Compare::reverse_class_map{ref($self)},
57             args => $self->{args},
58 1         10 );
59             }
60              
61             sub compare_images {
62 18     18 1 22 my $self = shift;
63 18         21 my ($img1, $img2, $mask) = @_;
64             # This will die if there's a problem.
65 18         176 $self->setup($img1, $img2, $mask);
66             # We spin over each pixel in img1.
67 18         79 my $wid = $img1->getwidth();
68 18         135 my $hig = $img1->getheight();
69 18         117 OUTER: for my $x (0 .. $wid - 1) {
70 34         48 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     137 if ($mask && (($mask->getpixel(x => $x, y => $y)->rgba())[0] == 255)) {
76 5         68 next;
77             }
78 61         368 my @pix1 = $img1->getpixel(x => $x, y => $y)->rgba();
79 61         908 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       786 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         66 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 51 my $self = shift;
97 61         48 my ($img2, $x, $y) = @_;
98 61         98 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 42 my $self = shift;
104 61         45 my ($pix1, $pix2) = @_;
105             # The sum of the squaws of the other two hides...
106             return sqrt(
107 61         187 ( ($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         70 my %args = @_;
128 48 50       140 unless (UNIVERSAL::isa($cmp_pkg, __PACKAGE__)) {
129 0         0 die "Comparaters must subclass __PACKAGE__!";
130             }
131 48         43 my $name = $cmp_pkg;
132 48 50 33     232 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   39 no strict qw/refs/;
  8         8  
  8         1182  
  48         43  
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     0   2540 *{"Image::Compare::$name"} = sub () { $name };
  48         256  
  0         0  
147 48         79 $Image::Compare::class_map{$name} = $cmp_pkg;
148 48         793 $Image::Compare::reverse_class_map{$cmp_pkg} = $name;
149             }
150             }
151              
152             # We will read in the list of packages to load from the documentation.
153             while () {
154             if (/^=item \* L<([^>]+)>/) {
155 8     8   2718 eval "use $1";
  8     8   18  
  8     8   49  
  8     8   2463  
  8     8   13  
  8         64  
  8         30  
  8         9  
  8         41  
  8         2669  
  8         15  
  8         50  
  8         2657  
  8         14  
  8         55  
156             die "Failed loading module '$1': $@" if $@;
157             }
158             }
159              
160             close Image::Compare::Comparator::DATA;
161              
162             1;
163              
164             __DATA__