File Coverage

blib/lib/Image/Hash.pm
Criterion Covered Total %
statement 20 213 9.3
branch 4 66 6.0
condition 0 37 0.0
subroutine 5 21 23.8
pod 5 17 29.4
total 34 354 9.6


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