File Coverage

lib/Image/Info/PNG.pm
Criterion Covered Total %
statement 79 85 92.9
branch 49 62 79.0
condition 15 24 62.5
subroutine 4 4 100.0
pod 0 2 0.0
total 147 177 83.0


line stmt bran cond sub pod time code
1             package Image::Info::PNG;
2              
3             # Copyright 1999-2000, Gisle Aas.
4             #
5             # This library is free software; you can redistribute it and/or
6             # modify it under the same terms as Perl itself.
7              
8             =begin register
9              
10             MAGIC: /^\x89PNG\x0d\x0a\x1a\x0a/
11              
12             Information from IHDR, PLTE, gAMA, pHYs, tEXt, tIME chunks are
13             extracted. The sequence of chunks are also given by the C
14             key.
15              
16             =end register
17              
18             =cut
19              
20 2     2   13 use strict;
  2         2  
  2         77  
21 2     2   9 use vars qw/$VERSION/;
  2         4  
  2         2270  
22              
23             $VERSION = 1.03;
24              
25             # Test for Compress::Zlib (for reading zTXt chunks)
26             my $have_zlib = 0;
27             eval {
28             require Compress::Zlib;
29             $have_zlib++;
30             };
31              
32             # Test for Encode (for reading iTXt chunks)
33             my $have_encode = 0;
34             eval {
35             require Encode;
36             $have_encode++;
37             };
38              
39             sub my_read
40             {
41 90     90 0 106 my($source, $len) = @_;
42 90         86 my $buf;
43 90         253 my $n = read($source, $buf, $len);
44 90 50       151 die "read failed: $!" unless defined $n;
45 90 50       116 die "short read ($len/$n) at pos " . tell($source) unless $n == $len;
46 90         249 $buf;
47             }
48              
49              
50             sub process_file
51             {
52 8     8 0 14 my($info, $fh) = @_;
53              
54 8         17 my $signature = my_read($fh, 8);
55 8 50       24 die "Bad PNG signature"
56             unless $signature eq "\x89PNG\x0d\x0a\x1a\x0a";
57              
58 8         28 $info->push_info(0, "file_media_type" => "image/png");
59 8         21 $info->push_info(0, "file_ext" => "png");
60              
61 8         12 my @chunks;
62              
63 8         12 while (1) {
64 45         71 my($len, $type) = unpack("Na4", my_read($fh, 8));
65              
66 45 100       92 if (@chunks) {
67 37         44 my $last = $chunks[-1];
68 37         38 my $count = 1;
69 37 100       103 $count = $1 if $last =~ s/\s(\d+)$//;
70 37 100       57 if ($last eq $type) {
71 2         4 $count++;
72 2         4 $chunks[-1] = "$type $count";
73             }
74             else {
75 35         55 push(@chunks, $type);
76             }
77             }
78             else {
79 8         15 push(@chunks, $type);
80             }
81              
82 45 100       77 last if $type eq "IEND";
83 37         59 my $data = my_read($fh, $len + 4);
84 37         86 my $crc = unpack("N", substr($data, -4, 4, ""));
85 37 100 66     266 if ($type eq "IHDR" && $len == 13) {
    100 33        
    50 66        
    100 100        
    100 100        
    100 66        
    50          
    100          
86 8         25 my($w, $h, $depth, $ctype, $compression, $filter, $interlace) =
87             unpack("NNCCCCC", $data);
88             $ctype = {
89             0 => "Gray",
90             2 => "RGB",
91             3 => "Indexed-RGB",
92             4 => "GrayA",
93             6 => "RGBA",
94 8   33     49 }->{$ctype} || "PNG-$ctype";
95              
96 8 50       27 $compression = "Deflate" if $compression == 0;
97 8 50       22 $filter = "Adaptive" if $filter == 0;
98 8 100       20 $interlace = "Adam7" if $interlace == 1;
99              
100 8         23 $info->push_info(0, "width", $w);
101 8         18 $info->push_info(0, "height", $h);
102 8         26 $info->push_info(0, "SampleFormat", "U$depth");
103 8         19 $info->push_info(0, "color_type", $ctype);
104              
105 8         18 $info->push_info(0, "Compression", $compression);
106 8         20 $info->push_info(0, "PNG_Filter", $filter);
107 8 100       21 $info->push_info(0, "Interlace", $interlace)
108             if $interlace;
109             }
110             elsif ($type eq "PLTE") {
111 3         5 my @table;
112 3         7 while (length $data) {
113 36         125 push(@table, sprintf("#%02x%02x%02x",
114             unpack("C3", substr($data, 0, 3, ""))));
115             }
116 3         11 $info->push_info(0, "ColorPalette" => \@table);
117             }
118             elsif ($type eq "gAMA" && $len == 4) {
119 0         0 $info->push_info(0, "Gamma", unpack("N", $data)/100_000);
120             }
121             elsif ($type eq "pHYs" && $len == 9) {
122 5         7 my $res;
123 5         28 my($res_x, $res_y, $unit) = unpack("NNC", $data);
124 5         6 if (0 && $unit == 1) {
125             # convert to dpi
126             $unit = "dpi";
127             for ($res_x, $res_y) {
128             $_ *= 0.0254;
129             }
130             }
131 5 50       11 $res = ($res_x == $res_y) ? $res_x : "$res_x/$res_y";
132 5 50       11 if ($unit) {
133 5 50       8 if ($unit == 1) {
134 5         11 $res .= " dpm";
135             }
136             else {
137 0         0 $res .= " png-unit-$unit";
138             }
139             }
140 5         12 $info->push_info(0, "resolution" => $res)
141             }
142             elsif ($type eq "tEXt" || $type eq "zTXt" || $type eq "iTXt") {
143 7         22 my($key, $val) = split(/\0/, $data, 2);
144 7         9 my($method,$ctext,$is_i);
145 7 100       20 if ($type eq "iTXt") {
    100          
146 3         4 ++$is_i;
147 3         10 (my $compressed, $method, my $lang, my $trans, $ctext)
148             = unpack "CaZ*Z*a*", $val;
149 3 100       8 unless ($compressed) {
150 1         2 undef $method;
151 1         1 $val = $ctext;
152             }
153             }
154             elsif ($type eq "zTXt") {
155 3         9 ($method,$ctext) = split(//, $val, 2);
156             }
157              
158 7 100       14 if (defined $method) {
159 5 50 33     15 if ($have_zlib && $method eq "\0") {
160 5         12 $val = Compress::Zlib::uncompress($ctext);
161             } else {
162 0         0 undef $val;
163             }
164             }
165              
166 7 100       327 if ($is_i) {
167 3 50       5 if ($have_encode) {
168 3         9 $val = Encode::decode("UTF-8", $val);
169             } else {
170 0         0 undef $val;
171             }
172             }
173              
174 7 50       306 if (defined $val) {
175             # XXX should make sure $key is not in conflict with any
176             # other key we might generate
177 7         14 $info->push_info(0, $key, $val);
178             } else {
179 0         0 $info->push_info(0, "Chunk-$type" => $data);
180             }
181             }
182             elsif ($type eq "tIME" && $len == 7) {
183 4         31 $info->push_info(0, "LastModificationTime",
184             sprintf("%04d-%02d-%02d %02d:%02d:%02d",
185             unpack("nC5", $data)));
186             }
187             elsif ($type eq "sBIT") {
188 0         0 $info->push_info(0, "SignificantBits" => unpack("C*", $data));
189             }
190             elsif ($type eq "IDAT") {
191             # ignore
192             }
193             else {
194 2         6 $info->push_info(0, "Chunk-$type" => $data);
195             }
196             }
197              
198 8         24 $info->push_info(0, "PNG_Chunks", @chunks);
199              
200 8 100       27 unless ($info->get_info(0, "resolution")) {
201 3         35 $info->push_info(0, "resolution", "1/1");
202             }
203             }
204              
205             1;