diff --git a/PIL/Image.py b/PIL/Image.py index 08b0dbe7d..c6ab76b6d 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -30,6 +30,7 @@ from PIL import VERSION, PILLOW_VERSION, _plugins import warnings + class _imaging_not_installed: # module placeholder def __getattr__(self, id): @@ -52,7 +53,7 @@ try: # directly; import Image and use the Image.core variable instead. from PIL import _imaging as core if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None): - raise ImportError("The _imaging extension was built for another " + raise ImportError("The _imaging extension was built for another " " version of Pillow or PIL") except ImportError as v: @@ -91,10 +92,13 @@ except ImportError: builtins = __builtin__ from PIL import ImageMode -from PIL._binary import i8, o8 -from PIL._util import isPath, isStringType, deferred_error +from PIL._binary import i8 +from PIL._util import isPath +from PIL._util import isStringType +from PIL._util import deferred_error -import os, sys +import os +import sys # type stuff import collections @@ -104,9 +108,10 @@ import numbers USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info') try: import cffi - HAS_CFFI=True + HAS_CFFI = True except: - HAS_CFFI=False + HAS_CFFI = False + def isImageType(t): """ @@ -148,16 +153,16 @@ MESH = 4 # resampling filters NONE = 0 NEAREST = 0 -ANTIALIAS = 1 # 3-lobed lanczos +ANTIALIAS = 1 # 3-lobed lanczos LINEAR = BILINEAR = 2 CUBIC = BICUBIC = 3 # dithers NONE = 0 NEAREST = 0 -ORDERED = 1 # Not yet implemented -RASTERIZE = 2 # Not yet implemented -FLOYDSTEINBERG = 3 # default +ORDERED = 1 # Not yet implemented +RASTERIZE = 2 # Not yet implemented +FLOYDSTEINBERG = 3 # default # palettes/quantizers WEB = 0 @@ -222,7 +227,7 @@ else: _MODE_CONV = { # official modes - "1": ('|b1', None), # broken + "1": ('|b1', None), # broken "L": ('|u1', None), "I": (_ENDIAN + 'i4', None), "F": (_ENDIAN + 'f4', None), @@ -232,8 +237,8 @@ _MODE_CONV = { "RGBA": ('|u1', 4), "CMYK": ('|u1', 4), "YCbCr": ('|u1', 3), - "LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1 - # I;16 == I;16L, and I;32 == I;32L + "LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1 + # I;16 == I;16L, and I;32 == I;32L "I;16": ('u2', None), "I;16L": (' size[0]: y = int(max(y * size[0] / x, 1)); x = int(size[0]) - if y > size[1]: x = int(max(x * size[1] / y, 1)); y = int(size[1]) + if x > size[0]: + y = int(max(y * size[0] / x, 1)) + x = int(size[0]) + if y > size[1]: + x = int(max(x * size[1] / y, 1)) + y = int(size[1]) size = x, y if size == self.size: @@ -1717,7 +1736,7 @@ class Image: except ValueError: if resample != ANTIALIAS: raise - im = self.resize(size, NEAREST) # fallback + im = self.resize(size, NEAREST) # fallback self.im = im.im self.mode = im.mode @@ -1753,7 +1772,9 @@ class Image: """ if self.mode == 'RGBA': - return self.convert('RGBa').transform(size, method, data, resample, fill).convert('RGBA') + return self.convert('RGBa') \ + .transform(size, method, data, resample, fill) \ + .convert('RGBA') if isinstance(method, ImageTransformHandler): return method.transform(size, self, resample=resample, fill=fill) @@ -1800,8 +1821,13 @@ class Image: elif method == QUAD: # quadrilateral warp. data specifies the four corners # given as NW, SW, SE, and NE. - nw = data[0:2]; sw = data[2:4]; se = data[4:6]; ne = data[6:8] - x0, y0 = nw; As = 1.0 / w; At = 1.0 / h + nw = data[0:2] + sw = data[2:4] + se = data[4:6] + ne = data[6:8] + x0, y0 = nw + As = 1.0 / w + At = 1.0 / h data = (x0, (ne[0]-x0)*As, (sw[0]-x0)*At, (se[0]-sw[0]-ne[0]+x0)*As*At, y0, (ne[1]-y0)*As, (sw[1]-y0)*At, @@ -1835,6 +1861,7 @@ class Image: im = self.im.transpose(method) return self._new(im) + # -------------------------------------------------------------------- # Lazy operations @@ -1870,6 +1897,7 @@ class _ImageCrop(Image): # FIXME: future versions should optimize crop/paste # sequences! + # -------------------------------------------------------------------- # Abstract handlers. @@ -1877,10 +1905,12 @@ class ImagePointHandler: # used as a mixin by point transforms (for use with im.point) pass + class ImageTransformHandler: # used as a mixin by geometry transforms (for use with im.transform) pass + # -------------------------------------------------------------------- # Factories @@ -1956,6 +1986,7 @@ def frombytes(mode, size, data, decoder_name="raw", *args): im.frombytes(data, decoder_name, args) return im + def fromstring(*args, **kw): """Deprecated alias to frombytes. @@ -2018,9 +2049,9 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): " frombuffer(mode, size, data, 'raw', mode, 0, 1)", RuntimeWarning, stacklevel=2 ) - args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6 + args = mode, 0, -1 # may change to (mode, 0, 1) post-1.1.6 if args[0] in _MAPMODES: - im = new(mode, (1,1)) + im = new(mode, (1, 1)) im = im._new( core.map_buffer(data, size, decoder_name, None, 0, args) ) @@ -2140,8 +2171,8 @@ def open(fp, mode="r"): fp.seek(0) return factory(fp, filename) except (SyntaxError, IndexError, TypeError): - #import traceback - #traceback.print_exc() + # import traceback + # traceback.print_exc() pass if init(): @@ -2153,13 +2184,14 @@ def open(fp, mode="r"): fp.seek(0) return factory(fp, filename) except (SyntaxError, IndexError, TypeError): - #import traceback - #traceback.print_exc() + # import traceback + # traceback.print_exc() pass raise IOError("cannot identify image file %r" % (filename if filename else fp)) + # # Image processing. @@ -2258,6 +2290,7 @@ def merge(mode, bands): im.putband(bands[i].im, i) return bands[0]._new(im) + # -------------------------------------------------------------------- # Plugin registry @@ -2316,6 +2349,7 @@ def _show(image, **options): # override me, as necessary _showxv(image, **options) + def _showxv(image, title=None, **options): from PIL import ImageShow ImageShow.show(image, title, **options) diff --git a/PIL/PdfImagePlugin.py b/PIL/PdfImagePlugin.py index 725f22ecf..fcc841438 100644 --- a/PIL/PdfImagePlugin.py +++ b/PIL/PdfImagePlugin.py @@ -46,9 +46,11 @@ def _obj(fp, obj, **dict): fp.write("/%s %s\n" % (k, v)) fp.write(">>\n") + def _endobj(fp): fp.write("endobj\n") + ## # (Internal) Image save plugin for the PDF format. @@ -59,13 +61,15 @@ def _save(im, fp, filename): # make sure image data is available im.load() - xref = [0]*(5+1) # placeholders + xref = [0]*(5+1) # placeholders class TextWriter: def __init__(self, fp): self.fp = fp + def __getattr__(self, name): return getattr(self.fp, name) + def write(self, value): self.fp.write(value.encode('latin-1')) @@ -89,13 +93,13 @@ def _save(im, fp, filename): if im.mode == "1": filter = "/ASCIIHexDecode" colorspace = "/DeviceGray" - procset = "/ImageB" # grayscale + procset = "/ImageB" # grayscale bits = 1 elif im.mode == "L": filter = "/DCTDecode" # params = "<< /Predictor 15 /Columns %d >>" % (width-2) colorspace = "/DeviceGray" - procset = "/ImageB" # grayscale + procset = "/ImageB" # grayscale elif im.mode == "P": filter = "/ASCIIHexDecode" colorspace = "[ /Indexed /DeviceRGB 255 <" @@ -105,16 +109,16 @@ def _save(im, fp, filename): g = i8(palette[i*3+1]) b = i8(palette[i*3+2]) colorspace = colorspace + "%02x%02x%02x " % (r, g, b) - colorspace = colorspace + b"> ]" - procset = "/ImageI" # indexed color + colorspace = colorspace + "> ]" + procset = "/ImageI" # indexed color elif im.mode == "RGB": filter = "/DCTDecode" colorspace = "/DeviceRGB" - procset = "/ImageC" # color images + procset = "/ImageC" # color images elif im.mode == "CMYK": filter = "/DCTDecode" colorspace = "/DeviceCMYK" - procset = "/ImageC" # color images + procset = "/ImageC" # color images else: raise ValueError("cannot save mode %s" % im.mode) @@ -122,17 +126,21 @@ def _save(im, fp, filename): # catalogue xref[1] = fp.tell() - _obj(fp, 1, Type = "/Catalog", - Pages = "2 0 R") + _obj( + fp, 1, + Type="/Catalog", + Pages="2 0 R") _endobj(fp) # # pages xref[2] = fp.tell() - _obj(fp, 2, Type = "/Pages", - Count = 1, - Kids = "[4 0 R]") + _obj( + fp, 2, + Type="/Pages", + Count=1, + Kids="[4 0 R]") _endobj(fp) # @@ -144,29 +152,31 @@ def _save(im, fp, filename): if bits == 1: # FIXME: the hex encoder doesn't support packed 1-bit # images; do things the hard way... - data = im.tostring("raw", "1") + data = im.tobytes("raw", "1") im = Image.new("L", (len(data), 1), None) im.putdata(data) - ImageFile._save(im, op, [("hex", (0,0)+im.size, 0, im.mode)]) + ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)]) elif filter == "/DCTDecode": Image.SAVE["JPEG"](im, op, filename) elif filter == "/FlateDecode": - ImageFile._save(im, op, [("zip", (0,0)+im.size, 0, im.mode)]) + ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)]) elif filter == "/RunLengthDecode": - ImageFile._save(im, op, [("packbits", (0,0)+im.size, 0, im.mode)]) + ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)]) else: raise ValueError("unsupported PDF filter (%s)" % filter) xref[3] = fp.tell() - _obj(fp, 3, Type = "/XObject", - Subtype = "/Image", - Width = width, # * 72.0 / resolution, - Height = height, # * 72.0 / resolution, - Length = len(op.getvalue()), - Filter = filter, - BitsPerComponent = bits, - DecodeParams = params, - ColorSpace = colorspace) + _obj( + fp, 3, + Type="/XObject", + Subtype="/Image", + Width=width, # * 72.0 / resolution, + Height=height, # * 72.0 / resolution, + Length=len(op.getvalue()), + Filter=filter, + BitsPerComponent=bits, + DecodeParams=params, + ColorSpace=colorspace) fp.write("stream\n") fp.fp.write(op.getvalue()) @@ -179,11 +189,14 @@ def _save(im, fp, filename): xref[4] = fp.tell() _obj(fp, 4) - fp.write("<<\n/Type /Page\n/Parent 2 0 R\n"\ - "/Resources <<\n/ProcSet [ /PDF %s ]\n"\ - "/XObject << /image 3 0 R >>\n>>\n"\ - "/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" %\ - (procset, int(width * 72.0 /resolution) , int(height * 72.0 / resolution))) + fp.write( + "<<\n/Type /Page\n/Parent 2 0 R\n" + "/Resources <<\n/ProcSet [ /PDF %s ]\n" + "/XObject << /image 3 0 R >>\n>>\n" + "/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" % ( + procset, + int(width * 72.0 / resolution), + int(height * 72.0 / resolution))) _endobj(fp) # @@ -191,10 +204,13 @@ def _save(im, fp, filename): op = TextWriter(io.BytesIO()) - op.write("q %d 0 0 %d 0 0 cm /image Do Q\n" % (int(width * 72.0 / resolution), int(height * 72.0 / resolution))) + op.write( + "q %d 0 0 %d 0 0 cm /image Do Q\n" % ( + int(width * 72.0 / resolution), + int(height * 72.0 / resolution))) xref[5] = fp.tell() - _obj(fp, 5, Length = len(op.fp.getvalue())) + _obj(fp, 5, Length=len(op.fp.getvalue())) fp.write("stream\n") fp.fp.write(op.fp.getvalue()) diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py new file mode 100644 index 000000000..e99f22db1 --- /dev/null +++ b/Tests/test_file_pdf.py @@ -0,0 +1,58 @@ +from tester import * +import os.path + + +def helper_save_as_pdf(mode): + # Arrange + im = lena(mode) + outfile = tempfile("temp_" + mode + ".pdf") + + # Act + im.save(outfile) + + # Assert + assert_true(os.path.isfile(outfile)) + assert_greater(os.path.getsize(outfile), 0) + + +def test_monochrome(): + # Arrange + mode = "1" + + # Act / Assert + helper_save_as_pdf(mode) + + +def test_greyscale(): + # Arrange + mode = "L" + + # Act / Assert + helper_save_as_pdf(mode) + + +def test_rgb(): + # Arrange + mode = "RGB" + + # Act / Assert + helper_save_as_pdf(mode) + + +def test_p_mode(): + # Arrange + mode = "P" + + # Act / Assert + helper_save_as_pdf(mode) + + +def test_cmyk_mode(): + # Arrange + mode = "CMYK" + + # Act / Assert + helper_save_as_pdf(mode) + + +# End of file