Merge branch 'master' into flake8

This commit is contained in:
hugovk 2014-09-14 10:23:51 +03:00
commit 1de128d6b6
74 changed files with 878 additions and 541 deletions

View File

@ -16,15 +16,14 @@ python:
- 3.4 - 3.4
install: install:
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick lcov" - "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
- "pip install cffi" - "travis_retry pip install cffi"
- "pip install coveralls nose coveralls-merge" - "travis_retry pip install coverage nose"
- "gem install coveralls-lcov"
# Pyroma installation is slow on Py3, so just do it for Py2. # Pyroma installation is slow on Py3, so just do it for Py2.
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then travis_retry pip install pyroma; fi - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then travis_retry pip install pyroma; fi
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
# webp # webp
- pushd depends && ./install_webp.sh && popd - pushd depends && ./install_webp.sh && popd
@ -42,18 +41,20 @@ script:
after_success: after_success:
# gather the coverage data # gather the coverage data
- travis_retry sudo apt-get -qq install lcov
- lcov --capture --directory . -b . --output-file coverage.info - lcov --capture --directory . -b . --output-file coverage.info
# filter to remove system headers # filter to remove system headers
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info - lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
# convert to json # convert to json
- travis_retry gem install coveralls-lcov
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json - coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
- coverage report - coverage report
- travis_retry pip install coveralls-merge
- coveralls-merge coverage.c.json - coveralls-merge coverage.c.json
- pip install pep8 pyflakes - travis_retry pip install pep8 pyflakes
- pep8 --statistics --count *.py
- pep8 --statistics --count PIL/*.py - pep8 --statistics --count PIL/*.py
- pep8 --statistics --count Tests/*.py - pep8 --statistics --count Tests/*.py
- pyflakes *.py | tee >(wc -l) - pyflakes *.py | tee >(wc -l)
@ -65,3 +66,5 @@ after_success:
# (Installation is very slow on Py3, so just do it for Py2.) # (Installation is very slow on Py3, so just do it for Py2.)
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi
matrix:
fast_finish: true

View File

@ -4,6 +4,27 @@ Changelog (Pillow)
2.6.0 (unreleased) 2.6.0 (unreleased)
------------------ ------------------
- Jpeg2k Decode/Encode Memory Leak Fix #898
[joshware, wiredfool]
- EpsFilePlugin Speed improvements #886
[wiredfool, karstenw]
- Don't resize if already the right size.
[radarhere]
- Fix for reading multipage TIFFs #885
[kostrom, wiredfool]
- Correctly handle saving gray and CMYK JPEGs with quality=keep #857
[etienned]
- Correct duplicate Tiff Metadata and Exif tag values
[hugovk]
- Windows fixes #871
[wiredfool]
- Fix TGA files with image ID field #856 - Fix TGA files with image ID field #856
[megabuz] [megabuz]
@ -55,7 +76,7 @@ Changelog (Pillow)
- Added docs for ExifTags - Added docs for ExifTags
[Wintermute3] [Wintermute3]
- More tests for CurImagePlugin, DcxImagePlugin, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util - More tests for CurImagePlugin, DcxImagePlugin, Effects.c, GimpGradientFile, ImageFont, ImageMath, ImagePalette, IptcImagePlugin, SpiderImagePlugin, SgiImagePlugin, XpmImagePlugin and _util
[hugovk] [hugovk]
- Fix return value of FreeTypeFont.textsize() does not include font offsets - Fix return value of FreeTypeFont.textsize() does not include font offsets

View File

@ -91,26 +91,32 @@ def Ghostscript(tile, size, fp, scale=1):
out_fd, outfile = tempfile.mkstemp() out_fd, outfile = tempfile.mkstemp()
os.close(out_fd) os.close(out_fd)
in_fd, infile = tempfile.mkstemp()
os.close(in_fd)
# ignore length and offset! infile_temp = None
# ghostscript can read it if hasattr(fp, 'name') and os.path.exists(fp.name):
# copy whole file to read in ghostscript infile = fp.name
with open(infile, 'wb') as f: else:
# fetch length of fp in_fd, infile_temp = tempfile.mkstemp()
fp.seek(0, 2) os.close(in_fd)
fsize = fp.tell() infile = infile_temp
# ensure start position
# go back # ignore length and offset!
fp.seek(0) # ghostscript can read it
lengthfile = fsize # copy whole file to read in ghostscript
while lengthfile > 0: with open(infile_temp, 'wb') as f:
s = fp.read(min(lengthfile, 100*1024)) # fetch length of fp
if not s: fp.seek(0, 2)
break fsize = fp.tell()
length -= len(s) # ensure start position
f.write(s) # go back
fp.seek(0)
lengthfile = fsize
while lengthfile > 0:
s = fp.read(min(lengthfile, 100*1024))
if not s:
break
lengthfile -= len(s)
f.write(s)
# Build ghostscript command # Build ghostscript command
command = ["gs", command = ["gs",
@ -143,56 +149,36 @@ def Ghostscript(tile, size, fp, scale=1):
finally: finally:
try: try:
os.unlink(outfile) os.unlink(outfile)
os.unlink(infile) if infile_temo:
except: os.unlink(infile_temp)
pass except: pass
return im return im
class PSFile: class PSFile:
"""Wrapper that treats either CR or LF as end of line.""" """Wrapper for bytesio object that treats either CR or LF as end of line."""
def __init__(self, fp): def __init__(self, fp):
self.fp = fp self.fp = fp
self.char = None self.char = None
def __getattr__(self, id):
v = getattr(self.fp, id)
setattr(self, id, v)
return v
def seek(self, offset, whence=0): def seek(self, offset, whence=0):
self.char = None self.char = None
self.fp.seek(offset, whence) self.fp.seek(offset, whence)
def read(self, count):
return self.fp.read(count).decode('latin-1')
def readbinary(self, count):
return self.fp.read(count)
def tell(self):
pos = self.fp.tell()
if self.char:
pos -= 1
return pos
def readline(self): def readline(self):
s = b"" s = self.char or b""
if self.char: self.char = None
c = self.char
self.char = None c = self.fp.read(1)
else:
c = self.fp.read(1)
while c not in b"\r\n": while c not in b"\r\n":
s = s + c s = s + c
c = self.fp.read(1) c = self.fp.read(1)
if c == b"\r":
self.char = self.fp.read(1)
if self.char == b"\n":
self.char = None
return s.decode('latin-1') + "\n"
self.char = self.fp.read(1)
# line endings can be 1 or 2 of \r \n, in either order
if self.char in b"\r\n":
self.char = None
return s.decode('latin-1')
def _accept(prefix): def _accept(prefix):
return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5 return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5
@ -208,33 +194,23 @@ class EpsImageFile(ImageFile.ImageFile):
format = "EPS" format = "EPS"
format_description = "Encapsulated Postscript" format_description = "Encapsulated Postscript"
mode_map = { 1:"L", 2:"LAB", 3:"RGB" }
def _open(self): def _open(self):
(length, offset) = self._find_offset(self.fp)
fp = PSFile(self.fp) # Rewrap the open file pointer in something that will
# convert line endings and decode to latin-1.
# FIX for: Some EPS file not handled correctly / issue #302 try:
# EPS can contain binary data if bytes is str:
# or start directly with latin coding # Python2, no encoding conversion necessary
# read header in both ways to handle both fp = open(self.fp.name, "Ur")
# file types else:
# more info see: # Python3, can use bare open command.
# http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf fp = open(self.fp.name, "Ur", encoding='latin-1')
except Exception as msg:
# for HEAD without binary preview # Expect this for bytesio/stringio
s = fp.read(4) fp = PSFile(self.fp)
# for HEAD with binary preview
fp.seek(0)
sb = fp.readbinary(160)
if s[:4] == "%!PS":
fp.seek(0, 2)
length = fp.tell()
offset = 0
elif i32(sb[0:4]) == 0xC6D3D0C5:
offset = i32(sb[4:8])
length = i32(sb[8:12])
else:
raise SyntaxError("not an EPS file")
# go to offset - start of "%!PS" # go to offset - start of "%!PS"
fp.seek(offset) fp.seek(offset)
@ -247,18 +223,12 @@ class EpsImageFile(ImageFile.ImageFile):
# #
# Load EPS header # Load EPS header
s = fp.readline() s = fp.readline().strip('\r\n')
while s: while s:
if len(s) > 255: if len(s) > 255:
raise SyntaxError("not an EPS file") raise SyntaxError("not an EPS file")
if s[-2:] == '\r\n':
s = s[:-2]
elif s[-1:] == '\n':
s = s[:-1]
try: try:
m = split.match(s) m = split.match(s)
except re.error as v: except re.error as v:
@ -280,9 +250,7 @@ class EpsImageFile(ImageFile.ImageFile):
pass pass
else: else:
m = field.match(s) m = field.match(s)
if m: if m:
k = m.group(1) k = m.group(1)
@ -292,16 +260,16 @@ class EpsImageFile(ImageFile.ImageFile):
self.info[k[:8]] = k[9:] self.info[k[:8]] = k[9:]
else: else:
self.info[k] = "" self.info[k] = ""
elif s[0:1] == '%': elif s[0] == '%':
# handle non-DSC Postscript comments that some # handle non-DSC Postscript comments that some
# tools mistakenly put in the Comments section # tools mistakenly put in the Comments section
pass pass
else: else:
raise IOError("bad EPS header") raise IOError("bad EPS header")
s = fp.readline() s = fp.readline().strip('\r\n')
if s[:1] != "%": if s[0] != "%":
break break
# #
@ -312,64 +280,48 @@ class EpsImageFile(ImageFile.ImageFile):
if len(s) > 255: if len(s) > 255:
raise SyntaxError("not an EPS file") raise SyntaxError("not an EPS file")
if s[-2:] == '\r\n':
s = s[:-2]
elif s[-1:] == '\n':
s = s[:-1]
if s[:11] == "%ImageData:": if s[:11] == "%ImageData:":
# Encoded bitmapped image.
[x, y, bi, mo, z3, z4, en, id] = s[11:].split(None, 7)
[x, y, bi, mo, z3, z4, en, id] =\ if int(bi) != 8:
s[11:].split(None, 7)
x = int(x)
y = int(y)
bi = int(bi)
mo = int(mo)
en = int(en)
if en == 1:
decoder = "eps_binary"
elif en == 2:
decoder = "eps_hex"
else:
break break
if bi != 8: try:
self.mode = self.mode_map[int(mo)]
except:
break break
if mo == 1:
self.mode = "L" self.size = int(x), int(y)
elif mo == 2: return
self.mode = "LAB"
elif mo == 3: s = fp.readline().strip('\r\n')
self.mode = "RGB"
else:
break
if id[:1] == id[-1:] == '"':
id = id[1:-1]
# Scan forward to the actual image data
while True:
s = fp.readline()
if not s:
break
if s[:len(id)] == id:
self.size = x, y
self.tile2 = [(decoder,
(0, 0, x, y),
fp.tell(),
0)]
return
s = fp.readline()
if not s: if not s:
break break
if not box: if not box:
raise IOError("cannot determine EPS bounding box") raise IOError("cannot determine EPS bounding box")
def _find_offset(self, fp):
s = fp.read(160)
if s[:4] == b"%!PS":
# for HEAD without binary preview
fp.seek(0, 2)
length = fp.tell()
offset = 0
elif i32(s[0:4]) == 0xC6D3D0C5:
# FIX for: Some EPS file not handled correctly / issue #302
# EPS can contain binary data
# or start directly with latin coding
# more info see http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
offset = i32(s[4:8])
length = i32(s[8:12])
else:
raise SyntaxError("not an EPS file")
return (length, offset)
def load(self, scale=1): def load(self, scale=1):
# Load EPS via Ghostscript # Load EPS via Ghostscript
if not self.tile: if not self.tile:

View File

@ -67,8 +67,8 @@ TAGS = {
0x0213: "YCbCrPositioning", 0x0213: "YCbCrPositioning",
0x0214: "ReferenceBlackWhite", 0x0214: "ReferenceBlackWhite",
0x1000: "RelatedImageFileFormat", 0x1000: "RelatedImageFileFormat",
0x1001: "RelatedImageLength", # FIXME / Dictionary contains duplicate keys 0x1001: "RelatedImageWidth",
0x1001: "RelatedImageWidth", # FIXME \ Dictionary contains duplicate keys 0x1002: "RelatedImageLength",
0x828d: "CFARepeatPatternDim", 0x828d: "CFARepeatPatternDim",
0x828e: "CFAPattern", 0x828e: "CFAPattern",
0x828f: "BatteryLevel", 0x828f: "BatteryLevel",

View File

@ -24,6 +24,7 @@ from PIL._binary import o8
EPSILON = 1e-10 EPSILON = 1e-10
def linear(middle, pos): def linear(middle, pos):
if pos <= middle: if pos <= middle:
if middle < EPSILON: if middle < EPSILON:
@ -38,25 +39,30 @@ def linear(middle, pos):
else: else:
return 0.5 + 0.5 * pos / middle return 0.5 + 0.5 * pos / middle
def curved(middle, pos): def curved(middle, pos):
return pos ** (log(0.5) / log(max(middle, EPSILON))) return pos ** (log(0.5) / log(max(middle, EPSILON)))
def sine(middle, pos): def sine(middle, pos):
return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0 return (sin((-pi / 2.0) + pi * linear(middle, pos)) + 1.0) / 2.0
def sphere_increasing(middle, pos): def sphere_increasing(middle, pos):
return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2) return sqrt(1.0 - (linear(middle, pos) - 1.0) ** 2)
def sphere_decreasing(middle, pos): def sphere_decreasing(middle, pos):
return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2) return 1.0 - sqrt(1.0 - linear(middle, pos) ** 2)
SEGMENTS = [ linear, curved, sine, sphere_increasing, sphere_decreasing ] SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing]
class GradientFile: class GradientFile:
gradient = None gradient = None
def getpalette(self, entries = 256): def getpalette(self, entries=256):
palette = [] palette = []
@ -89,6 +95,7 @@ class GradientFile:
return b"".join(palette), "RGBA" return b"".join(palette), "RGBA"
## ##
# File handler for GIMP's gradient format. # File handler for GIMP's gradient format.
@ -99,7 +106,13 @@ class GimpGradientFile(GradientFile):
if fp.readline()[:13] != b"GIMP Gradient": if fp.readline()[:13] != b"GIMP Gradient":
raise SyntaxError("not a GIMP gradient file") raise SyntaxError("not a GIMP gradient file")
count = int(fp.readline()) line = fp.readline()
# GIMP 1.2 gradient files don't contain a name, but GIMP 1.3 files do
if line.startswith(b"Name: "):
line = fp.readline().strip()
count = int(line)
gradient = [] gradient = []
@ -108,13 +121,13 @@ class GimpGradientFile(GradientFile):
s = fp.readline().split() s = fp.readline().split()
w = [float(x) for x in s[:11]] w = [float(x) for x in s[:11]]
x0, x1 = w[0], w[2] x0, x1 = w[0], w[2]
xm = w[1] xm = w[1]
rgb0 = w[3:7] rgb0 = w[3:7]
rgb1 = w[7:11] rgb1 = w[7:11]
segment = SEGMENTS[int(s[11])] segment = SEGMENTS[int(s[11])]
cspace = int(s[12]) cspace = int(s[12])
if cspace != 0: if cspace != 0:
raise IOError("cannot handle HSV colour space") raise IOError("cannot handle HSV colour space")

View File

@ -1516,6 +1516,9 @@ class Image:
self.load() self.load()
if self.size == size:
return self._new(self.im)
if self.mode in ("1", "P"): if self.mode in ("1", "P"):
resample = NEAREST resample = NEAREST
@ -1912,6 +1915,16 @@ class Image:
im = self.im.transpose(method) im = self.im.transpose(method)
return self._new(im) return self._new(im)
def effect_spread(self, distance):
"""
Randomly spread pixels in an image.
:param distance: Distance to spread pixels.
"""
self.load()
im = self.im.effect_spread(distance)
return self._new(im)
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Lazy operations # Lazy operations
@ -2421,3 +2434,32 @@ def _show(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)
# --------------------------------------------------------------------
# Effects
def effect_mandelbrot(size, extent, quality):
"""
Generate a Mandelbrot set covering the given extent.
:param size: The requested size in pixels, as a 2-tuple:
(width, height).
:param extent: The extent to cover, as a 4-tuple:
(x0, y0, x1, y2).
:param quality: Quality.
"""
return Image()._new(core.effect_mandelbrot(size, extent, quality))
def effect_noise(size, sigma):
"""
Generate Gaussian noise centered around 128.
:param size: The requested size in pixels, as a 2-tuple:
(width, height).
:param sigma: Standard deviation of noise.
"""
return Image()._new(core.effect_noise(size, sigma))
# End of file

View File

@ -234,6 +234,8 @@ class ImageFile(Image.Image):
break break
b = b[n:] b = b[n:]
t = t + n t = t + n
# Need to cleanup here to prevent leaks in PyPy
d.cleanup()
self.tile = [] self.tile = []
self.readonly = readonly self.readonly = readonly
@ -479,6 +481,7 @@ def _save(im, fp, tile, bufsize=0):
break break
if s < 0: if s < 0:
raise IOError("encoder error %d when writing image file" % s) raise IOError("encoder error %d when writing image file" % s)
e.cleanup()
else: else:
# slight speedup: compress to real file object # slight speedup: compress to real file object
for e, b, o, a in tile: for e, b, o, a in tile:
@ -489,6 +492,7 @@ def _save(im, fp, tile, bufsize=0):
s = e.encode_to_file(fh, bufsize) s = e.encode_to_file(fh, bufsize)
if s < 0: if s < 0:
raise IOError("encoder error %d when writing image file" % s) raise IOError("encoder error %d when writing image file" % s)
e.cleanup()
try: try:
fp.flush() fp.flush()
except: except:

View File

@ -550,6 +550,15 @@ def convert_dict_qtables(qtables):
def get_sampling(im): def get_sampling(im):
# There's no subsampling when image have only 1 layer
# (grayscale images) or when they are CMYK (4 layers),
# so set subsampling to default value.
#
# NOTE: currently Pillow can't encode JPEG to YCCK format.
# If YCCK support is added in the future, subsampling code will have
# to be updated (here and in JpegEncode.c) to deal with 4 layers.
if not hasattr(im, 'layers') or im.layers in (1, 4):
return -1
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)

View File

@ -281,6 +281,7 @@ class ImageFileDirectory(collections.MutableMapping):
self.tagdata = {} self.tagdata = {}
self.tagtype = {} # added 2008-06-05 by Florian Hoech self.tagtype = {} # added 2008-06-05 by Florian Hoech
self.next = None self.next = None
self.offset = None
def __str__(self): def __str__(self):
return str(self.as_dict()) return str(self.as_dict())
@ -415,6 +416,7 @@ class ImageFileDirectory(collections.MutableMapping):
# load tag dictionary # load tag dictionary
self.reset() self.reset()
self.offset = fp.tell()
i16 = self.i16 i16 = self.i16
i32 = self.i32 i32 = self.i32
@ -446,7 +448,11 @@ class ImageFileDirectory(collections.MutableMapping):
# Get and expand tag value # Get and expand tag value
if size > 4: if size > 4:
here = fp.tell() here = fp.tell()
if Image.DEBUG:
print ("Tag Location: %s" %here)
fp.seek(i32(ifd, 8)) fp.seek(i32(ifd, 8))
if Image.DEBUG:
print ("Data Location: %s" %fp.tell())
data = ImageFile._safe_read(fp, size) data = ImageFile._safe_read(fp, size)
fp.seek(here) fp.seek(here)
else: else:
@ -630,18 +636,20 @@ class TiffImageFile(ImageFile.ImageFile):
def seek(self, frame): def seek(self, frame):
"Select a given frame as current image" "Select a given frame as current image"
if frame < 0: if frame < 0:
frame = 0 frame = 0
self._seek(frame) self._seek(frame)
# Create a new core image object on second and
# subsequent frames in the image. Image may be
# different size/mode.
Image._decompression_bomb_check(self.size)
self.im = Image.core.new(self.mode, self.size)
def tell(self): def tell(self):
"Return the current frame number" "Return the current frame number"
return self._tell() return self._tell()
def _seek(self, frame): def _seek(self, frame):
self.fp = self.__fp self.fp = self.__fp
if frame < self.__frame: if frame < self.__frame:
# rewind file # rewind file
@ -650,14 +658,21 @@ class TiffImageFile(ImageFile.ImageFile):
while self.__frame < frame: while self.__frame < frame:
if not self.__next: if not self.__next:
raise EOFError("no more images in TIFF file") raise EOFError("no more images in TIFF file")
if Image.DEBUG:
print("Seeking to frame %s, on frame %s, __next %s, location: %s"%
(frame, self.__frame, self.__next, self.fp.tell()))
# reset python3 buffered io handle in case fp
# was passed to libtiff, invalidating the buffer
self.fp.tell()
self.fp.seek(self.__next) self.fp.seek(self.__next)
if Image.DEBUG:
print("Loading tags, location: %s"%self.fp.tell())
self.tag.load(self.fp) self.tag.load(self.fp)
self.__next = self.tag.next self.__next = self.tag.next
self.__frame += 1 self.__frame += 1
self._setup() self._setup()
def _tell(self): def _tell(self):
return self.__frame return self.__frame
def _decoder(self, rawmode, layer, tile=None): def _decoder(self, rawmode, layer, tile=None):
@ -706,6 +721,7 @@ class TiffImageFile(ImageFile.ImageFile):
# (self._compression, (extents tuple), # (self._compression, (extents tuple),
# 0, (rawmode, self._compression, fp)) # 0, (rawmode, self._compression, fp))
ignored, extents, ignored_2, args = self.tile[0] ignored, extents, ignored_2, args = self.tile[0]
args = args + (self.ifd.offset,)
decoder = Image._getdecoder(self.mode, 'libtiff', args, decoder = Image._getdecoder(self.mode, 'libtiff', args,
self.decoderconfig) self.decoderconfig)
try: try:
@ -744,7 +760,8 @@ class TiffImageFile(ImageFile.ImageFile):
self.readonly = 0 self.readonly = 0
# libtiff closed the fp in a, we need to close self.fp, if possible # libtiff closed the fp in a, we need to close self.fp, if possible
if hasattr(self.fp, 'close'): if hasattr(self.fp, 'close'):
self.fp.close() if not self.__next:
self.fp.close()
self.fp = None # might be shared self.fp = None # might be shared
if err < 0: if err < 0:

View File

@ -46,8 +46,8 @@ TAGS = {
(262, 5): "CMYK", (262, 5): "CMYK",
(262, 6): "YCbCr", (262, 6): "YCbCr",
(262, 8): "CieLAB", (262, 8): "CieLAB",
(262, 32803): "CFA", # TIFF/EP, Adobe DNG (262, 32803): "CFA", # TIFF/EP, Adobe DNG
(262, 32892): "LinearRaw", # Adobe DNG (262, 32892): "LinearRaw", # Adobe DNG
263: "Thresholding", 263: "Thresholding",
264: "CellWidth", 264: "CellWidth",
@ -240,7 +240,7 @@ TAGS = {
45579: "YawAngle", 45579: "YawAngle",
45580: "PitchAngle", 45580: "PitchAngle",
45581: "RollAngle", 45581: "RollAngle",
# Adobe DNG # Adobe DNG
50706: "DNGVersion", 50706: "DNGVersion",
50707: "DNGBackwardVersion", 50707: "DNGBackwardVersion",
@ -255,7 +255,6 @@ TAGS = {
50716: "BlackLevelDeltaV", 50716: "BlackLevelDeltaV",
50717: "WhiteLevel", 50717: "WhiteLevel",
50718: "DefaultScale", 50718: "DefaultScale",
50741: "BestQualityScale", # FIXME! Dictionary contains duplicate keys 50741
50719: "DefaultCropOrigin", 50719: "DefaultCropOrigin",
50720: "DefaultCropSize", 50720: "DefaultCropSize",
50778: "CalibrationIlluminant1", 50778: "CalibrationIlluminant1",
@ -279,11 +278,12 @@ TAGS = {
50737: "ChromaBlurRadius", 50737: "ChromaBlurRadius",
50738: "AntiAliasStrength", 50738: "AntiAliasStrength",
50740: "DNGPrivateData", 50740: "DNGPrivateData",
50741: "MakerNoteSafety", # FIXME! Dictionary contains duplicate keys 50741 50741: "MakerNoteSafety",
50780: "BestQualityScale",
#ImageJ # ImageJ
50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe 50838: "ImageJMetaDataByteCounts", # private tag registered with Adobe
50839: "ImageJMetaData", # private tag registered with Adobe 50839: "ImageJMetaData", # private tag registered with Adobe
} }
## ##

View File

@ -20,3 +20,6 @@ Pillow is the "friendly" PIL fork by `Alex Clark and Contributors <https://githu
.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master .. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master :target: https://coveralls.io/r/python-pillow/Pillow?branch=master
.. image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.png
:target: https://landscape.io/github/python-pillow/Pillow/master
:alt: Code Health

View File

@ -10,6 +10,9 @@ Install::
pip install coverage nose pip install coverage nose
If you're using Python 2.6, there's one additional dependency::
pip install unittest2
Execution Execution
--------- ---------

42
Tests/check_j2k_leaks.py Executable file
View File

@ -0,0 +1,42 @@
from helper import unittest, PillowTestCase
import sys
from PIL import Image
from io import BytesIO
# Limits for testing the leak
mem_limit = 1024*1048576
stack_size = 8*1048576
iterations = int((mem_limit/stack_size)*2)
codecs = dir(Image.core)
test_file = "Tests/images/rgb_trns_ycbc.jp2"
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
class TestJpegLeaks(PillowTestCase):
def setUp(self):
if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs:
self.skipTest('JPEG 2000 support not available')
def test_leak_load(self):
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
for count in range(iterations):
with Image.open(test_file) as im:
im.load()
def test_leak_save(self):
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
for count in range(iterations):
with Image.open(test_file) as im:
im.load()
test_output = BytesIO()
im.save(test_output, "JPEG2000")
test_output.seek(0)
output = test_output.read()
if __name__ == '__main__':
unittest.main()

View File

@ -182,6 +182,26 @@ def tostring(im, format, **options):
return out.getvalue() return out.getvalue()
def hopper(mode="RGB", cache={}):
from PIL import Image
im = None
# FIXME: Implement caching to reduce reading from disk but so an original
# copy is returned each time and the cached image isn't modified by tests
# (for fast, isolated, repeatable tests).
# im = cache.get(mode)
if im is None:
if mode == "RGB":
im = Image.open("Tests/images/hopper.ppm")
elif mode == "F":
im = lena("L").convert(mode)
elif mode[:4] == "I;16":
im = lena("I").convert(mode)
else:
im = lena("RGB").convert(mode)
# cache[mode] = im
return im
def lena(mode="RGB", cache={}): def lena(mode="RGB", cache={}):
from PIL import Image from PIL import Image
im = None im = None

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,6 @@
GIMP Gradient
4
0.000000 0.351923 0.534893 1.000000 1.000000 1.000000 0.910000 0.730303 0.510606 1.000000 0.480000 1 0
0.501504 0.611002 0.637730 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 1 0
0.931891 0.951264 1.000000 0.531255 0.316078 1.031173 1.000000 0.000000 0.000000 0.000000 1.000000 0 0
0.810551 0.881217 0.921883 0.717576 0.441331 0.081217 1.000000 0.751576 0.410331 0.081217 1.000000 0 0

View File

@ -0,0 +1,7 @@
GIMP Gradient
Name: A GIMP 1.3 gradient file
4
0.000000 0.351923 0.534893 1.000000 1.000000 1.000000 0.910000 0.730303 0.510606 1.000000 0.480000 1 0
0.501504 0.611002 0.637730 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 1.000000 1 0
0.931891 0.951264 1.000000 0.531255 0.316078 1.031173 1.000000 0.000000 0.000000 0.000000 1.000000 0 0
0.810551 0.881217 0.921883 0.717576 0.441331 0.081217 1.000000 0.751576 0.410331 0.081217 1.000000 0 0

BIN
Tests/images/hopper.bw Normal file

Binary file not shown.

BIN
Tests/images/hopper.dcx Normal file

Binary file not shown.

BIN
Tests/images/hopper.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
Tests/images/hopper.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
Tests/images/hopper.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
Tests/images/hopper.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
Tests/images/hopper.ppm Normal file

Binary file not shown.

BIN
Tests/images/hopper.ras Normal file

Binary file not shown.

BIN
Tests/images/hopper.rgb Normal file

Binary file not shown.

BIN
Tests/images/hopper.spider Normal file

Binary file not shown.

BIN
Tests/images/hopper.tar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Tests/images/lena_gray.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

BIN
Tests/images/multipage.tiff Normal file

Binary file not shown.

View File

@ -1,9 +1,9 @@
from helper import unittest, PillowTestCase, lena from helper import unittest, PillowTestCase, hopper
from PIL import Image, DcxImagePlugin from PIL import Image, DcxImagePlugin
# Created with ImageMagick: convert lena.ppm lena.dcx # Created with ImageMagick: convert hopper.ppm hopper.dcx
TEST_FILE = "Tests/images/lena.dcx" TEST_FILE = "Tests/images/hopper.dcx"
class TestFileDcx(PillowTestCase): class TestFileDcx(PillowTestCase):
@ -17,7 +17,7 @@ class TestFileDcx(PillowTestCase):
# Assert # Assert
self.assertEqual(im.size, (128, 128)) self.assertEqual(im.size, (128, 128))
self.assertIsInstance(im, DcxImagePlugin.DcxImageFile) self.assertIsInstance(im, DcxImagePlugin.DcxImageFile)
orig = lena() orig = hopper()
self.assert_image_equal(im, orig) self.assert_image_equal(im, orig)
def test_tell(self): def test_tell(self):

View File

@ -63,6 +63,17 @@ class TestFileEps(PillowTestCase):
with io.open(self.tempfile('temp_iobase.eps'), 'wb') as fh: with io.open(self.tempfile('temp_iobase.eps'), 'wb') as fh:
image1.save(fh, 'EPS') image1.save(fh, 'EPS')
def test_bytesio_object(self):
with open(file1, 'rb') as f:
img_bytes = io.BytesIO(f.read())
img = Image.open(img_bytes)
img.load()
image1_scale1_compare = Image.open(file1_compare).convert("RGB")
image1_scale1_compare.load()
self.assert_image_similar(img, image1_scale1_compare, 5)
def test_render_scale1(self): def test_render_scale1(self):
# We need png support for these render test # We need png support for these render test
codecs = dir(Image.core) codecs = dir(Image.core)
@ -137,6 +148,83 @@ class TestFileEps(PillowTestCase):
# open image with binary preview # open image with binary preview
Image.open(file3) Image.open(file3)
def _test_readline(self,t, ending):
ending = "Failure with line ending: %s" %("".join("%s" %ord(s) for s in ending))
self.assertEqual(t.readline().strip('\r\n'), 'something', ending)
self.assertEqual(t.readline().strip('\r\n'), 'else', ending)
self.assertEqual(t.readline().strip('\r\n'), 'baz', ending)
self.assertEqual(t.readline().strip('\r\n'), 'bif', ending)
def _test_readline_stringio(self, test_string, ending):
# check all the freaking line endings possible
try:
import StringIO
except:
# don't skip, it skips everything in the parent test
return
t = StringIO.StringIO(test_string)
self._test_readline(t, ending)
def _test_readline_io(self, test_string, ending):
import io
if str is bytes:
t = io.StringIO(unicode(test_string))
else:
t = io.StringIO(test_string)
self._test_readline(t, ending)
def _test_readline_file_universal(self, test_string, ending):
f = self.tempfile('temp.txt')
with open(f,'wb') as w:
if str is bytes:
w.write(test_string)
else:
w.write(test_string.encode('UTF-8'))
with open(f,'rU') as t:
self._test_readline(t, ending)
def _test_readline_file_psfile(self, test_string, ending):
f = self.tempfile('temp.txt')
with open(f,'wb') as w:
if str is bytes:
w.write(test_string)
else:
w.write(test_string.encode('UTF-8'))
with open(f,'rb') as r:
t = EpsImagePlugin.PSFile(r)
self._test_readline(t, ending)
def test_readline(self):
# check all the freaking line endings possible from the spec
#test_string = u'something\r\nelse\n\rbaz\rbif\n'
line_endings = ['\r\n', '\n']
not_working_endings = ['\n\r', '\r']
strings = ['something', 'else', 'baz', 'bif']
for ending in line_endings:
s = ending.join(strings)
# Native python versions will pass these endings.
#self._test_readline_stringio(s, ending)
#self._test_readline_io(s, ending)
#self._test_readline_file_universal(s, ending)
self._test_readline_file_psfile(s, ending)
for ending in not_working_endings:
# these only work with the PSFile, while they're in spec,
# they're not likely to be used
s = ending.join(strings)
# Native python versions may fail on these endings.
#self._test_readline_stringio(s, ending)
#self._test_readline_io(s, ending)
#self._test_readline_file_universal(s, ending)
self._test_readline_file_psfile(s, ending)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,4 +1,4 @@
from helper import unittest, PillowTestCase, lena, netpbm_available from helper import unittest, PillowTestCase, hopper, netpbm_available
from PIL import Image from PIL import Image
from PIL import GifImagePlugin from PIL import GifImagePlugin
@ -6,8 +6,9 @@ from PIL import GifImagePlugin
codecs = dir(Image.core) codecs = dir(Image.core)
# sample gif stream # sample gif stream
file = "Tests/images/lena.gif" TEST_GIF = "Tests/images/hopper.gif"
with open(file, "rb") as f:
with open(TEST_GIF, "rb") as f:
data = f.read() data = f.read()
@ -18,7 +19,7 @@ class TestFileGif(PillowTestCase):
self.skipTest("gif support not available") # can this happen? self.skipTest("gif support not available") # can this happen?
def test_sanity(self): def test_sanity(self):
im = Image.open(file) im = Image.open(TEST_GIF)
im.load() im.load()
self.assertEqual(im.mode, "P") self.assertEqual(im.mode, "P")
self.assertEqual(im.size, (128, 128)) self.assertEqual(im.size, (128, 128))
@ -45,7 +46,7 @@ class TestFileGif(PillowTestCase):
def test_roundtrip(self): def test_roundtrip(self):
out = self.tempfile('temp.gif') out = self.tempfile('temp.gif')
im = lena() im = hopper()
im.save(out) im.save(out)
reread = Image.open(out) reread = Image.open(out)
@ -54,17 +55,17 @@ class TestFileGif(PillowTestCase):
def test_roundtrip2(self): def test_roundtrip2(self):
# see https://github.com/python-pillow/Pillow/issues/403 # see https://github.com/python-pillow/Pillow/issues/403
out = self.tempfile('temp.gif') out = self.tempfile('temp.gif')
im = Image.open('Tests/images/lena.gif') im = Image.open(TEST_GIF)
im2 = im.copy() im2 = im.copy()
im2.save(out) im2.save(out)
reread = Image.open(out) reread = Image.open(out)
self.assert_image_similar(reread.convert('RGB'), lena(), 50) self.assert_image_similar(reread.convert('RGB'), hopper(), 50)
def test_palette_handling(self): def test_palette_handling(self):
# see https://github.com/python-pillow/Pillow/issues/513 # see https://github.com/python-pillow/Pillow/issues/513
im = Image.open('Tests/images/lena.gif') im = Image.open(TEST_GIF)
im = im.convert('RGB') im = im.convert('RGB')
im = im.resize((100, 100), Image.ANTIALIAS) im = im.resize((100, 100), Image.ANTIALIAS)
@ -100,7 +101,7 @@ class TestFileGif(PillowTestCase):
@unittest.skipUnless(netpbm_available(), "netpbm not available") @unittest.skipUnless(netpbm_available(), "netpbm not available")
def test_save_netpbm_bmp_mode(self): def test_save_netpbm_bmp_mode(self):
img = Image.open(file).convert("RGB") img = Image.open(TEST_GIF).convert("RGB")
tempfile = self.tempfile("temp.gif") tempfile = self.tempfile("temp.gif")
GifImagePlugin._save_netpbm(img, 0, tempfile) GifImagePlugin._save_netpbm(img, 0, tempfile)
@ -108,7 +109,7 @@ class TestFileGif(PillowTestCase):
@unittest.skipUnless(netpbm_available(), "netpbm not available") @unittest.skipUnless(netpbm_available(), "netpbm not available")
def test_save_netpbm_l_mode(self): def test_save_netpbm_l_mode(self):
img = Image.open(file).convert("L") img = Image.open(TEST_GIF).convert("L")
tempfile = self.tempfile("temp.gif") tempfile = self.tempfile("temp.gif")
GifImagePlugin._save_netpbm(img, 0, tempfile) GifImagePlugin._save_netpbm(img, 0, tempfile)

View File

@ -0,0 +1,127 @@
from helper import unittest, PillowTestCase
from PIL import GimpGradientFile
class TestImage(PillowTestCase):
def test_linear_pos_le_middle(self):
# Arrange
middle = 0.5
pos = 0.25
# Act
ret = GimpGradientFile.linear(middle, pos)
# Assert
self.assertEqual(ret, 0.25)
def test_linear_pos_le_small_middle(self):
# Arrange
middle = 1e-11
pos = 1e-12
# Act
ret = GimpGradientFile.linear(middle, pos)
# Assert
self.assertEqual(ret, 0.0)
def test_linear_pos_gt_middle(self):
# Arrange
middle = 0.5
pos = 0.75
# Act
ret = GimpGradientFile.linear(middle, pos)
# Assert
self.assertEqual(ret, 0.75)
def test_linear_pos_gt_small_middle(self):
# Arrange
middle = 1 - 1e-11
pos = 1 - 1e-12
# Act
ret = GimpGradientFile.linear(middle, pos)
# Assert
self.assertEqual(ret, 1.0)
def test_curved(self):
# Arrange
middle = 0.5
pos = 0.75
# Act
ret = GimpGradientFile.curved(middle, pos)
# Assert
self.assertEqual(ret, 0.75)
def test_sine(self):
# Arrange
middle = 0.5
pos = 0.75
# Act
ret = GimpGradientFile.sine(middle, pos)
# Assert
self.assertEqual(ret, 0.8535533905932737)
def test_sphere_increasing(self):
# Arrange
middle = 0.5
pos = 0.75
# Act
ret = GimpGradientFile.sphere_increasing(middle, pos)
# Assert
self.assertEqual(ret, 0.9682458365518543)
def test_sphere_decreasing(self):
# Arrange
middle = 0.5
pos = 0.75
# Act
ret = GimpGradientFile.sphere_decreasing(middle, pos)
# Assert
self.assertEqual(ret, 0.3385621722338523)
def test_load_via_imagepalette(self):
# Arrange
from PIL import ImagePalette
test_file = "Tests/images/gimp_gradient.ggr"
# Act
palette = ImagePalette.load(test_file)
# Assert
# load returns raw palette information
self.assertEqual(len(palette[0]), 1024)
self.assertEqual(palette[1], "RGBA")
def test_load_1_3_via_imagepalette(self):
# Arrange
from PIL import ImagePalette
# GIMP 1.3 gradient files contain a name field
test_file = "Tests/images/gimp_gradient_with_name.ggr"
# Act
palette = ImagePalette.load(test_file)
# Assert
# load returns raw palette information
self.assertEqual(len(palette[0]), 1024)
self.assertEqual(palette[1], "RGBA")
if __name__ == '__main__':
unittest.main()
# End of file

View File

@ -3,14 +3,14 @@ from helper import unittest, PillowTestCase
from PIL import Image from PIL import Image
# sample ppm stream # sample ppm stream
file = "Tests/images/lena.ico" TEST_ICO_FILE = "Tests/images/hopper.ico"
data = open(file, "rb").read() TEST_DATA = open(TEST_ICO_FILE, "rb").read()
class TestFileIco(PillowTestCase): class TestFileIco(PillowTestCase):
def test_sanity(self): def test_sanity(self):
im = Image.open(file) im = Image.open(TEST_ICO_FILE)
im.load() im.load()
self.assertEqual(im.mode, "RGBA") self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (16, 16)) self.assertEqual(im.size, (16, 16))

View File

@ -60,8 +60,8 @@ class TestFileJpeg(PillowTestCase):
self.assertGreater(y, 0.8) self.assertGreater(y, 0.8)
self.assertEqual(k, 0.0) self.assertEqual(k, 0.0)
# the opposite corner is black # the opposite corner is black
c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1, c, m, y, k = [x / 255.0 for x in im.getpixel((
im.size[1]-1))] im.size[0]-1, im.size[1]-1))]
self.assertGreater(k, 0.9) self.assertGreater(k, 0.9)
# roundtrip, and check again # roundtrip, and check again
im = self.roundtrip(im) im = self.roundtrip(im)
@ -70,8 +70,8 @@ class TestFileJpeg(PillowTestCase):
self.assertGreater(m, 0.8) self.assertGreater(m, 0.8)
self.assertGreater(y, 0.8) self.assertGreater(y, 0.8)
self.assertEqual(k, 0.0) self.assertEqual(k, 0.0)
c, m, y, k = [x / 255.0 for x in im.getpixel((im.size[0]-1, c, m, y, k = [x / 255.0 for x in im.getpixel((
im.size[1]-1))] im.size[0]-1, im.size[1]-1))]
self.assertGreater(k, 0.9) self.assertGreater(k, 0.9)
def test_dpi(self): def test_dpi(self):
@ -152,8 +152,8 @@ class TestFileJpeg(PillowTestCase):
if py3: if py3:
a = bytes(random.randint(0, 255) for _ in range(256 * 256 * 3)) a = bytes(random.randint(0, 255) for _ in range(256 * 256 * 3))
else: else:
a = b''.join(chr(random.randint(0, 255)) for _ in a = b''.join(chr(random.randint(0, 255)) for _ in range(
range(256 * 256 * 3)) 256 * 256 * 3))
im = Image.frombuffer("RGB", (256, 256), a, "raw", "RGB", 0, 1) im = Image.frombuffer("RGB", (256, 256), a, "raw", "RGB", 0, 1)
# 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)
@ -224,9 +224,18 @@ class TestFileJpeg(PillowTestCase):
self.assertIsNone(im._getmp()) self.assertIsNone(im._getmp())
def test_quality_keep(self): def test_quality_keep(self):
# RGB
im = Image.open("Tests/images/lena.jpg") im = Image.open("Tests/images/lena.jpg")
f = self.tempfile('temp.jpg') f = self.tempfile('temp.jpg')
im.save(f, quality='keep') im.save(f, quality='keep')
# Grayscale
im = Image.open("Tests/images/lena_gray.jpg")
f = self.tempfile('temp.jpg')
im.save(f, quality='keep')
# CMYK
im = Image.open("Tests/images/pil_sample_cmyk.jpg")
f = self.tempfile('temp.jpg')
im.save(f, quality='keep')
def test_junk_jpeg_header(self): def test_junk_jpeg_header(self):
# https://github.com/python-pillow/Pillow/issues/630 # https://github.com/python-pillow/Pillow/issues/630
@ -279,11 +288,11 @@ class TestFileJpeg(PillowTestCase):
30) 30)
# dict of qtable lists # dict of qtable lists
self.assert_image_similar( self.assert_image_similar(im,
im, self.roundtrip( self.roundtrip(im,
im, qtables={ qtables={0: standard_l_qtable,
0: standard_l_qtable, 1: standard_chrominance_qtable}), 1: standard_chrominance_qtable}),
30) 30)
@unittest.skipUnless(djpeg_available(), "djpeg not available") @unittest.skipUnless(djpeg_available(), "djpeg not available")
def test_load_djpeg(self): def test_load_djpeg(self):
@ -300,6 +309,15 @@ class TestFileJpeg(PillowTestCase):
# Default save quality is 75%, so a tiny bit of difference is alright # Default save quality is 75%, so a tiny bit of difference is alright
self.assert_image_similar(img, Image.open(tempfile), 1) self.assert_image_similar(img, Image.open(tempfile), 1)
def test_no_duplicate_0x1001_tag(self):
# Arrange
from PIL import ExifTags
tag_ids = dict(zip(ExifTags.TAGS.values(), ExifTags.TAGS.keys()))
# Assert
self.assertEqual(tag_ids['RelatedImageWidth'], 0x1001)
self.assertEqual(tag_ids['RelatedImageLength'], 0x1002)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -4,8 +4,7 @@ import os
from PIL import Image, TiffImagePlugin from PIL import Image, TiffImagePlugin
class LibTiffTestCase(PillowTestCase):
class TestFileLibTiff(PillowTestCase):
def setUp(self): def setUp(self):
codecs = dir(Image.core) codecs = dir(Image.core)
@ -32,6 +31,8 @@ class TestFileLibTiff(PillowTestCase):
out = self.tempfile("temp.png") out = self.tempfile("temp.png")
im.save(out) im.save(out)
class TestFileLibTiff(LibTiffTestCase):
def test_g4_tiff(self): def test_g4_tiff(self):
"""Test the ordinary file path load path""" """Test the ordinary file path load path"""
@ -311,6 +312,35 @@ class TestFileLibTiff(PillowTestCase):
self.assertRaises(OSError, lambda: os.fstat(fn)) self.assertRaises(OSError, lambda: os.fstat(fn))
self.assertRaises(OSError, lambda: os.close(fn)) self.assertRaises(OSError, lambda: os.close(fn))
def test_multipage(self):
# issue #862
TiffImagePlugin.READ_LIBTIFF = True
im = Image.open('Tests/images/multipage.tiff')
# file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue
im.seek(0)
self.assertEqual(im.size, (10,10))
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,128,0))
self.assertTrue(im.tag.next)
im.seek(1)
self.assertEqual(im.size, (10,10))
self.assertEqual(im.convert('RGB').getpixel((0,0)), (255,0,0))
self.assertTrue(im.tag.next)
im.seek(2)
self.assertFalse(im.tag.next)
self.assertEqual(im.size, (20,20))
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255))
TiffImagePlugin.READ_LIBTIFF = False
def test__next(self):
TiffImagePlugin.READ_LIBTIFF = True
im = Image.open('Tests/images/lena.tif')
self.assertFalse(im.tag.next)
im.load()
self.assertFalse(im.tag.next)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -2,12 +2,10 @@ from helper import unittest
from PIL import Image from PIL import Image
from test_file_libtiff import TestFileLibTiff from test_file_libtiff import LibTiffTestCase
class TestFileLibTiffSmall(TestFileLibTiff): class TestFileLibTiffSmall(LibTiffTestCase):
# Inherits TestFileLibTiff's setUp() and self._assert_noerr()
""" The small lena image was failing on open in the libtiff """ The small lena image was failing on open in the libtiff
decoder because the file pointer was set to the wrong place decoder because the file pointer was set to the wrong place

View File

@ -1,4 +1,4 @@
from helper import unittest, PillowTestCase, lena from helper import unittest, PillowTestCase, hopper
from io import BytesIO from io import BytesIO
@ -10,8 +10,8 @@ codecs = dir(Image.core)
# sample png stream # sample png stream
file = "Tests/images/lena.png" TEST_PNG_FILE = "Tests/images/hopper.png"
data = open(file, "rb").read() TEST_DATA = open(TEST_PNG_FILE, "rb").read()
# stuff to create inline PNG images # stuff to create inline PNG images
@ -58,7 +58,7 @@ class TestFilePng(PillowTestCase):
file = self.tempfile("temp.png") file = self.tempfile("temp.png")
lena("RGB").save(file) hopper("RGB").save(file)
im = Image.open(file) im = Image.open(file)
im.load() im.load()
@ -66,19 +66,19 @@ class TestFilePng(PillowTestCase):
self.assertEqual(im.size, (128, 128)) self.assertEqual(im.size, (128, 128))
self.assertEqual(im.format, "PNG") self.assertEqual(im.format, "PNG")
lena("1").save(file) hopper("1").save(file)
im = Image.open(file) im = Image.open(file)
lena("L").save(file) hopper("L").save(file)
im = Image.open(file) im = Image.open(file)
lena("P").save(file) hopper("P").save(file)
im = Image.open(file) im = Image.open(file)
lena("RGB").save(file) hopper("RGB").save(file)
im = Image.open(file) im = Image.open(file)
lena("I").save(file) hopper("I").save(file)
im = Image.open(file) im = Image.open(file)
def test_broken(self): def test_broken(self):
@ -240,17 +240,17 @@ class TestFilePng(PillowTestCase):
def test_load_verify(self): def test_load_verify(self):
# Check open/load/verify exception (@PIL150) # Check open/load/verify exception (@PIL150)
im = Image.open("Tests/images/lena.png") im = Image.open(TEST_PNG_FILE)
im.verify() im.verify()
im = Image.open("Tests/images/lena.png") im = Image.open(TEST_PNG_FILE)
im.load() im.load()
self.assertRaises(RuntimeError, lambda: im.verify()) self.assertRaises(RuntimeError, lambda: im.verify())
def test_roundtrip_dpi(self): def test_roundtrip_dpi(self):
# Check dpi roundtripping # Check dpi roundtripping
im = Image.open(file) im = Image.open(TEST_PNG_FILE)
im = roundtrip(im, dpi=(100, 100)) im = roundtrip(im, dpi=(100, 100))
self.assertEqual(im.info["dpi"], (100, 100)) self.assertEqual(im.info["dpi"], (100, 100))
@ -258,7 +258,7 @@ class TestFilePng(PillowTestCase):
def test_roundtrip_text(self): def test_roundtrip_text(self):
# Check text roundtripping # Check text roundtripping
im = Image.open(file) im = Image.open(TEST_PNG_FILE)
info = PngImagePlugin.PngInfo() info = PngImagePlugin.PngInfo()
info.add_text("TXT", "VALUE") info.add_text("TXT", "VALUE")
@ -342,7 +342,7 @@ class TestFilePng(PillowTestCase):
def test_trns_p(self): def test_trns_p(self):
# Check writing a transparency of 0, issue #528 # Check writing a transparency of 0, issue #528
im = lena('P') im = hopper('P')
im.info['transparency'] = 0 im.info['transparency'] = 0
f = self.tempfile("temp.png") f = self.tempfile("temp.png")
@ -364,7 +364,7 @@ class TestFilePng(PillowTestCase):
def test_roundtrip_icc_profile(self): def test_roundtrip_icc_profile(self):
# check that we can roundtrip the icc profile # check that we can roundtrip the icc profile
im = lena('RGB') im = hopper('RGB')
jpeg_image = Image.open('Tests/images/flower2.jpg') jpeg_image = Image.open('Tests/images/flower2.jpg')
expected_icc = jpeg_image.info['icc_profile'] expected_icc = jpeg_image.info['icc_profile']

View File

@ -8,8 +8,8 @@ class TestFileSgi(PillowTestCase):
def test_rgb(self): def test_rgb(self):
# Arrange # Arrange
# Created with ImageMagick then renamed: # Created with ImageMagick then renamed:
# convert lena.ppm lena.sgi # convert hopper.ppm hopper.sgi
test_file = "Tests/images/lena.rgb" test_file = "Tests/images/hopper.rgb"
# Act / Assert # Act / Assert
self.assertRaises(ValueError, lambda: Image.open(test_file)) self.assertRaises(ValueError, lambda: Image.open(test_file))
@ -17,8 +17,8 @@ class TestFileSgi(PillowTestCase):
def test_l(self): def test_l(self):
# Arrange # Arrange
# Created with ImageMagick then renamed: # Created with ImageMagick then renamed:
# convert lena.ppm -monochrome lena.sgi # convert hopper.ppm -monochrome hopper.sgi
test_file = "Tests/images/lena.bw" test_file = "Tests/images/hopper.bw"
# Act / Assert # Act / Assert
self.assertRaises(ValueError, lambda: Image.open(test_file)) self.assertRaises(ValueError, lambda: Image.open(test_file))

View File

@ -1,9 +1,9 @@
from helper import unittest, PillowTestCase, lena from helper import unittest, PillowTestCase, hopper
from PIL import Image from PIL import Image
from PIL import SpiderImagePlugin from PIL import SpiderImagePlugin
TEST_FILE = "Tests/images/lena.spider" TEST_FILE = "Tests/images/hopper.spider"
class TestImageSpider(PillowTestCase): class TestImageSpider(PillowTestCase):
@ -18,7 +18,7 @@ class TestImageSpider(PillowTestCase):
def test_save(self): def test_save(self):
# Arrange # Arrange
temp = self.tempfile('temp.spider') temp = self.tempfile('temp.spider')
im = lena() im = hopper()
# Act # Act
im.save(temp, "SPIDER") im.save(temp, "SPIDER")
@ -44,7 +44,7 @@ class TestImageSpider(PillowTestCase):
def test_loadImageSeries(self): def test_loadImageSeries(self):
# Arrange # Arrange
not_spider_file = "Tests/images/lena.ppm" not_spider_file = "Tests/images/hopper.ppm"
file_list = [TEST_FILE, not_spider_file, "path/not_found.ext"] file_list = [TEST_FILE, not_spider_file, "path/not_found.ext"]
# Act # Act

View File

@ -7,8 +7,8 @@ class TestFileSun(PillowTestCase):
def test_sanity(self): def test_sanity(self):
# Arrange # Arrange
# Created with ImageMagick: convert lena.ppm lena.ras # Created with ImageMagick: convert hopper.jpg hopper.ras
test_file = "Tests/images/lena.ras" test_file = "Tests/images/hopper.ras"
# Act # Act
im = Image.open(test_file) im = Image.open(test_file)

View File

@ -4,8 +4,8 @@ from PIL import Image, TarIO
codecs = dir(Image.core) codecs = dir(Image.core)
# sample ppm stream # Sample tar archive
tarfile = "Tests/images/lena.tar" TEST_TAR_FILE = "Tests/images/hopper.tar"
class TestFileTar(PillowTestCase): class TestFileTar(PillowTestCase):
@ -16,7 +16,7 @@ class TestFileTar(PillowTestCase):
def test_sanity(self): def test_sanity(self):
if "zip_decoder" in codecs: if "zip_decoder" in codecs:
tar = TarIO.TarIO(tarfile, 'lena.png') tar = TarIO.TarIO(TEST_TAR_FILE, 'hopper.png')
im = Image.open(tar) im = Image.open(tar)
im.load() im.load()
self.assertEqual(im.mode, "RGB") self.assertEqual(im.mode, "RGB")
@ -24,7 +24,7 @@ class TestFileTar(PillowTestCase):
self.assertEqual(im.format, "PNG") self.assertEqual(im.format, "PNG")
if "jpeg_decoder" in codecs: if "jpeg_decoder" in codecs:
tar = TarIO.TarIO(tarfile, 'lena.jpg') tar = TarIO.TarIO(TEST_TAR_FILE, 'hopper.jpg')
im = Image.open(tar) im = Image.open(tar)
im.load() im.load()
self.assertEqual(im.mode, "RGB") self.assertEqual(im.mode, "RGB")

View File

@ -1,6 +1,6 @@
from helper import unittest, PillowTestCase, lena, py3 from helper import unittest, PillowTestCase, lena, py3
from PIL import Image from PIL import Image, TiffImagePlugin
class TestFileTiff(PillowTestCase): class TestFileTiff(PillowTestCase):
@ -141,6 +141,32 @@ class TestFileTiff(PillowTestCase):
self.assertEqual( self.assertEqual(
im.getextrema(), (-3.140936851501465, 3.140684127807617)) im.getextrema(), (-3.140936851501465, 3.140684127807617))
def test_multipage(self):
# issue #862
im = Image.open('Tests/images/multipage.tiff')
# file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue
im.seek(0)
self.assertEqual(im.size, (10,10))
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,128,0))
im.seek(1)
im.load()
self.assertEqual(im.size, (10,10))
self.assertEqual(im.convert('RGB').getpixel((0,0)), (255,0,0))
im.seek(2)
im.load()
self.assertEqual(im.size, (20,20))
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255))
def test_multipage_last_frame(self):
im = Image.open('Tests/images/multipage-lastframe.tif')
im.load()
self.assertEqual(im.size, (20,20))
self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255))
def test___str__(self): def test___str__(self):
# Arrange # Arrange
file = "Tests/images/pil136.tiff" file = "Tests/images/pil136.tiff"

View File

@ -8,7 +8,7 @@ tag_ids = dict(zip(TiffTags.TAGS.values(), TiffTags.TAGS.keys()))
class TestFileTiffMetadata(PillowTestCase): class TestFileTiffMetadata(PillowTestCase):
def test_rt_metadata(self): def test_rt_metadata(self):
""" Test writing arbitray metadata into the tiff image directory """ Test writing arbitrary metadata into the tiff image directory
Use case is ImageJ private tags, one numeric, one arbitrary Use case is ImageJ private tags, one numeric, one arbitrary
data. https://github.com/python-pillow/Pillow/issues/291 data. https://github.com/python-pillow/Pillow/issues/291
""" """
@ -87,6 +87,10 @@ class TestFileTiffMetadata(PillowTestCase):
self.assertEqual( self.assertEqual(
value, reloaded[tag], "%s didn't roundtrip" % tag) value, reloaded[tag], "%s didn't roundtrip" % tag)
def test_no_duplicate_50741_tag(self):
self.assertEqual(tag_ids['MakerNoteSafety'], 50741)
self.assertEqual(tag_ids['BestQualityScale'], 50780)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,6 +1,7 @@
from helper import unittest, PillowTestCase, lena from helper import unittest, PillowTestCase, hopper
from PIL import Image from PIL import Image
import sys
class TestImage(PillowTestCase): class TestImage(PillowTestCase):
@ -57,7 +58,7 @@ class TestImage(PillowTestCase):
def test_expand_x(self): def test_expand_x(self):
# Arrange # Arrange
im = lena() im = hopper()
orig_size = im.size orig_size = im.size
xmargin = 5 xmargin = 5
@ -70,7 +71,7 @@ class TestImage(PillowTestCase):
def test_expand_xy(self): def test_expand_xy(self):
# Arrange # Arrange
im = lena() im = hopper()
orig_size = im.size orig_size = im.size
xmargin = 5 xmargin = 5
ymargin = 3 ymargin = 3
@ -84,7 +85,7 @@ class TestImage(PillowTestCase):
def test_getbands(self): def test_getbands(self):
# Arrange # Arrange
im = lena() im = hopper()
# Act # Act
bands = im.getbands() bands = im.getbands()
@ -94,7 +95,7 @@ class TestImage(PillowTestCase):
def test_getbbox(self): def test_getbbox(self):
# Arrange # Arrange
im = lena() im = hopper()
# Act # Act
bbox = im.getbbox() bbox = im.getbbox()
@ -140,6 +141,60 @@ class TestImage(PillowTestCase):
img_colors = sorted(img.getcolors()) img_colors = sorted(img.getcolors())
self.assertEqual(img_colors, expected_colors) self.assertEqual(img_colors, expected_colors)
def test_effect_mandelbrot(self):
# Arrange
size = (512, 512)
extent = (-3, -2.5, 2, 2.5)
quality = 100
# Act
im = Image.effect_mandelbrot(size, extent, quality)
# Assert
self.assertEqual(im.size, (512, 512))
im2 = Image.open('Tests/images/effect_mandelbrot.png')
self.assert_image_equal(im, im2)
def test_effect_mandelbrot_bad_arguments(self):
# Arrange
size = (512, 512)
# Get coordinates the wrong way round:
extent = (+3, +2.5, -2, -2.5)
# Quality < 2:
quality = 1
# Act/Assert
self.assertRaises(
ValueError,
lambda: Image.effect_mandelbrot(size, extent, quality))
@unittest.skipUnless(sys.platform.startswith('win32'),
"Stalls on Travis CI, passes on Windows")
def test_effect_noise(self):
# Arrange
size = (100, 100)
sigma = 128
# Act
im = Image.effect_noise(size, sigma)
# Assert
self.assertEqual(im.size, (100, 100))
self.assertEqual(im.getpixel((0, 0)), 60)
self.assertEqual(im.getpixel((0, 1)), 28)
def test_effect_spread(self):
# Arrange
im = hopper()
distance = 10
# Act
im2 = im.effect_spread(distance)
# Assert
self.assertEqual(im.size, (128, 128))
im3 = Image.open('Tests/images/effect_spread.png')
self.assert_image_similar(im2, im3, 110)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,4 +1,4 @@
from helper import unittest, PillowTestCase, lena from helper import unittest, PillowTestCase, hopper
from PIL import Image from PIL import Image
@ -9,21 +9,21 @@ class TestImageLoad(PillowTestCase):
def test_sanity(self): def test_sanity(self):
im = lena() im = hopper()
pix = im.load() pix = im.load()
self.assertEqual(pix[0, 0], (223, 162, 133)) self.assertEqual(pix[0, 0], (20, 20, 70))
def test_close(self): def test_close(self):
im = Image.open("Tests/images/lena.gif") im = Image.open("Tests/images/hopper.gif")
im.close() im.close()
self.assertRaises(ValueError, lambda: im.load()) self.assertRaises(ValueError, lambda: im.load())
self.assertRaises(ValueError, lambda: im.getpixel((0, 0))) self.assertRaises(ValueError, lambda: im.getpixel((0, 0)))
def test_contextmanager(self): def test_contextmanager(self):
fn = None fn = None
with Image.open("Tests/images/lena.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
fn = im.fp.fileno() fn = im.fp.fileno()
os.fstat(fn) os.fstat(fn)

View File

@ -1,6 +1,6 @@
from helper import unittest, PillowTestCase, lena from helper import unittest, PillowTestCase, lena
from PIL import ImageSequence from PIL import Image, ImageSequence, TiffImagePlugin
class TestImageSequence(PillowTestCase): class TestImageSequence(PillowTestCase):
@ -22,7 +22,31 @@ class TestImageSequence(PillowTestCase):
self.assertEqual(index, 1) self.assertEqual(index, 1)
def _test_multipage_tiff(self, dbg=False):
# debug had side effect of calling fp.tell.
Image.DEBUG=dbg
im = Image.open('Tests/images/multipage.tiff')
for index, frame in enumerate(ImageSequence.Iterator(im)):
frame.load()
self.assertEqual(index, im.tell())
frame.convert('RGB')
Image.DEBUG=False
def test_tiff(self):
#self._test_multipage_tiff(True)
self._test_multipage_tiff(False)
def test_libtiff(self):
codecs = dir(Image.core)
if "libtiff_encoder" not in codecs or "libtiff_decoder" not in codecs:
self.skipTest("tiff support not available")
TiffImagePlugin.READ_LIBTIFF = True
#self._test_multipage_tiff(True)
self._test_multipage_tiff(False)
TiffImagePlugin.READ_LIBTIFF = False
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -6,8 +6,8 @@ import shutil
from PIL import Image, JpegImagePlugin, GifImagePlugin from PIL import Image, JpegImagePlugin, GifImagePlugin
test_jpg = "Tests/images/lena.jpg" TEST_JPG = "Tests/images/hopper.jpg"
test_gif = "Tests/images/lena.gif" TEST_GIF = "Tests/images/hopper.gif"
test_filenames = ( test_filenames = (
"temp_';", "temp_';",
@ -32,24 +32,24 @@ class TestShellInjection(PillowTestCase):
def test_load_djpeg_filename(self): def test_load_djpeg_filename(self):
for filename in test_filenames: for filename in test_filenames:
src_file = self.tempfile(filename) src_file = self.tempfile(filename)
shutil.copy(test_jpg, src_file) shutil.copy(TEST_JPG, src_file)
im = Image.open(src_file) im = Image.open(src_file)
im.load_djpeg() im.load_djpeg()
@unittest.skipUnless(cjpeg_available(), "cjpeg not available") @unittest.skipUnless(cjpeg_available(), "cjpeg not available")
def test_save_cjpeg_filename(self): def test_save_cjpeg_filename(self):
im = Image.open(test_jpg) im = Image.open(TEST_JPG)
self.assert_save_filename_check(im, JpegImagePlugin._save_cjpeg) self.assert_save_filename_check(im, JpegImagePlugin._save_cjpeg)
@unittest.skipUnless(netpbm_available(), "netpbm not available") @unittest.skipUnless(netpbm_available(), "netpbm not available")
def test_save_netpbm_filename_bmp_mode(self): def test_save_netpbm_filename_bmp_mode(self):
im = Image.open(test_gif).convert("RGB") im = Image.open(TEST_GIF).convert("RGB")
self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) self.assert_save_filename_check(im, GifImagePlugin._save_netpbm)
@unittest.skipUnless(netpbm_available(), "netpbm not available") @unittest.skipUnless(netpbm_available(), "netpbm not available")
def test_save_netpbm_filename_l_mode(self): def test_save_netpbm_filename_l_mode(self):
im = Image.open(test_gif).convert("L") im = Image.open(TEST_GIF).convert("L")
self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) self.assert_save_filename_check(im, GifImagePlugin._save_netpbm)

View File

@ -103,6 +103,8 @@ PyImaging_DecoderNew(int contextsize)
static void static void
_dealloc(ImagingDecoderObject* decoder) _dealloc(ImagingDecoderObject* decoder)
{ {
if (decoder->cleanup)
decoder->cleanup(&decoder->state);
free(decoder->state.buffer); free(decoder->state.buffer);
free(decoder->state.context); free(decoder->state.context);
Py_XDECREF(decoder->lock); Py_XDECREF(decoder->lock);
@ -442,8 +444,9 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args)
char* rawmode; char* rawmode;
char* compname; char* compname;
int fp; int fp;
int ifdoffset;
if (! PyArg_ParseTuple(args, "sssi", &mode, &rawmode, &compname, &fp)) if (! PyArg_ParseTuple(args, "sssii", &mode, &rawmode, &compname, &fp, &ifdoffset))
return NULL; return NULL;
TRACE(("new tiff decoder %s\n", compname)); TRACE(("new tiff decoder %s\n", compname));
@ -455,7 +458,7 @@ PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args)
if (get_unpacker(decoder, mode, rawmode) < 0) if (get_unpacker(decoder, mode, rawmode) < 0)
return NULL; return NULL;
if (! ImagingLibTiffInit(&decoder->state, fp)) { if (! ImagingLibTiffInit(&decoder->state, fp, ifdoffset)) {
Py_DECREF(decoder); Py_DECREF(decoder);
PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed"); PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed");
return NULL; return NULL;

View File

@ -11,9 +11,9 @@ rm -r openjpeg-2.1.0
tar -xvzf openjpeg-2.1.0.tar.gz tar -xvzf openjpeg-2.1.0.tar.gz
pushd openjpeg-2.1.0 pushd openjpeg-2.1.0
cmake -DCMAKE_INSTALL_PREFIX=/usr . && make && sudo make install cmake -DCMAKE_INSTALL_PREFIX=/usr . && make -j4 && sudo make -j4 install
popd popd

View File

@ -1,18 +1,15 @@
#!/bin/bash #!/bin/bash
# install webp # install webp
if [ ! -f libwebp-0.4.1.tar.gz ]; then
if [ ! -f libwebp-0.4.0.tar.gz ]; then wget 'http://downloads.webmproject.org/releases/webp/libwebp-0.4.1.tar.gz'
wget 'https://webp.googlecode.com/files/libwebp-0.4.0.tar.gz'
fi fi
rm -r libwebp-0.4.0 rm -r libwebp-0.4.1
tar -xvzf libwebp-0.4.0.tar.gz tar -xvzf libwebp-0.4.1.tar.gz
pushd libwebp-0.4.1
pushd libwebp-0.4.0 ./configure --prefix=/usr --enable-libwebpmux --enable-libwebpdemux && make -j4 && sudo make -j4 install
./configure --prefix=/usr --enable-libwebpmux --enable-libwebpdemux && make && sudo make install
popd popd

View File

@ -670,6 +670,7 @@ files, using either JPEG or HEX encoding depending on the image mode (and
whether JPEG support is available or not). whether JPEG support is available or not).
PIXAR (read only) PIXAR (read only)
^^^^
PIL provides limited support for PIXAR raster files. The library can identify PIL provides limited support for PIXAR raster files. The library can identify
and read “dumped” RGB files. and read “dumped” RGB files.

View File

@ -99,6 +99,18 @@ _dealloc(ImagingEncoderObject* encoder)
PyObject_Del(encoder); PyObject_Del(encoder);
} }
static PyObject*
_encode_cleanup(ImagingEncoderObject* encoder, PyObject* args)
{
int status = 0;
if (encoder->cleanup){
status = encoder->cleanup(&encoder->state);
}
return Py_BuildValue("i", status);
}
static PyObject* static PyObject*
_encode(ImagingEncoderObject* encoder, PyObject* args) _encode(ImagingEncoderObject* encoder, PyObject* args)
{ {
@ -239,6 +251,7 @@ _setimage(ImagingEncoderObject* encoder, PyObject* args)
static struct PyMethodDef methods[] = { static struct PyMethodDef methods[] = {
{"encode", (PyCFunction)_encode, 1}, {"encode", (PyCFunction)_encode, 1},
{"cleanup", (PyCFunction)_encode_cleanup, 1},
{"encode_to_file", (PyCFunction)_encode_to_file, 1}, {"encode_to_file", (PyCFunction)_encode_to_file, 1},
{"setimage", (PyCFunction)_setimage, 1}, {"setimage", (PyCFunction)_setimage, 1},
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
@ -540,8 +553,8 @@ static unsigned int** get_qtables_arrays(PyObject* qtables) {
tables = PySequence_Fast(qtables, "expected a sequence"); tables = PySequence_Fast(qtables, "expected a sequence");
num_tables = PySequence_Size(qtables); num_tables = PySequence_Size(qtables);
if (num_tables < 2 || num_tables > NUM_QUANT_TBLS) { if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) {
PyErr_SetString(PyExc_ValueError, "Not a valid numbers of quantization tables. Should be between 2 and 4."); PyErr_SetString(PyExc_ValueError, "Not a valid numbers of quantization tables. Should be between 1 and 4.");
return NULL; return NULL;
} }
qarrays = (unsigned int**) PyMem_Malloc(num_tables * sizeof(unsigned int*)); qarrays = (unsigned int**) PyMem_Malloc(num_tables * sizeof(unsigned int*));
@ -760,7 +773,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args)
(ttag_t) PyInt_AsLong(key), (ttag_t) PyInt_AsLong(key),
intav); intav);
free(intav); free(intav);
} }
} else { } else {
TRACE((" %d elements, setting as floats \n", len)); TRACE((" %d elements, setting as floats \n", len));
floatav = malloc(sizeof(float)*len); floatav = malloc(sizeof(float)*len);
@ -903,7 +916,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
j2k_decode_coord_tuple(tile_offset, j2k_decode_coord_tuple(tile_offset,
&context->tile_offset_x, &context->tile_offset_x,
&context->tile_offset_y); &context->tile_offset_y);
j2k_decode_coord_tuple(tile_size, j2k_decode_coord_tuple(tile_size,
&context->tile_size_x, &context->tile_size_x,
&context->tile_size_y); &context->tile_size_y);
@ -918,7 +931,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args)
if (context->tile_offset_x > context->offset_x if (context->tile_offset_x > context->offset_x
|| context->tile_offset_y > context->offset_y) { || context->tile_offset_y > context->offset_y) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"JPEG 2000 tile offset too large to cover image area"); "JPEG 2000 tile offset too large to cover image area");
Py_DECREF(encoder); Py_DECREF(encoder);
return NULL; return NULL;

View File

@ -49,6 +49,12 @@
#define L(rgb)\ #define L(rgb)\
((INT32) (rgb)[0]*299 + (INT32) (rgb)[1]*587 + (INT32) (rgb)[2]*114) ((INT32) (rgb)[0]*299 + (INT32) (rgb)[1]*587 + (INT32) (rgb)[2]*114)
#ifndef round
double round(double x) {
return floor(x+0.5);
}
#endif
/* ------------------- */ /* ------------------- */
/* 1 (bit) conversions */ /* 1 (bit) conversions */
/* ------------------- */ /* ------------------- */

View File

@ -48,25 +48,25 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality)
for (y = 0; y < ysize; y++) { for (y = 0; y < ysize; y++) {
UINT8* buf = im->image8[y]; UINT8* buf = im->image8[y];
for (x = 0; x < xsize; x++) { for (x = 0; x < xsize; x++) {
x1 = y1 = xi2 = yi2 = 0.0; x1 = y1 = xi2 = yi2 = 0.0;
cr = x*dr + extent[0]; cr = x*dr + extent[0];
ci = y*di + extent[1]; ci = y*di + extent[1];
for (k = 1;; k++) { for (k = 1;; k++) {
y1 = 2*x1*y1 + ci; y1 = 2*x1*y1 + ci;
x1 = xi2 - yi2 + cr; x1 = xi2 - yi2 + cr;
xi2 = x1*x1; xi2 = x1*x1;
yi2 = y1*y1; yi2 = y1*y1;
if ((xi2 + yi2) > radius) { if ((xi2 + yi2) > radius) {
buf[x] = k*255/quality; buf[x] = k*255/quality;
break; break;
} }
if (k > quality) { if (k > quality) {
buf[x] = 0; buf[x] = 0;
break; break;
} }
} }
} }
} }
return im; return im;
} }
@ -74,7 +74,7 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality)
Imaging Imaging
ImagingEffectNoise(int xsize, int ysize, float sigma) ImagingEffectNoise(int xsize, int ysize, float sigma)
{ {
/* Generate gaussian noise centered around 128 */ /* Generate Gaussian noise centered around 128 */
Imaging imOut; Imaging imOut;
int x, y; int x, y;
@ -83,19 +83,19 @@ ImagingEffectNoise(int xsize, int ysize, float sigma)
imOut = ImagingNew("L", xsize, ysize); imOut = ImagingNew("L", xsize, ysize);
if (!imOut) if (!imOut)
return NULL; return NULL;
next = 0.0; next = 0.0;
nextok = 0; nextok = 0;
for (y = 0; y < imOut->ysize; y++) { for (y = 0; y < imOut->ysize; y++) {
UINT8* out = imOut->image8[y]; UINT8* out = imOut->image8[y];
for (x = 0; x < imOut->xsize; x++) { for (x = 0; x < imOut->xsize; x++) {
if (nextok) { if (nextok) {
this = next; this = next;
nextok = 0; nextok = 0;
} else { } else {
/* after numerical recepies */ /* after numerical recipes */
double v1, v2, radius, factor; double v1, v2, radius, factor;
do { do {
v1 = rand()*(2.0/32767.0) - 1.0; v1 = rand()*(2.0/32767.0) - 1.0;
@ -113,14 +113,6 @@ ImagingEffectNoise(int xsize, int ysize, float sigma)
return imOut; return imOut;
} }
Imaging
ImagingEffectPerlinTurbulence(int xsize, int ysize)
{
/* Perlin turbulence (In progress) */
return NULL;
}
Imaging Imaging
ImagingEffectSpread(Imaging imIn, int distance) ImagingEffectSpread(Imaging imIn, int distance)
{ {
@ -132,11 +124,11 @@ ImagingEffectSpread(Imaging imIn, int distance)
imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize); imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize);
if (!imOut) if (!imOut)
return NULL; return NULL;
#define SPREAD(type, image)\ #define SPREAD(type, image)\
for (y = 0; y < imIn->ysize; y++)\ for (y = 0; y < imIn->ysize; y++)\
for (x = 0; x < imIn->xsize; x++) {\ for (x = 0; x < imIn->xsize; x++) {\
int xx = x + (rand() % distance) - distance/2;\ int xx = x + (rand() % distance) - distance/2;\
int yy = y + (rand() % distance) - distance/2;\ int yy = y + (rand() % distance) - distance/2;\
if (xx >= 0 && xx < imIn->xsize && yy >= 0 && yy < imIn->ysize) {\ if (xx >= 0 && xx < imIn->xsize && yy >= 0 && yy < imIn->ysize) {\
@ -147,9 +139,9 @@ ImagingEffectSpread(Imaging imIn, int distance)
} }
if (imIn->image8) { if (imIn->image8) {
SPREAD(UINT8, image8); SPREAD(UINT8, image8);
} else { } else {
SPREAD(INT32, image32); SPREAD(INT32, image32);
} }
ImagingCopyInfo(imOut, imIn); ImagingCopyInfo(imOut, imIn);
@ -157,217 +149,4 @@ ImagingEffectSpread(Imaging imIn, int distance)
return imOut; return imOut;
} }
/* -------------------------------------------------------------------- */ // End of file
/* Taken from the "C" code in the W3C SVG specification. Translated
to C89 by Fredrik Lundh */
#if 0
/* Produces results in the range [1, 2**31 - 2].
Algorithm is: r = (a * r) mod m
where a = 16807 and m = 2**31 - 1 = 2147483647
See [Park & Miller], CACM vol. 31 no. 10 p. 1195, Oct. 1988
To test: the algorithm should produce the result 1043618065
as the 10,000th generated number if the original seed is 1.
*/
#define RAND_m 2147483647 /* 2**31 - 1 */
#define RAND_a 16807 /* 7**5; primitive root of m */
#define RAND_q 127773 /* m / a */
#define RAND_r 2836 /* m % a */
static long
perlin_setup_seed(long lSeed)
{
if (lSeed <= 0) lSeed = -(lSeed % (RAND_m - 1)) + 1;
if (lSeed > RAND_m - 1) lSeed = RAND_m - 1;
return lSeed;
}
static long
perlin_random(long lSeed)
{
long result;
result = RAND_a * (lSeed % RAND_q) - RAND_r * (lSeed / RAND_q);
if (result <= 0) result += RAND_m;
return result;
}
#define BSize 0x100
#define BM 0xff
#define PerlinN 0x1000
#define NP 12 /* 2^PerlinN */
#define NM 0xfff
static int perlin_uLatticeSelector[BSize + BSize + 2];
static double perlin_fGradient[4][BSize + BSize + 2][2];
typedef struct
{
int nWidth; /* How much to subtract to wrap for stitching. */
int nHeight;
int nWrapX; /* Minimum value to wrap. */
int nWrapY;
} StitchInfo;
static void
perlin_init(long lSeed)
{
double s;
int i, j, k;
lSeed = perlin_setup_seed(lSeed);
for(k = 0; k < 4; k++)
{
for(i = 0; i < BSize; i++)
{
perlin_uLatticeSelector[i] = i;
for (j = 0; j < 2; j++)
perlin_fGradient[k][i][j] = (double)(((lSeed = perlin_random(lSeed)) % (BSize + BSize)) - BSize) / BSize;
s = (double) (sqrt(perlin_fGradient[k][i][0] * perlin_fGradient[k][i][0] + perlin_fGradient[k][i][1] * perlin_fGradient[k][i][1]));
perlin_fGradient[k][i][0] /= s;
perlin_fGradient[k][i][1] /= s;
}
}
while(--i)
{
k = perlin_uLatticeSelector[i];
perlin_uLatticeSelector[i] = perlin_uLatticeSelector[j = (lSeed = perlin_random(lSeed)) % BSize];
perlin_uLatticeSelector[j] = k;
}
for(i = 0; i < BSize + 2; i++)
{
perlin_uLatticeSelector[BSize + i] = perlin_uLatticeSelector[i];
for(k = 0; k < 4; k++)
for(j = 0; j < 2; j++)
perlin_fGradient[k][BSize + i][j] = perlin_fGradient[k][i][j];
}
}
#define s_curve(t) ( t * t * (3. - 2. * t) )
#define lerp(t, a, b) ( a + t * (b - a) )
static double
perlin_noise2(int nColorChannel, double vec[2], StitchInfo *pStitchInfo)
{
int bx0, bx1, by0, by1, b00, b10, b01, b11;
double rx0, rx1, ry0, ry1, *q, sx, sy, a, b, t, u, v;
register int i, j;
t = vec[0] + (double) PerlinN;
bx0 = (int)t;
bx1 = bx0+1;
rx0 = t - (int)t;
rx1 = rx0 - 1.0f;
t = vec[1] + (double) PerlinN;
by0 = (int)t;
by1 = by0+1;
ry0 = t - (int)t;
ry1 = ry0 - 1.0f;
/* If stitching, adjust lattice points accordingly. */
if(pStitchInfo != NULL)
{
if(bx0 >= pStitchInfo->nWrapX)
bx0 -= pStitchInfo->nWidth;
if(bx1 >= pStitchInfo->nWrapX)
bx1 -= pStitchInfo->nWidth;
if(by0 >= pStitchInfo->nWrapY)
by0 -= pStitchInfo->nHeight;
if(by1 >= pStitchInfo->nWrapY)
by1 -= pStitchInfo->nHeight;
}
bx0 &= BM;
bx1 &= BM;
by0 &= BM;
by1 &= BM;
i = perlin_uLatticeSelector[bx0];
j = perlin_uLatticeSelector[bx1];
b00 = perlin_uLatticeSelector[i + by0];
b10 = perlin_uLatticeSelector[j + by0];
b01 = perlin_uLatticeSelector[i + by1];
b11 = perlin_uLatticeSelector[j + by1];
sx = (double) (s_curve(rx0));
sy = (double) (s_curve(ry0));
q = perlin_fGradient[nColorChannel][b00]; u = rx0 * q[0] + ry0 * q[1];
q = perlin_fGradient[nColorChannel][b10]; v = rx1 * q[0] + ry0 * q[1];
a = lerp(sx, u, v);
q = perlin_fGradient[nColorChannel][b01]; u = rx0 * q[0] + ry1 * q[1];
q = perlin_fGradient[nColorChannel][b11]; v = rx1 * q[0] + ry1 * q[1];
b = lerp(sx, u, v);
return lerp(sy, a, b);
}
double
perlin_turbulence(
int nColorChannel, double *point, double fBaseFreqX, double fBaseFreqY,
int nNumOctaves, int bFractalSum, int bDoStitching,
double fTileX, double fTileY, double fTileWidth, double fTileHeight)
{
StitchInfo stitch;
StitchInfo *pStitchInfo = NULL; /* Not stitching when NULL. */
double fSum = 0.0f;
double vec[2];
double ratio = 1;
int nOctave;
vec[0] = point[0] * fBaseFreqX;
vec[1] = point[1] * fBaseFreqY;
/* Adjust the base frequencies if necessary for stitching. */
if(bDoStitching)
{
/* When stitching tiled turbulence, the frequencies must be adjusted */
/* so that the tile borders will be continuous. */
if(fBaseFreqX != 0.0)
{
double fLoFreq = (double) (floor(fTileWidth * fBaseFreqX)) / fTileWidth;
double fHiFreq = (double) (ceil(fTileWidth * fBaseFreqX)) / fTileWidth;
if(fBaseFreqX / fLoFreq < fHiFreq / fBaseFreqX)
fBaseFreqX = fLoFreq;
else
fBaseFreqX = fHiFreq;
}
if(fBaseFreqY != 0.0)
{
double fLoFreq = (double) (floor(fTileHeight * fBaseFreqY)) / fTileHeight;
double fHiFreq = (double) (ceil(fTileHeight * fBaseFreqY)) / fTileHeight;
if(fBaseFreqY / fLoFreq < fHiFreq / fBaseFreqY)
fBaseFreqY = fLoFreq;
else
fBaseFreqY = fHiFreq;
}
/* Set up initial stitch values. */
pStitchInfo = &stitch;
stitch.nWidth = (int) (fTileWidth * fBaseFreqX + 0.5f);
stitch.nWrapX = (int) (fTileX * fBaseFreqX + PerlinN + stitch.nWidth);
stitch.nHeight = (int) (fTileHeight * fBaseFreqY + 0.5f);
stitch.nWrapY = (int) (fTileY * fBaseFreqY + PerlinN + stitch.nHeight);
}
for(nOctave = 0; nOctave < nNumOctaves; nOctave++)
{
if(bFractalSum)
fSum += (double) (perlin_noise2(nColorChannel, vec, pStitchInfo) / ratio);
else
fSum += (double) (fabs(perlin_noise2(nColorChannel, vec, pStitchInfo)) / ratio);
vec[0] *= 2;
vec[1] *= 2;
ratio *= 2;
if(pStitchInfo != NULL)
{
/* Update stitch values. Subtracting PerlinN before the multiplication and */
/* adding it afterward simplifies to subtracting it once. */
stitch.nWidth *= 2;
stitch.nWrapX = 2 * stitch.nWrapX - PerlinN;
stitch.nHeight *= 2;
stitch.nWrapY = 2 * stitch.nWrapY - PerlinN;
}
}
return fSum;
}
#endif

View File

@ -800,6 +800,11 @@ ImagingJpeg2KDecodeCleanup(ImagingCodecState state) {
if (context->decoder) if (context->decoder)
ImagingIncrementalCodecDestroy(context->decoder); ImagingIncrementalCodecDestroy(context->decoder);
context->error_msg = NULL;
/* Prevent multiple calls to ImagingIncrementalCodecDestroy */
context->decoder = NULL;
return -1; return -1;
} }

7
libImaging/Jpeg2KEncode.c Normal file → Executable file
View File

@ -576,15 +576,20 @@ int
ImagingJpeg2KEncodeCleanup(ImagingCodecState state) { ImagingJpeg2KEncodeCleanup(ImagingCodecState state) {
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context; JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
if (context->quality_layers) if (context->quality_layers && context->encoder)
Py_DECREF(context->quality_layers); Py_DECREF(context->quality_layers);
if (context->error_msg) if (context->error_msg)
free ((void *)context->error_msg); free ((void *)context->error_msg);
context->error_msg = NULL;
if (context->encoder) if (context->encoder)
ImagingIncrementalCodecDestroy(context->encoder); ImagingIncrementalCodecDestroy(context->encoder);
/* Prevent multiple calls to ImagingIncrementalCodecDestroy */
context->encoder = NULL;
return -1; return -1;
} }

View File

@ -21,8 +21,8 @@
#include "TiffDecode.h" #include "TiffDecode.h"
void dump_state(const TIFFSTATE *state){ void dump_state(const TIFFSTATE *state){
TRACE(("State: Location %u size %d eof %d data: %p \n", (uint)state->loc, TRACE(("State: Location %u size %d eof %d data: %p ifd: %d\n", (uint)state->loc,
(int)state->size, (uint)state->eof, state->data)); (int)state->size, (uint)state->eof, state->data, state->ifd));
} }
/* /*
@ -142,7 +142,7 @@ void _tiffUnmapProc(thandle_t hdata, tdata_t base, toff_t size) {
(void) hdata; (void) base; (void) size; (void) hdata; (void) base; (void) size;
} }
int ImagingLibTiffInit(ImagingCodecState state, int fp) { int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset) {
TIFFSTATE *clientstate = (TIFFSTATE *)state->context; TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
TRACE(("initing libtiff\n")); TRACE(("initing libtiff\n"));
@ -158,6 +158,7 @@ int ImagingLibTiffInit(ImagingCodecState state, int fp) {
clientstate->size = 0; clientstate->size = 0;
clientstate->data = 0; clientstate->data = 0;
clientstate->fp = fp; clientstate->fp = fp;
clientstate->ifd = offset;
clientstate->eof = 0; clientstate->eof = 0;
return 1; return 1;
@ -195,7 +196,6 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
clientstate->loc = 0; clientstate->loc = 0;
clientstate->data = (tdata_t)buffer; clientstate->data = (tdata_t)buffer;
clientstate->flrealloc = 0; clientstate->flrealloc = 0;
dump_state(clientstate); dump_state(clientstate);
TIFFSetWarningHandler(NULL); TIFFSetWarningHandler(NULL);
@ -220,6 +220,16 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
return -1; return -1;
} }
if (clientstate->ifd){
unsigned int ifdoffset = clientstate->ifd;
TRACE(("reading tiff ifd %d\n", ifdoffset));
int rv = TIFFSetSubDirectory(tiff, ifdoffset);
if (!rv){
TRACE(("error in TIFFSetSubDirectory"));
return -1;
}
}
size = TIFFScanlineSize(tiff); size = TIFFScanlineSize(tiff);
TRACE(("ScanlineSize: %d \n", size)); TRACE(("ScanlineSize: %d \n", size));
if (size > state->bytes) { if (size > state->bytes) {

View File

@ -26,6 +26,7 @@ typedef struct {
toff_t loc; /* toff_t == uint32 */ toff_t loc; /* toff_t == uint32 */
tsize_t size; /* tsize_t == int32 */ tsize_t size; /* tsize_t == int32 */
int fp; int fp;
int ifd; /* offset of the ifd, used for multipage */
TIFF *tiff; /* Used in write */ TIFF *tiff; /* Used in write */
toff_t eof; toff_t eof;
int flrealloc; /* may we realloc */ int flrealloc; /* may we realloc */
@ -33,7 +34,7 @@ typedef struct {
extern int ImagingLibTiffInit(ImagingCodecState state, int fp); extern int ImagingLibTiffInit(ImagingCodecState state, int fp, int offset);
extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp); extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp);
extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...);
@ -50,5 +51,4 @@ extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...);
#define TRACE(args) #define TRACE(args)
#endif #endif

View File

@ -3,7 +3,7 @@
from multiprocessing import Pool, cpu_count from multiprocessing import Pool, cpu_count
from distutils.ccompiler import CCompiler from distutils.ccompiler import CCompiler
import os import os, sys
try: try:
MAX_PROCS = int(os.environ.get('MAX_CONCURRENCY', cpu_count())) MAX_PROCS = int(os.environ.get('MAX_CONCURRENCY', cpu_count()))
@ -50,7 +50,7 @@ def _mp_compile(self, sources, output_dir=None, macros=None,
return objects return objects
# explicitly don't enable if environment says 1 processor # explicitly don't enable if environment says 1 processor
if MAX_PROCS != 1: if MAX_PROCS != 1 and not sys.platform.startswith('win'):
try: try:
# bug, only enable if we can make a Pool. see issue #790 and # bug, only enable if we can make a Pool. see issue #790 and
# http://stackoverflow.com/questions/6033599/oserror-38-errno-38-with-multiprocessing # http://stackoverflow.com/questions/6033599/oserror-38-errno-38-with-multiprocessing

View File

@ -49,13 +49,13 @@ def testimage():
Or open existing files: Or open existing files:
>>> im = Image.open(os.path.join(ROOT, "Tests/images/lena.gif")) >>> im = Image.open(os.path.join(ROOT, "Tests/images/hopper.gif"))
>>> _info(im) >>> _info(im)
('GIF', 'P', (128, 128)) ('GIF', 'P', (128, 128))
>>> _info(Image.open(os.path.join(ROOT, "Tests/images/lena.ppm"))) >>> _info(Image.open(os.path.join(ROOT, "Tests/images/hopper.ppm")))
('PPM', 'RGB', (128, 128)) ('PPM', 'RGB', (128, 128))
>>> try: >>> try:
... _info(Image.open(os.path.join(ROOT, "Tests/images/lena.jpg"))) ... _info(Image.open(os.path.join(ROOT, "Tests/images/hopper.jpg")))
... except IOError as v: ... except IOError as v:
... print(v) ... print(v)
('JPEG', 'RGB', (128, 128)) ('JPEG', 'RGB', (128, 128))
@ -63,7 +63,7 @@ def testimage():
PIL doesn't actually load the image data until it's needed, PIL doesn't actually load the image data until it's needed,
or you call the "load" method: or you call the "load" method:
>>> im = Image.open(os.path.join(ROOT, "Tests/images/lena.ppm")) >>> im = Image.open(os.path.join(ROOT, "Tests/images/hopper.ppm"))
>>> print(im.im) # internal image attribute >>> print(im.im) # internal image attribute
None None
>>> a = im.load() >>> a = im.load()
@ -73,7 +73,7 @@ def testimage():
You can apply many different operations on images. Most You can apply many different operations on images. Most
operations return a new image: operations return a new image:
>>> im = Image.open(os.path.join(ROOT, "Tests/images/lena.ppm")) >>> im = Image.open(os.path.join(ROOT, "Tests/images/hopper.ppm"))
>>> _info(im.convert("L")) >>> _info(im.convert("L"))
(None, 'L', (128, 128)) (None, 'L', (128, 128))
>>> _info(im.copy()) >>> _info(im.copy())
@ -89,9 +89,9 @@ def testimage():
>>> len(im.getdata()) >>> len(im.getdata())
16384 16384
>>> im.getextrema() >>> im.getextrema()
((61, 255), (26, 234), (44, 223)) ((0, 255), (0, 255), (0, 255))
>>> im.getpixel((0, 0)) >>> im.getpixel((0, 0))
(223, 162, 133) (20, 20, 70)
>>> len(im.getprojection()) >>> len(im.getprojection())
2 2
>>> len(im.histogram()) >>> len(im.histogram())

View File

@ -487,6 +487,8 @@ class pil_build_ext(build_ext):
# In Google's precompiled zip it is call "libwebp": # In Google's precompiled zip it is call "libwebp":
if _find_library_file(self, "webp"): if _find_library_file(self, "webp"):
feature.webp = "webp" feature.webp = "webp"
elif _find_library_file(self, "libwebp"):
feature.webp = "libwebp"
if feature.want('webpmux'): if feature.want('webpmux'):
if (_find_include_file(self, "webp/mux.h") and if (_find_include_file(self, "webp/mux.h") and
@ -494,6 +496,9 @@ class pil_build_ext(build_ext):
if (_find_library_file(self, "webpmux") and if (_find_library_file(self, "webpmux") and
_find_library_file(self, "webpdemux")): _find_library_file(self, "webpdemux")):
feature.webpmux = "webpmux" feature.webpmux = "webpmux"
if (_find_library_file(self, "libwebpmux") and
_find_library_file(self, "libwebpdemux")):
feature.webpmux = "libwebpmux"
for f in feature: for f in feature:
if not getattr(feature, f) and feature.require(f): if not getattr(feature, f) and feature.require(f):
@ -559,13 +564,13 @@ class pil_build_ext(build_ext):
libraries=["lcms2"] + extra)) libraries=["lcms2"] + extra))
if os.path.isfile("_webp.c") and feature.webp: if os.path.isfile("_webp.c") and feature.webp:
libs = ["webp"] libs = [feature.webp]
defs = [] defs = []
if feature.webpmux: if feature.webpmux:
defs.append(("HAVE_WEBPMUX", None)) defs.append(("HAVE_WEBPMUX", None))
libs.append("webpmux") libs.append(feature.webpmux)
libs.append("webpdemux") libs.append(feature.webpmux.replace('pmux','pdemux'))
exts.append(Extension( exts.append(Extension(
"PIL._webp", ["_webp.c"], libraries=libs, define_macros=defs)) "PIL._webp", ["_webp.c"], libraries=libs, define_macros=defs))