/usr/local/CPAN/perl-pdf/PDF/Image/JPEGImage.pm
# -*- mode: Perl -*-
# PDF::Image::JPEGImage - JPEG image support
# Author: Michael Gross <mdgrosse@sbox.tugraz.at>
# Version: 0.06
# Copyright 2001 Michael Gross <mdgrosse@sbox.tugraz.at>
#
# 27.11.2001 - Bugfix, now also works on Windows (binmode)
package PDF::Image::JPEGImage;
use strict;
use vars qw(@ISA @EXPORT $VERSION $DEBUG);
use Exporter;
use FileHandle;
@ISA = qw(Exporter);
@EXPORT = qw();
$VERSION = 0.06;
$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;
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} = "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} = "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} = "JPEG compression " . ord($c) . " not supported in PDF 1.3.",
return 0;
}
if ($c==$M_SOF2 || $c==$M_SOF10) {
close($fh);
$self->{error} = "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} = "Bad image parameters in JPEG file.";
return 0;
}
if ($self->{bpc} != 8) {
close($fh);
$self->{error} = "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} = "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};
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;