# -*- mode: Perl -*- # PDF::Image::JPEG - JPEG image support # Author: Michael Gross # # Copyright 2001 Michael Gross # Copyright 2007 Markus Baertschi # # 27.11.2001 - Bugfix, now also works on Windows (binmode) # 03.09.2007 0.07 Markus Baertschi # - Added error checking on file open # 31.05.2008 1.00 Markus Baertschi # - Set version to 1.00 to go with PDF::Create package PDF::Image::JPEG; use strict; use vars qw(@ISA @EXPORT $VERSION $DEBUG); use Exporter; use FileHandle; @ISA = qw(Exporter); @EXPORT = qw(); $VERSION = 1.00; $DEBUG = 0; sub new { my $self = {}; $self->{private} = {}; $self->{width} = 0; $self->{height} = 0; $self->{colorspacedata} = ""; $self->{colorspace} = ""; $self->{colorspacesize} = 0; $self->{filename} = ""; $self->{error} = ""; $self->{imagesize} = 0; $self->{transparent} = 0; $self->{filter} = ["DCTDecode"]; $self->{decodeparms} = {}; bless($self); return $self; } sub pdf_next_jpeg_marker { my $self = shift; my $fh = shift; my $c = 0; my $s; my $M_ERROR = 0x100; #dummy marker, internal use only #my $dbg = ""; while ($c == 0) { while ($c != 0xFF) { if (eof($fh)) { #print "EOF in next_marker ($dbg)\n"; return $M_ERROR; } read $fh, $s, 1; $c = unpack("C", $s); #$dbg.=" " . sprintf("%x", $c); } while ($c == 0xFF) { if (eof($fh)) { #print "EOF in next_marker ($dbg)\n"; return $M_ERROR; } read $fh, $s, 1; $c = unpack("C", $s); #$dbg.=" " . sprintf("%x", $c); } } #print "next_marker: $dbg\n"; return $c; } sub Open { my $self = shift; my $filename = shift; $self->{filename} = $filename; my $M_SOF0 = 0xc0; # baseline DCT my $M_SOF1 = 0xc1; # extended sequential DCT my $M_SOF2 = 0xc2; # progressive DCT my $M_SOF3 = 0xc3; # lossless (sequential) my $M_SOF5 = 0xc5; # differential sequential DCT my $M_SOF6 = 0xc6; # differential progressive DCT my $M_SOF7 = 0xc7; # differential lossless my $M_JPG = 0xc8; # JPEG extensions my $M_SOF9 = 0xc9; # extended sequential DCT my $M_SOF10 = 0xca; # progressive DCT my $M_SOF11 = 0xcb; # lossless (sequential) my $M_SOF13 = 0xcd; # differential sequential DCT my $M_SOF14 = 0xce; # differential progressive DCT my $M_SOF15 = 0xcf; # differential lossless my $M_DHT = 0xc4; # define Huffman tables my $M_DAC = 0xcc; # define arithmetic conditioning table my $M_RST0 = 0xd0; # restart my $M_RST1 = 0xd1; # restart my $M_RST2 = 0xd2; # restart my $M_RST3 = 0xd3; # restart my $M_RST4 = 0xd4; # restart my $M_RST5 = 0xd5; # restart my $M_RST6 = 0xd6; # restart my $M_RST7 = 0xd7; # restart my $M_SOI = 0xd8; # start of image my $M_EOI = 0xd9; # end of image my $M_SOS = 0xda; # start of scan my $M_DQT = 0xdb; # define quantization tables my $M_DNL = 0xdc; # define number of lines my $M_DRI = 0xdd; # define restart interval my $M_DHP = 0xde; # define hierarchical progression my $M_EXP = 0xdf; # expand reference image(s) my $M_APP0 = 0xe0; # application marker, used for JFIF my $M_APP1 = 0xe1; # application marker my $M_APP2 = 0xe2; # application marker my $M_APP3 = 0xe3; # application marker my $M_APP4 = 0xe4; # application marker my $M_APP5 = 0xe5; # application marker my $M_APP6 = 0xe6; # application marker my $M_APP7 = 0xe7; # application marker my $M_APP8 = 0xe8; # application marker my $M_APP9 = 0xe9; # application marker my $M_APP10 = 0xea; # application marker my $M_APP11 = 0xeb; # application marker my $M_APP12 = 0xec; # application marker my $M_APP13 = 0xed; # application marker my $M_APP14 = 0xee; # application marker, used by Adobe my $M_APP15 = 0xef; # application marker my $M_JPG0 = 0xf0; # reserved for JPEG extensions my $M_JPG13 = 0xfd; # reserved for JPEG extensions my $M_COM = 0xfe; # comment my $M_TEM = 0x01; # temporary use my $M_ERROR = 0x100; #dummy marker, internal use only my $b; my $c; my $s; my $i; my $length; my $APP_MAX = 255; my $appstring; my $SOF_done = 0; my $mask = -1; my $adobeflag = 0; my $components = 0; my $fh = new FileHandle $filename; if (!defined $fh) { $self->{error} = "PDF::Image::JPEG.pm: $filename: $!"; return 0 } binmode $fh; #Tommy's special trick for Macintosh JPEGs: simply skip some # hundred bytes at the beginning of the file! MACTrick: while (!eof($fh)) { $c = 0; while (!eof($fh) && $c!=0xFF) { # skip if not FF read $fh, $s, 1; $c = unpack("C", $s); } if (eof($fh)) { close($fh); $self->{error} = "PDF::Image::JPEG.pm: Not a JPEG file."; return 0; } while (!eof($fh) && $c==0xFF) { # skip repeated FFs read $fh, $s, 1; $c = unpack("C", $s); } $self->{private}->{datapos} = tell($fh) - 2; if ($c == $M_SOI) { seek($fh, $self->{private}->{datapos}, 0); last MACTrick; } }; my $BOGUS_LENGTH = 768; #Heuristics: if we are that far from the start chances are # it is a TIFF file with embedded JPEG data which we cannot # handle - regard as hopeless... if (eof($fh) || $self->{private}->{datapos} > $BOGUS_LENGTH) { close($fh); $self->{error} = "PDF::Image::JPEG.pm: Not a JPEG file."; return 0; } #process JPEG markers */ JPEGMarkers: while (!$SOF_done && ($c = $self->pdf_next_jpeg_marker($fh)) != $M_EOI) { #print "Marker: " . sprintf("%x", $c) . "\n"; if ($c==$M_ERROR || $c==$M_SOF3 || $c==$M_SOF5 || $c==$M_SOF6 || $c==$M_SOF7 || $c==$M_SOF9 || $c==$M_SOF11 || $c==$M_SOF13 || $c==$M_SOF14 || $c==$M_SOF15) { close($fh); $self->{error} = "PDF::Image::JPEG.pm: JPEG compression " . ord($c) . " not supported in PDF 1.3.", return 0; } if ($c==$M_SOF2 || $c==$M_SOF10) { close($fh); $self->{error} = "PDF::Image::JPEG.pm: JPEG compression " . ord($c) . " not supported in PDF 1.2.", return 0; } if ($c==$M_SOF0 || $c==$M_SOF1) { read $fh, $s, 12; ($c, $self->{bpc}, $self->{height}, $self->{width}, $components) = unpack("nCnnC", $s); $SOF_done = 1; last JPEGMarkers; } elsif ($c==$M_APP0) { read $fh, $s, 2; $length = unpack("n", $s) - 2; read $fh, $appstring, $length; #Check for JFIF application marker and read density values # per JFIF spec version 1.02. my $ASPECT_RATIO = 0; #JFIF unit byte: aspect ratio only my $DOTS_PER_INCH = 1; #JFIF unit byte: dots per inch my $DOTS_PER_CM = 2; #JFIF unit byte: dots per cm if ($length >= 12 && $appstring=~/^JFIF/) { ($c, $c, $c, $c, $c, $c, $c, $self->{private}->{unit}, $self->{dpi_x}, $self->{dpi_y}) = unpack("CCCCCCCCnn", $appstring); if ($self->{dpi_x} <= 0 || $self->{dpi_y} <= 0) { $self->{dpi_x} = 0; $self->{dpi_y} = 0; } elsif ($self->{private}->{unit} == $DOTS_PER_INCH) { } elsif ($self->{private}->{unit} == $DOTS_PER_CM) { $self->{dpi_x} *= 2.54; $self->{dpi_y} *= 2.54; } elsif ($self->{private}->{unit} == $ASPECT_RATIO) { $self->{dpi_x} *= -1; $self->{dpi_y} *= -1; } } } elsif ($c==$M_APP14) { #check for Adobe marker read $fh, $s, 2; $length = unpack("n", $s) - 2; read $fh, $appstring, $length; #Check for Adobe application marker. It is known (per Adobe's TN5116) #to contain the string "Adobe" at the start of the APP14 marker. if ($length >= 10 && $appstring=~/^Adobe/) { $adobeflag = 1; } } elsif ($c==$M_SOI || $c==$M_EOI || $c==$M_TEM || $c==$M_RST0 || $c==$M_RST1 || $c==$M_RST2 || $c==$M_RST3 || $c==$M_RST4 || $c==$M_RST5 || $c==$M_RST6 || $c==$M_RST7) { #no parameters --> ignore } else { #skip variable length markers read $fh, $s, 2; $length = unpack("n", $s) - 2; read $fh, $s, $length; } } if ($self->{height} <= 0 || $self->{width} <= 0 || $components <= 0) { close($fh); $self->{error} = "PDF::Image::JPEG.pm: Bad image parameters in JPEG file."; return 0; } if ($self->{bpc} != 8) { close($fh); $self->{error} = "PDF::Image::JPEG.pm: Bad bpc in JPEG file."; return 0; } if ($components==1) { $self->{colorspace} = "DeviceGray"; } elsif ($components==3) { $self->{colorspace} = "DeviceRGB"; } elsif ($components==4) { $self->{colorspace} = "DeviceCMYK"; #special handling of Photoshop-generated CMYK JPEG files if ($adobeflag) { $self->{invert} = 1; } } else { close($fh); $self->{error} = "PDF::Image::JPEG.pm: Unknown number of color components in JPEG file.", return 0; } close($fh); 1; } sub ReadData { my $self = shift; my $s = ""; my $result; my $JPEG_BUFSIZE = 1024; my $fh = new FileHandle $self->{filename}; if (!defined $fh) { $self->{error} = "PDF::Image::JPEG.pm: $self->{filename}: $!"; return 0 } binmode $fh; seek($fh, $self->{private}->{datapos}, 0); while (read($fh, $s, $JPEG_BUFSIZE) > 0) { $result.=$s; } $self->{imagesize} = length($result); close $fh; $result; } 1;