Chapter A
# -*- Perl -*-
# PNGObject.pm
# A Perl module that will decode the chunks of a PNG formatted file and
# dump a printout of the various fields.
#
package PNGObject;
use strict;
# This hash defines the ordering of fields within the critical chunks
#
my %png_fields = (
# Critical chunks
#
header =>
[qw( width height bit_depth color_type
compression filter interlace )],
palette =>
[qw( red green blue )],
image_data =>
[qw( data )]
);
# This will create a string which can be used for
# valid signature comparisons
#
my $png_signature = pack "H16", "89504E470D0A1A0A";
# A list of the standard ancillary chunks for use in a later
# regular exression.
#
my $ancillary_chunks =
join "|",
qw( bKGD cHRM gAMA hIST pHYs sBIT tEXT
tIME tRNS oFFs sCAL tIME );
# This hash may be referenced to determine the length of
# each field (in bytes) for a particular chunk.
#
my %png_format = (
# Every chunk has an 8 byte header and a 4 byte trailer
#
all => {
'length' => 4,
type => 4,
# Chunk-specific data goes here
#
crc => 4
},
header => {
width => 4,
height => 4,
bit_depth => 1,
color_type => 1,
compression => 1,
filter => 1,
interlace => 1,
},
palette => {
red => 1,
green => 1,
blue => 1,
},
image_data => {
data => undef,
},
image_trailer => {
type => 4,
}
)
# Three variables for storing the decoded values
#
my $header;
my @image;
my @palette;
# The new method is the constructor for the PNGObject.
# It creates an anonymour array which may be used to store the
# decoded chunk data.
#
sub new {
my $class = shift;
my $self = [];
bless ($self, $class);
return $self;
}
sub readPNG {
my $self = shift;
my $filename = shift;
my ($nextlength, $nexttype) = (0, '');
my ($crc, $imagedata);
open PNG, $filename || return 1000; # Error 1000 = couldn't open file
binmode(PNG);
# Read and check signature
#
my $signature;
sysread(PNG, $signature, 8);
unless ($signature eq $png_signature) {
return 2000; # Error 2000 = not a valid PNG file
}
# The sysread() function returns the number of bytes read.
#
while (sysread(PNG, $nextlength, $png_format{'all'}->{'length'})) {
# The length field is more than one byte so we must worry about the
# byte ordering. PNG is saved in big-endian order so we
# can use Perl's unpack function with the 'N' option (N for
# 'network' or big-endian order) to convert it into a long integer.
#
$nextlength =unpack "N", $nextlength;
unless (sysread(PNG, $nexttype, $png_format{'all'}->{'type'})) {
print "Premature end-of-file!\n";
return 3000; # Error 3000 = premature end of file
}
# Now check to see what the type of the next field is
#
if ($nexttype eq 'IHDR') {
# A Header chunk
#
print "Found a Header Chunk!\n";
$header = $self->readChunk(*PNG, 'header');
# The data in CRC can be used to determine whether the
# data in $header has been corrpted
#
sysread(PNG, $crc, $png_format{'all'}->{'crc'});
} elsif ($nexttype eq 'PLTE') {
# A Palette chunk
#
print "Found a palette!\n";
my $size = $nextlength/3;
print "Color table with $size entries:\n";
for (my $index=0; $index < $size; $index++) {
print "Index $index:\n";
push @palette,
$self->readChunk(*PNG, 'palette');
print " \n";
}
sysread(PNG, $crc, $png_format{'all'}->{'crc'});
} elsif ($nexttype eq 'IDAT') {
# An Image Data chunk
#
print "Found an image data chunk!\n";
print "Read ".sysread(PNG, $imagedata, $nextlength)." bytes.\n";
# The @image list of image data must still be decoded
#
push @image, $imagedata;
sysread(PNG, $crc, $png_format{'all'}->{'crc'});
} elsif ($nexttype eq 'IEND') {
# An Image Trailer chunk
#
print "Found the image trailer chunk!\n";
return 1;
} elsif ($nexttype =~ /$ancillary_chunks/) {
# One of the standard ancillary chunks.
# A more robust decoder should recognize all of these.
#
print "Skipping an ancillary chunk of type $nexttype.\n";
print " This chunk is a standard part of the PNG 1.0 spec,\n";
print " but is not supported by this decoder. Sorry.\n";
skipChunk(*PNG, $nextlength);
} else {
# An unrecognized chunk. It should be skipped.
#
print "Skipping a chunk of type $nexttype.\n";
print " This chunk is not recognized as a standard part of the\n";
print " PNG 1.0 spec, but it could be a private or\n";
print " special purpose public chunk.\n";
skipChunk(*PNG, $nextlength);
}
}
print "Premature end of file (no IEND chunk encountered)...\n";
return 3000; # Error 3000 = premature end of file
}
sub skipChunk {
my ($fh, $bytes) = @_;
my $skip;
# Skip the chunk, throwing the data away.
# The number of bytes to skip is +4 because of CRC bytes
#
for (my $count=0; $count < $bytes + 4; $count++) {
sysread($fh, $skip, 1);
}
}
sub readChunk {
# A convenience routine to read in generic chunk data.
# Note that 'chunk data' does not include the chunk header or
# the crc trailer. This method returns a hash containing
# the data. It also prints each field as it is read as an
# appropriate sized integer for easy reading.
#
my $self = shift;
my $fh = shift; # the filehandle
my $chunkname = shift; # a string that is the key to the png_format hash
my @fields = @{$png_fields{$chunkname}};
my %returnhash = ();
foreach my $field (@fields) {
sysread($fh,
$returnhash{$field},
$png_format{$chunkname}->{$field});
print "$field: ";
# If the field is more four bytes long, we must unpack
# it as a 'network' (big-endian) long integer.
# If it is a two byte field, it must be unpacked as a
# 'network' short integer.
# Otherwise it is only a single byte and we can make it an
# integer with ord.
#
if ($png_format{$chunkname}->{$field} == 4) {
print unpack("N", $returnhash{$field});
}elsif ($png_format{$chunkname}->{$field} == 2) {
print unpack("n", $returnhash{$field});
} else {
print ord($returnhash{$field});
}
print "\n";
}
return { %returnhash };
}
1; # All Perl modules should return true
DRAFT, August 21, 1998
DRAFT, August 21, 1998