| 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__ |