#!/usr/bin/perl #$Id: jpeg2eps,v 1.7 2003/11/21 14:30:12 danlee Exp $ # # Copyright Lee Sau Dan (c) 2002, 2003 # # Converts jpeg to EPS (Postscript Level 2) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # or download it from http://www.gnu.org/licenses/gpl.txt # use strict; use Fcntl 'SEEK_SET', 'SEEK_CUR'; use Getopt::Long; use File::Basename; use POSIX 'ceil'; our $version = version_string('$Revision: 1.7 $', '$Name: $'); our $opt_version; our $opt_help; our $opt_dpi; our $opt_interpolate; our $dpi = 300; # default to 300dpi our $jpeg_filename; our ($ps_width, $ps_height); sub show_version { my $out = shift; print $out < This program is released under the GNU General Public License EOF } sub show_usage { my $out = shift; show_version($out); print $out " Usage: jpeg2eps [options] jpeg_file [eps_file] Convert jpegfile into an EPS file. If eps_file is not specified, the result goes to standard output. jpeg2eps is \"lossless\" in the sense that it passes the JPEG data to the output file, which a Postscript Level 2 interpreter will read, without changing the contents. (It just encodes the contents so that it is ASCII compatible.) So, the Postscript interpreter gets the original JPEG data. This is quite different from the approach of first decompressing the JPEG file (e.g. with 'djpeg') into a bitmap file (e.g. pnm) and then encapsulating the bitmap file into an EPS file. Such an approach would increase the file size because of the decompression, as well as not keeping the original image data. jpeg2eps, by contrast, directly encapsulates the JPEG file into the output file. It is thus faster and lets the Postscript interpreter see the originial JPEG data stream, which is decompressed at the Postscript interpreter. Options: --version Show version of this program --help Show this help message --dpi= Specify the image resolution, in dots-per-inch. (default: 300) --width= --height= Specify the desired size of the image in the EPS file. takes the form: , where is 'in' for inch, 'cm' for centimetre and 'pt' for Postscript points. An omitted is equivalent to 'pt'. If only width or height is given, the other is determined by keeping the aspect ratio or the image. You may not specify --dpi together with these options. --interpolate Causes the generated Postscript code to exploit the \"interpolation\" feature. When your image has a lower resolution than the device resolution, enabling this option *may* make the output look smoother on the device. But the Postscript interpreter may need much more time to handle such an image. Note that this option doesn't affect the output file size (up to a few bytes), because the interpolation is done by the Postscript interpreter, not this program. This option only adds code to enable that feature in the Postscript interpreter. "; } unless (GetOptions("version" => \$opt_version, "help" => \$opt_help, "dpi=f" => \$opt_dpi, "width=s" => \$ps_width, "height=s" => \$ps_height, "interpolate" => \$opt_interpolate, )) { show_usage(*STDERR{IO}); exit 1; } if ($opt_help) { show_usage(*STDOUT{IO}); exit 0; } if ($opt_version) { show_version(*STDOUT{IO}); exit 0; } if ($opt_dpi) { $dpi = $opt_dpi; die "dpi must be non-zero\n" if ($dpi == 0); } if ($ps_width) { die "You cannot specify both --width and --dpi together\n" if $opt_dpi; $ps_width = parse_measure($ps_width); die "Bad width specification '$ps_width'\n" unless $ps_width; } if ($ps_height) { die "You cannot specify both --height and --dpi together\n" if $opt_dpi; $ps_height = parse_measure($ps_height); die "Bad height specification '$ps_height'\n" unless $ps_height; } die "no JPEG file specified" unless ($jpeg_filename = shift @ARGV); { my $out_filename = shift @ARGV; if (defined($out_filename)) { open(STDOUT, "<$out_filename") || die "Can't open $out_filename: $!\n"; } } warn "extra arguments ignored: " . join(" ", @ARGV) if ($#ARGV > 0); open(JPEG, "<$jpeg_filename") || die "Can't open $jpeg_filename: $!\n"; jpeg_info_init(); my ($width, $height, $nr_cc) = jpeg_get_info(*JPEG{IO}); die "$width\n" unless defined($nr_cc); # error message instead of info my ($decode, $dcs); if ($nr_cc == 1) { ($decode, $dcs) = ("[0 1]", "/DeviceGray"); } elsif ($nr_cc == 3) { ($decode, $dcs) = ("[0 1 0 1 0 1]", "/DeviceRGB"); } elsif ($nr_cc == 4) { ($decode, $dcs) = ("[1 0 1 0 1 0 1 0]", "/DeviceCMYK"); } else { die "Unsupported: $nr_cc color components"; } my $interpolate = $opt_interpolate? "/Interpolate true" : ""; if (!defined($ps_width)) { if (!defined($ps_height)) { # neither width nor height was specified: use dpi ($ps_width, $ps_height) = ($width * 72 / $dpi, $height * 72 / $dpi); } else { # have height, but not width: keep aspect ratio $ps_width = $width * $ps_height / $height; $dpi = $height * 72 / $ps_height; } } else { # we have width specified if (!defined($ps_height)) { # height not specified: keep aspect ratio $ps_height = $height * $ps_width / $width; $dpi = $width * 72 / $ps_width; } else { # aspect ratio may have changed: we may have different DPI on # the two dimensions... my ($xdpi, $ydpi) = ($width * 72 / $ps_width, $height * 72 / $ps_height); if ($xdpi == $ydpi) { $dpi = $xdpi; } else { $dpi = "$xdpi $ydpi"; } } } my ($ps_width_ceil, $ps_height_ceil) = (POSIX::ceil($ps_width), POSIX::ceil($ps_height)); my $title = basename($jpeg_filename, (".jpg", ".JPG", ".jpeg")); my $date = `date`; chomp $date; print < %%+ $version %%CreationDate: $date %%BoundingBox: 0 0 $ps_width_ceil $ps_height_ceil %%HiResBoundingBox: 0 0 $ps_width $ps_height %%Pages: 1 %%LanguageLevel: 2 %%EndComments %jpeg2eps: resolution = $dpi %%EndProlog %%Page: 1 1 gsave $dcs setcolorspace $ps_width $ps_height scale <>image EOF # now, encode and output the image data seek(JPEG, 0, SEEK_SET) || die "can't rewind: $!\n"; encode_ascii85(*JPEG{IO}); close(JPEG) || die "can't close $jpeg_filename: $!\n"; # trailer print <= 0xC0 && $markertype <= 0xC2) { # SOF0==baseline; SOF1==extended sequential; SOF2==progressive read($jpeg_ref, $buffer, 3)==3 || return "$!"; return "JPEG file is not 8 bits per component!\n" if (ord substr($buffer, 2, 1) != 8); read($jpeg_ref, $buffer, 5)==5 || return "$!"; my ($height, $width, $nr_cc) = unpack("nnC", $buffer); return ($width, $height, $nr_cc); } elsif ($jpeg_not_supported_markers[$markertype]) { return sprintf("Unsupported JPEG marker (0x$02x)\n", $markertype); } else { # skip segment next if ($jpeg_no_params_markers[$markertype]); read($jpeg_ref, $buffer, 2)==2 || return "$!"; my $segment_length = unpack("n", $buffer); seek($jpeg_ref, $segment_length-2, SEEK_CUR) || return "$!"; } } return "Can't find JPEG markers to determine image size"; } ### ascii85 encoder sub encode_ascii85 { my $in = shift; my $buffer; my $out_buf; my $r; while (($r=read($in, $buffer, 4))==4) { my $b = unpack("N", $buffer); my $c; if ($b == 0) { $c = 'z'; } else { for (my $i = 4; $i >= 0; --$i) { $c = chr($b % 85 + ord '!') . $c; $b /= 85; } } $out_buf .= $c; print $&, "\n" if ($out_buf =~ s/^.{70}//); } die if $r>4; if ($r > 0) { # the remaining tail $buffer .= chr(0) x 4; my $b = unpack("N", $buffer); my $c; for (my $i = 4; $i >= 0; --$i) { $c = chr($b % 85 + ord '!') . $c; $b /= 85; } $out_buf .= substr($c, 0, $r+1); } $out_buf .= "~>"; # end mark of ASCII85 stream while ($out_buf =~ s/^.{70}//) { print $&, "\n"; } print $out_buf, "\n" if $out_buf; } ### misc. routines sub version_string { my($rev, $name) = @_; if ($name =~ /^[\$]Name: (.+) [\$]/) { $name = $1; if ($name =~ s/^r//) { $name =~ s/_/./g; return "Release $name"; } return $name; } if ($rev =~ /^[\$]Revision: (.*) [\$]/) { return "Revision $1"; } else { return "(unknown version)"; } } sub parse_measure { # returns undef, or equivalent measure in points my $measure = shift; $measure =~ /^([0-9]+(?:\.[0-9]*)?|\.[0-9]+)(pt|in|cm)?$/ || return; my ($value, $unit) = ($1, $2); return $value * 72 if ($unit eq 'in'); return $value * 72 / 2.54 if ($unit eq 'cm'); return $value; # 'in' or no unit specified } #bye!