File Coverage

blib/lib/Image/Size.pm
Criterion Covered Total %
statement 243 357 68.0
branch 48 136 35.2
condition 18 41 43.9
subroutine 25 31 80.6
pod 3 22 13.6
total 337 587 57.4


line stmt bran cond sub pod time code
1             ###############################################################################
2             #
3             # This file copyright (c) 2015 by Randy J. Ray, all rights reserved
4             #
5             # Copying and distribution are permitted under the terms of the Artistic
6             # License 2.0 (http://www.opensource.org/licenses/artistic-license-2.0.php) or
7             # the GNU LGPL (http://www.opensource.org/licenses/lgpl-2.1.php).
8             #
9             ###############################################################################
10             #
11             # Once upon a time, this code was lifted almost verbatim from wwwis by Alex
12             # Knowles, alex@ed.ac.uk. Since then, even I barely recognize it. It has
13             # contributions, fixes, additions and enhancements from all over the world.
14             #
15             # See the file ChangeLog for change history.
16             #
17             ###############################################################################
18              
19             package Image::Size;
20              
21             require 5.006001;
22              
23             # These are the Perl::Critic policies that are being turned off globally:
24             ## no critic(RequireBriefOpen)
25             ## no critic(ProhibitAutomaticExportation)
26              
27 2     2   22826 use strict;
  2         3  
  2         65  
28 2     2   7 use warnings;
  2         2  
  2         47  
29 2     2   1129 use bytes;
  2         23  
  2         9  
30 2         254 use vars qw(
31             @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION $NO_CACHE %CACHE
32             $GIF_BEHAVIOR @TYPE_MAP %PCD_MAP $PCD_SCALE $READ_IN $LAST_POS
33 2     2   74 );
  2         3  
34              
35 2     2   9 use Exporter 'import';
  2         2  
  2         130  
36              
37             BEGIN
38             {
39 2     2   4 @EXPORT = qw(imgsize);
40 2         4 @EXPORT_OK = qw(imgsize html_imgsize attr_imgsize
41             %CACHE $NO_CACHE $PCD_SCALE $GIF_BEHAVIOR);
42 2         6 %EXPORT_TAGS = ('all' => [ @EXPORT_OK ]);
43              
44 2         2 $VERSION = '3.300';
45 2         85 $VERSION = eval $VERSION; ## no critic(ProhibitStringyEval)
46              
47             # Default behavior for GIFs is to return the "screen" size
48 2         6365 $GIF_BEHAVIOR = 0;
49             }
50              
51             # This allows people to specifically request that the cache not be used
52             $NO_CACHE = 0;
53              
54             # Package lexicals - invisible to outside world, used only in imgsize
55             #
56             # Mapping of patterns to the sizing routines
57             @TYPE_MAP = (
58             qr{^GIF8[79]a} => \&gifsize,
59             qr{^\xFF\xD8} => \&jpegsize,
60             qr{^\x89PNG\x0d\x0a\x1a\x0a} => \&pngsize,
61             qr{^P[1-7]} => \&ppmsize, # also XVpics
62             qr{#define\s+\S+\s+\d+} => \&xbmsize,
63             qr{/[*] XPM [*]/} => \&xpmsize,
64             qr{^MM\x00\x2a} => \&tiffsize,
65             qr{^II\x2a\x00} => \&tiffsize,
66             qr{^BM} => \&bmpsize,
67             qr{^8BPS} => \&psdsize,
68             qr{^PCD_OPA} => \&pcdsize,
69             qr{^FWS} => \&swfsize,
70             qr{^CWS} => \&swfmxsize,
71             qr{^\x8aMNG\x0d\x0a\x1a\x0a} => \&mngsize,
72             qr{^\x01\x00\x00\x00} => \&emfsize,
73             qr{^RIFF(?s:....)WEBP} => \&webpsize,
74             qr{^\x00\x00\x01\x00} => \&icosize,
75             qr{^\x00\x00\x02\x00} => \&cursize,
76             );
77             # Kodak photo-CDs are weird. Don't ask me why, you really don't want details.
78             %PCD_MAP = ( 'base/16' => [ 192, 128 ],
79             'base/4' => [ 384, 256 ],
80             'base' => [ 768, 512 ],
81             'base4' => [ 1536, 1024 ],
82             'base16' => [ 3072, 2048 ],
83             'base64' => [ 6144, 4096 ], );
84             # Default scale for PCD images
85             $PCD_SCALE = 'base';
86              
87             # These are lexically-scoped anonymous subroutines for reading the three
88             # types of input streams. When the input to imgsize() is typed, then the
89             # lexical "read_in" is assigned one of these, thus allowing the individual
90             # routines to operate on these streams abstractly.
91              
92             my $read_io = sub {
93             my $handle = shift;
94             my ($length, $offset) = @_;
95              
96             if (defined($offset) && ($offset != $LAST_POS))
97             {
98             $LAST_POS = $offset;
99             return q{} if (! seek $handle, $offset, 0);
100             }
101              
102             my ($buffer, $rtn) = (q{}, 0);
103             $rtn = read $handle, $buffer, $length;
104             if (! $rtn)
105             {
106             $buffer = q{};
107             }
108             $LAST_POS = tell $handle;
109              
110             return $buffer;
111             };
112              
113             my $read_buf = sub {
114             my $buf = shift;
115             my ($length, $offset) = @_;
116              
117             if (defined($offset) && ($offset != $LAST_POS))
118             {
119             $LAST_POS = $offset;
120             return q{} if ($LAST_POS > length ${$buf});
121             }
122              
123             my $content = substr ${$buf}, $LAST_POS, $length;
124             $LAST_POS += length $content;
125              
126             return $content;
127             };
128              
129             sub imgsize ## no critic(ProhibitExcessComplexity)
130             {
131 23     23 1 65938 my $stream = shift;
132              
133 23         37 my ($handle, $header);
134 0         0 my ($x, $y, $id, $mtime, @list);
135             # These only used if $stream is an existing open FH
136 23         26 my ($save_pos, $need_restore) = (0, 0);
137             # This is for when $stream is a locally-opened file
138 23         20 my $need_close = 0;
139             # This will contain the file name, if we got one
140 23         40 my $file_name = undef;
141              
142 23         22 $header = q{};
143              
144 23 100       84 if (ref($stream) eq 'SCALAR')
    100          
145             {
146 1         2 $handle = $stream;
147 1         2 $READ_IN = $read_buf;
148 1   50     1 $header = substr ${$handle} || q{}, 0, 256;
149             }
150             elsif (ref $stream)
151             {
152             # I no longer require $stream to be in the IO::* space. So I'm assuming
153             # you don't hose yourself by passing a ref that can't do fileops. If
154             # you do, you fix it.
155 2         2 $handle = $stream;
156 2         3 $READ_IN = $read_io;
157 2         5 $save_pos = tell $handle;
158 2         2 $need_restore = 1;
159              
160             # First alteration (didn't wait long, did I?) to the existing handle:
161             #
162             # assist dain-bramaged operating systems -- SWD
163             # SWD: I'm a bit uncomfortable with changing the mode on a file
164             # that something else "owns" ... the change is global, and there
165             # is no way to reverse it.
166             # But image files ought to be handled as binary anyway.
167 2         5 binmode $handle;
168 2         3 seek $handle, 0, 0;
169 2         12 read $handle, $header, 256;
170 2         5 seek $handle, 0, 0;
171             }
172             else
173             {
174 20 50       51 if (! $NO_CACHE)
175             {
176 20         85 require Cwd;
177 20         44 require File::Spec;
178              
179 20 50       221 if (! File::Spec->file_name_is_absolute($stream))
180             {
181 20         31647 $stream = File::Spec->catfile(Cwd::cwd(), $stream);
182             }
183 20         470 $mtime = (stat $stream)[9];
184 20 50 66     334 if (-e "$stream" and exists $CACHE{$stream})
185             {
186 0         0 @list = split /,/, $CACHE{$stream}, 4;
187              
188             # Don't return the cache if the file is newer.
189 0 0       0 if ($mtime <= $list[0])
190             {
191 0         0 return @list[1 .. 3];
192             }
193             # In fact, clear it
194 0         0 delete $CACHE{$stream};
195             }
196             }
197              
198             # first try to open the stream
199 20         148 require Symbol;
200 20         111 $handle = Symbol::gensym();
201 20 100       1004 if (! open $handle, '<', $stream)
202             {
203 1         20 return (undef, undef, "Can't open image file $stream: $!");
204             }
205              
206 19         32 $need_close = 1;
207             # assist dain-bramaged operating systems -- SWD
208 19         52 binmode $handle;
209 19         191 read $handle, $header, 256;
210 19         65 seek $handle, 0, 0;
211 19         56 $READ_IN = $read_io;
212 19         37 $file_name = $stream;
213             }
214 22         23 $LAST_POS = 0;
215              
216             # Right now, $x, $y and $id are undef. If the while-loop below doesn't
217             # match the header to a file-type and call a subroutine, then the later
218             # block that tried Image::Magick will default to setting the id/error to
219             # "unknown file type".
220 22         24 my $tm_idx = 0;
221 22         86 while ($tm_idx < @TYPE_MAP)
222             {
223 161 100       553 if ($header =~ $TYPE_MAP[$tm_idx])
224             {
225 22         90 ($x, $y, $id) = $TYPE_MAP[$tm_idx + 1]->($handle);
226 22         35 last;
227             }
228 139         173 $tm_idx += 2;
229             }
230              
231             # Added as an afterthought: I'm probably not the only one who uses the
232             # same shaded-sphere image for several items on a bulleted list:
233 22 100 66     151 if (! ($NO_CACHE or (ref $stream) or (! defined $x)))
      66        
234             {
235 19         95 $CACHE{$stream} = join q{,}, $mtime, $x, $y, $id;
236             }
237              
238             # If we were passed an existing file handle, we need to restore the
239             # old filepos:
240 22 100       33 if ($need_restore)
241             {
242 2         7 seek $handle, $save_pos, 0;
243             }
244             # ...and if we opened the file ourselves, we need to close it
245 22 100       31 if ($need_close)
246             {
247 19         123 close $handle; ## no critic(RequireCheckedClose)
248             }
249              
250 22 50       35 if (! defined $id)
251             {
252 0 0       0 if ($file_name)
253             {
254             # Image::Magick operates on file names.
255 0         0 ($x, $y, $id) = imagemagick_size($file_name);
256             }
257             else
258             {
259 0         0 $id = 'Data stream is not a known image file format';
260             }
261             }
262              
263             # results:
264 22 50       200 return (wantarray) ? ($x, $y, $id) : ();
265             }
266              
267             sub imagemagick_size
268             {
269 0     0 0 0 my $file_name = shift;
270              
271 0         0 my $module_name;
272             # First see if we have already loaded Graphics::Magick or Image::Magick
273             # If so, just use whichever one is already loaded.
274 0 0       0 if (exists $INC{'Graphics/Magick.pm'})
    0          
    0          
    0          
275             {
276 0         0 $module_name = 'Graphics::Magick';
277             }
278             elsif (exists $INC{'Image/Magick.pm'})
279             {
280 0         0 $module_name = 'Image::Magick';
281             }
282             # If neither are already loaded, try loading either one.
283             elsif (_load_magick_module('Graphics::Magick'))
284             {
285 0         0 $module_name = 'Graphics::Magick';
286             }
287             elsif (_load_magick_module('Image::Magick'))
288             {
289 0         0 $module_name = 'Image::Magick';
290             }
291              
292 0 0       0 if ($module_name)
293             {
294 0         0 my $img = $module_name->new();
295 0         0 my $x = $img->Read($file_name);
296             # Image::Magick error handling is a bit weird, see
297             #
298 0 0       0 if("$x") {
299 0         0 return (undef, undef, "$x");
300             } else {
301 0         0 return ($img->Get('width', 'height', 'format'));
302             }
303              
304             }
305             else {
306 0         0 return (undef, undef, 'Data stream is not a known image file format');
307             }
308             }
309              
310             # load Graphics::Magick or Image::Magick if one is not already loaded.
311             sub _load_magick_module {
312 0     0   0 my $module_name = shift;
313 0         0 my $retval = eval {
314 0         0 local $SIG{__DIE__} = q{};
315 0         0 require $module_name;
316 0         0 1;
317             };
318 0 0       0 return $retval ? 1 : 0;
319             }
320              
321              
322             sub html_imgsize
323             {
324 1     1 1 490 my @args = @_;
325 1         7 @args = imgsize(@args);
326              
327             # Use lowercase and quotes so that it works with xhtml.
328 1 50       11 return ((defined $args[0]) ?
329             sprintf('width="%d" height="%d"', @args[0,1]) :
330             undef);
331             }
332              
333             sub attr_imgsize
334             {
335 1     1 1 291 my @args = @_;
336 1         3 @args = imgsize(@args);
337              
338 1 50       12 return ((defined $args[0]) ?
339             (('-width', '-height', @args)[0, 2, 1, 3]) :
340             undef);
341             }
342              
343             # This used only in gifsize:
344             sub img_eof
345             {
346 0     0 0 0 my $stream = shift;
347              
348 0 0       0 if (ref($stream) eq 'SCALAR')
349             {
350 0         0 return ($LAST_POS >= length ${$stream});
  0         0  
351             }
352              
353 0         0 return eof $stream;
354             }
355              
356             # Simple converter-routine used by SWF and CWS code
357             sub _bin2int
358             {
359 8     8   13 my $val = shift;
360             # "no critic" because I want it clear which args are being used by
361             # substr() versus unpack().
362             ## no critic (ProhibitParensWithBuiltins)
363 8         32 return unpack 'N', pack 'B32', substr(('0' x 32) . $val, -32);
364             }
365              
366             ###########################################################################
367             # Subroutine gets the size of the specified GIF
368             ###########################################################################
369             sub gifsize ## no critic(ProhibitExcessComplexity)
370             {
371 5     5 0 7 my $stream = shift;
372              
373 5         5 my ($cmapsize, $buf, $sh, $sw, $x, $y, $type);
374              
375             my $gif_blockskip = sub {
376 0     0   0 my ($skip, $blocktype) = @_;
377 0         0 my ($lbuf);
378              
379 0         0 $READ_IN->($stream, $skip); # Skip header (if any)
380 0         0 while (1)
381             {
382 0 0       0 if (img_eof($stream))
383             {
384             return (undef, undef,
385 0         0 "Invalid/Corrupted GIF (at EOF in GIF $blocktype)");
386             }
387 0         0 $lbuf = $READ_IN->($stream, 1); # Block size
388 0 0       0 last if ord($lbuf) == 0; # Block terminator
389 0         0 $READ_IN->($stream, ord $lbuf); # Skip data
390             }
391 5         40 };
392              
393 5 50       13 if ($GIF_BEHAVIOR > 2)
394             {
395             return (undef, undef,
396 0         0 "\$Image::Size::GIF_BEHAVIOR out of range: $GIF_BEHAVIOR");
397             }
398              
399             # Skip over the identifying string, since we already know this is a GIF
400 5         18 $type = $READ_IN->($stream, 6);
401 5 50       9 if (length($buf = $READ_IN->($stream, 7)) != 7 )
402             {
403 0         0 return (undef, undef, 'Invalid/Corrupted GIF (bad header)');
404             }
405 5         26 ($sw, $sh, $x) = unpack 'vv C', $buf;
406 5 50       12 if ($GIF_BEHAVIOR == 0)
407             {
408 5         59 return ($sw, $sh, 'GIF');
409             }
410              
411 0 0       0 if ($x & 0x80)
412             {
413 0         0 $cmapsize = 3 * (2**(($x & 0x07) + 1));
414 0 0       0 if (! $READ_IN->($stream, $cmapsize))
415             {
416             return (undef, undef,
417 0         0 'Invalid/Corrupted GIF (global color map too small?)');
418             }
419             }
420              
421             # Before we start this loop, set $sw and $sh to 0s and use them to track
422             # the largest sub-image in the overall GIF.
423 0         0 $sw = $sh = 0;
424              
425             FINDIMAGE:
426 0         0 while (1)
427             {
428 0 0       0 if (img_eof($stream))
429             {
430             # At this point, if we haven't returned then the user wants the
431             # largest of the sub-images. So, if $sh and $sw are still 0s, then
432             # we didn't see even one Image Descriptor block. Otherwise, return
433             # those two values.
434 0 0 0     0 if ($sw and $sh)
435             {
436 0         0 return ($sw, $sh, 'GIF');
437             }
438             else
439             {
440             return (undef, undef,
441 0         0 'Invalid/Corrupted GIF (no Image Descriptors)');
442             }
443             }
444 0         0 $buf = $READ_IN->($stream, 1);
445 0         0 ($x) = unpack 'C', $buf;
446 0 0       0 if ($x == 0x2c)
447             {
448             # Image Descriptor (GIF87a, GIF89a 20.c.i)
449 0 0       0 if (length($buf = $READ_IN->($stream, 8)) != 8)
450             {
451             return (undef, undef,
452 0         0 'Invalid/Corrupted GIF (missing image header?)');
453             }
454 0         0 ($x, $y) = unpack 'x4 vv', $buf;
455 0 0       0 return ($x, $y, 'GIF') if ($GIF_BEHAVIOR == 1);
456 0 0 0     0 if ($x > $sw and $y > $sh)
457             {
458 0         0 $sw = $x;
459 0         0 $sh = $y;
460             }
461             }
462 0 0       0 if ($x == 0x21)
463             {
464             # Extension Introducer (GIF89a 23.c.i, could also be in GIF87a)
465 0         0 $buf = $READ_IN->($stream, 1);
466 0         0 ($x) = unpack 'C', $buf;
467 0 0       0 if ($x == 0xF9)
    0          
    0          
    0          
468             {
469             # Graphic Control Extension (GIF89a 23.c.ii)
470 0         0 $READ_IN->($stream, 6); # Skip it
471 0         0 next FINDIMAGE; # Look again for Image Descriptor
472             }
473             elsif ($x == 0xFE)
474             {
475             # Comment Extension (GIF89a 24.c.ii)
476 0         0 $gif_blockskip->(0, 'Comment');
477 0         0 next FINDIMAGE; # Look again for Image Descriptor
478             }
479             elsif ($x == 0x01)
480             {
481             # Plain Text Label (GIF89a 25.c.ii)
482 0         0 $gif_blockskip->(13, 'text data');
483 0         0 next FINDIMAGE; # Look again for Image Descriptor
484             }
485             elsif ($x == 0xFF)
486             {
487             # Application Extension Label (GIF89a 26.c.ii)
488 0         0 $gif_blockskip->(12, 'application data');
489 0         0 next FINDIMAGE; # Look again for Image Descriptor
490             }
491             else
492             {
493             return (undef, undef,
494 0         0 sprintf 'Invalid/Corrupted GIF (Unknown ' .
495             'extension %#x)', $x);
496             }
497             }
498             else
499             {
500             return (undef, undef,
501 0         0 sprintf 'Invalid/Corrupted GIF (Unknown code %#x)', $x);
502             }
503             }
504              
505 0         0 return (undef, undef, 'gifsize fell through to the end, error');
506             }
507              
508             sub xbmsize
509             {
510 1     1 0 3 my $stream = shift;
511              
512 1         3 my $input;
513 1         3 my ($x, $y, $id) = (undef, undef, 'Could not determine XBM size');
514              
515 1         20 $input = $READ_IN->($stream, 1024);
516 1 50       14 if ($input =~ /^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)/ix)
517             {
518 1         10 ($x, $y) = ($1, $2);
519 1         26 $id = 'XBM';
520             }
521              
522 1         6 return ($x, $y, $id);
523             }
524              
525             # Added by Randy J. Ray, 30 Jul 1996
526             # Size an XPM file by looking for the "X Y N W" line, where X and Y are
527             # dimensions, N is the total number of colors defined, and W is the width of
528             # a color in the ASCII representation, in characters. We only care about X & Y.
529             sub xpmsize
530             {
531 1     1 0 4 my $stream = shift;
532              
533 1         2 my $line;
534 1         4 my ($x, $y, $id) = (undef, undef, 'Could not determine XPM size');
535              
536 1         4 while ($line = $READ_IN->($stream, 1024))
537             {
538 1 50       12 if ($line =~ /"\s*(\d+)\s+(\d+)(\s+\d+\s+\d+){1,2}\s*"/)
539             {
540 1         7 ($x, $y) = ($1, $2);
541 1         2 $id = 'XPM';
542 1         26 last;
543             }
544             }
545              
546 1         5 return ($x, $y, $id);
547             }
548              
549             # pngsize : gets the width & height (in pixels) of a png file
550             # cor this program is on the cutting edge of technology! (pity it's blunt!)
551             #
552             # Re-written and tested by tmetro@vl.com
553             sub pngsize
554             {
555 1     1 0 5 my $stream = shift;
556              
557 1         5 my ($x, $y, $id) = (undef, undef, 'Could not determine PNG size');
558 1         5 my ($offset, $length);
559              
560             # Offset to first Chunk Type code = 8-byte ident + 4-byte chunk length + 1
561 1         3 $offset = 12; $length = 4;
  1         3  
562 1 50       6 if ($READ_IN->($stream, $length, $offset) eq 'IHDR')
563             {
564             # IHDR = Image Header
565 1         2 $length = 8;
566 1         3 ($x, $y) = unpack 'NN', $READ_IN->($stream, $length);
567 1         4 $id = 'PNG';
568             }
569              
570 1         3 return ($x, $y, $id);
571             }
572              
573             # mngsize: gets the width and height (in pixels) of an MNG file.
574             # See for the specification.
575             #
576             # Basically a copy of pngsize.
577             sub mngsize
578             {
579 0     0 0 0 my $stream = shift;
580              
581 0         0 my ($x, $y, $id) = (undef, undef, 'Could not determine MNG size');
582 0         0 my ($offset, $length);
583              
584             # Offset to first Chunk Type code = 8-byte ident + 4-byte chunk length + 1
585 0         0 $offset = 12; $length = 4;
  0         0  
586 0 0       0 if ($READ_IN->($stream, $length, $offset) eq 'MHDR')
587             {
588             # MHDR = Image Header
589 0         0 $length = 8;
590 0         0 ($x, $y) = unpack 'NN', $READ_IN->($stream, $length);
591 0         0 $id = 'MNG';
592             }
593              
594 0         0 return ($x, $y, $id);
595             }
596              
597             # jpegsize: gets the width and height (in pixels) of a jpeg file
598             # Andrew Tong, werdna@ugcs.caltech.edu February 14, 1995
599             # modified slightly by alex@ed.ac.uk
600             # and further still by rjray@blackperl.com
601             # optimization and general re-write from tmetro@vl.com
602             sub jpegsize
603             {
604 2     2 0 4 my $stream = shift;
605              
606 2         6 my $MARKER = chr 0xff; # Section marker
607              
608 2         2 my $SIZE_FIRST = 0xC0; # Range of segment identifier codes
609 2         4 my $SIZE_LAST = 0xC3; # that hold size info.
610              
611 2         6 my ($x, $y, $id) = (undef, undef, 'Could not determine JPEG size');
612              
613 2         4 my ($marker, $code, $length);
614 0         0 my $segheader;
615              
616             # Dummy read to skip header ID
617 2         8 $READ_IN->($stream, 2);
618 2         2 while (1)
619             {
620 8         11 $segheader = $READ_IN->($stream, 2);
621              
622             # Extract the segment header.
623 8         19 ($marker, $code) = unpack 'a a', $segheader;
624              
625 8   66     31 while ( $code eq $MARKER && ($marker = $code) ) {
626 36         45 $segheader = $READ_IN->($stream, 1);
627 36         130 ($code) = unpack 'a', $segheader;
628             }
629 8         12 $segheader = $READ_IN->($stream, 2);
630 8         7 $length = unpack 'n', $segheader;
631              
632             # Verify that it's a valid segment.
633 8 50 66     42 if ($marker ne $MARKER)
    100          
634             {
635             # Was it there?
636 0         0 $id = 'JPEG marker not found';
637 0         0 last;
638             }
639             elsif ((ord($code) >= $SIZE_FIRST) && (ord($code) <= $SIZE_LAST))
640             {
641             # Segments that contain size info
642 2         2 $length = 5;
643 2         4 my $buf = $READ_IN->($stream, $length);
644             # unpack dies on truncated data
645 2 50       6 last if (length($buf) < $length);
646 2         5 ($y, $x) = unpack 'xnn', $buf;
647 2         2 $id = 'JPG';
648 2         8 last;
649             }
650             else
651             {
652             # Dummy read to skip over data
653 6         12 $READ_IN->($stream, ($length - 2));
654             }
655             }
656              
657 2         5 return ($x, $y, $id);
658             }
659              
660             # ppmsize: gets data on the PPM/PGM/PBM family.
661             #
662             # Contributed by Carsten Dominik
663             sub ppmsize
664             {
665 1     1 0 4 my $stream = shift;
666              
667 1         4 my ($x, $y, $id) =
668             (undef, undef, 'Unable to determine size of PPM/PGM/PBM data');
669 1         2 my $n;
670 1         6 my @table = qw(nil PBM PGM PPM PBM PGM PPM);
671              
672 1         4 my $header = $READ_IN->($stream, 1024);
673              
674             # PPM file of some sort
675 1         4 $header =~ s/^\#.*//mg;
676 1 50       18 if ($header =~ /^(?:P([1-7]))\s+(\d+)\s+(\d+)/)
677             {
678 1         10 ($n, $x, $y) = ($1, $2, $3);
679              
680 1 50       5 if ($n == 7)
681             {
682             # John Bradley's XV thumbnail pics (from inwap@jomis.Tymnet.COM)
683 0         0 $id = 'XV';
684 0         0 ($x, $y) = ($header =~ /IMGINFO:(\d+)x(\d+)/s);
685             }
686             else
687             {
688 1         3 $id = $table[$n];
689             }
690             }
691              
692 1         5 return ($x, $y, $id);
693             }
694              
695             # tiffsize: size a TIFF image
696             #
697             # Contributed by Cloyce Spradling
698             sub tiffsize
699             {
700 2     2 0 5 my $stream = shift;
701              
702 2         13 my ($x, $y, $id) = (undef, undef, 'Unable to determine size of TIFF data');
703              
704 2         3 my $endian = 'n'; # Default to big-endian; I like it better
705 2         8 my $header = $READ_IN->($stream, 4);
706 2 100       10 if ($header =~ /II\x2a\x00/o)
707             {
708             # little-endian
709 1         2 $endian = 'v';
710             }
711              
712             # Set up an association between data types and their corresponding
713             # pack/unpack specification. Don't take any special pains to deal with
714             # signed numbers; treat them as unsigned because none of the image
715             # dimensions should ever be negative. (I hope.)
716 2         11 my @packspec = ( undef, # nothing (shouldn't happen)
717             'C', # BYTE (8-bit unsigned integer)
718             undef, # ASCII
719             $endian, # SHORT (16-bit unsigned integer)
720             uc $endian, # LONG (32-bit unsigned integer)
721             undef, # RATIONAL
722             'c', # SBYTE (8-bit signed integer)
723             undef, # UNDEFINED
724             $endian, # SSHORT (16-bit unsigned integer)
725             uc $endian, # SLONG (32-bit unsigned integer)
726             );
727              
728 2         4 my $offset = $READ_IN->($stream, 4, 4); # Get offset to IFD
729 2         8 $offset = unpack uc $endian, $offset; # Fix it so we can use it
730              
731 2         4 my $ifd = $READ_IN->($stream, 2, $offset); # Get num. of directory entries
732 2         6 my $num_dirent = unpack $endian, $ifd; # Make it useful
733 2         3 $offset += 2;
734 2         4 $num_dirent = $offset + ($num_dirent * 12); # Calc. maximum offset of IFD
735              
736             # Do all the work
737 2         3 $ifd = q{};
738 2         4 my $tag = 0;
739 2         1 my $type = 0;
740 2   66     7 while ((! defined $x) || (! defined$y)) {
741 4         8 $ifd = $READ_IN->($stream, 12, $offset); # Get first directory entry
742 4 50 33     18 last if (($ifd eq q{}) || ($offset > $num_dirent));
743 4         3 $offset += 12;
744 4         4 $tag = unpack $endian, $ifd; # ...and decode its tag
745 4         6 $type = unpack $endian, substr $ifd, 2, 2; # ...and the data type
746             # Check the type for sanity.
747 4 50 33     18 next if (($type > @packspec+0) || (! defined $packspec[$type]));
748 4 100       10 if ($tag == 0x0100) # ImageWidth (x)
    50          
749             {
750             # Decode the value
751 2         9 $x = unpack $packspec[$type], substr $ifd, 8, 4;
752             }
753             elsif ($tag == 0x0101) # ImageLength (y)
754             {
755             # Decode the value
756 2         7 $y = unpack $packspec[$type], substr $ifd, 8, 4;
757             }
758             }
759              
760             # Decide if we were successful or not
761 2 50 33     12 if (defined $x and defined $y)
762             {
763 2         3 $id = 'TIF';
764             }
765             else
766             {
767 0         0 $id = q{};
768 0 0       0 if (! defined $x)
769             {
770 0         0 $id = 'ImageWidth ';
771             }
772 0 0       0 if (! defined $y)
773             {
774 0 0       0 if ($id ne q{})
775             {
776 0         0 $id .= 'and ';
777             }
778 0         0 $id .= 'ImageLength ';
779             }
780 0         0 $id .= 'tag(s) could not be found';
781             }
782              
783 2         6 return ($x, $y, $id);
784             }
785              
786             # bmpsize: size a Windows-ish BitMaP image
787             #
788             # Adapted from code contributed by Aldo Calpini
789             sub bmpsize
790             {
791 2     2 0 6 my $stream = shift;
792              
793 2         4 my ($x, $y, $id) = (undef, undef, 'Unable to determine size of BMP data');
794 2         4 my $buffer;
795              
796 2         9 $buffer = $READ_IN->($stream, 26);
797 2         9 my $header_size = unpack 'x14V', $buffer;
798 2 100       5 if ($header_size == 12)
799             {
800 1         5 ($x, $y) = unpack 'x18vv', $buffer; # old OS/2 header
801             }
802             else
803             {
804 1         3 ($x, $y) = unpack 'x18VV', $buffer; # modern Windows header
805             }
806 2 50 33     14 if (defined $x and defined $y)
807             {
808 2         4 $id = 'BMP';
809             }
810              
811 2         4 return ($x, $y, $id);
812             }
813              
814             # psdsize: determine the size of a PhotoShop save-file (*.PSD)
815             sub psdsize
816             {
817 1     1 0 4 my $stream = shift;
818              
819 1         3 my ($x, $y, $id) = (undef, undef, 'Unable to determine size of PSD data');
820 1         2 my $buffer;
821              
822 1         5 $buffer = $READ_IN->($stream, 26);
823 1         5 ($y, $x) = unpack 'x14NN', $buffer;
824 1 50 33     11 if (defined $x and defined $y)
825             {
826 1         3 $id = 'PSD';
827             }
828              
829 1         4 return ($x, $y, $id);
830             }
831              
832             # swfsize: determine size of ShockWave/Flash files. Adapted from code sent by
833             # Dmitry Dorofeev
834             sub swfsize
835             {
836 1     1 0 4 my $image = shift;
837 1         5 my $header = $READ_IN->($image, 33);
838              
839 1         9 my $ver = _bin2int(unpack 'B8', substr $header, 3, 1);
840 1         41 my $bs = unpack 'B133', substr $header, 8, 17;
841 1         4 my $bits = _bin2int(substr $bs, 0, 5);
842 1         3 my $x = int _bin2int(substr $bs, 5+$bits, $bits)/20;
843 1         3 my $y = int _bin2int(substr $bs, 5+$bits*3, $bits)/20;
844              
845 1         3 return ($x, $y, 'SWF');
846             }
847              
848             # Suggested by Matt Mueller , and based on a piece of
849             # sample Perl code by a currently-unknown author. Credit will be placed here
850             # once the name is determined.
851             sub pcdsize
852             {
853 0     0 0 0 my $stream = shift;
854              
855 0         0 my ($x, $y, $id) = (undef, undef, 'Unable to determine size of PCD data');
856 0         0 my $buffer = $READ_IN->($stream, 0xf00);
857              
858             # Second-tier sanity check
859 0 0       0 if (substr($buffer, 0x800, 3) ne 'PCD')
860             {
861 0         0 return ($x, $y, $id);
862             }
863              
864 0         0 my $orient = ord(substr $buffer, 0x0e02, 1) & 1; # Clear down to one bit
865 0 0       0 ($x, $y) = @{$Image::Size::PCD_MAP{lc $Image::Size::PCD_SCALE}}
  0         0  
866             [($orient ? (0, 1) : (1, 0))];
867              
868 0         0 return ($x, $y, 'PCD');
869             }
870              
871             # swfmxsize: determine size of compressed ShockWave/Flash MX files. Adapted
872             # from code sent by Victor Kuriashkin
873             sub swfmxsize
874             {
875 1     1 0 4 my $image = shift;
876              
877 1         3 my $retval = eval {
878 1         7 local $SIG{__DIE__} = q{};
879 1         5 require Compress::Zlib;
880 1         4 1;
881             };
882 1 50       5 if (! $retval)
883             {
884 0         0 return (undef, undef, "Error loading Compress::Zlib: $@");
885             }
886              
887 1         5 my $header = $READ_IN->($image, 1058);
888 1         13 my $ver = _bin2int(unpack 'B8', substr $header, 3, 1);
889              
890 1         8 my ($d, $status) = Compress::Zlib::inflateInit();
891 1         204 $header = substr $header, 8, 1024;
892 1         7 $header = $d->inflate($header);
893              
894 1         59 my $bs = unpack 'B133', substr $header, 0, 17;
895 1         5 my $bits = _bin2int(substr $bs, 0, 5);
896 1         4 my $x = int _bin2int(substr $bs, 5+$bits, $bits)/20;
897 1         4 my $y = int _bin2int(substr $bs, 5+$bits*3, $bits)/20;
898              
899 1         13 return ($x, $y, 'CWS');
900             }
901              
902             # Windows EMF files, requested by Jan v/d Zee
903             sub emfsize
904             {
905 1     1 0 3 my $image = shift;
906              
907 1         3 my ($x, $y);
908 1         5 my $buffer = $READ_IN->($image, 24);
909              
910 1         5 my ($topleft_x, $topleft_y, $bottomright_x, $bottomright_y) =
911             unpack 'x8V4', $buffer;
912              
913             # The four values describe a box *around* the image, not *of* the image.
914             # In other words, the dimensions are not inclusive.
915 1         2 $x = $bottomright_x - $topleft_x - 1;
916 1         2 $y = $bottomright_y - $topleft_y - 1;
917              
918 1         3 return ($x, $y, 'EMF');
919             }
920              
921             # WEBP files, see https://developers.google.com/speed/webp/docs/riff_container
922             # Added by Baldur Kristinsson, github.com/bk
923             sub webpsize {
924 1     1 0 7 my $img = shift;
925              
926             # There are 26 bytes of lead-in, before the width and height info:
927             # 1. WEBP container
928             # - 'RIFF', 4 bytes
929             # - filesize, 4 bytes
930             # - 'WEBP', 4 bytes
931             # 2. VP8 frame
932             # - 'VP8', 3 bytes
933             # - frame meta, 8 bytes
934             # - marker, 3 bytes
935 1         7 my $buf = $READ_IN->($img, 4, 26);
936 1         6 my ($raw_w, $raw_h) = unpack 'SS', $buf;
937 1         3 my $b14 = 2**14 - 1;
938              
939             # The width and height values contain a 2-bit scaling factor,
940             # which is left-shifted by 14 bits. We ignore this, since it seems
941             # not to be relevant for our purposes. WEBP images in actual use
942             # all seem to have a scaling factor of 0, anyway. (The meaning
943             # of the scaling factor is as follows: 0=no upscale, 1=upscale by 5/4,
944             # 2=upscale by 5/3, 3=upscale by 2).
945             #
946             # my $wscale = $raw_w >> 14;
947             # my $hscale = $raw_h >> 14;
948 1         2 my $x = $raw_w & $b14;
949 1         3 my $y = $raw_h & $b14;
950              
951 1         5 return ($x, $y, 'WEBP');
952             }
953              
954             # ICO files, originally contributed by Thomas Walloschke ,
955             # see https://rt.cpan.org/Public/Bug/Display.html?id=46279
956             # (revised by Baldur Kristinsson, github.com/bk)
957             sub icosize {
958 2     2 0 4 my $img = shift;
959 2         7 my ($x, $y) = unpack 'CC', $READ_IN->($img, 2, 6);
960 2 50       8 if ($x == 0) { $x = 256; }
  0         0  
961 2 50       6 if ($y == 0) { $y = 256; }
  0         0  
962 2         14 return ($x, $y, 'ICO');
963             }
964              
965             # CUR files, originally contributed by Thomas Walloschke ,
966             # see https://rt.cpan.org/Public/Bug/Display.html?id=46279
967             # (revised by Baldur Kristinsson, github.com/bk)
968             sub cursize {
969 1     1 0 7 my ($x, $y, $ico) = icosize(shift);
970 1         4 return ($x, $y, 'CUR');
971             }
972              
973              
974             1;
975              
976             __END__