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