This commit is contained in:
Karsten Wolf 2014-08-05 09:58:08 +00:00
commit abdf466d84
2 changed files with 107 additions and 57 deletions

156
PIL/EpsImagePlugin.py Normal file → Executable file
View File

@ -23,6 +23,8 @@ __version__ = "0.5"
import re
import io
import unicodedata
from PIL import Image, ImageFile, _binary
#
@ -34,6 +36,8 @@ o32 = _binary.o32le
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
defaultDPI = 600.0
gs_windows_binary = None
import sys
if sys.platform.startswith('win'):
@ -64,23 +68,29 @@ def has_ghostscript():
# no ghostscript
pass
return False
def Ghostscript(tile, size, fp, scale=1):
def makeunicode( s, enc="latin-1", normalizer='NFC'):
"""return a normalized unicode string"""
try:
if type(s) != unicode:
s = unicode(s, enc)
except:
pass
return unicodedata.normalize(normalizer, s)
def Ghostscript(tile, size, fp):
"""Render an image using Ghostscript"""
# 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)
# bbox is in points == 1/72 inch
# size is in pixels, a device unit
xpointsize = bbox[2] - bbox[0]
ypointsize = bbox[3] - bbox[1]
xdpi = size[0] / (xpointsize / 72.0)
ydpi = size[1] / (ypointsize / 72.0)
import tempfile, os, subprocess
@ -90,28 +100,27 @@ def Ghostscript(tile, size, fp, scale=1):
os.close(in_fd)
# ignore length and offset!
# ghostscript can read it
# 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()
lengthfile = fp.tell()
# ensure start position
# go back
fp.seek(0)
lengthfile = fsize
while lengthfile > 0:
s = fp.read(min(lengthfile, 100*1024))
s = fp.read( 4*1024*1024 )
if not s:
break
length -= len(s)
lengthfile -= len(s)
f.write(s)
# 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)
"-r%fx%f" % (xdpi,ydpi), # set input DPI (dots per inch)
"-dNOPAUSE -dSAFER", # don't pause between pages, safe mode
"-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file
@ -119,7 +128,7 @@ def Ghostscript(tile, size, fp, scale=1):
# adjust for image origin
"-f", infile, # input file
]
if gs_windows_binary is not None:
if not gs_windows_binary:
raise WindowsError('Unable to locate Ghostscript on paths')
@ -210,10 +219,10 @@ class EpsImageFile(ImageFile.ImageFile):
fp.seek(0)
sb = fp.readbinary(160)
offset = 0
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])
@ -222,27 +231,26 @@ class EpsImageFile(ImageFile.ImageFile):
# go to offset - start of "%!PS"
fp.seek(offset)
box = None
self.mode = "RGB"
self.size = 1, 1 # FIXME: huh?
self.pixelsize = False
self.pointsize = False
self.bbox = False
#
# Load EPS header
s = fp.readline()
s = s.rstrip("\r\n")
s = makeunicode(s)
while s:
if len(s) > 255:
raise SyntaxError("not an EPS file")
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:
@ -256,10 +264,31 @@ class EpsImageFile(ImageFile.ImageFile):
# 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()]
# if self.pointsize is not False, we already have a hiresbbox
if not self.pointsize:
box = [int(float(s)) for s in v.split()]
self.bbox = box
pointsize = box[2] - box[0], box[3] - box[1]
self.size = box[2] - box[0], box[3] - box[1]
self.tile = [ ("eps",
(0,0) + self.size,
offset,
(length, box))]
self.pointsize = pointsize
except:
pass
if k == "HiResBoundingBox":
try:
box = [float(s) for s in v.split()]
self.bbox = box
pointsize = box[2] - box[0], box[3] - box[1]
self.size = box[2] - box[0], box[3] - box[1]
self.tile = [("eps", (0,0) + self.size, offset,
self.tile = [ ("eps",
(0,0) + self.size,
offset,
(length, box))]
self.pointsize = pointsize
except:
pass
@ -284,11 +313,12 @@ class EpsImageFile(ImageFile.ImageFile):
raise IOError("bad EPS header")
s = fp.readline()
s = s.rstrip("\r\n")
s = makeunicode(s)
if s[:1] != "%":
break
#
# Scan for an "ImageData" descriptor
@ -303,12 +333,12 @@ class EpsImageFile(ImageFile.ImageFile):
s = s[:-1]
if s[:11] == "%ImageData:":
[x, y, bi, mo, z3, z4, en, starttag] = s[11:].split(None, 7)
[x, y, bi, mo, z3, z4, en, id] =\
s[11:].split(None, 7)
x = int(x); y = int(y)
x = int(x)
y = int(y)
self.pixelsize = (x,y)
bi = int(bi)
mo = int(mo)
@ -319,46 +349,66 @@ class EpsImageFile(ImageFile.ImageFile):
elif en == 2:
decoder = "eps_hex"
else:
break
pass
if bi != 8:
break
if mo == 1:
self.mode = "L"
elif mo == 2:
self.mode = "LAB"
elif mo == 3:
self.mode = "RGB"
elif mo == 4:
self.mode = "CMYK"
else:
break
pass
if id[:1] == id[-1:] == '"':
id = id[1:-1]
bbox = (0, 0, x, y)
if self.bbox:
bbox = self.bbox
# Scan forward to the actual image data
while True:
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
self.tile = [("eps",
(0, 0, x, y),
0,
(length, bbox))]
xdpi = round(x / (self.size[0] / 72.0))
ydpi = round(y / (self.size[1] / 72.0))
self.info["dpi"] = (xdpi,ydpi)
return
s = fp.readline()
s = s.rstrip("\r\n")
s = makeunicode(s)
if not s:
break
if not box:
raise IOError("cannot determine EPS bounding box")
def load(self, scale=1):
if not self.pixelsize:
self.info["dpi"] = (defaultDPI, defaultDPI)
def load(self, scale=None):
# Load EPS via Ghostscript
if not self.tile:
return
self.im = Ghostscript(self.tile, self.size, self.fp, scale)
size = self.size
if self.pixelsize:
# pixel based eps
# size is imagesize in pixels
size = self.pixelsize
else:
# generic eps
# size is in points (== 72dpi uglyness)
if not scale:
scale = (defaultDPI/72.0)
size = ( size[0] * scale, size[1] * scale)
self.im = Ghostscript(self.tile, size, self.fp)
self.mode = self.im.mode
self.size = self.im.size
self.tile = []

View File

@ -27,13 +27,13 @@ class TestFileEps(PillowTestCase):
def test_sanity(self):
# Regular scale
image1 = Image.open(file1)
image1.load()
image1.load(scale=1.0)
self.assertEqual(image1.mode, "RGB")
self.assertEqual(image1.size, (460, 352))
self.assertEqual(image1.format, "EPS")
image2 = Image.open(file2)
image2.load()
image2.load(scale=1.0)
self.assertEqual(image2.mode, "RGB")
self.assertEqual(image2.size, (360, 252))
self.assertEqual(image2.format, "EPS")
@ -71,14 +71,14 @@ class TestFileEps(PillowTestCase):
# Zero bounding box
image1_scale1 = Image.open(file1)
image1_scale1.load()
image1_scale1.load(scale=1.0)
image1_scale1_compare = Image.open(file1_compare).convert("RGB")
image1_scale1_compare.load()
self.assert_image_similar(image1_scale1, image1_scale1_compare, 5)
# Non-Zero bounding box
image2_scale1 = Image.open(file2)
image2_scale1.load()
image2_scale1.load(scale=1.0)
image2_scale1_compare = Image.open(file2_compare).convert("RGB")
image2_scale1_compare.load()
self.assert_image_similar(image2_scale1, image2_scale1_compare, 10)