diff --git a/PIL/EpsImagePlugin.py b/PIL/EpsImagePlugin.py old mode 100644 new mode 100755 index 9f963f7e6..00d948fe5 --- a/PIL/EpsImagePlugin.py +++ b/PIL/EpsImagePlugin.py @@ -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 = [] diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 0ca4249a3..e0099b168 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -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)