3 use IO::Uncompress::Inflate qw(inflate);
8 GetOptions(dumpall => \$dumpall,
12 or die "Usage: $0 filename\n";
14 open my $fh, "<", $file
15 or die "$0: cannot open '$file': $!\n";
20 read($fh, $head, 8) == 8
21 or die "Cann't read header: $!\n";
24 dump_data($offset, $head);
26 $offset += length $head;
27 unless ($head eq "\x89PNG\x0d\x0A\cZ\x0A") {
28 die "Header isn't a PNG header\n";
36 while (my ($dlen, $data, $len, $type, $payload, $crc) = read_chunk($fh)) {
37 dump_data($offset, $data);
39 my $calc_crc = crc($type . $payload);
40 my $src_crc = unpack("N", $crc);
42 $type =~ s/([^ -\x7f])/sprintf("\\x%02x", ord $1)/ge;
43 print " Type: $type\n";
44 print " Length: $len\n";
45 printf " CRC: %x (calculated: %x)\n", $src_crc, $calc_crc;
46 if ($type eq 'IHDR') {
47 my ($w, $h, $d, $ct, $comp, $filter, $inter) =
48 unpack("NNCCCCC", $payload);
59 my $channels = $ct == 2 ? 3 : $ct == 4 ? 2 : $ct == 6 ? 4 : 1;
60 my $bitspp = $channels * $d;
61 $sline_len = int((($w * $bitspp) + 7) / 8);
62 ++$sline_len; # filter byte
63 print " Line length: $sline_len\n";
65 elsif ($type eq 'sRGB') {
66 print " Rendering intent: ", ord($payload), "\n";
68 elsif ($type eq 'PLTE') {
70 while ($index * 3 < $len) {
71 my @rgb = unpack("CCC", substr($payload, $index * 3, 3));
72 print " $index: @rgb\n";
76 elsif ($type eq 'tRNS') {
77 if ($colour_type == 0) {
78 my $g = unpack("n", $payload);
79 printf " Grey: %d (%x)\n", $g, $g;
81 elsif ($colour_type == 2) {
82 my @rgb = unpack("nnn", $payload);
83 printf " RGB: %d, %d, %d (%x, %x, %x)\n", @rgb, @rgb;
85 elsif ($colour_type == 3) {
87 for my $alpha (unpack("C*", $payload)) {
88 print " Index: $index: $alpha\n";
93 print " Unexpected tRNS for colour type $colour_type\n";
96 elsif ($type eq 'pHYs') {
97 my ($hres, $vres, $unit) = unpack("NNC", $payload);
98 my $unitname = $unit == 1 ? "metre" : "unknown";
100 hRes: $hres / $unitname
101 vRes: $vres / $unitname
102 Unit: $unit ($unitname)
105 elsif ($type eq 'tEXt') {
106 my ($key, $value) = split /\0/, $payload, 2;
111 do_more_text($key, $value);
113 elsif ($type eq 'zTXt') {
114 my ($key, $rest) = split /\0/, $payload, 2;
115 my $ctype = ord $rest;
116 my $ztxt = substr($rest, 1);
117 my $value = do_inflate($ztxt);
122 do_more_text($key, $value);
124 elsif ($type eq 'tIME') {
125 my @when = unpack("nCCCCC", $payload);
126 printf " Date: %d-%02d-%02d %02d:%02d:%02d\n", @when;
128 elsif ($type eq 'bKGD') {
129 if ($colour_type == 2 || $colour_type == 6) {
130 my @rgb = unpack("nnn", $payload);
131 printf " Background: rgb$bits(%d,%d,%d)\n", @rgb;
133 elsif ($colour_type == 0 || $colour_type == 4) {
134 my $g = unpack("n", $payload);
135 printf " Background: grey$bits(%d)\n", $g;
137 if ($colour_type == 3) {
138 my $index = unpack("C", $payload);
139 printf " Background: index(%d)\n", $index;
142 elsif ($type eq "IDAT" && $image) {
144 or die "IDAT before IHDR!?";
145 my $raw = do_inflate($payload);
147 print " Continuing $row:\n";
148 print " ", unpack("H*", substr($raw, 0, $sline_left, "")), "\n";
152 while (length $raw >= $sline_len) {
153 my $row_data = substr($raw, 0, $sline_len, "");
154 my ($filter, $data) = unpack("CH*", $row_data);
155 print " Row $row, filter $filter\n";
160 $sline_left = $sline_len - length $raw;
161 my ($filter, $data) = unpack("CH*", $raw);
162 print " Row $row, filter $filter (partial)\n";
163 print " $data\n" if length $data;
172 my ($key, $text) = @_;
174 if ($key eq 'Raw profile type xmp'
175 && $text =~ s/^\s*xmp\s+\d+\s+//) {
176 print " XMP: ", pack("H*", join('', split ' ', $text)), "\n";
185 or die "Cannot read chunk length\n";
186 my $len = unpack("N", $rlen);
189 or die "Cannot read chunk type\n";
192 read($fh, $payload, $len) == $len
193 or die "Cannot read payload\n";
196 read($fh, $crc, 4) == 4
197 or die "Cannot read CRC\n";
199 return ( $len + 12, $rlen . $type . $payload . $crc, $len, $type, $payload, $crc );
203 my ($offset, $data) = @_;
205 if (length $data > 28) {
207 for my $i (0 .. int((15 + length $data) / 16) - 1) {
208 my $row = substr($data, $i * 16, 16);
209 (my $clean = $row) =~ tr/ -~/./c;
210 printf("%08x: %-32s %s\n", $offset, unpack("H*", $row), $clean);
214 printf "%08x: %s...\n", $offset, unpack("H*", substr($data, 0, 26));
218 printf "%08x: %s\n", $offset, unpack("H*", $data), "\n";
222 #unsigned long crc_table[256];
225 #/* Flag: has the table been computed? Initially false. */
226 # int crc_table_computed = 0;
228 # /* Make the table for a fast CRC. */
229 # void make_crc_table(void)
235 # for (n = 0; n < 256; n++) {
236 for my $n (0 .. 255) {
237 # c = (unsigned long) n;
239 # for (k = 0; k < 8; k++) {
242 # c = 0xedb88320L ^ (c >> 1);
246 $c = 0xedb88320 ^ ($c >> 1);
257 # crc_table_computed = 1;
261 # /* Update a running CRC with the bytes buf[0..len-1]--the CRC
262 # should be initialized to all 1's, and the transmitted value
263 # is the 1's complement of the final running CRC (see the
264 # crc() routine below). */
266 # unsigned long update_crc(unsigned long crc, unsigned char *buf,
270 my ($crc, $data) = @_;
271 # unsigned long c = crc;
274 # if (!crc_table_computed)
276 @crc_table or make_crc_table();
277 # for (n = 0; n < len; n++) {
278 # c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
280 for my $code (unpack("C*", $data)) {
281 $crc = $crc_table[($crc ^ $code) & 0xFF] ^ ($crc >> 8);
288 # /* Return the CRC of the bytes buf[0..len-1]. */
289 # unsigned long crc(unsigned char *buf, int len)
291 # return update_crc(0xffffffffL, buf, len) ^ 0xffffffffL;
297 return update_crc(0xFFFFFFFF, $data) ^ 0xFFFFFFFF;
310 pngdump.pl - dump the structure of a PNG image file.
314 perl [-dumpall] [-image] pngdump.pl filename
318 Dumps the structure of a PNG image file, listing chunk types, length,
319 CRC and optionally the entire content of each chunk.
327 C<-dumpall> - dump the entire contents of each chunk rather than just
332 C<-image> - decompress the image data (IDAT chunk) and break the
333 result into rows, listing the filter and filtered (raw) data for each
338 Several chunk types are extracted and displayed in a more readable