# # PDF::Image::GIF - GIF image support for PDF::Create # # Author: Michael Gross # # Copyright 1999-2001 Fabien Tassin # Copyright 2007 Markus Baertschi # # Please see the CHANGES and Changes file for the detailed change log # # Please do not use any of the methods here directly. You will be # punished with your application no longer working after an upgrade ! # package PDF::Image::GIF; use 5.006; use strict; use warnings; use FileHandle; our $VERSION = '1.46'; our $DEBUG = 0; sub new { my $self = {}; $self->{private} = {}; $self->{colorspace} = 0; $self->{width} = 0; $self->{height} = 0; $self->{colorspace} = "DeviceRGB"; $self->{colorspacedata} = ""; $self->{colorspacesize} = 0; $self->{filename} = ""; $self->{error} = ""; $self->{imagesize} = 0; $self->{transparent} = 0; $self->{filter} = ["LZWDecode"]; $self->{decodeparms} = { 'EarlyChange' => 0 }; $self->{private}->{interlaced} = 0; bless($self); return $self; } sub LZW { my $self = shift; my $data = shift; my $result = ""; my $prefix = ""; my $c; my %hash; my $num; my $codesize = 9; #init hash-table for ( $num = 0 ; $num < 256 ; $num++ ) { $hash{ chr($num) } = $num; } #start with a clear $num = 258; my $currentvalue = 256; my $bits = 9; my $pos = 0; while ( $pos < length($data) ) { $c = substr( $data, $pos, 1 ); if ( exists( $hash{ $prefix . $c } ) ) { $prefix .= $c; } else { #save $hash{$prefix} $currentvalue <<= $codesize; $currentvalue |= $hash{$prefix}; $bits += $codesize; while ( $bits >= 8 ) { $result .= chr( ( $currentvalue >> ( $bits - 8 ) ) & 255 ); $bits -= 8; $currentvalue &= ( 1 << $bits ) - 1; } $hash{ $prefix . $c } = $num; $prefix = $c; $num++; #increase code size? if ( $num == 513 || $num == 1025 || $num == 2049 ) { $codesize++; } #hash table overflow? if ( $num == 4097 ) { #save clear $currentvalue <<= $codesize; $currentvalue |= 256; $bits += $codesize; while ( $bits >= 8 ) { $result .= chr( ( $currentvalue >> ( $bits - 8 ) ) & 255 ); $bits -= 8; $currentvalue &= ( 1 << $bits ) - 1; } #reset hash table $codesize = 9; %hash = (); for ( $num = 0 ; $num < 256 ; $num++ ) { $hash{ chr($num) } = $num; } $num = 258; } } $pos++; } #save value for prefix $currentvalue <<= $codesize; $currentvalue |= $hash{$prefix}; $bits += $codesize; while ( $bits >= 8 ) { $result .= chr( ( $currentvalue >> ( $bits - 8 ) ) & 255 ); $bits -= 8; $currentvalue &= ( 1 << $bits ) - 1; } #save eoi $currentvalue <<= $codesize; $currentvalue |= 257; $bits += $codesize; while ( $bits >= 8 ) { $result .= chr( ( $currentvalue >> ( $bits - 8 ) ) & 255 ); $bits -= 8; $currentvalue &= ( 1 << $bits ) - 1; } #save remainder in $currentvalue if ( $bits > 0 ) { $currentvalue = $currentvalue << ( 8 - $bits ); $result .= chr( $currentvalue & 255 ); } $result; } sub UnLZW { my $self = shift; my $data = shift; my $result = ""; my $bits = 0; my $currentvalue = 0; my $codesize = 9; my $pos = 0; my $prefix = ""; my $suffix; my @table; #initialize lookup-table my $num; for ( $num = 0 ; $num < 256 ; $num++ ) { $table[$num] = chr($num); } $table[256] = ""; $num = 257; my $c1; #get first word while ( $bits < $codesize ) { my $d = ord( substr( $data, $pos, 1 ) ); $currentvalue = ( $currentvalue << 8 ) + $d; $bits += 8; $pos++; } my $c2 = $currentvalue >> ( $bits - $codesize ); $bits -= $codesize; my $mask = ( 1 << $bits ) - 1; $currentvalue = $currentvalue & $mask; DECOMPRESS: while ( $pos < length($data) ) { $c1 = $c2; #get next word while ( $bits < $codesize ) { my $d = ord( substr( $data, $pos, 1 ) ); $currentvalue = ( $currentvalue << 8 ) + $d; $bits += 8; $pos++; } $c2 = $currentvalue >> ( $bits - $codesize ); $bits -= $codesize; $mask = ( 1 << $bits ) - 1; $currentvalue = $currentvalue & $mask; #clear code? if ( $c2 == 256 ) { $result .= $table[$c1]; $#table = 256; $codesize = 9; $num = 257; next DECOMPRESS; } #End Of Image? if ( $c2 == 257 ) { last DECOMPRESS; } #get prefix if ( $c1 < $num ) { $prefix = $table[$c1]; } else { print "Compression Error ($c1>=$num)\n"; } #write prefix $result .= $prefix; #get suffix if ( $c2 < $num ) { $suffix = substr( $table[$c2], 0, 1 ); } elsif ( $c2 == $num ) { $suffix = substr( $prefix, 0, 1 ); } else { print "Compression Error ($c2>$num)\n"; } #new table entry is prefix.suffix $table[$num] = $prefix . $suffix; #next table entry $num++; #increase code size? if ( $num == 512 || $num == 1024 || $num == 2048 ) { $codesize++; } } $result .= $table[$c1] if defined $table[$c1]; $result; } sub UnInterlace { my $self = shift; my $data = shift; my $row; my @result; my $width = $self->{width}; my $height = $self->{height}; my $idx = 0; #Pass 1 - every 8th row, starting with row 0 $row = 0; while ( $row < $height ) { $result[$row] = substr( $data, $idx * $width, $width ); $row += 8; $idx++; } #Pass 2 - every 8th row, starting with row 4 $row = 4; while ( $row < $height ) { $result[$row] = substr( $data, $idx * $width, $width ); $row += 8; $idx++; } #Pass 3 - every 4th row, starting with row 2 $row = 2; while ( $row < $height ) { $result[$row] = substr( $data, $idx * $width, $width ); $row += 4; $idx++; } #Pass 4 - every 2th row, starting with row 1 $row = 1; while ( $row < $height ) { $result[$row] = substr( $data, $idx * $width, $width ); $row += 2; $idx++; } join( '', @result ); } sub GetDataBlock { my $self = shift; my $fh = shift; my $s; my $count; my $buf; read $fh, $s, 1; $count = unpack( "C", $s ); if ($count) { read $fh, $buf, $count; } ( $count, $buf ); } sub ReadColorMap { my $self = shift; my $fh = shift; read $fh, $self->{'colorspacedata'}, 3 * $self->{'colormapsize'}; 1; } sub DoExtension { my $self = shift; my $label = shift; my $fh = shift; my $res; my $buf; my $c; my $c2; my $c3; if ( $label eq "\001" ) { #Plain Text Extension } elsif ( ord($label) == 0xFF ) { #Application Extension } elsif ( ord($label) == 0xFE ) { #Comment Extension } elsif ( ord($label) == 0xF9 ) { #Grapgic Control Extension ( $res, $buf ) = $self->GetDataBlock($fh); #(p, image, (unsigned char*) buf); ( $c, $c2, $c2, $c3 ) = unpack( "CCCC", $buf ); if ( $c && 0x1 != 0 ) { $self->{transparent} = 1; $self->{mask} = $c3; } } BLOCK: while (1) { ( $res, $buf ) = $self->GetDataBlock($fh); if ( $res == 0 ) { last BLOCK; } } 1; } sub Open { my $self = shift; my $filename = shift; my $PDF_STRING_GIF = "\107\111\106"; my $PDF_STRING_87a = "\070\067\141"; my $PDF_STRING_89a = "\070\071\141"; my $LOCALCOLORMAP = 0x80; my $INTERLACE = 0x40; my $s; my $c; my $ar; my $flags; $self->{filename} = $filename; my $fh = FileHandle->new("$filename"); if ( !defined $fh ) { $self->{error} = "PDF::Image::GIF.pm: $filename: $!"; return 0 } binmode $fh; read $fh, $s, 3; if ( $s ne $PDF_STRING_GIF ) { close $fh; $self->{error} = "PDF::Image::GIF.pm: Not a gif file."; return 0; } read $fh, $s, 3; if ( $s ne $PDF_STRING_87a && $s ne $PDF_STRING_89a ) { close $fh; $self->{error} = "PDF::Image::GIF.pm: GIF version $s not supported."; return 0; } read $fh, $s, 7; ( $self->{width}, $self->{height}, $flags, $self->{private}->{background}, $ar ) = unpack( "vvCCC", $s ); $self->{colormapsize} = 2 << ( $flags & 0x07 ); $self->{colorspacesize} = 3 * $self->{colormapsize}; if ( $flags & $LOCALCOLORMAP ) { if ( !$self->ReadColorMap($fh) ) { close $fh; $self->{error} = "PDF::Image::GIF.pm: Cant read color map."; return 0; } } if ( $ar != 0 ) { $self->{private}->{dpi_x} = -( $ar + 15.0 ) / 64.0; $self->{private}->{dpi_y} = -1.0; } my $imageCount = 0; IMAGES: while (1) { read $fh, $c, 1; if ( $c eq ";" ) { #GIF file terminator close $fh; $self->{error} = "PDF::Image::GIF.pm: Cant find image in gif file."; return 0; } if ( $c eq "!" ) { #Extension read $fh, $c, 1; $self->DoExtension( $c, $fh ); next; } if ( $c ne "," ) { #must be comma next; #ignore } $imageCount++; read $fh, $s, 9; my $x; ( $x, $c, $self->{width}, $self->{height}, $flags ) = unpack( "vvvvC", $s ); if ( $flags && $INTERLACE ) { $self->{private}->{interlaced} = 1; } if ( $flags & $LOCALCOLORMAP ) { if ( !$self->ReadColorMap($fh) ) { close $fh; $self->{error} = "PDF::Image::GIF.pm: Cant read color map."; return 0; } } read $fh, $s, 1; #read "LZW initial code size" $self->{bpc} = unpack( "C", $s ); if ( $self->{bpc} != 8 ) { close $fh; $self->{error} = "PDF::Image::GIF.pm: LZW minimum code size is " . $self->{bpc} . ", must be 8 to be supported."; return 0; } if ( $imageCount == 1 ) { last IMAGES; } } $self->{private}->{datapos} = tell($fh); close $fh; 1; } sub ReadData { my $self = shift; # init the LZW transformation vars my $c_size = 9; # initial code size my $t_size = 257; # initial "table" size my $i_buff = 0; # input buffer my $i_bits = 0; # input buffer empty my $o_bits = 0; # output buffer empty my $o_buff = 0; my $c_mask; my $bytes_available = 0; my $n_bytes; my $s; my $c; my $flag13; my $code; my $w_bits; my $result = ""; my $fh = FileHandle->new($self->{filename}); if ( !defined $fh ) { $self->{error} = "PDF::Image::GIF.pm: $self->{filename}: $!"; return 0 } binmode $fh; seek( $fh, $self->{private}->{datapos}, 0 ); my $pos = 0; my $data; read $fh, $data, ( -s $self->{filename} ); use integer; $self->{imagesize} = 0; BLOCKS: while (1) { $s = substr( $data, $pos, 1 ); $pos++; $n_bytes = unpack( "C", $s ); if ( !$n_bytes ) { last BLOCKS; } $c_mask = ( 1 << $c_size ) - 1; $flag13 = 0; BLOCK: while (1) { $w_bits = $c_size; # number of bits to write $code = 0; #get at least c_size bits into i_buff while ( $i_bits < $c_size ) { if ( $n_bytes == 0 ) { last BLOCK; } $n_bytes--; $s = substr( $data, $pos, 1 ); $pos++; $c = unpack( "C", $s ); $i_buff |= $c << $i_bits; #EOF will be caught later $i_bits += 8; } $code = $i_buff & $c_mask; $i_bits -= $c_size; $i_buff >>= $c_size; if ( $flag13 && $code != 256 && $code != 257 ) { $self->{error} = "PDF::Image::GIF.pm: LZW code size overflow."; return 0; } if ( $o_bits > 0 ) { $o_buff |= $code >> ( $c_size - 8 + $o_bits ); $w_bits -= 8 - $o_bits; $result .= chr( $o_buff & 255 ); } if ( $w_bits >= 8 ) { $w_bits -= 8; $result .= chr( ( $code >> $w_bits ) & 255 ); } $o_bits = $w_bits; if ( $o_bits > 0 ) { $o_buff = $code << ( 8 - $o_bits ); } $t_size++; if ( $code == 256 ) { #clear code $c_size = 9; $c_mask = ( 1 << $c_size ) - 1; $t_size = 257; $flag13 = 0; } if ( $code == 257 ) { #end code last BLOCK; } if ( $t_size == ( 1 << $c_size ) ) { if ( ++$c_size > 12 ) { $c_size--; $flag13 = 1; } else { $c_mask = ( 1 << $c_size ) - 1; } } } # while () for block } # while () for all blocks #interlaced? if ( $self->{private}->{interlaced} ) { #when interlaced first uncompress image $result = $self->UnLZW($result); #remove interlacing $result = $self->UnInterlace($result); #compress image again $result = $self->LZW($result); } $self->{imagesize} = length($result); $result; } 1;