mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
Merge remote-tracking branch 'upstream/master' into hopper4
This commit is contained in:
commit
0ccc445ebf
22
.travis.yml
22
.travis.yml
|
@ -16,14 +16,14 @@ python:
|
|||
- 3.4
|
||||
|
||||
install:
|
||||
- "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
|
||||
- "pip install cffi"
|
||||
- "pip install coverage nose"
|
||||
- "travis_retry sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick"
|
||||
- "travis_retry pip install cffi"
|
||||
- "travis_retry pip install coverage nose"
|
||||
|
||||
# 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" == "2.6" ]; then pip install unittest2; fi
|
||||
- if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then travis_retry pip install unittest2; fi
|
||||
|
||||
# webp
|
||||
- pushd depends && ./install_webp.sh && popd
|
||||
|
@ -37,26 +37,24 @@ script:
|
|||
- CFLAGS="-coverage" python setup.py build_ext --inplace
|
||||
|
||||
- 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 -v Tests/test_*.py
|
||||
- coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py
|
||||
|
||||
after_success:
|
||||
# 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
|
||||
# filter to remove system headers
|
||||
- lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
|
||||
# convert to json
|
||||
- gem install coveralls-lcov
|
||||
- travis_retry gem install coveralls-lcov
|
||||
- coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
|
||||
|
||||
- coverage report
|
||||
- pip install coveralls-merge
|
||||
- travis_retry pip install coveralls-merge
|
||||
- coveralls-merge coverage.c.json
|
||||
|
||||
|
||||
- pip install pep8 pyflakes
|
||||
- travis_retry pip install pep8 pyflakes
|
||||
- pep8 --statistics --count PIL/*.py
|
||||
- pep8 --statistics --count Tests/*.py
|
||||
- pyflakes PIL/*.py | tee >(wc -l)
|
||||
|
@ -67,3 +65,5 @@ after_success:
|
|||
# (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-run.sh; fi
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
|
27
CHANGES.rst
27
CHANGES.rst
|
@ -4,10 +4,31 @@ Changelog (Pillow)
|
|||
2.6.0 (unreleased)
|
||||
------------------
|
||||
|
||||
- Fix for reading multipage TIFFs #885
|
||||
[kostrom, wiredfool]
|
||||
- On Windows, do not execute convert.exe without specifying path #912
|
||||
[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]
|
||||
|
||||
- Correct duplicate Tiff Metadata and Exif tag values
|
||||
|
|
|
@ -7,7 +7,7 @@ Send a pull request. We'll generally want documentation and [tests](Tests/README
|
|||
- Fork the repo
|
||||
- Make a branch
|
||||
- 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.
|
||||
|
||||
A few guidelines:
|
||||
|
|
|
@ -86,26 +86,32 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
|
||||
out_fd, outfile = tempfile.mkstemp()
|
||||
os.close(out_fd)
|
||||
in_fd, infile = tempfile.mkstemp()
|
||||
os.close(in_fd)
|
||||
|
||||
# ignore length and offset!
|
||||
# ghostscript can read it
|
||||
# copy whole file to read in ghostscript
|
||||
with open(infile, 'wb') as f:
|
||||
# fetch length of fp
|
||||
fp.seek(0, 2)
|
||||
fsize = fp.tell()
|
||||
# ensure start position
|
||||
# go back
|
||||
fp.seek(0)
|
||||
lengthfile = fsize
|
||||
while lengthfile > 0:
|
||||
s = fp.read(min(lengthfile, 100*1024))
|
||||
if not s:
|
||||
break
|
||||
length -= len(s)
|
||||
f.write(s)
|
||||
|
||||
infile_temp = None
|
||||
if hasattr(fp, 'name') and os.path.exists(fp.name):
|
||||
infile = fp.name
|
||||
else:
|
||||
in_fd, infile_temp = tempfile.mkstemp()
|
||||
os.close(in_fd)
|
||||
infile = infile_temp
|
||||
|
||||
# ignore length and offset!
|
||||
# ghostscript can read it
|
||||
# copy whole file to read in ghostscript
|
||||
with open(infile_temp, 'wb') as f:
|
||||
# fetch length of fp
|
||||
fp.seek(0, 2)
|
||||
fsize = fp.tell()
|
||||
# ensure start position
|
||||
# go back
|
||||
fp.seek(0)
|
||||
lengthfile = fsize
|
||||
while lengthfile > 0:
|
||||
s = fp.read(min(lengthfile, 100*1024))
|
||||
if not s:
|
||||
break
|
||||
lengthfile -= len(s)
|
||||
f.write(s)
|
||||
|
||||
# Build ghostscript command
|
||||
command = ["gs",
|
||||
|
@ -136,49 +142,36 @@ def Ghostscript(tile, size, fp, scale=1):
|
|||
finally:
|
||||
try:
|
||||
os.unlink(outfile)
|
||||
os.unlink(infile)
|
||||
if infile_temo:
|
||||
os.unlink(infile_temp)
|
||||
except: pass
|
||||
|
||||
return im
|
||||
|
||||
|
||||
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):
|
||||
self.fp = fp
|
||||
self.char = None
|
||||
def __getattr__(self, id):
|
||||
v = getattr(self.fp, id)
|
||||
setattr(self, id, v)
|
||||
return v
|
||||
def seek(self, offset, whence=0):
|
||||
self.char = None
|
||||
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):
|
||||
s = b""
|
||||
if self.char:
|
||||
c = self.char
|
||||
self.char = None
|
||||
else:
|
||||
c = self.fp.read(1)
|
||||
s = self.char or b""
|
||||
self.char = None
|
||||
|
||||
c = self.fp.read(1)
|
||||
while c not in b"\r\n":
|
||||
s = s + c
|
||||
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):
|
||||
return prefix[:4] == b"%!PS" or i32(prefix) == 0xC6D3D0C5
|
||||
|
@ -193,36 +186,27 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
format = "EPS"
|
||||
format_description = "Encapsulated Postscript"
|
||||
|
||||
mode_map = { 1:"L", 2:"LAB", 3:"RGB" }
|
||||
|
||||
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.
|
||||
try:
|
||||
if bytes is str:
|
||||
# Python2, no encoding conversion necessary
|
||||
fp = open(self.fp.name, "Ur")
|
||||
else:
|
||||
# Python3, can use bare open command.
|
||||
fp = open(self.fp.name, "Ur", encoding='latin-1')
|
||||
except Exception as msg:
|
||||
# Expect this for bytesio/stringio
|
||||
fp = PSFile(self.fp)
|
||||
|
||||
# FIX for: Some EPS file not handled correctly / issue #302
|
||||
# EPS can contain binary data
|
||||
# or start directly with latin coding
|
||||
# read header in both ways to handle both
|
||||
# file types
|
||||
# more info see http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
||||
|
||||
# for HEAD without binary preview
|
||||
s = fp.read(4)
|
||||
# for HEAD with binary preview
|
||||
fp.seek(0)
|
||||
sb = fp.readbinary(160)
|
||||
|
||||
if s[:4] == "%!PS":
|
||||
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)
|
||||
|
||||
|
||||
box = None
|
||||
|
||||
self.mode = "RGB"
|
||||
|
@ -231,18 +215,12 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
#
|
||||
# Load EPS header
|
||||
|
||||
s = fp.readline()
|
||||
s = fp.readline().strip('\r\n')
|
||||
|
||||
while s:
|
||||
|
||||
if len(s) > 255:
|
||||
raise SyntaxError("not an EPS file")
|
||||
|
||||
if s[-2:] == '\r\n':
|
||||
s = s[:-2]
|
||||
elif s[-1:] == '\n':
|
||||
s = s[:-1]
|
||||
|
||||
try:
|
||||
m = split.match(s)
|
||||
except re.error as v:
|
||||
|
@ -264,9 +242,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
pass
|
||||
|
||||
else:
|
||||
|
||||
m = field.match(s)
|
||||
|
||||
if m:
|
||||
k = m.group(1)
|
||||
|
||||
|
@ -276,16 +252,16 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
self.info[k[:8]] = k[9:]
|
||||
else:
|
||||
self.info[k] = ""
|
||||
elif s[0:1] == '%':
|
||||
elif s[0] == '%':
|
||||
# handle non-DSC Postscript comments that some
|
||||
# tools mistakenly put in the Comments section
|
||||
pass
|
||||
else:
|
||||
raise IOError("bad EPS header")
|
||||
|
||||
s = fp.readline()
|
||||
s = fp.readline().strip('\r\n')
|
||||
|
||||
if s[:1] != "%":
|
||||
if s[0] != "%":
|
||||
break
|
||||
|
||||
|
||||
|
@ -297,63 +273,48 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
if len(s) > 255:
|
||||
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:":
|
||||
# Encoded bitmapped image.
|
||||
[x, y, bi, mo, z3, z4, en, id] = s[11:].split(None, 7)
|
||||
|
||||
[x, y, bi, mo, z3, z4, en, id] =\
|
||||
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:
|
||||
if int(bi) != 8:
|
||||
break
|
||||
if bi != 8:
|
||||
try:
|
||||
self.mode = self.mode_map[int(mo)]
|
||||
except:
|
||||
break
|
||||
if mo == 1:
|
||||
self.mode = "L"
|
||||
elif mo == 2:
|
||||
self.mode = "LAB"
|
||||
elif mo == 3:
|
||||
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()
|
||||
|
||||
self.size = int(x), int(y)
|
||||
return
|
||||
|
||||
s = fp.readline().strip('\r\n')
|
||||
if not s:
|
||||
break
|
||||
|
||||
if not 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):
|
||||
# Load EPS via Ghostscript
|
||||
if not self.tile:
|
||||
|
|
15
PIL/Image.py
15
PIL/Image.py
|
@ -530,7 +530,7 @@ class Image:
|
|||
"""
|
||||
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.
|
||||
|
||||
This function is only required to close images that have not
|
||||
|
@ -867,7 +867,17 @@ class Image:
|
|||
trns = trns_im.getpixel((0,0))
|
||||
|
||||
elif self.mode == 'P' and mode == 'RGBA':
|
||||
t = self.info['transparency']
|
||||
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:
|
||||
im = self.im.quantize(colors)
|
||||
|
@ -1514,6 +1524,9 @@ class Image:
|
|||
|
||||
self.load()
|
||||
|
||||
if self.size == size:
|
||||
return self._new(self.im)
|
||||
|
||||
if self.mode in ("1", "P"):
|
||||
resample = NEAREST
|
||||
|
||||
|
|
|
@ -47,8 +47,11 @@ class Color(_Enhance):
|
|||
"""
|
||||
def __init__(self, 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):
|
||||
"""Adjust image contrast.
|
||||
|
@ -62,6 +65,9 @@ class Contrast(_Enhance):
|
|||
mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5)
|
||||
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):
|
||||
"""Adjust image brightness.
|
||||
|
@ -74,6 +80,9 @@ class Brightness(_Enhance):
|
|||
self.image = image
|
||||
self.degenerate = Image.new(image.mode, image.size, 0)
|
||||
|
||||
if 'A' in image.getbands():
|
||||
self.degenerate.putalpha(image.split()[-1])
|
||||
|
||||
|
||||
class Sharpness(_Enhance):
|
||||
"""Adjust image sharpness.
|
||||
|
@ -85,3 +94,6 @@ class Sharpness(_Enhance):
|
|||
def __init__(self, image):
|
||||
self.image = image
|
||||
self.degenerate = image.filter(ImageFilter.SMOOTH)
|
||||
|
||||
if 'A' in image.getbands():
|
||||
self.degenerate.putalpha(image.split()[-1])
|
||||
|
|
|
@ -227,6 +227,8 @@ class ImageFile(Image.Image):
|
|||
break
|
||||
b = b[n:]
|
||||
t = t + n
|
||||
# Need to cleanup here to prevent leaks in PyPy
|
||||
d.cleanup()
|
||||
|
||||
self.tile = []
|
||||
self.readonly = readonly
|
||||
|
@ -471,6 +473,7 @@ def _save(im, fp, tile, bufsize=0):
|
|||
break
|
||||
if s < 0:
|
||||
raise IOError("encoder error %d when writing image file" % s)
|
||||
e.cleanup()
|
||||
else:
|
||||
# slight speedup: compress to real file object
|
||||
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)
|
||||
if s < 0:
|
||||
raise IOError("encoder error %d when writing image file" % s)
|
||||
e.cleanup()
|
||||
try:
|
||||
fp.flush()
|
||||
except: pass
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
from PIL import Image
|
||||
|
||||
import sys
|
||||
if sys.platform != "win32":
|
||||
raise ImportError("ImageGrab is Windows only")
|
||||
|
||||
try:
|
||||
# built-in driver (1.1.3 and later)
|
||||
|
@ -40,7 +43,7 @@ def grab(bbox=None):
|
|||
|
||||
|
||||
def grabclipboard():
|
||||
debug = 0 # temporary interface
|
||||
debug = 0 # temporary interface
|
||||
data = Image.core.grabclipboard(debug)
|
||||
if isinstance(data, bytes):
|
||||
from PIL import BmpImagePlugin
|
||||
|
|
|
@ -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
|
||||
:target: https://coveralls.io/r/python-pillow/Pillow?branch=master
|
||||
|
||||
|
|
42
Tests/check_j2k_leaks.py
Executable file
42
Tests/check_j2k_leaks.py
Executable 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()
|
|
@ -155,7 +155,7 @@ class PillowTestCase(unittest.TestCase):
|
|||
raise IOError()
|
||||
|
||||
outfile = self.tempfile("temp.png")
|
||||
if command_succeeds(['convert', f, outfile]):
|
||||
if command_succeeds([IMCONVERT, f, outfile]):
|
||||
from PIL import Image
|
||||
return Image.open(outfile)
|
||||
raise IOError()
|
||||
|
@ -251,6 +251,14 @@ def netpbm_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
|
||||
|
|
|
@ -63,6 +63,17 @@ class TestFileEps(PillowTestCase):
|
|||
with io.open(self.tempfile('temp_iobase.eps'), 'wb') as fh:
|
||||
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):
|
||||
# We need png support for these render test
|
||||
codecs = dir(Image.core)
|
||||
|
@ -137,6 +148,83 @@ class TestFileEps(PillowTestCase):
|
|||
# open image with binary preview
|
||||
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__':
|
||||
unittest.main()
|
||||
|
||||
|
|
|
@ -347,8 +347,8 @@ class TestFilePng(PillowTestCase):
|
|||
im2 = Image.open(f)
|
||||
self.assertIn('transparency', im2.info)
|
||||
|
||||
self.assert_image_similar(im2.convert('RGBA'), im.convert('RGBA'),
|
||||
16)
|
||||
self.assert_image_equal(im2.convert('RGBA'),
|
||||
im.convert('RGBA'))
|
||||
|
||||
def test_save_icc_profile_none(self):
|
||||
# check saving files with an ICC profile set to None (omit profile)
|
||||
|
|
|
@ -2,13 +2,13 @@ from helper import unittest, PillowTestCase, fromstring, tostring
|
|||
|
||||
from PIL import Image
|
||||
|
||||
codecs = dir(Image.core)
|
||||
fihopperme = "Tests/images/hopper.jpg"
|
||||
data = tostring(Image.open(fihopperme).resize((512, 512)), "JPEG")
|
||||
CODECS = dir(Image.core)
|
||||
FILENAME = "Tests/images/hopper.jpg"
|
||||
DATA = tostring(Image.open(FILENAME).resize((512, 512)), "JPEG")
|
||||
|
||||
|
||||
def draft(mode, size):
|
||||
im = fromstring(data)
|
||||
im = fromstring(DATA)
|
||||
im.draft(mode, size)
|
||||
return im
|
||||
|
||||
|
@ -16,7 +16,7 @@ def draft(mode, size):
|
|||
class TestImageDraft(PillowTestCase):
|
||||
|
||||
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")
|
||||
|
||||
def test_size(self):
|
||||
|
|
|
@ -22,6 +22,34 @@ class TestImageEnhance(PillowTestCase):
|
|||
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__':
|
||||
unittest.main()
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from helper import unittest, PillowTestCase
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
from PIL import ImageGrab
|
||||
|
||||
|
@ -19,6 +21,27 @@ except 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__':
|
||||
unittest.main()
|
||||
|
||||
|
|
|
@ -39,10 +39,10 @@ class TestModeI16(PillowTestCase):
|
|||
imOut = imIn.transform((w, h), Image.EXTENT, (0, 0, w, h))
|
||||
self.verify(imOut) # transform
|
||||
|
||||
fihopperme = self.tempfile("temp.im")
|
||||
imIn.save(fihopperme)
|
||||
filename = self.tempfile("temp.im")
|
||||
imIn.save(filename)
|
||||
|
||||
imOut = Image.open(fihopperme)
|
||||
imOut = Image.open(filename)
|
||||
|
||||
self.verify(imIn)
|
||||
self.verify(imOut)
|
||||
|
|
|
@ -6,13 +6,14 @@ from PIL import Image
|
|||
class TestPickle(PillowTestCase):
|
||||
|
||||
def helper_pickle_file(self, pickle, protocol=0):
|
||||
# Arrange
|
||||
im = Image.open('Tests/images/hopper.jpg')
|
||||
fihopperme = self.tempfile('temp.pkl')
|
||||
filename = self.tempfile('temp.pkl')
|
||||
|
||||
# Act
|
||||
with open(fihopperme, 'wb') as f:
|
||||
with open(filename, 'wb') as f:
|
||||
pickle.dump(im, f, protocol)
|
||||
with open(fihopperme, 'rb') as f:
|
||||
with open(filename, 'rb') as f:
|
||||
loaded_im = pickle.load(f)
|
||||
|
||||
# Assert
|
||||
|
|
2
decode.c
2
decode.c
|
@ -103,6 +103,8 @@ PyImaging_DecoderNew(int contextsize)
|
|||
static void
|
||||
_dealloc(ImagingDecoderObject* decoder)
|
||||
{
|
||||
if (decoder->cleanup)
|
||||
decoder->cleanup(&decoder->state);
|
||||
free(decoder->state.buffer);
|
||||
free(decoder->state.context);
|
||||
Py_XDECREF(decoder->lock);
|
||||
|
|
|
@ -670,6 +670,7 @@ files, using either JPEG or HEX encoding depending on the image mode (and
|
|||
whether JPEG support is available or not).
|
||||
|
||||
PIXAR (read only)
|
||||
^^^^
|
||||
|
||||
PIL provides limited support for PIXAR raster files. The library can identify
|
||||
and read “dumped” RGB files.
|
||||
|
|
13
encode.c
13
encode.c
|
@ -99,6 +99,18 @@ _dealloc(ImagingEncoderObject* 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*
|
||||
_encode(ImagingEncoderObject* encoder, PyObject* args)
|
||||
{
|
||||
|
@ -239,6 +251,7 @@ _setimage(ImagingEncoderObject* encoder, PyObject* args)
|
|||
|
||||
static struct PyMethodDef methods[] = {
|
||||
{"encode", (PyCFunction)_encode, 1},
|
||||
{"cleanup", (PyCFunction)_encode_cleanup, 1},
|
||||
{"encode_to_file", (PyCFunction)_encode_to_file, 1},
|
||||
{"setimage", (PyCFunction)_setimage, 1},
|
||||
{NULL, NULL} /* sentinel */
|
||||
|
|
|
@ -800,6 +800,11 @@ ImagingJpeg2KDecodeCleanup(ImagingCodecState state) {
|
|||
if (context->decoder)
|
||||
ImagingIncrementalCodecDestroy(context->decoder);
|
||||
|
||||
context->error_msg = NULL;
|
||||
|
||||
/* Prevent multiple calls to ImagingIncrementalCodecDestroy */
|
||||
context->decoder = NULL;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
7
libImaging/Jpeg2KEncode.c
Normal file → Executable file
7
libImaging/Jpeg2KEncode.c
Normal file → Executable file
|
@ -576,15 +576,20 @@ int
|
|||
ImagingJpeg2KEncodeCleanup(ImagingCodecState state) {
|
||||
JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context;
|
||||
|
||||
if (context->quality_layers)
|
||||
if (context->quality_layers && context->encoder)
|
||||
Py_DECREF(context->quality_layers);
|
||||
|
||||
if (context->error_msg)
|
||||
free ((void *)context->error_msg);
|
||||
|
||||
context->error_msg = NULL;
|
||||
|
||||
if (context->encoder)
|
||||
ImagingIncrementalCodecDestroy(context->encoder);
|
||||
|
||||
/* Prevent multiple calls to ImagingIncrementalCodecDestroy */
|
||||
context->encoder = NULL;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -221,9 +221,10 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, int
|
|||
}
|
||||
|
||||
if (clientstate->ifd){
|
||||
unsigned int ifdoffset = clientstate->ifd;
|
||||
int rv;
|
||||
unsigned int ifdoffset = clientstate->ifd;
|
||||
TRACE(("reading tiff ifd %d\n", ifdoffset));
|
||||
int rv = TIFFSetSubDirectory(tiff, ifdoffset);
|
||||
rv = TIFFSetSubDirectory(tiff, ifdoffset);
|
||||
if (!rv){
|
||||
TRACE(("error in TIFFSetSubDirectory"));
|
||||
return -1;
|
||||
|
|
Loading…
Reference in New Issue
Block a user