File Coverage

blib/lib/PDF/API3/Compat/API2/Resource/XObject/Image/PNG.pm
Criterion Covered Total %
statement 23 238 9.6
branch 0 64 0.0
condition 0 15 0.0
subroutine 8 12 66.6
pod 2 4 50.0
total 33 333 9.9


line stmt bran cond sub pod time code
1             #=======================================================================
2             # ____ ____ _____ _ ____ ___ ____
3             # | _ \| _ \| ___| _ _ / \ | _ \_ _| |___ \
4             # | |_) | | | | |_ (_) (_) / _ \ | |_) | | __) |
5             # | __/| |_| | _| _ _ / ___ \| __/| | / __/
6             # |_| |____/|_| (_) (_) /_/ \_\_| |___| |_____|
7             #
8             # A Perl Module Chain to faciliate the Creation and Modification
9             # of High-Quality "Portable Document Format (PDF)" Files.
10             #
11             # Copyright 1999-2005 Alfred Reibenschuh .
12             #
13             #=======================================================================
14             #
15             # This library is free software; you can redistribute it and/or
16             # modify it under the terms of the GNU Lesser General Public
17             # License as published by the Free Software Foundation; either
18             # version 2 of the License, or (at your option) any later version.
19             #
20             # This library is distributed in the hope that it will be useful,
21             # but WITHOUT ANY WARRANTY; without even the implied warranty of
22             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23             # Lesser General Public License for more details.
24             #
25             # You should have received a copy of the GNU Lesser General Public
26             # License along with this library; if not, write to the
27             # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
28             # Boston, MA 02111-1307, USA.
29             #
30             # $Id: PNG.pm,v 2.0 2005/11/16 02:18:23 areibens Exp $
31             #
32             #=======================================================================
33             package PDF::API3::Compat::API2::Resource::XObject::Image::PNG;
34            
35             BEGIN {
36            
37 1     1   7 use PDF::API3::Compat::API2::Util;
  1         2  
  1         243  
38 1     1   9 use PDF::API3::Compat::API2::Basic::PDF::Utils;
  1         2  
  1         145  
39 1     1   7 use PDF::API3::Compat::API2::Resource::XObject::Image;
  1         2  
  1         22  
40            
41 1     1   6 use POSIX;
  1         1  
  1         10  
42 1     1   3517 use Compress::Zlib;
  1         2  
  1         437  
43            
44 1     1   9 use vars qw(@ISA $VERSION);
  1         3  
  1         165  
45 1     1   31 @ISA = qw( PDF::API3::Compat::API2::Resource::XObject::Image );
46 1         55 ( $VERSION ) = sprintf '%i.%03i', split(/\./,('$Revision: 2.0 $' =~ /Revision: (\S+)\s/)[0]); # $Date: 2005/11/16 02:18:23 $
47             }
48 1     1   7 no warnings qw[ deprecated recursion uninitialized ];
  1         3  
  1         3024  
49            
50             =item $res = PDF::API3::Compat::API2::Resource::XObject::Image::PNG->new $pdf, $file [, $name]
51            
52             Returns a png-image object.
53            
54             =cut
55            
56             sub new {
57 0     0 1   my ($class,$pdf,$file,$name,%opts) = @_;
58 0           my $self;
59            
60 0 0         $class = ref $class if ref $class;
61            
62 0   0       $self=$class->SUPER::new($pdf,$name || 'Px'.pdfkey());
63 0 0         $pdf->new_obj($self) unless($self->is_obj($pdf));
64            
65 0           $self->{' apipdf'}=$pdf;
66            
67 0           my $fh = IO::File->new;
68 0           open($fh,$file);
69 0           binmode($fh,':raw');
70            
71 0           my ($buf,$l,$crc,$w,$h,$bpc,$cs,$cm,$fm,$im,$palete,$trns);
72 0           open($fh,$file);
73 0           binmode($fh);
74 0           seek($fh,8,0);
75 0           $self->{' stream'}='';
76 0           $self->{' nofilt'}=1;
77 0           while(!eof($fh)) {
78 0           read($fh,$buf,4);
79 0           $l=unpack('N',$buf);
80 0           read($fh,$buf,4);
81 0 0         if($buf eq 'IHDR') {
    0          
    0          
    0          
    0          
82 0           read($fh,$buf,$l);
83 0           ($w,$h,$bpc,$cs,$cm,$fm,$im)=unpack('NNCCCCC',$buf);
84 0 0         die "Unsupported Compression($cm) Method" if($cm);
85 0 0         die "Unsupported Interlace($im) Method" if($im);
86 0 0         die "Unsupported Filter($fm) Method" if($fm);
87             } elsif($buf eq 'PLTE') {
88 0           read($fh,$buf,$l);
89 0           $palete=$buf;
90             } elsif($buf eq 'IDAT') {
91 0           read($fh,$buf,$l);
92 0           $self->{' stream'}.=$buf;
93             } elsif($buf eq 'tRNS') {
94 0           read($fh,$buf,$l);
95 0           $trns=$buf;
96             } elsif($buf eq 'IEND') {
97 0           last;
98             } else {
99             # skip ahead
100 0           seek($fh,$l,1);
101             }
102 0           read($fh,$buf,4);
103 0           $crc=$buf;
104             }
105 0           close($fh);
106            
107 0           $self->width($w);
108 0           $self->height($h);
109            
110 0 0         if($cs==0){ # greyscale
    0          
    0          
    0          
    0          
111             # scanline = ceil(bpc * comp / 8)+1
112 0 0         if($bpc>8) {
113 0           die "16-bits of greylevel in png not supported.";
114             } else {
115 0           $self->filters('FlateDecode');
116 0           $self->colorspace('DeviceGray');
117 0           $self->bpc($bpc);
118 0           my $dict=PDFDict();
119 0           $self->{DecodeParms}=PDFArray($dict);
120 0           $dict->{Predictor}=PDFNum(15);
121 0           $dict->{BitsPerComponent}=PDFNum($bpc);
122 0           $dict->{Colors}=PDFNum(1);
123 0           $dict->{Columns}=PDFNum($w);
124 0 0 0       if(defined $trns && !$opts{-notrans}) {
125 0           my $m=mMax(unpack('n*',$trns));
126 0           my $n=mMin(unpack('n*',$trns));
127 0           $self->{Mask}=PDFArray(PDFNum($n),PDFNum($m));
128             }
129             }
130             } elsif($cs==2){ # rgb 8/16 bits
131 0 0         if($bpc>8) {
132 0           die "16-bits of rgb in png not supported.";
133             } else {
134 0           $self->filters('FlateDecode');
135 0           $self->colorspace('DeviceRGB');
136 0           $self->bpc($bpc);
137 0           my $dict=PDFDict();
138 0           $self->{DecodeParms}=PDFArray($dict);
139 0           $dict->{Predictor}=PDFNum(15);
140 0           $dict->{BitsPerComponent}=PDFNum($bpc);
141 0           $dict->{Colors}=PDFNum(3);
142 0           $dict->{Columns}=PDFNum($w);
143 0 0 0       if(defined $trns && !$opts{-notrans}) {
144 0           my @v=unpack('n*',$trns);
145 0           my (@cr,@cg,@cb,$m,$n);
146 0           while(scalar @v > 0) {
147 0           push(@cr,shift(@v));
148 0           push(@cg,shift(@v));
149 0           push(@cb,shift(@v));
150             }
151 0           @v=();
152 0           $m=mMax(@cr);
153 0           $n=mMin(@cr);
154 0           push @v,$n,$m;
155 0           $m=mMax(@cg);
156 0           $n=mMin(@cg);
157 0           push @v,$n,$m;
158 0           $m=mMax(@cb);
159 0           $n=mMin(@cb);
160 0           push @v,$n,$m;
161 0           $self->{Mask}=PDFArray(map { PDFNum($_) } @v);
  0            
162             }
163             }
164             } elsif($cs==3){ # palette
165 0 0         if($bpc>8) {
166 0           die "bits>8 of palette in png not supported.";
167             } else {
168 0           my $dict=PDFDict();
169 0           $pdf->new_obj($dict);
170 0           $dict->{Filter}=PDFArray(PDFName('FlateDecode'));
171 0           $dict->{' stream'}=$palete;
172 0           $palete="";
173 0           $self->filters('FlateDecode');
174 0           $self->colorspace(PDFArray(PDFName('Indexed'),PDFName('DeviceRGB'),PDFNum(int(length($dict->{' stream'})/3)-1),$dict));
175 0           $self->bpc($bpc);
176 0           $dict=PDFDict();
177 0           $self->{DecodeParms}=PDFArray($dict);
178 0           $dict->{Predictor}=PDFNum(15);
179 0           $dict->{BitsPerComponent}=PDFNum($bpc);
180 0           $dict->{Colors}=PDFNum(1);
181 0           $dict->{Columns}=PDFNum($w);
182 0 0 0       if(defined $trns && !$opts{-notrans}) {
183 0           $trns.="\xFF" x 256;
184 0           $dict=PDFDict();
185 0           $pdf->new_obj($dict);
186 0           $dict->{Type}=PDFName('XObject');
187 0           $dict->{Subtype}=PDFName('Image');
188 0           $dict->{Width}=PDFNum($w);
189 0           $dict->{Height}=PDFNum($h);
190 0           $dict->{ColorSpace}=PDFName('DeviceGray');
191 0           $dict->{Filter}=PDFArray(PDFName('FlateDecode'));
192             # $dict->{Filter}=PDFArray(PDFName('ASCIIHexDecode'));
193 0           $dict->{BitsPerComponent}=PDFNum(8);
194 0           $self->{SMask}=$dict;
195 0           my $scanline=1+ceil($bpc*$w/8);
196 0           my $bpp=ceil($bpc/8);
197 0           my $clearstream=unprocess($bpc,$bpp,1,$w,$h,$scanline,\$self->{' stream'});
198 0           foreach my $n (0..($h*$w)-1) {
199 0           vec($dict->{' stream'},$n,8)=vec($trns,vec($clearstream,$n,$bpc),8);
200             # print STDERR vec($trns,vec($clearstream,$n,$bpc),8)."=".vec($clearstream,$n,$bpc).",";
201             }
202             # print STDERR "\n";
203             }
204             }
205             } elsif($cs==4){ # greyscale+alpha
206             # die "greylevel+alpha in png not supported.";
207 0 0         if($bpc>8) {
208 0           die "16-bits of greylevel+alpha in png not supported.";
209             } else {
210 0           $self->filters('FlateDecode');
211 0           $self->colorspace('DeviceGray');
212 0           $self->bpc($bpc);
213 0           my $dict=PDFDict();
214 0           $self->{DecodeParms}=PDFArray($dict);
215             # $dict->{Predictor}=PDFNum(15);
216 0           $dict->{BitsPerComponent}=PDFNum($bpc);
217 0           $dict->{Colors}=PDFNum(1);
218 0           $dict->{Columns}=PDFNum($w);
219            
220 0           $dict=PDFDict();
221 0 0         unless($opts{-notrans}) {
222 0           $pdf->new_obj($dict);
223 0           $dict->{Type}=PDFName('XObject');
224 0           $dict->{Subtype}=PDFName('Image');
225 0           $dict->{Width}=PDFNum($w);
226 0           $dict->{Height}=PDFNum($h);
227 0           $dict->{ColorSpace}=PDFName('DeviceGray');
228 0           $dict->{Filter}=PDFArray(PDFName('FlateDecode'));
229 0           $dict->{BitsPerComponent}=PDFNum($bpc);
230 0           $self->{SMask}=$dict;
231             }
232 0           my $scanline=1+ceil($bpc*2*$w/8);
233 0           my $bpp=ceil($bpc*2/8);
234 0           my $clearstream=unprocess($bpc,$bpp,2,$w,$h,$scanline,\$self->{' stream'});
235 0           delete $self->{' nofilt'};
236 0           delete $self->{' stream'};
237 0           foreach my $n (0..($h*$w)-1) {
238 0           vec($dict->{' stream'},$n,$bpc)=vec($clearstream,($n*2)+1,$bpc);
239 0           vec($self->{' stream'},$n,$bpc)=vec($clearstream,$n*2,$bpc);
240             }
241             }
242             } elsif($cs==6){ # rgb+alpha
243             # die "rgb+alpha in png not supported.";
244 0 0         if($bpc>8) {
245 0           die "16-bits of rgb+alpha in png not supported.";
246             } else {
247 0           $self->filters('FlateDecode');
248 0           $self->colorspace('DeviceRGB');
249 0           $self->bpc($bpc);
250 0           my $dict=PDFDict();
251 0           $self->{DecodeParms}=PDFArray($dict);
252             # $dict->{Predictor}=PDFNum(15);
253 0           $dict->{BitsPerComponent}=PDFNum($bpc);
254 0           $dict->{Colors}=PDFNum(3);
255 0           $dict->{Columns}=PDFNum($w);
256            
257 0           $dict=PDFDict();
258 0 0         unless($opts{-notrans}) {
259 0           $pdf->new_obj($dict);
260 0           $dict->{Type}=PDFName('XObject');
261 0           $dict->{Subtype}=PDFName('Image');
262 0           $dict->{Width}=PDFNum($w);
263 0           $dict->{Height}=PDFNum($h);
264 0           $dict->{ColorSpace}=PDFName('DeviceGray');
265 0           $dict->{Filter}=PDFArray(PDFName('FlateDecode'));
266 0           $dict->{BitsPerComponent}=PDFNum($bpc);
267 0           $self->{SMask}=$dict;
268             }
269 0           my $scanline=1+ceil($bpc*4*$w/8);
270 0           my $bpp=ceil($bpc*4/8);
271 0           my $clearstream=unprocess($bpc,$bpp,4,$w,$h,$scanline,\$self->{' stream'});
272 0           delete $self->{' nofilt'};
273 0           delete $self->{' stream'};
274 0           foreach my $n (0..($h*$w)-1) {
275 0           vec($dict->{' stream'},$n,$bpc)=vec($clearstream,($n*4)+3,$bpc);
276 0           vec($self->{' stream'},($n*3),$bpc)=vec($clearstream,($n*4),$bpc);
277 0           vec($self->{' stream'},($n*3)+1,$bpc)=vec($clearstream,($n*4)+1,$bpc);
278 0           vec($self->{' stream'},($n*3)+2,$bpc)=vec($clearstream,($n*4)+2,$bpc);
279             }
280             }
281             } else {
282 0           die "unsupported png-type ($cs).";
283             }
284            
285 0           return($self);
286             }
287            
288             =item $res = PDF::API3::Compat::API2::Resource::XObject::Image::PNG->new_api $api, $file [, $name]
289            
290             Returns a png-image object. This method is different from 'new' that
291             it needs an PDF::API3::Compat::API2-object rather than a Text::PDF::File-object.
292            
293             =cut
294            
295             sub new_api {
296 0     0 1   my ($class,$api,@opts)=@_;
297            
298 0           my $obj=$class->new($api->{pdf},@opts);
299 0           $obj->{' api'}=$api;
300            
301 0           return($obj);
302             }
303            
304             sub PaethPredictor {
305 0     0 0   my ($a, $b, $c)=@_;
306 0           my $p = $a + $b - $c;
307 0           my $pa = abs($p - $a);
308 0           my $pb = abs($p - $b);
309 0           my $pc = abs($p - $c);
310 0 0 0       if(($pa <= $pb) && ($pa <= $pc)) {
    0          
311 0           return $a;
312             } elsif($pb <= $pc) {
313 0           return $b;
314             } else {
315 0           return $c;
316             }
317             }
318            
319             sub unprocess {
320 0     0 0   my ($bpc,$bpp,$comp,$width,$height,$scanline,$sstream)=@_;
321 0           my $stream=uncompress($$sstream);
322 0           my $prev='';
323 0           my $clearstream='';
324 0           foreach my $n (0..$height-1) {
325             # print STDERR "line $n:";
326 0           my $line=substr($stream,$n*$scanline,$scanline);
327 0           my $filter=vec($line,0,8);
328 0           my $clear='';
329 0           $line=substr($line,1);
330             # print STDERR " filter=$filter";
331 0 0         if($filter==0) {
    0          
    0          
    0          
    0          
332 0           $clear=$line;
333             } elsif($filter==1) {
334 0           foreach my $x (0..length($line)-1) {
335 0           vec($clear,$x,8)=(vec($line,$x,8)+vec($clear,$x-$bpp,8))%256;
336             }
337             } elsif($filter==2) {
338 0           foreach my $x (0..length($line)-1) {
339 0           vec($clear,$x,8)=(vec($line,$x,8)+vec($prev,$x,8))%256;
340             }
341             } elsif($filter==3) {
342 0           foreach my $x (0..length($line)-1) {
343 0           vec($clear,$x,8)=(vec($line,$x,8)+floor((vec($clear,$x-$bpp,8)+vec($prev,$x,8))/2))%256;
344             }
345             } elsif($filter==4) {
346             # die "paeth/png filter not supported.";
347 0           foreach my $x (0..length($line)-1) {
348 0           vec($clear,$x,8)=(vec($line,$x,8)+PaethPredictor(vec($clear,$x-$bpp,8),vec($prev,$x,8),vec($prev,$x-$bpp,8)))%256;
349             }
350             }
351 0           $prev=$clear;
352 0           foreach my $x (0..($width*$comp)-1) {
353 0           vec($clearstream,($n*$width*$comp)+$x,$bpc)=vec($clear,$x,$bpc);
354             # print STDERR "".vec($clear,$x,$bpc).",";
355             }
356             # print STDERR "\n";
357             }
358 0           return($clearstream);
359             }
360            
361             1;
362            
363             __END__