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

This commit is contained in:
Hugo 2014-09-22 10:34:14 +03:00
commit 0ccc445ebf
24 changed files with 398 additions and 168 deletions

View File

@ -16,14 +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" - "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 coverage nose" - "travis_retry pip install coverage nose"
# 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
@ -37,26 +37,24 @@ script:
- CFLAGS="-coverage" python setup.py build_ext --inplace - CFLAGS="-coverage" python setup.py build_ext --inplace
- coverage run --append --include=PIL/* selftest.py - coverage run --append --include=PIL/* selftest.py
# FIXME: re-add -x option to fail fast - coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
# - coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
- coverage run --append --include=PIL/* -m nose -v Tests/test_*.py
after_success: after_success:
# gather the coverage data # gather the coverage data
- sudo apt-get -qq install lcov - 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
- gem install coveralls-lcov - 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
- pip install coveralls-merge - 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 PIL/*.py - pep8 --statistics --count PIL/*.py
- pep8 --statistics --count Tests/*.py - pep8 --statistics --count Tests/*.py
- pyflakes PIL/*.py | tee >(wc -l) - pyflakes PIL/*.py | tee >(wc -l)
@ -67,3 +65,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,10 +4,31 @@ Changelog (Pillow)
2.6.0 (unreleased) 2.6.0 (unreleased)
------------------ ------------------
- Fix for reading multipage TIFFs #885 - On Windows, do not execute convert.exe without specifying path #912
[kostrom, wiredfool] [cgohlke]
- Correctly handle saving gray and CMYK JPEGs with quality-keep #857 - Fix msvc build error #911
[cgohlke]
- Fix for handling P + transparency -> RGBA conversions #904
[wiredfool]
- Retain alpha in ImageEnhance operations #909
[wiredfool]
- Jpeg2k Decode/encode memory leak fix #898
[joshware, wiredfool]
- EpsFilePlugin Speed improvements #886
[wiredfool, karstenw]
- Don't resize if already the right size #892
[radarhere]
- Fix for reading multipage TIFFs #885
[kostrom, wiredfool]
- Correctly handle saving gray and CMYK JPEGs with quality=keep #857
[etienned] [etienned]
- Correct duplicate Tiff Metadata and Exif tag values - Correct duplicate Tiff Metadata and Exif tag values

View File

@ -7,7 +7,7 @@ Send a pull request. We'll generally want documentation and [tests](Tests/README
- Fork the repo - Fork the repo
- Make a branch - Make a branch
- Add your changes + Tests - Add your changes + Tests
- Run the test suite. Try to run on both Python 2.x and 3.x, or you'll get tripped up. You can enable [Travis CI on your repo](https://travis-ci.org/profile/) to catch test failures prior to the pull request. - Run the test suite. Try to run on both Python 2.x and 3.x, or you'll get tripped up. You can enable [Travis CI on your repo](https://travis-ci.org/profile/) to catch test failures prior to the pull request, and [Coveralls](https://coveralls.io/repos/new) to see if the changed code is covered by tests.
- Push to your fork, and make a pull request. - Push to your fork, and make a pull request.
A few guidelines: A few guidelines:

View File

@ -86,26 +86,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",
@ -136,49 +142,36 @@ def Ghostscript(tile, size, fp, scale=1):
finally: finally:
try: try:
os.unlink(outfile) os.unlink(outfile)
os.unlink(infile) if infile_temo:
os.unlink(infile_temp)
except: 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
@ -193,32 +186,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 http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf # Python3, can use bare open command.
fp = open(self.fp.name, "Ur", encoding='latin-1')
# for HEAD without binary preview except Exception as msg:
s = fp.read(4) # Expect this for bytesio/stringio
# for HEAD with binary preview fp = PSFile(self.fp)
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)
@ -231,18 +215,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:
@ -264,9 +242,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)
@ -276,16 +252,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
@ -297,63 +273,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:
break self.mode = self.mode_map[int(mo)]
if mo == 1: except:
self.mode = "L"
elif mo == 2:
self.mode = "LAB"
elif mo == 3:
self.mode = "RGB"
else:
break break
if id[:1] == id[-1:] == '"': self.size = int(x), int(y)
id = id[1:-1] return
# Scan forward to the actual image data s = fp.readline().strip('\r\n')
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

@ -530,7 +530,7 @@ class Image:
""" """
Closes the file pointer, if possible. Closes the file pointer, if possible.
This operation will destroy the image core and release it's memory. This operation will destroy the image core and release its memory.
The image data will be unusable afterward. The image data will be unusable afterward.
This function is only required to close images that have not This function is only required to close images that have not
@ -867,8 +867,18 @@ class Image:
trns = trns_im.getpixel((0,0)) trns = trns_im.getpixel((0,0))
elif self.mode == 'P' and mode == 'RGBA': elif self.mode == 'P' and mode == 'RGBA':
t = self.info['transparency']
delete_trns = True delete_trns = True
if isinstance(t, bytes):
self.im.putpalettealphas(t)
elif isinstance(t, int):
self.im.putpalettealpha(t,0)
else:
raise ValueError("Transparency for P mode should" +
" be bytes or int")
if mode == "P" and palette == ADAPTIVE: if mode == "P" and palette == ADAPTIVE:
im = self.im.quantize(colors) im = self.im.quantize(colors)
new = self._new(im) new = self._new(im)
@ -1514,6 +1524,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

View File

@ -47,8 +47,11 @@ class Color(_Enhance):
""" """
def __init__(self, image): def __init__(self, image):
self.image = image self.image = image
self.degenerate = image.convert("L").convert(image.mode) self.intermediate_mode = 'L'
if 'A' in image.getbands():
self.intermediate_mode = 'LA'
self.degenerate = image.convert(self.intermediate_mode).convert(image.mode)
class Contrast(_Enhance): class Contrast(_Enhance):
"""Adjust image contrast. """Adjust image contrast.
@ -62,6 +65,9 @@ class Contrast(_Enhance):
mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5) mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
self.degenerate = Image.new("L", image.size, mean).convert(image.mode) self.degenerate = Image.new("L", image.size, mean).convert(image.mode)
if 'A' in image.getbands():
self.degenerate.putalpha(image.split()[-1])
class Brightness(_Enhance): class Brightness(_Enhance):
"""Adjust image brightness. """Adjust image brightness.
@ -74,6 +80,9 @@ class Brightness(_Enhance):
self.image = image self.image = image
self.degenerate = Image.new(image.mode, image.size, 0) self.degenerate = Image.new(image.mode, image.size, 0)
if 'A' in image.getbands():
self.degenerate.putalpha(image.split()[-1])
class Sharpness(_Enhance): class Sharpness(_Enhance):
"""Adjust image sharpness. """Adjust image sharpness.
@ -85,3 +94,6 @@ class Sharpness(_Enhance):
def __init__(self, image): def __init__(self, image):
self.image = image self.image = image
self.degenerate = image.filter(ImageFilter.SMOOTH) self.degenerate = image.filter(ImageFilter.SMOOTH)
if 'A' in image.getbands():
self.degenerate.putalpha(image.split()[-1])

View File

@ -227,6 +227,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
@ -471,6 +473,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:
@ -481,6 +484,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: pass except: pass

View File

@ -17,6 +17,9 @@
from PIL import Image from PIL import Image
import sys
if sys.platform != "win32":
raise ImportError("ImageGrab is Windows only")
try: try:
# built-in driver (1.1.3 and later) # built-in driver (1.1.3 and later)
@ -40,7 +43,7 @@ def grab(bbox=None):
def grabclipboard(): def grabclipboard():
debug = 0 # temporary interface debug = 0 # temporary interface
data = Image.core.grabclipboard(debug) data = Image.core.grabclipboard(debug)
if isinstance(data, bytes): if isinstance(data, bytes):
from PIL import BmpImagePlugin from PIL import BmpImagePlugin

View File

@ -19,4 +19,3 @@ 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

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

@ -155,7 +155,7 @@ class PillowTestCase(unittest.TestCase):
raise IOError() raise IOError()
outfile = self.tempfile("temp.png") outfile = self.tempfile("temp.png")
if command_succeeds(['convert', f, outfile]): if command_succeeds([IMCONVERT, f, outfile]):
from PIL import Image from PIL import Image
return Image.open(outfile) return Image.open(outfile)
raise IOError() raise IOError()
@ -251,6 +251,14 @@ def netpbm_available():
def imagemagick_available(): def imagemagick_available():
return command_succeeds(['convert', '-version']) return IMCONVERT and command_succeeds([IMCONVERT, '-version'])
if sys.platform == 'win32':
IMCONVERT = os.environ.get('MAGICK_HOME', '')
if IMCONVERT:
IMCONVERT = os.path.join(IMCONVERT, 'convert.exe')
else:
IMCONVERT = 'convert'
# End of file # End of file

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

@ -347,8 +347,8 @@ class TestFilePng(PillowTestCase):
im2 = Image.open(f) im2 = Image.open(f)
self.assertIn('transparency', im2.info) self.assertIn('transparency', im2.info)
self.assert_image_similar(im2.convert('RGBA'), im.convert('RGBA'), self.assert_image_equal(im2.convert('RGBA'),
16) im.convert('RGBA'))
def test_save_icc_profile_none(self): def test_save_icc_profile_none(self):
# check saving files with an ICC profile set to None (omit profile) # check saving files with an ICC profile set to None (omit profile)

View File

@ -2,13 +2,13 @@ from helper import unittest, PillowTestCase, fromstring, tostring
from PIL import Image from PIL import Image
codecs = dir(Image.core) CODECS = dir(Image.core)
fihopperme = "Tests/images/hopper.jpg" FILENAME = "Tests/images/hopper.jpg"
data = tostring(Image.open(fihopperme).resize((512, 512)), "JPEG") DATA = tostring(Image.open(FILENAME).resize((512, 512)), "JPEG")
def draft(mode, size): def draft(mode, size):
im = fromstring(data) im = fromstring(DATA)
im.draft(mode, size) im.draft(mode, size)
return im return im
@ -16,7 +16,7 @@ def draft(mode, size):
class TestImageDraft(PillowTestCase): class TestImageDraft(PillowTestCase):
def setUp(self): def setUp(self):
if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: if "jpeg_encoder" not in CODECS or "jpeg_decoder" not in CODECS:
self.skipTest("jpeg support not available") self.skipTest("jpeg support not available")
def test_size(self): def test_size(self):

View File

@ -22,6 +22,34 @@ class TestImageEnhance(PillowTestCase):
ImageEnhance.Sharpness(im).enhance(0.5) ImageEnhance.Sharpness(im).enhance(0.5)
def _half_transparent_image(self):
# returns an image, half transparent, half solid
im = hopper('RGB')
transparent = Image.new('L', im.size, 0)
solid = Image.new('L', (im.size[0]//2, im.size[1]), 255)
transparent.paste(solid, (0,0))
im.putalpha(transparent)
return im
def _check_alpha(self,im, original, op, amount):
self.assertEqual(im.getbands(), original.getbands())
self.assert_image_equal(im.split()[-1], original.split()[-1],
"Diff on %s: %s" % (op, amount))
def test_alpha(self):
# Issue https://github.com/python-pillow/Pillow/issues/899
# Is alpha preserved through image enhancement?
original = self._half_transparent_image()
for op in ['Color', 'Brightness', 'Contrast', 'Sharpness']:
for amount in [0,0.5,1.0]:
self._check_alpha(getattr(ImageEnhance,op)(original).enhance(amount),
original, op, amount)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,5 +1,7 @@
from helper import unittest, PillowTestCase from helper import unittest, PillowTestCase
import sys
try: try:
from PIL import ImageGrab from PIL import ImageGrab
@ -19,6 +21,27 @@ except ImportError:
self.skipTest("ImportError") self.skipTest("ImportError")
class TestImageGrabImport(PillowTestCase):
def test_import(self):
# Arrange
exception = None
# Act
try:
from PIL import ImageGrab
ImageGrab.__name__ # dummy to prevent Pyflakes warning
except Exception as e:
exception = e
# Assert
if sys.platform == 'win32':
self.assertIsNone(exception, None)
else:
self.assertIsInstance(exception, ImportError)
self.assertEqual(str(exception), "ImageGrab is Windows only")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -39,10 +39,10 @@ class TestModeI16(PillowTestCase):
imOut = imIn.transform((w, h), Image.EXTENT, (0, 0, w, h)) imOut = imIn.transform((w, h), Image.EXTENT, (0, 0, w, h))
self.verify(imOut) # transform self.verify(imOut) # transform
fihopperme = self.tempfile("temp.im") filename = self.tempfile("temp.im")
imIn.save(fihopperme) imIn.save(filename)
imOut = Image.open(fihopperme) imOut = Image.open(filename)
self.verify(imIn) self.verify(imIn)
self.verify(imOut) self.verify(imOut)

View File

@ -6,13 +6,14 @@ from PIL import Image
class TestPickle(PillowTestCase): class TestPickle(PillowTestCase):
def helper_pickle_file(self, pickle, protocol=0): def helper_pickle_file(self, pickle, protocol=0):
# Arrange
im = Image.open('Tests/images/hopper.jpg') im = Image.open('Tests/images/hopper.jpg')
fihopperme = self.tempfile('temp.pkl') filename = self.tempfile('temp.pkl')
# Act # Act
with open(fihopperme, 'wb') as f: with open(filename, 'wb') as f:
pickle.dump(im, f, protocol) pickle.dump(im, f, protocol)
with open(fihopperme, 'rb') as f: with open(filename, 'rb') as f:
loaded_im = pickle.load(f) loaded_im = pickle.load(f)
# Assert # Assert

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);

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 */

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

@ -221,9 +221,10 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
} }
if (clientstate->ifd){ if (clientstate->ifd){
unsigned int ifdoffset = clientstate->ifd; int rv;
unsigned int ifdoffset = clientstate->ifd;
TRACE(("reading tiff ifd %d\n", ifdoffset)); TRACE(("reading tiff ifd %d\n", ifdoffset));
int rv = TIFFSetSubDirectory(tiff, ifdoffset); rv = TIFFSetSubDirectory(tiff, ifdoffset);
if (!rv){ if (!rv){
TRACE(("error in TIFFSetSubDirectory")); TRACE(("error in TIFFSetSubDirectory"));
return -1; return -1;