Merge pull request #619 from eliempje/master

Bugfix: EPS thumbnail failed
This commit is contained in:
wiredfool 2014-05-24 13:09:25 +01:00
commit 3f18e0adfe
3 changed files with 101 additions and 36 deletions

View File

@ -11,6 +11,7 @@
# 1996-08-23 fl Handle files from Macintosh (0.3) # 1996-08-23 fl Handle files from Macintosh (0.3)
# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4) # 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) # 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
# #
# Copyright (c) 1997-2003 by Secret Labs AB. # Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-2003 by Fredrik Lundh # Copyright (c) 1995-2003 by Fredrik Lundh
@ -71,14 +72,15 @@ def Ghostscript(tile, size, fp, scale=1):
# Unpack decoder tile # Unpack decoder tile
decoder, tile, offset, data = tile[0] decoder, tile, offset, data = tile[0]
length, bbox = data length, bbox = data
#Hack to support hi-res rendering #Hack to support hi-res rendering
scale = int(scale) or 1 scale = int(scale) or 1
orig_size = size orig_size = size
orig_bbox = bbox orig_bbox = bbox
size = (size[0] * scale, size[1] * scale) size = (size[0] * scale, size[1] * scale)
bbox = [bbox[0], bbox[1], bbox[2] * scale, bbox[3] * scale] # resolution is dependend on bbox and size
#print("Ghostscript", scale, size, orig_size, bbox, orig_bbox) 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 import tempfile, os, subprocess
@ -86,21 +88,30 @@ def Ghostscript(tile, size, fp, scale=1):
os.close(out_fd) os.close(out_fd)
in_fd, infile = tempfile.mkstemp() in_fd, infile = tempfile.mkstemp()
os.close(in_fd) 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: with open(infile, 'wb') as f:
fp.seek(offset) # fetch length of fp
while length >0: fp.seek(0, 2)
s = fp.read(100*1024) 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: if not s:
break break
length = length - len(s) lengthfile = lengthfile - len(s)
f.write(s) f.write(s)
# Build ghostscript command # Build ghostscript command
command = ["gs", command = ["gs",
"-q", # quiet mode "-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels) "-g%dx%d" % size, # set output geometry (pixels)
"-r%d" % (72*scale), # set input DPI (dots per inch) "-r%fx%f" % res, # set input DPI (dots per inch)
"-dNOPAUSE -dSAFER", # don't pause between pages, safe mode "-dNOPAUSE -dSAFER", # don't pause between pages, safe mode
"-sDEVICE=ppmraw", # ppm driver "-sDEVICE=ppmraw", # ppm driver
"-sOutputFile=%s" % outfile, # output file "-sOutputFile=%s" % outfile, # output file
@ -108,7 +119,7 @@ def Ghostscript(tile, size, fp, scale=1):
# adjust for image origin # adjust for image origin
"-f", infile, # input file "-f", infile, # input file
] ]
if gs_windows_binary is not None: if gs_windows_binary is not None:
if not gs_windows_binary: if not gs_windows_binary:
raise WindowsError('Unable to locate Ghostscript on paths') raise WindowsError('Unable to locate Ghostscript on paths')
@ -127,7 +138,7 @@ def Ghostscript(tile, size, fp, scale=1):
os.unlink(outfile) os.unlink(outfile)
os.unlink(infile) os.unlink(infile)
except: pass except: pass
return im return im
@ -145,6 +156,8 @@ class PSFile:
self.fp.seek(offset, whence) self.fp.seek(offset, whence)
def read(self, count): def read(self, count):
return self.fp.read(count).decode('latin-1') return self.fp.read(count).decode('latin-1')
def readbinary(self, count):
return self.fp.read(count)
def tell(self): def tell(self):
pos = self.fp.tell() pos = self.fp.tell()
if self.char: if self.char:
@ -182,26 +195,34 @@ class EpsImageFile(ImageFile.ImageFile):
def _open(self): def _open(self):
# FIXME: should check the first 512 bytes to see if this
# really is necessary (platform-dependent, though...)
fp = PSFile(self.fp) fp = PSFile(self.fp)
# HEAD # FIX for: Some EPS file not handled correctly / issue #302
s = fp.read(512) # 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)
if s[:4] == "%!PS": if s[:4] == "%!PS":
offset = 0
fp.seek(0, 2) fp.seek(0, 2)
length = fp.tell() length = fp.tell()
elif i32(s) == 0xC6D3D0C5: offset = 0
offset = i32(s[4:]) elif i32(sb[0:4]) == 0xC6D3D0C5:
length = i32(s[8:]) offset = i32(sb[4:8])
fp.seek(offset) length = i32(sb[8:12])
else: else:
raise SyntaxError("not an EPS file") raise SyntaxError("not an EPS file")
# go to offset - start of "%!PS"
fp.seek(offset) fp.seek(offset)
box = None box = None
self.mode = "RGB" self.mode = "RGB"
@ -211,7 +232,7 @@ class EpsImageFile(ImageFile.ImageFile):
# Load EPS header # Load EPS header
s = fp.readline() s = fp.readline()
while s: while s:
if len(s) > 255: if len(s) > 255:

Binary file not shown.

View File

@ -1,25 +1,27 @@
from tester import * from tester import *
from PIL import Image, EpsImagePlugin from PIL import Image, EpsImagePlugin
import sys
import io import io
if not EpsImagePlugin.has_ghostscript(): if not EpsImagePlugin.has_ghostscript():
skip() skip()
#Our two EPS test files (they are identical except for their bounding boxes) # Our two EPS test files (they are identical except for their bounding boxes)
file1 = "Tests/images/zero_bb.eps" file1 = "Tests/images/zero_bb.eps"
file2 = "Tests/images/non_zero_bb.eps" file2 = "Tests/images/non_zero_bb.eps"
#Due to palletization, we'll need to convert these to RGB after load # Due to palletization, we'll need to convert these to RGB after load
file1_compare = "Tests/images/zero_bb.png" file1_compare = "Tests/images/zero_bb.png"
file1_compare_scale2 = "Tests/images/zero_bb_scale2.png" file1_compare_scale2 = "Tests/images/zero_bb_scale2.png"
file2_compare = "Tests/images/non_zero_bb.png" file2_compare = "Tests/images/non_zero_bb.png"
file2_compare_scale2 = "Tests/images/non_zero_bb_scale2.png" file2_compare_scale2 = "Tests/images/non_zero_bb_scale2.png"
# EPS test files with binary preview
file3 = "Tests/images/binary_preview_map.eps"
def test_sanity(): def test_sanity():
#Regular scale # Regular scale
image1 = Image.open(file1) image1 = Image.open(file1)
image1.load() image1.load()
assert_equal(image1.mode, "RGB") assert_equal(image1.mode, "RGB")
@ -32,7 +34,7 @@ def test_sanity():
assert_equal(image2.size, (360, 252)) assert_equal(image2.size, (360, 252))
assert_equal(image2.format, "EPS") assert_equal(image2.format, "EPS")
#Double scale # Double scale
image1_scale2 = Image.open(file1) image1_scale2 = Image.open(file1)
image1_scale2.load(scale=2) image1_scale2.load(scale=2)
assert_equal(image1_scale2.mode, "RGB") assert_equal(image1_scale2.mode, "RGB")
@ -45,54 +47,96 @@ def test_sanity():
assert_equal(image2_scale2.size, (720, 504)) assert_equal(image2_scale2.size, (720, 504))
assert_equal(image2_scale2.format, "EPS") assert_equal(image2_scale2.format, "EPS")
def test_file_object(): def test_file_object():
#issue 479 # issue 479
image1 = Image.open(file1) image1 = Image.open(file1)
with open(tempfile('temp_file.eps'), 'wb') as fh: with open(tempfile('temp_file.eps'), 'wb') as fh:
image1.save(fh, 'EPS') image1.save(fh, 'EPS')
def test_iobase_object(): def test_iobase_object():
#issue 479 # issue 479
image1 = Image.open(file1) image1 = Image.open(file1)
with io.open(tempfile('temp_iobase.eps'), 'wb') as fh: with io.open(tempfile('temp_iobase.eps'), 'wb') as fh:
image1.save(fh, 'EPS') image1.save(fh, 'EPS')
def test_render_scale1(): def test_render_scale1():
#We need png support for these render test # We need png support for these render test
codecs = dir(Image.core) codecs = dir(Image.core)
if "zip_encoder" not in codecs or "zip_decoder" not in codecs: if "zip_encoder" not in codecs or "zip_decoder" not in codecs:
skip("zip/deflate support not available") skip("zip/deflate support not available")
#Zero bounding box # Zero bounding box
image1_scale1 = Image.open(file1) image1_scale1 = Image.open(file1)
image1_scale1.load() image1_scale1.load()
image1_scale1_compare = Image.open(file1_compare).convert("RGB") image1_scale1_compare = Image.open(file1_compare).convert("RGB")
image1_scale1_compare.load() image1_scale1_compare.load()
assert_image_similar(image1_scale1, image1_scale1_compare, 5) assert_image_similar(image1_scale1, image1_scale1_compare, 5)
#Non-Zero bounding box # Non-Zero bounding box
image2_scale1 = Image.open(file2) image2_scale1 = Image.open(file2)
image2_scale1.load() image2_scale1.load()
image2_scale1_compare = Image.open(file2_compare).convert("RGB") image2_scale1_compare = Image.open(file2_compare).convert("RGB")
image2_scale1_compare.load() image2_scale1_compare.load()
assert_image_similar(image2_scale1, image2_scale1_compare, 10) assert_image_similar(image2_scale1, image2_scale1_compare, 10)
def test_render_scale2(): def test_render_scale2():
#We need png support for these render test # We need png support for these render test
codecs = dir(Image.core) codecs = dir(Image.core)
if "zip_encoder" not in codecs or "zip_decoder" not in codecs: if "zip_encoder" not in codecs or "zip_decoder" not in codecs:
skip("zip/deflate support not available") skip("zip/deflate support not available")
#Zero bounding box # Zero bounding box
image1_scale2 = Image.open(file1) image1_scale2 = Image.open(file1)
image1_scale2.load(scale=2) image1_scale2.load(scale=2)
image1_scale2_compare = Image.open(file1_compare_scale2).convert("RGB") image1_scale2_compare = Image.open(file1_compare_scale2).convert("RGB")
image1_scale2_compare.load() image1_scale2_compare.load()
assert_image_similar(image1_scale2, image1_scale2_compare, 5) assert_image_similar(image1_scale2, image1_scale2_compare, 5)
#Non-Zero bounding box # Non-Zero bounding box
image2_scale2 = Image.open(file2) image2_scale2 = Image.open(file2)
image2_scale2.load(scale=2) image2_scale2.load(scale=2)
image2_scale2_compare = Image.open(file2_compare_scale2).convert("RGB") image2_scale2_compare = Image.open(file2_compare_scale2).convert("RGB")
image2_scale2_compare.load() image2_scale2_compare.load()
assert_image_similar(image2_scale2, image2_scale2_compare, 10) assert_image_similar(image2_scale2, image2_scale2_compare, 10)
def test_resize():
# Arrange
image1 = Image.open(file1)
image2 = Image.open(file2)
new_size = (100, 100)
# Act
image1 = image1.resize(new_size)
image2 = image2.resize(new_size)
# Assert
assert_equal(image1.size, new_size)
assert_equal(image2.size, new_size)
def test_thumbnail():
# Issue #619
# Arrange
image1 = Image.open(file1)
image2 = Image.open(file2)
new_size = (100, 100)
# Act
image1.thumbnail(new_size)
image2.thumbnail(new_size)
# Assert
assert_equal(max(image1.size), max(new_size))
assert_equal(max(image2.size), max(new_size))
def test_read_binary_preview():
# Issue 302
# open image with binary preview
image1 = Image.open(file3)
# End of file