File Coverage

blib/lib/File/Properties/Image.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             # ----------------------------------------------------------------------------
2             #
3             # This module supports computing and caching of image file properties.
4             #
5             # Copyright © 2010,2011 Brendt Wohlberg
6             # See distribution LICENSE file for license details.
7             #
8             # Most recent modification: 18 December 2011
9             #
10             # ----------------------------------------------------------------------------
11              
12             package File::Properties::Image;
13             our $VERSION = 0.02;
14              
15 1     1   8 use File::Properties::Error;
  1         3  
  1         14  
16 1     1   66 use File::Properties::Media;
  1         2  
  1         27  
17              
18             require 5.005;
19 1     1   5 use strict;
  1         3  
  1         37  
20 1     1   7 use warnings;
  1         3  
  1         46  
21 1     1   7 use Error qw(:try);
  1         2  
  1         6  
22 1     1   818 use Image::Magick;
  0            
  0            
23              
24              
25             our $DCRawBin = 'dcraw'; # DCRaw utility binary
26             our $RawBufferSize = 1048576; # Buffer size for use in raw image handling
27             our $CacheTableName = 'ImageFileCache';
28             our $CacheTableCols = ['ContentDigest TEXT','ImageDigest TEXT'];
29              
30              
31             # ----------------------------------------------------------------------------
32             # Constructor
33             # ----------------------------------------------------------------------------
34             sub new {
35             my $clss = shift;
36              
37             my $self = {};
38             bless $self, $clss;
39             $self->_init(@_);
40             return $self;
41             }
42              
43              
44             # ----------------------------------------------------------------------------
45             # Initialiser
46             # ----------------------------------------------------------------------------
47             sub _init {
48             my $self = shift;
49             my $fpmr = shift; # File::Properties::Media reference
50             my $fpcr = shift; # File::Properties::Cache reference
51              
52             # Ensure that a File::Properties::Media reference is specified
53             throw File::Properties::Error("Init value is not defined")
54             if not defined $fpmr;
55             ## If File::Properties::Cache reference specified and the cache
56             ## contains an entry with a matching digest value, set the image
57             ## digest from the cache entry; otherwise the image digest must be
58             ## computed.
59             if (defined $fpcr and
60             my $cent = $fpcr->cretrieve($CacheTableName,
61             {'ContentDigest' => $fpmr->cdigest})) {
62             $self->idigest($cent->{'ImageDigest'});
63             # Set flag indicating that this entry was obtained from the cache
64             $self->_fromcache(1);
65             } else {
66             # Get a file handle to the file, or to uncompressed content if it
67             # is compressed
68             my $fcfh = $fpmr->cfilehandle; ## NB: requires attention
69             # Image digest computation is handled differently for raw images
70             my $idgs = ($fpmr->mmimetype eq 'image/x-raw')?
71             _rawimagedigest($fcfh):_imagedigest($fcfh);
72             # Record the computed image digest
73             $self->idigest($idgs);
74             # Set flag indicating that this entry was not obtained from the cache
75             $self->_fromcache(0);
76             ## If a File::Properties::Cache reference is specified, record the
77             ## image digest entry in the cache
78             if (defined $fpcr) {
79             my $row = {'ContentDigest' => $fpmr->cdigest, 'ImageDigest' => $idgs};
80             $fpcr->cinsert($CacheTableName, $row);
81             }
82             }
83             }
84              
85              
86             # ----------------------------------------------------------------------------
87             # Get (or set) image digest
88             # ----------------------------------------------------------------------------
89             sub idigest {
90             my $self = shift;
91              
92             $self->{'idgs'} = shift if (@_);
93             return $self->{'idgs'};
94             }
95              
96              
97             # ----------------------------------------------------------------------------
98             # Construct string representing properties hash
99             # ----------------------------------------------------------------------------
100             sub string {
101             my $self = shift;
102             my $levl = shift;
103              
104             $levl = 0 if (!defined $levl);
105             my $lpfx = ' ' x (2*$levl);
106             return $lpfx . " Image Digest: ".substr($self->idigest,0,40)."...\n";
107             }
108              
109              
110             # ----------------------------------------------------------------------------
111             # Initialise cache table for File::Properties::Image data
112             # ----------------------------------------------------------------------------
113             sub _cacheinit {
114             my $fpcr = shift; # File::Properties::Cache reference
115              
116             $fpcr->define($CacheTableName, $CacheTableCols,
117             {'TableVersion' => [__PACKAGE__.'::Version', $VERSION]});
118             }
119              
120              
121             # ----------------------------------------------------------------------------
122             # Clear invalid entries in cache table for File::Properties::Image data
123             # ----------------------------------------------------------------------------
124             sub _cacheclean {
125             my $self = shift;
126             my $fpcr = shift; # File::Properties::Cache reference
127              
128             my $itbl = $CacheTableName;
129             my $mtbl = $File::Properties::Media::CacheTableName;
130             # Remove any entries in the File::Properties::Image cache table
131             # for which there is not a corresponding entry with the same content
132             # digest in the File::Properties::Media cache table
133             $fpcr->remove($itbl, {'Where' => "NOT EXISTS (SELECT * FROM $mtbl " .
134             "WHERE ContentDigest = $itbl.ContentDigest)"});
135             }
136              
137              
138             # ----------------------------------------------------------------------------
139             # Get or set flag indicating whether data was retrieved from the cache
140             # ----------------------------------------------------------------------------
141             sub _fromcache {
142             my $self = shift;
143              
144             $self->{'rfcf'} = shift if (@_);
145             return $self->{'rfcf'};
146             }
147              
148              
149             # ----------------------------------------------------------------------------
150             # Compute digest of image file image data
151             # ----------------------------------------------------------------------------
152             sub _imagedigest {
153             my $fhnd = shift; # File handle
154              
155             # Ensure that $fhnd is an IO::Handle object
156             throw File::Properties::Error("Argument is not an IO::Handle",$fhnd)
157             if (not defined $fhnd or not $fhnd->isa('IO::Handle'));
158             # Ensure that file handle position is at the start of the file
159             _seek0($fhnd) or
160             throw File::Properties::Error("Seek on file handle failed",$fhnd);
161             ## Initialise Image::Magick object, read in image pointed to by
162             ## $fhnd, and check for errors
163             my $imgk = Image::Magick->new;
164             my $err = $imgk->Read(file=>$fhnd);
165             throw File::Properties::Error("ImageMagick error: $err") if 0+$err < 1;
166             ## Construct temporary file and write image data to it
167             my $tmp = File::Temp->new;
168             $err = $imgk->Write(file=>$tmp,filename=>"rgb:");
169             # Return file handle position to start of file
170             _seek0($tmp) or
171             throw File::Properties::Error("Seek on file handle failed",$tmp);
172             ## Compute SHA-512 digest on file containing image data
173             my $sha = Digest::SHA->new(512);
174             $sha->addfile($tmp, 'b');
175             return $sha->hexdigest;
176             }
177              
178              
179             # ----------------------------------------------------------------------------
180             # Compute digest of raw image file image data
181             # ----------------------------------------------------------------------------
182             sub _rawimagedigest {
183             my $fhnd = shift; # File handle
184              
185             # Ensure that $fhnd is an IO::Handle object
186             throw File::Properties::Error("Argument is not an IO::Handle",$fhnd)
187             if (not defined $fhnd or not $fhnd->isa('IO::Handle'));
188             # Ensure that file handle position is at the start of the file
189             _seek0($fhnd) or
190             throw File::Properties::Error("Seek on file handle failed",$fhnd);
191             ## Construct a temporary file and write the content of the file
192             ## pointed to by $fhnd into it (the name of the file associated with
193             ## the file handle is not necessarily known, but the DCRaw interface
194             ## requires a filename).
195             my $tmp = File::Temp->new;
196             my ($rsz,$buf);
197             while ($rsz = $fhnd->read($buf, $RawBufferSize)) {
198             $tmp->write($buf, $rsz);
199             }
200             ## Apply DCRaw to the temporary file and receive the output via a pipe
201             my $cmd = "$DCRawBin -D -c " . $tmp->filename . " |";
202             my $pipe = IO::File->new($cmd);
203             throw File::Properties::Error("Failed to open pipe from dcraw",
204             {'cmd' => $cmd})
205             if not defined $pipe;
206             # Compute the image digest for the pipe file handle
207             return _imagedigest($pipe);
208             }
209              
210              
211             # ----------------------------------------------------------------------------
212             # Ensure file handle position is at start of file
213             # ----------------------------------------------------------------------------
214             sub _seek0 {
215             my $fhnd = shift; # IO::Handle reference
216              
217             return ($fhnd->tell > 0)?$fhnd->seek(0,0):1;
218             }
219              
220              
221             # ----------------------------------------------------------------------------
222             # End of method definitions
223             # ----------------------------------------------------------------------------
224              
225              
226             1;
227             __END__