From e42aec8c9018abcede6440733032cedb29698cc7 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 15 Jul 2020 13:37:43 +0300 Subject: [PATCH 1/4] Update Black target to py36 --- .pre-commit-config.yaml | 2 +- src/PIL/ImageDraw.py | 2 +- src/PIL/ImageFont.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a37674036..80a068df5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: rev: 6bedb5c58a7d8c25aa9509f8217bc24e9797e90d # frozen: 19.10b0 hooks: - id: black - args: ["--target-version", "py35"] + args: ["--target-version", "py36"] # Only .py files, until https://github.com/psf/black/issues/402 resolved files: \.py$ types: [] diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index cbecf652d..e7646a25c 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -277,7 +277,7 @@ class ImageDraw: stroke_width=0, stroke_fill=None, *args, - **kwargs + **kwargs, ): if self._multiline_check(text): return self.multiline_text( diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 8f792d55b..1e0576be0 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -415,7 +415,7 @@ class FreeTypeFont: language=None, stroke_width=0, *args, - **kwargs + **kwargs, ): """ Create a bitmap for the text. From 10615a7da7a5254de9696f4f3f14d229098b67cc Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 16 Jul 2020 12:43:29 +0300 Subject: [PATCH 2/4] Upgrade Python syntax for 3.6+ Co-authored-by: nulano --- Tests/bench_cffi_access.py | 10 +++-- Tests/check_imaging_leaks.py | 2 +- Tests/check_png_dos.py | 4 +- Tests/conftest.py | 2 +- Tests/createfontdatachunk.py | 2 +- Tests/helper.py | 41 ++++++++----------- Tests/test_bmp_reference.py | 6 +-- Tests/test_file_apng.py | 2 +- Tests/test_file_libtiff.py | 12 +++--- Tests/test_file_sun.py | 2 +- Tests/test_file_tga.py | 4 +- Tests/test_file_tiff_metadata.py | 10 ++--- Tests/test_font_pcf_charsets.py | 4 +- Tests/test_image_access.py | 13 +++--- Tests/test_image_reduce.py | 15 ++++--- Tests/test_image_resample.py | 37 +++++++---------- Tests/test_imagedraw.py | 10 ++--- Tests/test_imagedraw2.py | 2 +- Tests/test_imageenhance.py | 4 +- Tests/test_imagemath.py | 2 +- Tests/test_imagemorph.py | 4 +- Tests/test_imagetk.py | 2 +- Tests/test_mode_i16.py | 6 +-- Tests/test_qt_image_toqimage.py | 2 +- Tests/test_qt_image_toqpixmap.py | 2 +- docs/example/DdsImagePlugin.py | 6 +-- docs/handbook/tutorial.rst | 2 +- docs/reference/ImageEnhance.rst | 2 +- docs/reference/ImageSequence.rst | 2 +- setup.py | 67 +++++++++++++++---------------- src/PIL/BlpImagePlugin.py | 14 ++++--- src/PIL/BmpImagePlugin.py | 10 ++--- src/PIL/DdsImagePlugin.py | 8 ++-- src/PIL/EpsImagePlugin.py | 8 ++-- src/PIL/FliImagePlugin.py | 2 +- src/PIL/FontFile.py | 2 +- src/PIL/FpxImagePlugin.py | 6 +-- src/PIL/FtexImagePlugin.py | 2 +- src/PIL/GbrImagePlugin.py | 4 +- src/PIL/GifImagePlugin.py | 2 +- src/PIL/IcnsImagePlugin.py | 4 +- src/PIL/ImImagePlugin.py | 22 +++++------ src/PIL/Image.py | 50 +++++++++++------------ src/PIL/ImageCms.py | 7 ++-- src/PIL/ImageColor.py | 2 +- src/PIL/ImageFile.py | 12 +++--- src/PIL/ImageFilter.py | 11 +++--- src/PIL/ImageMath.py | 6 +-- src/PIL/ImagePalette.py | 8 ++-- src/PIL/ImageQt.py | 2 +- src/PIL/ImageShow.py | 16 ++++---- src/PIL/ImageTk.py | 4 +- src/PIL/JpegImagePlugin.py | 8 ++-- src/PIL/MspImagePlugin.py | 6 +-- src/PIL/PSDraw.py | 14 +++---- src/PIL/PalmImagePlugin.py | 4 +- src/PIL/PcxImagePlugin.py | 2 +- src/PIL/PdfImagePlugin.py | 8 ++-- src/PIL/PdfParser.py | 15 ++++--- src/PIL/PngImagePlugin.py | 26 ++++++------ src/PIL/PpmImagePlugin.py | 4 +- src/PIL/SgiImagePlugin.py | 4 +- src/PIL/SpiderImagePlugin.py | 6 +-- src/PIL/TgaImagePlugin.py | 2 +- src/PIL/TiffImagePlugin.py | 68 ++++++++++++++++---------------- src/PIL/XbmImagePlugin.py | 10 ++--- src/PIL/__init__.py | 2 +- src/PIL/features.py | 24 ++++++----- winbuild/build_prepare.py | 2 +- 69 files changed, 323 insertions(+), 363 deletions(-) diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index f196757dc..f9edcf09a 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -28,15 +28,17 @@ def timer(func, label, *args): func(*args) if time.time() - starttime > 10: print( - "%s: breaking at %s iterations, %.6f per iteration" - % (label, x + 1, (time.time() - starttime) / (x + 1.0)) + "{}: breaking at {} iterations, {:.6f} per iteration".format( + label, x + 1, (time.time() - starttime) / (x + 1.0) + ) ) break if x == iterations - 1: endtime = time.time() print( - "%s: %.4f s %.6f per iteration" - % (label, endtime - starttime, (endtime - starttime) / (x + 1.0)) + "{}: {:.4f} s {:.6f} per iteration".format( + label, endtime - starttime, (endtime - starttime) / (x + 1.0) + ) ) diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index db12d00e3..ae05ebade 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -25,7 +25,7 @@ def _test_leak(min_iterations, max_iterations, fn, *args, **kwargs): if i < min_iterations: mem_limit = mem + 1 continue - msg = "memory usage limit exceeded after %d iterations" % (i + 1) + msg = f"memory usage limit exceeded after {i + 1} iterations" assert mem <= mem_limit, msg diff --git a/Tests/check_png_dos.py b/Tests/check_png_dos.py index 86eb937e9..d8d645189 100644 --- a/Tests/check_png_dos.py +++ b/Tests/check_png_dos.py @@ -42,8 +42,8 @@ def test_dos_total_memory(): info = PngImagePlugin.PngInfo() for x in range(64): - info.add_text("t%s" % x, compressed_data, zip=True) - info.add_itxt("i%s" % x, compressed_data, zip=True) + info.add_text(f"t{x}", compressed_data, zip=True) + info.add_itxt(f"i{x}", compressed_data, zip=True) b = BytesIO() im.save(b, "PNG", pnginfo=info) diff --git a/Tests/conftest.py b/Tests/conftest.py index 624eab73c..082f2f7c3 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -9,4 +9,4 @@ def pytest_report_header(config): features.pilinfo(out=out, supported_formats=False) return out.getvalue() except Exception as e: - return "pytest_report_header failed: %s" % e + return f"pytest_report_header failed: {e}" diff --git a/Tests/createfontdatachunk.py b/Tests/createfontdatachunk.py index c7055995e..011bb0bed 100755 --- a/Tests/createfontdatachunk.py +++ b/Tests/createfontdatachunk.py @@ -6,7 +6,7 @@ if __name__ == "__main__": # create font data chunk for embedding font = "Tests/images/courB08" print(" f._load_pilfont_data(") - print(" # %s" % os.path.basename(font)) + print(f" # {os.path.basename(font)}") print(" BytesIO(base64.decodestring(b'''") with open(font + ".pil", "rb") as fp: print(base64.b64encode(fp.read()).decode()) diff --git a/Tests/helper.py b/Tests/helper.py index cdc5f4efe..4c5f2198e 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -67,37 +67,31 @@ def convert_to_comparable(a, b): def assert_deep_equal(a, b, msg=None): try: - assert len(a) == len(b), msg or "got length {}, expected {}".format( - len(a), len(b) - ) + assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}" except Exception: assert a == b, msg def assert_image(im, mode, size, msg=None): if mode is not None: - assert im.mode == mode, msg or "got mode {!r}, expected {!r}".format( - im.mode, mode + assert im.mode == mode, ( + msg or f"got mode {repr(im.mode)}, expected {repr(mode)}" ) if size is not None: - assert im.size == size, msg or "got size {!r}, expected {!r}".format( - im.size, size + assert im.size == size, ( + msg or f"got size {repr(im.size)}, expected {repr(size)}" ) def assert_image_equal(a, b, msg=None): - assert a.mode == b.mode, msg or "got mode {!r}, expected {!r}".format( - a.mode, b.mode - ) - assert a.size == b.size, msg or "got size {!r}, expected {!r}".format( - a.size, b.size - ) + assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}" + assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}" if a.tobytes() != b.tobytes(): if HAS_UPLOADER: try: url = test_image_results.upload(a, b) - logger.error("Url for test images: %s" % url) + logger.error(f"Url for test images: {url}") except Exception: pass @@ -112,12 +106,8 @@ def assert_image_equal_tofile(a, filename, msg=None, mode=None): def assert_image_similar(a, b, epsilon, msg=None): - assert a.mode == b.mode, msg or "got mode {!r}, expected {!r}".format( - a.mode, b.mode - ) - assert a.size == b.size, msg or "got size {!r}, expected {!r}".format( - a.size, b.size - ) + assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}" + assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}" a, b = convert_to_comparable(a, b) @@ -129,13 +119,14 @@ def assert_image_similar(a, b, epsilon, msg=None): ave_diff = diff / (a.size[0] * a.size[1]) try: assert epsilon >= ave_diff, ( - msg or "" - ) + " average pixel value difference %.4f > epsilon %.4f" % (ave_diff, epsilon) + (msg or "") + + f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}" + ) except Exception as e: if HAS_UPLOADER: try: url = test_image_results.upload(a, b) - logger.error("Url for test images: %s" % url) + logger.error(f"Url for test images: {url}") except Exception: pass raise e @@ -166,7 +157,7 @@ def assert_tuple_approx_equal(actuals, targets, threshold, msg): def skip_unless_feature(feature): - reason = "%s not available" % feature + reason = f"{feature} not available" return pytest.mark.skipif(not features.check(feature), reason=reason) @@ -204,7 +195,7 @@ class PillowLeakTestCase: for cycle in range(self.iterations): core() mem = self._get_mem_usage() - start_mem - msg = "memory usage limit exceeded in iteration %d" % cycle + msg = f"memory usage limit exceeded in iteration {cycle}" assert mem < self.mem_limit, msg diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index ade2901b7..64156a4ec 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -49,7 +49,7 @@ def test_questionable(): with Image.open(f) as im: im.load() if os.path.basename(f) not in supported: - print("Please add %s to the partially supported bmp specs." % f) + print(f"Please add {f} to the partially supported bmp specs.") except Exception: # as msg: if os.path.basename(f) in supported: raise @@ -84,7 +84,7 @@ def test_good(): if name in file_map: return os.path.join(base, "html", file_map[name]) name = os.path.splitext(name)[0] - return os.path.join(base, "html", "%s.png" % name) + return os.path.join(base, "html", f"{name}.png") for f in get_files("g"): try: @@ -107,4 +107,4 @@ def test_good(): os.path.join(base, "g", "pal8rle.bmp"), os.path.join(base, "g", "pal4rle.bmp"), ) - assert f in unsupported, "Unsupported Image {}: {}".format(f, msg) + assert f in unsupported, f"Unsupported Image {f}: {msg}" diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 0eca78690..7a46f4019 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -311,7 +311,7 @@ def test_apng_sequence_errors(): ] for f in test_files: with pytest.raises(SyntaxError): - with Image.open("Tests/images/apng/{0}".format(f)) as im: + with Image.open(f"Tests/images/apng/{f}") as im: im.seek(im.n_frames - 1) im.load() diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 300967a30..19a06a15d 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -173,18 +173,18 @@ class TestFileLibTiff(LibTiffTestCase): assert ( c_float(val[0][0] / val[0][1]).value == c_float(value[0][0] / value[0][1]).value - ), ("%s didn't roundtrip" % tag) + ), f"{tag} didn't roundtrip" else: - assert c_float(val).value == c_float(value).value, ( - "%s didn't roundtrip" % tag - ) + assert ( + c_float(val).value == c_float(value).value + ), f"{tag} didn't roundtrip" else: - assert val == value, "%s didn't roundtrip" % tag + assert val == value, f"{tag} didn't roundtrip" # https://github.com/python-pillow/Pillow/issues/1561 requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"] for field in requested_fields: - assert field in reloaded, "%s not in metadata" % field + assert field in reloaded, f"{field} not in metadata" def test_additional_metadata(self, tmp_path): # these should not crash. Seriously dummy data, most of it doesn't make diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 03e26ef8b..b2bfb5b9e 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -45,7 +45,7 @@ def test_others(): with Image.open(path) as im: im.load() assert isinstance(im, SunImagePlugin.SunImageFile) - target_path = "%s.png" % os.path.splitext(path)[0] + target_path = f"{os.path.splitext(path)[0]}.png" # im.save(target_file) with Image.open(target_path) as target: assert_image_equal(im, target) diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 4919ad766..bac1b4dd6 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -35,9 +35,7 @@ def test_sanity(tmp_path): assert_image_equal(saved_im, original_im) - png_paths = glob( - os.path.join(_TGA_DIR_COMMON, "*x*_{}.png".format(mode.lower())) - ) + png_paths = glob(os.path.join(_TGA_DIR_COMMON, f"*x*_{mode.lower()}.png")) for png_path in png_paths: with Image.open(png_path) as reference_im: diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index d57f63717..179d1adf3 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -144,16 +144,16 @@ def test_write_metadata(tmp_path): assert_deep_equal( original[tag], value, - "{} didn't roundtrip, {}, {}".format(tag, original[tag], value), + f"{tag} didn't roundtrip, {original[tag]}, {value}", ) else: - assert original[tag] == value, "{} didn't roundtrip, {}, {}".format( - tag, original[tag], value - ) + assert ( + original[tag] == value + ), f"{tag} didn't roundtrip, {original[tag]}, {value}" for tag, value in original.items(): if tag not in ignored: - assert value == reloaded[tag], "%s didn't roundtrip" % tag + assert value == reloaded[tag], f"{tag} didn't roundtrip" def test_change_stripbytecounts_tag_type(tmp_path): diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py index 4a39803be..d7d1bf200 100644 --- a/Tests/test_font_pcf_charsets.py +++ b/Tests/test_font_pcf_charsets.py @@ -47,11 +47,11 @@ def save_font(request, tmp_path, encoding): font.save(tempname) with Image.open(tempname.replace(".pil", ".pbm")) as loaded: - with Image.open("Tests/fonts/ter-x20b-%s.pbm" % encoding) as target: + with Image.open(f"Tests/fonts/ter-x20b-{encoding}.pbm") as target: assert_image_equal(loaded, target) with open(tempname, "rb") as f_loaded: - with open("Tests/fonts/ter-x20b-%s.pil" % encoding, "rb") as f_target: + with open(f"Tests/fonts/ter-x20b-{encoding}.pil", "rb") as f_target: assert f_loaded.read() == f_target.read() return tempname diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 25cc9fef4..af51a4fb3 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -125,14 +125,13 @@ class TestImageGetPixel(AccessTest): im.putpixel((0, 0), c) assert ( im.getpixel((0, 0)) == c - ), "put/getpixel roundtrip failed for mode {}, color {}".format(mode, c) + ), f"put/getpixel roundtrip failed for mode {mode}, color {c}" # check putpixel negative index im.putpixel((-1, -1), c) - assert im.getpixel((-1, -1)) == c, ( - "put/getpixel roundtrip negative index failed for mode %s, color %s" - % (mode, c) - ) + assert ( + im.getpixel((-1, -1)) == c + ), f"put/getpixel roundtrip negative index failed for mode {mode}, color {c}" # Check 0 im = Image.new(mode, (0, 0), None) @@ -150,11 +149,11 @@ class TestImageGetPixel(AccessTest): im = Image.new(mode, (1, 1), c) assert ( im.getpixel((0, 0)) == c - ), "initial color failed for mode {}, color {} ".format(mode, c) + ), f"initial color failed for mode {mode}, color {c} " # check initial color negative index assert ( im.getpixel((-1, -1)) == c - ), "initial color failed with negative index for mode %s, color %s " % (mode, c) + ), f"initial color failed with negative index for mode {mode}, color {c} " # Check 0 im = Image.new(mode, (0, 0), c) diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 0f92b87f8..b11269918 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -161,8 +161,8 @@ def compare_reduce_with_reference(im, factor, average_diff=0.4, max_diff=1): def assert_compare_images(a, b, max_average_diff, max_diff=255): - assert a.mode == b.mode, "got mode %r, expected %r" % (a.mode, b.mode) - assert a.size == b.size, "got size %r, expected %r" % (a.size, b.size) + assert a.mode == b.mode, f"got mode {repr(a.mode)}, expected {repr(b.mode)}" + assert a.size == b.size, f"got size {repr(a.size)}, expected {repr(b.size)}" a, b = convert_to_comparable(a, b) @@ -175,16 +175,15 @@ def assert_compare_images(a, b, max_average_diff, max_diff=255): a.size[0] * a.size[1] ) msg = ( - "average pixel value difference {:.4f} > expected {:.4f} " - "for '{}' band".format(average_diff, max_average_diff, band) + f"average pixel value difference {average_diff:.4f} > " + f"expected {max_average_diff:.4f} for '{band}' band" ) assert max_average_diff >= average_diff, msg last_diff = [i for i, num in enumerate(ch_hist) if num > 0][-1] - assert ( - max_diff >= last_diff - ), "max pixel value difference {} > expected {} for '{}' band".format( - last_diff, max_diff, band + assert max_diff >= last_diff, ( + f"max pixel value difference {last_diff} > expected {max_diff} " + f"for '{band}' band" ) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 35eae128b..a6d861520 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -81,15 +81,16 @@ class TestImagingCoreResampleAccuracy: for y in range(case.size[1]): for x in range(case.size[0]): if c_px[x, y] != s_px[x, y]: - message = "\nHave: \n{}\n\nExpected: \n{}".format( - self.serialize_image(case), self.serialize_image(sample) + message = ( + f"\nHave: \n{self.serialize_image(case)}\n" + f"\nExpected: \n{self.serialize_image(sample)}" ) assert s_px[x, y] == c_px[x, y], message def serialize_image(self, image): s_px = image.load() return "\n".join( - " ".join("{:02x}".format(s_px[x, y]) for x in range(image.size[0])) + " ".join(f"{s_px[x, y]:02x}" for x in range(image.size[0])) for y in range(image.size[1]) ) @@ -229,7 +230,7 @@ class TestCoreResampleConsistency: for x in range(channel.size[0]): for y in range(channel.size[1]): if px[x, y] != color: - message = "{} != {} for pixel {}".format(px[x, y], color, (x, y)) + message = f"{px[x, y]} != {color} for pixel {(x, y)}" assert px[x, y] == color, message def test_8u(self): @@ -268,10 +269,9 @@ class TestCoreResampleAlphaCorrect: px = i.load() for y in range(i.size[1]): used_colors = {px[x, y][0] for x in range(i.size[0])} - assert 256 == len( - used_colors - ), "All colors should present in resized image. Only {} on {} line.".format( - len(used_colors), y + assert 256 == len(used_colors), ( + "All colors should be present in resized image. " + f"Only {len(used_colors)} on {y} line." ) @pytest.mark.xfail(reason="Current implementation isn't precise enough") @@ -307,8 +307,9 @@ class TestCoreResampleAlphaCorrect: for y in range(i.size[1]): for x in range(i.size[0]): if px[x, y][-1] != 0 and px[x, y][:-1] != clean_pixel: - message = "pixel at ({}, {}) is differ:\n{}\n{}".format( - x, y, px[x, y], clean_pixel + message = ( + f"pixel at ({x}, {y}) is different:\n" + f"{px[x, y]}\n{clean_pixel}" ) assert px[x, y][:3] == clean_pixel, message @@ -503,7 +504,7 @@ class TestCoreResampleBox: ]: res = im.resize(size, Image.LANCZOS, box) assert res.size == size - assert_image_equal(res, im.crop(box), ">>> {} {}".format(size, box)) + assert_image_equal(res, im.crop(box), f">>> {size} {box}") def test_no_passthrough(self): # When resize is required @@ -519,9 +520,7 @@ class TestCoreResampleBox: assert res.size == size with pytest.raises(AssertionError, match=r"difference \d"): # check that the difference at least that much - assert_image_similar( - res, im.crop(box), 20, ">>> {} {}".format(size, box) - ) + assert_image_similar(res, im.crop(box), 20, f">>> {size} {box}") def test_skip_horizontal(self): # Can skip resize for one dimension @@ -538,10 +537,7 @@ class TestCoreResampleBox: assert res.size == size # Borders should be slightly different assert_image_similar( - res, - im.crop(box).resize(size, flt), - 0.4, - ">>> {} {} {}".format(size, box, flt), + res, im.crop(box).resize(size, flt), 0.4, f">>> {size} {box} {flt}", ) def test_skip_vertical(self): @@ -559,8 +555,5 @@ class TestCoreResampleBox: assert res.size == size # Borders should be slightly different assert_image_similar( - res, - im.crop(box).resize(size, flt), - 0.4, - ">>> {} {} {}".format(size, box, flt), + res, im.crop(box).resize(size, flt), 0.4, f">>> {size} {box} {flt}", ) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 56b189ecd..283006b3d 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -181,7 +181,7 @@ def helper_chord(mode, bbox, start, end): # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_chord_{}.png".format(mode) + expected = f"Tests/images/imagedraw_chord_{mode}.png" # Act draw.chord(bbox, start, end, fill="red", outline="yellow") @@ -243,7 +243,7 @@ def helper_ellipse(mode, bbox): # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_ellipse_{}.png".format(mode) + expected = f"Tests/images/imagedraw_ellipse_{mode}.png" # Act draw.ellipse(bbox, fill="green", outline="blue") @@ -513,7 +513,7 @@ def test_polygon_kite(): # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_polygon_kite_{}.png".format(mode) + expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png" # Act draw.polygon(KITE_POINTS, fill="blue", outline="yellow") @@ -1087,7 +1087,5 @@ def test_same_color_outline(): draw_method(*args) # Assert - expected = "Tests/images/imagedraw_outline_{}_{}.png".format( - operation, mode - ) + expected = f"Tests/images/imagedraw_outline_{operation}_{mode}.png" assert_image_similar_tofile(im, expected, 1) diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index 72cbb79b8..0d9f16ce8 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -55,7 +55,7 @@ def helper_ellipse(mode, bbox): draw = ImageDraw2.Draw(im) pen = ImageDraw2.Pen("blue", width=2) brush = ImageDraw2.Brush("green") - expected = "Tests/images/imagedraw_ellipse_{}.png".format(mode) + expected = f"Tests/images/imagedraw_ellipse_{mode}.png" # Act draw.ellipse(bbox, pen, brush) diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index 32ab05f17..32222c1d3 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -33,9 +33,7 @@ def _half_transparent_image(): def _check_alpha(im, original, op, amount): assert im.getbands() == original.getbands() assert_image_equal( - im.getchannel("A"), - original.getchannel("A"), - "Diff on {}: {}".format(op, amount), + im.getchannel("A"), original.getchannel("A"), f"Diff on {op}: {amount}", ) diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index bc4f1af28..239806796 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -3,7 +3,7 @@ from PIL import Image, ImageMath def pixel(im): if hasattr(im, "im"): - return "{} {!r}".format(im.mode, im.getpixel((0, 0))) + return "{} {}".format(im.mode, repr(im.getpixel((0, 0)))) else: if isinstance(im, int): return int(im) # hack to deal with booleans diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 62119e4b3..5b0be938a 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -64,7 +64,7 @@ def create_lut(): for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"): lb = ImageMorph.LutBuilder(op_name=op) lut = lb.build_lut() - with open("Tests/images/%s.lut" % op, "wb") as f: + with open(f"Tests/images/{op}.lut", "wb") as f: f.write(lut) @@ -75,7 +75,7 @@ def test_lut(): assert lb.get_lut() is None lut = lb.build_lut() - with open("Tests/images/%s.lut" % op, "rb") as f: + with open(f"Tests/images/{op}.lut", "rb") as f: assert lut == bytearray(f.read()) diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index d13920c16..7e87ac902 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -26,7 +26,7 @@ def setup_module(): tk.Frame() # root = tk.Tk() except tk.TclError as v: - pytest.skip("TCL Error: %s" % v) + pytest.skip(f"TCL Error: {v}") def test_kw(): diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index 19e16f2c4..0571aabf4 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -15,9 +15,9 @@ def verify(im1): xy = x, y p1 = pix1[xy] p2 = pix2[xy] - assert p1 == p2, "got {!r} from mode {} at {}, expected {!r}".format( - p1, im1.mode, xy, p2 - ) + assert ( + p1 == p2 + ), f"got {repr(p1)} from mode {im1.mode} at {xy}, expected {repr(p2)}" def test_basic(tmp_path): diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index 4c98bf0b4..fcb8c2f2a 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -42,7 +42,7 @@ def test_sanity(tmp_path): continue # Test saving the file - tempfile = str(tmp_path / "temp_{}.png".format(mode)) + tempfile = str(tmp_path / f"temp_{mode}.png") data.save(tempfile) # Check that it actually worked. diff --git a/Tests/test_qt_image_toqpixmap.py b/Tests/test_qt_image_toqpixmap.py index af281da69..f38cc7f13 100644 --- a/Tests/test_qt_image_toqpixmap.py +++ b/Tests/test_qt_image_toqpixmap.py @@ -16,5 +16,5 @@ class TestToQPixmap(PillowQPixmapTestCase): assert not data.isNull() # Test saving the file - tempfile = str(tmp_path / "temp_{}.png".format(mode)) + tempfile = str(tmp_path / f"temp_{mode}.png") data.save(tempfile) diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index 1e36f093a..78aa3ce72 100644 --- a/docs/example/DdsImagePlugin.py +++ b/docs/example/DdsImagePlugin.py @@ -212,10 +212,10 @@ class DdsImageFile(ImageFile.ImageFile): def _open(self): magic, header_size = struct.unpack("= (3, 9): warnings.warn( - "Pillow {} does not support Python {}.{} and does not provide prebuilt " - "Windows binaries. We do not recommend building from source on Windows.".format( - PILLOW_VERSION, sys.version_info.major, sys.version_info.minor - ), + f"Pillow {PILLOW_VERSION} does not support Python " + f"{sys.version_info.major}.{sys.version_info.minor} and does not provide " + "prebuilt Windows binaries. We do not recommend building from source on " + "Windows.", RuntimeWarning, ) @@ -175,7 +175,7 @@ def _find_library_dirs_ldconfig(): # Assuming GLIBC's ldconfig (with option -p) # Alpine Linux uses musl that can't print cache args = ["/sbin/ldconfig", "-p"] - expr = r".*\(%s.*\) => (.*)" % abi_type + expr = fr".*\({abi_type}.*\) => (.*)" env = dict(os.environ) env["LC_ALL"] = "C" env["LANG"] = "C" @@ -308,8 +308,8 @@ class pil_build_ext(build_ext): user_options = ( build_ext.user_options - + [("disable-%s" % x, None, "Disable support for %s" % x) for x in feature] - + [("enable-%s" % x, None, "Enable support for %s" % x) for x in feature] + + [(f"disable-{x}", None, f"Disable support for {x}") for x in feature] + + [(f"enable-{x}", None, f"Enable support for {x}") for x in feature] + [ ("disable-platform-guessing", None, "Disable platform guessing on Linux"), ("debug", None, "Debug logging"), @@ -322,8 +322,8 @@ class pil_build_ext(build_ext): self.add_imaging_libs = "" build_ext.initialize_options(self) for x in self.feature: - setattr(self, "disable_%s" % x, None) - setattr(self, "enable_%s" % x, None) + setattr(self, f"disable_{x}", None) + setattr(self, f"enable_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -340,15 +340,15 @@ class pil_build_ext(build_ext): except TypeError: self.parallel = None for x in self.feature: - if getattr(self, "disable_%s" % x): + if getattr(self, f"disable_{x}"): setattr(self.feature, x, False) self.feature.required.discard(x) _dbg("Disabling %s", x) - if getattr(self, "enable_%s" % x): + if getattr(self, f"enable_{x}"): raise ValueError( - "Conflicting options: --enable-{} and --disable-{}".format(x, x) + f"Conflicting options: --enable-{x} and --disable-{x}" ) - if getattr(self, "enable_%s" % x): + if getattr(self, f"enable_{x}"): _dbg("Requiring %s", x) self.feature.required.add(x) @@ -383,12 +383,12 @@ class pil_build_ext(build_ext): if root is None and pkg_config: if isinstance(lib_name, tuple): for lib_name2 in lib_name: - _dbg("Looking for `%s` using pkg-config." % lib_name2) + _dbg(f"Looking for `{lib_name2}` using pkg-config.") root = pkg_config(lib_name2) if root: break else: - _dbg("Looking for `%s` using pkg-config." % lib_name) + _dbg(f"Looking for `{lib_name}` using pkg-config.") root = pkg_config(lib_name) if isinstance(root, tuple): @@ -733,9 +733,9 @@ class pil_build_ext(build_ext): and sys.version_info < (3, 9) and not (PLATFORM_PYPY or PLATFORM_MINGW) ): - defs.append(("PILLOW_VERSION", '"\\"%s\\""' % PILLOW_VERSION)) + defs.append(("PILLOW_VERSION", f'"\\"{PILLOW_VERSION}\\""')) else: - defs.append(("PILLOW_VERSION", '"%s"' % PILLOW_VERSION)) + defs.append(("PILLOW_VERSION", f'"{PILLOW_VERSION}"')) exts = [(Extension("PIL._imaging", files, libraries=libs, define_macros=defs))] @@ -808,11 +808,11 @@ class pil_build_ext(build_ext): print("-" * 68) print("PIL SETUP SUMMARY") print("-" * 68) - print("version Pillow %s" % PILLOW_VERSION) + print(f"version Pillow {PILLOW_VERSION}") v = sys.version.split("[") - print("platform {} {}".format(sys.platform, v[0].strip())) + print(f"platform {sys.platform} {v[0].strip()}") for v in v[1:]: - print(" [%s" % v.strip()) + print(f" [{v.strip()}") print("-" * 68) options = [ @@ -833,10 +833,10 @@ class pil_build_ext(build_ext): if option[0]: version = "" if len(option) >= 3 and option[2]: - version = " (%s)" % option[2] - print("--- {} support available{}".format(option[1], version)) + version = f" ({option[2]})" + print(f"--- {option[1]} support available{version}") else: - print("*** %s support not available" % option[1]) + print(f"*** {option[1]} support not available") all = 0 print("-" * 68) @@ -900,28 +900,23 @@ try: zip_safe=not (debug_build() or PLATFORM_MINGW), ) except RequiredDependencyException as err: - msg = """ + msg = f""" -The headers or library files could not be found for %s, +The headers or library files could not be found for {str(err)}, a required dependency when compiling Pillow from source. Please see the install instructions at: https://pillow.readthedocs.io/en/latest/installation.html -""" % ( - str(err) - ) +""" sys.stderr.write(msg) raise RequiredDependencyException(msg) except DependencyException as err: - msg = """ + msg = f""" -The headers or library files could not be found for %s, -which was requested by the option flag --enable-%s +The headers or library files could not be found for {str(err)}, +which was requested by the option flag --enable-{str(err)} -""" % ( - str(err), - str(err), - ) +""" sys.stderr.write(msg) raise DependencyException(msg) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index cb8a08e20..d5d7c0e05 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -250,7 +250,7 @@ class BlpImageFile(ImageFile.ImageFile): decoder = "BLP2" self.mode = "RGBA" if self._blp_alpha_depth else "RGB" else: - raise BLPFormatError("Bad BLP magic %r" % (self.magic)) + raise BLPFormatError(f"Bad BLP magic {repr(self.magic)}") self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))] @@ -336,11 +336,11 @@ class BLP1Decoder(_BLPBaseDecoder): self.set_as_raw(bytes(data)) else: raise BLPFormatError( - "Unsupported BLP encoding %r" % (self._blp_encoding) + f"Unsupported BLP encoding {repr(self._blp_encoding)}" ) else: raise BLPFormatError( - "Unsupported BLP compression %r" % (self._blp_encoding) + f"Unsupported BLP compression {repr(self._blp_encoding)}" ) def _decode_jpeg_stream(self): @@ -400,13 +400,15 @@ class BLP2Decoder(_BLPBaseDecoder): data += d else: raise BLPFormatError( - "Unsupported alpha encoding %r" % (self._blp_alpha_encoding) + f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}" ) else: - raise BLPFormatError("Unknown BLP encoding %r" % (self._blp_encoding)) + raise BLPFormatError(f"Unknown BLP encoding {repr(self._blp_encoding)}") else: - raise BLPFormatError("Unknown BLP compression %r" % (self._blp_compression)) + raise BLPFormatError( + f"Unknown BLP compression {repr(self._blp_compression)}" + ) self.set_as_raw(bytes(data)) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 1d348b5a3..c1931b791 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -144,7 +144,7 @@ class BmpImageFile(ImageFile.ImageFile): file_info["a_mask"], ) else: - raise OSError("Unsupported BMP header type (%d)" % file_info["header_size"]) + raise OSError(f"Unsupported BMP header type ({file_info['header_size']})") # ------------------ Special case : header is reported 40, which # ---------------------- is shorter than real size for bpp >= 16 @@ -164,7 +164,7 @@ class BmpImageFile(ImageFile.ImageFile): # ---------------------- Check bit depth for unusual unsupported values self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) if self.mode is None: - raise OSError("Unsupported BMP pixel depth (%d)" % file_info["bits"]) + raise OSError(f"Unsupported BMP pixel depth ({file_info['bits']})") # ---------------- Process BMP with Bitfields compression (not palette) if file_info["compression"] == self.BITFIELDS: @@ -209,14 +209,14 @@ class BmpImageFile(ImageFile.ImageFile): if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" else: - raise OSError("Unsupported BMP compression (%d)" % file_info["compression"]) + raise OSError(f"Unsupported BMP compression ({file_info['compression']})") # --------------- Once the header is processed, process the palette/LUT if self.mode == "P": # Paletted for 1, 4 and 8 bit images # ---------------------------------------------------- 1-bit images if not (0 < file_info["colors"] <= 65536): - raise OSError("Unsupported BMP Palette size (%d)" % file_info["colors"]) + raise OSError(f"Unsupported BMP Palette size ({file_info['colors']})") else: padding = file_info["palette_padding"] palette = read(padding * file_info["colors"]) @@ -305,7 +305,7 @@ def _save(im, fp, filename, bitmap_header=True): try: rawmode, bits, colors = SAVE[im.mode] except KeyError as e: - raise OSError("cannot write mode %s as BMP" % im.mode) from e + raise OSError(f"cannot write mode {im.mode} as BMP") from e info = im.encoderinfo diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 9ba6e0ff8..3837192ab 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -106,10 +106,10 @@ class DdsImageFile(ImageFile.ImageFile): def _open(self): magic, header_size = struct.unpack("= (3, 7): if name == "PILLOW_VERSION": _raise_version_warning() return __version__ - raise AttributeError("module '{}' has no attribute '{}'".format(__name__, name)) + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") else: @@ -96,8 +96,8 @@ try: if __version__ != getattr(core, "PILLOW_VERSION", None): raise ImportError( "The _imaging extension was built for another version of Pillow or PIL:\n" - "Core version: %s\n" - "Pillow version: %s" % (getattr(core, "PILLOW_VERSION", None), __version__) + f"Core version: {getattr(core, 'PILLOW_VERSION', None)}\n" + f"Pillow version: {__version__}" ) except ImportError as v: @@ -403,7 +403,7 @@ def init(): for plugin in _plugins: try: logger.debug("Importing %s", plugin) - __import__("PIL.%s" % plugin, globals(), locals(), []) + __import__(f"PIL.{plugin}", globals(), locals(), []) except ImportError as e: logger.debug("Image: failed to import %s: %s", plugin, e) @@ -435,7 +435,7 @@ def _getdecoder(mode, decoder_name, args, extra=()): # get decoder decoder = getattr(core, decoder_name + "_decoder") except AttributeError as e: - raise OSError("decoder %s not available" % decoder_name) from e + raise OSError(f"decoder {decoder_name} not available") from e return decoder(mode, *args + extra) @@ -458,7 +458,7 @@ def _getencoder(mode, encoder_name, args, extra=()): # get encoder encoder = getattr(core, encoder_name + "_encoder") except AttributeError as e: - raise OSError("encoder %s not available" % encoder_name) from e + raise OSError(f"encoder {encoder_name} not available") from e return encoder(mode, *args + extra) @@ -742,7 +742,7 @@ class Image: if s: break if s < 0: - raise RuntimeError("encoder error %d in tobytes" % s) + raise RuntimeError(f"encoder error {s} in tobytes") return b"".join(data) @@ -763,9 +763,9 @@ class Image: data = self.tobytes("xbm") return b"".join( [ - ("#define %s_width %d\n" % (name, self.size[0])).encode("ascii"), - ("#define %s_height %d\n" % (name, self.size[1])).encode("ascii"), - ("static char %s_bits[] = {\n" % name).encode("ascii"), + f"#define {name}_width {self.size[0]}\n".encode("ascii"), + f"#define {name}_height {self.size[1]}\n".encode("ascii"), + f"static char {name}_bits[] = {{\n".encode("ascii"), data, b"};", ] @@ -1861,7 +1861,7 @@ class Image: """ if resample not in (NEAREST, BILINEAR, BICUBIC, LANCZOS, BOX, HAMMING): - message = "Unknown resampling filter ({}).".format(resample) + message = f"Unknown resampling filter ({resample})." filters = [ "{} ({})".format(filter[1], filter[0]) @@ -2129,7 +2129,7 @@ class Image: try: format = EXTENSION[ext] except KeyError as e: - raise ValueError("unknown file extension: {}".format(ext)) from e + raise ValueError(f"unknown file extension: {ext}") from e if format.upper() not in SAVE: init() @@ -2241,7 +2241,7 @@ class Image: try: channel = self.getbands().index(channel) except ValueError as e: - raise ValueError('The image has no channel "{}"'.format(channel)) from e + raise ValueError(f'The image has no channel "{channel}"') from e return self._new(self.im.getband(channel)) @@ -2462,9 +2462,9 @@ class Image: BOX: "Image.BOX", HAMMING: "Image.HAMMING", LANCZOS: "Image.LANCZOS/Image.ANTIALIAS", - }[resample] + " ({}) cannot be used.".format(resample) + }[resample] + f" ({resample}) cannot be used." else: - message = "Unknown resampling filter ({}).".format(resample) + message = f"Unknown resampling filter ({resample})." filters = [ "{} ({})".format(filter[1], filter[0]) @@ -2758,7 +2758,7 @@ def fromarray(obj, mode=None): else: ndmax = 4 if ndim > ndmax: - raise ValueError("Too many dimensions: %d > %d." % (ndim, ndmax)) + raise ValueError(f"Too many dimensions: {ndim} > {ndmax}.") size = 1 if ndim == 1 else shape[1], shape[0] if strides is not None: @@ -2824,14 +2824,14 @@ def _decompression_bomb_check(size): if pixels > 2 * MAX_IMAGE_PIXELS: raise DecompressionBombError( - "Image size (%d pixels) exceeds limit of %d pixels, " - "could be decompression bomb DOS attack." % (pixels, 2 * MAX_IMAGE_PIXELS) + f"Image size ({pixels} pixels) exceeds limit of {2 * MAX_IMAGE_PIXELS} " + "pixels, could be decompression bomb DOS attack." ) if pixels > MAX_IMAGE_PIXELS: warnings.warn( - "Image size (%d pixels) exceeds limit of %d pixels, " - "could be decompression bomb DOS attack." % (pixels, MAX_IMAGE_PIXELS), + f"Image size ({pixels} pixels) exceeds limit of {MAX_IMAGE_PIXELS} pixels, " + "could be decompression bomb DOS attack.", DecompressionBombWarning, ) @@ -2860,7 +2860,7 @@ def open(fp, mode="r"): """ if mode != "r": - raise ValueError("bad mode %r" % mode) + raise ValueError(f"bad mode {repr(mode)}") elif isinstance(fp, io.StringIO): raise ValueError( "StringIO cannot be used to open an image. " @@ -3241,13 +3241,13 @@ def _apply_env_variables(env=None): try: var = int(var) * units except ValueError: - warnings.warn("{} is not int".format(var_name)) + warnings.warn(f"{var_name} is not int") continue try: setter(var) except ValueError as e: - warnings.warn("{}: {}".format(var_name, e)) + warnings.warn(f"{var_name}: {e}") _apply_env_variables() @@ -3370,8 +3370,8 @@ class Exif(MutableMapping): if len(data) != size: warnings.warn( "Possibly corrupt EXIF MakerNote data. " - "Expecting to read %d bytes but only got %d." - " Skipping tag %s" % (size, len(data), ifd_tag) + f"Expecting to read {size} bytes but only got " + f"{len(data)}. Skipping tag {ifd_tag}" ) continue diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 1c4ce5a08..f854959a7 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -678,8 +678,7 @@ def createProfile(colorSpace, colorTemp=-1): if colorSpace not in ["LAB", "XYZ", "sRGB"]: raise PyCMSError( - "Color space not supported for on-the-fly profile creation (%s)" - % colorSpace + f"Color space not supported for on-the-fly profile creation ({colorSpace})" ) if colorSpace == "LAB": @@ -687,7 +686,7 @@ def createProfile(colorSpace, colorTemp=-1): colorTemp = float(colorTemp) except (TypeError, ValueError) as e: raise PyCMSError( - 'Color temperature must be numeric, "%s" not valid' % colorTemp + f'Color temperature must be numeric, "{colorTemp}" not valid' ) from e try: @@ -732,7 +731,7 @@ def getProfileName(profile): return (profile.profile.profile_description or "") + "\n" if not manufacturer or len(model) > 30: return model + "\n" - return "{} - {}\n".format(model, manufacturer) + return f"{model} - {manufacturer}\n" except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) from v diff --git a/src/PIL/ImageColor.py b/src/PIL/ImageColor.py index 9cf7a9912..909117449 100644 --- a/src/PIL/ImageColor.py +++ b/src/PIL/ImageColor.py @@ -113,7 +113,7 @@ def getrgb(color): m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) if m: return (int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))) - raise ValueError("unknown color specifier: %r" % color) + raise ValueError(f"unknown color specifier: {repr(color)}") def getcolor(color, mode): diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 648101ee3..bb8737d33 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -61,7 +61,7 @@ def raise_oserror(error): except AttributeError: message = ERRORS.get(error) if not message: - message = "decoder error %d" % error + message = f"decoder error {error}" raise OSError(message + " when reading image file") @@ -201,7 +201,7 @@ class ImageFile(Image.Image): # use mmap, if possible import mmap - with open(self.filename, "r") as fp: + with open(self.filename) as fp: self.map = mmap.mmap( fp.fileno(), 0, access=mmap.ACCESS_READ ) @@ -256,7 +256,7 @@ class ImageFile(Image.Image): else: raise OSError( "image file is truncated " - "(%d bytes not processed)" % len(b) + f"({len(b)} bytes not processed)" ) b = b + s @@ -332,7 +332,7 @@ class StubImageFile(ImageFile): def load(self): loader = self._load() if loader is None: - raise OSError("cannot find loader for this %s file" % self.format) + raise OSError(f"cannot find loader for this {self.format} file") image = loader.load(self) assert image is not None # become the other object (!) @@ -524,7 +524,7 @@ def _save(im, fp, tile, bufsize=0): if s: break if s < 0: - raise OSError("encoder error %d when writing image file" % s) from e + raise OSError(f"encoder error {s} when writing image file") from e e.cleanup() else: # slight speedup: compress to real file object @@ -539,7 +539,7 @@ def _save(im, fp, tile, bufsize=0): else: s = e.encode_to_file(fh, bufsize) if s < 0: - raise OSError("encoder error %d when writing image file" % s) + raise OSError(f"encoder error {s} when writing image file") e.cleanup() if hasattr(fp, "flush"): fp.flush() diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 18c550c84..c00261e47 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -401,9 +401,8 @@ class Color3DLUT(MultibandFilter): raise ValueError( "The table should have either channels * size**3 float items " "or size**3 items of channels-sized tuples with floats. " - "Table should be: {}x{}x{}x{}. Actual length: {}".format( - channels, size[0], size[1], size[2], len(table) - ) + f"Table should be: {channels}x{size[0]}x{size[1]}x{size[2]}. " + f"Actual length: {len(table)}" ) self.table = table @@ -513,12 +512,12 @@ class Color3DLUT(MultibandFilter): def __repr__(self): r = [ - "{} from {}".format(self.__class__.__name__, self.table.__class__.__name__), + f"{self.__class__.__name__} from {self.table.__class__.__name__}", "size={:d}x{:d}x{:d}".format(*self.size), - "channels={:d}".format(self.channels), + f"channels={self.channels:d}", ] if self.mode: - r.append("target_mode={}".format(self.mode)) + r.append(f"target_mode={self.mode}") return "<{}>".format(" ".join(r)) def filter(self, image): diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 9a2d0b78e..7f9c88e14 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -41,7 +41,7 @@ class _Operand: elif im1.im.mode in ("I", "F"): return im1.im else: - raise ValueError("unsupported mode: %s" % im1.im.mode) + raise ValueError(f"unsupported mode: {im1.im.mode}") else: # argument was a constant if _isconstant(im1) and self.im.mode in ("1", "L", "I"): @@ -58,7 +58,7 @@ class _Operand: try: op = getattr(_imagingmath, op + "_" + im1.mode) except AttributeError as e: - raise TypeError("bad operand type for '%s'" % op) from e + raise TypeError(f"bad operand type for '{op}'") from e _imagingmath.unop(op, out.im.id, im1.im.id) else: # binary operation @@ -86,7 +86,7 @@ class _Operand: try: op = getattr(_imagingmath, op + "_" + im1.mode) except AttributeError as e: - raise TypeError("bad operand type for '%s'" % op) from e + raise TypeError(f"bad operand type for '{op}'") from e _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id) return _Operand(out) diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 5dba6176f..d0604112f 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -111,7 +111,7 @@ class ImagePalette: self.dirty = 1 return index else: - raise ValueError("unknown color specifier: %r" % color) + raise ValueError(f"unknown color specifier: {repr(color)}") def save(self, fp): """Save palette to text file. @@ -123,12 +123,12 @@ class ImagePalette: if isinstance(fp, str): fp = open(fp, "w") fp.write("# Palette\n") - fp.write("# Mode: %s\n" % self.mode) + fp.write(f"# Mode: {self.mode}\n") for i in range(256): - fp.write("%d" % i) + fp.write(f"{i}") for j in range(i * len(self.mode), (i + 1) * len(self.mode)): try: - fp.write(" %d" % self.palette[j]) + fp.write(f" {self.palette[j]}") except IndexError: fp.write(" 0") fp.write("\n") diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index a15f4ab5e..63cc6ae5a 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -145,7 +145,7 @@ def _toqclass_helper(im): data = im.tobytes("raw", "BGRA") format = QImage.Format_ARGB32 else: - raise ValueError("unsupported image mode %r" % im.mode) + raise ValueError(f"unsupported image mode {repr(im.mode)}") __data = data or align8to32(im.tobytes(), im.size[0], im.mode) return {"data": __data, "im": im, "format": format, "colortable": colortable} diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 3ffb4d632..1ada8252c 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -123,9 +123,9 @@ class WindowsViewer(Viewer): def get_command(self, file, **options): return ( - 'start "Pillow" /WAIT "%s" ' + f'start "Pillow" /WAIT "{file}" ' "&& ping -n 2 127.0.0.1 >NUL " - '&& del /f "%s"' % (file, file) + f'&& del /f "{file}"' ) @@ -143,9 +143,7 @@ class MacViewer(Viewer): # on darwin open returns immediately resulting in the temp # file removal while app is opening command = "open -a Preview.app" - command = "({} {}; sleep 20; rm -f {})&".format( - command, quote(file), quote(file) - ) + command = f"({command} {quote(file)}; sleep 20; rm -f {quote(file)})&" return command def show_file(self, file, **options): @@ -153,7 +151,7 @@ class MacViewer(Viewer): fd, path = tempfile.mkstemp() with os.fdopen(fd, "w") as f: f.write(file) - with open(path, "r") as f: + with open(path) as f: subprocess.Popen( ["im=$(cat); open -a Preview.app $im; sleep 20; rm -f $im"], shell=True, @@ -173,14 +171,14 @@ class UnixViewer(Viewer): def get_command(self, file, **options): command = self.get_command_ex(file, **options)[0] - return "({} {}; rm -f {})&".format(command, quote(file), quote(file)) + return f"({command} {quote(file)}; rm -f {quote(file)})&" def show_file(self, file, **options): """Display given file""" fd, path = tempfile.mkstemp() with os.fdopen(fd, "w") as f: f.write(file) - with open(path, "r") as f: + with open(path) as f: command = self.get_command_ex(file, **options)[0] subprocess.Popen( ["im=$(cat);" + command + " $im; rm -f $im"], shell=True, stdin=f @@ -216,7 +214,7 @@ class XVViewer(UnixViewer): # imagemagick's display command instead. command = executable = "xv" if title: - command += " -name %s" % quote(title) + command += f" -name {quote(title)}" return command, executable diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index ee707cffb..e8148ad01 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -41,7 +41,7 @@ def _pilbitmap_check(): if _pilbitmap_ok is None: try: im = Image.new("1", (1, 1)) - tkinter.BitmapImage(data="PIL:%d" % im.im.id) + tkinter.BitmapImage(data=f"PIL:{im.im.id}") _pilbitmap_ok = 1 except tkinter.TclError: _pilbitmap_ok = 0 @@ -229,7 +229,7 @@ class BitmapImage: if _pilbitmap_check(): # fast way (requires the pilbitmap booster patch) image.load() - kw["data"] = "PIL:%d" % image.im.id + kw["data"] = f"PIL:{image.im.id}" self.__im = image # must keep a reference else: # slow but safe way diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index f18eedb20..dfd96d2df 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -195,7 +195,7 @@ def SOF(self, marker): self.bits = i8(s[0]) if self.bits != 8: - raise SyntaxError("cannot handle %d-bit layers" % self.bits) + raise SyntaxError(f"cannot handle {self.bits}-bit layers") self.layers = i8(s[5]) if self.layers == 1: @@ -205,7 +205,7 @@ def SOF(self, marker): elif self.layers == 4: self.mode = "CMYK" else: - raise SyntaxError("cannot handle %d-layer images" % self.layers) + raise SyntaxError(f"cannot handle {self.layers}-layer images") if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]: self.info["progressive"] = self.info["progression"] = 1 @@ -518,7 +518,7 @@ def _getmp(self): rawmpentries = mp[0xB002] for entrynum in range(0, quant): unpackedentry = struct.unpack_from( - "{}LLLHH".format(endianness), rawmpentries, entrynum * 16 + f"{endianness}LLLHH", rawmpentries, entrynum * 16 ) labels = ("Attribute", "Size", "DataOffset", "EntryNo1", "EntryNo2") mpentry = dict(zip(labels, unpackedentry)) @@ -613,7 +613,7 @@ def _save(im, fp, filename): try: rawmode = RAWMODE[im.mode] except KeyError as e: - raise OSError("cannot write mode %s as JPEG" % im.mode) from e + raise OSError(f"cannot write mode {im.mode} as JPEG") from e info = im.encoderinfo diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index ca9572187..03add47f4 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -114,7 +114,7 @@ class MspDecoder(ImageFile.PyDecoder): try: self.fd.seek(32) rowmap = struct.unpack_from( - "<%dH" % (self.state.ysize), self.fd.read(self.state.ysize * 2) + f"<{self.state.ysize}H", self.fd.read(self.state.ysize * 2) ) except struct.error as e: raise OSError("Truncated MSP file in row map") from e @@ -143,7 +143,7 @@ class MspDecoder(ImageFile.PyDecoder): idx += runcount except struct.error as e: - raise OSError("Corrupted MSP file in row %d" % x) from e + raise OSError(f"Corrupted MSP file in row {x}") from e self.set_as_raw(img.getvalue(), ("1", 0, 1)) @@ -160,7 +160,7 @@ Image.register_decoder("MSP", MspDecoder) def _save(im, fp, filename): if im.mode != "1": - raise OSError("cannot write mode %s as MSP" % im.mode) + raise OSError(f"cannot write mode {im.mode} as MSP") # create MSP header header = [0] * 16 diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index 3cfcbaf28..3d96b481b 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -71,10 +71,10 @@ class PSDraw: """ if font not in self.isofont: # reencode font - self._fp_write("/PSDraw-{} ISOLatin1Encoding /{} E\n".format(font, font)) + self._fp_write(f"/PSDraw-{font} ISOLatin1Encoding /{font} E\n") self.isofont[font] = 1 # rough - self._fp_write("/F0 %d /PSDraw-%s F\n" % (size, font)) + self._fp_write(f"/F0 {size} /PSDraw-{font} F\n") def line(self, xy0, xy1): """ @@ -82,8 +82,7 @@ class PSDraw: PostScript point coordinates (72 points per inch, (0, 0) is the lower left corner of the page). """ - xy = xy0 + xy1 - self._fp_write("%d %d %d %d Vl\n" % xy) + self._fp_write("%d %d %d %d Vl\n" % (*xy0, *xy1)) def rectangle(self, box): """ @@ -107,8 +106,7 @@ class PSDraw: """ text = "\\(".join(text.split("(")) text = "\\)".join(text.split(")")) - xy = xy + (text,) - self._fp_write("%d %d M (%s) S\n" % xy) + self._fp_write(f"{xy[0]} {xy[1]} M ({text}) S\n") def image(self, box, im, dpi=None): """Draw a PIL image, centered in the given box.""" @@ -132,12 +130,12 @@ class PSDraw: y = ymax dx = (xmax - x) / 2 + box[0] dy = (ymax - y) / 2 + box[1] - self._fp_write("gsave\n{:f} {:f} translate\n".format(dx, dy)) + self._fp_write(f"gsave\n{dx:f} {dy:f} translate\n") if (x, y) != im.size: # EpsImagePlugin._save prints the image at (0,0,xsize,ysize) sx = x / im.size[0] sy = y / im.size[1] - self._fp_write("{:f} {:f} scale\n".format(sx, sy)) + self._fp_write(f"{sx:f} {sy:f} scale\n") EpsImagePlugin._save(im, self.fp, None, 0) self._fp_write("\ngrestore\n") diff --git a/src/PIL/PalmImagePlugin.py b/src/PIL/PalmImagePlugin.py index 9fc55d795..553fe7b77 100644 --- a/src/PIL/PalmImagePlugin.py +++ b/src/PIL/PalmImagePlugin.py @@ -137,7 +137,7 @@ def _save(im, fp, filename): bpp = im.info["bpp"] im = im.point(lambda x, maxval=(1 << bpp) - 1: maxval - (x & maxval)) else: - raise OSError("cannot write mode %s as Palm" % im.mode) + raise OSError(f"cannot write mode {im.mode} as Palm") # we ignore the palette here im.mode = "P" @@ -153,7 +153,7 @@ def _save(im, fp, filename): else: - raise OSError("cannot write mode %s as Palm" % im.mode) + raise OSError(f"cannot write mode {im.mode} as Palm") # # make sure image data is available diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index f7ae3bf70..0306237dc 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -132,7 +132,7 @@ def _save(im, fp, filename): try: version, bits, planes, rawmode = SAVE[im.mode] except KeyError as e: - raise ValueError("Cannot save %s images as PCX" % im.mode) from e + raise ValueError(f"Cannot save {im.mode} images as PCX") from e # bytes per plane stride = (im.size[0] * bits + 7) // 8 diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 47500baf7..9583f704a 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -77,7 +77,7 @@ def _save(im, fp, filename, save_all=False): existing_pdf.start_writing() existing_pdf.write_header() - existing_pdf.write_comment("created by Pillow {} PDF driver".format(__version__)) + existing_pdf.write_comment(f"created by Pillow {__version__} PDF driver") # # pages @@ -129,7 +129,7 @@ def _save(im, fp, filename, save_all=False): bits = 1 elif im.mode == "L": filter = "DCTDecode" - # params = "<< /Predictor 15 /Columns %d >>" % (width-2) + # params = f"<< /Predictor 15 /Columns {width-2} >>" colorspace = PdfParser.PdfName("DeviceGray") procset = "ImageB" # grayscale elif im.mode == "P": @@ -151,7 +151,7 @@ def _save(im, fp, filename, save_all=False): colorspace = PdfParser.PdfName("DeviceCMYK") procset = "ImageC" # color images else: - raise ValueError("cannot save mode %s" % im.mode) + raise ValueError(f"cannot save mode {im.mode}") # # image @@ -173,7 +173,7 @@ def _save(im, fp, filename, save_all=False): elif filter == "RunLengthDecode": ImageFile._save(im, op, [("packbits", (0, 0) + im.size, 0, im.mode)]) else: - raise ValueError("unsupported PDF filter (%s)" % filter) + raise ValueError(f"unsupported PDF filter ({filter})") # # Get image characteristics diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 3c343c5e8..975905f96 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -183,8 +183,8 @@ class XrefTable: this_deleted_object_id = deleted_keys.pop(0) check_format_condition( object_id == this_deleted_object_id, - "expected the next deleted object ID to be %s, instead found %s" - % (object_id, this_deleted_object_id), + f"expected the next deleted object ID to be {object_id}, " + f"instead found {this_deleted_object_id}", ) try: next_in_linked_list = deleted_keys[0] @@ -218,7 +218,7 @@ class PdfName: return hash(self.name) def __repr__(self): - return "PdfName(%s)" % repr(self.name) + return f"PdfName({repr(self.name)})" @classmethod def from_pdf_stream(cls, data): @@ -315,7 +315,7 @@ class PdfStream: return zlib.decompress(self.buf, bufsize=int(expected_length)) else: raise NotImplementedError( - "stream filter %s unknown/unsupported" % repr(self.dictionary.Filter) + f"stream filter {repr(self.dictionary.Filter)} unknown/unsupported" ) @@ -423,7 +423,7 @@ class PdfParser: self.f.write(b"%PDF-1.4\n") def write_comment(self, s): - self.f.write(("% {}\n".format(s)).encode("utf-8")) + self.f.write(f"% {s}\n".encode("utf-8")) def write_catalog(self): self.del_root() @@ -966,9 +966,8 @@ class PdfParser: offset, generation = self.xref_table[ref[0]] check_format_condition( generation == ref[1], - "expected to find generation %s for object ID %s in xref table, " - "instead found generation %s at offset %s" - % (ref[1], ref[0], generation, offset), + f"expected to find generation {ref[1]} for object ID {ref[0]} in xref " + f"table, instead found generation {generation} at offset {offset}", ) value = self.get_value( self.buf, diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 9d048b211..9900a5e12 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -159,7 +159,7 @@ class ChunkStream: if not is_cid(cid): if not ImageFile.LOAD_TRUNCATED_IMAGES: - raise SyntaxError("broken PNG file (chunk %s)" % repr(cid)) + raise SyntaxError(f"broken PNG file (chunk {repr(cid)})") return cid, pos, length @@ -196,10 +196,12 @@ class ChunkStream: crc1 = _crc32(data, _crc32(cid)) crc2 = i32(self.fp.read(4)) if crc1 != crc2: - raise SyntaxError("broken PNG file (bad header checksum in %r)" % cid) + raise SyntaxError( + f"broken PNG file (bad header checksum in {repr(cid)})" + ) except struct.error as e: raise SyntaxError( - "broken PNG file (incomplete checksum in %r)" % cid + f"broken PNG file (incomplete checksum in {repr(cid)})" ) from e def crc_skip(self, cid, data): @@ -351,8 +353,8 @@ class PngStream(ChunkStream): self.text_memory += chunklen if self.text_memory > MAX_TEXT_MEMORY: raise ValueError( - "Too much memory used in text chunks: %s>MAX_TEXT_MEMORY" - % self.text_memory + "Too much memory used in text chunks: " + f"{self.text_memory}>MAX_TEXT_MEMORY" ) def save_rewind(self): @@ -381,9 +383,7 @@ class PngStream(ChunkStream): logger.debug("Compression method %s", i8(s[i])) comp_method = i8(s[i]) if comp_method != 0: - raise SyntaxError( - "Unknown compression method %s in iCCP chunk" % comp_method - ) + raise SyntaxError(f"Unknown compression method {comp_method} in iCCP chunk") try: icc_profile = _safe_zlib_decompress(s[i + 2 :]) except ValueError: @@ -530,9 +530,7 @@ class PngStream(ChunkStream): else: comp_method = 0 if comp_method != 0: - raise SyntaxError( - "Unknown compression method %s in zTXt chunk" % comp_method - ) + raise SyntaxError(f"Unknown compression method {comp_method} in zTXt chunk") try: v = _safe_zlib_decompress(v[1:]) except ValueError: @@ -794,7 +792,7 @@ class PngImageFile(ImageFile.ImageFile): return else: if frame != self.__frame + 1: - raise ValueError("cannot seek to frame %d" % frame) + raise ValueError(f"cannot seek to frame {frame}") # ensure previous frame was loaded self.load() @@ -1186,7 +1184,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): else: bits = 8 if bits != 8: - mode = "%s;%d" % (mode, bits) + mode = f"{mode};{bits}" # encoder options im.encoderconfig = ( @@ -1200,7 +1198,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): try: rawmode, mode = _OUTMODES[mode] except KeyError as e: - raise OSError("cannot write mode %s as PNG" % mode) from e + raise OSError(f"cannot write mode {mode} as PNG") from e # # write minimal PNG file diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 35a77bafb..abf4d651d 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -104,7 +104,7 @@ class PpmImageFile(ImageFile.ImageFile): # maxgrey if s > 255: if not mode == "L": - raise ValueError("Too many colors for band: %s" % s) + raise ValueError(f"Too many colors for band: {s}") if s < 2 ** 16: self.mode = "I" rawmode = "I;16B" @@ -135,7 +135,7 @@ def _save(im, fp, filename): elif im.mode == "RGBA": rawmode, head = "RGB", b"P6" else: - raise OSError("cannot write mode %s as PPM" % im.mode) + raise OSError(f"cannot write mode {im.mode} as PPM") fp.write(head + ("\n%d %d\n" % im.size).encode("ascii")) if head == b"P6": fp.write(b"255\n") diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index ec9855e77..612ebe5a8 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -158,9 +158,7 @@ def _save(im, fp, filename): # assert we've got the right number of bands. if len(im.getbands()) != z: raise ValueError( - "incorrect number of bands in SGI write: {} vs {}".format( - z, len(im.getbands()) - ) + f"incorrect number of bands in SGI write: {z} vs {len(im.getbands())}" ) # Minimum Byte value diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 56aac2987..819f2ed0a 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -213,7 +213,7 @@ def loadImageSeries(filelist=None): imglist = [] for img in filelist: if not os.path.exists(img): - print("unable to find %s" % img) + print(f"unable to find {img}") continue try: with Image.open(img) as im: @@ -318,7 +318,7 @@ if __name__ == "__main__": # perform some image operation im = im.transpose(Image.FLIP_LEFT_RIGHT) print( - "saving a flipped version of %s as %s " - % (os.path.basename(filename), outfile) + f"saving a flipped version of {os.path.basename(filename)} " + f"as {outfile} " ) im.save(outfile, SpiderImageFile.format) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index 566f0ac18..d3f780d4d 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -168,7 +168,7 @@ def _save(im, fp, filename): try: rawmode, bits, colormaptype, imagetype = SAVE[im.mode] except KeyError as e: - raise OSError("cannot write mode %s as TGA" % im.mode) from e + raise OSError(f"cannot write mode {im.mode} as TGA") from e if "rle" in im.encoderinfo: rle = im.encoderinfo["rle"] diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 6a772e48b..453cc8660 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -464,7 +464,7 @@ class ImageFileDirectory_v2(MutableMapping): :param prefix: Override the endianness of the file. """ if ifh[:4] not in PREFIXES: - raise SyntaxError("not a TIFF file (header %r not valid)" % ifh) + raise SyntaxError(f"not a TIFF file (header {repr(ifh)} not valid)") self._prefix = prefix if prefix is not None else ifh[:2] if self._prefix == MM: self._endian = ">" @@ -592,8 +592,8 @@ class ImageFileDirectory_v2(MutableMapping): except ValueError: # We've got a builtin tag with 1 expected entry warnings.warn( - "Metadata Warning, tag %s had too many entries: %s, expected 1" - % (tag, len(values)) + f"Metadata Warning, tag {tag} had too many entries: " + f"{len(values)}, expected 1" ) dest[tag] = values[0] @@ -728,7 +728,7 @@ class ImageFileDirectory_v2(MutableMapping): if len(ret) != size: raise OSError( "Corrupt EXIF data. " - + "Expecting to read %d bytes but only got %d. " % (size, len(ret)) + f"Expecting to read {size} bytes but only got {len(ret)}. " ) return ret @@ -743,18 +743,18 @@ class ImageFileDirectory_v2(MutableMapping): tagname = TiffTags.lookup(tag).name typname = TYPES.get(typ, "unknown") - msg = "tag: %s (%d) - type: %s (%d)" % (tagname, tag, typname, typ) + msg = f"tag: {tagname} ({tag}) - type: {typname} ({typ})" try: unit_size, handler = self._load_dispatch[typ] except KeyError: - logger.debug(msg + " - unsupported type {}".format(typ)) + logger.debug(msg + f" - unsupported type {typ}") continue # ignore unsupported type size = count * unit_size if size > 4: here = fp.tell() (offset,) = self._unpack("L", data) - msg += " Tag Location: {} - Data Location: {}".format(here, offset) + msg += f" Tag Location: {here} - Data Location: {offset}" fp.seek(offset) data = ImageFile._safe_read(fp, size) fp.seek(here) @@ -764,8 +764,8 @@ class ImageFileDirectory_v2(MutableMapping): if len(data) != size: warnings.warn( "Possibly corrupt EXIF data. " - "Expecting to read %d bytes but only got %d." - " Skipping tag %s" % (size, len(data), tag) + f"Expecting to read {size} bytes but only got {len(data)}." + f" Skipping tag {tag}" ) logger.debug(msg) continue @@ -801,13 +801,13 @@ class ImageFileDirectory_v2(MutableMapping): if tag == STRIPOFFSETS: stripoffsets = len(entries) typ = self.tagtype.get(tag) - logger.debug("Tag {}, Type: {}, Value: {}".format(tag, typ, value)) + logger.debug(f"Tag {tag}, Type: {typ}, Value: {value}") values = value if isinstance(value, tuple) else (value,) data = self._write_dispatch[typ](self, *values) tagname = TiffTags.lookup(tag).name typname = TYPES.get(typ, "unknown") - msg = "save: %s (%d) - type: %s (%d)" % (tagname, tag, typname, typ) + msg = f"save: {tagname} ({tag}) - type: {typname} ({typ})" msg += " - value: " + ( "" % len(data) if len(data) >= 16 else str(values) ) @@ -835,9 +835,7 @@ class ImageFileDirectory_v2(MutableMapping): # pass 2: write entries to file for tag, typ, count, value, data in entries: - logger.debug( - "{} {} {} {} {}".format(tag, typ, count, repr(value), repr(data)) - ) + logger.debug(f"{tag} {typ} {count} {repr(value)} {repr(data)}") result += self._pack("HHL4s", tag, typ, count, value) # -- overwrite here for multi-page -- @@ -1002,8 +1000,8 @@ class TiffImageFile(ImageFile.ImageFile): self._n_frames = None logger.debug("*** TiffImageFile._open ***") - logger.debug("- __first: {}".format(self.__first)) - logger.debug("- ifh: {!r}".format(ifh)) # Use !r to avoid str(bytes) + logger.debug(f"- __first: {self.__first}") + logger.debug(f"- ifh: {repr(ifh)}") # Use repr to avoid str(bytes) # and load the first frame self._seek(0) @@ -1035,8 +1033,8 @@ class TiffImageFile(ImageFile.ImageFile): if not self.__next: raise EOFError("no more images in TIFF file") logger.debug( - "Seeking to frame %s, on frame %s, __next %s, location: %s" - % (frame, self.__frame, self.__next, self.fp.tell()) + f"Seeking to frame {frame}, on frame {self.__frame}, " + f"__next {self.__next}, location: {self.fp.tell()}" ) # reset buffered io handle in case fp # was passed to libtiff, invalidating the buffer @@ -1193,18 +1191,18 @@ class TiffImageFile(ImageFile.ImageFile): fillorder = self.tag_v2.get(FILLORDER, 1) logger.debug("*** Summary ***") - logger.debug("- compression: {}".format(self._compression)) - logger.debug("- photometric_interpretation: {}".format(photo)) - logger.debug("- planar_configuration: {}".format(self._planar_configuration)) - logger.debug("- fill_order: {}".format(fillorder)) - logger.debug("- YCbCr subsampling: {}".format(self.tag.get(530))) + logger.debug(f"- compression: {self._compression}") + logger.debug(f"- photometric_interpretation: {photo}") + logger.debug(f"- planar_configuration: {self._planar_configuration}") + logger.debug(f"- fill_order: {fillorder}") + logger.debug(f"- YCbCr subsampling: {self.tag.get(530)}") # size xsize = int(self.tag_v2.get(IMAGEWIDTH)) ysize = int(self.tag_v2.get(IMAGELENGTH)) self._size = xsize, ysize - logger.debug("- size: {}".format(self.size)) + logger.debug(f"- size: {self.size}") sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,)) if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1: @@ -1238,15 +1236,15 @@ class TiffImageFile(ImageFile.ImageFile): bps_tuple, extra_tuple, ) - logger.debug("format key: {}".format(key)) + logger.debug(f"format key: {key}") try: self.mode, rawmode = OPEN_INFO[key] except KeyError as e: logger.debug("- unsupported format") raise SyntaxError("unknown pixel mode") from e - logger.debug("- raw mode: {}".format(rawmode)) - logger.debug("- pil mode: {}".format(self.mode)) + logger.debug(f"- raw mode: {rawmode}") + logger.debug(f"- pil mode: {self.mode}") self.info["compression"] = self._compression @@ -1287,7 +1285,7 @@ class TiffImageFile(ImageFile.ImageFile): if fillorder == 2: # Replace fillorder with fillorder=1 key = key[:3] + (1,) + key[4:] - logger.debug("format key: {}".format(key)) + logger.debug(f"format key: {key}") # this should always work, since all the # fillorder==2 modes have a corresponding # fillorder=1 mode @@ -1411,7 +1409,7 @@ def _save(im, fp, filename): try: rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode] except KeyError as e: - raise OSError("cannot write mode %s as TIFF" % im.mode) from e + raise OSError(f"cannot write mode {im.mode} as TIFF") from e ifd = ImageFileDirectory_v2(prefix=prefix) @@ -1608,7 +1606,7 @@ def _save(im, fp, filename): if s: break if s < 0: - raise OSError("encoder error %d when writing image file" % s) + raise OSError(f"encoder error {s} when writing image file") else: offset = ifd.save(fp) @@ -1774,29 +1772,29 @@ class AppendingTiffWriter: self.f.seek(-2, os.SEEK_CUR) bytesWritten = self.f.write(struct.pack(self.longFmt, value)) if bytesWritten is not None and bytesWritten != 4: - raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten) + raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4") def rewriteLastShort(self, value): self.f.seek(-2, os.SEEK_CUR) bytesWritten = self.f.write(struct.pack(self.shortFmt, value)) if bytesWritten is not None and bytesWritten != 2: - raise RuntimeError("wrote only %u bytes but wanted 2" % bytesWritten) + raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 2") def rewriteLastLong(self, value): self.f.seek(-4, os.SEEK_CUR) bytesWritten = self.f.write(struct.pack(self.longFmt, value)) if bytesWritten is not None and bytesWritten != 4: - raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten) + raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4") def writeShort(self, value): bytesWritten = self.f.write(struct.pack(self.shortFmt, value)) if bytesWritten is not None and bytesWritten != 2: - raise RuntimeError("wrote only %u bytes but wanted 2" % bytesWritten) + raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 2") def writeLong(self, value): bytesWritten = self.f.write(struct.pack(self.longFmt, value)) if bytesWritten is not None and bytesWritten != 4: - raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten) + raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4") def close(self): self.finalize() diff --git a/src/PIL/XbmImagePlugin.py b/src/PIL/XbmImagePlugin.py index ead9722c8..644cfb39b 100644 --- a/src/PIL/XbmImagePlugin.py +++ b/src/PIL/XbmImagePlugin.py @@ -69,15 +69,15 @@ class XbmImageFile(ImageFile.ImageFile): def _save(im, fp, filename): if im.mode != "1": - raise OSError("cannot write mode %s as XBM" % im.mode) + raise OSError(f"cannot write mode {im.mode} as XBM") - fp.write(("#define im_width %d\n" % im.size[0]).encode("ascii")) - fp.write(("#define im_height %d\n" % im.size[1]).encode("ascii")) + fp.write(f"#define im_width {im.size[0]}\n".encode("ascii")) + fp.write(f"#define im_height {im.size[1]}\n".encode("ascii")) hotspot = im.encoderinfo.get("hotspot") if hotspot: - fp.write(("#define im_x_hot %d\n" % hotspot[0]).encode("ascii")) - fp.write(("#define im_y_hot %d\n" % hotspot[1]).encode("ascii")) + fp.write(f"#define im_x_hot {hotspot[0]}\n".encode("ascii")) + fp.write(f"#define im_y_hot {hotspot[1]}\n".encode("ascii")) fp.write(b"static char im_bits[] = {\n") diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index d225ed134..d4f5ea76a 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -39,7 +39,7 @@ if sys.version_info >= (3, 7): if name == "PILLOW_VERSION": _raise_version_warning() return __version__ - raise AttributeError("module '{}' has no attribute '{}'".format(__name__, name)) + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") else: diff --git a/src/PIL/features.py b/src/PIL/features.py index 66b093350..d2e861372 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -25,7 +25,7 @@ def check_module(feature): :raises ValueError: If the module is not defined in this version of Pillow. """ if not (feature in modules): - raise ValueError("Unknown module %s" % feature) + raise ValueError(f"Unknown module {feature}") module, ver = modules[feature] @@ -78,7 +78,7 @@ def check_codec(feature): :raises ValueError: If the codec is not defined in this version of Pillow. """ if feature not in codecs: - raise ValueError("Unknown codec %s" % feature) + raise ValueError(f"Unknown codec {feature}") codec, lib = codecs[feature] @@ -133,7 +133,7 @@ def check_feature(feature): :raises ValueError: If the feature is not defined in this version of Pillow. """ if feature not in features: - raise ValueError("Unknown feature %s" % feature) + raise ValueError(f"Unknown feature {feature}") module, flag, ver = features[feature] @@ -182,7 +182,7 @@ def check(feature): return check_codec(feature) if feature in features: return check_feature(feature) - warnings.warn("Unknown feature '%s'." % feature, stacklevel=2) + warnings.warn(f"Unknown feature '{feature}'.", stacklevel=2) return False @@ -230,19 +230,17 @@ def pilinfo(out=None, supported_formats=True): Image.init() print("-" * 68, file=out) - print("Pillow {}".format(PIL.__version__), file=out) + print(f"Pillow {PIL.__version__}", file=out) py_version = sys.version.splitlines() - print("Python {}".format(py_version[0].strip()), file=out) + print(f"Python {py_version[0].strip()}", file=out) for py_version in py_version[1:]: - print(" {}".format(py_version.strip()), file=out) + print(f" {py_version.strip()}", file=out) print("-" * 68, file=out) print( - "Python modules loaded from {}".format(os.path.dirname(Image.__file__)), - file=out, + f"Python modules loaded from {os.path.dirname(Image.__file__)}", file=out, ) print( - "Binary modules loaded from {}".format(os.path.dirname(Image.core.__file__)), - file=out, + f"Binary modules loaded from {os.path.dirname(Image.core.__file__)}", file=out, ) print("-" * 68, file=out) @@ -283,9 +281,9 @@ def pilinfo(out=None, supported_formats=True): extensions[i].append(ext) for i in sorted(Image.ID): - line = "{}".format(i) + line = f"{i}" if i in Image.MIME: - line = "{} {}".format(line, Image.MIME[i]) + line = f"{line} {Image.MIME[i]}" print(line, file=out) if i in extensions: diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 7edc7df5b..ec292b14d 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -417,7 +417,7 @@ def build_dep(name): for patch_file, patch_list in dep.get("patch", {}).items(): patch_file = os.path.join(build_dir, dir, patch_file.format(**prefs)) - with open(patch_file, "r") as f: + with open(patch_file) as f: text = f.read() for patch_from, patch_to in patch_list.items(): text = text.replace(patch_from.format(**prefs), patch_to.format(**prefs)) From 1fe4070af6ac796ebdf2aedf91e64519ed1e25aa Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 11 Aug 2020 22:29:44 +0300 Subject: [PATCH 3/4] Drop support for EOL Python 3.5 --- Tests/test_util.py | 1 - src/PIL/_util.py | 17 +++-------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index 3d88b9472..0bc8b0702 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -13,7 +13,6 @@ def test_is_path(): assert it_is -@pytest.mark.skipif(not _util.py36, reason="os.path support for Paths added in 3.6") def test_path_obj_is_path(): # Arrange from pathlib import Path diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 755b4b272..0c5d3892e 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -1,20 +1,9 @@ import os -import sys - -py36 = sys.version_info[0:2] >= (3, 6) +from pathlib import Path -if py36: - from pathlib import Path - - def isPath(f): - return isinstance(f, (bytes, str, Path)) - - -else: - - def isPath(f): - return isinstance(f, (bytes, str)) +def isPath(f): + return isinstance(f, (bytes, str, Path)) # Checks if an object is a string, and that it points to a directory. From e0eec1eb566f10695077cb6f6b700956db0c8df8 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 1 Sep 2020 20:16:46 +0300 Subject: [PATCH 4/4] Merge branch 'master' into rm-3.5 --- .ci/install.sh | 5 +- .github/workflows/macos-install.sh | 3 + .github/workflows/test-windows.yml | 76 ++++++--- .github/workflows/test.yml | 4 + .pre-commit-config.yaml | 14 +- CHANGES.rst | 24 +++ Makefile | 30 ++-- README.md | 96 ++++++++++++ README.rst | 103 ------------ RELEASING.md | 6 +- Tests/check_imaging_leaks.py | 3 +- Tests/check_j2k_leaks.py | 5 +- Tests/check_j2k_overflow.py | 1 + Tests/check_jpeg_leaks.py | 148 +++++++++--------- Tests/check_large_memory.py | 1 + Tests/check_large_memory_numpy.py | 1 + Tests/check_libtiff_segfault.py | 7 +- Tests/helper.py | 3 +- Tests/images/exif_text.png | Bin 0 -> 178480 bytes Tests/test_bmp_reference.py | 13 +- Tests/test_box_blur.py | 1 + Tests/test_color_lut.py | 1 + Tests/test_core_resources.py | 1 + Tests/test_decompression_bomb.py | 1 + Tests/test_features.py | 1 + Tests/test_file_apng.py | 6 +- Tests/test_file_bmp.py | 1 + Tests/test_file_bufrstub.py | 1 + Tests/test_file_cur.py | 1 + Tests/test_file_dcx.py | 1 + Tests/test_file_dds.py | 1 + Tests/test_file_eps.py | 1 + Tests/test_file_fitsstub.py | 1 + Tests/test_file_fli.py | 1 + Tests/test_file_fpx.py | 1 + Tests/test_file_gbr.py | 1 + Tests/test_file_gd.py | 1 + Tests/test_file_gif.py | 1 + Tests/test_file_gimppalette.py | 1 + Tests/test_file_gribstub.py | 1 + Tests/test_file_hdf5stub.py | 1 + Tests/test_file_icns.py | 1 + Tests/test_file_ico.py | 1 + Tests/test_file_im.py | 1 + Tests/test_file_jpeg.py | 16 +- Tests/test_file_jpeg2k.py | 1 + Tests/test_file_libtiff.py | 16 +- Tests/test_file_libtiff_small.py | 12 +- Tests/test_file_mcidas.py | 1 + Tests/test_file_mic.py | 1 + Tests/test_file_mpo.py | 1 + Tests/test_file_msp.py | 1 + Tests/test_file_palm.py | 1 + Tests/test_file_pcx.py | 1 + Tests/test_file_pdf.py | 1 + Tests/test_file_pixar.py | 1 + Tests/test_file_png.py | 6 + Tests/test_file_ppm.py | 1 + Tests/test_file_psd.py | 1 + Tests/test_file_sgi.py | 1 + Tests/test_file_spider.py | 1 + Tests/test_file_sun.py | 1 + Tests/test_file_tar.py | 1 + Tests/test_file_tga.py | 1 + Tests/test_file_tiff.py | 8 +- Tests/test_file_tiff_metadata.py | 9 +- Tests/test_file_webp.py | 1 + Tests/test_file_webp_alpha.py | 1 + Tests/test_file_webp_animated.py | 1 + Tests/test_file_webp_lossless.py | 1 + Tests/test_file_wmf.py | 1 + Tests/test_file_xbm.py | 1 + Tests/test_file_xpm.py | 1 + Tests/test_file_xvthumb.py | 1 + Tests/test_font_bdf.py | 1 + Tests/test_font_pcf.py | 1 + Tests/test_format_hsv.py | 25 ++- Tests/test_image.py | 8 +- Tests/test_image_access.py | 18 ++- Tests/test_image_array.py | 1 + Tests/test_image_convert.py | 1 + Tests/test_image_crop.py | 1 + Tests/test_image_filter.py | 1 + Tests/test_image_fromqimage.py | 1 + Tests/test_image_load.py | 10 ++ Tests/test_image_point.py | 6 +- Tests/test_image_putpalette.py | 1 + Tests/test_image_quantize.py | 1 + Tests/test_image_reduce.py | 1 + Tests/test_image_resample.py | 11 +- Tests/test_image_resize.py | 1 + Tests/test_image_thumbnail.py | 1 + Tests/test_image_transform.py | 1 + Tests/test_imagecms.py | 3 +- Tests/test_imagecolor.py | 1 + Tests/test_imagedraw.py | 6 +- Tests/test_imageenhance.py | 4 +- Tests/test_imagefile.py | 6 + Tests/test_imagefont.py | 1 + Tests/test_imagefont_bitmap.py | 6 +- Tests/test_imagefontctl.py | 1 + Tests/test_imagegrab.py | 1 + Tests/test_imagemorph.py | 1 + Tests/test_imageops.py | 53 ++++++- Tests/test_imageops_usm.py | 1 + Tests/test_imagepalette.py | 1 + Tests/test_imagepath.py | 1 + Tests/test_imageqt.py | 1 + Tests/test_imagesequence.py | 1 + Tests/test_imageshow.py | 7 +- Tests/test_imagestat.py | 1 + Tests/test_imagetk.py | 5 +- Tests/test_imagewin.py | 1 + Tests/test_lib_image.py | 1 + Tests/test_lib_pack.py | 1 + Tests/test_locale.py | 1 + Tests/test_map.py | 1 + Tests/test_numpy.py | 1 + Tests/test_pdfparser.py | 1 + Tests/test_pickle.py | 1 + Tests/test_pyroma.py | 1 + Tests/test_qt_image_toqimage.py | 5 +- Tests/test_sgi_crash.py | 1 + Tests/test_shell_injection.py | 1 + Tests/test_tiff_ifdrational.py | 6 + Tests/test_util.py | 1 + docs/conf.py | 5 +- docs/deprecations.rst | 35 +++-- docs/handbook/image-file-formats.rst | 6 + .../writing-your-own-file-decoder.rst | 22 ++- docs/installation.rst | 4 +- docs/reference/ImageChops.rst | 7 +- docs/reference/ImageFile.rst | 7 + docs/reference/ImageFont.rst | 16 ++ docs/reference/ImagePath.rst | 4 +- docs/reference/block_allocator.rst | 2 +- docs/releasenotes/8.0.0.rst | 81 ++++++++++ docs/releasenotes/index.rst | 1 + docs/releasenotes/template.rst | 45 ++++++ setup.cfg | 7 +- setup.py | 103 ++++++------ src/PIL/BmpImagePlugin.py | 7 +- src/PIL/CurImagePlugin.py | 4 +- src/PIL/FliImagePlugin.py | 5 +- src/PIL/FpxImagePlugin.py | 3 +- src/PIL/GdImageFile.py | 4 +- src/PIL/GifImagePlugin.py | 5 +- src/PIL/IcoImagePlugin.py | 4 +- src/PIL/Image.py | 39 ++--- src/PIL/ImageChops.py | 4 +- src/PIL/ImageCms.py | 10 +- src/PIL/ImageDraw.py | 1 - src/PIL/ImageFile.py | 12 +- src/PIL/ImageFilter.py | 2 +- src/PIL/ImageFont.py | 2 +- src/PIL/ImageGrab.py | 2 +- src/PIL/ImageMorph.py | 42 ++--- src/PIL/ImageOps.py | 21 +-- src/PIL/ImageQt.py | 4 +- src/PIL/ImageTk.py | 4 +- src/PIL/ImageWin.py | 2 +- src/PIL/IptcImagePlugin.py | 8 +- src/PIL/JpegImagePlugin.py | 5 +- src/PIL/JpegPresets.py | 4 +- src/PIL/MspImagePlugin.py | 4 +- src/PIL/PSDraw.py | 2 +- src/PIL/PalmImagePlugin.py | 3 +- src/PIL/PcfFontFile.py | 6 +- src/PIL/PcxImagePlugin.py | 5 +- src/PIL/PdfImagePlugin.py | 3 + src/PIL/PngImagePlugin.py | 23 ++- src/PIL/PsdImagePlugin.py | 4 +- src/PIL/SgiImagePlugin.py | 4 +- src/PIL/TgaImagePlugin.py | 5 +- src/PIL/TiffImagePlugin.py | 20 ++- src/PIL/WmfImagePlugin.py | 5 +- src/PIL/features.py | 6 +- src/decode.c | 2 +- src/encode.c | 2 +- src/libImaging/{Zip.h => ZipCodecs.h} | 0 src/libImaging/ZipDecode.c | 2 +- src/libImaging/ZipEncode.c | 2 +- winbuild/appveyor_build_msys2.sh | 3 - winbuild/appveyor_install_msys2_deps.sh | 14 -- winbuild/build.rst | 3 + winbuild/build_prepare.py | 35 +++-- 186 files changed, 1047 insertions(+), 523 deletions(-) create mode 100644 README.md delete mode 100644 README.rst create mode 100644 Tests/images/exif_text.png create mode 100644 docs/releasenotes/8.0.0.rst create mode 100644 docs/releasenotes/template.rst rename src/libImaging/{Zip.h => ZipCodecs.h} (100%) delete mode 100644 winbuild/appveyor_build_msys2.sh delete mode 100644 winbuild/appveyor_install_msys2_deps.sh diff --git a/.ci/install.sh b/.ci/install.sh index 36bce295c..2143c0613 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -30,7 +30,10 @@ pip install -U pytest-cov pip install pyroma pip install test-image-results pip install numpy -if [ "$TRAVIS_PYTHON_VERSION" == "3.9-dev" ]; then pip install setuptools==47.3.1 ; fi + +# TODO Remove when 3.9-dev includes setuptools 49.3.2+: +if [ "$GHA_PYTHON_VERSION" == "3.9-dev" ]; then pip install -U "setuptools>=49.3.2" ; fi + if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then # arm64, ppc64le, s390x CPUs: # "ERROR: Could not find a version that satisfies the requirement pyqt5" diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index 76a3ef2b7..a0418c3ba 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -15,5 +15,8 @@ pip install test-image-results echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg pip install numpy +# TODO Remove when 3.9-dev includes setuptools 49.3.2+: +if [ "$GHA_PYTHON_VERSION" == "3.9-dev" ]; then pip install -U "setuptools>=49.3.2" ; fi + # extra test images pushd depends && ./install_extra_test_images.sh && popd diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 9b04b2bd7..a90a0a954 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -63,7 +63,12 @@ jobs: - name: pip install wheel pytest pytest-cov run: python -m pip install wheel pytest pytest-cov - - name: Prepare dependencies + # TODO Remove when 3.9-dev includes setuptools 49.3.2+: + - name: Upgrade setuptools + if: "contains(matrix.python-version, '3.9-dev')" + run: python -m pip install -U "setuptools>=49.3.2" + + - name: Install dependencies run: | 7z x winbuild\depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\" Write-Host "::add-path::$env:RUNNER_WORKSPACE\nasm-2.14.02" @@ -72,41 +77,71 @@ jobs: Write-Host "::add-path::C:\Program Files (x86)\gs\gs9.50\bin" xcopy /s winbuild\depends\test_images\* Tests\images\ + shell: pwsh - & python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation + - name: Cache build + id: build-cache + uses: actions/cache@v2 + with: + path: winbuild\build + key: + ${{ hashFiles('winbuild\build_prepare.py') }}-${{ hashFiles('.github\workflows\test-windows.yml') }}-${{ env.pythonLocation }} + + - name: Prepare build + if: steps.build-cache.outputs.cache-hit != 'true' + run: | + & python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation --srcdir shell: pwsh - name: Build dependencies / libjpeg-turbo + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libjpeg.cmd" - name: Build dependencies / zlib + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_zlib.cmd" - name: Build dependencies / LibTiff + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libtiff.cmd" - name: Build dependencies / WebP + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libwebp.cmd" - name: Build dependencies / FreeType + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_freetype.cmd" - name: Build dependencies / LCMS2 + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_lcms2.cmd" - name: Build dependencies / OpenJPEG + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_openjpeg.cmd" - # GPL licensed; skip if building wheels + # GPL licensed - name: Build dependencies / libimagequant - if: "github.event_name != 'push'" + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libimagequant.cmd" # Raqm dependencies - name: Build dependencies / HarfBuzz + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_harfbuzz.cmd" - name: Build dependencies / FriBidi + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_fribidi.cmd" - name: Build dependencies / Raqm + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libraqm.cmd" + # trim ~150MB x 9 + - name: Optimize build cache + if: steps.build-cache.outputs.cache-hit != 'true' + run: rmdir /S /Q winbuild\build\src + shell: cmd + - name: Build Pillow run: | - & winbuild\build\build_pillow.cmd install + $FLAGS="" + if ('${{ github.event_name }}' -eq 'push') { $FLAGS="--disable-imagequant" } + & winbuild\build\build_pillow.cmd $FLAGS install & $env:pythonLocation\python.exe selftest.py --installed shell: pwsh @@ -151,7 +186,7 @@ jobs: if: "github.event_name == 'push'" run: | for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ::set-output name=dist::dist-%%a - winbuild\\build\\build_pillow.cmd bdist_wheel" + winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel shell: cmd - uses: actions/upload-artifact@v2 @@ -169,8 +204,10 @@ jobs: mingw: ["MINGW32", "MINGW64"] include: - mingw: "MINGW32" + name: "MSYS2 MinGW 32-bit" package: "mingw-w64-i686" - mingw: "MINGW64" + name: "MSYS2 MinGW 64-bit" package: "mingw-w64-x86_64" defaults: @@ -181,7 +218,7 @@ jobs: CHERE_INVOKING: 1 timeout-minutes: 30 - name: MSYS2 ${{ matrix.mingw }} + name: ${{ matrix.name }} steps: - uses: actions/checkout@v2 @@ -193,23 +230,22 @@ jobs: - name: Install Dependencies run: | pacman -S --noconfirm \ + ${{ matrix.package }}-python3-cffi \ + ${{ matrix.package }}-python3-numpy \ + ${{ matrix.package }}-python3-olefile \ ${{ matrix.package }}-python3-pip \ - ${{ matrix.package }}-python3-setuptools \ + ${{ matrix.package }}-python3-pyqt5 \ ${{ matrix.package }}-python3-pytest \ ${{ matrix.package }}-python3-pytest-cov \ - ${{ matrix.package }}-python3-cffi \ - ${{ matrix.package }}-python3-olefile \ - ${{ matrix.package }}-python3-numpy \ - ${{ matrix.package }}-python3-pyqt5 \ - ${{ matrix.package }}-python3-numpy \ + ${{ matrix.package }}-python3-setuptools \ ${{ matrix.package }}-freetype \ - ${{ matrix.package }}-lcms2 \ - ${{ matrix.package }}-libwebp \ - ${{ matrix.package }}-libjpeg-turbo \ - ${{ matrix.package }}-openjpeg2 \ - ${{ matrix.package }}-libimagequant \ - ${{ matrix.package }}-libraqm \ ${{ matrix.package }}-ghostscript \ + ${{ matrix.package }}-lcms2 \ + ${{ matrix.package }}-libimagequant \ + ${{ matrix.package }}-libjpeg-turbo \ + ${{ matrix.package }}-libraqm \ + ${{ matrix.package }}-libwebp \ + ${{ matrix.package }}-openjpeg2 \ subversion python3 -m pip install pyroma @@ -231,4 +267,4 @@ jobs: python3 -m pip install codecov bash <(curl -s https://codecov.io/bash) -F GHA_Windows env: - CODECOV_NAME: MSYS2 ${{ matrix.mingw }} + CODECOV_NAME: ${{ matrix.name }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 65673b0a2..dbe0eb822 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,11 +62,15 @@ jobs: if: startsWith(matrix.os, 'ubuntu') run: | .ci/install.sh + env: + GHA_PYTHON_VERSION: ${{ matrix.python-version }} - name: Install macOS dependencies if: startsWith(matrix.os, 'macOS') run: | .github/workflows/macos-install.sh + env: + GHA_PYTHON_VERSION: ${{ matrix.python-version }} - name: Build run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 80a068df5..459251d77 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: 6bedb5c58a7d8c25aa9509f8217bc24e9797e90d # frozen: 19.10b0 + rev: e66be67b9b6811913470f70c28b4d50f94d05b22 # frozen: 20.8b1 hooks: - id: black args: ["--target-version", "py36"] @@ -9,35 +9,35 @@ repos: types: [] - repo: https://github.com/timothycrosley/isort - rev: 7c29dd9d55161704cfc45998c6f5c2c43d39264b # frozen: 4.3.21 + rev: 377d260ffa6f746693f97b46d95025afc4bd8275 # frozen: 5.4.2 hooks: - id: isort - repo: https://github.com/asottile/yesqa - rev: b13a51aa54142c59219c764e9f9362c049b439ed # frozen: v1.2.0 + rev: 7a009f3ee493c796827ee334f9058b110a0e0db8 # frozen: v1.2.1 hooks: - id: yesqa - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: ffbd448645bad2e7ca13f96fca5830058d27ccd5 # frozen: v1.1.7 + rev: f30f4974a08a6b2f6a1eeaf30a4d501cf909163a # frozen: v1.1.9 hooks: - id: remove-tabs exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$) - repo: https://gitlab.com/pycqa/flake8 - rev: 735cfe7e1c57a8e05f660ba75de72313005af54a # frozen: 3.8.2 + rev: 05f6544aef321e2fee03a1277ce2eef8880fb927 # frozen: 3.8.3 hooks: - id: flake8 additional_dependencies: [flake8-2020, flake8-implicit-str-concat] - repo: https://github.com/pre-commit/pygrep-hooks - rev: 0d7d077d6ed5624854f93ac601739c1804ebeb98 # frozen: v1.5.1 + rev: eae6397e4c259ed3d057511f6dd5330b92867e62 # frozen: v1.6.0 hooks: - id: python-check-blanket-noqa - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: ebc15addedad713c86ef18ae9632c88e187dd0af # frozen: v3.1.0 + rev: e1668fe86af3810fbca72b8653fe478e66a0afdc # frozen: v3.2.0 hooks: - id: check-merge-conflict - id: check-yaml diff --git a/CHANGES.rst b/CHANGES.rst index 6083cc7f3..263c2a823 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,9 +5,33 @@ Changelog (Pillow) 8.0.0 (unreleased) ------------------ +- Fix IFDRational __eq__ bug #4888 + [luphord, radarhere] + +- Fixed duplicate variable name #4885 + [liZe, radarhere] + +- Added homebrew zlib include directory #4842 + [radarhere] + +- Corrected inverted PDF CMYK colors #4866 + [radarhere] + +- Do not try to close file pointer if file pointer is empty #4823 + [radarhere] + +- ImageOps.autocontrast: add mask parameter #4843 + [navneeth, hugovk] + +- Read EXIF data tEXt chunk into info as bytes instead of string #4828 + [radarhere] + - Remove long-deprecated Image.py functions #4798 [hugovk, nulano, radarhere] +- Replaced most uses of distutils with setuptools #4797, #4809, #4814, #4817, #4829 + [hugovk, radarhere] + - Add MIME type to PsdImagePlugin #4788 [samamorgan] diff --git a/Makefile b/Makefile index 06208ad98..cbee5923b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ -# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: clean coverage doc docserve help inplace install install-req release-test sdist test upload upload-test .DEFAULT_GOAL := release-test +.PHONY: clean clean: python3 setup.py clean rm src/PIL/*.so || true @@ -9,28 +8,34 @@ clean: find . -name __pycache__ | xargs rm -r || true BRANCHES=`git branch -a | grep -v HEAD | grep -v master | grep remote` +.PHONY: co co: -for i in $(BRANCHES) ; do \ git checkout -t $$i ; \ done +.PHONY: coverage coverage: pytest -qq rm -r htmlcov || true coverage report +.PHONY: doc doc: $(MAKE) -C docs html +.PHONY: doccheck doccheck: $(MAKE) -C docs html # Don't make our tests rely on the links in the docs being up every single build. # We don't control them. But do check, and update them to the target of their redirects. $(MAKE) -C docs linkcheck || true +.PHONY: docserve docserve: cd docs/_build/html && python3 -mSimpleHTTPServer 2> /dev/null& +.PHONY: help help: @echo "Welcome to Pillow development. Please use \`make \` where is one of" @echo " clean remove build products" @@ -48,17 +53,21 @@ help: @echo " upload build and upload sdists to PyPI" @echo " upload-test build and upload sdists to test.pythonpackages.com" +.PHONY: inplace inplace: clean python3 setup.py develop build_ext --inplace +.PHONY: install install: python3 setup.py install python3 selftest.py +.PHONY: install-coverage install-coverage: CFLAGS="-coverage" python3 setup.py build_ext install python3 selftest.py +.PHONY: debug debug: # make a debug version if we don't have a -dbg python. Leaves in symbols # for our stuff, kills optimization, and redirects to dev null so we @@ -66,13 +75,16 @@ debug: make clean > /dev/null CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null +.PHONY: install-req install-req: python3 -m pip install -r requirements.txt +.PHONY: install-venv install-venv: virtualenv . bin/pip install -r requirements.txt +.PHONY: release-test release-test: $(MAKE) install-req python3 setup.py develop @@ -84,22 +96,14 @@ release-test: pyroma . viewdoc +.PHONY: sdist sdist: python3 setup.py sdist --format=gztar +.PHONY: test test: pytest -qq -# https://docs.python.org/3/distutils/packageindex.html#the-pypirc-file -upload-test: -# [test] -# username: -# password: -# repository = http://test.pythonpackages.com - python3 setup.py sdist --format=gztar upload -r test - -upload: - python3 setup.py sdist --format=gztar upload - +.PHONY: readme readme: viewdoc diff --git a/README.md b/README.md new file mode 100644 index 000000000..6ca6cbf83 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +

+ Pillow logo +

+ +# Pillow + +## Python Imaging Library (Fork) + +Pillow is the friendly PIL fork by [Alex Clark and +Contributors](https://github.com/python-pillow/Pillow/graphs/contributors). +PIL is the Python Imaging Library by Fredrik Lundh and Contributors. +As of 2019, Pillow development is +[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise). + + + + + + + + + + + + + + + + + + +
docs + Documentation Status +
tests + Travis CI build status (Linux) + Travis CI build status (macOS) + AppVeyor CI build status (Windows) + GitHub Actions build status (Lint) + GitHub Actions build status (Test Linux and macOS) + GitHub Actions build status (Test Windows) + GitHub Actions build status (Test Docker) + Code coverage +
package + Zenodo + Tidelift + Newest PyPI version + Number of PyPI downloads +
social + Join the chat at https://gitter.im/python-pillow/Pillow + Follow on https://twitter.com/PythonPillow +
+ +## More Information + +- [Documentation](https://pillow.readthedocs.io/) + - [Installation](https://pillow.readthedocs.io/en/latest/installation.html) + - [Handbook](https://pillow.readthedocs.io/en/latest/handbook/index.html) +- [Contribute](https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md) + - [Issues](https://github.com/python-pillow/Pillow/issues) + - [Pull requests](https://github.com/python-pillow/Pillow/pulls) +- [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) + - [Pre-fork](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork) + +## Report a Vulnerability + +To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security). diff --git a/README.rst b/README.rst deleted file mode 100644 index c1d5be579..000000000 --- a/README.rst +++ /dev/null @@ -1,103 +0,0 @@ -Pillow -====== - -Python Imaging Library (Fork) ------------------------------ - -Pillow is the friendly PIL fork by `Alex Clark and Contributors `_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. As of 2019, Pillow development is `supported by Tidelift `_. - -.. start-badges - -.. list-table:: - :stub-columns: 1 - - * - docs - - |docs| - * - tests - - |linux| |macos| |windows| |gha_lint| |gha| |gha_windows| |gha_docker| |coverage| - * - package - - |zenodo| |tidelift| |version| |downloads| - * - social - - |gitter| |twitter| - -.. end-badges - -More Information ----------------- - -- `Documentation `_ - - - `Installation `_ - - `Handbook `_ - -- `Contribute `_ - - - `Issues `_ - - `Pull requests `_ - -- `Changelog `_ - - - `Pre-fork `_ - -Report a Vulnerability ----------------------- - -To report a security vulnerability, please follow the procedure described in the `Tidelift security policy `_. - -.. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest - :target: https://pillow.readthedocs.io/?badge=latest - :alt: Documentation Status - -.. |linux| image:: https://img.shields.io/travis/python-pillow/Pillow/master.svg?label=Linux%20build - :target: https://travis-ci.org/python-pillow/Pillow - :alt: Travis CI build status (Linux) - -.. |macos| image:: https://img.shields.io/travis/python-pillow/pillow-wheels/master.svg?label=macOS%20build - :target: https://travis-ci.org/python-pillow/pillow-wheels - :alt: Travis CI build status (macOS) - -.. |windows| image:: https://img.shields.io/appveyor/build/python-pillow/Pillow/master.svg?label=Windows%20build - :target: https://ci.appveyor.com/project/python-pillow/Pillow - :alt: AppVeyor CI build status (Windows) - -.. |gha_lint| image:: https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg - :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint - :alt: GitHub Actions build status (Lint) - -.. |gha_docker| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg - :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Docker%22 - :alt: GitHub Actions build status (Test Docker) - -.. |gha| image:: https://github.com/python-pillow/Pillow/workflows/Test/badge.svg - :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ATest - :alt: GitHub Actions build status (Test Linux and macOS) - -.. |gha_windows| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg - :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Windows%22 - :alt: GitHub Actions build status (Test Windows) - -.. |coverage| image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg - :target: https://codecov.io/gh/python-pillow/Pillow - :alt: Code coverage - -.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg - :target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow - -.. |tidelift| image:: https://tidelift.com/badges/package/pypi/Pillow?style=flat - :target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge - -.. |version| image:: https://img.shields.io/pypi/v/pillow.svg - :target: https://pypi.org/project/Pillow/ - :alt: Latest PyPI version - -.. |downloads| image:: https://img.shields.io/pypi/dm/pillow.svg - :target: https://pypi.org/project/Pillow/ - :alt: Number of PyPI downloads - -.. |gitter| image:: https://badges.gitter.im/python-pillow/Pillow.svg - :target: https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge - :alt: Join the chat at https://gitter.im/python-pillow/Pillow - -.. |twitter| image:: https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg - :target: https://twitter.com/PythonPillow - :alt: Follow on https://twitter.com/PythonPillow diff --git a/RELEASING.md b/RELEASING.md index 3f62a70c4..c9a0439d8 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -101,11 +101,7 @@ Released as needed privately to individual vendors for critical security-related cd pillow-wheels ./update-pillow-tag.sh [[release tag]] ``` -* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/). - ```bash - wget -m -A 'Pillow--*' \ - http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com - ``` +* [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases). ## Publicize Release diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index ae05ebade..407f3ea80 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -1,5 +1,6 @@ #!/usr/bin/env python import pytest + from PIL import Image from .helper import is_win32 @@ -11,7 +12,7 @@ pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") def _get_mem_usage(): - from resource import getpagesize, getrusage, RUSAGE_SELF + from resource import RUSAGE_SELF, getpagesize, getrusage mem = getrusage(RUSAGE_SELF).ru_maxrss return mem * getpagesize() / 1024 / 1024 diff --git a/Tests/check_j2k_leaks.py b/Tests/check_j2k_leaks.py index 5cef4b544..afe5836f3 100755 --- a/Tests/check_j2k_leaks.py +++ b/Tests/check_j2k_leaks.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import Image from .helper import is_win32, skip_unless_feature @@ -18,7 +19,7 @@ pytestmark = [ def test_leak_load(): - from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK + from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit setrlimit(RLIMIT_STACK, (stack_size, stack_size)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) @@ -28,7 +29,7 @@ def test_leak_load(): def test_leak_save(): - from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK + from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit setrlimit(RLIMIT_STACK, (stack_size, stack_size)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) diff --git a/Tests/check_j2k_overflow.py b/Tests/check_j2k_overflow.py index 7a0a5f948..b16412898 100644 --- a/Tests/check_j2k_overflow.py +++ b/Tests/check_j2k_overflow.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py index b63fa2a1e..ab8d77719 100644 --- a/Tests/check_jpeg_leaks.py +++ b/Tests/check_jpeg_leaks.py @@ -119,60 +119,59 @@ def test_qtables_leak(): def test_exif_leak(): """ -pre patch: + pre patch: - MB -177.1^ # - | @@@# - | :@@@@@@# - | ::::@@@@@@# - | ::::::::@@@@@@# - | @@::::: ::::@@@@@@# - | @@@@ ::::: ::::@@@@@@# - | @@@@@@@ ::::: ::::@@@@@@# - | @@::@@@@@@@ ::::: ::::@@@@@@# - | @@@@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - 0 +----------------------------------------------------------------------->Gi - 0 11.37 + MB + 177.1^ # + | @@@# + | :@@@@@@# + | ::::@@@@@@# + | ::::::::@@@@@@# + | @@::::: ::::@@@@@@# + | @@@@ ::::: ::::@@@@@@# + | @@@@@@@ ::::: ::::@@@@@@# + | @@::@@@@@@@ ::::: ::::@@@@@@# + | @@@@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + 0 +----------------------------------------------------------------------->Gi + 0 11.37 -post patch: + post patch: - MB -21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - 0 +----------------------------------------------------------------------->Gi - 0 11.33 - -""" + MB + 21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + 0 +----------------------------------------------------------------------->Gi + 0 11.33 + """ im = hopper("RGB") exif = b"12345678" * 4096 @@ -183,31 +182,30 @@ post patch: def test_base_save(): """ -base case: - MB -20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@::: - | ##: : ::::::@::::::: :::: :::: : : : : : : :::::::::::: :::@::: - | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@# : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@@ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@@@@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - 0 +----------------------------------------------------------------------->Gi - 0 7.882 -""" + base case: + MB + 20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@::: + | ##: : ::::::@::::::: :::: :::: : : : : : : :::::::::::: :::@::: + | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@# : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@@ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@@@@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + 0 +----------------------------------------------------------------------->Gi + 0 7.882""" im = hopper("RGB") for _ in range(iterations): diff --git a/Tests/check_large_memory.py b/Tests/check_large_memory.py index f44a5a5bb..723a1a21e 100644 --- a/Tests/check_large_memory.py +++ b/Tests/check_large_memory.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image # This test is not run automatically. diff --git a/Tests/check_large_memory_numpy.py b/Tests/check_large_memory_numpy.py index de6f4571c..79d1cfd5b 100644 --- a/Tests/check_large_memory_numpy.py +++ b/Tests/check_large_memory_numpy.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image # This test is not run automatically. diff --git a/Tests/check_libtiff_segfault.py b/Tests/check_libtiff_segfault.py index 6663ac097..bd7f407e4 100644 --- a/Tests/check_libtiff_segfault.py +++ b/Tests/check_libtiff_segfault.py @@ -1,13 +1,14 @@ import pytest + from PIL import Image TEST_FILE = "Tests/images/libtiff_segfault.tif" def test_libtiff_segfault(): - """ This test should not segfault. It will on Pillow <= 3.1.0 and - libtiff >= 4.0.0 - """ + """This test should not segfault. It will on Pillow <= 3.1.0 and + libtiff >= 4.0.0 + """ with pytest.raises(OSError): with Image.open(TEST_FILE) as im: diff --git a/Tests/helper.py b/Tests/helper.py index 4c5f2198e..c8cbb80e1 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -11,6 +11,7 @@ import tempfile from io import BytesIO import pytest + from PIL import Image, ImageMath, features logger = logging.getLogger(__name__) @@ -175,7 +176,7 @@ class PillowLeakTestCase: :returns: memory usage in kilobytes """ - from resource import getrusage, RUSAGE_SELF + from resource import RUSAGE_SELF, getrusage mem = getrusage(RUSAGE_SELF).ru_maxrss if sys.platform == "darwin": diff --git a/Tests/images/exif_text.png b/Tests/images/exif_text.png new file mode 100644 index 0000000000000000000000000000000000000000..e2d8dc0fffa067b8952477e7402945b3fa3218a4 GIT binary patch literal 178480 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 z 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 L>0K*B+lK!i`J 0.9 @pytest.mark.parametrize( - "test_image_path", [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"], + "test_image_path", + [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"], ) def test_dpi(self, test_image_path): def test(xdpi, ydpi=None): @@ -241,6 +243,16 @@ class TestFileJpeg: # Assert assert exif[gps_index] == expected_exif_gps + def test_exif_equality(self): + # In 7.2.0, Exif rationals were changed to be read as + # TiffImagePlugin.IFDRational. This class had a bug in __eq__, + # breaking the self-equality of Exif data + exifs = [] + for i in range(2): + with Image.open("Tests/images/exif-200dpcm.jpg") as im: + exifs.append(im._getexif()) + assert exifs[0] == exifs[1] + def test_exif_rollback(self): # rolling back exif support in 3.1 to pre-3.0 formatting. # expected from 2.9, with b/u qualifiers switched for 3.2 compatibility diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index d558843b8..c9e37f8b0 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -2,6 +2,7 @@ import re from io import BytesIO import pytest + from PIL import Image, ImageFile, Jpeg2KImagePlugin, features from .helper import ( diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 19a06a15d..da955b3de 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,13 +1,13 @@ import base64 import io import itertools -import logging import os import re from collections import namedtuple from ctypes import c_float import pytest + from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features from .helper import ( @@ -19,8 +19,6 @@ from .helper import ( skip_unless_feature, ) -logger = logging.getLogger(__name__) - @skip_unless_feature("libtiff") class LibTiffTestCase: @@ -404,8 +402,8 @@ class TestFileLibTiff(LibTiffTestCase): assert "temp.tif" == reread.tag[269][0] def test_12bit_rawmode(self): - """ Are we generating the same interpretation - of the image as Imagemagick is? """ + """Are we generating the same interpretation + of the image as Imagemagick is?""" TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/12bit.cropped.tif") as im: im.load() @@ -505,9 +503,9 @@ class TestFileLibTiff(LibTiffTestCase): assert len(reloaded.tag_v2[320]) == 768 def xtest_bw_compression_w_rgb(self, tmp_path): - """ This test passes, but when running all tests causes a failure due - to output on stderr from the error thrown by libtiff. We need to - capture that but not now""" + """This test passes, but when running all tests causes a failure due + to output on stderr from the error thrown by libtiff. We need to + capture that but not now""" im = hopper("RGB") out = str(tmp_path / "temp.tif") @@ -770,7 +768,7 @@ class TestFileLibTiff(LibTiffTestCase): assert im.mode == "RGBA" assert im.size == (100, 40) assert im.tile, [ - ("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236),) + ("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236)) ] im.load() diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index 593a8eda8..03137c8b6 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -7,13 +7,13 @@ from .test_file_libtiff import LibTiffTestCase class TestFileLibTiffSmall(LibTiffTestCase): - """ The small lena image was failing on open in the libtiff - decoder because the file pointer was set to the wrong place - by a spurious seek. It wasn't failing with the byteio method. + """The small lena image was failing on open in the libtiff + decoder because the file pointer was set to the wrong place + by a spurious seek. It wasn't failing with the byteio method. - It was fixed by forcing an lseek to the beginning of the - file just before reading in libtiff. These tests remain - to ensure that it stays fixed. """ + It was fixed by forcing an lseek to the beginning of the + file just before reading in libtiff. These tests remain + to ensure that it stays fixed.""" def test_g4_hopper_file(self, tmp_path): """Testing the open file load path""" diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py index 516dbb208..88c8f8f4f 100644 --- a/Tests/test_file_mcidas.py +++ b/Tests/test_file_mcidas.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, McIdasImagePlugin from .helper import assert_image_equal diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index 5003090c7..464d138e2 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImagePalette from .helper import assert_image_similar, hopper, skip_unless_feature diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 893f9075d..791efcc3f 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import Image from .helper import assert_image_similar, is_pypy, skip_unless_feature diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 9b508a4e4..293b856b0 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -1,6 +1,7 @@ import os import pytest + from PIL import Image, MspImagePlugin from .helper import assert_image_equal, hopper diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index e7afeef23..25d194b62 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -2,6 +2,7 @@ import os.path import subprocess import pytest + from PIL import Image from .helper import IMCONVERT, assert_image_equal, hopper, imagemagick_available diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 5af7469c7..670c03b95 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageFile, PcxImagePlugin from .helper import assert_image_equal, hopper diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 14a7a654f..3e23beae7 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -5,6 +5,7 @@ import tempfile import time import pytest + from PIL import Image, PdfParser from .helper import hopper diff --git a/Tests/test_file_pixar.py b/Tests/test_file_pixar.py index 5e83c6104..315ea4676 100644 --- a/Tests/test_file_pixar.py +++ b/Tests/test_file_pixar.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, PixarImagePlugin from .helper import assert_image_similar, hopper diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 9891b6399..f580f4ae7 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -3,6 +3,7 @@ import zlib from io import BytesIO import pytest + from PIL import Image, ImageFile, PngImagePlugin, features from .helper import ( @@ -606,6 +607,11 @@ class TestFilePng: exif = im.copy().getexif() assert exif[274] == 1 + # With a tEXt chunk + with Image.open("Tests/images/exif_text.png") as im: + exif = im._getexif() + assert exif[274] == 1 + # With XMP tags with Image.open("Tests/images/xmp_tags_orientation.png") as im: exif = im.getexif() diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 15c08e438..e7c3fb06f 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 6b26fe442..8bb45630e 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, PsdImagePlugin from .helper import assert_image_similar, hopper, is_pypy diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index cb16276ce..a197fa775 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, SgiImagePlugin from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 8c69491e6..9cdb451c9 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -2,6 +2,7 @@ import tempfile from io import BytesIO import pytest + from PIL import Image, ImageSequence, SpiderImagePlugin from .helper import assert_image_equal, hopper, is_pypy diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index b2bfb5b9e..8421106a2 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -1,6 +1,7 @@ import os import pytest + from PIL import Image, SunImagePlugin from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 3fe0cd04e..02001e5b1 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, TarIO, features from .helper import is_pypy diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index bac1b4dd6..465e13316 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -3,6 +3,7 @@ from glob import glob from itertools import product import pytest + from PIL import Image from .helper import assert_image_equal, hopper diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 7aa55dad0..594115042 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -1,8 +1,8 @@ -import logging import os from io import BytesIO import pytest + from PIL import Image, TiffImagePlugin from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION @@ -16,8 +16,6 @@ from .helper import ( is_win32, ) -logger = logging.getLogger(__name__) - class TestFileTiff: def test_sanity(self, tmp_path): @@ -227,8 +225,8 @@ class TestFileTiff: assert im.getpixel((0, 1)) == 0 def test_12bit_rawmode(self): - """ Are we generating the same interpretation - of the image as Imagemagick is? """ + """Are we generating the same interpretation + of the image as Imagemagick is?""" with Image.open("Tests/images/12bit.cropped.tif") as im: # to make the target -- diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 179d1adf3..0f7f8adf1 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -2,6 +2,7 @@ import io import struct import pytest + from PIL import Image, TiffImagePlugin, TiffTags from PIL.TiffImagePlugin import IFDRational @@ -11,10 +12,10 @@ TAG_IDS = {info.name: info.value for info in TiffTags.TAGS_V2.values()} def test_rt_metadata(tmp_path): - """ Test writing arbitrary metadata into the tiff image directory - Use case is ImageJ private tags, one numeric, one arbitrary - data. https://github.com/python-pillow/Pillow/issues/291 - """ + """Test writing arbitrary metadata into the tiff image directory + Use case is ImageJ private tags, one numeric, one arbitrary + data. https://github.com/python-pillow/Pillow/issues/291 + """ img = hopper() diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 25a4bb8da..11fbd9fd5 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -2,6 +2,7 @@ import io import re import pytest + from PIL import Image, WebPImagePlugin, features from .helper import ( diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index c624156df..362edac1a 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index cd272f154..26e903488 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import ( diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index 4d06f53b1..2da443628 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image_equal, hopper diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 3339cbfd3..d18225680 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, WmfImagePlugin from .helper import assert_image_similar, hopper diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index 23a540569..487920a92 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import Image from .helper import hopper diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index 187440d4e..8595b07eb 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, XpmImagePlugin from .helper import assert_image_similar, hopper diff --git a/Tests/test_file_xvthumb.py b/Tests/test_file_xvthumb.py index 7c8c45113..ae53d2b63 100644 --- a/Tests/test_file_xvthumb.py +++ b/Tests/test_file_xvthumb.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, XVThumbImagePlugin from .helper import assert_image_similar, hopper diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index 4be39c383..1e7caee32 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -1,4 +1,5 @@ import pytest + from PIL import BdfFontFile, FontFile filename = "Tests/images/courB08.bdf" diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index a60163713..4db73e56e 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,6 +1,7 @@ import os import pytest + from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile from .helper import assert_image_equal, assert_image_similar, skip_unless_feature diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index d10b1acfd..3b9c8b071 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -85,7 +85,10 @@ def test_wedge(): im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong" ) assert_image_similar( - im.getchannel(1), comparable.getchannel(1), 1, "Saturation conversion is wrong", + im.getchannel(1), + comparable.getchannel(1), + 1, + "Saturation conversion is wrong", ) assert_image_similar( im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" @@ -113,7 +116,10 @@ def test_convert(): im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong" ) assert_image_similar( - im.getchannel(1), comparable.getchannel(1), 1, "Saturation conversion is wrong", + im.getchannel(1), + comparable.getchannel(1), + 1, + "Saturation conversion is wrong", ) assert_image_similar( im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" @@ -126,11 +132,20 @@ def test_hsv_to_rgb(): comparable = to_rgb_colorsys(comparable) assert_image_similar( - converted.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong", + converted.getchannel(0), + comparable.getchannel(0), + 3, + "R conversion is wrong", ) assert_image_similar( - converted.getchannel(1), comparable.getchannel(1), 3, "G conversion is wrong", + converted.getchannel(1), + comparable.getchannel(1), + 3, + "G conversion is wrong", ) assert_image_similar( - converted.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong", + converted.getchannel(2), + comparable.getchannel(2), + 3, + "B conversion is wrong", ) diff --git a/Tests/test_image.py b/Tests/test_image.py index 068fb8172..6d188e740 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -3,8 +3,9 @@ import os import shutil import tempfile -import PIL import pytest + +import PIL from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageError from .helper import ( @@ -706,7 +707,8 @@ class TestImage: } @pytest.mark.parametrize( - "test_module", [PIL, Image], + "test_module", + [PIL, Image], ) def test_pillow_version(self, test_module): with pytest.warns(DeprecationWarning): @@ -734,7 +736,7 @@ class TestImage: assert test_module.PILLOW_VERSION > "7.0.0" def test_overrun(self): - """ For overrun completeness, test as: + """For overrun completeness, test as: valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c """ for file in [ diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index af51a4fb3..3f0c6ab3b 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -2,9 +2,11 @@ import ctypes import os import subprocess import sys -from distutils import ccompiler, sysconfig +import sysconfig import pytest +from setuptools.command.build_ext import new_compiler + from PIL import Image from .helper import assert_image_equal, hopper, is_win32, on_ci @@ -15,8 +17,9 @@ if os.environ.get("PYTHONOPTIMIZE") == "2": cffi = None else: try: - from PIL import PyAccess import cffi + + from PIL import PyAccess except ImportError: cffi = None @@ -358,13 +361,12 @@ int main(int argc, char* argv[]) % sys.prefix.replace("\\", "\\\\") ) - compiler = ccompiler.new_compiler() - compiler.add_include_dir(sysconfig.get_python_inc()) + compiler = new_compiler() + compiler.add_include_dir(sysconfig.get_config_var("INCLUDEPY")) - libdir = sysconfig.get_config_var( - "LIBDIR" - ) or sysconfig.get_python_inc().replace("include", "libs") - print(libdir) + libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var( + "INCLUDEPY" + ).replace("include", "libs") compiler.add_library_dir(libdir) objects = compiler.compile(["embed_pil.c"]) compiler.link_executable(objects, "embed_pil") diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index bf6d88a97..980458407 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import hopper diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index cf83922b6..6fe1bd962 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image, assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index 3a2ce150d..e2228758c 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image_equal, hopper diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index ed71ea968..df8c353f3 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageFilter from .helper import assert_image_equal, hopper diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index 170d49ae1..5ad5b5c3c 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageQt from .helper import assert_image_equal, hopper diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py index efb9a1452..f7fe99bb4 100644 --- a/Tests/test_image_load.py +++ b/Tests/test_image_load.py @@ -1,6 +1,8 @@ +import logging import os import pytest + from PIL import Image from .helper import hopper @@ -22,6 +24,14 @@ def test_close(): im.getpixel((0, 0)) +def test_close_after_load(caplog): + im = Image.open("Tests/images/hopper.gif") + im.load() + with caplog.at_level(logging.DEBUG): + im.close() + assert len(caplog.records) == 0 + + def test_contextmanager(): fn = None with Image.open("Tests/images/hopper.gif") as im: diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index fe868b7c2..51108ead2 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -24,9 +24,9 @@ def test_sanity(): def test_16bit_lut(): - """ Tests for 16 bit -> 8 bit lut for converting I->L images - see https://github.com/python-pillow/Pillow/issues/440 - """ + """Tests for 16 bit -> 8 bit lut for converting I->L images + see https://github.com/python-pillow/Pillow/issues/440 + """ im = hopper("I") im.point(list(range(256)) * 256, "L") diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index 7b05e88b6..32f8de2c0 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -1,4 +1,5 @@ import pytest + from PIL import ImagePalette from .helper import hopper diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 96fa143a9..192617a52 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image, assert_image_similar, hopper diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index b11269918..b4eebc142 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageMath, ImageMode from .helper import convert_to_comparable, skip_unless_feature diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index a6d861520..ef4ca4101 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -1,6 +1,7 @@ from contextlib import contextmanager import pytest + from PIL import Image, ImageDraw from .helper import assert_image_equal, assert_image_similar, hopper @@ -537,7 +538,10 @@ class TestCoreResampleBox: assert res.size == size # Borders should be slightly different assert_image_similar( - res, im.crop(box).resize(size, flt), 0.4, f">>> {size} {box} {flt}", + res, + im.crop(box).resize(size, flt), + 0.4, + f">>> {size} {box} {flt}", ) def test_skip_vertical(self): @@ -555,5 +559,8 @@ class TestCoreResampleBox: assert res.size == size # Borders should be slightly different assert_image_similar( - res, im.crop(box).resize(size, flt), 0.4, f">>> {size} {box} {flt}", + res, + im.crop(box).resize(size, flt), + 0.4, + f">>> {size} {box} {flt}", ) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index ad4be135a..a49abe1b9 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -4,6 +4,7 @@ Tests for resize functionality. from itertools import permutations import pytest + from PIL import Image from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index da63efe55..c42310c32 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import ( diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 3409d86f0..3ee51178d 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -1,6 +1,7 @@ import math import pytest + from PIL import Image, ImageTransform from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index e549f0922..e9149b843 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -4,6 +4,7 @@ import re from io import BytesIO import pytest + from PIL import Image, ImageMode, features from .helper import assert_image, assert_image_equal, assert_image_similar, hopper @@ -436,7 +437,7 @@ def test_extended_information(): def test_profile_typesafety(): - """ Profile init type safety + """Profile init type safety prepatch, these would segfault, postpatch they should emit a typeerror """ diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index d2fd07c81..b5d693796 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageColor diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 283006b3d..271a1629d 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,6 +1,7 @@ import os.path import pytest + from PIL import Image, ImageColor, ImageDraw, ImageFont from .helper import ( @@ -666,7 +667,10 @@ def test_floodfill_border(): # Act ImageDraw.floodfill( - im, centre_point, ImageColor.getrgb("red"), border=ImageColor.getrgb("black"), + im, + centre_point, + ImageColor.getrgb("red"), + border=ImageColor.getrgb("black"), ) # Assert diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index 32222c1d3..8bc94401e 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -33,7 +33,9 @@ def _half_transparent_image(): def _check_alpha(im, original, op, amount): assert im.getbands() == original.getbands() assert_image_equal( - im.getchannel("A"), original.getchannel("A"), f"Diff on {op}: {amount}", + im.getchannel("A"), + original.getchannel("A"), + f"Diff on {op}: {amount}", ) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 48fecc26e..b4107e8e3 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import EpsImagePlugin, Image, ImageFile, features from .helper import ( @@ -243,3 +244,8 @@ class TestPyDecoder: im = MockImageFile(buf) assert im.format is None assert im.get_format_mimetype() is None + + def test_oserror(self): + im = Image.new("RGB", (1, 1)) + with pytest.raises(OSError): + im.save(BytesIO(), "JPEG2000") diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 6668a100b..aa7ec6fa6 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -7,6 +7,7 @@ from io import BytesIO import pytest from packaging.version import parse as parse_version + from PIL import Image, ImageDraw, ImageFont, features from .helper import ( diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py index c4032d55d..0ba682885 100644 --- a/Tests/test_imagefont_bitmap.py +++ b/Tests/test_imagefont_bitmap.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageDraw, ImageFont from .helper import assert_image_similar @@ -33,6 +34,9 @@ def test_similar(): (0, size_final[1] - size_bitmap[1]), text, fill=(0, 0, 0), font=font_bitmap ) draw_outline.text( - (0, size_final[1] - size_outline[1]), text, fill=(0, 0, 0), font=font_outline, + (0, size_final[1] - size_outline[1]), + text, + fill=(0, 0, 0), + font=font_outline, ) assert_image_similar(im_bitmap, im_outline, 20) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 386dd3be6..edd6fab57 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageDraw, ImageFont from .helper import assert_image_similar, skip_unless_feature diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index ae1277ced..c36285451 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -3,6 +3,7 @@ import subprocess import sys import pytest + from PIL import Image, ImageGrab from .helper import assert_image, assert_image_equal_tofile, skip_unless_feature diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 5b0be938a..087c39e01 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -1,5 +1,6 @@ # Test the ImageMorphology functionality import pytest + from PIL import Image, ImageMorph, _imagingmorph from .helper import assert_image_equal, hopper diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 864df447e..f17bfdd2f 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -1,5 +1,6 @@ import pytest -from PIL import Image, ImageOps, features + +from PIL import Image, ImageDraw, ImageOps, ImageStat, features from .helper import ( assert_image_equal, @@ -24,7 +25,9 @@ def test_sanity(): ImageOps.autocontrast(hopper("RGB")) ImageOps.autocontrast(hopper("L"), cutoff=10) + ImageOps.autocontrast(hopper("L"), cutoff=(2, 10)) ImageOps.autocontrast(hopper("L"), ignore=[0, 255]) + ImageOps.autocontrast(hopper("L"), mask=hopper("L")) ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255)) ImageOps.colorize(hopper("L"), "black", "white") @@ -311,3 +314,51 @@ def test_autocontrast_cutoff(): assert autocontrast(10) == autocontrast((10, 10)) assert autocontrast(10) != autocontrast((1, 10)) + + +def test_autocontrast_mask_toy_input(): + # Test the mask argument of autocontrast + with Image.open("Tests/images/bw_gradient.png") as img: + + rect_mask = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(rect_mask) + x0 = img.size[0] // 4 + y0 = img.size[1] // 4 + x1 = 3 * img.size[0] // 4 + y1 = 3 * img.size[1] // 4 + draw.rectangle((x0, y0, x1, y1), fill=255) + + result = ImageOps.autocontrast(img, mask=rect_mask) + result_nomask = ImageOps.autocontrast(img) + + assert result != result_nomask + assert ImageStat.Stat(result, mask=rect_mask).median == [127] + assert ImageStat.Stat(result_nomask).median == [128] + + +def test_auto_contrast_mask_real_input(): + # Test the autocontrast with a rectangular mask + with Image.open("Tests/images/iptc.jpg") as img: + + rect_mask = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(rect_mask) + x0, y0 = img.size[0] // 2, img.size[1] // 2 + x1, y1 = img.size[0] - 40, img.size[1] + draw.rectangle((x0, y0, x1, y1), fill=255) + + result = ImageOps.autocontrast(img, mask=rect_mask) + result_nomask = ImageOps.autocontrast(img) + + assert result_nomask != result + assert_tuple_approx_equal( + ImageStat.Stat(result, mask=rect_mask).median, + [195, 202, 184], + threshold=2, + msg="autocontrast with mask pixel incorrect", + ) + assert_tuple_approx_equal( + ImageStat.Stat(result_nomask).median, + [119, 106, 79], + threshold=2, + msg="autocontrast without mask pixel incorrect", + ) diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 61f8dc2ba..8837ed2a2 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageFilter diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 4ef2d3ffd..a2b0d2b02 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImagePalette from .helper import assert_image_equal diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 52af16455..7cc89ae39 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -2,6 +2,7 @@ import array import struct import pytest + from PIL import Image, ImagePath diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index d723690ef..c39bb0a06 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,4 +1,5 @@ import pytest + from PIL import ImageQt from .helper import hopper diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index b3fe9df97..7cf237b46 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageSequence, TiffImagePlugin from .helper import assert_image_equal, hopper, skip_unless_feature diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index fddc73bd1..78e80f521 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageShow from .helper import hopper, is_win32, on_ci @@ -18,7 +19,8 @@ def test_register(): @pytest.mark.parametrize( - "order", [-1, 0], + "order", + [-1, 0], ) def test_viewer_show(order): class TestViewer(ImageShow.Viewer): @@ -40,7 +42,8 @@ def test_viewer_show(order): @pytest.mark.skipif( - not on_ci() or is_win32(), reason="Only run on CIs; hangs on Windows CIs", + not on_ci() or is_win32(), + reason="Only run on CIs; hangs on Windows CIs", ) def test_show(): for mode in ("1", "I;16", "LA", "RGB", "RGBA"): diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py index 6c70193ce..9474ff6f9 100644 --- a/Tests/test_imagestat.py +++ b/Tests/test_imagestat.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageStat from .helper import hopper diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 7e87ac902..928b8cbd1 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,13 +1,14 @@ import pytest + from PIL import Image from .helper import assert_image_equal, hopper try: - from PIL import ImageTk - import tkinter as tk + from PIL import ImageTk + dir(ImageTk) HAS_TK = True except (OSError, ImportError): diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index b1ddc75e9..9d64d17a3 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -1,4 +1,5 @@ import pytest + from PIL import ImageWin from .helper import hopper, is_win32 diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py index 7115e62ad..37ed3659d 100644 --- a/Tests/test_lib_image.py +++ b/Tests/test_lib_image.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 8e3c1fda9..8a1460346 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image X = 255 diff --git a/Tests/test_locale.py b/Tests/test_locale.py index c5e54883d..7a07fbbe0 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -1,6 +1,7 @@ import locale import pytest + from PIL import Image # ref https://github.com/python-pillow/Pillow/issues/272 diff --git a/Tests/test_map.py b/Tests/test_map.py index bdb59bfe0..2b65fb3f9 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image from .helper import is_win32 diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 56addca1b..da367fa46 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_deep_equal, assert_image, hopper diff --git a/Tests/test_pdfparser.py b/Tests/test_pdfparser.py index f5cd403d5..2d428e95f 100644 --- a/Tests/test_pdfparser.py +++ b/Tests/test_pdfparser.py @@ -1,6 +1,7 @@ import time import pytest + from PIL.PdfParser import ( IndirectObjectDef, IndirectReference, diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index dd241fd74..a10dcec8c 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -1,6 +1,7 @@ import pickle import pytest + from PIL import Image from .helper import skip_unless_feature diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index f4302350d..aa05c2cfd 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,4 +1,5 @@ import pytest + from PIL import __version__ pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed") diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index fcb8c2f2a..8d599f9bf 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageQt from .helper import assert_image_equal, hopper @@ -12,10 +13,10 @@ if ImageQt.qt_is_installed: try: from PyQt5 import QtGui - from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication + from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget except (ImportError, RuntimeError): from PySide2 import QtGui - from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication + from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget def test_sanity(tmp_path): diff --git a/Tests/test_sgi_crash.py b/Tests/test_sgi_crash.py index b1a3e1515..2b671244a 100644 --- a/Tests/test_sgi_crash.py +++ b/Tests/test_sgi_crash.py @@ -1,5 +1,6 @@ #!/usr/bin/env python import pytest + from PIL import Image diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 45c60fa10..d25d42dfc 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -1,6 +1,7 @@ import shutil import pytest + from PIL import GifImagePlugin, Image, JpegImagePlugin from .helper import cjpeg_available, djpeg_available, is_win32, netpbm_available diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index 707284d7b..1697a8d49 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -29,6 +29,12 @@ def test_sanity(): _test_equal(1, 2, IFDRational(1, 2)) +def test_ranges(): + for num in range(1, 10): + for denom in range(1, 10): + assert IFDRational(num, denom) == IFDRational(num, denom) + + def test_nonetype(): # Fails if the _delegate function doesn't return a valid function diff --git a/Tests/test_util.py b/Tests/test_util.py index 0bc8b0702..b5bfca012 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,4 +1,5 @@ import pytest + from PIL import _util diff --git a/docs/conf.py b/docs/conf.py index caddae327..a022e61cb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,9 +16,10 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) -import PIL import sphinx_rtd_theme +import PIL + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -76,7 +77,7 @@ language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ["_build"] +exclude_patterns = ["_build", "releasenotes/template.rst"] # The reST default role (used for this markup: `text`) to use for all # documents. diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 10ccec632..e3ad2a9e3 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -18,16 +18,16 @@ Image.show command parameter .. deprecated:: 7.2.0 The ``command`` parameter was deprecated and will be removed in a future release. -Use a subclass of ``ImageShow.Viewer`` instead. +Use a subclass of :py:class:`.ImageShow.Viewer` instead. Image._showxv ~~~~~~~~~~~~~ .. deprecated:: 7.2.0 -``Image._showxv`` has been deprecated. Use :py:meth:`~PIL.Image.Image.show` -instead. If custom behaviour is required, use :py:meth:`~PIL.ImageShow.register` to add -a custom :py:class:`~PIL.ImageShow.Viewer` class. +``Image._showxv`` has been deprecated. Use :py:meth:`.Image.Image.show` +instead. If custom behaviour is required, use :py:func:`.ImageShow.register` to add +a custom :py:class:`.ImageShow.Viewer` class. ImageFile.raise_ioerror ~~~~~~~~~~~~~~~~~~~~~~~ @@ -61,7 +61,7 @@ im.offset .. deprecated:: 1.1.2 .. versionremoved:: 8.0.0 -``im.offset()`` has been removed, call ``ImageChops.offset()`` instead. +``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead. It was documented as deprecated in PIL 1.1.2, raised a ``DeprecationWarning`` since 1.1.5, @@ -88,20 +88,21 @@ ImageCms.CmsProfile attributes .. deprecated:: 3.2.0 .. versionremoved:: 8.0.0 -Some attributes in ``ImageCms.CmsProfile`` have been removed. From 6.0.0, they issued a -``DeprecationWarning``: +Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed. From 6.0.0, +they issued a ``DeprecationWarning``: + +======================== =================================================== -======================== =============================== Removed Use instead -======================== =============================== -``color_space`` Padded ``xcolor_space`` -``pcs`` Padded ``connection_space`` -``product_copyright`` Unicode ``copyright`` -``product_desc`` Unicode ``profile_description`` -``product_description`` Unicode ``profile_description`` -``product_manufacturer`` Unicode ``manufacturer`` -``product_model`` Unicode ``model`` -======================== =============================== +======================== =================================================== +``color_space`` Padded :py:attr:`~.CmsProfile.xcolor_space` +``pcs`` Padded :py:attr:`~.CmsProfile.connection_space` +``product_copyright`` Unicode :py:attr:`~.CmsProfile.copyright` +``product_desc`` Unicode :py:attr:`~.CmsProfile.profile_description` +``product_description`` Unicode :py:attr:`~.CmsProfile.profile_description` +``product_manufacturer`` Unicode :py:attr:`~.CmsProfile.manufacturer` +``product_model`` Unicode :py:attr:`~.CmsProfile.model` +======================== =================================================== Python 2.7 ~~~~~~~~~~ diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 89e3dfd4a..f54b7acf7 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -257,6 +257,9 @@ Using the :py:meth:`~PIL.Image.Image.draft` method, you can speed things up by converting ``RGB`` images to ``L``, and resize images to 1/2, 1/4 or 1/8 of their original size while loading them. +By default Pillow doesn't allow loading of truncated JPEG files, set +:data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this. + The :py:meth:`~PIL.Image.open` method may set the following :py:attr:`~PIL.Image.Image.info` properties if available: @@ -473,6 +476,9 @@ image formats, EXIF data is not guaranteed to be present in :py:attr:`~PIL.Image.Image.info` until :py:meth:`~PIL.Image.Image.load` has been called. +By default Pillow doesn't allow loading of truncated PNG files, set +:data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this. + The :py:func:`~PIL.Image.open` function sets the following :py:attr:`~PIL.Image.Image.info` properties, when appropriate: diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index 471ae3377..97cb5abe1 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -87,8 +87,10 @@ true color. Image.register_open(SpamImageFile.format, SpamImageFile, _accept) - Image.register_extension(SpamImageFile.format, ".spam") - Image.register_extension(SpamImageFile.format, ".spa") # DOS version + Image.register_extensions(SpamImageFile.format, [ + ".spam", + ".spa", # DOS version + ]) The format handler must always set the @@ -103,6 +105,15 @@ Note that the image plugin must be explicitly registered using :py:func:`PIL.Image.register_open`. Although not required, it is also a good idea to register any extensions used by this format. +Once the plugin has been imported, it can be used: + +.. code-block:: python + + from PIL import Image + import SpamImagePlugin + with Image.open("hopper.spam") as im: + pass + The ``tile`` attribute ---------------------- @@ -148,6 +159,8 @@ can be used with most uncompressed file formats, such as PPM, BMP, uncompressed TIFF, and many others. To use the raw decoder with the :py:func:`PIL.Image.frombytes` function, use the following syntax:: +.. code-block:: python + image = Image.frombytes( mode, size, data, "raw", raw mode, stride, orientation @@ -258,6 +271,8 @@ image memory. To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use the following syntax:: +.. code-block:: python + image = Image.frombytes( mode, size, data, "bit", bits, pad, fill, sign, orientation @@ -350,7 +365,7 @@ interest in this object are: The target image, will be set by Pillow. **state** - An ImagingCodecStateInstance, will be set by Pillow. The **context** + An ImagingCodecStateInstance, will be set by Pillow. The ``context`` member is an opaque struct that can be used by the decoder to store any format specific state or options. @@ -414,4 +429,3 @@ Python-based file decoder: 3. Cleanup: The decoder instance's ``cleanup`` method is called. - diff --git a/docs/installation.rst b/docs/installation.rst index 706cfb1d7..4c41cc9ee 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -412,12 +412,12 @@ These platforms are built and tested for every change. | Windows Server 2016 | 3.8 |x86 | | +--------------------------+-----------------------+ | | 3.6 |x86-64 | -| +--------------------------+-----------------------+ -| | 3.7/MinGW |x86 | +----------------------------------+--------------------------+-----------------------+ | Windows Server 2019 | 3.6, 3.7, 3.8 |x86, x86-64 | | +--------------------------+-----------------------+ | | PyPy3 |x86 | +| +--------------------------+-----------------------+ +| | 3.8/MinGW |x86, x86-64 | +----------------------------------+--------------------------+-----------------------+ diff --git a/docs/reference/ImageChops.rst b/docs/reference/ImageChops.rst index 772d9c983..9519361a7 100644 --- a/docs/reference/ImageChops.rst +++ b/docs/reference/ImageChops.rst @@ -39,12 +39,7 @@ operations in this module). .. autofunction:: PIL.ImageChops.soft_light .. autofunction:: PIL.ImageChops.hard_light .. autofunction:: PIL.ImageChops.overlay -.. py:method:: PIL.ImageChops.offset(image, xoffset, yoffset=None) - - Returns a copy of the image where data has been offset by the given - distances. Data wraps around the edges. If **yoffset** is omitted, it - is assumed to be equal to **xoffset**. - +.. autofunction:: PIL.ImageChops.offset .. autofunction:: PIL.ImageChops.screen .. autofunction:: PIL.ImageChops.subtract .. autofunction:: PIL.ImageChops.subtract_modulo diff --git a/docs/reference/ImageFile.rst b/docs/reference/ImageFile.rst index 333876bc8..e0ce389e8 100644 --- a/docs/reference/ImageFile.rst +++ b/docs/reference/ImageFile.rst @@ -52,3 +52,10 @@ Classes .. autoclass:: PIL.ImageFile.StubImageFile() :members: :show-inheritance: + +Constants +--------- + +.. autodata:: PIL.ImageFile.LOAD_TRUNCATED_IMAGES +.. autodata:: PIL.ImageFile.ERRORS + :annotation: diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index fc61cac13..ff79bdcd8 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -56,3 +56,19 @@ Methods .. autoclass:: PIL.ImageFont.TransposedFont :members: + +Constants +--------- + +.. data:: PIL.ImageFont.LAYOUT_BASIC + + Use basic text layout for TrueType font. + Advanced features such as text direction are not supported. + +.. data:: PIL.ImageFont.LAYOUT_RAQM + + Use Raqm text layout for TrueType font. + Advanced features are supported. + + Requires Raqm, you can check support using + :py:func:`PIL.features.check_feature` with ``feature="raqm"``. diff --git a/docs/reference/ImagePath.rst b/docs/reference/ImagePath.rst index 21a202b5e..b9bdfc507 100644 --- a/docs/reference/ImagePath.rst +++ b/docs/reference/ImagePath.rst @@ -33,7 +33,7 @@ vector data. Path objects can be passed to the methods on the method modifies the path in place, and returns the number of points left in the path. - **distance** is measured as `Manhattan distance`_ and defaults to two + ``distance`` is measured as `Manhattan distance`_ and defaults to two pixels. .. _Manhattan distance: https://en.wikipedia.org/wiki/Manhattan_distance @@ -55,7 +55,7 @@ vector data. Path objects can be passed to the methods on the :param flat: By default, this function returns a list of 2-tuples [(x, y), ...]. If this argument is ``True``, it returns a flat list [x, y, ...] instead. - :return: A list of coordinates. See **flat**. + :return: A list of coordinates. See ``flat``. .. py:method:: PIL.ImagePath.Path.transform(matrix) diff --git a/docs/reference/block_allocator.rst b/docs/reference/block_allocator.rst index 400f236dc..1abe5280f 100644 --- a/docs/reference/block_allocator.rst +++ b/docs/reference/block_allocator.rst @@ -40,7 +40,7 @@ variables: * ``PILLOW_BLOCK_SIZE``, in bytes, K, or M. Specifies the maximum block size for ``ImagingAllocateArray``. Valid values are - integers, with an optional `k` or `m` suffix. Defaults to 16M. + integers, with an optional ``k`` or ``m`` suffix. Defaults to 16M. * ``PILLOW_BLOCKS_MAX`` Specifies the number of freed blocks to retain to fill future memory requests. Any freed blocks over this diff --git a/docs/releasenotes/8.0.0.rst b/docs/releasenotes/8.0.0.rst new file mode 100644 index 000000000..bbb954e25 --- /dev/null +++ b/docs/releasenotes/8.0.0.rst @@ -0,0 +1,81 @@ +8.0.0 +----- + +Backwards Incompatible Changes +============================== + +Python 3.5 +^^^^^^^^^^ + +Pillow has dropped support for Python 3.5, which reached end-of-life on 2020-09-13. + +im.offset +^^^^^^^^^ + +``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead. + +Image.fromstring, im.fromstring and im.tostring +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* ``Image.fromstring()`` has been removed, call :py:func:`.Image.frombytes()` instead. +* ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead. +* ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead. + +ImageCms.CmsProfile attributes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed: + +======================== =================================================== +Removed Use instead +======================== =================================================== +``color_space`` Padded :py:attr:`~.CmsProfile.xcolor_space` +``pcs`` Padded :py:attr:`~.CmsProfile.connection_space` +``product_copyright`` Unicode :py:attr:`~.CmsProfile.copyright` +``product_desc`` Unicode :py:attr:`~.CmsProfile.profile_description` +``product_description`` Unicode :py:attr:`~.CmsProfile.profile_description` +``product_manufacturer`` Unicode :py:attr:`~.CmsProfile.manufacturer` +``product_model`` Unicode :py:attr:`~.CmsProfile.model` +======================== =================================================== + +API Changes +=========== + +Add MIME type to PsdImagePlugin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +"image/vnd.adobe.photoshop" is now registered as the +:py:class:`.PsdImagePlugin.PsdImageFile` MIME type. + +API Additions +============= + +ImageOps.autocontrast: add mask parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:py:func:`.ImageOps.autocontrast` can now take a ``mask`` parameter: + +* Histogram used in contrast operation is computed using pixels within the mask. + If no mask is given the entire image is used for histogram computation. + +ImageOps.autocontrast cutoffs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, the ``cutoff`` parameter of :py:func:`.ImageOps.autocontrast` could only +be a single number, used as the percent to cut off from the histogram on the low and +high ends. + +Now, it can also be a tuple ``(low, high)``. + +Security +======== + +TODO + +Other Changes +============= + +TODO +^^^^ + +TODO diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 2d7747c3e..ba81fbaf8 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -13,6 +13,7 @@ expected to be backported to earlier versions. .. toctree:: :maxdepth: 2 + 8.0.0 7.2.0 7.1.2 7.1.1 diff --git a/docs/releasenotes/template.rst b/docs/releasenotes/template.rst new file mode 100644 index 000000000..bf381114e --- /dev/null +++ b/docs/releasenotes/template.rst @@ -0,0 +1,45 @@ +x.y.z +----- + +Backwards Incompatible Changes +============================== + +TODO +^^^^ + +Deprecations +============ + +TODO +^^^^ + +TODO + +API Changes +=========== + +TODO +^^^^ + +TODO + +API Additions +============= + +TODO +^^^^ + +TODO + +Security +======== + +TODO + +Other Changes +============= + +TODO +^^^^ + +TODO diff --git a/setup.cfg b/setup.cfg index 19979cf77..129adeee7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,12 +1,9 @@ [flake8] -extend-ignore = E203, W503 +extend-ignore = E203 max-line-length = 88 [isort] -combine_as_imports = True -include_trailing_comma = True -line_length = 88 -multi_line_output = 3 +profile = black [tool:pytest] addopts = -ra --color=yes diff --git a/setup.py b/setup.py index 1e6759ee5..a6a39e57b 100755 --- a/setup.py +++ b/setup.py @@ -14,10 +14,9 @@ import struct import subprocess import sys import warnings -from distutils import ccompiler -from distutils.command.build_ext import build_ext from setuptools import Extension, setup +from setuptools.command.build_ext import build_ext def get_version(): @@ -131,7 +130,7 @@ class RequiredDependencyException(Exception): pass -PLATFORM_MINGW = "mingw" in ccompiler.get_default_compiler() +PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version PLATFORM_PYPY = hasattr(sys, "pypy_version_info") if sys.platform == "win32" and PLATFORM_MINGW: @@ -244,11 +243,6 @@ def _cmd_exists(cmd): ) -def _read(file): - with open(file, "rb") as fp: - return fp.read() - - def _pkg_config(name): try: command = os.environ.get("PKG_CONFIG", "pkg-config") @@ -352,6 +346,22 @@ class pil_build_ext(build_ext): _dbg("Requiring %s", x) self.feature.required.add(x) + def _update_extension(self, name, libraries, define_macros=None, include_dirs=None): + for extension in self.extensions: + if extension.name == name: + extension.libraries += libraries + if define_macros is not None: + extension.define_macros += define_macros + if include_dirs is not None: + extension.include_dirs += include_dirs + break + + def _remove_extension(self, name): + for extension in self.extensions: + if extension.name == name: + self.extensions.remove(extension) + break + def build_extensions(self): library_dirs = [] @@ -464,6 +474,9 @@ class pil_build_ext(build_ext): # add Homebrew's include and lib directories _add_directory(library_dirs, os.path.join(prefix, "lib")) _add_directory(include_dirs, os.path.join(prefix, "include")) + _add_directory( + include_dirs, os.path.join(prefix, "opt", "zlib", "include") + ) ft_prefix = os.path.join(prefix, "opt", "freetype") if ft_prefix and os.path.isdir(ft_prefix): @@ -695,12 +708,6 @@ class pil_build_ext(build_ext): # # core library - files = ["src/_imaging.c"] - for src_file in _IMAGING: - files.append("src/" + src_file + ".c") - for src_file in _LIB_IMAGING: - files.append(os.path.join("src/libImaging", src_file + ".c")) - libs = self.add_imaging_libs.split() defs = [] if feature.jpeg: @@ -737,7 +744,7 @@ class pil_build_ext(build_ext): else: defs.append(("PILLOW_VERSION", f'"{PILLOW_VERSION}"')) - exts = [(Extension("PIL._imaging", files, libraries=libs, define_macros=defs))] + self._update_extension("PIL._imaging", libs, defs) # # additional libraries @@ -745,26 +752,17 @@ class pil_build_ext(build_ext): if feature.freetype: libs = ["freetype"] defs = [] - exts.append( - Extension( - "PIL._imagingft", - ["src/_imagingft.c"], - libraries=libs, - define_macros=defs, - ) - ) + self._update_extension("PIL._imagingft", libs, defs) + else: + self._remove_extension("PIL._imagingft") if feature.lcms: extra = [] if sys.platform == "win32": extra.extend(["user32", "gdi32"]) - exts.append( - Extension( - "PIL._imagingcms", - ["src/_imagingcms.c"], - libraries=[feature.lcms] + extra, - ) - ) + self._update_extension("PIL._imagingcms", [feature.lcms] + extra) + else: + self._remove_extension("PIL._imagingcms") if feature.webp: libs = [feature.webp] @@ -775,26 +773,12 @@ class pil_build_ext(build_ext): libs.append(feature.webpmux) libs.append(feature.webpmux.replace("pmux", "pdemux")) - exts.append( - Extension( - "PIL._webp", ["src/_webp.c"], libraries=libs, define_macros=defs - ) - ) + self._update_extension("PIL._webp", libs, defs) + else: + self._remove_extension("PIL._webp") tk_libs = ["psapi"] if sys.platform == "win32" else [] - exts.append( - Extension( - "PIL._imagingtk", - ["src/_imagingtk.c", "src/Tk/tkImaging.c"], - include_dirs=["src/Tk"], - libraries=tk_libs, - ) - ) - - exts.append(Extension("PIL._imagingmath", ["src/_imagingmath.c"])) - exts.append(Extension("PIL._imagingmorph", ["src/_imagingmorph.c"])) - - self.extensions[:] = exts + self._update_extension("PIL._imagingtk", tk_libs, include_dirs=["src/Tk"]) build_ext.build_extensions(self) @@ -858,12 +842,31 @@ def debug_build(): return hasattr(sys, "gettotalrefcount") +files = ["src/_imaging.c"] +for src_file in _IMAGING: + files.append("src/" + src_file + ".c") +for src_file in _LIB_IMAGING: + files.append(os.path.join("src/libImaging", src_file + ".c")) +ext_modules = [ + Extension("PIL._imaging", files), + Extension("PIL._imagingft", ["src/_imagingft.c"]), + Extension("PIL._imagingcms", ["src/_imagingcms.c"]), + Extension("PIL._webp", ["src/_webp.c"]), + Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), + Extension("PIL._imagingmath", ["src/_imagingmath.c"]), + Extension("PIL._imagingmorph", ["src/_imagingmorph.c"]), +] + +with open("README.md") as f: + long_description = f.read() + try: setup( name=NAME, version=PILLOW_VERSION, description="Python Imaging Library (Fork)", - long_description=_read("README.rst").decode("utf-8"), + long_description=long_description, + long_description_content_type="text/markdown", license="HPND", author="Alex Clark (PIL Fork Author)", author_email="aclark@python-pillow.org", @@ -892,7 +895,7 @@ try: ], python_requires=">=3.6", cmdclass={"build_ext": pil_build_ext}, - ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], + ext_modules=ext_modules, include_package_data=True, packages=["PIL"], package_dir={"": "src"}, diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index c1931b791..711e030e1 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -25,7 +25,12 @@ from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16le as i16, i32le as i32, o8, o16le as o16, o32le as o32 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import i32le as i32 +from ._binary import o8 +from ._binary import o16le as o16 +from ._binary import o32le as o32 # # -------------------------------------------------------------------- diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index 3a1b6d2e5..35123f789 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -16,7 +16,9 @@ # See the README file for information on usage and redistribution. # from . import BmpImagePlugin, Image -from ._binary import i8, i16le as i16, i32le as i32 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import i32le as i32 # # -------------------------------------------------------------------- diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index 2c8d03d0b..3c88d53af 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -17,7 +17,10 @@ from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16le as i16, i32le as i32, o8 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import i32le as i32 +from ._binary import o8 # # decoder diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index 51e78bc9a..14070eebf 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -17,7 +17,8 @@ import olefile from . import Image, ImageFile -from ._binary import i8, i32le as i32 +from ._binary import i8 +from ._binary import i32le as i32 # we map from colour field tuples to (mode, rawmode) descriptors MODES = { diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 0c4574f9e..8561f7b74 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -28,7 +28,9 @@ from . import ImageFile, ImagePalette, UnidentifiedImageError -from ._binary import i8, i16be as i16, i32be as i32 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 class GdImageFile(ImageFile.ImageFile): diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index c1a6a63ed..4ca5a697e 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -30,7 +30,10 @@ import os import subprocess from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence -from ._binary import i8, i16le as i16, o8, o16le as o16 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 # -------------------------------------------------------------------- # Identify/read GIF files diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index e4a74321b..57bb17ee9 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -28,7 +28,9 @@ from io import BytesIO from math import ceil, log from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin -from ._binary import i8, i16le as i16, i32le as i32 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import i32le as i32 # # -------------------------------------------------------------------- diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b0ec86d47..30ee8606a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -594,7 +594,8 @@ class Image: try: if hasattr(self, "_close__fp"): self._close__fp() - self.fp.close() + if self.fp: + self.fp.close() self.fp = None except Exception as msg: logger.debug("Error closing: %s", msg) @@ -664,7 +665,7 @@ class Image: ) def _repr_png_(self): - """ iPython display hook support + """iPython display hook support :returns: png version of the image as bytes """ @@ -855,7 +856,7 @@ class Image: and the palette can be represented without a palette. The current version supports all possible conversions between - "L", "RGB" and "CMYK." The **matrix** argument only supports "L" + "L", "RGB" and "CMYK." The ``matrix`` argument only supports "L" and "RGB". When translating a color image to greyscale (mode "L"), @@ -870,9 +871,9 @@ class Image: all other values to 0 (black). To use other thresholds, use the :py:meth:`~PIL.Image.Image.point` method. - When converting from "RGBA" to "P" without a **matrix** argument, + When converting from "RGBA" to "P" without a ``matrix`` argument, this passes the operation to :py:meth:`~PIL.Image.Image.quantize`, - and **dither** and **palette** are ignored. + and ``dither`` and ``palette`` are ignored. :param mode: The requested mode. See: :ref:`concept-modes`. :param matrix: An optional conversion matrix. If given, this @@ -880,7 +881,7 @@ class Image: :param dither: Dithering method, used when converting from mode "RGB" to "P" or from "RGB" or "L" to "1". Available methods are :data:`NONE` or :data:`FLOYDSTEINBERG` (default). - Note that this is not used when **matrix** is supplied. + Note that this is not used when ``matrix`` is supplied. :param palette: Palette to use when converting from mode "RGB" to "P". Available palettes are :data:`WEB` or :data:`ADAPTIVE`. :param colors: Number of colors to use for the :data:`ADAPTIVE` palette. @@ -1180,7 +1181,7 @@ class Image: available filters, see the :py:mod:`~PIL.ImageFilter` module. :param filter: Filter kernel. - :returns: An :py:class:`~PIL.Image.Image` object. """ + :returns: An :py:class:`~PIL.Image.Image` object.""" from . import ImageFilter @@ -1205,7 +1206,7 @@ class Image: def getbands(self): """ Returns a tuple containing the name of each band in this image. - For example, **getbands** on an RGB image returns ("R", "G", "B"). + For example, ``getbands`` on an RGB image returns ("R", "G", "B"). :returns: A tuple containing band names. :rtype: tuple @@ -1259,7 +1260,7 @@ class Image: Note that the sequence object returned by this method is an internal PIL data type, which only supports certain sequence operations. To convert it to an ordinary sequence (e.g. for - printing), use **list(im.getdata())**. + printing), use ``list(im.getdata())``. :param band: What band to return. The default is to return all bands. To return a single band, pass in the index @@ -1505,7 +1506,7 @@ class Image: self.im.paste(im, box) def alpha_composite(self, im, dest=(0, 0), source=(0, 0)): - """ 'In-place' analog of Image.alpha_composite. Composites an image + """'In-place' analog of Image.alpha_composite. Composites an image onto this image. :param im: image to composite over this one @@ -1740,7 +1741,7 @@ class Image: Rewrites the image to reorder the palette. :param dest_map: A list of indexes into the original palette. - e.g. [1,0] would swap a two item palette, and list(range(256)) + e.g. ``[1,0]`` would swap a two item palette, and ``list(range(256))`` is the identity transform. :param source_palette: Bytes or None. :returns: An :py:class:`~PIL.Image.Image` object. @@ -1922,16 +1923,16 @@ class Image: def reduce(self, factor, box=None): """ - Returns a copy of the image reduced by `factor` times. - If the size of the image is not dividable by the `factor`, + Returns a copy of the image reduced ``factor`` times. + If the size of the image is not dividable by ``factor``, the resulting size will be rounded up. :param factor: A greater than 0 integer or tuple of two integers for width and height separately. :param box: An optional 4-tuple of ints providing the source image region to be reduced. - The values must be within (0, 0, width, height) rectangle. - If omitted or None, the entire source is used. + The values must be within ``(0, 0, width, height)`` rectangle. + If omitted or ``None``, the entire source is used. """ if not isinstance(factor, (list, tuple)): factor = (factor, factor) @@ -2354,7 +2355,7 @@ class Image: # Return result It may also be an object with a ``method.getdata`` method - that returns a tuple supplying new **method** and **data** values:: + that returns a tuple supplying new ``method`` and ``data`` values:: class Example: def getdata(self): @@ -2369,7 +2370,7 @@ class Image: interpolation in a 4x4 environment). If omitted, or if the image has mode "1" or "P", it is set to :py:data:`PIL.Image.NEAREST`. See: :ref:`concept-filters`. - :param fill: If **method** is an + :param fill: If ``method`` is an :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of the arguments passed to it. Otherwise, it is unused. :param fillcolor: Optional fill color for the area outside the @@ -2668,7 +2669,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): Note that this function decodes pixel data only, not entire images. If you have an entire image file in a string, wrap it in a - **BytesIO** object, and use :py:func:`~PIL.Image.open` to load it. + :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load it. In the current version, the default parameters used for the "raw" decoder differs from that used for :py:func:`~PIL.Image.frombytes`. This is a @@ -2715,7 +2716,7 @@ def fromarray(obj, mode=None): Creates an image memory from an object exporting the array interface (using the buffer protocol). - If **obj** is not contiguous, then the tobytes method is called + If ``obj`` is not contiguous, then the ``tobytes`` method is called and :py:func:`~PIL.Image.frombuffer` is used. If you have an image in NumPy:: diff --git a/src/PIL/ImageChops.py b/src/PIL/ImageChops.py index 9fccedcb2..61d3a295b 100644 --- a/src/PIL/ImageChops.py +++ b/src/PIL/ImageChops.py @@ -313,8 +313,8 @@ def composite(image1, image2, mask): def offset(image, xoffset, yoffset=None): """Returns a copy of the image where data has been offset by the given - distances. Data wraps around the edges. If **yoffset** is omitted, it - is assumed to be equal to **xoffset**. + distances. Data wraps around the edges. If ``yoffset`` is omitted, it + is assumed to be equal to ``xoffset``. :param xoffset: The horizontal distance. :param yoffset: The vertical distance. If omitted, both diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index f854959a7..3856cb843 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -250,7 +250,9 @@ class ImageCmsTransform(Image.ImagePointHandler): def get_display_profile(handle=None): - """ (experimental) Fetches the profile for the current display device. + """ + (experimental) Fetches the profile for the current display device. + :returns: ``None`` if the profile is not known. """ @@ -275,8 +277,8 @@ def get_display_profile(handle=None): class PyCMSError(Exception): - """ (pyCMS) Exception class. - This is used for all errors in the pyCMS API. """ + """(pyCMS) Exception class. + This is used for all errors in the pyCMS API.""" pass @@ -624,7 +626,7 @@ def applyTransform(im, transform, inPlace=False): :param im: An :py:class:`~PIL.Image.Image` object, and im.mode must be the same as the ``inMode`` supported by the transform. :param transform: A valid CmsTransform class object - :param inPlace: Bool. If ``True``, ``im` is modified in place and ``None`` is + :param inPlace: Bool. If ``True``, ``im`` is modified in place and ``None`` is returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the transform applied is returned (and ``im`` is not changed). The default is ``False``. diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index e7646a25c..6775ba43e 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -35,7 +35,6 @@ import numbers from . import Image, ImageColor - """ A simple 2D drawing interface for PIL images.

diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index bb8737d33..49633ef51 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -40,6 +40,7 @@ MAXBLOCK = 65536 SAFEBLOCK = 1024 * 1024 LOAD_TRUNCATED_IMAGES = False +"""Whether or not to load truncated image files. User code may change this.""" ERRORS = { -1: "image buffer overrun error", @@ -48,6 +49,7 @@ ERRORS = { -8: "bad configuration", -9: "out of memory error", } +"""Dict of known error codes returned from :meth:`.PyDecoder.decode`.""" # @@ -507,7 +509,7 @@ def _save(im, fp, tile, bufsize=0): try: fh = fp.fileno() fp.flush() - except (AttributeError, io.UnsupportedOperation) as e: + except (AttributeError, io.UnsupportedOperation) as exc: # compress to Python file-compatible object for e, b, o, a in tile: e = Image._getencoder(im.mode, e, a, im.encoderconfig) @@ -524,7 +526,7 @@ def _save(im, fp, tile, bufsize=0): if s: break if s < 0: - raise OSError(f"encoder error {s} when writing image file") from e + raise OSError(f"encoder error {s} when writing image file") from exc e.cleanup() else: # slight speedup: compress to real file object @@ -583,7 +585,7 @@ class PyCodecState: class PyDecoder: """ Python implementation of a format decoder. Override this class and - add the decoding logic in the `decode` method. + add the decoding logic in the :meth:`decode` method. See :ref:`Writing Your Own File Decoder in Python` """ @@ -615,9 +617,9 @@ class PyDecoder: Override to perform the decoding process. :param buffer: A bytes object with the data to be decoded. - :returns: A tuple of (bytes consumed, errcode). + :returns: A tuple of ``(bytes consumed, errcode)``. If finished with decoding return <0 for the bytes consumed. - Err codes are from `ERRORS` + Err codes are from :data:`.ImageFile.ERRORS`. """ raise NotImplementedError() diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index c00261e47..9ca17d9ad 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -69,7 +69,7 @@ class Kernel(BuiltinFilter): class RankFilter(Filter): """ Create a rank filter. The rank filter sorts all pixels in - a window of the given size, and returns the **rank**'th value. + a window of the given size, and returns the ``rank``'th value. :param size: The kernel size, in pixels. :param rank: What pixel value to pick. Use 0 for a min filter, diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 1e0576be0..63f819151 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -638,7 +638,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): This specifies the character set to use. It does not alter the encoding of any text provided in subsequent operations. :param layout_engine: Which layout engine to use, if available: - `ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`. + :data:`.ImageFont.LAYOUT_BASIC` or :data:`.ImageFont.LAYOUT_RAQM`. You can check support for Raqm layout using :py:func:`PIL.features.check_feature` with ``feature="raqm"``. diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 3fa338b0a..b93ec3f2a 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -21,8 +21,8 @@ from . import Image if sys.platform == "darwin": import os - import tempfile import subprocess + import tempfile def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None): diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index d1ec09eac..b76dfa01f 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -28,36 +28,36 @@ MIRROR_MATRIX = [ class LutBuilder: """A class for building a MorphLut from a descriptive language - The input patterns is a list of a strings sequences like these:: + The input patterns is a list of a strings sequences like these:: - 4:(... - .1. - 111)->1 + 4:(... + .1. + 111)->1 - (whitespaces including linebreaks are ignored). The option 4 - describes a series of symmetry operations (in this case a - 4-rotation), the pattern is described by: + (whitespaces including linebreaks are ignored). The option 4 + describes a series of symmetry operations (in this case a + 4-rotation), the pattern is described by: - - . or X - Ignore - - 1 - Pixel is on - - 0 - Pixel is off + - . or X - Ignore + - 1 - Pixel is on + - 0 - Pixel is off - The result of the operation is described after "->" string. + The result of the operation is described after "->" string. - The default is to return the current pixel value, which is - returned if no other match is found. + The default is to return the current pixel value, which is + returned if no other match is found. - Operations: + Operations: - - 4 - 4 way rotation - - N - Negate - - 1 - Dummy op for no other operation (an op must always be given) - - M - Mirroring + - 4 - 4 way rotation + - N - Negate + - 1 - Dummy op for no other operation (an op must always be given) + - M - Mirroring - Example:: + Example:: - lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) - lut = lb.build_lut() + lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) + lut = lb.build_lut() """ diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 157da0b52..14602a5c8 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -61,10 +61,10 @@ def _lut(image, lut): # actions -def autocontrast(image, cutoff=0, ignore=None): +def autocontrast(image, cutoff=0, ignore=None, mask=None): """ Maximize (normalize) image contrast. This function calculates a - histogram of the input image, removes **cutoff** percent of the + histogram of the input image (or mask region), removes ``cutoff`` percent of the lightest and darkest pixels from the histogram, and remaps the image so that the darkest pixel becomes black (0), and the lightest becomes white (255). @@ -74,9 +74,12 @@ def autocontrast(image, cutoff=0, ignore=None): high ends. Either a tuple of (low, high), or a single number for both. :param ignore: The background pixel value (use None for no background). + :param mask: Histogram used in contrast operation is computed using pixels + within the mask. If no mask is given the entire image is used + for histogram computation. :return: An image. """ - histogram = image.histogram() + histogram = image.histogram(mask) lut = [] for layer in range(0, len(histogram), 256): h = histogram[layer : layer + 256] @@ -146,14 +149,14 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi Colorize grayscale image. This function calculates a color wedge which maps all black pixels in the source image to the first color and all white pixels to the - second color. If **mid** is specified, it uses three-color mapping. - The **black** and **white** arguments should be RGB tuples or color names; - optionally you can use three-color mapping by also specifying **mid**. + second color. If ``mid`` is specified, it uses three-color mapping. + The ``black`` and ``white`` arguments should be RGB tuples or color names; + optionally you can use three-color mapping by also specifying ``mid``. Mapping positions for any of the colors can be specified - (e.g. **blackpoint**), where these parameters are the integer + (e.g. ``blackpoint``), where these parameters are the integer value corresponding to where the corresponding color should be mapped. These parameters must have logical order, such that - **blackpoint** <= **midpoint** <= **whitepoint** (if **mid** is specified). + ``blackpoint <= midpoint <= whitepoint`` (if ``mid`` is specified). :param image: The image to colorize. :param black: The color to use for black input pixels. @@ -312,7 +315,7 @@ def deform(image, deformer, resample=Image.BILINEAR): :param image: The image to deform. :param deformer: A deformer object. Any object that implements a - **getmesh** method can be used. + ``getmesh`` method can be used. :param resample: An optional resampling filter. Same values possible as in the PIL.Image.transform function. :return: An image. diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 63cc6ae5a..91be53488 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -29,11 +29,11 @@ qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=Tr for qt_version, qt_module in qt_versions: try: if qt_module == "PyQt5": - from PyQt5.QtGui import QImage, qRgba, QPixmap from PyQt5.QtCore import QBuffer, QIODevice + from PyQt5.QtGui import QImage, QPixmap, qRgba elif qt_module == "PySide2": - from PySide2.QtGui import QImage, qRgba, QPixmap from PySide2.QtCore import QBuffer, QIODevice + from PySide2.QtGui import QImage, QPixmap, qRgba except (ImportError, RuntimeError): continue qt_is_installed = True diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index e8148ad01..62db7a717 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -69,7 +69,7 @@ class PhotoImage: image, pixels having alpha 0 are treated as transparent. The constructor takes either a PIL image, or a mode and a size. - Alternatively, you can use the **file** or **data** options to initialize + Alternatively, you can use the ``file`` or ``data`` options to initialize the photo image object. :param image: Either a PIL image, or a mode string. If a mode string is @@ -210,7 +210,7 @@ class BitmapImage: The given image must have mode "1". Pixels having value 0 are treated as transparent. Options, if any, are passed on to Tkinter. The most commonly - used option is **foreground**, which is used to specify the color for the + used option is ``foreground``, which is used to specify the color for the non-transparent parts. See the Tkinter documentation for information on how to specify colours. diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py index 2ca4acdf8..ca9b14c8a 100644 --- a/src/PIL/ImageWin.py +++ b/src/PIL/ImageWin.py @@ -59,7 +59,7 @@ class Dib: with 20 greylevels. To make sure that palettes work properly under Windows, you must call the - **palette** method upon certain events from Windows. + ``palette`` method upon certain events from Windows. :param image: Either a PIL image, or a mode string. If a mode string is used, a size must also be given. The mode can be one of "1", diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 75e7b5a2a..f407b7e5f 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -18,7 +18,10 @@ import os import tempfile from . import Image, ImageFile -from ._binary import i8, i16be as i16, i32be as i32, o8 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 COMPRESSION = {1: "raw", 5: "jpeg"} @@ -181,9 +184,10 @@ def getiptcinfo(im): :returns: A dictionary containing IPTC information, or None if no IPTC information block was found. """ - from . import TiffImagePlugin, JpegImagePlugin import io + from . import JpegImagePlugin, TiffImagePlugin + data = None if isinstance(im, IptcImageFile): diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index dfd96d2df..6145e5da7 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -40,7 +40,10 @@ import tempfile import warnings from . import Image, ImageFile, TiffImagePlugin -from ._binary import i8, i16be as i16, i32be as i32, o8 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 from .JpegPresets import presets # diff --git a/src/PIL/JpegPresets.py b/src/PIL/JpegPresets.py index 09691d79d..79d10ebb2 100644 --- a/src/PIL/JpegPresets.py +++ b/src/PIL/JpegPresets.py @@ -34,7 +34,7 @@ Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and 4:2:0. You can get the subsampling of a JPEG with the -`JpegImagePlugin.get_sampling(im)` function. +:func:`.JpegImagePlugin.get_sampling` function. In JPEG compressed data a JPEG marker is used instead of an EXIF tag. (ref.: https://www.exiv2.org/tags.html) @@ -64,7 +64,7 @@ The tables format between im.quantization and quantization in presets differ in 3. The zigzag order is remove in the preset (needed by libjpeg >= 6a). You can convert the dict format to the preset format with the -`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function. +:func:`.JpegImagePlugin.convert_dict_qtables()` function. Libjpeg ref.: https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index 03add47f4..9dd6e9f32 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -27,7 +27,9 @@ import io import struct from . import Image, ImageFile -from ._binary import i8, i16le as i16, o16le as o16 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import o16le as o16 # # read MSP files diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index 3d96b481b..c1bd933d3 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -25,7 +25,7 @@ from . import EpsImagePlugin class PSDraw: """ - Sets up printing to the given file. If **fp** is omitted, + Sets up printing to the given file. If ``fp`` is omitted, :py:data:`sys.stdout` is assumed. """ diff --git a/src/PIL/PalmImagePlugin.py b/src/PIL/PalmImagePlugin.py index 553fe7b77..700f10e3f 100644 --- a/src/PIL/PalmImagePlugin.py +++ b/src/PIL/PalmImagePlugin.py @@ -8,7 +8,8 @@ ## from . import Image, ImageFile -from ._binary import o8, o16be as o16b +from ._binary import o8 +from ._binary import o16be as o16b # fmt: off _Palm8BitColormapValues = ( diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py index f8836ad88..6a4eb22a6 100644 --- a/src/PIL/PcfFontFile.py +++ b/src/PIL/PcfFontFile.py @@ -19,7 +19,11 @@ import io from . import FontFile, Image -from ._binary import i8, i16be as b16, i16le as l16, i32be as b32, i32le as l32 +from ._binary import i8 +from ._binary import i16be as b16 +from ._binary import i16le as l16 +from ._binary import i32be as b32 +from ._binary import i32le as l32 # -------------------------------------------------------------------- # declarations diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 0306237dc..767f9945a 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -29,7 +29,10 @@ import io import logging from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16le as i16, o8, o16le as o16 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 logger = logging.getLogger(__name__) diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 9583f704a..36c8fb849 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -121,6 +121,7 @@ def _save(im, fp, filename, save_all=False): bits = 8 params = None + decode = None if im.mode == "1": filter = "ASCIIHexDecode" @@ -150,6 +151,7 @@ def _save(im, fp, filename, save_all=False): filter = "DCTDecode" colorspace = PdfParser.PdfName("DeviceCMYK") procset = "ImageC" # color images + decode = [1, 0, 1, 0, 1, 0, 1, 0] else: raise ValueError(f"cannot save mode {im.mode}") @@ -189,6 +191,7 @@ def _save(im, fp, filename, save_all=False): Height=height, # * 72.0 / resolution, Filter=PdfParser.PdfName(filter), BitsPerComponent=bits, + Decode=decode, DecodeParams=params, ColorSpace=colorspace, ) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 9900a5e12..bcc6b1c98 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -39,7 +39,12 @@ import warnings import zlib from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence -from ._binary import i8, i16be as i16, i32be as i32, o8, o16be as o16, o32be as o32 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 +from ._binary import o16be as o16 +from ._binary import o32be as o32 logger = logging.getLogger(__name__) @@ -509,10 +514,11 @@ class PngStream(ChunkStream): v = b"" if k: k = k.decode("latin-1", "strict") - v = v.decode("latin-1", "replace") + v_str = v.decode("latin-1", "replace") - self.im_info[k] = self.im_text[k] = v - self.check_text_memory(len(v)) + self.im_info[k] = v if k == "exif" else v_str + self.im_text[k] = v_str + self.check_text_memory(len(v_str)) return s @@ -1102,7 +1108,10 @@ def _write_multiple_frames(im, fp, chunk, rawmode): # animation control chunk( - fp, b"acTL", o32(len(im_frames)), o32(loop), # 0: num_frames # 4: num_plays + fp, + b"acTL", + o32(len(im_frames)), # 0: num_frames + o32(loop), # 4: num_plays ) # default image IDAT (if it exists) @@ -1147,7 +1156,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode): else: fdat_chunks = _fdat(fp, chunk, seq_num) ImageFile._save( - im_frame, fdat_chunks, [("zip", (0, 0) + im_frame.size, 0, rawmode)], + im_frame, + fdat_chunks, + [("zip", (0, 0) + im_frame.size, 0, rawmode)], ) seq_num = fdat_chunks.seq_num diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index 80bc116fc..8d1dbf2b2 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -19,7 +19,9 @@ import io from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16be as i16, i32be as i32 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 MODES = { # (photoshop mode, bits) -> (pil mode, required channels) diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index 612ebe5a8..f878fefa9 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -26,7 +26,9 @@ import os import struct from . import Image, ImageFile -from ._binary import i8, i16be as i16, o8 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import o8 def _accept(prefix): diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index d3f780d4d..69b3e0678 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -20,7 +20,10 @@ import warnings from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16le as i16, o8, o16le as o16 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 # # -------------------------------------------------------------------- diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 453cc8660..6e1c45636 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -286,7 +286,7 @@ _write_dispatch = {} class IFDRational(Rational): - """ Implements a rational class where 0/0 is a legal value to match + """Implements a rational class where 0/0 is a legal value to match the in the wild use of exif rationals. e.g., DigitalZoomRatio - 0.00/0.00 indicates that no digital zoom was used @@ -353,6 +353,8 @@ class IFDRational(Rational): return self._val.__hash__() def __eq__(self, other): + if isinstance(other, IFDRational): + other = other._val return self._val == other def _delegate(op): @@ -414,7 +416,7 @@ class ImageFileDirectory_v2(MutableMapping): The tiff metadata type of each item is stored in a dictionary of tag types in - `~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types + :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types are read from a tiff file, guessed from the type added, or added manually. @@ -883,7 +885,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): ('Some Data',) Also contains a dictionary of tag types as read from the tiff image file, - `~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`. + :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`. Values are returned as a tuple. @@ -897,9 +899,13 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): tags = property(lambda self: self._tags_v1) tagdata = property(lambda self: self._tagdata) + # defined in ImageFileDirectory_v2 + tagtype: dict + """Dictionary of tag types""" + @classmethod def from_v2(cls, original): - """ Returns an + """Returns an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` instance with the same data as is contained in the original :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` @@ -916,7 +922,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): return ifd def to_v2(self): - """ Returns an + """Returns an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` instance with the same data as is contained in the original :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` @@ -1086,8 +1092,8 @@ class TiffImageFile(ImageFile.ImageFile): self._close_exclusive_fp_after_loading = True def _load_libtiff(self): - """ Overload method triggered when we detect a compressed tiff - Calls out to libtiff """ + """Overload method triggered when we detect a compressed tiff + Calls out to libtiff""" Image.Image.load(self) diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 024222c9b..87847a107 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -20,7 +20,10 @@ # http://wvware.sourceforge.net/caolan/ora-wmf.html from . import Image, ImageFile -from ._binary import i16le as word, i32le as dword, si16le as short, si32le as _long +from ._binary import i16le as word +from ._binary import i32le as dword +from ._binary import si16le as short +from ._binary import si32le as _long _handler = None diff --git a/src/PIL/features.py b/src/PIL/features.py index d2e861372..fa86b9cb0 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -237,10 +237,12 @@ def pilinfo(out=None, supported_formats=True): print(f" {py_version.strip()}", file=out) print("-" * 68, file=out) print( - f"Python modules loaded from {os.path.dirname(Image.__file__)}", file=out, + f"Python modules loaded from {os.path.dirname(Image.__file__)}", + file=out, ) print( - f"Binary modules loaded from {os.path.dirname(Image.core.__file__)}", file=out, + f"Binary modules loaded from {os.path.dirname(Image.core.__file__)}", + file=out, ) print("-" * 68, file=out) diff --git a/src/decode.c b/src/decode.c index 25ce7316b..f60fb490e 100644 --- a/src/decode.c +++ b/src/decode.c @@ -807,7 +807,7 @@ PyImaging_XbmDecoderNew(PyObject* self, PyObject* args) #ifdef HAVE_LIBZ -#include "Zip.h" +#include "ZipCodecs.h" PyObject* PyImaging_ZipDecoderNew(PyObject* self, PyObject* args) diff --git a/src/encode.c b/src/encode.c index d64f47d2b..62a6ec387 100644 --- a/src/encode.c +++ b/src/encode.c @@ -578,7 +578,7 @@ PyImaging_XbmEncoderNew(PyObject* self, PyObject* args) #ifdef HAVE_LIBZ -#include "Zip.h" +#include "ZipCodecs.h" PyObject* PyImaging_ZipEncoderNew(PyObject* self, PyObject* args) diff --git a/src/libImaging/Zip.h b/src/libImaging/ZipCodecs.h similarity index 100% rename from src/libImaging/Zip.h rename to src/libImaging/ZipCodecs.h diff --git a/src/libImaging/ZipDecode.c b/src/libImaging/ZipDecode.c index b0f8ad326..a09ee82f7 100644 --- a/src/libImaging/ZipDecode.c +++ b/src/libImaging/ZipDecode.c @@ -20,7 +20,7 @@ #ifdef HAVE_LIBZ -#include "Zip.h" +#include "ZipCodecs.h" static const int OFFSET[] = { 7, 3, 3, 1, 1, 0, 0 }; static const int STARTING_COL[] = { 0, 4, 0, 2, 0, 1, 0 }; diff --git a/src/libImaging/ZipEncode.c b/src/libImaging/ZipEncode.c index 84ccb14ea..4e862af57 100644 --- a/src/libImaging/ZipEncode.c +++ b/src/libImaging/ZipEncode.c @@ -19,7 +19,7 @@ #ifdef HAVE_LIBZ -#include "Zip.h" +#include "ZipCodecs.h" int ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) diff --git a/winbuild/appveyor_build_msys2.sh b/winbuild/appveyor_build_msys2.sh deleted file mode 100644 index 489f9411e..000000000 --- a/winbuild/appveyor_build_msys2.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -cd /c/pillow && /mingw32/$EXECUTABLE setup.py install diff --git a/winbuild/appveyor_install_msys2_deps.sh b/winbuild/appveyor_install_msys2_deps.sh deleted file mode 100644 index 4cc01082d..000000000 --- a/winbuild/appveyor_install_msys2_deps.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -mkdir /var/cache/pacman/pkg -pacman -S --noconfirm mingw32/mingw-w64-i686-python3-pip \ - mingw32/mingw-w64-i686-python3-setuptools \ - mingw32/mingw-w64-i686-python3-pytest \ - mingw32/mingw-w64-i686-python3-pytest-cov \ - mingw-w64-i686-libjpeg-turbo \ - mingw-w64-i686-libimagequant - -C:/msys64/mingw32/bin/python3 -m pip install --upgrade pip - -/mingw32/bin/pip install olefile -/mingw32/bin/pip3 install olefile diff --git a/winbuild/build.rst b/winbuild/build.rst index aaed2c43f..ba568a030 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -81,6 +81,9 @@ Pillow for the selected version of Python. ``winbuild\build\build_pillow.cmd bdist_wheel`` will build wheels instead of installing Pillow. +You can also use ``winbuild\build\build_pillow.cmd --inplace develop`` to build +and install Pillow in develop mode (instead of ``pip install --editable``). + Testing Pillow -------------- diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index ec292b14d..e05ac1a29 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -205,7 +205,7 @@ deps = { # retarget to default toolset (selected by vcvarsall.bat) "v141": "$(DefaultPlatformToolset)", # noqa: E501 # retarget to latest (selected by vcvarsall.bat) - "8.1": "$(WindowsSDKVersion)", # noqa: E501 + "10.0.17134.0": "$(WindowsSDKVersion)", # noqa: E501 } }, "build": [ @@ -251,9 +251,9 @@ deps = { "libs": [r"*.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/2.7.0.zip", - "filename": "harfbuzz-2.7.0.zip", - "dir": "harfbuzz-2.7.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/2.7.2.zip", + "filename": "harfbuzz-2.7.2.zip", + "dir": "harfbuzz-2.7.2", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), @@ -356,8 +356,8 @@ def find_msvs(): def extract_dep(url, filename): - import urllib.request import tarfile + import urllib.request import zipfile file = os.path.join(depends_dir, filename) @@ -378,10 +378,10 @@ def extract_dep(url, filename): print("Extracting " + filename) if filename.endswith(".zip"): with zipfile.ZipFile(file) as zf: - zf.extractall(build_dir) + zf.extractall(sources_dir) elif filename.endswith(".tar.gz") or filename.endswith(".tgz"): with tarfile.open(file, "r:gz") as tgz: - tgz.extractall(build_dir) + tgz.extractall(sources_dir) else: raise RuntimeError("Unknown archive type: " + filename) @@ -416,11 +416,14 @@ def build_dep(name): extract_dep(dep["url"], dep["filename"]) for patch_file, patch_list in dep.get("patch", {}).items(): - patch_file = os.path.join(build_dir, dir, patch_file.format(**prefs)) + patch_file = os.path.join(sources_dir, dir, patch_file.format(**prefs)) with open(patch_file) as f: text = f.read() for patch_from, patch_to in patch_list.items(): - text = text.replace(patch_from.format(**prefs), patch_to.format(**prefs)) + patch_from = patch_from.format(**prefs) + patch_to = patch_to.format(**prefs) + assert patch_from in text + text = text.replace(patch_from, patch_to) with open(patch_file, "w") as f: f.write(text) @@ -429,7 +432,7 @@ def build_dep(name): "@echo " + ("=" * 70), f"@echo ==== {banner:<60} ====", "@echo " + ("=" * 70), - "cd /D %s" % os.path.join(build_dir, dir), + "cd /D %s" % os.path.join(sources_dir, dir), *prefs["header"], *dep.get("build", []), *get_footer(dep), @@ -478,6 +481,7 @@ if __name__ == "__main__": "ARCHITECTURE", "x86" if struct.calcsize("P") == 4 else "x64" ) build_dir = os.environ.get("PILLOW_BUILD", os.path.join(winbuild_dir, "build")) + sources_dir = "" for arg in sys.argv[1:]: if arg == "-v": verbose = True @@ -495,6 +499,8 @@ if __name__ == "__main__": architecture = arg[15:] elif arg.startswith("--dir="): build_dir = arg[6:] + elif arg == "--srcdir": + sources_dir = os.path.sep + "src" else: raise ValueError("Unknown parameter: " + arg) @@ -525,10 +531,13 @@ if __name__ == "__main__": lib_dir = os.path.join(build_dir, "lib") # build directory for *.bin files bin_dir = os.path.join(build_dir, "bin") + # directory for storing project files + sources_dir = build_dir + sources_dir shutil.rmtree(build_dir, ignore_errors=True) - for path in [build_dir, inc_dir, lib_dir, bin_dir]: - os.makedirs(path) + os.makedirs(build_dir, exist_ok=False) + for path in [inc_dir, lib_dir, bin_dir, sources_dir]: + os.makedirs(path, exist_ok=True) prefs = { # Python paths / preferences @@ -544,6 +553,7 @@ if __name__ == "__main__": "inc_dir": inc_dir, "lib_dir": lib_dir, "bin_dir": bin_dir, + "src_dir": sources_dir, # Compilers / Tools **msvs, "cmake": "cmake.exe", # TODO find CMAKE automatically @@ -554,5 +564,6 @@ if __name__ == "__main__": print() + write_script(".gitignore", ["*"]) build_dep_all() build_pillow()