Pillow/PIL/EpsImagePlugin.py

447 lines
13 KiB
Python
Raw Normal View History

2010-07-31 06:52:47 +04:00
#
# The Python Imaging Library.
# $Id$
#
# EPS file handling
#
# History:
# 1995-09-01 fl Created (0.1)
# 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2)
# 1996-08-22 fl Don't choke on floating point BoundingBox values
# 1996-08-23 fl Handle files from Macintosh (0.3)
# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
# 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
# 2014-05-07 e Handling of EPS with binary preview and fixed resolution resizing
2010-07-31 06:52:47 +04:00
#
# Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-2003 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
__version__ = "0.5"
import re
import io
2013-03-07 20:20:28 +04:00
from PIL import Image, ImageFile, _binary
2010-07-31 06:52:47 +04:00
#
# --------------------------------------------------------------------
i32 = _binary.i32le
o32 = _binary.o32le
2010-07-31 06:52:47 +04:00
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
2013-03-06 21:36:22 +04:00
gs_windows_binary = None
import sys
if sys.platform.startswith('win'):
2013-03-06 21:36:22 +04:00
import shutil
if hasattr(shutil, 'which'):
which = shutil.which
else:
# Python < 3.3
import distutils.spawn
2013-03-06 21:36:22 +04:00
which = distutils.spawn.find_executable
for binary in ('gswin32c', 'gswin64c', 'gs'):
if which(binary) is not None:
gs_windows_binary = binary
break
else:
gs_windows_binary = False
def has_ghostscript():
if gs_windows_binary:
return True
if not sys.platform.startswith('win'):
import subprocess
try:
gs = subprocess.Popen(['gs','--version'], stdout=subprocess.PIPE)
gs.stdout.read()
return True
except OSError:
# no ghostscript
pass
return False
def Ghostscript(tile, size, fp, scale=1):
2013-03-06 21:36:22 +04:00
"""Render an image using Ghostscript"""
2010-07-31 06:52:47 +04:00
# Unpack decoder tile
decoder, tile, offset, data = tile[0]
length, bbox = data
#Hack to support hi-res rendering
scale = int(scale) or 1
orig_size = size
orig_bbox = bbox
size = (size[0] * scale, size[1] * scale)
# resolution is dependend on bbox and size
res = ( float((72.0 * size[0]) / (bbox[2]-bbox[0])), float((72.0 * size[1]) / (bbox[3]-bbox[1])) )
#print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res)
import tempfile, os, subprocess
2010-07-31 06:52:47 +04:00
out_fd, outfile = tempfile.mkstemp()
os.close(out_fd)
in_fd, infile = tempfile.mkstemp()
os.close(in_fd)
# ignore length and offset!
# ghostscript can read it
# copy whole file to read in ghostscript
with open(infile, 'wb') as f:
# fetch length of fp
fp.seek(0, 2)
fsize = fp.tell()
# ensure start position
# go back
fp.seek(0)
lengthfile = fsize
while lengthfile > 0:
s = fp.read(min(lengthfile, 100*1024))
if not s:
break
length -= len(s)
f.write(s)
2010-07-31 06:52:47 +04:00
# Build ghostscript command
command = ["gs",
"-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch)
"-dNOPAUSE -dSAFER", # don't pause between pages, safe mode
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
"-c", "%d %d translate" % (-bbox[0], -bbox[1]),
# adjust for image origin
"-f", infile, # input file
]
2013-03-06 21:36:22 +04:00
if gs_windows_binary is not None:
2014-01-09 07:07:35 +04:00
if not gs_windows_binary:
2013-03-06 21:36:22 +04:00
raise WindowsError('Unable to locate Ghostscript on paths')
command[0] = gs_windows_binary
2010-07-31 06:52:47 +04:00
# push data through ghostscript
try:
gs = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
gs.stdin.close()
status = gs.wait()
2010-07-31 06:52:47 +04:00
if status:
raise IOError("gs failed (status %d)" % status)
im = Image.core.open_ppm(outfile)
2010-07-31 06:52:47 +04:00
finally:
try:
os.unlink(outfile)
os.unlink(infile)
2010-07-31 06:52:47 +04:00
except: pass
2010-07-31 06:52:47 +04:00
return im
class PSFile:
"""Wrapper that treats either CR or LF as end of line."""
def __init__(self, fp):
self.fp = fp
self.char = None
def __getattr__(self, id):
v = getattr(self.fp, id)
setattr(self, id, v)
return v
def seek(self, offset, whence=0):
self.char = None
self.fp.seek(offset, whence)
def read(self, count):
return self.fp.read(count).decode('latin-1')
def readbinary(self, count):
return self.fp.read(count)
2010-07-31 06:52:47 +04:00
def tell(self):
pos = self.fp.tell()
if self.char:
pos -= 1
2010-07-31 06:52:47 +04:00
return pos
def readline(self):
s = b""
2010-07-31 06:52:47 +04:00
if self.char:
c = self.char
self.char = None
else:
c = self.fp.read(1)
while c not in b"\r\n":
2010-07-31 06:52:47 +04:00
s = s + c
c = self.fp.read(1)
if c == b"\r":
2010-07-31 06:52:47 +04:00
self.char = self.fp.read(1)
if self.char == b"\n":
2010-07-31 06:52:47 +04:00
self.char = None
return s.decode('latin-1') + "\n"
2010-07-31 06:52:47 +04:00
def _accept(prefix):
return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5
2010-07-31 06:52:47 +04:00
##
# Image plugin for Encapsulated Postscript. This plugin supports only
# a few variants of this format.
class EpsImageFile(ImageFile.ImageFile):
"""EPS File Parser for the Python Imaging Library"""
format = "EPS"
format_description = "Encapsulated Postscript"
def _open(self):
fp = PSFile(self.fp)
# FIX for: Some EPS file not handled correctly / issue #302
# EPS can contain binary data
# or start directly with latin coding
# read header in both ways to handle both
# file types
# more info see http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
# for HEAD without binary preview
s = fp.read(4)
# for HEAD with binary preview
fp.seek(0)
sb = fp.readbinary(160)
2010-07-31 06:52:47 +04:00
if s[:4] == "%!PS":
fp.seek(0, 2)
length = fp.tell()
offset = 0
elif i32(sb[0:4]) == 0xC6D3D0C5:
offset = i32(sb[4:8])
length = i32(sb[8:12])
2010-07-31 06:52:47 +04:00
else:
raise SyntaxError("not an EPS file")
2010-07-31 06:52:47 +04:00
# go to offset - start of "%!PS"
2010-07-31 06:52:47 +04:00
fp.seek(offset)
2010-07-31 06:52:47 +04:00
box = None
self.mode = "RGB"
self.size = 1, 1 # FIXME: huh?
#
# Load EPS header
s = fp.readline()
2010-07-31 06:52:47 +04:00
while s:
if len(s) > 255:
raise SyntaxError("not an EPS file")
2010-07-31 06:52:47 +04:00
if s[-2:] == '\r\n':
s = s[:-2]
elif s[-1:] == '\n':
s = s[:-1]
try:
m = split.match(s)
except re.error as v:
raise SyntaxError("not an EPS file")
2010-07-31 06:52:47 +04:00
if m:
k, v = m.group(1, 2)
self.info[k] = v
if k == "BoundingBox":
try:
# Note: The DSC spec says that BoundingBox
# fields should be integers, but some drivers
# put floating point values there anyway.
box = [int(float(s)) for s in v.split()]
2010-07-31 06:52:47 +04:00
self.size = box[2] - box[0], box[3] - box[1]
self.tile = [("eps", (0,0) + self.size, offset,
(length, box))]
except:
pass
else:
m = field.match(s)
if m:
k = m.group(1)
2010-07-31 06:52:47 +04:00
if k == "EndComments":
break
if k[:8] == "PS-Adobe":
self.info[k[:8]] = k[9:]
else:
self.info[k] = ""
elif s[0:1] == '%':
# handle non-DSC Postscript comments that some
# tools mistakenly put in the Comments section
pass
2010-07-31 06:52:47 +04:00
else:
raise IOError("bad EPS header")
2010-07-31 06:52:47 +04:00
s = fp.readline()
if s[:1] != "%":
break
#
# Scan for an "ImageData" descriptor
while s[0] == "%":
if len(s) > 255:
raise SyntaxError("not an EPS file")
2010-07-31 06:52:47 +04:00
if s[-2:] == '\r\n':
s = s[:-2]
elif s[-1:] == '\n':
s = s[:-1]
if s[:11] == "%ImageData:":
[x, y, bi, mo, z3, z4, en, id] =\
s[11:].split(None, 7)
2010-07-31 06:52:47 +04:00
x = int(x); y = int(y)
bi = int(bi)
mo = int(mo)
en = int(en)
if en == 1:
decoder = "eps_binary"
elif en == 2:
decoder = "eps_hex"
else:
break
if bi != 8:
break
if mo == 1:
self.mode = "L"
elif mo == 2:
self.mode = "LAB"
elif mo == 3:
self.mode = "RGB"
else:
break
if id[:1] == id[-1:] == '"':
id = id[1:-1]
# Scan forward to the actual image data
while True:
2010-07-31 06:52:47 +04:00
s = fp.readline()
if not s:
break
if s[:len(id)] == id:
self.size = x, y
self.tile2 = [(decoder,
(0, 0, x, y),
fp.tell(),
0)]
return
s = fp.readline()
if not s:
break
if not box:
raise IOError("cannot determine EPS bounding box")
2010-07-31 06:52:47 +04:00
def load(self, scale=1):
2010-07-31 06:52:47 +04:00
# Load EPS via Ghostscript
if not self.tile:
return
self.im = Ghostscript(self.tile, self.size, self.fp, scale)
2010-07-31 06:52:47 +04:00
self.mode = self.im.mode
self.size = self.im.size
self.tile = []
def load_seek(self,*args,**kwargs):
# we can't incrementally load, so force ImageFile.parser to
# use our custom load method by defining this method.
pass
2010-07-31 06:52:47 +04:00
#
# --------------------------------------------------------------------
def _save(im, fp, filename, eps=1):
"""EPS Writer for the Python Imaging Library."""
#
# make sure image data is available
im.load()
#
# determine postscript image mode
if im.mode == "L":
operator = (8, 1, "image")
2010-07-31 06:52:47 +04:00
elif im.mode == "RGB":
operator = (8, 3, "false 3 colorimage")
2010-07-31 06:52:47 +04:00
elif im.mode == "CMYK":
operator = (8, 4, "false 4 colorimage")
2010-07-31 06:52:47 +04:00
else:
raise ValueError("image mode is not supported")
2010-07-31 06:52:47 +04:00
class NoCloseStream:
def __init__(self, fp):
self.fp = fp
def __getattr__(self, name):
return getattr(self.fp, name)
def close(self):
pass
base_fp = fp
fp = NoCloseStream(fp)
if sys.version_info[0] > 2:
fp = io.TextIOWrapper(fp, encoding='latin-1')
2010-07-31 06:52:47 +04:00
if eps:
#
# write EPS header
fp.write("%!PS-Adobe-3.0 EPSF-3.0\n")
fp.write("%%Creator: PIL 0.1 EpsEncode\n")
2010-07-31 06:52:47 +04:00
#fp.write("%%CreationDate: %s"...)
fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size)
fp.write("%%Pages: 1\n")
fp.write("%%EndComments\n")
fp.write("%%Page: 1 1\n")
fp.write("%%ImageData: %d %d " % im.size)
fp.write("%d %d 0 1 1 \"%s\"\n" % operator)
2010-07-31 06:52:47 +04:00
#
# image header
fp.write("gsave\n")
fp.write("10 dict begin\n")
fp.write("/buf %d string def\n" % (im.size[0] * operator[1]))
fp.write("%d %d scale\n" % im.size)
fp.write("%d %d 8\n" % im.size) # <= bits
fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
fp.write("{ currentfile buf readhexstring pop } bind\n")
fp.write(operator[2] + "\n")
fp.flush()
ImageFile._save(im, base_fp, [("eps", (0,0)+im.size, 0, None)])
fp.write("\n%%%%EndBinary\n")
fp.write("grestore end\n")
2010-07-31 06:52:47 +04:00
fp.flush()
#
# --------------------------------------------------------------------
Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
Image.register_save(EpsImageFile.format, _save)
Image.register_extension(EpsImageFile.format, ".ps")
Image.register_extension(EpsImageFile.format, ".eps")
Image.register_mime(EpsImageFile.format, "application/postscript")