File Coverage

blib/lib/PDF/Builder/Resource/XObject/Image/GIF.pm
Criterion Covered Total %
statement 108 206 52.4
branch 24 56 42.8
condition 8 27 29.6
subroutine 9 10 90.0
pod 1 3 33.3
total 150 302 49.6


line stmt bran cond sub pod time code
1             package PDF::Builder::Resource::XObject::Image::GIF;
2              
3 2     2   1043 use base 'PDF::Builder::Resource::XObject::Image';
  2         3  
  2         510  
4              
5 2     2   9 use strict;
  2         4  
  2         28  
6 2     2   7 use warnings;
  2         4  
  2         100  
7              
8             our $VERSION = '3.028'; # VERSION
9             our $LAST_UPDATE = '3.027'; # manually update whenever code is changed
10              
11 2     2   8 use IO::File;
  2         2  
  2         259  
12 2     2   9 use PDF::Builder::Util;
  2         3  
  2         291  
13 2     2   10 use PDF::Builder::Basic::PDF::Utils;
  2         2  
  2         135  
14 2     2   8 use Scalar::Util qw(weaken);
  2         2  
  2         3076  
15              
16             =head1 NAME
17              
18             PDF::Builder::Resource::XObject::Image::GIF - Support routines for GIF image library
19              
20             Inherits from L<PDF::Builder::Resource::XObject::Image>
21              
22             =head2 History
23              
24             GIF89a Specification: https://www.w3.org/Graphics/GIF/spec-gif89a.txt
25              
26             A fairly thorough description of the GIF format may be found in
27             L<http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html>.
28              
29             Code originally from PDF::Create, PDF::Image::GIFImage - GIF image support
30             Author: Michael Gross <mdgrosse@sboxtugraz.at>
31              
32             =head1 Supported Formats
33              
34             GIF87a and GIF89a headers are supported. The Image block (x2C) is supported.
35              
36             The Graphic Control Extension block (x21 + xF9) is supported for transparency
37             control. Animation is not supported.
38              
39             The Comment Extension block (x21 + xFE), Plain Text Extension block (x21 + x01),
40             and Application Extension block (x21 + xFF) are read, but ignored. Any other
41             block or Extension block will be flagged as an error.
42              
43             If given, Local Color Tables are read and used, supposedly permitting more
44             than 256 colors to be used overall in the image (despite the 8 bit color table
45             depth).
46              
47             =head2 Options
48              
49             =over
50              
51             =item notrans
52              
53             When defined and not 0, C<notrans> suppresses the use of transparency if such
54             is defined in the GIF file.
55              
56             =item name => 'string'
57              
58             This is the name you can give for the GIF image object. The default is Gxnnnn.
59              
60             =item multi
61              
62             When defined and not 0, C<multi> continues processing past the end of the
63             first Image Block. The old behavior, which is now the default, is to stop
64             processing at the end of the first Image Block.
65              
66             =back
67              
68             =cut
69              
70             # modified for internal use. (c) 2004 fredo.
71             sub unInterlace {
72 0     0 0 0 my $self = shift;
73              
74 0         0 my $data = $self->{' stream'};
75 0         0 my $row;
76             my @result;
77 0         0 my $width = $self->width();
78 0         0 my $height = $self->height();
79 0         0 my $idx = 0;
80              
81             # Pass 1 - every 8th row, starting with row 0
82 0         0 $row = 0;
83 0         0 while ($row < $height) {
84 0         0 $result[$row] = substr($data, $idx*$width, $width);
85 0         0 $row += 8;
86 0         0 $idx++;
87             }
88              
89             # Pass 2 - every 8th row, starting with row 4
90 0         0 $row = 4;
91 0         0 while ($row < $height) {
92 0         0 $result[$row] = substr($data, $idx*$width, $width);
93 0         0 $row += 8;
94 0         0 $idx++;
95             }
96              
97             # Pass 3 - every 4th row, starting with row 2
98 0         0 $row = 2;
99 0         0 while ($row < $height) {
100 0         0 $result[$row] = substr($data, $idx*$width, $width);
101 0         0 $row += 4;
102 0         0 $idx++;
103             }
104              
105             # Pass 4 - every 2nd row, starting with row 1
106 0         0 $row = 1;
107 0         0 while ($row < $height) {
108 0         0 $result[$row] = substr($data, $idx*$width, $width);
109 0         0 $row += 2;
110 0         0 $idx++;
111             }
112              
113 0         0 return $self->{' stream'} = join('', @result);
114             }
115              
116             sub deGIF {
117 2     2 0 2 my ($ibits, $stream) = @_;
118              
119 2         3 my $bits = $ibits;
120 2         3 my $resetcode = 1 << ($ibits-1);
121 2         3 my $endcode = $resetcode+1;
122 2         2 my $nextcode = $endcode+1;
123 2         3 my $ptr = 0;
124 2         3 my $maxptr = 8*length($stream);
125 2         2 my $tag;
126 2         2 my $out = '';
127 2         2 my $outptr = 0;
128              
129             # print STDERR "reset=$resetcode\nend=$endcode\nmax=$maxptr\n";
130              
131 2         4 my @d = map { chr($_) } (0 .. $resetcode-1);
  8         17  
132              
133 2         5 while ($ptr+$bits <= $maxptr) {
134 6         7 $tag = 0;
135 6         10 foreach my $off (reverse 0 .. $bits-1) {
136 18         14 $tag <<= 1;
137 18         45 $tag |= vec($stream, $ptr+$off, 1);
138             }
139             # foreach my $off (0 .. $bits-1) {
140             # $tag <<= 1;
141             # $tag |= vec($stream, $ptr+$off, 1);
142             # }
143             # print STDERR "ptr=$ptr,tag=$tag,bits=$bits,next=$nextcode\n";
144             # print STDERR "tag too large\n" if($tag>$nextcode);
145 6         6 $ptr += $bits;
146 6 50 33     10 $bits++ if $nextcode == 1 << $bits and $bits < 12;
147 6 100       12 if ($tag == $resetcode) {
    100          
    50          
    0          
148 2         2 $bits = $ibits;
149 2         3 $nextcode = $endcode+1;
150 2         8 next;
151             } elsif ($tag == $endcode) {
152 2         4 last;
153             } elsif ($tag < $resetcode) {
154 2         4 $d[$nextcode] = $d[$tag];
155 2         2 $out .= $d[$nextcode];
156 2         4 $nextcode++;
157             } elsif ($tag > $endcode) {
158 0         0 $d[$nextcode] = $d[$tag];
159 0         0 $d[$nextcode] .= substr($d[$tag+1], 0, 1);
160 0         0 $out .= $d[$nextcode];
161 0         0 $nextcode++;
162             }
163             }
164 2         5 return $out;
165             }
166              
167             =head1 METHODS
168              
169             =head2 new
170              
171             PDF::Builder::Resource::XObject::Image::GIF->new()
172              
173             =over
174              
175             Create an image object from a GIF input file.
176             Remember that this should be invoked via the Builder.pm method!
177              
178             =back
179              
180             =cut
181              
182             sub new {
183 3     3 1 8 my ($class, $pdf, $file, %opts) = @_;
184             # copy dashed option names to preferred undashed names
185 3 50 33     9 if (defined $opts{'-notrans'} && !defined $opts{'notrans'}) { $opts{'notrans'} = delete($opts{'-notrans'}); }
  0         0  
186 3 50 33     7 if (defined $opts{'-name'} && !defined $opts{'name'}) { $opts{'name'} = delete($opts{'-name'}); }
  0         0  
187 3 50 33     7 if (defined $opts{'-multi'} && !defined $opts{'multi'}) { $opts{'multi'} = delete($opts{'-multi'}); }
  0         0  
188 3 50 33     6 if (defined $opts{'-compress'} && !defined $opts{'compress'}) { $opts{'compress'} = delete($opts{'-compress'}); }
  0         0  
189              
190 3         5 my ($name, $compress);
191 3 50       7 if (exists $opts{'name'}) { $name = $opts{'name'}; }
  0         0  
192             #if (exists $opts{'compress'}) { $compress = $opts{'compress'}; }
193              
194 3         3 my $self;
195              
196 3         4 my $interlaced = 0;
197              
198 3 50       4 $class = ref($class) if ref($class);
199              
200 3   33     13 $self = $class->SUPER::new($pdf, $name || 'Gx'.pdfkey());
201 3 50       5 $pdf->new_obj($self) unless $self->is_obj($pdf);
202              
203 3         6 $self->{' apipdf'} = $pdf;
204 3         3 weaken $self->{' apipdf'};
205              
206 3         13 my $fh = IO::File->new();
207 3 100       95 if (ref($file)) {
208 1         2 $fh = $file;
209             } else {
210 2 100       148 open $fh, "<", $file or die "$!: $file";
211             }
212 2         8 binmode $fh, ':raw';
213 2         3 my $buf;
214 2         14 $fh->seek(0, 0);
215              
216             # start reading in the GIF file
217             # GIF Header
218             # 6 bytes "GIF87a" or "GIF89a"
219 2         20 $fh->read($buf, 6); # signature
220 2 50       64 unless ($buf =~ /^GIF[0-9][0-9][a-b]/) {
221             # TBD b? is anything other than 87a and 89a valid?
222             # PDF::API2 allows a-z, not just a-b
223 0         0 die "unknown image signature '$buf' -- not a GIF."
224             }
225              
226             # 4 bytes logical screen width and height (2 x 16 bit LSB first)
227             # 1 byte flags, 1 byte background color index, 1 byte pixel aspect ratio
228 2         7 $fh->read($buf, 7); # logical screen descriptor
229 2         26 my($wg, $hg, $flags, $bgColorIndex, $aspect) = unpack('vvCCC', $buf);
230              
231             # flags numbered left to right 0-7:
232             # bit 0 = 1 (x80) Global Color Table Flag (GCTF)
233             # bits 1-3 = color resolution
234             # bit 4 = 1 (x08) sort flag for Global Color Table
235             # bits 5-7 = size of Global Color Table 2**(n+1), $colSize
236 2 50       7 if ($flags & 0x80) { # GCTF is set?
237 2         4 my $colSize = 2**(($flags & 0x7)+1); # 2 - 256 entries
238 2         5 my $dict = PDFDict();
239 2         6 $pdf->new_obj($dict);
240 2         12 $self->colorspace(PDFArray(PDFName('Indexed'),
241             PDFName('DeviceRGB'),
242             PDFNum($colSize-1),
243             $dict));
244 2         8 $fh->read($dict->{' stream'}, 3*$colSize); # Global Color Table
245             }
246              
247             # further content in file is blocks and trailer
248 2         21 while (!$fh->eof()) {
249 2         13 $fh->read($buf, 1); # 1 byte block tag (type)
250 2         8 my $sep = unpack('C', $buf);
251              
252 2 50       4 if ($sep == 0x2C) {
    0          
    0          
253             # x2C = image block (separator, equals ASCII comma ',')
254 2         5 $fh->read($buf, 9); # image descriptor
255             # image left (16 bits), image top (16 bits LSB first)
256             # image width (16 bits), image height (16 bits LSB first)
257             # flags (1 byte):
258             # bit 0 = 1 (x80) Local Color Table Flag (LCTF)
259             # bit 1 = 1 (x40) interlaced
260             # bit 2 = 1 (x20) sort flag
261             # bits 3-4 = reserved
262             # bits 5-7 = size of Local Color Table 2**(n+1) if LCTF=1
263 2         8 my ($left, $top, $w,$h, $flags) = unpack('vvvvC', $buf);
264              
265 2   33     8 $self->width($w||$wg);
266 2   33     6 $self->height($h||$hg);
267 2         39 $self->bits_per_component(8);
268              
269 2 50       5 if ($flags & 0x80) { # Local Color Table (LCTF = 1)
270 0         0 my $colSize = 2**(($flags & 0x7)+1);
271 0         0 my $dict = PDFDict();
272 0         0 $pdf->new_obj($dict);
273 0         0 $self->colorspace(PDFArray(PDFName('Indexed'),
274             PDFName('DeviceRGB'),
275             PDFNum($colSize-1),
276             $dict));
277 0         0 $fh->read($dict->{' stream'}, 3*$colSize); # Local Color Table
278             }
279 2 50       4 if ($flags & 0x40) { # need de-interlace
280 0         0 $interlaced = 1; # default to 0 earlier
281             }
282              
283             # LZW Minimum Code Size
284 2         5 $fh->read($buf, 1); # image-lzw-start (should be 9).
285 2         7 my ($sep) = unpack('C', $buf);
286              
287             # read one or more blocks. first byte is length.
288             # if 0, done (Block Terminator)
289 2         5 $fh->read($buf, 1); # first chunk.
290 2         6 my ($len) = unpack('C', $buf);
291 2         4 my $stream = '';
292 2         4 while ($len > 0) { # loop through blocks as long as non-0 length
293 2         3 $fh->read($buf, $len);
294 2         7 $stream .= $buf;
295 2         4 $fh->read($buf, 1);
296 2         8 $len = unpack('C', $buf);
297             }
298 2         5 $self->{' stream'} = deGIF($sep+1, $stream);
299 2 50       4 $self->unInterlace() if $interlaced;
300             # old (and current default) behavior is to quit processing at the
301             # end of the first Image Block. This means that any other blocks,
302             # including the Trailer, will not be processed.
303 2 50       4 if (!$opts{'multi'}) { last; }
  2         4  
304              
305             } elsif ($sep == 0x3b) {
306             # trailer (EOF) equals ASCII semicolon (;)
307 0         0 last;
308              
309             } elsif ($sep == 0x21) {
310             # Extension block (x21 + subtag) = ASCII '!'
311 0         0 $fh->read($buf, 1); # tag.
312 0         0 my $tag = unpack('C', $buf);
313              
314 0 0       0 if ($tag == 0xF9) {
    0          
    0          
    0          
315             # xF9 graphic control extension block
316 0         0 $fh->read($buf, 1); # len. should be 04
317 0         0 my $len = unpack('C', $buf);
318 0         0 my $stream = '';
319 0         0 while ($len > 0) {
320 0         0 $fh->read($buf, $len);
321 0         0 $stream .= $buf;
322 0         0 $fh->read($buf, 1);
323 0         0 $len = unpack('C', $buf);
324             }
325 0         0 my ($cFlags, $delay, $transIndex) = unpack('CvC', $stream);
326 0 0 0     0 if (($cFlags & 0x01) && !$opts{'notrans'}) {
327 0         0 $self->{'Mask'} = PDFArray(PDFNum($transIndex),
328             PDFNum($transIndex));
329             }
330              
331             } elsif ($tag == 0xFE) {
332             # xFE comment extension block
333             # read comment data block(s) until 0 length
334             # currently just discard comment ($stream)
335 0         0 $fh->read($buf, 1); # len.
336 0         0 my $len = unpack('C', $buf);
337 0         0 my $stream = '';
338 0         0 while ($len > 0) {
339 0         0 $fh->read($buf, $len);
340 0         0 $stream .= $buf;
341 0         0 $fh->read($buf, 1);
342 0         0 $len = unpack('C', $buf);
343             }
344              
345             } elsif ($tag == 0x01) {
346             # x01 plain text extension block
347 0         0 $fh->read($buf, 13); # len.
348 0         0 my ($blkSize,$tgL,$tgT,$tgW,$tgH,$ccW,$ccH,$tFci,$tBci) =
349             unpack('CvvvvCCCC', $buf);
350              
351             # read plain text data block(s) until 0 length
352             # currently just discard comment ($stream)
353 0         0 $fh->read($buf, 1); # len.
354 0         0 my $len = unpack('C', $buf);
355 0         0 my $stream = '';
356 0         0 while ($len > 0) {
357 0         0 $fh->read($buf, $len);
358 0         0 $stream .= $buf;
359 0         0 $fh->read($buf, 1);
360 0         0 $len = unpack('C', $buf);
361             }
362              
363             } elsif ($tag == 0xFF) {
364             # xFF application extension block
365 0         0 $fh->read($buf, 1);
366 0         0 my $blkSize = unpack('C', $buf);
367 0         0 $fh->read($buf, 8);
368 0         0 my $appID = unpack('C8', $buf);
369              
370 0         0 $fh->read($buf, 1); # len.
371 0         0 my $len = unpack('C', $buf);
372 0         0 my $stream = '';
373 0         0 while ($len > 0) {
374 0         0 $fh->read($buf, $len);
375 0         0 $stream .= $buf;
376 0         0 $fh->read($buf, 1);
377 0         0 $len = unpack('C', $buf);
378             }
379              
380             } else {
381 0         0 print "unsupported extension block (".
382             sprintf("0x%02X",$tag).") ignored!\n";
383             }
384              
385             } else {
386             # other extensions and blocks (ignored)
387 0         0 print "unsupported extension or block (".
388             sprintf("0x%02X",$sep).") ignored.\n";
389              
390 0         0 $fh->read($buf, 1); # tag.
391 0         0 my $tag = unpack('C', $buf);
392 0         0 $fh->read($buf, 1); # tag.
393 0         0 my $len = unpack('C', $buf);
394 0         0 while ($len > 0) {
395 0         0 $fh->read($buf, $len);
396 0         0 $fh->read($buf, 1);
397 0         0 $len = unpack('C', $buf);
398             }
399             }
400             }
401 2         12 $fh->close();
402              
403 2         48 $self->filters('FlateDecode');
404              
405 2         7 return $self;
406             }
407              
408             1;