File Coverage

lib/Image/Info/PNG.pm
Criterion Covered Total %
statement 76 82 92.6
branch 49 62 79.0
condition 15 24 62.5
subroutine 3 3 100.0
pod 0 2 0.0
total 143 173 82.6


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