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";
35 while (my ($dlen, $data, $len, $type, $payload, $crc) = read_chunk($fh)) {
36 dump_data($offset, $data);
38 my $calc_crc = crc($type . $payload);
39 my $src_crc = unpack("N", $crc);
41 $type =~ s/([^ -\x7f])/sprintf("\\x%02x", ord $1)/ge;
42 print " Type: $type\n";
43 print " Length: $len\n";
44 printf " CRC: %x (calculated: %x)\n", $src_crc, $calc_crc;
45 if ($type eq 'IHDR') {
46 my ($w, $h, $d, $ct, $comp, $filter, $inter) =
47 unpack("NNCCCCC", $payload);
57 my $channels = $ct == 2 ? 3 : $ct == 4 ? 2 : $ct == 6 ? 4 : 0;
58 my $bitspp = $channels * $d;
59 $sline_len = int((($w * $bitspp) + 7) / 8);
60 ++$sline_len; # filter byte
62 elsif ($type eq 'sRGB') {
63 print " Rendering intent: ", ord($payload), "\n";
65 elsif ($type eq 'PLTE') {
67 while ($index * 3 < $len) {
68 my @rgb = unpack("CCC", substr($payload, $index * 3, 3));
69 print " $index: @rgb\n";
73 elsif ($type eq 'tRNS') {
74 if ($colour_type == 0) {
75 my $g = unpack("n", $payload);
76 printf " Grey: %d (%x)\n", $g, $g;
78 elsif ($colour_type == 2) {
79 my @rgb = unpack("nnn", $payload);
80 printf " RGB: %d, %d, %d (%x, %x, %x)\n", @rgb, @rgb;
82 elsif ($colour_type == 3) {
84 for my $alpha (unpack("C*", $payload)) {
85 print " Index: $index: $alpha\n";
90 print " Unexpected tRNS for colour type $colour_type\n";
93 elsif ($type eq 'pHYs') {
94 my ($hres, $vres, $unit) = unpack("NNC", $payload);
95 my $unitname = $unit == 1 ? "metre" : "unknown";
97 hRes: $hres / $unitname
98 vRes: $vres / $unitname
99 Unit: $unit ($unitname)
102 elsif ($type eq 'tEXt') {
103 my ($key, $value) = split /\0/, $payload, 2;
108 do_more_text($key, $value);
110 elsif ($type eq 'zTXt') {
111 my ($key, $rest) = split /\0/, $payload, 2;
112 my $ctype = ord $rest;
113 my $ztxt = substr($rest, 1);
114 my $value = do_inflate($ztxt);
119 do_more_text($key, $value);
121 elsif ($type eq 'tIME') {
122 my @when = unpack("nCCCCC", $payload);
123 printf " Date: %d-%02d-%02d %02d:%02d:%02d\n", @when;
125 elsif ($type eq "IDAT" && $image) {
127 or die "IDAT before IHDR!?";
128 my $raw = do_inflate($payload);
130 print " Continuing $row:\n";
131 print " ", unpack("H*", substr($raw, 0, $sline_left, "")), "\n";
135 while (length $raw >= $sline_len) {
136 my $row_data = substr($raw, 0, $sline_len, "");
137 my ($filter, $data) = unpack("CH*", $row_data);
138 print " Row $row, filter $filter\n";
143 $sline_left = $sline_len - length $raw;
144 my ($filter, $data) = unpack("CH*", $raw);
145 print " Row $row, filter $filter (partial)\n";
146 print " $data\n" if length $data;
155 my ($key, $text) = @_;
157 if ($key eq 'Raw profile type xmp'
158 && $text =~ s/^\s*xmp\s+\d+\s+//) {
159 print " XMP: ", pack("H*", join('', split ' ', $text)), "\n";
168 or die "Cannot read chunk length\n";
169 my $len = unpack("N", $rlen);
172 or die "Cannot read chunk type\n";
175 read($fh, $payload, $len) == $len
176 or die "Cannot read payload\n";
179 read($fh, $crc, 4) == 4
180 or die "Cannot read CRC\n";
182 return ( $len + 12, $rlen . $type . $payload . $crc, $len, $type, $payload, $crc );
186 my ($offset, $data) = @_;
188 if (length $data > 28) {
190 for my $i (0 .. int((15 + length $data) / 16) - 1) {
191 my $row = substr($data, $i * 16, 16);
192 (my $clean = $row) =~ tr/ -~/./c;
193 printf("%08x: %-32s %s\n", $offset, unpack("H*", $row), $clean);
197 printf "%08x: %s...\n", $offset, unpack("H*", substr($data, 0, 26));
201 printf "%08x: %s\n", $offset, unpack("H*", $data), "\n";
205 #unsigned long crc_table[256];
208 #/* Flag: has the table been computed? Initially false. */
209 # int crc_table_computed = 0;
211 # /* Make the table for a fast CRC. */
212 # void make_crc_table(void)
218 # for (n = 0; n < 256; n++) {
219 for my $n (0 .. 255) {
220 # c = (unsigned long) n;
222 # for (k = 0; k < 8; k++) {
225 # c = 0xedb88320L ^ (c >> 1);
229 $c = 0xedb88320 ^ ($c >> 1);
240 # crc_table_computed = 1;
244 # /* Update a running CRC with the bytes buf[0..len-1]--the CRC
245 # should be initialized to all 1's, and the transmitted value
246 # is the 1's complement of the final running CRC (see the
247 # crc() routine below). */
249 # unsigned long update_crc(unsigned long crc, unsigned char *buf,
253 my ($crc, $data) = @_;
254 # unsigned long c = crc;
257 # if (!crc_table_computed)
259 @crc_table or make_crc_table();
260 # for (n = 0; n < len; n++) {
261 # c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
263 for my $code (unpack("C*", $data)) {
264 $crc = $crc_table[($crc ^ $code) & 0xFF] ^ ($crc >> 8);
271 # /* Return the CRC of the bytes buf[0..len-1]. */
272 # unsigned long crc(unsigned char *buf, int len)
274 # return update_crc(0xffffffffL, buf, len) ^ 0xffffffffL;
280 return update_crc(0xFFFFFFFF, $data) ^ 0xFFFFFFFF;
293 pngdump.pl - dump the structure of a PNG image file.
297 perl [-dumpall] [-image] pngdump.pl filename
301 Dumps the structure of a PNG image file, listing chunk types, length,
302 CRC and optionally the entire content of each chunk.
310 C<-dumpall> - dump the entire contents of each chunk rather than just
315 C<-image> - decompress the image data (IDAT chunk) and break the
316 result into rows, listing the filter and filtered (raw) data for each
321 Several chunk types are extracted and displayed in a more readable