diff --git a/.gitignore b/.gitignore index 95ed4bac5..aa45f946f 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ htmlcov/ nosetests.xml coverage.xml +# Test files +test_images + # Translations *.mo diff --git a/.travis.yml b/.travis.yml index 8fc19a876..6349db0fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,8 @@ notifications: matrix: fast_finish: true include: - - python: "pypy" - - python: "pypy3" + - python: "pypy-5.7.1" + - python: "pypy3.3-5.2-alpha1" - python: '3.6' - python: '2.7' - python: "2.7_with_system_site_packages" # For PyQt4 diff --git a/.travis/install.sh b/.travis/install.sh index 4b7503bed..d5f190619 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -3,18 +3,16 @@ set -e 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 pip install cffi pip install nose pip install check-manifest pip install olefile -# Pyroma tests sometimes hang on PyPy; skip -if [ "$TRAVIS_PYTHON_VERSION" != "pypy" ]; then pip install pyroma; fi - +pip install pyroma 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 # clean checkout for manifest diff --git a/CHANGES.rst b/CHANGES.rst index 52f8cea43..18e6cd440 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,15 @@ Changelog (Pillow) 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 [wiredfool] diff --git a/PIL/IcoImagePlugin.py b/PIL/IcoImagePlugin.py index e4db4e766..428fdd41a 100644 --- a/PIL/IcoImagePlugin.py +++ b/PIL/IcoImagePlugin.py @@ -176,8 +176,8 @@ class IcoFile(object): # figure out where AND mask image starts mode = a[0] bpp = 8 - for k in BmpImagePlugin.BIT2MODE.keys(): - if mode == BmpImagePlugin.BIT2MODE[k][1]: + for k, v in BmpImagePlugin.BIT2MODE.items(): + if mode == v[1]: bpp = k break diff --git a/PIL/Image.py b/PIL/Image.py index c8315ba76..1f968daf5 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -274,7 +274,7 @@ def _conv_type_shape(im): return shape+(extra,), typ -MODES = sorted(_MODEINFO.keys()) +MODES = sorted(_MODEINFO) # raw modes that may be memory mapped. NOTE: if you change this, you # may have to modify the stride calculation in map.c too! diff --git a/PIL/ImageDraw.py b/PIL/ImageDraw.py index ddf669f78..71e29ee48 100644 --- a/PIL/ImageDraw.py +++ b/PIL/ImageDraw.py @@ -283,6 +283,7 @@ def Draw(im, mode=None): except AttributeError: return ImageDraw(im, mode) + # experimental access to the outline API try: Outline = Image.core.outline diff --git a/PIL/ImageMorph.py b/PIL/ImageMorph.py index 8382f7ce0..d2367737f 100644 --- a/PIL/ImageMorph.py +++ b/PIL/ImageMorph.py @@ -123,7 +123,7 @@ class LutBuilder(object): .replace('0', 'Z') .replace('1', '0') .replace('Z', '1')) - res = '%d' % (1-int(res)) + res = 1-int(res) patterns.append((pattern, res)) return patterns @@ -152,8 +152,8 @@ class LutBuilder(object): patterns += self._pattern_permute(pattern, options, result) # # Debugging -# for p,r in patterns: -# print(p,r) +# for p, r in patterns: +# print(p, r) # print('--') # compile the patterns into regular expressions for speed @@ -234,7 +234,7 @@ class MorphOp(object): with open(filename, 'rb') as f: self.lut = bytearray(f.read()) - if len(self.lut) != 8192: + if len(self.lut) != LUT_SIZE: self.lut = None raise Exception('Wrong size operator file!') diff --git a/PIL/IptcImagePlugin.py b/PIL/IptcImagePlugin.py index 8941643bb..f5a8de17e 100644 --- a/PIL/IptcImagePlugin.py +++ b/PIL/IptcImagePlugin.py @@ -95,7 +95,7 @@ class IptcImageFile(ImageFile.ImageFile): tagdata = self.fp.read(size) else: tagdata = None - if tag in list(self.info.keys()): + if tag in self.info: if isinstance(self.info[tag], list): self.info[tag].append(tagdata) else: diff --git a/PIL/McIdasImagePlugin.py b/PIL/McIdasImagePlugin.py index 08eeec39f..06da33f77 100644 --- a/PIL/McIdasImagePlugin.py +++ b/PIL/McIdasImagePlugin.py @@ -66,6 +66,7 @@ class McIdasImageFile(ImageFile.ImageFile): self.tile = [("raw", (0, 0) + self.size, offset, (rawmode, stride, 1))] + # -------------------------------------------------------------------- # registry diff --git a/PIL/PdfImagePlugin.py b/PIL/PdfImagePlugin.py index b615fe1e0..a6ca0320a 100644 --- a/PIL/PdfImagePlugin.py +++ b/PIL/PdfImagePlugin.py @@ -20,7 +20,7 @@ # Image plugin for PDF images (output only). ## -from . import Image, ImageFile +from . import Image, ImageFile, ImageSequence from ._binary import i8 import io @@ -133,13 +133,24 @@ def _save(im, fp, filename, save_all=False): # # pages - numberOfPages = 1 + ims = [im] if save_all: - try: - numberOfPages = im.n_frames - except AttributeError: - # Image format does not have n_frames. It is a single frame image - pass + append_images = im.encoderinfo.get("append_images", []) + for append_im in append_images: + if append_im.mode != im.mode: + append_im = append_im.convert(im.mode) + 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" for pageNumber in range(0, numberOfPages)] @@ -151,90 +162,92 @@ def _save(im, fp, filename, save_all=False): Kids="["+"\n".join(pages)+"]") _endobj(fp) - for pageNumber in range(0, numberOfPages): - im.seek(pageNumber) + pageNumber = 0 + for imSequence in ims: + for im in ImageSequence.Iterator(imSequence): + # + # image - # - # image + op = io.BytesIO() - 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: - # 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) + # + # Get image characteristics - # - # Get image characteristics + width, height = im.size - 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()) - _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) + fp.write("stream\n") + fp.fp.write(op.getvalue()) + fp.write("\nendstream\n") - fp.write("stream\n") - fp.fp.write(op.getvalue()) - fp.write("\nendstream\n") + _endobj(fp) - _endobj(fp) + # + # page - # - # page + xref.append(fp.tell()) + _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) - 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) + # + # page contents - # - # page contents + op = TextWriter(io.BytesIO()) - op = TextWriter(io.BytesIO()) + op.write( + "q %d 0 0 %d 0 0 cm /image Do Q\n" % ( + int(width * 72.0 / resolution), + int(height * 72.0 / resolution))) - op.write( - "q %d 0 0 %d 0 0 cm /image Do Q\n" % ( - int(width * 72.0 / resolution), - int(height * 72.0 / resolution))) + xref.append(fp.tell()) + _obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue())) - xref.append(fp.tell()) - _obj(fp, pageNumber*3+5, Length=len(op.fp.getvalue())) + fp.write("stream\n") + fp.fp.write(op.fp.getvalue()) + fp.write("\nendstream\n") - fp.write("stream\n") - fp.fp.write(op.fp.getvalue()) - fp.write("\nendstream\n") + _endobj(fp) - _endobj(fp) + pageNumber += 1 # # trailer diff --git a/PIL/features.py b/PIL/features.py index fb8e4371b..e01a32193 100644 --- a/PIL/features.py +++ b/PIL/features.py @@ -2,45 +2,26 @@ from . import Image modules = { "pil": "PIL._imaging", - "tkinter": "PIL._imagingtk", + "tkinter": "PIL._tkinter_finder", "freetype2": "PIL._imagingft", "littlecms2": "PIL._imagingcms", "webp": "PIL._webp", - "transp_webp": ("WEBP", "WebPDecoderBuggyAlpha") } - def check_module(feature): - if feature not in modules: + if not (feature in modules): raise ValueError("Unknown module %s" % feature) module = modules[feature] - method_to_call = None - if isinstance(module, tuple): - module, method_to_call = module - try: 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 - + except ImportError: + return False def get_supported_modules(): - supported_modules = [] - for feature in modules: - if check_module(feature): - supported_modules.append(feature) - return supported_modules + return [f for f in modules if check_module(f)] codecs = { "jpg": "jpeg", @@ -49,7 +30,6 @@ codecs = { "libtiff": "libtiff" } - def check_codec(feature): if feature not in codecs: raise ValueError("Unknown codec %s" % feature) @@ -60,8 +40,38 @@ def check_codec(feature): def get_supported_codecs(): - supported_codecs = [] - for feature in codecs: - if check_codec(feature): - supported_codecs.append(feature) - return supported_codecs + return [f for f in codecs if check_codec(f)] + +features = { + "webp_mux": ("PIL._webp", 'HAVE_WEBPMUX'), + "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 + diff --git a/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.ara b/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.ara new file mode 100644 index 000000000..4cdc741d7 Binary files /dev/null and b/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.ara differ diff --git a/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png b/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png new file mode 100644 index 000000000..2b84283b7 Binary files /dev/null and b/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png differ diff --git a/Tests/images/imagedraw_arc_end_le_start.png b/Tests/images/imagedraw_arc_end_le_start.png new file mode 100644 index 000000000..aee48e1c6 Binary files /dev/null and b/Tests/images/imagedraw_arc_end_le_start.png differ diff --git a/Tests/images/imagedraw_arc_no_loops.png b/Tests/images/imagedraw_arc_no_loops.png new file mode 100644 index 000000000..e45ad57a5 Binary files /dev/null and b/Tests/images/imagedraw_arc_no_loops.png differ diff --git a/Tests/images/imagedraw_big_rectangle.png b/Tests/images/imagedraw_big_rectangle.png new file mode 100644 index 000000000..fa2370b28 Binary files /dev/null and b/Tests/images/imagedraw_big_rectangle.png differ diff --git a/Tests/images/imagedraw_chord.png b/Tests/images/imagedraw_chord.png deleted file mode 100644 index db3b35310..000000000 Binary files a/Tests/images/imagedraw_chord.png and /dev/null differ diff --git a/Tests/images/imagedraw_chord_L.png b/Tests/images/imagedraw_chord_L.png new file mode 100644 index 000000000..a5a0078d0 Binary files /dev/null and b/Tests/images/imagedraw_chord_L.png differ diff --git a/Tests/images/imagedraw_chord_RGB.png b/Tests/images/imagedraw_chord_RGB.png new file mode 100644 index 000000000..af6fc7660 Binary files /dev/null and b/Tests/images/imagedraw_chord_RGB.png differ diff --git a/Tests/images/imagedraw_ellipse_L.png b/Tests/images/imagedraw_ellipse_L.png new file mode 100644 index 000000000..e47e6e441 Binary files /dev/null and b/Tests/images/imagedraw_ellipse_L.png differ diff --git a/Tests/images/imagedraw_ellipse.png b/Tests/images/imagedraw_ellipse_RGB.png similarity index 100% rename from Tests/images/imagedraw_ellipse.png rename to Tests/images/imagedraw_ellipse_RGB.png diff --git a/Tests/images/imagedraw_polygon_kite_L.png b/Tests/images/imagedraw_polygon_kite_L.png new file mode 100644 index 000000000..241d86bf4 Binary files /dev/null and b/Tests/images/imagedraw_polygon_kite_L.png differ diff --git a/Tests/images/imagedraw_polygon_kite_RGB.png b/Tests/images/imagedraw_polygon_kite_RGB.png new file mode 100644 index 000000000..e48d6660f Binary files /dev/null and b/Tests/images/imagedraw_polygon_kite_RGB.png differ diff --git a/Tests/images/imagedraw_wide_line_dot.png b/Tests/images/imagedraw_wide_line_dot.png new file mode 100644 index 000000000..d6f0e789c Binary files /dev/null and b/Tests/images/imagedraw_wide_line_dot.png differ diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index fa4571e60..8e84cc8f1 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -99,8 +99,7 @@ class TestBmpReference(PillowTestCase): os.path.join(base, 'g', 'pal8rle.bmp'), os.path.join(base, 'g', 'pal4rle.bmp')) if f not in unsupported: - self.assertTrue( - False, "Unsupported Image %s: %s" % (f, msg)) + self.fail("Unsupported Image %s: %s" % (f, msg)) if __name__ == '__main__': diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index e952f6586..675005d3f 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -21,7 +21,7 @@ class TestDecompressionBomb(PillowTestCase): # Arrange # Turn limit off Image.MAX_IMAGE_PIXELS = None - self.assertEqual(Image.MAX_IMAGE_PIXELS, None) + self.assertIsNone(Image.MAX_IMAGE_PIXELS) # Act / Assert # Implicit assert: no warning. diff --git a/Tests/test_features.py b/Tests/test_features.py index b9afe9b1d..d14aaca69 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -2,19 +2,50 @@ from helper import unittest, PillowTestCase from PIL import features +try: + from PIL import _webp + HAVE_WEBP = True +except: + HAVE_WEBP = False + class TestFeatures(PillowTestCase): - def test_check_features(self): - for feature in features.modules: - self.assertTrue( - features.check_module(feature) in [True, False, None]) - for feature in features.codecs: - self.assertTrue(features.check_codec(feature) in [True, False]) + def test_check(self): + # Check the correctness of the convenience function + for module in features.modules: + self.assertEqual(features.check_module(module), + features.check(module)) + 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_codecs(), list) + self.assertIsInstance(features.get_supported_features(), list) + self.assertIsInstance(features.get_supported(), list) def test_unsupported_codec(self): # Arrange diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index 5ae0e7eff..e6d13ef30 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -26,8 +26,8 @@ class TestFileCur(PillowTestCase): no_cursors_file = "Tests/images/no_cursors.cur" cur = CurImagePlugin.CurImageFile(TEST_FILE) - cur.fp = open(no_cursors_file, "rb") - self.assertRaises(TypeError, cur._open) + with open(no_cursors_file, "rb") as cur.fp: + self.assertRaises(TypeError, cur._open) if __name__ == '__main__': diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index 09da3c439..3da146d01 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -50,7 +50,7 @@ class TestFileDcx(PillowTestCase): im.seek(n_frames) break except EOFError: - self.assertTrue(im.tell() < n_frames) + self.assertLess(im.tell(), n_frames) def test_seek_too_far(self): # Arrange diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index a49301de1..ad4e8bc3f 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -38,7 +38,7 @@ class TestFileFli(PillowTestCase): im.seek(n_frames) break except EOFError: - self.assertTrue(im.tell() < n_frames) + self.assertLess(im.tell(), n_frames) if __name__ == '__main__': diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 1a67b7db5..bb2aa8a0d 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -226,7 +226,7 @@ class TestFileGif(PillowTestCase): im.seek(n_frames) break except EOFError: - self.assertTrue(im.tell() < n_frames) + self.assertLess(im.tell(), n_frames) def test_dispose_none(self): img = Image.open("Tests/images/dispose_none.gif") diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index 94d8bcce6..fcf4f52f4 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -30,7 +30,7 @@ class TestFileIm(PillowTestCase): im.seek(n_frames) break except EOFError: - self.assertTrue(im.tell() < n_frames) + self.assertLess(im.tell(), n_frames) def test_roundtrip(self): out = self.tempfile('temp.im') diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index ab5875a7d..6858e52f5 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -93,7 +93,7 @@ class TestFileJpeg(PillowTestCase): self.assertEqual(test(72), (72, 72)) self.assertEqual(test(300), (300, 300)) self.assertEqual(test(100, 200), (100, 200)) - self.assertEqual(test(0), None) # square pixels + self.assertIsNone(test(0)) # square pixels def test_icc(self): # Test ICC support @@ -439,7 +439,7 @@ class TestFileJpeg(PillowTestCase): def test_no_duplicate_0x1001_tag(self): # Arrange 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 self.assertEqual(tag_ids['RelatedImageWidth'], 0x1001) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 9c60aedf9..c4e4e7d0b 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -173,8 +173,7 @@ class TestFileLibTiff(LibTiffTestCase): 'RowsPerStrip', 'StripOffsets'] for field in requested_fields: - self.assertTrue(field in reloaded, - "%s not in metadata" % field) + self.assertIn(field, reloaded, "%s not in metadata" % field) def test_additional_metadata(self): # 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 # that we're already testing them im = Image.open('Tests/images/hopper_g4.tif') - for tag in im.tag_v2.keys(): + for tag in im.tag_v2: try: del(core_items[tag]) except: diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py index cd601cca3..6785ac4d9 100644 --- a/Tests/test_file_mcidas.py +++ b/Tests/test_file_mcidas.py @@ -1,6 +1,6 @@ from helper import unittest, PillowTestCase -from PIL import McIdasImagePlugin +from PIL import Image, McIdasImagePlugin class TestFileMcIdas(PillowTestCase): @@ -12,6 +12,24 @@ class TestFileMcIdas(PillowTestCase): lambda: 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__': unittest.main() diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 4c9f31abd..b4d0c696e 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -110,7 +110,7 @@ class TestFileMpo(PillowTestCase): im.seek(n_frames) break except EOFError: - self.assertTrue(im.tell() < n_frames) + self.assertLess(im.tell(), n_frames) def test_image_grab(self): for test_file in test_files: diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index cfefe2f9e..2caa4cdab 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -74,6 +74,12 @@ class TestFilePdf(PillowTestCase): self.assertTrue(os.path.isfile(outfile)) 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__': unittest.main() diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 32d6a3acd..f9b9a1eb4 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -316,7 +316,7 @@ class TestFilePng(PillowTestCase): test_file = f.read()[:offset] im = Image.open(BytesIO(test_file)) - self.assertTrue(im.fp is not None) + self.assertIsNotNone(im.fp) self.assertRaises((IOError, SyntaxError), im.verify) def test_verify_ignores_crc_error(self): @@ -331,7 +331,7 @@ class TestFilePng(PillowTestCase): ImageFile.LOAD_TRUNCATED_IMAGES = True try: im = load(image_data) - self.assertTrue(im is not None) + self.assertIsNotNone(im) finally: ImageFile.LOAD_TRUNCATED_IMAGES = False @@ -462,7 +462,7 @@ class TestFilePng(PillowTestCase): def test_save_icc_profile(self): 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") expected_icc = with_icc.info['icc_profile'] @@ -485,7 +485,7 @@ class TestFilePng(PillowTestCase): def test_roundtrip_no_icc_profile(self): 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) self.assertNotIn('icc_profile', im.info) diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 04a171cbb..ddd2507bc 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -44,7 +44,7 @@ class TestImagePsd(PillowTestCase): im.seek(n_frames+1) break except EOFError: - self.assertTrue(im.tell() < n_frames) + self.assertLess(im.tell(), n_frames) def test_seek_tell(self): im = Image.open(test_file) diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 96f82054e..32f1f55e5 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -69,7 +69,7 @@ class TestImageSpider(PillowTestCase): img_list = SpiderImagePlugin.loadImageSeries(file_list) # Assert - self.assertEqual(img_list, None) + self.assertIsNone(img_list) def test_isInt_not_a_number(self): # Arrange diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 9e7f3735d..403396f64 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -234,7 +234,7 @@ class TestFileTiff(PillowTestCase): im.seek(n_frames) break except EOFError: - self.assertTrue(im.tell() < n_frames) + self.assertLess(im.tell(), n_frames) def test_multipage(self): # issue #862 @@ -482,7 +482,6 @@ class TestFileTiff(PillowTestCase): im.load() self.assertFalse(fp.closed) - @unittest.skipUnless(sys.platform.startswith('win32'), "Windows only") class TestFileTiffW32(PillowTestCase): def test_fd_leak(self): diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 947f8464a..88e2b3b88 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -96,7 +96,7 @@ class TestFileWebpMetadata(PillowTestCase): file_path = "Tests/images/flower.jpg" image = Image.open(file_path) - self.assertTrue('exif' in image.info) + self.assertIn('exif', image.info) test_buffer = BytesIO() diff --git a/Tests/test_image.py b/Tests/test_image.py index 27f5ad885..ea813cd84 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -170,7 +170,7 @@ class TestImage(PillowTestCase): im2 = Image.new('RGB', (25, 25), 'white') # Act / Assert - self.assertTrue(im1 != im2) + self.assertNotEqual(im1, im2) def test_alpha_composite(self): # https://stackoverflow.com/questions/3374878 @@ -390,7 +390,7 @@ class TestRegistry(PillowTestCase): def test_encode_registry(self): 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',)) diff --git a/Tests/test_image_getbbox.py b/Tests/test_image_getbbox.py index 116f26497..f29032143 100644 --- a/Tests/test_image_getbbox.py +++ b/Tests/test_image_getbbox.py @@ -14,7 +14,7 @@ class TestImageGetBbox(PillowTestCase): # 8-bit mode im = Image.new("L", (100, 100), 0) - self.assertEqual(im.getbbox(), None) + self.assertIsNone(im.getbbox()) im.paste(255, (10, 25, 90, 75)) self.assertEqual(im.getbbox(), (10, 25, 90, 75)) @@ -27,7 +27,7 @@ class TestImageGetBbox(PillowTestCase): # 32-bit mode im = Image.new("RGB", (100, 100), 0) - self.assertEqual(im.getbbox(), None) + self.assertIsNone(im.getbbox()) im.paste(255, (10, 25, 90, 75)) self.assertEqual(im.getbbox(), (10, 25, 90, 75)) diff --git a/Tests/test_image_getcolors.py b/Tests/test_image_getcolors.py index a6a20b288..ca7a9d93d 100644 --- a/Tests/test_image_getcolors.py +++ b/Tests/test_image_getcolors.py @@ -20,15 +20,15 @@ class TestImageGetColors(PillowTestCase): self.assertEqual(getcolors("I"), 255) self.assertEqual(getcolors("F"), 255) self.assertEqual(getcolors("P"), 90) # fixed palette - self.assertEqual(getcolors("RGB"), None) - self.assertEqual(getcolors("RGBA"), None) - self.assertEqual(getcolors("CMYK"), None) - self.assertEqual(getcolors("YCbCr"), None) + self.assertIsNone(getcolors("RGB")) + self.assertIsNone(getcolors("RGBA")) + self.assertIsNone(getcolors("CMYK")) + self.assertIsNone(getcolors("YCbCr")) - self.assertEqual(getcolors("L", 128), None) + self.assertIsNone(getcolors("L", 128)) 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", 100000), 10100) @@ -48,7 +48,7 @@ class TestImageGetColors(PillowTestCase): (7960, (31, 20, 33))] A = im.getcolors(maxcolors=2) - self.assertEqual(A, None) + self.assertIsNone(A) A = im.getcolors(maxcolors=3) A.sort() diff --git a/Tests/test_image_getpalette.py b/Tests/test_image_getpalette.py index 14ecddbbf..01a6ac7ad 100644 --- a/Tests/test_image_getpalette.py +++ b/Tests/test_image_getpalette.py @@ -9,15 +9,15 @@ class TestImageGetPalette(PillowTestCase): if p: return p[:10] return None - self.assertEqual(palette("1"), None) - self.assertEqual(palette("L"), None) - self.assertEqual(palette("I"), None) - self.assertEqual(palette("F"), None) + self.assertIsNone(palette("1")) + self.assertIsNone(palette("L")) + self.assertIsNone(palette("I")) + self.assertIsNone(palette("F")) self.assertEqual(palette("P"), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - self.assertEqual(palette("RGB"), None) - self.assertEqual(palette("RGBA"), None) - self.assertEqual(palette("CMYK"), None) - self.assertEqual(palette("YCbCr"), None) + self.assertIsNone(palette("RGB")) + self.assertIsNone(palette("RGBA")) + self.assertIsNone(palette("CMYK")) + self.assertIsNone(palette("YCbCr")) if __name__ == '__main__': diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 98fe3b0f7..96ad93578 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -16,8 +16,8 @@ class TestImagingResampleVulnerability(PillowTestCase): def test_invalid_size(self): im = hopper() + # Should not crash im.resize((100, 100)) - self.assertTrue(True, "Should not Crash") with self.assertRaises(ValueError): im.resize((-100, 100)) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 21493e8d5..ef30f3e00 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -222,7 +222,7 @@ class TestImageCms(PillowTestCase): self.assertTrue(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): # 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_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)))) - 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.color_space, 'RGB') - self.assertEqual(p.colorant_table, None) - self.assertEqual(p.colorant_table_out, None) - self.assertEqual(p.colorimetric_intent, None) + self.assertIsNone(p.colorant_table) + self.assertIsNone(p.colorant_table_out) + self.assertIsNone(p.colorimetric_intent) self.assertEqual(p.connection_space, 'XYZ ') self.assertEqual(p.copyright, 'Copyright International Color Consortium, 2009') 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.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_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.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.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_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,)) self.assertEqual(p.model, 'IEC 61966-2-1 Default RGB Colour Space - sRGB') 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_desc, '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_primary, ((0.43606566581047446, 0.22248840582960838, 0.013916015621759925), (0.6484536250319214, 0.3308524944738204, 0.22248840582960838))) self.assertEqual(p.rendering_intent, 0) - self.assertEqual(p.saturation_rendering_intent_gamut, None) - self.assertEqual(p.screening_description, None) - self.assertEqual(p.target, None) + self.assertIsNone(p.saturation_rendering_intent_gamut) + self.assertIsNone(p.screening_description) + self.assertIsNone(p.target) self.assertEqual(p.technology, 'CRT ') self.assertEqual(p.version, 2.0) self.assertEqual(p.viewing_condition, 'Reference Viewing Condition in IEC 61966-2-1') diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index 996367b30..64e88cf9c 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -119,7 +119,7 @@ class TestImageColor(PillowTestCase): # look for rounding errors (based on code by Tim Hatch) def test_rounding_errors(self): - for color in list(ImageColor.colormap.keys()): + for color in ImageColor.colormap: expected = Image.new( "RGB", (1, 1), color).convert("L").getpixel((0, 0)) actual = ImageColor.getcolor(color, 'L') diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 441a34a88..2ca5d0882 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -30,6 +30,8 @@ BBOX2 = [X0, Y0, X1, Y1] POINTS1 = [(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): @@ -78,6 +80,37 @@ class TestImageDraw(PillowTestCase): self.helper_arc(BBOX2, 0, 180) 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): # Arrange small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) @@ -92,45 +125,49 @@ class TestImageDraw(PillowTestCase): self.assert_image_equal( im, Image.open("Tests/images/imagedraw_bitmap.png")) - def helper_chord(self, bbox, start, end): + def helper_chord(self, mode, bbox, start, end): # Arrange - im = Image.new("RGB", (W, H)) + im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_chord_{}.png".format(mode) # Act draw.chord(bbox, start, end, fill="red", outline="yellow") del draw # Assert - self.assert_image_similar( - im, Image.open("Tests/images/imagedraw_chord.png"), 1) + self.assert_image_similar(im, Image.open(expected), 1) def test_chord1(self): - self.helper_chord(BBOX1, 0, 180) - self.helper_chord(BBOX1, 0.5, 180.4) + for mode in ["RGB", "L"]: + self.helper_chord(mode, BBOX1, 0, 180) + self.helper_chord(mode, BBOX1, 0.5, 180.4) def test_chord2(self): - self.helper_chord(BBOX2, 0, 180) - self.helper_chord(BBOX2, 0.5, 180.4) + for mode in ["RGB", "L"]: + 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 - im = Image.new("RGB", (W, H)) + im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_ellipse_{}.png".format(mode) # Act draw.ellipse(bbox, fill="green", outline="blue") del draw # Assert - self.assert_image_similar( - im, Image.open("Tests/images/imagedraw_ellipse.png"), 1) + self.assert_image_similar(im, Image.open(expected), 1) def test_ellipse1(self): - self.helper_ellipse(BBOX1) + for mode in ["RGB", "L"]: + self.helper_ellipse(mode, BBOX1) def test_ellipse2(self): - self.helper_ellipse(BBOX2) + for mode in ["RGB", "L"]: + self.helper_ellipse(mode, BBOX2) def test_ellipse_edge(self): # Arrange @@ -267,6 +304,23 @@ class TestImageDraw(PillowTestCase): def test_polygon2(self): 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): # Arrange im = Image.new("RGB", (W, H)) @@ -286,6 +340,21 @@ class TestImageDraw(PillowTestCase): def test_rectangle2(self): 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): # Arrange im = Image.new("RGB", (W, H)) @@ -478,6 +547,21 @@ class TestImageDraw(PillowTestCase): self.assert_image_equal(img, expected, '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__': unittest.main() diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 840df31c2..b2edffa57 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -38,7 +38,7 @@ class TestImageGrabImport(PillowTestCase): # Assert if sys.platform in ["win32", "darwin"]: - self.assertIsNone(exception, None) + self.assertIsNone(exception) else: self.assertIsInstance(exception, ImportError) self.assertEqual(str(exception), diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index f2247bdca..0ce8ac6da 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -176,6 +176,40 @@ class MorphTests(PillowTestCase): self.assertEqual(len(coords), 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): im = hopper('RGB') 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.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__': unittest.main() diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 8c8723927..fbf48a1b6 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -45,7 +45,7 @@ class TestImageTk(PillowTestCase): # Test no relevant entry im = ImageTk._get_image_from_kw(kw) - self.assertEqual(im, None) + self.assertIsNone(im) def test_photoimage(self): for mode in TK_MODES: diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index 54f330ec3..0bf4503c4 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -34,10 +34,10 @@ class Test_IFDRational(PillowTestCase): xres = IFDRational(72) yres = IFDRational(72) - self.assertTrue(xres._val is not None) - self.assertTrue(xres.numerator is not None) - self.assertTrue(xres.denominator is not None) - self.assertTrue(yres._val is not None) + self.assertIsNotNone(xres._val) + self.assertIsNotNone(xres.numerator) + self.assertIsNotNone(xres.denominator) + self.assertIsNotNone(yres._val) self.assertTrue(xres and 1) self.assertTrue(xres and yres) diff --git a/_imaging.c b/_imaging.c index af15cae91..f6e3eed16 100644 --- a/_imaging.c +++ b/_imaging.c @@ -631,18 +631,6 @@ _new(PyObject* self, PyObject* args) 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* _new_block(PyObject* self, PyObject* args) { @@ -3027,7 +3015,6 @@ static struct PyMethodDef methods[] = { #endif /* Misc. */ - {"new_array", (PyCFunction)_new_array, 1}, {"new_block", (PyCFunction)_new_block, 1}, {"save_ppm", (PyCFunction)_save_ppm, 1}, diff --git a/_webp.c b/_webp.c index 70830ec8b..421f49957 100644 --- a/_webp.c +++ b/_webp.c @@ -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. * Files that are valid with 0.3 are reported as being invalid. */ +int WebPDecoderBuggyAlpha() { + return WebPGetDecoderVersion()==0x0103; +} + PyObject* WebPDecoderBuggyAlpha_wrapper(PyObject* self, PyObject* args){ - return Py_BuildValue("i", WebPGetDecoderVersion()==0x0103); + return Py_BuildValue("i", WebPDecoderBuggyAlpha()); } static PyMethodDef webpMethods[] = @@ -268,6 +272,11 @@ void addMuxFlagToModule(PyObject* m) { #endif } +void addTransparencyFlagToModule(PyObject* m) { + PyModule_AddObject(m, "HAVE_TRANSPARENCY", + PyBool_FromLong(!WebPDecoderBuggyAlpha())); +} + #if PY_VERSION_HEX >= 0x03000000 PyMODINIT_FUNC @@ -284,6 +293,7 @@ PyInit__webp(void) { m = PyModule_Create(&module_def); addMuxFlagToModule(m); + addTransparencyFlagToModule(m); return m; } #else @@ -292,5 +302,6 @@ init_webp(void) { PyObject* m = Py_InitModule("_webp", webpMethods); addMuxFlagToModule(m); + addTransparencyFlagToModule(m); } #endif diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst index 3023e1e67..fd410afe0 100644 --- a/docs/handbook/concepts.rst +++ b/docs/handbook/concepts.rst @@ -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 with alpha), ``RGBX`` (true color with padding) and ``RGBa`` (true color with premultiplied alpha). However, PIL doesn’t support user-defined modes; if you -to handle band combinations that are not listed above, use a sequence of Image -objects. +need to handle band combinations that are not listed above, use a sequence of +Image objects. 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. @@ -114,7 +114,7 @@ pixel, the Python Imaging Library provides different resampling *filters*. in the input image is used. ``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``. This filter can only be used with the :py:meth:`~PIL.Image.Image.resize` and :py:meth:`~PIL.Image.Image.thumbnail` methods. diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 245d4fc2c..e822f5a08 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -276,6 +276,7 @@ Converting between modes :: + from PIL import Image im = Image.open("hopper.ppm").convert("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 simply pass it the filename as an argument:: + from PIL import Image im = Image.open("hopper.ppm") 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") - im = Image.open(fp) + from PIL import Image + with open("hopper.ppm", "rb") as fp: + im = Image.open(fp) To read an image from string data, use the :py:class:`~StringIO.StringIO` class: diff --git a/docs/installation.rst b/docs/installation.rst index be1b18988..277239a8f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -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 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ -| 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 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 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ diff --git a/docs/reference/limits.rst b/docs/reference/limits.rst index 6f81c9e65..79dc66e67 100644 --- a/docs/reference/limits.rst +++ b/docs/reference/limits.rst @@ -10,7 +10,7 @@ Internal Limits * Image sizes cannot be negative. These are checked both in ``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 in the image header. diff --git a/docs/releasenotes/2.7.0.rst b/docs/releasenotes/2.7.0.rst index a51ca81b4..4bb25e371 100644 --- a/docs/releasenotes/2.7.0.rst +++ b/docs/releasenotes/2.7.0.rst @@ -148,7 +148,7 @@ Blur performance Box filter computation time is constant relative to the radius and depends 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. 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 dictionary. These parameters now use the underscore character instead of space. (e.g. 'x_resolution') - diff --git a/docs/releasenotes/3.1.0.rst b/docs/releasenotes/3.1.0.rst index 7f0a7f052..388af03ac 100644 --- a/docs/releasenotes/3.1.0.rst +++ b/docs/releasenotes/3.1.0.rst @@ -68,7 +68,7 @@ Out of Spec Metadata ++++++++++++++++++++ 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 is specified to have a single value is stored with an array of values. diff --git a/docs/releasenotes/3.3.0.rst b/docs/releasenotes/3.3.0.rst index 544c7162e..39ffdbb2e 100644 --- a/docs/releasenotes/3.3.0.rst +++ b/docs/releasenotes/3.3.0.rst @@ -29,9 +29,9 @@ Resizing ======== 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 -precision errors on the wide range of scales. With other performance -improvements, this makes resampling 60% faster on average. +computings. This is faster on most platforms and doesn't introduce precision +errors on the wide range of scales. With other performance improvements, this +makes resampling 60% faster on average. Color calculation for images in the ``LA`` mode on semitransparent pixels was fixed. @@ -41,7 +41,7 @@ Rotation ======== 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 was fixed. @@ -52,4 +52,3 @@ Image 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 from the behavior since ``3.0.0``. - diff --git a/docs/releasenotes/3.4.0.rst b/docs/releasenotes/3.4.0.rst index 65b52e958..dc5e2e295 100644 --- a/docs/releasenotes/3.4.0.rst +++ b/docs/releasenotes/3.4.0.rst @@ -8,7 +8,7 @@ New resizing filters Two new filters available for ``Image.resize()`` and ``Image.thumbnail()`` functions: ``BOX`` and ``HAMMING``. ``BOX`` is the high-performance filter with 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 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 ================ -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 better performance. @@ -44,7 +44,7 @@ in effect, e.g.:: 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.:: im.save("filename.tiff", format="TIFF", save_all=True) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index ca26282be..65ab34a66 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -605,11 +605,6 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, DRAWINIT(); - if (width <= 1) { - draw->line(im, x0, y0, x1, y1, ink); - return 0; - } - dx = x1-x0; dy = y1-y0; if (dx == 0 && dy == 0) { @@ -1030,20 +1025,6 @@ ImagingOutlineCurve(ImagingOutline outline, float x1, float y1, 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 ImagingOutlineClose(ImagingOutline outline) { diff --git a/libImaging/JpegDecode.c b/libImaging/JpegDecode.c index 6ebdb8f93..4bb929b6a 100644 --- a/libImaging/JpegDecode.c +++ b/libImaging/JpegDecode.c @@ -268,7 +268,7 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) /* -------------------------------------------------------------------- */ 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 */ JPEGSTATE* context = (JPEGSTATE*) state->context; diff --git a/mp_compile.py b/mp_compile.py index 31833dee3..59f14a651 100644 --- a/mp_compile.py +++ b/mp_compile.py @@ -54,7 +54,8 @@ def _mp_compile(self, sources, output_dir=None, macros=None, 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_cygwin = sys.platform.startswith('cygwin') @@ -82,4 +83,5 @@ def install(): print("Single threaded build, not installing mp_compile:" "%s processes" % MAX_PROCS) + install() diff --git a/selftest.py b/selftest.py index 067db4d79..f6e8e5ed8 100755 --- a/selftest.py +++ b/selftest.py @@ -178,25 +178,14 @@ if __name__ == "__main__": ("freetype2", "FREETYPE2"), ("littlecms2", "LITTLECMS2"), ("webp", "WEBP"), - ("transp_webp", "Transparent WEBP") - ]: - 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 [ + ("transp_webp", "Transparent WEBP"), + ("webp_mux", "WEBPMUX"), ("jpg", "JPEG"), ("jpg_2000", "OPENJPEG (JPEG2000)"), ("zlib", "ZLIB (PNG/ZIP)"), ("libtiff", "LIBTIFF") ]: - if features.check_codec(name): + if features.check(name): print("---", feature, "support ok") else: print("***", feature, "support not installed") diff --git a/winbuild/build.py b/winbuild/build.py index 1fbfa14a6..99502938d 100755 --- a/winbuild/build.py +++ b/winbuild/build.py @@ -12,7 +12,7 @@ from config import (compilers, compiler_from_env, pythons, pyversion_from_env, def setup_vms(): ret = [] - for py in pythons.keys(): + for py in pythons: for arch in ('', X64_EXT): ret.append("virtualenv -p c:/Python%s%s/python.exe --clear %s%s%s" % (py, arch, VIRT_BASE, py, arch)) diff --git a/winbuild/config.py b/winbuild/config.py index 177bb66b5..50f2cb4dd 100644 --- a/winbuild/config.py +++ b/winbuild/config.py @@ -116,7 +116,7 @@ def pyversion_from_env(): py = os.environ['PYTHON'] py_version = '27' - for k in pythons.keys(): + for k in pythons: if k in py: py_version = k break