Merge remote-tracking branch 'upstream/master' into docstring

Conflicts:
	PIL/ImageCms.py
This commit is contained in:
hugovk 2014-06-02 11:04:29 +03:00
commit 4c2b3a6afc
59 changed files with 1307 additions and 469 deletions

11
.coveragerc Normal file
View File

@ -0,0 +1,11 @@
# .coveragerc to control coverage.py
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma:
pragma: no cover
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:

View File

@ -1,18 +1,15 @@
language: python language: python
# for python-qt4
virtualenv:
system_site_packages: true
notifications: notifications:
irc: "chat.freenode.net#pil" irc: "chat.freenode.net#pil"
python: python:
- "pypy"
- 2.6 - 2.6
- 2.7 - 2.7
- 3.2 - 3.2
- 3.3 - 3.3
- "pypy" - 3.4
install: install:
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake"
@ -29,8 +26,14 @@ script:
- coverage erase - coverage erase
- python setup.py clean - python setup.py clean
- python setup.py build_ext --inplace - python setup.py build_ext --inplace
- coverage run --append --include=PIL/* selftest.py
- python Tests/run.py --coverage # Don't cover PyPy: it fails intermittently and is x5.8 slower (#640)
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python selftest.py; fi
- if [ "$TRAVIS_PYTHON_VERSION" == "pypy" ]; then python Tests/run.py; fi
# Cover the others
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then coverage run --append --include=PIL/* selftest.py; fi
- if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then python Tests/run.py --coverage; fi
after_success: after_success:
- coverage report - coverage report
@ -38,7 +41,5 @@ after_success:
- pip install pep8 pyflakes - pip install pep8 pyflakes
- pep8 PIL/*.py - pep8 PIL/*.py
- pyflakes PIL/*.py - pyflakes PIL/*.py
- pep8 Tests/*.py
matrix: - pyflakes Tests/*.py
allow_failures:
- python: "pypy"

View File

@ -3,6 +3,56 @@ Changelog (Pillow)
2.5.0 (unreleased) 2.5.0 (unreleased)
------------------ ------------------
- Added more ImageDraw tests
[hugovk]
- Added tests for Spider files
[hugovk]
- Use libtiff to write any compressed tiff files
[wiredfool]
- Support for pickling Image objects
[hugovk]
- Fixed resolution handling for EPS thumbnails
[eliempje]
- Fixed rendering of some binary EPS files (Issue #302)
[eliempje]
- Rename variables not to use built-in function names
[hugovk]
- Ignore junk JPEG markers
[hugovk]
- Change default interpolation for Image.thumbnail to Image.ANTIALIAS
[hugovk]
- Add tests and fixes for saving PDFs
[hugovk]
- Remove transparency resource after P->RGBA conversion
[hugovk]
- Clean up preprocessor cruft for Windows
[CounterPillow]
- Adjust Homebrew freetype detection logic
[jacknagel]
- Added Image.close, context manager support.
[wiredfool]
- Added support for 16 bit PGM files.
[wiredfool]
- Updated OleFileIO to version 0.30 from upstream
[hugovk]
- Added support for additional TIFF floating point format
[Hijackal]
- Have the tempfile use a suffix with a dot - Have the tempfile use a suffix with a dot
[wiredfool] [wiredfool]

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
@ -77,8 +78,9 @@ def Ghostscript(tile, size, fp, scale=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
@ -87,20 +89,29 @@ def Ghostscript(tile, size, fp, scale=1):
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
@ -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,24 +195,32 @@ 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

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):
@ -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 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):
""" """
@ -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,14 +429,18 @@ 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
@ -438,13 +450,15 @@ def _getscaleoffset(expr):
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")
@ -497,6 +511,35 @@ class Image:
_makeself = _new # compatibility _makeself = _new # compatibility
# Context Manager Support
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def close(self):
"""
Closes the file pointer, if possible.
This operation will destroy the image core and release it's memory.
The image data will be unusable afterward.
This function is only required to close images that have not
had their file read and closed by the
:py:meth:`~PIL.Image.Image.load` method.
"""
try:
self.fp.close()
except Exception as msg:
if Image.DEBUG:
print ("Error closing: %s" % msg)
# Instead of simply setting to None, we're setting up a
# deferred error that will better explain that the core image
# object is gone.
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()
@ -504,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
@ -521,6 +565,20 @@ class Image:
self.save(file, format) self.save(file, format)
return file return file
def __eq__(self, other):
a = (self.mode == other.mode)
b = (self.size == other.size)
c = (self.getpalette() == other.getpalette())
d = (self.info == other.info)
e = (self.category == other.category)
f = (self.readonly == other.readonly)
g = (self.tobytes() == other.tobytes())
return a and b and c and d and e and f and g
def __ne__(self, other):
eq = (self == other)
return not eq
def __repr__(self): def __repr__(self):
return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % ( return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % (
self.__class__.__module__, self.__class__.__name__, self.__class__.__module__, self.__class__.__name__,
@ -539,6 +597,26 @@ class Image:
return new return new
raise AttributeError(name) raise AttributeError(name)
def __getstate__(self):
return [
self.info,
self.mode,
self.size,
self.getpalette(),
self.tobytes()]
def __setstate__(self, state):
Image.__init__(self)
self.tile = []
info, mode, size, palette, data = state
self.info = info
self.mode = mode
self.size = size
self.im = core.new(mode, size)
if mode in ("L", "P"):
self.putpalette(palette)
self.frombytes(data)
def tobytes(self, encoder_name="raw", *args): def tobytes(self, encoder_name="raw", *args):
""" """
Return image as a bytes object Return image as a bytes object
@ -599,9 +677,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):
""" """
@ -634,7 +714,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):
@ -642,7 +724,8 @@ class Image:
Allocates storage for the image and loads the pixel data. In Allocates storage for the image and loads the pixel data. In
normal cases, you don't need to call this method, since the normal cases, you don't need to call this method, since the
Image class automatically loads an opened image when it is Image class automatically loads an opened image when it is
accessed for the first time. accessed for the first time. This method will close the file
associated with the image.
:returns: An image access object. :returns: An image access object.
""" """
@ -744,7 +827,8 @@ 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.
@ -754,18 +838,18 @@ class Image:
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
@ -773,6 +857,8 @@ class Image:
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':
delete_trns = True
if mode == "P" and palette == ADAPTIVE: if mode == "P" and palette == ADAPTIVE:
im = self.im.quantize(colors) im = self.im.quantize(colors)
@ -790,7 +876,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
@ -809,7 +896,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':
@ -817,7 +904,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
@ -933,7 +1021,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))
@ -989,7 +1078,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
@ -1040,7 +1129,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.
@ -1058,7 +1146,6 @@ class Image:
except ValueError: except ValueError:
return None # no palette return None # no palette
def getpixel(self, xy): def getpixel(self, xy):
""" """
Returns the pixel value at a given position. Returns the pixel value at a given position.
@ -1179,7 +1266,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
@ -1392,7 +1480,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):
@ -1462,6 +1550,8 @@ 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
@ -1637,7 +1727,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
@ -1652,26 +1742,28 @@ 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`. (was :py:attr:`PIL.Image.NEAREST`
future version). prior to version 2.5.0)
: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:
@ -1722,7 +1814,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)
@ -1769,8 +1862,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,
@ -1804,6 +1902,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
@ -1839,6 +1938,7 @@ class _ImageCrop(Image):
# FIXME: future versions should optimize crop/paste # FIXME: future versions should optimize crop/paste
# sequences! # sequences!
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Abstract handlers. # Abstract handlers.
@ -1846,10 +1946,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
@ -1925,6 +2027,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.
@ -1989,7 +2092,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args):
) )
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)
) )
@ -2074,10 +2177,11 @@ def open(fp, mode="r"):
""" """
Opens and identifies the given image file. Opens and identifies the given image file.
This is a lazy operation; this function identifies the file, but the This is a lazy operation; this function identifies the file, but
actual image data is not read from the file until you try to process the file remains open and the actual image data is not read from
the data (or call the :py:meth:`~PIL.Image.Image.load` method). the file until you try to process the data (or call the
See :py:func:`~PIL.Image.new`. :py:meth:`~PIL.Image.Image.load` method). See
:py:func:`~PIL.Image.new`.
:param file: A filename (string) or a file object. The file object :param file: A filename (string) or a file object. The file object
must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and
@ -2108,8 +2212,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():
@ -2121,13 +2225,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.
@ -2226,6 +2331,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
@ -2284,6 +2390,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)

View File

@ -34,7 +34,8 @@
__version__ = "0.6" __version__ = "0.6"
import array, struct import array
import struct
from PIL import Image, ImageFile, _binary from PIL import Image, ImageFile, _binary
from PIL.JpegPresets import presets from PIL.JpegPresets import presets
from PIL._util import isStringType from PIL._util import isStringType
@ -44,6 +45,7 @@ o8 = _binary.o8
i16 = _binary.i16be i16 = _binary.i16be
i32 = _binary.i32be i32 = _binary.i32be
# #
# Parser # Parser
@ -51,6 +53,7 @@ def Skip(self, marker):
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2))-2
ImageFile._safe_read(self.fp, n) ImageFile._safe_read(self.fp, n)
def APP(self, marker): def APP(self, marker):
# #
# Application marker. Store these in the APP dictionary. # Application marker. Store these in the APP dictionary.
@ -59,7 +62,7 @@ def APP(self, marker):
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2))-2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
app = "APP%d" % (marker&15) app = "APP%d" % (marker & 15)
self.app[app] = s # compatibility self.app[app] = s # compatibility
self.applist.append((app, s)) self.applist.append((app, s))
@ -108,16 +111,17 @@ def APP(self, marker):
else: else:
self.info["adobe_transform"] = adobe_transform self.info["adobe_transform"] = adobe_transform
def COM(self, marker): def COM(self, marker):
# #
# Comment marker. Store these in the APP dictionary. # Comment marker. Store these in the APP dictionary.
n = i16(self.fp.read(2))-2 n = i16(self.fp.read(2))-2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
self.app["COM"] = s # compatibility self.app["COM"] = s # compatibility
self.applist.append(("COM", s)) self.applist.append(("COM", s))
def SOF(self, marker): def SOF(self, marker):
# #
# Start of frame marker. Defines the size and mode of the # Start of frame marker. Defines the size and mode of the
@ -163,7 +167,8 @@ def SOF(self, marker):
for i in range(6, len(s), 3): for i in range(6, len(s), 3):
t = s[i:i+3] t = s[i:i+3]
# 4-tuples: id, vsamp, hsamp, qtable # 4-tuples: id, vsamp, hsamp, qtable
self.layer.append((t[0], i8(t[1])//16, i8(t[1])&15, i8(t[2]))) self.layer.append((t[0], i8(t[1])//16, i8(t[1]) & 15, i8(t[2])))
def DQT(self, marker): def DQT(self, marker):
# #
@ -181,7 +186,7 @@ def DQT(self, marker):
raise SyntaxError("bad quantization table marker") raise SyntaxError("bad quantization table marker")
v = i8(s[0]) v = i8(s[0])
if v//16 == 0: if v//16 == 0:
self.quantization[v&15] = array.array("b", s[1:65]) self.quantization[v & 15] = array.array("b", s[1:65])
s = s[65:] s = s[65:]
else: else:
return # FIXME: add code to read 16-bit tables! return # FIXME: add code to read 16-bit tables!
@ -261,6 +266,7 @@ MARKER = {
def _accept(prefix): def _accept(prefix):
return prefix[0:1] == b"\377" return prefix[0:1] == b"\377"
## ##
# Image plugin for JPEG and JFIF images. # Image plugin for JPEG and JFIF images.
@ -290,9 +296,14 @@ class JpegImageFile(ImageFile.ImageFile):
while True: while True:
i = i8(s)
if i == 0xFF:
s = s + self.fp.read(1) s = s + self.fp.read(1)
i = i16(s) i = i16(s)
else:
# Skip non-0xFF junk
s = b"\xff"
continue
if i in MARKER: if i in MARKER:
name, description, handler = MARKER[i] name, description, handler = MARKER[i]
@ -303,13 +314,13 @@ class JpegImageFile(ImageFile.ImageFile):
rawmode = self.mode rawmode = self.mode
if self.mode == "CMYK": if self.mode == "CMYK":
rawmode = "CMYK;I" # assume adobe conventions rawmode = "CMYK;I" # assume adobe conventions
self.tile = [("jpeg", (0,0) + self.size, 0, (rawmode, ""))] self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))]
# self.__offset = self.fp.tell() # self.__offset = self.fp.tell()
break break
s = self.fp.read(1) s = self.fp.read(1)
elif i == 0 or i == 65535: elif i == 0 or i == 0xFFFF:
# padded marker or junk; move on # padded marker or junk; move on
s = "\xff" s = b"\xff"
else: else:
raise SyntaxError("no marker found") raise SyntaxError("no marker found")
@ -343,7 +354,8 @@ class JpegImageFile(ImageFile.ImageFile):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities # ALTERNATIVE: handle JPEGs via the IJG command line utilities
import tempfile, os import tempfile
import os
f, path = tempfile.mkstemp() f, path = tempfile.mkstemp()
os.close(f) os.close(f)
if os.path.exists(self.filename): if os.path.exists(self.filename):
@ -354,8 +366,10 @@ class JpegImageFile(ImageFile.ImageFile):
try: try:
self.im = Image.core.open_ppm(path) self.im = Image.core.open_ppm(path)
finally: finally:
try: os.unlink(path) try:
except: pass os.unlink(path)
except:
pass
self.mode = self.im.mode self.mode = self.im.mode
self.size = self.im.size self.size = self.im.size
@ -372,6 +386,7 @@ def _getexif(self):
# version. # version.
from PIL import TiffImagePlugin from PIL import TiffImagePlugin
import io import io
def fixup(value): def fixup(value):
if len(value) == 1: if len(value) == 1:
return value[0] return value[0]
@ -441,16 +456,19 @@ samplings = {
(2, 2, 1, 1, 1, 1): 2, (2, 2, 1, 1, 1, 1): 2,
} }
def convert_dict_qtables(qtables): def convert_dict_qtables(qtables):
qtables = [qtables[key] for key in range(len(qtables)) if key in qtables] qtables = [qtables[key] for key in range(len(qtables)) if key in qtables]
for idx, table in enumerate(qtables): for idx, table in enumerate(qtables):
qtables[idx] = [table[i] for i in zigzag_index] qtables[idx] = [table[i] for i in zigzag_index]
return qtables return qtables
def get_sampling(im): def get_sampling(im):
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
return samplings.get(sampling, -1) return samplings.get(sampling, -1)
def _save(im, fp, filename): def _save(im, fp, filename):
try: try:
@ -563,12 +581,11 @@ def _save(im, fp, filename):
info.get("exif", b"") info.get("exif", b"")
) )
# if we optimize, libjpeg needs a buffer big enough to hold the whole image
# if we optimize, libjpeg needs a buffer big enough to hold the whole image in a shot. # in a shot. Guessing on the size, at im.size bytes. (raw pizel size is
# Guessing on the size, at im.size bytes. (raw pizel size is channels*size, this # channels*size, this is a value that's been used in a django patch.
# is a value that's been used in a django patch.
# https://github.com/jdriscoll/django-imagekit/issues/50 # https://github.com/jdriscoll/django-imagekit/issues/50
bufsize=0 bufsize = 0
if "optimize" in info or "progressive" in info or "progression" in info: if "optimize" in info or "progressive" in info or "progression" in info:
if quality >= 95: if quality >= 95:
bufsize = 2 * im.size[0] * im.size[1] bufsize = 2 * im.size[0] * im.size[1]
@ -577,17 +594,20 @@ def _save(im, fp, filename):
# The exif info needs to be written as one block, + APP1, + one spare byte. # The exif info needs to be written as one block, + APP1, + one spare byte.
# Ensure that our buffer is big enough # Ensure that our buffer is big enough
bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif",b"")) + 5 ) bufsize = max(ImageFile.MAXBLOCK, bufsize, len(info.get("exif", b"")) + 5)
ImageFile._save(im, fp, [("jpeg", (0, 0)+im.size, 0, rawmode)], bufsize)
ImageFile._save(im, fp, [("jpeg", (0,0)+im.size, 0, rawmode)], bufsize)
def _save_cjpeg(im, fp, filename): def _save_cjpeg(im, fp, filename):
# ALTERNATIVE: handle JPEGs via the IJG command line utilities. # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
import os import os
file = im._dump() file = im._dump()
os.system("cjpeg %s >%s" % (file, filename)) os.system("cjpeg %s >%s" % (file, filename))
try: os.unlink(file) try:
except: pass os.unlink(file)
except:
pass
# -------------------------------------------------------------------q- # -------------------------------------------------------------------q-
# Registry stuff # Registry stuff

View File

@ -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.
@ -64,8 +66,10 @@ def _save(im, fp, filename):
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'))
@ -105,7 +109,7 @@ 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"
@ -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())

View File

@ -89,33 +89,33 @@ class ChunkStream:
"Fetch a new chunk. Returns header information." "Fetch a new chunk. Returns header information."
if self.queue: if self.queue:
cid, pos, len = self.queue[-1] cid, pos, length = self.queue[-1]
del self.queue[-1] del self.queue[-1]
self.fp.seek(pos) self.fp.seek(pos)
else: else:
s = self.fp.read(8) s = self.fp.read(8)
cid = s[4:] cid = s[4:]
pos = self.fp.tell() pos = self.fp.tell()
len = i32(s) length = i32(s)
if not is_cid(cid): if not is_cid(cid):
raise SyntaxError("broken PNG file (chunk %s)" % repr(cid)) raise SyntaxError("broken PNG file (chunk %s)" % repr(cid))
return cid, pos, len return cid, pos, length
def close(self): def close(self):
self.queue = self.crc = self.fp = None self.queue = self.crc = self.fp = None
def push(self, cid, pos, len): def push(self, cid, pos, length):
self.queue.append((cid, pos, len)) self.queue.append((cid, pos, length))
def call(self, cid, pos, len): def call(self, cid, pos, length):
"Call the appropriate chunk handler" "Call the appropriate chunk handler"
if Image.DEBUG: if Image.DEBUG:
print("STREAM", cid, pos, len) print("STREAM", cid, pos, length)
return getattr(self, "chunk_" + cid.decode('ascii'))(pos, len) return getattr(self, "chunk_" + cid.decode('ascii'))(pos, length)
def crc(self, cid, data): def crc(self, cid, data):
"Read and verify checksum" "Read and verify checksum"
@ -139,10 +139,10 @@ class ChunkStream:
cids = [] cids = []
while True: while True:
cid, pos, len = self.read() cid, pos, length = self.read()
if cid == endchunk: if cid == endchunk:
break break
self.crc(cid, ImageFile._safe_read(self.fp, len)) self.crc(cid, ImageFile._safe_read(self.fp, length))
cids.append(cid) cids.append(cid)
return cids return cids
@ -190,10 +190,10 @@ class PngStream(ChunkStream):
self.im_tile = None self.im_tile = None
self.im_palette = None self.im_palette = None
def chunk_iCCP(self, pos, len): def chunk_iCCP(self, pos, length):
# ICC profile # ICC profile
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
# according to PNG spec, the iCCP chunk contains: # according to PNG spec, the iCCP chunk contains:
# Profile name 1-79 bytes (character string) # Profile name 1-79 bytes (character string)
# Null separator 1 byte (null character) # Null separator 1 byte (null character)
@ -213,10 +213,10 @@ class PngStream(ChunkStream):
self.im_info["icc_profile"] = icc_profile self.im_info["icc_profile"] = icc_profile
return s return s
def chunk_IHDR(self, pos, len): def chunk_IHDR(self, pos, length):
# image header # image header
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
self.im_size = i32(s), i32(s[4:]) self.im_size = i32(s), i32(s[4:])
try: try:
self.im_mode, self.im_rawmode = _MODES[(i8(s[8]), i8(s[9]))] self.im_mode, self.im_rawmode = _MODES[(i8(s[8]), i8(s[9]))]
@ -228,30 +228,30 @@ class PngStream(ChunkStream):
raise SyntaxError("unknown filter category") raise SyntaxError("unknown filter category")
return s return s
def chunk_IDAT(self, pos, len): def chunk_IDAT(self, pos, length):
# image data # image data
self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)] self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)]
self.im_idat = len self.im_idat = length
raise EOFError raise EOFError
def chunk_IEND(self, pos, len): def chunk_IEND(self, pos, length):
# end of PNG image # end of PNG image
raise EOFError raise EOFError
def chunk_PLTE(self, pos, len): def chunk_PLTE(self, pos, length):
# palette # palette
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P": if self.im_mode == "P":
self.im_palette = "RGB", s self.im_palette = "RGB", s
return s return s
def chunk_tRNS(self, pos, len): def chunk_tRNS(self, pos, length):
# transparency # transparency
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P": if self.im_mode == "P":
if _simple_palette.match(s): if _simple_palette.match(s):
i = s.find(b"\0") i = s.find(b"\0")
@ -265,17 +265,17 @@ class PngStream(ChunkStream):
self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:]) self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:])
return s return s
def chunk_gAMA(self, pos, len): def chunk_gAMA(self, pos, length):
# gamma setting # gamma setting
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
self.im_info["gamma"] = i32(s) / 100000.0 self.im_info["gamma"] = i32(s) / 100000.0
return s return s
def chunk_pHYs(self, pos, len): def chunk_pHYs(self, pos, length):
# pixels per unit # pixels per unit
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
px, py = i32(s), i32(s[4:]) px, py = i32(s), i32(s[4:])
unit = i8(s[8]) unit = i8(s[8])
if unit == 1: # meter if unit == 1: # meter
@ -285,10 +285,10 @@ class PngStream(ChunkStream):
self.im_info["aspect"] = px, py self.im_info["aspect"] = px, py
return s return s
def chunk_tEXt(self, pos, len): def chunk_tEXt(self, pos, length):
# text # text
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
except ValueError: except ValueError:
@ -301,10 +301,10 @@ class PngStream(ChunkStream):
self.im_info[k] = self.im_text[k] = v self.im_info[k] = self.im_text[k] = v
return s return s
def chunk_zTXt(self, pos, len): def chunk_zTXt(self, pos, length):
# compressed text # compressed text
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
except ValueError: except ValueError:
@ -358,16 +358,16 @@ class PngImageFile(ImageFile.ImageFile):
# #
# get next chunk # get next chunk
cid, pos, len = self.png.read() cid, pos, length = self.png.read()
try: try:
s = self.png.call(cid, pos, len) s = self.png.call(cid, pos, length)
except EOFError: except EOFError:
break break
except AttributeError: except AttributeError:
if Image.DEBUG: if Image.DEBUG:
print(cid, pos, len, "(unknown)") print(cid, pos, length, "(unknown)")
s = ImageFile._safe_read(self.fp, len) s = ImageFile._safe_read(self.fp, length)
self.png.crc(cid, s) self.png.crc(cid, s)
@ -388,7 +388,7 @@ class PngImageFile(ImageFile.ImageFile):
rawmode, data = self.png.im_palette rawmode, data = self.png.im_palette
self.palette = ImagePalette.raw(rawmode, data) self.palette = ImagePalette.raw(rawmode, data)
self.__idat = len # used by load_read() self.__idat = length # used by load_read()
def verify(self): def verify(self):
@ -413,7 +413,7 @@ class PngImageFile(ImageFile.ImageFile):
ImageFile.ImageFile.load_prepare(self) ImageFile.ImageFile.load_prepare(self)
def load_read(self, bytes): def load_read(self, read_bytes):
"internal: read more image data" "internal: read more image data"
while self.__idat == 0: while self.__idat == 0:
@ -421,23 +421,23 @@ class PngImageFile(ImageFile.ImageFile):
self.fp.read(4) # CRC self.fp.read(4) # CRC
cid, pos, len = self.png.read() cid, pos, length = self.png.read()
if cid not in [b"IDAT", b"DDAT"]: if cid not in [b"IDAT", b"DDAT"]:
self.png.push(cid, pos, len) self.png.push(cid, pos, length)
return b"" return b""
self.__idat = len # empty chunks are allowed self.__idat = length # empty chunks are allowed
# read more data from this chunk # read more data from this chunk
if bytes <= 0: if read_bytes <= 0:
bytes = self.__idat read_bytes = self.__idat
else: else:
bytes = min(bytes, self.__idat) read_bytes = min(read_bytes, self.__idat)
self.__idat = self.__idat - bytes self.__idat = self.__idat - read_bytes
return self.fp.read(bytes) return self.fp.read(read_bytes)
def load_end(self): def load_end(self):

View File

@ -63,7 +63,11 @@ class PpmImageFile(ImageFile.ImageFile):
c = self.fp.read(1) c = self.fp.read(1)
if not c or c in b_whitespace: if not c or c in b_whitespace:
break break
if c > b'\x79':
raise ValueError("Expected ASCII value, found binary")
s = s + c s = s + c
if (len(s) > 9):
raise ValueError("Expected int, got > 9 digits")
return s return s
def _open(self): def _open(self):
@ -96,6 +100,17 @@ class PpmImageFile(ImageFile.ImageFile):
ysize = s ysize = s
if mode == "1": if mode == "1":
break break
elif ix == 2:
# maxgrey
if s > 255:
if not mode == 'L':
raise ValueError("Too many colors for band: %s" %s)
if s < 2**16:
self.mode = 'I'
rawmode = 'I;16B'
else:
self.mode = 'I';
rawmode = 'I;32B'
self.size = xsize, ysize self.size = xsize, ysize
self.tile = [("raw", self.tile = [("raw",
@ -116,6 +131,11 @@ def _save(im, fp, filename):
rawmode, head = "1;I", b"P4" rawmode, head = "1;I", b"P4"
elif im.mode == "L": elif im.mode == "L":
rawmode, head = "L", b"P5" rawmode, head = "L", b"P5"
elif im.mode == "I":
if im.getextrema()[1] < 2**16:
rawmode, head = "I;16B", b"P5"
else:
rawmode, head = "I;32B", b"P5"
elif im.mode == "RGB": elif im.mode == "RGB":
rawmode, head = "RGB", b"P6" rawmode, head = "RGB", b"P6"
elif im.mode == "RGBA": elif im.mode == "RGBA":
@ -123,8 +143,15 @@ def _save(im, fp, filename):
else: else:
raise IOError("cannot write mode %s as PPM" % im.mode) raise IOError("cannot write mode %s as PPM" % im.mode)
fp.write(head + ("\n%d %d\n" % im.size).encode('ascii')) fp.write(head + ("\n%d %d\n" % im.size).encode('ascii'))
if head != b"P4": if head == b"P6":
fp.write(b"255\n") fp.write(b"255\n")
if head == b"P5":
if rawmode == "L":
fp.write(b"255\n")
elif rawmode == "I;16B":
fp.write(b"65535\n")
elif rawmode == "I;32B":
fp.write(b"2147483648\n")
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))]) ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode, 0, 1))])
# ALTERNATIVE: save via builtin debug function # ALTERNATIVE: save via builtin debug function

View File

@ -36,17 +36,23 @@
from __future__ import print_function from __future__ import print_function
from PIL import Image, ImageFile from PIL import Image, ImageFile
import os, struct, sys import os
import struct
import sys
def isInt(f): def isInt(f):
try: try:
i = int(f) i = int(f)
if f-i == 0: return 1 if f-i == 0:
else: return 0 return 1
else:
return 0
except: except:
return 0 return 0
iforms = [1,3,-11,-12,-21,-22] iforms = [1, 3, -11, -12, -21, -22]
# There is no magic number to identify Spider files, so just check a # There is no magic number to identify Spider files, so just check a
# series of header locations to see if they have reasonable values. # series of header locations to see if they have reasonable values.
@ -56,30 +62,32 @@ iforms = [1,3,-11,-12,-21,-22]
def isSpiderHeader(t): def isSpiderHeader(t):
h = (99,) + t # add 1 value so can use spider header index start=1 h = (99,) + t # add 1 value so can use spider header index start=1
# header values 1,2,5,12,13,22,23 should be integers # header values 1,2,5,12,13,22,23 should be integers
for i in [1,2,5,12,13,22,23]: for i in [1, 2, 5, 12, 13, 22, 23]:
if not isInt(h[i]): return 0 if not isInt(h[i]):
return 0
# check iform # check iform
iform = int(h[5]) iform = int(h[5])
if not iform in iforms: return 0 if iform not in iforms:
return 0
# check other header values # check other header values
labrec = int(h[13]) # no. records in file header labrec = int(h[13]) # no. records in file header
labbyt = int(h[22]) # total no. of bytes in header labbyt = int(h[22]) # total no. of bytes in header
lenbyt = int(h[23]) # record length in bytes lenbyt = int(h[23]) # record length in bytes
#print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt) # print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt)
if labbyt != (labrec * lenbyt): return 0 if labbyt != (labrec * lenbyt):
return 0
# looks like a valid header # looks like a valid header
return labbyt return labbyt
def isSpiderImage(filename): def isSpiderImage(filename):
fp = open(filename,'rb') fp = open(filename, 'rb')
f = fp.read(92) # read 23 * 4 bytes f = fp.read(92) # read 23 * 4 bytes
fp.close() fp.close()
bigendian = 1 t = struct.unpack('>23f', f) # try big-endian first
t = struct.unpack('>23f',f) # try big-endian first
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
if hdrlen == 0: if hdrlen == 0:
bigendian = 0 t = struct.unpack('<23f', f) # little-endian
t = struct.unpack('<23f',f) # little-endian
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
return hdrlen return hdrlen
@ -96,11 +104,11 @@ class SpiderImageFile(ImageFile.ImageFile):
try: try:
self.bigendian = 1 self.bigendian = 1
t = struct.unpack('>27f',f) # try big-endian first t = struct.unpack('>27f', f) # try big-endian first
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
if hdrlen == 0: if hdrlen == 0:
self.bigendian = 0 self.bigendian = 0
t = struct.unpack('<27f',f) # little-endian t = struct.unpack('<27f', f) # little-endian
hdrlen = isSpiderHeader(t) hdrlen = isSpiderHeader(t)
if hdrlen == 0: if hdrlen == 0:
raise SyntaxError("not a valid Spider file") raise SyntaxError("not a valid Spider file")
@ -141,7 +149,8 @@ class SpiderImageFile(ImageFile.ImageFile):
self.rawmode = "F;32F" self.rawmode = "F;32F"
self.mode = "F" self.mode = "F"
self.tile = [("raw", (0, 0) + self.size, offset, self.tile = [
("raw", (0, 0) + self.size, offset,
(self.rawmode, 0, 1))] (self.rawmode, 0, 1))]
self.__fp = self.fp # FIXME: hack self.__fp = self.fp # FIXME: hack
@ -176,6 +185,7 @@ class SpiderImageFile(ImageFile.ImageFile):
from PIL import ImageTk from PIL import ImageTk
return ImageTk.PhotoImage(self.convert2byte(), palette=256) return ImageTk.PhotoImage(self.convert2byte(), palette=256)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Image series # Image series
@ -200,17 +210,19 @@ def loadImageSeries(filelist=None):
imglist.append(im) imglist.append(im)
return imglist return imglist
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# For saving images in Spider format # For saving images in Spider format
def makeSpiderHeader(im): def makeSpiderHeader(im):
nsam,nrow = im.size nsam, nrow = im.size
lenbyt = nsam * 4 # There are labrec records in the header lenbyt = nsam * 4 # There are labrec records in the header
labrec = 1024 / lenbyt labrec = 1024 / lenbyt
if 1024%lenbyt != 0: labrec += 1 if 1024 % lenbyt != 0:
labrec += 1
labbyt = labrec * lenbyt labbyt = labrec * lenbyt
hdr = [] hdr = []
nvalues = labbyt / 4 nvalues = int(labbyt / 4)
for i in range(nvalues): for i in range(nvalues):
hdr.append(0.0) hdr.append(0.0)
@ -232,9 +244,10 @@ def makeSpiderHeader(im):
# pack binary data into a string # pack binary data into a string
hdrstr = [] hdrstr = []
for v in hdr: for v in hdr:
hdrstr.append(struct.pack('f',v)) hdrstr.append(struct.pack('f', v))
return hdrstr return hdrstr
def _save(im, fp, filename): def _save(im, fp, filename):
if im.mode[0] != "F": if im.mode[0] != "F":
im = im.convert('F') im = im.convert('F')
@ -250,11 +263,12 @@ def _save(im, fp, filename):
raise IOError("Unable to open %s for writing" % filename) raise IOError("Unable to open %s for writing" % filename)
fp.writelines(hdr) fp.writelines(hdr)
rawmode = "F;32NF" #32-bit native floating point rawmode = "F;32NF" # 32-bit native floating point
ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode,0,1))]) ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))])
fp.close() fp.close()
def _save_spider(im, fp, filename): def _save_spider(im, fp, filename):
# get the filename extension and register it with Image # get the filename extension and register it with Image
fn, ext = os.path.splitext(filename) fn, ext = os.path.splitext(filename)
@ -292,5 +306,7 @@ if __name__ == "__main__":
if outfile != "": if outfile != "":
# perform some image operation # perform some image operation
im = im.transpose(Image.FLIP_LEFT_RIGHT) im = im.transpose(Image.FLIP_LEFT_RIGHT)
print("saving a flipped version of %s as %s " % (os.path.basename(filename), outfile)) print(
"saving a flipped version of %s as %s " %
(os.path.basename(filename), outfile))
im.save(outfile, "SPIDER") im.save(outfile, "SPIDER")

View File

@ -146,6 +146,7 @@ OPEN_INFO = {
(II, 0, 1, 2, (1,), ()): ("1", "1;IR"), (II, 0, 1, 2, (1,), ()): ("1", "1;IR"),
(II, 0, 1, 1, (8,), ()): ("L", "L;I"), (II, 0, 1, 1, (8,), ()): ("L", "L;I"),
(II, 0, 1, 2, (8,), ()): ("L", "L;IR"), (II, 0, 1, 2, (8,), ()): ("L", "L;IR"),
(II, 0, 3, 1, (32,), ()): ("F", "F;32F"),
(II, 1, 1, 1, (1,), ()): ("1", "1"), (II, 1, 1, 1, (1,), ()): ("1", "1"),
(II, 1, 1, 2, (1,), ()): ("1", "1;R"), (II, 1, 1, 2, (1,), ()): ("1", "1;R"),
(II, 1, 1, 1, (8,), ()): ("L", "L"), (II, 1, 1, 1, (8,), ()): ("L", "L"),
@ -983,11 +984,7 @@ def _save(im, fp, filename):
compression = im.encoderinfo.get('compression',im.info.get('compression','raw')) compression = im.encoderinfo.get('compression',im.info.get('compression','raw'))
libtiff = WRITE_LIBTIFF or compression in ["tiff_ccitt", "group3", "group4", libtiff = WRITE_LIBTIFF or compression != 'raw'
"tiff_jpeg", "tiff_adobe_deflate",
"tiff_thunderscan", "tiff_deflate",
"tiff_sgilog", "tiff_sgilog24",
"tiff_raw_16"]
# required for color libtiff images # required for color libtiff images
ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1) ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1)

View File

@ -15,7 +15,7 @@ else:
def isDirectory(f): def isDirectory(f):
return isPath(f) and os.path.isdir(f) return isPath(f) and os.path.isdir(f)
class import_err(object): class deferred_error(object):
def __init__(self, ex): def __init__(self, ex):
self.ex = ex self.ex = ex
def __getattr__(self, elt): def __getattr__(self, elt):

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
Tests/images/lena.spider Normal file

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

View File

@ -12,6 +12,7 @@ if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs:
test_file = "Images/lena.jpg" test_file = "Images/lena.jpg"
def roundtrip(im, **options): def roundtrip(im, **options):
out = BytesIO() out = BytesIO()
im.save(out, "JPEG", **options) im.save(out, "JPEG", **options)
@ -23,6 +24,7 @@ def roundtrip(im, **options):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def test_sanity(): def test_sanity():
# internal version number # internal version number
@ -34,6 +36,7 @@ def test_sanity():
assert_equal(im.size, (128, 128)) assert_equal(im.size, (128, 128))
assert_equal(im.format, "JPEG") assert_equal(im.format, "JPEG")
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def test_app(): def test_app():
@ -44,6 +47,7 @@ def test_app():
assert_equal(im.applist[1], ("COM", b"Python Imaging Library")) assert_equal(im.applist[1], ("COM", b"Python Imaging Library"))
assert_equal(len(im.applist), 2) assert_equal(len(im.applist), 2)
def test_cmyk(): def test_cmyk():
# Test CMYK handling. Thanks to Tim and Charlie for test data, # Test CMYK handling. Thanks to Tim and Charlie for test data,
# Michael for getting me to look one more time. # Michael for getting me to look one more time.
@ -62,6 +66,7 @@ def test_cmyk():
c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1, im.size[1]-1))] c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1, im.size[1]-1))]
assert_true(k > 0.9) assert_true(k > 0.9)
def test_dpi(): def test_dpi():
def test(xdpi, ydpi=None): def test(xdpi, ydpi=None):
im = Image.open(test_file) im = Image.open(test_file)
@ -72,6 +77,7 @@ def test_dpi():
assert_equal(test(100, 200), (100, 200)) assert_equal(test(100, 200), (100, 200))
assert_equal(test(0), None) # square pixels assert_equal(test(0), None) # square pixels
def test_icc(): def test_icc():
# Test ICC support # Test ICC support
im1 = Image.open("Tests/images/rgb.jpg") im1 = Image.open("Tests/images/rgb.jpg")
@ -89,6 +95,7 @@ def test_icc():
assert_false(im1.info.get("icc_profile")) assert_false(im1.info.get("icc_profile"))
assert_true(im2.info.get("icc_profile")) assert_true(im2.info.get("icc_profile"))
def test_icc_big(): def test_icc_big():
# Make sure that the "extra" support handles large blocks # Make sure that the "extra" support handles large blocks
def test(n): def test(n):
@ -99,39 +106,47 @@ def test_icc_big():
assert len(icc_profile) == n # sanity assert len(icc_profile) == n # sanity
im1 = roundtrip(lena(), icc_profile=icc_profile) im1 = roundtrip(lena(), icc_profile=icc_profile)
assert_equal(im1.info.get("icc_profile"), icc_profile or None) assert_equal(im1.info.get("icc_profile"), icc_profile or None)
test(0); test(1) test(0)
test(3); test(4); test(5) test(1)
test(3)
test(4)
test(5)
test(65533-14) # full JPEG marker block test(65533-14) # full JPEG marker block
test(65533-14+1) # full block plus one byte test(65533-14+1) # full block plus one byte
test(ImageFile.MAXBLOCK) # full buffer block test(ImageFile.MAXBLOCK) # full buffer block
test(ImageFile.MAXBLOCK+1) # full buffer block plus one byte test(ImageFile.MAXBLOCK+1) # full buffer block plus one byte
test(ImageFile.MAXBLOCK*4+3) # large block test(ImageFile.MAXBLOCK*4+3) # large block
def test_optimize(): def test_optimize():
im1 = roundtrip(lena()) im1 = roundtrip(lena())
im2 = roundtrip(lena(), optimize=1) im2 = roundtrip(lena(), optimize=1)
assert_image_equal(im1, im2) assert_image_equal(im1, im2)
assert_true(im1.bytes >= im2.bytes) assert_true(im1.bytes >= im2.bytes)
def test_optimize_large_buffer(): def test_optimize_large_buffer():
#https://github.com/python-imaging/Pillow/issues/148 # https://github.com/python-imaging/Pillow/issues/148
f = tempfile('temp.jpg') f = tempfile('temp.jpg')
# this requires ~ 1.5x Image.MAXBLOCK # this requires ~ 1.5x Image.MAXBLOCK
im = Image.new("RGB", (4096,4096), 0xff3333) im = Image.new("RGB", (4096, 4096), 0xff3333)
im.save(f, format="JPEG", optimize=True) im.save(f, format="JPEG", optimize=True)
def test_progressive(): def test_progressive():
im1 = roundtrip(lena()) im1 = roundtrip(lena())
im2 = roundtrip(lena(), progressive=True) im2 = roundtrip(lena(), progressive=True)
assert_image_equal(im1, im2) assert_image_equal(im1, im2)
assert_true(im1.bytes >= im2.bytes) assert_true(im1.bytes >= im2.bytes)
def test_progressive_large_buffer(): def test_progressive_large_buffer():
f = tempfile('temp.jpg') f = tempfile('temp.jpg')
# this requires ~ 1.5x Image.MAXBLOCK # this requires ~ 1.5x Image.MAXBLOCK
im = Image.new("RGB", (4096,4096), 0xff3333) im = Image.new("RGB", (4096, 4096), 0xff3333)
im.save(f, format="JPEG", progressive=True) im.save(f, format="JPEG", progressive=True)
def test_progressive_large_buffer_highest_quality(): def test_progressive_large_buffer_highest_quality():
f = tempfile('temp.jpg') f = tempfile('temp.jpg')
if py3: if py3:
@ -142,11 +157,13 @@ def test_progressive_large_buffer_highest_quality():
# this requires more bytes than pixels in the image # this requires more bytes than pixels in the image
im.save(f, format="JPEG", progressive=True, quality=100) im.save(f, format="JPEG", progressive=True, quality=100)
def test_large_exif(): def test_large_exif():
#https://github.com/python-imaging/Pillow/issues/148 # https://github.com/python-imaging/Pillow/issues/148
f = tempfile('temp.jpg') f = tempfile('temp.jpg')
im = lena() im = lena()
im.save(f,'JPEG', quality=90, exif=b"1"*65532) im.save(f, 'JPEG', quality=90, exif=b"1"*65532)
def test_progressive_compat(): def test_progressive_compat():
im1 = roundtrip(lena()) im1 = roundtrip(lena())
@ -161,17 +178,20 @@ def test_progressive_compat():
assert_true(im3.info.get("progressive")) assert_true(im3.info.get("progressive"))
assert_true(im3.info.get("progression")) assert_true(im3.info.get("progression"))
def test_quality(): def test_quality():
im1 = roundtrip(lena()) im1 = roundtrip(lena())
im2 = roundtrip(lena(), quality=50) im2 = roundtrip(lena(), quality=50)
assert_image(im1, im2.mode, im2.size) assert_image(im1, im2.mode, im2.size)
assert_true(im1.bytes >= im2.bytes) assert_true(im1.bytes >= im2.bytes)
def test_smooth(): def test_smooth():
im1 = roundtrip(lena()) im1 = roundtrip(lena())
im2 = roundtrip(lena(), smooth=100) im2 = roundtrip(lena(), smooth=100)
assert_image(im1, im2.mode, im2.size) assert_image(im1, im2.mode, im2.size)
def test_subsampling(): def test_subsampling():
def getsampling(im): def getsampling(im):
layer = im.layer layer = im.layer
@ -197,6 +217,7 @@ def test_subsampling():
assert_exception(TypeError, lambda: roundtrip(lena(), subsampling="1:1:1")) assert_exception(TypeError, lambda: roundtrip(lena(), subsampling="1:1:1"))
def test_exif(): def test_exif():
im = Image.open("Tests/images/pil_sample_rgb.jpg") im = Image.open("Tests/images/pil_sample_rgb.jpg")
info = im._getexif() info = im._getexif()
@ -207,3 +228,11 @@ def test_quality_keep():
im = Image.open("Images/lena.jpg") im = Image.open("Images/lena.jpg")
f = tempfile('temp.jpg') f = tempfile('temp.jpg')
assert_no_exception(lambda: im.save(f, quality='keep')) assert_no_exception(lambda: im.save(f, quality='keep'))
def test_junk_jpeg_header():
# https://github.com/python-imaging/Pillow/issues/630
filename = "Tests/images/junk_jpeg_header.jpg"
assert_no_exception(lambda: Image.open(filename))
# End of file

View File

@ -258,9 +258,6 @@ def test_compressions():
im = lena('RGB') im = lena('RGB')
out = tempfile('temp.tif') out = tempfile('temp.tif')
TiffImagePlugin.READ_LIBTIFF = True
TiffImagePlugin.WRITE_LIBTIFF = True
for compression in ('packbits', 'tiff_lzw'): for compression in ('packbits', 'tiff_lzw'):
im.save(out, compression=compression) im.save(out, compression=compression)
im2 = Image.open(out) im2 = Image.open(out)
@ -270,11 +267,6 @@ def test_compressions():
im2 = Image.open(out) im2 = Image.open(out)
assert_image_similar(im, im2, 30) assert_image_similar(im, im2, 30)
TiffImagePlugin.READ_LIBTIFF = False
TiffImagePlugin.WRITE_LIBTIFF = False
def test_cmyk_save(): def test_cmyk_save():
im = lena('CMYK') im = lena('CMYK')

58
Tests/test_file_pdf.py Normal file
View 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

View File

@ -12,3 +12,25 @@ def test_sanity():
assert_equal(im.mode, "RGB") assert_equal(im.mode, "RGB")
assert_equal(im.size, (128, 128)) assert_equal(im.size, (128, 128))
assert_equal(im.format, "PPM") assert_equal(im.format, "PPM")
def test_16bit_pgm():
im = Image.open('Tests/images/16_bit_binary.pgm')
im.load()
assert_equal(im.mode, 'I')
assert_equal(im.size, (20,100))
tgt = Image.open('Tests/images/16_bit_binary_pgm.png')
assert_image_equal(im, tgt)
def test_16bit_pgm_write():
im = Image.open('Tests/images/16_bit_binary.pgm')
im.load()
f = tempfile('temp.pgm')
assert_no_exception(lambda: im.save(f, 'PPM'))
reloaded = Image.open(f)
assert_image_equal(im, reloaded)

36
Tests/test_file_spider.py Normal file
View File

@ -0,0 +1,36 @@
from tester import *
from PIL import Image
from PIL import SpiderImagePlugin
test_file = "Tests/images/lena.spider"
def test_sanity():
im = Image.open(test_file)
im.load()
assert_equal(im.mode, "F")
assert_equal(im.size, (128, 128))
assert_equal(im.format, "SPIDER")
def test_save():
# Arrange
temp = tempfile('temp.spider')
im = lena()
# Act
im.save(temp, "SPIDER")
# Assert
im2 = Image.open(temp)
assert_equal(im2.mode, "F")
assert_equal(im2.size, (128, 128))
assert_equal(im2.format, "SPIDER")
def test_isSpiderImage():
assert_true(SpiderImagePlugin.isSpiderImage(test_file))
# End of file

View File

@ -128,3 +128,14 @@ def test_12bit_rawmode():
print (im2.getpixel((0,2))) print (im2.getpixel((0,2)))
assert_image_equal(im, im2) assert_image_equal(im, im2)
def test_32bit_float():
# Issue 614, specific 32 bit float format
path = 'Tests/images/10ct_32bit_128.tiff'
im = Image.open(path)
im.load()
assert_equal(im.getpixel((0,0)), -0.4526388943195343)
assert_equal(im.getextrema(), (-3.140936851501465, 3.140684127807617))

View File

@ -2,6 +2,7 @@ from tester import *
from PIL import Image from PIL import Image
def test_sanity(): def test_sanity():
def convert(im, mode): def convert(im, mode):
@ -16,6 +17,7 @@ def test_sanity():
for mode in modes: for mode in modes:
yield_test(convert, im, mode) yield_test(convert, im, mode)
def test_default(): def test_default():
im = lena("P") im = lena("P")
@ -26,26 +28,29 @@ def test_default():
assert_image(im, "RGB", im.size) assert_image(im, "RGB", im.size)
# ref https://github.com/python-imaging/Pillow/issues/274 # ref https://github.com/python-imaging/Pillow/issues/274
def _test_float_conversion(im): def _test_float_conversion(im):
orig = im.getpixel((5,5)) orig = im.getpixel((5, 5))
converted = im.convert('F').getpixel((5,5)) converted = im.convert('F').getpixel((5, 5))
assert_equal(orig, converted) assert_equal(orig, converted)
def test_8bit(): def test_8bit():
im = Image.open('Images/lena.jpg') im = Image.open('Images/lena.jpg')
_test_float_conversion(im.convert('L')) _test_float_conversion(im.convert('L'))
def test_16bit(): def test_16bit():
im = Image.open('Tests/images/16bit.cropped.tif') im = Image.open('Tests/images/16bit.cropped.tif')
_test_float_conversion(im) _test_float_conversion(im)
def test_16bit_workaround(): def test_16bit_workaround():
im = Image.open('Tests/images/16bit.cropped.tif') im = Image.open('Tests/images/16bit.cropped.tif')
_test_float_conversion(im.convert('I')) _test_float_conversion(im.convert('I'))
def test_rgba_p(): def test_rgba_p():
im = lena('RGBA') im = lena('RGBA')
im.putalpha(lena('L')) im.putalpha(lena('L'))
@ -55,9 +60,10 @@ def test_rgba_p():
assert_image_similar(im, comparable, 20) assert_image_similar(im, comparable, 20)
def test_trns_p(): def test_trns_p():
im = lena('P') im = lena('P')
im.info['transparency']=0 im.info['transparency'] = 0
f = tempfile('temp.png') f = tempfile('temp.png')
@ -65,11 +71,25 @@ def test_trns_p():
assert_equal(l.info['transparency'], 0) # undone assert_equal(l.info['transparency'], 0) # undone
assert_no_exception(lambda: l.save(f)) assert_no_exception(lambda: l.save(f))
rgb = im.convert('RGB') rgb = im.convert('RGB')
assert_equal(rgb.info['transparency'], (0,0,0)) # undone assert_equal(rgb.info['transparency'], (0, 0, 0)) # undone
assert_no_exception(lambda: rgb.save(f)) assert_no_exception(lambda: rgb.save(f))
# ref https://github.com/python-imaging/Pillow/issues/664
def test_trns_p_rgba():
# Arrange
im = lena('P')
im.info['transparency'] = 128
# Act
rgba = im.convert('RGBA')
# Assert
assert_false('transparency' in rgba.info)
def test_trns_l(): def test_trns_l():
im = lena('L') im = lena('L')
im.info['transparency'] = 128 im.info['transparency'] = 128
@ -77,7 +97,7 @@ def test_trns_l():
f = tempfile('temp.png') f = tempfile('temp.png')
rgb = im.convert('RGB') rgb = im.convert('RGB')
assert_equal(rgb.info['transparency'], (128,128,128)) # undone assert_equal(rgb.info['transparency'], (128, 128, 128)) # undone
assert_no_exception(lambda: rgb.save(f)) assert_no_exception(lambda: rgb.save(f))
p = im.convert('P') p = im.convert('P')
@ -85,19 +105,19 @@ def test_trns_l():
assert_no_exception(lambda: p.save(f)) assert_no_exception(lambda: p.save(f))
p = assert_warning(UserWarning, p = assert_warning(UserWarning,
lambda: im.convert('P', palette = Image.ADAPTIVE)) lambda: im.convert('P', palette=Image.ADAPTIVE))
assert_false('transparency' in p.info) assert_false('transparency' in p.info)
assert_no_exception(lambda: p.save(f)) assert_no_exception(lambda: p.save(f))
def test_trns_RGB(): def test_trns_RGB():
im = lena('RGB') im = lena('RGB')
im.info['transparency'] = im.getpixel((0,0)) im.info['transparency'] = im.getpixel((0, 0))
f = tempfile('temp.png') f = tempfile('temp.png')
l = im.convert('L') l = im.convert('L')
assert_equal(l.info['transparency'], l.getpixel((0,0))) # undone assert_equal(l.info['transparency'], l.getpixel((0, 0))) # undone
assert_no_exception(lambda: l.save(f)) assert_no_exception(lambda: l.save(f))
p = im.convert('P') p = im.convert('P')
@ -105,8 +125,6 @@ def test_trns_RGB():
assert_no_exception(lambda: p.save(f)) assert_no_exception(lambda: p.save(f))
p = assert_warning(UserWarning, p = assert_warning(UserWarning,
lambda: im.convert('P', palette = Image.ADAPTIVE)) lambda: im.convert('P', palette=Image.ADAPTIVE))
assert_false('transparency' in p.info) assert_false('transparency' in p.info)
assert_no_exception(lambda: p.save(f)) assert_no_exception(lambda: p.save(f))

View File

@ -2,6 +2,8 @@ from tester import *
from PIL import Image from PIL import Image
import os
def test_sanity(): def test_sanity():
im = lena() im = lena()
@ -9,3 +11,17 @@ def test_sanity():
pix = im.load() pix = im.load()
assert_equal(pix[0, 0], (223, 162, 133)) assert_equal(pix[0, 0], (223, 162, 133))
def test_close():
im = Image.open("Images/lena.gif")
assert_no_exception(lambda: im.close())
assert_exception(ValueError, lambda: im.load())
assert_exception(ValueError, lambda: im.getpixel((0,0)))
def test_contextmanager():
fn = None
with Image.open("Images/lena.gif") as im:
fn = im.fp.fileno()
assert_no_exception(lambda: os.fstat(fn))
assert_exception(OSError, lambda: os.fstat(fn))

View File

@ -1,8 +1,27 @@
from tester import * from tester import *
from PIL import Image from PIL import Image
from PIL import ImageColor
from PIL import ImageDraw from PIL import ImageDraw
# Image size
w, h = 100, 100
# Bounding box points
x0 = int(w / 4)
x1 = int(x0 * 3)
y0 = int(h / 4)
y1 = int(x0 * 3)
# Two kinds of bounding box
bbox1 = [(x0, y0), (x1, y1)]
bbox2 = [x0, y0, x1, y1]
# Two kinds of coordinate sequences
points1 = [(10, 10), (20, 40), (30, 30)]
points2 = [10, 10, 20, 40, 30, 30]
def test_sanity(): def test_sanity():
im = lena("RGB").copy() im = lena("RGB").copy()
@ -17,6 +36,7 @@ def test_sanity():
success() success()
def test_deprecated(): def test_deprecated():
im = lena().copy() im = lena().copy()
@ -26,3 +46,220 @@ def test_deprecated():
assert_warning(DeprecationWarning, lambda: draw.setink(0)) assert_warning(DeprecationWarning, lambda: draw.setink(0))
assert_warning(DeprecationWarning, lambda: draw.setfill(0)) assert_warning(DeprecationWarning, lambda: draw.setfill(0))
def helper_arc(bbox):
# Arrange
im = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(im)
# Act
# FIXME Fill param should be named outline.
draw.arc(bbox, 0, 180)
del draw
# Assert
assert_image_equal(im, Image.open("Tests/images/imagedraw_arc.png"))
def test_arc1():
helper_arc(bbox1)
def test_arc2():
helper_arc(bbox2)
def test_bitmap():
# Arrange
small = Image.open("Tests/images/pil123rgba.png").resize((50, 50))
im = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(im)
# Act
draw.bitmap((10, 10), small)
del draw
# Assert
assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png"))
def helper_chord(bbox):
# Arrange
im = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(im)
# Act
draw.chord(bbox, 0, 180, fill="red", outline="yellow")
del draw
# Assert
assert_image_equal(im, Image.open("Tests/images/imagedraw_chord.png"))
def test_chord1():
helper_chord(bbox1)
def test_chord2():
helper_chord(bbox2)
def helper_ellipse(bbox):
# Arrange
im = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(im)
# Act
draw.ellipse(bbox, fill="green", outline="blue")
del draw
# Assert
assert_image_equal(im, Image.open("Tests/images/imagedraw_ellipse.png"))
def test_ellipse1():
helper_ellipse(bbox1)
def test_ellipse2():
helper_ellipse(bbox2)
def helper_line(points):
# Arrange
im = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(im)
# Act
draw.line(points1, fill="yellow", width=2)
del draw
# Assert
assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png"))
def test_line1():
helper_line(points1)
def test_line2():
helper_line(points2)
def helper_pieslice(bbox):
# Arrange
im = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(im)
# Act
draw.pieslice(bbox, -90, 45, fill="white", outline="blue")
del draw
# Assert
assert_image_equal(im, Image.open("Tests/images/imagedraw_pieslice.png"))
def test_pieslice1():
helper_pieslice(bbox1)
def test_pieslice2():
helper_pieslice(bbox2)
def helper_point(points):
# Arrange
im = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(im)
# Act
draw.point(points1, fill="yellow")
del draw
# Assert
assert_image_equal(im, Image.open("Tests/images/imagedraw_point.png"))
def test_point1():
helper_point(points1)
def test_point2():
helper_point(points2)
def helper_polygon(points):
# Arrange
im = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(im)
# Act
draw.polygon(points1, fill="red", outline="blue")
del draw
# Assert
assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png"))
def test_polygon1():
helper_polygon(points1)
def test_polygon2():
helper_polygon(points2)
def helper_rectangle(bbox):
# Arrange
im = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(im)
# Act
draw.rectangle(bbox, fill="black", outline="green")
del draw
# Assert
assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png"))
def test_rectangle1():
helper_rectangle(bbox1)
def test_rectangle2():
helper_rectangle(bbox2)
def test_floodfill():
# Arrange
im = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(im)
draw.rectangle(bbox2, outline="yellow", fill="green")
centre_point = (int(w/2), int(h/2))
# Act
ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red"))
del draw
# Assert
assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill.png"))
def test_floodfill_border():
# Arrange
im = Image.new("RGB", (w, h))
draw = ImageDraw.Draw(im)
draw.rectangle(bbox2, outline="yellow", fill="green")
centre_point = (int(w/2), int(h/2))
# Act
ImageDraw.floodfill(
im, centre_point, ImageColor.getrgb("red"),
border=ImageColor.getrgb("black"))
del draw
# Assert
assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png"))
# End of file

70
Tests/test_pickle.py Normal file
View File

@ -0,0 +1,70 @@
from tester import *
from PIL import Image
def helper_test_pickle_file(pickle, protocol=0):
im = Image.open('Images/lena.jpg')
filename = tempfile('temp.pkl')
# Act
with open(filename, 'wb') as f:
pickle.dump(im, f, protocol)
with open(filename, 'rb') as f:
loaded_im = pickle.load(f)
# Assert
assert_image_completely_equal(im, loaded_im)
def helper_test_pickle_string(pickle, protocol=0, file='Images/lena.jpg'):
im = Image.open(file)
# Act
dumped_string = pickle.dumps(im, protocol)
loaded_im = pickle.loads(dumped_string)
# Assert
assert_image_completely_equal(im, loaded_im)
def test_pickle_image():
# Arrange
import pickle
# Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
helper_test_pickle_string(pickle, protocol)
helper_test_pickle_file(pickle, protocol)
def test_cpickle_image():
# Arrange
try:
import cPickle
except ImportError:
return
# Act / Assert
for protocol in range(0, cPickle.HIGHEST_PROTOCOL + 1):
helper_test_pickle_string(cPickle, protocol)
helper_test_pickle_file(cPickle, protocol)
def test_pickle_p_mode():
# Arrange
import pickle
# Act / Assert
for file in [
"Tests/images/test-card.png",
"Tests/images/zero_bb.png",
"Tests/images/zero_bb_scale2.png",
"Tests/images/non_zero_bb.png",
"Tests/images/non_zero_bb_scale2.png",
"Tests/images/p_trns_single.png",
"Tests/images/pil123p.png"
]:
helper_test_pickle_string(pickle, file=file)
# End of file

View File

@ -242,7 +242,13 @@ def assert_image_equal(a, b, msg=None):
failure(msg or "got size %r, expected %r" % (a.size, b.size)) failure(msg or "got size %r, expected %r" % (a.size, b.size))
elif a.tobytes() != b.tobytes(): elif a.tobytes() != b.tobytes():
failure(msg or "got different content") failure(msg or "got different content")
# generate better diff? else:
success()
def assert_image_completely_equal(a, b, msg=None):
if a != b:
failure(msg or "images different")
else: else:
success() success()
@ -345,6 +351,9 @@ def _setup():
import sys import sys
if "--coverage" in sys.argv: if "--coverage" in sys.argv:
# Temporary: ignore PendingDeprecationWarning from Coverage (Py3.4)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
import coverage import coverage
cov = coverage.coverage(auto_data=True, include="PIL/*") cov = coverage.coverage(auto_data=True, include="PIL/*")
cov.start() cov.start()

View File

@ -2399,17 +2399,35 @@ _draw_ink(ImagingDrawObject* self, PyObject* args)
static PyObject* static PyObject*
_draw_arc(ImagingDrawObject* self, PyObject* args) _draw_arc(ImagingDrawObject* self, PyObject* args)
{ {
int x0, y0, x1, y1; double* xy;
int n;
PyObject* data;
int ink; int ink;
int start, end; int start, end;
int op = 0; int op = 0;
if (!PyArg_ParseTuple(args, "(iiii)iii|i", if (!PyArg_ParseTuple(args, "Oiii|i", &data, &start, &end, &ink))
&x0, &y0, &x1, &y1,
&start, &end, &ink))
return NULL; return NULL;
if (ImagingDrawArc(self->image->image, x0, y0, x1, y1, start, end, n = PyPath_Flatten(data, &xy);
&ink, op) < 0) if (n < 0)
return NULL;
if (n != 2) {
PyErr_SetString(PyExc_TypeError,
"coordinate list must contain exactly 2 coordinates"
);
return NULL;
}
n = ImagingDrawArc(self->image->image,
(int) xy[0], (int) xy[1],
(int) xy[2], (int) xy[3],
start, end, &ink, op
);
free(xy);
if (n < 0)
return NULL; return NULL;
Py_INCREF(Py_None); Py_INCREF(Py_None);
@ -2455,15 +2473,35 @@ _draw_bitmap(ImagingDrawObject* self, PyObject* args)
static PyObject* static PyObject*
_draw_chord(ImagingDrawObject* self, PyObject* args) _draw_chord(ImagingDrawObject* self, PyObject* args)
{ {
int x0, y0, x1, y1; double* xy;
int n;
PyObject* data;
int ink, fill; int ink, fill;
int start, end; int start, end;
if (!PyArg_ParseTuple(args, "(iiii)iiii", if (!PyArg_ParseTuple(args, "Oiiii",
&x0, &y0, &x1, &y1, &start, &end, &ink, &fill)) &data, &start, &end, &ink, &fill))
return NULL; return NULL;
if (ImagingDrawChord(self->image->image, x0, y0, x1, y1, n = PyPath_Flatten(data, &xy);
start, end, &ink, fill, self->blend) < 0) if (n < 0)
return NULL;
if (n != 2) {
PyErr_SetString(PyExc_TypeError,
"coordinate list must contain exactly 2 coordinates"
);
return NULL;
}
n = ImagingDrawChord(self->image->image,
(int) xy[0], (int) xy[1],
(int) xy[2], (int) xy[3],
start, end, &ink, fill, self->blend
);
free(xy);
if (n < 0)
return NULL; return NULL;
Py_INCREF(Py_None); Py_INCREF(Py_None);
@ -2656,15 +2694,34 @@ _draw_outline(ImagingDrawObject* self, PyObject* args)
static PyObject* static PyObject*
_draw_pieslice(ImagingDrawObject* self, PyObject* args) _draw_pieslice(ImagingDrawObject* self, PyObject* args)
{ {
int x0, y0, x1, y1; double* xy;
int n;
PyObject* data;
int ink, fill; int ink, fill;
int start, end; int start, end;
if (!PyArg_ParseTuple(args, "(iiii)iiii", if (!PyArg_ParseTuple(args, "Oiiii", &data, &start, &end, &ink, &fill))
&x0, &y0, &x1, &y1, &start, &end, &ink, &fill))
return NULL; return NULL;
if (ImagingDrawPieslice(self->image->image, x0, y0, x1, y1, n = PyPath_Flatten(data, &xy);
start, end, &ink, fill, self->blend) < 0) if (n < 0)
return NULL;
if (n != 2) {
PyErr_SetString(PyExc_TypeError,
"coordinate list must contain exactly 2 coordinates"
);
return NULL;
}
n = ImagingDrawPieslice(self->image->image,
(int) xy[0], (int) xy[1],
(int) xy[2], (int) xy[3],
start, end, &ink, fill, self->blend
);
free(xy);
if (n < 0)
return NULL; return NULL;
Py_INCREF(Py_None); Py_INCREF(Py_None);
@ -3350,7 +3407,7 @@ extern PyObject* PyImaging_ZipEncoderNew(PyObject* self, PyObject* args);
extern PyObject* PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args); extern PyObject* PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args);
/* Display support etc (in display.c) */ /* Display support etc (in display.c) */
#ifdef WIN32 #ifdef _WIN32
extern PyObject* PyImaging_CreateWindowWin32(PyObject* self, PyObject* args); extern PyObject* PyImaging_CreateWindowWin32(PyObject* self, PyObject* args);
extern PyObject* PyImaging_DisplayWin32(PyObject* self, PyObject* args); extern PyObject* PyImaging_DisplayWin32(PyObject* self, PyObject* args);
extern PyObject* PyImaging_DisplayModeWin32(PyObject* self, PyObject* args); extern PyObject* PyImaging_DisplayModeWin32(PyObject* self, PyObject* args);
@ -3423,14 +3480,14 @@ static PyMethodDef functions[] = {
/* Memory mapping */ /* Memory mapping */
#ifdef WITH_MAPPING #ifdef WITH_MAPPING
#ifdef WIN32 #ifdef _WIN32
{"map", (PyCFunction)PyImaging_Mapper, 1}, {"map", (PyCFunction)PyImaging_Mapper, 1},
#endif #endif
{"map_buffer", (PyCFunction)PyImaging_MapBuffer, 1}, {"map_buffer", (PyCFunction)PyImaging_MapBuffer, 1},
#endif #endif
/* Display support */ /* Display support */
#ifdef WIN32 #ifdef _WIN32
{"display", (PyCFunction)PyImaging_DisplayWin32, 1}, {"display", (PyCFunction)PyImaging_DisplayWin32, 1},
{"display_mode", (PyCFunction)PyImaging_DisplayModeWin32, 1}, {"display_mode", (PyCFunction)PyImaging_DisplayModeWin32, 1},
{"grabscreen", (PyCFunction)PyImaging_GrabScreenWin32, 1}, {"grabscreen", (PyCFunction)PyImaging_GrabScreenWin32, 1},

View File

@ -28,12 +28,6 @@ http://www.cazabon.com\n\
#include "Imaging.h" #include "Imaging.h"
#include "py3.h" #include "py3.h"
#ifdef WIN32
#include <windows.h>
#include <windef.h>
#include <wingdi.h>
#endif
#define PYCMSVERSION "1.0.0 pil" #define PYCMSVERSION "1.0.0 pil"
/* version history */ /* version history */
@ -450,7 +444,7 @@ cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args)
return PyInt_FromLong(result != 0); return PyInt_FromLong(result != 0);
} }
#ifdef WIN32 #ifdef _WIN32
static PyObject * static PyObject *
cms_get_display_profile_win32(PyObject* self, PyObject* args) cms_get_display_profile_win32(PyObject* self, PyObject* args)
{ {
@ -496,7 +490,7 @@ static PyMethodDef pyCMSdll_methods[] = {
{"createProfile", createProfile, 1}, {"createProfile", createProfile, 1},
/* platform specific tools */ /* platform specific tools */
#ifdef WIN32 #ifdef _WIN32
{"get_display_profile_win32", cms_get_display_profile_win32, 1}, {"get_display_profile_win32", cms_get_display_profile_win32, 1},
#endif #endif

View File

@ -433,9 +433,6 @@ PyImaging_TiffLzwDecoderNew(PyObject* self, PyObject* args)
#include "TiffDecode.h" #include "TiffDecode.h"
#include <string.h> #include <string.h>
#ifdef __WIN32__
#define strcasecmp(s1, s2) stricmp(s1, s2)
#endif
PyObject* PyObject*
PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args) PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args)

View File

@ -31,7 +31,7 @@
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/* Windows DIB support */ /* Windows DIB support */
#ifdef WIN32 #ifdef _WIN32
#include "ImDib.h" #include "ImDib.h"
@ -864,4 +864,4 @@ error:
return buffer; return buffer;
} }
#endif /* WIN32 */ #endif /* _WIN32 */

View File

@ -39,7 +39,7 @@ Why a fork?
PIL is not setuptools compatible. Please see `this Image-SIG post`_ for a more PIL is not setuptools compatible. Please see `this Image-SIG post`_ for a more
detailed explanation. Also, PIL's current bi-yearly (or greater) release detailed explanation. Also, PIL's current bi-yearly (or greater) release
schedule is too infrequent to accomodate the large number and frequency of schedule is too infrequent to accommodate the large number and frequency of
issues reported. issues reported.
.. _this Image-SIG post: https://mail.python.org/pipermail/image-sig/2010-August/006480.html .. _this Image-SIG post: https://mail.python.org/pipermail/image-sig/2010-August/006480.html
@ -52,7 +52,7 @@ What about PIL?
Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0 Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0
added Python 3 support and includes many bug fixes from many contributors. added Python 3 support and includes many bug fixes from many contributors.
As more time passes since the last PIL release, the likelyhood of a new PIL As more time passes since the last PIL release, the likelihood of a new PIL
release decreases. However, we've yet to hear an official "PIL is dead" release decreases. However, we've yet to hear an official "PIL is dead"
announcement. So if you still want to support PIL, please announcement. So if you still want to support PIL, please
`report issues here first`_, then `report issues here first`_, then

View File

@ -126,7 +126,7 @@ Identify Image Files
for infile in sys.argv[1:]: for infile in sys.argv[1:]:
try: try:
im = Image.open(infile) with Image.open(infile) as im:
print(infile, im.format, "%dx%d" % im.size, im.mode) print(infile, im.format, "%dx%d" % im.size, im.mode)
except IOError: except IOError:
pass pass

View File

@ -143,7 +143,7 @@ distribution. Otherwise, use whatever XCode you used to compile Python.)
The easiest way to install the prerequisites is via `Homebrew The easiest way to install the prerequisites is via `Homebrew
<http://mxcl.github.com/homebrew/>`_. After you install Homebrew, run:: <http://mxcl.github.com/homebrew/>`_. After you install Homebrew, run::
$ brew install libtiff libjpeg webp littlecms $ brew install libtiff libjpeg webp little-cms2
If you've built your own Python, then you should be able to install Pillow If you've built your own Python, then you should be able to install Pillow
using:: using::

View File

@ -136,9 +136,9 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space:
.. automethod:: PIL.Image.Image.verify .. automethod:: PIL.Image.Image.verify
.. automethod:: PIL.Image.Image.fromstring .. automethod:: PIL.Image.Image.fromstring
.. deprecated:: 2.0
.. automethod:: PIL.Image.Image.load .. automethod:: PIL.Image.Image.load
.. automethod:: PIL.Image.Image.close
Attributes Attributes
---------- ----------

View File

@ -91,9 +91,12 @@ Methods
Draws an arc (a portion of a circle outline) between the start and end Draws an arc (a portion of a circle outline) between the start and end
angles, inside the given bounding box. angles, inside the given bounding box.
:param xy: Four points to define the bounding box. Sequence of either :param xy: Four points to define the bounding box. Sequence of
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``.
:param outline: Color to use for the outline. :param start: Starting angle, in degrees. Angles are measured from
3 o'clock, increasing clockwise.
:param end: Ending angle, in degrees.
:param fill: Color to use for the arc.
.. py:method:: PIL.ImageDraw.Draw.bitmap(xy, bitmap, fill=None) .. py:method:: PIL.ImageDraw.Draw.bitmap(xy, bitmap, fill=None)
@ -111,7 +114,7 @@ Methods
Same as :py:meth:`~PIL.ImageDraw.Draw.arc`, but connects the end points Same as :py:meth:`~PIL.ImageDraw.Draw.arc`, but connects the end points
with a straight line. with a straight line.
:param xy: Four points to define the bounding box. Sequence of either :param xy: Four points to define the bounding box. Sequence of
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``.
:param outline: Color to use for the outline. :param outline: Color to use for the outline.
:param fill: Color to use for the fill. :param fill: Color to use for the fill.
@ -144,7 +147,7 @@ Methods
Same as arc, but also draws straight lines between the end points and the Same as arc, but also draws straight lines between the end points and the
center of the bounding box. center of the bounding box.
:param xy: Four points to define the bounding box. Sequence of either :param xy: Four points to define the bounding box. Sequence of
``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``.
:param outline: Color to use for the outline. :param outline: Color to use for the outline.
:param fill: Color to use for the fill. :param fill: Color to use for the fill.

View File

@ -670,9 +670,6 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args)
#include "TiffDecode.h" #include "TiffDecode.h"
#include <string.h> #include <string.h>
#ifdef __WIN32__
#define strcasecmp(s1, s2) stricmp(s1, s2)
#endif
PyObject* PyObject*
PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)

View File

@ -22,7 +22,7 @@
#include "Imaging.h" #include "Imaging.h"
#ifdef WIN32 #ifdef _WIN32
#include "ImDib.h" #include "ImDib.h"
@ -308,4 +308,4 @@ ImagingDeleteDIB(ImagingDIB dib)
free(dib->info); free(dib->info);
} }
#endif /* WIN32 */ #endif /* _WIN32 */

View File

@ -59,7 +59,7 @@ typedef struct {
unsigned char buffer[GIFTABLE]; unsigned char buffer[GIFTABLE];
/* Symbol table */ /* Symbol table */
unsigned INT16 link[GIFTABLE]; UINT16 link[GIFTABLE];
unsigned char data[GIFTABLE]; unsigned char data[GIFTABLE];
int next; int next;

View File

@ -10,20 +10,9 @@
* See the README file for information on usage and redistribution. * See the README file for information on usage and redistribution.
*/ */
#ifdef WIN32 #ifdef _WIN32
#if (defined(_MSC_VER) && _MSC_VER >= 1200) || (defined __GNUC__) #include "ImPlatform.h"
/* already defined in basetsd.h */
#undef INT8
#undef UINT8
#undef INT16
#undef UINT16
#undef INT32
#undef INT64
#undef UINT32
#endif
#include <windows.h>
#if defined(__cplusplus) #if defined(__cplusplus)
extern "C" { extern "C" {

View File

@ -17,26 +17,22 @@
#error Sorry, this library requires ANSI header files. #error Sorry, this library requires ANSI header files.
#endif #endif
#if defined(_MSC_VER) #if defined(_MSC_VER) && !defined(__GNUC__)
#ifndef WIN32 #define inline __inline
#define WIN32
#endif
/* VC++ 4.0 is a bit annoying when it comes to precision issues (like
claiming that "float a = 0.0;" would lead to loss of precision). I
don't like to see warnings from my code, but since I still want to
keep it readable, I simply switch off a few warnings instead of adding
the tons of casts that VC++ seem to require. This code is compiled
with numerous other compilers as well, so any real errors are likely
to be catched anyway. */
#pragma warning(disable: 4244) /* conversion from 'float' to 'int' */
#endif #endif
#if defined(_MSC_VER) #if !defined(PIL_USE_INLINE)
#define inline __inline
#elif !defined(USE_INLINE)
#define inline #define inline
#endif #endif
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
/* For System that are not Windows, we'll need to define these. */
#if SIZEOF_SHORT == 2 #if SIZEOF_SHORT == 2
#define INT16 short #define INT16 short
#elif SIZEOF_INT == 2 #elif SIZEOF_INT == 2
@ -61,12 +57,16 @@
#define INT64 long #define INT64 long
#endif #endif
/* assume IEEE; tweak if necessary (patches are welcome) */
#define FLOAT32 float
#define FLOAT64 double
#define INT8 signed char #define INT8 signed char
#define UINT8 unsigned char #define UINT8 unsigned char
#define UINT16 unsigned INT16 #define UINT16 unsigned INT16
#define UINT32 unsigned INT32 #define UINT32 unsigned INT32
#endif
/* assume IEEE; tweak if necessary (patches are welcome) */
#define FLOAT32 float
#define FLOAT64 double

View File

@ -41,7 +41,6 @@
two cases. */ two cases. */
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h>
#include <process.h> #include <process.h>
#else #else
#include <pthread.h> #include <pthread.h>

View File

@ -45,7 +45,7 @@ typedef struct {
unsigned char buffer[LZWTABLE]; unsigned char buffer[LZWTABLE];
/* Symbol table */ /* Symbol table */
unsigned INT16 link[LZWTABLE]; UINT16 link[LZWTABLE];
unsigned char data[LZWTABLE]; unsigned char data[LZWTABLE];
int next; int next;

18
map.c
View File

@ -22,18 +22,6 @@
#include "Imaging.h" #include "Imaging.h"
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#undef INT8
#undef UINT8
#undef INT16
#undef UINT16
#undef INT32
#undef INT64
#undef UINT32
#include "windows.h"
#endif
#include "py3.h" #include "py3.h"
/* compatibility wrappers (defined in _imaging.c) */ /* compatibility wrappers (defined in _imaging.c) */
@ -48,7 +36,7 @@ typedef struct {
char* base; char* base;
int size; int size;
int offset; int offset;
#ifdef WIN32 #ifdef _WIN32
HANDLE hFile; HANDLE hFile;
HANDLE hMap; HANDLE hMap;
#endif #endif
@ -71,7 +59,7 @@ PyImaging_MapperNew(const char* filename, int readonly)
mapper->base = NULL; mapper->base = NULL;
mapper->size = mapper->offset = 0; mapper->size = mapper->offset = 0;
#ifdef WIN32 #ifdef _WIN32
mapper->hFile = (HANDLE)-1; mapper->hFile = (HANDLE)-1;
mapper->hMap = (HANDLE)-1; mapper->hMap = (HANDLE)-1;
@ -114,7 +102,7 @@ PyImaging_MapperNew(const char* filename, int readonly)
static void static void
mapping_dealloc(ImagingMapperObject* mapper) mapping_dealloc(ImagingMapperObject* mapper)
{ {
#ifdef WIN32 #ifdef _WIN32
if (mapper->base != 0) if (mapper->base != 0)
UnmapViewOfFile(mapper->base); UnmapViewOfFile(mapper->base);
if (mapper->hMap != (HANDLE)-1) if (mapper->hMap != (HANDLE)-1)

View File

@ -206,22 +206,28 @@ class pil_build_ext(build_ext):
_add_directory(library_dirs, "/opt/local/lib") _add_directory(library_dirs, "/opt/local/lib")
_add_directory(include_dirs, "/opt/local/include") _add_directory(include_dirs, "/opt/local/include")
# if homebrew is installed, use its lib and include directories # if Homebrew is installed, use its lib and include directories
import subprocess import subprocess
try: try:
prefix = subprocess.check_output(['brew', '--prefix']) prefix = subprocess.check_output(['brew', '--prefix']).strip()
except:
# Homebrew not installed
prefix = None
ft_prefix = None
if prefix: if prefix:
prefix = prefix.strip() # add Homebrew's include and lib directories
_add_directory(library_dirs, os.path.join(prefix, 'lib')) _add_directory(library_dirs, os.path.join(prefix, 'lib'))
_add_directory(include_dirs, os.path.join(prefix, 'include')) _add_directory(include_dirs, os.path.join(prefix, 'include'))
ft_prefix = os.path.join(prefix, 'opt', 'freetype')
# freetype2 is a key-only brew under opt/ if ft_prefix and os.path.isdir(ft_prefix):
_add_directory(library_dirs, os.path.join(prefix, 'opt', 'freetype', 'lib')) # freetype might not be linked into Homebrew's prefix
_add_directory(include_dirs, os.path.join(prefix, 'opt', 'freetype', 'include')) _add_directory(library_dirs, os.path.join(ft_prefix, 'lib'))
except: _add_directory(include_dirs, os.path.join(ft_prefix, 'include'))
pass # homebrew not installed else:
# fall back to freetype from XQuartz if Homebrew's freetype is missing
# freetype2 ships with X11 (after homebrew, so that homebrew freetype is preferred)
_add_directory(library_dirs, "/usr/X11/lib") _add_directory(library_dirs, "/usr/X11/lib")
_add_directory(include_dirs, "/usr/X11/include") _add_directory(include_dirs, "/usr/X11/include")