Merge branch 'master' into rm-deprecated-fn

This commit is contained in:
wiredfool 2017-06-13 13:32:38 +01:00 committed by GitHub
commit c3e041e9e6
71 changed files with 519 additions and 275 deletions

3
.gitignore vendored
View File

@ -34,6 +34,9 @@ htmlcov/
nosetests.xml nosetests.xml
coverage.xml coverage.xml
# Test files
test_images
# Translations # Translations
*.mo *.mo

View File

@ -10,8 +10,8 @@ notifications:
matrix: matrix:
fast_finish: true fast_finish: true
include: include:
- python: "pypy" - python: "pypy-5.7.1"
- python: "pypy3" - python: "pypy3.3-5.2-alpha1"
- python: '3.6' - python: '3.6'
- python: '2.7' - python: '2.7'
- python: "2.7_with_system_site_packages" # For PyQt4 - python: "2.7_with_system_site_packages" # For PyQt4

View File

@ -3,18 +3,16 @@
set -e set -e
sudo apt-get update sudo apt-get update
sudo apt-get -qq install libfreetype6-dev liblcms2-dev\ sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk \
python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick
pip install cffi pip install cffi
pip install nose pip install nose
pip install check-manifest pip install check-manifest
pip install olefile pip install olefile
# Pyroma tests sometimes hang on PyPy; skip pip install pyroma
if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then pip install pyroma; fi
pip install coverage pip install coverage
# docs only on python 2.7 # docs only on Python 2.7
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi
# clean checkout for manifest # clean checkout for manifest

View File

@ -4,6 +4,15 @@ Changelog (Pillow)
4.2.0 (unreleased) 4.2.0 (unreleased)
------------------ ------------------
- Update Feature Detection
[wiredfool]
- CI: Update pypy on TravisCI
[hugovk]
- ImageMorph: Fix wrong expected size of MRLs read from disk #2561
[dov]
- Docs: Update install docs for FreeBSD #2546 - Docs: Update install docs for FreeBSD #2546
[wiredfool] [wiredfool]

View File

@ -176,8 +176,8 @@ class IcoFile(object):
# figure out where AND mask image starts # figure out where AND mask image starts
mode = a[0] mode = a[0]
bpp = 8 bpp = 8
for k in BmpImagePlugin.BIT2MODE.keys(): for k, v in BmpImagePlugin.BIT2MODE.items():
if mode == BmpImagePlugin.BIT2MODE[k][1]: if mode == v[1]:
bpp = k bpp = k
break break

View File

@ -274,7 +274,7 @@ def _conv_type_shape(im):
return shape+(extra,), typ return shape+(extra,), typ
MODES = sorted(_MODEINFO.keys()) MODES = sorted(_MODEINFO)
# raw modes that may be memory mapped. NOTE: if you change this, you # raw modes that may be memory mapped. NOTE: if you change this, you
# may have to modify the stride calculation in map.c too! # may have to modify the stride calculation in map.c too!

View File

@ -283,6 +283,7 @@ def Draw(im, mode=None):
except AttributeError: except AttributeError:
return ImageDraw(im, mode) return ImageDraw(im, mode)
# experimental access to the outline API # experimental access to the outline API
try: try:
Outline = Image.core.outline Outline = Image.core.outline

View File

@ -123,7 +123,7 @@ class LutBuilder(object):
.replace('0', 'Z') .replace('0', 'Z')
.replace('1', '0') .replace('1', '0')
.replace('Z', '1')) .replace('Z', '1'))
res = '%d' % (1-int(res)) res = 1-int(res)
patterns.append((pattern, res)) patterns.append((pattern, res))
return patterns return patterns
@ -152,8 +152,8 @@ class LutBuilder(object):
patterns += self._pattern_permute(pattern, options, result) patterns += self._pattern_permute(pattern, options, result)
# # Debugging # # Debugging
# for p,r in patterns: # for p, r in patterns:
# print(p,r) # print(p, r)
# print('--') # print('--')
# compile the patterns into regular expressions for speed # compile the patterns into regular expressions for speed
@ -234,7 +234,7 @@ class MorphOp(object):
with open(filename, 'rb') as f: with open(filename, 'rb') as f:
self.lut = bytearray(f.read()) self.lut = bytearray(f.read())
if len(self.lut) != 8192: if len(self.lut) != LUT_SIZE:
self.lut = None self.lut = None
raise Exception('Wrong size operator file!') raise Exception('Wrong size operator file!')

View File

@ -95,7 +95,7 @@ class IptcImageFile(ImageFile.ImageFile):
tagdata = self.fp.read(size) tagdata = self.fp.read(size)
else: else:
tagdata = None tagdata = None
if tag in list(self.info.keys()): if tag in self.info:
if isinstance(self.info[tag], list): if isinstance(self.info[tag], list):
self.info[tag].append(tagdata) self.info[tag].append(tagdata)
else: else:

View File

@ -66,6 +66,7 @@ class McIdasImageFile(ImageFile.ImageFile):
self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))] self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))]
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# registry # registry

View File

@ -20,7 +20,7 @@
# Image plugin for PDF images (output only). # Image plugin for PDF images (output only).
## ##
from . import Image, ImageFile from . import Image, ImageFile, ImageSequence
from ._binary import i8 from ._binary import i8
import io import io
@ -133,13 +133,24 @@ def _save(im, fp, filename, save_all=False):
# #
# pages # pages
numberOfPages = 1 ims = [im]
if save_all: if save_all:
try: append_images = im.encoderinfo.get("append_images", [])
numberOfPages = im.n_frames for append_im in append_images:
except AttributeError: if append_im.mode != im.mode:
# Image format does not have n_frames. It is a single frame image append_im = append_im.convert(im.mode)
pass append_im.encoderinfo = im.encoderinfo.copy()
ims.append(append_im)
numberOfPages = 0
for im in ims:
im_numberOfPages = 1
if save_all:
try:
im_numberOfPages = im.n_frames
except AttributeError:
# Image format does not have n_frames. It is a single frame image
pass
numberOfPages += im_numberOfPages
pages = [str(pageNumber*3+4)+" 0 R" pages = [str(pageNumber*3+4)+" 0 R"
for pageNumber in range(0, numberOfPages)] for pageNumber in range(0, numberOfPages)]
@ -151,90 +162,92 @@ def _save(im, fp, filename, save_all=False):
Kids="["+"\n".join(pages)+"]") Kids="["+"\n".join(pages)+"]")
_endobj(fp) _endobj(fp)
for pageNumber in range(0, numberOfPages): pageNumber = 0
im.seek(pageNumber) for imSequence in ims:
for im in ImageSequence.Iterator(imSequence):
#
# image
# op = io.BytesIO()
# image
op = io.BytesIO() if filter == "/ASCIIHexDecode":
if bits == 1:
# FIXME: the hex encoder doesn't support packed 1-bit
# images; do things the hard way...
data = im.tobytes("raw", "1")
im = Image.new("L", (len(data), 1), None)
im.putdata(data)
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
elif filter == "/DCTDecode":
Image.SAVE["JPEG"](im, op, filename)
elif filter == "/FlateDecode":
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
elif filter == "/RunLengthDecode":
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
else:
raise ValueError("unsupported PDF filter (%s)" % filter)
if filter == "/ASCIIHexDecode": #
if bits == 1: # Get image characteristics
# FIXME: the hex encoder doesn't support packed 1-bit
# images; do things the hard way...
data = im.tobytes("raw", "1")
im = Image.new("L", (len(data), 1), None)
im.putdata(data)
ImageFile._save(im, op, [("hex", (0, 0)+im.size, 0, im.mode)])
elif filter == "/DCTDecode":
Image.SAVE["JPEG"](im, op, filename)
elif filter == "/FlateDecode":
ImageFile._save(im, op, [("zip", (0, 0)+im.size, 0, im.mode)])
elif filter == "/RunLengthDecode":
ImageFile._save(im, op, [("packbits", (0, 0)+im.size, 0, im.mode)])
else:
raise ValueError("unsupported PDF filter (%s)" % filter)
# width, height = im.size
# Get image characteristics
width, height = im.size xref.append(fp.tell())
_obj(
fp, pageNumber*3+3,
Type="/XObject",
Subtype="/Image",
Width=width, # * 72.0 / resolution,
Height=height, # * 72.0 / resolution,
Length=len(op.getvalue()),
Filter=filter,
BitsPerComponent=bits,
DecodeParams=params,
ColorSpace=colorspace)
xref.append(fp.tell()) fp.write("stream\n")
_obj( fp.fp.write(op.getvalue())
fp, pageNumber*3+3, fp.write("\nendstream\n")
Type="/XObject",
Subtype="/Image",
Width=width, # * 72.0 / resolution,
Height=height, # * 72.0 / resolution,
Length=len(op.getvalue()),
Filter=filter,
BitsPerComponent=bits,
DecodeParams=params,
ColorSpace=colorspace)
fp.write("stream\n") _endobj(fp)
fp.fp.write(op.getvalue())
fp.write("\nendstream\n")
_endobj(fp) #
# page
# xref.append(fp.tell())
# page _obj(fp, pageNumber*3+4)
fp.write(
"<<\n/Type /Page\n/Parent 2 0 R\n"
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
"/XObject << /image %d 0 R >>\n>>\n"
"/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\n" % (
procset,
pageNumber*3+3,
int(width * 72.0 / resolution),
int(height * 72.0 / resolution),
pageNumber*3+5))
_endobj(fp)
xref.append(fp.tell()) #
_obj(fp, pageNumber*3+4) # page contents
fp.write(
"<<\n/Type /Page\n/Parent 2 0 R\n"
"/Resources <<\n/ProcSet [ /PDF %s ]\n"
"/XObject << /image %d 0 R >>\n>>\n"
"/MediaBox [ 0 0 %d %d ]\n/Contents %d 0 R\n>>\n" % (
procset,
pageNumber*3+3,
int(width * 72.0 / resolution),
int(height * 72.0 / resolution),
pageNumber*3+5))
_endobj(fp)
# op = TextWriter(io.BytesIO())
# page contents
op = TextWriter(io.BytesIO()) op.write(
"q %d 0 0 %d 0 0 cm /image Do Q\n" % (
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
op.write( xref.append(fp.tell())
"q %d 0 0 %d 0 0 cm /image Do Q\n" % ( _obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue()))
int(width * 72.0 / resolution),
int(height * 72.0 / resolution)))
xref.append(fp.tell()) fp.write("stream\n")
_obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue())) fp.fp.write(op.fp.getvalue())
fp.write("\nendstream\n")
fp.write("stream\n") _endobj(fp)
fp.fp.write(op.fp.getvalue())
fp.write("\nendstream\n")
_endobj(fp) pageNumber += 1
# #
# trailer # trailer

View File

@ -2,45 +2,26 @@ from . import Image
modules = { modules = {
"pil": "PIL._imaging", "pil": "PIL._imaging",
"tkinter": "PIL._imagingtk", "tkinter": "PIL._tkinter_finder",
"freetype2": "PIL._imagingft", "freetype2": "PIL._imagingft",
"littlecms2": "PIL._imagingcms", "littlecms2": "PIL._imagingcms",
"webp": "PIL._webp", "webp": "PIL._webp",
"transp_webp": ("WEBP", "WebPDecoderBuggyAlpha")
} }
def check_module(feature): def check_module(feature):
if feature not in modules: if not (feature in modules):
raise ValueError("Unknown module %s" % feature) raise ValueError("Unknown module %s" % feature)
module = modules[feature] module = modules[feature]
method_to_call = None
if isinstance(module, tuple):
module, method_to_call = module
try: try:
imported_module = __import__(module) imported_module = __import__(module)
except ImportError:
# If a method is being checked, None means that
# rather than the method failing, the module required for the method
# failed to be imported first
return None if method_to_call else False
if method_to_call:
method = getattr(imported_module, method_to_call)
return method() is True
else:
return True return True
except ImportError:
return False
def get_supported_modules(): def get_supported_modules():
supported_modules = [] return [f for f in modules if check_module(f)]
for feature in modules:
if check_module(feature):
supported_modules.append(feature)
return supported_modules
codecs = { codecs = {
"jpg": "jpeg", "jpg": "jpeg",
@ -49,7 +30,6 @@ codecs = {
"libtiff": "libtiff" "libtiff": "libtiff"
} }
def check_codec(feature): def check_codec(feature):
if feature not in codecs: if feature not in codecs:
raise ValueError("Unknown codec %s" % feature) raise ValueError("Unknown codec %s" % feature)
@ -60,8 +40,38 @@ def check_codec(feature):
def get_supported_codecs(): def get_supported_codecs():
supported_codecs = [] return [f for f in codecs if check_codec(f)]
for feature in codecs:
if check_codec(feature): features = {
supported_codecs.append(feature) "webp_mux": ("PIL._webp", 'HAVE_WEBPMUX'),
return supported_codecs "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"),
}
def check_feature(feature):
if feature not in features:
raise ValueError("Unknown feature %s" % feature)
module, flag = features[feature]
try:
imported_module = __import__(module, fromlist=['PIL'])
return getattr(imported_module, flag)
except ImportError:
return None
def get_supported_features():
return [f for f in features if check_feature(f)]
def check(feature):
return (feature in modules and check_module(feature) or \
feature in codecs and check_codec(feature) or \
feature in features and check_feature(feature))
def get_supported():
ret = get_supported_modules()
ret.extend(get_supported_features())
ret.extend(get_supported_codecs())
return ret

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

View File

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

View File

@ -99,8 +99,7 @@ class TestBmpReference(PillowTestCase):
os.path.join(base, 'g', 'pal8rle.bmp'), os.path.join(base, 'g', 'pal8rle.bmp'),
os.path.join(base, 'g', 'pal4rle.bmp')) os.path.join(base, 'g', 'pal4rle.bmp'))
if f not in unsupported: if f not in unsupported:
self.assertTrue( self.fail("Unsupported Image %s: %s" % (f, msg))
False, "Unsupported Image %s: %s" % (f, msg))
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -21,7 +21,7 @@ class TestDecompressionBomb(PillowTestCase):
# Arrange # Arrange
# Turn limit off # Turn limit off
Image.MAX_IMAGE_PIXELS = None Image.MAX_IMAGE_PIXELS = None
self.assertEqual(Image.MAX_IMAGE_PIXELS, None) self.assertIsNone(Image.MAX_IMAGE_PIXELS)
# Act / Assert # Act / Assert
# Implicit assert: no warning. # Implicit assert: no warning.

View File

@ -2,19 +2,50 @@ from helper import unittest, PillowTestCase
from PIL import features from PIL import features
try:
from PIL import _webp
HAVE_WEBP = True
except:
HAVE_WEBP = False
class TestFeatures(PillowTestCase): class TestFeatures(PillowTestCase):
def test_check_features(self): def test_check(self):
for feature in features.modules: # Check the correctness of the convenience function
self.assertTrue( for module in features.modules:
features.check_module(feature) in [True, False, None]) self.assertEqual(features.check_module(module),
for feature in features.codecs: features.check(module))
self.assertTrue(features.check_codec(feature) in [True, False]) for codec in features.codecs:
self.assertEqual(features.check_codec(codec),
features.check(codec))
for feature in features.features:
self.assertEqual(features.check_feature(feature),
features.check(feature))
def test_supported_features(self): @unittest.skipUnless(HAVE_WEBP, True)
def check_webp_transparency(self):
self.assertEqual(features.check('transp_webp'),
not _webp.WebPDecoderBuggyAlpha())
self.assertEqual(features.check('transp_webp'),
_webp.HAVE_TRANSPARENCY)
@unittest.skipUnless(HAVE_WEBP, True)
def check_webp_mux(self):
self.assertEqual(features.check('webp_mux'),
_webp.HAVE_WEBPMUX)
def test_check_modules(self):
for feature in features.modules:
self.assertIn(features.check_module(feature), [True, False])
for feature in features.codecs:
self.assertIn(features.check_codec(feature), [True, False])
def test_supported_modules(self):
self.assertIsInstance(features.get_supported_modules(), list) self.assertIsInstance(features.get_supported_modules(), list)
self.assertIsInstance(features.get_supported_codecs(), list) self.assertIsInstance(features.get_supported_codecs(), list)
self.assertIsInstance(features.get_supported_features(), list)
self.assertIsInstance(features.get_supported(), list)
def test_unsupported_codec(self): def test_unsupported_codec(self):
# Arrange # Arrange

View File

@ -26,8 +26,8 @@ class TestFileCur(PillowTestCase):
no_cursors_file = "Tests/images/no_cursors.cur" no_cursors_file = "Tests/images/no_cursors.cur"
cur = CurImagePlugin.CurImageFile(TEST_FILE) cur = CurImagePlugin.CurImageFile(TEST_FILE)
cur.fp = open(no_cursors_file, "rb") with open(no_cursors_file, "rb") as cur.fp:
self.assertRaises(TypeError, cur._open) self.assertRaises(TypeError, cur._open)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -50,7 +50,7 @@ class TestFileDcx(PillowTestCase):
im.seek(n_frames) im.seek(n_frames)
break break
except EOFError: except EOFError:
self.assertTrue(im.tell() < n_frames) self.assertLess(im.tell(), n_frames)
def test_seek_too_far(self): def test_seek_too_far(self):
# Arrange # Arrange

View File

@ -38,7 +38,7 @@ class TestFileFli(PillowTestCase):
im.seek(n_frames) im.seek(n_frames)
break break
except EOFError: except EOFError:
self.assertTrue(im.tell() < n_frames) self.assertLess(im.tell(), n_frames)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -226,7 +226,7 @@ class TestFileGif(PillowTestCase):
im.seek(n_frames) im.seek(n_frames)
break break
except EOFError: except EOFError:
self.assertTrue(im.tell() < n_frames) self.assertLess(im.tell(), n_frames)
def test_dispose_none(self): def test_dispose_none(self):
img = Image.open("Tests/images/dispose_none.gif") img = Image.open("Tests/images/dispose_none.gif")

View File

@ -30,7 +30,7 @@ class TestFileIm(PillowTestCase):
im.seek(n_frames) im.seek(n_frames)
break break
except EOFError: except EOFError:
self.assertTrue(im.tell() < n_frames) self.assertLess(im.tell(), n_frames)
def test_roundtrip(self): def test_roundtrip(self):
out = self.tempfile('temp.im') out = self.tempfile('temp.im')

View File

@ -93,7 +93,7 @@ class TestFileJpeg(PillowTestCase):
self.assertEqual(test(72), (72, 72)) self.assertEqual(test(72), (72, 72))
self.assertEqual(test(300), (300, 300)) self.assertEqual(test(300), (300, 300))
self.assertEqual(test(100, 200), (100, 200)) self.assertEqual(test(100, 200), (100, 200))
self.assertEqual(test(0), None) # square pixels self.assertIsNone(test(0)) # square pixels
def test_icc(self): def test_icc(self):
# Test ICC support # Test ICC support
@ -439,7 +439,7 @@ class TestFileJpeg(PillowTestCase):
def test_no_duplicate_0x1001_tag(self): def test_no_duplicate_0x1001_tag(self):
# Arrange # Arrange
from PIL import ExifTags from PIL import ExifTags
tag_ids = dict(zip(ExifTags.TAGS.values(), ExifTags.TAGS.keys())) tag_ids = {v: k for k, v in ExifTags.TAGS.items()}
# Assert # Assert
self.assertEqual(tag_ids['RelatedImageWidth'], 0x1001) self.assertEqual(tag_ids['RelatedImageWidth'], 0x1001)

View File

@ -173,8 +173,7 @@ class TestFileLibTiff(LibTiffTestCase):
'RowsPerStrip', 'RowsPerStrip',
'StripOffsets'] 'StripOffsets']
for field in requested_fields: for field in requested_fields:
self.assertTrue(field in reloaded, self.assertIn(field, reloaded, "%s not in metadata" % field)
"%s not in metadata" % field)
def test_additional_metadata(self): def test_additional_metadata(self):
# these should not crash. Seriously dummy data, most of it doesn't make # these should not crash. Seriously dummy data, most of it doesn't make
@ -190,7 +189,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Exclude ones that have special meaning # Exclude ones that have special meaning
# that we're already testing them # that we're already testing them
im = Image.open('Tests/images/hopper_g4.tif') im = Image.open('Tests/images/hopper_g4.tif')
for tag in im.tag_v2.keys(): for tag in im.tag_v2:
try: try:
del(core_items[tag]) del(core_items[tag])
except: except:

View File

@ -1,6 +1,6 @@
from helper import unittest, PillowTestCase from helper import unittest, PillowTestCase
from PIL import McIdasImagePlugin from PIL import Image, McIdasImagePlugin
class TestFileMcIdas(PillowTestCase): class TestFileMcIdas(PillowTestCase):
@ -12,6 +12,24 @@ class TestFileMcIdas(PillowTestCase):
lambda: lambda:
McIdasImagePlugin.McIdasImageFile(invalid_file)) McIdasImagePlugin.McIdasImageFile(invalid_file))
def test_valid_file(self):
# Arrange
# https://ghrc.nsstc.nasa.gov/hydro/details/cmx3g8
# https://ghrc.nsstc.nasa.gov/pub/fieldCampaigns/camex3/cmx3g8/browse/
test_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.ara"
saved_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png"
# Act
im = Image.open(test_file)
im.load()
# Assert
self.assertEqual(im.format, "MCIDAS")
self.assertEqual(im.mode, "I")
self.assertEqual(im.size, (1800, 400))
im2 = Image.open(saved_file)
self.assert_image_equal(im, im2)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -110,7 +110,7 @@ class TestFileMpo(PillowTestCase):
im.seek(n_frames) im.seek(n_frames)
break break
except EOFError: except EOFError:
self.assertTrue(im.tell() < n_frames) self.assertLess(im.tell(), n_frames)
def test_image_grab(self): def test_image_grab(self):
for test_file in test_files: for test_file in test_files:

View File

@ -74,6 +74,12 @@ class TestFilePdf(PillowTestCase):
self.assertTrue(os.path.isfile(outfile)) self.assertTrue(os.path.isfile(outfile))
self.assertGreater(os.path.getsize(outfile), 0) self.assertGreater(os.path.getsize(outfile), 0)
# Append images
im.save(outfile, save_all=True, append_images=[hopper()])
self.assertTrue(os.path.isfile(outfile))
self.assertGreater(os.path.getsize(outfile), 0)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -316,7 +316,7 @@ class TestFilePng(PillowTestCase):
test_file = f.read()[:offset] test_file = f.read()[:offset]
im = Image.open(BytesIO(test_file)) im = Image.open(BytesIO(test_file))
self.assertTrue(im.fp is not None) self.assertIsNotNone(im.fp)
self.assertRaises((IOError, SyntaxError), im.verify) self.assertRaises((IOError, SyntaxError), im.verify)
def test_verify_ignores_crc_error(self): def test_verify_ignores_crc_error(self):
@ -331,7 +331,7 @@ class TestFilePng(PillowTestCase):
ImageFile.LOAD_TRUNCATED_IMAGES = True ImageFile.LOAD_TRUNCATED_IMAGES = True
try: try:
im = load(image_data) im = load(image_data)
self.assertTrue(im is not None) self.assertIsNotNone(im)
finally: finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False
@ -462,7 +462,7 @@ class TestFilePng(PillowTestCase):
def test_save_icc_profile(self): def test_save_icc_profile(self):
im = Image.open("Tests/images/icc_profile_none.png") im = Image.open("Tests/images/icc_profile_none.png")
self.assertEqual(im.info['icc_profile'], None) self.assertIsNone(im.info['icc_profile'])
with_icc = Image.open("Tests/images/icc_profile.png") with_icc = Image.open("Tests/images/icc_profile.png")
expected_icc = with_icc.info['icc_profile'] expected_icc = with_icc.info['icc_profile']
@ -485,7 +485,7 @@ class TestFilePng(PillowTestCase):
def test_roundtrip_no_icc_profile(self): def test_roundtrip_no_icc_profile(self):
im = Image.open("Tests/images/icc_profile_none.png") im = Image.open("Tests/images/icc_profile_none.png")
self.assertEqual(im.info['icc_profile'], None) self.assertIsNone(im.info['icc_profile'])
im = roundtrip(im) im = roundtrip(im)
self.assertNotIn('icc_profile', im.info) self.assertNotIn('icc_profile', im.info)

View File

@ -44,7 +44,7 @@ class TestImagePsd(PillowTestCase):
im.seek(n_frames+1) im.seek(n_frames+1)
break break
except EOFError: except EOFError:
self.assertTrue(im.tell() < n_frames) self.assertLess(im.tell(), n_frames)
def test_seek_tell(self): def test_seek_tell(self):
im = Image.open(test_file) im = Image.open(test_file)

View File

@ -69,7 +69,7 @@ class TestImageSpider(PillowTestCase):
img_list = SpiderImagePlugin.loadImageSeries(file_list) img_list = SpiderImagePlugin.loadImageSeries(file_list)
# Assert # Assert
self.assertEqual(img_list, None) self.assertIsNone(img_list)
def test_isInt_not_a_number(self): def test_isInt_not_a_number(self):
# Arrange # Arrange

View File

@ -234,7 +234,7 @@ class TestFileTiff(PillowTestCase):
im.seek(n_frames) im.seek(n_frames)
break break
except EOFError: except EOFError:
self.assertTrue(im.tell() < n_frames) self.assertLess(im.tell(), n_frames)
def test_multipage(self): def test_multipage(self):
# issue #862 # issue #862
@ -482,7 +482,6 @@ class TestFileTiff(PillowTestCase):
im.load() im.load()
self.assertFalse(fp.closed) self.assertFalse(fp.closed)
@unittest.skipUnless(sys.platform.startswith('win32'), "Windows only") @unittest.skipUnless(sys.platform.startswith('win32'), "Windows only")
class TestFileTiffW32(PillowTestCase): class TestFileTiffW32(PillowTestCase):
def test_fd_leak(self): def test_fd_leak(self):

View File

@ -96,7 +96,7 @@ class TestFileWebpMetadata(PillowTestCase):
file_path = "Tests/images/flower.jpg" file_path = "Tests/images/flower.jpg"
image = Image.open(file_path) image = Image.open(file_path)
self.assertTrue('exif' in image.info) self.assertIn('exif', image.info)
test_buffer = BytesIO() test_buffer = BytesIO()

View File

@ -170,7 +170,7 @@ class TestImage(PillowTestCase):
im2 = Image.new('RGB', (25, 25), 'white') im2 = Image.new('RGB', (25, 25), 'white')
# Act / Assert # Act / Assert
self.assertTrue(im1 != im2) self.assertNotEqual(im1, im2)
def test_alpha_composite(self): def test_alpha_composite(self):
# https://stackoverflow.com/questions/3374878 # https://stackoverflow.com/questions/3374878
@ -390,7 +390,7 @@ class TestRegistry(PillowTestCase):
def test_encode_registry(self): def test_encode_registry(self):
Image.register_encoder('MOCK', mock_encode) Image.register_encoder('MOCK', mock_encode)
self.assert_('MOCK' in Image.ENCODERS) self.assertIn('MOCK', Image.ENCODERS)
enc = Image._getencoder('RGB', 'MOCK', ('args',), extra=('extra',)) enc = Image._getencoder('RGB', 'MOCK', ('args',), extra=('extra',))

View File

@ -14,7 +14,7 @@ class TestImageGetBbox(PillowTestCase):
# 8-bit mode # 8-bit mode
im = Image.new("L", (100, 100), 0) im = Image.new("L", (100, 100), 0)
self.assertEqual(im.getbbox(), None) self.assertIsNone(im.getbbox())
im.paste(255, (10, 25, 90, 75)) im.paste(255, (10, 25, 90, 75))
self.assertEqual(im.getbbox(), (10, 25, 90, 75)) self.assertEqual(im.getbbox(), (10, 25, 90, 75))
@ -27,7 +27,7 @@ class TestImageGetBbox(PillowTestCase):
# 32-bit mode # 32-bit mode
im = Image.new("RGB", (100, 100), 0) im = Image.new("RGB", (100, 100), 0)
self.assertEqual(im.getbbox(), None) self.assertIsNone(im.getbbox())
im.paste(255, (10, 25, 90, 75)) im.paste(255, (10, 25, 90, 75))
self.assertEqual(im.getbbox(), (10, 25, 90, 75)) self.assertEqual(im.getbbox(), (10, 25, 90, 75))

View File

@ -20,15 +20,15 @@ class TestImageGetColors(PillowTestCase):
self.assertEqual(getcolors("I"), 255) self.assertEqual(getcolors("I"), 255)
self.assertEqual(getcolors("F"), 255) self.assertEqual(getcolors("F"), 255)
self.assertEqual(getcolors("P"), 90) # fixed palette self.assertEqual(getcolors("P"), 90) # fixed palette
self.assertEqual(getcolors("RGB"), None) self.assertIsNone(getcolors("RGB"))
self.assertEqual(getcolors("RGBA"), None) self.assertIsNone(getcolors("RGBA"))
self.assertEqual(getcolors("CMYK"), None) self.assertIsNone(getcolors("CMYK"))
self.assertEqual(getcolors("YCbCr"), None) self.assertIsNone(getcolors("YCbCr"))
self.assertEqual(getcolors("L", 128), None) self.assertIsNone(getcolors("L", 128))
self.assertEqual(getcolors("L", 1024), 255) self.assertEqual(getcolors("L", 1024), 255)
self.assertEqual(getcolors("RGB", 8192), None) self.assertIsNone(getcolors("RGB", 8192))
self.assertEqual(getcolors("RGB", 16384), 10100) self.assertEqual(getcolors("RGB", 16384), 10100)
self.assertEqual(getcolors("RGB", 100000), 10100) self.assertEqual(getcolors("RGB", 100000), 10100)
@ -48,7 +48,7 @@ class TestImageGetColors(PillowTestCase):
(7960, (31, 20, 33))] (7960, (31, 20, 33))]
A = im.getcolors(maxcolors=2) A = im.getcolors(maxcolors=2)
self.assertEqual(A, None) self.assertIsNone(A)
A = im.getcolors(maxcolors=3) A = im.getcolors(maxcolors=3)
A.sort() A.sort()

View File

@ -9,15 +9,15 @@ class TestImageGetPalette(PillowTestCase):
if p: if p:
return p[:10] return p[:10]
return None return None
self.assertEqual(palette("1"), None) self.assertIsNone(palette("1"))
self.assertEqual(palette("L"), None) self.assertIsNone(palette("L"))
self.assertEqual(palette("I"), None) self.assertIsNone(palette("I"))
self.assertEqual(palette("F"), None) self.assertIsNone(palette("F"))
self.assertEqual(palette("P"), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) self.assertEqual(palette("P"), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
self.assertEqual(palette("RGB"), None) self.assertIsNone(palette("RGB"))
self.assertEqual(palette("RGBA"), None) self.assertIsNone(palette("RGBA"))
self.assertEqual(palette("CMYK"), None) self.assertIsNone(palette("CMYK"))
self.assertEqual(palette("YCbCr"), None) self.assertIsNone(palette("YCbCr"))
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -16,8 +16,8 @@ class TestImagingResampleVulnerability(PillowTestCase):
def test_invalid_size(self): def test_invalid_size(self):
im = hopper() im = hopper()
# Should not crash
im.resize((100, 100)) im.resize((100, 100))
self.assertTrue(True, "Should not Crash")
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
im.resize((-100, 100)) im.resize((-100, 100))

View File

@ -222,7 +222,7 @@ class TestImageCms(PillowTestCase):
self.assertTrue(img_srgb.info['icc_profile']) self.assertTrue(img_srgb.info['icc_profile'])
profile = ImageCmsProfile(BytesIO(img_srgb.info['icc_profile'])) profile = ImageCmsProfile(BytesIO(img_srgb.info['icc_profile']))
self.assertTrue('sRGB' in ImageCms.getProfileDescription(profile)) self.assertIn('sRGB', ImageCms.getProfileDescription(profile))
def test_lab_roundtrip(self): def test_lab_roundtrip(self):
# check to see if we're at least internally consistent. # check to see if we're at least internally consistent.
@ -275,12 +275,12 @@ class TestImageCms(PillowTestCase):
assert_truncated_tuple_equal(p.blue_colorant, ((0.14306640625, 0.06060791015625, 0.7140960693359375), (0.1558847490315394, 0.06603820639433387, 0.06060791015625))) assert_truncated_tuple_equal(p.blue_colorant, ((0.14306640625, 0.06060791015625, 0.7140960693359375), (0.1558847490315394, 0.06603820639433387, 0.06060791015625)))
assert_truncated_tuple_equal(p.blue_primary, ((0.14306641366715667, 0.06060790921083026, 0.7140960805782015), (0.15588475410450106, 0.06603820408959558, 0.06060790921083026))) assert_truncated_tuple_equal(p.blue_primary, ((0.14306641366715667, 0.06060790921083026, 0.7140960805782015), (0.15588475410450106, 0.06603820408959558, 0.06060790921083026)))
assert_truncated_tuple_equal(p.chromatic_adaptation, (((1.04791259765625, 0.0229339599609375, -0.050201416015625), (0.02960205078125, 0.9904632568359375, -0.0170745849609375), (-0.009246826171875, 0.0150604248046875, 0.7517852783203125)), ((1.0267159024652783, 0.022470062342089134, 0.0229339599609375), (0.02951378324103937, 0.9875098886387147, 0.9904632568359375), (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875)))) assert_truncated_tuple_equal(p.chromatic_adaptation, (((1.04791259765625, 0.0229339599609375, -0.050201416015625), (0.02960205078125, 0.9904632568359375, -0.0170745849609375), (-0.009246826171875, 0.0150604248046875, 0.7517852783203125)), ((1.0267159024652783, 0.022470062342089134, 0.0229339599609375), (0.02951378324103937, 0.9875098886387147, 0.9904632568359375), (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875))))
self.assertEqual(p.chromaticity, None) self.assertIsNone(p.chromaticity)
self.assertEqual(p.clut, {0: (False, False, True), 1: (False, False, True), 2: (False, False, True), 3: (False, False, True)}) self.assertEqual(p.clut, {0: (False, False, True), 1: (False, False, True), 2: (False, False, True), 3: (False, False, True)})
self.assertEqual(p.color_space, 'RGB') self.assertEqual(p.color_space, 'RGB')
self.assertEqual(p.colorant_table, None) self.assertIsNone(p.colorant_table)
self.assertEqual(p.colorant_table_out, None) self.assertIsNone(p.colorant_table_out)
self.assertEqual(p.colorimetric_intent, None) self.assertIsNone(p.colorimetric_intent)
self.assertEqual(p.connection_space, 'XYZ ') self.assertEqual(p.connection_space, 'XYZ ')
self.assertEqual(p.copyright, 'Copyright International Color Consortium, 2009') self.assertEqual(p.copyright, 'Copyright International Color Consortium, 2009')
self.assertEqual(p.creation_date, datetime.datetime(2009, 2, 27, 21, 36, 31)) self.assertEqual(p.creation_date, datetime.datetime(2009, 2, 27, 21, 36, 31))
@ -292,17 +292,17 @@ class TestImageCms(PillowTestCase):
self.assertEqual(p.header_model, '\x00\x00\x00\x00') self.assertEqual(p.header_model, '\x00\x00\x00\x00')
self.assertEqual(p.icc_measurement_condition, {'backing': (0.0, 0.0, 0.0), 'flare': 0.0, 'geo': 'unknown', 'observer': 1, 'illuminant_type': 'D65'}) self.assertEqual(p.icc_measurement_condition, {'backing': (0.0, 0.0, 0.0), 'flare': 0.0, 'geo': 'unknown', 'observer': 1, 'illuminant_type': 'D65'})
self.assertEqual(p.icc_version, 33554432) self.assertEqual(p.icc_version, 33554432)
self.assertEqual(p.icc_viewing_condition, None) self.assertIsNone(p.icc_viewing_condition)
self.assertEqual(p.intent_supported, {0: (True, True, True), 1: (True, True, True), 2: (True, True, True), 3: (True, True, True)}) self.assertEqual(p.intent_supported, {0: (True, True, True), 1: (True, True, True), 2: (True, True, True), 3: (True, True, True)})
self.assertEqual(p.is_matrix_shaper, True) self.assertTrue(p.is_matrix_shaper)
self.assertEqual(p.luminance, ((0.0, 80.0, 0.0), (0.0, 1.0, 80.0))) self.assertEqual(p.luminance, ((0.0, 80.0, 0.0), (0.0, 1.0, 80.0)))
self.assertEqual(p.manufacturer, None) self.assertIsNone(p.manufacturer)
assert_truncated_tuple_equal(p.media_black_point, ((0.012054443359375, 0.0124969482421875, 0.01031494140625), (0.34573304157549234, 0.35842450765864337, 0.0124969482421875))) assert_truncated_tuple_equal(p.media_black_point, ((0.012054443359375, 0.0124969482421875, 0.01031494140625), (0.34573304157549234, 0.35842450765864337, 0.0124969482421875)))
assert_truncated_tuple_equal(p.media_white_point, ((0.964202880859375, 1.0, 0.8249053955078125), (0.3457029219802284, 0.3585375327567059, 1.0))) assert_truncated_tuple_equal(p.media_white_point, ((0.964202880859375, 1.0, 0.8249053955078125), (0.3457029219802284, 0.3585375327567059, 1.0)))
assert_truncated_tuple_equal((p.media_white_point_temperature,), (5000.722328847392,)) assert_truncated_tuple_equal((p.media_white_point_temperature,), (5000.722328847392,))
self.assertEqual(p.model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB') self.assertEqual(p.model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB')
self.assertEqual(p.pcs, 'XYZ') self.assertEqual(p.pcs, 'XYZ')
self.assertEqual(p.perceptual_rendering_intent_gamut, None) self.assertIsNone(p.perceptual_rendering_intent_gamut)
self.assertEqual(p.product_copyright, 'Copyright International Color Consortium, 2009') self.assertEqual(p.product_copyright, 'Copyright International Color Consortium, 2009')
self.assertEqual(p.product_desc, 'sRGB IEC61966-2-1 black scaled') self.assertEqual(p.product_desc, 'sRGB IEC61966-2-1 black scaled')
self.assertEqual(p.product_description, 'sRGB IEC61966-2-1 black scaled') self.assertEqual(p.product_description, 'sRGB IEC61966-2-1 black scaled')
@ -313,9 +313,9 @@ class TestImageCms(PillowTestCase):
assert_truncated_tuple_equal(p.red_colorant, ((0.436065673828125, 0.2224884033203125, 0.013916015625), (0.6484536316398539, 0.3308524880306778, 0.2224884033203125))) assert_truncated_tuple_equal(p.red_colorant, ((0.436065673828125, 0.2224884033203125, 0.013916015625), (0.6484536316398539, 0.3308524880306778, 0.2224884033203125)))
assert_truncated_tuple_equal(p.red_primary, ((0.43606566581047446, 0.22248840582960838, 0.013916015621759925), (0.6484536250319214, 0.3308524944738204, 0.22248840582960838))) assert_truncated_tuple_equal(p.red_primary, ((0.43606566581047446, 0.22248840582960838, 0.013916015621759925), (0.6484536250319214, 0.3308524944738204, 0.22248840582960838)))
self.assertEqual(p.rendering_intent, 0) self.assertEqual(p.rendering_intent, 0)
self.assertEqual(p.saturation_rendering_intent_gamut, None) self.assertIsNone(p.saturation_rendering_intent_gamut)
self.assertEqual(p.screening_description, None) self.assertIsNone(p.screening_description)
self.assertEqual(p.target, None) self.assertIsNone(p.target)
self.assertEqual(p.technology, 'CRT ') self.assertEqual(p.technology, 'CRT ')
self.assertEqual(p.version, 2.0) self.assertEqual(p.version, 2.0)
self.assertEqual(p.viewing_condition, 'Reference Viewing Condition in IEC 61966-2-1') self.assertEqual(p.viewing_condition, 'Reference Viewing Condition in IEC 61966-2-1')

View File

@ -119,7 +119,7 @@ class TestImageColor(PillowTestCase):
# look for rounding errors (based on code by Tim Hatch) # look for rounding errors (based on code by Tim Hatch)
def test_rounding_errors(self): def test_rounding_errors(self):
for color in list(ImageColor.colormap.keys()): for color in ImageColor.colormap:
expected = Image.new( expected = Image.new(
"RGB", (1, 1), color).convert("L").getpixel((0, 0)) "RGB", (1, 1), color).convert("L").getpixel((0, 0))
actual = ImageColor.getcolor(color, 'L') actual = ImageColor.getcolor(color, 'L')

View File

@ -30,6 +30,8 @@ BBOX2 = [X0, Y0, X1, Y1]
POINTS1 = [(10, 10), (20, 40), (30, 30)] POINTS1 = [(10, 10), (20, 40), (30, 30)]
POINTS2 = [10, 10, 20, 40, 30, 30] POINTS2 = [10, 10, 20, 40, 30, 30]
KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)]
class TestImageDraw(PillowTestCase): class TestImageDraw(PillowTestCase):
@ -78,6 +80,37 @@ class TestImageDraw(PillowTestCase):
self.helper_arc(BBOX2, 0, 180) self.helper_arc(BBOX2, 0, 180)
self.helper_arc(BBOX2, 0.5, 180.4) self.helper_arc(BBOX2, 0.5, 180.4)
def test_arc_end_le_start(self):
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
start = 270.5
end = 0
# Act
draw.arc(BBOX1, start=start, end=end)
del draw
# Assert
self.assert_image_equal(
im, Image.open("Tests/images/imagedraw_arc_end_le_start.png"))
def test_arc_no_loops(self):
# No need to go in loops
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
start = 5
end = 370
# Act
draw.arc(BBOX1, start=start, end=end)
del draw
# Assert
self.assert_image_similar(
im, Image.open("Tests/images/imagedraw_arc_no_loops.png"), 1)
def test_bitmap(self): def test_bitmap(self):
# Arrange # Arrange
small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) small = Image.open("Tests/images/pil123rgba.png").resize((50, 50))
@ -92,45 +125,49 @@ class TestImageDraw(PillowTestCase):
self.assert_image_equal( self.assert_image_equal(
im, Image.open("Tests/images/imagedraw_bitmap.png")) im, Image.open("Tests/images/imagedraw_bitmap.png"))
def helper_chord(self, bbox, start, end): def helper_chord(self, mode, bbox, start, end):
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new(mode, (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_chord_{}.png".format(mode)
# Act # Act
draw.chord(bbox, start, end, fill="red", outline="yellow") draw.chord(bbox, start, end, fill="red", outline="yellow")
del draw del draw
# Assert # Assert
self.assert_image_similar( self.assert_image_similar(im, Image.open(expected), 1)
im, Image.open("Tests/images/imagedraw_chord.png"), 1)
def test_chord1(self): def test_chord1(self):
self.helper_chord(BBOX1, 0, 180) for mode in ["RGB", "L"]:
self.helper_chord(BBOX1, 0.5, 180.4) self.helper_chord(mode, BBOX1, 0, 180)
self.helper_chord(mode, BBOX1, 0.5, 180.4)
def test_chord2(self): def test_chord2(self):
self.helper_chord(BBOX2, 0, 180) for mode in ["RGB", "L"]:
self.helper_chord(BBOX2, 0.5, 180.4) self.helper_chord(mode, BBOX2, 0, 180)
self.helper_chord(mode, BBOX2, 0.5, 180.4)
def helper_ellipse(self, bbox): def helper_ellipse(self, mode, bbox):
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new(mode, (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_ellipse_{}.png".format(mode)
# Act # Act
draw.ellipse(bbox, fill="green", outline="blue") draw.ellipse(bbox, fill="green", outline="blue")
del draw del draw
# Assert # Assert
self.assert_image_similar( self.assert_image_similar(im, Image.open(expected), 1)
im, Image.open("Tests/images/imagedraw_ellipse.png"), 1)
def test_ellipse1(self): def test_ellipse1(self):
self.helper_ellipse(BBOX1) for mode in ["RGB", "L"]:
self.helper_ellipse(mode, BBOX1)
def test_ellipse2(self): def test_ellipse2(self):
self.helper_ellipse(BBOX2) for mode in ["RGB", "L"]:
self.helper_ellipse(mode, BBOX2)
def test_ellipse_edge(self): def test_ellipse_edge(self):
# Arrange # Arrange
@ -267,6 +304,23 @@ class TestImageDraw(PillowTestCase):
def test_polygon2(self): def test_polygon2(self):
self.helper_polygon(POINTS2) self.helper_polygon(POINTS2)
def test_polygon_kite(self):
# Test drawing lines of different gradients (dx>dy, dy>dx) and
# vertical (dx==0) and horizontal (dy==0) lines
for mode in ["RGB", "L"]:
# Arrange
im = Image.new(mode, (W, H))
draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_polygon_kite_{}.png".format(
mode)
# Act
draw.polygon(KITE_POINTS, fill="blue", outline="yellow")
del draw
# Assert
self.assert_image_equal(im, Image.open(expected))
def helper_rectangle(self, bbox): def helper_rectangle(self, bbox):
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
@ -286,6 +340,21 @@ class TestImageDraw(PillowTestCase):
def test_rectangle2(self): def test_rectangle2(self):
self.helper_rectangle(BBOX2) self.helper_rectangle(BBOX2)
def test_big_rectangle(self):
# Test drawing a rectangle bigger than the image
# Arrange
im = Image.new("RGB", (W, H))
bbox = [(-1, -1), (W+1, H+1)]
draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_big_rectangle.png"
# Act
draw.rectangle(bbox, fill="orange")
del draw
# Assert
self.assert_image_similar(im, Image.open(expected), 1)
def test_floodfill(self): def test_floodfill(self):
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
@ -478,6 +547,21 @@ class TestImageDraw(PillowTestCase):
self.assert_image_equal(img, expected, self.assert_image_equal(img, expected,
'line oblique 45 inverted 3px wide B failed') 'line oblique 45 inverted 3px wide B failed')
def test_wide_line_dot(self):
# Test drawing a wide "line" from one point to another just draws
# a single point
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_wide_line_dot.png"
# Act
draw.line([(50, 50), (50, 50)], width=3)
del draw
# Assert
self.assert_image_similar(im, Image.open(expected), 1)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -38,7 +38,7 @@ class TestImageGrabImport(PillowTestCase):
# Assert # Assert
if sys.platform in ["win32", "darwin"]: if sys.platform in ["win32", "darwin"]:
self.assertIsNone(exception, None) self.assertIsNone(exception)
else: else:
self.assertIsInstance(exception, ImportError) self.assertIsInstance(exception, ImportError)
self.assertEqual(str(exception), self.assertEqual(str(exception),

View File

@ -176,6 +176,40 @@ class MorphTests(PillowTestCase):
self.assertEqual(len(coords), 4) self.assertEqual(len(coords), 4)
self.assertEqual(tuple(coords), ((2, 2), (4, 2), (2, 4), (4, 4))) self.assertEqual(tuple(coords), ((2, 2), (4, 2), (2, 4), (4, 4)))
def test_mirroring(self):
# Test 'M' for mirroring
mop = ImageMorph.MorphOp(patterns=['1:(... ... ...)->0',
'M:(00. 01. ...)->1'])
count, Aout = mop.apply(self.A)
self.assertEqual(count, 7)
self.assert_img_equal_img_string(Aout,
"""
.......
.......
..1.1..
.......
.......
.......
.......
""")
def test_negate(self):
# Test 'N' for negate
mop = ImageMorph.MorphOp(patterns=['1:(... ... ...)->0',
'N:(00. 01. ...)->1'])
count, Aout = mop.apply(self.A)
self.assertEqual(count, 8)
self.assert_img_equal_img_string(Aout,
"""
.......
.......
..1....
.......
.......
.......
.......
""")
def test_non_binary_images(self): def test_non_binary_images(self):
im = hopper('RGB') im = hopper('RGB')
mop = ImageMorph.MorphOp(op_name="erosion8") mop = ImageMorph.MorphOp(op_name="erosion8")
@ -184,6 +218,74 @@ class MorphTests(PillowTestCase):
self.assertRaises(Exception, lambda: mop.match(im)) self.assertRaises(Exception, lambda: mop.match(im))
self.assertRaises(Exception, lambda: mop.get_on_pixels(im)) self.assertRaises(Exception, lambda: mop.get_on_pixels(im))
def test_add_patterns(self):
# Arrange
lb = ImageMorph.LutBuilder(op_name='corner')
self.assertEqual(lb.patterns, ['1:(... ... ...)->0',
'4:(00. 01. ...)->1'])
new_patterns = ['M:(00. 01. ...)->1',
'N:(00. 01. ...)->1']
# Act
lb.add_patterns(new_patterns)
# Assert
self.assertEqual(
lb.patterns,
['1:(... ... ...)->0',
'4:(00. 01. ...)->1',
'M:(00. 01. ...)->1',
'N:(00. 01. ...)->1'])
def test_unknown_pattern(self):
self.assertRaises(
Exception,
lambda: ImageMorph.LutBuilder(op_name='unknown'))
def test_pattern_syntax_error(self):
# Arrange
lb = ImageMorph.LutBuilder(op_name='corner')
new_patterns = ['a pattern with a syntax error']
lb.add_patterns(new_patterns)
# Act / Assert
self.assertRaises(
Exception,
lambda: lb.build_lut())
def test_load_invalid_mrl(self):
# Arrange
invalid_mrl = 'Tests/images/hopper.png'
mop = ImageMorph.MorphOp()
# Act / Assert
self.assertRaises(Exception, lambda: mop.load_lut(invalid_mrl))
def test_roundtrip_mrl(self):
# Arrange
tempfile = self.tempfile('temp.mrl')
mop = ImageMorph.MorphOp(op_name='corner')
initial_lut = mop.lut
# Act
mop.save_lut(tempfile)
mop.load_lut(tempfile)
# Act / Assert
self.assertEqual(mop.lut, initial_lut)
def test_set_lut(self):
# Arrange
lb = ImageMorph.LutBuilder(op_name='corner')
lut = lb.build_lut()
mop = ImageMorph.MorphOp()
# Act
mop.set_lut(lut)
# Assert
self.assertEqual(mop.lut, lut)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -45,7 +45,7 @@ class TestImageTk(PillowTestCase):
# Test no relevant entry # Test no relevant entry
im = ImageTk._get_image_from_kw(kw) im = ImageTk._get_image_from_kw(kw)
self.assertEqual(im, None) self.assertIsNone(im)
def test_photoimage(self): def test_photoimage(self):
for mode in TK_MODES: for mode in TK_MODES:

View File

@ -34,10 +34,10 @@ class Test_IFDRational(PillowTestCase):
xres = IFDRational(72) xres = IFDRational(72)
yres = IFDRational(72) yres = IFDRational(72)
self.assertTrue(xres._val is not None) self.assertIsNotNone(xres._val)
self.assertTrue(xres.numerator is not None) self.assertIsNotNone(xres.numerator)
self.assertTrue(xres.denominator is not None) self.assertIsNotNone(xres.denominator)
self.assertTrue(yres._val is not None) self.assertIsNotNone(yres._val)
self.assertTrue(xres and 1) self.assertTrue(xres and 1)
self.assertTrue(xres and yres) self.assertTrue(xres and yres)

View File

@ -631,18 +631,6 @@ _new(PyObject* self, PyObject* args)
return PyImagingNew(ImagingNew(mode, xsize, ysize)); return PyImagingNew(ImagingNew(mode, xsize, ysize));
} }
static PyObject*
_new_array(PyObject* self, PyObject* args)
{
char* mode;
int xsize, ysize;
if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize))
return NULL;
return PyImagingNew(ImagingNewArray(mode, xsize, ysize));
}
static PyObject* static PyObject*
_new_block(PyObject* self, PyObject* args) _new_block(PyObject* self, PyObject* args)
{ {
@ -3027,7 +3015,6 @@ static struct PyMethodDef methods[] = {
#endif #endif
/* Misc. */ /* Misc. */
{"new_array", (PyCFunction)_new_array, 1},
{"new_block", (PyCFunction)_new_block, 1}, {"new_block", (PyCFunction)_new_block, 1},
{"save_ppm", (PyCFunction)_save_ppm, 1}, {"save_ppm", (PyCFunction)_save_ppm, 1},

13
_webp.c
View File

@ -247,8 +247,12 @@ PyObject* WebPDecoderVersion_wrapper(PyObject* self, PyObject* args){
* The version of webp that ships with (0.1.3) Ubuntu 12.04 doesn't handle alpha well. * The version of webp that ships with (0.1.3) Ubuntu 12.04 doesn't handle alpha well.
* Files that are valid with 0.3 are reported as being invalid. * Files that are valid with 0.3 are reported as being invalid.
*/ */
int WebPDecoderBuggyAlpha() {
return WebPGetDecoderVersion()==0x0103;
}
PyObject* WebPDecoderBuggyAlpha_wrapper(PyObject* self, PyObject* args){ PyObject* WebPDecoderBuggyAlpha_wrapper(PyObject* self, PyObject* args){
return Py_BuildValue("i", WebPGetDecoderVersion()==0x0103); return Py_BuildValue("i", WebPDecoderBuggyAlpha());
} }
static PyMethodDef webpMethods[] = static PyMethodDef webpMethods[] =
@ -268,6 +272,11 @@ void addMuxFlagToModule(PyObject* m) {
#endif #endif
} }
void addTransparencyFlagToModule(PyObject* m) {
PyModule_AddObject(m, "HAVE_TRANSPARENCY",
PyBool_FromLong(!WebPDecoderBuggyAlpha()));
}
#if PY_VERSION_HEX >= 0x03000000 #if PY_VERSION_HEX >= 0x03000000
PyMODINIT_FUNC PyMODINIT_FUNC
@ -284,6 +293,7 @@ PyInit__webp(void) {
m = PyModule_Create(&module_def); m = PyModule_Create(&module_def);
addMuxFlagToModule(m); addMuxFlagToModule(m);
addTransparencyFlagToModule(m);
return m; return m;
} }
#else #else
@ -292,5 +302,6 @@ init_webp(void)
{ {
PyObject* m = Py_InitModule("_webp", webpMethods); PyObject* m = Py_InitModule("_webp", webpMethods);
addMuxFlagToModule(m); addMuxFlagToModule(m);
addTransparencyFlagToModule(m);
} }
#endif #endif

View File

@ -45,8 +45,8 @@ image. The current release supports the following standard modes:
PIL also provides limited support for a few special modes, including ``LA`` (L PIL also provides limited support for a few special modes, including ``LA`` (L
with alpha), ``RGBX`` (true color with padding) and ``RGBa`` (true color with with alpha), ``RGBX`` (true color with padding) and ``RGBa`` (true color with
premultiplied alpha). However, PIL doesnt support user-defined modes; if you premultiplied alpha). However, PIL doesnt support user-defined modes; if you
to handle band combinations that are not listed above, use a sequence of Image need to handle band combinations that are not listed above, use a sequence of
objects. Image objects.
You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode` You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode`
attribute. This is a string containing one of the above values. attribute. This is a string containing one of the above values.
@ -114,7 +114,7 @@ pixel, the Python Imaging Library provides different resampling *filters*.
in the input image is used. in the input image is used.
``HAMMING`` ``HAMMING``
Produces more sharp image than ``BILINEAR``, doesn't have dislocations Produces a sharper image than ``BILINEAR``, doesn't have dislocations
on local level like with ``BOX``. on local level like with ``BOX``.
This filter can only be used with the :py:meth:`~PIL.Image.Image.resize` This filter can only be used with the :py:meth:`~PIL.Image.Image.resize`
and :py:meth:`~PIL.Image.Image.thumbnail` methods. and :py:meth:`~PIL.Image.Image.thumbnail` methods.

View File

@ -276,6 +276,7 @@ Converting between modes
:: ::
from PIL import Image
im = Image.open("hopper.ppm").convert("L") im = Image.open("hopper.ppm").convert("L")
The library supports transformations between each supported mode and the “L” The library supports transformations between each supported mode and the “L”
@ -459,6 +460,7 @@ As described earlier, the :py:func:`~PIL.Image.open` function of the
:py:mod:`~PIL.Image` module is used to open an image file. In most cases, you :py:mod:`~PIL.Image` module is used to open an image file. In most cases, you
simply pass it the filename as an argument:: simply pass it the filename as an argument::
from PIL import Image
im = Image.open("hopper.ppm") im = Image.open("hopper.ppm")
If everything goes well, the result is an :py:class:`PIL.Image.Image` object. If everything goes well, the result is an :py:class:`PIL.Image.Image` object.
@ -473,8 +475,9 @@ Reading from an open file
:: ::
fp = open("hopper.ppm", "rb") from PIL import Image
im = Image.open(fp) with open("hopper.ppm", "rb") as fp:
im = Image.open(fp)
To read an image from string data, use the :py:class:`~StringIO.StringIO` To read an image from string data, use the :py:class:`~StringIO.StringIO`
class: class:

View File

@ -402,11 +402,11 @@ These platforms have been reported to work at the versions mentioned.
+----------------------------------+------------------------------+--------------------------------+-----------------------+ +----------------------------------+------------------------------+--------------------------------+-----------------------+
| Gentoo Linux | 2.7,3.2 | 2.1.0 |x86-64 | | Gentoo Linux | 2.7,3.2 | 2.1.0 |x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+ +----------------------------------+------------------------------+--------------------------------+-----------------------+
| FreeBSD 10.2 | 2.7,3.4 | 3.1.0 |x86-64 | | FreeBSD 11.0 | 2.7,3.4,3.5,3.6 | 4.1.1 |x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+ +----------------------------------+------------------------------+--------------------------------+-----------------------+
| FreeBSD 10.3 | 2.7,3.4,3.5 | 4.1.1 |x86-64 | | FreeBSD 10.3 | 2.7,3.4,3.5 | 4.1.1 |x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+ +----------------------------------+------------------------------+--------------------------------+-----------------------+
| FreeBSD 11.0 | 2.7,3.4,3.5,3.6 | 4.1.1 |x86-64 | | FreeBSD 10.2 | 2.7,3.4 | 3.1.0 |x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+ +----------------------------------+------------------------------+--------------------------------+-----------------------+
| Windows 8.1 Pro | 2.6,2.7,3.2,3.3,3.4 | 2.4.0 |x86,x86-64 | | Windows 8.1 Pro | 2.6,2.7,3.2,3.3,3.4 | 2.4.0 |x86,x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+ +----------------------------------+------------------------------+--------------------------------+-----------------------+

View File

@ -10,7 +10,7 @@ Internal Limits
* Image sizes cannot be negative. These are checked both in * Image sizes cannot be negative. These are checked both in
``Storage.c`` and ``Image.py`` ``Storage.c`` and ``Image.py``
* Image sizes may be 0. (At least, prior to 3.4) * Image sizes may be 0. (Although not in 3.4)
* Maximum pixel dimensions are limited to INT32, or 2^31 by the sizes * Maximum pixel dimensions are limited to INT32, or 2^31 by the sizes
in the image header. in the image header.

View File

@ -148,7 +148,7 @@ Blur performance
Box filter computation time is constant relative to the radius and depends Box filter computation time is constant relative to the radius and depends
on source image size only. Because the new Gaussian blur implementation on source image size only. Because the new Gaussian blur implementation
is based on box filter, its computation time also doesn't depends on the blur is based on box filter, its computation time also doesn't depend on the blur
radius. radius.
For example, previously, if the execution time for a given test image was 1 For example, previously, if the execution time for a given test image was 1
@ -172,4 +172,3 @@ specified as strings with included spaces (e.g. 'x resolution'). This
was difficult to use as kwargs without constructing and passing a was difficult to use as kwargs without constructing and passing a
dictionary. These parameters now use the underscore character instead dictionary. These parameters now use the underscore character instead
of space. (e.g. 'x_resolution') of space. (e.g. 'x_resolution')

View File

@ -68,7 +68,7 @@ Out of Spec Metadata
++++++++++++++++++++ ++++++++++++++++++++
In Pillow 3.0 and 3.1, images that contain metadata that is internally In Pillow 3.0 and 3.1, images that contain metadata that is internally
consistent but not in agreement with the TIFF spec may cause an consistent, but not in agreement with the TIFF spec, may cause an
exception when reading the metadata. This can happen when a tag that exception when reading the metadata. This can happen when a tag that
is specified to have a single value is stored with an array of values. is specified to have a single value is stored with an array of values.

View File

@ -29,9 +29,9 @@ Resizing
======== ========
Image resampling for 8-bit per channel images was rewritten using only integer Image resampling for 8-bit per channel images was rewritten using only integer
computings. This is faster on most of the platforms and doesn't introduce computings. This is faster on most platforms and doesn't introduce precision
precision errors on the wide range of scales. With other performance errors on the wide range of scales. With other performance improvements, this
improvements, this makes resampling 60% faster on average. makes resampling 60% faster on average.
Color calculation for images in the ``LA`` mode on semitransparent pixels Color calculation for images in the ``LA`` mode on semitransparent pixels
was fixed. was fixed.
@ -41,7 +41,7 @@ Rotation
======== ========
Rotation for angles divisible by 90 degrees now always uses transposition. Rotation for angles divisible by 90 degrees now always uses transposition.
This greatly improve both quality and performance in this cases. This greatly improves both quality and performance in this case.
Also, the bug with wrong image size calculation when rotating by 90 degrees Also, the bug with wrong image size calculation when rotating by 90 degrees
was fixed. was fixed.
@ -52,4 +52,3 @@ Image Metadata
The return type for binary data in version 2 Exif and Tiff metadata The return type for binary data in version 2 Exif and Tiff metadata
has been changed from a tuple of integers to bytes. This is a change has been changed from a tuple of integers to bytes. This is a change
from the behavior since ``3.0.0``. from the behavior since ``3.0.0``.

View File

@ -8,7 +8,7 @@ New resizing filters
Two new filters available for ``Image.resize()`` and ``Image.thumbnail()`` Two new filters available for ``Image.resize()`` and ``Image.thumbnail()``
functions: ``BOX`` and ``HAMMING``. ``BOX`` is the high-performance filter with functions: ``BOX`` and ``HAMMING``. ``BOX`` is the high-performance filter with
two times shorter window than ``BILINEAR``. It can be used for image reduction two times shorter window than ``BILINEAR``. It can be used for image reduction
3 and more times and produces a more sharp result than ``BILINEAR``. 3 and more times and produces a sharper result than ``BILINEAR``.
``HAMMING`` filter has the same performance as ``BILINEAR`` filter while ``HAMMING`` filter has the same performance as ``BILINEAR`` filter while
providing the image downscaling quality comparable to ``BICUBIC``. providing the image downscaling quality comparable to ``BICUBIC``.
@ -25,7 +25,7 @@ image as a JPEG. This will become an error in Pillow 4.2.
New DDS Decoders New DDS Decoders
================ ================
Pillow can now decode DXT3 images, as well as the previously support Pillow can now decode DXT3 images, as well as the previously supported
DXT1 and DXT5 formats. All three formats are now decoded in C code for DXT1 and DXT5 formats. All three formats are now decoded in C code for
better performance. better performance.
@ -44,7 +44,7 @@ in effect, e.g.::
Save multiple frame TIFF Save multiple frame TIFF
======================== ========================
Multiple frames can now be saved in a TIFF file by using the ``save_all`` option. Multiple frames can now be saved in a TIFF file by using the ``save_all`` option.
e.g.:: e.g.::
im.save("filename.tiff", format="TIFF", save_all=True) im.save("filename.tiff", format="TIFF", save_all=True)

View File

@ -605,11 +605,6 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1,
DRAWINIT(); DRAWINIT();
if (width <= 1) {
draw->line(im, x0, y0, x1, y1, ink);
return 0;
}
dx = x1-x0; dx = x1-x0;
dy = y1-y0; dy = y1-y0;
if (dx == 0 && dy == 0) { if (dx == 0 && dy == 0) {
@ -1030,20 +1025,6 @@ ImagingOutlineCurve(ImagingOutline outline, float x1, float y1,
return 0; return 0;
} }
int
ImagingOutlineCurve2(ImagingOutline outline, float cx, float cy,
float x3, float y3)
{
/* add bezier curve based on three control points (as
in the Flash file format) */
return ImagingOutlineCurve(
outline,
(outline->x + cx + cx)/3, (outline->y + cy + cy)/3,
(cx + cx + x3)/3, (cy + cy + y3)/3,
x3, y3);
}
int int
ImagingOutlineClose(ImagingOutline outline) ImagingOutlineClose(ImagingOutline outline)
{ {

View File

@ -268,7 +268,7 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
int ImagingJpegDecodeCleanup(ImagingCodecState state){ int ImagingJpegDecodeCleanup(ImagingCodecState state){
/* called to fee the decompression engine when the decode terminates /* called to free the decompression engine when the decode terminates
due to a corrupt or truncated image due to a corrupt or truncated image
*/ */
JPEGSTATE* context = (JPEGSTATE*) state->context; JPEGSTATE* context = (JPEGSTATE*) state->context;

View File

@ -54,7 +54,8 @@ def _mp_compile(self, sources, output_dir=None, macros=None,
def install(): def install():
fl_pypy3 = hasattr(sys, 'pypy_version_info') and sys.version_info > (3, 0) fl_pypy3 = (hasattr(sys, 'pypy_version_info') and
(3, 0) < sys.version_info < (3, 3))
fl_win = sys.platform.startswith('win') fl_win = sys.platform.startswith('win')
fl_cygwin = sys.platform.startswith('cygwin') fl_cygwin = sys.platform.startswith('cygwin')
@ -82,4 +83,5 @@ def install():
print("Single threaded build, not installing mp_compile:" print("Single threaded build, not installing mp_compile:"
"%s processes" % MAX_PROCS) "%s processes" % MAX_PROCS)
install() install()

View File

@ -178,25 +178,14 @@ if __name__ == "__main__":
("freetype2", "FREETYPE2"), ("freetype2", "FREETYPE2"),
("littlecms2", "LITTLECMS2"), ("littlecms2", "LITTLECMS2"),
("webp", "WEBP"), ("webp", "WEBP"),
("transp_webp", "Transparent WEBP") ("transp_webp", "Transparent WEBP"),
]: ("webp_mux", "WEBPMUX"),
supported = features.check_module(name)
if supported is None:
# A method was being tested, but the module required
# for the method could not be correctly imported
pass
elif supported:
print("---", feature, "support ok")
else:
print("***", feature, "support not installed")
for name, feature in [
("jpg", "JPEG"), ("jpg", "JPEG"),
("jpg_2000", "OPENJPEG (JPEG2000)"), ("jpg_2000", "OPENJPEG (JPEG2000)"),
("zlib", "ZLIB (PNG/ZIP)"), ("zlib", "ZLIB (PNG/ZIP)"),
("libtiff", "LIBTIFF") ("libtiff", "LIBTIFF")
]: ]:
if features.check_codec(name): if features.check(name):
print("---", feature, "support ok") print("---", feature, "support ok")
else: else:
print("***", feature, "support not installed") print("***", feature, "support not installed")

View File

@ -12,7 +12,7 @@ from config import (compilers, compiler_from_env, pythons, pyversion_from_env,
def setup_vms(): def setup_vms():
ret = [] ret = []
for py in pythons.keys(): for py in pythons:
for arch in ('', X64_EXT): for arch in ('', X64_EXT):
ret.append("virtualenv -p c:/Python%s%s/python.exe --clear %s%s%s" ret.append("virtualenv -p c:/Python%s%s/python.exe --clear %s%s%s"
% (py, arch, VIRT_BASE, py, arch)) % (py, arch, VIRT_BASE, py, arch))

View File

@ -116,7 +116,7 @@ def pyversion_from_env():
py = os.environ['PYTHON'] py = os.environ['PYTHON']
py_version = '27' py_version = '27'
for k in pythons.keys(): for k in pythons:
if k in py: if k in py:
py_version = k py_version = k
break break