This commit is contained in:
hugovk 2014-04-22 09:23:34 +03:00
parent adfbe8323a
commit a3edb45f08

View File

@ -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,8 +53,8 @@ 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:
core = _imaging_not_installed() core = _imaging_not_installed()
@ -94,7 +95,8 @@ from PIL import ImageMode
from PIL._binary import i8, o8 from PIL._binary import i8, o8
from PIL._util import isPath, isStringType, deferred_error from PIL._util import isPath, isStringType, deferred_error
import os, sys import os
import sys
# type stuff # type stuff
import collections import collections
@ -104,9 +106,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 +151,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 +225,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 +235,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 +251,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 +372,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 +383,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 +403,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 +427,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 +507,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 +531,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 +545,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 tempfile
import os
suffix = '' suffix = ''
if format: if format:
suffix = '.'+format suffix = '.'+format
@ -600,7 +613,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:
@ -637,9 +650,11 @@ class Image:
if self.mode != "1": if self.mode != "1":
raise ValueError("not a bitmap") raise ValueError("not a bitmap")
data = self.tobytes("xbm") data = self.tobytes("xbm")
return b"".join([("#define %s_width %d\n" % (name, self.size[0])).encode('ascii'), return b"".join([
("#define %s_height %d\n"% (name, self.size[1])).encode('ascii'), ("#define %s_width %d\n" % (name, self.size[0])).encode('ascii'),
("static char %s_bits[] = {\n" % name).encode('ascii'), data, b"};"]) ("#define %s_height %d\n" % (name, self.size[1])).encode('ascii'),
("static char %s_bits[] = {\n" % name).encode('ascii'), data, b"};"
])
def frombytes(self, data, decoder_name="raw", *args): def frombytes(self, data, decoder_name="raw", *args):
""" """
@ -672,7 +687,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):
@ -783,35 +800,36 @@ class Image:
trns = None trns = None
delete_trns = False delete_trns = False
# transparency handling # transparency handling
if "transparency" in self.info and self.info['transparency'] is not None: if "transparency" in self.info and \
self.info['transparency'] is not None:
if self.mode in ('L', 'RGB') and mode == 'RGBA': if self.mode in ('L', 'RGB') and mode == 'RGBA':
# 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(
' in bytes should be converted to RGBA images') 'Palette images with Transparency expressed ' +
' 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))
if mode == "P" and palette == ADAPTIVE: if mode == "P" and palette == ADAPTIVE:
im = self.im.quantize(colors) im = self.im.quantize(colors)
@ -829,7 +847,8 @@ class Image:
# if we can't make a transparent color, don't leave the old # if we can't make a transparent color, don't leave the old
# transparency hanging around to mess us up. # transparency hanging around to mess us up.
del(new.info['transparency']) del(new.info['transparency'])
warnings.warn("Couldn't allocate palette entry for transparency") warnings.warn("Couldn't allocate palette entry " +
"for transparency")
return new return new
# colorspace conversion # colorspace conversion
@ -856,7 +875,8 @@ class Image:
new_im.info['transparency'] = new_im.palette.getcolor(trns) new_im.info['transparency'] = new_im.palette.getcolor(trns)
except: except:
del(new_im.info['transparency']) del(new_im.info['transparency'])
warnings.warn("Couldn't allocate palette entry for transparency") warnings.warn("Couldn't allocate palette entry " +
"for transparency")
else: else:
new_im.info['transparency'] = trns new_im.info['transparency'] = trns
return new_im return new_im
@ -972,7 +992,8 @@ class Image:
if isinstance(filter, collections.Callable): if isinstance(filter, collections.Callable):
filter = filter() filter = filter()
if not hasattr(filter, "filter"): if not hasattr(filter, "filter"):
raise TypeError("filter argument should be ImageFilter.Filter instance or class") raise TypeError("filter argument should be ImageFilter.Filter " +
"instance or class")
if self.im.bands == 1: if self.im.bands == 1:
return self._new(filter.filter(self.im)) return self._new(filter.filter(self.im))
@ -1028,7 +1049,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
@ -1049,7 +1070,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):
""" """
@ -1079,7 +1100,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.
@ -1095,8 +1115,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):
""" """
@ -1218,7 +1237,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
@ -1324,7 +1344,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
@ -1402,7 +1422,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):
""" """
@ -1431,7 +1451,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):
@ -1498,9 +1518,10 @@ class Image:
import math import math
angle = -angle * math.pi / 180 angle = -angle * math.pi / 180
matrix = [ matrix = [
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
@ -1588,13 +1609,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")
@ -1691,8 +1712,9 @@ 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
@ -1709,8 +1731,12 @@ class Image:
# 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:
@ -1725,7 +1751,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
@ -1761,7 +1787,8 @@ 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)
@ -1808,8 +1835,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,
@ -1843,6 +1875,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
@ -1878,6 +1911,7 @@ class _ImageCrop(Image):
# FIXME: future versions should optimize crop/paste # FIXME: future versions should optimize crop/paste
# sequences! # sequences!
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Abstract handlers. # Abstract handlers.
@ -1885,10 +1919,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
@ -1964,6 +2000,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.
@ -2026,9 +2063,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)
) )
@ -2168,6 +2205,7 @@ def open(fp, mode="r"):
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.
@ -2266,6 +2304,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
@ -2324,6 +2363,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)