File Coverage

blib/lib/Image/Hash.pm
Criterion Covered Total %
statement 20 205 9.7
branch 4 66 6.0
condition 0 37 0.0
subroutine 5 18 27.7
pod 5 14 35.7
total 34 340 10.0


line stmt bran cond sub pod time code
1             package Image::Hash;
2              
3 1     1   28379 use strict;
  1         2  
  1         30  
4 1     1   3 use warnings;
  1         2  
  1         32  
5              
6 1     1   4 use List::Util qw(sum);
  1         4  
  1         82  
7 1     1   4 use Carp;
  1         2  
  1         2020  
8              
9             our $VERSION = '0.03';
10              
11              
12             =head1 NAME
13              
14             Image::Hash - Perceptual image hashing [aHash, dHash, pHash].
15              
16             =head1 SYNOPSIS
17              
18             use Image::Hash;
19             use File::Slurp;
20            
21             # Read a image from the command line
22             my $image = read_file( shift @ARGV, binmode => ':raw' ) ;
23              
24             my $ihash = Image::Hash->new($image);
25              
26             # Calculate the average hash
27             my $a = $ihash->ahash();
28              
29             # Calculate the difference hash
30             my $b = $ihash->dhash();
31              
32             # Calculate the perception hash
33             my $p = $ihash->phash();
34              
35             print "$a\n$b\n$p\n";
36              
37              
38              
39             =head1 DESCRIPTION
40              
41             Image::Hash allows you to calculate the average hash, difference hash and perception hash an image.
42              
43             Depending on what is available on your system Image::Hash will use GD, Image::Magick or Imager to interact with your image.
44              
45              
46              
47             =head1 CONSTRUCTOR METHODS
48              
49             my $ihash = Image::Hash->new($image [, $module ]);
50            
51             The first argument is a scalar with a binary representation of an image.
52              
53             You may also optionally specify a second argument of "GD", "ImageMagick" or "Imager" to force Image::Hash to use the specific image module when it interacts with the image.
54             The different image modules may give direct hashes for the same image. Using GD normally hives the best results, and are is highly recommended.
55              
56              
57             =cut
58              
59             sub new {
60 1     1 0 233 my $class = shift;
61              
62              
63 1         2 my $self = {};
64 1         2 bless( $self, $class );
65            
66 1         5 $self->{'image'} = shift;
67 1         3 $self->{'module'} = shift;
68            
69 1 50       3 if ($self->{'module'}) {
70             # Try to load the image handler the user asked for
71 0 0 0     0 if ($self->{'module'} eq "GD") {
    0          
    0          
72 0         0 require GD;
73             }
74             elsif ($self->{'module'} eq "ImageMagick" || $self->{'module'} eq "Image::Magick") {
75 0         0 require Image::Magick;
76             }
77             elsif ($self->{'module'} eq "Imager") {
78 0         0 require Imager;
79             }
80             else {
81 0         0 croak("Unknown mudule: '" . $self->{'module'} . "'. Please use either GD, ImageMagick or Imager as module.");
82             }
83             }
84             else {
85             # Try to load GD, ImageMagic or Imager
86 1 50       68 if (eval 'require GD') {
    50          
    50          
87 0         0 $self->{'module'} = "GD";
88             }
89             elsif (eval 'require Image::Magick') {
90 0         0 $self->{'module'} = "ImageMagick";
91             }
92             elsif (eval 'require Imager') {
93 0         0 $self->{'module'} = "Imager";
94             }
95             else {
96 1         368 croak("No image maudule avalibal. Can't load GD, ImageMagic or Imager.");
97             }
98             }
99            
100            
101              
102            
103 0 0         if ($self->{'module'} eq 'GD') {
    0          
    0          
104 0           $self->{'im'} = GD::Image->new( $self->{'image'} );
105 0 0         if ($self->{'im'} == 0) {
106 0           warn("Can't make image from this value.");
107 0           return undef;
108             }
109 0           $self->{'reduse'} = \&reduse_GD;
110 0           $self->{'pixels'} = \&pixels_GD;
111             }
112             elsif ($self->{'module'} eq 'ImageMagick') {
113 0           $self->{'im'} = Image::Magick->new();
114 0           my $ret = $self->{'im'}->BlobToImage( $self->{'image'} );
115 0 0         if ($ret == 0) {
116 0           warn("Can't make image from this value.");
117 0           return undef;
118             }
119 0           $self->{'reduse'} = \&reduse_ImageMagick;
120 0           $self->{'pixels'} = \&pixels_ImageMagick;
121             }
122             elsif ($self->{'module'} eq 'Imager') {
123 0           $self->{'im'} = Imager->new(data=>$self->{'image'});
124 0 0         if ($self->{'im'} == 0) {
125 0           warn("Can't make image from this value.");
126 0           return undef;
127             }
128 0           $self->{'reduse'} = \&reduse_Imager;
129 0           $self->{'pixels'} = \&pixels_Imager;
130             }
131            
132              
133              
134 0           return $self;
135             }
136              
137              
138             # Helper function:
139             # Convert from binary to hexadecimal
140             #
141             # Borrowed from http://www.perlmonks.org/index.pl?node_id=644225
142             sub b2h {
143 0     0 0   my $num = shift;
144 0           my $WIDTH = 32;
145 0           my $index = length($num) - $WIDTH;
146 0           my $hex = '';
147 0           do {
148 0           my $width = $WIDTH;
149 0 0         if ($index < 0) {
150 0           $width += $index;
151 0           $index = 0;
152             }
153 0           my $cut_string = substr($num, $index, $width);
154 0           $hex = sprintf('%X', oct("0b$cut_string")) . $hex;
155 0           $index -= $WIDTH;
156             } while ($index > (-1 * $WIDTH));
157 0           return $hex;
158             }
159              
160             # Reduse the size of an image using GD
161             sub reduse_GD {
162 0     0 0   my ($self, %opt) = @_;
163 0           $self->{ $opt{'im'} } = $self->{'im'};
164              
165 0           my ($xs, $ys) = split(/x/, $opt{'geometry'});
166              
167 0           $self->{ $opt{'im'} } = $self->{ $opt{'im'} }->copyResized($self->{ $opt{'im'} }, 0, 0, 0, 0, $xs, $ys, $self->{ $opt{'im'} }->getBounds);
168             }
169              
170             # Reduse the size of an image using Image::Magick
171             sub reduse_ImageMagick {
172 0     0 0   my ($self, %opt) = @_;
173 0           $self->{ $opt{'im'} } = $self->{'im'};
174              
175 0           $self->{ $opt{'im'} }->Set(antialias=>'True');
176 0           $self->{ $opt{'im'} }->Resize($opt{'geometry'});
177             }
178              
179             # Reduse the size of an image using Imager
180             sub reduse_Imager {
181 0     0 0   my ($self, %opt) = @_;
182 0           $self->{ $opt{'im'} } = $self->{'im'};
183              
184 0           my ($xs, $ys) = split(/x/, $opt{'geometry'});
185              
186 0           $self->{ $opt{'im'} } = $self->{ $opt{'im'} }->scale(xpixels=>30,ypixels=>30);
187             }
188              
189             # Return the pixel values for an image when using GD
190             sub pixels_GD {
191 0     0 0   my ($self, %opt) = @_;
192 0           $self->{ $opt{'im'} } = $self->{'im'};
193            
194 0           my ($xs, $ys) = split(/x/, $opt{'geometry'});
195            
196 0           my @pixels;
197 0           for(my $y=0; $y<$ys;$y++) {
198 0           for(my $x=0; $x<$xs;$x++) {
199              
200 0           my $color = $self->{ $opt{'im'} }->getPixel($x, $y);
201 0           my ($red, $green, $blue) = $self->{ $opt{'im'} }->rgb($color);
202 0           my $grey = $red*0.3 + $green*0.59 + $blue*0.11;
203 0           push(@pixels, $grey);
204             }
205             }
206            
207 0           return @pixels;
208             }
209              
210             # Return the pixel values for an image when using Image::Magick
211             sub pixels_ImageMagick {
212 0     0 0   my ($self, %opt) = @_;
213 0           $self->{ $opt{'im'} } = $self->{'im'};
214            
215 0           my ($xs, $ys) = split(/x/, $opt{'geometry'});
216            
217 0           my @pixels;
218 0           for(my $y=0; $y<$ys;$y++) {
219 0           for(my $x=0; $x<$xs;$x++) {
220              
221 0           my @pixel = $self->{ $opt{'im'} }->GetPixel(x=>$x,y=>$y,normalize => 0);
222 0           my $grey = $pixel[0]*0.3 + $pixel[1]*0.59 + $pixel[2]*0.11;
223 0           push(@pixels, $grey);
224             }
225             }
226            
227            
228 0           for (my $i = 0; $i <= $#pixels; $i++) {
229 0           $pixels[$i] = $pixels[$i] / 256;
230             }
231            
232 0           return @pixels;
233             }
234              
235             # Return the pixel values for an image when using Imager
236             sub pixels_Imager {
237 0     0 0   my ($self, %opt) = @_;
238 0           $self->{ $opt{'im'} } = $self->{'im'};
239            
240 0           my ($xs, $ys) = split(/x/, $opt{'geometry'});
241            
242 0           my @pixels;
243 0           for(my $y=0; $y<$ys;$y++) {
244 0           for(my $x=0; $x<$xs;$x++) {
245              
246 0           my @color = $self->{ $opt{'im'} }->getpixel(x => $x, y => $y);
247 0           my ($red, $green, $blue, $alpha) = $color[0]->rgba();
248 0           my $grey = $red*0.3 + $green*0.59 + $blue*0.11;
249 0           push(@pixels, $grey);
250             }
251             }
252            
253 0           return @pixels;
254             }
255              
256             =head1 HASHES
257              
258             =head2 ahash
259              
260             $ihash->ahash();
261             $ihash->ahash('geometry' => '8x8');
262              
263             Calculate the Average Hash
264            
265             Return an array of binary values in array context and a hex representative in scalar context.
266              
267             =cut
268             sub ahash {
269 0     0 1   my ($self, %opt) = @_;
270              
271 0   0       $opt{'geometry'} ||= '8x8';
272 0   0       $opt{'im'} ||= 'im_' . $opt{'geometry'};
273              
274 0 0         if(!$self->{ $opt{'im'} }) {
275 0           $self->{'reduse'}->($self, %opt );
276             }
277              
278 0           my @pixels = $self->{'pixels'}->($self, %opt );
279            
280             # aHash specific code
281            
282             # Find the mean values of all the values in the array
283 0           my $m = sum(@pixels)/@pixels;
284              
285 0           my @binvalue;
286              
287 0           foreach my $p (@pixels) {
288 0 0         if ($p > $m) {
289 0           push(@binvalue,'1');
290             }
291             else {
292 0           push(@binvalue,'0');
293             }
294             }
295            
296             # Return an array of binary values in array context and a hex representative in scalar context.
297 0 0         if ( wantarray() ) {
298 0           return @binvalue;
299             }
300             else {
301 0           return b2h( join('',@binvalue) );
302             }
303              
304             }
305              
306             =head2 dhash
307              
308             $ihash->dhash();
309             $ihash->dhash('geometry' => '8x8');
310              
311             Calculate the Dynamic Hash
312            
313             Return an array of binary values in array context and a hex representative in scalar context.
314            
315             =cut
316             sub dhash {
317 0     0 1   my ($self, %opt) = @_;
318            
319 0   0       $opt{'geometry'} ||= '9x8';
320 0   0       $opt{'im'} ||= 'im_' . $opt{'geometry'};
321              
322 0 0         if(!$self->{ $opt{'im'} }) {
323 0           $self->{'reduse'}->($self, %opt );
324             }
325              
326 0           my @pixels = $self->{'pixels'}->($self, %opt );
327            
328             # dHash specific code
329              
330 0           my ($xs, $ys) = split(/x/, $opt{'geometry'});
331              
332 0           my @binvalue;
333              
334 0           for (my $i = 0; $i <= $#pixels; $i++) {
335              
336 0 0         if(($i % $xs) != $xs -1) {
337 0 0         if ($pixels[$i] < $pixels[$i+1]) {
338 0           push(@binvalue,'1');
339             }
340             else {
341 0           push(@binvalue,'0');
342             }
343             }
344             }
345              
346             # Return an array of binary values in array context and a hex representative in scalar context.
347 0 0         if ( wantarray() ) {
348 0           return @binvalue;
349             }
350             else {
351 0           return b2h( join('',@binvalue) );
352             }
353             }
354              
355             =head2 phash
356              
357             $ihash->phash();
358             $ihash->phash('geometry' => '8x8');
359              
360             Calculate the Perceptual Hash
361            
362             Return an array of binary values in array context and a hex representative in scalar context.
363              
364             =cut
365             # Some code taken from http://jax-work-archive.blogspot.no/2013/05/php-ahash-phash-dhash.html
366             sub getDctConst{
367            
368 0     0 0   my @_dctConst;
369 0           for (my $dctP=0; $dctP<8; $dctP++) {
370 0           for (my $p=0;$p<32;$p++) {
371 0           $_dctConst[$dctP][$p] =
372             cos( ((2*$p + 1)/64) * $dctP * '3.1415926535898' );
373             }
374             }
375            
376 0           return @_dctConst;
377             }
378              
379             # Some code taken from http://jax-work-archive.blogspot.no/2013/05/php-ahash-phash-dhash.html
380             sub phash {
381 0     0 1   my ($self, %opt) = @_;
382              
383 0   0       $opt{'geometry'} ||= '32x32';
384 0   0       $opt{'im'} ||= 'im_' . $opt{'geometry'};
385              
386 0 0         if(!$self->{ $opt{'im'} }) {
387 0           $self->{'reduse'}->($self, %opt );
388             }
389              
390 0           my @pixels = $self->{'pixels'}->($self, %opt );
391            
392             # Put the pixel into a multi dimentional array
393 0           my @grays;
394 0           for (my $y=0; $y<32; $y++){
395 0           for (my $x=0; $x<32; $x++){
396 0           $grays[$y][$x] = shift @pixels;
397             }
398             }
399            
400             # pHash specific code
401             # DCT 8x8
402 0           my @dctConst = getDctConst();
403 0           my $dctSum = 0;
404 0           my @dcts;
405 0           for (my $dctY=0; $dctY<8; $dctY++) {
406 0           for (my $dctX=0; $dctX<8; $dctX++) {
407              
408 0           my $sum = 1;
409 0           for (my $y=0;$y<32;$y++) {
410 0           for (my $x=0;$x<32;$x++) {
411 0           $sum +=
412             $dctConst[$dctY][$y] *
413             $dctConst[$dctX][$x] *
414             $grays[$y][$x];
415             }
416             }
417              
418             # apply coefficients
419 0           $sum *= .25;
420 0 0 0       if ($dctY == 0 || $dctX == 0) {
421 0           $sum *= 1/sqrt(2);
422             }
423              
424 0           push(@dcts,$sum);
425 0           $dctSum += $sum;
426             }
427             }
428              
429            
430 0           my $average = $dctSum/64;
431              
432 0           my @binvalue;
433 0           foreach my $dct (@dcts) {
434 0 0         push(@binvalue,($dct>=$average) ? '1' : '0');
435             }
436              
437             # Return an array of binary values in array context and a hex representative in scalar context.
438 0 0         if ( wantarray() ) {
439 0           return @binvalue;
440             }
441             else {
442 0           return b2h( join('',@binvalue) );
443             }
444             }
445              
446             =head1 DEBUGGING
447              
448             Functions useful for debug purposes.
449              
450             =head2 dump
451              
452              
453              
454             my $ihash = Image::Hash->new($image, $module);
455              
456             my @hash = $ihash->ahash();
457             $ihash->dump('hash' => \@hash );
458              
459            
460             array( [ 183 (1), 189 (1), 117 (0), 80 (0), 183 (1), 189 (1), 189 (1), 189 (1) ],
461             [ 183 (1), 158 (0), 89 (0), 211 (1), 89 (0), 189 (1), 168 (1), 162 (1) ],
462             [ 176 (1), 151 (0), 93 (0), 160 (1), 160 (1), 191 (1), 154 (0), 154 (0) ],
463             [ 195 (1), 139 (0), 53 (0), 168 (1), 83 (0), 205 (1), 146 (0), 146 (0) ],
464             [ 195 (1), 195 (1), 183 (1), 160 (1), 160 (1), 199 (1), 124 (0), 129 (0) ],
465             [ 187 (1), 183 (1), 183 (1), 195 (1), 180 (1), 193 (1), 129 (0), 135 (0) ],
466             [ 176 (1), 180 (1), 174 (1), 183 (1), 176 (1), 176 (1), 135 (0), 146 (0) ],
467             [ 162 (1), 171 (1), 99 (0), 149 (0), 129 (0), 162 (1), 140 (0), 146 (0) ])
468              
469             Dump the array used when generating hashes. Option 'hash' may be specified to show with pixel has witch value in the hash.
470              
471             =cut
472             sub dump {
473 0     0 1   my ($self, %opt) = @_;
474            
475 0   0       $opt{'geometry'} ||= '8x8';
476 0   0       $opt{'im'} ||= 'im_' . $opt{'geometry'};
477              
478 0 0         if(!$self->{ $opt{'im'} }) {
479 0           $self->{'reduse'}->($self, %opt );
480             }
481              
482 0           my @pixels = $self->{'pixels'}->($self, %opt );
483            
484             # dump specific code
485 0 0 0       if ($opt{'hash'} && $opt{'geometry'} ne '8x8') {
486 0           carp("The geometry must be 8x8 when calling dump with a hash to highlight.");
487             }
488              
489 0 0         if (scalar @{ $opt{'hash'} } != 64) {
  0            
490 0           carp("'hash' must be a 64 element array.");
491             }
492            
493 0           my ($xs, $ys) = split(/x/, $opt{'geometry'});
494              
495 0           print "array(\t[ ";
496 0           for (my $i = 0; $i <= $#pixels; $i++) {
497 0 0 0       if (($i % $xs) == 0 && $i != 0) {print " ],\n\t[ "} elsif($i != 0) { print ', '; }
  0 0          
  0            
498              
499 0 0         if ($opt{'hash'}) {
500 0           printf("%3s (%1s)", int($pixels[$i]), shift @{ $opt{'hash'} });
  0            
501             }
502             else {
503 0           printf("%3s", int($pixels[$i]));
504             }
505             }
506 0           print " ])\n";
507              
508             }
509              
510             =head2 reducedimage
511              
512             use Image::Hash;
513             use File::Slurp;
514              
515             my $file = shift @ARGV or die("Pleas spesyfi a file to read!");
516              
517             my $image = read_file( $file, binmode => ':raw' ) ;
518              
519             my $ihash = Image::Hash->new($image);
520              
521             binmode STDOUT;
522             print STDOUT $ihash->reducedimage();
523            
524             Returns the reduced image that will be used by the hash functions.
525            
526             =cut
527             sub reducedimage {
528 0     0 1   my ($self, %opt) = @_;
529              
530            
531 0   0       $opt{'geometry'} ||= '8x8';
532 0   0       $opt{'im'} ||= 'im_' . $opt{'geometry'};
533              
534              
535 0 0         if(!$self->{ $opt{'im'} }) {
536 0           $self->reduse( %opt );
537             }
538              
539 0           my $data;
540 0 0         $self->{ $opt{'im'} }->write(data => \$data, type => 'png') or carp $self->{ $opt{'im'} }->errstr;
541 0           return $data;
542             }
543              
544             =head1 EXAMPLES
545              
546             Please see the C directory for further examples.
547              
548             =head1 BUGS
549              
550             =head1 AUTHOR
551              
552             Runar Buvik
553             CPAN ID: RUNARB
554             runarb@gmail.com
555             http://www.runarb.com
556              
557             =head1 Git
558              
559             https://github.com/runarbu/PerlImageHash
560              
561             =head1 COPYRIGHT
562              
563             This program is free software; you can redistribute
564             it and/or modify it under the same terms as Perl itself.
565              
566             The full text of the license can be found in the
567             LICENSE file included with this module.
568              
569              
570             =head1 SEE ALSO
571              
572             Articles L and L by Neal Krawetz that describes the theory behind aHash, dHash, pHash.
573              
574             L image hashing library written in Python that dos the same thing.
575              
576             L a PHP class that do the same thing.
577              
578             =cut
579              
580             #################### main pod documentation end ###################
581              
582              
583             1;
584             # The preceding line will help the module return a true value
585