mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 01:46:18 +03:00
Merge pull request #667 from wiredfool/hugovk_pdf
PDF Tests and Fixes merge
This commit is contained in:
commit
97ee7938cb
158
PIL/Image.py
158
PIL/Image.py
|
@ -30,6 +30,7 @@ from PIL import VERSION, PILLOW_VERSION, _plugins
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
class _imaging_not_installed:
|
class _imaging_not_installed:
|
||||||
# module placeholder
|
# module placeholder
|
||||||
def __getattr__(self, id):
|
def __getattr__(self, id):
|
||||||
|
@ -52,7 +53,7 @@ try:
|
||||||
# directly; import Image and use the Image.core variable instead.
|
# directly; import Image and use the Image.core variable instead.
|
||||||
from PIL import _imaging as core
|
from PIL import _imaging as core
|
||||||
if PILLOW_VERSION != getattr(core, 'PILLOW_VERSION', None):
|
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")
|
" version of Pillow or PIL")
|
||||||
|
|
||||||
except ImportError as v:
|
except ImportError as v:
|
||||||
|
@ -91,10 +92,13 @@ except ImportError:
|
||||||
builtins = __builtin__
|
builtins = __builtin__
|
||||||
|
|
||||||
from PIL import ImageMode
|
from PIL import ImageMode
|
||||||
from PIL._binary import i8, o8
|
from PIL._binary import i8
|
||||||
from PIL._util import isPath, isStringType, deferred_error
|
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
|
# type stuff
|
||||||
import collections
|
import collections
|
||||||
|
@ -104,9 +108,10 @@ import numbers
|
||||||
USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info')
|
USE_CFFI_ACCESS = hasattr(sys, 'pypy_version_info')
|
||||||
try:
|
try:
|
||||||
import cffi
|
import cffi
|
||||||
HAS_CFFI=True
|
HAS_CFFI = True
|
||||||
except:
|
except:
|
||||||
HAS_CFFI=False
|
HAS_CFFI = False
|
||||||
|
|
||||||
|
|
||||||
def isImageType(t):
|
def isImageType(t):
|
||||||
"""
|
"""
|
||||||
|
@ -148,16 +153,16 @@ MESH = 4
|
||||||
# resampling filters
|
# resampling filters
|
||||||
NONE = 0
|
NONE = 0
|
||||||
NEAREST = 0
|
NEAREST = 0
|
||||||
ANTIALIAS = 1 # 3-lobed lanczos
|
ANTIALIAS = 1 # 3-lobed lanczos
|
||||||
LINEAR = BILINEAR = 2
|
LINEAR = BILINEAR = 2
|
||||||
CUBIC = BICUBIC = 3
|
CUBIC = BICUBIC = 3
|
||||||
|
|
||||||
# dithers
|
# dithers
|
||||||
NONE = 0
|
NONE = 0
|
||||||
NEAREST = 0
|
NEAREST = 0
|
||||||
ORDERED = 1 # Not yet implemented
|
ORDERED = 1 # Not yet implemented
|
||||||
RASTERIZE = 2 # Not yet implemented
|
RASTERIZE = 2 # Not yet implemented
|
||||||
FLOYDSTEINBERG = 3 # default
|
FLOYDSTEINBERG = 3 # default
|
||||||
|
|
||||||
# palettes/quantizers
|
# palettes/quantizers
|
||||||
WEB = 0
|
WEB = 0
|
||||||
|
@ -222,7 +227,7 @@ else:
|
||||||
|
|
||||||
_MODE_CONV = {
|
_MODE_CONV = {
|
||||||
# official modes
|
# official modes
|
||||||
"1": ('|b1', None), # broken
|
"1": ('|b1', None), # broken
|
||||||
"L": ('|u1', None),
|
"L": ('|u1', None),
|
||||||
"I": (_ENDIAN + 'i4', None),
|
"I": (_ENDIAN + 'i4', None),
|
||||||
"F": (_ENDIAN + 'f4', None),
|
"F": (_ENDIAN + 'f4', None),
|
||||||
|
@ -232,8 +237,8 @@ _MODE_CONV = {
|
||||||
"RGBA": ('|u1', 4),
|
"RGBA": ('|u1', 4),
|
||||||
"CMYK": ('|u1', 4),
|
"CMYK": ('|u1', 4),
|
||||||
"YCbCr": ('|u1', 3),
|
"YCbCr": ('|u1', 3),
|
||||||
"LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1
|
"LAB": ('|u1', 3), # UNDONE - unsigned |u1i1i1
|
||||||
# I;16 == I;16L, and I;32 == I;32L
|
# I;16 == I;16L, and I;32 == I;32L
|
||||||
"I;16": ('<u2', None),
|
"I;16": ('<u2', None),
|
||||||
"I;16B": ('>u2', None),
|
"I;16B": ('>u2', None),
|
||||||
"I;16L": ('<u2', None),
|
"I;16L": ('<u2', None),
|
||||||
|
@ -248,6 +253,7 @@ _MODE_CONV = {
|
||||||
"I;32LS": ('<i4', None),
|
"I;32LS": ('<i4', None),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _conv_type_shape(im):
|
def _conv_type_shape(im):
|
||||||
shape = im.size[1], im.size[0]
|
shape = im.size[1], im.size[0]
|
||||||
typ, extra = _MODE_CONV[im.mode]
|
typ, extra = _MODE_CONV[im.mode]
|
||||||
|
@ -368,8 +374,8 @@ def init():
|
||||||
for plugin in _plugins:
|
for plugin in _plugins:
|
||||||
try:
|
try:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print ("Importing %s"%plugin)
|
print ("Importing %s" % plugin)
|
||||||
__import__("PIL.%s"%plugin, globals(), locals(), [])
|
__import__("PIL.%s" % plugin, globals(), locals(), [])
|
||||||
except ImportError:
|
except ImportError:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print("Image: failed to import", end=' ')
|
print("Image: failed to import", end=' ')
|
||||||
|
@ -379,6 +385,7 @@ def init():
|
||||||
_initialized = 2
|
_initialized = 2
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Codec factories (used by tobytes/frombytes and ImageFile.load)
|
# Codec factories (used by tobytes/frombytes and ImageFile.load)
|
||||||
|
|
||||||
|
@ -398,6 +405,7 @@ def _getdecoder(mode, decoder_name, args, extra=()):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise IOError("decoder %s not available" % decoder_name)
|
raise IOError("decoder %s not available" % decoder_name)
|
||||||
|
|
||||||
|
|
||||||
def _getencoder(mode, encoder_name, args, extra=()):
|
def _getencoder(mode, encoder_name, args, extra=()):
|
||||||
|
|
||||||
# tweak arguments
|
# tweak arguments
|
||||||
|
@ -421,30 +429,36 @@ def _getencoder(mode, encoder_name, args, extra=()):
|
||||||
def coerce_e(value):
|
def coerce_e(value):
|
||||||
return value if isinstance(value, _E) else _E(value)
|
return value if isinstance(value, _E) else _E(value)
|
||||||
|
|
||||||
|
|
||||||
class _E:
|
class _E:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
return _E((self.data, "__add__", coerce_e(other).data))
|
return _E((self.data, "__add__", coerce_e(other).data))
|
||||||
|
|
||||||
def __mul__(self, other):
|
def __mul__(self, other):
|
||||||
return _E((self.data, "__mul__", coerce_e(other).data))
|
return _E((self.data, "__mul__", coerce_e(other).data))
|
||||||
|
|
||||||
|
|
||||||
def _getscaleoffset(expr):
|
def _getscaleoffset(expr):
|
||||||
stub = ["stub"]
|
stub = ["stub"]
|
||||||
data = expr(_E(stub)).data
|
data = expr(_E(stub)).data
|
||||||
try:
|
try:
|
||||||
(a, b, c) = data # simplified syntax
|
(a, b, c) = data # simplified syntax
|
||||||
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number)):
|
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number)):
|
||||||
return c, 0.0
|
return c, 0.0
|
||||||
if (a is stub and b == "__add__" and isinstance(c, numbers.Number)):
|
if (a is stub and b == "__add__" and isinstance(c, numbers.Number)):
|
||||||
return 1.0, c
|
return 1.0, c
|
||||||
except TypeError: pass
|
except TypeError:
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
((a, b, c), d, e) = data # full syntax
|
((a, b, c), d, e) = data # full syntax
|
||||||
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number) and
|
if (a is stub and b == "__mul__" and isinstance(c, numbers.Number) and
|
||||||
d == "__add__" and isinstance(e, numbers.Number)):
|
d == "__add__" and isinstance(e, numbers.Number)):
|
||||||
return c, e
|
return c, e
|
||||||
except TypeError: pass
|
except TypeError:
|
||||||
|
pass
|
||||||
raise ValueError("illegal expression")
|
raise ValueError("illegal expression")
|
||||||
|
|
||||||
|
|
||||||
|
@ -495,11 +509,12 @@ class Image:
|
||||||
new.info[k] = v
|
new.info[k] = v
|
||||||
return new
|
return new
|
||||||
|
|
||||||
_makeself = _new # compatibility
|
_makeself = _new # compatibility
|
||||||
|
|
||||||
# Context Manager Support
|
# Context Manager Support
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
@ -518,14 +533,13 @@ class Image:
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
except Exception as msg:
|
except Exception as msg:
|
||||||
if Image.DEBUG:
|
if Image.DEBUG:
|
||||||
print ("Error closing: %s" %msg)
|
print ("Error closing: %s" % msg)
|
||||||
|
|
||||||
# Instead of simply setting to None, we're setting up a
|
# Instead of simply setting to None, we're setting up a
|
||||||
# deferred error that will better explain that the core image
|
# deferred error that will better explain that the core image
|
||||||
# object is gone.
|
# object is gone.
|
||||||
self.im = deferred_error(ValueError("Operation on closed image"))
|
self.im = deferred_error(ValueError("Operation on closed image"))
|
||||||
|
|
||||||
|
|
||||||
def _copy(self):
|
def _copy(self):
|
||||||
self.load()
|
self.load()
|
||||||
self.im = self.im.copy()
|
self.im = self.im.copy()
|
||||||
|
@ -533,7 +547,8 @@ class Image:
|
||||||
self.readonly = 0
|
self.readonly = 0
|
||||||
|
|
||||||
def _dump(self, file=None, format=None):
|
def _dump(self, file=None, format=None):
|
||||||
import tempfile, os
|
import os
|
||||||
|
import tempfile
|
||||||
suffix = ''
|
suffix = ''
|
||||||
if format:
|
if format:
|
||||||
suffix = '.'+format
|
suffix = '.'+format
|
||||||
|
@ -591,7 +606,7 @@ class Image:
|
||||||
e = _getencoder(self.mode, encoder_name, args)
|
e = _getencoder(self.mode, encoder_name, args)
|
||||||
e.setimage(self.im)
|
e.setimage(self.im)
|
||||||
|
|
||||||
bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
|
bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
while True:
|
while True:
|
||||||
|
@ -663,7 +678,9 @@ class Image:
|
||||||
|
|
||||||
.. deprecated:: 2.0
|
.. deprecated:: 2.0
|
||||||
"""
|
"""
|
||||||
warnings.warn('fromstring() is deprecated. Please call frombytes() instead.', DeprecationWarning)
|
warnings.warn(
|
||||||
|
'fromstring() is deprecated. Please call frombytes() instead.',
|
||||||
|
DeprecationWarning)
|
||||||
return self.frombytes(*args, **kw)
|
return self.frombytes(*args, **kw)
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
|
@ -779,29 +796,30 @@ class Image:
|
||||||
# Use transparent conversion to promote from transparent
|
# Use transparent conversion to promote from transparent
|
||||||
# color to an alpha channel.
|
# color to an alpha channel.
|
||||||
return self._new(self.im.convert_transparent(
|
return self._new(self.im.convert_transparent(
|
||||||
mode, self.info['transparency']))
|
mode, self.info['transparency']))
|
||||||
elif self.mode in ('L', 'RGB', 'P') and mode in ('L', 'RGB', 'P'):
|
elif self.mode in ('L', 'RGB', 'P') and mode in ('L', 'RGB', 'P'):
|
||||||
t = self.info['transparency']
|
t = self.info['transparency']
|
||||||
if isinstance(t, bytes):
|
if isinstance(t, bytes):
|
||||||
# Dragons. This can't be represented by a single color
|
# Dragons. This can't be represented by a single color
|
||||||
warnings.warn('Palette images with Transparency expressed '+
|
warnings.warn('Palette images with Transparency expressed ' +
|
||||||
' in bytes should be converted to RGBA images')
|
' in bytes should be converted to RGBA images')
|
||||||
delete_trns = True
|
delete_trns = True
|
||||||
else:
|
else:
|
||||||
# get the new transparency color.
|
# get the new transparency color.
|
||||||
# use existing conversions
|
# use existing conversions
|
||||||
trns_im = Image()._new(core.new(self.mode, (1,1)))
|
trns_im = Image()._new(core.new(self.mode, (1, 1)))
|
||||||
if self.mode == 'P':
|
if self.mode == 'P':
|
||||||
trns_im.putpalette(self.palette)
|
trns_im.putpalette(self.palette)
|
||||||
trns_im.putpixel((0,0), t)
|
trns_im.putpixel((0, 0), t)
|
||||||
|
|
||||||
if mode in ('L','RGB'):
|
if mode in ('L', 'RGB'):
|
||||||
trns_im = trns_im.convert(mode)
|
trns_im = trns_im.convert(mode)
|
||||||
else:
|
else:
|
||||||
# can't just retrieve the palette number, got to do it
|
# can't just retrieve the palette number, got to do it
|
||||||
# after quantization.
|
# after quantization.
|
||||||
trns_im = trns_im.convert('RGB')
|
trns_im = trns_im.convert('RGB')
|
||||||
trns = trns_im.getpixel((0,0))
|
trns = trns_im.getpixel((0,0))
|
||||||
|
|
||||||
elif self.mode == 'P' and mode == 'RGBA':
|
elif self.mode == 'P' and mode == 'RGBA':
|
||||||
delete_trns = True
|
delete_trns = True
|
||||||
|
|
||||||
|
@ -840,7 +858,7 @@ class Image:
|
||||||
|
|
||||||
new_im = self._new(im)
|
new_im = self._new(im)
|
||||||
if delete_trns:
|
if delete_trns:
|
||||||
#crash fail if we leave a bytes transparency in an rgb/l mode.
|
# crash fail if we leave a bytes transparency in an rgb/l mode.
|
||||||
del(new_im.info['transparency'])
|
del(new_im.info['transparency'])
|
||||||
if trns is not None:
|
if trns is not None:
|
||||||
if new_im.mode == 'P':
|
if new_im.mode == 'P':
|
||||||
|
@ -1020,7 +1038,7 @@ class Image:
|
||||||
return out
|
return out
|
||||||
return self.im.getcolors(maxcolors)
|
return self.im.getcolors(maxcolors)
|
||||||
|
|
||||||
def getdata(self, band = None):
|
def getdata(self, band=None):
|
||||||
"""
|
"""
|
||||||
Returns the contents of this image as a sequence object
|
Returns the contents of this image as a sequence object
|
||||||
containing pixel values. The sequence object is flattened, so
|
containing pixel values. The sequence object is flattened, so
|
||||||
|
@ -1041,7 +1059,7 @@ class Image:
|
||||||
self.load()
|
self.load()
|
||||||
if band is not None:
|
if band is not None:
|
||||||
return self.im.getband(band)
|
return self.im.getband(band)
|
||||||
return self.im # could be abused
|
return self.im # could be abused
|
||||||
|
|
||||||
def getextrema(self):
|
def getextrema(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1071,7 +1089,6 @@ class Image:
|
||||||
self.load()
|
self.load()
|
||||||
return self.im.ptr
|
return self.im.ptr
|
||||||
|
|
||||||
|
|
||||||
def getpalette(self):
|
def getpalette(self):
|
||||||
"""
|
"""
|
||||||
Returns the image palette as a list.
|
Returns the image palette as a list.
|
||||||
|
@ -1087,8 +1104,7 @@ class Image:
|
||||||
else:
|
else:
|
||||||
return list(self.im.getpalette())
|
return list(self.im.getpalette())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None # no palette
|
return None # no palette
|
||||||
|
|
||||||
|
|
||||||
def getpixel(self, xy):
|
def getpixel(self, xy):
|
||||||
"""
|
"""
|
||||||
|
@ -1210,7 +1226,8 @@ class Image:
|
||||||
|
|
||||||
if isImageType(box) and mask is None:
|
if isImageType(box) and mask is None:
|
||||||
# abbreviated paste(im, mask) syntax
|
# abbreviated paste(im, mask) syntax
|
||||||
mask = box; box = None
|
mask = box
|
||||||
|
box = None
|
||||||
|
|
||||||
if box is None:
|
if box is None:
|
||||||
# cover all of self
|
# cover all of self
|
||||||
|
@ -1316,7 +1333,7 @@ class Image:
|
||||||
# do things the hard way
|
# do things the hard way
|
||||||
im = self.im.convert(mode)
|
im = self.im.convert(mode)
|
||||||
if im.mode not in ("LA", "RGBA"):
|
if im.mode not in ("LA", "RGBA"):
|
||||||
raise ValueError # sanity check
|
raise ValueError # sanity check
|
||||||
self.im = im
|
self.im = im
|
||||||
self.pyaccess = None
|
self.pyaccess = None
|
||||||
self.mode = self.im.mode
|
self.mode = self.im.mode
|
||||||
|
@ -1394,7 +1411,7 @@ class Image:
|
||||||
self.mode = "P"
|
self.mode = "P"
|
||||||
self.palette = palette
|
self.palette = palette
|
||||||
self.palette.mode = "RGB"
|
self.palette.mode = "RGB"
|
||||||
self.load() # install new palette
|
self.load() # install new palette
|
||||||
|
|
||||||
def putpixel(self, xy, value):
|
def putpixel(self, xy, value):
|
||||||
"""
|
"""
|
||||||
|
@ -1423,7 +1440,7 @@ class Image:
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
if self.pyaccess:
|
if self.pyaccess:
|
||||||
return self.pyaccess.putpixel(xy,value)
|
return self.pyaccess.putpixel(xy, value)
|
||||||
return self.im.putpixel(xy, value)
|
return self.im.putpixel(xy, value)
|
||||||
|
|
||||||
def resize(self, size, resample=NEAREST):
|
def resize(self, size, resample=NEAREST):
|
||||||
|
@ -1493,6 +1510,7 @@ class Image:
|
||||||
math.cos(angle), math.sin(angle), 0.0,
|
math.cos(angle), math.sin(angle), 0.0,
|
||||||
-math.sin(angle), math.cos(angle), 0.0
|
-math.sin(angle), math.cos(angle), 0.0
|
||||||
]
|
]
|
||||||
|
|
||||||
def transform(x, y, matrix=matrix):
|
def transform(x, y, matrix=matrix):
|
||||||
(a, b, c, d, e, f) = matrix
|
(a, b, c, d, e, f) = matrix
|
||||||
return a*x + b*y + c, d*x + e*y + f
|
return a*x + b*y + c, d*x + e*y + f
|
||||||
|
@ -1580,13 +1598,13 @@ class Image:
|
||||||
try:
|
try:
|
||||||
format = EXTENSION[ext]
|
format = EXTENSION[ext]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeyError(ext) # unknown extension
|
raise KeyError(ext) # unknown extension
|
||||||
|
|
||||||
try:
|
try:
|
||||||
save_handler = SAVE[format.upper()]
|
save_handler = SAVE[format.upper()]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
init()
|
init()
|
||||||
save_handler = SAVE[format.upper()] # unknown format
|
save_handler = SAVE[format.upper()] # unknown format
|
||||||
|
|
||||||
if isPath(fp):
|
if isPath(fp):
|
||||||
fp = builtins.open(fp, "wb")
|
fp = builtins.open(fp, "wb")
|
||||||
|
@ -1668,7 +1686,7 @@ class Image:
|
||||||
"""
|
"""
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def thumbnail(self, size, resample=NEAREST):
|
def thumbnail(self, size, resample=ANTIALIAS):
|
||||||
"""
|
"""
|
||||||
Make this image into a thumbnail. This method modifies the
|
Make this image into a thumbnail. This method modifies the
|
||||||
image to contain a thumbnail version of itself, no larger than
|
image to contain a thumbnail version of itself, no larger than
|
||||||
|
@ -1683,26 +1701,27 @@ class Image:
|
||||||
important than quality.
|
important than quality.
|
||||||
|
|
||||||
Also note that this function modifies the :py:class:`~PIL.Image.Image`
|
Also note that this function modifies the :py:class:`~PIL.Image.Image`
|
||||||
object in place. If you need to use the full resolution image as well, apply
|
object in place. If you need to use the full resolution image as well,
|
||||||
this method to a :py:meth:`~PIL.Image.Image.copy` of the original image.
|
apply this method to a :py:meth:`~PIL.Image.Image.copy` of the original
|
||||||
|
image.
|
||||||
|
|
||||||
:param size: Requested size.
|
:param size: Requested size.
|
||||||
:param resample: Optional resampling filter. This can be one
|
:param resample: Optional resampling filter. This can be one
|
||||||
of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
|
of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`,
|
||||||
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS`
|
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.ANTIALIAS`
|
||||||
(best quality). If omitted, it defaults to
|
(best quality). If omitted, it defaults to
|
||||||
:py:attr:`PIL.Image.NEAREST` (this will be changed to ANTIALIAS in a
|
:py:attr:`PIL.Image.ANTIALIAS`.
|
||||||
future version).
|
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# FIXME: the default resampling filter will be changed
|
|
||||||
# to ANTIALIAS in future versions
|
|
||||||
|
|
||||||
# preserve aspect ratio
|
# preserve aspect ratio
|
||||||
x, y = self.size
|
x, y = self.size
|
||||||
if x > size[0]: y = int(max(y * size[0] / x, 1)); x = int(size[0])
|
if x > size[0]:
|
||||||
if y > size[1]: x = int(max(x * size[1] / y, 1)); y = int(size[1])
|
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
|
size = x, y
|
||||||
|
|
||||||
if size == self.size:
|
if size == self.size:
|
||||||
|
@ -1717,7 +1736,7 @@ class Image:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
if resample != ANTIALIAS:
|
if resample != ANTIALIAS:
|
||||||
raise
|
raise
|
||||||
im = self.resize(size, NEAREST) # fallback
|
im = self.resize(size, NEAREST) # fallback
|
||||||
|
|
||||||
self.im = im.im
|
self.im = im.im
|
||||||
self.mode = im.mode
|
self.mode = im.mode
|
||||||
|
@ -1753,7 +1772,9 @@ class Image:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.mode == 'RGBA':
|
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):
|
if isinstance(method, ImageTransformHandler):
|
||||||
return method.transform(size, self, resample=resample, fill=fill)
|
return method.transform(size, self, resample=resample, fill=fill)
|
||||||
|
@ -1800,8 +1821,13 @@ class Image:
|
||||||
elif method == QUAD:
|
elif method == QUAD:
|
||||||
# quadrilateral warp. data specifies the four corners
|
# quadrilateral warp. data specifies the four corners
|
||||||
# given as NW, SW, SE, and NE.
|
# given as NW, SW, SE, and NE.
|
||||||
nw = data[0:2]; sw = data[2:4]; se = data[4:6]; ne = data[6:8]
|
nw = data[0:2]
|
||||||
x0, y0 = nw; As = 1.0 / w; At = 1.0 / h
|
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,
|
data = (x0, (ne[0]-x0)*As, (sw[0]-x0)*At,
|
||||||
(se[0]-sw[0]-ne[0]+x0)*As*At,
|
(se[0]-sw[0]-ne[0]+x0)*As*At,
|
||||||
y0, (ne[1]-y0)*As, (sw[1]-y0)*At,
|
y0, (ne[1]-y0)*As, (sw[1]-y0)*At,
|
||||||
|
@ -1835,6 +1861,7 @@ class Image:
|
||||||
im = self.im.transpose(method)
|
im = self.im.transpose(method)
|
||||||
return self._new(im)
|
return self._new(im)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Lazy operations
|
# Lazy operations
|
||||||
|
|
||||||
|
@ -1870,6 +1897,7 @@ class _ImageCrop(Image):
|
||||||
# FIXME: future versions should optimize crop/paste
|
# FIXME: future versions should optimize crop/paste
|
||||||
# sequences!
|
# sequences!
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Abstract handlers.
|
# Abstract handlers.
|
||||||
|
|
||||||
|
@ -1877,10 +1905,12 @@ class ImagePointHandler:
|
||||||
# used as a mixin by point transforms (for use with im.point)
|
# used as a mixin by point transforms (for use with im.point)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ImageTransformHandler:
|
class ImageTransformHandler:
|
||||||
# used as a mixin by geometry transforms (for use with im.transform)
|
# used as a mixin by geometry transforms (for use with im.transform)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Factories
|
# Factories
|
||||||
|
|
||||||
|
@ -1956,6 +1986,7 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
|
||||||
im.frombytes(data, decoder_name, args)
|
im.frombytes(data, decoder_name, args)
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
def fromstring(*args, **kw):
|
def fromstring(*args, **kw):
|
||||||
"""Deprecated alias to frombytes.
|
"""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)",
|
" frombuffer(mode, size, data, 'raw', mode, 0, 1)",
|
||||||
RuntimeWarning, stacklevel=2
|
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:
|
if args[0] in _MAPMODES:
|
||||||
im = new(mode, (1,1))
|
im = new(mode, (1, 1))
|
||||||
im = im._new(
|
im = im._new(
|
||||||
core.map_buffer(data, size, decoder_name, None, 0, args)
|
core.map_buffer(data, size, decoder_name, None, 0, args)
|
||||||
)
|
)
|
||||||
|
@ -2140,8 +2171,8 @@ def open(fp, mode="r"):
|
||||||
fp.seek(0)
|
fp.seek(0)
|
||||||
return factory(fp, filename)
|
return factory(fp, filename)
|
||||||
except (SyntaxError, IndexError, TypeError):
|
except (SyntaxError, IndexError, TypeError):
|
||||||
#import traceback
|
# import traceback
|
||||||
#traceback.print_exc()
|
# traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if init():
|
if init():
|
||||||
|
@ -2153,13 +2184,14 @@ def open(fp, mode="r"):
|
||||||
fp.seek(0)
|
fp.seek(0)
|
||||||
return factory(fp, filename)
|
return factory(fp, filename)
|
||||||
except (SyntaxError, IndexError, TypeError):
|
except (SyntaxError, IndexError, TypeError):
|
||||||
#import traceback
|
# import traceback
|
||||||
#traceback.print_exc()
|
# traceback.print_exc()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise IOError("cannot identify image file %r"
|
raise IOError("cannot identify image file %r"
|
||||||
% (filename if filename else fp))
|
% (filename if filename else fp))
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Image processing.
|
# Image processing.
|
||||||
|
|
||||||
|
@ -2258,6 +2290,7 @@ def merge(mode, bands):
|
||||||
im.putband(bands[i].im, i)
|
im.putband(bands[i].im, i)
|
||||||
return bands[0]._new(im)
|
return bands[0]._new(im)
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Plugin registry
|
# Plugin registry
|
||||||
|
|
||||||
|
@ -2316,6 +2349,7 @@ def _show(image, **options):
|
||||||
# override me, as necessary
|
# override me, as necessary
|
||||||
_showxv(image, **options)
|
_showxv(image, **options)
|
||||||
|
|
||||||
|
|
||||||
def _showxv(image, title=None, **options):
|
def _showxv(image, title=None, **options):
|
||||||
from PIL import ImageShow
|
from PIL import ImageShow
|
||||||
ImageShow.show(image, title, **options)
|
ImageShow.show(image, title, **options)
|
||||||
|
|
|
@ -46,9 +46,11 @@ def _obj(fp, obj, **dict):
|
||||||
fp.write("/%s %s\n" % (k, v))
|
fp.write("/%s %s\n" % (k, v))
|
||||||
fp.write(">>\n")
|
fp.write(">>\n")
|
||||||
|
|
||||||
|
|
||||||
def _endobj(fp):
|
def _endobj(fp):
|
||||||
fp.write("endobj\n")
|
fp.write("endobj\n")
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# (Internal) Image save plugin for the PDF format.
|
# (Internal) Image save plugin for the PDF format.
|
||||||
|
|
||||||
|
@ -59,13 +61,15 @@ def _save(im, fp, filename):
|
||||||
# make sure image data is available
|
# make sure image data is available
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
xref = [0]*(5+1) # placeholders
|
xref = [0]*(5+1) # placeholders
|
||||||
|
|
||||||
class TextWriter:
|
class TextWriter:
|
||||||
def __init__(self, fp):
|
def __init__(self, fp):
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
return getattr(self.fp, name)
|
return getattr(self.fp, name)
|
||||||
|
|
||||||
def write(self, value):
|
def write(self, value):
|
||||||
self.fp.write(value.encode('latin-1'))
|
self.fp.write(value.encode('latin-1'))
|
||||||
|
|
||||||
|
@ -89,13 +93,13 @@ def _save(im, fp, filename):
|
||||||
if im.mode == "1":
|
if im.mode == "1":
|
||||||
filter = "/ASCIIHexDecode"
|
filter = "/ASCIIHexDecode"
|
||||||
colorspace = "/DeviceGray"
|
colorspace = "/DeviceGray"
|
||||||
procset = "/ImageB" # grayscale
|
procset = "/ImageB" # grayscale
|
||||||
bits = 1
|
bits = 1
|
||||||
elif im.mode == "L":
|
elif im.mode == "L":
|
||||||
filter = "/DCTDecode"
|
filter = "/DCTDecode"
|
||||||
# params = "<< /Predictor 15 /Columns %d >>" % (width-2)
|
# params = "<< /Predictor 15 /Columns %d >>" % (width-2)
|
||||||
colorspace = "/DeviceGray"
|
colorspace = "/DeviceGray"
|
||||||
procset = "/ImageB" # grayscale
|
procset = "/ImageB" # grayscale
|
||||||
elif im.mode == "P":
|
elif im.mode == "P":
|
||||||
filter = "/ASCIIHexDecode"
|
filter = "/ASCIIHexDecode"
|
||||||
colorspace = "[ /Indexed /DeviceRGB 255 <"
|
colorspace = "[ /Indexed /DeviceRGB 255 <"
|
||||||
|
@ -105,16 +109,16 @@ def _save(im, fp, filename):
|
||||||
g = i8(palette[i*3+1])
|
g = i8(palette[i*3+1])
|
||||||
b = i8(palette[i*3+2])
|
b = i8(palette[i*3+2])
|
||||||
colorspace = colorspace + "%02x%02x%02x " % (r, g, b)
|
colorspace = colorspace + "%02x%02x%02x " % (r, g, b)
|
||||||
colorspace = colorspace + b"> ]"
|
colorspace = colorspace + "> ]"
|
||||||
procset = "/ImageI" # indexed color
|
procset = "/ImageI" # indexed color
|
||||||
elif im.mode == "RGB":
|
elif im.mode == "RGB":
|
||||||
filter = "/DCTDecode"
|
filter = "/DCTDecode"
|
||||||
colorspace = "/DeviceRGB"
|
colorspace = "/DeviceRGB"
|
||||||
procset = "/ImageC" # color images
|
procset = "/ImageC" # color images
|
||||||
elif im.mode == "CMYK":
|
elif im.mode == "CMYK":
|
||||||
filter = "/DCTDecode"
|
filter = "/DCTDecode"
|
||||||
colorspace = "/DeviceCMYK"
|
colorspace = "/DeviceCMYK"
|
||||||
procset = "/ImageC" # color images
|
procset = "/ImageC" # color images
|
||||||
else:
|
else:
|
||||||
raise ValueError("cannot save mode %s" % im.mode)
|
raise ValueError("cannot save mode %s" % im.mode)
|
||||||
|
|
||||||
|
@ -122,17 +126,21 @@ def _save(im, fp, filename):
|
||||||
# catalogue
|
# catalogue
|
||||||
|
|
||||||
xref[1] = fp.tell()
|
xref[1] = fp.tell()
|
||||||
_obj(fp, 1, Type = "/Catalog",
|
_obj(
|
||||||
Pages = "2 0 R")
|
fp, 1,
|
||||||
|
Type="/Catalog",
|
||||||
|
Pages="2 0 R")
|
||||||
_endobj(fp)
|
_endobj(fp)
|
||||||
|
|
||||||
#
|
#
|
||||||
# pages
|
# pages
|
||||||
|
|
||||||
xref[2] = fp.tell()
|
xref[2] = fp.tell()
|
||||||
_obj(fp, 2, Type = "/Pages",
|
_obj(
|
||||||
Count = 1,
|
fp, 2,
|
||||||
Kids = "[4 0 R]")
|
Type="/Pages",
|
||||||
|
Count=1,
|
||||||
|
Kids="[4 0 R]")
|
||||||
_endobj(fp)
|
_endobj(fp)
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -144,29 +152,31 @@ def _save(im, fp, filename):
|
||||||
if bits == 1:
|
if bits == 1:
|
||||||
# FIXME: the hex encoder doesn't support packed 1-bit
|
# FIXME: the hex encoder doesn't support packed 1-bit
|
||||||
# images; do things the hard way...
|
# 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 = Image.new("L", (len(data), 1), None)
|
||||||
im.putdata(data)
|
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":
|
elif filter == "/DCTDecode":
|
||||||
Image.SAVE["JPEG"](im, op, filename)
|
Image.SAVE["JPEG"](im, op, filename)
|
||||||
elif filter == "/FlateDecode":
|
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":
|
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:
|
else:
|
||||||
raise ValueError("unsupported PDF filter (%s)" % filter)
|
raise ValueError("unsupported PDF filter (%s)" % filter)
|
||||||
|
|
||||||
xref[3] = fp.tell()
|
xref[3] = fp.tell()
|
||||||
_obj(fp, 3, Type = "/XObject",
|
_obj(
|
||||||
Subtype = "/Image",
|
fp, 3,
|
||||||
Width = width, # * 72.0 / resolution,
|
Type="/XObject",
|
||||||
Height = height, # * 72.0 / resolution,
|
Subtype="/Image",
|
||||||
Length = len(op.getvalue()),
|
Width=width, # * 72.0 / resolution,
|
||||||
Filter = filter,
|
Height=height, # * 72.0 / resolution,
|
||||||
BitsPerComponent = bits,
|
Length=len(op.getvalue()),
|
||||||
DecodeParams = params,
|
Filter=filter,
|
||||||
ColorSpace = colorspace)
|
BitsPerComponent=bits,
|
||||||
|
DecodeParams=params,
|
||||||
|
ColorSpace=colorspace)
|
||||||
|
|
||||||
fp.write("stream\n")
|
fp.write("stream\n")
|
||||||
fp.fp.write(op.getvalue())
|
fp.fp.write(op.getvalue())
|
||||||
|
@ -179,11 +189,14 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
xref[4] = fp.tell()
|
xref[4] = fp.tell()
|
||||||
_obj(fp, 4)
|
_obj(fp, 4)
|
||||||
fp.write("<<\n/Type /Page\n/Parent 2 0 R\n"\
|
fp.write(
|
||||||
"/Resources <<\n/ProcSet [ /PDF %s ]\n"\
|
"<<\n/Type /Page\n/Parent 2 0 R\n"
|
||||||
"/XObject << /image 3 0 R >>\n>>\n"\
|
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
|
||||||
"/MediaBox [ 0 0 %d %d ]\n/Contents 5 0 R\n>>\n" %\
|
"/XObject << /image 3 0 R >>\n>>\n"
|
||||||
(procset, int(width * 72.0 /resolution) , int(height * 72.0 / resolution)))
|
"/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)
|
_endobj(fp)
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -191,10 +204,13 @@ def _save(im, fp, filename):
|
||||||
|
|
||||||
op = TextWriter(io.BytesIO())
|
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()
|
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.write("stream\n")
|
||||||
fp.fp.write(op.fp.getvalue())
|
fp.fp.write(op.fp.getvalue())
|
||||||
|
|
58
Tests/test_file_pdf.py
Normal file
58
Tests/test_file_pdf.py
Normal file
|
@ -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
|
Loading…
Reference in New Issue
Block a user