From bed2ed7438b512e4cfb18bc3a3bb18f4a53d8a87 Mon Sep 17 00:00:00 2001 From: Meithal Date: Fri, 12 Jun 2020 09:08:20 +0200 Subject: [PATCH 001/633] Fixes BLP1 picture handling --- src/PIL/BlpImagePlugin.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 5ccba37db..8cdf0aae1 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -245,7 +245,7 @@ class BlpImageFile(ImageFile.ImageFile): if self.magic == b"BLP1": decoder = "BLP1" - self.mode = "RGB" + self.mode = "RGBA" elif self.magic == b"BLP2": decoder = "BLP2" self.mode = "RGBA" if self._blp_alpha_depth else "RGB" @@ -331,7 +331,7 @@ class BLP1Decoder(_BLPBaseDecoder): except struct.error: break b, g, r, a = palette[offset] - data.extend([r, g, b]) + data.extend([r, g, b, 0xFF]) # is there a case where alpha is used? self.set_as_raw(bytes(data)) else: @@ -353,9 +353,19 @@ class BLP1Decoder(_BLPBaseDecoder): data = jpeg_header + data data = BytesIO(data) image = JpegImageFile(data) - self.tile = image.tile # :/ - self.fd = image.fp - self.mode = image.mode + image.mode = "RGBA" + image.tile = [("jpeg", (0, 0) + self.size, 0, ("RGBA", ""))] + + b, g, r, a = image.split() + if not any( + [a.getpixel((x, y)) for x in range(a.width) for y in range(a.height)] + ): + # try to unprotect completely transparent pictures + from PIL import ImageOps + + a = ImageOps.invert(a) + image = Image.merge("RGBA", (r, g, b, a)) + self.set_as_raw(image.tobytes()) class BLP2Decoder(_BLPBaseDecoder): From 096d2bf64a6d5d738c84cca7b57e19a0c468f14f Mon Sep 17 00:00:00 2001 From: Meithal Date: Fri, 12 Jun 2020 18:48:15 +0200 Subject: [PATCH 002/633] Coverage change fix --- src/PIL/BlpImagePlugin.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 8cdf0aae1..bb3a90bb8 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -357,13 +357,6 @@ class BLP1Decoder(_BLPBaseDecoder): image.tile = [("jpeg", (0, 0) + self.size, 0, ("RGBA", ""))] b, g, r, a = image.split() - if not any( - [a.getpixel((x, y)) for x in range(a.width) for y in range(a.height)] - ): - # try to unprotect completely transparent pictures - from PIL import ImageOps - - a = ImageOps.invert(a) image = Image.merge("RGBA", (r, g, b, a)) self.set_as_raw(image.tobytes()) From 6b81e34d676070e6fe519b525c27621ea6f5fe68 Mon Sep 17 00:00:00 2001 From: Piolie Date: Mon, 21 Dec 2020 00:56:30 -0300 Subject: [PATCH 003/633] Improve handling of PPM headers --- src/PIL/PpmImagePlugin.py | 76 +++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index abf4d651d..05993a7d1 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -20,7 +20,7 @@ from . import Image, ImageFile # # -------------------------------------------------------------------- -b_whitespace = b"\x20\x09\x0a\x0b\x0c\x0d" +B_WHITESPACE = b"\x20\x09\x0a\x0b\x0c\x0d" MODES = { # standard @@ -49,25 +49,39 @@ class PpmImageFile(ImageFile.ImageFile): format = "PPM" format_description = "Pbmplus image" - def _token(self, s=b""): + def _read_token(self, token=b""): + def _ignore_comment(): # ignores rest of the line; stops at CR, LF or EOF + while True: + c = self.fp.read(1) + if c in b"\r\n": + break + + while True: # read until non-whitespace is found + c = self.fp.read(1) + if c == b"#": # found comment, ignore it + _ignore_comment() + continue + if c in B_WHITESPACE: # found whitespace, ignore it + if c == b"": # reached EOF + raise EOFError("Reached EOF while reading header") + continue + break + + token += c + while True: # read until next whitespace c = self.fp.read(1) - if not c or c in b_whitespace: + if c == b"#": + _ignore_comment() + continue + if c in B_WHITESPACE: # token ended break - if c > b"\x79": - raise ValueError("Expected ASCII value, found binary") - s = s + c - if len(s) > 9: - raise ValueError("Expected int, got > 9 digits") - return s + token += c + return token def _open(self): - - # check magic - s = self.fp.read(1) - if s != b"P": - raise SyntaxError("not a PPM file") - magic_number = self._token(s) + P = self.fp.read(1) + magic_number = self._read_token(P) mode = MODES[magic_number] self.custom_mimetype = { @@ -83,29 +97,21 @@ class PpmImageFile(ImageFile.ImageFile): self.mode = rawmode = mode for ix in range(3): - while True: - while True: - s = self.fp.read(1) - if s not in b_whitespace: - break - if s == b"": - raise ValueError("File does not extend beyond magic number") - if s != b"#": - break - s = self.fp.readline() - s = int(self._token(s)) - if ix == 0: - xsize = s - elif ix == 1: - ysize = s + try: # check token sanity + token = int(self._read_token()) + except ValueError: + raise SyntaxError("Non-decimal-ASCII found in header") + if ix == 0: # token is the x size + xsize = token + elif ix == 1: # token is the y size + ysize = token if mode == "1": break - elif ix == 2: - # maxgrey - if s > 255: + elif ix == 2: # token is maxval + if token > 255: if not mode == "L": - raise ValueError(f"Too many colors for band: {s}") - if s < 2 ** 16: + raise SyntaxError(f"Too many colors for band: {token}") + if token < 2 ** 16: self.mode = "I" rawmode = "I;16B" else: From 699afe1e8915c8332e1116fbd08ef972deb9af35 Mon Sep 17 00:00:00 2001 From: Piolie Date: Mon, 21 Dec 2020 01:15:49 -0300 Subject: [PATCH 004/633] Improve PPM tests --- Tests/test_file_ppm.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index e7c3fb06f..9429f1e2a 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -1,6 +1,6 @@ import pytest -from PIL import Image +from PIL import Image, UnidentifiedImageError from .helper import assert_image_equal, assert_image_similar, hopper @@ -50,12 +50,30 @@ def test_pnm(tmp_path): assert_image_equal(im, reloaded) +def test_header_with_comments(tmp_path): + path = str(tmp_path / "temp.ppm") + with open(path, "wb") as f: + f.write(b"P6 #comment\n#comment\r12#comment\r8\n128 #comment\n255\n") + + with Image.open(path) as im: + assert im.size == (128, 128) + + +def test_nondecimal_header(tmp_path): + path = str(tmp_path / "temp.djvurle") + with open(path, "wb") as f: + f.write(b"P6\n128\x00") + + with pytest.raises(UnidentifiedImageError): + Image.open(path) + + def test_truncated_file(tmp_path): path = str(tmp_path / "temp.pgm") with open(path, "w") as f: f.write("P6") - with pytest.raises(ValueError): + with pytest.raises(UnidentifiedImageError): Image.open(path) @@ -65,7 +83,7 @@ def test_neg_ppm(): # has been removed. The default opener doesn't accept negative # sizes. - with pytest.raises(OSError): + with pytest.raises(UnidentifiedImageError): Image.open("Tests/images/negative_size.ppm") From d2ad27d70a00efa98560cf5036326abcae850d5e Mon Sep 17 00:00:00 2001 From: Piolie Date: Mon, 4 Jan 2021 01:49:19 -0300 Subject: [PATCH 005/633] Correctly check magic number --- src/PIL/PpmImagePlugin.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 05993a7d1..b9837a0fe 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -49,6 +49,16 @@ class PpmImageFile(ImageFile.ImageFile): format = "PPM" format_description = "Pbmplus image" + def _read_magic(self, s=b""): + while True: # read until next whitespace + c = self.fp.read(1) + if c in B_WHITESPACE: + break + s = s + c + if len(s) > 6: # exceeded max magic number length + break + return s + def _read_token(self, token=b""): def _ignore_comment(): # ignores rest of the line; stops at CR, LF or EOF while True: @@ -80,9 +90,11 @@ class PpmImageFile(ImageFile.ImageFile): return token def _open(self): - P = self.fp.read(1) - magic_number = self._read_token(P) - mode = MODES[magic_number] + magic_number = self._read_magic() + try: + mode = MODES[magic_number] + except KeyError: + raise SyntaxError("Not a PPM image file") from None self.custom_mimetype = { b"P4": "image/x-portable-bitmap", From 4dbe244e42cd7225ef3b77b3d592e3dd522e4386 Mon Sep 17 00:00:00 2001 From: Piolie Date: Wed, 6 Jan 2021 01:07:14 -0300 Subject: [PATCH 006/633] Add token limit --- src/PIL/PpmImagePlugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index b9837a0fe..6ece914c3 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -87,6 +87,8 @@ class PpmImageFile(ImageFile.ImageFile): if c in B_WHITESPACE: # token ended break token += c + if len(token) > 9: + raise ValueError(f"Token too long: {token}") return token def _open(self): @@ -109,8 +111,9 @@ class PpmImageFile(ImageFile.ImageFile): self.mode = rawmode = mode for ix in range(3): + token = self._read_token() try: # check token sanity - token = int(self._read_token()) + token = int(token) except ValueError: raise SyntaxError("Non-decimal-ASCII found in header") if ix == 0: # token is the x size From 5d0ad5e2e9a5065d69a00890342334f374277d1c Mon Sep 17 00:00:00 2001 From: Piolie Date: Wed, 6 Jan 2021 01:15:07 -0300 Subject: [PATCH 007/633] Revert exception types to `ValueError` --- Tests/test_file_ppm.py | 6 +++--- src/PIL/PpmImagePlugin.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 9429f1e2a..21a810e30 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -64,7 +64,7 @@ def test_nondecimal_header(tmp_path): with open(path, "wb") as f: f.write(b"P6\n128\x00") - with pytest.raises(UnidentifiedImageError): + with pytest.raises(ValueError): Image.open(path) @@ -73,7 +73,7 @@ def test_truncated_file(tmp_path): with open(path, "w") as f: f.write("P6") - with pytest.raises(UnidentifiedImageError): + with pytest.raises(ValueError): Image.open(path) @@ -83,7 +83,7 @@ def test_neg_ppm(): # has been removed. The default opener doesn't accept negative # sizes. - with pytest.raises(UnidentifiedImageError): + with pytest.raises(OSError): Image.open("Tests/images/negative_size.ppm") diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 6ece914c3..efd845d3b 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -73,7 +73,7 @@ class PpmImageFile(ImageFile.ImageFile): continue if c in B_WHITESPACE: # found whitespace, ignore it if c == b"": # reached EOF - raise EOFError("Reached EOF while reading header") + raise ValueError("Reached EOF while reading header") continue break @@ -115,7 +115,7 @@ class PpmImageFile(ImageFile.ImageFile): try: # check token sanity token = int(token) except ValueError: - raise SyntaxError("Non-decimal-ASCII found in header") + raise ValueError(f"Non-decimal-ASCII found in header: {token}") if ix == 0: # token is the x size xsize = token elif ix == 1: # token is the y size @@ -125,7 +125,7 @@ class PpmImageFile(ImageFile.ImageFile): elif ix == 2: # token is maxval if token > 255: if not mode == "L": - raise SyntaxError(f"Too many colors for band: {token}") + raise ValueError(f"Too many colors for band: {token}") if token < 2 ** 16: self.mode = "I" rawmode = "I;16B" @@ -156,7 +156,7 @@ def _save(im, fp, filename): elif im.mode == "RGBA": rawmode, head = "RGB", b"P6" else: - raise OSError(f"cannot write mode {im.mode} as PPM") + 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") From 002e0bd6975974288eb46e1609ba319699dc44ba Mon Sep 17 00:00:00 2001 From: Piolie Date: Wed, 6 Jan 2021 01:21:35 -0300 Subject: [PATCH 008/633] Add tests for token size and wrong magic number --- Tests/test_file_ppm.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 21a810e30..0ea6b277e 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -49,6 +49,13 @@ def test_pnm(tmp_path): with Image.open(f) as reloaded: assert_image_equal(im, reloaded) +def test_not_ppm(tmp_path): + path = str(tmp_path / "temp.djvurle") + with open(path, "wb") as f: + f.write(b"PyXX") + + with pytest.raises(UnidentifiedImageError): + Image.open(path) def test_header_with_comments(tmp_path): path = str(tmp_path / "temp.ppm") @@ -67,6 +74,13 @@ def test_nondecimal_header(tmp_path): with pytest.raises(ValueError): Image.open(path) +def test_token_too_long(tmp_path): + path = str(tmp_path / "temp.djvurle") + with open(path, "wb") as f: + f.write(b"P6\n 0123456789") + + with pytest.raises(ValueError): + Image.open(path) def test_truncated_file(tmp_path): path = str(tmp_path / "temp.pgm") From 73fed77c0c58efd2ce11b8c0e96858bd03655163 Mon Sep 17 00:00:00 2001 From: Piolie Date: Wed, 6 Jan 2021 14:46:30 -0300 Subject: [PATCH 009/633] Suppress exception context --- src/PIL/PpmImagePlugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index efd845d3b..8bd7d16db 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -115,7 +115,9 @@ class PpmImageFile(ImageFile.ImageFile): try: # check token sanity token = int(token) except ValueError: - raise ValueError(f"Non-decimal-ASCII found in header: {token}") + raise ValueError( + f"Non-decimal-ASCII found in header: {token}" + ) from None if ix == 0: # token is the x size xsize = token elif ix == 1: # token is the y size From bc5ecfb79c3dca0553e2b863ba7d306f2e0fd434 Mon Sep 17 00:00:00 2001 From: Piolie Date: Wed, 6 Jan 2021 14:53:30 -0300 Subject: [PATCH 010/633] Make minor changes to tests - add test for maxcolors; - extend coverage for wrong magic number; - fix linting. --- Tests/test_file_ppm.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 0ea6b277e..77798514d 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -49,14 +49,16 @@ def test_pnm(tmp_path): with Image.open(f) as reloaded: assert_image_equal(im, reloaded) + def test_not_ppm(tmp_path): path = str(tmp_path / "temp.djvurle") with open(path, "wb") as f: - f.write(b"PyXX") + f.write(b"PyInvalid") with pytest.raises(UnidentifiedImageError): Image.open(path) + def test_header_with_comments(tmp_path): path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: @@ -74,6 +76,7 @@ def test_nondecimal_header(tmp_path): with pytest.raises(ValueError): Image.open(path) + def test_token_too_long(tmp_path): path = str(tmp_path / "temp.djvurle") with open(path, "wb") as f: @@ -82,6 +85,16 @@ def test_token_too_long(tmp_path): with pytest.raises(ValueError): Image.open(path) + +def test_too_many_colors(tmp_path): + path = str(tmp_path / "temp.djvurle") + with open(path, "wb") as f: + f.write(b"P6\n1 1\n1000\n") + + with pytest.raises(ValueError): + Image.open(path) + + def test_truncated_file(tmp_path): path = str(tmp_path / "temp.pgm") with open(path, "w") as f: From 50522d932ea93dc09db0fae52a0ebe80a5f4eb08 Mon Sep 17 00:00:00 2001 From: Piolie Date: Sun, 31 Jan 2021 00:31:32 -0300 Subject: [PATCH 011/633] Change max token size to 10 - ...so as not to reject "2,147,483,647" (2 ** 31 - 1); - reword exception message. --- src/PIL/PpmImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 8bd7d16db..5ff98e346 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -87,8 +87,8 @@ class PpmImageFile(ImageFile.ImageFile): if c in B_WHITESPACE: # token ended break token += c - if len(token) > 9: - raise ValueError(f"Token too long: {token}") + if len(token) > 10: + raise ValueError(f"Token too long in file header: {token}") return token def _open(self): From 39288f0fb0755b8ef9bbc4867bda6a19e91287fd Mon Sep 17 00:00:00 2001 From: Piolie Date: Sun, 31 Jan 2021 00:51:39 -0300 Subject: [PATCH 012/633] Create `maxval` variable --- src/PIL/PpmImagePlugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 5ff98e346..93ce3a4d2 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -125,7 +125,8 @@ class PpmImageFile(ImageFile.ImageFile): if mode == "1": break elif ix == 2: # token is maxval - if token > 255: + maxval = token + if maxval > 255: if not mode == "L": raise ValueError(f"Too many colors for band: {token}") if token < 2 ** 16: From b43654d15954a5af46ad618291b7c859549ccc63 Mon Sep 17 00:00:00 2001 From: Piolie Date: Sun, 10 Jan 2021 18:45:46 -0300 Subject: [PATCH 013/633] Change variable name in `_read_magic()` --- src/PIL/PpmImagePlugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 93ce3a4d2..e25b4bcec 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -49,15 +49,15 @@ class PpmImageFile(ImageFile.ImageFile): format = "PPM" format_description = "Pbmplus image" - def _read_magic(self, s=b""): + def _read_magic(self, magic=b""): while True: # read until next whitespace c = self.fp.read(1) if c in B_WHITESPACE: break - s = s + c - if len(s) > 6: # exceeded max magic number length + magic += c + if len(magic) > 6: # exceeded max magic number length break - return s + return magic def _read_token(self, token=b""): def _ignore_comment(): # ignores rest of the line; stops at CR, LF or EOF From b6f6fba8cf170e60619da81ff1a24c2ef4364be5 Mon Sep 17 00:00:00 2001 From: Piolie Date: Wed, 13 Jan 2021 18:45:29 -0300 Subject: [PATCH 014/633] Rewrite `_ignore_comment` as one-liner --- src/PIL/PpmImagePlugin.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index e25b4bcec..95987b4a4 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -61,10 +61,8 @@ class PpmImageFile(ImageFile.ImageFile): def _read_token(self, token=b""): def _ignore_comment(): # ignores rest of the line; stops at CR, LF or EOF - while True: - c = self.fp.read(1) - if c in b"\r\n": - break + while self.fp.read(1) not in b"\r\n": + pass while True: # read until non-whitespace is found c = self.fp.read(1) From 41a439da7d7c38e54660011abee948d177f67126 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Mar 2021 14:42:36 +1100 Subject: [PATCH 015/633] Added context managers --- Tests/test_file_ppm.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 77798514d..94fbc62a2 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -56,7 +56,8 @@ def test_not_ppm(tmp_path): f.write(b"PyInvalid") with pytest.raises(UnidentifiedImageError): - Image.open(path) + with Image.open(path): + pass def test_header_with_comments(tmp_path): @@ -74,7 +75,8 @@ def test_nondecimal_header(tmp_path): f.write(b"P6\n128\x00") with pytest.raises(ValueError): - Image.open(path) + with Image.open(path): + pass def test_token_too_long(tmp_path): @@ -83,7 +85,8 @@ def test_token_too_long(tmp_path): f.write(b"P6\n 0123456789") with pytest.raises(ValueError): - Image.open(path) + with Image.open(path): + pass def test_too_many_colors(tmp_path): @@ -92,7 +95,8 @@ def test_too_many_colors(tmp_path): f.write(b"P6\n1 1\n1000\n") with pytest.raises(ValueError): - Image.open(path) + with Image.open(path): + pass def test_truncated_file(tmp_path): From 8ad5172e8858b5dd023612bbacb37f066b577012 Mon Sep 17 00:00:00 2001 From: Piolie Date: Sun, 21 Mar 2021 02:16:39 -0300 Subject: [PATCH 016/633] Fix wrong extension in temp test files --- Tests/test_file_ppm.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 94fbc62a2..ecc3401b5 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -51,7 +51,7 @@ def test_pnm(tmp_path): def test_not_ppm(tmp_path): - path = str(tmp_path / "temp.djvurle") + path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"PyInvalid") @@ -70,7 +70,7 @@ def test_header_with_comments(tmp_path): def test_nondecimal_header(tmp_path): - path = str(tmp_path / "temp.djvurle") + path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"P6\n128\x00") @@ -80,7 +80,7 @@ def test_nondecimal_header(tmp_path): def test_token_too_long(tmp_path): - path = str(tmp_path / "temp.djvurle") + path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"P6\n 0123456789") @@ -90,7 +90,7 @@ def test_token_too_long(tmp_path): def test_too_many_colors(tmp_path): - path = str(tmp_path / "temp.djvurle") + path = str(tmp_path / "temp.ppm") with open(path, "wb") as f: f.write(b"P6\n1 1\n1000\n") From 9c2cbcf4520fe92d3d6b70bda13e3b1078ff73b1 Mon Sep 17 00:00:00 2001 From: Piolie Date: Mon, 22 Mar 2021 13:06:16 -0300 Subject: [PATCH 017/633] Keep case consistency in error messages Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/PpmImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 95987b4a4..725ddec10 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -157,7 +157,7 @@ def _save(im, fp, filename): elif im.mode == "RGBA": rawmode, head = "RGB", b"P6" else: - raise OSError(f"Cannot write mode {im.mode} as PPM") + 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") From 35107e96375346e84c1f0d1e90ff6149fbe06a08 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 14 Apr 2021 20:01:56 +1000 Subject: [PATCH 018/633] Changed failure to create decoder to OSError for Parser --- Tests/test_imagefile.py | 13 +++++++++++++ src/_webp.c | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index b4107e8e3..862da5a1b 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -82,6 +82,19 @@ class TestImageFile: p.feed(data) assert (48, 48) == p.image.size + @skip_unless_feature("webp") + @skip_unless_feature("webp_anim") + def test_incremental_webp(self): + with ImageFile.Parser() as p: + with open("Tests/images/hopper.webp", "rb") as f: + p.feed(f.read(1024)) + + # Check that insufficient data was given in the first feed + assert not p.image + + p.feed(f.read()) + assert (128, 128) == p.image.size + @skip_unless_feature("zlib") def test_safeblock(self): im1 = hopper() diff --git a/src/_webp.c b/src/_webp.c index 4d51d99df..0c2dacadc 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -395,7 +395,7 @@ _anim_decoder_new(PyObject *self, PyObject *args) { } PyObject_Del(decp); } - PyErr_SetString(PyExc_RuntimeError, "could not create decoder object"); + PyErr_SetString(PyExc_OSError, "could not create decoder object"); return NULL; } From fa66d150e24d39b718ebd570e9f184f9868d5932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Komar=C4=8Devi=C4=87?= <4973094+kmilos@users.noreply.github.com> Date: Mon, 31 May 2021 12:18:34 +0200 Subject: [PATCH 019/633] Enable strip chopping for large TIFFs --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 46bd101b5..38deb5360 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -543,7 +543,7 @@ ImagingLibTiffDecode( Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes) { TIFFSTATE *clientstate = (TIFFSTATE *)state->context; char *filename = "tempfile.tif"; - char *mode = "r"; + char *mode = "rC"; TIFF *tiff; uint16_t photometric = 0; // init to not PHOTOMETRIC_YCBCR uint16_t compression; From c0f619384cbccef86185b1cc1b4da9755b1e344b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 4 Jul 2021 12:33:55 +1000 Subject: [PATCH 020/633] Added "exif" keyword argument to save Image.Exif instance --- Tests/test_file_tiff.py | 17 +++++++++++++---- docs/handbook/image-file-formats.rst | 5 +++++ src/PIL/TiffImagePlugin.py | 18 ++++++++++++++++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 57f45bd09..94a3851c9 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -403,10 +403,8 @@ class TestFileTiff: with Image.open("Tests/images/ifd_tag_type.tiff") as im: assert 0x8825 in im.tag_v2 - def test_exif(self): - with Image.open("Tests/images/ifd_tag_type.tiff") as im: - exif = im.getexif() - + def test_exif(self, tmp_path): + def check_exif(exif): assert sorted(exif.keys()) == [ 256, 257, @@ -439,6 +437,17 @@ class TestFileTiff: assert gps[0] == b"\x03\x02\x00\x00" assert gps[18] == "WGS-84" + outfile = str(tmp_path / "temp.tif") + with Image.open("Tests/images/ifd_tag_type.tiff") as im: + exif = im.getexif() + check_exif(exif) + + im.save(outfile, exif=exif) + + with Image.open(outfile) as im: + exif = im.getexif() + check_exif(exif) + def test_exif_frames(self): # Test that EXIF data can change across frames with Image.open("Tests/images/g4-multi.tiff") as im: diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 5d4e83494..1b65ef236 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -898,6 +898,11 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum require a matching type in :py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype` tagtype. +**exif** + Alternate keyword to "tiffinfo", for consistency with other formats. + + .. versionadded:: 8.4.0 + **compression** A string containing the desired compression method for the file. (valid only with libtiff installed) Valid compression diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a5e2bb53d..7a581bd31 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1142,6 +1142,17 @@ class TiffImageFile(ImageFile.ImageFile): if not self.is_animated: self._close_exclusive_fp_after_loading = True + # reset buffered io handle in case fp + # was passed to libtiff, invalidating the buffer + self.fp.tell() + + # load IFD data from fp before it is closed + exif = self.getexif() + for key in TiffTags.TAGS_V2_GROUPS.keys(): + if key not in exif: + continue + exif.get_ifd(key) + def _load_libtiff(self): """Overload method triggered when we detect a compressed tiff Calls out to libtiff""" @@ -1504,12 +1515,15 @@ def _save(im, fp, filename): ifd[IMAGELENGTH] = im.size[1] # write any arbitrary tags passed in as an ImageFileDirectory - info = im.encoderinfo.get("tiffinfo", {}) + info = im.encoderinfo.get("tiffinfo", im.encoderinfo.get("exif", {})) logger.debug("Tiffinfo Keys: %s" % list(info)) if isinstance(info, ImageFileDirectory_v1): info = info.to_v2() for key in info: - ifd[key] = info.get(key) + if isinstance(info, Image.Exif) and key in TiffTags.TAGS_V2_GROUPS.keys(): + ifd[key] = info.get_ifd(key) + else: + ifd[key] = info.get(key) try: ifd.tagtype[key] = info.tagtype[key] except Exception: From 9707d33ed979433211af1934f0a07568d9809548 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 4 Jul 2021 13:32:41 +1000 Subject: [PATCH 021/633] Allow "exif" to also accept bytestring --- Tests/test_file_tiff.py | 7 +++++++ src/PIL/TiffImagePlugin.py | 11 ++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 94a3851c9..4d4857340 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -444,10 +444,17 @@ class TestFileTiff: im.save(outfile, exif=exif) + outfile2 = str(tmp_path / "temp2.tif") with Image.open(outfile) as im: exif = im.getexif() check_exif(exif) + im.save(outfile2, exif=exif.tobytes()) + + with Image.open(outfile2) as im: + exif = im.getexif() + check_exif(exif) + def test_exif_frames(self): # Test that EXIF data can change across frames with Image.open("Tests/images/g4-multi.tiff") as im: diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 7a581bd31..b531e333c 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1515,7 +1515,16 @@ def _save(im, fp, filename): ifd[IMAGELENGTH] = im.size[1] # write any arbitrary tags passed in as an ImageFileDirectory - info = im.encoderinfo.get("tiffinfo", im.encoderinfo.get("exif", {})) + if "tiffinfo" in im.encoderinfo: + info = im.encoderinfo["tiffinfo"] + elif "exif" in im.encoderinfo: + info = im.encoderinfo["exif"] + if isinstance(info, bytes): + exif = Image.Exif() + exif.load(info) + info = exif + else: + info = {} logger.debug("Tiffinfo Keys: %s" % list(info)) if isinstance(info, ImageFileDirectory_v1): info = info.to_v2() From 919f38e3d9b13aef38b87a7e08ce8ba879be7cf3 Mon Sep 17 00:00:00 2001 From: Meithal Date: Mon, 5 Jul 2021 00:20:15 +0200 Subject: [PATCH 022/633] Try test --- Tests/images/blp/blp1_jpeg.jpg | Bin 0 -> 1651 bytes Tests/images/blp/blp1_jpeg.png | Bin 0 -> 270 bytes Tests/images/blp/war3mapMap.blp | Bin 0 -> 19430 bytes Tests/images/blp/war3mapMap.jpg | Bin 0 -> 1651 bytes Tests/images/blp/war3mapMap.png | Bin 0 -> 270 bytes Tests/test_file_blp.py | 6 ++++++ src/PIL/BlpImagePlugin.py | 14 ++++++++++++-- 7 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 Tests/images/blp/blp1_jpeg.jpg create mode 100644 Tests/images/blp/blp1_jpeg.png create mode 100644 Tests/images/blp/war3mapMap.blp create mode 100644 Tests/images/blp/war3mapMap.jpg create mode 100644 Tests/images/blp/war3mapMap.png diff --git a/Tests/images/blp/blp1_jpeg.jpg b/Tests/images/blp/blp1_jpeg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c045c5945c1071f3d4bd4e3f0d878cec6899bc17 GIT binary patch literal 1651 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<uA z0}~@NGZPClD=P~NP<1U(o`FS>RY=j$kxe)-kzJ`!#HexNLJno8jR!@8E`CrkPAY2R z|V^&07y2J$~}^+4C1KUw!=a`ODXD-+%o41@afjpD+ON7@EHXf&OA*VPR%r2lxYE#}NLy#lXYN2#h>tK?Zwfl gP32wyWDa4_AkC=Dz<6e<1v|(Sp00i_>zopr0LjD?Hvj+t literal 0 HcmV?d00001 diff --git a/Tests/images/blp/war3mapMap.blp b/Tests/images/blp/war3mapMap.blp new file mode 100644 index 0000000000000000000000000000000000000000..03afb3aa5919a856946c916056f89a522ad82692 GIT binary patch literal 19430 zcmeIaXH-*N+b+67RZvj`DI%bNR0RbT5C|4PKzi>8A|+C!7a_rhQbehWKtxLDL3$60 zfOP2s(tEF=rR^2a=Y7BT+k205zF+6ZVGKwzGuK+P-1BbNb#v>U))nCI4+MMz{!;(> zoIv&Wi~rfHs`CHry1!SsQUCzy7ikPQsj8%+1VF%{LCE7H5dk3;XAj#)0y=`+s#=Q5 z-1i);-R`^jig0_`y16^JICEbSkra^-5x)kIhJf1u(+LJfhT}|(CmC6onOHfmp6BG? z;FJ@&%zITu;hvhZf|8<^iMzFyzLSBX(t}sFPTu}OPlGh9!sEgMV%-CSo{(<>VP;|B zWaqqb{``$6x=OlF{$Kx)zC%8P6QG71{QR9Ggo2WanuhksQ9AmQ00o4Sl7fnonwkn+ zc}M{G9YA%8n(_RVn>44j@6%p*%yc#AUGfp0TVK91>vXR1id(xqJxX_mg_Vt+?;^i| zppb;*wd+#SGPmz2DkpCHn*_bJG*=P5PBh5&A8k2<9Lad@0n;- zH|OBTId4H=3P38r1TdPy^Gt@0<9!lYxpp$1kO1z>g;~eG)aK1=h?#YHDQD}X3M$9D z!~h*diiCxToUTRD)|SPxz(PDL?cfFC&b->9gwuN9F-!Bhp-FO^4cMl+sE7Hu#(>r$ z!oMN{K8vnuW8*E`cCQe;8So_F8Kg_fD0H@zX;D91=4xgDI+E2{cVsU-gQmjT!VPj**V1BO*M+8WO zYkiSOjJt;Zapv0doa%J+kE1dn{hrA+iwakVs(qOQkNSz%#%W#pMFO&*tA-ld#AVTS zVsKgvys`yv`RR;bzZc=nG7iwg+BuZh7vQfEgHd9100jd0bTA1D1m!Jx9t=p?S;C6$=FG z=dV_DqRz?wo)l>$U@cH`#tyo%IJ?j3>_P%A*H{@oBe)S@aS-Ar*J5V<3liXiBJ$E8 zRd>7={BfQ@3XG-?_+0yE#kqE*@fh)sT*Uc*TkyAk7o6HasP@@Y!3K@1>NlM9tR$MUnP33+euUByUUZN2qAeV3^B2TZR6ZZ_Knp?vf zP^;#0CHie0O-Y@h=YyV@co|n8l!Z=Mm%8vUgn4wob1UZxvfd*2vMt_|c#;D?*@)lo zG$krV5-Z}NdrTz2yw;Qpt4vrh*2Gm|zFX)MS4Gz$7M55vRx;6YQWJk~{5!l#j|7~C z6w_ES)zW@To~HBD|M@i0sKHd+?2=UPcO}MwAphnFT1r|Sj2BX;{>3lKIS{;K^S{i%WyY#_;d?0p9><+Wp@B{>zkv z8U8aYgbC#CTXyT}-W|>j;Pb-Hx!ziV#SG6Lnu?<*Gv^$Iy22gZp4XgFG{>@a3 z$JD;|D-}WAPV=bJsp-PNoNrQerwksu1wNw2$yyE*90(?H8|W&yPBI=D3jA#fui>hX zSTKT5;f-hqCWxzx%DtIz(Frc?IugJ?QEOSKNEq!4evyX9yplb^wFfd0a?DvaBr}4KJ^q^7eR$O+WI7}|zOY(&^X~9Izdc;<^4VM5#9@C_{ zeb3JEjQw%m`-t7J8M7D==;s=R!x(@pV$_TsMZ_K~Zv!Y9poaP*88QDM85gZUTQLMjoAlF>fWZqMR`8MWO+2FbYN66C4)&$o zqpBaZyzEk5qhz9O*_}H!DbNGc;MC3F2Dy05>)8OcQ+Q+uxI~y?0yrrb@}!I+rtn&{ z2i=r=7d;{-xOBh6`6m$a9ilsM6Zu@jJ^0D}Se`v76QuI+*;zT1&9)kApn7b33v_ho68TKlA4XjeyJjU#R|14oaw61^U5aBo`A3 zt)gs&ua?d#7b7=YRvBCjiGiA{z$O=Nd9QQX5iVP^wvC0)P7&7_u8@G5(L~NZ)c>X5 z(!cdftC0>EUtR!5)kGNMv>^cj7#X=-&DppS&Ji@8fFx2u4#tx**hBRF(~~@Yh5yzU zj~t;?X==)Js1@vaCfuk^_!m7Crg_^OZu)GF zIQf&KDuZlGX-hU_&YaF%e}urP#d%dkDzbrP@=wHIimg_c^c6$Gfo z4$LF(OCDhxDavXGrO@1qC2C$yFY8)PU5qL)&A*7_!okyAu%i3EyXPzikXxX)!v~RX zB#zFjEm)4?DRJV&BoZ+7iR%FgaM?;(FnIz;z#lr`E%DKmOUPwnbbuy~1b|H>pnH_u z1}C?vX-2FuEOqSr?*aptR0XXD>ecV@JU5Fj&#^NVjgfJF93N9^kblxpy5S%B$OYx2 z9B!_8n`jH|cRkZc&?W(XVJUfzjSA*a>xMj*e3qu4XmqU^dW+xoRaWFfGkjJ!CNBmH>(GlT< z$$y*^3V$dTHE+(!hnt=AvPG^kl7J)hWZh$v0B=EmFvfriD9C~Vn~lvgqs6sqh=}6q zq3hhDX2-sb6farp&$W~yGqZ&8&D&N8GQn*j}Knx2kiMrm3{y6UbB?7VmzZ zq9gR!``(c}i#uO665DJ-GIiMNoO5Ox^>bAObqgX4d+11jeH?By%{|~DdXq@Y(*Kxi zb8}6Be~|CMcXrH8+okP?p-XqUnVla{J(%*-eIxYQab+=o{7p`3d0H#F7*Wo@pT5t& zoQ-E&UMuWI6IXTdBF7ds8IIBO6J_BYxh|kz;zq1UdPV}?X<~m=#hQqb-8<=2PKg!>vTYQK*Jc8Y?&echs@%X;9E>HQU+JrY1g5%Ae^H8A>Mb`b1u z9*BqNM0H@(KrFmU4bRO&0>19{Ns~=e%tsQ?f_~3D&V4s^i>6%clJd!q z0~}Jm0{zdEUQ7I9_oc-!uMFSt!pbeCM^!lNA(G7Czb%_1QEE9nJB%5fiyB<6v~gTJ zh8t;1nIwQWaNM5`y1DYVF=arPc$e^X30&@uvVb3SJK9+pwybhec~8uxl!UZZR5LLU5L6N~3yFMCm+w zxO+6SBdorN5Pr5??kvO#!To#GhZtk@L?*o)m4h6!IDI7ZvR`}~5zv5n?Nw2(TAi0)_5(+Gler&{AR^Dj5gZvUL|lc_IPLj2S`sQQr$jeVsJB;dGTqA_xfhsaH@ ziB(=yV%XK}F?LzwBXVGnalcM^_h)ZM-bEo_&#Nm&5jz#So&O>EHAIOrY^;S!T)Jm7b?w8X~J^ zF_|b^ftoSBBnFWc%SwE9#HpLzRVg`8p0@6pbETmhtRp#XYnlxPJwIXJbhw1P;m!g2AL9@6SN*ndy#NjyIY|_ zfMD>rB1BDrr-1vs*=EATmlmET#jnL}{=XW9qxA)EEynNUGgfJrx14~bdt|naXjiUy z=+$**Uv{X!J~Ae)ukBhzm+?(v(mwmbeD@0hx39c;)N1K87AIWAU=b{QLKQIEhrZ>{ zQ$&<2R&DJTsP4WPa>V8&)9R>RF?p|(Ce*F*Xy~#Im%DLcCGj?1fdM=?La&>3V&Sey`?R# zIlc#uI1%gl$`7fURXnM}x2{$odMfk@=&a80Hp$`K=+Pn{> zbJET@}=AAu#?ADG){ID3uDg97L z(}H7+RD*`L3HIT;G>y~IlV?>{YO@a<`2kQ{mLlg|$-=uZ0{_H3_P@)LXoXF2zO3#; zdu-cP^>%CO?NCIov9?d+FwJF;S-sUamB-;h(>7vnLT8^=ag9+)1~pT~jxP2UlhZ7B z=4!#fvu<_|V!cVw+V5AozN^-=)C&6STV;eedR$_ba1e*i2RaOzCX1Oz zdY*+2VCC40l0ki15}=SpZz|6q0DC8W-_8L>$%^#$?O}ZC}m}yZ47Mu5{zbFn|TwFwL}fuY(9bS zPfWN`p_Yd_^QhaSU*DDc8NrbN`v@j{PDjua*ER8Xk6*M3G1_8YQB^(V=5nW4ZlGOa zY?lAC2|}1wOZoO4UKCSbt6-b{(`YYyy10`|H=>`Qw(>;OPbhaVuV897uap^TSai|< z4yXWH;Mf-3mqgjSWMR5Z7N*rd!X%7WerQWv!k4=g+9iV(T9az7=Nkg z)qX1Ttcp2jjH*^Hy}CK@!+N9OIwM1|ol@xM_!%cCkI1!i<2`XVBI9Cf?<)E5NM;*$ zsm?453aeO*-Ez`O2#aw1W~AygZ0^fZ(2`MptvqGw{n70^SVv1;)QF`IrqBT{LEM6n zTjWtEHo&}}Wg8f+F1=ppA)++*Qn?0-;PF;m4>Waku`>RdcP$5ca@W;X0o(<@VrXXf z>P6_%f@PZ+-`&0|FPL*>T+?%z%d6QX<{EyQuGAV(WuB8G2&H~}W_rWrS2;>RRdApf zwoM_G`~3m33MM3Ft6sV;Kg0Tn!(OGS#@8EW#W>+2VihpCul*Xm6qB+Jtu<=Z%nwgl zZ*k!kEIcF4&)a`2b!snlWn?x^y4lSws?v&2h1*`4nCZK0kq8}Yv@G!VfDg|vZW~Tf zvy!!1U)4UmAvjAmgliSdn|;TKU9x~@ahWWF_UtJ9)s{GP2h5?oqVxRj3uZ%358bJM zwnd4}3%kUos_M_ToA~~i?6joec*trG&8Q$F&1C#++eGhOHFakV7)`d4b6;fsshh;& zz~T-BnA3jU_9wSUy@cd*$0YDl7! z$j*A_#P^d5JU|BW#Zs%|qB`!-F=f2Mp#tOjtH2OXrvi6p%GcF)Ru+ahe5U$@3=OJt zsPx~pgCTj+*{AOfrUL?m(_iv^>rDEx`fKUiq^-u!%$yro1(S#0ZG)xQ@-jPO!_tcj z%IDfYhqLKfU}I{7+oFejG2Cp87O>bPp`x{s&J5n+sC$z>S*u5vbJZ`yEcOYtH@9EI ztM~N|BaZ9+FwisY_zL`W)U$AwPZ{ziScdQ?8Nc2rsx*D;xQ1P5$A~OV^o5PmhGy$j zyiiJw$06dq?I-z^q0JS-Jux!s^{~?9-q36r364*BtDpp-4k8#cq;WRBF%dfmK4X`g zWf{~Z{#z-Fl75ZZ9c!bSe4%^6u7+M z>D;kGVQ<;`N_tYV^F`L;_@s9B^`8rM`kom|DF!Z|j>SLbqKr+msB*w%8?#M(d7QF> z_VXIT*QU1`MQW(zv8lAHmuAbBx9dhlCk{ab86gUg6xQ!*Nm6Ll_+o)kW!nw83Zb6! zo_5VO3R)3lz0{G0w}-#xRAkr*frFZ`@{cBjms_BcKfo&+@d~4nN3=$sOR#QsHBUU4BL9O%6KxKO~UjjSsU2 zKHv=1NqC-RuG<)N#D?H0m5(_%1BhyOVp?|pcQBu&oJ$r@WXmQ+xGSdu}IS0PIaHz@OTvye` zN^c%W;?=%8`a+Gg(ivZSpIcAz+XbfkdiuFOPnH&_9M$4~_`0G1nd>5`9lP-i`=wWU z9OdlL9aC9UYkv~CVz-tWLyIbh3p2(tR|d}4G}lTe-zpoV380qNPM>r(%HB3*mX#E| znRhoEt7av>*e5jYO-qMl0_o}<>ueU-igXYFlr{j7wm(t^YQ{U_=1GCLXIo*Cy z=Yj?kvX-x+_ZCi9A+ER3prf8WbjRs=JnGf+cqfZN$ftnykS*j+PUljkai($JCo$f8 zSDEkb>nSC&31`%!Z%cL7wVB^KF8_m0S3u?NIpL>OOhQe{HqG_A;X)f>UJ6Qn?9HgP z={f6dD~n<=P6FMrDL%Nls$r>Kh()B7MgF)gVYCAECE&`nq^{mPzje6t3FUlu1 zt`E)wAD(lBN26~_^z)CYuVtoJ#}E~y)4~LA1pj2u>`P-UZ96A(QU9Jg>(`c~%fqM3 zn7||mh&*l1!_RO{$i^S4L`0=TprehHTy{-06n4UNU`y_iK9`GH%nivb3Z^3Iy&sLA zas|eU=uO?uf@eh&N}^5{b;HTBG*7&U$n7&bwj3TQS%DdEcJ84gGlD%*rb# zW8$32n`;FnFIgX%qD{24i=0rd7cfMIhO$B=84Nq|WRz(BgajxEefv-fVByVGu1Y%5m(azXux#mk1C!H!c zK8a_y$zCrzb06FoXom(@((Nf0 zUz=tY9Jxa=joMGa=KW?_v)ql)y>yt1dpZ>*{;+&qy<(%hF~VsA6&On)^kWvxcR{$H z%7mPDNH4t|mhd38RAY|*!X{-j0hVd=C}td;In2?PidJ3C`^5E#1lV9x7Ax>vuSfux zQ<(3cpkln+#ZfwKq@OL6w}E@3ik-DE{5bTWL_y7gSVRyuV_COMt}!OAk$`U$qa=WK z(rEUE_TIl@Rg=@N4dukm-k*uRaD9k)wEm@oVfx7LAIES1klP%rp3U;Vu_ZSX{kgkP z^V6w-B+(UOaU2N2@K4;wia_zWe}iS@l6Uj>W6v#X{zX@~49Mt8fKu>Apa|t3Sf&UQ zx6Y6;P69v-Ms(owe*<(h3+p2)_kZU1h!`tO|0&6y!+PYt~~`aAN)yw{mv^NISt`L)~WpMu$-ad z&e8X(+I5!=VU=PR-ux<{xfIr?w+04oX*bMfQxGw#j&8_HST$F^H$yfwBR;tfM<3k*B(D#!OYlOmM?6Cjath)hTe@K7%sR@ z8&+GsJ@uOeSLZ`X!;wt0_T=%Y*A=~rIx+hr|& z_F|`BL0~8Edij)GZRta}mvXh&7;VNE+G6Np@iuTX`I5@TJ2KU&5dtNKw!Eyj6 zsi2>M=-T~Jd?i@`CJ(ofj5=wP(K|@spKeO~%H(dVYk?xPypGSNS+ieAbz4l7>Z+puwBw%)jc(3-IbySO0MgB*8M=Hr*XNo1&0Gk0iScDSE968at(QDRVnzOWPN3h`IpOJ!`$Koue56>iJb z#?gHW>W^Iy3X^qucv?K4>G-8 zSrV*NLcx6J-ZEep-_DfTUHM$kx7cezQATgx@}0n_q1D*}X(QCJ^?4%T~8a*}N@osR$&fqkIK`Nh&r?iZ3KG&2P zyhF36hoE^D1PQ_X#zPqPp538L96gi?UA6z?z6U>V5Zwc1BudQ34Ak~5nPVajznLUU*o$WkTOigKC1!NbQWbm* z#Ng$W`5T42iD6S0E~17OsS#GY%4!M? z_b`;L`^zA`=Oq0>6akWqFdZO7@gJ}S3AjGSWsb&!fC}Ug7mz1k3;cIfAQ18=IAAz< zMnq08(WO?t(((X2Abh?@SRyh7JWN|{gTEE=^dD{~8Yr0SmQ(tLtcK1!w;krp;p@;4 zUqM@m!&+qd|IJO1SjGyCL~O_%dLwhuaBtTSN;nH`v%E!&yY^Yah25y}ra?IPDS6^Pp zA@sMMcrAwrUD^OGHZ~^^ZMp|P{c*vEjIN)#lw#krvN~8H7L8J6oON6+y=~$4i*^n3y{dCAeV~_+K@K~j1<7#AqEB>p~l_89B{cT zXUSZyh`qgkU3|DZhZlpKuY-QQ;6o@Ee;xGWEB%ldQYyD;dOus$slfzW-W&A2Jtu)b zFRttu*}E(+BvY-#+c4Apt^Zq^6$SPOp~dh^h(WS2+$5<++LWHm^7#YGk?BZWOqVWu zZDVrkQ1oC6&ZpP`f+SYhwvYku*R^+L#86A&;l|*EG-|h~{P}#(eJHpH^Ew z8@wF@0|`6``5t1Z+M*{k_5!gp8=sFuyi~rdVq;|ZG2y`!won?xvyG+IhlvViEclMBA0u9uAu5?#w*p~Fz!o)#$Rdw!=-e`LNRxVWfV zb4C{_VL@oBx3V=r%kmbFj45v`V@*01JXwt1lK?w?qCFzqaa?7LAgFd!Td@-5-1=cm zsNsZ4gTdVUlYO)T)Br>Q&;j^C2h$`lru!LD7+k0U5b)r%9EZ4&xj{MZ zuHDy(6-rHUVo4Tn?2G0I_%)hE!Kd>bxT>-?(lyDb?n?b$VNHNMklS<+27$-gJ-v2z{r@be$_> z+fQNdOkdMSImBFiOf6yWL0rt6gv;0T9o;m9&2y-{ zZ5SicY{OIxYT9z|iSx#u_kP@SM40j|IQ2o*NC>y5e2nC2kMyq2OUf<$M^!F%4R2E{ zqwxOdN%H|0T(s!yUQ&rAah86MXl0mmA?z-fAVHWzk>zM9vro2*Q1@cY4ds<F)ccdIda-4}N0c1By#=%YO$TB=K^R3w zy}WEOjd8mQqK7g-KJtHL;4ty~A51^$#RxDPSHr){DEG)4mrAD0!a8ITocWbuS?eaZ z!c`j9!#i_jB^|%aJilCtz}UrsoGioMEKoyFSoVD3YTEt8zFuJC+TbXp8*{^5*sML5 zJ=$dZUGX+WFKWLKn^y&*2AOjQdK2+2w&K-6aMjAf;!>6ImHVdHVnZpj-0OQs*v2BYsRsYezPs-a+&?!_!W_lyX zYzZGm&|?&Y2m+=H*;Co^hc5x))jCq%t`1W^kqPs!&hxmR|G2JX<#)*=3VW(ljgQ-qJrY#T|3CUi4*WcT1Wu-wlALcPvU6_A4q^EbVG~R)aqpB z!@Y`$?1jfM{6|$yR_$y|d{`TzKh5&DRVTh>{CuQGV2o}sMxZl2`5dE+U{|<+fbt!L z?vJlCnF~3+&0dj(h6>tc!GD3|&TWl#FdyzK)p|WYW^xT$dnEhxdy2OZ5K2hfH$aI3pxWy~`<9B40%TC-K)T|KKr*q2;)c0rCNzo~EOmML}x9tnBG{ib$ zemG!7l)+%}ABI}i&^TV}kjF;hZ*R<6__*PU>A1ZHqg!meNuvQRy-xJBOuWMb1Q-i} zMBeFk=yZR_*42N^3+UsJS-c>phfFp*;Pm*-64XHZLZr$&R5+vL+?wy~8=B`2ju^_v z%!3&4WQ|vH21+0${Q* zLKg^;#Mdn2w+`y^NERQbTIWH7B{W?ZSCs&eH zIA8Skkublgk;;sp;R5pyM|FGa?%UkD+$Jse>r|UG?Sm)9c9th62#IpGUt)z>CkQd; z>hzsH`EKd z3sJb*!+!oI$PUpexoXQj+`o8png6$bd(f(*$>oiV91$iXFNFs6yg~E%qEQN0oY=1d zu^;~#wg$@`bamVpug>U6@7;#2|Co7?XkKdRu`3B^VSIM*sbG&$3D1Zr-}kLNpZAh;$Oz->W|}a|g*d|C zRz`Mb#IB|b196pMjhmcTQZ8e;1nuPl9jpEoBsu?LNp|Q?GDLcWBc%BU^U^OcO-1L8 z+Wzb+xthq9CS2Nk86m#xFxSgvh8k3Y4@zKS;jXlU-JKaeAPEj6xnW|h3@Sdh-48+t zceF0z?C6F0{5g#9ntgm{g_UlY(cA=cX`c%YCDP{Ku!u_*4WcHDa!=s6oIl#nRTSP* zil^K+Oa}cx(u&(lz3a3rahf-DMQE7^$*Omq$KW|Ql zx9wnJWaSthmZz-;Dq5{2)w_3?M1V+ST#FgyCVP&ei|tu{hk`Hfk@qz^;Z^<+W!@xT zwES{qsh97i@*AaIwfQB_GemyLzt4L=HWcC(gM^*SZ~6GtEwg!4&5Zglo2#k6hY&zXA!lSlw zU3<$dd65+)fbz*1{(+W(c7tc2)i0Vm=aq!*mlAqA9iXX{Ce@cdehE27tuN|ykwFL+ zc{S-wT3U|r>@9sU=5pIrT`SYQ2b%g9Dp@Ywb@6H93~|lX^lwo6#FQSCGUZlWXUZTH zxY2k0_r|)EbZ1q{>Mh(h#gxq{0lCNKMcsPViQOfQC`HNOr{3y{_B5Wp!7stgA(Cv` zz|7%j=KM7BE6BDtcKRMs#^KkU`!ZZ*lOMJ^W57erj+LIgY_PNF%64cdBFOB#tHf3k zfWqtv96fBb)B)2UV_YDOZ*z^J`n%Ev@gH)>T^~xA3*OpC6-ur9_}-t2^45GvL6+ZQ zR)!tWJv6@yN?=ttSO`O@@>3>8?zq4E(@d5#MRw&n365XCsVO1yME0gZgEwn*$rIV! z$k@UNi%(C~N5tQJWpn~zU6PS|OXX3DO;(YfYj>EJHf)U|v2=erPgZn2WmEPy1FnZ$ zECKqpy$rb?Hqhso?_Lir$sjb#9E9u?+JA)~-G6C}2SLK4i=P{95oj_EBXdE~-CB5H zLw7f6nM118RC7SPPgCjViKh%A&dx?4CCHDn&0XC>J)nME3>4QM!;hA(RGWKGAh8nb ztP!@UI&J}tq4hWhFG1-V0sultQwj0o(Fq^jyzTzMGcOp7Ib0chzJIJglfQ-ba`U#_ zKuctF+JHr^4(=Xiq;cGg%Oo4$XP)Eha?WC3qow%m>8cmG^fpv|Wg!K=Pts#Pn&R6l ziqk+$s_9w|5eLMYeRn4;BxGqA*?DhcZrb{d4}Cx@2f@NyTDVBjLEOVOWZ!H|M9@zQm#cVGft|xa2m_ z{#S{v1yQ>vSgBJ@pEw&s)UQ5v`&rS-9$#B3%x$do2H#wa7?(Y>AFh(C1D!ijiP6Rc7kqryj==g zL#p2AZp{q(LFCOXO7Uu6WyB7oiRb*DhETGO9yYYN1>~TZdQ8I*s|S%jdQlRNT$B87 zG3P=3Pz0D@IBlxBa@RRvF@@g5lOyHQllCf_3uz9j;{9sBX)Ljt4dvpA^G{j}1LmL9 zW&|I9XJ%yRSh^v*do9f~MZttc$L0%KZ83&rqp=r323y7KrrsqUMPgP!+@Nk+97kSDz0Ga zOb98Uf~6pfA%%(9FGJZ`iF_T4cI}$d>ens&T($z1Bh|}O5D5_LV{4MDXI|&Bj;ai} z%^V7bKziqfhCiL7g3!$1Mw_!4g7x|(YD9ljT|A|8$l&O{cfrzNNhrHtFU4uPlO^PP zBLlt5^#X#z=T@WXH!5g(9XF?@g^r8v(Noj>UbU)s5ZieX4H$N&Y|oYg`YPzz`} zV{la_PZAO{q5h`?%{7IGmFkz2Tl_@ySj zARBMIU-%M3E&~dZL#`8kUel&|^%!#Y9y}ikH12-T+$r~ z(mPY~W7~rSL_%Ht4}hTEBPRe+1t2~0O%t!44XXpw=PE?z zliC7=FI$^$`S;J$>ik*N9XGBe*a|(?Shg6`w8{8uWa|544M17fm51Z zH@#hWX=*#SWuml#x?Q;Nc6SBj4Vakmq14q$ltHrJYtI$Z&t1Oo5rR3WVNvd#cWOar zB74@rOgETA1P(x&q5$&H&SPbvAoxOU#H0H*g=Zv7%I|SZkAD9Lt#4Y-qobMo27gmL zq`27B$Yc}v`=s81GoYy)sFUC_gc;BgU-q>2&oXs7ose8w|aSrE~3Wf!Py8sYku_7*;B;73ADy%MC{;Ux`i* zHY+F|Mt)){G`nsW;{{PL-vQcK=n;K+k1Pi+T3x_q)C3o2;0(S>Crihb)dj3MlCebpB zZ>4w~@^|g6oD`8GoY>2ZAyp~7RX@`-QuSxq_+}8QcOYO9LSx(>Ha8pOSQmgaUozJ^ zdw+%E7rBf*X0;}u#tb>^(wse@M>Gzo+m^)4{wB^qrke#-T4$IX0?4 zYjD=!H>|a|gw3ML@8ej^x>iLQOJXmJh^ugo zd$`o5%7BNQW6XI~*={d$AmX6lHHTQJ24>i~4vToRWgjUVZ$fb>N*6^J+bXjtrAFJx zS=5TuvVNS`D9{MQN7-8I`1rj>7mR$e>yP(A7#~)B`i*V~gY_y~V6_?ipxW$jHBjR| z^FcKb$laVG0Y;-HV9lrPLOc7Pau3IQwech8X5AU$_WE`mIXgGi{j=9)){HD+m;FL_ zVCxh!`m~>zhkU+zsg=)uuF~Kf{j}s`{yu#T_in0v)HWAJ3x{I))Gc{&mE%BDVD3@ zfohR_NO_2g^dF!i8Eb#YINfNk#wy5dV%~Lv8AT9;xqts9UihOJ32@Kcz_)`T+d;zW zpvLh|alYrM0WbrMy2?2Z`gCVvb=yQQ*9- zEPwW>$Ss$TvBGLWLW97~OYgRFlb@-e1kFWx(%Bq!G;}M>FIn-&5ljP)yBO~3Vjg;0 zLxXfk0JsJRp{#-kau;-t<%_G~d(44v+;b6*<;t08>tK5}q^$M{f!N~G}&E?Qv(cz2G5~Gv2cg^am2UvQ7m3(tGI%|CP z$dh}GEb6_Gn7A4UlPd-8de-N`x0yNUlJ~4zu zgZ`P|5ePYJE)Q01-^N`l-MkG_FM0NJxpXr}&J#Ww?j`R8@m`zeazE9W=Ae9RTw7J$ z;nxuT(-@&F_sH%C2&Y-F99nAh%O*eQGRDEhxX5w4f@&bgjIjugJ(ws4IFa ze04#!wtT`)sN&WRmck2^0}W~qRc+MS(iZ0A0qJkyH<~u;?WwKV!8ottX}L+PkhjIs znMQiu#-55DhsKB0G5W8?q$qPeZ(T{Fzr;A4*fU?ppwZbS>_x3Ids!7dO!REDJh^b4 z70>ly)_VO-w(HAiFx>%)g2JEj^V>gr$aTq(rCM@<`9EdJjr*Lj2Q}4!2xIh3XKznn zSuH)D{uA{%+MoaM?7%~YobxR&C6|9uaV^Fznw5c(>%R#?u^cvoTB3OFDC{Ri+H1s{ z@soufOj*6*LT(8op6$W6*T_;&#_B;dU5 zU+r=Kz0lN}9IQk~D?+@}`v$*McW88CRi+Fjmnjnihys*{7;FmI6Qm4fS|6Bb%fc40-a8^& zpQ!m={?En#6hd20gE?hY%toL9O%WpyXcGV4v8LT(R^w^xi7M-%bfQVn!%nsf=Y!ZJ zOH4I9@|V@6N`(g!%RQxa>WU>Mexn~!VF$ecodKF3)aif@8P%++nmi!O-EKR`JkwFF(`4_9@_CnQ`aQ zhx(DyoV5S@2xKBZSx3I@Vi&BuISFxDITyBk9^bNhuHW@oh+Hc4L%_HP!Q#5|U4}fCiRfS@J-zHmn#*B0zD2+t(F^U#)oT zu#zWxO>lf>P*r6G@o_l?2l7}6DJMF~?i+ep5gZ(bJT?Y!Y?vaR0r=Q0Vz=eyDtMX! zoE4d!wEg^sDEOUrs%v=9O%;wI*x)K5~w={50?CJS_f+j zqQF4_C`jNxGynxD1^h(24T=sDd>-@c`_{UA=DLXQ1Z(Y*PU$N?ajzqtj4!=CWq7_F Tgm1M#U*!PD{hz-9NCW>1<>tIM literal 0 HcmV?d00001 diff --git a/Tests/images/blp/war3mapMap.jpg b/Tests/images/blp/war3mapMap.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c045c5945c1071f3d4bd4e3f0d878cec6899bc17 GIT binary patch literal 1651 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<uA z0}~@NGZPClD=P~NP<1U(o`FS>RY=j$kxe)-kzJ`!#HexNLJno8jR!@8E`CrkPAY2R z|V^&07y2J$~}^+4C1KUw!=a`ODXD-+%o41@afjpD+ON7@EHXf&OA*VPR%r2lxYE#}NLy#lXYN2#h>tK?Zwfl gP32wyWDa4_AkC=Dz<6e<1v|(Sp00i_>zopr0LjD?Hvj+t literal 0 HcmV?d00001 diff --git a/Tests/test_file_blp.py b/Tests/test_file_blp.py index 15bd7e4f8..3e4401996 100644 --- a/Tests/test_file_blp.py +++ b/Tests/test_file_blp.py @@ -20,6 +20,12 @@ def test_load_blp2_dxt1a(): assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png") +def test_load_blp1(): + with Image.open("Tests/images/blp/war3mapMap.blp") as im: + im.convert("RGB") + assert_image_equal_tofile(im, "Tests/images/blp/war3mapMap.png") + + @pytest.mark.parametrize( "test_file", [ diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index f62b1bebe..c286a8197 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -28,7 +28,7 @@ BLP files come in many different flavours: - DXT3 compression is used if alpha_encoding == 1. - DXT5 compression is used if alpha_encoding == 7. """ - +print("foo") import struct from io import BytesIO @@ -245,7 +245,7 @@ class BlpImageFile(ImageFile.ImageFile): if self.magic == b"BLP1": decoder = "BLP1" - self.mode = "RGBA" + self.mode = "BGRA" elif self.magic == b"BLP2": decoder = "BLP2" self.mode = "RGBA" if self._blp_alpha_depth else "RGB" @@ -361,6 +361,16 @@ class BLP1Decoder(_BLPBaseDecoder): image.tile = [("jpeg", (0, 0) + self.size, 0, ("RGBA", ""))] b, g, r, a = image.split() + print(b, g, r, a) + if not any( + [a.getpixel((x, y)) for x in range(a.width) for y in range(a.height)] + ): + # try to unprotect completely transparent pictures + from PIL import ImageOps + + a = ImageOps.invert(a) + + image = Image.merge("RGBA", (r, g, b, a)) self.set_as_raw(image.tobytes()) From 63c3b26f6aa91740dff5ea041fc713cfb53aee51 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 13 Jul 2021 23:02:23 +1000 Subject: [PATCH 023/633] Fixed using info dictionary when writing multiple frames --- Tests/test_file_apng.py | 24 ++++++++++++++++++++++++ src/PIL/PngImagePlugin.py | 19 +++++++++++-------- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 15e007ca1..d48e5ce07 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -441,6 +441,12 @@ def test_apng_save_duration_loop(tmp_path): assert im.n_frames == 1 assert im.info.get("duration") == 750 + # test info duration + frame.info["duration"] = 750 + frame.save(test_file, save_all=True) + with Image.open(test_file) as im: + assert im.info.get("duration") == 750 + def test_apng_save_disposal(tmp_path): test_file = str(tmp_path / "temp.png") @@ -531,6 +537,17 @@ def test_apng_save_disposal(tmp_path): assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) + # test info disposal + red.info["disposal"] = PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND + red.save( + test_file, + save_all=True, + append_images=[Image.new("RGBA", (10, 10), (0, 255, 0, 255))], + ) + with Image.open(test_file) as im: + im.seek(1) + assert im.getpixel((64, 32)) == (0, 0, 0, 0) + def test_apng_save_disposal_previous(tmp_path): test_file = str(tmp_path / "temp.png") @@ -611,3 +628,10 @@ def test_apng_save_blend(tmp_path): im.seek(2) assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + # test info blend + red.info["blend"] = PngImagePlugin.APNG_BLEND_OP_OVER + red.save(test_file, save_all=True, append_images=[green, transparent]) + with Image.open(test_file) as im: + im.seek(2) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index a91393726..0c466da51 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1061,8 +1061,10 @@ def _write_multiple_frames(im, fp, chunk, rawmode): default_image = im.encoderinfo.get("default_image", im.info.get("default_image")) duration = im.encoderinfo.get("duration", im.info.get("duration", 0)) loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) - disposal = im.encoderinfo.get("disposal", im.info.get("disposal")) - blend = im.encoderinfo.get("blend", im.info.get("blend")) + disposal = im.encoderinfo.get( + "disposal", im.info.get("disposal", APNG_DISPOSE_OP_NONE) + ) + blend = im.encoderinfo.get("blend", im.info.get("blend", APNG_BLEND_OP_SOURCE)) if default_image: chain = itertools.chain(im.encoderinfo.get("append_images", [])) @@ -1149,9 +1151,10 @@ def _write_multiple_frames(im, fp, chunk, rawmode): bbox = frame_data["bbox"] im_frame = im_frame.crop(bbox) size = im_frame.size - duration = int(round(frame_data["encoderinfo"].get("duration", 0))) - disposal = frame_data["encoderinfo"].get("disposal", APNG_DISPOSE_OP_NONE) - blend = frame_data["encoderinfo"].get("blend", APNG_BLEND_OP_SOURCE) + encoderinfo = frame_data["encoderinfo"] + frame_duration = int(round(encoderinfo.get("duration", duration))) + frame_disposal = encoderinfo.get("disposal", disposal) + frame_blend = encoderinfo.get("blend", blend) # frame control chunk( fp, @@ -1161,10 +1164,10 @@ def _write_multiple_frames(im, fp, chunk, rawmode): o32(size[1]), # height o32(bbox[0]), # x_offset o32(bbox[1]), # y_offset - o16(duration), # delay_numerator + o16(frame_duration), # delay_numerator o16(1000), # delay_denominator - o8(disposal), # dispose_op - o8(blend), # blend_op + o8(frame_disposal), # dispose_op + o8(frame_blend), # blend_op ) seq_num += 1 # frame data From e766ddbc399b30164a9ce195d072cce0a0054507 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 13 Jul 2021 22:59:49 +1000 Subject: [PATCH 024/633] Removed unnecessary code --- src/PIL/PngImagePlugin.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 0c466da51..0f596f1fd 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1119,12 +1119,8 @@ def _write_multiple_frames(im, fp, chunk, rawmode): and prev_disposal == encoderinfo.get("disposal") and prev_blend == encoderinfo.get("blend") ): - frame_duration = encoderinfo.get("duration", 0) - if frame_duration: - if "duration" in previous["encoderinfo"]: - previous["encoderinfo"]["duration"] += frame_duration - else: - previous["encoderinfo"]["duration"] = frame_duration + if isinstance(duration, (list, tuple)): + previous["encoderinfo"]["duration"] += encoderinfo["duration"] continue else: bbox = None From 3fbc9eb2290a9fcaba31f972dc8aaf09358a2e45 Mon Sep 17 00:00:00 2001 From: Meithal Date: Thu, 15 Jul 2021 20:33:35 +0200 Subject: [PATCH 025/633] self.mode = "BGRA" wasn't correct and captured by #affa059 --- Tests/images/blp/war3mapMap.jpg | Bin 1651 -> 0 bytes Tests/images/blp/war3mapMap.png | Bin 270 -> 0 bytes Tests/test_file_blp.py | 3 ++- src/PIL/BlpImagePlugin.py | 2 +- 4 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 Tests/images/blp/war3mapMap.jpg delete mode 100644 Tests/images/blp/war3mapMap.png diff --git a/Tests/images/blp/war3mapMap.jpg b/Tests/images/blp/war3mapMap.jpg deleted file mode 100644 index c045c5945c1071f3d4bd4e3f0d878cec6899bc17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1651 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<uA z0}~@NGZPClD=P~NP<1U(o`FS>RY=j$kxe)-kzJ`!#HexNLJno8jR!@8E`CrkPAY2R z|V^&07y2J$~}^+4C1KUw!=a`ODXD-+%o41@afjpD+ON7@EHXf&OA*VPR%r2lxYE#}NLy#lXYN2#h>tK?Zwfl gP32wyWDa4_AkC=Dz<6e<1v|(Sp00i_>zopr0LjD?Hvj+t diff --git a/Tests/test_file_blp.py b/Tests/test_file_blp.py index 3e4401996..c3540f4fa 100644 --- a/Tests/test_file_blp.py +++ b/Tests/test_file_blp.py @@ -22,7 +22,8 @@ def test_load_blp2_dxt1a(): def test_load_blp1(): with Image.open("Tests/images/blp/war3mapMap.blp") as im: - im.convert("RGB") + png = im.copy() + png.save(fp="Tests/images/blp/war3mapMap.png") assert_image_equal_tofile(im, "Tests/images/blp/war3mapMap.png") diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index c286a8197..907d33a20 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -245,7 +245,7 @@ class BlpImageFile(ImageFile.ImageFile): if self.magic == b"BLP1": decoder = "BLP1" - self.mode = "BGRA" + self.mode = "RGBA" elif self.magic == b"BLP2": decoder = "BLP2" self.mode = "RGBA" if self._blp_alpha_depth else "RGB" From a139b9784541659c86ba9994563f07a3ba3ef4bc Mon Sep 17 00:00:00 2001 From: Meithal Date: Thu, 15 Jul 2021 20:48:47 +0200 Subject: [PATCH 026/633] Cleanup --- src/PIL/BlpImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 907d33a20..6909599c1 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -28,7 +28,7 @@ BLP files come in many different flavours: - DXT3 compression is used if alpha_encoding == 1. - DXT5 compression is used if alpha_encoding == 7. """ -print("foo") + import struct from io import BytesIO From cf275737ee6abbd656fd352cfbe9618af35e8c5c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 10 Jul 2021 00:00:50 +1000 Subject: [PATCH 027/633] Do not rearrange palette channels --- Tests/test_file_gif.py | 2 +- src/PIL/GifImagePlugin.py | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 2632ab7c0..05e2ddcbe 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -833,7 +833,7 @@ def test_palette_save_ImagePalette(tmp_path): with Image.open(out) as reloaded: im.putpalette(palette) - assert_image_equal(reloaded, im) + assert_image_equal(reloaded.convert("RGB"), im.convert("RGB")) def test_save_I(tmp_path): diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index fe57e24e2..ad97b81a5 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -396,15 +396,7 @@ def _normalize_palette(im, palette, info): if isinstance(palette, (bytes, bytearray, list)): source_palette = bytearray(palette[:768]) if isinstance(palette, ImagePalette.ImagePalette): - source_palette = bytearray( - itertools.chain.from_iterable( - zip( - palette.palette[:256], - palette.palette[256:512], - palette.palette[512:768], - ) - ) - ) + source_palette = bytearray(palette.palette) if im.mode == "P": if not source_palette: From d0a30ec369e5e786a709e27b92f5abf05213d187 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 10 Jul 2021 00:36:24 +1000 Subject: [PATCH 028/633] Updated documentation --- docs/reference/ImagePalette.rst | 4 ---- src/PIL/ImagePalette.py | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/reference/ImagePalette.rst b/docs/reference/ImagePalette.rst index f14c1c3a4..72ccfac7d 100644 --- a/docs/reference/ImagePalette.rst +++ b/docs/reference/ImagePalette.rst @@ -9,10 +9,6 @@ represent the color palette of palette mapped images. .. note:: - This module was never well-documented. It hasn't changed since 2001, - though, so it's probably safe for you to read the source code and puzzle - out the internals if you need to. - The :py:class:`~PIL.ImagePalette.ImagePalette` class has several methods, but they are all marked as "experimental." Read that as you will. The ``[source]`` link is there for a reason. diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 214059f51..36fb7fd13 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -25,12 +25,12 @@ class ImagePalette: """ Color palette for palette mapped images - :param mode: The mode to use for the Palette. See: + :param mode: The mode to use for the palette. See: :ref:`concept-modes`. Defaults to "RGB" :param palette: An optional palette. If given, it must be a bytearray, - an array or a list of ints between 0-255. The list must be aligned - by channel (All R values must be contiguous in the list before G - and B values.) Defaults to 0 through 255 per channel. + an array or a list of ints between 0-255. The list must consist of + all channels for one color followed by the next color (e.g. RGBRGBRGB). + Defaults to an empty palette. :param size: An optional palette size. If given, an error is raised if ``palette`` is not of equal length. """ From a9372d5cf0aa6a56f6e4aa5399f01b2485b1012f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 10 Jul 2021 23:13:46 +1000 Subject: [PATCH 029/633] Fixed generated palettes --- Tests/images/palette_negative.png | Bin 0 -> 17070 bytes Tests/images/palette_sepia.png | Bin 0 -> 20339 bytes Tests/images/palette_wedge.png | Bin 0 -> 17065 bytes Tests/test_image_putpalette.py | 8 +++++++- src/PIL/ImagePalette.py | 14 ++++++-------- 5 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 Tests/images/palette_negative.png create mode 100644 Tests/images/palette_sepia.png create mode 100644 Tests/images/palette_wedge.png diff --git a/Tests/images/palette_negative.png b/Tests/images/palette_negative.png new file mode 100644 index 0000000000000000000000000000000000000000..938a7285fd75af8fcdb5e5857e05e9674b9ae337 GIT binary patch literal 17070 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%0DN|MLC=0`L9H+oFLFil*`C(bS9|Oy`Ir!0kQo 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 F{{cuUG`Ii& literal 0 HcmV?d00001 diff --git a/Tests/images/palette_sepia.png b/Tests/images/palette_sepia.png new file mode 100644 index 0000000000000000000000000000000000000000..f3fc932531f9b73c8d214e7c236c556ab3775d22 GIT binary patch literal 20339 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 literal 0 HcmV?d00001 diff --git a/Tests/images/palette_wedge.png b/Tests/images/palette_wedge.png new file mode 100644 index 0000000000000000000000000000000000000000..23fb7940d6d61c42e69ad3c80fca5e77c26994d3 GIT binary patch literal 17065 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 mp1g?hKH_PmYNYWMwhbk94 Date: Mon, 2 Aug 2021 23:17:44 +0200 Subject: [PATCH 030/633] hide FriBiDi shim symbols to avoid conflict with real FriBiDi library --- src/thirdparty/fribidi-shim/fribidi.c | 4 ++-- src/thirdparty/fribidi-shim/fribidi.h | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c index abbab07b0..76fd5b9a4 100644 --- a/src/thirdparty/fribidi-shim/fribidi.c +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -12,7 +12,7 @@ /* FriBiDi>=1.0.0 adds bracket_types param, ignore and call legacy function */ -FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( +static FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( const FriBidiCharType *bidi_types, const FriBidiBracketType *bracket_types, const FriBidiStrIndex len, @@ -24,7 +24,7 @@ FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( } /* FriBiDi>=1.0.0 gets bracket types here, ignore */ -void fribidi_get_bracket_types_compat( +static void fribidi_get_bracket_types_compat( const FriBidiChar *str, const FriBidiStrIndex len, const FriBidiCharType *types, diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h index 7712a5b22..7e175c3db 100644 --- a/src/thirdparty/fribidi-shim/fribidi.h +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -63,8 +63,12 @@ typedef uint32_t FriBidiParType; /* functions */ #ifdef FRIBIDI_SHIM_IMPLEMENTATION +#ifdef _MSC_VER #define FRIBIDI_ENTRY #else +#define FRIBIDI_ENTRY __attribute__((visibility ("hidden"))) +#endif +#else #define FRIBIDI_ENTRY extern #endif From 6596e31605b7916610a69c491d76114f2058fede Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 5 Aug 2021 01:06:01 +1000 Subject: [PATCH 031/633] Determine mode purely from ihdr header box --- Tests/images/balloon_eciRGBv2_aware.jp2 | Bin 0 -> 7419 bytes Tests/test_file_jpeg2k.py | 10 +++++ src/PIL/Jpeg2KImagePlugin.py | 49 ++++++------------------ 3 files changed, 21 insertions(+), 38 deletions(-) create mode 100644 Tests/images/balloon_eciRGBv2_aware.jp2 diff --git a/Tests/images/balloon_eciRGBv2_aware.jp2 b/Tests/images/balloon_eciRGBv2_aware.jp2 new file mode 100644 index 0000000000000000000000000000000000000000..18fd1e1723de3237802ecf53839ac530d5321822 GIT binary patch literal 7419 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") + height, width, nc, bpc = header.read_fields(">IIHB") size = (width, height) - if unkc: - if nc == 1 and (bpc & 0x7F) > 8: - mode = "I;16" - elif nc == 1: - mode = "L" - elif nc == 2: - mode = "LA" - elif nc == 3: - mode = "RGB" - elif nc == 4: - mode = "RGBA" - elif tbox == b"colr": - meth, prec, approx = header.read_fields(">BBB") - if meth == 1 and unkc == 0: - cs = header.read_fields(">I")[0] - if cs == 16: # sRGB - if nc == 1 and (bpc & 0x7F) > 8: - mode = "I;16" - elif nc == 1: - mode = "L" - elif nc == 3: - mode = "RGB" - elif nc == 4: - mode = "RGBA" - elif cs == 17: # grayscale - if nc == 1 and (bpc & 0x7F) > 8: - mode = "I;16" - elif nc == 1: - mode = "L" - elif nc == 2: - mode = "LA" - elif cs == 18: # sYCC - if nc == 3: - mode = "RGB" - elif nc == 4: - mode = "RGBA" + if nc == 1 and (bpc & 0x7F) > 8: + mode = "I;16" + elif nc == 1: + mode = "L" + elif nc == 2: + mode = "LA" + elif nc == 3: + mode = "RGB" + elif nc == 4: + mode = "RGBA" elif tbox == b"res ": res = header.read_boxes() while res.has_next_box(): From 8f300af691efb83b6f7db76bf5f6fe66cbce6f02 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 8 Aug 2021 15:01:19 +0200 Subject: [PATCH 032/633] Actually check the framesize in FliDecode. --- src/libImaging/FliDecode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c index 3a6030703..f56432034 100644 --- a/src/libImaging/FliDecode.c +++ b/src/libImaging/FliDecode.c @@ -46,7 +46,8 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt ptr = buf; framesize = I32(ptr); - if (framesize < I32(ptr)) { + // there can be one pad byte in the framesize + if (bytes + (bytes % 2) < framesize) { return 0; } From f55ccd95630153026a41f85bea71dcbae25bb4ef Mon Sep 17 00:00:00 2001 From: Yutao Yuan Date: Mon, 9 Aug 2021 23:02:33 +0800 Subject: [PATCH 033/633] Remove stdout check in ImageFile._save --- src/PIL/ImageFile.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 43d2bf0cc..2a7febb2b 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -493,13 +493,6 @@ def _save(im, fp, tile, bufsize=0): # But, it would need at least the image size in most cases. RawEncode is # a tricky case. bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c - try: - stdout = fp == sys.stdout or fp == sys.stdout.buffer - except (OSError, AttributeError): - stdout = False - if stdout: - fp.flush() - return try: fh = fp.fileno() fp.flush() From cbdc7516280776945579d14c017a0d636a9df9e4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 10 Aug 2021 07:04:36 +1000 Subject: [PATCH 034/633] Read AND mask from end --- Tests/images/hopper_mask.ico | Bin 0 -> 262 bytes Tests/images/hopper_mask.png | Bin 0 -> 208 bytes Tests/test_file_ico.py | 5 +++++ src/PIL/IcoImagePlugin.py | 2 +- 4 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 Tests/images/hopper_mask.ico create mode 100644 Tests/images/hopper_mask.png diff --git a/Tests/images/hopper_mask.ico b/Tests/images/hopper_mask.ico new file mode 100644 index 0000000000000000000000000000000000000000..e8d66c689fd42918c887a316f34a8e0d3ae8c154 GIT binary patch literal 262 zcmZQzU<5(|0R|voWcUCi#ei4?h(SUMKn#)x0S6#H!Uw@jKsE@2z<(eB(WVVRwX8sK zP6h^MAls>vfx&}^fkEU61B2iT1_s_L28MSJ7#LdT0~I#{)dDSQ{l~z-X#g|>C^V25 E0C?>&ZU6uP literal 0 HcmV?d00001 diff --git a/Tests/images/hopper_mask.png b/Tests/images/hopper_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..c7bd2f70842801aa0db7150ece834f839cf10376 GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`(>+}rLn`L<21oN9Q4nZa_I!qfYIUFP)0s%}Q9FmwCmw=?2hLgO~cOSmGXm_xh*B`+Q&1olYF0)pbt5W}{+Q^;C{dV?S z8Hqw`c2h^It&)X%L^GPNyb5**di(nezsJ8iZi_u`3>6H8wpEfdPQP6NbRvVNtDnm{ Hr-UW|-Oo~i literal 0 HcmV?d00001 diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 8060d1b76..317264db6 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -18,6 +18,11 @@ def test_sanity(): assert im.get_format_mimetype() == "image/x-icon" +def test_mask(): + with Image.open("Tests/images/hopper_mask.ico") as im: + assert_image_equal_tofile(im, "Tests/images/hopper_mask.png") + + def test_black_and_white(): with Image.open("Tests/images/black_and_white.ico") as im: assert im.mode == "RGBA" diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index ffb1e873d..d9ff9b5e7 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -235,8 +235,8 @@ class IcoFile: # the total mask data is # padded row size * height / bits per char - and_mask_offset = o + int(im.size[0] * im.size[1] * (bpp / 8.0)) total_bytes = int((w * im.size[1]) / 8) + and_mask_offset = header["offset"] + header["size"] - total_bytes self.buf.seek(and_mask_offset) mask_data = self.buf.read(total_bytes) From 0f11d22cceb1afcbde38496c7523af453730336e Mon Sep 17 00:00:00 2001 From: Yutao Yuan Date: Tue, 10 Aug 2021 17:56:52 +0800 Subject: [PATCH 035/633] Add tests for saving to stdout --- Tests/test_file_jpeg.py | 28 ++++++++++++++++++++++++++++ Tests/test_file_ppm.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 15518756c..da78e4911 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -1,5 +1,6 @@ import os import re +import sys from io import BytesIO import pytest @@ -870,6 +871,33 @@ class TestFileJpeg: with Image.open("Tests/images/hopper.jpg") as im: assert im.getxmp() == {} + @pytest.mark.parametrize("buffer", (True, False)) + def test_save_stdout(self, buffer): + old_stdout = sys.stdout + + if buffer: + + class MyStdOut: + buffer = BytesIO() + + mystdout = MyStdOut() + else: + mystdout = BytesIO() + + sys.stdout = mystdout + + with Image.open(TEST_FILE) as im: + im.save(sys.stdout, "JPEG") + im_roundtrip = self.roundtrip(im) + + # Reset stdout + sys.stdout = old_stdout + + if buffer: + mystdout = mystdout.buffer + reloaded = Image.open(mystdout) + assert_image_equal(reloaded, im_roundtrip) + @pytest.mark.skipif(not is_win32(), reason="Windows only") @skip_unless_feature("jpg") diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 0ccfb5e88..07c9bda84 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -1,3 +1,6 @@ +import sys +from io import BytesIO + import pytest from PIL import Image @@ -80,3 +83,30 @@ def test_mimetypes(tmp_path): f.write("PyCMYK\n128 128\n255") with Image.open(path) as im: assert im.get_format_mimetype() == "image/x-portable-anymap" + + +@pytest.mark.parametrize("buffer", (True, False)) +def test_save_stdout(buffer): + old_stdout = sys.stdout + + if buffer: + + class MyStdOut: + buffer = BytesIO() + + mystdout = MyStdOut() + else: + mystdout = BytesIO() + + sys.stdout = mystdout + + with Image.open(TEST_FILE) as im: + im.save(sys.stdout, "PPM") + + # Reset stdout + sys.stdout = old_stdout + + if buffer: + mystdout = mystdout.buffer + reloaded = Image.open(mystdout) + assert_image_equal_tofile(reloaded, TEST_FILE) From c5ac9326388d3f9995699ca201c82d242d7f03ab Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 18 Aug 2021 21:55:12 +1000 Subject: [PATCH 036/633] Updated harfbuzz to 2.9.0 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 493665902..ffffaf662 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -277,9 +277,9 @@ deps = { "libs": [r"*.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/2.8.2.zip", - "filename": "harfbuzz-2.8.2.zip", - "dir": "harfbuzz-2.8.2", + "url": "https://github.com/harfbuzz/harfbuzz/archive/2.9.0.zip", + "filename": "harfbuzz-2.9.0.zip", + "dir": "harfbuzz-2.9.0", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 2b9e230f76d28b868297039dfcbbeef98dca9ceb Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 24 Aug 2021 10:58:02 +0300 Subject: [PATCH 037/633] Fix ResourceWarning: unclosed file --- Tests/test_imagepalette.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 5a59b7799..475d249ed 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -16,10 +16,10 @@ def test_sanity(): def test_reload(): - im = Image.open("Tests/images/hopper.gif") - original = im.copy() - im.palette.dirty = 1 - assert_image_equal(im.convert("RGB"), original.convert("RGB")) + with Image.open("Tests/images/hopper.gif") as im: + original = im.copy() + im.palette.dirty = 1 + assert_image_equal(im.convert("RGB"), original.convert("RGB")) def test_getcolor(): From 474270fbd587cf303232dcde5bae350f2d34ce38 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 24 Aug 2021 11:03:10 +0300 Subject: [PATCH 038/633] Filter out UserWarning: Truncated File Read --- Tests/test_tiff_crashes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/test_tiff_crashes.py b/Tests/test_tiff_crashes.py index 6cdb8e44d..143765b8e 100644 --- a/Tests/test_tiff_crashes.py +++ b/Tests/test_tiff_crashes.py @@ -40,6 +40,7 @@ from .helper import on_ci ) @pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") @pytest.mark.filterwarnings("ignore:Metadata warning") +@pytest.mark.filterwarnings("ignore:Truncated File Read") def test_tiff_crashes(test_file): try: with Image.open(test_file) as im: From 320ab8172e0d45b57a01c21cb0905abdf8593849 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 24 Aug 2021 11:19:45 +0300 Subject: [PATCH 039/633] Avoid DecompressionBombWarning: Image size (151587072 pixels) exceeds limit of 89478485 pixels, could be decompression bomb DOS attack. --- Tests/test_map.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/test_map.py b/Tests/test_map.py index 752c5f268..42f3447eb 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -24,11 +24,17 @@ def test_overflow(): def test_tobytes(): + # Note that this image triggers the decompression bomb warning: + max_pixels = Image.MAX_IMAGE_PIXELS + Image.MAX_IMAGE_PIXELS = None + # Previously raised an access violation on Windows with Image.open("Tests/images/l2rgb_read.bmp") as im: with pytest.raises((ValueError, MemoryError, OSError)): im.tobytes() + Image.MAX_IMAGE_PIXELS = max_pixels + @pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="Requires 64-bit system") def test_ysize(): From 2f8b3d0ff948942e609dd377c5ad1827c6830587 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 24 Aug 2021 11:20:09 +0300 Subject: [PATCH 040/633] Ignore new test_images file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5500ec037..790404535 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,7 @@ docs/_build/ Tests/images/README.md Tests/images/crash_1.tif Tests/images/crash_2.tif +Tests/images/crash-81154a65438ba5aaeca73fd502fa4850fbde60f8.tif Tests/images/string_dimension.tiff Tests/images/jpeg2000 Tests/images/msp From d773fcd23d01c19af2cfbc280b8421ddfa0908e6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 24 Aug 2021 11:33:43 +0300 Subject: [PATCH 041/633] Filter out UserWarning: Truncated File Read --- Tests/test_file_tiff.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index d5dda5799..236fcf445 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -698,6 +698,8 @@ class TestFileTiff: # Ignore this UserWarning which triggers for four tags: # "Possibly corrupt EXIF data. Expecting to read 50404352 bytes but..." @pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") + # Ignore this UserWarning: + @pytest.mark.filterwarnings("ignore:Truncated File Read") @pytest.mark.skipif( not os.path.exists("Tests/images/string_dimension.tiff"), reason="Extra image files not installed", From 7f3646493cbe4ae5e3edc44cd5e4b8f7f04a410a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 24 Aug 2021 21:56:36 +1000 Subject: [PATCH 042/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 4e4e9648b..9de7dd82e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 8.4.0 (unreleased) ------------------ +- Updates for ImagePalette channel order #5599 + [radarhere] + +- Fixed using info dictionary when writing multiple APNG frames #5611 + [radarhere] + - Allow saving 1 and L mode TIFF with PhotometricInterpretation 0 #5655 [radarhere] From d50052a75c4d0d17d3d37c24a4558c9e0736f0e8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 24 Aug 2021 22:33:08 +1000 Subject: [PATCH 043/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 9de7dd82e..977323f9f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.4.0 (unreleased) ------------------ +- Determine JPEG2000 mode purely from ihdr header box #5654 + [radarhere] + - Updates for ImagePalette channel order #5599 [radarhere] From 1b397751ec35f6f2a64a73dd7cec4ccd4a04bb62 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 24 Aug 2021 23:43:38 +1000 Subject: [PATCH 044/633] Added context managers --- Tests/test_file_jpeg.py | 4 ++-- Tests/test_file_ppm.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index da78e4911..5bd16e356 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -895,8 +895,8 @@ class TestFileJpeg: if buffer: mystdout = mystdout.buffer - reloaded = Image.open(mystdout) - assert_image_equal(reloaded, im_roundtrip) + with Image.open(mystdout) as reloaded: + assert_image_equal(reloaded, im_roundtrip) @pytest.mark.skipif(not is_win32(), reason="Windows only") diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 07c9bda84..ad36319db 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -108,5 +108,5 @@ def test_save_stdout(buffer): if buffer: mystdout = mystdout.buffer - reloaded = Image.open(mystdout) - assert_image_equal_tofile(reloaded, TEST_FILE) + with Image.open(mystdout) as reloaded: + assert_image_equal_tofile(reloaded, TEST_FILE) From 9d4814356700e166ff0ceeb8cb713d23fc231b93 Mon Sep 17 00:00:00 2001 From: Julien Voisin Date: Wed, 25 Aug 2021 16:45:39 +0200 Subject: [PATCH 045/633] Improve the fuzzer wrt. the current atheris version --- Tests/oss-fuzz/fuzz_font.py | 12 ++++++------ Tests/oss-fuzz/fuzz_pillow.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py index e11471011..0cc4a99ef 100755 --- a/Tests/oss-fuzz/fuzz_font.py +++ b/Tests/oss-fuzz/fuzz_font.py @@ -14,10 +14,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys -import atheris_no_libfuzzer as atheris -import fuzzers +import atheris +with atheris.instrument_imports(): + import sys + import fuzzers def TestOneInput(data): @@ -26,13 +27,12 @@ def TestOneInput(data): except Exception: # We're catching all exceptions because Pillow's exceptions are # directly inheriting from Exception. - return - return + pass def main(): fuzzers.enable_decompressionbomb_error() - atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) + atheris.Setup(sys.argv, TestOneInput) atheris.Fuzz() fuzzers.disable_decompressionbomb_error() diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py index b3c55fe22..3dd1aea0d 100644 --- a/Tests/oss-fuzz/fuzz_pillow.py +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -14,10 +14,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys -import atheris_no_libfuzzer as atheris -import fuzzers +import atheris +with atheris.instrument_imports(): + import sys + import fuzzers def TestOneInput(data): @@ -26,13 +27,12 @@ def TestOneInput(data): except Exception: # We're catching all exceptions because Pillow's exceptions are # directly inheriting from Exception. - return - return + pass def main(): fuzzers.enable_decompressionbomb_error() - atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True) + atheris.Setup(sys.argv, TestOneInput) atheris.Fuzz() fuzzers.disable_decompressionbomb_error() From 3b69035d4b36af359917136c6dde2d953be5a332 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 25 Aug 2021 14:52:16 +0000 Subject: [PATCH 046/633] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/oss-fuzz/fuzz_font.py | 2 ++ Tests/oss-fuzz/fuzz_pillow.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py index 0cc4a99ef..bc2ba9a7e 100755 --- a/Tests/oss-fuzz/fuzz_font.py +++ b/Tests/oss-fuzz/fuzz_font.py @@ -16,8 +16,10 @@ import atheris + with atheris.instrument_imports(): import sys + import fuzzers diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py index 3dd1aea0d..545daccb6 100644 --- a/Tests/oss-fuzz/fuzz_pillow.py +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -16,8 +16,10 @@ import atheris + with atheris.instrument_imports(): import sys + import fuzzers From 0dba28613bc94084dc668b924db7e6b5ec944a87 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 31 Aug 2021 00:33:10 +1000 Subject: [PATCH 047/633] Copy Python palette to new image in quantize() --- Tests/test_image_quantize.py | 1 + src/PIL/Image.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 1ceff0842..bd9db362c 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -63,6 +63,7 @@ def test_quantize_no_dither(): converted = image.quantize(dither=0, palette=palette) assert_image(converted, "P", converted.size) + assert converted.palette.palette == palette.palette.palette def test_quantize_dither_diff(): diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f53dbe016..7dd5b35bd 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1130,7 +1130,9 @@ class Image: "only RGB or L mode images can be quantized to a palette" ) im = self.im.convert("P", dither, palette.im) - return self._new(im) + new_im = self._new(im) + new_im.palette = palette.palette.copy() + return new_im im = self._new(self.im.quantize(colors, method, kmeans)) From 9189c4b08bb5b239a708da5280d4ce4e4dad6a77 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 1 Sep 2021 19:46:24 +1000 Subject: [PATCH 048/633] Pillow 8.3.2 supports Python 3.10 [ci skip] --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 1eb26e4c6..03f7157bb 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -18,9 +18,9 @@ Pillow supports these Python versions. +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ | Python |3.10 | 3.9 | 3.8 | 3.7 | 3.6 | 3.5 | 3.4 | 2.7 | +======================+=====+=====+=====+=====+=====+=====+=====+=====+ -| Pillow >= 8.4 | Yes | Yes | Yes | Yes | Yes | | | | +| Pillow >= 8.3.2 | Yes | Yes | Yes | Yes | Yes | | | | +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ -| Pillow 8.0 - 8.3 | | Yes | Yes | Yes | Yes | | | | +| Pillow 8.0 - 8.3.1 | | Yes | Yes | Yes | Yes | | | | +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ | Pillow 7.0 - 7.2 | | | Yes | Yes | Yes | Yes | | | +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ From ad1522aa550257c2b9ea79aa1826e0c6316d12d4 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 1 Sep 2021 13:14:47 +0300 Subject: [PATCH 049/633] Add release notes for Pillow 8.3.2 --- docs/releasenotes/8.3.2.rst | 27 +++++++++++++++++++++++++++ docs/releasenotes/index.rst | 1 + 2 files changed, 28 insertions(+) create mode 100644 docs/releasenotes/8.3.2.rst diff --git a/docs/releasenotes/8.3.2.rst b/docs/releasenotes/8.3.2.rst new file mode 100644 index 000000000..0a795957e --- /dev/null +++ b/docs/releasenotes/8.3.2.rst @@ -0,0 +1,27 @@ +8.3.2 +----- + +Other Changes +============= + +Python 3.10 wheels +^^^^^^^^^^^^^^^^^^ + +Pillow now includes binary wheels for Python 3.10. + +The Python 3.10 release candidate was released on 2021-08-03 with the final release due +2021-10-04 (:pep:`619`). The CPython core team strongly encourages maintainers of +third-party Python projects to prepare for 3.10 compatibility. And as there are `no ABI +changes`_ planned we are releasing wheels to help others prepare for 3.10, and ensure +Pillow can be used immediately on release day of 3.10.0 final. + +Fixed regressions +^^^^^^^^^^^^^^^^^ + +* Ensure TIFF ``RowsPerStrip`` is multiple of 8 for JPEG compression (:pr:`5588`). + +* Updates for :py:class:`~PIL.ImagePalette` channel order (:pr:`5599`). + +* Hide FriBiDi shim symbols to avoid conflict with real FriBiDi library (:pr:`5651`). + +.. _no ABI changes: https://www.python.org/downloads/release/python-3100rc1/ diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 55c51a401..f42ea72e8 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -15,6 +15,7 @@ expected to be backported to earlier versions. :maxdepth: 2 8.4.0 + 8.3.2 8.3.1 8.3.0 8.2.0 From ec1a00c7fe085bc47ff18f4f78b82e3916dde4af Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 1 Sep 2021 18:55:54 +0300 Subject: [PATCH 050/633] Add 8.3.2 (2021-09-02) [CI skip] --- CHANGES.rst | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 977323f9f..ca4145e90 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,9 +8,6 @@ Changelog (Pillow) - Determine JPEG2000 mode purely from ihdr header box #5654 [radarhere] -- Updates for ImagePalette channel order #5599 - [radarhere] - - Fixed using info dictionary when writing multiple APNG frames #5611 [radarhere] @@ -68,12 +65,24 @@ Changelog (Pillow) - Fixed ImageOps expand with tuple border on P image #5615 [radarhere] -- Ensure TIFF RowsPerStrip is multiple of 8 for JPEG compression #5588 - [kmilos, radarhere] - - Fixed error saving APNG with duplicate frames and different duration times #5609 [thak1411, radarhere] +8.3.2 (2021-09-02) +------------------ + +- Add support for Python 3.10 #5569, #5570 + [hugovk, radarhere] + +- Ensure TIFF ``RowsPerStrip`` is multiple of 8 for JPEG compression #5588 + [kmilos, radarhere] + +- Updates for ``ImagePalette`` channel order #5599 + [radarhere] + +- Hide FriBiDi shim symbols to avoid conflict with real FriBiDi library #5651 + [nulano] + 8.3.1 (2021-07-06) ------------------ From a20d45fc0b7c450ce56f9aaa28eb81e245b8b651 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 8 Aug 2021 13:54:48 +0200 Subject: [PATCH 051/633] Fix 6-byte OOB read in FliDecode --- src/libImaging/FliDecode.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c index 3a6030703..7a396fb1f 100644 --- a/src/libImaging/FliDecode.c +++ b/src/libImaging/FliDecode.c @@ -223,8 +223,15 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt break; case 16: /* COPY chunk */ - if (state->xsize > bytes / state->ysize) { + if (INT32_MAX / state->xsize < state->ysize) { + /* Integer overflow, bail */ + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } + /* Note, have to check Data + size, not just ptr + size) */ + if (data + (state->xsize * state->ysize) > ptr + bytes) { /* not enough data for frame */ + /* UNDONE Unclear that we're actually going to leave the buffer at the right place. */ return ptr - buf; /* bytes consumed */ } for (y = 0; y < state->ysize; y++) { From d5edc5ff09060281f49dcb49dec2e3ab4d88b07e Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sun, 8 Aug 2021 14:04:05 +0200 Subject: [PATCH 052/633] FLI tests for Oss-fuzz crash. * Note, valgrind doesn't pick this up, it's only the oss-fuzz reproducer that catches it OMM. --- Tests/images/crash-5762152299364352.fli | Bin 0 -> 8731 bytes Tests/test_file_fli.py | 13 +++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 Tests/images/crash-5762152299364352.fli diff --git a/Tests/images/crash-5762152299364352.fli b/Tests/images/crash-5762152299364352.fli new file mode 100644 index 0000000000000000000000000000000000000000..944fe0b56c73b016c7599beb5b8e47cd33f0432f GIT binary patch literal 8731 zcmeGhe{2)y^_}g@4`aKIaSJo+dW4`dO##PA)8G+^?Zm_^!LIE5d?6(%)mHwLLHc48!QEj&Uk96VA=NgRotsN z3st@w0QmPMlg@I2)eK+(2EgWD3Q%8Xx7E39wvF|*_3JzDbJ^^6L{F{tK9_yH$L@AH zJfA~DO{%~Tqm{X&XYPP=eEYwC)*$72tiig~!?mBDEi&ai`4Ht;69`6HJ3nC*I- z4fL_crrA>qU+vb=I5dJmt^ACOO=j1|O)I@a`Bp*$0K=GRowXfjLYnrO0~uzd30TA$ zU5EC^y*f>L9){KW{s-wzI$eW(KaVFTjS4picl|-1 zI0oUm=J4XLX5YQ84mYeU9L9Zg4!bu=rJRWL$4I+#LaK=8f{pmM`HYB1!eMPRLjdGg zNY{adap8%JoJh~0Kt@RmTx?iTmEy3H=RzUQ z=WThI+t#1rMVXji1Y-ETt^Sr)&euwWcPCE5Gy`T~>}TgMjGZ`fa>MMIAD@hl?4A0< zTmPJVw(-vga(}5NS%LB6~xB6axFaGP_MSl_d&gA5YiOAMH z<##V1KRR{vwXxa1OeCfYyUyMJ0S|!}e;fp0C$u0%P#ICIpeRELK`rH@>Bo6w+w^Vqi&!;ELClKP)dB@g$nRXWRrKMt3vOlYiR2w}xsk_?1S=Ww9v7Q1I>J?jcG2@D*4SC~kS&2370JrQ~6r8V8 zxAgvxXkGR5!zno{WI7vnH162V?b=bu4EYnn6GV>Y^8Nk(Ua^Qr*_a{g7K(g9)B-w# z74Hsf>VE8y6V%jh?)Jf++xMuIo}~H9H5a;P3|?!88@?Y`k0aiMuyM`rFdFyB!0~fiIPx04Q{9I zo|<=ZRF;!x0S;i4cJMiXM=t|>bpYV8*8!eF;WrVVM*J4yF`cG1$*3lzK#yX{QN$4l z5CDu}0~1ylvjcw01B@e*F)~E;I3gLO+Grm|+K>}XGDGJZNFPCb5|Q+e0G;EK$Q9L| z5!IjK=>xp8v6vbWGL1S@l#6Og=7mUiXCt26+eI0f~GrVj1 z(l`-=wc#H*FHLQD@Bw^P&k&#N!`gz_r6X1d41T>c-+ty<`r`ljP$obr7Gj0%Kpd&hD@~9S*14;c&RAaJVa7 z8uXr31F3mf&S3czG}J*a^qQGQFf}md2B!Q0j*KmzX7=EKSW2KW3=Iweli?-{TT@$C zZ(WB8!No!bOlCvq1jfRcO%@YdTT{oHZ5=3VFk3deTG+rNsr8Ld9P`zsvuOdGhe_*=~)OZfp#~6ayNmpcJixMutJhNV)z2c}5L8KJqS2Bqn&N zl1PkU;Kz3z((Yy|oP0saAH09F0TLp literal 0 HcmV?d00001 diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 1c1abf2b1..675e06bf8 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -138,3 +138,16 @@ def test_timeouts(test_file): with Image.open(f) as im: with pytest.raises(OSError): im.load() + + +@pytest.mark.parametrize( + "test_file", + [ + "Tests/images/crash-5762152299364352.fli", + ], +) +def test_crash(test_file): + with open(test_file, "rb") as f: + with Image.open(f) as im: + with pytest.raises(OSError): + im.load() From 1dc6564eb7ee8f28fb16eeffaf3572f3e1d5aa29 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 23 Aug 2021 19:10:49 +0300 Subject: [PATCH 053/633] Raise ValueError if color specifier is too long --- Tests/test_imagecolor.py | 9 +++++++++ src/PIL/ImageColor.py | 2 ++ 2 files changed, 11 insertions(+) diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index b5d693796..dbe8b9e95 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -191,3 +191,12 @@ def test_rounding_errors(): assert (255, 255) == ImageColor.getcolor("white", "LA") assert (163, 33) == ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA") Image.new("LA", (1, 1), "white") + + +def test_color_too_long(): + # Arrange + color_too_long = "hsl(" + "1" * 100 + ")" + + # Act / Assert + with pytest.raises(ValueError): + ImageColor.getrgb(color_too_long) diff --git a/src/PIL/ImageColor.py b/src/PIL/ImageColor.py index 51df44040..25f92f2c7 100644 --- a/src/PIL/ImageColor.py +++ b/src/PIL/ImageColor.py @@ -32,6 +32,8 @@ def getrgb(color): :param color: A color string :return: ``(red, green, blue[, alpha])`` """ + if len(color) > 100: + raise ValueError("color specifier is too long") color = color.lower() rgb = colormap.get(color, None) From 5d399603dbec531a6fd68da17cdbe6398a7a54ac Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 24 Aug 2021 16:08:31 +0300 Subject: [PATCH 054/633] Update test case Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/test_imagecolor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index dbe8b9e95..dcc44e6e3 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -195,7 +195,7 @@ def test_rounding_errors(): def test_color_too_long(): # Arrange - color_too_long = "hsl(" + "1" * 100 + ")" + color_too_long = "hsl(" + "1" * 40 + "," + "1" * 40 + "%," + "1" * 40 + "%)" # Act / Assert with pytest.raises(ValueError): From 949503b160b64c5671c690dd42d544ef2b145088 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 2 Sep 2021 15:04:28 +0300 Subject: [PATCH 055/633] Update release notes --- docs/releasenotes/2.7.0.rst | 2 +- docs/releasenotes/8.3.2.rst | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/releasenotes/2.7.0.rst b/docs/releasenotes/2.7.0.rst index 03000528f..660d33164 100644 --- a/docs/releasenotes/2.7.0.rst +++ b/docs/releasenotes/2.7.0.rst @@ -14,7 +14,7 @@ Png text chunk size limits To prevent potential denial of service attacks using compressed text chunks, there are now limits to the decompressed size of text chunks decoded from PNG images. If the limits are exceeded when opening a PNG -image a ``ValueError`` will be raised. +image a :py:exc:`ValueError` will be raised. Individual text chunks are limited to :py:attr:`PIL.PngImagePlugin.MAX_TEXT_CHUNK`, set to 1MB by diff --git a/docs/releasenotes/8.3.2.rst b/docs/releasenotes/8.3.2.rst index 0a795957e..6b5c759fc 100644 --- a/docs/releasenotes/8.3.2.rst +++ b/docs/releasenotes/8.3.2.rst @@ -1,6 +1,18 @@ 8.3.2 ----- +Security +======== + +* :cve:`CVE-2021-23437`: Avoid a potential ReDoS (regular expression denial of service) + in :py:class:`~PIL.ImageColor`'s :py:meth:`~PIL.ImageColor.getrgb` by raising + :py:exc:`ValueError` if the color specifier is too long. Present since Pillow 5.2.0. + +* Fix 6-byte out-of-bounds (OOB) read. The previous bounds check in ``FliDecode.c`` + incorrectly calculated the required read buffer size when copying a chunk, potentially + reading six extra bytes off the end of the allocated buffer from the heap. Present + since Pillow 7.1.0. This bug was found by Google's `OSS-Fuzz`_ `CIFuzz`_ runs. + Other Changes ============= @@ -24,4 +36,6 @@ Fixed regressions * Hide FriBiDi shim symbols to avoid conflict with real FriBiDi library (:pr:`5651`). +.. _OSS-Fuzz: https://github.com/google/oss-fuzz +.. _CIFuzz: https://google.github.io/oss-fuzz/getting-started/continuous-integration/ .. _no ABI changes: https://www.python.org/downloads/release/python-3100rc1/ From 98319efcc5460e4d9a2c14e83f8fcdf082754918 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 2 Sep 2021 15:07:13 +0300 Subject: [PATCH 056/633] Update CHANGES.rst --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ca4145e90..8d3b9e0d8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -71,6 +71,12 @@ Changelog (Pillow) 8.3.2 (2021-09-02) ------------------ +- CVE-2021-23437 Raise ValueError if color specifier is too long + [hugovk, radarhere] + +- Fix 6-byte OOB read in FliDecode + [wiredfool] + - Add support for Python 3.10 #5569, #5570 [hugovk, radarhere] From ab90e939b53587bb6ca1e2709c797c0b9178c4e5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 6 Sep 2021 06:35:43 +1000 Subject: [PATCH 057/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8d3b9e0d8..da6c2e36f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 8.4.0 (unreleased) ------------------ +- Read ICO AND mask from end #5667 + [radarhere] + +- Actually check the framesize in FliDecode.c #5659 + [wiredfool] + - Determine JPEG2000 mode purely from ihdr header box #5654 [radarhere] From 632c11b2abe97bc80af696ace2da0bd8a6d74c74 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 6 Sep 2021 07:36:40 +1000 Subject: [PATCH 058/633] Install numpy on Python 3.10 --- .ci/install.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.ci/install.sh b/.ci/install.sh index 0f3a36bc4..c48acf9ee 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -32,8 +32,7 @@ python3 -m pip install -U pytest-cov python3 -m pip install -U pytest-timeout python3 -m pip install pyroma python3 -m pip install test-image-results -# TODO Remove condition when numpy supports 3.10 -if ! [ "$GHA_PYTHON_VERSION" == "3.10-dev" ]; then python3 -m pip install numpy ; fi +python3 -m pip install numpy # PyQt5 doesn't support PyPy3 if [[ $GHA_PYTHON_VERSION == 3.* ]]; then From ecf3a34717f077045f8f473e17546477fc1f74ad Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 7 Sep 2021 06:30:44 +1000 Subject: [PATCH 059/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index da6c2e36f..f39612e70 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.4.0 (unreleased) ------------------ +- Copy Python palette to new image in quantize() #5696 + [radarhere] + - Read ICO AND mask from end #5667 [radarhere] From 75a2ece0d1a436bc18a2301ece11b2b800ca50d4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 7 Sep 2021 08:25:56 +1000 Subject: [PATCH 060/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f39612e70..3e5c8029f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.4.0 (unreleased) ------------------ +- Added "exif" keyword argument to TIFF saving #5575 + [radarhere] + - Copy Python palette to new image in quantize() #5696 [radarhere] From 9509a73ed9080640d8d6b95f514b77329d123fa1 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 7 Sep 2021 17:00:47 +0300 Subject: [PATCH 061/633] Temporarily pin docutils to fix bullets in sphinx_rtd_theme --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 38011fd39..5f7f01aae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,8 @@ black check-manifest coverage defusedxml +# Remove docutils once https://github.com/readthedocs/sphinx_rtd_theme/issues/1115 released +docutils==0.16 markdown2 olefile packaging From df105aa3f7b564e472c491c71eea0473a333e411 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 8 Sep 2021 07:40:58 +1000 Subject: [PATCH 062/633] Updated harfbuzz to 2.9.1 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index ffffaf662..361beb4df 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -277,9 +277,9 @@ deps = { "libs": [r"*.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/2.9.0.zip", - "filename": "harfbuzz-2.9.0.zip", - "dir": "harfbuzz-2.9.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/2.9.1.zip", + "filename": "harfbuzz-2.9.1.zip", + "dir": "harfbuzz-2.9.1", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From c39e545949ecd2a3cbb81c9bc0813903a3d501ed Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 11 Sep 2021 19:24:24 +1000 Subject: [PATCH 063/633] Updated docstring --- src/PIL/TiffImagePlugin.py | 48 +++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index d1e48ef81..bd760d35f 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -437,35 +437,41 @@ class ImageFileDirectory_v2(MutableMapping): Data Structures: - * self.tagtype = {} + * ``self.tagtype = {}`` * Key: numerical tiff tag number * Value: integer corresponding to the data type from - ~PIL.TiffTags.TYPES` + :py:data:`.TiffTags.TYPES` - .. versionadded:: 3.0.0 - """ + .. versionadded:: 3.0.0 - """ - Documentation: + 'internal' data structures: - 'internal' data structures: - * self._tags_v2 = {} Key: numerical tiff tag number - Value: decoded data, as tuple for multiple values - * self._tagdata = {} Key: numerical tiff tag number - Value: undecoded byte string from file - * self._tags_v1 = {} Key: numerical tiff tag number - Value: decoded data in the v1 format + * ``self._tags_v2 = {}`` - Tags will be found in the private attributes self._tagdata, and in - self._tags_v2 once decoded. + * Key: numerical tiff tag number + * Value: decoded data, as tuple for multiple values - self.legacy_api is a value for internal use, and shouldn't be - changed from outside code. In cooperation with the - ImageFileDirectory_v1 class, if legacy_api is true, then decoded - tags will be populated into both _tags_v1 and _tags_v2. _tags_v2 - will be used if this IFD is used in the TIFF save routine. Tags - should be read from _tags_v1 if legacy_api == true. + * ``self._tagdata = {}`` + + * Key: numerical tiff tag number + * Value: undecoded byte string from file + + * ``self._tags_v1 = {}`` + + * Key: numerical tiff tag number + * Value: decoded data in the v1 format + + Tags will be found in the private attributes ``self._tagdata``, and in + ``self._tags_v2`` once decoded. + + ``self.legacy_api`` is a value for internal use, and shouldn't be changed + from outside code. In cooperation with + :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`, if ``legacy_api`` + is true, then decoded tags will be populated into both ``_tags_v1`` and + ``_tags_v2``. ``_tags_v2`` will be used if this IFD is used in the TIFF + save routine. Tags should be read from ``_tags_v1`` if + ``legacy_api == true``. """ From 8264aa81d92477b8338616b37165e41ee57accf0 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 11 Sep 2021 19:48:43 +1000 Subject: [PATCH 064/633] Updated capitalisation Co-authored-by: Hugo van Kemenade --- src/PIL/TiffImagePlugin.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index bd760d35f..eb33e3218 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -439,27 +439,27 @@ class ImageFileDirectory_v2(MutableMapping): * ``self.tagtype = {}`` - * Key: numerical tiff tag number + * Key: numerical TIFF tag number * Value: integer corresponding to the data type from :py:data:`.TiffTags.TYPES` .. versionadded:: 3.0.0 - 'internal' data structures: + 'Internal' data structures: * ``self._tags_v2 = {}`` - * Key: numerical tiff tag number + * Key: numerical TIFF tag number * Value: decoded data, as tuple for multiple values * ``self._tagdata = {}`` - * Key: numerical tiff tag number + * Key: numerical TIFF tag number * Value: undecoded byte string from file * ``self._tags_v1 = {}`` - * Key: numerical tiff tag number + * Key: numerical TIFF tag number * Value: decoded data in the v1 format Tags will be found in the private attributes ``self._tagdata``, and in From d72b507a4276eef58beee4ad399bf0684c055c68 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 5 Sep 2021 23:34:03 +0300 Subject: [PATCH 065/633] Remove unused job --- .github/workflows/test-valgrind.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index 7b8474d0f..28b1caff5 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -42,11 +42,3 @@ jobs: sudo chown -R 1000 $GITHUB_WORKSPACE docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} sudo chown -R runner $GITHUB_WORKSPACE - - success: - needs: build - runs-on: ubuntu-latest - name: Valgrind Test Successful - steps: - - name: Success - run: echo Valgrind Test Successful From 2bd2fbbf0be56a9bad56eef3e64f4f21f002ba2d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 6 Sep 2021 15:07:53 +0300 Subject: [PATCH 066/633] Move MinGW to own workflow --- .github/mergify.yml | 1 + .github/workflows/test-mingw.yml | 84 ++++++++++++++++++++++++++++++ .github/workflows/test-windows.yml | 75 +------------------------- 3 files changed, 87 insertions(+), 73 deletions(-) create mode 100644 .github/workflows/test-mingw.yml diff --git a/.github/mergify.yml b/.github/mergify.yml index 4b8b113d3..8b289bda6 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -7,6 +7,7 @@ pull_request_rules: - status-success=Test Successful - status-success=Docker Test Successful - status-success=Windows Test Successful + - status-success=MinGW Test Successful - status-success=continuous-integration/appveyor/pr actions: merge: diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml new file mode 100644 index 000000000..982ef2d08 --- /dev/null +++ b/.github/workflows/test-mingw.yml @@ -0,0 +1,84 @@ +name: Test MinGW + +on: [push, pull_request] + +jobs: + msys: + runs-on: windows-2019 + strategy: + fail-fast: false + matrix: + 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: + run: + shell: bash.exe --login -eo pipefail "{0}" + env: + MSYSTEM: ${{ matrix.mingw }} + CHERE_INVOKING: 1 + + timeout-minutes: 30 + name: ${{ matrix.name }} + + steps: + - name: Checkout Pillow + uses: actions/checkout@v2 + + - name: Set up shell + run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH + shell: pwsh + + - 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-pyqt5 \ + ${{ matrix.package }}-python3-setuptools \ + ${{ matrix.package }}-freetype \ + ${{ matrix.package }}-ghostscript \ + ${{ matrix.package }}-lcms2 \ + ${{ matrix.package }}-libimagequant \ + ${{ matrix.package }}-libjpeg-turbo \ + ${{ matrix.package }}-libraqm \ + ${{ matrix.package }}-libtiff \ + ${{ matrix.package }}-libwebp \ + ${{ matrix.package }}-openjpeg2 \ + subversion + + python3 -m pip install pyroma pytest pytest-cov + + pushd depends && ./install_extra_test_images.sh && popd + + - name: Build Pillow + run: CFLAGS="-coverage" python3 setup.py build_ext install + + - name: Test Pillow + run: | + python3 selftest.py --installed + python3 -c "from PIL import Image" + python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests + + - name: Upload coverage + run: | + python3 -m pip install codecov + bash <(curl -s https://codecov.io/bash) -F GHA_Windows + env: + CODECOV_NAME: ${{ matrix.name }} + + success: + needs: msys + runs-on: ubuntu-latest + name: MinGW Test Successful + steps: + - name: Success + run: echo MinGW Test Successful diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index ce04ba5ca..6ef93b337 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -17,6 +17,7 @@ jobs: # PyPy 7.3.4+ only ships 64-bit binaries for Windows - python-version: "pypy-3.7" architecture: "x64" + timeout-minutes: 30 name: Python ${{ matrix.python-version }} ${{ matrix.architecture }} @@ -195,80 +196,8 @@ jobs: name: ${{ steps.wheel.outputs.dist }} path: dist\*.whl - msys: - runs-on: windows-2019 - - strategy: - fail-fast: false - matrix: - 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: - run: - shell: bash.exe --login -eo pipefail "{0}" - env: - MSYSTEM: ${{ matrix.mingw }} - CHERE_INVOKING: 1 - - timeout-minutes: 30 - name: ${{ matrix.name }} - - steps: - - uses: actions/checkout@v2 - - - name: Set up shell - run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH - shell: pwsh - - - 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-pyqt5 \ - ${{ matrix.package }}-python3-setuptools \ - ${{ matrix.package }}-freetype \ - ${{ matrix.package }}-ghostscript \ - ${{ matrix.package }}-lcms2 \ - ${{ matrix.package }}-libimagequant \ - ${{ matrix.package }}-libjpeg-turbo \ - ${{ matrix.package }}-libraqm \ - ${{ matrix.package }}-libtiff \ - ${{ matrix.package }}-libwebp \ - ${{ matrix.package }}-openjpeg2 \ - subversion - - python3 -m pip install pyroma pytest pytest-cov - - pushd depends && ./install_extra_test_images.sh && popd - - - name: Build Pillow - run: CFLAGS="-coverage" python3 setup.py build_ext install - - - name: Test Pillow - run: | - python3 selftest.py --installed - python3 -c "from PIL import Image" - python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests - - - name: Upload coverage - run: | - python3 -m pip install codecov - bash <(curl -s https://codecov.io/bash) -F GHA_Windows - env: - CODECOV_NAME: ${{ matrix.name }} - success: - needs: [build, msys] + needs: build runs-on: ubuntu-latest name: Windows Test Successful steps: From a761d9b0c747e8da63048ee2e56a4276e07519e3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 12 Sep 2021 10:10:31 +0300 Subject: [PATCH 067/633] Rename to "Build" for consistency Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/test-mingw.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index 982ef2d08..b9d2abeb3 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -3,7 +3,7 @@ name: Test MinGW on: [push, pull_request] jobs: - msys: + build: runs-on: windows-2019 strategy: fail-fast: false @@ -76,7 +76,7 @@ jobs: CODECOV_NAME: ${{ matrix.name }} success: - needs: msys + needs: build runs-on: ubuntu-latest name: MinGW Test Successful steps: From 84ee93f7bfec22028581b68c5e899197b24be4f6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 13 Sep 2021 17:14:59 +1000 Subject: [PATCH 068/633] Moved _info function into docstring --- selftest.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/selftest.py b/selftest.py index 8d77cc5a9..4ebd7cc00 100755 --- a/selftest.py +++ b/selftest.py @@ -14,17 +14,13 @@ except AttributeError: pass -def _info(im): - im.load() - return im.format, im.mode, im.size - - def testimage(): """ PIL lets you create in-memory images with various pixel types: >>> from PIL import Image, ImageDraw, ImageFilter, ImageMath >>> im = Image.new("1", (128, 128)) # monochrome + >>> def _info(im): return (im.format, im.mode, im.size) >>> _info(im) (None, '1', (128, 128)) >>> _info(Image.new("L", (128, 128))) # grayscale (luminance) From 21906a8172d6fcd79f8cbd3cc1e9fc0a9c29d71b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 15 Sep 2021 16:13:53 +0300 Subject: [PATCH 069/633] Docs: Update CI targets table --- docs/installation.rst | 69 +++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 03f7157bb..63984d0cc 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -443,41 +443,40 @@ Continuous Integration Targets These platforms are built and tested for every change. -+----------------------------------+---------------------------+---------------------+ -| Operating system | Tested Python versions | Tested architecture | -+==================================+===========================+=====================+ -| Alpine | 3.8 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| Arch | 3.8 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| Amazon Linux 2 | 3.7 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| CentOS 7 | 3.6 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| CentOS 8 | 3.6 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| Debian 10 Buster | 3.7 | x86 | -+----------------------------------+---------------------------+---------------------+ -| Fedora 33 | 3.9 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| Fedora 34 | 3.9 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9, PyPy3 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| Ubuntu Linux 16.04 LTS (Xenial) | 3.6, 3.7, 3.8, 3.9, PyPy3 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| Ubuntu Linux 18.04 LTS (Bionic) | 3.6, 3.7, 3.8, 3.9, PyPy3 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| Windows Server 2016 | 3.6 | x86-64 | -+----------------------------------+---------------------------+---------------------+ -| Windows Server 2019 | 3.6, 3.7, 3.8, 3.9 | x86, x86-64 | -| +---------------------------+---------------------+ -| | PyPy3 | x86 | -| +---------------------------+---------------------+ -| | 3.9/MinGW | x86, x86-64 | -+----------------------------------+---------------------------+---------------------+ ++----------------------------------+---------------------------------+---------------------+ +| Operating system | Tested Python versions | Tested architecture | ++==================================+=================================+=====================+ +| Alpine | 3.9 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ +| Amazon Linux 2 | 3.7 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ +| Arch | 3.9 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ +| CentOS 7 | 3.6 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ +| CentOS 8 | 3.6 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ +| Debian 10 Buster | 3.7 | x86 | ++----------------------------------+---------------------------------+---------------------+ +| Fedora 33 | 3.9 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ +| Fedora 34 | 3.9 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ +| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ +| Ubuntu Linux 18.04 LTS (Bionic) | 3.6 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ +| Ubuntu Linux 20.04 LTS (Focal) | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ +| Ubuntu Linux 20.04 LTS (Focal) | 3.8 | arm64v8, ppc64le, | +| | | s390x | ++----------------------------------+---------------------------------+---------------------+ +| Windows Server 2016 | 3.6 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ +| Windows Server 2019 | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86, x86-64 | +| +---------------------------------+---------------------+ +| | 3.9/MinGW | x86, x86-64 | ++----------------------------------+---------------------------------+---------------------+ Other Platforms From 9301422d16bf2e020c7e5fcfef67d411153844b8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Sep 2021 08:25:51 +1000 Subject: [PATCH 070/633] Merged identical cells --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 63984d0cc..426c1e526 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -467,8 +467,8 @@ These platforms are built and tested for every change. | Ubuntu Linux 18.04 LTS (Bionic) | 3.6 | x86-64 | +----------------------------------+---------------------------------+---------------------+ | Ubuntu Linux 20.04 LTS (Focal) | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ -| Ubuntu Linux 20.04 LTS (Focal) | 3.8 | arm64v8, ppc64le, | +| +---------------------------------+---------------------+ +| | 3.8 | arm64v8, ppc64le, | | | | s390x | +----------------------------------+---------------------------------+---------------------+ | Windows Server 2016 | 3.6 | x86-64 | From 5b7add9df76724a1534464bf396f9aa63935f595 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 16 Sep 2021 14:40:34 +0300 Subject: [PATCH 071/633] Add CentOS Stream 8 --- .github/workflows/test-docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 8274549d4..3ce2508de 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -20,6 +20,7 @@ jobs: arch, centos-7-amd64, centos-8-amd64, + centos-stream-8-amd64, debian-10-buster-x86, fedora-33-amd64, fedora-34-amd64, From 4cfc9721d292cc78681e5ed57f9e27e2596cd0ea Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 17 Sep 2021 21:26:10 +1000 Subject: [PATCH 072/633] Added CentOS Stream 8 to CI targets --- docs/installation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index 426c1e526..88fac2e58 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -456,6 +456,8 @@ These platforms are built and tested for every change. +----------------------------------+---------------------------------+---------------------+ | CentOS 8 | 3.6 | x86-64 | +----------------------------------+---------------------------------+---------------------+ +| CentOS Stream 8 | 3.6 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ | Debian 10 Buster | 3.7 | x86 | +----------------------------------+---------------------------------+---------------------+ | Fedora 33 | 3.9 | x86-64 | From 83f6a909d61a5035b601ad1d950762687899ed6f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 17 Sep 2021 21:40:00 +1000 Subject: [PATCH 073/633] Updated macOS tested Pillow versions [ci skip] --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 426c1e526..17efb911b 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -493,9 +493,9 @@ These platforms have been reported to work at the versions mentioned. | Operating system | | Tested Python | | Latest tested | | Tested | | | | versions | | Pillow version | | processors | +==================================+===========================+==================+==============+ -| macOS 11.0 Big Sur | 3.7, 3.8, 3.9 | 8.3.1 |arm | +| macOS 11.0 Big Sur | 3.7, 3.8, 3.9 | 8.3.2 |arm | | +---------------------------+------------------+--------------+ -| | 3.6, 3.7, 3.8, 3.9 | 8.3.1 |x86-64 | +| | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 | +----------------------------------+---------------------------+------------------+--------------+ | macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.1 |x86-64 | | +---------------------------+------------------+ | From 58fcc6edeee367f956397ad2372414f916527d22 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 18 Sep 2021 08:19:11 +1000 Subject: [PATCH 074/633] Updated harfbuzz to 3.0.0 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 361beb4df..e10e3756f 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -277,9 +277,9 @@ deps = { "libs": [r"*.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/2.9.1.zip", - "filename": "harfbuzz-2.9.1.zip", - "dir": "harfbuzz-2.9.1", + "url": "https://github.com/harfbuzz/harfbuzz/archive/3.0.0.zip", + "filename": "harfbuzz-3.0.0.zip", + "dir": "harfbuzz-3.0.0", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 8fd843bf16f8534626f75708115077baaf4d5de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Sat, 18 Sep 2021 22:57:43 +0200 Subject: [PATCH 075/633] Delete raqm.cmake --- winbuild/raqm.cmake | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 winbuild/raqm.cmake diff --git a/winbuild/raqm.cmake b/winbuild/raqm.cmake deleted file mode 100644 index 82c9cdc70..000000000 --- a/winbuild/raqm.cmake +++ /dev/null @@ -1,39 +0,0 @@ -cmake_minimum_required(VERSION 3.12) - -project(libraqm) - - -find_library(fribidi NAMES fribidi) -find_library(harfbuzz NAMES harfbuzz) -find_library(freetype NAMES freetype) - -add_definitions(-DFRIBIDI_LIB_STATIC) - - -function(raqm_conf) - file(READ configure.ac RAQM_CONF) - string(REGEX MATCH "\\[([0-9]+)\\.([0-9]+)\\.([0-9]+)\\]," _ "${RAQM_CONF}") - set(RAQM_VERSION_MAJOR "${CMAKE_MATCH_1}") - set(RAQM_VERSION_MINOR "${CMAKE_MATCH_2}") - set(RAQM_VERSION_MICRO "${CMAKE_MATCH_3}") - set(RAQM_VERSION "${RAQM_VERSION_MAJOR}.${RAQM_VERSION_MINOR}.${RAQM_VERSION_MICRO}") - message("detected libraqm version ${RAQM_VERSION}") - configure_file(src/raqm-version.h.in src/raqm-version.h @ONLY) -endfunction() -raqm_conf() - - -set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) -set(RAQM_SOURCES - src/raqm.c) -set(RAQM_HEADERS - src/raqm.h - src/raqm-version.h) - -add_library(libraqm SHARED - ${RAQM_SOURCES} - ${RAQM_HEADERS}) -target_link_libraries(libraqm - ${fribidi} - ${harfbuzz} - ${freetype}) From 2205f6050e4183b1ecc3f05e7f30efd827ca7853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Sat, 18 Sep 2021 23:00:39 +0200 Subject: [PATCH 076/633] document --no-fribidi option --- winbuild/build.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/winbuild/build.rst b/winbuild/build.rst index 7493c30e5..b30a94226 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -55,8 +55,8 @@ behaviour of ``build_prepare.py``: * ``-v`` will print generated scripts. * ``--no-imagequant`` will skip GPL-licensed ``libimagequant`` optional dependency -* ``--no-raqm`` will skip optional dependency Raqm (which itself depends on - LGPL-licensed ``fribidi``). +* ``--no-fribidi`` or ``--no-raqm`` will skip optional LGPL-licensed dependency FriBiDi + (required for Raqm text shaping). * ``--python=`` and ``--executable=`` override ``PYTHON`` and ``EXECUTABLE``. * ``--architecture=`` overrides ``ARCHITECTURE``. * ``--dir=`` and ``--depends=`` override ``PILLOW_BUILD`` From c1db69cc3f245d0565c4a39556ab4574d2c61dd5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 20 Sep 2021 22:16:37 +1000 Subject: [PATCH 077/633] Renamed register_open accept method for consistency --- docs/example/DdsImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index 78aa3ce72..272409416 100644 --- a/docs/example/DdsImagePlugin.py +++ b/docs/example/DdsImagePlugin.py @@ -269,9 +269,9 @@ Image.register_decoder("DXT1", DXT1Decoder) Image.register_decoder("DXT5", DXT5Decoder) -def _validate(prefix): +def _accept(prefix): return prefix[:4] == b"DDS " -Image.register_open(DdsImageFile.format, DdsImageFile, _validate) +Image.register_open(DdsImageFile.format, DdsImageFile, _accept) Image.register_extension(DdsImageFile.format, ".dds") From 312ed69c0196e2fbeaa9a7856e9e34e41c1e4b0c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 20 Sep 2021 22:17:06 +1000 Subject: [PATCH 078/633] Commented unused method --- src/PIL/MpoImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 7244aa2a9..707ce182c 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -22,8 +22,8 @@ from . import Image, ImageFile, JpegImagePlugin from ._binary import i16be as i16 -def _accept(prefix): - return JpegImagePlugin._accept(prefix) +# def _accept(prefix): +# return JpegImagePlugin._accept(prefix) def _save(im, fp, filename): From 99428bb878ac1cd8a61a5089ca43741d918c4ffb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Sep 2021 12:22:07 +0000 Subject: [PATCH 079/633] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/MpoImagePlugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 707ce182c..7ccf27c42 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -21,7 +21,6 @@ from . import Image, ImageFile, JpegImagePlugin from ._binary import i16be as i16 - # def _accept(prefix): # return JpegImagePlugin._accept(prefix) From 836e6d98a3ce7cded8942d485ecf35010d996694 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 21 Sep 2021 07:46:37 +1000 Subject: [PATCH 080/633] Updated libimagequant to 2.16.0 --- depends/install_imagequant.sh | 2 +- docs/installation.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index c6c7506a3..38f0e75d6 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,7 +1,7 @@ #!/bin/bash # install libimagequant -archive=libimagequant-2.15.1 +archive=libimagequant-2.16.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/docs/installation.rst b/docs/installation.rst index 9d384e65b..f795111e8 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -185,7 +185,7 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-2.15.1** + * Pillow has been tested with libimagequant **2.6-2.16.0** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. From 52fa7aefb59a37eb0db7dc8ddef2f53b018abe60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Tue, 21 Sep 2021 03:37:58 +0200 Subject: [PATCH 081/633] update winbuild imagequant to 2.16.0 --- winbuild/build_prepare.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index e10e3756f..64bea70e8 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -257,10 +257,10 @@ deps = { "libs": [r"bin\*.lib"], }, "libimagequant": { - # Merge master into msvc (matches 2.14.1 except for version bump) - "url": "https://github.com/ImageOptim/libimagequant/archive/16adaded22d1f90db5c9154a06d00a8b672ca09a.zip", # noqa: E501 - "filename": "libimagequant-16adaded22d1f90db5c9154a06d00a8b672ca09a.zip", - "dir": "libimagequant-16adaded22d1f90db5c9154a06d00a8b672ca09a", + # commit: Merge branch 'master' into msvc (matches 2.16.0 tag) + "url": "https://github.com/ImageOptim/libimagequant/archive/f41ee301ff3a407b16991af3dbe03910919bbdc3.zip", # noqa: E501 + "filename": "libimagequant-f41ee301ff3a407b16991af3dbe03910919bbdc3.zip", + "dir": "libimagequant-f41ee301ff3a407b16991af3dbe03910919bbdc3", "patch": { "CMakeLists.txt": { "add_library": "add_compile_options(-openmp-)\r\nadd_library", From aa27c4d96b80b51fefddc6d4d4fda27bd885c710 Mon Sep 17 00:00:00 2001 From: nulano Date: Tue, 21 Sep 2021 04:49:35 +0200 Subject: [PATCH 082/633] update imagequant patches --- winbuild/build_prepare.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 64bea70e8..5f2643985 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -263,18 +263,19 @@ deps = { "dir": "libimagequant-f41ee301ff3a407b16991af3dbe03910919bbdc3", "patch": { "CMakeLists.txt": { - "add_library": "add_compile_options(-openmp-)\r\nadd_library", - " SHARED": " STATIC", + "if(OPENMP_FOUND)": "if(false)", + "install": "#install", } }, "build": [ # lint: do not inline cmd_cmake(), cmd_nmake(target="clean"), - cmd_nmake(), + cmd_nmake(target="imagequant_a"), + cmd_copy("imagequant_a.lib", "imagequant.lib"), ], "headers": [r"*.h"], - "libs": [r"*.lib"], + "libs": [r"imagequant.lib"], }, "harfbuzz": { "url": "https://github.com/harfbuzz/harfbuzz/archive/3.0.0.zip", @@ -437,6 +438,7 @@ def build_dep(name): assert patch_from in text text = text.replace(patch_from, patch_to) with open(patch_file, "w") as f: + print(f"Patching {patch_file}") f.write(text) banner = f"Building {name} ({dir})" From 3fdb2190d251db0934d4513cf1f873cc4c87a37f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 24 Sep 2021 08:36:57 +1000 Subject: [PATCH 083/633] Updated fribidi to 1.0.11 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 5f2643985..807cbbf27 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -290,9 +290,9 @@ deps = { "libs": [r"*.lib"], }, "fribidi": { - "url": "https://github.com/fribidi/fribidi/archive/v1.0.10.zip", - "filename": "fribidi-1.0.10.zip", - "dir": "fribidi-1.0.10", + "url": "https://github.com/fribidi/fribidi/archive/v1.0.11.zip", + "filename": "fribidi-1.0.11.zip", + "dir": "fribidi-1.0.11", "build": [ cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"), cmd_cmake(), From a80f8133d623fa371e23cb35799a009f4cbe1bdc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 27 Sep 2021 23:19:05 +1000 Subject: [PATCH 084/633] Updated Ghostscript to 9.55.0 --- .appveyor.yml | 4 ++-- .github/workflows/test-windows.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 0cf1d5a9e..93140b89c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -24,8 +24,8 @@ install: - mv c:\pillow-depends-master c:\pillow-depends - xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images - 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\ -- ..\pillow-depends\gs9540w32.exe /S -- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.54.0\bin;%PATH% +- ..\pillow-depends\gs9550w32.exe /S +- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.55.0\bin;%PATH% - cd c:\pillow\winbuild\ - ps: | c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\ diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 6ef93b337..e2a1e8cd4 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -61,8 +61,8 @@ jobs: 7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\" echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH - winbuild\depends\gs9540w32.exe /S - echo "C:\Program Files (x86)\gs\gs9.54.0\bin" >> $env:GITHUB_PATH + winbuild\depends\gs9550w32.exe /S + echo "C:\Program Files (x86)\gs\gs9.55.0\bin" >> $env:GITHUB_PATH xcopy /S /Y winbuild\depends\test_images\* Tests\images\ From aeb3c810d05730251ebfeaceccc88b0b77108b71 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 27 Sep 2021 22:37:55 +0200 Subject: [PATCH 085/633] upgrade vendored raqm to 0.7.2 --- src/thirdparty/raqm/NEWS | 14 ++++++++ src/thirdparty/raqm/{README => README.md} | 22 ++++++------ src/thirdparty/raqm/raqm-version.h | 4 +-- src/thirdparty/raqm/raqm.c | 33 ++++++++++++++++-- src/thirdparty/raqm/raqm.h | 42 +++++++++++++---------- 5 files changed, 80 insertions(+), 35 deletions(-) rename src/thirdparty/raqm/{README => README.md} (79%) diff --git a/src/thirdparty/raqm/NEWS b/src/thirdparty/raqm/NEWS index 29c9ae0e5..c49176a95 100644 --- a/src/thirdparty/raqm/NEWS +++ b/src/thirdparty/raqm/NEWS @@ -1,3 +1,17 @@ +Overview of changes leading to 0.7.1 +Monday, September 27, 2021 +==================================== + +Fix test failure with newer HarfBuzz versions. + +Apply FT_Face transformation matrix when built against FreeType 2.11 or later. + +Add meson build system. Autotools build system will be dropped in next release. + +Improve MSVC support. + +Build and documentation fixes. + Overview of changes leading to 0.7.1 Sunday, November 22, 2020 ==================================== diff --git a/src/thirdparty/raqm/README b/src/thirdparty/raqm/README.md similarity index 79% rename from src/thirdparty/raqm/README rename to src/thirdparty/raqm/README.md index 7940bf3b6..64937343a 100644 --- a/src/thirdparty/raqm/README +++ b/src/thirdparty/raqm/README.md @@ -1,8 +1,7 @@ Raqm ==== -[![Linux & macOS build](https://travis-ci.org/HOST-Oman/libraqm.svg?branch=master)](https://travis-ci.org/HOST-Oman/libraqm) -[![Windows build](https://img.shields.io/appveyor/ci/HOSTOman/libraqm/master.svg)](https://ci.appveyor.com/project/HOSTOman/libraqm) +[![Build](https://github.com/HOST-Oman/libraqm/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/HOST-Oman/libraqm/actions) Raqm is a small library that encapsulates the logic for complex text layout and provides a convenient API. @@ -15,7 +14,7 @@ The documentation can be accessed on the web at: > http://host-oman.github.io/libraqm/ Raqm (Arabic: رَقْم) is writing, also number or digit and the Arabic word for -digital (رَقَمِيّ) shares the same root, so it is a play on “digital writing”. +digital (رَقَمِيّ) shares the same root, so it is a play on “digital writing”. Building -------- @@ -30,31 +29,30 @@ To build the documentation you will also need: To install dependencies on Fedora: - sudo dnf install freetype-devel harfbuzz-devel fribidi-devel gtk-doc + sudo dnf install freetype-devel harfbuzz-devel fribidi-devel meson gtk-doc To install dependencies on Ubuntu: - sudo apt-get install libfreetype6-dev libharfbuzz-dev libfribidi-dev \ - gtk-doc-tools + sudo apt-get install libfreetype6-dev libharfbuzz-dev libfribidi-dev meson gtk-doc-tools On Mac OS X you can use Homebrew: - brew install freetype harfbuzz fribidi gtk-doc + brew install freetype harfbuzz fribidi meson gtk-doc export XML_CATALOG_FILES="/usr/local/etc/xml/catalog" # for the docs Once you have the source code and the dependencies, you can proceed to build. To do that, run the customary sequence of commands in the source code directory: - $ ./configure - $ make - $ make install + $ meson build + $ ninja -C build + $ ninja -C build install -To build the documentation, pass `--enable-gtk-doc` to the `configure` script. +To build the documentation, pass `-Ddocs=enable` to the `meson`. To run the tests: - $ make check + $ ninja -C test Contributing ------------ diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h index 94b25ada7..8b115f612 100644 --- a/src/thirdparty/raqm/raqm-version.h +++ b/src/thirdparty/raqm/raqm-version.h @@ -33,9 +33,9 @@ #define RAQM_VERSION_MAJOR 0 #define RAQM_VERSION_MINOR 7 -#define RAQM_VERSION_MICRO 1 +#define RAQM_VERSION_MICRO 2 -#define RAQM_VERSION_STRING "0.7.1" +#define RAQM_VERSION_STRING "0.7.2" #define RAQM_VERSION_ATLEAST(major,minor,micro) \ ((major)*10000+(minor)*100+(micro) <= \ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 9f6be676c..f396a545c 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -455,8 +455,6 @@ raqm_set_text_utf8 (raqm_t *rq, return true; } - RAQM_TEST ("Text is: %s\n", text); - rq->flags |= RAQM_FLAG_UTF8; rq->text_utf8 = malloc (sizeof (char) * len); @@ -1556,6 +1554,21 @@ _raqm_resolve_scripts (raqm_t *rq) return true; } +static void +_raqm_ft_transform (int *x, + int *y, + FT_Matrix matrix) +{ + FT_Vector vector; + vector.x = *x; + vector.y = *y; + + FT_Vector_Transform (&vector, &matrix); + + *x = vector.x; + *y = vector.y; +} + static bool _raqm_shape (raqm_t *rq) { @@ -1585,6 +1598,22 @@ _raqm_shape (raqm_t *rq) hb_shape_full (run->font, run->buffer, rq->features, rq->features_len, NULL); + +#ifdef HAVE_FT_GET_TRANSFORM + { + FT_Matrix matrix; + hb_glyph_position_t *pos; + unsigned int len; + + FT_Get_Transform (hb_ft_font_get_face (run->font), &matrix, NULL); + pos = hb_buffer_get_glyph_positions (run->buffer, &len); + for (unsigned int i = 0; i < len; i++) + { + _raqm_ft_transform (&pos[i].x_advance, &pos[i].y_advance, matrix); + _raqm_ft_transform (&pos[i].x_offset, &pos[i].y_offset, matrix); + } + } +#endif } return true; diff --git a/src/thirdparty/raqm/raqm.h b/src/thirdparty/raqm/raqm.h index 1a33fe8ba..342afc8b2 100644 --- a/src/thirdparty/raqm/raqm.h +++ b/src/thirdparty/raqm/raqm.h @@ -30,6 +30,10 @@ #include "config.h" #endif +#ifndef RAQM_API +#define RAQM_API +#endif + #include #include #include @@ -93,86 +97,86 @@ typedef struct raqm_glyph_t { FT_Face ftface; } raqm_glyph_t; -raqm_t * +RAQM_API raqm_t * raqm_create (void); -raqm_t * +RAQM_API raqm_t * raqm_reference (raqm_t *rq); -void +RAQM_API void raqm_destroy (raqm_t *rq); -bool +RAQM_API bool raqm_set_text (raqm_t *rq, const uint32_t *text, size_t len); -bool +RAQM_API bool raqm_set_text_utf8 (raqm_t *rq, const char *text, size_t len); -bool +RAQM_API bool raqm_set_par_direction (raqm_t *rq, raqm_direction_t dir); -bool +RAQM_API bool raqm_set_language (raqm_t *rq, const char *lang, size_t start, size_t len); -bool +RAQM_API bool raqm_add_font_feature (raqm_t *rq, const char *feature, int len); -bool +RAQM_API bool raqm_set_freetype_face (raqm_t *rq, FT_Face face); -bool +RAQM_API bool raqm_set_freetype_face_range (raqm_t *rq, FT_Face face, size_t start, size_t len); -bool +RAQM_API bool raqm_set_freetype_load_flags (raqm_t *rq, int flags); -bool +RAQM_API bool raqm_set_invisible_glyph (raqm_t *rq, int gid); -bool +RAQM_API bool raqm_layout (raqm_t *rq); -raqm_glyph_t * +RAQM_API raqm_glyph_t * raqm_get_glyphs (raqm_t *rq, size_t *length); -bool +RAQM_API bool raqm_index_to_position (raqm_t *rq, size_t *index, int *x, int *y); -bool +RAQM_API bool raqm_position_to_index (raqm_t *rq, int x, int y, size_t *index); -void +RAQM_API void raqm_version (unsigned int *major, unsigned int *minor, unsigned int *micro); -const char * +RAQM_API const char * raqm_version_string (void); -bool +RAQM_API bool raqm_version_atleast (unsigned int major, unsigned int minor, unsigned int micro); From 6565d13275cead21dc6f369204f0dc3d0b43bc18 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 27 Sep 2021 22:50:30 +0200 Subject: [PATCH 086/633] detect FreeType / HarfBuzz features --- src/thirdparty/raqm/raqm.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index f396a545c..31161c9d9 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -39,6 +39,21 @@ #include #include +#if FREETYPE_MAJOR > 2 || \ + FREETYPE_MAJOR == 2 && FREETYPE_MINOR >= 11 +#define HAVE_FT_GET_TRANSFORM +#endif + +#if HB_VERSION_ATLEAST(2, 0, 0) +#define HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH +#endif + +#if HB_VERSION_ATLEAST(1, 8, 0) +#define HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES 1 +#else +#define HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES 0 +#endif + #include "raqm.h" #if FRIBIDI_MAJOR_VERSION >= 1 From 5b7fb4f9607b75ef3e1e5bdb898fc2e0bb210d72 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Sep 2021 11:44:47 +1000 Subject: [PATCH 087/633] Updated macOS tested Pillow versions [ci skip] --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index f795111e8..3b0ff96d6 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -499,7 +499,7 @@ These platforms have been reported to work at the versions mentioned. | +---------------------------+------------------+--------------+ | | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 | +----------------------------------+---------------------------+------------------+--------------+ -| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.1 |x86-64 | +| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 | | +---------------------------+------------------+ | | | 3.5 | 7.2.0 | | +----------------------------------+---------------------------+------------------+--------------+ From 63879f04b119551a3d562dd2da24464e199b9ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Komar=C4=8Devi=C4=87?= <4973094+kmilos@users.noreply.github.com> Date: Fri, 1 Oct 2021 13:50:02 +0200 Subject: [PATCH 088/633] Make TIFF strip size configurable --- src/PIL/TiffImagePlugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index eb33e3218..ab9ccd1cd 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -58,6 +58,7 @@ logger = logging.getLogger(__name__) READ_LIBTIFF = False WRITE_LIBTIFF = False IFD_LEGACY_API = True +STRIP_SIZE = 65536 II = b"II" # little-endian (Intel style) MM = b"MM" # big-endian (Motorola style) @@ -1617,9 +1618,9 @@ def _save(im, fp, filename): ifd[COLORMAP] = tuple(v * 256 for v in lut) # data orientation stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8) - # aim for 64 KB strips when using libtiff writer + # aim for given strip size (64 KB by default) when using libtiff writer if libtiff: - rows_per_strip = min((2 ** 16 + stride - 1) // stride, im.size[1]) + rows_per_strip = min(STRIP_SIZE // stride, im.size[1]) # JPEG encoder expects multiple of 8 rows if compression == "jpeg": rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1]) From 515314b85c2da6b53a48df9c78452450faba8a4a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Oct 2021 09:50:27 +1000 Subject: [PATCH 089/633] Updated capitalization --- src/PIL/ImageShow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index c3693eb61..60c97542f 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -133,7 +133,7 @@ if sys.platform == "win32": class MacViewer(Viewer): - """The default viewer on MacOS using ``Preview.app``.""" + """The default viewer on macOS using ``Preview.app``.""" format = "PNG" options = {"compress_level": 1} From 448de228d26b040a9f0b2a2d862ca55ba752656e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Oct 2021 16:54:25 +0000 Subject: [PATCH 090/633] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: e3000ace2fd1fcb1c181bb7a8285f1f976bcbdc7 → 911470a610e47d9da5ea938b0887c3df62819b85](https://github.com/psf/black/compare/e3000ace2fd1fcb1c181bb7a8285f1f976bcbdc7...911470a610e47d9da5ea938b0887c3df62819b85) - https://gitlab.com/pycqa/flake8 → https://github.com/PyCQA/flake8 --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 55fe9c4a7..4a0256085 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: e3000ace2fd1fcb1c181bb7a8285f1f976bcbdc7 # frozen: 21.7b0 + rev: 911470a610e47d9da5ea938b0887c3df62819b85 # frozen: 21.9b0 hooks: - id: black args: ["--target-version", "py36"] @@ -24,7 +24,7 @@ repos: - id: remove-tabs exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$) - - repo: https://gitlab.com/pycqa/flake8 + - repo: https://github.com/PyCQA/flake8 rev: dcd740bc0ebaf2b3d43e59a0060d157c97de13f3 # frozen: 3.9.2 hooks: - id: flake8 From 8eb3c9791ddb94fd3a2ae08ff44aa82f04b0c2e3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 5 Oct 2021 11:54:12 +0300 Subject: [PATCH 091/633] Test Python 3.10.0 final on GitHub Actions --- .github/workflows/test-windows.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index e2a1e8cd4..431d28285 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10-dev"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] architecture: ["x86", "x64"] include: # PyPy3.6 only ships 32-bit binaries for Windows diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cd85bc537..53ecbb32b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: python-version: [ "pypy-3.7", "pypy-3.6", - "3.10-dev", + "3.10", "3.9", "3.8", "3.7", From 8de429ecb9ca4e18df237ea4071a9066ff3d147d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 8 Oct 2021 13:12:21 +1100 Subject: [PATCH 092/633] Fixed Python errors when saving a (0, 0) TIFF image --- Tests/test_file_libtiff.py | 7 +++++++ src/PIL/TiffImagePlugin.py | 8 ++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 1d0c93f06..fc1831d99 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -986,3 +986,10 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as im: # Assert that there are multiple strips assert len(im.tag_v2[STRIPOFFSETS]) > 1 + + @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None)) + def test_save_zero(self, compression, tmp_path): + im = Image.new("RGB", (0, 0)) + out = str(tmp_path / "temp.tif") + with pytest.raises(SystemError): + im.save(out, compression=compression) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index eb33e3218..baaa709d8 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1619,13 +1619,17 @@ def _save(im, fp, filename): stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8) # aim for 64 KB strips when using libtiff writer if libtiff: - rows_per_strip = min((2 ** 16 + stride - 1) // stride, im.size[1]) + rows_per_strip = ( + 1 if stride == 0 else min((2 ** 16 + stride - 1) // stride, im.size[1]) + ) # JPEG encoder expects multiple of 8 rows if compression == "jpeg": rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1]) else: rows_per_strip = im.size[1] - strip_byte_counts = stride * rows_per_strip + if rows_per_strip == 0: + rows_per_strip = 1 + strip_byte_counts = 1 if stride == 0 else stride * rows_per_strip strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip ifd[ROWSPERSTRIP] = rows_per_strip if strip_byte_counts >= 2 ** 16: From 174b4893f3a6d092b4766d43ac570f0e57ecd718 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 12 Oct 2021 09:45:52 +1100 Subject: [PATCH 093/633] Prefer global transparency for disposal method 2 --- Tests/images/dispose_bgnd_transparency.gif | Bin 0 -> 5616 bytes Tests/test_file_gif.py | 7 +++++++ src/PIL/GifImagePlugin.py | 8 +++----- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 Tests/images/dispose_bgnd_transparency.gif diff --git a/Tests/images/dispose_bgnd_transparency.gif b/Tests/images/dispose_bgnd_transparency.gif new file mode 100644 index 0000000000000000000000000000000000000000..7c626fe72c0b521e9f5becc70c52231a4e235930 GIT binary patch literal 5616 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 Date: Wed, 13 Oct 2021 09:40:18 +1100 Subject: [PATCH 094/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3e5c8029f..23fb5de59 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 8.4.0 (unreleased) ------------------ +- Prefer global transparency in GIF when replacing with background color #5756 + [radarhere] + - Added "exif" keyword argument to TIFF saving #5575 [radarhere] From b5d6a73da9533ed5510383a6302fa1f17bdfe4a4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 8 Oct 2021 12:53:53 +1100 Subject: [PATCH 095/633] Added test --- Tests/test_file_libtiff.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 1d0c93f06..ebc8e5e07 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -986,3 +986,16 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as im: # Assert that there are multiple strips assert len(im.tag_v2[STRIPOFFSETS]) > 1 + + def test_save_single_strip(self, tmp_path): + im = hopper("RGB").resize((256, 256)) + out = str(tmp_path / "temp.tif") + + TiffImagePlugin.STRIP_SIZE = 2 ** 18 + im.save(out, compression="tiff_adobe_deflate") + + try: + with Image.open(out) as im: + assert len(im.tag_v2[STRIPOFFSETS]) == 1 + finally: + TiffImagePlugin.STRIP_SIZE = 65536 From 1140f6538d03ef0b481773edcc0f8c90f992c7f1 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Thu, 14 Oct 2021 08:09:36 +1100 Subject: [PATCH 096/633] Ensure reset after test failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- Tests/test_file_libtiff.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 2c12efb81..aed1cabc2 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -992,9 +992,10 @@ class TestFileLibTiff(LibTiffTestCase): out = str(tmp_path / "temp.tif") TiffImagePlugin.STRIP_SIZE = 2 ** 18 - im.save(out, compression="tiff_adobe_deflate") - try: + + im.save(out, compression="tiff_adobe_deflate") + with Image.open(out) as im: assert len(im.tag_v2[STRIPOFFSETS]) == 1 finally: From dbb0a41600ee26faa00d3eaacb6eb37b078493eb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 13 Oct 2021 21:10:19 +0000 Subject: [PATCH 097/633] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_file_libtiff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index aed1cabc2..6ac90f778 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -993,7 +993,7 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.STRIP_SIZE = 2 ** 18 try: - + im.save(out, compression="tiff_adobe_deflate") with Image.open(out) as im: From 19f4c6fd233a03d0b43ccbb7b0c45e23de6ed38c Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 14 Oct 2021 06:46:27 +0100 Subject: [PATCH 098/633] don't use bitmap glyphs when drawing text with stroker --- Tests/images/bitmap_font_stroke_basic.png | Bin 0 -> 2676 bytes Tests/images/bitmap_font_stroke_raqm.png | Bin 0 -> 2655 bytes Tests/test_imagefont.py | 16 ++++++++++++++++ src/_imagingft.c | 2 +- 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Tests/images/bitmap_font_stroke_basic.png create mode 100644 Tests/images/bitmap_font_stroke_raqm.png diff --git a/Tests/images/bitmap_font_stroke_basic.png b/Tests/images/bitmap_font_stroke_basic.png new file mode 100644 index 0000000000000000000000000000000000000000..86b2d09f66e4a0b385c7e53210df882e1d4409a9 GIT binary patch literal 2676 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 Date: Thu, 14 Oct 2021 13:42:18 -0400 Subject: [PATCH 099/633] Create tidelift.yml --- .github/workflows/tidelift.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/tidelift.yml diff --git a/.github/workflows/tidelift.yml b/.github/workflows/tidelift.yml new file mode 100644 index 000000000..af6f01b1c --- /dev/null +++ b/.github/workflows/tidelift.yml @@ -0,0 +1,16 @@ +name: Tidelift Align +on: [push] + +jobs: + build: + name: Run Tidelift to ensure approved open source packages are in use + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Scan + uses: tidelift/scan-action@main + env: + TIDELIFT_API_KEY: ${{ secrets.TIDELIFT_API_KEY }} + TIDELIFT_ORGANIZATION: ${{ secrets.TIDELIFT_ORGANIZATION }} + TIDELIFT_PROJECT: ${{ secrets.TIDELIFT_PROJECT }} From ce3c925a517f2a85c2dafbae79903980a3a2c5ba Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 14 Oct 2021 22:46:42 +0300 Subject: [PATCH 100/633] Delete tidelift.yml --- .github/workflows/tidelift.yml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .github/workflows/tidelift.yml diff --git a/.github/workflows/tidelift.yml b/.github/workflows/tidelift.yml deleted file mode 100644 index af6f01b1c..000000000 --- a/.github/workflows/tidelift.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Tidelift Align -on: [push] - -jobs: - build: - name: Run Tidelift to ensure approved open source packages are in use - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Scan - uses: tidelift/scan-action@main - env: - TIDELIFT_API_KEY: ${{ secrets.TIDELIFT_API_KEY }} - TIDELIFT_ORGANIZATION: ${{ secrets.TIDELIFT_ORGANIZATION }} - TIDELIFT_PROJECT: ${{ secrets.TIDELIFT_PROJECT }} From c8822a6cac65bbe0a7d831ef2b8431435f1feb1d Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 14 Oct 2021 13:49:45 -0400 Subject: [PATCH 101/633] Add tidelift alignment badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 29b5b8a6a..376991161 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,9 @@ As of 2019, Pillow development is Code coverage + From 76b5760b384823586e2d08bea99e9fba89e172fe Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 14 Oct 2021 13:42:18 -0400 Subject: [PATCH 102/633] Create tidelift.yml --- .github/workflows/tidelift.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/tidelift.yml diff --git a/.github/workflows/tidelift.yml b/.github/workflows/tidelift.yml new file mode 100644 index 000000000..af6f01b1c --- /dev/null +++ b/.github/workflows/tidelift.yml @@ -0,0 +1,16 @@ +name: Tidelift Align +on: [push] + +jobs: + build: + name: Run Tidelift to ensure approved open source packages are in use + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Scan + uses: tidelift/scan-action@main + env: + TIDELIFT_API_KEY: ${{ secrets.TIDELIFT_API_KEY }} + TIDELIFT_ORGANIZATION: ${{ secrets.TIDELIFT_ORGANIZATION }} + TIDELIFT_PROJECT: ${{ secrets.TIDELIFT_PROJECT }} From 836f740f7b3b107e3b20e8317b514da79584622e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 14 Oct 2021 23:00:02 +0300 Subject: [PATCH 103/633] Don't run for forks: missing secrets would fail --- .github/workflows/tidelift.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tidelift.yml b/.github/workflows/tidelift.yml index af6f01b1c..328305ce4 100644 --- a/.github/workflows/tidelift.yml +++ b/.github/workflows/tidelift.yml @@ -3,6 +3,7 @@ on: [push] jobs: build: + if: github.repository_owner == 'python-pillow' name: Run Tidelift to ensure approved open source packages are in use runs-on: ubuntu-latest steps: From 3c92b34b70dcdd4295865ca11c16ce768e104114 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 14 Oct 2021 23:04:22 +0300 Subject: [PATCH 104/633] Fix badge HTML --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 376991161..7c4b58d70 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ As of 2019, Pillow development is Code coverage - Tidelift Align From 40e7ff622669550733b26f14dc817fb72e096250 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 15 Oct 2021 09:27:22 +0300 Subject: [PATCH 105/633] 8.4.0 version bump --- CHANGES.rst | 2 +- src/PIL/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 23fb5de59..934c634aa 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,7 +2,7 @@ Changelog (Pillow) ================== -8.4.0 (unreleased) +8.4.0 (2021-10-15) ------------------ - Prefer global transparency in GIF when replacing with background color #5756 diff --git a/src/PIL/_version.py b/src/PIL/_version.py index 31f5daa47..d7f0f7ea1 100644 --- a/src/PIL/_version.py +++ b/src/PIL/_version.py @@ -1,2 +1,2 @@ # Master version for Pillow -__version__ = "8.4.0.dev0" +__version__ = "8.4.0" From d7b64a6621ee2290e531624fb9dabceddf8ed23f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 15 Oct 2021 11:17:23 +0300 Subject: [PATCH 106/633] 9.0.0.dev0 version bump --- src/PIL/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/_version.py b/src/PIL/_version.py index d7f0f7ea1..43e3e4768 100644 --- a/src/PIL/_version.py +++ b/src/PIL/_version.py @@ -1,2 +1,2 @@ # Master version for Pillow -__version__ = "8.4.0" +__version__ = "9.0.0.dev0" From 90ffafeebccdfc4ec3e348cc768042d48a4b78da Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 15 Oct 2021 18:29:46 +1100 Subject: [PATCH 107/633] Removed Fedora 33 docker job --- .github/workflows/test-docker.yml | 1 - docs/installation.rst | 2 -- 2 files changed, 3 deletions(-) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 3ce2508de..d4aeb1fb3 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -22,7 +22,6 @@ jobs: centos-8-amd64, centos-stream-8-amd64, debian-10-buster-x86, - fedora-33-amd64, fedora-34-amd64, ubuntu-18.04-bionic-amd64, ubuntu-20.04-focal-amd64, diff --git a/docs/installation.rst b/docs/installation.rst index 3b0ff96d6..fc5b0e7e9 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -460,8 +460,6 @@ These platforms are built and tested for every change. +----------------------------------+---------------------------------+---------------------+ | Debian 10 Buster | 3.7 | x86 | +----------------------------------+---------------------------------+---------------------+ -| Fedora 33 | 3.9 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ | Fedora 34 | 3.9 | x86-64 | +----------------------------------+---------------------------------+---------------------+ | macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | From adfe06ef9619f4e81bd2bb1a5d6c1797370bc773 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 15 Oct 2021 11:23:38 +0300 Subject: [PATCH 108/633] Docs: No security updates in 8.4.0 --- docs/releasenotes/8.4.0.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/releasenotes/8.4.0.rst b/docs/releasenotes/8.4.0.rst index e23a1eefe..67f259efb 100644 --- a/docs/releasenotes/8.4.0.rst +++ b/docs/releasenotes/8.4.0.rst @@ -38,14 +38,6 @@ Added WalImageFile class :py:class:`PIL.Image.Image` instance. It now returns a dedicated :py:class:`PIL.WalImageFile.WalImageFile` class. -Security -======== - -TODO -^^^^ - -TODO - Other Changes ============= From a8c18d0817af8e48de1fcdc70a8a7538344f1000 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 15 Oct 2021 17:30:05 +0300 Subject: [PATCH 109/633] Rename master to main --- .appveyor.yml | 6 +++--- .github/CONTRIBUTING.md | 10 +++++----- .github/workflows/release-drafter.yml | 4 ++-- .github/workflows/test-docker.yml | 2 +- .github/workflows/test-valgrind.yml | 2 +- README.md | 14 +++++++------- RELEASING.md | 16 ++++++++-------- Tests/test_font_leaks.py | 4 ++-- depends/install_imagequant.sh | 2 +- depends/install_openjpeg.sh | 2 +- depends/install_raqm.sh | 2 +- depends/install_raqm_cmake.sh | 2 +- depends/install_webp.sh | 2 +- docs/about.rst | 2 +- docs/conf.py | 2 +- docs/index.rst | 6 +++--- docs/reference/ImageFont.rst | 2 +- docs/releasenotes/versioning.rst | 8 ++++---- setup.py | 2 +- 19 files changed, 45 insertions(+), 45 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 93140b89c..6cf40130e 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -19,9 +19,9 @@ environment: install: -- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/master.zip +- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip - 7z x pillow-depends.zip -oc:\ -- mv c:\pillow-depends-master c:\pillow-depends +- mv c:\pillow-depends-main c:\pillow-depends - xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images - 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\ - ..\pillow-depends\gs9550w32.exe /S @@ -84,7 +84,7 @@ deploy: artifact: /.*egg|wheel/ on: APPVEYOR_REPO_NAME: python-pillow/Pillow - branch: master + branch: main deploy: YES diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 35bd47be8..bc9587744 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,13 +4,13 @@ Bug fixes, feature additions, tests, documentation and more can be contributed v ## Bug fixes, feature additions, etc. -Please send a pull request to the master branch. Please include [documentation](https://pillow.readthedocs.io) and [tests](../Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new), [Gitter](https://gitter.im/python-pillow/Pillow) or irc://irc.freenode.net#pil +Please send a pull request to the `main` branch. Please include [documentation](https://pillow.readthedocs.io) and [tests](../Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new), [Gitter](https://gitter.im/python-pillow/Pillow) or irc://irc.freenode.net#pil - Fork the Pillow repository. -- Create a branch from master. +- Create a branch from `main`. - Develop bug fixes, features, tests, etc. - Run the test suite. You can enable GitHub Actions (https://github.com/MY-USERNAME/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests. -- Create a pull request to pull the changes from your branch to the Pillow master. +- Create a pull request to pull the changes from your branch to the Pillow `main`. ### Guidelines @@ -18,7 +18,7 @@ Please send a pull request to the master branch. Please include [documentation]( - Provide tests for any newly added code. - Follow PEP 8. - When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor. -- Include [release notes](https://github.com/python-pillow/Pillow/tree/master/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests. +- Include [release notes](https://github.com/python-pillow/Pillow/tree/main/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests. ## Reporting Issues @@ -35,4 +35,4 @@ The best reproductions are self-contained scripts with minimal dependencies. If ## Security vulnerabilities -Please see our [security policy](https://github.com/python-pillow/Pillow/blob/master/.github/SECURITY.md). +Please see our [security policy](https://github.com/python-pillow/Pillow/blob/main/.github/SECURITY.md). diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 52456597b..58bb2cddf 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -4,14 +4,14 @@ on: push: # branches to consider in the event; optional, defaults to all branches: - - master + - main jobs: update_release_draft: if: github.repository == 'python-pillow/Pillow' runs-on: ubuntu-latest steps: - # Drafts your next release notes as pull requests are merged into "master" + # Drafts your next release notes as pull requests are merged into "main" - uses: release-drafter/release-drafter@v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index d4aeb1fb3..46e9e5f2c 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -26,7 +26,7 @@ jobs: ubuntu-18.04-bionic-amd64, ubuntu-20.04-focal-amd64, ] - dockerTag: [master] + dockerTag: [main] include: - docker: "ubuntu-20.04-focal-arm64v8" qemu-arch: "aarch64" diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index 28b1caff5..03c8022f3 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -22,7 +22,7 @@ jobs: docker: [ ubuntu-20.04-focal-amd64-valgrind, ] - dockerTag: [master] + dockerTag: [main] name: ${{ matrix.docker }} diff --git a/README.md b/README.md index 29b5b8a6a..205eada76 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- Pillow logo + Pillow logo

# Pillow @@ -38,16 +38,16 @@ As of 2019, Pillow development is src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"> AppVeyor CI build status (Windows) + src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"> GitHub Actions wheels build status (Wheels) Travis CI wheels build status (aarch64) + src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels"> Code coverage + src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"> @@ -93,12 +93,12 @@ The core image library is designed for fast access to data stored in a few basic - [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) +- [Contribute](https://github.com/python-pillow/Pillow/blob/main/.github/CONTRIBUTING.md) - [Issues](https://github.com/python-pillow/Pillow/issues) - [Pull requests](https://github.com/python-pillow/Pillow/pulls) - [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html) -- [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) +- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) + - [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork) ## Report a Vulnerability diff --git a/RELEASING.md b/RELEASING.md index 6045f84ac..cbedd449c 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -8,8 +8,8 @@ information about how the version numbers line up with releases. Released quarterly on January 2nd, April 1st, July 1st and October 15th. * [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154 -* [ ] Develop and prepare release in `master` branch. -* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `master` branch. +* [ ] Develop and prepare release in `main` branch. +* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch. * [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI and GitHub Actions. * [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py` * [ ] Update `CHANGES.rst`. @@ -26,7 +26,7 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th. make sdist twine check dist/* ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions) +* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) * [ ] Check and upload all binaries and source distributions e.g.: ```bash twine check dist/* @@ -39,13 +39,13 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th. Released as needed for security, installation or critical bug fixes. -* [ ] Make necessary changes in `master` branch. +* [ ] Make necessary changes in `main` branch. * [ ] Update `CHANGES.rst`. * [ ] Check out release branch e.g.: ```bash git checkout -t remotes/origin/5.2.x ``` -* [ ] Cherry pick individual commits from `master` branch to release branch e.g. `5.2.x`, then `git push`. +* [ ] Cherry pick individual commits from `main` branch to release branch e.g. `5.2.x`, then `git push`. @@ -63,7 +63,7 @@ Released as needed for security, installation or critical bug fixes. make sdist twine check dist/* ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions) +* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) * [ ] Check and upload all binaries and source distributions e.g.: ```bash twine check dist/* @@ -76,7 +76,7 @@ Released as needed for security, installation or critical bug fixes. Released as needed privately to individual vendors for critical security-related bug fixes. * [ ] Prepare patch for all versions that will get a fix. Test against local installations. -* [ ] Commit against master, cherry pick to affected release branches. +* [ ] Commit against `main`, cherry pick to affected release branches. * [ ] Run local test matrix on each release & Python version. * [ ] Privately send to distros. * [ ] Run pre-release check via `make release-test` @@ -93,7 +93,7 @@ Released as needed privately to individual vendors for critical security-related make sdist twine check dist/* ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions) +* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) ## Binary Distributions diff --git a/Tests/test_font_leaks.py b/Tests/test_font_leaks.py index 015210b4d..38f7ddac5 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -4,7 +4,7 @@ from .helper import PillowLeakTestCase, skip_unless_feature class TestTTypeFontLeak(PillowLeakTestCase): - # fails at iteration 3 in master + # fails at iteration 3 in main iterations = 10 mem_limit = 4096 # k @@ -24,7 +24,7 @@ class TestTTypeFontLeak(PillowLeakTestCase): class TestDefaultFontLeak(TestTTypeFontLeak): - # fails at iteration 37 in master + # fails at iteration 37 in main iterations = 100 mem_limit = 1024 # k diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 38f0e75d6..8c6704ac1 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -3,7 +3,7 @@ archive=libimagequant-2.16.0 -./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz pushd $archive diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index 7321b80f0..914e71e53 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -3,7 +3,7 @@ archive=openjpeg-2.4.0 -./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz pushd $archive diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index a7ce16792..3105465ec 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -4,7 +4,7 @@ archive=raqm-0.7.1 -./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz pushd $archive diff --git a/depends/install_raqm_cmake.sh b/depends/install_raqm_cmake.sh index c0dcd93b7..7d2c399df 100755 --- a/depends/install_raqm_cmake.sh +++ b/depends/install_raqm_cmake.sh @@ -4,7 +4,7 @@ archive=raqm-cmake-99300ff3 -./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz pushd $archive diff --git a/depends/install_webp.sh b/depends/install_webp.sh index 4a4e74305..8a9c96804 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -3,7 +3,7 @@ archive=libwebp-1.2.1 -./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz pushd $archive diff --git a/docs/about.rst b/docs/about.rst index 51b583ea0..96885d08d 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -19,7 +19,7 @@ The fork author's goal is to foster and support active development of PIL throug License ------- -Like PIL, Pillow is `licensed under the open source HPND License `_ +Like PIL, Pillow is `licensed under the open source HPND License `_ Why a fork? ----------- diff --git a/docs/conf.py b/docs/conf.py index 807281965..5c797b21c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -322,7 +322,7 @@ issues_github_path = "python-pillow/Pillow" # sphinxext.opengraph ogp_image = ( - "https://raw.githubusercontent.com/python-pillow/pillow-logo/master/" + "https://raw.githubusercontent.com/python-pillow/pillow-logo/main/" "pillow-logo-dark-text-1280x640.png" ) ogp_image_alt = "Pillow" diff --git a/docs/index.rst b/docs/index.rst index 3348feb89..3fbc8d0e6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -25,7 +25,7 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more `_ +`pilfont.py `_ from `pillow-scripts `_ to convert BDF and PCF font descriptors (X window font formats) to this format. diff --git a/docs/releasenotes/versioning.rst b/docs/releasenotes/versioning.rst index a8c9fc998..87f2ba422 100644 --- a/docs/releasenotes/versioning.rst +++ b/docs/releasenotes/versioning.rst @@ -11,7 +11,7 @@ Pillow follows `Semantic Versioning `_: 2. MINOR version when you add functionality in a backwards compatible manner, and 3. PATCH version when you make backwards compatible bug fixes. -Quarterly releases ("`Main Release `_") +Quarterly releases ("`Main Release `_") bump at least the MINOR version, as new functionality has likely been added in the prior three months. @@ -21,10 +21,10 @@ these occur every 12-18 months, guided by `Python's EOL schedule `_, and any APIs that have been deprecated for at least a year are removed at the same time. -PATCH versions ("`Point Release `_" -or "`Embargoed Release `_") +PATCH versions ("`Point Release `_" +or "`Embargoed Release `_") are for security, installation or critical bug fixes. These are less common as it is preferred to stick to quarterly releases. -Between quarterly releases, ".dev0" is appended to the "master" branch, indicating that +Between quarterly releases, ``.dev0`` is appended to the ``main`` branch, indicating that this is not a formally released copy. diff --git a/setup.py b/setup.py index b56e90634..3574c9137 100755 --- a/setup.py +++ b/setup.py @@ -992,7 +992,7 @@ try: "utm_source=pypi-pillow&utm_medium=pypi", "Release notes": "https://pillow.readthedocs.io/en/stable/releasenotes/" "index.html", - "Changelog": "https://github.com/python-pillow/Pillow/blob/master/" + "Changelog": "https://github.com/python-pillow/Pillow/blob/main/" "CHANGES.rst", "Twitter": "https://twitter.com/PythonPillow", }, From fa74f2272bfadd47b006001e0aa46c910729adbe Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 16 Oct 2021 21:32:27 +1100 Subject: [PATCH 110/633] Copied images are not associated with the original file --- docs/reference/open_files.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/reference/open_files.rst b/docs/reference/open_files.rst index ed0ab1a0c..7c52ef3a7 100644 --- a/docs/reference/open_files.rst +++ b/docs/reference/open_files.rst @@ -47,6 +47,10 @@ Image Lifecycle memory. The image can now be used independently of the underlying image file. + Any Pillow method that creates a new image instance based on another will + internally call ``load()`` on the original image and then read the data. + The new image instance will not be associated with the original image file. + If a filename or a ``Path`` object was passed to ``Image.open()``, then the file object was opened by Pillow and is considered to be used exclusively by Pillow. So if the image is a single-frame image, the file will be closed in From 669512c2ace63fec98eb22479a77bc3031a09878 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 16 Oct 2021 23:26:43 +1100 Subject: [PATCH 111/633] __exit__ does not destroy the core image object --- docs/reference/open_files.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/reference/open_files.rst b/docs/reference/open_files.rst index 7c52ef3a7..fd6d769b7 100644 --- a/docs/reference/open_files.rst +++ b/docs/reference/open_files.rst @@ -59,10 +59,14 @@ Image Lifecycle ``Image.Image.seek()`` can load the appropriate frame. * ``Image.Image.close()`` Closes the file and destroys the core image object. - This is used in the Pillow context manager support. e.g.:: + + The Pillow context manager will also close the file, but will not destroy + the core image object. e.g.:: with Image.open('test.jpg') as img: - ... # image operations here. + img.load() + assert img.fp is None + img.save('test.png') The lifecycle of a single-frame image is relatively simple. The file must From 2f29c1233adf1f7f90798ca368cce438711edd3a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 16 Oct 2021 22:55:26 +0300 Subject: [PATCH 112/633] Test PyQt6 on MinGW --- .github/workflows/test-mingw.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index b9d2abeb3..6125af024 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -42,7 +42,7 @@ jobs: ${{ matrix.package }}-python3-numpy \ ${{ matrix.package }}-python3-olefile \ ${{ matrix.package }}-python3-pip \ - ${{ matrix.package }}-python3-pyqt5 \ + ${{ matrix.package }}-python-pyqt6 \ ${{ matrix.package }}-python3-setuptools \ ${{ matrix.package }}-freetype \ ${{ matrix.package }}-ghostscript \ From d1148378bcc671a2da5ba3101c44b822dec7ff0c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sat, 16 Oct 2021 23:04:43 +0300 Subject: [PATCH 113/633] Fix for PyQt6 --- src/PIL/ImageQt.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 32630f2ca..6b965ffe4 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -34,7 +34,7 @@ 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 == "PyQt6": - from PyQt6.QtCore import QBuffer, QIODevice + from PyQt6.QtCore import QBuffer, QIODevice, QIODeviceBase from PyQt6.QtGui import QImage, QPixmap, qRgba elif qt_module == "PySide6": from PySide6.QtCore import QBuffer, QIODevice @@ -66,7 +66,13 @@ def fromqimage(im): :param im: QImage or PIL ImageQt object """ buffer = QBuffer() - qt_openmode = QIODevice.OpenMode if qt_version == "6" else QIODevice + if qt_version == "6": + try: + qt_openmode = QIODeviceBase.OpenModeFlag + except AttributeError: + qt_openmode = QIODevice.OpenMode + else: + qt_openmode = QIODevice buffer.open(qt_openmode.ReadWrite) # preserve alpha channel with png # otherwise ppm is more friendly with Image.open From dd08584f7a0dadcec45efc71c871a1bd8932f7aa Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sun, 17 Oct 2021 10:37:19 +1100 Subject: [PATCH 114/633] Updated syntax Co-authored-by: Hugo van Kemenade --- docs/reference/open_files.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/reference/open_files.rst b/docs/reference/open_files.rst index fd6d769b7..f66184ba3 100644 --- a/docs/reference/open_files.rst +++ b/docs/reference/open_files.rst @@ -61,12 +61,14 @@ Image Lifecycle * ``Image.Image.close()`` Closes the file and destroys the core image object. The Pillow context manager will also close the file, but will not destroy - the core image object. e.g.:: + the core image object. e.g.: - with Image.open('test.jpg') as img: +.. code-block:: python + + with Image.open("test.jpg") as img: img.load() assert img.fp is None - img.save('test.png') + img.save("test.png") The lifecycle of a single-frame image is relatively simple. The file must From 43ceaa1614b7d81ac5b2c7483bc9bfb3126d39a4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 17 Oct 2021 13:14:47 +1100 Subject: [PATCH 115/633] Use QIODevice instead of QIODeviceBase --- src/PIL/ImageQt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 6b965ffe4..e142f1f27 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -34,7 +34,7 @@ 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 == "PyQt6": - from PyQt6.QtCore import QBuffer, QIODevice, QIODeviceBase + from PyQt6.QtCore import QBuffer, QIODevice from PyQt6.QtGui import QImage, QPixmap, qRgba elif qt_module == "PySide6": from PySide6.QtCore import QBuffer, QIODevice @@ -68,7 +68,7 @@ def fromqimage(im): buffer = QBuffer() if qt_version == "6": try: - qt_openmode = QIODeviceBase.OpenModeFlag + qt_openmode = QIODevice.OpenModeFlag except AttributeError: qt_openmode = QIODevice.OpenMode else: From cd50d468ba9255fca1fcb0a4bc6f35836d76c53e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 18 Oct 2021 11:05:53 +1100 Subject: [PATCH 116/633] Removed PILLOW_VERSION --- Tests/test_image.py | 29 ------------------ docs/deprecations.rst | 22 ++++++------- docs/releasenotes/9.0.0.rst | 10 ++++++ docs/releasenotes/index.rst | 1 + src/PIL/Image.py | 39 +++++++----------------- src/PIL/__init__.py | 61 ++----------------------------------- 6 files changed, 35 insertions(+), 127 deletions(-) create mode 100644 docs/releasenotes/9.0.0.rst diff --git a/Tests/test_image.py b/Tests/test_image.py index 2d661a903..6717cbc5a 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -813,35 +813,6 @@ class TestImage: with pytest.warns(DeprecationWarning): assert Image.CONTAINER == 2 - @pytest.mark.parametrize( - "test_module", - [PIL, Image], - ) - def test_pillow_version(self, test_module): - with pytest.warns(DeprecationWarning): - assert test_module.PILLOW_VERSION == PIL.__version__ - - with pytest.warns(DeprecationWarning): - str(test_module.PILLOW_VERSION) - - with pytest.warns(DeprecationWarning): - assert int(test_module.PILLOW_VERSION[0]) >= 7 - - with pytest.warns(DeprecationWarning): - assert test_module.PILLOW_VERSION < "9.9.0" - - with pytest.warns(DeprecationWarning): - assert test_module.PILLOW_VERSION <= "9.9.0" - - with pytest.warns(DeprecationWarning): - assert test_module.PILLOW_VERSION != "7.0.0" - - with pytest.warns(DeprecationWarning): - assert test_module.PILLOW_VERSION >= "7.0.0" - - with pytest.warns(DeprecationWarning): - assert test_module.PILLOW_VERSION > "7.0.0" - @pytest.mark.parametrize( "path", [ diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 45720ccc0..812ed3194 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -51,17 +51,6 @@ ImageFile.raise_ioerror So, ``ImageFile.raise_ioerror`` will be removed in Pillow 9.0.0 (2022-01-02). Use ``ImageFile.raise_oserror`` instead. -PILLOW_VERSION constant -~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 5.2.0 - -``PILLOW_VERSION`` will be removed in Pillow 9.0.0 (2022-01-02). -Use ``__version__`` instead. - -It was initially removed in Pillow 7.0.0, but brought back in 7.1.0 to give projects -more time to upgrade. - Tk/Tcl 8.4 ~~~~~~~~~~ @@ -109,6 +98,17 @@ Removed features Deprecated features are only removed in major releases after an appropriate period of deprecation has passed. +PILLOW_VERSION constant +~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 5.2.0 +.. versionremoved:: 9.0.0 + +Use ``__version__`` instead. + +It was initially removed in Pillow 7.0.0, but temporarily brought back in 7.1.0 +to give projects more time to upgrade. + im.offset ~~~~~~~~~ diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst new file mode 100644 index 000000000..e10287dca --- /dev/null +++ b/docs/releasenotes/9.0.0.rst @@ -0,0 +1,10 @@ +9.0.0 +----- + +Backwards Incompatible Changes +============================== + +PILLOW_VERSION constant +^^^^^^^^^^^^^^^^^^^^^^^ + +``PILLOW_VERSION`` has been removed. Use ``__version__`` instead. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index f42ea72e8..8d1ad7837 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -14,6 +14,7 @@ expected to be backported to earlier versions. .. toctree:: :maxdepth: 2 + 9.0.0 8.4.0 8.3.2 8.3.1 diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 7dd5b35bd..439b1c932 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -45,45 +45,28 @@ except ImportError: ElementTree = None # VERSION was removed in Pillow 6.0.0. -# PILLOW_VERSION is deprecated and will be removed in a future release. +# PILLOW_VERSION was removed in Pillow 9.0.0. # Use __version__ instead. -from . import ( - ImageMode, - TiffTags, - UnidentifiedImageError, - __version__, - _plugins, - _raise_version_warning, -) +from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins from ._binary import i32le from ._util import deferred_error, isPath if sys.version_info >= (3, 7): def __getattr__(name): - if name == "PILLOW_VERSION": - _raise_version_warning() - return __version__ - else: - categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2} - if name in categories: - warnings.warn( - "Image categories are deprecated and will be removed in Pillow 10 " - "(2023-01-02). Use is_animated instead.", - DeprecationWarning, - stacklevel=2, - ) - return categories[name] + categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2} + if name in categories: + warnings.warn( + "Image categories are deprecated and will be removed in Pillow 10 " + "(2023-01-02). Use is_animated instead.", + DeprecationWarning, + stacklevel=2, + ) + return categories[name] raise AttributeError(f"module '{__name__}' has no attribute '{name}'") else: - - from . import PILLOW_VERSION - - # Silence warning - assert PILLOW_VERSION - # categories NORMAL = 0 SEQUENCE = 1 diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index 890ae44f5..dbde52a1b 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -19,66 +19,9 @@ import warnings from . import _version # VERSION was removed in Pillow 6.0.0. -__version__ = _version.__version__ - - -# PILLOW_VERSION is deprecated and will be removed in a future release. +# PILLOW_VERSION was removed in Pillow 9.0.0. # Use __version__ instead. -def _raise_version_warning(): - warnings.warn( - "PILLOW_VERSION is deprecated and will be removed in Pillow 9 (2022-01-02). " - "Use __version__ instead.", - DeprecationWarning, - stacklevel=3, - ) - - -if sys.version_info >= (3, 7): - - def __getattr__(name): - if name == "PILLOW_VERSION": - _raise_version_warning() - return __version__ - raise AttributeError(f"module '{__name__}' has no attribute '{name}'") - - -else: - - class _Deprecated_Version(str): - def __str__(self): - _raise_version_warning() - return super().__str__() - - def __getitem__(self, key): - _raise_version_warning() - return super().__getitem__(key) - - def __eq__(self, other): - _raise_version_warning() - return super().__eq__(other) - - def __ne__(self, other): - _raise_version_warning() - return super().__ne__(other) - - def __gt__(self, other): - _raise_version_warning() - return super().__gt__(other) - - def __lt__(self, other): - _raise_version_warning() - return super().__lt__(other) - - def __ge__(self, other): - _raise_version_warning() - return super().__gt__(other) - - def __le__(self, other): - _raise_version_warning() - return super().__lt__(other) - - PILLOW_VERSION = _Deprecated_Version(__version__) - +__version__ = _version.__version__ del _version From e444e7ab6db84d552ee442c7e7723ab072ca28da Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 18 Oct 2021 10:53:16 +1100 Subject: [PATCH 117/633] Removed ImageFile.raise_ioerror --- Tests/test_imagefile.py | 6 ------ docs/deprecations.rst | 19 ++++++++++--------- docs/releasenotes/9.0.0.rst | 6 ++++++ src/PIL/ImageFile.py | 9 --------- 4 files changed, 16 insertions(+), 24 deletions(-) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 892087916..f4e5a6a59 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -94,12 +94,6 @@ class TestImageFile: assert_image_equal(im1, im2) - def test_raise_ioerror(self): - with pytest.raises(IOError): - with pytest.warns(DeprecationWarning) as record: - ImageFile.raise_ioerror(1) - assert len(record) == 1 - def test_raise_oserror(self): with pytest.raises(OSError): ImageFile.raise_oserror(1) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 812ed3194..004c2d51a 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -42,15 +42,6 @@ Image._showxv 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 -~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 7.2.0 - -``IOError`` was merged into ``OSError`` in Python 3.3. -So, ``ImageFile.raise_ioerror`` will be removed in Pillow 9.0.0 (2022-01-02). -Use ``ImageFile.raise_oserror`` instead. - Tk/Tcl 8.4 ~~~~~~~~~~ @@ -98,6 +89,16 @@ Removed features Deprecated features are only removed in major releases after an appropriate period of deprecation has passed. +ImageFile.raise_ioerror +~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.2.0 +.. versionremoved:: 9.0.0 + +``IOError`` was merged into ``OSError`` in Python 3.3. +So, ``ImageFile.raise_ioerror`` has been removed. +Use ``ImageFile.raise_oserror`` instead. + PILLOW_VERSION constant ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index e10287dca..9891fda39 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -8,3 +8,9 @@ PILLOW_VERSION constant ^^^^^^^^^^^^^^^^^^^^^^^ ``PILLOW_VERSION`` has been removed. Use ``__version__`` instead. + +ImageFile.raise_ioerror +~~~~~~~~~~~~~~~~~~~~~~~ + +``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` +has been removed. Use ``ImageFile.raise_oserror`` instead. diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 43d2bf0cc..5ab53fa39 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -67,15 +67,6 @@ def raise_oserror(error): raise OSError(message + " when reading image file") -def raise_ioerror(error): - warnings.warn( - "raise_ioerror is deprecated and will be removed in Pillow 9 (2022-01-02). " - "Use raise_oserror instead.", - DeprecationWarning, - ) - return raise_oserror(error) - - def _tilesort(t): # sort on offset return t[2] From 499040491bfcc8804b2cb896f8c20802813939f2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 18 Oct 2021 10:53:48 +1100 Subject: [PATCH 118/633] Removed Image._showxv --- Tests/test_image.py | 16 ---------------- docs/deprecations.rst | 18 +++++++++--------- docs/releasenotes/9.0.0.rst | 7 +++++++ src/PIL/Image.py | 15 +-------------- 4 files changed, 17 insertions(+), 39 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 6717cbc5a..8acd5daa9 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -626,22 +626,6 @@ class TestImage: expected = Image.new(mode, (100, 100), color) assert_image_equal(im.convert(mode), expected) - def test_showxv_deprecation(self): - class TestViewer(ImageShow.Viewer): - def show_image(self, image, **options): - return True - - viewer = TestViewer() - ImageShow.register(viewer, -1) - - im = Image.new("RGB", (50, 50), "white") - - with pytest.warns(DeprecationWarning): - Image._showxv(im) - - # Restore original state - ImageShow._viewers.pop(0) - def test_no_resource_warning_on_save(self, tmp_path): # https://github.com/python-pillow/Pillow/issues/835 # Arrange diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 004c2d51a..2a4813b1f 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -33,15 +33,6 @@ Image.show command parameter The ``command`` parameter will be removed in Pillow 9.0.0 (2022-01-02). Use a subclass of :py:class:`.ImageShow.Viewer` instead. -Image._showxv -~~~~~~~~~~~~~ - -.. deprecated:: 7.2.0 - -``Image._showxv`` will be removed in Pillow 9.0.0 (2022-01-02). -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. - Tk/Tcl 8.4 ~~~~~~~~~~ @@ -89,6 +80,15 @@ Removed features Deprecated features are only removed in major releases after an appropriate period of deprecation has passed. +Image._showxv +~~~~~~~~~~~~~ + +.. deprecated:: 7.2.0 +.. versionremoved:: 9.0.0 + +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 ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 9891fda39..91f6c3b71 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -14,3 +14,10 @@ ImageFile.raise_ioerror ``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` has been removed. Use ``ImageFile.raise_oserror`` instead. + +Image._showxv +~~~~~~~~~~~~~ + +``Image._showxv`` has been removed. 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. diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 439b1c932..6314160cb 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3233,22 +3233,9 @@ def register_encoder(name, encoder): def _show(image, **options): - options["_internal_pillow"] = True - _showxv(image, **options) - - -def _showxv(image, title=None, **options): from . import ImageShow - if "_internal_pillow" in options: - del options["_internal_pillow"] - else: - warnings.warn( - "_showxv is deprecated and will be removed in Pillow 9 (2022-01-02). " - "Use Image.show instead.", - DeprecationWarning, - ) - ImageShow.show(image, title, **options) + ImageShow.show(image, **options) # -------------------------------------------------------------------- From 83864b01cf9f0e7dac9890308dc76390f6f06856 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 18 Oct 2021 11:08:51 +1100 Subject: [PATCH 119/633] Removed Image.show command parameter --- Tests/test_image.py | 15 +----------- docs/deprecations.rst | 17 +++++++------- docs/releasenotes/9.0.0.rst | 46 +++++++++++++++++++++++++++++++++---- src/PIL/Image.py | 11 ++------- src/PIL/ImageFile.py | 1 - src/PIL/__init__.py | 3 --- 6 files changed, 54 insertions(+), 39 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 8acd5daa9..c185a1cb4 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -6,8 +6,7 @@ import tempfile import pytest -import PIL -from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageError +from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError from .helper import ( assert_image_equal, @@ -832,18 +831,6 @@ class TestImage: except OSError as e: assert str(e) == "buffer overrun when reading image file" - def test_show_deprecation(self, monkeypatch): - monkeypatch.setattr(Image, "_show", lambda *args, **kwargs: None) - - im = Image.new("RGB", (50, 50), "white") - - with pytest.warns(None) as raised: - im.show() - assert not raised - - with pytest.warns(DeprecationWarning): - im.show(command="mock") - class MockEncoder: pass diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 2a4813b1f..46596abda 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -25,14 +25,6 @@ vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`). .. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/ -Image.show command parameter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 7.2.0 - -The ``command`` parameter will be removed in Pillow 9.0.0 (2022-01-02). -Use a subclass of :py:class:`.ImageShow.Viewer` instead. - Tk/Tcl 8.4 ~~~~~~~~~~ @@ -80,6 +72,15 @@ Removed features Deprecated features are only removed in major releases after an appropriate period of deprecation has passed. +Image.show command parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.2.0 +.. versionremoved:: 9.0.0 + +The ``command`` parameter has been removed. Use a subclass of +:py:class:`.ImageShow.Viewer` instead. + Image._showxv ~~~~~~~~~~~~~ diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 91f6c3b71..9991655e7 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -9,11 +9,11 @@ PILLOW_VERSION constant ``PILLOW_VERSION`` has been removed. Use ``__version__`` instead. -ImageFile.raise_ioerror -~~~~~~~~~~~~~~~~~~~~~~~ +Image.show command parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` -has been removed. Use ``ImageFile.raise_oserror`` instead. +The ``command`` parameter has been removed. Use a subclass of +:py:class:`PIL.ImageShow.Viewer` instead. Image._showxv ~~~~~~~~~~~~~ @@ -21,3 +21,41 @@ Image._showxv ``Image._showxv`` has been removed. 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. + +ImageFile.raise_ioerror +~~~~~~~~~~~~~~~~~~~~~~~ + +``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` +has been removed. Use ``ImageFile.raise_oserror`` instead. + +API Changes +=========== + +TODO +^^^^ + +TODO + +API Additions +============= + +TODO +^^^^ + +TODO + +Security +======== + +TODO +^^^^ + +TODO + +Other Changes +============= + +TODO +^^^^ + +TODO diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 6314160cb..da0bda7ec 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2247,7 +2247,7 @@ class Image: if frame != 0: raise EOFError - def show(self, title=None, command=None): + def show(self, title=None): """ Displays this image. This method is mainly intended for debugging purposes. @@ -2267,14 +2267,7 @@ class Image: :param title: Optional title to use for the image window, where possible. """ - if command is not None: - warnings.warn( - "The command parameter is deprecated and will be removed in Pillow 9 " - "(2022-01-02). Use a subclass of ImageShow.Viewer instead.", - DeprecationWarning, - ) - - _show(self, title=title, command=command) + _show(self, title=title) def split(self): """ diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 5ab53fa39..25b481243 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -30,7 +30,6 @@ import io import struct import sys -import warnings from . import Image from ._util import isPath diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index dbde52a1b..45fef241e 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -13,9 +13,6 @@ Use PIL.__version__ for this Pillow version. ;-) """ -import sys -import warnings - from . import _version # VERSION was removed in Pillow 6.0.0. From 20cf82b2aff04397b759f93242862f22e932a630 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 18 Oct 2021 17:20:56 +1100 Subject: [PATCH 120/633] Moved PILLOW_VERSION deprecation to be more prominent --- docs/deprecations.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 46596abda..7318da5b7 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -72,6 +72,17 @@ Removed features Deprecated features are only removed in major releases after an appropriate period of deprecation has passed. +PILLOW_VERSION constant +~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 5.2.0 +.. versionremoved:: 9.0.0 + +Use ``__version__`` instead. + +It was initially removed in Pillow 7.0.0, but temporarily brought back in 7.1.0 +to give projects more time to upgrade. + Image.show command parameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -100,17 +111,6 @@ ImageFile.raise_ioerror So, ``ImageFile.raise_ioerror`` has been removed. Use ``ImageFile.raise_oserror`` instead. -PILLOW_VERSION constant -~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 5.2.0 -.. versionremoved:: 9.0.0 - -Use ``__version__`` instead. - -It was initially removed in Pillow 7.0.0, but temporarily brought back in 7.1.0 -to give projects more time to upgrade. - im.offset ~~~~~~~~~ From 606b5ae1e53f5e2e26977c293747e6291283b2b9 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Sun, 17 Oct 2021 23:29:41 +0300 Subject: [PATCH 121/633] Remove support for FreeType 2.7 and older --- Tests/test_imagefont.py | 23 +---------------------- Tests/test_imagefontctl.py | 22 ++-------------------- docs/deprecations.rst | 26 +++++++++++++------------- docs/releasenotes/9.0.0.rst | 25 ++++++++++++++++++++++--- docs/releasenotes/template.rst | 3 +++ src/PIL/ImageFont.py | 20 +------------------- src/_imagingft.c | 4 ---- 7 files changed, 42 insertions(+), 81 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 6f7646cb0..cb1f45ca2 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -757,11 +757,7 @@ class TestImageFont: name, text = "quick", "Quick" path = f"Tests/images/test_anchor_{name}_{anchor}.png" - freetype = parse_version(features.version_module("freetype2")) - if freetype < parse_version("2.4"): - width, height = (129, 44) - left = left_old - elif self.LAYOUT_ENGINE == ImageFont.LAYOUT_RAQM: + if self.LAYOUT_ENGINE == ImageFont.LAYOUT_RAQM: width, height = (129, 44) else: width, height = (128, 44) @@ -894,7 +890,6 @@ class TestImageFont: assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 6.2) - @skip_unless_feature_version("freetype2", "2.5.0") def test_cbdt(self): try: font = ImageFont.truetype( @@ -913,7 +908,6 @@ class TestImageFont: assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or CBDT support") - @skip_unless_feature_version("freetype2", "2.5.0") def test_cbdt_mask(self): try: font = ImageFont.truetype( @@ -934,7 +928,6 @@ class TestImageFont: assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or CBDT support") - @skip_unless_feature_version("freetype2", "2.5.1") def test_sbix(self): try: font = ImageFont.truetype( @@ -953,7 +946,6 @@ class TestImageFont: assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or SBIX support") - @skip_unless_feature_version("freetype2", "2.5.1") def test_sbix_mask(self): try: font = ImageFont.truetype( @@ -1008,7 +1000,6 @@ class TestImageFont_RaqmLayout(TestImageFont): LAYOUT_ENGINE = ImageFont.LAYOUT_RAQM -@skip_unless_feature_version("freetype2", "2.4", "Different metrics") def test_render_mono_size(): # issue 4177 @@ -1024,18 +1015,6 @@ def test_render_mono_size(): assert_image_equal_tofile(im, "Tests/images/text_mono.gif") -def test_freetype_deprecation(monkeypatch): - # Arrange: mock features.version_module to return fake FreeType version - def fake_version_module(module): - return "2.7" - - monkeypatch.setattr(features, "version_module", fake_version_module) - - # Act / Assert - with pytest.warns(DeprecationWarning): - ImageFont.truetype(FONT_PATH, FONT_SIZE) - - @pytest.mark.parametrize( "test_file", [ diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index f2a914ff7..ffb70cf17 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -1,13 +1,8 @@ import pytest -from packaging.version import parse as parse_version -from PIL import Image, ImageDraw, ImageFont, features +from PIL import Image, ImageDraw, ImageFont -from .helper import ( - assert_image_similar_tofile, - skip_unless_feature, - skip_unless_feature_version, -) +from .helper import assert_image_similar_tofile, skip_unless_feature FONT_SIZE = 20 FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" @@ -252,11 +247,6 @@ def test_getlength_combine(mode, direction, text): pytest.skip("libraqm 0.7 or greater not available") -# FreeType 2.5.1 README: Miscellaneous Changes: -# Improved computation of emulated vertical metrics for TrueType fonts. -@skip_unless_feature_version( - "freetype2", "2.5.1", "FreeType <2.5.1 has incompatible ttb metrics" -) @pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm")) def test_anchor_ttb(anchor): text = "f" @@ -315,14 +305,6 @@ combine_tests = ( "name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests] ) def test_combine(name, text, dir, anchor, epsilon): - if ( - parse_version(features.version_module("freetype2")) < parse_version("2.5.1") - and dir == "ttb" - ): - # FreeType 2.5.1 README: Miscellaneous Changes: - # Improved computation of emulated vertical metrics for TrueType fonts. - pytest.skip("FreeType <2.5.1 has incompatible ttb metrics") - path = f"Tests/images/test_combine_{name}.png" f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 7318da5b7..077f78ef5 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -12,19 +12,6 @@ Deprecated features Below are features which are considered deprecated. Where appropriate, a ``DeprecationWarning`` is issued. -FreeType 2.7 -~~~~~~~~~~~~ - -.. deprecated:: 8.1.0 - -Support for FreeType 2.7 is deprecated and will be removed in Pillow 9.0.0 (2022-01-02), -when FreeType 2.8 will be the minimum supported. - -We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe -vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`). - -.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/ - Tk/Tcl 8.4 ~~~~~~~~~~ @@ -111,6 +98,19 @@ ImageFile.raise_ioerror So, ``ImageFile.raise_ioerror`` has been removed. Use ``ImageFile.raise_oserror`` instead. +FreeType 2.7 +~~~~~~~~~~~~ + +.. deprecated:: 8.1.0 +.. versionremoved:: 9.0.0 + +Support for FreeType 2.7 has been removed. + +We recommend upgrading to at least `FreeType`_ 2.10.4, which fixed a severe +vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`). + +.. _FreeType: https://www.freetype.org + im.offset ~~~~~~~~~ diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 9991655e7..6965b65bf 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -9,25 +9,44 @@ PILLOW_VERSION constant ``PILLOW_VERSION`` has been removed. Use ``__version__`` instead. +FreeType 2.7 +^^^^^^^^^^^^ + +Support for FreeType 2.7 has been removed; FreeType 2.8 is the minimum supported. + +We recommend upgrading to at least `FreeType`_ 2.10.4, which fixed a severe +vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`). + +.. _FreeType: https://www.freetype.org + Image.show command parameter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``command`` parameter has been removed. Use a subclass of :py:class:`PIL.ImageShow.Viewer` instead. Image._showxv -~~~~~~~~~~~~~ +^^^^^^^^^^^^^ ``Image._showxv`` has been removed. 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. ImageFile.raise_ioerror -~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^ ``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` has been removed. Use ``ImageFile.raise_oserror`` instead. + +Deprecations +============ + +TODO +^^^^ + +TODO + API Changes =========== diff --git a/docs/releasenotes/template.rst b/docs/releasenotes/template.rst index bf381114e..f7271ae2b 100644 --- a/docs/releasenotes/template.rst +++ b/docs/releasenotes/template.rst @@ -34,6 +34,9 @@ TODO Security ======== +TODO +^^^^ + TODO Other Changes diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index e99ca21b2..a212110a8 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -28,10 +28,9 @@ import base64 import os import sys -import warnings from io import BytesIO -from . import Image, features +from . import Image from ._util import isDirectory, isPath LAYOUT_BASIC = 0 @@ -165,23 +164,6 @@ class FreeTypeFont: self.index = index self.encoding = encoding - try: - from packaging.version import parse as parse_version - except ImportError: - pass - else: - freetype_version = features.version_module("freetype2") - if freetype_version is not None and parse_version( - freetype_version - ) < parse_version("2.8"): - warnings.warn( - "Support for FreeType 2.7 is deprecated and will be removed" - " in Pillow 9 (2022-01-02). Please upgrade to FreeType 2.8 " - "or newer, preferably FreeType 2.10.4 which fixes " - "CVE-2020-15999.", - DeprecationWarning, - ) - if layout_engine not in (LAYOUT_BASIC, LAYOUT_RAQM): layout_engine = LAYOUT_BASIC if core.HAVE_RAQM: diff --git a/src/_imagingft.c b/src/_imagingft.c index bbfca187e..8f19b763c 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -933,11 +933,7 @@ font_render(FontObject *self, PyObject *args) { case FT_PIXEL_MODE_GRAY2: case FT_PIXEL_MODE_GRAY4: if (!bitmap_converted_ready) { -#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 6) FT_Bitmap_Init(&bitmap_converted); -#else - FT_Bitmap_New(&bitmap_converted); -#endif bitmap_converted_ready = 1; } error = FT_Bitmap_Convert(library, &bitmap, &bitmap_converted, 1); From cb5c8f6f869b6ea2306b860f5096e9e36cff3f1d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 18 Oct 2021 11:28:14 +0300 Subject: [PATCH 122/633] Remove unused left_old variable --- Tests/test_imagefont.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index cb1f45ca2..acc1cdb13 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -738,22 +738,22 @@ class TestImageFont: d.textbbox((0, 0), "test", font=default_font) @pytest.mark.parametrize( - "anchor, left, left_old, top", + "anchor, left, top", ( # test horizontal anchors - ("ls", 0, 0, -36), - ("ms", -64, -65, -36), - ("rs", -128, -129, -36), + ("ls", 0, -36), + ("ms", -64, -36), + ("rs", -128, -36), # test vertical anchors - ("ma", -64, -65, 16), - ("mt", -64, -65, 0), - ("mm", -64, -65, -17), - ("mb", -64, -65, -44), - ("md", -64, -65, -51), + ("ma", -64, 16), + ("mt", -64, 0), + ("mm", -64, -17), + ("mb", -64, -44), + ("md", -64, -51), ), ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"), ) - def test_anchor(self, anchor, left, left_old, top): + def test_anchor(self, anchor, left, top): name, text = "quick", "Quick" path = f"Tests/images/test_anchor_{name}_{anchor}.png" From 67b4cb52d117a804c1dfc00b4648ed438a936118 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 18 Oct 2021 10:14:18 +0300 Subject: [PATCH 123/633] Stop testing Python 3.6 on CI --- .github/workflows/test-windows.yml | 5 +---- .github/workflows/test.yml | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 431d28285..f352b89af 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -8,12 +8,9 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10"] architecture: ["x86", "x64"] include: - # PyPy3.6 only ships 32-bit binaries for Windows - - python-version: "pypy-3.6" - architecture: "x86" # PyPy 7.3.4+ only ships 64-bit binaries for Windows - python-version: "pypy-3.7" architecture: "x64" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 53ecbb32b..14aadca36 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,18 +14,16 @@ jobs: ] python-version: [ "pypy-3.7", - "pypy-3.6", "3.10", "3.9", "3.8", "3.7", - "3.6", ] include: - - python-version: "3.6" + - python-version: "3.7" PYTHONOPTIMIZE: 1 REVERSE: "--reverse" - - python-version: "3.7" + - python-version: "3.8" PYTHONOPTIMIZE: 2 # Include new variables for Codecov - os: ubuntu-latest From 86f32f6074aa834a76b01132609354c672cb5bc0 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 18 Oct 2021 10:16:29 +0300 Subject: [PATCH 124/633] Test PyPy3.8 --- .github/workflows/test-windows.yml | 2 ++ .github/workflows/test.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index f352b89af..9cd1cbcde 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -14,6 +14,8 @@ jobs: # PyPy 7.3.4+ only ships 64-bit binaries for Windows - python-version: "pypy-3.7" architecture: "x64" + - python-version: "pypy-3.8" + architecture: "x64" timeout-minutes: 30 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 14aadca36..ad8e5e914 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,6 +13,7 @@ jobs: "macOS-latest", ] python-version: [ + "pypy-3.8", "pypy-3.7", "3.10", "3.9", From 3e9a6f750847bdd473cd2fc959d234b849f5bbc4 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 18 Oct 2021 11:29:01 +0300 Subject: [PATCH 125/633] Test slower macOS first, for slightly faster builds --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad8e5e914..43ca263a6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,8 +9,8 @@ jobs: fail-fast: false matrix: os: [ - "ubuntu-latest", "macOS-latest", + "ubuntu-latest", ] python-version: [ "pypy-3.8", From 6e310e3e2e343888740f9ce46b9305be3f74d564 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 18 Oct 2021 15:48:49 +0300 Subject: [PATCH 126/633] Update expected Pillow 10 release date: 2023-07-01 --- CHANGES.rst | 2 +- docs/deprecations.rst | 8 ++++---- docs/releasenotes/8.2.0.rst | 4 ++-- docs/releasenotes/8.3.0.rst | 2 +- docs/releasenotes/8.4.0.rst | 2 +- src/PIL/Image.py | 4 ++-- src/PIL/ImagePalette.py | 2 +- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/_tkinter_finder.py | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 934c634aa..de8377e21 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -377,7 +377,7 @@ Changelog (Pillow) - Changed Image.open formats parameter to be case-insensitive #5250 [Piolie, radarhere] -- Deprecate Tk/Tcl 8.4, to be removed in Pillow 10 (2023-01-02) #5216 +- Deprecate Tk/Tcl 8.4, to be removed in Pillow 10 (2023-07-01) #5216 [radarhere] - Added tk version to pilinfo #5226 diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 7318da5b7..c8257ee19 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -30,7 +30,7 @@ Tk/Tcl 8.4 .. deprecated:: 8.2.0 -Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-07-01), when Tk/Tcl 8.5 will be the minimum supported. Categories @@ -38,7 +38,7 @@ Categories .. deprecated:: 8.2.0 -``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-07-01), along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and ``Image.CONTAINER`` attributes. @@ -53,14 +53,14 @@ JpegImagePlugin.convert_dict_qtables JPEG ``quantization`` is now automatically converted, but still returned as a dictionary. The :py:attr:`~PIL.JpegImagePlugin.convert_dict_qtables` method no longer performs any operations on the data given to it, has been deprecated and will be -removed in Pillow 10.0.0 (2023-01-02). +removed in Pillow 10.0.0 (2023-07-01). ImagePalette size parameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 8.4.0 -The ``size`` parameter will be removed in Pillow 10.0.0 (2023-01-02). +The ``size`` parameter will be removed in Pillow 10.0.0 (2023-07-01). Before Pillow 8.3.0, ``ImagePalette`` required palette data of particular lengths by default, and the size parameter could be used to override that. Pillow 8.3.0 removed diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst index 912af3ad2..c902ccf71 100644 --- a/docs/releasenotes/8.2.0.rst +++ b/docs/releasenotes/8.2.0.rst @@ -7,7 +7,7 @@ Deprecations Categories ^^^^^^^^^^ -``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-07-01), along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and ``Image.CONTAINER`` attributes. @@ -17,7 +17,7 @@ To determine if an image has multiple frames or not, Tk/Tcl 8.4 ^^^^^^^^^^ -Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-01-02), +Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-07-01), when Tk/Tcl 8.5 will be the minimum supported. API Changes diff --git a/docs/releasenotes/8.3.0.rst b/docs/releasenotes/8.3.0.rst index eb4883deb..0bfead144 100644 --- a/docs/releasenotes/8.3.0.rst +++ b/docs/releasenotes/8.3.0.rst @@ -10,7 +10,7 @@ JpegImagePlugin.convert_dict_qtables JPEG ``quantization`` is now automatically converted, but still returned as a dictionary. The :py:attr:`~PIL.JpegImagePlugin.convert_dict_qtables` method no longer performs any operations on the data given to it, has been deprecated and will be -removed in Pillow 10.0.0 (2023-01-02). +removed in Pillow 10.0.0 (2023-07-01). API Changes =========== diff --git a/docs/releasenotes/8.4.0.rst b/docs/releasenotes/8.4.0.rst index 67f259efb..9becf9146 100644 --- a/docs/releasenotes/8.4.0.rst +++ b/docs/releasenotes/8.4.0.rst @@ -10,7 +10,7 @@ Deprecations ImagePalette size parameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``size`` parameter will be removed in Pillow 10.0.0 (2023-01-02). +The ``size`` parameter will be removed in Pillow 10.0.0 (2023-07-01). Before Pillow 8.3.0, ``ImagePalette`` required palette data of particular lengths by default, and the size parameter could be used to override that. Pillow 8.3.0 removed diff --git a/src/PIL/Image.py b/src/PIL/Image.py index da0bda7ec..6ecd5fac8 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -58,7 +58,7 @@ if sys.version_info >= (3, 7): if name in categories: warnings.warn( "Image categories are deprecated and will be removed in Pillow 10 " - "(2023-01-02). Use is_animated instead.", + "(2023-07-01). Use is_animated instead.", DeprecationWarning, stacklevel=2, ) @@ -521,7 +521,7 @@ class Image: if name == "category": warnings.warn( "Image categories are deprecated and will be removed in Pillow 10 " - "(2023-01-02). Use is_animated instead.", + "(2023-07-01). Use is_animated instead.", DeprecationWarning, stacklevel=2, ) diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 36826bdf3..1e0d36b41 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -44,7 +44,7 @@ class ImagePalette: if size != 0: warnings.warn( "The size parameter is deprecated and will be removed in Pillow 10 " - "(2023-01-02).", + "(2023-07-01).", DeprecationWarning, ) if size != len(self.palette): diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index b8674eeed..a4fc5936b 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -603,7 +603,7 @@ samplings = { def convert_dict_qtables(qtables): warnings.warn( "convert_dict_qtables is deprecated and will be removed in Pillow 10" - "(2023-01-02). Conversion is no longer needed.", + "(2023-07-01). Conversion is no longer needed.", DeprecationWarning, ) return qtables diff --git a/src/PIL/_tkinter_finder.py b/src/PIL/_tkinter_finder.py index 58aeffbfb..ba4d045e6 100644 --- a/src/PIL/_tkinter_finder.py +++ b/src/PIL/_tkinter_finder.py @@ -14,7 +14,7 @@ tk_version = str(tkinter.TkVersion) if tk_version == "8.4": warnings.warn( "Support for Tk/Tcl 8.4 is deprecated and will be removed" - " in Pillow 10 (2023-01-02). Please upgrade to Tk/Tcl 8.5 " + " in Pillow 10 (2023-07-01). Please upgrade to Tk/Tcl 8.5 " "or newer.", DeprecationWarning, ) From 90d5edb0e4722a5aca2f66ebaf80e0ce327773fc Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 18 Oct 2021 16:06:07 +0300 Subject: [PATCH 127/633] Install pytest-timeout for MinGW CI --- .github/workflows/test-mingw.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index 6125af024..bd2de2381 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -55,7 +55,7 @@ jobs: ${{ matrix.package }}-openjpeg2 \ subversion - python3 -m pip install pyroma pytest pytest-cov + python3 -m pip install pyroma pytest pytest-cov pytest-timeout pushd depends && ./install_extra_test_images.sh && popd From e6ffeac66f35ff011c59e5698d68a534a4b7ac28 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 19 Oct 2021 16:08:23 +1100 Subject: [PATCH 128/633] Updated image comparison --- Tests/images/multiline_text.png | Bin 2843 -> 3197 bytes Tests/images/multiline_text_center.png | Bin 2845 -> 3203 bytes Tests/images/multiline_text_right.png | Bin 2846 -> 3200 bytes Tests/images/multiline_text_spacing.png | Bin 2840 -> 3197 bytes Tests/test_imagefont.py | 10 +++------- 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Tests/images/multiline_text.png b/Tests/images/multiline_text.png index ff1308c5ef277048d26abec36dd76610e8fbf3fe..e39c6586ca507b48ddcf0091d682d7a5dcd43bee 100644 GIT binary patch delta 3125 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 JRRzEK;6I`8ReS&d literal 2843 zcmb_e_g9ly6Go6;R1lFa$^ueXno=akEJ(nFq7Vd;E+AD5MOtt*f)N8sQ33%Ll=ji2 z1?g?0OGpS(gwP2HBF&JH2r;m)>)CVm5BPqWcg~sn-g#!`nVE}ru(K8ykrUzJ;Ssli zSYGAfIiwBfenPx}Z1Bx}$-^T;v$4G7bPu;N6AZZ)t|0i8OxC&aNvC4;Pw)WoimQ{K zmCph1FPfrpQdn^tkta27dfOOjezlCq=Z2c8xeJ=AT0PnWw~NGIFz{;rrH7;IqU*X> z4rtxG%9LeRNI@i*gY?+IG1g;|E4YjK8>B=jG~ky6dR#m2lw+(l4w;kv$lqYkr3mxojpE535cqs$C{#9w4t%FoAwJzA*gcf%f!U> z&5wAzG5D{cp`q5OwH%|my1FhT_cW@psp-SGB{luJ_G}BH?DDH~Sg2ZNMQ0~+yym7) zT~4SM$F=#@tA|G)uxIDdLMkOCCF=GCViFQ8I^6~W2@DC**3g*8_fsgf6&0=>aXU-> znZJR-`uh5&W@b{7k`n?~D;ir`Of)nebF{Hg)|W9YEcCLc7z`wdeqB{n1z-2xq@)X1KqNC)_Omg{0VPkZx6b=^tf{8!iKt*R%LztRn6?XQ;?Ej>vW}vJa5Tf<-XO_ zrt)%)vuFG9FdLix`db4N1Ys$)#>PfyfjQRocq$<(D(YG(G@?nVjC)cnIVb1jl=?dY zA!4rH=jZP`3w^ z&*CGN)|0jI+LfiH36F%gF=u3f*n^r(b#y{^w?AEeRaaX}=^!ewuerM?u-08?xi`YY zx7yp=X*8c`3^hbTOl%47TJMLB-{JZ0Vd(M{hr_kDwk|C#{qe^sEcAq|EEbEc7z>io zzTO@0tIV0EPR(-Xi z)V{FBLF0wajp&8mN6E=#Tid9p9ro57OfA!{%nY#%wX{Ud zScr;<5Q)Ugo_PB@*MP*qKh z(j4-ip7~dJ`6=bB7CUJM2M2TZ_K^GEsbgbNo6{{enQHXOlVSpRMt662adC02XO|r2 zT~^ldFoX9k7aoi@gw3=T6{$>V0hzQjG0)XawpQhLlT9}=k{rvet(8VLl z7VWVNe?Pz1?z-uZAHR*>kYk@5#)*O<2n1uYDOg&>DlBZX%)T&Z&d=Z9|4U@+TuefD zqEf-?>1iLpW%A0KbVGLo)zadK$rRA&G8@81i|WAE-US3{@p zEp8NmcD%g2Z-aUH_&6L6)hZWp$w2s#sl_SPBmI#r@$4{1N5?yN-T-7sOBNofH zq=SN^!((FvygKgfDSdO(K<0bF!^u#Y`*ai_BL2~ShsykH+&h1=njhHF_am3j?|!47 zVK}my(`fHD)6FQgRECy9u)xEf2tJqe@pNU-WyiGOck}N1Xg!&q#%p#*1l1O{l)!fi zJR4SAE|eZFMEMG54(a|h^LdydDv7oyx=IZM20}X*q zTUuH=I_l(-PC8H~XTND|WCE+Or>Dol!a`VB7=yv^^Ye#Ae!lP%9MV1JAU8hU$sM-LoL=(5~grVjnU zyBPPj50k}4eEaI%-G)vqY=Z6W)>%1}`#)B9foTucul4j7cnF?PK-c zUffi<=*Q3OE9K_$#N2$=8K+qa@Dzy!p^u|2N#QW-IO1F&Nv&u^O#1wUVyar|BYm?J zS+MpAK>>kIBsUjkvn=V?g(5Z-RZ(w)z@NBG7ArX=g#ibIucU-;lUwLkT!gTZaK8V4 zBA-?%h2?!59c}a{%gx?-u>R5^);P<^Y{R;|y%6Z~D=&5F@DtL~0Bpv5&qH7YK6`dl zS-G$Uu@W}d$z2^OYY3qD%L^F^qZ|H<>%1id@)%zE=@)0wdQaXT$y{$#F4{%{9m}oYrtL){s!ysj86an diff --git a/Tests/images/multiline_text_center.png b/Tests/images/multiline_text_center.png index f44d0783a0944986efdc62d4f0bcc7530b74ab5b..837c6382a97ecc064fddf85f902e2fba8ba0ce90 100644 GIT binary patch literal 3203 zcmbtWX*8Sb7Ir$AT6K=0T0^zeP(y=~nyUAhipE%{HET^FB3e=NP)Zd&6jc?6oK$0q zDosO!(4&Ycay5+@VwR$63<)B7zn-)1TKE5bf9&=C_}2d3{p@Ey&(3smxF#klD=Hu$ zAZBZ0=^`NTlNN9f5IzJ%yg#Z)K;XEYt>v$-F@LN~Btzs_ibCtDHsIh;6&zw&RTXaY zHpL+$-SBdw5m=K=c6)gaYuy3FWp9e55|bn-%*;PEO9m$jFwq1w|)X`yBre=Hlj7#aPDG z)VwS&S5{Jz674F~yfr&FM@%%^U;nt){ymAmx2=?CqgoK6FN>q^bxJzcD|B>psAuKd zVVTkC85zJE(=sv&WGoDG?65D@zm7uD(xU0+n7cgAij-Bgt40$)g7@LVg$p7DKc_n1 zxpOBlFwn+Ec>Lv1@Cik;fr}NdUj1Q&{Ic4z_N7hJ%&ZNE)70N;&rW4BnH?SG2=upa z-wq3%*zwGz-V4xkcXV`gb(LxYf~%t=^%M3wk(X|3m_Z@*4}JX#Lsg7g;pol5LrPJ) z%F1&qO;c_6<^;LgL3kpNR#!JKYkuaZUEjnTsIMRD*v;d&xtS&t4^IliHq*wODLA=V@sv|KAqk4HNPwG8`($TcSbu z7tfzZeHkcjXwXf*ynvK1hH#eOw8ym#`88^o9YQtV5>>5zrRp>k#! zfWheK=m2H>fX};r{d#y<_9S^}ZOymAFMXlqX04m9j?Uds&oq<4eN>un(k`ncylrWB zSCk9R*9C)bdV2CU`$1|dD*OZ9S~9?j*I`3ELvpSnl|s4SqV~)V3t&%M-iG&gc6O4u zll?2~9_3u7#}$D~{p`a}D$C+(UcE{i-yHS~FTI-VgK@IKx?r46DJr513jFnoZ@%_m zfl;m+2E`BpfuMP-?)WQ>wYe%fEbC=s_|#y6@g5UN-R5v==r9C&dzo=VN?Jyy>F$g< z(JN_daVT2`H2d(hu<-ylLf`(DtE-MS7K?>s(_t)5!k1#Gho@&4czkE){U|(Xjl``W zsRmeARac9&g4gEYWAA!z>x`^KwM-y%PJt23#Zfg7XzeQ{_W77=XcoR6_ywpparh{r%

C-~D+k#=R*`)Mfol`;4 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%0AI~}qU7P2X8@rDUK>UF9CWiCjXlr9v8+L-( zMjM~ytDS|Kmp1mfW{Qh))KpcctLnD~5fYm2*Hy$Eik(!%+B-VtW@j^O3U5%HsmV%2 zD_bSUz`*4$qB_cfxPxV)FTm}`s_&v;($Oo_xkx;I0DYmF{&QS}5kFZ%l(3fs_?!FbpvO}DhP6iNz=ieh1)O*<2KhNq~G zJd??!)k9ZjFryzjy|jjxadqQCT4$g^tnVYr5XkCiWq_!t=+!*?0tZE9Wrkk#*H4Kr zhfWAH0d{K0nJ1#o{jmpEB3yt%Ptf(bHhXzwqu${PEgJ4Jbq9*s`)+&eNw)XF0pgHrx@uiLT%YY6uF2 zn)3)7|C>7KqXS$QNeu?FIas{=?+q7ZG|^*GcPG9)l8N5RF~%ERk~Ip~QdjRTj~pyT zpekGz-d$@Ig1EkIt|(&6vudz;ZMWCm;Th}}Yvb|paV!>l z^uc)rg)5YS#_iDn8$1Ssv9n7-Ze_uu4S}*0JH3~L3J3_WP)R50ZOLaZW=J?XI+~f8 z(E^GXo!7KvQs~*rTm-GxI@VV}eLIt}5b-2{r#5(!c-yB>vvYHYdvjz44riDxFE7t1 z1JQuy%A|MyeNP_*0*Q!<_V`u|pgk(l#48|4Y3ch^_n3i@`ThdwOE?8(<)NV=N3j7G zD=R&{BYQ0>`?tntbZJ%ad4z_#dR_SXS995Dy$csA0mf-Go0U;24QyZRl$@L#5fOp0 z4hsuIBFRlNT2#nn411ZyWQ>gLea}l#6XR>}Tn(lI<)o8MCj0uP?;5A3rutUw5nViQ ziFEfr#i|(O{YzzVxo6hVcBYOkUsT2E__j@ZR1YYtgJFWLgy2!Luu-@KO;FYYB*_ z4WQCOLde?C;;VVuq#iMNsYl&lMMbC2hK^XoN;hpWV(I};%Y_tKnSOe;<5F7nSxJzl{WOE~kovWwjz+c& z8Zs;?DJjptwzxQPa^_f+sW0#E=jAGnpKpHdc2xMxXYi5#2t-X!IIKy6f0*Z#e`arI z=gS5GVtP75&jX1x!IP#u)`n=XGpb^I$2_d>2`9A?iNpzZILvT1zSGA+ji_6e^cj#~ z-}@qb$KwDs(#nYaSJ8b06Qd`k&3m}l@bS+-Pge5;9`OpI4LXYLvQxMonZiMR!^6W< zQ&RxQ)X6ET@qTX?jRv>~V8+tY(ugVy7SYYQU=2zCQ-P`!wVsyp)kXG&XE_SD_G6wA zL;IOum{H@Kpjb2I?%4n{RDs>gwqU3<_$T%fb8Nn~v*tXRN#F zPV2)KZ$;3jR8>^`{QcePaDn0B;h~|S0I3lX1}nUWaD85Nb#>}tq`zKfYl4v7*hu%M zwU+^3%MVAzEn$4Oas=4NJe-{J0ova?oT*=xSQOXswLmMK>p&TUA(BRkiHWG*EG&Ly zc54V3rhhKu(Sb1&;o)J+(5v&Syg7mq35JE(?wY2hnC75F#l;I5^Pg^Got zbG|BiKhF1U!G?EE0km;*b6+bWjO`wT<%)#)cnmACycW5EwH1drDlAy8qoex1YuOC7zET zKd!B+Geg5bnI@|l%$kYTBpy$hYwlp&!0b6w0^OBiJ3h|KIBza zR-RkxXQ9e?kro^FpLLxw7T2n(pry|E1*R&!{6@qGq*2HBWWct8AX3r@Fp1d{M|5$^ zqm|+7w__7(H}^Nqbaiw@BqgaRy39LQaH_@N8@G_D<_B((-?tmtE0nUbZ{ISbwgy}b zv!CqGIx9cr?8A?1?Pu?18Q$*-T-bPz)X>nlS^9#c z%@`c4x;G8{gqN3>0lDXJI5tk7nwy)gAq~uli34QoflCjpL+(w7G9XoEBNPtjW;oou z+4aG-)VUB2N(Ee>j5D5Y8q=bj9)t(RxL_ybI7$IH?uTQPxwcc2{{R(v`~WbLni z3T&|;N1Ngm5EwxJ`UnFpYC1f?+uQfWasT%8syxZ^*#Fx7|2r1`alkXNb$WX$PS*3+ OT9)u@SISJi;{FZ&TsSHK delta 2771 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 LS{jwZ-5>uK@T!eO diff --git a/Tests/images/multiline_text_spacing.png b/Tests/images/multiline_text_spacing.png index 3c3bc0f267d365d5a29531d9d0f18cc8e2aac7b6..3b367c7ddee8587239069ae3340b608364164438 100644 GIT binary patch delta 3127 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 diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index acc1cdb13..3ed3a2f02 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -155,7 +155,6 @@ class TestImageFont: draw.text((10, 10), txt, font=ttf) draw.rectangle((10, 10, 10 + size[0], 10 + size[1])) - # Epsilon ~.5 fails with FreeType 2.7 assert_image_similar_tofile( im, "Tests/images/rectangle_surrounding_text.png", 2.5 ) @@ -216,8 +215,7 @@ class TestImageFont: draw = ImageDraw.Draw(im) draw.text((0, 0), TEST_TEXT, font=ttf) - # Epsilon ~.5 fails with FreeType 2.7 - assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 6.2) + assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 0.01) # Test that text() can pass on additional arguments # to multiline_text() @@ -232,9 +230,8 @@ class TestImageFont: draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=ttf, align=align) - # Epsilon ~.5 fails with FreeType 2.7 assert_image_similar_tofile( - im, "Tests/images/multiline_text" + ext + ".png", 6.2 + im, "Tests/images/multiline_text" + ext + ".png", 0.01 ) def test_unknown_align(self): @@ -289,8 +286,7 @@ class TestImageFont: draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=ttf, spacing=10) - # Epsilon ~.5 fails with FreeType 2.7 - assert_image_similar_tofile(im, "Tests/images/multiline_text_spacing.png", 6.2) + assert_image_similar_tofile(im, "Tests/images/multiline_text_spacing.png", 2.5) def test_rotated_transposed_font(self): img_grey = Image.new("L", (100, 100)) From 06ab0324a3bb66965c7c1505dbdd0aa640ba308b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 19 Oct 2021 17:07:55 +1100 Subject: [PATCH 129/633] Added Tidelift Align badge to docs --- docs/index.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 3fbc8d0e6..0e16259f3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -49,6 +49,10 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more Date: Tue, 19 Oct 2021 19:40:46 +1100 Subject: [PATCH 130/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 934c634aa..5cf1ce1a8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,18 @@ Changelog (Pillow) ================== +9.0.0 (unreleased) +------------------ + +- Remove support for FreeType 2.7 and older #5777 + [hugovk, radarhere] + +- Fix for PyQt6 #5775 + [hugovk, radarhere] + +- Removed deprecated PILLOW_VERSION, Image.show command parameter, Image._showxv and ImageFile.raise_ioerror #5776 + [radarhere] + 8.4.0 (2021-10-15) ------------------ From 716a0baf74ec9fa07255ae59d00a339c763616c4 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 15 Oct 2021 13:07:53 +0300 Subject: [PATCH 131/633] Drop support for EOL Python 3.6 --- .appveyor.yml | 2 +- .github/workflows/test-docker.yml | 4 -- .pre-commit-config.yaml | 2 +- Makefile | 2 +- Tests/test_image.py | 4 -- docs/installation.rst | 64 ++++++++++++++----------------- docs/releasenotes/9.0.0.rst | 5 +++ setup.py | 3 +- src/PIL/Image.py | 30 ++++++--------- src/libImaging/ImPlatform.h | 6 --- tox.ini | 2 +- winbuild/build_prepare.py | 1 - 12 files changed, 50 insertions(+), 75 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 6cf40130e..fb19fb7cb 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -13,7 +13,7 @@ environment: - PYTHON: C:/Python39 ARCHITECTURE: x86 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - - PYTHON: C:/Python36-x64 + - PYTHON: C:/Python37-x64 ARCHITECTURE: x64 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 46e9e5f2c..490af5e4b 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -18,12 +18,8 @@ jobs: alpine, amazon-2-amd64, arch, - centos-7-amd64, - centos-8-amd64, - centos-stream-8-amd64, debian-10-buster-x86, fedora-34-amd64, - ubuntu-18.04-bionic-amd64, ubuntu-20.04-focal-amd64, ] dockerTag: [main] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4a0256085..5f1d16709 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: rev: 911470a610e47d9da5ea938b0887c3df62819b85 # frozen: 21.9b0 hooks: - id: black - args: ["--target-version", "py36"] + args: ["--target-version", "py37"] # Only .py files, until https://github.com/psf/black/issues/402 resolved files: \.py$ types: [] diff --git a/Makefile b/Makefile index af3059f34..2d5c3e12e 100644 --- a/Makefile +++ b/Makefile @@ -121,5 +121,5 @@ lint: .PHONY: lint-fix lint-fix: - black --target-version py36 . + black --target-version py37 . isort . diff --git a/Tests/test_image.py b/Tests/test_image.py index c185a1cb4..cf60f42af 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,7 +1,6 @@ import io import os import shutil -import sys import tempfile import pytest @@ -782,9 +781,6 @@ class TestImage: 34665: 196, } - @pytest.mark.skipif( - sys.version_info < (3, 7), reason="Python 3.7 or greater required" - ) def test_categories_deprecation(self): with pytest.warns(DeprecationWarning): assert hopper().category == 0 diff --git a/docs/installation.rst b/docs/installation.rst index fc5b0e7e9..c19bb2dd1 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -18,7 +18,9 @@ Pillow supports these Python versions. +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ | Python |3.10 | 3.9 | 3.8 | 3.7 | 3.6 | 3.5 | 3.4 | 2.7 | +======================+=====+=====+=====+=====+=====+=====+=====+=====+ -| Pillow >= 8.3.2 | Yes | Yes | Yes | Yes | Yes | | | | +| Pillow >= 9.0 | Yes | Yes | Yes | Yes | | | | | ++----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ +| Pillow 8.3.2 - 8.4 | Yes | Yes | Yes | Yes | Yes | | | | +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ | Pillow 8.0 - 8.3.1 | | Yes | Yes | Yes | Yes | | | | +----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ @@ -443,40 +445,32 @@ Continuous Integration Targets These platforms are built and tested for every change. -+----------------------------------+---------------------------------+---------------------+ -| Operating system | Tested Python versions | Tested architecture | -+==================================+=================================+=====================+ -| Alpine | 3.9 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ -| Amazon Linux 2 | 3.7 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ -| Arch | 3.9 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ -| CentOS 7 | 3.6 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ -| CentOS 8 | 3.6 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ -| CentOS Stream 8 | 3.6 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ -| Debian 10 Buster | 3.7 | x86 | -+----------------------------------+---------------------------------+---------------------+ -| Fedora 34 | 3.9 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ -| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ -| Ubuntu Linux 18.04 LTS (Bionic) | 3.6 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ -| Ubuntu Linux 20.04 LTS (Focal) | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | -| +---------------------------------+---------------------+ -| | 3.8 | arm64v8, ppc64le, | -| | | s390x | -+----------------------------------+---------------------------------+---------------------+ -| Windows Server 2016 | 3.6 | x86-64 | -+----------------------------------+---------------------------------+---------------------+ -| Windows Server 2019 | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86, x86-64 | -| +---------------------------------+---------------------+ -| | 3.9/MinGW | x86, x86-64 | -+----------------------------------+---------------------------------+---------------------+ ++----------------------------------+----------------------------+---------------------+ +| Operating system | Tested Python versions | Tested architecture | ++==================================+============================+=====================+ +| Alpine | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Amazon Linux 2 | 3.7 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Arch | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Debian 10 Buster | 3.7 | x86 | ++----------------------------------+----------------------------+---------------------+ +| Fedora 34 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| macOS 10.15 Catalina | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Ubuntu Linux 20.04 LTS (Focal) | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | +| +----------------------------+---------------------+ +| | 3.8 | arm64v8, ppc64le, | +| | | s390x | ++----------------------------------+----------------------------+---------------------+ +| Windows Server 2016 | 3.7 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Windows Server 2019 | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86, x86-64 | +| +----------------------------+---------------------+ +| | 3.9/MinGW | x86, x86-64 | ++----------------------------------+----------------------------+---------------------+ Other Platforms diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 6965b65bf..2652d7fbc 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -4,6 +4,11 @@ Backwards Incompatible Changes ============================== +Python 3.6 +^^^^^^^^^^ + +Pillow has dropped support for Python 3.6, which reached end-of-life on 2021-12-23. + PILLOW_VERSION constant ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/setup.py b/setup.py index 3574c9137..7e8e7c4f0 100755 --- a/setup.py +++ b/setup.py @@ -1000,7 +1000,6 @@ try: "Development Status :: 6 - Mature", "License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)", # noqa: E501 "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -1014,7 +1013,7 @@ try: "Topic :: Multimedia :: Graphics :: Graphics Conversion", "Topic :: Multimedia :: Graphics :: Viewers", ], - python_requires=">=3.6", + python_requires=">=3.7", cmdclass={"build_ext": pil_build_ext}, ext_modules=ext_modules, include_package_data=True, diff --git a/src/PIL/Image.py b/src/PIL/Image.py index da0bda7ec..0697fc01d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -51,26 +51,18 @@ from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins from ._binary import i32le from ._util import deferred_error, isPath -if sys.version_info >= (3, 7): - def __getattr__(name): - categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2} - if name in categories: - warnings.warn( - "Image categories are deprecated and will be removed in Pillow 10 " - "(2023-01-02). Use is_animated instead.", - DeprecationWarning, - stacklevel=2, - ) - return categories[name] - raise AttributeError(f"module '{__name__}' has no attribute '{name}'") - - -else: - # categories - NORMAL = 0 - SEQUENCE = 1 - CONTAINER = 2 +def __getattr__(name): + categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2} + if name in categories: + warnings.warn( + "Image categories are deprecated and will be removed in Pillow 10 " + "(2023-01-02). Use is_animated instead.", + DeprecationWarning, + stacklevel=2, + ) + return categories[name] + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") logger = logging.getLogger(__name__) diff --git a/src/libImaging/ImPlatform.h b/src/libImaging/ImPlatform.h index 9a2060edf..26b49d6d0 100644 --- a/src/libImaging/ImPlatform.h +++ b/src/libImaging/ImPlatform.h @@ -9,12 +9,6 @@ #include "Python.h" -/* Workaround issue #2479 */ -#if PY_VERSION_HEX < 0x03070000 && defined(PySlice_GetIndicesEx) && \ - !defined(PYPY_VERSION) -#undef PySlice_GetIndicesEx -#endif - /* Check that we have an ANSI compliant compiler */ #ifndef HAVE_PROTOTYPES #error Sorry, this library requires support for ANSI prototypes. diff --git a/tox.ini b/tox.ini index cdc2ab881..96d84f933 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ [tox] envlist = lint - py{36,37,38,39,310,py3} + py{37,38,39,310,py3} minversion = 1.9 [testenv] diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 807cbbf27..b62bf8b4e 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -474,7 +474,6 @@ def build_pillow(): cmd_cd("{pillow_dir}"), *prefs["header"], cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow - cmd_set("MSSdk", "1"), # for PyPy3.6 cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', # noqa: E501 ] From 31a96b9c9b323488e70eff69fa3433347f649861 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 15 Oct 2021 13:10:22 +0300 Subject: [PATCH 132/633] Upgrade Python syntax with pyupgrade --py36-plus and format with Black --- Tests/test_file_gif.py | 4 ++-- Tests/test_file_jpeg.py | 12 ++++++------ Tests/test_file_webp.py | 4 +--- Tests/test_imagefont.py | 8 ++++---- Tests/test_imagemath.py | 2 +- src/PIL/GifImagePlugin.py | 2 +- src/PIL/Image.py | 12 ++++-------- src/PIL/ImageDraw.py | 14 ++++++-------- src/PIL/PdfParser.py | 4 ++-- src/PIL/TiffImagePlugin.py | 6 +++--- 10 files changed, 30 insertions(+), 38 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 985f8e52c..fae7e912c 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -818,7 +818,7 @@ def test_palette_save_P(tmp_path): # Forcing a non-straight grayscale palette. im = hopper("P") - palette = bytes([255 - i // 3 for i in range(768)]) + palette = bytes(255 - i // 3 for i in range(768)) out = str(tmp_path / "temp.gif") im.save(out, palette=palette) @@ -885,7 +885,7 @@ def test_getdata(): im.putpalette(ImagePalette.ImagePalette("RGB")) im.info = {"background": 0} - passed_palette = bytes([255 - i // 3 for i in range(768)]) + passed_palette = bytes(255 - i // 3 for i in range(768)) GifImagePlugin._FORCE_OPTIMIZE = True try: diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 13d99c15d..f2002ecb8 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -85,26 +85,26 @@ class TestFileJpeg: f = "Tests/images/pil_sample_cmyk.jpg" with Image.open(f) as im: # the source image has red pixels in the upper left corner. - c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] + c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) assert c == 0.0 assert m > 0.8 assert y > 0.8 assert k == 0.0 # the opposite corner is black - c, m, y, k = [ + c, m, y, k = ( x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) - ] + ) assert k > 0.9 # roundtrip, and check again im = self.roundtrip(im) - c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] + c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) assert c == 0.0 assert m > 0.8 assert y > 0.8 assert k == 0.0 - c, m, y, k = [ + c, m, y, k = ( x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) - ] + ) assert k > 0.9 @pytest.mark.parametrize( diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 420594b0c..e72b4993c 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -188,9 +188,7 @@ class TestFileWebp: with Image.open(out_gif) as reread: reread_value = reread.convert("RGB").getpixel((1, 1)) - difference = sum( - [abs(original_value[i] - reread_value[i]) for i in range(0, 3)] - ) + difference = sum(abs(original_value[i] - reread_value[i]) for i in range(0, 3)) assert difference < 5 @skip_unless_feature("webp") diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 3ed3a2f02..0d423aab7 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -900,7 +900,7 @@ class TestImageFont: d.text((10, 10), "\U0001f469", font=font, embedded_color=True) assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2) - except IOError as e: # pragma: no cover + except OSError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or CBDT support") @@ -920,7 +920,7 @@ class TestImageFont: assert_image_similar_tofile( im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2 ) - except IOError as e: # pragma: no cover + except OSError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or CBDT support") @@ -938,7 +938,7 @@ class TestImageFont: d.text((50, 50), "\uE901", font=font, embedded_color=True) assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1) - except IOError as e: # pragma: no cover + except OSError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or SBIX support") @@ -956,7 +956,7 @@ class TestImageFont: d.text((50, 50), "\uE901", (100, 0, 0), font=font) assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1) - except IOError as e: # pragma: no cover + except OSError as e: # pragma: no cover assert str(e) in ("unimplemented feature", "unknown file format") pytest.skip("freetype compiled without libpng or SBIX support") diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index 239806796..e7afd1abf 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 "{} {}".format(im.mode, repr(im.getpixel((0, 0)))) + return f"{im.mode} {repr(im.getpixel((0, 0)))}" else: if isinstance(im, int): return int(im) # hack to deal with booleans diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 128afc428..5b4668844 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -324,7 +324,7 @@ class GifImageFile(ImageFile.ImageFile): if not self.im and "transparency" in self.info: self.im = Image.core.fill(self.mode, self.size, self.info["transparency"]) - super(GifImageFile, self).load_prepare() + super().load_prepare() def tell(self): return self.__frame diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0697fc01d..831970cfb 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -918,12 +918,8 @@ class Image: transparency = convert_transparency(matrix, transparency) elif len(mode) == 3: transparency = tuple( - [ - convert_transparency( - matrix[i * 4 : i * 4 + 4], transparency - ) - for i in range(0, len(transparency)) - ] + convert_transparency(matrix[i * 4 : i * 4 + 4], transparency) + for i in range(0, len(transparency)) ) new.info["transparency"] = transparency return new @@ -1926,7 +1922,7 @@ class Image: message = f"Unknown resampling filter ({resample})." filters = [ - "{} ({})".format(filter[1], filter[0]) + f"{filter[1]} ({filter[0]})" for filter in ( (NEAREST, "Image.NEAREST"), (LANCZOS, "Image.LANCZOS"), @@ -2521,7 +2517,7 @@ class Image: message = f"Unknown resampling filter ({resample})." filters = [ - "{} ({})".format(filter[1], filter[0]) + f"{filter[1]} ({filter[0]})" for filter in ( (NEAREST, "Image.NEAREST"), (BILINEAR, "Image.BILINEAR"), diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index aea0cc680..eeae1782a 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -174,13 +174,11 @@ class ImageDraw: angle -= 90 distance = width / 2 - 1 return tuple( - [ - p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d)) - for p, p_d in ( - (x, distance * math.cos(math.radians(angle))), - (y, distance * math.sin(math.radians(angle))), - ) - ] + p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d)) + for p, p_d in ( + (x, distance * math.cos(math.radians(angle))), + (y, distance * math.sin(math.radians(angle))), + ) ) flipped = ( @@ -979,6 +977,6 @@ def _color_diff(color1, color2): Uses 1-norm distance to calculate difference between two values. """ if isinstance(color2, tuple): - return sum([abs(color1[i] - color2[i]) for i in range(0, len(color2))]) + return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2))) else: return abs(color1 - color2) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index b5279e0d7..b95abbe2f 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -425,7 +425,7 @@ class PdfParser: self.f.write(b"%PDF-1.4\n") def write_comment(self, s): - self.f.write(f"% {s}\n".encode("utf-8")) + self.f.write(f"% {s}\n".encode()) def write_catalog(self): self.del_root() @@ -862,7 +862,7 @@ class PdfParser: if m: # filter out whitespace hex_string = bytearray( - [b for b in m.group(1) if b in b"0123456789abcdefABCDEF"] + b for b in m.group(1) if b in b"0123456789abcdefABCDEF" ) if len(hex_string) % 2 == 1: # append a 0 if the length is not even - yes, at the end diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 6b311bbf5..29de8a06b 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -674,7 +674,7 @@ class ImageFileDirectory_v2(MutableMapping): _load_dispatch[idx] = ( # noqa: F821 size, lambda self, data, legacy_api=True: ( - self._unpack("{}{}".format(len(data) // size, fmt), data) + self._unpack(f"{len(data) // size}{fmt}", data) ), ) _write_dispatch[idx] = lambda self, *values: ( # noqa: F821 @@ -718,7 +718,7 @@ class ImageFileDirectory_v2(MutableMapping): @_register_loader(5, 8) def load_rational(self, data, legacy_api=True): - vals = self._unpack("{}L".format(len(data) // 4), data) + vals = self._unpack(f"{len(data) // 4}L", data) def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b) @@ -741,7 +741,7 @@ class ImageFileDirectory_v2(MutableMapping): @_register_loader(10, 8) def load_signed_rational(self, data, legacy_api=True): - vals = self._unpack("{}l".format(len(data) // 4), data) + vals = self._unpack(f"{len(data) // 4}l", data) def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b) From 24ca657e29d942f0f55d042070cd626c6144d86b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 15 Oct 2021 16:57:09 +0300 Subject: [PATCH 133/633] Remove command for Python 3.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- winbuild/build_prepare.py | 1 - 1 file changed, 1 deletion(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index b62bf8b4e..7568d2359 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -474,7 +474,6 @@ def build_pillow(): cmd_cd("{pillow_dir}"), *prefs["header"], cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow - cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', # noqa: E501 ] From a44e8e626d4d9c55f03a342e15a8ed3e7f65423f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 21 Oct 2021 09:41:34 +0300 Subject: [PATCH 134/633] Use declarative package configuration --- setup.cfg | 38 ++++++++++++++++++++++++++++++++++++++ setup.py | 43 ------------------------------------------- 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/setup.cfg b/setup.cfg index 129adeee7..7e43a33c9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,41 @@ +[metadata] +name = Pillow +description = Python Imaging Library (Fork) +long_description = file: README.md +long_description_content_type = text/markdown +url = https://python-pillow.org +author = Alex Clark (PIL Fork Author) +author_email = aclark@python-pillow.org +license = HPND +classifiers = + Development Status :: 6 - Mature + License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND) + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: Implementation :: CPython + Programming Language :: Python :: Implementation :: PyPy + Topic :: Multimedia :: Graphics + Topic :: Multimedia :: Graphics :: Capture :: Digital Camera + Topic :: Multimedia :: Graphics :: Capture :: Screen Capture + Topic :: Multimedia :: Graphics :: Graphics Conversion + Topic :: Multimedia :: Graphics :: Viewers +keywords = Imaging +project_urls = + Documentation=https://pillow.readthedocs.io + Source=https://github.com/python-pillow/Pillow + Funding=https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi + Release notes=https://pillow.readthedocs.io/en/stable/releasenotes/index.html + Changelog=https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst + Twitter=https://twitter.com/PythonPillow + +[options] +python_requires = >=3.6 + [flake8] extend-ignore = E203 max-line-length = 88 diff --git a/setup.py b/setup.py index 3574c9137..9f5d48bf0 100755 --- a/setup.py +++ b/setup.py @@ -26,7 +26,6 @@ def get_version(): return locals()["__version__"] -NAME = "Pillow" PILLOW_VERSION = get_version() FREETYPE_ROOT = None HARFBUZZ_ROOT = None @@ -971,56 +970,14 @@ ext_modules = [ 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=long_description, - long_description_content_type="text/markdown", - license="HPND", - author="Alex Clark (PIL Fork Author)", - author_email="aclark@python-pillow.org", - url="https://python-pillow.org", - project_urls={ - "Documentation": "https://pillow.readthedocs.io", - "Source": "https://github.com/python-pillow/Pillow", - "Funding": "https://tidelift.com/subscription/pkg/pypi-pillow?" - "utm_source=pypi-pillow&utm_medium=pypi", - "Release notes": "https://pillow.readthedocs.io/en/stable/releasenotes/" - "index.html", - "Changelog": "https://github.com/python-pillow/Pillow/blob/main/" - "CHANGES.rst", - "Twitter": "https://twitter.com/PythonPillow", - }, - classifiers=[ - "Development Status :: 6 - Mature", - "License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)", # noqa: E501 - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Multimedia :: Graphics", - "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", - "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", - "Topic :: Multimedia :: Graphics :: Graphics Conversion", - "Topic :: Multimedia :: Graphics :: Viewers", - ], - python_requires=">=3.6", cmdclass={"build_ext": pil_build_ext}, ext_modules=ext_modules, include_package_data=True, packages=["PIL"], package_dir={"": "src"}, - keywords=["Imaging"], zip_safe=not (debug_build() or PLATFORM_MINGW), ) except RequiredDependencyException as err: From 5057f0afaf43aacde659ee7798567d9cb3629a89 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 22 Oct 2021 12:01:42 +0300 Subject: [PATCH 135/633] Replace 'setup.py sdist' with '-m build --sdist' --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index af3059f34..a15d6bb97 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,8 @@ release-test: .PHONY: sdist sdist: - python3 setup.py sdist --format=gztar + python3 -m build --help > /dev/null 2>&1 || python3 -m pip install build + python3 -m build --sdist .PHONY: test test: From b3e690a27020e0b5fee856b9699eea7a12698384 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 23 Oct 2021 15:53:08 +1100 Subject: [PATCH 136/633] Use title for display --- src/PIL/ImageShow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 60c97542f..130e21d05 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -189,8 +189,10 @@ class UnixViewer(Viewer): class DisplayViewer(UnixViewer): """The ImageMagick ``display`` command.""" - def get_command_ex(self, file, **options): + def get_command_ex(self, file, title=None, **options): command = executable = "display" + if title: + command += f" -name {quote(title)}" return command, executable From 954baa1e732ab06f2d8e6961fc633bc7b5b213e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Sat, 23 Oct 2021 10:51:46 +0200 Subject: [PATCH 137/633] document #5788 --- src/PIL/ImageShow.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 130e21d05..cd0737c36 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -187,7 +187,10 @@ class UnixViewer(Viewer): class DisplayViewer(UnixViewer): - """The ImageMagick ``display`` command.""" + """ + The ImageMagick ``display`` command. + This viewer supports the ``title`` parameter. + """ def get_command_ex(self, file, title=None, **options): command = executable = "display" From ef99db8871dc0b7031e4ed9f7a1778be4681906e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 24 Oct 2021 11:53:45 +1100 Subject: [PATCH 138/633] Added release notes for #5788 --- docs/releasenotes/9.0.0.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 6965b65bf..1a46cf086 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -58,10 +58,14 @@ TODO API Additions ============= -TODO -^^^^ +Added support for "title" argument to DisplayViewer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +Support has been added for the "title" argument in +:py:class:`~PIL.ImageShow.UnixViewer.DisplayViewer`, so that when ``im.show()`` or +:py:func:`.ImageShow.show()` use the ``display`` command line tool, the "title" +argument will also now be supported, e.g. ``im.show(title="My Image")`` and +``ImageShow.show(im, title="My Image")``. Security ======== From 127315275fe1eaa870f51f07038c34db4d6b8b92 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 24 Oct 2021 12:23:07 +1100 Subject: [PATCH 139/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 5cf1ce1a8..e831a1082 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Use title for display in ImageShow #5788 + [radarhere] + - Remove support for FreeType 2.7 and older #5777 [hugovk, radarhere] From e3c8ef980b9faeb2f30056e42eba9da7e1dceccf Mon Sep 17 00:00:00 2001 From: Lucy Phipps Date: Sun, 24 Oct 2021 12:38:13 +0100 Subject: [PATCH 140/633] fix compilation on Termux --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3574c9137..b0426961c 100755 --- a/setup.py +++ b/setup.py @@ -561,7 +561,11 @@ class pil_build_ext(build_ext): # headers are at $PREFIX/include # user libs are at $PREFIX/lib _add_directory( - library_dirs, os.path.join(os.environ["ANDROID_ROOT"], "lib") + library_dirs, + os.path.join( + os.environ["ANDROID_ROOT"], + "lib" if struct.calcsize("l") == 4 else "lib64" + ) ) elif sys.platform.startswith("netbsd"): From f246049c4a96aa87fd81a24490fc74399cfb8f28 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 24 Oct 2021 11:39:07 +0000 Subject: [PATCH 141/633] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b0426961c..c53813a1e 100755 --- a/setup.py +++ b/setup.py @@ -564,8 +564,8 @@ class pil_build_ext(build_ext): library_dirs, os.path.join( os.environ["ANDROID_ROOT"], - "lib" if struct.calcsize("l") == 4 else "lib64" - ) + "lib" if struct.calcsize("l") == 4 else "lib64", + ), ) elif sys.platform.startswith("netbsd"): From 97ed2ecd97d025afa862164d82409ea351175658 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 27 Oct 2021 22:51:54 +1100 Subject: [PATCH 142/633] Changed URLs to https --- src/PIL/ImageCms.py | 4 ++-- src/PIL/ImageOps.py | 2 +- src/_imagingcms.c | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 369909590..60e700f09 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -34,9 +34,9 @@ pyCMS a Python / PIL interface to the littleCMS ICC Color Management System Copyright (C) 2002-2003 Kevin Cazabon kevin@cazabon.com - http://www.cazabon.com + https://www.cazabon.com - pyCMS home page: http://www.cazabon.com/pyCMS + pyCMS home page: https://www.cazabon.com/pyCMS littleCMS home page: https://www.littlecms.com (littleCMS is Copyright (C) 1998-2001 Marti Maria) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index f0c932d33..b170e9d8c 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -439,7 +439,7 @@ def fit(image, size, method=Image.BICUBIC, bleed=0.0, centering=(0.5, 0.5)): # by Kevin Cazabon, Feb 17/2000 # kevin@cazabon.com - # http://www.cazabon.com + # https://www.cazabon.com # ensure centering is mutable centering = list(centering) diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 1446bd02b..9b5a121d7 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -3,14 +3,14 @@ * a Python / PIL interface to the littleCMS ICC Color Management System * Copyright (C) 2002-2003 Kevin Cazabon * kevin@cazabon.com - * http://www.cazabon.com + * https://www.cazabon.com * Adapted/reworked for PIL by Fredrik Lundh * Copyright (c) 2009 Fredrik Lundh * Updated to LCMS2 * Copyright (c) 2013 Eric Soroos * - * pyCMS home page: http://www.cazabon.com/pyCMS - * littleCMS home page: http://www.littlecms.com + * pyCMS home page: https://www.cazabon.com/pyCMS + * littleCMS home page: https://www.littlecms.com * (littleCMS is Copyright (C) 1998-2001 Marti Maria) * * Originally released under LGPL. Graciously donated to PIL in @@ -23,7 +23,7 @@ pyCMS\n\ a Python / PIL interface to the littleCMS ICC Color Management System\n\ Copyright (C) 2002-2003 Kevin Cazabon\n\ kevin@cazabon.com\n\ -http://www.cazabon.com\n\ +https://www.cazabon.com\n\ " #define PY_SSIZE_T_CLEAN From 596e80c6545d32c123525e85d29b6de1692e5a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ila=C3=AF=20Deutel?= Date: Thu, 28 Oct 2021 18:15:43 +0000 Subject: [PATCH 143/633] WebP: Fix memory leak during decoding on failure When creating the `WebPAnimDecoder` object, we create a `WebPAnimDecoderObject` and populate its data using `WebPDataCopy()`. Subsequently, if either `WebPAnimDecoderNew()` or `WebPAnimDecoderGetInfo()` fails, data is not currently deallocated. This commit clears the decoder object's data in that situation. --- src/_webp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/_webp.c b/src/_webp.c index 6c357dbb0..90719b466 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -392,6 +392,7 @@ _anim_decoder_new(PyObject *self, PyObject *args) { return (PyObject *)decp; } } + WebPDataClear(&(decp->data)); } PyObject_Del(decp); } From a215f725ba98efeb48194460b8796be3acd6173f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 31 Oct 2021 17:51:23 +1100 Subject: [PATCH 144/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f362c6ce4..3d422a52d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Fix compilation on 64-bit Termux #5793 + [landfillbaby] + - Use title for display in ImageShow #5788 [radarhere] From 3b7890c709cb2f8759e19f541d480d1a9f5be5a2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 2 Nov 2021 23:07:32 +1100 Subject: [PATCH 145/633] Python 3.6 Docker jobs have been updated to Python 3.9 --- .github/workflows/test-docker.yml | 4 ++++ docs/installation.rst | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 490af5e4b..46e9e5f2c 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -18,8 +18,12 @@ jobs: alpine, amazon-2-amd64, arch, + centos-7-amd64, + centos-8-amd64, + centos-stream-8-amd64, debian-10-buster-x86, fedora-34-amd64, + ubuntu-18.04-bionic-amd64, ubuntu-20.04-focal-amd64, ] dockerTag: [main] diff --git a/docs/installation.rst b/docs/installation.rst index c19bb2dd1..88aa4eecd 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -454,12 +454,20 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Arch | 3.9 | x86-64 | +----------------------------------+----------------------------+---------------------+ +| CentOS 7 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| CentOS 8 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| CentOS Stream 8 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ | Debian 10 Buster | 3.7 | x86 | +----------------------------------+----------------------------+---------------------+ | Fedora 34 | 3.9 | x86-64 | +----------------------------------+----------------------------+---------------------+ | macOS 10.15 Catalina | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | +----------------------------------+----------------------------+---------------------+ +| Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ | Ubuntu Linux 20.04 LTS (Focal) | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | | +----------------------------+---------------------+ | | 3.8 | arm64v8, ppc64le, | From 73d9209bf98c2f9f3f3c883b7b43b63a7048f98e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 5 Oct 2021 12:18:50 +0300 Subject: [PATCH 146/633] Test Python 3.10.0 final on AppVeyor --- .appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 6cf40130e..72f79248f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,7 +10,7 @@ environment: TEST_OPTIONS: DEPLOY: YES matrix: - - PYTHON: C:/Python39 + - PYTHON: C:/Python310 ARCHITECTURE: x86 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON: C:/Python36-x64 @@ -19,6 +19,7 @@ environment: install: +- '%PYTHON%\%EXECUTABLE% --version' - curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip - 7z x pillow-depends.zip -oc:\ - mv c:\pillow-depends-main c:\pillow-depends From 25979a51e73033aeb44d398b94e280c61641b7f2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 Nov 2021 21:31:12 +1100 Subject: [PATCH 147/633] Added Fedora 35 --- .github/workflows/test-docker.yml | 1 + docs/installation.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 46e9e5f2c..3a2b501b1 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -23,6 +23,7 @@ jobs: centos-stream-8-amd64, debian-10-buster-x86, fedora-34-amd64, + fedora-35-amd64, ubuntu-18.04-bionic-amd64, ubuntu-20.04-focal-amd64, ] diff --git a/docs/installation.rst b/docs/installation.rst index fc5b0e7e9..218a123bc 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -462,6 +462,8 @@ These platforms are built and tested for every change. +----------------------------------+---------------------------------+---------------------+ | Fedora 34 | 3.9 | x86-64 | +----------------------------------+---------------------------------+---------------------+ +| Fedora 35 | 3.10 | x86-64 | ++----------------------------------+---------------------------------+---------------------+ | macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | +----------------------------------+---------------------------------+---------------------+ | Ubuntu Linux 18.04 LTS (Bionic) | 3.6 | x86-64 | From c8391aaa4b665eb8383f487a3c8b93fc61fc1b91 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Wed, 3 Nov 2021 13:03:55 -0400 Subject: [PATCH 148/633] Use the Windows functions to get TCL functions on Cygwin. This is related to linking semantics, so Cygwin should follow the Windows codepath. --- setup.py | 2 +- src/Tk/tkImaging.c | 2 +- src/libImaging/ImPlatform.h | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 75b87ba97..2dcbbdd64 100755 --- a/setup.py +++ b/setup.py @@ -887,7 +887,7 @@ class pil_build_ext(build_ext): else: self._remove_extension("PIL._webp") - tk_libs = ["psapi"] if sys.platform == "win32" else [] + tk_libs = ["psapi"] if (sys.platform == "win32" or sys.platform == "cygwin") else [] self._update_extension("PIL._imagingtk", tk_libs) build_ext.build_extensions(self) diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index 1c6c5f34a..9ae7edff1 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -219,7 +219,7 @@ TkImaging_Init(Tcl_Interp *interp) { #define TKINTER_FINDER "PIL._tkinter_finder" -#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__CYGWIN__) /* * On Windows, we can't load the tkinter module to get the Tcl or Tk symbols, diff --git a/src/libImaging/ImPlatform.h b/src/libImaging/ImPlatform.h index 9a2060edf..f6332f969 100644 --- a/src/libImaging/ImPlatform.h +++ b/src/libImaging/ImPlatform.h @@ -31,11 +31,18 @@ #endif #endif -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) #define WIN32_LEAN_AND_MEAN #include +#ifdef __CYGWIN__ +#undef _WIN64 +#undef _WIN32 +#undef __WIN32__ +#undef WIN32 +#endif + #else /* For System that are not Windows, we'll need to define these. */ From b542da8eb5965042347f3b56933b062a3ee71d27 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Nov 2021 17:16:19 +0000 Subject: [PATCH 149/633] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2dcbbdd64..896ffa074 100755 --- a/setup.py +++ b/setup.py @@ -887,7 +887,9 @@ class pil_build_ext(build_ext): else: self._remove_extension("PIL._webp") - tk_libs = ["psapi"] if (sys.platform == "win32" or sys.platform == "cygwin") else [] + tk_libs = ( + ["psapi"] if (sys.platform == "win32" or sys.platform == "cygwin") else [] + ) self._update_extension("PIL._imagingtk", tk_libs) build_ext.build_extensions(self) From 814680fc427f93882976da9c88a605ef80f22f5e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 4 Nov 2021 10:34:02 +1100 Subject: [PATCH 150/633] Updated harfbuzz to 3.1.0 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 807cbbf27..cafb3ea23 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -278,9 +278,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/3.0.0.zip", - "filename": "harfbuzz-3.0.0.zip", - "dir": "harfbuzz-3.0.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/3.1.0.zip", + "filename": "harfbuzz-3.1.0.zip", + "dir": "harfbuzz-3.1.0", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 29b92391bcf30b59fcc876d168b0b0b3677fc349 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Wed, 3 Nov 2021 21:26:55 -0400 Subject: [PATCH 151/633] Suggestion: use 'var in tuple' instead of chained comparisons. --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 896ffa074..82529e5cb 100755 --- a/setup.py +++ b/setup.py @@ -887,9 +887,7 @@ class pil_build_ext(build_ext): else: self._remove_extension("PIL._webp") - tk_libs = ( - ["psapi"] if (sys.platform == "win32" or sys.platform == "cygwin") else [] - ) + tk_libs = ["psapi"] if sys.platform in ("win32", "cygwin") else [] self._update_extension("PIL._imagingtk", tk_libs) build_ext.build_extensions(self) From 5a41417224ba8f83d4d062360336f1491f7b9498 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Thu, 4 Nov 2021 19:48:53 -0400 Subject: [PATCH 152/633] Skip test_readonly_save on Cygwin. The test seems to require opening a file for reading, mmapping it, then opening that file for writing. Windows doesn't allow this. --- Tests/test_image.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index c185a1cb4..46770ac02 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -193,6 +193,10 @@ class TestImage: assert not im.readonly @pytest.mark.skipif(is_win32(), reason="Test requires opening tempfile twice") + @pytest.mark.skipif( + sys.platform == "cygwin", + reason="Test requires opening an mmaped file for writing", + ) def test_readonly_save(self, tmp_path): temp_file = str(tmp_path / "temp.bmp") shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file) From a3d1e2f85db890c6a1623e3902711863f55cc54e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 5 Nov 2021 19:10:06 +1100 Subject: [PATCH 153/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3d422a52d..cbc50373b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Drop support for soon-EOL Python 3.6 #5768 + [hugovk, nulano, radarhere] + - Fix compilation on 64-bit Termux #5793 [landfillbaby] From 08504ead91a2bc4dd203e1867e6601573b835a4a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Nov 2021 22:09:18 +1100 Subject: [PATCH 154/633] Updated harfbuzz to 3.1.1 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index f55e3eda7..97872cd6a 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -278,9 +278,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/3.1.0.zip", - "filename": "harfbuzz-3.1.0.zip", - "dir": "harfbuzz-3.1.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/3.1.1.zip", + "filename": "harfbuzz-3.1.1.zip", + "dir": "harfbuzz-3.1.1", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 5d3e0d795b47b25a8da3ca1c1dbb569753917a1f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 8 Nov 2021 23:01:51 +0200 Subject: [PATCH 155/633] Revert "Temporarily pin docutils to fix bullets in sphinx_rtd_theme" This reverts commit 9509a73ed9080640d8d6b95f514b77329d123fa1. --- requirements.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5f7f01aae..38011fd39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,6 @@ black check-manifest coverage defusedxml -# Remove docutils once https://github.com/readthedocs/sphinx_rtd_theme/issues/1115 released -docutils==0.16 markdown2 olefile packaging From 6bada2872eb2b7c77d76e1bbde0f4486a6c28121 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 8 Nov 2021 23:34:31 +0200 Subject: [PATCH 156/633] Require sphinx_rtd_theme>=1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 38011fd39..feaa2f718 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,5 +14,5 @@ sphinx>=2.4 sphinx-copybutton sphinx-issues sphinx-removed-in -sphinx-rtd-theme +sphinx-rtd-theme>=1.0 sphinxext-opengraph From fa4ae3dc2cbc14269cd8e798dd48bcf2c4aa9fd3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 9 Nov 2021 18:21:09 +1100 Subject: [PATCH 157/633] Updated macOS tested Pillow versions [ci skip] --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index d271d827a..e94e8d355 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -497,9 +497,9 @@ These platforms have been reported to work at the versions mentioned. | Operating system | | Tested Python | | Latest tested | | Tested | | | | versions | | Pillow version | | processors | +==================================+===========================+==================+==============+ -| macOS 11.0 Big Sur | 3.7, 3.8, 3.9 | 8.3.2 |arm | +| macOS 11.0 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | | +---------------------------+------------------+--------------+ -| | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 | +| | 3.6, 3.7, 3.8, 3.9, 3.10 | 8.4.0 |x86-64 | +----------------------------------+---------------------------+------------------+--------------+ | macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 | | +---------------------------+------------------+ | From 2401c590a29f21c08b236c2d8d938f82cccc6385 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 9 Nov 2021 19:37:34 +1100 Subject: [PATCH 158/633] Removed setuptools install --- .appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 6116c3b44..d525e4cfc 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -33,7 +33,6 @@ install: c:\pillow\winbuild\build\build_dep_all.cmd $host.SetShouldExit(0) - path C:\pillow\winbuild\build\bin;%PATH% -- '%PYTHON%\%EXECUTABLE% -m pip install -U setuptools' build_script: - ps: | From bb6212a332785ab3d07321811d2ab0d7d4677926 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 10 Nov 2021 08:00:58 +1100 Subject: [PATCH 159/633] Added rounding when converting P and PA --- Tests/test_image_convert.py | 16 +++++++++++++++- src/libImaging/Convert.c | 12 ++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 436a417d1..f12516952 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -93,7 +93,7 @@ def test_trns_p(tmp_path): f = str(tmp_path / "temp.png") im_l = im.convert("L") - assert im_l.info["transparency"] == 0 # undone + assert im_l.info["transparency"] == 1 # undone im_l.save(f) im_rgb = im.convert("RGB") @@ -170,6 +170,20 @@ def test_trns_RGB(tmp_path): im_p.save(f) +@pytest.mark.parametrize("convert_mode", ("L", "LA", "I")) +def test_l_macro_rounding(convert_mode): + for mode in ("P", "PA"): + im = Image.new(mode, (1, 1)) + im.palette.getcolor((0, 1, 2)) + + converted_im = im.convert(convert_mode) + px = converted_im.load() + converted_color = px[0, 0] + if convert_mode == "LA": + converted_color = converted_color[0] + assert converted_color == 1 + + def test_gif_with_rgba_palette_to_p(): # See https://github.com/python-pillow/Pillow/issues/2433 with Image.open("Tests/images/hopper.gif") as im: diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 9012cfcd7..517a4dbe3 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1013,7 +1013,7 @@ p2l(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; /* FIXME: precalculate greyscale palette? */ for (x = 0; x < xsize; x++) { - *out++ = L(&palette[in[x] * 4]) / 1000; + *out++ = L24(&palette[in[x] * 4]) >> 16; } } @@ -1022,7 +1022,7 @@ pa2l(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; /* FIXME: precalculate greyscale palette? */ for (x = 0; x < xsize; x++, in += 4) { - *out++ = L(&palette[in[0] * 4]) / 1000; + *out++ = L24(&palette[in[0] * 4]) >> 16; } } @@ -1044,7 +1044,7 @@ p2la(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { /* FIXME: precalculate greyscale palette? */ for (x = 0; x < xsize; x++, out += 4) { const UINT8 *rgba = &palette[*in++ * 4]; - out[0] = out[1] = out[2] = L(rgba) / 1000; + out[0] = out[1] = out[2] = L24(rgba) >> 16; out[3] = rgba[3]; } } @@ -1054,7 +1054,7 @@ pa2la(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; /* FIXME: precalculate greyscale palette? */ for (x = 0; x < xsize; x++, in += 4, out += 4) { - out[0] = out[1] = out[2] = L(&palette[in[0] * 4]) / 1000; + out[0] = out[1] = out[2] = L24(&palette[in[0] * 4]) >> 16; out[3] = in[3]; } } @@ -1063,7 +1063,7 @@ static void p2i(UINT8 *out_, const UINT8 *in, int xsize, const UINT8 *palette) { int x; for (x = 0; x < xsize; x++, out_ += 4) { - INT32 v = L(&palette[in[x] * 4]) / 1000; + INT32 v = L24(&palette[in[x] * 4]) >> 16; memcpy(out_, &v, sizeof(v)); } } @@ -1073,7 +1073,7 @@ pa2i(UINT8 *out_, const UINT8 *in, int xsize, const UINT8 *palette) { int x; INT32 *out = (INT32 *)out_; for (x = 0; x < xsize; x++, in += 4) { - *out++ = L(&palette[in[0] * 4]) / 1000; + *out++ = L24(&palette[in[0] * 4]) >> 16; } } From 18d34b287f88f62fd185619daa3ed624d4801450 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 10 Nov 2021 14:34:20 +0200 Subject: [PATCH 160/633] Add support for pickling TrueType fonts --- Tests/test_pickle.py | 52 +++++++++++++++++++++++++++++++++++-- docs/releasenotes/9.0.0.rst | 15 +++++++++++ src/PIL/ImageFont.py | 7 +++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index a10dcec8c..f87801d7e 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -2,9 +2,12 @@ import pickle import pytest -from PIL import Image +from PIL import Image, ImageDraw, ImageFont -from .helper import skip_unless_feature +from .helper import assert_image_equal, skip_unless_feature + +FONT_SIZE = 20 +FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" def helper_pickle_file(tmp_path, pickle, protocol, test_file, mode): @@ -92,3 +95,48 @@ def test_pickle_tell(): # Assert assert unpickled_image.tell() == 0 + + +def helper_assert_pickled_font_images(font1, font2): + # Arrange + im1 = Image.new(mode="RGBA", size=(300, 100)) + im2 = Image.new(mode="RGBA", size=(300, 100)) + draw1 = ImageDraw.Draw(im1) + draw2 = ImageDraw.Draw(im2) + txt = "Hello World!" + + # Act + draw1.text((10, 10), txt, font=font1) + draw2.text((10, 10), txt, font=font2) + + # Assert + assert_image_equal(im1, im2) + + +@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) +def test_pickle_font_string(protocol): + # Arrange + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) + + # Act: roundtrip + pickled_font = pickle.dumps(font, protocol) + unpickled_font = pickle.loads(pickled_font) + + # Assert + helper_assert_pickled_font_images(font, unpickled_font) + + +@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) +def test_pickle_font_file(tmp_path, protocol): + # Arrange + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) + filename = str(tmp_path / "temp.pkl") + + # Act: roundtrip + with open(filename, "wb") as f: + pickle.dump(font, f, protocol) + with open(filename, "rb") as f: + unpickled_font = pickle.load(f) + + # Assert + helper_assert_pickled_font_images(font, unpickled_font) diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index db3c16ac6..748e54dd7 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -72,6 +72,21 @@ Support has been added for the "title" argument in argument will also now be supported, e.g. ``im.show(title="My Image")`` and ``ImageShow.show(im, title="My Image")``. +Added support for pickling TrueType fonts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +TrueType fonts may now be pickled and unpickled. For example: + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + font = ImageFont.truetype("arial.ttf", size=30) + pickled_font = pickle.dumps(font1, protocol=pickle.HIGHEST_PROTOCOL) + + # Later... + unpickled_font = pickle.loads(pickled_font) + Security ======== diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index a212110a8..805c8fff9 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -196,6 +196,13 @@ class FreeTypeFont: else: load_from_bytes(font) + def __getstate__(self): + return [self.path, self.size, self.index, self.encoding, self.layout_engine] + + def __setstate__(self, state): + path, size, index, encoding, layout_engine = state + self.__init__(path, size, index, encoding, layout_engine) + def _multiline_split(self, text): split_character = "\n" if isinstance(text, str) else b"\n" return text.split(split_character) From 81af72bb89ab0d24761a017ae6843902c6bac27c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 11 Nov 2021 11:27:13 +0200 Subject: [PATCH 161/633] Fix code example Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/releasenotes/9.0.0.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 748e54dd7..5ed065ce1 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -79,10 +79,11 @@ TrueType fonts may now be pickled and unpickled. For example: .. code-block:: python - from PIL import Image, ImageDraw, ImageFont + import pickle + from PIL import ImageFont font = ImageFont.truetype("arial.ttf", size=30) - pickled_font = pickle.dumps(font1, protocol=pickle.HIGHEST_PROTOCOL) + pickled_font = pickle.dumps(font, protocol=pickle.HIGHEST_PROTOCOL) # Later... unpickled_font = pickle.loads(pickled_font) From 3b701dcc16973af85639d0b7cb03d855c1e70436 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 Nov 2021 20:47:46 +1100 Subject: [PATCH 162/633] Only prefer command line tools SDK on macOS over the default --- setup.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 75b87ba97..22f1bc1d7 100755 --- a/setup.py +++ b/setup.py @@ -532,16 +532,24 @@ class pil_build_ext(build_ext): _add_directory(include_dirs, "/usr/X11/include") # SDK install path - sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" - if not os.path.exists(sdk_path): - try: - sdk_path = ( - subprocess.check_output(["xcrun", "--show-sdk-path"]) - .strip() - .decode("latin1") - ) - except Exception: - sdk_path = None + try: + sdk_path = ( + subprocess.check_output(["xcrun", "--show-sdk-path"]) + .strip() + .decode("latin1") + ) + except Exception: + sdk_path = None + if ( + not sdk_path + or sdk_path == "/Applications/Xcode.app/Contents/Developer" + "/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" + ): + commandlinetools_sdk_path = ( + "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" + ) + if os.path.exists(commandlinetools_sdk_path): + sdk_path = commandlinetools_sdk_path if sdk_path: _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) From 2ce7dd5ef60c58577f3dbe2616c7640892184f97 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 Nov 2021 20:51:13 +1100 Subject: [PATCH 163/633] Moved macOS SDK logic into a separate method --- setup.py | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/setup.py b/setup.py index 22f1bc1d7..174d3360a 100755 --- a/setup.py +++ b/setup.py @@ -404,6 +404,27 @@ class pil_build_ext(build_ext): self.extensions.remove(extension) break + def get_macos_sdk_path(self): + try: + sdk_path = ( + subprocess.check_output(["xcrun", "--show-sdk-path"]) + .strip() + .decode("latin1") + ) + except Exception: + sdk_path = None + if ( + not sdk_path + or sdk_path == "/Applications/Xcode.app/Contents/Developer" + "/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" + ): + commandlinetools_sdk_path = ( + "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" + ) + if os.path.exists(commandlinetools_sdk_path): + sdk_path = commandlinetools_sdk_path + return sdk_path + def build_extensions(self): library_dirs = [] @@ -531,25 +552,7 @@ class pil_build_ext(build_ext): _add_directory(library_dirs, "/usr/X11/lib") _add_directory(include_dirs, "/usr/X11/include") - # SDK install path - try: - sdk_path = ( - subprocess.check_output(["xcrun", "--show-sdk-path"]) - .strip() - .decode("latin1") - ) - except Exception: - sdk_path = None - if ( - not sdk_path - or sdk_path == "/Applications/Xcode.app/Contents/Developer" - "/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" - ): - commandlinetools_sdk_path = ( - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" - ) - if os.path.exists(commandlinetools_sdk_path): - sdk_path = commandlinetools_sdk_path + sdk_path = self.get_macos_sdk_path() if sdk_path: _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) From fc90a9285acd910a4ad6547584511546fc0edb5f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 11 Nov 2021 21:53:28 +1100 Subject: [PATCH 164/633] Added support for top right and bottom right orientations --- Tests/images/rgb32rle_bottom_right.tga | Bin 0 -> 129803 bytes Tests/images/rgb32rle_top_right.tga | Bin 0 -> 129803 bytes Tests/test_file_tga.py | 9 +++++++++ src/PIL/TgaImagePlugin.py | 9 +++++++-- 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 Tests/images/rgb32rle_bottom_right.tga create mode 100644 Tests/images/rgb32rle_top_right.tga diff --git a/Tests/images/rgb32rle_bottom_right.tga b/Tests/images/rgb32rle_bottom_right.tga new file mode 100644 index 0000000000000000000000000000000000000000..bd4609e9c1c08cec1d04d745ee7db6adcb25703b GIT binary patch literal 129803 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 Date: Thu, 11 Nov 2021 14:46:48 +0200 Subject: [PATCH 165/633] Move to 'Other Changes' --- docs/releasenotes/9.0.0.rst | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 5ed065ce1..067bda832 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -72,6 +72,17 @@ Support has been added for the "title" argument in argument will also now be supported, e.g. ``im.show(title="My Image")`` and ``ImageShow.show(im, title="My Image")``. +Security +======== + +TODO +^^^^ + +TODO + +Other Changes +============= + Added support for pickling TrueType fonts ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -87,19 +98,3 @@ TrueType fonts may now be pickled and unpickled. For example: # Later... unpickled_font = pickle.loads(pickled_font) - -Security -======== - -TODO -^^^^ - -TODO - -Other Changes -============= - -TODO -^^^^ - -TODO From be4c59d2a9af98efb5d89a592b50799a6b298c3c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 12 Nov 2021 23:08:56 +1100 Subject: [PATCH 166/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index cbc50373b..23b7e1638 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Add support for pickling TrueType fonts #5826 + [hugovk, radarhere] + +- Only prefer command line tools SDK on macOS over default MacOSX SDK #5828 + [radarhere] + - Drop support for soon-EOL Python 3.6 #5768 [hugovk, nulano, radarhere] From ca15c684eaa55755891d4dc70082b1ef51da0ee6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Nov 2021 23:28:29 +1100 Subject: [PATCH 167/633] Only prevent repeated polygon pixels when drawing with transparency --- src/libImaging/Draw.c | 61 +++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 161895dc6..1cd9a95ad 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -444,7 +444,7 @@ draw_horizontal_lines( * Filled polygon draw function using scan line algorithm. */ static inline int -polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline) { +polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, int hasAlpha) { Edge **edge_table; float *xx; int edge_count = 0; @@ -471,6 +471,9 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h ymax = e[i].ymax; } if (e[i].ymin == e[i].ymax) { + if (hasAlpha != 1) { + (*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink); + } continue; } edge_table[edge_count++] = (e + i); @@ -491,7 +494,6 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h } for (; ymin <= ymax; ymin++) { int j = 0; - int x_pos = 0; for (i = 0; i < edge_count; i++) { Edge *current = edge_table[i]; if (ymin >= current->ymin && ymin <= current->ymax) { @@ -504,31 +506,38 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h } } qsort(xx, j, sizeof(float), x_cmp); - for (i = 1; i < j; i += 2) { - int x_end = ROUND_DOWN(xx[i]); - if (x_end < x_pos) { - // Line would be before the current position - continue; - } - draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline); - if (x_end < x_pos) { - // Line would be before the current position - continue; - } - - int x_start = ROUND_UP(xx[i - 1]); - if (x_pos > x_start) { - // Line would be partway through x_pos, so increase the starting point - x_start = x_pos; - if (x_end < x_start) { - // Line would now end before it started + if (hasAlpha == 1) { + int x_pos = 0; + for (i = 1; i < j; i += 2) { + int x_end = ROUND_DOWN(xx[i]); + if (x_end < x_pos) { + // Line would be before the current position continue; } + draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline); + if (x_end < x_pos) { + // Line would be before the current position + continue; + } + + int x_start = ROUND_UP(xx[i - 1]); + if (x_pos > x_start) { + // Line would be partway through x_pos, so increase the starting point + x_start = x_pos; + if (x_end < x_start) { + // Line would now end before it started + continue; + } + } + (*hline)(im, x_start, ymin, x_end, ink); + x_pos = x_end + 1; + } + draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline); + } else { + for (i = 1; i < j; i += 2) { + (*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink); } - (*hline)(im, x_start, ymin, x_end, ink); - x_pos = x_end + 1; } - draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline); } free(xx); @@ -538,17 +547,17 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h static inline int polygon8(Imaging im, int n, Edge *e, int ink, int eofill) { - return polygon_generic(im, n, e, ink, eofill, hline8); + return polygon_generic(im, n, e, ink, eofill, hline8, 0); } static inline int polygon32(Imaging im, int n, Edge *e, int ink, int eofill) { - return polygon_generic(im, n, e, ink, eofill, hline32); + return polygon_generic(im, n, e, ink, eofill, hline32, 0); } static inline int polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) { - return polygon_generic(im, n, e, ink, eofill, hline32rgba); + return polygon_generic(im, n, e, ink, eofill, hline32rgba, 1); } static inline void From 7a93328834c6fc74791aa54be9e4d6851c79ea5f Mon Sep 17 00:00:00 2001 From: Hood Date: Sat, 13 Nov 2021 14:10:54 -0800 Subject: [PATCH 168/633] Fix _get_pushes_fd and _get_pulls_fd method signatures Getters are supposed to have signature "PyObject *(PyObject *self, void *closure)", but the closure argument is often not used. In wasm it causes a trap if a function is declared with one argument and then called with two. --- src/decode.c | 2 +- src/encode.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/decode.c b/src/decode.c index 91bfabf34..cb018a4e7 100644 --- a/src/decode.c +++ b/src/decode.c @@ -235,7 +235,7 @@ _setfd(ImagingDecoderObject *decoder, PyObject *args) { } static PyObject * -_get_pulls_fd(ImagingDecoderObject *decoder) { +_get_pulls_fd(ImagingDecoderObject *decoder, void *closure) { return PyBool_FromLong(decoder->pulls_fd); } diff --git a/src/encode.c b/src/encode.c index 5933e79a5..2ecf9723b 100644 --- a/src/encode.c +++ b/src/encode.c @@ -299,7 +299,7 @@ _setfd(ImagingEncoderObject *encoder, PyObject *args) { } static PyObject * -_get_pushes_fd(ImagingEncoderObject *encoder) { +_get_pushes_fd(ImagingEncoderObject *encoder, void *closure) { return PyBool_FromLong(encoder->pushes_fd); } From d7873e02ab81815f613c5b7e3ebf8662211f052a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 16 Nov 2021 11:12:48 +1100 Subject: [PATCH 169/633] Added test that translucent polygon pixels do not combine --- Tests/images/imagedraw_polygon_translucent.png | Bin 0 -> 385 bytes Tests/test_imagedraw.py | 13 +++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 Tests/images/imagedraw_polygon_translucent.png diff --git a/Tests/images/imagedraw_polygon_translucent.png b/Tests/images/imagedraw_polygon_translucent.png new file mode 100644 index 0000000000000000000000000000000000000000..da8d790a36fe574e3924063d5a443e0c01dfee82 GIT binary patch literal 385 zcmeAS@N?(olHy`uVBq!ia0vp^DImcK+Q8;9vC-7Ii(!pnkk9d1Dn~Qc zBnF*1I!pa%z?z9cK1XJ09kp0qS^{?q^N^QL6 zd9ihCV3*jMr9oGc-%ed5k-b%9YQ*iO8i%VKx}?^aFVQ$q<<_-6Lf>;H0{o5NZ@W-# VPel7{2Vl@Lc)I$ztaD0e0synDs#gF2 literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 6be8fafa1..1b2909bed 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -638,6 +638,19 @@ def test_polygon_1px_high(): assert_image_equal_tofile(im, expected) +def test_polygon_translucent(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im, "RGBA") + + # Act + draw.polygon([(20, 80), (80, 80), (80, 20)], fill=(0, 255, 0, 127)) + + # Assert + expected = "Tests/images/imagedraw_polygon_translucent.png" + assert_image_equal_tofile(im, expected) + + def helper_rectangle(bbox): # Arrange im = Image.new("RGB", (W, H)) From d6e42f330710ddb854ba56eda7a025b4025fab1e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 16 Nov 2021 18:09:12 +1100 Subject: [PATCH 170/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 23b7e1638..fadf98d02 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Only prevent repeated polygon pixels when drawing with transparency #5835 + [radarhere] + - Add support for pickling TrueType fonts #5826 [hugovk, radarhere] From 90a52d3c0d3d8b62b00786a3ad3a0716aae8650e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 16 Nov 2021 22:02:54 +1100 Subject: [PATCH 171/633] Added width argument to polygon --- .../images/imagedraw/triangle_right_width.png | Bin 0 -> 472 bytes .../triangle_right_width_no_fill.png | Bin 0 -> 479 bytes Tests/test_imagedraw.py | 12 ++++++++ src/PIL/ImageDraw.py | 28 ++++++++++++++++-- src/_imaging.c | 5 ++-- src/libImaging/Draw.c | 15 +++++++--- src/libImaging/Imaging.h | 2 +- 7 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 Tests/images/imagedraw/triangle_right_width.png create mode 100644 Tests/images/imagedraw/triangle_right_width_no_fill.png diff --git a/Tests/images/imagedraw/triangle_right_width.png b/Tests/images/imagedraw/triangle_right_width.png new file mode 100644 index 0000000000000000000000000000000000000000..57b73553a6d9b10f1a0b250b86dd5a34bcc1390c GIT binary patch literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^DIm!c{2-gm-DY||UO%t8k?@S}Tw7u_U?+7_-(;WU~mcFT?_r=petJLnlEBRV9b=KsoS5HT*dUjv> z-Iq_(W=-3A`E=Z>nEWO4zjaH-bAXp{F<=Z>)0sV+@|IelF{r5}E)Q{^g?p literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw/triangle_right_width_no_fill.png b/Tests/images/imagedraw/triangle_right_width_no_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..dd65be6be7b451f1a3d7642c0db73a7775b2a9ea GIT binary patch literal 479 zcmeAS@N?(olHy`uVBq!ia0vp^DImK9vS+?OYR$|+?4j(CU(_L6IykumAE5MY*NR^ z^j6Eh?yZ`=AB6X**=_!|W6S(V>#tvrU)%mq+U%U-se7F7?b21hU$u)qTpX4b-?g%8 z-h<1(gw9_6#xno%^t?&u-nVhqdZuUm%X{(GyY~CPVD|8M=I>MISe^N9TI_zc@>)uK z@^_(m>1W><{0zT1Z^|n3nK@qC-h0m%1e+GKt-q1AZzX4B*Igaot50svUB?^id-mk3 zTbFNyoZWA}XY<Ea@%eI+^t~0+BZtkCPO8NDxe~P|Szi;Q$)_r2~bjsFCt|7rw zKWNWdJ-zT&NBl*bMJv4~Y`r(V_8do~cCD(<45ioQ@tm7ePyV_Veki1KlihEx8S3HJ zs^d8)FFD3Jud#5TPlm$`=fCsXEs&Jh1=pX-1T$KdJe=d#Wzp$Pyg Cd*i79 literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 1b2909bed..1423d9cbc 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -945,6 +945,18 @@ def test_triangle_right(): ) +@pytest.mark.parametrize( + "fill, suffix", + ((BLACK, "width"), (None, "width_no_fill")), +) +def test_triangle_right_width(fill, suffix): + img, draw = create_base_image_draw((100, 100)) + draw.polygon([(15, 25), (85, 25), (50, 60)], fill, WHITE, width=5) + assert_image_equal_tofile( + img, os.path.join(IMAGES_PATH, "triangle_right_" + suffix + ".png") + ) + + def test_line_horizontal(): img, draw = create_base_image_draw((20, 20)) draw.line((5, 5, 14, 5), BLACK, 2) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index eeae1782a..610ccd4c7 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -233,13 +233,35 @@ class ImageDraw: if ink is not None: self.draw.draw_points(xy, ink) - def polygon(self, xy, fill=None, outline=None): + def polygon(self, xy, fill=None, outline=None, width=1): """Draw a polygon.""" ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_polygon(xy, fill, 1) - if ink is not None and ink != fill: - self.draw.draw_polygon(xy, ink, 0) + if ink is not None and ink != fill and width != 0: + if width == 1: + self.draw.draw_polygon(xy, ink, 0, width) + else: + # To avoid expanding the polygon outwards, + # use the fill as a mask + mask = Image.new("1", self.im.size) + mask_ink = self._getink(1)[0] + + fill_im = mask.copy() + draw = Draw(fill_im) + draw.draw.draw_polygon(xy, mask_ink, 1) + + ink_im = mask.copy() + draw = Draw(ink_im) + width = width * 2 - 1 + draw.draw.draw_polygon(xy, mask_ink, 0, width) + + mask.paste(ink_im, mask=fill_im) + + im = Image.new(self.mode, self.im.size) + draw = Draw(im) + draw.draw.draw_polygon(xy, ink, 0, width) + self.im.paste(im.im, (0, 0) + im.size, mask.im) def regular_polygon( self, bounding_circle, n_sides, rotation=0, fill=None, outline=None diff --git a/src/_imaging.c b/src/_imaging.c index e2193fec3..aba907f88 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3124,7 +3124,8 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) { PyObject *data; int ink; int fill = 0; - if (!PyArg_ParseTuple(args, "Oi|i", &data, &ink, &fill)) { + int width = 0; + if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) { return NULL; } @@ -3153,7 +3154,7 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) { free(xy); - if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, self->blend) < 0) { + if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, width, self->blend) < 0) { free(ixy); return NULL; } diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 1cd9a95ad..69b804dee 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -742,7 +742,7 @@ ImagingDrawRectangle( } int -ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, int op) { +ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op) { int i, n, x0, y0, x1, y1; DRAW *draw; INT32 ink; @@ -790,10 +790,17 @@ ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, i } else { /* Outline */ - for (i = 0; i < count - 1; i++) { - draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink); + if (width == 1) { + for (i = 0; i < count - 1; i++) { + draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink); + } + draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink); + } else { + for (i = 0; i < count - 1; i++) { + ImagingDrawWideLine(im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink_, width, op); + } + ImagingDrawWideLine(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op); } - draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink); } return 0; diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 6d18dee4e..9b1c1024d 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -487,7 +487,7 @@ ImagingDrawPieslice( extern int ImagingDrawPoint(Imaging im, int x, int y, const void *ink, int op); extern int -ImagingDrawPolygon(Imaging im, int points, int *xy, const void *ink, int fill, int op); +ImagingDrawPolygon(Imaging im, int points, int *xy, const void *ink, int fill, int width, int op); extern int ImagingDrawRectangle( Imaging im, From 7d4a8668b19313e18d7b7fc203202322125dd621 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 18 Nov 2021 22:01:53 +1100 Subject: [PATCH 172/633] Block tile TIFF tags when saving --- Tests/test_file_libtiff.py | 17 +++++++++++++++++ src/PIL/TiffImagePlugin.py | 8 +++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 6ac90f778..d9fa1e4b4 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -920,6 +920,23 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im: assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") + @pytest.mark.parametrize("compression", (None, "jpeg")) + def test_block_tile_tags(self, compression, tmp_path): + im = hopper() + out = str(tmp_path / "temp.tif") + + tags = { + TiffImagePlugin.TILEWIDTH: 256, + TiffImagePlugin.TILELENGTH: 256, + TiffImagePlugin.TILEOFFSETS: 256, + TiffImagePlugin.TILEBYTECOUNTS: 256, + } + im.save(out, exif=tags, compression=compression) + + with Image.open(out) as reloaded: + for tag in tags.keys(): + assert tag not in reloaded.getexif() + def test_old_style_jpeg(self): with Image.open("Tests/images/old-style-jpeg-compression.tif") as im: assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png") diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 29de8a06b..a6a842b1d 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -89,7 +89,10 @@ DATE_TIME = 306 ARTIST = 315 PREDICTOR = 317 COLORMAP = 320 +TILEWIDTH = 322 +TILELENGTH = 323 TILEOFFSETS = 324 +TILEBYTECOUNTS = 325 SUBIFD = 330 EXTRASAMPLES = 338 SAMPLEFORMAT = 339 @@ -1649,6 +1652,7 @@ def _save(im, fp, filename): }.items(): ifd.setdefault(tag, value) + blocklist = [TILEWIDTH, TILELENGTH, TILEOFFSETS, TILEBYTECOUNTS] if libtiff: if "quality" in encoderinfo: quality = encoderinfo["quality"] @@ -1680,7 +1684,7 @@ def _save(im, fp, filename): # BITSPERSAMPLE, etc), passing arrays with a different length will result in # segfaults. Block these tags until we add extra validation. # SUBIFD may also cause a segfault. - blocklist = [ + blocklist += [ REFERENCEBLACKWHITE, SAMPLEFORMAT, STRIPBYTECOUNTS, @@ -1753,6 +1757,8 @@ def _save(im, fp, filename): raise OSError(f"encoder error {s} when writing image file") else: + for tag in blocklist: + del ifd[tag] offset = ifd.save(fp) ImageFile._save( From b744e7933a53124be5992a533203fce1097162c4 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 18 Nov 2021 13:42:41 +0200 Subject: [PATCH 173/633] Use actions/setup-python's pip cache --- .github/workflows/lint.yml | 13 +++---------- .github/workflows/test-windows.yml | 12 ++---------- .github/workflows/test.yml | 16 ++-------------- 3 files changed, 7 insertions(+), 34 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bddeb6150..8234d57dc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,14 +12,6 @@ jobs: steps: - uses: actions/checkout@v2 - - name: pip cache - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: lint-pip-${{ hashFiles('**/setup.py') }} - restore-keys: | - lint-pip- - - name: pre-commit cache uses: actions/cache@v2 with: @@ -31,7 +23,9 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: "3.10" + cache: pip + cache-dependency-path: "setup.py" - name: Build system information run: python3 .github/workflows/system-info.py @@ -45,4 +39,3 @@ jobs: run: tox -e lint env: PRE_COMMIT_COLOR: always - diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 9cd1cbcde..027d39fd1 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -31,22 +31,14 @@ jobs: repository: python-pillow/pillow-depends path: winbuild\depends - - name: Cache pip - uses: actions/cache@v2 - with: - path: ~\AppData\Local\pip\Cache - key: - ${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.architecture }}-${{ hashFiles('**/.github/workflows/test-windows.yml') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.architecture }}- - ${{ runner.os }}-${{ matrix.python-version }}- - # sets env: pythonLocation - name: Set up Python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} + cache: pip + cache-dependency-path: ".github/workflows/test-windows.yml" - name: Print build system information run: python .github/workflows/system-info.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 43ca263a6..8c2a91b2a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,20 +42,8 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(python3 -m pip cache dir)" - - - name: pip cache - uses: actions/cache@v2 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: - ${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.ci/*.sh') }} - restore-keys: | - ${{ matrix.os }}-${{ matrix.python-version }}- + cache: pip + cache-dependency-path: "**/.ci/*.sh" - name: Build system information run: python3 .github/workflows/system-info.py From 71f7c806ac6700d23a70002f6b66d08d071a1c97 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 18 Nov 2021 14:33:57 +0200 Subject: [PATCH 174/633] Simplify cache-dependency-path Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8c2a91b2a..3d18052f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: pip - cache-dependency-path: "**/.ci/*.sh" + cache-dependency-path: ".ci/*.sh" - name: Build system information run: python3 .github/workflows/system-info.py From 9e3263290a7acfdd86fd5b956931bfc1ff385148 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Nov 2021 19:04:17 +1100 Subject: [PATCH 175/633] Updated libjpeg-turbo to 2.1.2 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 97872cd6a..0fd08519e 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -105,9 +105,9 @@ header = [ # dependencies, listed in order of compilation deps = { "libjpeg": { - "url": SF_MIRROR + "/project/libjpeg-turbo/2.1.1/libjpeg-turbo-2.1.1.tar.gz", - "filename": "libjpeg-turbo-2.1.1.tar.gz", - "dir": "libjpeg-turbo-2.1.1", + "url": SF_MIRROR + "/project/libjpeg-turbo/2.1.2/libjpeg-turbo-2.1.2.tar.gz", + "filename": "libjpeg-turbo-2.1.2.tar.gz", + "dir": "libjpeg-turbo-2.1.2", "build": [ cmd_cmake( [ From 83c42fcabd55a378ec2a1b0d59b41a2f01f3f586 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Nov 2021 10:13:10 +1100 Subject: [PATCH 176/633] Do not redeclare class each time when converting to NumPy --- src/PIL/Image.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 67365d79e..3b4e1e720 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -656,6 +656,10 @@ class Image: raise ValueError("Could not save to PNG for display") from e return b.getvalue() + class _ArrayData: + def __init__(self, new): + self.__array_interface__ = new + def __array__(self, dtype=None): # numpy array interface support import numpy as np @@ -672,10 +676,7 @@ class Image: else: new["data"] = self.tobytes() - class ArrayData: - __array_interface__ = new - - return np.array(ArrayData(), dtype) + return np.array(self._ArrayData(new), dtype) def __getstate__(self): return [self.info, self.mode, self.size, self.getpalette(), self.tobytes()] From 838c8efa2515bb2d160175c0d1edbeecb69cb86e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Nov 2021 14:17:42 +1100 Subject: [PATCH 177/633] Corrected file length in header --- Tests/test_file_icns.py | 8 +++++++- src/PIL/IcnsImagePlugin.py | 13 +++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 47de38d06..3afbbeaac 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -1,8 +1,9 @@ import io +import os import pytest -from PIL import IcnsImagePlugin, Image, features +from PIL import IcnsImagePlugin, Image, _binary, features from .helper import assert_image_equal, assert_image_similar_tofile @@ -38,6 +39,11 @@ def test_save(tmp_path): assert reread.size == (1024, 1024) assert reread.format == "ICNS" + file_length = os.path.getsize(temp_file) + with open(temp_file, "rb") as fp: + fp.seek(4) + assert _binary.i32be(fp.read(4)) == file_length + def test_save_append_images(tmp_path): temp_file = str(tmp_path / "temp.icns") diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index d30eaf90f..6412d1cfb 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -337,23 +337,28 @@ def _save(im, fp, filename): entries = [] for type, size in sizes.items(): stream = size_streams[size] - entries.append({"type": type, "size": len(stream), "stream": stream}) + entries.append( + {"type": type, "size": HEADERSIZE + len(stream), "stream": stream} + ) # Header fp.write(MAGIC) - fp.write(struct.pack(">i", sum(entry["size"] for entry in entries))) + file_length = HEADERSIZE # Header + file_length += HEADERSIZE + 8 * len(entries) # TOC + file_length += sum(entry["size"] for entry in entries) + fp.write(struct.pack(">i", file_length)) # TOC fp.write(b"TOC ") fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE)) for entry in entries: fp.write(entry["type"]) - fp.write(struct.pack(">i", HEADERSIZE + entry["size"])) + fp.write(struct.pack(">i", entry["size"])) # Data for entry in entries: fp.write(entry["type"]) - fp.write(struct.pack(">i", HEADERSIZE + entry["size"])) + fp.write(struct.pack(">i", entry["size"])) fp.write(entry["stream"]) if hasattr(fp, "flush"): From 2ad857358121d45f3b6869c897f7585792454209 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Nov 2021 08:50:38 +1100 Subject: [PATCH 178/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fadf98d02..1a4eaec65 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,21 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Added support for top right and bottom right TGA orientations #5829 + [radarhere] + +- Corrected ICNS file length in header #5845 + [radarhere] + +- Block tile TIFF tags when saving #5839 + [radarhere] + +- Added width argument to polygon #5694 + [radarhere] + +- Do not redeclare class each time when converting to NumPy #5844 + [radarhere] + - Only prevent repeated polygon pixels when drawing with transparency #5835 [radarhere] From b70384653e75eef9aeb89c2945117426bf468e49 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Nov 2021 09:48:31 +1100 Subject: [PATCH 179/633] Corrected parameter order --- docs/reference/ImageDraw.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 46e1595c2..4e1c6e094 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -252,8 +252,8 @@ Methods :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or numeric values like ``[x, y, x, y, ...]``. - :param outline: Color to use for the outline. :param fill: Color to use for the fill. + :param outline: Color to use for the outline. .. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None) From db6e75156cdc299faa75122ebb8b19e4f1e84ad0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Nov 2021 09:49:35 +1100 Subject: [PATCH 180/633] Document polygon width parameter --- docs/reference/ImageDraw.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 4e1c6e094..1e34cd7b6 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -242,7 +242,7 @@ Methods numeric values like ``[x, y, x, y, ...]``. :param fill: Color to use for the point. -.. py:method:: ImageDraw.polygon(xy, fill=None, outline=None) +.. py:method:: ImageDraw.polygon(xy, fill=None, outline=None, width=1) Draws a polygon. @@ -254,6 +254,7 @@ Methods numeric values like ``[x, y, x, y, ...]``. :param fill: Color to use for the fill. :param outline: Color to use for the outline. + :param width: The line width, in pixels. .. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None) From 9dad104385c291687dd2118df5bd46646deb4d79 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Nov 2021 09:56:32 +1100 Subject: [PATCH 181/633] Added release notes for #5694 --- docs/releasenotes/9.0.0.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 067bda832..ca8bc8167 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -55,8 +55,10 @@ TODO API Changes =========== -TODO -^^^^ +Added line width parameter to ImageDraw polygon +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +An optional line ``width`` parameter has been added to ``ImageDraw.Draw.polygon``. TODO From e06e18a8efcf06d51f2532b481cfb2dc37dac449 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Nov 2021 10:12:48 +1100 Subject: [PATCH 182/633] Added release notes for #5829 --- docs/releasenotes/9.0.0.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index ca8bc8167..c8c62c436 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -100,3 +100,8 @@ TrueType fonts may now be pickled and unpickled. For example: # Later... unpickled_font = pickle.loads(pickled_font) + +Added support for additional TGA orientations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +TGA images with top right or bottom right orientations are now supported. From 2e9193a485a005f41a355e53b3e637c38511db33 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Nov 2021 08:10:18 +1100 Subject: [PATCH 183/633] Pass SAMPLEFORMAT to libtiff --- Tests/test_file_libtiff.py | 13 ++++++++++++- src/PIL/TiffImagePlugin.py | 17 +++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index d9fa1e4b4..e40a19394 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -9,7 +9,7 @@ from ctypes import c_float import pytest from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features -from PIL.TiffImagePlugin import STRIPOFFSETS, SUBIFD +from PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD from .helper import ( assert_image_equal, @@ -825,6 +825,17 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB") + def test_sampleformat_write(self, tmp_path): + im = Image.new("F", (1, 1)) + out = str(tmp_path / "temp.tif") + TiffImagePlugin.WRITE_LIBTIFF = True + im.save(out) + TiffImagePlugin.WRITE_LIBTIFF = False + + with Image.open(out) as reloaded: + assert reloaded.mode == "F" + assert reloaded.getexif()[SAMPLEFORMAT] == 3 + def test_lzw(self): with Image.open("Tests/images/hopper_lzw.tif") as im: assert im.mode == "RGB" diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a6a842b1d..5df5c4f4c 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1676,8 +1676,6 @@ def _save(im, fp, filename): # optional types for non core tags types = {} - # SAMPLEFORMAT is determined by the image format and should not be copied - # from legacy_ifd. # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library # based on the data in the strip. # The other tags expect arrays with a certain length (fixed or depending on @@ -1686,7 +1684,6 @@ def _save(im, fp, filename): # SUBIFD may also cause a segfault. blocklist += [ REFERENCEBLACKWHITE, - SAMPLEFORMAT, STRIPBYTECOUNTS, STRIPOFFSETS, TRANSFERFUNCTION, @@ -1702,9 +1699,14 @@ def _save(im, fp, filename): legacy_ifd = {} if hasattr(im, "tag"): legacy_ifd = im.tag.to_v2() - for tag, value in itertools.chain( - ifd.items(), getattr(im, "tag_v2", {}).items(), legacy_ifd.items() - ): + + # SAMPLEFORMAT is determined by the image format and should not be copied + # from legacy_ifd. + supplied_tags = {**getattr(im, "tag_v2", {}), **legacy_ifd} + if SAMPLEFORMAT in supplied_tags: + del supplied_tags[SAMPLEFORMAT] + + for tag, value in itertools.chain(ifd.items(), supplied_tags.items()): # Libtiff can only process certain core items without adding # them to the custom dictionary. # Custom items are supported for int, float, unicode, string and byte @@ -1729,6 +1731,9 @@ def _save(im, fp, filename): else: atts[tag] = value + if SAMPLEFORMAT in atts and len(atts[SAMPLEFORMAT]) == 1: + atts[SAMPLEFORMAT] = atts[SAMPLEFORMAT][0] + logger.debug("Converted items: %s" % sorted(atts.items())) # libtiff always expects the bytes in native order. From cea84e6b2d7df6d74f46532b18e40eef1debcc41 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 23 Nov 2021 20:35:35 +1100 Subject: [PATCH 184/633] Improved explanation of fromarray "mode" parameter --- src/PIL/Image.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 3b4e1e720..0fca3fa5c 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2773,7 +2773,7 @@ def fromarray(obj, mode=None): from PIL import Image import numpy as np - im = Image.open('hopper.jpg') + im = Image.open("hopper.jpg") a = np.asarray(im) Then this can be used to convert it to a Pillow image:: @@ -2781,8 +2781,21 @@ def fromarray(obj, mode=None): im = Image.fromarray(a) :param obj: Object with array interface - :param mode: Mode to use (will be determined from type if None) - See: :ref:`concept-modes`. + :param mode: Optional mode to use when reading ``obj``. Will be determined from + type if ``None``. + + This will not be used to convert the data after reading, but will be used to + change how the data is read:: + + from PIL import Image + import numpy as np + a = np.full((1, 1), 300) + im = Image.fromarray(a, mode="L") + im.getpixel((0, 0)) # 44 + im = Image.fromarray(a, mode="RGB") + im.getpixel((0, 0)) # (44, 1, 0) + + See: :ref:`concept-modes` for general information about modes. :returns: An image object. .. versionadded:: 1.1.6 From 3af00edc85266f8be58bdb48f9964ad1743593f9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Nov 2021 23:16:07 +1100 Subject: [PATCH 185/633] Added context managers --- Tests/check_fli_oob.py | 10 +++++----- Tests/check_jp2_overflow.py | 10 +++++----- Tests/test_file_jpeg2k.py | 6 +++--- Tests/test_file_png.py | 4 ++-- Tests/test_image_resize.py | 4 ++-- Tests/test_imagechops.py | 24 ++++++++++++------------ Tests/test_pickle.py | 6 +++--- Tests/test_sgi_crash.py | 6 +++--- 8 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Tests/check_fli_oob.py b/Tests/check_fli_oob.py index 6b63a6826..7b3d4d7ee 100644 --- a/Tests/check_fli_oob.py +++ b/Tests/check_fli_oob.py @@ -61,8 +61,8 @@ repro_copy = ( for path in repro_ss2 + repro_lc + repro_advance + repro_brun + repro_copy: - im = Image.open(path) - try: - im.load() - except Exception as msg: - print(msg) + with Image.open(path) as im: + try: + im.load() + except Exception as msg: + print(msg) diff --git a/Tests/check_jp2_overflow.py b/Tests/check_jp2_overflow.py index f81a360ce..0210505f5 100755 --- a/Tests/check_jp2_overflow.py +++ b/Tests/check_jp2_overflow.py @@ -19,8 +19,8 @@ from PIL import Image repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2") for path in repro: - im = Image.open(path) - try: - im.load() - except Exception as msg: - print(msg) + with Image.open(path) as im: + try: + im.load() + except Exception as msg: + print(msg) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 0b4af4524..2ef262e3e 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -31,9 +31,9 @@ def roundtrip(im, **options): im.save(out, "JPEG2000", **options) test_bytes = out.tell() out.seek(0) - im = Image.open(out) - im.bytes = test_bytes # for testing only - im.load() + with Image.open(out) as im: + im.bytes = test_bytes # for testing only + im.load() return im diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index ffacbbbf4..9a5577bba 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -757,8 +757,8 @@ class TestFilePng: if buffer: mystdout = mystdout.buffer - reloaded = Image.open(mystdout) - assert_image_equal_tofile(reloaded, TEST_PNG_FILE) + with Image.open(mystdout) as reloaded: + assert_image_equal_tofile(reloaded, TEST_PNG_FILE) @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 17490e1a8..1fe278052 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -154,8 +154,8 @@ class TestImagingCoreResize: @pytest.fixture def gradients_image(): - im = Image.open("Tests/images/radial_gradients.png") - im.load() + with Image.open("Tests/images/radial_gradients.png") as im: + im.load() try: yield im finally: diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index a19fbf239..b839a7b14 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -368,11 +368,11 @@ def test_subtract_modulo_no_clip(): def test_soft_light(): # Arrange - im1 = Image.open("Tests/images/hopper.png") - im2 = Image.open("Tests/images/hopper-XYZ.png") + with Image.open("Tests/images/hopper.png") as im1: + with Image.open("Tests/images/hopper-XYZ.png") as im2: - # Act - new = ImageChops.soft_light(im1, im2) + # Act + new = ImageChops.soft_light(im1, im2) # Assert assert new.getpixel((64, 64)) == (163, 54, 32) @@ -381,11 +381,11 @@ def test_soft_light(): def test_hard_light(): # Arrange - im1 = Image.open("Tests/images/hopper.png") - im2 = Image.open("Tests/images/hopper-XYZ.png") + with Image.open("Tests/images/hopper.png") as im1: + with Image.open("Tests/images/hopper-XYZ.png") as im2: - # Act - new = ImageChops.hard_light(im1, im2) + # Act + new = ImageChops.hard_light(im1, im2) # Assert assert new.getpixel((64, 64)) == (144, 50, 27) @@ -394,11 +394,11 @@ def test_hard_light(): def test_overlay(): # Arrange - im1 = Image.open("Tests/images/hopper.png") - im2 = Image.open("Tests/images/hopper-XYZ.png") + with Image.open("Tests/images/hopper.png") as im1: + with Image.open("Tests/images/hopper-XYZ.png") as im2: - # Act - new = ImageChops.overlay(im1, im2) + # Act + new = ImageChops.overlay(im1, im2) # Assert assert new.getpixel((64, 64)) == (159, 50, 27) diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index f87801d7e..5fd045855 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -88,10 +88,10 @@ def test_pickle_la_mode_with_palette(tmp_path): @skip_unless_feature("webp") def test_pickle_tell(): # Arrange - image = Image.open("Tests/images/hopper.webp") + with Image.open("Tests/images/hopper.webp") as image: - # Act: roundtrip - unpickled_image = pickle.loads(pickle.dumps(image)) + # Act: roundtrip + unpickled_image = pickle.loads(pickle.dumps(image)) # Assert assert unpickled_image.tell() == 0 diff --git a/Tests/test_sgi_crash.py b/Tests/test_sgi_crash.py index f9eaf9b19..b5f9d4424 100644 --- a/Tests/test_sgi_crash.py +++ b/Tests/test_sgi_crash.py @@ -21,6 +21,6 @@ from PIL import Image ) def test_crashes(test_file): with open(test_file, "rb") as f: - im = Image.open(f) - with pytest.raises(OSError): - im.load() + with Image.open(f) as im: + with pytest.raises(OSError): + im.load() From 3a302f3e4b941f3336d9a7d8d4c1fbbb40c6b8ff Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Nov 2021 23:33:59 +1100 Subject: [PATCH 186/633] Only add test to PPM --- Tests/test_file_jpeg.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 5bd16e356..15518756c 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -1,6 +1,5 @@ import os import re -import sys from io import BytesIO import pytest @@ -871,33 +870,6 @@ class TestFileJpeg: with Image.open("Tests/images/hopper.jpg") as im: assert im.getxmp() == {} - @pytest.mark.parametrize("buffer", (True, False)) - def test_save_stdout(self, buffer): - old_stdout = sys.stdout - - if buffer: - - class MyStdOut: - buffer = BytesIO() - - mystdout = MyStdOut() - else: - mystdout = BytesIO() - - sys.stdout = mystdout - - with Image.open(TEST_FILE) as im: - im.save(sys.stdout, "JPEG") - im_roundtrip = self.roundtrip(im) - - # Reset stdout - sys.stdout = old_stdout - - if buffer: - mystdout = mystdout.buffer - with Image.open(mystdout) as reloaded: - assert_image_equal(reloaded, im_roundtrip) - @pytest.mark.skipif(not is_win32(), reason="Windows only") @skip_unless_feature("jpg") From 317247fab2425fa4fc0e10d6aebdabb521d4f254 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 26 Nov 2021 00:14:33 +1100 Subject: [PATCH 187/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1a4eaec65..8692ecbc7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Do not prematurely return in ImageFile when saving to stdout #5665 + [infmagic2047, radarhere] + - Added support for top right and bottom right TGA orientations #5829 [radarhere] @@ -14,7 +17,7 @@ Changelog (Pillow) - Block tile TIFF tags when saving #5839 [radarhere] -- Added width argument to polygon #5694 +- Added line width argument to polygon #5694 [radarhere] - Do not redeclare class each time when converting to NumPy #5844 From 1c54a4be45ac6fc7888474d6e339df8c0b747aea Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 27 Nov 2021 14:55:54 +1100 Subject: [PATCH 188/633] Updated harfbuzz to 3.1.2 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 0fd08519e..a58ab95fa 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -278,9 +278,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/3.1.1.zip", - "filename": "harfbuzz-3.1.1.zip", - "dir": "harfbuzz-3.1.1", + "url": "https://github.com/harfbuzz/harfbuzz/archive/3.1.2.zip", + "filename": "harfbuzz-3.1.2.zip", + "dir": "harfbuzz-3.1.2", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From b383a175be70da0df33babea3d480efd9768e937 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Nov 2021 17:49:06 +1100 Subject: [PATCH 189/633] Convert subsequent GIF frames to RGB or RGBA --- .../images/different_transparency_merged.gif | Bin 3046 -> 0 bytes .../images/different_transparency_merged.png | Bin 0 -> 333 bytes Tests/images/dispose_none_load_end_second.gif | Bin 17208 -> 0 bytes Tests/images/dispose_none_load_end_second.png | Bin 0 -> 27507 bytes .../dispose_prev_first_frame_seeked.gif | Bin 1028 -> 0 bytes .../dispose_prev_first_frame_seeked.png | Bin 0 -> 208 bytes .../images/missing_background_first_frame.gif | Bin 950 -> 0 bytes .../images/missing_background_first_frame.png | Bin 0 -> 382 bytes Tests/test_file_gif.py | 20 ++-- src/PIL/GifImagePlugin.py | 89 +++++++++++++++--- src/PIL/PdfImagePlugin.py | 2 +- 11 files changed, 87 insertions(+), 24 deletions(-) delete mode 100644 Tests/images/different_transparency_merged.gif create mode 100644 Tests/images/different_transparency_merged.png delete mode 100644 Tests/images/dispose_none_load_end_second.gif create mode 100644 Tests/images/dispose_none_load_end_second.png delete mode 100644 Tests/images/dispose_prev_first_frame_seeked.gif create mode 100644 Tests/images/dispose_prev_first_frame_seeked.png delete mode 100644 Tests/images/missing_background_first_frame.gif create mode 100644 Tests/images/missing_background_first_frame.png diff --git a/Tests/images/different_transparency_merged.gif b/Tests/images/different_transparency_merged.gif deleted file mode 100644 index 94d0f53e0dd6a4716c934cd321c625cb5ca210c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3046 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 diff --git a/Tests/images/different_transparency_merged.png b/Tests/images/different_transparency_merged.png new file mode 100644 index 0000000000000000000000000000000000000000..3438f62a6f47b24abdce3462d2a7e1e1a47888de GIT binary patch literal 333 zcmeAS@N?(olHy`uVBq!ia0vp^DImUnZ;%s)2>}N-cwp*%i!O1ghNQ#qesDz4vg&O{YBP4{(A4Z it=IiDb|F^fXjWJ@3jsb(w-5~;_8z~8;1q4J&L_m~{?r!N&N?Hj~N2f}tAQmkM zDyg8z?!)IB_xI;t{Nv91apHZR=Xzc*eM3EE71vQv9w?p+Iz2s25)-p}XcZK+EiT^1 z!7e%+j%m*L~{S|CxNtyR*bz{GFdkD9uD zXvlYedU_gzX)!Sox+&Pp$CsAIj>9Fz#m&sm&wc&+^39w2FJDfA0=ly#Bpy6SeDNZ+ z8-s~&j32hXILgk>n#CF(el9=%dYF*V$d{3%q!xjD0{QuDBcmhj-`eBiaPvY!1A>C# zii!#f^T}?K+}uG$#zyR+>{g>zxw%R=@^3s!Nm*Dpnodv8%@q`S0Q`y&FK>TS({vCG z_3G;5xsAE~!~L)99D;)Lv9U^*E}hIaH2!RFR|r)oD$;pz{W@n2r=?}Ro?eY|ymEa# zrbt=&@NkWVC8x8ydwl$;V54ATe?592T0|sIr9j2RB-hSvp>V9QxA&K->T*hoR-K<; z!CXO_k56^=iJje}$UArL3kt@aKQAD#p`l|y7em8!85u{%uMg^L z9(Xl%Stqjvs@oLtOoOndteZSB=fPtVd)`@OxtKaUR$4UcDMzl%H; zX@Ek%Cnm+v!fb8FTf+8vr^#jM3h{N77lkTmoB;s!0yvi#RARTFHx+5pY7tSjqvm4ICNsLNp%?P@s zCj5hkM@_w1PA-8bo5%15Z)R6{0{d5@uB5Qq#C2Lb=io(VjH3?fru z3|h+jq7jUu4x=p<1M&1Yqhf=ITv36auQ z#)}Qx@4vXGToY>_G~QQO=8Gv_W`UEQR~clBI*xbL%|0-%GWsjTKU;be*5>mfeAR%6 zWbWB!GY-+uZ{J4HD{D1BeBTK>bvHU_Zug_x$(aH=%~(3Fq*xyA zyMyNl(tV2`RM;`)rfcT86!Kn}jvzu_no%-K_F%n*_E2MyVFJC33wEiPhMmTQ0Fs6c z)p>mG1+`X@nxlLgJ$+Xw2GgQM5Tq&4O0-FLz*>@htL0#u;H0j6YsJB<{i{sA*FpP6dQ8oLxB=hBmGA)nU`$C^V=IdlX$gv*^jpWcCW-!~D_Ps5M` zx|xJS4~>XX+}{>}Q`V2v5z`g!J-WKfH=M$u6tumudxcNw_6!v1zEREVA9-568@Gv^ zRFd^@+jQCYq%@W;QaH-PLvw?JheO4m~v zzwINL)zK^=t0r;#n0w&cliT*I?S1#EH%4g@DaV6P9md-%?T^) z9xgS__s-GJhkc#?K@l}Z%TX-+2Dj7?soy-fP}hU>I+v%S?Jv!TwQ6ggmS`i z!i^O;rY1^J{^r5~L`2tB_-RY{*P$%wnbVtxznOjQTCa@dDcokce(BMKwM&Aw77X6a zR-1`!b#(wey?y+(iq+&JooCoz&m;#dLG;}**MqOOM5w&%mEbfBR&NCt zmqk_?<}2Rexasqz!M-!(FNfk5FPpVX%N_{vtT?QLK12p-nhEp+ycE*{!11#6}AzcBQ6PdV+ zq6ZtT6{4PHny(u(m5GcGFn8?`Q&dm^uPHDULsww*dNRVBTy*XZQlb8Jopr;dryLg- zvg8n>Tt~>Cz};alh{c2XjZw5q_gBYj@MI#KR*eoLtec6+?)Orp!UpHKSo~HJjT4r^D%WLjC=vmNO z=r+jR{griuFKYws z9<1D(-ZMI{ctuKw@33cF;UeN-$l%J(G&&+qf4+5#{>I28ox=5ymYB^KFU8cFUD$sL zWfkJ%W-z)%o}}7H(-QSqj*DNd>&X`zEZwRM8DU!LjS+u)mI;l%?;7pAR|n!jVEZ7E zQI7{489v=iABOHJ*>;Jq@#!R>2gZ}!R!6bQLt49T!!?)AcZH;0NMxz0W{$FaVD1b8f;P48t923%Y&8@fMsf{y^<1zR!o#`BQxNUGwSDryyV zwNa-b+@KFXuTycF+CY_EEGalTI5g;Ug^36>Gap9M$>i4xWfj;AIaPIQ74bCd0LkPs z=PEFfCuy6;9_KxuiBuG8A>&ACj`@n}2U->gh4RrIFlo0<>v z2w}_WOf*h6DVF*HFUG#jimBYRq2dD+>PBxu>U5L**gce#$Z*4`D2txuR2C7DhH&lw zQY-D^LL$Hz1+|31eY%%;(_r299W^es&IK^le^?^2Qtxw^@W-GLicM*1%e z=c?xWee5wYOVq^QcjyX2C&2`jl=aXSMe^(x+tH8$R@YPAyz_-Y9uxU8SKs!kv~_4v z1ToR8{T@X_9#COa`qLcaxtebUXQ&Nl0WYn970+Az@sxTd1&G~@Xs2Bacg%^^%S1yq zdLx%u;JnAcsNpS~EiE5os3MK<0l!C9bJ#3~Y%u1LF1oja5SWPhI6i81C*+RUsDgk) zBrO>fMm1>Ln3)-!DxVluu~GJ){8KrUOjj1hW>tZElFlQR;67I|P#^+4z{*REEQKI-bXiem#qt_%m;z34e)g9P`6(L+E4dH&)YyB3%J z7~&0C<<~Bn#i4bE65Kh$({7d`EK^nbQVMmtXDh5W{uNDxF&cHPk}dJidXIe?AGbo> z^fjBrA;RW66?GP9F|i$8Vb7Z9%!H!(UYC{VC<cx#e!w z4Ao+g;ZwMfE6Xe@?KS(D^b2u9}wl zX@7#b+mG+gl;iuvcAA4sr^|V%>1`U#tta}aKZB2UQ9ASi>#>!Q$A>?@eEzIKiiA)y zh|2#Fu6(89Tbf{{!vy(#m=9RlOr@=jqFHFE$a>%=mc)#+ZJbYP z8A=)wwn+iDo~uqV;GG{5AAtNb^YGd)$xprEzh2U|5uu67Z15}QdX*^QOJJ7SN`cMY z5>>4(vpndhq(DxN+Ry?hvN4SJ-7D1 zfBU1fv)CWcNXZSOfHRNFNc+D$(Iaet*Dacz)JVPC=Rv>;cco9<*>}ijj<0g!R1Ua% zeLldAB0`hXEQRydvbr*e{B!@u51vI0A_WzTd?>x^zw%F400N%KD9}$4u*-*H*mr;y zPHzu%`t!WakPc&58%OfYK2c8#A_L_Tqhx3--$0uRG)}O9U#!yf$Fe^Fr>Z6?2+T5ugX!eP1&en;w z_&HKMbLi-rJvUu_54roXKEn+Hx|8EBIWJ_jgs_(P7%xJ!^Oz1%(NMEY?xD=*qf8st z*@C0!`-OFr@_4&vcq?_>Y(LpW+*ljGrxZz3BLih{FUNaA3JpmcW#hTxmwO_pwpDLlp+tb?7QMNjXqEiIoYpCd^oZfE(vBk-32&=W4fcG+vcA zf9YIsOK4QyP_~f5YfZ*XEZMZ=TnW;;*1R0_M3U?G?{80V0gH4o)VJ8KP)+0xrL@wt z>j!y_5mHc>bbT_4X;vxsHwL%9`o&@-a$YE%hX(RyP5?n1H2G?-|NCI2+W8 zX&xNZXdi8b4-!!>ZQy7?(PBV_43Z9#v`3=}49r(znJwJ%YLA=N4eJb7-<+=_s;kL+ zRl%pWtg-4(A+QZ*<(V1^4Su;^5-7oAl0*li<;G;U7{lTm#a)TYDQ0Q%-a!tN=uwku zjZ6l_F{|aOL6(4NsZOD6jXVdFJaeL}E+em>)L@24emjP}bV*(q#9>_vq3vDZQYIy{ zK0}BVvm_Im3xs9J#tA0{@^!k@I@Zb&u`Dbclx-UHCD*~_^f9=tpkBw$mx+vk?(Na^OWLt4~N`Irew0vbLblHV`>X3n=-%2BYvrs{1Mtsp?^LOB2$>y`hu=PrlsS{{p8owyaUv9e z?TdnwMJjbDXn2tjZnFXq6pXZ?%acBS5QrN|X2;&>AJ3f>DzH z%|8xdeDd7ADk{K<&V;;uXj?nwp$}v>Q6^!$e*`%E#>-jb=4T)x`eF6oWT{B?zrTO} zn4sKQP5}jpftnJeGaOo(#gcS|^(FOaz`k9?-RW8bT~j7fz{)}T|H(h(>eM}#1SfF5 zlp{B}q+Zygr1NT&|ZZ%8b8gmZTgA0WCV) zHamfPm2m`zn4mSj#mky{lgaQGMPTwEFn{ig)SxF~3dMo@-~73g zW>dgg|4_PfT8bb~YSJpJE>W)_^8fOmqh{vkGTUiP1qPYR=nz#|hAmR&$4pm$Y5TAE zwra!d?X;=OhA2=1J^b$L+6uS@mAZgYM|oCL+yyU86kI^7W+R(JL3y-5(KH!KecHuz znV6m*$88tpZBBaBFxSM&WOdo`On9RPJd@!?v2LF`Bg2Q z8lQq1ankpV^N|@iTbHX;$u)EZa@Rf;cT=mHwa`>Y!%ZDiXEBxyPA`@yD;WTO!AVJ8b*5DzlXXy8a_u>C~M?uoeeF1)@oyAAK(^ZrkM2`npCdR+5X~ ztXesc27C^eAs_m7ksCxWD4ybujRr`Lf^}2l?%*0Di;PEJrQ!ZqHABRagP28>QBUv} zap5I~CJv-cBVpcR%DRt<$vE%M4&)z_Gf$ZQqHUFeSmI-9w|tv~QUnmwPM_eUMpxk4 z%bLq`-yM5zGJ42n54`6*AIiu6d*H`hG`gT^@%RwPqb9RY$kWN+5Y};j*k5zz$->Yz#6h%d$QCgiXV|o^feHQDP zCu2pdBt72_5Vg&djLu0F-rnQq$}Hb3H4)vdxFiWP%9p)9e!%>?773t~x#)bjQ*U@j zwm@=mfdF|pOXAv3@r4ebxhB&b?+Zm;fwa@KOq=PQKnC5pv-0aKgTfgF9pXMSpEE0N zT&W(60W#?QF@p!umpbgJS-iG<2jU*Xs4EW*JrUs|#luX| zQvJ?i@sCmftT~QRsHsB#NeJb_?s83q0~A0@78=q^CIyiToi~1b|EqemvpjMwKh6`6 zmGi{X%(stD@o5wJo`&66JH2<}-x9p>nFssCpbErv0&y*QfQ@F6cPKaIgfoj)O0p%OR{m+GhFH{X&uk_~t24iybOWZV#gX)~|3 zS<%*&1aO3R-1yz`kx0Qf*qiYabpjCBuzFD zxz@JxF4yglZ|}Qr{c>`I7D3HfB%<1`qL_q}%kl3h!2JZUr}(!P9@ym$!ExYY7cPBC z-lH5Z!g;XG&gc!QRvxOWIKGPr8hvfoF)k1WoXp$P2U3?4wxlpTE%*jJES zDCYjIquWilwFJErE5=GpA;>-GKT%DRubZhk%Rd1Bv93>LEOzp+Fe zUmU39hZt{J8jgEWtYn>ugA)l&rFiU;x!c)jF2J85Vv6tHSSgsSoz^gRu{n1bbAO7_hxFszq;3C}Z zBWhnCSnP{}TBv3!2iAy^F&c3g`#tyZTnXp1{IfoxWL-<5zjWY56S!WYrzS;sBmrK3 zTFP2(AANPAK==~N5U~1vhm%vs3(A<~D8*uY>B(SMpir?b@8N}i@t=`W$3G$8KR%6| zYx1Ii@2$PKVov0J3E)bHd7l=OOnFdJB@~-kHlm^cd1L$LR%Ni~=}`VLDv&nmgFqY|=R zq`&~=9|a$Y=yHj->#zXci_90*p1`}6D{j&&_y|3_`%TWTP#(<}Q_jrG2dsnq>3{Q2 zXH%sB-5>%BP~aH1RUmj*gtoeg*+kvM0pB5T8j&=v{pFe@bymyuA9SKPuc%YGz2qZ|h*IWzp7 zXu6~N>-(qY(I~0A5x=A#44NbaxTyU({6_U`wrSAE-A?RyUs-4dLtw`Zo zU)05$UzJs6Dh8y5%>k0Rb1jA)A$@l#vBL`$gUiMQ8_nK|91M^dQ zxh1@PyGPhA(w25Kni5}cI-MalC-&r)v&b25=??DzMKd&-4AE*v!RyS|M=fNXB^?T~ ze8+W#J<97RkA|VZXW5gOSUR=j;7xnc--DNRg%~(M&C>-c{Y~1}-RzEbZx5t@=KOr3 z2W+=q)7+?E=kbV+k<^69R~7pC>LuYfx8_>)BZ=64V#sII;=oAq2{D9{u4AhL_&odd z!&xLlqwHCf$eY1=BwmaT&P)bfblJ$O9IrUvUxc`vplRPBINd{ApR+tw0h`Vdv8v!J zQVhFi^AHzM4SIU+=DKpA>;u-A+XgNJM9|w;( z9{G3wRbuqWr$Ekq!CNf=#3SVdfM%~b?SY1NuUS#MiO1J3tKyG*Rim-PNzCtT*k*}7 zbKHCh;vBW=N_&`as9&LoRbOy%Hg;)(;B@mGb1kV`>tBQyUv!W*`#`nU>0=+m zeI;ABkzv`!pFG;$L0c~~1z!*>$WhchX1!gzK>vk{>|PRgego?ZUI(Fj#kgZZbX5A%#uMhAEksMC4-;H)<6gs(c9yAntHBq;WCvvcT?>+t{zrD5Vda}pLk1{7fw zdVg2m&3?I14)k9Fq6aWuo|vwu*w>@uvur~`2spV z{VZ{n;Vgj=W}7y5rQsRrb1Ffh`+7q9X*X4ZPIgHfJX7Ub@eC3^2f{N|NnYl{ZfkYS zrc4N`1ayUiN>C-#WVCWoY`(>f*=xh~?KUxp(Yzdw9ZwR}GJZWBJG><6)T{9Ge$~u! zpMrW77NgSU=a_~u+mN|$OK^-o>%X|=wrK?+Fwqvy*@}U*DIntuJ1&l9LdZbdw&Au} z+7a1yb++mHpMUs|ah0ZlRVJGa$1#@QQ>%M*tv`1nOE)@dW2WlbVk|)8m=`+z)6JVO zD&VWarm7<(eBwbuE&bBssoKdejamaS_hG}dlPv_EEf;|$NYNvLr9*cX0si0O(kg$S zLgu{FGnsMsbpvoJrwTVOo^%<4CL83*hcS2<<%-!qKF>Z3hT-H=<=fhpNks16(4}O0 z$zlkMBa|yans~9@!+0>mz7^segE*pNViZ`-9?!$f)|KQ|qF5f$wHE3{a5RY!kpSXO9(55J~&NSR|PW3>2A-*iUuMBe@T z=lItz)J_?R2&Lw*)=7l3-BC}GZv#*YO%T(VF3DE%0~9Na5kIDstsc$fH!eC)wZ{&y z{j{knThiD$hJ=Dn{Lz)3Cot z#m4zo_lY8AF;jyID8)~_JyeO@M6>t%R?!g zs&{Er+E&MM#4bNeQE6X4<3HcUOfT`Fs~H8d1izJiebadP$z592|DS*S7G`3YjHF#a zIl6<%obhc}@lje@|J#45I~MT?nlKsup;YfZ+FhNvr;)Dy^!MR*jZBanDC)$(JN#Ye zkHN8GV7r~7Fi0N-iDxysZa*8Jpw)T|nD>a6%I? z-E6wh1m4wwgiO7n2>MD*Dw4(-|M^eXLr*-edB4J2n zqDK6uh~N9vQ^1z-`G5HjOjbp}6Vgo~#+`-6x&VJ1jprttPbLY=eCR^YFkNxS;nYb1 z*+2cy`Y-4H6p`fYBi@M^7mE+GGWllC;B41sNE-pm@FIu~wJ zf@eGQ7n^HxdLH!`=`zL+C>lD#tggB;P&)O=MXQG&z!7TUi(E#fHG$ZvdD87nPirTzPJr~hCuH;v;!zh zmd6V_(}Vy*J{*SZk3EY$&D@l9jV&{yh)5h0=p14{-9S)zw=hzi4b3J}%;;(rh@BWHyGvClRa590!sl9*-O zsx314AoBr#I`jRVpGSfzSb zR%$dgZ*IL&a38ajbWr)RCJ5YRRhhYLy7K5s-fzygfBje*$=4{md*=^XCzMU^jHZ!6 z)Ci354no!K#AQ)LyOBcG*zS`H`)mc!f3duKD3>#wn0>PtA0v%l8)HZpbUkd?YiHNja#R@EM{%|BdZK#Q<9fGm@Tk;0e4t zGjPVGlTDzf6RuipSjfYDl#alW!|f@6sc_V))H*%oDC(J;`VVw6Qs&y|yVnVak)o-2 zny?d4aOG&O7%OiaNQ+6{zR<4*0pmzI_5S?w>62eS76+2aD5-u5aESg44=?Eyp&H`Y z({e?h;28UdIgMsO-q(bZzy+n_w`-wz0)gQR$|0sDJsb+<=wr>bk@R4Uybr$?3|+|} zK>xQEOnWIcSjW9FN9KAF0Kb$EH!56>6qx1+^J6t6ApfX8Kwxz=@p%DDe1k0jTtSOj zJY=fCYfo!5+CTDJTX?f&+v3C?c9e}9>A|RMgzo3zC`(-PhFcoU=Ok5)^M->L-_QN{ zEHemnTO$I{=wOU8zQH1T;L2zw4;lY2wsR&n4Ye2XY`UAwR(b%-;O$y1f~;CcqnrTf zWPVw*$w!zQ)S7J!P%sxSdWH-EvO^a_rjg0jX>fd>kIBp6kL}0bH@DwC^8fP5@ks_W z^HfKcQF!DS<2|!lE=MzQ+t^Wb@`vp3UCNW=!}O(e7#uGpm$9=2>W={)jhBIoAX<+2 zgek$dwO+f4MTQ*4Y8ERNFREGijM-f90$h}EmYjeh$e?C4s6X8{f*Iszuc&Rh!+t))N|2P5^Z2u~h#+Dmfod{hi zrIh>Blmc{q$X2@T`I{SCzx+STK!I~dG63SBfwD-eOCO;1$hrsATC9vQvJ@Gc`YeS3 zU1+6#x}-wg&?8E6{bh5*{4^pg602-l=lMP@>O=R93gt)J6b(LBOzxWagba0_qNG7U zMYE$3o8d%<_oZf_w4_c0|HFT>QBs529RB`0CsE)f)N^N)3!W4M)V0?_$6Z?8sestS zg!#x9mun@SA01F#iOX&CF6b=QuV0VjpT`KjtCN(dMBwZl?80QwrZyM49 z^|7?HOCL1B0U7%9ps)eZFhT$yBUhII;!)?YZ_Sdp*GM7OX#MbOG(F7pj9>oxum6*` zT?+>C<-o!Eg(CTaqot)^lz{p5{O<0PAdmSLU_GpgA6!k`#832G$eSd?M3}^vm+UPr zL~uOE=8T$apPMD{U>oGS1#;XJ0XSiYY$}n85kB+A;FcL&{-#3ddDmTj95vTDpRmRu?RW#03#GLx z$GA0=!Ue%rpA2K-WTE`E^`1K32T``?!$ha1fDfCBBf7@XJc+mh*8lN;EV&f0rbQU< zo^XYOs|9xAw&QtsqQ@-bANi_ohyHm?j&|tY2JREAW^&#Q8X7jdj=)D1Du$Pv2+whA zHLDcPd-b-c_+lf1=#Pciua@W7XYZp8AKZtEgF=`J)KfVQ8-Vyz-UAt?v$|@a#i9G|T51STQHPR0xyB#St-fnj4T{NT zpk@GIsorWln2{8kvIVq~y_zR=*FgpRhV}gSX^QG?siw5>;USx2*_7P9#$OUnq(R0- zB@W2*ccI5d0RQ<1mNrpJIjxN^sZKS0B!;XG_8#S1eK9LlyK@TVu-z{{d?^!_Ph?9U0XwbDIA9vadz|wKfb^j*%Eqce_%={BfA{?;3 zu@C_g-1{;;cQlfjda_J|#}xM1{af}SmCJZeGUtx$hvr}aQiT0o19t?j#Bm4A?0B1( zn=duQ(ea2*(MuZV1@!{#_(5RRFa@PxvHhMu|5%pbg-VmUAOYiSz7N|}(7pjR)NK|C z@`9WT^tqDy@fla(VR7S1Yg2WD(-Q7`fuM<1km$$tHjnDeiLPMVrY9#L1ZT~wICH}+ zgFTP&91KHX?e#>t?}Le)cg#j7u3h<26~5MQI##pDywu+uv^FDM!;wLWB3M(8B+n7Y z^TaOD?`usze_5&#{qO!u*H~N51qD5ia-o^000SlijxRM}4GD+JdB&x>0s=$Hxnwv$ zf?*te!+cs=XZEqn?_g$878mD$jY2-qC-8g^&if?P`TqKtVY5>P7XQ(AltRANloPMG z2NM2Q?*tMx2LMbCfQ;S2QINhEap0GX?V*Ig0IY4NJZ5Q93HtUF-2mJU0sLb0+Q<<>CopG%jc;h&>E zpjCgSNwIc7M*CVrlCeHY*1=nzRkRpocHdPniP||Ps23E^_@PSw2}S&SF7sZyz!)kw zv)E&$|N1|LH@=MXA!!M12?u<_x6eLNJYFTBx#2VjopR%Ry6sgR#Wj(62i;MvLpWS}_00@NAwfgVn|LZ?Hf1n|>Jy_uu#%h0mMsrR0f^DO1gW$N?lABoV- zmk-`|{`hcqbRO0y=<(hfB)*syIJ(_4fLwo>>SIsVOao}bP}0MBC-FF8Fydt~xw3kr z?0A1;w!!(;zxuD|lvs&O1X^n^nV$EdCWbJMp-q`td?zbtQu=5&cd&w*JBa@dlDYm> zqI$)5e9!)-RqX{{;HcJn+5DKxk6$;$Q;c+RDgjzaYu~!*z1;I9K>_P2Y~`Boio?-* z3mVmixso0WuYvz&QDasks(`2s9M2(MYQL^0*D~VJWw6Fa;xh35(fIdZ^W}ThjPH{* zs=sE1470qtGjNn6&KQ0_UoQ$uvwO6O%tG9hn9&V&a3 z62u!O%tnG~#q6`drDIok2bDPm7kY+qX2rA|O2usQ?xW8$&iIe_9{+fbsBN3?rmTPn zvr=QGIdRET=^DSmd^z)?&=hEY9fpwp0l0>ec|C|lH}2PA{0^@NT;KQ*XfJhU)n2_m z5Xb07d3=2JU;S6x^{t=(_&+_c0E*}&Mj`2)p@0DnD&~(6dP#>+?&ZOxFWm@yln&_H z+jR@UrG8yCKwCbE8n{BSF?IRzpo;54aIP?!Y}4-d?YF}A{D1h*yNl8;=0GD5x7erT zvcVz*3R>OIlk?|xaUv*y?o+U7UC&Tp*f;3fNLx`Y-nnk(d#3#;XukeO`x%V$Ag|9h z`;*mAPk#UUKmN}WUPF|vp0q>HH|Ph3Oty!juMomGDRjc_Bk%?wZTJhhO^ARIMmhLz z6ovj2yimw%2BY*{=OypN>EXdKmL3M6i!_9h>4LwKPiXtuP2AJ|T_rC9uDE zrXc<2I&ESY|9W@8pT|yWn2{!`bAhZsJ_fFs9Xw=Ob9U*Areal$U<-5`N@iCghZGm& zAeq^OZ6Bvpa3+S`b8NJLg5)=_{095VKy~x@`Za?NK^cE15NrF@xpqhY4^)8vv;T7+ z6&kQKkjScZ7ZVnA#(yrPA+>`yXTXNLxSE+>5YScp-g(&9ewEf2hLuT&CW81O838{& zE#!rP9bPv#*E>HAmsBj?LG8qlh!i8lKk?EP{joHh=OPd?m4k^)Vn)>Mt3!3H7?D^D z9V1v=CclgWh``{H1N`#FUfcD!$tz%k`QF6YbrwxPnWA3g^U}DeQ}-tM2Hz&!N2ur= z*W8!>=l}T6NeT^bA38h<|J!pBU1Q}th^5Dh#x1b93nK7XJu+Qfcd%8&wH;sf?5jM$ z5|{W$EZpY}*wj;wS{^a6L$4RIVhPl&f352vkHl`2sSBCWRC-FhOq>?_Z7!B0LZn0i z)^%9w`UER4AH&}#|L(v3oC5hL;3KHNS2-G>5pF|nOk$7u!dw68MJ#+VaG%%GE*t9} zfLy;S2Am*&?!?3G_swOL&gMyH2W?eM&yzB&P!|9f=vdY=-uX@FUl)GIa*cnuguv!A zl16u>_ZuwjK!M>WS`C^Zcib z;TZt(k5-CsAm30D3mJ@90f7rCxHayk;62@AxYguM3=55;;Z8?%7`$Gx4Tk1pk1M&W zHFgm03p|iG&Hkctrl<%3!y@Y}sZ0C@#R+F>oFC`1c~!_2Z4AR}DD5!`=w~j3c}iq- z*4vhxyHSkUbP=IvLU21cMuWjo@s!W?3f`?Ofw}MAhBFx*`2Uyx$a;(=ITfSnmq1@$ z0`>8@B`r+M2H)m2B`q-5@V1#rc#72Ez~bKF1)E}_)$ZVY?Pt!I!%OkV{FwvxLQ^q| zutz(c?(T|V5fkSg%Lg|1D}CZ>B6MrhRa);7LmSRZ8}ajNHmm3ZpMWk@v-gy6E+n?y zxU%@$zd%~j?IY0**Q6)64R_X@wc}B|dO6COZo0|nnzOcw45+0%nvb_Cc#BJXgubm> zwpjLwZBW)uYxX(@#9k39+5K?bF=%xp0p zC698q#IJTD8X2jVs6p*je63q8Arg5?{+}H(|E!x<M&e&q#+ z!9Kr0tgL?dZ{ceU4-GrK#&ha6SWNFpyG~1c-BbAcQ37)C)qT$Cs>OYO+M>Re*Tjfp zI=fHR7b0WT1pU2FPj)>Mo>kz9GuwgL8_tpB|KtD2v#kSG<2OET5-WhQoB&(SYpy9i z&K9d3XMN`Xz(NSV*LHXZsF>L*$9(x3*CCvHtDEm?OmrDJFB(WtfJj!Qg430*^BH{O zYI4CFXG30AB`RBO=K{69AfL-}^(Qf_!YA&v`#04N$xgm6Ga%B8nQG^MFEvR#-TZI< zVQCyC5-7eg@F<_ne*szTaH@PUcWt78+3VA!y{4opsyD(eNbfb$VS@#6`RO7&5hSUI zV0^0+o5~cwx8dD}eN{zW>HtkX6em`w0EE`giSViC=Gj~X{^>y4)|{%>XLVp1O1Ay+ z0{} z6k~E3X&SJo<_ZyaTZJt*Q@R$SY~qnE;6HbGziIM#AM%YF zsoOaDsLgGN81gL~FtfkrLV&6+6KCr;e%3@ud48}1tgfRSzz+5uT4Qz}>~)!bw}16j zjge`QbrOvFW(xeE{_N-Z z|3xKeiYmu&P~%_x=Pdtx>_+=o>tqW8W@~fCo4dNLY%)n+d%P}Mc_PSzFU!p&4Ll4& ziJW9^a)?2!0`S+a5s)g4?aUj!T3=VQptv2xWa@G{w9LO|Y+IL;GI87ITDAk$xT`F< zrO{XbG{*y!KaE$KW`g z)?vWF$J;P{e4jDKcy!5atLxL2bkeo z9go7Nuz__Oy%^eKjJyl>f$fIuFawJ?a3_yt4qT@DrqcWOctvwmV|QgN|6lW88zQaK?yzUMd@^Tv zuMyyU?(_s#3VKaR7&7Qxou^QKR4ny3&=N30l>|&2*>&u@=usH?M%YaU3r?m7^Vf9< z2c}-2($P@viWmdqfq@#I88LH0=}QP~{^lk~zILiYQ^K`%uqkS$2AFnX*9O43KQNEk z)#Jy-1}_|5mQ)BhWc%Da;Q!pB zG%k=v3!C#Q|1+PlL28N$3Yjdl4OJf=Xpj8Y{MVD8-#&C(xmArB0WDKROIV*Cs5@wT zETJd^XjzXBwr1x@4e@trdv_j!>{2FU0N02Ky@ah1p2R@4q)u71-MQh_#bNvJ;A{uPEW#OKC<0?c*^zv{h#0I z3~W5)RN$dqjee0V2O0MeVO0dk2?tZLZn6yp(4yEhDictjiOe@`bx6i=B8Bc3`rNMJ z_zP5`I?5Qm;(}3>sP$chVa655GDs>3d*I;tn;@=B!SOPA(ypzqD}c7QNnLsm z|HS~j@aP&3Z-%NRFNhpLhI2a{1uPGx^8Dh8gTpLzi6Ng0zAf8`#rWZfIr%MWj_r7 z)m$oCF7klJfh;#Gxq|2QCOZwJN!(Waw(!`0rOA{k1%-RY$=vGZ0aKB6=UYrRH;aw~ zDyqWD<)@7XtJPLd&^m!ekONIwMPS5lcgeyilQ+doEY>jZWbyQ+a3ZwK*IV(Xu5Q zqHo`>+5-#}oY@8bOV1oTg|aguZ|$zXdGhc6>*WM6oq;S$;`dvnpixqUxD8@$CAH2J z?abtsupwj+QKoBL3IQ-%$Ivi+n@6Fc{hHxsA{K7d@kdm99;7km=&DCSs!dCST zV{J7P`O?OsfN!6o&Xd|Wn4l@C)eTICn}DMF&qz+zf9Buw)wN~a<})F%x87<&gH%t= z02lt=Y=dvJ2KRT5y_T!52!IPPR$|=wule^g|Je!T+Eo%nT2FDMe2^ED`LnroW#p{? zGU=8j_#2!rFkFe@ehLWu2OOq7X`AxI$OV|M@@sXF`dG9;EvJ0LkgE AS^xk5 diff --git a/Tests/images/dispose_none_load_end_second.png b/Tests/images/dispose_none_load_end_second.png new file mode 100644 index 0000000000000000000000000000000000000000..dc01ccbdd0c67bce32fe5dd103bc0c7dab7f03a6 GIT binary patch literal 27507 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 Y4p{dnan(Fx!$P)CJU%&B3QP+%Ez+Tq zs7w_>6|HVV?i-n87wW{9^ONAJ@}0E04IcJt`2J*w*w8Tw;GA>%_6z#_f?#J07q@!_ znUF=eY(HxA#Epc#8lLVS9G`gHNTB~Z;-dWQx()zXU0*g?Eg;Ip`BZ@pZH{eOgpcG4 zr$?bIPh(eJx3KY(+e>2?$-vV}lnQiclZ)bPuwX4%R0hA(5t4zID=0&mlr2wV7l-X_ zIR}sv%L-O42Q;r>_h4Us`N!v*xV!1+%7F3X7ss=dYXv4H%9y2GDBc0nucVPWFjyumAu607*qoM6N<$f?k%Zp8x;= literal 0 HcmV?d00001 diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index fae7e912c..497cc5ed3 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -324,7 +324,7 @@ def test_dispose_none_load_end(): with Image.open("Tests/images/dispose_none_load_end.gif") as img: img.seek(1) - assert_image_equal_tofile(img, "Tests/images/dispose_none_load_end_second.gif") + assert_image_equal_tofile(img, "Tests/images/dispose_none_load_end_second.png") def test_dispose_background(): @@ -340,12 +340,16 @@ def test_dispose_background(): def test_dispose_background_transparency(): with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img: img.seek(2) - px = img.convert("RGBA").load() + px = img.load() assert px[35, 30][3] == 0 def test_transparent_dispose(): - expected_colors = [(2, 1, 2), (0, 1, 0), (2, 1, 2)] + expected_colors = [ + (2, 1, 2), + ((0, 255, 24, 255), (0, 0, 255, 255), (0, 255, 24, 255)), + ((0, 0, 0, 0), (0, 0, 255, 255), (0, 0, 0, 0)), + ] with Image.open("Tests/images/transparent_dispose.gif") as img: for frame in range(3): img.seek(frame) @@ -368,7 +372,7 @@ def test_dispose_previous_first_frame(): with Image.open("Tests/images/dispose_prev_first_frame.gif") as im: im.seek(1) assert_image_equal_tofile( - im, "Tests/images/dispose_prev_first_frame_seeked.gif" + im, "Tests/images/dispose_prev_first_frame_seeked.png" ) @@ -508,7 +512,7 @@ def test_dispose2_background(tmp_path): with Image.open(out) as im: im.seek(1) - assert im.getpixel((0, 0)) == 0 + assert im.getpixel((0, 0)) == (255, 0, 0) def test_transparency_in_second_frame(): @@ -517,9 +521,9 @@ def test_transparency_in_second_frame(): # Seek to the second frame im.seek(im.tell() + 1) - assert im.info["transparency"] == 0 + assert "transparency" not in im.info - assert_image_equal_tofile(im, "Tests/images/different_transparency_merged.gif") + assert_image_equal_tofile(im, "Tests/images/different_transparency_merged.png") def test_no_transparency_in_second_frame(): @@ -926,4 +930,4 @@ def test_missing_background(): # but the disposal method is "Restore to background color" with Image.open("Tests/images/missing_background.gif") as im: im.seek(1) - assert_image_equal_tofile(im, "Tests/images/missing_background_first_frame.gif") + assert_image_equal_tofile(im, "Tests/images/missing_background_first_frame.png") diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 5b4668844..44d15b596 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -124,8 +124,7 @@ class GifImageFile(ImageFile.ImageFile): if not self._seek_check(frame): return if frame < self.__frame: - if frame != 0: - self.im = None + self.im = None self._seek(0) last_frame = self.__frame @@ -165,12 +164,21 @@ class GifImageFile(ImageFile.ImageFile): pass self.__offset = 0 + if self.__frame == 1: + self.pyaccess = None + if "transparency" in self.info: + self.mode = "RGBA" + self.im.putpalettealpha(self.info["transparency"], 0) + self.im = self.im.convert("RGBA", Image.FLOYDSTEINBERG) + + del self.info["transparency"] + else: + self.mode = "RGB" + self.im = self.im.convert("RGB", Image.FLOYDSTEINBERG) if self.dispose: self.im.paste(self.dispose, self.dispose_extent) - from copy import copy - - self.palette = copy(self.global_palette) + palette = None info = {} frame_transparency = None @@ -246,7 +254,7 @@ class GifImageFile(ImageFile.ImageFile): if flags & 128: bits = (flags & 7) + 1 - self.palette = ImagePalette.raw("RGB", self.fp.read(3 << bits)) + palette = ImagePalette.raw("RGB", self.fp.read(3 << bits)) # image data bits = self.fp.read(1)[0] @@ -257,6 +265,15 @@ class GifImageFile(ImageFile.ImageFile): pass # raise OSError, "illegal GIF tag `%x`" % s[0] + frame_palette = palette or self.global_palette + + def _rgb(color): + if frame_palette: + color = tuple(frame_palette.palette[color * 3 : color * 3 + 3]) + else: + color = (color, color, color) + return color + try: if self.disposal_method < 2: # do not dispose or none specified @@ -272,9 +289,13 @@ class GifImageFile(ImageFile.ImageFile): # by convention, attempt to use transparency first color = self.info.get("transparency", frame_transparency) - if color is None: - color = self.info.get("background", 0) - self.dispose = Image.core.fill("P", dispose_size, color) + if color is not None: + dispose_mode = "RGBA" + color = _rgb(color) + (0,) + else: + dispose_mode = "RGB" + color = _rgb(self.info.get("background", 0)) + self.dispose = Image.core.fill(dispose_mode, dispose_size, color) else: # replace with previous contents if self.im: @@ -286,7 +307,7 @@ class GifImageFile(ImageFile.ImageFile): Image._decompression_bomb_check(dispose_size) self.dispose = Image.core.fill( - "P", dispose_size, frame_transparency + "RGBA", dispose_size, _rgb(frame_transparency) + (0,) ) except AttributeError: pass @@ -316,16 +337,54 @@ class GifImageFile(ImageFile.ImageFile): elif k in self.info: del self.info[k] - self.mode = "L" - if self.palette: - self.mode = "P" + if frame == 0: + self.mode = "P" if frame_palette else "L" + + if self.mode == "P" and not palette: + from copy import copy + + palette = copy(self.global_palette) + self.palette = palette + else: + self._frame_palette = frame_palette + self._frame_transparency = frame_transparency def load_prepare(self): - if not self.im and "transparency" in self.info: - self.im = Image.core.fill(self.mode, self.size, self.info["transparency"]) + if self.__frame == 0: + if "transparency" in self.info: + self.im = Image.core.fill( + self.mode, self.size, self.info["transparency"] + ) + else: + self._prev_im = self.im + if self._frame_palette: + self.mode = "P" + self.im = Image.core.fill("P", self.size, self._frame_transparency or 0) + self.im.putpalette(*self._frame_palette.getdata()) + self._frame_palette = None + else: + self.mode = "L" + self.im = None super().load_prepare() + def load_end(self): + if self.__frame == 0: + return + if self._frame_transparency is not None: + self.im.putpalettealpha(self._frame_transparency, 0) + frame_im = self.im.convert("RGBA") + else: + frame_im = self.im.convert("RGB") + frame_im = self._crop(frame_im, self.dispose_extent) + + self.im = self._prev_im + self.mode = self.im.mode + if frame_im.mode == "RGBA": + self.im.paste(frame_im, self.dispose_extent, frame_im) + else: + self.im.paste(frame_im, self.dispose_extent) + def tell(self): return self.__frame diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 49ba077e6..1131c6325 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -135,7 +135,7 @@ def _save(im, fp, filename, save_all=False): procset = "ImageB" # grayscale elif im.mode == "P": filter = "ASCIIHexDecode" - palette = im.im.getpalette("RGB") + palette = im.getpalette() colorspace = [ PdfParser.PdfName("Indexed"), PdfParser.PdfName("DeviceRGB"), From 6337428df1e11b1e02d1757bcc288520e8fd33a8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Nov 2021 14:10:18 +1100 Subject: [PATCH 190/633] Loading transparent pixels in C from subsequent GIF frames is no longer a problem --- src/PIL/GifImagePlugin.py | 10 +++------- src/decode.c | 4 +--- src/libImaging/Gif.h | 3 --- src/libImaging/GifDecode.c | 36 +++++++++++++++--------------------- 4 files changed, 19 insertions(+), 34 deletions(-) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 44d15b596..55b09abec 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -313,18 +313,14 @@ class GifImageFile(ImageFile.ImageFile): pass if interlace is not None: - transparency = -1 - if frame_transparency is not None: - if frame == 0: - self.info["transparency"] = frame_transparency - else: - transparency = frame_transparency + if frame == 0 and frame_transparency is not None: + self.info["transparency"] = frame_transparency self.tile = [ ( "gif", (x0, y0, x1, y1), self.__offset, - (bits, interlace, transparency), + (bits, interlace), ) ] else: diff --git a/src/decode.c b/src/decode.c index cb018a4e7..e236264cd 100644 --- a/src/decode.c +++ b/src/decode.c @@ -433,8 +433,7 @@ PyImaging_GifDecoderNew(PyObject *self, PyObject *args) { char *mode; int bits = 8; int interlace = 0; - int transparency = -1; - if (!PyArg_ParseTuple(args, "s|iii", &mode, &bits, &interlace, &transparency)) { + if (!PyArg_ParseTuple(args, "s|ii", &mode, &bits, &interlace)) { return NULL; } @@ -452,7 +451,6 @@ PyImaging_GifDecoderNew(PyObject *self, PyObject *args) { ((GIFDECODERSTATE *)decoder->state.context)->bits = bits; ((GIFDECODERSTATE *)decoder->state.context)->interlace = interlace; - ((GIFDECODERSTATE *)decoder->state.context)->transparency = transparency; return (PyObject *)decoder; } diff --git a/src/libImaging/Gif.h b/src/libImaging/Gif.h index 91132e2e6..4029bbfe5 100644 --- a/src/libImaging/Gif.h +++ b/src/libImaging/Gif.h @@ -30,9 +30,6 @@ typedef struct { */ int interlace; - /* The transparent palette index, or -1 for no transparency. */ - int transparency; - /* PRIVATE CONTEXT (set by decoder) */ /* Interlace parameters */ diff --git a/src/libImaging/GifDecode.c b/src/libImaging/GifDecode.c index 301f604b9..30478e24a 100644 --- a/src/libImaging/GifDecode.c +++ b/src/libImaging/GifDecode.c @@ -248,33 +248,27 @@ ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t /* To squeeze some extra pixels out of this loop, we test for some common cases and handle them separately. */ - /* If we have transparency, we need to use the regular loop. */ - if (context->transparency == -1) { - if (i == 1) { - if (state->x < state->xsize - 1) { - /* Single pixel, not at the end of the line. */ - *out++ = p[0]; - state->x++; - continue; - } - } else if (state->x + i <= state->xsize) { - /* This string fits into current line. */ - memcpy(out, p, i); - out += i; - state->x += i; - if (state->x == state->xsize) { - NEWLINE(state, context); - } + if (i == 1) { + if (state->x < state->xsize - 1) { + /* Single pixel, not at the end of the line. */ + *out++ = p[0]; + state->x++; continue; } + } else if (state->x + i <= state->xsize) { + /* This string fits into current line. */ + memcpy(out, p, i); + out += i; + state->x += i; + if (state->x == state->xsize) { + NEWLINE(state, context); + } + continue; } /* No shortcut, copy pixel by pixel */ for (c = 0; c < i; c++) { - if (p[c] != context->transparency) { - *out = p[c]; - } - out++; + *out++ = p[c]; if (++state->x >= state->xsize) { NEWLINE(state, context); } From 1fb1bec0e80084aa675d7a8fd3e1d0525fe1cc06 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Nov 2021 15:12:24 +1100 Subject: [PATCH 191/633] Added test --- Tests/images/dispose_bgnd_rgba.gif | Bin 0 -> 1450 bytes Tests/test_file_gif.py | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 Tests/images/dispose_bgnd_rgba.gif diff --git a/Tests/images/dispose_bgnd_rgba.gif b/Tests/images/dispose_bgnd_rgba.gif new file mode 100644 index 0000000000000000000000000000000000000000..c18a0ba71f1abf98b1bbd212dc55577255c79bc6 GIT binary patch literal 1450 zcmYk*dpOg390%~62GBX5oue1U_A1tz!r@Ujn+no+zYwJuV)B_ znV}s`@cU;ti8XCXi#g1>=mUS5bxv5ueW_~PlHd-a6}i5XX~oO#{Y2t>s0Xqp_$OoI zUrl2Sp4Q0PGE?GNhyap6iO8e74*8TFNYDb5$x{a z=Wi3h%Et{HU;8CKaN&d>dF3_oL(Xxt9tNv>LduQ6^wwUCbTX_enL$lg7CB&ZOwUixc()pKms!LXjT;ss!Ml$w|sSzW#S{PEqr z3!tCnzPq>fYI_aeE$}*yXUBP9L>V#P;l}_PYBWSJosG{CW*2Zt6SWi82L^3n@sCNyYbIK&yK8-9us$r?T19uJX!JbXToDqi8^WWZx zCB>0#@RC?_GEsv9kZzwz?2FfhP?CEq%fFTp5bl$No}LemV*OTetgvWBM0!}awL zD+mDTWHus?tGaC^I@#B3c} zkK%+UwZM3P!oz%;o~EAHPQ1gYk}&uTsynD!VZqI5Y3IdWR;G9i%G#|iG&n3oqJsp& zkN6~QIagCBKY}Sm(G_hN7am}(XcG}fD{_R#66J#miOAUKE9S8V1>x505S$%c9+zZ@ zi&npSHZL*jUU^5mj-*mcVQH@uF0Xi?_ZK&-3&SJM-kF2a2@Ve@`YtG-#-`a;71Pod z#Koo8ovVT??xEEJ_XSAzQsA#}*#8b@0Omgj(f>u$`VB#?x%OLJ#CAjuW|vgxtvg3ml3tIHsek?H7Be zr%ahi?;NfhAl;|-)pm_&FdvTchxU(75~m3Lsk6w@<;U~BgZjL=T%WezSevdgjx#R{ z99ITeUZ`Y;6d&kjjt@=h#rimrd$qqUL!M*gN(X%x8cE>vSFGfJ;=J+=IbYR*!;5E9;%?d-y;vN=!cWOYPXYS#y(fUpXR7Vnet!Lr^(OCP4SWuxu* z6S(fAzr$ia9~*=Z`uzg5eM9pDLlYPD zOdG21V}vq(F;h($ literal 0 HcmV?d00001 diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 497cc5ed3..13eb06897 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -163,6 +163,32 @@ def test_roundtrip_save_all(tmp_path): assert reread.n_frames == 5 +@pytest.mark.parametrize( + "path, mode", + ( + ("Tests/images/dispose_bgnd.gif", "RGB"), + # Hexeditted copy of dispose_bgnd to add transparency + ("Tests/images/dispose_bgnd_rgba.gif", "RGBA"), + ), +) +def test_loading_multiple_palettes(path, mode): + with Image.open(path) as im: + assert im.mode == "P" + first_frame_colors = im.palette.colors.keys() + original_color = im.convert("RGB").load()[0, 0] + + im.seek(1) + assert im.mode == mode + if mode == "RGBA": + im = im.convert("RGB") + + # Check a color only from the old palette + assert im.load()[0, 0] == original_color + + # Check a color from the new palette + assert im.load()[24, 24] not in first_frame_colors + + def test_headers_saving_for_animated_gifs(tmp_path): important_headers = ["background", "version", "duration", "loop"] # Multiframe image From eeb685b6c43ed2961d45583651d3b35cc2bdd4a8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 29 Nov 2021 18:50:22 +1100 Subject: [PATCH 192/633] GIFs change mode for later frames [ci skip] --- docs/handbook/image-file-formats.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 1a5dee0b4..dc1b89075 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -91,8 +91,9 @@ Pillow reads GIF87a and GIF89a versions of the GIF file format. The library writes run-length encoded files in GIF87a by default, unless GIF89a features are used or GIF89a is already in use. -Note that GIF files are always read as grayscale (``L``) -or palette mode (``P``) images. +GIF files are initially read as grayscale (``L``) or palette mode (``P``) +images, but seeking to later frames in an image will change the mode to either +``RGB`` or ``RGBA``, depending on whether the first frame had transparency. The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: From 22ad04743cad4861e3a7e7196bb9e70c3793e696 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 30 Nov 2021 22:14:34 +0200 Subject: [PATCH 193/633] GHA: Add workflow_dispatch to enable manually triggering builds --- .github/workflows/cifuzz.yml | 2 ++ .github/workflows/lint.yml | 2 +- .github/workflows/release-drafter.yml | 1 + .github/workflows/test-docker.yml | 2 +- .github/workflows/test-mingw.yml | 2 +- .github/workflows/test-valgrind.yml | 1 + .github/workflows/test-windows.yml | 2 +- .github/workflows/test.yml | 2 +- 8 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 9fe8f774f..7e2fbf28f 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -1,4 +1,5 @@ name: CIFuzz + on: push: paths: @@ -8,6 +9,7 @@ on: paths: - "**.c" - "**.h" + workflow_dispatch: jobs: Fuzzing: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8234d57dc..533ce8cbd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,6 +1,6 @@ name: Lint -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 58bb2cddf..ad66117b1 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -5,6 +5,7 @@ on: # branches to consider in the event; optional, defaults to all branches: - main + workflow_dispatch: jobs: update_release_draft: diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 3a2b501b1..57396fddc 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -1,6 +1,6 @@ name: Test Docker -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index bd2de2381..051cfc483 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -1,6 +1,6 @@ name: Test MinGW -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index 03c8022f3..4a8966ca8 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -11,6 +11,7 @@ on: paths: - "**.c" - "**.h" + workflow_dispatch: jobs: build: diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 027d39fd1..3a0a16416 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -1,6 +1,6 @@ name: Test Windows -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3d18052f8..9bbc426e2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,6 @@ name: Test -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: From 915d014136d769f07654109fab383796ddccf4ed Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 1 Dec 2021 12:42:11 +0200 Subject: [PATCH 194/633] GHA: Change condition to allow manually building wheels in forks --- .github/workflows/test-windows.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 3a0a16416..c768838eb 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -132,7 +132,7 @@ jobs: - name: Build Pillow run: | $FLAGS="" - if ('${{ github.event_name }}' -eq 'push') { $FLAGS="--disable-imagequant" } + if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS="--disable-imagequant" } & winbuild\build\build_pillow.cmd $FLAGS install & $env:pythonLocation\python.exe selftest.py --installed shell: pwsh @@ -175,14 +175,14 @@ jobs: - name: Build wheel id: wheel - if: "github.event_name == 'push'" + if: "github.event_name != 'pull_request'" run: | for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ::set-output name=dist::dist-%%a winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel shell: cmd - uses: actions/upload-artifact@v2 - if: "github.event_name == 'push'" + if: "github.event_name != 'pull_request'" with: name: ${{ steps.wheel.outputs.dist }} path: dist\*.whl From af924a1f96ae25eca765e7e272d6ac3ac8b0177f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Hendrik=20M=C3=BCller?= <44469195+kolibril13@users.noreply.github.com> Date: Wed, 1 Dec 2021 17:21:21 +0100 Subject: [PATCH 195/633] added pathlib tutorial --- docs/handbook/tutorial.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index cdac0ae2d..abb1605ac 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -406,6 +406,38 @@ Using the ImageSequence Iterator class # ...do something to frame... +Batch processing with Pathlib +----------------------------- + +This example uses PIL togehter with pathlib, in order to reduce the quality of all png images in a folder + +:: + + from PIL import Image + from pathlib import Path + + + def compressImg(filepath, verbose=False): + file = filepath.stem + img = Image.open(filepath) + if img.mode != 'RGB': + img = img.convert('RGB') + img.save(file + ".jpg", + "JPEG", + optimize=True, + quality=80) + return + + + base_directory = Path.cwd() + + for path in base_directory.iterdir(): + if path.suffix == ".png": + print(path) + compressImg(path) + + + PostScript printing ------------------- From 91fb7fc0673fb1cd9c98fb7c6113bdcf6e33e19a Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Wed, 1 Dec 2021 13:58:13 -0500 Subject: [PATCH 196/633] Add hugovk suggestion --- .github/workflows/tidelift.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tidelift.yml b/.github/workflows/tidelift.yml index 328305ce4..626b23823 100644 --- a/.github/workflows/tidelift.yml +++ b/.github/workflows/tidelift.yml @@ -1,5 +1,14 @@ name: Tidelift Align -on: [push] +on: + schedule: + - cron: "30 2 * * *" # daily at 02:30 UTC + push: + paths: + - ".github/workflows/tidelift.yml" + pull_request: + paths: + - ".github/workflows/tidelift.yml" + workflow_dispatch: jobs: build: From c373ac188c87adefc59c0d1469e78b6d16d98218 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Wed, 1 Dec 2021 14:13:07 -0500 Subject: [PATCH 197/633] Remove org and proj because deprecated, I think --- .github/workflows/tidelift.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/tidelift.yml b/.github/workflows/tidelift.yml index 626b23823..ec3c04287 100644 --- a/.github/workflows/tidelift.yml +++ b/.github/workflows/tidelift.yml @@ -22,5 +22,3 @@ jobs: uses: tidelift/scan-action@main env: TIDELIFT_API_KEY: ${{ secrets.TIDELIFT_API_KEY }} - TIDELIFT_ORGANIZATION: ${{ secrets.TIDELIFT_ORGANIZATION }} - TIDELIFT_PROJECT: ${{ secrets.TIDELIFT_PROJECT }} From ae4d5d913c5a5bcc3a91789c80fd2fb9654d649e Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Wed, 1 Dec 2021 14:15:40 -0500 Subject: [PATCH 198/633] Not deprecated, re-add org and proj --- .github/workflows/tidelift.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tidelift.yml b/.github/workflows/tidelift.yml index ec3c04287..bf75da55c 100644 --- a/.github/workflows/tidelift.yml +++ b/.github/workflows/tidelift.yml @@ -22,3 +22,5 @@ jobs: uses: tidelift/scan-action@main env: TIDELIFT_API_KEY: ${{ secrets.TIDELIFT_API_KEY }} + TIDELIFT_ORGANIZATION: team/aclark4life + TIDELIFT_PROJECT: pillow From d23afb3e267ed65de29854091110f2fa99e54bb0 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Wed, 1 Dec 2021 17:05:02 -0500 Subject: [PATCH 199/633] Update .github/workflows/tidelift.yml Co-authored-by: Hugo van Kemenade --- .github/workflows/tidelift.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tidelift.yml b/.github/workflows/tidelift.yml index bf75da55c..c2b8b3bda 100644 --- a/.github/workflows/tidelift.yml +++ b/.github/workflows/tidelift.yml @@ -19,7 +19,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 - name: Scan - uses: tidelift/scan-action@main + uses: tidelift/alignment-action@main env: TIDELIFT_API_KEY: ${{ secrets.TIDELIFT_API_KEY }} TIDELIFT_ORGANIZATION: team/aclark4life From 52ac725ae0110cfcd74a2d090a85cdbda22e21b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Hendrik=20M=C3=BCller?= <44469195+kolibril13@users.noreply.github.com> Date: Wed, 1 Dec 2021 23:06:52 +0100 Subject: [PATCH 200/633] Apply suggestions from code review Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/handbook/tutorial.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index abb1605ac..b5d7407da 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -409,7 +409,7 @@ Using the ImageSequence Iterator class Batch processing with Pathlib ----------------------------- -This example uses PIL togehter with pathlib, in order to reduce the quality of all png images in a folder +This example uses PIL together with pathlib, in order to reduce the quality of all png images in a folder :: @@ -419,13 +419,13 @@ This example uses PIL togehter with pathlib, in order to reduce the quality of a def compressImg(filepath, verbose=False): file = filepath.stem - img = Image.open(filepath) - if img.mode != 'RGB': - img = img.convert('RGB') - img.save(file + ".jpg", - "JPEG", - optimize=True, - quality=80) + with Image.open(filepath) as img: + if img.mode != 'RGB': + img = img.convert('RGB') + img.save(file + ".jpg", + "JPEG", + optimize=True, + quality=80) return From 21351c8982cb24a2d2cf40994054e7680f283dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Hendrik=20M=C3=BCller?= <44469195+kolibril13@users.noreply.github.com> Date: Wed, 1 Dec 2021 23:23:17 +0100 Subject: [PATCH 201/633] Update docs/handbook/tutorial.rst Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/handbook/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index b5d7407da..26625eacc 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -417,7 +417,7 @@ This example uses PIL together with pathlib, in order to reduce the quality of a from pathlib import Path - def compressImg(filepath, verbose=False): + def compressImg(filepath): file = filepath.stem with Image.open(filepath) as img: if img.mode != 'RGB': From 1d82b6b74eed4b1b537ed2b4664443f6842c2cfb Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Wed, 1 Dec 2021 19:14:50 -0500 Subject: [PATCH 202/633] Add pipenv files for tidelift cli --- Pipfile | 28 +++ Pipfile.lock | 619 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 647 insertions(+) create mode 100644 Pipfile create mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile new file mode 100644 index 000000000..e745453ee --- /dev/null +++ b/Pipfile @@ -0,0 +1,28 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +black = "*" +check-manifest = "*" +coverage = "*" +defusedxml = "*" +docutils = "==0.16" +markdown2 = "*" +olefile = "*" +pyroma = "*" +pytest = "*" +pytest-cov = "*" +pytest-timeout = "*" +sphinx-copybutton = "*" +sphinx-issues = "*" +sphinx-removed-in = "*" +sphinx-rtd-theme = "*" +sphinxext-opengraph = "*" +Sphinx = ">=2.4" + +[dev-packages] + +[requires] +python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 000000000..68663d861 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,619 @@ +{ + "_meta": { + "hash": { + "sha256": "fcc7808b9f6f4e421a1efd94192435cf0658a19e47d6a17fb170a9440b3c2960" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "alabaster": { + "hashes": [ + "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", + "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" + ], + "version": "==0.7.12" + }, + "attrs": { + "hashes": [ + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" + }, + "babel": { + "hashes": [ + "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9", + "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.9.1" + }, + "black": { + "hashes": [ + "sha256:802c6c30b637b28645b7fde282ed2569c0cd777dbe493a41b6a03c1d903f99ac", + "sha256:a042adbb18b3262faad5aff4e834ff186bb893f95ba3a8013f09de1e5569def2" + ], + "index": "pypi", + "version": "==21.11b1" + }, + "build": { + "hashes": [ + "sha256:1aaadcd69338252ade4f7ec1265e1a19184bf916d84c9b7df095f423948cb89f", + "sha256:21b7ebbd1b22499c4dac536abc7606696ea4d909fd755e00f09f3c0f2c05e3c8" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "certifi": { + "hashes": [ + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + ], + "version": "==2021.10.8" + }, + "charset-normalizer": { + "hashes": [ + "sha256:735e240d9a8506778cd7a453d97e817e536bb1fc29f4f6961ce297b9c7a917b0", + "sha256:83fcdeb225499d6344c8f7f34684c2981270beacc32ede2e669e94f7fa544405" + ], + "markers": "python_version >= '3'", + "version": "==2.0.8" + }, + "check-manifest": { + "hashes": [ + "sha256:365c94d65de4c927d9d8b505371d08ee19f9f369c86b9ac3db97c2754c827c95", + "sha256:56dadd260a9c7d550b159796d2894b6d0bcc176a94cbc426d9bb93e5e48d12ce" + ], + "index": "pypi", + "version": "==0.47" + }, + "click": { + "hashes": [ + "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", + "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.3" + }, + "coverage": { + "hashes": [ + "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0", + "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd", + "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884", + "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48", + "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76", + "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0", + "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64", + "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685", + "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47", + "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d", + "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840", + "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f", + "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971", + "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c", + "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a", + "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de", + "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17", + "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4", + "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521", + "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57", + "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b", + "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282", + "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644", + "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475", + "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d", + "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da", + "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953", + "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2", + "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e", + "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c", + "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc", + "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64", + "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74", + "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617", + "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3", + "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d", + "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa", + "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739", + "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8", + "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8", + "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781", + "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58", + "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9", + "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c", + "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd", + "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e", + "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49" + ], + "index": "pypi", + "version": "==6.2" + }, + "defusedxml": { + "hashes": [ + "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" + ], + "index": "pypi", + "version": "==0.7.1" + }, + "docutils": { + "hashes": [ + "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", + "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" + ], + "index": "pypi", + "version": "==0.16" + }, + "idna": { + "hashes": [ + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + ], + "markers": "python_version >= '3'", + "version": "==3.3" + }, + "imagesize": { + "hashes": [ + "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c", + "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.3.0" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "jinja2": { + "hashes": [ + "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8", + "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.3" + }, + "markdown2": { + "hashes": [ + "sha256:93a29c5cad95b00d82908590548479638251c9188df449561f614d2f9756e15a", + "sha256:ce9265cf179c4e07934e7b6a4b03f3edb7891e66e6d0f7017755f6064bbbe13f" + ], + "index": "pypi", + "version": "==2.4.1" + }, + "markupsafe": { + "hashes": [ + "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", + "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", + "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", + "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194", + "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", + "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", + "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", + "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", + "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", + "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", + "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", + "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a", + "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", + "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", + "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", + "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", + "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", + "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", + "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", + "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047", + "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", + "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", + "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b", + "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", + "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", + "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", + "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1", + "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", + "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", + "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", + "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee", + "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f", + "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", + "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", + "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", + "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", + "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", + "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", + "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86", + "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6", + "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", + "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", + "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", + "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", + "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e", + "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", + "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f", + "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", + "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", + "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", + "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", + "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", + "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", + "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", + "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a", + "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207", + "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", + "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", + "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd", + "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", + "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", + "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9", + "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", + "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", + "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", + "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", + "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" + ], + "markers": "python_version >= '3.6'", + "version": "==2.0.1" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "olefile": { + "hashes": [ + "sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964" + ], + "index": "pypi", + "version": "==0.46" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "pathspec": { + "hashes": [ + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + ], + "version": "==0.9.0" + }, + "pep517": { + "hashes": [ + "sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0", + "sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161" + ], + "version": "==0.12.0" + }, + "platformdirs": { + "hashes": [ + "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2", + "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d" + ], + "markers": "python_version >= '3.6'", + "version": "==2.4.0" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" + }, + "pygments": { + "hashes": [ + "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380", + "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6" + ], + "markers": "python_version >= '3.5'", + "version": "==2.10.0" + }, + "pyparsing": { + "hashes": [ + "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4", + "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.6" + }, + "pyroma": { + "hashes": [ + "sha256:0fba67322913026091590e68e0d9e0d4fbd6420fcf34d315b2ad6985ab104d65", + "sha256:f8c181e0d5d292f11791afc18f7d0218a83c85cf64d6f8fb1571ce9d29a24e4a" + ], + "index": "pypi", + "version": "==3.2" + }, + "pytest": { + "hashes": [ + "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", + "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" + ], + "index": "pypi", + "version": "==6.2.5" + }, + "pytest-cov": { + "hashes": [ + "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6", + "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470" + ], + "index": "pypi", + "version": "==3.0.0" + }, + "pytest-timeout": { + "hashes": [ + "sha256:329bdea323d3e5bea4737070dd85a0d1021dbecb2da5342dc25284fdb929dff0", + "sha256:a5ec4eceddb8ea726911848593d668594107e797621e97f93a1d1dbc6fbb9080" + ], + "index": "pypi", + "version": "==2.0.1" + }, + "pytz": { + "hashes": [ + "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c", + "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326" + ], + "version": "==2021.3" + }, + "regex": { + "hashes": [ + "sha256:0416f7399e918c4b0e074a0f66e5191077ee2ca32a0f99d4c187a62beb47aa05", + "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f", + "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc", + "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4", + "sha256:0f594b96fe2e0821d026365f72ac7b4f0b487487fb3d4aaf10dd9d97d88a9737", + "sha256:139a23d1f5d30db2cc6c7fd9c6d6497872a672db22c4ae1910be22d4f4b2068a", + "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4", + "sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8", + "sha256:2409b5c9cef7054dde93a9803156b411b677affc84fca69e908b1cb2c540025d", + "sha256:2fee3ed82a011184807d2127f1733b4f6b2ff6ec7151d83ef3477f3b96a13d03", + "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f", + "sha256:3598893bde43091ee5ca0a6ad20f08a0435e93a69255eeb5f81b85e81e329264", + "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a", + "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef", + "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f", + "sha256:42b50fa6666b0d50c30a990527127334d6b96dd969011e843e726a64011485da", + "sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc", + "sha256:473e67837f786404570eae33c3b64a4b9635ae9f00145250851a1292f484c063", + "sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50", + "sha256:50a7ddf3d131dc5633dccdb51417e2d1910d25cbcf842115a3a5893509140a3a", + "sha256:529801a0d58809b60b3531ee804d3e3be4b412c94b5d267daa3de7fadef00f49", + "sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d", + "sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d", + "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733", + "sha256:563d5f9354e15e048465061509403f68424fef37d5add3064038c2511c8f5e00", + "sha256:5d408a642a5484b9b4d11dea15a489ea0928c7e410c7525cd892f4d04f2f617b", + "sha256:61600a7ca4bcf78a96a68a27c2ae9389763b5b94b63943d5158f2a377e09d29a", + "sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36", + "sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345", + "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0", + "sha256:6e1d2cc79e8dae442b3fa4a26c5794428b98f81389af90623ffcc650ce9f6732", + "sha256:74cbeac0451f27d4f50e6e8a8f3a52ca074b5e2da9f7b505c4201a57a8ed6286", + "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12", + "sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646", + "sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667", + "sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244", + "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29", + "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec", + "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf", + "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4", + "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449", + "sha256:96fc32c16ea6d60d3ca7f63397bff5c75c5a562f7db6dec7d412f7c4d2e78ec0", + "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a", + "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d", + "sha256:a955b747d620a50408b7fdf948e04359d6e762ff8a85f5775d907ceced715129", + "sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb", + "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e", + "sha256:b9ed0b1e5e0759d6b7f8e2f143894b2a7f3edd313f38cf44e1e15d360e11749b", + "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83", + "sha256:ca49e1ab99593438b204e00f3970e7a5f70d045267051dfa6b5f4304fcfa1dbf", + "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e", + "sha256:cd410a1cbb2d297c67d8521759ab2ee3f1d66206d2e4328502a487589a2cb21b", + "sha256:ce298e3d0c65bd03fa65ffcc6db0e2b578e8f626d468db64fdf8457731052942", + "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a", + "sha256:d5fd67df77bab0d3f4ea1d7afca9ef15c2ee35dfb348c7b57ffb9782a6e4db6e", + "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94", + "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc", + "sha256:dc07f021ee80510f3cd3af2cad5b6a3b3a10b057521d9e6aaeb621730d320c5a", + "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e", + "sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965", + "sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0", + "sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36", + "sha256:e6096b0688e6e14af6a1b10eaad86b4ff17935c49aa774eac7c95a57a4e8c296", + "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec", + "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23", + "sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7", + "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe", + "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6", + "sha256:f5be7805e53dafe94d295399cfbe5227f39995a997f4fd8539bf3cbdc8f47ca8", + "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b", + "sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb", + "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b", + "sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30", + "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e" + ], + "version": "==2021.11.10" + }, + "requests": { + "hashes": [ + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.26.0" + }, + "setuptools": { + "hashes": [ + "sha256:b4c634615a0cf5b02cf83c7bedffc8da0ca439f00e79452699454da6fbd4153d", + "sha256:feb5ff19b354cde9efd2344ef6d5e79880ce4be643037641b49508bbb850d060" + ], + "markers": "python_version >= '3.6'", + "version": "==59.4.0" + }, + "snowballstemmer": { + "hashes": [ + "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", + "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" + ], + "version": "==2.2.0" + }, + "sphinx": { + "hashes": [ + "sha256:048dac56039a5713f47a554589dc98a442b39226a2b9ed7f82797fcb2fe9253f", + "sha256:32a5b3e9a1b176cc25ed048557d4d3d01af635e6b76c5bc7a43b0a34447fbd45" + ], + "index": "pypi", + "version": "==4.3.1" + }, + "sphinx-copybutton": { + "hashes": [ + "sha256:4340d33c169dac6dd82dce2c83333412aa786a42dd01a81a8decac3b130dc8b0", + "sha256:8daed13a87afd5013c3a9af3575cc4d5bec052075ccd3db243f895c07a689386" + ], + "index": "pypi", + "version": "==0.4.0" + }, + "sphinx-issues": { + "hashes": [ + "sha256:1208e1869742b7800a45b3c47ab987b87b2ad2024cbc36e0106e8bba3549dd22", + "sha256:845294736c7ac4c09c706f13431f709e1164037cbb00f6bf623ae16eccf509f3" + ], + "index": "pypi", + "version": "==1.2.0" + }, + "sphinx-removed-in": { + "hashes": [ + "sha256:0588239cb534cd97b1d3900d0444311c119e45296a9f73f1ea81ea81a2cd3db1", + "sha256:434b1a1c28b12021de4c84fc2fbb7168fdf1f56a410a42bf8d0af938d0855ad1" + ], + "index": "pypi", + "version": "==0.2.1" + }, + "sphinx-rtd-theme": { + "hashes": [ + "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8", + "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c" + ], + "index": "pypi", + "version": "==1.0.0" + }, + "sphinxcontrib-applehelp": { + "hashes": [ + "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", + "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.2" + }, + "sphinxcontrib-devhelp": { + "hashes": [ + "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", + "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.2" + }, + "sphinxcontrib-htmlhelp": { + "hashes": [ + "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", + "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" + ], + "markers": "python_version >= '3.6'", + "version": "==2.0.0" + }, + "sphinxcontrib-jsmath": { + "hashes": [ + "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", + "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.1" + }, + "sphinxcontrib-qthelp": { + "hashes": [ + "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", + "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.3" + }, + "sphinxcontrib-serializinghtml": { + "hashes": [ + "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", + "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" + ], + "markers": "python_version >= '3.5'", + "version": "==1.1.5" + }, + "sphinxext-opengraph": { + "hashes": [ + "sha256:50d493dc3bbcec324211ca56f9c04218fac1c4311cfc33a9cc75cb0bed63bd13", + "sha256:6efb05fe077e00b7a487e03b24bf56268bae92d42ef3f9da89616b60bc4a702c" + ], + "index": "pypi", + "version": "==0.5.0" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.2" + }, + "tomli": { + "hashes": [ + "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee", + "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.2" + }, + "typing-extensions": { + "hashes": [ + "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", + "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.1" + }, + "urllib3": { + "hashes": [ + "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", + "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.7" + } + }, + "develop": {} +} From cab7d8a8ab77f3dce3a536902ea7307e7e115b06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Hendrik=20M=C3=BCller?= <44469195+kolibril13@users.noreply.github.com> Date: Thu, 2 Dec 2021 16:20:13 +0100 Subject: [PATCH 203/633] Apply suggestions from code review Co-authored-by: Hugo van Kemenade --- docs/handbook/tutorial.rst | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 26625eacc..e4848fa62 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -406,26 +406,23 @@ Using the ImageSequence Iterator class # ...do something to frame... -Batch processing with Pathlib +Batch processing with pathlib ----------------------------- -This example uses PIL together with pathlib, in order to reduce the quality of all png images in a folder +This example uses Pillow together with pathlib, in order to reduce the quality of all PNG images in a folder: :: - from PIL import Image from pathlib import Path + from PIL import Image - def compressImg(filepath): + def compress_image(filepath): file = filepath.stem with Image.open(filepath) as img: - if img.mode != 'RGB': - img = img.convert('RGB') - img.save(file + ".jpg", - "JPEG", - optimize=True, - quality=80) + if img.mode != "RGB": + img = img.convert("RGB") + img.save(file + ".jpg", "JPEG", optimize=True, quality=80) return @@ -434,7 +431,7 @@ This example uses PIL together with pathlib, in order to reduce the quality of a for path in base_directory.iterdir(): if path.suffix == ".png": print(path) - compressImg(path) + compress_image(path) From 360b1693d4ade685392390aa2b9e9512f15993ec Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 2 Dec 2021 11:34:53 -0500 Subject: [PATCH 204/633] Add packaging remove docutils --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index e745453ee..a6153e1db 100644 --- a/Pipfile +++ b/Pipfile @@ -8,7 +8,7 @@ black = "*" check-manifest = "*" coverage = "*" defusedxml = "*" -docutils = "==0.16" +packaging = "*" markdown2 = "*" olefile = "*" pyroma = "*" From 789fcb8b05ceb329f7d0b0902aacdfcf3a099bd3 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 3 Dec 2021 07:26:39 -0500 Subject: [PATCH 205/633] Remove sphinx for testing tidelift alignment --- Pipfile | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Pipfile b/Pipfile index a6153e1db..1e611a63c 100644 --- a/Pipfile +++ b/Pipfile @@ -15,12 +15,6 @@ pyroma = "*" pytest = "*" pytest-cov = "*" pytest-timeout = "*" -sphinx-copybutton = "*" -sphinx-issues = "*" -sphinx-removed-in = "*" -sphinx-rtd-theme = "*" -sphinxext-opengraph = "*" -Sphinx = ">=2.4" [dev-packages] From 675c5e1a9cb2c6d7bfaf341a93ade9bac4e46c3b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 4 Dec 2021 07:50:08 +1100 Subject: [PATCH 206/633] Apply black formatting to code examples --- docs/handbook/image-file-formats.rst | 11 ++++++--- docs/handbook/tutorial.rst | 22 ++++++++++------- .../writing-your-own-file-decoder.rst | 12 ++++++---- docs/reference/ImageDraw.rst | 13 ++++++---- docs/reference/PixelAccess.rst | 13 +++++----- docs/reference/PyAccess.rst | 13 +++++----- docs/reference/c_extension_debugging.rst | 1 + docs/reference/open_files.rst | 24 +++++++++---------- 8 files changed, 64 insertions(+), 45 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 1a5dee0b4..692d43531 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -75,9 +75,9 @@ method with the following parameters to affect how Ghostscript renders the EPS relative position of the bounding box is maintained:: im = Image.open(...) - im.size #(100,100) + im.size # (100,100) im.load(scale=2) - im.size #(200,200) + im.size # (200,200) **transparency** If true, generates an RGBA image with a transparent background, instead of @@ -760,7 +760,7 @@ The :py:meth:`~PIL.Image.open` method sets the following attributes: A convenience method, :py:meth:`~PIL.SpiderImagePlugin.SpiderImageFile.convert2byte`, is provided for converting floating point data to byte data (mode ``L``):: - im = Image.open('image001.spi').convert2byte() + im = Image.open("image001.spi").convert2byte() Writing files in SPIDER format ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1201,6 +1201,7 @@ dpi. To load it at another resolution: .. code-block:: python from PIL import Image + with Image.open("drawing.wmf") as im: im.load(dpi=144) @@ -1212,15 +1213,19 @@ To add other read or write support, use from PIL import Image from PIL import WmfImagePlugin + class WmfHandler: def open(self, im): ... + def load(self, im): ... return image + def save(self, im, fp, filename): ... + wmf_handler = WmfHandler() WmfImagePlugin.register_handler(wmf_handler) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index cdac0ae2d..727eb7327 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -176,12 +176,13 @@ Rolling an image xsize, ysize = image.size delta = delta % xsize - if delta == 0: return image + if delta == 0: + return image part1 = image.crop((0, 0, delta, ysize)) part2 = image.crop((delta, 0, xsize, ysize)) - image.paste(part1, (xsize-delta, 0, xsize, ysize)) - image.paste(part2, (0, 0, xsize-delta, ysize)) + image.paste(part1, (xsize - delta, 0, xsize, ysize)) + image.paste(part2, (0, 0, xsize - delta, ysize)) return image @@ -264,6 +265,7 @@ Converting between modes :: from PIL import Image + with Image.open("hopper.ppm") as im: im = im.convert("L") @@ -382,14 +384,14 @@ Reading sequences from PIL import Image with Image.open("animation.gif") as im: - im.seek(1) # skip to the second frame + im.seek(1) # skip to the second frame try: while 1: - im.seek(im.tell()+1) + im.seek(im.tell() + 1) # do something to im except EOFError: - pass # end of sequence + pass # end of sequence As seen in this example, you’ll get an :py:exc:`EOFError` exception when the sequence ends. @@ -422,9 +424,9 @@ Drawing PostScript with Image.open("hopper.ppm") as im: title = "hopper" - box = (1*72, 2*72, 7*72, 10*72) # in points + box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points - ps = PSDraw.PSDraw() # default is sys.stdout or sys.stdout.buffer + ps = PSDraw.PSDraw() # default is sys.stdout or sys.stdout.buffer ps.begin_document(title) # draw the image (75 dpi) @@ -433,7 +435,7 @@ Drawing PostScript # draw title ps.setfont("HelveticaNarrow-Bold", 36) - ps.text((3*72, 4*72), title) + ps.text((3 * 72, 4 * 72), title) ps.end_document() @@ -462,6 +464,7 @@ Reading from an open file :: from PIL import Image + with open("hopper.ppm", "rb") as fp: im = Image.open(fp) @@ -475,6 +478,7 @@ Reading from binary data from PIL import Image import io + im = Image.open(io.BytesIO(buffer)) Note that the library rewinds the file (using ``seek(0)``) before reading the diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index 5f600a667..697da58be 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -87,10 +87,13 @@ true color. Image.register_open(SpamImageFile.format, SpamImageFile, _accept) - Image.register_extensions(SpamImageFile.format, [ - ".spam", - ".spa", # DOS version - ]) + Image.register_extensions( + SpamImageFile.format, + [ + ".spam", + ".spa", # DOS version + ], + ) The format handler must always set the @@ -111,6 +114,7 @@ Once the plugin has been imported, it can be used: from PIL import Image import SpamImagePlugin + with Image.open("hopper.spam") as im: pass diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 1e34cd7b6..b95d8d591 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -81,11 +81,12 @@ Example: Draw Partial Opacity Text .. code-block:: python from PIL import Image, ImageDraw, ImageFont + # get an image with Image.open("Pillow/Tests/images/hopper.png").convert("RGBA") as base: # make a blank image for the text, initialized to transparent text color - txt = Image.new("RGBA", base.size, (255,255,255,0)) + txt = Image.new("RGBA", base.size, (255, 255, 255, 0)) # get a font fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 40) @@ -93,9 +94,9 @@ Example: Draw Partial Opacity Text d = ImageDraw.Draw(txt) # draw text, half opacity - d.text((10,10), "Hello", font=fnt, fill=(255,255,255,128)) + d.text((10, 10), "Hello", font=fnt, fill=(255, 255, 255, 128)) # draw text, full opacity - d.text((10,60), "World", font=fnt, fill=(255,255,255,255)) + d.text((10, 60), "World", font=fnt, fill=(255, 255, 255, 255)) out = Image.alpha_composite(base, txt) @@ -117,7 +118,7 @@ Example: Draw Multiline Text d = ImageDraw.Draw(out) # draw multiline text - d.multiline_text((10,10), "Hello\nWorld", font=fnt, fill=(0, 0, 0)) + d.multiline_text((10, 10), "Hello\nWorld", font=fnt, fill=(0, 0, 0)) out.show() @@ -557,7 +558,9 @@ Methods .. code-block:: python - hello = draw.textlength("HelloW", font) - draw.textlength("W", font) # adjusted for kerning + hello = draw.textlength("HelloW", font) - draw.textlength( + "W", font + ) # adjusted for kerning world = draw.textlength("World", font) hello_world = hello + world # adjusted for kerning assert hello_world == draw.textlength("HelloWorld", font) # True diff --git a/docs/reference/PixelAccess.rst b/docs/reference/PixelAccess.rst index f28e58f86..173a0bcc0 100644 --- a/docs/reference/PixelAccess.rst +++ b/docs/reference/PixelAccess.rst @@ -17,11 +17,12 @@ changes it. .. code-block:: python from PIL import Image - with Image.open('hopper.jpg') as im: + + with Image.open("hopper.jpg") as im: px = im.load() - print (px[4,4]) - px[4,4] = (0,0,0) - print (px[4,4]) + print(px[4, 4]) + px[4, 4] = (0, 0, 0) + print(px[4, 4]) Results in the following:: @@ -32,8 +33,8 @@ Access using negative indexes is also possible. .. code-block:: python - px[-1,-1] = (0,0,0) - print (px[-1,-1]) + px[-1, -1] = (0, 0, 0) + print(px[-1, -1]) diff --git a/docs/reference/PyAccess.rst b/docs/reference/PyAccess.rst index 486c9fc21..e77944d20 100644 --- a/docs/reference/PyAccess.rst +++ b/docs/reference/PyAccess.rst @@ -18,11 +18,12 @@ The following script loads an image, accesses one pixel from it, then changes it .. code-block:: python from PIL import Image - with Image.open('hopper.jpg') as im: + + with Image.open("hopper.jpg") as im: px = im.load() - print (px[4,4]) - px[4,4] = (0,0,0) - print (px[4,4]) + print(px[4, 4]) + px[4, 4] = (0, 0, 0) + print(px[4, 4]) Results in the following:: @@ -33,8 +34,8 @@ Access using negative indexes is also possible. .. code-block:: python - px[-1,-1] = (0,0,0) - print (px[-1,-1]) + px[-1, -1] = (0, 0, 0) + print(px[-1, -1]) diff --git a/docs/reference/c_extension_debugging.rst b/docs/reference/c_extension_debugging.rst index 66175ea0c..2ba95b8a6 100644 --- a/docs/reference/c_extension_debugging.rst +++ b/docs/reference/c_extension_debugging.rst @@ -63,6 +63,7 @@ Take your test image, and make a really simple harness. :: from PIL import Image + with Image.open(path) as im: im.load() diff --git a/docs/reference/open_files.rst b/docs/reference/open_files.rst index f66184ba3..6bfd50588 100644 --- a/docs/reference/open_files.rst +++ b/docs/reference/open_files.rst @@ -14,17 +14,17 @@ The following are all equivalent:: import io import pathlib - with Image.open('test.jpg') as im: + with Image.open("test.jpg") as im: ... - with Image.open(pathlib.Path('test.jpg')) as im2: + with Image.open(pathlib.Path("test.jpg")) as im2: ... - with open('test.jpg', 'rb') as f: + with open("test.jpg", "rb") as f: im3 = Image.open(f) ... - with open('test.jpg', 'rb') as f: + with open("test.jpg", "rb") as f: im4 = Image.open(io.BytesIO(f.read())) ... @@ -65,10 +65,10 @@ Image Lifecycle .. code-block:: python - with Image.open("test.jpg") as img: - img.load() - assert img.fp is None - img.save("test.png") + with Image.open("test.jpg") as img: + img.load() + assert img.fp is None + img.save("test.png") The lifecycle of a single-frame image is relatively simple. The file must @@ -90,13 +90,13 @@ Complications * After a file has been closed, operations that require file access will fail:: - with open('test.jpg', 'rb') as f: + with open("test.jpg", "rb") as f: im5 = Image.open(f) - im5.load() # FAILS, closed file + im5.load() # FAILS, closed file - with Image.open('test.jpg') as im6: + with Image.open("test.jpg") as im6: pass - im6.load() # FAILS, closed file + im6.load() # FAILS, closed file Proposed File Handling From 85a27c145dc115dea3b20a5dd8a9f6290c563564 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 4 Dec 2021 07:41:18 +1100 Subject: [PATCH 207/633] Python variables are one word --- docs/handbook/writing-your-own-file-decoder.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index 697da58be..f69da9a94 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -167,16 +167,16 @@ TIFF, and many others. To use the raw decoder with the image = Image.frombytes( mode, size, data, "raw", - raw mode, stride, orientation + raw_mode, stride, orientation ) When used in a tile descriptor, the parameter field should look like:: - (raw mode, stride, orientation) + (raw_mode, stride, orientation) The fields are used as follows: -**raw mode** +**raw_mode** The pixel layout used in the file, and is used to properly convert data to PIL’s internal layout. For a summary of the available formats, see the table below. From ca8fc4872f8924a847ccd4c985db77a6fb7ca416 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 4 Dec 2021 08:26:18 -0500 Subject: [PATCH 208/633] Update lock file --- Pipfile.lock | 619 --------------------------------------------------- 1 file changed, 619 deletions(-) delete mode 100644 Pipfile.lock diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 68663d861..000000000 --- a/Pipfile.lock +++ /dev/null @@ -1,619 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "fcc7808b9f6f4e421a1efd94192435cf0658a19e47d6a17fb170a9440b3c2960" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.9" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "alabaster": { - "hashes": [ - "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", - "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" - ], - "version": "==0.7.12" - }, - "attrs": { - "hashes": [ - "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", - "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.2.0" - }, - "babel": { - "hashes": [ - "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9", - "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.9.1" - }, - "black": { - "hashes": [ - "sha256:802c6c30b637b28645b7fde282ed2569c0cd777dbe493a41b6a03c1d903f99ac", - "sha256:a042adbb18b3262faad5aff4e834ff186bb893f95ba3a8013f09de1e5569def2" - ], - "index": "pypi", - "version": "==21.11b1" - }, - "build": { - "hashes": [ - "sha256:1aaadcd69338252ade4f7ec1265e1a19184bf916d84c9b7df095f423948cb89f", - "sha256:21b7ebbd1b22499c4dac536abc7606696ea4d909fd755e00f09f3c0f2c05e3c8" - ], - "markers": "python_version >= '3.6'", - "version": "==0.7.0" - }, - "certifi": { - "hashes": [ - "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", - "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" - ], - "version": "==2021.10.8" - }, - "charset-normalizer": { - "hashes": [ - "sha256:735e240d9a8506778cd7a453d97e817e536bb1fc29f4f6961ce297b9c7a917b0", - "sha256:83fcdeb225499d6344c8f7f34684c2981270beacc32ede2e669e94f7fa544405" - ], - "markers": "python_version >= '3'", - "version": "==2.0.8" - }, - "check-manifest": { - "hashes": [ - "sha256:365c94d65de4c927d9d8b505371d08ee19f9f369c86b9ac3db97c2754c827c95", - "sha256:56dadd260a9c7d550b159796d2894b6d0bcc176a94cbc426d9bb93e5e48d12ce" - ], - "index": "pypi", - "version": "==0.47" - }, - "click": { - "hashes": [ - "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", - "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" - ], - "markers": "python_version >= '3.6'", - "version": "==8.0.3" - }, - "coverage": { - "hashes": [ - "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0", - "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd", - "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884", - "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48", - "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76", - "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0", - "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64", - "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685", - "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47", - "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d", - "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840", - "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f", - "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971", - "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c", - "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a", - "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de", - "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17", - "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4", - "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521", - "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57", - "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b", - "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282", - "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644", - "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475", - "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d", - "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da", - "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953", - "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2", - "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e", - "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c", - "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc", - "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64", - "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74", - "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617", - "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3", - "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d", - "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa", - "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739", - "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8", - "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8", - "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781", - "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58", - "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9", - "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c", - "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd", - "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e", - "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49" - ], - "index": "pypi", - "version": "==6.2" - }, - "defusedxml": { - "hashes": [ - "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", - "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" - ], - "index": "pypi", - "version": "==0.7.1" - }, - "docutils": { - "hashes": [ - "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", - "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" - ], - "index": "pypi", - "version": "==0.16" - }, - "idna": { - "hashes": [ - "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", - "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" - ], - "markers": "python_version >= '3'", - "version": "==3.3" - }, - "imagesize": { - "hashes": [ - "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c", - "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.3.0" - }, - "iniconfig": { - "hashes": [ - "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", - "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" - ], - "version": "==1.1.1" - }, - "jinja2": { - "hashes": [ - "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8", - "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7" - ], - "markers": "python_version >= '3.6'", - "version": "==3.0.3" - }, - "markdown2": { - "hashes": [ - "sha256:93a29c5cad95b00d82908590548479638251c9188df449561f614d2f9756e15a", - "sha256:ce9265cf179c4e07934e7b6a4b03f3edb7891e66e6d0f7017755f6064bbbe13f" - ], - "index": "pypi", - "version": "==2.4.1" - }, - "markupsafe": { - "hashes": [ - "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", - "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", - "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", - "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194", - "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", - "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", - "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", - "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", - "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", - "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", - "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", - "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a", - "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", - "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", - "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", - "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", - "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", - "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", - "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", - "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047", - "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", - "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", - "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b", - "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", - "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", - "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", - "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", - "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1", - "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", - "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", - "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", - "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee", - "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f", - "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", - "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", - "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", - "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", - "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", - "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", - "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86", - "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6", - "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", - "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", - "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", - "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", - "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e", - "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", - "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", - "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f", - "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", - "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", - "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", - "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", - "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", - "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", - "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", - "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a", - "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207", - "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", - "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", - "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd", - "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", - "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", - "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9", - "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", - "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", - "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", - "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", - "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" - ], - "markers": "python_version >= '3.6'", - "version": "==2.0.1" - }, - "mypy-extensions": { - "hashes": [ - "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", - "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" - ], - "version": "==0.4.3" - }, - "olefile": { - "hashes": [ - "sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964" - ], - "index": "pypi", - "version": "==0.46" - }, - "packaging": { - "hashes": [ - "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", - "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" - ], - "markers": "python_version >= '3.6'", - "version": "==21.3" - }, - "pathspec": { - "hashes": [ - "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", - "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" - ], - "version": "==0.9.0" - }, - "pep517": { - "hashes": [ - "sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0", - "sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161" - ], - "version": "==0.12.0" - }, - "platformdirs": { - "hashes": [ - "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2", - "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d" - ], - "markers": "python_version >= '3.6'", - "version": "==2.4.0" - }, - "pluggy": { - "hashes": [ - "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", - "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" - ], - "markers": "python_version >= '3.6'", - "version": "==1.0.0" - }, - "py": { - "hashes": [ - "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", - "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==1.11.0" - }, - "pygments": { - "hashes": [ - "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380", - "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6" - ], - "markers": "python_version >= '3.5'", - "version": "==2.10.0" - }, - "pyparsing": { - "hashes": [ - "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4", - "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81" - ], - "markers": "python_version >= '3.6'", - "version": "==3.0.6" - }, - "pyroma": { - "hashes": [ - "sha256:0fba67322913026091590e68e0d9e0d4fbd6420fcf34d315b2ad6985ab104d65", - "sha256:f8c181e0d5d292f11791afc18f7d0218a83c85cf64d6f8fb1571ce9d29a24e4a" - ], - "index": "pypi", - "version": "==3.2" - }, - "pytest": { - "hashes": [ - "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", - "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" - ], - "index": "pypi", - "version": "==6.2.5" - }, - "pytest-cov": { - "hashes": [ - "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6", - "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470" - ], - "index": "pypi", - "version": "==3.0.0" - }, - "pytest-timeout": { - "hashes": [ - "sha256:329bdea323d3e5bea4737070dd85a0d1021dbecb2da5342dc25284fdb929dff0", - "sha256:a5ec4eceddb8ea726911848593d668594107e797621e97f93a1d1dbc6fbb9080" - ], - "index": "pypi", - "version": "==2.0.1" - }, - "pytz": { - "hashes": [ - "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c", - "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326" - ], - "version": "==2021.3" - }, - "regex": { - "hashes": [ - "sha256:0416f7399e918c4b0e074a0f66e5191077ee2ca32a0f99d4c187a62beb47aa05", - "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f", - "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc", - "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4", - "sha256:0f594b96fe2e0821d026365f72ac7b4f0b487487fb3d4aaf10dd9d97d88a9737", - "sha256:139a23d1f5d30db2cc6c7fd9c6d6497872a672db22c4ae1910be22d4f4b2068a", - "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4", - "sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8", - "sha256:2409b5c9cef7054dde93a9803156b411b677affc84fca69e908b1cb2c540025d", - "sha256:2fee3ed82a011184807d2127f1733b4f6b2ff6ec7151d83ef3477f3b96a13d03", - "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f", - "sha256:3598893bde43091ee5ca0a6ad20f08a0435e93a69255eeb5f81b85e81e329264", - "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a", - "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef", - "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f", - "sha256:42b50fa6666b0d50c30a990527127334d6b96dd969011e843e726a64011485da", - "sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc", - "sha256:473e67837f786404570eae33c3b64a4b9635ae9f00145250851a1292f484c063", - "sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50", - "sha256:50a7ddf3d131dc5633dccdb51417e2d1910d25cbcf842115a3a5893509140a3a", - "sha256:529801a0d58809b60b3531ee804d3e3be4b412c94b5d267daa3de7fadef00f49", - "sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d", - "sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d", - "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733", - "sha256:563d5f9354e15e048465061509403f68424fef37d5add3064038c2511c8f5e00", - "sha256:5d408a642a5484b9b4d11dea15a489ea0928c7e410c7525cd892f4d04f2f617b", - "sha256:61600a7ca4bcf78a96a68a27c2ae9389763b5b94b63943d5158f2a377e09d29a", - "sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36", - "sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345", - "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0", - "sha256:6e1d2cc79e8dae442b3fa4a26c5794428b98f81389af90623ffcc650ce9f6732", - "sha256:74cbeac0451f27d4f50e6e8a8f3a52ca074b5e2da9f7b505c4201a57a8ed6286", - "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12", - "sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646", - "sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667", - "sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244", - "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29", - "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec", - "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf", - "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4", - "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449", - "sha256:96fc32c16ea6d60d3ca7f63397bff5c75c5a562f7db6dec7d412f7c4d2e78ec0", - "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a", - "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d", - "sha256:a955b747d620a50408b7fdf948e04359d6e762ff8a85f5775d907ceced715129", - "sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb", - "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e", - "sha256:b9ed0b1e5e0759d6b7f8e2f143894b2a7f3edd313f38cf44e1e15d360e11749b", - "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83", - "sha256:ca49e1ab99593438b204e00f3970e7a5f70d045267051dfa6b5f4304fcfa1dbf", - "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e", - "sha256:cd410a1cbb2d297c67d8521759ab2ee3f1d66206d2e4328502a487589a2cb21b", - "sha256:ce298e3d0c65bd03fa65ffcc6db0e2b578e8f626d468db64fdf8457731052942", - "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a", - "sha256:d5fd67df77bab0d3f4ea1d7afca9ef15c2ee35dfb348c7b57ffb9782a6e4db6e", - "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94", - "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc", - "sha256:dc07f021ee80510f3cd3af2cad5b6a3b3a10b057521d9e6aaeb621730d320c5a", - "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e", - "sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965", - "sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0", - "sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36", - "sha256:e6096b0688e6e14af6a1b10eaad86b4ff17935c49aa774eac7c95a57a4e8c296", - "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec", - "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23", - "sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7", - "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe", - "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6", - "sha256:f5be7805e53dafe94d295399cfbe5227f39995a997f4fd8539bf3cbdc8f47ca8", - "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b", - "sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb", - "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b", - "sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30", - "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e" - ], - "version": "==2021.11.10" - }, - "requests": { - "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==2.26.0" - }, - "setuptools": { - "hashes": [ - "sha256:b4c634615a0cf5b02cf83c7bedffc8da0ca439f00e79452699454da6fbd4153d", - "sha256:feb5ff19b354cde9efd2344ef6d5e79880ce4be643037641b49508bbb850d060" - ], - "markers": "python_version >= '3.6'", - "version": "==59.4.0" - }, - "snowballstemmer": { - "hashes": [ - "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", - "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" - ], - "version": "==2.2.0" - }, - "sphinx": { - "hashes": [ - "sha256:048dac56039a5713f47a554589dc98a442b39226a2b9ed7f82797fcb2fe9253f", - "sha256:32a5b3e9a1b176cc25ed048557d4d3d01af635e6b76c5bc7a43b0a34447fbd45" - ], - "index": "pypi", - "version": "==4.3.1" - }, - "sphinx-copybutton": { - "hashes": [ - "sha256:4340d33c169dac6dd82dce2c83333412aa786a42dd01a81a8decac3b130dc8b0", - "sha256:8daed13a87afd5013c3a9af3575cc4d5bec052075ccd3db243f895c07a689386" - ], - "index": "pypi", - "version": "==0.4.0" - }, - "sphinx-issues": { - "hashes": [ - "sha256:1208e1869742b7800a45b3c47ab987b87b2ad2024cbc36e0106e8bba3549dd22", - "sha256:845294736c7ac4c09c706f13431f709e1164037cbb00f6bf623ae16eccf509f3" - ], - "index": "pypi", - "version": "==1.2.0" - }, - "sphinx-removed-in": { - "hashes": [ - "sha256:0588239cb534cd97b1d3900d0444311c119e45296a9f73f1ea81ea81a2cd3db1", - "sha256:434b1a1c28b12021de4c84fc2fbb7168fdf1f56a410a42bf8d0af938d0855ad1" - ], - "index": "pypi", - "version": "==0.2.1" - }, - "sphinx-rtd-theme": { - "hashes": [ - "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8", - "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c" - ], - "index": "pypi", - "version": "==1.0.0" - }, - "sphinxcontrib-applehelp": { - "hashes": [ - "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", - "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" - }, - "sphinxcontrib-devhelp": { - "hashes": [ - "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", - "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" - }, - "sphinxcontrib-htmlhelp": { - "hashes": [ - "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", - "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" - ], - "markers": "python_version >= '3.6'", - "version": "==2.0.0" - }, - "sphinxcontrib-jsmath": { - "hashes": [ - "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", - "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.1" - }, - "sphinxcontrib-qthelp": { - "hashes": [ - "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", - "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.3" - }, - "sphinxcontrib-serializinghtml": { - "hashes": [ - "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", - "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" - ], - "markers": "python_version >= '3.5'", - "version": "==1.1.5" - }, - "sphinxext-opengraph": { - "hashes": [ - "sha256:50d493dc3bbcec324211ca56f9c04218fac1c4311cfc33a9cc75cb0bed63bd13", - "sha256:6efb05fe077e00b7a487e03b24bf56268bae92d42ef3f9da89616b60bc4a702c" - ], - "index": "pypi", - "version": "==0.5.0" - }, - "toml": { - "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", - "version": "==0.10.2" - }, - "tomli": { - "hashes": [ - "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee", - "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade" - ], - "markers": "python_version >= '3.6'", - "version": "==1.2.2" - }, - "typing-extensions": { - "hashes": [ - "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", - "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" - ], - "markers": "python_version >= '3.6'", - "version": "==4.0.1" - }, - "urllib3": { - "hashes": [ - "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", - "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.7" - } - }, - "develop": {} -} From 5549e629c108ef50a44353eba0ea567dc06248c3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 5 Dec 2021 00:58:34 +1100 Subject: [PATCH 209/633] Updated freetype to 2.11.1 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index a58ab95fa..c3a825c37 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -184,9 +184,9 @@ deps = { "libs": [r"libpng16.lib"], }, "freetype": { - "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.10.4.tar.gz", # noqa: E501 - "filename": "freetype-2.10.4.tar.gz", - "dir": "freetype-2.10.4", + "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.11.1.tar.gz", # noqa: E501 + "filename": "freetype-2.11.1.tar.gz", + "dir": "freetype-2.11.1", "patch": { r"builds\windows\vc2010\freetype.vcxproj": { # freetype setting is /MD for .dll and /MT for .lib, we need /MD From 722126aa0ad03825b0cf4cd2c807dc446c8f445d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 5 Dec 2021 15:59:27 +1100 Subject: [PATCH 210/633] Use latin1 encoding to decode bytes --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 174d3360a..1cd22228b 100755 --- a/setup.py +++ b/setup.py @@ -185,7 +185,7 @@ def _find_library_dirs_ldconfig(): return [] [data, _] = p.communicate() if isinstance(data, bytes): - data = data.decode() + data = data.decode("latin1") dirs = [] for dll in re.findall(expr, data): From 6791112a98e8c13b91c39c1d4a34bcaaddffe878 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 7 Dec 2021 06:32:12 +1100 Subject: [PATCH 211/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8692ecbc7..3cf83a7a9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Convert subsequent GIF frames to RGB or RGBA #5857 + [radarhere] + - Do not prematurely return in ImageFile when saving to stdout #5665 [infmagic2047, radarhere] From 0e10a5cc7be0c5c17f1c0fd95c74cd2934c19632 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 7 Dec 2021 06:37:01 +1100 Subject: [PATCH 212/633] When saving RGBA, make use of first transparent palette entry --- Tests/test_file_gif.py | 10 ++++++++++ src/PIL/GifImagePlugin.py | 8 +++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 13eb06897..00bf582fa 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -957,3 +957,13 @@ def test_missing_background(): with Image.open("Tests/images/missing_background.gif") as im: im.seek(1) assert_image_equal_tofile(im, "Tests/images/missing_background_first_frame.png") + + +def test_saving_rgba(tmp_path): + out = str(tmp_path / "temp.gif") + with Image.open("Tests/images/transparent.png") as im: + im.save(out) + + with Image.open(out) as reloaded: + reloaded_rgba = reloaded.convert("RGBA") + assert reloaded_rgba.load()[0, 0][3] == 0 diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 55b09abec..8c2180bc1 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -425,7 +425,13 @@ def _normalize_mode(im, initial_call=False): palette_size = 256 if im.palette: palette_size = len(im.palette.getdata()[1]) // 3 - return im.convert("P", palette=Image.ADAPTIVE, colors=palette_size) + im = im.convert("P", palette=Image.ADAPTIVE, colors=palette_size) + if im.palette.mode == "RGBA": + for rgba in im.palette.colors.keys(): + if rgba[3] == 0: + im.info["transparency"] = im.palette.colors[rgba] + break + return im else: return im.convert("P") return im.convert("L") From 6f2f01b23fda36b3a99319a6f9816e1d3939d67d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 7 Dec 2021 06:46:07 +1100 Subject: [PATCH 213/633] Added release notes for #5857 --- docs/releasenotes/9.0.0.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index c8c62c436..1efe7a06b 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -85,6 +85,14 @@ TODO Other Changes ============= +Convert subsequent GIF frames to RGB or RGBA +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Since each frame of a GIF can have up to 256 colors, after the first frame it is +possible for there to be too many colors to fit in a P mode image. To allow for this, +seeking to any subsequent GIF frame will now convert the image to RGB or RGBA, +depending on whether or not the first frame had transparency. + Added support for pickling TrueType fonts ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 5c05fe4d9b98e3c11b186c407d462249e85999bc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 7 Dec 2021 11:40:44 +1100 Subject: [PATCH 214/633] Fixed raising OSError in _safe_read when size is greater than SAFEBLOCK --- Tests/test_imagefile.py | 16 +++++++++++++++- src/PIL/ImageFile.py | 7 ++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index f4e5a6a59..372cee8c0 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -2,7 +2,7 @@ from io import BytesIO import pytest -from PIL import EpsImagePlugin, Image, ImageFile, features +from PIL import BmpImagePlugin, EpsImagePlugin, Image, ImageFile, _binary, features from .helper import ( assert_image, @@ -111,6 +111,20 @@ class TestImageFile: with pytest.raises(OSError): p.close() + def test_truncated(self): + b = BytesIO( + b"BM000000000000" # head_data + + _binary.o32le( + ImageFile.SAFEBLOCK + 1 + 4 + ) # header_size, so BmpImagePlugin will try to read SAFEBLOCK + 1 bytes + + ( + b"0" * ImageFile.SAFEBLOCK + ) # only SAFEBLOCK bytes, so that the header is truncated + ) + with pytest.raises(OSError) as e: + BmpImagePlugin.BmpImageFile(b) + assert str(e.value) == "Truncated File Read" + @skip_unless_feature("zlib") def test_truncated_with_errors(self): with Image.open("Tests/images/truncated_image.png") as im: diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 453ed3e8c..d43667ca0 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -545,12 +545,13 @@ def _safe_read(fp, size): raise OSError("Truncated File Read") return data data = [] - while size > 0: - block = fp.read(min(size, SAFEBLOCK)) + remaining_size = size + while remaining_size > 0: + block = fp.read(min(remaining_size, SAFEBLOCK)) if not block: break data.append(block) - size -= len(block) + remaining_size -= len(block) if sum(len(d) for d in data) < size: raise OSError("Truncated File Read") return b"".join(data) From 7b201798ad3b2ca0831739812b769fcef84c061b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 8 Dec 2021 19:00:50 +1100 Subject: [PATCH 215/633] Updated libimagequant to 2.17.0 --- depends/install_imagequant.sh | 2 +- docs/installation.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 8c6704ac1..774f26767 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,7 +1,7 @@ #!/bin/bash # install libimagequant -archive=libimagequant-2.16.0 +archive=libimagequant-2.17.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/docs/installation.rst b/docs/installation.rst index e94e8d355..1fb6897d2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -187,7 +187,7 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-2.16.0** + * Pillow has been tested with libimagequant **2.6-2.17.0** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. From fdec387b4a8fdd8d8fb0e59e66922af835b4a5a6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 11 Dec 2021 16:07:45 +1100 Subject: [PATCH 216/633] Fixed palette index for zeroed color in FASTOCTREE quantize --- Tests/test_image_quantize.py | 17 +++++++++++++++++ src/libImaging/QuantOctree.c | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index bd9db362c..8a7b9b311 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -85,3 +85,20 @@ def test_transparent_colors_equal(): converted = im.quantize() converted_px = converted.load() assert converted_px[0, 0] == converted_px[0, 1] + + +@pytest.mark.parametrize( + "method, color", + ( + (Image.MEDIANCUT, (0, 0, 0)), + (Image.MAXCOVERAGE, (0, 0, 0)), + (Image.FASTOCTREE, (0, 0, 0)), + (Image.FASTOCTREE, (0, 0, 0, 0)), + ), +) +def test_palette(method, color): + im = Image.new("RGBA" if len(color) == 4 else "RGB", (1, 1), color) + + converted = im.quantize(method=method) + converted_px = converted.load() + assert converted_px[0, 0] == converted.palette.colors[color] diff --git a/src/libImaging/QuantOctree.c b/src/libImaging/QuantOctree.c index b8d4d1d7c..5e79bce35 100644 --- a/src/libImaging/QuantOctree.c +++ b/src/libImaging/QuantOctree.c @@ -317,7 +317,7 @@ void add_lookup_buckets(ColorCube cube, ColorBucket palette, long nColors, long offset) { long i; Pixel p; - for (i = offset; i < offset + nColors; i++) { + for (i = offset + nColors - 1; i >= offset; i--) { avg_color_from_color_bucket(&palette[i], &p); set_lookup_value(cube, &p, i); } From ec198899f694544ff0736c525a7424115dd38db2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 11 Dec 2021 16:23:37 +1100 Subject: [PATCH 217/633] Limit quantized palette to number of colors --- Tests/test_image_quantize.py | 7 +++++++ src/PIL/Image.py | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index bd9db362c..0be7feac5 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -77,6 +77,13 @@ def test_quantize_dither_diff(): assert dither.tobytes() != nodither.tobytes() +def test_colors(): + im = hopper() + colors = 2 + converted = im.quantize(colors) + assert len(converted.palette.palette) == colors * len("RGB") + + def test_transparent_colors_equal(): im = Image.new("RGBA", (1, 2), (0, 0, 0, 0)) px = im.load() diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0fca3fa5c..0f3b6fada 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1111,7 +1111,8 @@ class Image: from . import ImagePalette mode = im.im.getpalettemode() - im.palette = ImagePalette.ImagePalette(mode, im.im.getpalette(mode, mode)) + palette = im.im.getpalette(mode, mode)[: colors * len(mode)] + im.palette = ImagePalette.ImagePalette(mode, palette) return im From 3378799b4de9020b2c3aa9145ca406738f1e5f72 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 12 Dec 2021 15:59:29 +1100 Subject: [PATCH 218/633] Updated harfbuzz to 3.2.0 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index c3a825c37..b1c95aa74 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -278,9 +278,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/3.1.2.zip", - "filename": "harfbuzz-3.1.2.zip", - "dir": "harfbuzz-3.1.2", + "url": "https://github.com/harfbuzz/harfbuzz/archive/3.2.0.zip", + "filename": "harfbuzz-3.2.0.zip", + "dir": "harfbuzz-3.2.0", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 8c405a9ab554a0de649390dec7f4b0e17645c670 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 13 Dec 2021 23:43:04 +0200 Subject: [PATCH 219/633] Dedicate the release to Fredrik Lundh --- docs/releasenotes/9.0.0.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 1efe7a06b..39123012b 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -1,6 +1,29 @@ 9.0.0 ----- +Fredrik Lundh +============= + +This release is dedicated to the memory of Fredrik Lundh, aka Effbot, who died in +November 2021. Fredrik created PIL in 1995 and he was instrumental in the early +success of Python. + +`Guido wrote `_: + + Fredrik was an early Python contributor (e.g. Elementtree and the 're' + module) and his enthusiasm for the language and community were inspiring + for all who encountered him or his work. He spent countless hours on + comp.lang.python answering questions from newbies and advanced users alike. + + He also co-founded an early Python startup, Secret Labs AB, which among + other software released an IDE named PythonWorks. Fredrik also created the + Python Imaging Library (PIL) which is still THE way to interact with images + in Python, now most often through its Pillow fork. His effbot.org site was + a valuable resource for generations of Python users, especially its Tkinter + documentation. + +Thank you, Fredrik. + Backwards Incompatible Changes ============================== From 5b023c7d4823a18790ce09b188709cc006badbc3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 14 Dec 2021 16:58:42 +0200 Subject: [PATCH 220/633] GHA: Use macos-10.15 to fix build --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9bbc426e2..c287cf534 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: os: [ - "macOS-latest", + "macos-10.15", "ubuntu-latest", ] python-version: [ @@ -29,7 +29,7 @@ jobs: # Include new variables for Codecov - os: ubuntu-latest codecov-flag: GHA_Ubuntu - - os: macOS-latest + - os: macos-10.15 codecov-flag: GHA_macOS runs-on: ${{ matrix.os }} From 6aca23cfa32889f9dae83f5374d216662cb7de8e Mon Sep 17 00:00:00 2001 From: Christopher Bruns Date: Sun, 28 Nov 2021 19:34:14 -0800 Subject: [PATCH 221/633] Support 16-bit grayscale ImageQt conversion. --- src/PIL/ImageQt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index e142f1f27..f10de3258 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -108,7 +108,7 @@ def align8to32(bytes, width, mode): converts each scanline of data from 8 bit to 32 bit aligned """ - bits_per_pixel = {"1": 1, "L": 8, "P": 8}[mode] + bits_per_pixel = {"1": 1, "L": 8, "P": 8, "I;16": 16}[mode] # calculate bytes per line and the extra padding if needed bits_per_line = bits_per_pixel * width @@ -167,6 +167,8 @@ def _toqclass_helper(im): elif im.mode == "RGBA": data = im.tobytes("raw", "BGRA") format = qt_format.Format_ARGB32 + elif im.mode == "I;16": + format = qt_format.Format_Grayscale16 else: if exclusive_fp: im.close() From b1cc094f57b8dcc445dbf11e5f077f097940b8ad Mon Sep 17 00:00:00 2001 From: Christopher Bruns Date: Mon, 29 Nov 2021 16:54:09 -0800 Subject: [PATCH 222/633] Add 16-bit grayscale test --- Tests/test_imageqt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 53b1fef7c..a7947b0a2 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -42,7 +42,7 @@ def test_rgb(): def test_image(): - for mode in ("1", "RGB", "RGBA", "L", "P"): + for mode in ("1", "RGB", "RGBA", "L", "P", "I;16"): ImageQt.ImageQt(hopper(mode)) From e87745d9ec34e76fbe236bd3a6ff1225762e04ad Mon Sep 17 00:00:00 2001 From: Christopher Bruns Date: Mon, 29 Nov 2021 19:38:57 -0800 Subject: [PATCH 223/633] Check if installed Qt version supports Format_Grayscale16 --- Tests/test_imageqt.py | 5 ++++- src/PIL/ImageQt.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index a7947b0a2..08cab9976 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -42,8 +42,11 @@ def test_rgb(): def test_image(): - for mode in ("1", "RGB", "RGBA", "L", "P", "I;16"): + for mode in ("1", "RGB", "RGBA", "L", "P"): ImageQt.ImageQt(hopper(mode)) + qt_format = ImageQt.QImage.Format if ImageQt.qt_version == "6" else ImageQt.QImage + if hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+ + ImageQt.ImageQt(hopper("I;16")) def test_closed_file(): diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index f10de3258..8970314d9 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -167,7 +167,7 @@ def _toqclass_helper(im): elif im.mode == "RGBA": data = im.tobytes("raw", "BGRA") format = qt_format.Format_ARGB32 - elif im.mode == "I;16": + elif im.mode == "I;16" and hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+ format = qt_format.Format_Grayscale16 else: if exclusive_fp: From 768c189a2999553d91b271546e01df96c2dd1078 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 Dec 2021 10:13:09 +1100 Subject: [PATCH 224/633] Correct image by scaling pixels --- Tests/test_imageqt.py | 14 ++++++++++---- src/PIL/ImageQt.py | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 08cab9976..589cb5a21 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -2,7 +2,7 @@ import pytest from PIL import ImageQt -from .helper import hopper +from .helper import assert_image_similar, hopper pytestmark = pytest.mark.skipif( not ImageQt.qt_is_installed, reason="Qt bindings are not installed" @@ -42,11 +42,17 @@ def test_rgb(): def test_image(): - for mode in ("1", "RGB", "RGBA", "L", "P"): - ImageQt.ImageQt(hopper(mode)) + modes = ["1", "RGB", "RGBA", "L", "P"] qt_format = ImageQt.QImage.Format if ImageQt.qt_version == "6" else ImageQt.QImage if hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+ - ImageQt.ImageQt(hopper("I;16")) + modes.append("I;16") + + for mode in modes: + im = hopper(mode) + roundtripped_im = ImageQt.fromqimage(ImageQt.ImageQt(im)) + if mode not in ("RGB", "RGBA"): + im = im.convert("RGB") + assert_image_similar(roundtripped_im, im, 1) def test_closed_file(): diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 8970314d9..db8fa0fa9 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -168,6 +168,8 @@ def _toqclass_helper(im): data = im.tobytes("raw", "BGRA") format = qt_format.Format_ARGB32 elif im.mode == "I;16" and hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+ + im = im.point(lambda i: i * 256) + format = qt_format.Format_Grayscale16 else: if exclusive_fp: From 6899f06cf9db6b7578bc2073e2f832e7d13d767d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Dec 2021 07:26:19 +1100 Subject: [PATCH 225/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3cf83a7a9..7acc89492 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Support 16-bit grayscale ImageQt conversion #5856 + [cmbruns, radarhere] + - Convert subsequent GIF frames to RGB or RGBA #5857 [radarhere] From 603fb34701145de9951a31b6dd6a725697a73b6a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 Dec 2021 08:10:12 +1100 Subject: [PATCH 226/633] GHA: Still use macos-latest for non-PyPy builds --- .github/workflows/test.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c287cf534..273e3689a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: os: [ - "macos-10.15", + "macos-latest", "ubuntu-latest", ] python-version: [ @@ -29,8 +29,19 @@ jobs: # Include new variables for Codecov - os: ubuntu-latest codecov-flag: GHA_Ubuntu + - os: macos-latest + codecov-flag: GHA_macOS - os: macos-10.15 codecov-flag: GHA_macOS + python-version: pypy-3.8 + - os: macos-10.15 + codecov-flag: GHA_macOS + python-version: pypy-3.7 + exclude: + - os: macos-latest + python-version: pypy-3.8 + - os: macos-latest + python-version: pypy-3.7 runs-on: ${{ matrix.os }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} From b3cb55f82360bf39eebb363d8f65cfd97bebf5fe Mon Sep 17 00:00:00 2001 From: Alexey Shamrin Date: Wed, 15 Dec 2021 22:35:32 +0200 Subject: [PATCH 227/633] keep IPython/Jupyter text/plain output stable --- src/PIL/Image.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0fca3fa5c..2653c6274 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -644,6 +644,19 @@ class Image: id(self), ) + def _repr_pretty_(self, p, cycle): + """IPython plain text display support""" + + # Same as __repr__ but without unpredicatable id(self), + # to keep Jupyter notebook `text/plain` output stable. + p.text("<%s.%s image mode=%s size=%dx%d>" % ( + self.__class__.__module__, + self.__class__.__name__, + self.mode, + self.size[0], + self.size[1], + )) + def _repr_png_(self): """iPython display hook support From 56d630294c31ea7c1360a9830ad82d9303ea6602 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 15 Dec 2021 20:39:38 +0000 Subject: [PATCH 228/633] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/Image.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2653c6274..3677cccc3 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -649,13 +649,16 @@ class Image: # Same as __repr__ but without unpredicatable id(self), # to keep Jupyter notebook `text/plain` output stable. - p.text("<%s.%s image mode=%s size=%dx%d>" % ( - self.__class__.__module__, - self.__class__.__name__, - self.mode, - self.size[0], - self.size[1], - )) + p.text( + "<%s.%s image mode=%s size=%dx%d>" + % ( + self.__class__.__module__, + self.__class__.__name__, + self.mode, + self.size[0], + self.size[1], + ) + ) def _repr_png_(self): """iPython display hook support From e7e05e2701e9d39f4d2766a8c1d302b3db12f641 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 18 Dec 2021 15:45:49 +1100 Subject: [PATCH 229/633] GHA: Restored macos-latest for PyPy builds --- .github/workflows/test.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 273e3689a..414c7e94e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,17 +31,6 @@ jobs: codecov-flag: GHA_Ubuntu - os: macos-latest codecov-flag: GHA_macOS - - os: macos-10.15 - codecov-flag: GHA_macOS - python-version: pypy-3.8 - - os: macos-10.15 - codecov-flag: GHA_macOS - python-version: pypy-3.7 - exclude: - - os: macos-latest - python-version: pypy-3.8 - - os: macos-latest - python-version: pypy-3.7 runs-on: ${{ matrix.os }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} From a1677ead44326efbf6378eb2b55741c0bc326a5a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 18 Dec 2021 19:43:23 +1100 Subject: [PATCH 230/633] Switched from deprecated "setup.py install" to "pip install ." --- .github/workflows/test-mingw.yml | 2 +- Makefile | 8 ++++---- docs/installation.rst | 6 +----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index 051cfc483..d94c7d537 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -60,7 +60,7 @@ jobs: pushd depends && ./install_extra_test_images.sh && popd - name: Build Pillow - run: CFLAGS="-coverage" python3 setup.py build_ext install + run: CFLAGS="-coverage" python3 -m pip install --global-option="build_ext" . - name: Test Pillow run: | diff --git a/Makefile b/Makefile index 546b91838..04c586214 100644 --- a/Makefile +++ b/Makefile @@ -54,12 +54,12 @@ inplace: clean .PHONY: install install: - python3 setup.py install + python3 -m pip install . python3 selftest.py .PHONY: install-coverage install-coverage: - CFLAGS="-coverage -Werror=implicit-function-declaration" python3 setup.py build_ext install + CFLAGS="-coverage -Werror=implicit-function-declaration" python3 -m pip install --global-option="build_ext" . python3 selftest.py .PHONY: debug @@ -68,7 +68,7 @@ debug: # for our stuff, kills optimization, and redirects to dev null so we # see any build failures. make clean > /dev/null - CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null + CFLAGS='-g -O0' python3 -m pip install --global-option="build_ext" . > /dev/null .PHONY: install-req install-req: @@ -86,7 +86,7 @@ release-test: python3 setup.py develop python3 selftest.py python3 -m pytest Tests - python3 setup.py install + python3 -m pip install . -rm dist/*.egg -rmdir dist python3 -m pytest -qq diff --git a/docs/installation.rst b/docs/installation.rst index 1fb6897d2..df21a7cdc 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -275,10 +275,6 @@ Build Options Sample usage:: - MAX_CONCURRENCY=1 python3 setup.py build_ext --enable-[feature] install - -or using pip:: - python3 -m pip install --upgrade Pillow --global-option="build_ext" --global-option="--enable-[feature]" @@ -310,7 +306,7 @@ Now install Pillow with:: or from within the uncompressed source directory:: - python3 setup.py install + python3 -m pip install . Building on Windows ^^^^^^^^^^^^^^^^^^^ From d455abffeebd566f40ae859e89c590c3d260309e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 18 Dec 2021 21:16:50 +1100 Subject: [PATCH 231/633] Moved all pathlib logic out of function --- docs/handbook/tutorial.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index e4848fa62..dbbacceee 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -417,13 +417,11 @@ This example uses Pillow together with pathlib, in order to reduce the quality o from PIL import Image - def compress_image(filepath): - file = filepath.stem - with Image.open(filepath) as img: + def compress_image(source_path, dest_path): + with Image.open(source_path) as img: if img.mode != "RGB": img = img.convert("RGB") - img.save(file + ".jpg", "JPEG", optimize=True, quality=80) - return + img.save(dest_path, "JPEG", optimize=True, quality=80) base_directory = Path.cwd() @@ -431,7 +429,7 @@ This example uses Pillow together with pathlib, in order to reduce the quality o for path in base_directory.iterdir(): if path.suffix == ".png": print(path) - compress_image(path) + compress_image(path, filepath.stem + ".jpg") From 946571d4a3af5b605ee375df9cb2e3912148d1c7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 18 Dec 2021 21:23:07 +1100 Subject: [PATCH 232/633] Moved batch processing example under "More on reading images" --- docs/handbook/tutorial.rst | 54 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index dbbacceee..17c98ed94 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -406,33 +406,6 @@ Using the ImageSequence Iterator class # ...do something to frame... -Batch processing with pathlib ------------------------------ - -This example uses Pillow together with pathlib, in order to reduce the quality of all PNG images in a folder: - -:: - - from pathlib import Path - from PIL import Image - - - def compress_image(source_path, dest_path): - with Image.open(source_path) as img: - if img.mode != "RGB": - img = img.convert("RGB") - img.save(dest_path, "JPEG", optimize=True, quality=80) - - - base_directory = Path.cwd() - - for path in base_directory.iterdir(): - if path.suffix == ".png": - print(path) - compress_image(path, filepath.stem + ".jpg") - - - PostScript printing ------------------- @@ -520,6 +493,33 @@ Reading from a tar archive fp = TarIO.TarIO("Tests/images/hopper.tar", "hopper.jpg") im = Image.open(fp) + +Batch processing +^^^^^^^^^^^^^^^^ + +This example uses Pillow together with pathlib, in order to reduce the quality of all PNG images in a folder: + +:: + + from pathlib import Path + from PIL import Image + + + def compress_image(source_path, dest_path): + with Image.open(source_path) as img: + if img.mode != "RGB": + img = img.convert("RGB") + img.save(dest_path, "JPEG", optimize=True, quality=80) + + + base_directory = Path.cwd() + + for path in base_directory.iterdir(): + if path.suffix == ".png": + print(path) + compress_image(path, filepath.stem + ".jpg") + + Controlling the decoder ----------------------- From 6c8ac0e700740750da11368d9121338c7949a028 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 18 Dec 2021 21:59:09 +1100 Subject: [PATCH 233/633] Added "os" example --- docs/handbook/tutorial.rst | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 17c98ed94..a6b5e23d8 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -497,11 +497,12 @@ Reading from a tar archive Batch processing ^^^^^^^^^^^^^^^^ -This example uses Pillow together with pathlib, in order to reduce the quality of all PNG images in a folder: +Operations can be applied to multiple image files. For example, all PNG images +in the current directory can be saved as JPEGs at reduced quality. :: - from pathlib import Path + import os from PIL import Image @@ -512,12 +513,20 @@ This example uses Pillow together with pathlib, in order to reduce the quality o img.save(dest_path, "JPEG", optimize=True, quality=80) - base_directory = Path.cwd() + paths = [path for path in os.listdir(".") if path.endsWith(".png")] + for path in paths: + compress_image(path, path[:-4] + ".jpg") - for path in base_directory.iterdir(): - if path.suffix == ".png": - print(path) - compress_image(path, filepath.stem + ".jpg") +Since images can also be opened from a ``Path`` from the ``pathlib`` module, +the example could be modified to use ``pathlib`` instead of ``os``. + +:: + + from pathlib import Path + + paths = [path for path in Path.cwd().iterdir() if path.suffix == ".png"] + for path in paths: + compress_image(path, filepath.stem + ".jpg") Controlling the decoder From e94a54ce25a9692270d193eea5dabb5e71ed97dc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 19 Dec 2021 12:13:37 +1100 Subject: [PATCH 234/633] Replaced further direct invocations of setup.py --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 04c586214..0dac63d39 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ help: .PHONY: inplace inplace: clean - python3 setup.py develop build_ext --inplace + python3 -m pip install -e --global-option="build_ext" --global-option="--inplace" . .PHONY: install install: @@ -83,7 +83,7 @@ install-venv: .PHONY: release-test release-test: $(MAKE) install-req - python3 setup.py develop + python3 -m pip install -e . python3 selftest.py python3 -m pytest Tests python3 -m pip install . From cd613e68503ff130a32b44e80d18332809c01640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Hendrik=20M=C3=BCller?= <44469195+kolibril13@users.noreply.github.com> Date: Sun, 19 Dec 2021 13:26:30 +0100 Subject: [PATCH 235/633] Apply suggestions from code review Co-authored-by: Hugo van Kemenade --- docs/handbook/tutorial.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index a6b5e23d8..f71ad7698 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -513,7 +513,7 @@ in the current directory can be saved as JPEGs at reduced quality. img.save(dest_path, "JPEG", optimize=True, quality=80) - paths = [path for path in os.listdir(".") if path.endsWith(".png")] + paths = glob.glob(".png") for path in paths: compress_image(path, path[:-4] + ".jpg") @@ -524,7 +524,7 @@ the example could be modified to use ``pathlib`` instead of ``os``. from pathlib import Path - paths = [path for path in Path.cwd().iterdir() if path.suffix == ".png"] + paths = Path(".").glob("*.png") for path in paths: compress_image(path, filepath.stem + ".jpg") From 36caa53fedd11466066d3f55a826becba5220315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Hendrik=20M=C3=BCller?= <44469195+kolibril13@users.noreply.github.com> Date: Sun, 19 Dec 2021 22:48:23 +0100 Subject: [PATCH 236/633] Apply suggestions from code review Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/handbook/tutorial.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index f71ad7698..f8ee0c413 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -502,7 +502,7 @@ in the current directory can be saved as JPEGs at reduced quality. :: - import os + import glob from PIL import Image @@ -513,12 +513,13 @@ in the current directory can be saved as JPEGs at reduced quality. img.save(dest_path, "JPEG", optimize=True, quality=80) - paths = glob.glob(".png") + paths = glob.glob("*.png") for path in paths: compress_image(path, path[:-4] + ".jpg") Since images can also be opened from a ``Path`` from the ``pathlib`` module, -the example could be modified to use ``pathlib`` instead of ``os``. +the example could be modified to use ``pathlib`` instead of the ``glob`` +module. :: From a12c18608efbf21395662dccf2e105c6722d5652 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 20 Dec 2021 09:39:09 +1100 Subject: [PATCH 237/633] Corrected variable name --- docs/handbook/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index f8ee0c413..895408b79 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -527,7 +527,7 @@ module. paths = Path(".").glob("*.png") for path in paths: - compress_image(path, filepath.stem + ".jpg") + compress_image(path, path.stem + ".jpg") Controlling the decoder From 5cca90a37ce005498c80f4717ba67c5d8f45c540 Mon Sep 17 00:00:00 2001 From: mihail Date: Mon, 20 Dec 2021 12:08:31 +0300 Subject: [PATCH 238/633] Add: XDGViewer which uses xdg-open Synopsis xdg-open { file | URL } xdg-open { --help | --manual | --version } Use 'man xdg-open' or 'xdg-open --manual' for additional info. --- src/PIL/ImageShow.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index cd0737c36..2135293e5 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -186,6 +186,16 @@ class UnixViewer(Viewer): return 1 +class XDGViewer(UnixViewer): + """ + The freedesktop.org ``xdg-open`` command. + """ + + def get_command_ex(self, file, **options): + command = executable = "xdg-open" + return command, executable + + class DisplayViewer(UnixViewer): """ The ImageMagick ``display`` command. @@ -233,6 +243,8 @@ class XVViewer(UnixViewer): if sys.platform not in ("win32", "darwin"): # unixoids + if shutil.which("xdg-open"): + register(XDGViewer) if shutil.which("display"): register(DisplayViewer) if shutil.which("gm"): From 7a426109a10a234165283c4632534e9725ff0840 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Mon, 20 Dec 2021 13:19:45 -0500 Subject: [PATCH 239/633] Add lock file --- Pipfile.lock | 324 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 Pipfile.lock diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 000000000..600b19050 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,324 @@ +{ + "_meta": { + "hash": { + "sha256": "e5cad23bf4187647d53b613a64dc4792b7064bf86b08dfb5737580e32943f54d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "attrs": { + "hashes": [ + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" + }, + "black": { + "hashes": [ + "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3", + "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f" + ], + "index": "pypi", + "version": "==21.12b0" + }, + "build": { + "hashes": [ + "sha256:1aaadcd69338252ade4f7ec1265e1a19184bf916d84c9b7df095f423948cb89f", + "sha256:21b7ebbd1b22499c4dac536abc7606696ea4d909fd755e00f09f3c0f2c05e3c8" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "certifi": { + "hashes": [ + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + ], + "version": "==2021.10.8" + }, + "charset-normalizer": { + "hashes": [ + "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721", + "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c" + ], + "markers": "python_version >= '3'", + "version": "==2.0.9" + }, + "check-manifest": { + "hashes": [ + "sha256:365c94d65de4c927d9d8b505371d08ee19f9f369c86b9ac3db97c2754c827c95", + "sha256:56dadd260a9c7d550b159796d2894b6d0bcc176a94cbc426d9bb93e5e48d12ce" + ], + "index": "pypi", + "version": "==0.47" + }, + "click": { + "hashes": [ + "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", + "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.3" + }, + "coverage": { + "hashes": [ + "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0", + "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd", + "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884", + "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48", + "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76", + "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0", + "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64", + "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685", + "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47", + "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d", + "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840", + "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f", + "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971", + "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c", + "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a", + "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de", + "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17", + "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4", + "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521", + "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57", + "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b", + "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282", + "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644", + "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475", + "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d", + "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da", + "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953", + "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2", + "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e", + "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c", + "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc", + "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64", + "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74", + "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617", + "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3", + "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d", + "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa", + "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739", + "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8", + "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8", + "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781", + "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58", + "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9", + "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c", + "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd", + "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e", + "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49" + ], + "index": "pypi", + "version": "==6.2" + }, + "defusedxml": { + "hashes": [ + "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" + ], + "index": "pypi", + "version": "==0.7.1" + }, + "docutils": { + "hashes": [ + "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c", + "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==0.18.1" + }, + "idna": { + "hashes": [ + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + ], + "markers": "python_version >= '3'", + "version": "==3.3" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "markdown2": { + "hashes": [ + "sha256:8f4ac8d9a124ab408c67361090ed512deda746c04362c36c2ec16190c720c2b0", + "sha256:91113caf23aa662570fe21984f08fe74f814695c0a0ea8e863a8b4c4f63f9f6e" + ], + "index": "pypi", + "version": "==2.4.2" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "olefile": { + "hashes": [ + "sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964" + ], + "index": "pypi", + "version": "==0.46" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "index": "pypi", + "version": "==21.3" + }, + "pathspec": { + "hashes": [ + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + ], + "version": "==0.9.0" + }, + "pep517": { + "hashes": [ + "sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0", + "sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161" + ], + "version": "==0.12.0" + }, + "platformdirs": { + "hashes": [ + "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2", + "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d" + ], + "markers": "python_version >= '3.6'", + "version": "==2.4.0" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" + }, + "pygments": { + "hashes": [ + "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380", + "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6" + ], + "markers": "python_version >= '3.5'", + "version": "==2.10.0" + }, + "pyparsing": { + "hashes": [ + "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4", + "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.6" + }, + "pyroma": { + "hashes": [ + "sha256:0fba67322913026091590e68e0d9e0d4fbd6420fcf34d315b2ad6985ab104d65", + "sha256:f8c181e0d5d292f11791afc18f7d0218a83c85cf64d6f8fb1571ce9d29a24e4a" + ], + "index": "pypi", + "version": "==3.2" + }, + "pytest": { + "hashes": [ + "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", + "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" + ], + "index": "pypi", + "version": "==6.2.5" + }, + "pytest-cov": { + "hashes": [ + "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6", + "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470" + ], + "index": "pypi", + "version": "==3.0.0" + }, + "pytest-timeout": { + "hashes": [ + "sha256:e6f98b54dafde8d70e4088467ff621260b641eb64895c4195b6e5c8f45638112", + "sha256:fe9c3d5006c053bb9e062d60f641e6a76d6707aedb645350af9593e376fcc717" + ], + "index": "pypi", + "version": "==2.0.2" + }, + "requests": { + "hashes": [ + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.26.0" + }, + "setuptools": { + "hashes": [ + "sha256:5ec2bbb534ed160b261acbbdd1b463eb3cf52a8d223d96a8ab9981f63798e85c", + "sha256:75fd345a47ce3d79595b27bf57e6f49c2ca7904f3c7ce75f8a87012046c86b0b" + ], + "markers": "python_version >= '3.7'", + "version": "==60.0.0" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.2" + }, + "tomli": { + "hashes": [ + "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f", + "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.3" + }, + "typing-extensions": { + "hashes": [ + "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", + "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.1" + }, + "urllib3": { + "hashes": [ + "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", + "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.7" + } + }, + "develop": {} +} From ff723e45ab4af40ded75d3f3ba709b5ab13d820c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 21 Dec 2021 12:43:50 +1100 Subject: [PATCH 240/633] Ensure that pixel data offset does not ignore palette --- Tests/images/pal8_offset.bmp | Bin 0 -> 9254 bytes Tests/test_file_bmp.py | 7 +++++++ src/PIL/BmpImagePlugin.py | 2 ++ 3 files changed, 9 insertions(+) create mode 100644 Tests/images/pal8_offset.bmp diff --git a/Tests/images/pal8_offset.bmp b/Tests/images/pal8_offset.bmp new file mode 100644 index 0000000000000000000000000000000000000000..24be65f22c3c7b77adb011ab4d635d95b31ee15d GIT binary patch literal 9254 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 T<(E}?03>_8_`3k(yk-3da10=X literal 0 HcmV?d00001 diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 3374fe54e..47fc97df0 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -123,3 +123,10 @@ def test_rgba_bitfields(): im = Image.merge("RGB", (r, g, b)) assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp") + + +def test_offset(): + # This image has been hexedited + # to exclude the palette size from the pixel data offset + with Image.open("Tests/images/pal8_offset.bmp") as im: + assert_image_equal_tofile(im, "Tests/images/bmp/g/pal8.bmp") diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 7bfe733e5..7a7ad386c 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -158,6 +158,8 @@ class BmpImageFile(ImageFile.ImageFile): if file_info.get("colors", 0) else (1 << file_info["bits"]) ) + if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8: + offset += 4 * file_info["colors"] # ---------------------- Check bit depth for unusual unsupported values self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) From 085b05d87bb014bbc8857052c78cc63952df0655 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 21 Dec 2021 11:02:14 -0500 Subject: [PATCH 241/633] Lint fix --- MANIFEST.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index e9aaa8318..26f9401f2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ include *.c include *.h include *.in +include *.lock include *.md include *.py include *.rst @@ -9,6 +10,7 @@ include *.txt include *.yaml include LICENSE include Makefile +include Pipfile include tox.ini graft Tests graft src From 34ad580f42074622a1c1b1f3b4953532726ff2d6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 22 Dec 2021 16:42:39 +1100 Subject: [PATCH 242/633] Fixed typo --- src/_webp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_webp.c b/src/_webp.c index 90719b466..bccfb3d89 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -486,7 +486,7 @@ static struct PyMethodDef _anim_encoder_methods[] = { {NULL, NULL} /* sentinel */ }; -// WebPAnimDecoder type definition +// WebPAnimEncoder type definition static PyTypeObject WebPAnimEncoder_Type = { PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimEncoder", /*tp_name */ sizeof(WebPAnimEncoderObject), /*tp_size */ From d0faeb4e5e0b5dadc9d4593d911cec5b0f79904e Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Thu, 23 Dec 2021 11:15:29 +1100 Subject: [PATCH 243/633] Added XDGViewer class --- docs/reference/ImageShow.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/reference/ImageShow.rst b/docs/reference/ImageShow.rst index e4d9805ab..45b50c846 100644 --- a/docs/reference/ImageShow.rst +++ b/docs/reference/ImageShow.rst @@ -17,6 +17,7 @@ All default viewers convert the image to be shown to PNG format. The following viewers may be registered on Unix-based systems, if the given command is found: + .. autoclass:: PIL.ImageShow.XDGViewer .. autoclass:: PIL.ImageShow.DisplayViewer .. autoclass:: PIL.ImageShow.GmDisplayViewer .. autoclass:: PIL.ImageShow.EogViewer From 8568fab63ec687583a5f06179cfe55b597f7fe70 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 24 Dec 2021 08:15:33 +1100 Subject: [PATCH 244/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7acc89492..e0f347d58 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Added ImageShow support for xdg-open #5897 + [m-shinder, radarhere] + - Support 16-bit grayscale ImageQt conversion #5856 [cmbruns, radarhere] From b07404e14061962202e6c37722e7b273df1ca268 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 24 Dec 2021 09:05:54 +1100 Subject: [PATCH 245/633] Added release notes for #5897 --- docs/releasenotes/9.0.0.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 39123012b..edb2463c1 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -67,14 +67,6 @@ ImageFile.raise_ioerror has been removed. Use ``ImageFile.raise_oserror`` instead. -Deprecations -============ - -TODO -^^^^ - -TODO - API Changes =========== @@ -83,11 +75,19 @@ Added line width parameter to ImageDraw polygon An optional line ``width`` parameter has been added to ``ImageDraw.Draw.polygon``. -TODO API Additions ============= +ImageShow.XDGViewer +^^^^^^^^^^^^^^^^^^^ + +If ``xdg-open`` is present on Linux, this new :py:class:`PIL.ImageShow.Viewer` subclass +will be registered. It displays images using the application selected by the system. + +It is higher in priority than the other default :py:class:`PIL.ImageShow.Viewer` +instances, so it will be preferred by ``im.show()`` or :py:func:`.ImageShow.show()`. + Added support for "title" argument to DisplayViewer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From c202fc7f9304c425315e0241bd27bf7efdbbf08a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 24 Dec 2021 11:12:51 +1100 Subject: [PATCH 246/633] Replaced further direct invocations of setup.py --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 96d84f933..bdedc2bd5 100644 --- a/tox.ini +++ b/tox.ini @@ -11,8 +11,8 @@ minversion = 1.9 [testenv] commands = - {envpython} setup.py clean - {envpython} setup.py build_ext --inplace + make clean + {envpython} -m pip install --global-option="build_ext" --global-option="--inplace" . {envpython} selftest.py {envpython} -m pytest -W always {posargs} deps = From 73ccda9cd1275f1e7877d739a7461f6c0db4c012 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 26 Dec 2021 13:27:41 +1100 Subject: [PATCH 247/633] Do not compare properties to themselves --- Tests/test_image_convert.py | 2 +- Tests/test_image_quantize.py | 14 +++++++------- Tests/test_imagegrab.py | 22 ++++++++-------------- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 436a417d1..c26fc93bd 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -41,7 +41,7 @@ def test_sanity(): def test_default(): im = hopper("P") - assert_image(im, "P", im.size) + assert im.mode == "P" converted_im = im.convert() assert_image(converted_im, "RGB", im.size) converted_im = im.convert() diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index bd9db362c..27f4640e0 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -2,18 +2,18 @@ import pytest from PIL import Image -from .helper import assert_image, assert_image_similar, hopper, is_ppc64le +from .helper import assert_image_similar, hopper, is_ppc64le def test_sanity(): image = hopper() converted = image.quantize() - assert_image(converted, "P", converted.size) + assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 10) image = hopper() converted = image.quantize(palette=hopper("P")) - assert_image(converted, "P", converted.size) + assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 60) @@ -27,7 +27,7 @@ def test_libimagequant_quantize(): pytest.skip("libimagequant support not available") else: raise - assert_image(converted, "P", converted.size) + assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 15) assert len(converted.getcolors()) == 100 @@ -35,7 +35,7 @@ def test_libimagequant_quantize(): def test_octree_quantize(): image = hopper() converted = image.quantize(100, Image.FASTOCTREE) - assert_image(converted, "P", converted.size) + assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 20) assert len(converted.getcolors()) == 100 @@ -52,7 +52,7 @@ def test_quantize(): with Image.open("Tests/images/caption_6_33_22.png") as image: image = image.convert("RGB") converted = image.quantize() - assert_image(converted, "P", converted.size) + assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 1) @@ -62,7 +62,7 @@ def test_quantize_no_dither(): palette = palette.convert("P") converted = image.quantize(dither=0, palette=palette) - assert_image(converted, "P", converted.size) + assert converted.mode == "P" assert converted.palette.palette == palette.palette.palette diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index c36285451..fa2291582 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -6,7 +6,7 @@ import pytest from PIL import Image, ImageGrab -from .helper import assert_image, assert_image_equal_tofile, skip_unless_feature +from .helper import assert_image_equal_tofile, skip_unless_feature class TestImageGrab: @@ -14,25 +14,20 @@ class TestImageGrab: sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS" ) def test_grab(self): - for im in [ - ImageGrab.grab(), - ImageGrab.grab(include_layered_windows=True), - ImageGrab.grab(all_screens=True), - ]: - assert_image(im, im.mode, im.size) + ImageGrab.grab() + ImageGrab.grab(include_layered_windows=True) + ImageGrab.grab(all_screens=True) im = ImageGrab.grab(bbox=(10, 20, 50, 80)) - assert_image(im, im.mode, (40, 60)) + assert im.size == (40, 60) @skip_unless_feature("xcb") def test_grab_x11(self): try: if sys.platform not in ("win32", "darwin"): - im = ImageGrab.grab() - assert_image(im, im.mode, im.size) + ImageGrab.grab() - im2 = ImageGrab.grab(xdisplay="") - assert_image(im2, im2.mode, im2.size) + ImageGrab.grab(xdisplay="") except OSError as e: pytest.skip(str(e)) @@ -71,8 +66,7 @@ $bmp = New-Object Drawing.Bitmap 200, 200 assert str(e.value) == "ImageGrab.grabclipboard() is macOS and Windows only" return - im = ImageGrab.grabclipboard() - assert_image(im, im.mode, im.size) + ImageGrab.grabclipboard() @pytest.mark.skipif(sys.platform != "win32", reason="Windows only") def test_grabclipboard_file(self): From 0af91de452324ade77605abdcf82e7690689f28b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 27 Dec 2021 12:27:06 +1100 Subject: [PATCH 248/633] Image.NONE is only used for resampling and dithers --- src/PIL/Image.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0fca3fa5c..3fa0d7cad 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -138,8 +138,6 @@ def isImageType(t): # # Constants -NONE = 0 - # transpose FLIP_LEFT_RIGHT = 0 FLIP_TOP_BOTTOM = 1 From 422260544237ded803127dfcbd7100b73539ce9b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Dec 2021 23:29:12 +1100 Subject: [PATCH 249/633] Fixed freeing pointer --- Tests/test_imagedraw.py | 17 +++++++++++++++++ src/libImaging/Draw.c | 12 +++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 1423d9cbc..b661494c7 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -467,6 +467,23 @@ def test_shape2(): assert_image_equal_tofile(im, "Tests/images/imagedraw_shape2.png") +def test_transform(): + # Arrange + im = Image.new("RGB", (100, 100), "white") + expected = im.copy() + draw = ImageDraw.Draw(im) + + # Act + s = ImageDraw.Outline() + s.line(0, 0) + s.transform((0, 0, 0, 0, 0, 0)) + + draw.shape(s, fill=1) + + # Assert + assert_image_equal(im, expected) + + def helper_pieslice(bbox, start, end): # Arrange im = Image.new("RGB", (W, H)) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 69b804dee..0e4899b5b 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -1854,14 +1854,8 @@ ImagingOutlineTransform(ImagingOutline outline, double a[6]) { eIn = outline->edges; n = outline->count; - /* FIXME: ugly! */ - outline->edges = NULL; - outline->count = outline->size = 0; - eOut = allocate(outline, n); if (!eOut) { - outline->edges = eIn; - outline->count = outline->size = n; ImagingError_MemoryError(); return -1; } @@ -1897,7 +1891,11 @@ ImagingOutlineTransform(ImagingOutline outline, double a[6]) { eOut++; } - free(eIn); + free(outline->edges); + + /* FIXME: ugly! */ + outline->edges = NULL; + outline->count = outline->size = 0; return 0; } From 9bc02adcb41778b285448fc691ec6b6299d1b1d1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 27 Dec 2021 17:10:14 +1100 Subject: [PATCH 250/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e0f347d58..d10729b01 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Fixed freeing pointer in ImageDraw.Outline.transform #5909 + [radarhere] + - Added ImageShow support for xdg-open #5897 [m-shinder, radarhere] From 020308a7beee297cc767b315ee66976d35b281de Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 27 Dec 2021 16:11:43 +1100 Subject: [PATCH 251/633] Clarified that the sequence object for putdata() should be flattened --- src/PIL/Image.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 3fa0d7cad..3631bd869 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1705,13 +1705,14 @@ class Image: def putdata(self, data, scale=1.0, offset=0.0): """ - Copies pixel data to this image. This method copies data from a - sequence object into the image, starting at the upper left - corner (0, 0), and continuing until either the image or the - sequence ends. The scale and offset values are used to adjust - the sequence values: **pixel = value*scale + offset**. + Copies pixel data from a flattened sequence object into the image. The + values should start at the upper left corner (0, 0), continue to the + end of the line, followed directly by the first value of the second + line, and so on. Data will be read until either the image or the + sequence ends. The scale and offset values are used to adjust the + sequence values: **pixel = value*scale + offset**. - :param data: A sequence object. + :param data: A flattened sequence object. :param scale: An optional scale value. The default is 1.0. :param offset: An optional offset value. The default is 0.0. """ From e9294d890f08ad0337f9ffe013ac0baa17c81aa1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 27 Dec 2021 17:48:55 +1100 Subject: [PATCH 252/633] Accept float values for putdata() in Python 3.10 --- Tests/test_image_putdata.py | 6 ++++++ src/_imaging.c | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 54712fd6c..e8e754aaa 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -47,6 +47,12 @@ def test_pypy_performance(): im.putdata(list(range(256)) * 256) +def test_mode_with_L_with_float(): + im = Image.new("L", (1, 1), 0) + im.putdata([2.0]) + assert im.getpixel((0, 0)) == 2 + + def test_mode_i(): src = hopper("L") data = list(src.getdata()) diff --git a/src/_imaging.c b/src/_imaging.c index aba907f88..ef8466e86 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -1526,7 +1526,7 @@ _putdata(ImagingObject *self, PyObject *args) { /* Clipped data */ for (i = x = y = 0; i < n; i++) { op = PySequence_Fast_GET_ITEM(seq, i); - image->image8[y][x] = (UINT8)CLIP8(PyLong_AsLong(op)); + image->image8[y][x] = (UINT8)CLIP8((int)PyFloat_AsDouble(op)); if (++x >= (int)image->xsize) { x = 0, y++; } From e0d5417bcdffa63a080205ee7c7f7a20eb6d06da Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Dec 2021 09:38:10 +1100 Subject: [PATCH 253/633] Raise an error if sequence is not flattened --- Tests/test_image_putdata.py | 17 +++++++++++++++++ src/_imaging.c | 28 +++++++++++++++++++--------- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index e8e754aaa..7e4bbaaec 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -1,6 +1,8 @@ import sys from array import array +import pytest + from PIL import Image from .helper import assert_image_equal, hopper @@ -93,3 +95,18 @@ def test_array_F(): im.putdata(arr) assert len(im.getdata()) == len(arr) + + +def test_not_flattened(): + im = Image.new("L", (1, 1)) + with pytest.raises(TypeError): + im.putdata([[0]]) + with pytest.raises(TypeError): + im.putdata([[0]], 2) + + with pytest.raises(TypeError): + im = Image.new("I", (1, 1)) + im.putdata([[0]]) + with pytest.raises(TypeError): + im = Image.new("F", (1, 1)) + im.putdata([[0]]) diff --git a/src/_imaging.c b/src/_imaging.c index ef8466e86..2a42c0461 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -1494,6 +1494,14 @@ _putdata(ImagingObject *self, PyObject *args) { return NULL; } +#define set_value_to_item(seq, i) \ +op = PySequence_Fast_GET_ITEM(seq, i); \ +if (PySequence_Check(op)) { \ + PyErr_SetString(PyExc_TypeError, "sequence must be flattened"); \ + return NULL; \ +} else { \ + value = PyFloat_AsDouble(op); \ +} if (image->image8) { if (PyBytes_Check(data)) { unsigned char *p; @@ -1522,11 +1530,12 @@ _putdata(ImagingObject *self, PyObject *args) { PyErr_SetString(PyExc_TypeError, must_be_sequence); return NULL; } + double value; if (scale == 1.0 && offset == 0.0) { /* Clipped data */ for (i = x = y = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(seq, i); - image->image8[y][x] = (UINT8)CLIP8((int)PyFloat_AsDouble(op)); + set_value_to_item(seq, i); + image->image8[y][x] = (UINT8)CLIP8(value); if (++x >= (int)image->xsize) { x = 0, y++; } @@ -1535,9 +1544,8 @@ _putdata(ImagingObject *self, PyObject *args) { } else { /* Scaled and clipped data */ for (i = x = y = 0; i < n; i++) { - PyObject *op = PySequence_Fast_GET_ITEM(seq, i); - image->image8[y][x] = - CLIP8((int)(PyFloat_AsDouble(op) * scale + offset)); + set_value_to_item(seq, i); + image->image8[y][x] = CLIP8(value * scale + offset); if (++x >= (int)image->xsize) { x = 0, y++; } @@ -1555,9 +1563,10 @@ _putdata(ImagingObject *self, PyObject *args) { switch (image->type) { case IMAGING_TYPE_INT32: for (i = x = y = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(seq, i); + double value; + set_value_to_item(seq, i); IMAGING_PIXEL_INT32(image, x, y) = - (INT32)(PyFloat_AsDouble(op) * scale + offset); + (INT32)(value * scale + offset); if (++x >= (int)image->xsize) { x = 0, y++; } @@ -1566,9 +1575,10 @@ _putdata(ImagingObject *self, PyObject *args) { break; case IMAGING_TYPE_FLOAT32: for (i = x = y = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(seq, i); + double value; + set_value_to_item(seq, i); IMAGING_PIXEL_FLOAT32(image, x, y) = - (FLOAT32)(PyFloat_AsDouble(op) * scale + offset); + (FLOAT32)(value * scale + offset); if (++x >= (int)image->xsize) { x = 0, y++; } From dd8049363e16708293a7d19fec43bf08f8ea2667 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 11 Oct 2021 17:22:56 +0300 Subject: [PATCH 254/633] Use more specific regex chars to prevent ReDoS - exclude carriage return --- Tests/test_file_pdf.py | 5 +++-- src/PIL/PdfParser.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 40a027cc5..10daa414b 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -313,8 +313,9 @@ def test_pdf_append_to_bytesio(): @pytest.mark.timeout(1) -def test_redos(): - malicious = b" trailer<<>>" + b"\n" * 3456 +@pytest.mark.parametrize("newline", (b"\r", b"\n")) +def test_redos(newline): + malicious = b" trailer<<>>" + newline * 3456 # This particular exception isn't relevant here. # The important thing is it doesn't timeout, cause a ReDoS (CVE-2021-25292). diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index b95abbe2f..6ac9c7a7c 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -582,7 +582,8 @@ class PdfParser: whitespace_or_hex = br"[\000\011\012\014\015\0400-9a-fA-F]" whitespace_optional = whitespace + b"*" whitespace_mandatory = whitespace + b"+" - whitespace_optional_no_nl = br"[\000\011\014\015\040]*" # no "\012" aka "\n" + # No "\012" aka "\n" or "\015" aka "\r": + whitespace_optional_no_nl = br"[\000\011\014\040]*" newline_only = br"[\r\n]+" newline = whitespace_optional_no_nl + newline_only + whitespace_optional_no_nl re_trailer_end = re.compile( From 16167e82e6db0737cbc5bacfb1f7a207b01c69e0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Dec 2021 12:00:32 +1100 Subject: [PATCH 255/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d10729b01..7342116ed 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Exclude carriage return in PDF regex to help prevent ReDoS #5912 + [hugovk] + - Fixed freeing pointer in ImageDraw.Outline.transform #5909 [radarhere] From 4b7b07de701d210dbc65d7814eb2eda440b9dfe4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 22 Dec 2021 10:32:53 +1100 Subject: [PATCH 256/633] Fixed JPEG2000 I;16 images on big endian --- Tests/test_file_jpeg2k.py | 3 --- src/libImaging/Jpeg2KDecode.c | 6 ++++-- src/libImaging/Jpeg2KEncode.c | 19 ++++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 2ef262e3e..ca410162a 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -10,7 +10,6 @@ from .helper import ( assert_image_equal, assert_image_similar, assert_image_similar_tofile, - is_big_endian, skip_unless_feature, ) @@ -234,13 +233,11 @@ def test_16bit_monochrome_has_correct_mode(): assert jp2.mode == "I;16" -@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_16bit_monochrome_jp2_like_tiff(): with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: assert_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.jp2", 1e-3) -@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_16bit_monochrome_j2k_like_tiff(): with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: assert_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.j2k", 1e-3) diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index 601bd4b62..8f27d87d8 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -180,9 +180,11 @@ j2ku_gray_i( case 2: for (y = 0; y < h; ++y) { const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w]; - UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; for (x = 0; x < w; ++x) { - *row++ = j2ku_shift(offset + *data++, shift); + UINT16 pixel = j2ku_shift(offset + *data++, shift); + *row++ = pixel; + *row++ = pixel >> 8; } } break; diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c index 701853159..86cd7d5af 100644 --- a/src/libImaging/Jpeg2KEncode.c +++ b/src/libImaging/Jpeg2KEncode.c @@ -110,8 +110,15 @@ j2k_pack_i16(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsig for (y = 0; y < h; ++y) { UINT8 *data = (UINT8 *)(im->image[y + y0] + x0); for (x = 0; x < w; ++x) { - *ptr++ = *data++; - *ptr++ = *data++; +#ifdef WORDS_BIGENDIAN + ptr[0] = data[1]; + ptr[1] = data[0]; +#else + ptr[0] = data[0]; + ptr[1] = data[1]; +#endif + ptr += 2; + data += 2; } } } @@ -301,13 +308,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) { components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_l; - } else if (strcmp(im->mode, "I;16") == 0) { - components = 1; - color_space = OPJ_CLRSPC_GRAY; - pack = j2k_pack_i16; - prec = 16; - bpp = 12; - } else if (strcmp(im->mode, "I;16B") == 0) { + } else if (strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16B") == 0) { components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_i16; From aeb549ef8f4cb939c6dcfb36047712571282f6be Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Dec 2021 17:38:01 +1100 Subject: [PATCH 257/633] Fixed unpacking I;16B to I;16 on big endian --- Tests/test_file_png.py | 2 -- src/libImaging/Pack.c | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 9a5577bba..0869cc58b 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -13,7 +13,6 @@ from .helper import ( assert_image_equal, assert_image_equal_tofile, hopper, - is_big_endian, is_win32, mark_if_feature_version, skip_unless_feature, @@ -77,7 +76,6 @@ class TestFilePng: png.crc(cid, s) return chunks - @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_sanity(self, tmp_path): # internal version number diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index 2fdee919f..0c7c0497e 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -656,7 +656,11 @@ static struct { /* storage modes */ {"I;16", "I;16", 16, copy2}, +#ifdef WORDS_BIGENDIAN + {"I;16", "I;16B", 16, packI16N_I16}, +#else {"I;16", "I;16B", 16, packI16N_I16B}, +#endif {"I;16B", "I;16B", 16, copy2}, {"I;16L", "I;16L", 16, copy2}, {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian. From 44bd03fb6e5de3c756aac00f97619640b93e572c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Dec 2021 18:56:25 +1100 Subject: [PATCH 258/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7342116ed..e4bbb40b7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Improved putdata() documentation and data handling #5910 + [radarhere] + - Exclude carriage return in PDF regex to help prevent ReDoS #5912 [hugovk] From e61336681b29b565d418d8d371ac3547729db3dc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Dec 2021 19:17:31 +1100 Subject: [PATCH 259/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e4bbb40b7..15751394e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,24 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Improved I;16 operations on big endian #5901 + [radarhere] + +- Limit quantized palette to number of colors #5879 + [radarhere] + +- Fixed palette index for zeroed color in FASTOCTREE quantize #5869 + [radarhere] + +- When saving RGBA to GIF, make use of first transparent palette entry #5859 + [radarhere] + +- Pass SAMPLEFORMAT to libtiff #5848 + [radarhere] + +- Added rounding when converting P and PA #5824 + [radarhere] + - Improved putdata() documentation and data handling #5910 [radarhere] From 1998d12bb61e30763b07c01266ddc0165a54f8f4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 Dec 2021 00:11:20 +1100 Subject: [PATCH 260/633] Added sys import --- Tests/test_image.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index 85efd9fcd..4dde66f11 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,6 +1,7 @@ import io import os import shutil +import sys import tempfile import pytest From cdb0fba2ede3e6a62ab9d3d2ce87cc35f40ce9eb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 Dec 2021 11:45:40 +1100 Subject: [PATCH 261/633] Removed redundant part of condition --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d5113a71f..e5ea25fc4 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -812,7 +812,7 @@ class Image: palette_length = self.im.putpalette(mode, arr) self.palette.dirty = 0 self.palette.rawmode = None - if "transparency" in self.info and mode in ("RGBA", "LA", "PA"): + if "transparency" in self.info and mode in ("LA", "PA"): if isinstance(self.info["transparency"], int): self.im.putpalettealpha(self.info["transparency"], 0) else: From 8e9da1559b71a01380d3324e3ae12396fd07e395 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 Dec 2021 14:47:59 +1100 Subject: [PATCH 262/633] Lint fix --- src/PIL/BlpImagePlugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 6909599c1..f968ed851 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -370,7 +370,6 @@ class BLP1Decoder(_BLPBaseDecoder): a = ImageOps.invert(a) - image = Image.merge("RGBA", (r, g, b, a)) self.set_as_raw(image.tobytes()) From bd05a8dd13dd00d2af2ef7822d6cb95e181fce63 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 Dec 2021 15:42:30 +1100 Subject: [PATCH 263/633] Updated libimagequant to 2.17.0 --- winbuild/build_prepare.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index b1c95aa74..0589baf21 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -257,10 +257,10 @@ deps = { "libs": [r"bin\*.lib"], }, "libimagequant": { - # commit: Merge branch 'master' into msvc (matches 2.16.0 tag) - "url": "https://github.com/ImageOptim/libimagequant/archive/f41ee301ff3a407b16991af3dbe03910919bbdc3.zip", # noqa: E501 - "filename": "libimagequant-f41ee301ff3a407b16991af3dbe03910919bbdc3.zip", - "dir": "libimagequant-f41ee301ff3a407b16991af3dbe03910919bbdc3", + # commit: Merge branch 'master' into msvc (matches 2.17.0 tag) + "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", # noqa: E501 + "filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip", + "dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab", "patch": { "CMakeLists.txt": { "if(OPENMP_FOUND)": "if(false)", From 949b431f03cca69e9213f2169248a2be2eed119c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 31 Dec 2021 18:09:44 +1100 Subject: [PATCH 264/633] Added release notes for pillow-wheels#237 --- docs/releasenotes/9.0.0.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index edb2463c1..ec5208fde 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -116,6 +116,12 @@ possible for there to be too many colors to fit in a P mode image. To allow for seeking to any subsequent GIF frame will now convert the image to RGB or RGBA, depending on whether or not the first frame had transparency. +Switched to libjpeg-turbo in macOS and Linux wheels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Pillow wheels from PyPI for macOS and Linux have switched from libjpeg to +libjpeg-turbo. It is a fork of libjpeg, popular for its speed. + Added support for pickling TrueType fonts ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 0bb3f87dcc72a1a80f50690abde5a3ac4576d3d6 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 1 Jan 2022 10:40:57 +1100 Subject: [PATCH 265/633] Updated copyright year --- LICENSE | 2 +- docs/COPYING | 2 +- docs/conf.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 1197291bc..40aabc323 100644 --- a/LICENSE +++ b/LICENSE @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2010-2021 by Alex Clark and contributors + Copyright © 2010-2022 by Alex Clark and contributors Like PIL, Pillow is licensed under the open source HPND License: diff --git a/docs/COPYING b/docs/COPYING index f2466d659..25f03b343 100644 --- a/docs/COPYING +++ b/docs/COPYING @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2010-2021 by Alex Clark and contributors + Copyright © 2010-2022 by Alex Clark and contributors Like PIL, Pillow is licensed under the open source PIL Software License: diff --git a/docs/conf.py b/docs/conf.py index 5c797b21c..7bbe8c4c9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,7 +53,7 @@ master_doc = "index" # General information about the project. project = "Pillow (PIL Fork)" -copyright = "1995-2011 Fredrik Lundh, 2010-2021 Alex Clark and Contributors" +copyright = "1995-2011 Fredrik Lundh, 2010-2022 Alex Clark and Contributors" author = "Fredrik Lundh, Alex Clark and Contributors" # The version info for the project you're documenting, acts as replacement for From 7370a0b1cf30849ee132926e93b2084970a9033d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Jan 2022 14:54:23 +1100 Subject: [PATCH 266/633] Remove consecutive duplicates that only differ by their offset --- Tests/images/timeout-6646305047838720 | Bin 0 -> 69487 bytes Tests/test_file_tiff.py | 10 +++++++++- src/PIL/ImageFile.py | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Tests/images/timeout-6646305047838720 diff --git a/Tests/images/timeout-6646305047838720 b/Tests/images/timeout-6646305047838720 new file mode 100644 index 0000000000000000000000000000000000000000..eae1f333a031a2dec28b29961e620103daf64452 GIT binary patch literal 69487 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 literal 0 HcmV?d00001 diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 072f7d401..5801e1766 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -3,7 +3,7 @@ from io import BytesIO import pytest -from PIL import Image, TiffImagePlugin +from PIL import Image, ImageFile, TiffImagePlugin from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from .helper import ( @@ -726,6 +726,14 @@ class TestFileTiff: with pytest.raises(OSError): im.load() + @pytest.mark.timeout(6) + @pytest.mark.filterwarnings("ignore:Truncated File Read") + def test_timeout(self): + with Image.open("Tests/images/timeout-6646305047838720") as im: + ImageFile.LOAD_TRUNCATED_IMAGES = True + im.load() + ImageFile.LOAD_TRUNCATED_IMAGES = False + @pytest.mark.skipif(not is_win32(), reason="Windows only") class TestFileTiffW32: diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index d43667ca0..3374a5b1d 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -28,6 +28,7 @@ # import io +import itertools import struct import sys @@ -210,6 +211,13 @@ class ImageFile(Image.Image): except AttributeError: prefix = b"" + # Remove consecutive duplicates that only differ by their offset + self.tile = [ + list(tiles)[-1] + for _, tiles in itertools.groupby( + self.tile, lambda tile: (tile[0], tile[1], tile[3]) + ) + ] for decoder_name, extents, offset, args in self.tile: decoder = Image._getdecoder( self.mode, decoder_name, args, self.decoderconfig From 1e092419b6806495c683043ab3feb6ce264f3b9c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 6 Dec 2021 22:24:19 +1100 Subject: [PATCH 267/633] Initialize coordinates to zero --- Tests/test_imagepath.py | 1 + src/path.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 0835fdb43..cd850bb1c 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -90,6 +90,7 @@ def test_path_odd_number_of_coordinates(): [ ([0, 1, 2, 3], (0.0, 1.0, 2.0, 3.0)), ([3, 2, 1, 0], (1.0, 0.0, 3.0, 2.0)), + (1, (0.0, 0.0, 0.0, 0.0)), ], ) def test_getbbox(coords, expected): diff --git a/src/path.c b/src/path.c index 4764c58aa..64c767cb8 100644 --- a/src/path.c +++ b/src/path.c @@ -57,7 +57,7 @@ alloc_array(Py_ssize_t count) { if ((unsigned long long)count > (SIZE_MAX / (2 * sizeof(double))) - 1) { return ImagingError_MemoryError(); } - xy = malloc(2 * count * sizeof(double) + 1); + xy = calloc(2 * count * sizeof(double) + 1, sizeof(double)); if (!xy) { ImagingError_MemoryError(); } From c48271ab354db49cdbd740bc45e13be4f0f7993c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 6 Dec 2021 22:25:14 +1100 Subject: [PATCH 268/633] Handle case where path count is zero --- Tests/test_imagepath.py | 1 + src/path.c | 33 +++++++++++++++++++-------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index cd850bb1c..b18271cc5 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -90,6 +90,7 @@ def test_path_odd_number_of_coordinates(): [ ([0, 1, 2, 3], (0.0, 1.0, 2.0, 3.0)), ([3, 2, 1, 0], (1.0, 0.0, 3.0, 2.0)), + (0, (0.0, 0.0, 0.0, 0.0)), (1, (0.0, 0.0, 0.0, 0.0)), ], ) diff --git a/src/path.c b/src/path.c index 64c767cb8..dea274ee3 100644 --- a/src/path.c +++ b/src/path.c @@ -327,21 +327,26 @@ path_getbbox(PyPathObject *self, PyObject *args) { xy = self->xy; - x0 = x1 = xy[0]; - y0 = y1 = xy[1]; + if (self->count == 0) { + x0 = x1 = 0; + y0 = y1 = 0; + } else { + x0 = x1 = xy[0]; + y0 = y1 = xy[1]; - for (i = 1; i < self->count; i++) { - if (xy[i + i] < x0) { - x0 = xy[i + i]; - } - if (xy[i + i] > x1) { - x1 = xy[i + i]; - } - if (xy[i + i + 1] < y0) { - y0 = xy[i + i + 1]; - } - if (xy[i + i + 1] > y1) { - y1 = xy[i + i + 1]; + for (i = 1; i < self->count; i++) { + if (xy[i + i] < x0) { + x0 = xy[i + i]; + } + if (xy[i + i] > x1) { + x1 = xy[i + i]; + } + if (xy[i + i + 1] < y0) { + y0 = xy[i + i + 1]; + } + if (xy[i + i + 1] > y1) { + y1 = xy[i + i + 1]; + } } } From 525c840b0f8334118ae9a032cc7be2bffd4b8d46 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Jan 2022 20:15:06 +1100 Subject: [PATCH 269/633] Updated path after pillow-wheels switched to libjpeg-turbo --- Tests/oss-fuzz/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/oss-fuzz/build.sh b/Tests/oss-fuzz/build.sh index 513136fff..09cc7bc16 100755 --- a/Tests/oss-fuzz/build.sh +++ b/Tests/oss-fuzz/build.sh @@ -22,7 +22,7 @@ for fuzzer in $(find $SRC -name 'fuzz_*.py'); do fuzzer_basename=$(basename -s .py $fuzzer) fuzzer_package=${fuzzer_basename}.pkg pyinstaller \ - --add-binary /usr/local/lib/libjpeg.so.9:. \ + --add-binary /usr/local/lib/libjpeg.so.62.3.0:. \ --add-binary /usr/local/lib/libfreetype.so.6:. \ --add-binary /usr/local/lib/liblcms2.so.2:. \ --add-binary /usr/local/lib/libopenjp2.so.7:. \ From 1059eb537639925c96d3245dcd73c106d4266c83 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Jan 2022 21:04:32 +1100 Subject: [PATCH 270/633] If appended EOI did not work, do not keep trying --- Tests/test_file_jpeg.py | 24 ++++++++++++++++++++++++ src/PIL/JpegImagePlugin.py | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index f2002ecb8..4b2ffe70d 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -870,6 +870,30 @@ class TestFileJpeg: with Image.open("Tests/images/hopper.jpg") as im: assert im.getxmp() == {} + @pytest.mark.timeout(timeout=1) + def test_eof(self): + # Even though this decoder never says that it is finished + # the image should still end when there is no new data + class InfiniteMockPyDecoder(ImageFile.PyDecoder): + def decode(self, buffer): + return 0, 0 + + decoder = InfiniteMockPyDecoder(None) + + def closure(mode, *args): + decoder.__init__(mode, *args) + return decoder + + Image.register_decoder("INFINITE", closure) + + with Image.open(TEST_FILE) as im: + im.tile = [ + ("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)), + ] + ImageFile.LOAD_TRUNCATED_IMAGES = True + im.load() + ImageFile.LOAD_TRUNCATED_IMAGES = False + @pytest.mark.skipif(not is_win32(), reason="Windows only") @skip_unless_feature("jpg") diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index a4fc5936b..ccdcc20a8 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -401,9 +401,10 @@ class JpegImageFile(ImageFile.ImageFile): """ s = self.fp.read(read_bytes) - if not s and ImageFile.LOAD_TRUNCATED_IMAGES: + if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, "_ended"): # Premature EOF. # Pretend file is finished adding EOI marker + self._ended = True return b"\xFF\xD9" return s From 032d2dc3658f94718109068ac70799313e440754 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Jan 2022 22:00:12 +1100 Subject: [PATCH 271/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 15751394e..45a087322 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,15 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Ensure JpegImagePlugin stops at the end of a truncated file #5921 + [radarhere] + +- Fixed ImagePath.Path array handling #5920 + [radarhere] + +- Remove consecutive duplicate tiles that only differ by their offset #5919 + [radarhere] + - Improved I;16 operations on big endian #5901 [radarhere] From f6c78713a491764dfac576f6c42127755f2c62b3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Jan 2022 22:10:48 +1100 Subject: [PATCH 272/633] Added release notes for #5919, #5920 and #5921 --- docs/releasenotes/9.0.0.rst | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index ec5208fde..e778b20b6 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -100,10 +100,28 @@ argument will also now be supported, e.g. ``im.show(title="My Image")`` and Security ======== -TODO -^^^^ +Ensure JpegImagePlugin stops at the end of a truncated file +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +``JpegImagePlugin`` may append an EOF marker to the end of a truncated file, so that +the last segment of the data will still be processed by the decoder. + +If the EOF marker is not detected as such however, this could lead to an infinite +loop where ``JpegImagePlugin`` keeps trying to end the file. + +Remove consecutive duplicate tiles that only differ by their offset +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To prevent attempts to slow down loading times for images, if an image has consecutive +duplicate tiles that only differ by their offset, only load the last tile. Credit to +Google's `OSS-Fuzz`_ project for finding this issue. + +Fixed ImagePath.Path array handling +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +CWE-126 and CWE-665 were found when initializing ``ImagePath.Path``. + +.. _OSS-Fuzz: https://github.com/google/oss-fuzz Other Changes ============= From 8531b01d6cdf0b70f256f93092caa2a5d91afc11 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 2 Jan 2022 17:23:49 +1100 Subject: [PATCH 273/633] Restrict builtins for ImageMath.eval --- Tests/test_imagemath.py | 7 +++++++ docs/releasenotes/9.0.0.rst | 8 ++++++++ src/PIL/ImageMath.py | 7 ++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index e7afd1abf..25811aa89 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -1,3 +1,5 @@ +import pytest + from PIL import Image, ImageMath @@ -50,6 +52,11 @@ def test_ops(): assert pixel(ImageMath.eval("float(B)**33", images)) == "F 8589934592.0" +def test_prevent_exec(): + with pytest.raises(ValueError): + ImageMath.eval("exec('pass')") + + def test_logical(): assert pixel(ImageMath.eval("not A", images)) == 0 assert pixel(ImageMath.eval("A and B", images)) == "L 2" diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index e778b20b6..fb542636e 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -116,6 +116,14 @@ To prevent attempts to slow down loading times for images, if an image has conse duplicate tiles that only differ by their offset, only load the last tile. Credit to Google's `OSS-Fuzz`_ project for finding this issue. +Restrict builtins available to ImageMath.eval +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To limit :py:class:`PIL.ImageMath` to working with images, Pillow will now restrict the +builtins available to :py:meth:`PIL.ImageMath.eval`. This will help prevent problems +arising if users evaluate arbitrary expressions, such as +``ImageMath.eval("exec(exit())")``. + Fixed ImagePath.Path array handling ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 7f9c88e14..06bea800d 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -246,7 +246,12 @@ def eval(expression, _dict={}, **kw): if hasattr(v, "im"): args[k] = _Operand(v) - out = builtins.eval(expression, args) + code = compile(expression, "", "eval") + for name in code.co_names: + if name not in args and name != "abs": + raise ValueError(f"'{name}' not allowed") + + out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args) try: return out.im except AttributeError: From ed4cf7813777ad8478cac46f448bc45416a2a99e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 2 Jan 2022 18:09:45 +1100 Subject: [PATCH 274/633] CVEs TBD --- CHANGES.rst | 5 ++++- docs/releasenotes/9.0.0.rst | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 45a087322..b4cdeced6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,10 +5,13 @@ Changelog (Pillow) 9.0.0 (unreleased) ------------------ +- Restrict builtins for ImageMath.eval(). CVE TBD #5923 + [radarhere] + - Ensure JpegImagePlugin stops at the end of a truncated file #5921 [radarhere] -- Fixed ImagePath.Path array handling #5920 +- Fixed ImagePath.Path array handling. CVEs TBD #5920 [radarhere] - Remove consecutive duplicate tiles that only differ by their offset #5919 diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index fb542636e..f2be128bb 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -122,12 +122,12 @@ Restrict builtins available to ImageMath.eval To limit :py:class:`PIL.ImageMath` to working with images, Pillow will now restrict the builtins available to :py:meth:`PIL.ImageMath.eval`. This will help prevent problems arising if users evaluate arbitrary expressions, such as -``ImageMath.eval("exec(exit())")``. +``ImageMath.eval("exec(exit())")``. CVE TBD Fixed ImagePath.Path array handling ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -CWE-126 and CWE-665 were found when initializing ``ImagePath.Path``. +CWE-126 and CWE-665 were found when initializing ``ImagePath.Path``. CVEs TBD .. _OSS-Fuzz: https://github.com/google/oss-fuzz From 82541b6dec8452cb612067fcebba1c5a1a2bfdc8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 2 Jan 2022 20:51:23 +1100 Subject: [PATCH 275/633] 9.0.0 version bump --- CHANGES.rst | 2 +- src/PIL/_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index b4cdeced6..c2d4892cb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,7 +2,7 @@ Changelog (Pillow) ================== -9.0.0 (unreleased) +9.0.0 (2022-01-02) ------------------ - Restrict builtins for ImageMath.eval(). CVE TBD #5923 diff --git a/src/PIL/_version.py b/src/PIL/_version.py index 43e3e4768..0ee7ac8c5 100644 --- a/src/PIL/_version.py +++ b/src/PIL/_version.py @@ -1,2 +1,2 @@ # Master version for Pillow -__version__ = "9.0.0.dev0" +__version__ = "9.0.0" From 05b63366df05ebe8dd182f4e1596b23582631b3d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Jan 2022 09:22:31 +1100 Subject: [PATCH 276/633] 9.1.0.dev0 version bump --- src/PIL/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/_version.py b/src/PIL/_version.py index 0ee7ac8c5..62c7dba55 100644 --- a/src/PIL/_version.py +++ b/src/PIL/_version.py @@ -1,2 +1,2 @@ # Master version for Pillow -__version__ = "9.0.0" +__version__ = "9.1.0.dev0" From 3446537c60a0678761dc916603c6220370ad3834 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 17:10:44 +0000 Subject: [PATCH 277/633] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 911470a610e47d9da5ea938b0887c3df62819b85 → f1d4e742c91dd5179d742b0db9293c4472b765f8](https://github.com/psf/black/compare/911470a610e47d9da5ea938b0887c3df62819b85...f1d4e742c91dd5179d742b0db9293c4472b765f8) - [github.com/PyCQA/isort: fd5ba70665a37ec301a1f714ed09336048b3be63 → c5e8fa75dda5f764d20f66a215d71c21cfa198e1](https://github.com/PyCQA/isort/compare/fd5ba70665a37ec301a1f714ed09336048b3be63...c5e8fa75dda5f764d20f66a215d71c21cfa198e1) - [github.com/asottile/yesqa: 644ede78511c02fc6f8e03e014cc1ddcfbf1e1f5 → 35cf7dc24fa922927caded7a21b2a8cb04bf8e10](https://github.com/asottile/yesqa/compare/644ede78511c02fc6f8e03e014cc1ddcfbf1e1f5...35cf7dc24fa922927caded7a21b2a8cb04bf8e10) - [github.com/PyCQA/flake8: dcd740bc0ebaf2b3d43e59a0060d157c97de13f3 → cbeb4c9c4137cff1568659fcc48e8b85cddd0c8d](https://github.com/PyCQA/flake8/compare/dcd740bc0ebaf2b3d43e59a0060d157c97de13f3...cbeb4c9c4137cff1568659fcc48e8b85cddd0c8d) - [github.com/pre-commit/pre-commit-hooks: 38b88246ccc552bffaaf54259d064beeee434539 → 8fe62d14e0b4d7d845a7022c5c2c3ae41bdd3f26](https://github.com/pre-commit/pre-commit-hooks/compare/38b88246ccc552bffaaf54259d064beeee434539...8fe62d14e0b4d7d845a7022c5c2c3ae41bdd3f26) --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5f1d16709..822fa43ca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: 911470a610e47d9da5ea938b0887c3df62819b85 # frozen: 21.9b0 + rev: f1d4e742c91dd5179d742b0db9293c4472b765f8 # frozen: 21.12b0 hooks: - id: black args: ["--target-version", "py37"] @@ -9,12 +9,12 @@ repos: types: [] - repo: https://github.com/PyCQA/isort - rev: fd5ba70665a37ec301a1f714ed09336048b3be63 # frozen: 5.9.3 + rev: c5e8fa75dda5f764d20f66a215d71c21cfa198e1 # frozen: 5.10.1 hooks: - id: isort - repo: https://github.com/asottile/yesqa - rev: 644ede78511c02fc6f8e03e014cc1ddcfbf1e1f5 # frozen: v1.2.3 + rev: 35cf7dc24fa922927caded7a21b2a8cb04bf8e10 # frozen: v1.3.0 hooks: - id: yesqa @@ -25,7 +25,7 @@ repos: exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$) - repo: https://github.com/PyCQA/flake8 - rev: dcd740bc0ebaf2b3d43e59a0060d157c97de13f3 # frozen: 3.9.2 + rev: cbeb4c9c4137cff1568659fcc48e8b85cddd0c8d # frozen: 4.0.1 hooks: - id: flake8 additional_dependencies: [flake8-2020, flake8-implicit-str-concat] @@ -37,7 +37,7 @@ repos: - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: 38b88246ccc552bffaaf54259d064beeee434539 # frozen: v4.0.1 + rev: 8fe62d14e0b4d7d845a7022c5c2c3ae41bdd3f26 # frozen: v4.1.0 hooks: - id: check-merge-conflict - id: check-yaml From 52f29a537d5d979d7aa5e30a629beb67139fde20 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 17:11:30 +0000 Subject: [PATCH 278/633] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/helper.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 8504993fb..feccce6bc 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -30,7 +30,6 @@ if os.environ.get("SHOW_ERRORS", None): a.show() b.show() - elif "GITHUB_ACTIONS" in os.environ: HAS_UPLOADER = True @@ -44,7 +43,6 @@ elif "GITHUB_ACTIONS" in os.environ: b.save(os.path.join(tmpdir, "b.png")) return tmpdir - else: try: import test_image_results From e077229d7a165cade8c3538b3371f344fa28fc38 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 4 Jan 2022 11:32:15 +0200 Subject: [PATCH 279/633] Remove readonly from Image.__eq__ --- src/PIL/Image.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index e5ea25fc4..0bafd3907 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -627,7 +627,6 @@ class Image: and self.size == other.size and self.info == other.info and self._category == other._category - and self.readonly == other.readonly and self.getpalette() == other.getpalette() and self.tobytes() == other.tobytes() ) From 5a35e7baee6875123459e1f40827565d65e28416 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 6 Jan 2022 06:19:51 +1100 Subject: [PATCH 280/633] Replaced direct invocation of setup.py --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0dac63d39..44ab2ef1c 100644 --- a/Makefile +++ b/Makefile @@ -112,7 +112,7 @@ valgrind: .PHONY: readme readme: - python3 setup.py --long-description | markdown2 > .long-description.html && open .long-description.html + markdown2 README.md > .long-description.html && open .long-description.html .PHONY: lint From f40c005399e393eab5fec960247afe454fa072a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Wallk=C3=B6tter?= Date: Thu, 6 Jan 2022 07:58:57 +0100 Subject: [PATCH 281/633] Add missing ImageModes The modes are mentioned in the docs, but weren't part of ImageMode. --- src/PIL/ImageMode.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py index 0afcf9fe1..ae4ada75a 100644 --- a/src/PIL/ImageMode.py +++ b/src/PIL/ImageMode.py @@ -52,6 +52,11 @@ def getmode(mode): "HSV": ("RGB", "L", ("H", "S", "V")), # extra experimental modes "RGBa": ("RGB", "L", ("R", "G", "B", "a")), + "BGR": ("BGR", "L", ("B", "G", "R")), + "BGR;15": ("RGB", "L", ("B", "G", "R")), + "BGR;16": ("RGB", "L", ("B", "G", "R")), + "BGR;24": ("RGB", "L", ("B", "G", "R")), + "BGR;32": ("RGB", "L", ("B", "G", "R")), "LA": ("L", "L", ("L", "A")), "La": ("L", "L", ("L", "a")), "PA": ("RGB", "L", ("P", "A")), From fbe396f22aeb87c1987287681035714d348485ee Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 7 Jan 2022 12:34:58 +1100 Subject: [PATCH 282/633] Removed duplicate check --- Tests/test_image_mode.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py index 7f92c2264..0232a5536 100644 --- a/Tests/test_image_mode.py +++ b/Tests/test_image_mode.py @@ -65,6 +65,5 @@ def test_properties(): check("RGB", "RGB", "L", 3, ("R", "G", "B")) check("RGBA", "RGB", "L", 4, ("R", "G", "B", "A")) check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")) - check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")) check("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K")) check("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr")) From b5160591bc9da2425ff15b632215139eafc2cce6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 7 Jan 2022 16:29:38 +1100 Subject: [PATCH 283/633] Return an empty bytestring from tobytes() for an empty image --- Tests/test_image.py | 5 +++++ src/PIL/Image.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index 4dde66f11..2d46d760d 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -786,6 +786,11 @@ class TestImage: 34665: 196, } + @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) + def test_zero_tobytes(self, size): + im = Image.new("RGB", size) + assert im.tobytes() == b"" + def test_categories_deprecation(self): with pytest.warns(DeprecationWarning): assert hopper().category == 0 diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0bafd3907..69089a290 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -716,6 +716,9 @@ class Image: self.load() + if self.width == 0 or self.height == 0: + return b"" + # unpack data e = _getencoder(self.mode, encoder_name, args) e.setimage(self.im) From b2c6db8d3b0bf759a8611300a55f3d509d7b23e6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Fri, 7 Jan 2022 22:48:26 +0200 Subject: [PATCH 284/633] Add CVE IDs --- CHANGES.rst | 4 ++-- docs/releasenotes/9.0.0.rst | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index c2d4892cb..de3e9b9ca 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,13 +5,13 @@ Changelog (Pillow) 9.0.0 (2022-01-02) ------------------ -- Restrict builtins for ImageMath.eval(). CVE TBD #5923 +- Restrict builtins for ImageMath.eval(). CVE-2022-22817 #5923 [radarhere] - Ensure JpegImagePlugin stops at the end of a truncated file #5921 [radarhere] -- Fixed ImagePath.Path array handling. CVEs TBD #5920 +- Fixed ImagePath.Path array handling. CVE-2022-22815, CVE-2022-22816 #5920 [radarhere] - Remove consecutive duplicate tiles that only differ by their offset #5919 diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index f2be128bb..fbf2e7ce4 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -119,15 +119,16 @@ Google's `OSS-Fuzz`_ project for finding this issue. Restrict builtins available to ImageMath.eval ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -To limit :py:class:`PIL.ImageMath` to working with images, Pillow will now restrict the -builtins available to :py:meth:`PIL.ImageMath.eval`. This will help prevent problems -arising if users evaluate arbitrary expressions, such as -``ImageMath.eval("exec(exit())")``. CVE TBD +:cve:`CVE-2022-22817`: To limit :py:class:`PIL.ImageMath` to working with images, Pillow +will now restrict the builtins available to :py:meth:`PIL.ImageMath.eval`. This will +help prevent problems arising if users evaluate arbitrary expressions, such as +``ImageMath.eval("exec(exit())")``. Fixed ImagePath.Path array handling ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -CWE-126 and CWE-665 were found when initializing ``ImagePath.Path``. CVEs TBD +:cve:`CVE-2022-22815` (CWE-126) and :cve:`CVE-2022-22816` (CWE-665) were found when +initializing ``ImagePath.Path``. .. _OSS-Fuzz: https://github.com/google/oss-fuzz From 2d9dfefe6ecdd783fb8713b0810c851ef9a364bd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Jan 2022 13:47:51 +1100 Subject: [PATCH 285/633] Added specific error if coordinate type is incorrect --- Tests/test_imagepath.py | 4 ++- src/path.c | 62 +++++++++++++++-------------------------- 2 files changed, 25 insertions(+), 41 deletions(-) diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index b18271cc5..e38a2068a 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -70,9 +70,11 @@ def test_invalid_coords(): coords = ["a", "b"] # Act / Assert - with pytest.raises(SystemError): + with pytest.raises(ValueError) as e: ImagePath.Path(coords) + assert str(e.value) == "incorrect coordinate type" + def test_path_odd_number_of_coordinates(): # Arrange diff --git a/src/path.c b/src/path.c index dea274ee3..e2bdc9419 100644 --- a/src/path.c +++ b/src/path.c @@ -162,42 +162,37 @@ PyPath_Flatten(PyObject *data, double **pxy) { return -1; } +#define assign_item_to_array(op, decref) \ +if (PyFloat_Check(op)) { \ + xy[j++] = PyFloat_AS_DOUBLE(op); \ +} else if (PyLong_Check(op)) { \ + xy[j++] = (float)PyLong_AS_LONG(op); \ +} else if (PyNumber_Check(op)) { \ + xy[j++] = PyFloat_AsDouble(op); \ +} else if (PyArg_ParseTuple(op, "dd", &x, &y)) { \ + xy[j++] = x; \ + xy[j++] = y; \ +} else { \ + PyErr_SetString(PyExc_ValueError, "incorrect coordinate type"); \ + if (decref) { \ + Py_DECREF(op); \ + } \ + free(xy); \ + return -1; \ +} + /* Copy table to path array */ if (PyList_Check(data)) { for (i = 0; i < n; i++) { double x, y; PyObject *op = PyList_GET_ITEM(data, i); - if (PyFloat_Check(op)) { - xy[j++] = PyFloat_AS_DOUBLE(op); - } else if (PyLong_Check(op)) { - xy[j++] = (float)PyLong_AS_LONG(op); - } else if (PyNumber_Check(op)) { - xy[j++] = PyFloat_AsDouble(op); - } else if (PyArg_ParseTuple(op, "dd", &x, &y)) { - xy[j++] = x; - xy[j++] = y; - } else { - free(xy); - return -1; - } + assign_item_to_array(op, 0); } } else if (PyTuple_Check(data)) { for (i = 0; i < n; i++) { double x, y; PyObject *op = PyTuple_GET_ITEM(data, i); - if (PyFloat_Check(op)) { - xy[j++] = PyFloat_AS_DOUBLE(op); - } else if (PyLong_Check(op)) { - xy[j++] = (float)PyLong_AS_LONG(op); - } else if (PyNumber_Check(op)) { - xy[j++] = PyFloat_AsDouble(op); - } else if (PyArg_ParseTuple(op, "dd", &x, &y)) { - xy[j++] = x; - xy[j++] = y; - } else { - free(xy); - return -1; - } + assign_item_to_array(op, 0); } } else { for (i = 0; i < n; i++) { @@ -213,20 +208,7 @@ PyPath_Flatten(PyObject *data, double **pxy) { return -1; } } - if (PyFloat_Check(op)) { - xy[j++] = PyFloat_AS_DOUBLE(op); - } else if (PyLong_Check(op)) { - xy[j++] = (float)PyLong_AS_LONG(op); - } else if (PyNumber_Check(op)) { - xy[j++] = PyFloat_AsDouble(op); - } else if (PyArg_ParseTuple(op, "dd", &x, &y)) { - xy[j++] = x; - xy[j++] = y; - } else { - Py_DECREF(op); - free(xy); - return -1; - } + assign_item_to_array(op, 1); Py_DECREF(op); } } From 6e5e45a9ecb119c1c80f84ae32cb1fdb9c9e578d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 9 Jan 2022 16:05:48 +1100 Subject: [PATCH 286/633] Ensure duplicated file pointer is closed --- src/PIL/TiffImagePlugin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 5df5c4f4c..e54082fec 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1234,6 +1234,12 @@ class TiffImageFile(ImageFile.ImageFile): # UNDONE -- so much for that buffer size thing. n, err = decoder.decode(self.fp.read()) + if fp: + try: + os.close(fp) + except OSError: + pass + self.tile = [] self.readonly = 0 From 2d7c1bcc9d01f7424e9d10b0df659354734fe40b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Jan 2022 16:42:26 +1100 Subject: [PATCH 287/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index de3e9b9ca..49ab72962 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,21 @@ Changelog (Pillow) ================== +9.1.0 (unreleased) +------------------ + +- Ensure duplicated file pointer is closed #5946 + [radarhere] + +- Added specific error if ImagePath coordinate type is incorrect #5942 + [radarhere] + +- Return an empty bytestring from tobytes() for an empty image #5938 + [radarhere] + +- Remove readonly from Image.__eq__ #5930 + [hugovk] + 9.0.0 (2022-01-02) ------------------ From 11dd54383777c53dae5564e35f4e38476f161b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Wallk=C3=B6tter?= Date: Mon, 10 Jan 2022 08:17:53 +0100 Subject: [PATCH 288/633] remove mode BGR --- src/PIL/ImageMode.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py index ae4ada75a..e6bf0bb10 100644 --- a/src/PIL/ImageMode.py +++ b/src/PIL/ImageMode.py @@ -52,7 +52,6 @@ def getmode(mode): "HSV": ("RGB", "L", ("H", "S", "V")), # extra experimental modes "RGBa": ("RGB", "L", ("R", "G", "B", "a")), - "BGR": ("BGR", "L", ("B", "G", "R")), "BGR;15": ("RGB", "L", ("B", "G", "R")), "BGR;16": ("RGB", "L", ("B", "G", "R")), "BGR;24": ("RGB", "L", ("B", "G", "R")), From 90f3f72e1925622690d3d1e18dcae71ae794ba3e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Jan 2022 13:30:51 +1100 Subject: [PATCH 289/633] Removed unused constant --- src/PIL/ImageMath.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 06bea800d..b4579081b 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -19,8 +19,6 @@ import builtins from . import Image, _imagingmath -VERBOSE = 0 - def _isconstant(v): return isinstance(v, (int, float)) From 9c6df29b4ddc9a065d6c7d5c451f21d9bb9af34a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Jan 2022 13:42:58 +1100 Subject: [PATCH 290/633] Removed unreachable error --- src/PIL/ImageMath.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index b4579081b..8396148e8 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -67,8 +67,6 @@ class _Operand: im1 = im1.convert("F") if im2.mode != "F": im2 = im2.convert("F") - if im1.mode != im2.mode: - raise ValueError("mode mismatch") if im1.size != im2.size: # crop both arguments to a common size size = (min(im1.size[0], im2.size[0]), min(im1.size[1], im2.size[1])) From 01e7b3943d6ac4c208c3f6da8df27178e1e09155 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Jan 2022 13:50:00 +1100 Subject: [PATCH 291/633] Simplified code --- src/PIL/ImageMath.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 8396148e8..4b6e4ccda 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -74,9 +74,7 @@ class _Operand: im1 = im1.crop((0, 0) + size) if im2.size != size: im2 = im2.crop((0, 0) + size) - out = Image.new(mode or im1.mode, size, None) - else: - out = Image.new(mode or im1.mode, im1.size, None) + out = Image.new(mode or im1.mode, im1.size, None) im1.load() im2.load() try: From 47fe0b1b9ff8cfe6de1d111437988264700310ee Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Tue, 11 Jan 2022 18:48:56 +0200 Subject: [PATCH 292/633] Autolink CWE numbers with sphinx-issues --- .github/workflows/test.yml | 2 +- docs/releasenotes/9.0.0.rst | 4 ++-- requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 414c7e94e..82518f556 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -93,7 +93,7 @@ jobs: - name: Docs if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.9 run: | - python3 -m pip install sphinx-copybutton sphinx-issues sphinx-removed-in sphinx-rtd-theme sphinxext-opengraph + python3 -m pip install sphinx-copybutton sphinx-issues>=3.0.1 sphinx-removed-in sphinx-rtd-theme sphinxext-opengraph make doccheck - name: After success diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index fbf2e7ce4..947ccd849 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -127,8 +127,8 @@ help prevent problems arising if users evaluate arbitrary expressions, such as Fixed ImagePath.Path array handling ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -:cve:`CVE-2022-22815` (CWE-126) and :cve:`CVE-2022-22816` (CWE-665) were found when -initializing ``ImagePath.Path``. +:cve:`CVE-2022-22815` (:cwe:`CWE-126`) and :cve:`CVE-2022-22816` (:cwe:`CWE-665`) were +found when initializing ``ImagePath.Path``. .. _OSS-Fuzz: https://github.com/google/oss-fuzz diff --git a/requirements.txt b/requirements.txt index feaa2f718..1e150e304 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ pytest-cov pytest-timeout sphinx>=2.4 sphinx-copybutton -sphinx-issues +sphinx-issues>=3.0.1 sphinx-removed-in sphinx-rtd-theme>=1.0 sphinxext-opengraph From 2885b7f3ff6bb021d9646a609686c0f52d776904 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 12 Jan 2022 01:25:58 +0200 Subject: [PATCH 293/633] Dont need to pin for GHA Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 82518f556..414c7e94e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -93,7 +93,7 @@ jobs: - name: Docs if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.9 run: | - python3 -m pip install sphinx-copybutton sphinx-issues>=3.0.1 sphinx-removed-in sphinx-rtd-theme sphinxext-opengraph + python3 -m pip install sphinx-copybutton sphinx-issues sphinx-removed-in sphinx-rtd-theme sphinxext-opengraph make doccheck - name: After success From ba86d90a009621c052e0f268bb0062932b2096f3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 12 Jan 2022 15:17:04 +1100 Subject: [PATCH 294/633] Simplified code --- src/PIL/SpiderImagePlugin.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 062af9f98..061efe8ec 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -238,14 +238,14 @@ def makeSpiderHeader(im): if 1024 % lenbyt != 0: labrec += 1 labbyt = labrec * lenbyt - hdr = [] nvalues = int(labbyt / 4) + if nvalues < 23: + return [] + + hdr = [] for i in range(nvalues): hdr.append(0.0) - if len(hdr) < 23: - return [] - # NB these are Fortran indices hdr[1] = 1.0 # nslice (=1 for an image) hdr[2] = float(nrow) # number of rows per slice @@ -259,10 +259,7 @@ def makeSpiderHeader(im): hdr = hdr[1:] hdr.append(0.0) # pack binary data into a string - hdrstr = [] - for v in hdr: - hdrstr.append(struct.pack("f", v)) - return hdrstr + return [struct.pack("f", v) for v in hdr] def _save(im, fp, filename): From a9441a475d4c4133f161b7927352b4bda5cc9b6f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 12 Jan 2022 17:29:25 +1100 Subject: [PATCH 295/633] Added IREC header entry for use with Bio-formats library --- src/PIL/SpiderImagePlugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 061efe8ec..11c706b4d 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -249,6 +249,7 @@ def makeSpiderHeader(im): # NB these are Fortran indices hdr[1] = 1.0 # nslice (=1 for an image) hdr[2] = float(nrow) # number of rows per slice + hdr[3] = float(nrow) # number of records in the image hdr[5] = 1.0 # iform for 2D image hdr[12] = float(nsam) # number of pixels per line hdr[13] = float(labrec) # number of records in file header From fe32501922ef5e1be9a7d307132719bd5d52ca35 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 14 Jan 2022 10:16:35 +1100 Subject: [PATCH 296/633] Corrected allocation --- src/path.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/path.c b/src/path.c index e2bdc9419..3e3431575 100644 --- a/src/path.c +++ b/src/path.c @@ -57,7 +57,7 @@ alloc_array(Py_ssize_t count) { if ((unsigned long long)count > (SIZE_MAX / (2 * sizeof(double))) - 1) { return ImagingError_MemoryError(); } - xy = calloc(2 * count * sizeof(double) + 1, sizeof(double)); + xy = calloc(2 * count + 1, sizeof(double)); if (!xy) { ImagingError_MemoryError(); } From f8e4e9c2dd94c6f4789639dd891b8a6d5fb16e14 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Jan 2022 09:02:31 +1100 Subject: [PATCH 297/633] Added enums --- Tests/test_color_lut.py | 202 ++++++++++++------ Tests/test_file_apng.py | 74 +++---- Tests/test_file_gif.py | 6 +- Tests/test_file_ico.py | 12 +- Tests/test_file_jpeg.py | 2 +- Tests/test_file_jpeg2k.py | 2 +- Tests/test_file_libtiff.py | 2 +- Tests/test_format_hsv.py | 2 +- Tests/test_image_convert.py | 6 +- Tests/test_image_getdata.py | 2 +- Tests/test_image_paste.py | 18 +- Tests/test_image_quantize.py | 12 +- Tests/test_image_reduce.py | 12 +- Tests/test_image_resample.py | 134 ++++++------ Tests/test_image_resize.py | 98 ++++----- Tests/test_image_rotate.py | 14 +- Tests/test_image_thumbnail.py | 14 +- Tests/test_image_transform.py | 78 ++++--- Tests/test_image_transpose.py | 53 ++--- Tests/test_imagecms.py | 4 +- Tests/test_imagedraw.py | 4 +- Tests/test_imagefile.py | 2 +- Tests/test_imagefont.py | 20 +- Tests/test_mode_i16.py | 2 +- docs/handbook/image-file-formats.rst | 10 +- docs/handbook/tutorial.rst | 12 +- docs/reference/Image.rst | 114 +++++------ docs/reference/ImageCms.rst | 42 ++-- docs/reference/ImageFont.rst | 4 +- docs/reference/features.rst | 2 +- docs/reference/plugins.rst | 3 +- docs/releasenotes/2.7.0.rst | 14 +- selftest.py | 4 +- src/PIL/BlpImagePlugin.py | 40 ++-- src/PIL/FtexImagePlugin.py | 15 +- src/PIL/GifImagePlugin.py | 6 +- src/PIL/IcoImagePlugin.py | 2 +- src/PIL/Image.py | 296 ++++++++++++++++----------- src/PIL/ImageCms.py | 89 ++++---- src/PIL/ImageFilter.py | 2 +- src/PIL/ImageFont.py | 29 ++- src/PIL/ImageOps.py | 32 +-- src/PIL/ImageTransform.py | 8 +- src/PIL/PngImagePlugin.py | 88 ++++---- src/PIL/SpiderImagePlugin.py | 2 +- src/PIL/TgaImagePlugin.py | 2 +- src/PIL/TiffImagePlugin.py | 14 +- 47 files changed, 920 insertions(+), 685 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 99776ce58..120ff777e 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -43,107 +43,158 @@ class TestColorLut3DCoreAPI: im = Image.new("RGB", (10, 10), 0) with pytest.raises(ValueError, match="filter"): - im.im.color_lut_3d("RGB", Image.CUBIC, *self.generate_identity_table(3, 3)) + im.im.color_lut_3d( + "RGB", Image.Resampling.BICUBIC, *self.generate_identity_table(3, 3) + ) with pytest.raises(ValueError, match="image mode"): im.im.color_lut_3d( - "wrong", Image.LINEAR, *self.generate_identity_table(3, 3) + "wrong", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3) ) with pytest.raises(ValueError, match="table_channels"): - im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(5, 3)) - - with pytest.raises(ValueError, match="table_channels"): - im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(1, 3)) - - with pytest.raises(ValueError, match="table_channels"): - im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(2, 3)) - - with pytest.raises(ValueError, match="Table size"): im.im.color_lut_3d( - "RGB", Image.LINEAR, *self.generate_identity_table(3, (1, 3, 3)) + "RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(5, 3) + ) + + with pytest.raises(ValueError, match="table_channels"): + im.im.color_lut_3d( + "RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(1, 3) + ) + + with pytest.raises(ValueError, match="table_channels"): + im.im.color_lut_3d( + "RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(2, 3) ) with pytest.raises(ValueError, match="Table size"): im.im.color_lut_3d( - "RGB", Image.LINEAR, *self.generate_identity_table(3, (66, 3, 3)) + "RGB", + Image.Resampling.BILINEAR, + *self.generate_identity_table(3, (1, 3, 3)), + ) + + with pytest.raises(ValueError, match="Table size"): + im.im.color_lut_3d( + "RGB", + Image.Resampling.BILINEAR, + *self.generate_identity_table(3, (66, 3, 3)), ) with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"): - im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 7) + im.im.color_lut_3d( + "RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, 0] * 7 + ) with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"): - im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 9) + im.im.color_lut_3d( + "RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, 0] * 9 + ) with pytest.raises(TypeError): - im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, "0"] * 8) + im.im.color_lut_3d( + "RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, "0"] * 8 + ) with pytest.raises(TypeError): - im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, 16) + im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, 16) def test_correct_args(self): im = Image.new("RGB", (10, 10), 0) - im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(3, 3)) - - im.im.color_lut_3d("CMYK", Image.LINEAR, *self.generate_identity_table(4, 3)) - im.im.color_lut_3d( - "RGB", Image.LINEAR, *self.generate_identity_table(3, (2, 3, 3)) + "RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3) ) im.im.color_lut_3d( - "RGB", Image.LINEAR, *self.generate_identity_table(3, (65, 3, 3)) + "CMYK", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3) ) im.im.color_lut_3d( - "RGB", Image.LINEAR, *self.generate_identity_table(3, (3, 65, 3)) + "RGB", + Image.Resampling.BILINEAR, + *self.generate_identity_table(3, (2, 3, 3)), ) im.im.color_lut_3d( - "RGB", Image.LINEAR, *self.generate_identity_table(3, (3, 3, 65)) + "RGB", + Image.Resampling.BILINEAR, + *self.generate_identity_table(3, (65, 3, 3)), + ) + + im.im.color_lut_3d( + "RGB", + Image.Resampling.BILINEAR, + *self.generate_identity_table(3, (3, 65, 3)), + ) + + im.im.color_lut_3d( + "RGB", + Image.Resampling.BILINEAR, + *self.generate_identity_table(3, (3, 3, 65)), ) def test_wrong_mode(self): with pytest.raises(ValueError, match="wrong mode"): im = Image.new("L", (10, 10), 0) - im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(3, 3)) - - with pytest.raises(ValueError, match="wrong mode"): - im = Image.new("RGB", (10, 10), 0) - im.im.color_lut_3d("L", Image.LINEAR, *self.generate_identity_table(3, 3)) - - with pytest.raises(ValueError, match="wrong mode"): - im = Image.new("L", (10, 10), 0) - im.im.color_lut_3d("L", Image.LINEAR, *self.generate_identity_table(3, 3)) - - with pytest.raises(ValueError, match="wrong mode"): - im = Image.new("RGB", (10, 10), 0) im.im.color_lut_3d( - "RGBA", Image.LINEAR, *self.generate_identity_table(3, 3) + "RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3) ) with pytest.raises(ValueError, match="wrong mode"): im = Image.new("RGB", (10, 10), 0) - im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(4, 3)) + im.im.color_lut_3d( + "L", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3) + ) + + with pytest.raises(ValueError, match="wrong mode"): + im = Image.new("L", (10, 10), 0) + im.im.color_lut_3d( + "L", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3) + ) + + with pytest.raises(ValueError, match="wrong mode"): + im = Image.new("RGB", (10, 10), 0) + im.im.color_lut_3d( + "RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3) + ) + + with pytest.raises(ValueError, match="wrong mode"): + im = Image.new("RGB", (10, 10), 0) + im.im.color_lut_3d( + "RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3) + ) def test_correct_mode(self): im = Image.new("RGBA", (10, 10), 0) - im.im.color_lut_3d("RGBA", Image.LINEAR, *self.generate_identity_table(3, 3)) + im.im.color_lut_3d( + "RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3) + ) im = Image.new("RGBA", (10, 10), 0) - im.im.color_lut_3d("RGBA", Image.LINEAR, *self.generate_identity_table(4, 3)) + im.im.color_lut_3d( + "RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3) + ) im = Image.new("RGB", (10, 10), 0) - im.im.color_lut_3d("HSV", Image.LINEAR, *self.generate_identity_table(3, 3)) + im.im.color_lut_3d( + "HSV", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3) + ) im = Image.new("RGB", (10, 10), 0) - im.im.color_lut_3d("RGBA", Image.LINEAR, *self.generate_identity_table(4, 3)) + im.im.color_lut_3d( + "RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3) + ) def test_identities(self): g = Image.linear_gradient("L") im = Image.merge( - "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + "RGB", + [ + g, + g.transpose(Image.Transpose.ROTATE_90), + g.transpose(Image.Transpose.ROTATE_180), + ], ) # Fast test with small cubes @@ -152,7 +203,9 @@ class TestColorLut3DCoreAPI: im, im._new( im.im.color_lut_3d( - "RGB", Image.LINEAR, *self.generate_identity_table(3, size) + "RGB", + Image.Resampling.BILINEAR, + *self.generate_identity_table(3, size), ) ), ) @@ -162,7 +215,9 @@ class TestColorLut3DCoreAPI: im, im._new( im.im.color_lut_3d( - "RGB", Image.LINEAR, *self.generate_identity_table(3, (2, 2, 65)) + "RGB", + Image.Resampling.BILINEAR, + *self.generate_identity_table(3, (2, 2, 65)), ) ), ) @@ -170,7 +225,12 @@ class TestColorLut3DCoreAPI: def test_identities_4_channels(self): g = Image.linear_gradient("L") im = Image.merge( - "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + "RGB", + [ + g, + g.transpose(Image.Transpose.ROTATE_90), + g.transpose(Image.Transpose.ROTATE_180), + ], ) # Red channel copied to alpha @@ -178,7 +238,9 @@ class TestColorLut3DCoreAPI: Image.merge("RGBA", (im.split() * 2)[:4]), im._new( im.im.color_lut_3d( - "RGBA", Image.LINEAR, *self.generate_identity_table(4, 17) + "RGBA", + Image.Resampling.BILINEAR, + *self.generate_identity_table(4, 17), ) ), ) @@ -189,9 +251,9 @@ class TestColorLut3DCoreAPI: "RGBA", [ g, - g.transpose(Image.ROTATE_90), - g.transpose(Image.ROTATE_180), - g.transpose(Image.ROTATE_270), + g.transpose(Image.Transpose.ROTATE_90), + g.transpose(Image.Transpose.ROTATE_180), + g.transpose(Image.Transpose.ROTATE_270), ], ) @@ -199,7 +261,9 @@ class TestColorLut3DCoreAPI: im, im._new( im.im.color_lut_3d( - "RGBA", Image.LINEAR, *self.generate_identity_table(3, 17) + "RGBA", + Image.Resampling.BILINEAR, + *self.generate_identity_table(3, 17), ) ), ) @@ -207,14 +271,19 @@ class TestColorLut3DCoreAPI: def test_channels_order(self): g = Image.linear_gradient("L") im = Image.merge( - "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + "RGB", + [ + g, + g.transpose(Image.Transpose.ROTATE_90), + g.transpose(Image.Transpose.ROTATE_180), + ], ) # Reverse channels by splitting and using table # fmt: off assert_image_equal( Image.merge('RGB', im.split()[::-1]), - im._new(im.im.color_lut_3d('RGB', Image.LINEAR, + im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR, 3, 2, 2, 2, [ 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, @@ -227,11 +296,16 @@ class TestColorLut3DCoreAPI: def test_overflow(self): g = Image.linear_gradient("L") im = Image.merge( - "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + "RGB", + [ + g, + g.transpose(Image.Transpose.ROTATE_90), + g.transpose(Image.Transpose.ROTATE_180), + ], ) # fmt: off - transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR, + transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR, 3, 2, 2, 2, [ -1, -1, -1, 2, -1, -1, @@ -251,7 +325,7 @@ class TestColorLut3DCoreAPI: assert transformed[205, 205] == (255, 255, 0) # fmt: off - transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR, + transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR, 3, 2, 2, 2, [ -3, -3, -3, 5, -3, -3, @@ -354,7 +428,12 @@ class TestColorLut3DFilter: def test_numpy_formats(self): g = Image.linear_gradient("L") im = Image.merge( - "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + "RGB", + [ + g, + g.transpose(Image.Transpose.ROTATE_90), + g.transpose(Image.Transpose.ROTATE_180), + ], ) lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) @@ -445,7 +524,12 @@ class TestGenerateColorLut3D: g = Image.linear_gradient("L") im = Image.merge( - "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] + "RGB", + [ + g, + g.transpose(Image.Transpose.ROTATE_90), + g.transpose(Image.Transpose.ROTATE_180), + ], ) assert im == im.filter(lut) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index d48e5ce07..f5c767082 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -120,9 +120,9 @@ def test_apng_dispose_op_previous_frame(): # save_all=True, # append_images=[green, blue], # disposal=[ - # PngImagePlugin.APNG_DISPOSE_OP_NONE, - # PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, - # PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS + # PngImagePlugin.Disposal.OP_NONE, + # PngImagePlugin.Disposal.OP_PREVIOUS, + # PngImagePlugin.Disposal.OP_PREVIOUS # ], # ) with Image.open("Tests/images/apng/dispose_op_previous_frame.png") as im: @@ -455,31 +455,31 @@ def test_apng_save_disposal(tmp_path): green = Image.new("RGBA", size, (0, 255, 0, 255)) transparent = Image.new("RGBA", size, (0, 0, 0, 0)) - # test APNG_DISPOSE_OP_NONE + # test OP_NONE red.save( test_file, save_all=True, append_images=[green, transparent], - disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, - blend=PngImagePlugin.APNG_BLEND_OP_OVER, + disposal=PngImagePlugin.Disposal.OP_NONE, + blend=PngImagePlugin.Blend.OP_OVER, ) with Image.open(test_file) as im: im.seek(2) assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) - # test APNG_DISPOSE_OP_BACKGROUND + # test OP_BACKGROUND disposal = [ - PngImagePlugin.APNG_DISPOSE_OP_NONE, - PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND, - PngImagePlugin.APNG_DISPOSE_OP_NONE, + PngImagePlugin.Disposal.OP_NONE, + PngImagePlugin.Disposal.OP_BACKGROUND, + PngImagePlugin.Disposal.OP_NONE, ] red.save( test_file, save_all=True, append_images=[red, transparent], disposal=disposal, - blend=PngImagePlugin.APNG_BLEND_OP_OVER, + blend=PngImagePlugin.Blend.OP_OVER, ) with Image.open(test_file) as im: im.seek(2) @@ -487,26 +487,26 @@ def test_apng_save_disposal(tmp_path): assert im.getpixel((64, 32)) == (0, 0, 0, 0) disposal = [ - PngImagePlugin.APNG_DISPOSE_OP_NONE, - PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND, + PngImagePlugin.Disposal.OP_NONE, + PngImagePlugin.Disposal.OP_BACKGROUND, ] red.save( test_file, save_all=True, append_images=[green], disposal=disposal, - blend=PngImagePlugin.APNG_BLEND_OP_OVER, + blend=PngImagePlugin.Blend.OP_OVER, ) with Image.open(test_file) as im: im.seek(1) assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) - # test APNG_DISPOSE_OP_PREVIOUS + # test OP_PREVIOUS disposal = [ - PngImagePlugin.APNG_DISPOSE_OP_NONE, - PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, - PngImagePlugin.APNG_DISPOSE_OP_NONE, + PngImagePlugin.Disposal.OP_NONE, + PngImagePlugin.Disposal.OP_PREVIOUS, + PngImagePlugin.Disposal.OP_NONE, ] red.save( test_file, @@ -514,7 +514,7 @@ def test_apng_save_disposal(tmp_path): append_images=[green, red, transparent], default_image=True, disposal=disposal, - blend=PngImagePlugin.APNG_BLEND_OP_OVER, + blend=PngImagePlugin.Blend.OP_OVER, ) with Image.open(test_file) as im: im.seek(3) @@ -522,15 +522,15 @@ def test_apng_save_disposal(tmp_path): assert im.getpixel((64, 32)) == (0, 255, 0, 255) disposal = [ - PngImagePlugin.APNG_DISPOSE_OP_NONE, - PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, + PngImagePlugin.Disposal.OP_NONE, + PngImagePlugin.Disposal.OP_PREVIOUS, ] red.save( test_file, save_all=True, append_images=[green], disposal=disposal, - blend=PngImagePlugin.APNG_BLEND_OP_OVER, + blend=PngImagePlugin.Blend.OP_OVER, ) with Image.open(test_file) as im: im.seek(1) @@ -538,7 +538,7 @@ def test_apng_save_disposal(tmp_path): assert im.getpixel((64, 32)) == (0, 255, 0, 255) # test info disposal - red.info["disposal"] = PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND + red.info["disposal"] = PngImagePlugin.Disposal.OP_BACKGROUND red.save( test_file, save_all=True, @@ -556,12 +556,12 @@ def test_apng_save_disposal_previous(tmp_path): red = Image.new("RGBA", size, (255, 0, 0, 255)) green = Image.new("RGBA", size, (0, 255, 0, 255)) - # test APNG_DISPOSE_OP_NONE + # test OP_NONE transparent.save( test_file, save_all=True, append_images=[red, green], - disposal=PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, + disposal=PngImagePlugin.Disposal.OP_PREVIOUS, ) with Image.open(test_file) as im: im.seek(2) @@ -576,17 +576,17 @@ def test_apng_save_blend(tmp_path): green = Image.new("RGBA", size, (0, 255, 0, 255)) transparent = Image.new("RGBA", size, (0, 0, 0, 0)) - # test APNG_BLEND_OP_SOURCE on solid color + # test OP_SOURCE on solid color blend = [ - PngImagePlugin.APNG_BLEND_OP_OVER, - PngImagePlugin.APNG_BLEND_OP_SOURCE, + PngImagePlugin.Blend.OP_OVER, + PngImagePlugin.Blend.OP_SOURCE, ] red.save( test_file, save_all=True, append_images=[red, green], default_image=True, - disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, + disposal=PngImagePlugin.Disposal.OP_NONE, blend=blend, ) with Image.open(test_file) as im: @@ -594,17 +594,17 @@ def test_apng_save_blend(tmp_path): assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) - # test APNG_BLEND_OP_SOURCE on transparent color + # test OP_SOURCE on transparent color blend = [ - PngImagePlugin.APNG_BLEND_OP_OVER, - PngImagePlugin.APNG_BLEND_OP_SOURCE, + PngImagePlugin.Blend.OP_OVER, + PngImagePlugin.Blend.OP_SOURCE, ] red.save( test_file, save_all=True, append_images=[red, transparent], default_image=True, - disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, + disposal=PngImagePlugin.Disposal.OP_NONE, blend=blend, ) with Image.open(test_file) as im: @@ -612,14 +612,14 @@ def test_apng_save_blend(tmp_path): assert im.getpixel((0, 0)) == (0, 0, 0, 0) assert im.getpixel((64, 32)) == (0, 0, 0, 0) - # test APNG_BLEND_OP_OVER + # test OP_OVER red.save( test_file, save_all=True, append_images=[green, transparent], default_image=True, - disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, - blend=PngImagePlugin.APNG_BLEND_OP_OVER, + disposal=PngImagePlugin.Disposal.OP_NONE, + blend=PngImagePlugin.Blend.OP_OVER, ) with Image.open(test_file) as im: im.seek(1) @@ -630,7 +630,7 @@ def test_apng_save_blend(tmp_path): assert im.getpixel((64, 32)) == (0, 255, 0, 255) # test info blend - red.info["blend"] = PngImagePlugin.APNG_BLEND_OP_OVER + red.info["blend"] = PngImagePlugin.Blend.OP_OVER red.save(test_file, save_all=True, append_images=[green, transparent]) with Image.open(test_file) as im: im.seek(2) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 00bf582fa..8b0306db8 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -210,8 +210,8 @@ def test_palette_handling(tmp_path): with Image.open(TEST_GIF) as im: im = im.convert("RGB") - im = im.resize((100, 100), Image.LANCZOS) - im2 = im.convert("P", palette=Image.ADAPTIVE, colors=256) + im = im.resize((100, 100), Image.Resampling.LANCZOS) + im2 = im.convert("P", palette=Image.Palette.ADAPTIVE, colors=256) f = str(tmp_path / "temp.gif") im2.save(f, optimize=True) @@ -911,7 +911,7 @@ def test_save_I(tmp_path): def test_getdata(): # Test getheader/getdata against legacy values. # Create a 'P' image with holes in the palette. - im = Image._wedge().resize((16, 16), Image.NEAREST) + im = Image._wedge().resize((16, 16), Image.Resampling.NEAREST) im.putpalette(ImagePalette.ImagePalette("RGB")) im.info = {"background": 0} diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 317264db6..8eff2cc43 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -48,7 +48,9 @@ def test_save_to_bytes(): assert im.mode == reloaded.mode assert (64, 64) == reloaded.size assert reloaded.format == "ICO" - assert_image_equal(reloaded, hopper().resize((64, 64), Image.LANCZOS)) + assert_image_equal( + reloaded, hopper().resize((64, 64), Image.Resampling.LANCZOS) + ) # The other one output.seek(0) @@ -58,7 +60,9 @@ def test_save_to_bytes(): assert im.mode == reloaded.mode assert (32, 32) == reloaded.size assert reloaded.format == "ICO" - assert_image_equal(reloaded, hopper().resize((32, 32), Image.LANCZOS)) + assert_image_equal( + reloaded, hopper().resize((32, 32), Image.Resampling.LANCZOS) + ) @pytest.mark.parametrize("mode", ("1", "L", "P", "RGB", "RGBA")) @@ -75,7 +79,7 @@ def test_save_to_bytes_bmp(mode): assert "RGBA" == reloaded.mode assert (64, 64) == reloaded.size assert reloaded.format == "ICO" - im = hopper(mode).resize((64, 64), Image.LANCZOS).convert("RGBA") + im = hopper(mode).resize((64, 64), Image.Resampling.LANCZOS).convert("RGBA") assert_image_equal(reloaded, im) # The other one @@ -86,7 +90,7 @@ def test_save_to_bytes_bmp(mode): assert "RGBA" == reloaded.mode assert (32, 32) == reloaded.size assert reloaded.format == "ICO" - im = hopper(mode).resize((32, 32), Image.LANCZOS).convert("RGBA") + im = hopper(mode).resize((32, 32), Image.Resampling.LANCZOS).convert("RGBA") assert_image_equal(reloaded, im) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 4b2ffe70d..f0cfb7811 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -271,7 +271,7 @@ class TestFileJpeg: del exif[0x8769] # Assert that it needs to be transposed - assert exif[0x0112] == Image.TRANSVERSE + assert exif[0x0112] == Image.Transpose.TRANSVERSE # Assert that the GPS IFD is present and empty assert exif.get_ifd(0x8825) == {} diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index ca410162a..b5ea6d0a0 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -291,7 +291,7 @@ def test_subsampling_decode(name): # RGB reference images are downscaled epsilon = 3e-3 width, height = width * 2, height * 2 - expected = im2.resize((width, height), Image.NEAREST) + expected = im2.resize((width, height), Image.Resampling.NEAREST) assert_image_similar(im, expected, epsilon) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index e40a19394..53ed2520a 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -112,7 +112,7 @@ class TestFileLibTiff(LibTiffTestCase): test_file = "Tests/images/hopper_g4_500.tif" with Image.open(test_file) as orig: out = str(tmp_path / "temp.tif") - rot = orig.transpose(Image.ROTATE_90) + rot = orig.transpose(Image.Transpose.ROTATE_90) assert rot.size == (500, 500) rot.save(out) diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index 3b9c8b071..b485e854f 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -77,7 +77,7 @@ def to_rgb_colorsys(im): def test_wedge(): - src = wedge().resize((3 * 32, 32), Image.BILINEAR) + src = wedge().resize((3 * 32, 32), Image.Resampling.BILINEAR) im = src.convert("HSV") comparable = to_hsv_colorsys(src) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index a5a95e962..75af92427 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -136,7 +136,7 @@ def test_trns_l(tmp_path): assert "transparency" in im_p.info im_p.save(f) - im_p = im.convert("P", palette=Image.ADAPTIVE) + im_p = im.convert("P", palette=Image.Palette.ADAPTIVE) assert "transparency" in im_p.info im_p.save(f) @@ -159,13 +159,13 @@ def test_trns_RGB(tmp_path): assert "transparency" not in im_rgba.info im_rgba.save(f) - im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.ADAPTIVE) + im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.Palette.ADAPTIVE) assert "transparency" not in im_p.info im_p.save(f) im = Image.new("RGB", (1, 1)) im.info["transparency"] = im.getpixel((0, 0)) - im_p = im.convert("P", palette=Image.ADAPTIVE) + im_p = im.convert("P", palette=Image.Palette.ADAPTIVE) assert im_p.info["transparency"] == im_p.getpixel((0, 0)) im_p.save(f) diff --git a/Tests/test_image_getdata.py b/Tests/test_image_getdata.py index 159efd78a..36c81b40f 100644 --- a/Tests/test_image_getdata.py +++ b/Tests/test_image_getdata.py @@ -14,7 +14,7 @@ def test_sanity(): def test_roundtrip(): def getdata(mode): - im = hopper(mode).resize((32, 30), Image.NEAREST) + im = hopper(mode).resize((32, 30), Image.Resampling.NEAREST) data = im.getdata() return data[0], len(data), len(list(data)) diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index 1d3ca8135..dc3caef01 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -45,7 +45,7 @@ class TestImagingPaste: @cached_property def mask_L(self): - return self.gradient_L.transpose(Image.ROTATE_270) + return self.gradient_L.transpose(Image.Transpose.ROTATE_270) @cached_property def gradient_L(self): @@ -62,8 +62,8 @@ class TestImagingPaste: "RGB", [ self.gradient_L, - self.gradient_L.transpose(Image.ROTATE_90), - self.gradient_L.transpose(Image.ROTATE_180), + self.gradient_L.transpose(Image.Transpose.ROTATE_90), + self.gradient_L.transpose(Image.Transpose.ROTATE_180), ], ) @@ -73,9 +73,9 @@ class TestImagingPaste: "RGBA", [ self.gradient_L, - self.gradient_L.transpose(Image.ROTATE_90), - self.gradient_L.transpose(Image.ROTATE_180), - self.gradient_L.transpose(Image.ROTATE_270), + self.gradient_L.transpose(Image.Transpose.ROTATE_90), + self.gradient_L.transpose(Image.Transpose.ROTATE_180), + self.gradient_L.transpose(Image.Transpose.ROTATE_270), ], ) @@ -85,9 +85,9 @@ class TestImagingPaste: "RGBa", [ self.gradient_L, - self.gradient_L.transpose(Image.ROTATE_90), - self.gradient_L.transpose(Image.ROTATE_180), - self.gradient_L.transpose(Image.ROTATE_270), + self.gradient_L.transpose(Image.Transpose.ROTATE_90), + self.gradient_L.transpose(Image.Transpose.ROTATE_180), + self.gradient_L.transpose(Image.Transpose.ROTATE_270), ], ) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 53b6c9007..6ce39e0ac 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -21,7 +21,7 @@ def test_sanity(): def test_libimagequant_quantize(): image = hopper() try: - converted = image.quantize(100, Image.LIBIMAGEQUANT) + converted = image.quantize(100, Image.Quantize.LIBIMAGEQUANT) except ValueError as ex: # pragma: no cover if "dependency" in str(ex).lower(): pytest.skip("libimagequant support not available") @@ -34,7 +34,7 @@ def test_libimagequant_quantize(): def test_octree_quantize(): image = hopper() - converted = image.quantize(100, Image.FASTOCTREE) + converted = image.quantize(100, Image.Quantize.FASTOCTREE) assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 20) assert len(converted.getcolors()) == 100 @@ -97,10 +97,10 @@ def test_transparent_colors_equal(): @pytest.mark.parametrize( "method, color", ( - (Image.MEDIANCUT, (0, 0, 0)), - (Image.MAXCOVERAGE, (0, 0, 0)), - (Image.FASTOCTREE, (0, 0, 0)), - (Image.FASTOCTREE, (0, 0, 0, 0)), + (Image.Quantize.MEDIANCUT, (0, 0, 0)), + (Image.Quantize.MAXCOVERAGE, (0, 0, 0)), + (Image.Quantize.FASTOCTREE, (0, 0, 0)), + (Image.Quantize.FASTOCTREE, (0, 0, 0, 0)), ), ) def test_palette(method, color): diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index b4eebc142..70dc87f0a 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -97,7 +97,7 @@ def get_image(mode): bands = [gradients_image] for _ in mode_info.bands[1:]: # rotate previous image - band = bands[-1].transpose(Image.ROTATE_90) + band = bands[-1].transpose(Image.Transpose.ROTATE_90) bands.append(band) # Correct alpha channel by transforming completely transparent pixels. # Low alpha values also emphasize error after alpha multiplication. @@ -138,24 +138,26 @@ def compare_reduce_with_reference(im, factor, average_diff=0.4, max_diff=1): reference = Image.new(im.mode, reduced.size) area_size = (im.size[0] // factor[0], im.size[1] // factor[1]) area_box = (0, 0, area_size[0] * factor[0], area_size[1] * factor[1]) - area = im.resize(area_size, Image.BOX, area_box) + area = im.resize(area_size, Image.Resampling.BOX, area_box) reference.paste(area, (0, 0)) if area_size[0] < reduced.size[0]: assert reduced.size[0] - area_size[0] == 1 last_column_box = (area_box[2], 0, im.size[0], area_box[3]) - last_column = im.resize((1, area_size[1]), Image.BOX, last_column_box) + last_column = im.resize( + (1, area_size[1]), Image.Resampling.BOX, last_column_box + ) reference.paste(last_column, (area_size[0], 0)) if area_size[1] < reduced.size[1]: assert reduced.size[1] - area_size[1] == 1 last_row_box = (0, area_box[3], area_box[2], im.size[1]) - last_row = im.resize((area_size[0], 1), Image.BOX, last_row_box) + last_row = im.resize((area_size[0], 1), Image.Resampling.BOX, last_row_box) reference.paste(last_row, (0, area_size[1])) if area_size[0] < reduced.size[0] and area_size[1] < reduced.size[1]: last_pixel_box = (area_box[2], area_box[3], im.size[0], im.size[1]) - last_pixel = im.resize((1, 1), Image.BOX, last_pixel_box) + last_pixel = im.resize((1, 1), Image.Resampling.BOX, last_pixel_box) reference.paste(last_pixel, area_size) assert_compare_images(reduced, reference, average_diff, max_diff) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 8bf2ce916..125422337 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -24,7 +24,7 @@ class TestImagingResampleVulnerability: ): with pytest.raises(MemoryError): # any resampling filter will do here - im.im.resize((xsize, ysize), Image.BILINEAR) + im.im.resize((xsize, ysize), Image.Resampling.BILINEAR) def test_invalid_size(self): im = hopper() @@ -103,7 +103,7 @@ class TestImagingCoreResampleAccuracy: def test_reduce_box(self): for mode in ["RGBX", "RGB", "La", "L"]: case = self.make_case(mode, (8, 8), 0xE1) - case = case.resize((4, 4), Image.BOX) + case = case.resize((4, 4), Image.Resampling.BOX) # fmt: off data = ("e1 e1" "e1 e1") @@ -114,7 +114,7 @@ class TestImagingCoreResampleAccuracy: def test_reduce_bilinear(self): for mode in ["RGBX", "RGB", "La", "L"]: case = self.make_case(mode, (8, 8), 0xE1) - case = case.resize((4, 4), Image.BILINEAR) + case = case.resize((4, 4), Image.Resampling.BILINEAR) # fmt: off data = ("e1 c9" "c9 b7") @@ -125,7 +125,7 @@ class TestImagingCoreResampleAccuracy: def test_reduce_hamming(self): for mode in ["RGBX", "RGB", "La", "L"]: case = self.make_case(mode, (8, 8), 0xE1) - case = case.resize((4, 4), Image.HAMMING) + case = case.resize((4, 4), Image.Resampling.HAMMING) # fmt: off data = ("e1 da" "da d3") @@ -136,7 +136,7 @@ class TestImagingCoreResampleAccuracy: def test_reduce_bicubic(self): for mode in ["RGBX", "RGB", "La", "L"]: case = self.make_case(mode, (12, 12), 0xE1) - case = case.resize((6, 6), Image.BICUBIC) + case = case.resize((6, 6), Image.Resampling.BICUBIC) # fmt: off data = ("e1 e3 d4" "e3 e5 d6" @@ -148,7 +148,7 @@ class TestImagingCoreResampleAccuracy: def test_reduce_lanczos(self): for mode in ["RGBX", "RGB", "La", "L"]: case = self.make_case(mode, (16, 16), 0xE1) - case = case.resize((8, 8), Image.LANCZOS) + case = case.resize((8, 8), Image.Resampling.LANCZOS) # fmt: off data = ("e1 e0 e4 d7" "e0 df e3 d6" @@ -161,7 +161,7 @@ class TestImagingCoreResampleAccuracy: def test_enlarge_box(self): for mode in ["RGBX", "RGB", "La", "L"]: case = self.make_case(mode, (2, 2), 0xE1) - case = case.resize((4, 4), Image.BOX) + case = case.resize((4, 4), Image.Resampling.BOX) # fmt: off data = ("e1 e1" "e1 e1") @@ -172,7 +172,7 @@ class TestImagingCoreResampleAccuracy: def test_enlarge_bilinear(self): for mode in ["RGBX", "RGB", "La", "L"]: case = self.make_case(mode, (2, 2), 0xE1) - case = case.resize((4, 4), Image.BILINEAR) + case = case.resize((4, 4), Image.Resampling.BILINEAR) # fmt: off data = ("e1 b0" "b0 98") @@ -183,7 +183,7 @@ class TestImagingCoreResampleAccuracy: def test_enlarge_hamming(self): for mode in ["RGBX", "RGB", "La", "L"]: case = self.make_case(mode, (2, 2), 0xE1) - case = case.resize((4, 4), Image.HAMMING) + case = case.resize((4, 4), Image.Resampling.HAMMING) # fmt: off data = ("e1 d2" "d2 c5") @@ -194,7 +194,7 @@ class TestImagingCoreResampleAccuracy: def test_enlarge_bicubic(self): for mode in ["RGBX", "RGB", "La", "L"]: case = self.make_case(mode, (4, 4), 0xE1) - case = case.resize((8, 8), Image.BICUBIC) + case = case.resize((8, 8), Image.Resampling.BICUBIC) # fmt: off data = ("e1 e5 ee b9" "e5 e9 f3 bc" @@ -207,7 +207,7 @@ class TestImagingCoreResampleAccuracy: def test_enlarge_lanczos(self): for mode in ["RGBX", "RGB", "La", "L"]: case = self.make_case(mode, (6, 6), 0xE1) - case = case.resize((12, 12), Image.LANCZOS) + case = case.resize((12, 12), Image.Resampling.LANCZOS) data = ( "e1 e0 db ed f5 b8" "e0 df da ec f3 b7" @@ -220,7 +220,9 @@ class TestImagingCoreResampleAccuracy: self.check_case(channel, self.make_sample(data, (12, 12))) def test_box_filter_correct_range(self): - im = Image.new("RGB", (8, 8), "#1688ff").resize((100, 100), Image.BOX) + im = Image.new("RGB", (8, 8), "#1688ff").resize( + (100, 100), Image.Resampling.BOX + ) ref = Image.new("RGB", (100, 100), "#1688ff") assert_image_equal(im, ref) @@ -228,7 +230,7 @@ class TestImagingCoreResampleAccuracy: class TestCoreResampleConsistency: def make_case(self, mode, fill): im = Image.new(mode, (512, 9), fill) - return im.resize((9, 512), Image.LANCZOS), im.load()[0, 0] + return im.resize((9, 512), Image.Resampling.LANCZOS), im.load()[0, 0] def run_case(self, case): channel, color = case @@ -283,20 +285,20 @@ class TestCoreResampleAlphaCorrect: @pytest.mark.xfail(reason="Current implementation isn't precise enough") def test_levels_rgba(self): case = self.make_levels_case("RGBA") - self.run_levels_case(case.resize((512, 32), Image.BOX)) - self.run_levels_case(case.resize((512, 32), Image.BILINEAR)) - self.run_levels_case(case.resize((512, 32), Image.HAMMING)) - self.run_levels_case(case.resize((512, 32), Image.BICUBIC)) - self.run_levels_case(case.resize((512, 32), Image.LANCZOS)) + self.run_levels_case(case.resize((512, 32), Image.Resampling.BOX)) + self.run_levels_case(case.resize((512, 32), Image.Resampling.BILINEAR)) + self.run_levels_case(case.resize((512, 32), Image.Resampling.HAMMING)) + self.run_levels_case(case.resize((512, 32), Image.Resampling.BICUBIC)) + self.run_levels_case(case.resize((512, 32), Image.Resampling.LANCZOS)) @pytest.mark.xfail(reason="Current implementation isn't precise enough") def test_levels_la(self): case = self.make_levels_case("LA") - self.run_levels_case(case.resize((512, 32), Image.BOX)) - self.run_levels_case(case.resize((512, 32), Image.BILINEAR)) - self.run_levels_case(case.resize((512, 32), Image.HAMMING)) - self.run_levels_case(case.resize((512, 32), Image.BICUBIC)) - self.run_levels_case(case.resize((512, 32), Image.LANCZOS)) + self.run_levels_case(case.resize((512, 32), Image.Resampling.BOX)) + self.run_levels_case(case.resize((512, 32), Image.Resampling.BILINEAR)) + self.run_levels_case(case.resize((512, 32), Image.Resampling.HAMMING)) + self.run_levels_case(case.resize((512, 32), Image.Resampling.BICUBIC)) + self.run_levels_case(case.resize((512, 32), Image.Resampling.LANCZOS)) def make_dirty_case(self, mode, clean_pixel, dirty_pixel): i = Image.new(mode, (64, 64), dirty_pixel) @@ -321,19 +323,27 @@ class TestCoreResampleAlphaCorrect: def test_dirty_pixels_rgba(self): case = self.make_dirty_case("RGBA", (255, 255, 0, 128), (0, 0, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.BOX), (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.HAMMING), (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), (255, 255, 0)) - self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255, 255, 0)) + self.run_dirty_case(case.resize((20, 20), Image.Resampling.BOX), (255, 255, 0)) + self.run_dirty_case( + case.resize((20, 20), Image.Resampling.BILINEAR), (255, 255, 0) + ) + self.run_dirty_case( + case.resize((20, 20), Image.Resampling.HAMMING), (255, 255, 0) + ) + self.run_dirty_case( + case.resize((20, 20), Image.Resampling.BICUBIC), (255, 255, 0) + ) + self.run_dirty_case( + case.resize((20, 20), Image.Resampling.LANCZOS), (255, 255, 0) + ) def test_dirty_pixels_la(self): case = self.make_dirty_case("LA", (255, 128), (0, 0)) - self.run_dirty_case(case.resize((20, 20), Image.BOX), (255,)) - self.run_dirty_case(case.resize((20, 20), Image.BILINEAR), (255,)) - self.run_dirty_case(case.resize((20, 20), Image.HAMMING), (255,)) - self.run_dirty_case(case.resize((20, 20), Image.BICUBIC), (255,)) - self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255,)) + self.run_dirty_case(case.resize((20, 20), Image.Resampling.BOX), (255,)) + self.run_dirty_case(case.resize((20, 20), Image.Resampling.BILINEAR), (255,)) + self.run_dirty_case(case.resize((20, 20), Image.Resampling.HAMMING), (255,)) + self.run_dirty_case(case.resize((20, 20), Image.Resampling.BICUBIC), (255,)) + self.run_dirty_case(case.resize((20, 20), Image.Resampling.LANCZOS), (255,)) class TestCoreResamplePasses: @@ -346,26 +356,26 @@ class TestCoreResamplePasses: def test_horizontal(self): im = hopper("L") with self.count(1): - im.resize((im.size[0] - 10, im.size[1]), Image.BILINEAR) + im.resize((im.size[0] - 10, im.size[1]), Image.Resampling.BILINEAR) def test_vertical(self): im = hopper("L") with self.count(1): - im.resize((im.size[0], im.size[1] - 10), Image.BILINEAR) + im.resize((im.size[0], im.size[1] - 10), Image.Resampling.BILINEAR) def test_both(self): im = hopper("L") with self.count(2): - im.resize((im.size[0] - 10, im.size[1] - 10), Image.BILINEAR) + im.resize((im.size[0] - 10, im.size[1] - 10), Image.Resampling.BILINEAR) def test_box_horizontal(self): im = hopper("L") box = (20, 0, im.size[0] - 20, im.size[1]) with self.count(1): # the same size, but different box - with_box = im.resize(im.size, Image.BILINEAR, box) + with_box = im.resize(im.size, Image.Resampling.BILINEAR, box) with self.count(2): - cropped = im.crop(box).resize(im.size, Image.BILINEAR) + cropped = im.crop(box).resize(im.size, Image.Resampling.BILINEAR) assert_image_similar(with_box, cropped, 0.1) def test_box_vertical(self): @@ -373,9 +383,9 @@ class TestCoreResamplePasses: box = (0, 20, im.size[0], im.size[1] - 20) with self.count(1): # the same size, but different box - with_box = im.resize(im.size, Image.BILINEAR, box) + with_box = im.resize(im.size, Image.Resampling.BILINEAR, box) with self.count(2): - cropped = im.crop(box).resize(im.size, Image.BILINEAR) + cropped = im.crop(box).resize(im.size, Image.Resampling.BILINEAR) assert_image_similar(with_box, cropped, 0.1) @@ -388,7 +398,7 @@ class TestCoreResampleCoefficients: draw = ImageDraw.Draw(i) draw.rectangle((0, 0, i.size[0] // 2 - 1, 0), test_color) - px = i.resize((5, i.size[1]), Image.BICUBIC).load() + px = i.resize((5, i.size[1]), Image.Resampling.BICUBIC).load() if px[2, 0] != test_color // 2: assert test_color // 2 == px[2, 0] @@ -396,7 +406,7 @@ class TestCoreResampleCoefficients: # regression test for the wrong coefficients calculation # due to bug https://github.com/python-pillow/Pillow/issues/2161 im = Image.new("RGBA", (1280, 1280), (0x20, 0x40, 0x60, 0xFF)) - histogram = im.resize((256, 256), Image.BICUBIC).histogram() + histogram = im.resize((256, 256), Image.Resampling.BICUBIC).histogram() # first channel assert histogram[0x100 * 0 + 0x20] == 0x10000 @@ -412,12 +422,12 @@ class TestCoreResampleBox: def test_wrong_arguments(self): im = hopper() for resample in ( - Image.NEAREST, - Image.BOX, - Image.BILINEAR, - Image.HAMMING, - Image.BICUBIC, - Image.LANCZOS, + Image.Resampling.NEAREST, + Image.Resampling.BOX, + Image.Resampling.BILINEAR, + Image.Resampling.HAMMING, + Image.Resampling.BICUBIC, + Image.Resampling.LANCZOS, ): im.resize((32, 32), resample, (0, 0, im.width, im.height)) im.resize((32, 32), resample, (20, 20, im.width, im.height)) @@ -456,7 +466,7 @@ class TestCoreResampleBox: for y0, y1 in split_range(dst_size[1], ytiles): for x0, x1 in split_range(dst_size[0], xtiles): box = (x0 * scale[0], y0 * scale[1], x1 * scale[0], y1 * scale[1]) - tile = im.resize((x1 - x0, y1 - y0), Image.BICUBIC, box) + tile = im.resize((x1 - x0, y1 - y0), Image.Resampling.BICUBIC, box) tiled.paste(tile, (x0, y0)) return tiled @@ -467,7 +477,7 @@ class TestCoreResampleBox: with Image.open("Tests/images/flower.jpg") as im: assert im.size == (480, 360) dst_size = (251, 188) - reference = im.resize(dst_size, Image.BICUBIC) + reference = im.resize(dst_size, Image.Resampling.BICUBIC) for tiles in [(1, 1), (3, 3), (9, 7), (100, 100)]: tiled = self.resize_tiled(im, dst_size, *tiles) @@ -483,12 +493,16 @@ class TestCoreResampleBox: assert im.size == (480, 360) dst_size = (48, 36) # Reference is cropped image resized to destination - reference = im.crop((0, 0, 473, 353)).resize(dst_size, Image.BICUBIC) - # Image.BOX emulates supersampling (480 / 8 = 60, 360 / 8 = 45) - supersampled = im.resize((60, 45), Image.BOX) + reference = im.crop((0, 0, 473, 353)).resize( + dst_size, Image.Resampling.BICUBIC + ) + # Image.Resampling.BOX emulates supersampling (480 / 8 = 60, 360 / 8 = 45) + supersampled = im.resize((60, 45), Image.Resampling.BOX) - with_box = supersampled.resize(dst_size, Image.BICUBIC, (0, 0, 59.125, 44.125)) - without_box = supersampled.resize(dst_size, Image.BICUBIC) + with_box = supersampled.resize( + dst_size, Image.Resampling.BICUBIC, (0, 0, 59.125, 44.125) + ) + without_box = supersampled.resize(dst_size, Image.Resampling.BICUBIC) # error with box should be much smaller than without assert_image_similar(reference, with_box, 6) @@ -496,7 +510,7 @@ class TestCoreResampleBox: assert_image_similar(reference, without_box, 5) def test_formats(self): - for resample in [Image.NEAREST, Image.BILINEAR]: + for resample in [Image.Resampling.NEAREST, Image.Resampling.BILINEAR]: for mode in ["RGB", "L", "RGBA", "LA", "I", ""]: im = hopper(mode) box = (20, 20, im.size[0] - 20, im.size[1] - 20) @@ -514,7 +528,7 @@ class TestCoreResampleBox: ((40, 50), (10, 0, 50, 50)), ((40, 50), (10, 20, 50, 70)), ]: - res = im.resize(size, Image.LANCZOS, box) + res = im.resize(size, Image.Resampling.LANCZOS, box) assert res.size == size assert_image_equal(res, im.crop(box), f">>> {size} {box}") @@ -528,7 +542,7 @@ class TestCoreResampleBox: ((40, 50), (10.4, 0.4, 50.4, 50.4)), ((40, 50), (10.4, 20.4, 50.4, 70.4)), ]: - res = im.resize(size, Image.LANCZOS, box) + res = im.resize(size, Image.Resampling.LANCZOS, box) assert res.size == size with pytest.raises(AssertionError, match=r"difference \d"): # check that the difference at least that much @@ -538,7 +552,7 @@ class TestCoreResampleBox: # Can skip resize for one dimension im = hopper() - for flt in [Image.NEAREST, Image.BICUBIC]: + for flt in [Image.Resampling.NEAREST, Image.Resampling.BICUBIC]: for size, box in [ ((40, 50), (0, 0, 40, 90)), ((40, 50), (0, 20, 40, 90)), @@ -559,7 +573,7 @@ class TestCoreResampleBox: # Can skip resize for one dimension im = hopper() - for flt in [Image.NEAREST, Image.BICUBIC]: + for flt in [Image.Resampling.NEAREST, Image.Resampling.BICUBIC]: for size, box in [ ((40, 50), (0, 0, 90, 50)), ((40, 50), (20, 0, 90, 50)), diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 1fe278052..04b7c8c97 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -35,33 +35,33 @@ class TestImagingCoreResize: "I;16", ]: # exotic mode im = hopper(mode) - r = self.resize(im, (15, 12), Image.NEAREST) + r = self.resize(im, (15, 12), Image.Resampling.NEAREST) assert r.mode == mode assert r.size == (15, 12) assert r.im.bands == im.im.bands def test_convolution_modes(self): with pytest.raises(ValueError): - self.resize(hopper("1"), (15, 12), Image.BILINEAR) + self.resize(hopper("1"), (15, 12), Image.Resampling.BILINEAR) with pytest.raises(ValueError): - self.resize(hopper("P"), (15, 12), Image.BILINEAR) + self.resize(hopper("P"), (15, 12), Image.Resampling.BILINEAR) with pytest.raises(ValueError): - self.resize(hopper("I;16"), (15, 12), Image.BILINEAR) + self.resize(hopper("I;16"), (15, 12), Image.Resampling.BILINEAR) for mode in ["L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr"]: im = hopper(mode) - r = self.resize(im, (15, 12), Image.BILINEAR) + r = self.resize(im, (15, 12), Image.Resampling.BILINEAR) assert r.mode == mode assert r.size == (15, 12) assert r.im.bands == im.im.bands def test_reduce_filters(self): for f in [ - Image.NEAREST, - Image.BOX, - Image.BILINEAR, - Image.HAMMING, - Image.BICUBIC, - Image.LANCZOS, + Image.Resampling.NEAREST, + Image.Resampling.BOX, + Image.Resampling.BILINEAR, + Image.Resampling.HAMMING, + Image.Resampling.BICUBIC, + Image.Resampling.LANCZOS, ]: r = self.resize(hopper("RGB"), (15, 12), f) assert r.mode == "RGB" @@ -69,12 +69,12 @@ class TestImagingCoreResize: def test_enlarge_filters(self): for f in [ - Image.NEAREST, - Image.BOX, - Image.BILINEAR, - Image.HAMMING, - Image.BICUBIC, - Image.LANCZOS, + Image.Resampling.NEAREST, + Image.Resampling.BOX, + Image.Resampling.BILINEAR, + Image.Resampling.HAMMING, + Image.Resampling.BICUBIC, + Image.Resampling.LANCZOS, ]: r = self.resize(hopper("RGB"), (212, 195), f) assert r.mode == "RGB" @@ -95,12 +95,12 @@ class TestImagingCoreResize: samples["dirty"].putpixel((1, 1), 128) for f in [ - Image.NEAREST, - Image.BOX, - Image.BILINEAR, - Image.HAMMING, - Image.BICUBIC, - Image.LANCZOS, + Image.Resampling.NEAREST, + Image.Resampling.BOX, + Image.Resampling.BILINEAR, + Image.Resampling.HAMMING, + Image.Resampling.BICUBIC, + Image.Resampling.LANCZOS, ]: # samples resized with current filter references = { @@ -124,12 +124,12 @@ class TestImagingCoreResize: def test_enlarge_zero(self): for f in [ - Image.NEAREST, - Image.BOX, - Image.BILINEAR, - Image.HAMMING, - Image.BICUBIC, - Image.LANCZOS, + Image.Resampling.NEAREST, + Image.Resampling.BOX, + Image.Resampling.BILINEAR, + Image.Resampling.HAMMING, + Image.Resampling.BICUBIC, + Image.Resampling.LANCZOS, ]: r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), f) assert r.mode == "RGB" @@ -164,15 +164,19 @@ def gradients_image(): class TestReducingGapResize: def test_reducing_gap_values(self, gradients_image): - ref = gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=None) - im = gradients_image.resize((52, 34), Image.BICUBIC) + ref = gradients_image.resize( + (52, 34), Image.Resampling.BICUBIC, reducing_gap=None + ) + im = gradients_image.resize((52, 34), Image.Resampling.BICUBIC) assert_image_equal(ref, im) with pytest.raises(ValueError): - gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=0) + gradients_image.resize((52, 34), Image.Resampling.BICUBIC, reducing_gap=0) with pytest.raises(ValueError): - gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=0.99) + gradients_image.resize( + (52, 34), Image.Resampling.BICUBIC, reducing_gap=0.99 + ) def test_reducing_gap_1(self, gradients_image): for box, epsilon in [ @@ -180,9 +184,9 @@ class TestReducingGapResize: ((1.1, 2.2, 510.8, 510.9), 4), ((3, 10, 410, 256), 10), ]: - ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) im = gradients_image.resize( - (52, 34), Image.BICUBIC, box=box, reducing_gap=1.0 + (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0 ) with pytest.raises(AssertionError): @@ -196,9 +200,9 @@ class TestReducingGapResize: ((1.1, 2.2, 510.8, 510.9), 1.5), ((3, 10, 410, 256), 1), ]: - ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) im = gradients_image.resize( - (52, 34), Image.BICUBIC, box=box, reducing_gap=2.0 + (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0 ) with pytest.raises(AssertionError): @@ -212,9 +216,9 @@ class TestReducingGapResize: ((1.1, 2.2, 510.8, 510.9), 1), ((3, 10, 410, 256), 0.5), ]: - ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) im = gradients_image.resize( - (52, 34), Image.BICUBIC, box=box, reducing_gap=3.0 + (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0 ) with pytest.raises(AssertionError): @@ -224,9 +228,9 @@ class TestReducingGapResize: def test_reducing_gap_8(self, gradients_image): for box in [None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256)]: - ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box) im = gradients_image.resize( - (52, 34), Image.BICUBIC, box=box, reducing_gap=8.0 + (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=8.0 ) assert_image_equal(ref, im) @@ -236,8 +240,10 @@ class TestReducingGapResize: ((0, 0, 512, 512), 5.5), ((0.9, 1.7, 128, 128), 9.5), ]: - ref = gradients_image.resize((52, 34), Image.BOX, box=box) - im = gradients_image.resize((52, 34), Image.BOX, box=box, reducing_gap=1.0) + ref = gradients_image.resize((52, 34), Image.Resampling.BOX, box=box) + im = gradients_image.resize( + (52, 34), Image.Resampling.BOX, box=box, reducing_gap=1.0 + ) assert_image_similar(ref, im, epsilon) @@ -261,12 +267,12 @@ class TestImageResize: def test_default_filter(self): for mode in "L", "RGB", "I", "F": im = hopper(mode) - assert im.resize((20, 20), Image.BICUBIC) == im.resize((20, 20)) + assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20)) for mode in "1", "P": im = hopper(mode) - assert im.resize((20, 20), Image.NEAREST) == im.resize((20, 20)) + assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20)) for mode in "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16": im = hopper(mode) - assert im.resize((20, 20), Image.NEAREST) == im.resize((20, 20)) + assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20)) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 2d72ffa68..f96864c53 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -46,14 +46,14 @@ def test_zero(): def test_resample(): # Target image creation, inspected by eye. # >>> im = Image.open('Tests/images/hopper.ppm') - # >>> im = im.rotate(45, resample=Image.BICUBIC, expand=True) + # >>> im = im.rotate(45, resample=Image.Resampling.BICUBIC, expand=True) # >>> im.save('Tests/images/hopper_45.png') with Image.open("Tests/images/hopper_45.png") as target: for (resample, epsilon) in ( - (Image.NEAREST, 10), - (Image.BILINEAR, 5), - (Image.BICUBIC, 0), + (Image.Resampling.NEAREST, 10), + (Image.Resampling.BILINEAR, 5), + (Image.Resampling.BICUBIC, 0), ): im = hopper() im = im.rotate(45, resample=resample, expand=True) @@ -62,7 +62,7 @@ def test_resample(): def test_center_0(): im = hopper() - im = im.rotate(45, center=(0, 0), resample=Image.BICUBIC) + im = im.rotate(45, center=(0, 0), resample=Image.Resampling.BICUBIC) with Image.open("Tests/images/hopper_45.png") as target: target_origin = target.size[1] / 2 @@ -73,7 +73,7 @@ def test_center_0(): def test_center_14(): im = hopper() - im = im.rotate(45, center=(14, 14), resample=Image.BICUBIC) + im = im.rotate(45, center=(14, 14), resample=Image.Resampling.BICUBIC) with Image.open("Tests/images/hopper_45.png") as target: target_origin = target.size[1] / 2 - 14 @@ -90,7 +90,7 @@ def test_translate(): (target_origin, target_origin, target_origin + 128, target_origin + 128) ) - im = im.rotate(45, translate=(5, 5), resample=Image.BICUBIC) + im = im.rotate(45, translate=(5, 5), resample=Image.Resampling.BICUBIC) assert_image_similar(im, target, 1) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index dd140955d..6d4eb4cd1 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -97,24 +97,24 @@ def test_DCT_scaling_edges(): thumb = fromstring(tostring(im, "JPEG", quality=99, subsampling=0)) # small reducing_gap to amplify the effect - thumb.thumbnail((32, 32), Image.BICUBIC, reducing_gap=1.0) + thumb.thumbnail((32, 32), Image.Resampling.BICUBIC, reducing_gap=1.0) - ref = im.resize((32, 32), Image.BICUBIC) + ref = im.resize((32, 32), Image.Resampling.BICUBIC) # This is still JPEG, some error is present. Without the fix it is 11.5 assert_image_similar(thumb, ref, 1.5) def test_reducing_gap_values(): im = hopper() - im.thumbnail((18, 18), Image.BICUBIC) + im.thumbnail((18, 18), Image.Resampling.BICUBIC) ref = hopper() - ref.thumbnail((18, 18), Image.BICUBIC, reducing_gap=2.0) + ref.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=2.0) # reducing_gap=2.0 should be the default assert_image_equal(ref, im) ref = hopper() - ref.thumbnail((18, 18), Image.BICUBIC, reducing_gap=None) + ref.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=None) with pytest.raises(AssertionError): assert_image_equal(ref, im) @@ -125,9 +125,9 @@ def test_reducing_gap_for_DCT_scaling(): with Image.open("Tests/images/hopper.jpg") as ref: # thumbnail should call draft with reducing_gap scale ref.draft(None, (18 * 3, 18 * 3)) - ref = ref.resize((18, 18), Image.BICUBIC) + ref = ref.resize((18, 18), Image.Resampling.BICUBIC) with Image.open("Tests/images/hopper.jpg") as im: - im.thumbnail((18, 18), Image.BICUBIC, reducing_gap=3.0) + im.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=3.0) assert_image_equal(ref, im) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index ea208362b..ac0e74969 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -34,20 +34,22 @@ class TestImageTransform: def test_palette(self): with Image.open("Tests/images/hopper.gif") as im: - transformed = im.transform(im.size, Image.AFFINE, [1, 0, 0, 0, 1, 0]) + transformed = im.transform( + im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0] + ) assert im.palette.palette == transformed.palette.palette def test_extent(self): im = hopper("RGB") (w, h) = im.size # fmt: off - transformed = im.transform(im.size, Image.EXTENT, + transformed = im.transform(im.size, Image.Transform.EXTENT, (0, 0, w//2, h//2), # ul -> lr - Image.BILINEAR) + Image.Resampling.BILINEAR) # fmt: on - scaled = im.resize((w * 2, h * 2), Image.BILINEAR).crop((0, 0, w, h)) + scaled = im.resize((w * 2, h * 2), Image.Resampling.BILINEAR).crop((0, 0, w, h)) # undone -- precision? assert_image_similar(transformed, scaled, 23) @@ -57,15 +59,18 @@ class TestImageTransform: im = hopper("RGB") (w, h) = im.size # fmt: off - transformed = im.transform(im.size, Image.QUAD, + transformed = im.transform(im.size, Image.Transform.QUAD, (0, 0, 0, h//2, # ul -> ccw around quad: w//2, h//2, w//2, 0), - Image.BILINEAR) + Image.Resampling.BILINEAR) # fmt: on scaled = im.transform( - (w, h), Image.AFFINE, (0.5, 0, 0, 0, 0.5, 0), Image.BILINEAR + (w, h), + Image.Transform.AFFINE, + (0.5, 0, 0, 0, 0.5, 0), + Image.Resampling.BILINEAR, ) assert_image_equal(transformed, scaled) @@ -80,9 +85,9 @@ class TestImageTransform: (w, h) = im.size transformed = im.transform( im.size, - Image.EXTENT, + Image.Transform.EXTENT, (0, 0, w * 2, h * 2), - Image.BILINEAR, + Image.Resampling.BILINEAR, fillcolor="red", ) @@ -93,18 +98,21 @@ class TestImageTransform: im = hopper("RGBA") (w, h) = im.size # fmt: off - transformed = im.transform(im.size, Image.MESH, + transformed = im.transform(im.size, Image.Transform.MESH, [((0, 0, w//2, h//2), # box (0, 0, 0, h, w, h, w, 0)), # ul -> ccw around quad ((w//2, h//2, w, h), # box (0, 0, 0, h, w, h, w, 0))], # ul -> ccw around quad - Image.BILINEAR) + Image.Resampling.BILINEAR) # fmt: on scaled = im.transform( - (w // 2, h // 2), Image.AFFINE, (2, 0, 0, 0, 2, 0), Image.BILINEAR + (w // 2, h // 2), + Image.Transform.AFFINE, + (2, 0, 0, 0, 2, 0), + Image.Resampling.BILINEAR, ) checker = Image.new("RGBA", im.size) @@ -137,14 +145,16 @@ class TestImageTransform: def test_alpha_premult_resize(self): def op(im, sz): - return im.resize(sz, Image.BILINEAR) + return im.resize(sz, Image.Resampling.BILINEAR) self._test_alpha_premult(op) def test_alpha_premult_transform(self): def op(im, sz): (w, h) = im.size - return im.transform(sz, Image.EXTENT, (0, 0, w, h), Image.BILINEAR) + return im.transform( + sz, Image.Transform.EXTENT, (0, 0, w, h), Image.Resampling.BILINEAR + ) self._test_alpha_premult(op) @@ -171,7 +181,7 @@ class TestImageTransform: @pytest.mark.parametrize("mode", ("RGBA", "LA")) def test_nearest_resize(self, mode): def op(im, sz): - return im.resize(sz, Image.NEAREST) + return im.resize(sz, Image.Resampling.NEAREST) self._test_nearest(op, mode) @@ -179,7 +189,9 @@ class TestImageTransform: def test_nearest_transform(self, mode): def op(im, sz): (w, h) = im.size - return im.transform(sz, Image.EXTENT, (0, 0, w, h), Image.NEAREST) + return im.transform( + sz, Image.Transform.EXTENT, (0, 0, w, h), Image.Resampling.NEAREST + ) self._test_nearest(op, mode) @@ -213,13 +225,15 @@ class TestImageTransform: def test_unknown_resampling_filter(self): with hopper() as im: (w, h) = im.size - for resample in (Image.BOX, "unknown"): + for resample in (Image.Resampling.BOX, "unknown"): with pytest.raises(ValueError): - im.transform((100, 100), Image.EXTENT, (0, 0, w, h), resample) + im.transform( + (100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample + ) class TestImageTransformAffine: - transform = Image.AFFINE + transform = Image.Transform.AFFINE def _test_image(self): im = hopper("RGB") @@ -247,7 +261,11 @@ class TestImageTransformAffine: else: transposed = im - for resample in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC]: + for resample in [ + Image.Resampling.NEAREST, + Image.Resampling.BILINEAR, + Image.Resampling.BICUBIC, + ]: transformed = im.transform( transposed.size, self.transform, matrix, resample ) @@ -257,13 +275,13 @@ class TestImageTransformAffine: self._test_rotate(0, None) def test_rotate_90_deg(self): - self._test_rotate(90, Image.ROTATE_90) + self._test_rotate(90, Image.Transpose.ROTATE_90) def test_rotate_180_deg(self): - self._test_rotate(180, Image.ROTATE_180) + self._test_rotate(180, Image.Transpose.ROTATE_180) def test_rotate_270_deg(self): - self._test_rotate(270, Image.ROTATE_270) + self._test_rotate(270, Image.Transpose.ROTATE_270) def _test_resize(self, scale, epsilonscale): im = self._test_image() @@ -273,9 +291,9 @@ class TestImageTransformAffine: matrix_down = [scale, 0, 0, 0, scale, 0, 0, 0] for resample, epsilon in [ - (Image.NEAREST, 0), - (Image.BILINEAR, 2), - (Image.BICUBIC, 1), + (Image.Resampling.NEAREST, 0), + (Image.Resampling.BILINEAR, 2), + (Image.Resampling.BICUBIC, 1), ]: transformed = im.transform(size_up, self.transform, matrix_up, resample) transformed = transformed.transform( @@ -306,9 +324,9 @@ class TestImageTransformAffine: matrix_down = [1, 0, x, 0, 1, y, 0, 0] for resample, epsilon in [ - (Image.NEAREST, 0), - (Image.BILINEAR, 1.5), - (Image.BICUBIC, 1), + (Image.Resampling.NEAREST, 0), + (Image.Resampling.BILINEAR, 1.5), + (Image.Resampling.BICUBIC, 1), ]: transformed = im.transform(size_up, self.transform, matrix_up, resample) transformed = transformed.transform( @@ -328,4 +346,4 @@ class TestImageTransformAffine: class TestImageTransformPerspective(TestImageTransformAffine): # Repeat all tests for AFFINE transformations with PERSPECTIVE - transform = Image.PERSPECTIVE + transform = Image.Transform.PERSPECTIVE diff --git a/Tests/test_image_transpose.py b/Tests/test_image_transpose.py index a004434da..6408e1564 100644 --- a/Tests/test_image_transpose.py +++ b/Tests/test_image_transpose.py @@ -1,12 +1,4 @@ -from PIL.Image import ( - FLIP_LEFT_RIGHT, - FLIP_TOP_BOTTOM, - ROTATE_90, - ROTATE_180, - ROTATE_270, - TRANSPOSE, - TRANSVERSE, -) +from PIL.Image import Transpose from . import helper from .helper import assert_image_equal @@ -20,7 +12,7 @@ HOPPER = { def test_flip_left_right(): def transpose(mode): im = HOPPER[mode] - out = im.transpose(FLIP_LEFT_RIGHT) + out = im.transpose(Transpose.FLIP_LEFT_RIGHT) assert out.mode == mode assert out.size == im.size @@ -37,7 +29,7 @@ def test_flip_left_right(): def test_flip_top_bottom(): def transpose(mode): im = HOPPER[mode] - out = im.transpose(FLIP_TOP_BOTTOM) + out = im.transpose(Transpose.FLIP_TOP_BOTTOM) assert out.mode == mode assert out.size == im.size @@ -54,7 +46,7 @@ def test_flip_top_bottom(): def test_rotate_90(): def transpose(mode): im = HOPPER[mode] - out = im.transpose(ROTATE_90) + out = im.transpose(Transpose.ROTATE_90) assert out.mode == mode assert out.size == im.size[::-1] @@ -71,7 +63,7 @@ def test_rotate_90(): def test_rotate_180(): def transpose(mode): im = HOPPER[mode] - out = im.transpose(ROTATE_180) + out = im.transpose(Transpose.ROTATE_180) assert out.mode == mode assert out.size == im.size @@ -88,7 +80,7 @@ def test_rotate_180(): def test_rotate_270(): def transpose(mode): im = HOPPER[mode] - out = im.transpose(ROTATE_270) + out = im.transpose(Transpose.ROTATE_270) assert out.mode == mode assert out.size == im.size[::-1] @@ -105,7 +97,7 @@ def test_rotate_270(): def test_transpose(): def transpose(mode): im = HOPPER[mode] - out = im.transpose(TRANSPOSE) + out = im.transpose(Transpose.TRANSPOSE) assert out.mode == mode assert out.size == im.size[::-1] @@ -122,7 +114,7 @@ def test_transpose(): def test_tranverse(): def transpose(mode): im = HOPPER[mode] - out = im.transpose(TRANSVERSE) + out = im.transpose(Transpose.TRANSVERSE) assert out.mode == mode assert out.size == im.size[::-1] @@ -143,20 +135,31 @@ def test_roundtrip(): def transpose(first, second): return im.transpose(first).transpose(second) - assert_image_equal(im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT)) - assert_image_equal(im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM)) - assert_image_equal(im, transpose(ROTATE_90, ROTATE_270)) - assert_image_equal(im, transpose(ROTATE_180, ROTATE_180)) assert_image_equal( - im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM) + im, transpose(Transpose.FLIP_LEFT_RIGHT, Transpose.FLIP_LEFT_RIGHT) ) assert_image_equal( - im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT) + im, transpose(Transpose.FLIP_TOP_BOTTOM, Transpose.FLIP_TOP_BOTTOM) + ) + assert_image_equal(im, transpose(Transpose.ROTATE_90, Transpose.ROTATE_270)) + assert_image_equal(im, transpose(Transpose.ROTATE_180, Transpose.ROTATE_180)) + assert_image_equal( + im.transpose(Transpose.TRANSPOSE), + transpose(Transpose.ROTATE_90, Transpose.FLIP_TOP_BOTTOM), ) assert_image_equal( - im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT) + im.transpose(Transpose.TRANSPOSE), + transpose(Transpose.ROTATE_270, Transpose.FLIP_LEFT_RIGHT), ) assert_image_equal( - im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM) + im.transpose(Transpose.TRANSVERSE), + transpose(Transpose.ROTATE_90, Transpose.FLIP_LEFT_RIGHT), + ) + assert_image_equal( + im.transpose(Transpose.TRANSVERSE), + transpose(Transpose.ROTATE_270, Transpose.FLIP_TOP_BOTTOM), + ) + assert_image_equal( + im.transpose(Transpose.TRANSVERSE), + transpose(Transpose.ROTATE_180, Transpose.TRANSPOSE), ) - assert_image_equal(im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE)) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 99f3b4e03..09af11389 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -140,7 +140,7 @@ def test_intent(): skip_missing() assert ImageCms.getDefaultIntent(SRGB) == 0 support = ImageCms.isIntentSupported( - SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, ImageCms.DIRECTION_INPUT + SRGB, ImageCms.Intent.ABSOLUTE_COLORIMETRIC, ImageCms.Direction.INPUT ) assert support == 1 @@ -153,7 +153,7 @@ def test_profile_object(): # ["sRGB built-in", "", "WhitePoint : D65 (daylight)", "", ""] assert ImageCms.getDefaultIntent(p) == 0 support = ImageCms.isIntentSupported( - p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, ImageCms.DIRECTION_INPUT + p, ImageCms.Intent.ABSOLUTE_COLORIMETRIC, ImageCms.Direction.INPUT ) assert support == 1 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index b661494c7..3cd755cb4 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -183,7 +183,7 @@ def test_bitmap(): im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) with Image.open("Tests/images/pil123rgba.png") as small: - small = small.resize((50, 50), Image.NEAREST) + small = small.resize((50, 50), Image.Resampling.NEAREST) # Act draw.bitmap((10, 10), small) @@ -319,7 +319,7 @@ def test_ellipse_symmetric(): im = Image.new("RGB", (width, 100)) draw = ImageDraw.Draw(im) draw.ellipse(bbox, fill="green", outline="blue") - assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT)) + assert_image_equal(im, im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)) def test_ellipse_width(): diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index a5c76700d..3e86477c5 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -23,7 +23,7 @@ class TestImageFile: def test_parser(self): def roundtrip(format): - im = hopper("L").resize((1000, 1000), Image.NEAREST) + im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST) if format in ("MSP", "XBM"): im = im.convert("1") diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 0d423aab7..8e2c61848 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -29,7 +29,7 @@ pytestmark = skip_unless_feature("freetype2") class TestImageFont: - LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC + LAYOUT_ENGINE = ImageFont.Layout.BASIC def get_font(self): return ImageFont.truetype( @@ -94,12 +94,12 @@ class TestImageFont: try: ttf = ImageFont.truetype( - FONT_PATH, FONT_SIZE, layout_engine=ImageFont.LAYOUT_RAQM + FONT_PATH, FONT_SIZE, layout_engine=ImageFont.Layout.RAQM ) finally: ImageFont.core.HAVE_RAQM = have_raqm - assert ttf.layout_engine == ImageFont.LAYOUT_BASIC + assert ttf.layout_engine == ImageFont.Layout.BASIC def _render(self, font): txt = "Hello World!" @@ -182,7 +182,7 @@ class TestImageFont: im = Image.new(mode, (1, 1), 0) d = ImageDraw.Draw(im) - if self.LAYOUT_ENGINE == ImageFont.LAYOUT_BASIC: + if self.LAYOUT_ENGINE == ImageFont.Layout.BASIC: length = d.textlength(text, f) assert length == length_basic else: @@ -294,7 +294,7 @@ class TestImageFont: word = "testing" font = self.get_font() - orientation = Image.ROTATE_90 + orientation = Image.Transpose.ROTATE_90 transposed_font = ImageFont.TransposedFont(font, orientation=orientation) # Original font @@ -333,7 +333,7 @@ class TestImageFont: # Arrange text = "mask this" font = self.get_font() - orientation = Image.ROTATE_90 + orientation = Image.Transpose.ROTATE_90 transposed_font = ImageFont.TransposedFont(font, orientation=orientation) # Act @@ -604,7 +604,7 @@ class TestImageFont: # Arrange t = self.get_font() # Act / Assert - if t.layout_engine == ImageFont.LAYOUT_BASIC: + if t.layout_engine == ImageFont.Layout.BASIC: with pytest.raises(KeyError): t.getmask("абвг", direction="rtl") with pytest.raises(KeyError): @@ -753,7 +753,7 @@ class TestImageFont: name, text = "quick", "Quick" path = f"Tests/images/test_anchor_{name}_{anchor}.png" - if self.LAYOUT_ENGINE == ImageFont.LAYOUT_RAQM: + if self.LAYOUT_ENGINE == ImageFont.Layout.RAQM: width, height = (129, 44) else: width, height = (128, 44) @@ -993,7 +993,7 @@ class TestImageFont: @skip_unless_feature("raqm") class TestImageFont_RaqmLayout(TestImageFont): - LAYOUT_ENGINE = ImageFont.LAYOUT_RAQM + LAYOUT_ENGINE = ImageFont.Layout.RAQM def test_render_mono_size(): @@ -1004,7 +1004,7 @@ def test_render_mono_size(): ttf = ImageFont.truetype( "Tests/fonts/DejaVuSans/DejaVuSans.ttf", 18, - layout_engine=ImageFont.LAYOUT_BASIC, + layout_engine=ImageFont.Layout.BASIC, ) draw.text((10, 10), "r" * 10, "black", ttf) diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index 0571aabf4..688af7113 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -34,7 +34,7 @@ def test_basic(tmp_path): imOut = imIn.copy() verify(imOut) # copy - imOut = imIn.transform((w, h), Image.EXTENT, (0, 0, w, h)) + imOut = imIn.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h)) verify(imOut) # transform filename = str(tmp_path / "temp.im") diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index bd44f63a3..04966d5df 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -696,12 +696,12 @@ parameter must be set to ``True``. The following parameters can also be set: operation to be used for this frame before rendering the next frame. Defaults to 0. - * 0 (:py:data:`~PIL.PngImagePlugin.APNG_DISPOSE_OP_NONE`, default) - + * 0 (:py:data:`~PIL.PngImagePlugin.Disposal.OP_NONE`, default) - No disposal is done on this frame before rendering the next frame. - * 1 (:py:data:`PIL.PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND`) - + * 1 (:py:data:`PIL.PngImagePlugin.Disposal.OP_BACKGROUND`) - This frame's modified region is cleared to fully transparent black before rendering the next frame. - * 2 (:py:data:`~PIL.PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS`) - + * 2 (:py:data:`~PIL.PngImagePlugin.Disposal.OP_PREVIOUS`) - This frame's modified region is reverted to the previous frame's contents before rendering the next frame. @@ -710,10 +710,10 @@ parameter must be set to ``True``. The following parameters can also be set: operation to be used for this frame before rendering the next frame. Defaults to 0. - * 0 (:py:data:`~PIL.PngImagePlugin.APNG_BLEND_OP_SOURCE`) - + * 0 (:py:data:`~PIL.PngImagePlugin.Blend.OP_SOURCE`) - All color components of this frame, including alpha, overwrite the previous output image contents. - * 1 (:py:data:`~PIL.PngImagePlugin.APNG_BLEND_OP_OVER`) - + * 1 (:py:data:`~PIL.PngImagePlugin.Blend.OP_OVER`) - This frame should be alpha composited with the previous output image contents. .. note:: diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index aa9efe192..b0dbffda4 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -155,7 +155,7 @@ Processing a subrectangle, and pasting it back :: - region = region.transpose(Image.ROTATE_180) + region = region.transpose(Image.Transpose.ROTATE_180) im.paste(region, box) When pasting regions back, the size of the region must match the given region @@ -238,11 +238,11 @@ Transposing an image :: - out = im.transpose(Image.FLIP_LEFT_RIGHT) - out = im.transpose(Image.FLIP_TOP_BOTTOM) - out = im.transpose(Image.ROTATE_90) - out = im.transpose(Image.ROTATE_180) - out = im.transpose(Image.ROTATE_270) + out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) + out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + out = im.transpose(Image.Transpose.ROTATE_90) + out = im.transpose(Image.Transpose.ROTATE_180) + out = im.transpose(Image.Transpose.ROTATE_270) ``transpose(ROTATE)`` operations can also be performed identically with :py:meth:`~PIL.Image.Image.rotate` operations, provided the ``expand`` flag is diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index c80b28a98..2613b6585 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -254,7 +254,8 @@ This rotates the input image by ``theta`` degrees counter clockwise: .. automethod:: PIL.Image.Image.transform .. automethod:: PIL.Image.Image.transpose -This flips the input image by using the :data:`FLIP_LEFT_RIGHT` method. +This flips the input image by using the :data:`PIL.Image.Transpose.FLIP_LEFT_RIGHT` +method. .. code-block:: python @@ -263,9 +264,9 @@ This flips the input image by using the :data:`FLIP_LEFT_RIGHT` method. with Image.open("hopper.jpg") as im: # Flip the image from left to right - im_flipped = im.transpose(method=Image.FLIP_LEFT_RIGHT) + im_flipped = im.transpose(method=Image.Transpose.FLIP_LEFT_RIGHT) # To flip the image from top to bottom, - # use the method "Image.FLIP_TOP_BOTTOM" + # use the method "Image.Transpose.FLIP_TOP_BOTTOM" .. automethod:: PIL.Image.Image.verify @@ -389,68 +390,57 @@ Transpose methods Used to specify the :meth:`Image.transpose` method to use. -.. data:: FLIP_LEFT_RIGHT -.. data:: FLIP_TOP_BOTTOM -.. data:: ROTATE_90 -.. data:: ROTATE_180 -.. data:: ROTATE_270 -.. data:: TRANSPOSE -.. data:: TRANSVERSE +.. autoclass:: Transpose + :members: + :undoc-members: Transform methods ^^^^^^^^^^^^^^^^^ Used to specify the :meth:`Image.transform` method to use. -.. data:: AFFINE +.. py:class:: Transform - Affine transform + .. py:attribute:: AFFINE -.. data:: EXTENT + Affine transform - Cut out a rectangular subregion + .. py:attribute:: EXTENT -.. data:: PERSPECTIVE + Cut out a rectangular subregion - Perspective transform + .. py:attribute:: PERSPECTIVE -.. data:: QUAD + Perspective transform - Map a quadrilateral to a rectangle + .. py:attribute:: QUAD -.. data:: MESH + Map a quadrilateral to a rectangle - Map a number of source quadrilaterals in one operation + .. py:attribute:: MESH + + Map a number of source quadrilaterals in one operation Resampling filters ^^^^^^^^^^^^^^^^^^ See :ref:`concept-filters` for details. -.. data:: NEAREST - :noindex: -.. data:: BOX - :noindex: -.. data:: BILINEAR - :noindex: -.. data:: HAMMING - :noindex: -.. data:: BICUBIC - :noindex: -.. data:: LANCZOS - :noindex: +.. autoclass:: Resampling + :members: + :undoc-members: -Some filters are also available under the following names for backwards compatibility: +Some deprecated filters are also available under the following names: .. data:: NONE :noindex: - :value: NEAREST + :value: Resampling.NEAREST .. data:: LINEAR - :value: BILINEAR + :value: Resampling.BILINEAR .. data:: CUBIC - :value: BICUBIC + :value: Resampling.BICUBIC .. data:: ANTIALIAS - :value: LANCZOS + :value: Resampling.LANCZOS Dither modes ^^^^^^^^^^^^ @@ -458,48 +448,56 @@ Dither modes Used to specify the dithering method to use for the :meth:`~Image.convert` and :meth:`~Image.quantize` methods. -.. data:: NONE - :noindex: +.. py:class:: Dither - No dither + .. py:attribute:: NONE -.. comment: (not implemented) - .. data:: ORDERED - .. data:: RASTERIZE + No dither -.. data:: FLOYDSTEINBERG + .. py:attribute:: ORDERED - Floyd-Steinberg dither + Not implemented + + .. py:attribute:: RASTERIZE + + Not implemented + + .. py:attribute:: FLOYDSTEINBERG + + Floyd-Steinberg dither Palettes ^^^^^^^^ Used to specify the pallete to use for the :meth:`~Image.convert` method. -.. data:: WEB -.. data:: ADAPTIVE +.. autoclass:: Palette + :members: + :undoc-members: Quantization methods ^^^^^^^^^^^^^^^^^^^^ Used to specify the quantization method to use for the :meth:`~Image.quantize` method. -.. data:: MEDIANCUT +.. py:class:: Quantize - Median cut. Default method, except for RGBA images. This method does not support - RGBA images. + .. py:attribute:: MEDIANCUT -.. data:: MAXCOVERAGE + Median cut. Default method, except for RGBA images. This method does not support + RGBA images. - Maximum coverage. This method does not support RGBA images. + .. py:attribute:: MAXCOVERAGE -.. data:: FASTOCTREE + Maximum coverage. This method does not support RGBA images. - Fast octree. Default method for RGBA images. + .. py:attribute:: FASTOCTREE -.. data:: LIBIMAGEQUANT + Fast octree. Default method for RGBA images. - libimagequant + .. py:attribute:: LIBIMAGEQUANT - Check support using :py:func:`PIL.features.check_feature` - with ``feature="libimagequant"``. + libimagequant + + Check support using :py:func:`PIL.features.check_feature` with + ``feature="libimagequant"``. diff --git a/docs/reference/ImageCms.rst b/docs/reference/ImageCms.rst index f938e63a0..9b9b5e7b2 100644 --- a/docs/reference/ImageCms.rst +++ b/docs/reference/ImageCms.rst @@ -118,8 +118,8 @@ can be easily displayed in a chromaticity diagram, for example). another profile (usually overridden at run-time, but provided here for DeviceLink and embedded source profiles, see 7.2.15 of ICC.1:2010). - One of ``ImageCms.INTENT_ABSOLUTE_COLORIMETRIC``, ``ImageCms.INTENT_PERCEPTUAL``, - ``ImageCms.INTENT_RELATIVE_COLORIMETRIC`` and ``ImageCms.INTENT_SATURATION``. + One of ``ImageCms.Intent.ABSOLUTE_COLORIMETRIC``, ``ImageCms.Intent.PERCEPTUAL``, + ``ImageCms.Intent.RELATIVE_COLORIMETRIC`` and ``ImageCms.Intent.SATURATION``. .. py:attribute:: profile_id :type: bytes @@ -313,14 +313,14 @@ can be easily displayed in a chromaticity diagram, for example). the CLUT model. The dictionary is indexed by intents - (``ImageCms.INTENT_ABSOLUTE_COLORIMETRIC``, - ``ImageCms.INTENT_PERCEPTUAL``, - ``ImageCms.INTENT_RELATIVE_COLORIMETRIC`` and - ``ImageCms.INTENT_SATURATION``). + (``ImageCms.Intent.ABSOLUTE_COLORIMETRIC``, + ``ImageCms.Intent.PERCEPTUAL``, + ``ImageCms.Intent.RELATIVE_COLORIMETRIC`` and + ``ImageCms.Intent.SATURATION``). The values are 3-tuples indexed by directions - (``ImageCms.DIRECTION_INPUT``, ``ImageCms.DIRECTION_OUTPUT``, - ``ImageCms.DIRECTION_PROOF``). + (``ImageCms.Direction.INPUT``, ``ImageCms.Direction.OUTPUT``, + ``ImageCms.Direction.PROOF``). The elements of the tuple are booleans. If the value is ``True``, that intent is supported for that direction. @@ -331,14 +331,14 @@ can be easily displayed in a chromaticity diagram, for example). Returns a dictionary of all supported intents and directions. The dictionary is indexed by intents - (``ImageCms.INTENT_ABSOLUTE_COLORIMETRIC``, - ``ImageCms.INTENT_PERCEPTUAL``, - ``ImageCms.INTENT_RELATIVE_COLORIMETRIC`` and - ``ImageCms.INTENT_SATURATION``). + (``ImageCms.Intent.ABSOLUTE_COLORIMETRIC``, + ``ImageCms.Intent.PERCEPTUAL``, + ``ImageCms.Intent.RELATIVE_COLORIMETRIC`` and + ``ImageCms.Intent.SATURATION``). The values are 3-tuples indexed by directions - (``ImageCms.DIRECTION_INPUT``, ``ImageCms.DIRECTION_OUTPUT``, - ``ImageCms.DIRECTION_PROOF``). + (``ImageCms.Direction.INPUT``, ``ImageCms.Direction.OUTPUT``, + ``ImageCms.Direction.PROOF``). The elements of the tuple are booleans. If the value is ``True``, that intent is supported for that direction. @@ -352,11 +352,11 @@ can be easily displayed in a chromaticity diagram, for example). Note that you can also get this information for all intents and directions with :py:attr:`.intent_supported`. - :param intent: One of ``ImageCms.INTENT_ABSOLUTE_COLORIMETRIC``, - ``ImageCms.INTENT_PERCEPTUAL``, - ``ImageCms.INTENT_RELATIVE_COLORIMETRIC`` - and ``ImageCms.INTENT_SATURATION``. - :param direction: One of ``ImageCms.DIRECTION_INPUT``, - ``ImageCms.DIRECTION_OUTPUT`` - and ``ImageCms.DIRECTION_PROOF`` + :param intent: One of ``ImageCms.Intent.ABSOLUTE_COLORIMETRIC``, + ``ImageCms.Intent.PERCEPTUAL``, + ``ImageCms.Intent.RELATIVE_COLORIMETRIC`` + and ``ImageCms.Intent.SATURATION``. + :param direction: One of ``ImageCms.Direction.INPUT``, + ``ImageCms.Direction.OUTPUT`` + and ``ImageCms.Direction.PROOF`` :return: Boolean if the intent and direction is supported. diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 5f718ce19..8efef7cfd 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -60,12 +60,12 @@ Methods Constants --------- -.. data:: PIL.ImageFont.LAYOUT_BASIC +.. 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 +.. data:: PIL.ImageFont.Layout.RAQM Use Raqm text layout for TrueType font. Advanced features are supported. diff --git a/docs/reference/features.rst b/docs/reference/features.rst index 0a6381098..c66193061 100644 --- a/docs/reference/features.rst +++ b/docs/reference/features.rst @@ -57,7 +57,7 @@ Support for the following features can be checked: * ``transp_webp``: Support for transparency in WebP images. * ``webp_mux``: (compile time) Support for EXIF data in WebP images. * ``webp_anim``: (compile time) Support for animated WebP images. -* ``raqm``: Raqm library, required for ``ImageFont.LAYOUT_RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer. +* ``raqm``: Raqm library, required for ``ImageFont.Layout.RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer. * ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available. * ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library. diff --git a/docs/reference/plugins.rst b/docs/reference/plugins.rst index 7094f8784..5c833a35f 100644 --- a/docs/reference/plugins.rst +++ b/docs/reference/plugins.rst @@ -230,8 +230,7 @@ Plugin reference .. automodule:: PIL.PngImagePlugin :members: ChunkStream, PngImageFile, PngStream, getchunks, is_cid, putchunk, - MAX_TEXT_CHUNK, MAX_TEXT_MEMORY, APNG_BLEND_OP_SOURCE, APNG_BLEND_OP_OVER, - APNG_DISPOSE_OP_NONE, APNG_DISPOSE_OP_BACKGROUND, APNG_DISPOSE_OP_PREVIOUS + Blend, Disposal, MAX_TEXT_CHUNK, MAX_TEXT_MEMORY :undoc-members: :show-inheritance: :member-order: groupwise diff --git a/docs/releasenotes/2.7.0.rst b/docs/releasenotes/2.7.0.rst index 660d33164..dda814c1f 100644 --- a/docs/releasenotes/2.7.0.rst +++ b/docs/releasenotes/2.7.0.rst @@ -111,16 +111,14 @@ downscaling with libjpeg, which uses supersampling internally, not convolutions. Image transposition ------------------- -A new method :py:data:`PIL.Image.TRANSPOSE` has been added for the +A new method ``TRANSPOSE`` has been added for the :py:meth:`~PIL.Image.Image.transpose` operation in addition to -:py:data:`~PIL.Image.FLIP_LEFT_RIGHT`, :py:data:`~PIL.Image.FLIP_TOP_BOTTOM`, -:py:data:`~PIL.Image.ROTATE_90`, :py:data:`~PIL.Image.ROTATE_180`, -:py:data:`~PIL.Image.ROTATE_270`. :py:data:`~PIL.Image.TRANSPOSE` is an algebra -transpose, with an image reflected across its main diagonal. +``FLIP_LEFT_RIGHT``, ``FLIP_TOP_BOTTOM``, ``ROTATE_90``, ``ROTATE_180``, +``ROTATE_270``. ``TRANSPOSE`` is an algebra transpose, with an image reflected +across its main diagonal. -The speed of :py:data:`~PIL.Image.ROTATE_90`, :py:data:`~PIL.Image.ROTATE_270` -and :py:data:`~PIL.Image.TRANSPOSE` has been significantly improved for large -images which don't fit in the processor cache. +The speed of ``ROTATE_90``, ``ROTATE_270`` and ``TRANSPOSE`` has been significantly +improved for large images which don't fit in the processor cache. Gaussian blur and unsharp mask ------------------------------ diff --git a/selftest.py b/selftest.py index 4ebd7cc00..5010c1213 100755 --- a/selftest.py +++ b/selftest.py @@ -97,9 +97,9 @@ def testimage(): 10456 >>> len(im.tobytes()) 49152 - >>> _info(im.transform((512, 512), Image.AFFINE, (1,0,0,0,1,0))) + >>> _info(im.transform((512, 512), Image.Transform.AFFINE, (1,0,0,0,1,0))) (None, 'RGB', (512, 512)) - >>> _info(im.transform((512, 512), Image.EXTENT, (32,32,96,96))) + >>> _info(im.transform((512, 512), Image.Transform.EXTENT, (32,32,96,96))) (None, 'RGB', (512, 512)) The ImageDraw module lets you draw stuff in raster images: diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 7b78597b4..111f03ae6 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -30,19 +30,33 @@ BLP files come in many different flavours: """ import struct +from enum import IntEnum from io import BytesIO from . import Image, ImageFile -BLP_FORMAT_JPEG = 0 -BLP_ENCODING_UNCOMPRESSED = 1 -BLP_ENCODING_DXT = 2 -BLP_ENCODING_UNCOMPRESSED_RAW_BGRA = 3 +class Format(IntEnum): + JPEG = 0 -BLP_ALPHA_ENCODING_DXT1 = 0 -BLP_ALPHA_ENCODING_DXT3 = 1 -BLP_ALPHA_ENCODING_DXT5 = 7 + +class Encoding(IntEnum): + UNCOMPRESSED = 1 + DXT = 2 + UNCOMPRESSED_RAW_BGRA = 3 + + +class AlphaEncoding(IntEnum): + DXT1 = 0 + DXT3 = 1 + DXT5 = 7 + + +globals().update({"BLP_FORMAT_" + k: v for k, v in Format.__members__.items()}) +globals().update({"BLP_ENCODING_" + k: v for k, v in Encoding.__members__.items()}) +globals().update( + {"BLP_ALPHA_ENCODING_" + k: v for k, v in AlphaEncoding.__members__.items()} +) def unpack_565(i): @@ -320,7 +334,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder): class BLP1Decoder(_BLPBaseDecoder): def _load(self): - if self._blp_compression == BLP_FORMAT_JPEG: + if self._blp_compression == Format.JPEG: self._decode_jpeg_stream() elif self._blp_compression == 1: @@ -372,7 +386,7 @@ class BLP2Decoder(_BLPBaseDecoder): if self._blp_compression == 1: # Uncompressed or DirectX compression - if self._blp_encoding == BLP_ENCODING_UNCOMPRESSED: + if self._blp_encoding == Encoding.UNCOMPRESSED: _data = BytesIO(self._safe_read(self._blp_lengths[0])) while True: try: @@ -382,8 +396,8 @@ class BLP2Decoder(_BLPBaseDecoder): b, g, r, a = palette[offset] data.extend((r, g, b)) - elif self._blp_encoding == BLP_ENCODING_DXT: - if self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT1: + elif self._blp_encoding == Encoding.DXT: + if self._blp_alpha_encoding == AlphaEncoding.DXT1: linesize = (self.size[0] + 3) // 4 * 8 for yb in range((self.size[1] + 3) // 4): for d in decode_dxt1( @@ -391,13 +405,13 @@ class BLP2Decoder(_BLPBaseDecoder): ): data += d - elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT3: + elif self._blp_alpha_encoding == AlphaEncoding.DXT3: linesize = (self.size[0] + 3) // 4 * 16 for yb in range((self.size[1] + 3) // 4): for d in decode_dxt3(self._safe_read(linesize)): data += d - elif self._blp_alpha_encoding == BLP_ALPHA_ENCODING_DXT5: + elif self._blp_alpha_encoding == AlphaEncoding.DXT5: linesize = (self.size[0] + 3) // 4 * 16 for yb in range((self.size[1] + 3) // 4): for d in decode_dxt5(self._safe_read(linesize)): diff --git a/src/PIL/FtexImagePlugin.py b/src/PIL/FtexImagePlugin.py index 3b169038c..de5859ba5 100644 --- a/src/PIL/FtexImagePlugin.py +++ b/src/PIL/FtexImagePlugin.py @@ -52,13 +52,20 @@ Note: All data is stored in little-Endian (Intel) byte order. """ import struct +from enum import IntEnum from io import BytesIO from . import Image, ImageFile MAGIC = b"FTEX" -FORMAT_DXT1 = 0 -FORMAT_UNCOMPRESSED = 1 + + +class Format(IntEnum): + DXT1 = 0 + UNCOMPRESSED = 1 + + +globals().update({"FORMAT_" + k: v for k, v in Format.__members__.items()}) class FtexImageFile(ImageFile.ImageFile): @@ -83,10 +90,10 @@ class FtexImageFile(ImageFile.ImageFile): data = self.fp.read(mipmap_size) - if format == FORMAT_DXT1: + if format == Format.DXT1: self.mode = "RGBA" self.tile = [("bcn", (0, 0) + self.size, 0, (1))] - elif format == FORMAT_UNCOMPRESSED: + elif format == Format.UNCOMPRESSED: self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))] else: raise ValueError(f"Invalid texture compression format: {repr(format)}") diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 8c2180bc1..4f8ea209d 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -169,12 +169,12 @@ class GifImageFile(ImageFile.ImageFile): if "transparency" in self.info: self.mode = "RGBA" self.im.putpalettealpha(self.info["transparency"], 0) - self.im = self.im.convert("RGBA", Image.FLOYDSTEINBERG) + self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG) del self.info["transparency"] else: self.mode = "RGB" - self.im = self.im.convert("RGB", Image.FLOYDSTEINBERG) + self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG) if self.dispose: self.im.paste(self.dispose, self.dispose_extent) @@ -425,7 +425,7 @@ def _normalize_mode(im, initial_call=False): palette_size = 256 if im.palette: palette_size = len(im.palette.getdata()[1]) // 3 - im = im.convert("P", palette=Image.ADAPTIVE, colors=palette_size) + im = im.convert("P", palette=Image.Palette.ADAPTIVE, colors=palette_size) if im.palette.mode == "RGBA": for rgba in im.palette.colors.keys(): if rgba[3] == 0: diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index d9ff9b5e7..07ba12a09 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -69,7 +69,7 @@ def _save(im, fp, filename): if not tmp: # TODO: invent a more convenient method for proportional scalings tmp = im.copy() - tmp.thumbnail(size, Image.LANCZOS, reducing_gap=None) + tmp.thumbnail(size, Image.Resampling.LANCZOS, reducing_gap=None) bits = BmpImagePlugin.SAVE[tmp.mode][1] if bmp else 32 fp.write(struct.pack(" 1 or factor_y > 1: @@ -2017,7 +2057,7 @@ class Image: def rotate( self, angle, - resample=NEAREST, + resample=Resampling.NEAREST, expand=0, center=None, translate=None, @@ -2030,12 +2070,12 @@ class Image: :param angle: In degrees counter clockwise. :param resample: An optional resampling filter. This can be - one of :py:data:`PIL.Image.NEAREST` (use nearest neighbour), + one of :py:data:`PIL.Image.Resampling.NEAREST` (use nearest neighbour), :py:data:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 - environment), or :py:data:`PIL.Image.BICUBIC` + environment), or :py:data:`PIL.Image.Resampling.BICUBIC` (cubic spline 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`. + set to :py:data:`PIL.Image.Resampling.NEAREST`. See :ref:`concept-filters`. :param expand: Optional expansion flag. If true, expands the output image to make it large enough to hold the entire rotated image. If false or omitted, make the output image the same size as the @@ -2056,9 +2096,11 @@ class Image: if angle == 0: return self.copy() if angle == 180: - return self.transpose(ROTATE_180) + return self.transpose(Transpose.ROTATE_180) if angle in (90, 270) and (expand or self.width == self.height): - return self.transpose(ROTATE_90 if angle == 90 else ROTATE_270) + return self.transpose( + Transpose.ROTATE_90 if angle == 90 else Transpose.ROTATE_270 + ) # Calculate the affine matrix. Note that this is the reverse # transformation (from destination image to source) because we @@ -2127,7 +2169,9 @@ class Image: matrix[2], matrix[5] = transform(-(nw - w) / 2.0, -(nh - h) / 2.0, matrix) w, h = nw, nh - return self.transform((w, h), AFFINE, matrix, resample, fillcolor=fillcolor) + return self.transform( + (w, h), Transform.AFFINE, matrix, resample, fillcolor=fillcolor + ) def save(self, fp, format=None, **params): """ @@ -2313,7 +2357,7 @@ class Image: """ return 0 - def thumbnail(self, size, resample=BICUBIC, reducing_gap=2.0): + def thumbnail(self, size, resample=Resampling.BICUBIC, reducing_gap=2.0): """ Make this image into a thumbnail. This method modifies the image to contain a thumbnail version of itself, no larger than @@ -2329,11 +2373,14 @@ class Image: :param size: Requested size. :param resample: Optional resampling filter. This can be one - of :py:data:`PIL.Image.NEAREST`, :py:data:`PIL.Image.BOX`, - :py:data:`PIL.Image.BILINEAR`, :py:data:`PIL.Image.HAMMING`, - :py:data:`PIL.Image.BICUBIC` or :py:data:`PIL.Image.LANCZOS`. - If omitted, it defaults to :py:data:`PIL.Image.BICUBIC`. - (was :py:data:`PIL.Image.NEAREST` prior to version 2.5.0). + of :py:data:`PIL.Image.Resampling.NEAREST`, + :py:data:`PIL.Image.Resampling.BOX`, + :py:data:`PIL.Image.Resampling.BILINEAR`, + :py:data:`PIL.Image.Resampling.HAMMING`, + :py:data:`PIL.Image.Resampling.BICUBIC` or + :py:data:`PIL.Image.Resampling.LANCZOS`. + If omitted, it defaults to :py:data:`PIL.Image.Resampling.BICUBIC`. + (was :py:data:`PIL.Image.Resampling.NEAREST` prior to version 2.5.0). See: :ref:`concept-filters`. :param reducing_gap: Apply optimization by resizing the image in two steps. First, reducing the image by integer times @@ -2388,7 +2435,13 @@ class Image: # FIXME: the different transform methods need further explanation # instead of bloating the method docs, add a separate chapter. def transform( - self, size, method, data=None, resample=NEAREST, fill=1, fillcolor=None + self, + size, + method, + data=None, + resample=Resampling.NEAREST, + fill=1, + fillcolor=None, ): """ Transforms this image. This method creates a new image with the @@ -2397,11 +2450,11 @@ class Image: :param size: The output size. :param method: The transformation method. This is one of - :py:data:`PIL.Image.EXTENT` (cut out a rectangular subregion), - :py:data:`PIL.Image.AFFINE` (affine transform), - :py:data:`PIL.Image.PERSPECTIVE` (perspective transform), - :py:data:`PIL.Image.QUAD` (map a quadrilateral to a rectangle), or - :py:data:`PIL.Image.MESH` (map a number of source quadrilaterals + :py:data:`PIL.Image.Transform.EXTENT` (cut out a rectangular subregion), + :py:data:`PIL.Image.Transform.AFFINE` (affine transform), + :py:data:`PIL.Image.Transform.PERSPECTIVE` (perspective transform), + :py:data:`PIL.Image.Transform.QUAD` (map a quadrilateral to a rectangle), or + :py:data:`PIL.Image.Transform.MESH` (map a number of source quadrilaterals in one operation). It may also be an :py:class:`~PIL.Image.ImageTransformHandler` @@ -2416,16 +2469,16 @@ class Image: class Example: def getdata(self): - method = Image.EXTENT + method = Image.Transform.EXTENT data = (0, 0, 100, 100) return method, data :param data: Extra data to the transformation method. :param resample: Optional resampling filter. It can be one of - :py:data:`PIL.Image.NEAREST` (use nearest neighbour), - :py:data:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 + :py:data:`PIL.Image.Resampling.NEAREST` (use nearest neighbour), + :py:data:`PIL.Image.Resampling.BILINEAR` (linear interpolation in a 2x2 environment), or :py:data:`PIL.Image.BICUBIC` (cubic spline 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`. + has mode "1" or "P", it is set to :py:data:`PIL.Image.Resampling.NEAREST`. See: :ref:`concept-filters`. :param fill: If ``method`` is an :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of @@ -2435,7 +2488,7 @@ class Image: :returns: An :py:class:`~PIL.Image.Image` object. """ - if self.mode in ("LA", "RGBA") and resample != NEAREST: + if self.mode in ("LA", "RGBA") and resample != Resampling.NEAREST: return ( self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode]) .transform(size, method, data, resample, fill, fillcolor) @@ -2456,10 +2509,12 @@ class Image: if self.mode == "P" and self.palette: im.palette = self.palette.copy() im.info = self.info.copy() - if method == MESH: + if method == Transform.MESH: # list of quads for box, quad in data: - im.__transformer(box, self, QUAD, quad, resample, fillcolor is None) + im.__transformer( + box, self, Transform.QUAD, quad, resample, fillcolor is None + ) else: im.__transformer( (0, 0) + size, self, method, data, resample, fillcolor is None @@ -2467,25 +2522,27 @@ class Image: return im - def __transformer(self, box, image, method, data, resample=NEAREST, fill=1): + def __transformer( + self, box, image, method, data, resample=Resampling.NEAREST, fill=1 + ): w = box[2] - box[0] h = box[3] - box[1] - if method == AFFINE: + if method == Transform.AFFINE: data = data[0:6] - elif method == EXTENT: + elif method == Transform.EXTENT: # convert extent to an affine transform x0, y0, x1, y1 = data xs = (x1 - x0) / w ys = (y1 - y0) / h - method = AFFINE + method = Transform.AFFINE data = (xs, 0, x0, 0, ys, y0) - elif method == PERSPECTIVE: + elif method == Transform.PERSPECTIVE: data = data[0:8] - elif method == QUAD: + elif method == Transform.QUAD: # quadrilateral warp. data specifies the four corners # given as NW, SW, SE, and NE. nw = data[0:2] @@ -2509,12 +2566,16 @@ class Image: else: raise ValueError("unknown transformation method") - if resample not in (NEAREST, BILINEAR, BICUBIC): - if resample in (BOX, HAMMING, LANCZOS): + if resample not in ( + Resampling.NEAREST, + Resampling.BILINEAR, + Resampling.BICUBIC, + ): + if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS): message = { - BOX: "Image.BOX", - HAMMING: "Image.HAMMING", - LANCZOS: "Image.LANCZOS/Image.ANTIALIAS", + Resampling.BOX: "Image.Resampling.BOX", + Resampling.HAMMING: "Image.Resampling.HAMMING", + Resampling.LANCZOS: "Image.Resampling.LANCZOS", }[resample] + f" ({resample}) cannot be used." else: message = f"Unknown resampling filter ({resample})." @@ -2522,9 +2583,9 @@ class Image: filters = [ f"{filter[1]} ({filter[0]})" for filter in ( - (NEAREST, "Image.NEAREST"), - (BILINEAR, "Image.BILINEAR"), - (BICUBIC, "Image.BICUBIC"), + (Resampling.NEAREST, "Image.Resampling.NEAREST"), + (Resampling.BILINEAR, "Image.Resampling.BILINEAR"), + (Resampling.BICUBIC, "Image.Resampling.BICUBIC"), ) ] raise ValueError( @@ -2536,7 +2597,7 @@ class Image: self.load() if image.mode in ("1", "P"): - resample = NEAREST + resample = Resampling.NEAREST self.im.transform2(box, image.im, method, data, resample, fill) @@ -2544,10 +2605,13 @@ class Image: """ Transpose image (flip or rotate in 90 degree steps) - :param method: One of :py:data:`PIL.Image.FLIP_LEFT_RIGHT`, - :py:data:`PIL.Image.FLIP_TOP_BOTTOM`, :py:data:`PIL.Image.ROTATE_90`, - :py:data:`PIL.Image.ROTATE_180`, :py:data:`PIL.Image.ROTATE_270`, - :py:data:`PIL.Image.TRANSPOSE` or :py:data:`PIL.Image.TRANSVERSE`. + :param method: One of :py:data:`PIL.Image.Transpose.FLIP_LEFT_RIGHT`, + :py:data:`PIL.Image.Transpose.FLIP_TOP_BOTTOM`, + :py:data:`PIL.Image.Transpose.ROTATE_90`, + :py:data:`PIL.Image.Transpose.ROTATE_180`, + :py:data:`PIL.Image.Transpose.ROTATE_270`, + :py:data:`PIL.Image.Transpose.TRANSPOSE` or + :py:data:`PIL.Image.Transpose.TRANSVERSE`. :returns: Returns a flipped or rotated copy of this image. """ diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 60e700f09..1f104133d 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -16,6 +16,7 @@ # below for the original description. import sys +from enum import IntEnum from PIL import Image @@ -100,14 +101,22 @@ core = _imagingcms # # intent/direction values -INTENT_PERCEPTUAL = 0 -INTENT_RELATIVE_COLORIMETRIC = 1 -INTENT_SATURATION = 2 -INTENT_ABSOLUTE_COLORIMETRIC = 3 -DIRECTION_INPUT = 0 -DIRECTION_OUTPUT = 1 -DIRECTION_PROOF = 2 +class Intent(IntEnum): + PERCEPTUAL = 0 + RELATIVE_COLORIMETRIC = 1 + SATURATION = 2 + ABSOLUTE_COLORIMETRIC = 3 + + +class Direction(IntEnum): + INPUT = 0 + OUTPUT = 1 + PROOF = 2 + + +globals().update({"INTENT_" + k: v for k, v in Intent.__members__.items()}) +globals().update({"DIRECTION_" + k: v for k, v in Direction.__members__.items()}) # # flags @@ -211,9 +220,9 @@ class ImageCmsTransform(Image.ImagePointHandler): output, input_mode, output_mode, - intent=INTENT_PERCEPTUAL, + intent=Intent.PERCEPTUAL, proof=None, - proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, + proof_intent=Intent.ABSOLUTE_COLORIMETRIC, flags=0, ): if proof is None: @@ -295,7 +304,7 @@ def profileToProfile( im, inputProfile, outputProfile, - renderingIntent=INTENT_PERCEPTUAL, + renderingIntent=Intent.PERCEPTUAL, outputMode=None, inPlace=False, flags=0, @@ -331,10 +340,10 @@ def profileToProfile( :param renderingIntent: Integer (0-3) specifying the rendering intent you wish to use for the transform - ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) - ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 - ImageCms.INTENT_SATURATION = 2 - ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. @@ -412,7 +421,7 @@ def buildTransform( outputProfile, inMode, outMode, - renderingIntent=INTENT_PERCEPTUAL, + renderingIntent=Intent.PERCEPTUAL, flags=0, ): """ @@ -458,10 +467,10 @@ def buildTransform( :param renderingIntent: Integer (0-3) specifying the rendering intent you wish to use for the transform - ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) - ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 - ImageCms.INTENT_SATURATION = 2 - ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. @@ -494,8 +503,8 @@ def buildProofTransform( proofProfile, inMode, outMode, - renderingIntent=INTENT_PERCEPTUAL, - proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, + renderingIntent=Intent.PERCEPTUAL, + proofRenderingIntent=Intent.ABSOLUTE_COLORIMETRIC, flags=FLAGS["SOFTPROOFING"], ): """ @@ -550,20 +559,20 @@ def buildProofTransform( :param renderingIntent: Integer (0-3) specifying the rendering intent you wish to use for the input->proof (simulated) transform - ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) - ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 - ImageCms.INTENT_SATURATION = 2 - ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. :param proofRenderingIntent: Integer (0-3) specifying the rendering intent you wish to use for proof->output transform - ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) - ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 - ImageCms.INTENT_SATURATION = 2 - ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. @@ -922,10 +931,10 @@ def getDefaultIntent(profile): :returns: Integer 0-3 specifying the default rendering intent for this profile. - ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) - ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 - ImageCms.INTENT_SATURATION = 2 - ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. @@ -960,19 +969,19 @@ def isIntentSupported(profile, intent, direction): :param intent: Integer (0-3) specifying the rendering intent you wish to use with this profile - ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT) - ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1 - ImageCms.INTENT_SATURATION = 2 - ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3 + ImageCms.Intent.PERCEPTUAL = 0 (DEFAULT) + ImageCms.Intent.RELATIVE_COLORIMETRIC = 1 + ImageCms.Intent.SATURATION = 2 + ImageCms.Intent.ABSOLUTE_COLORIMETRIC = 3 see the pyCMS documentation for details on rendering intents and what they do. :param direction: Integer specifying if the profile is to be used for input, output, or proof - INPUT = 0 (or use ImageCms.DIRECTION_INPUT) - OUTPUT = 1 (or use ImageCms.DIRECTION_OUTPUT) - PROOF = 2 (or use ImageCms.DIRECTION_PROOF) + INPUT = 0 (or use ImageCms.Direction.INPUT) + OUTPUT = 1 (or use ImageCms.Direction.OUTPUT) + PROOF = 2 (or use ImageCms.Direction.PROOF) :returns: 1 if the intent/direction are supported, -1 if they are not. :exception PyCMSError: diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index d2ece3752..1320af8f9 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -529,7 +529,7 @@ class Color3DLUT(MultibandFilter): return image.color_lut_3d( self.mode or image.mode, - Image.LINEAR, + Image.Resampling.BILINEAR, self.channels, self.size[0], self.size[1], diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 805c8fff9..9282d5279 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -28,13 +28,19 @@ import base64 import os import sys +from enum import IntEnum from io import BytesIO from . import Image from ._util import isDirectory, isPath -LAYOUT_BASIC = 0 -LAYOUT_RAQM = 1 + +class Layout(IntEnum): + BASIC = 0 + RAQM = 1 + + +globals().update({"LAYOUT_" + k: v for k, v in Layout.__members__.items()}) class _imagingft_not_installed: @@ -164,12 +170,12 @@ class FreeTypeFont: self.index = index self.encoding = encoding - if layout_engine not in (LAYOUT_BASIC, LAYOUT_RAQM): - layout_engine = LAYOUT_BASIC + if layout_engine not in (Layout.BASIC, Layout.RAQM): + layout_engine = Layout.BASIC if core.HAVE_RAQM: - layout_engine = LAYOUT_RAQM - elif layout_engine == LAYOUT_RAQM and not core.HAVE_RAQM: - layout_engine = LAYOUT_BASIC + layout_engine = Layout.RAQM + elif layout_engine == Layout.RAQM and not core.HAVE_RAQM: + layout_engine = Layout.BASIC self.layout_engine = layout_engine @@ -751,15 +757,16 @@ class TransposedFont: :param font: A font object. :param orientation: An optional orientation. If given, this should - be one of Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM, - Image.ROTATE_90, Image.ROTATE_180, or Image.ROTATE_270. + be one of Image.Transpose.FLIP_LEFT_RIGHT, Image.Transpose.FLIP_TOP_BOTTOM, + Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_180, or + Image.Transpose.ROTATE_270. """ self.font = font self.orientation = orientation # any 'transpose' argument, or None def getsize(self, text, *args, **kwargs): w, h = self.font.getsize(text) - if self.orientation in (Image.ROTATE_90, Image.ROTATE_270): + if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270): return h, w return w, h @@ -827,7 +834,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: - :data:`.ImageFont.LAYOUT_BASIC` or :data:`.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/ImageOps.py b/src/PIL/ImageOps.py index b170e9d8c..c6201d8ff 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -237,7 +237,7 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi return _lut(image, red + green + blue) -def contain(image, size, method=Image.BICUBIC): +def contain(image, size, method=Image.Resampling.BICUBIC): """ Returns a resized version of the image, set to the maximum width and height within the requested size, while maintaining the original aspect ratio. @@ -265,7 +265,7 @@ def contain(image, size, method=Image.BICUBIC): return image.resize(size, resample=method) -def pad(image, size, method=Image.BICUBIC, color=None, centering=(0.5, 0.5)): +def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5, 0.5)): """ Returns a resized and padded version of the image, expanded to fill the requested aspect ratio and size. @@ -315,7 +315,7 @@ def crop(image, border=0): return image.crop((left, top, image.size[0] - right, image.size[1] - bottom)) -def scale(image, factor, resample=Image.BICUBIC): +def scale(image, factor, resample=Image.Resampling.BICUBIC): """ Returns a rescaled image by a specific factor given in parameter. A factor greater than 1 expands the image, between 0 and 1 contracts the @@ -336,7 +336,7 @@ def scale(image, factor, resample=Image.BICUBIC): return image.resize(size, resample) -def deform(image, deformer, resample=Image.BILINEAR): +def deform(image, deformer, resample=Image.Resampling.BILINEAR): """ Deform the image. @@ -347,7 +347,9 @@ def deform(image, deformer, resample=Image.BILINEAR): in the PIL.Image.transform function. :return: An image. """ - return image.transform(image.size, Image.MESH, deformer.getmesh(image), resample) + return image.transform( + image.size, Image.Transform.MESH, deformer.getmesh(image), resample + ) def equalize(image, mask=None): @@ -408,7 +410,7 @@ def expand(image, border=0, fill=0): return out -def fit(image, size, method=Image.BICUBIC, bleed=0.0, centering=(0.5, 0.5)): +def fit(image, size, method=Image.Resampling.BICUBIC, bleed=0.0, centering=(0.5, 0.5)): """ Returns a resized and cropped version of the image, cropped to the requested aspect ratio and size. @@ -500,7 +502,7 @@ def flip(image): :param image: The image to flip. :return: An image. """ - return image.transpose(Image.FLIP_TOP_BOTTOM) + return image.transpose(Image.Transpose.FLIP_TOP_BOTTOM) def grayscale(image): @@ -533,7 +535,7 @@ def mirror(image): :param image: The image to mirror. :return: An image. """ - return image.transpose(Image.FLIP_LEFT_RIGHT) + return image.transpose(Image.Transpose.FLIP_LEFT_RIGHT) def posterize(image, bits): @@ -579,13 +581,13 @@ def exif_transpose(image): exif = image.getexif() orientation = exif.get(0x0112) method = { - 2: Image.FLIP_LEFT_RIGHT, - 3: Image.ROTATE_180, - 4: Image.FLIP_TOP_BOTTOM, - 5: Image.TRANSPOSE, - 6: Image.ROTATE_270, - 7: Image.TRANSVERSE, - 8: Image.ROTATE_90, + 2: Image.Transpose.FLIP_LEFT_RIGHT, + 3: Image.Transpose.ROTATE_180, + 4: Image.Transpose.FLIP_TOP_BOTTOM, + 5: Image.Transpose.TRANSPOSE, + 6: Image.Transpose.ROTATE_270, + 7: Image.Transpose.TRANSVERSE, + 8: Image.Transpose.ROTATE_90, }.get(orientation) if method is not None: transposed_image = image.transpose(method) diff --git a/src/PIL/ImageTransform.py b/src/PIL/ImageTransform.py index 77791ab72..7881f0d26 100644 --- a/src/PIL/ImageTransform.py +++ b/src/PIL/ImageTransform.py @@ -47,7 +47,7 @@ class AffineTransform(Transform): from an affine transform matrix. """ - method = Image.AFFINE + method = Image.Transform.AFFINE class ExtentTransform(Transform): @@ -69,7 +69,7 @@ class ExtentTransform(Transform): input image's coordinate system. See :ref:`coordinate-system`. """ - method = Image.EXTENT + method = Image.Transform.EXTENT class QuadTransform(Transform): @@ -86,7 +86,7 @@ class QuadTransform(Transform): source quadrilateral. """ - method = Image.QUAD + method = Image.Transform.QUAD class MeshTransform(Transform): @@ -99,4 +99,4 @@ class MeshTransform(Transform): :param data: A list of (bbox, quad) tuples. """ - method = Image.MESH + method = Image.Transform.MESH diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 0f596f1fd..94ca933b0 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -37,6 +37,7 @@ import re import struct import warnings import zlib +from enum import IntEnum from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence from ._binary import i16be as i16 @@ -94,36 +95,43 @@ See :ref:`Text in PNG File Format`. # APNG frame disposal modes -APNG_DISPOSE_OP_NONE = 0 -""" -No disposal is done on this frame before rendering the next frame. -See :ref:`Saving APNG sequences`. -""" -APNG_DISPOSE_OP_BACKGROUND = 1 -""" -This frame’s modified region is cleared to fully transparent black before rendering -the next frame. -See :ref:`Saving APNG sequences`. -""" -APNG_DISPOSE_OP_PREVIOUS = 2 -""" -This frame’s modified region is reverted to the previous frame’s contents before -rendering the next frame. -See :ref:`Saving APNG sequences`. -""" +class Disposal(IntEnum): + OP_NONE = 0 + """ + No disposal is done on this frame before rendering the next frame. + See :ref:`Saving APNG sequences`. + """ + OP_BACKGROUND = 1 + """ + This frame’s modified region is cleared to fully transparent black before rendering + the next frame. + See :ref:`Saving APNG sequences`. + """ + OP_PREVIOUS = 2 + """ + This frame’s modified region is reverted to the previous frame’s contents before + rendering the next frame. + See :ref:`Saving APNG sequences`. + """ + # APNG frame blend modes -APNG_BLEND_OP_SOURCE = 0 -""" -All color components of this frame, including alpha, overwrite the previous output -image contents. -See :ref:`Saving APNG sequences`. -""" -APNG_BLEND_OP_OVER = 1 -""" -This frame should be alpha composited with the previous output image contents. -See :ref:`Saving APNG sequences`. -""" +class Blend(IntEnum): + OP_SOURCE = 0 + """ + All color components of this frame, including alpha, overwrite the previous output + image contents. + See :ref:`Saving APNG sequences`. + """ + OP_OVER = 1 + """ + This frame should be alpha composited with the previous output image contents. + See :ref:`Saving APNG sequences`. + """ + + +globals().update({"APNG_DISPOSE_" + k: v for k, v in Disposal.__members__.items()}) +globals().update(Blend.__members__) def _safe_zlib_decompress(s): @@ -861,13 +869,13 @@ class PngImageFile(ImageFile.ImageFile): raise EOFError # setup frame disposal (actual disposal done when needed in the next _seek()) - if self._prev_im is None and self.dispose_op == APNG_DISPOSE_OP_PREVIOUS: - self.dispose_op = APNG_DISPOSE_OP_BACKGROUND + if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS: + self.dispose_op = Disposal.OP_BACKGROUND - if self.dispose_op == APNG_DISPOSE_OP_PREVIOUS: + if self.dispose_op == Disposal.OP_PREVIOUS: self.dispose = self._prev_im.copy() self.dispose = self._crop(self.dispose, self.dispose_extent) - elif self.dispose_op == APNG_DISPOSE_OP_BACKGROUND: + elif self.dispose_op == Disposal.OP_BACKGROUND: self.dispose = Image.core.fill(self.mode, self.size) self.dispose = self._crop(self.dispose, self.dispose_extent) else: @@ -956,7 +964,7 @@ class PngImageFile(ImageFile.ImageFile): self.png.close() self.png = None else: - if self._prev_im and self.blend_op == APNG_BLEND_OP_OVER: + if self._prev_im and self.blend_op == Blend.OP_OVER: updated = self._crop(self.im, self.dispose_extent) self._prev_im.paste( updated, self.dispose_extent, updated.convert("RGBA") @@ -1061,10 +1069,8 @@ def _write_multiple_frames(im, fp, chunk, rawmode): default_image = im.encoderinfo.get("default_image", im.info.get("default_image")) duration = im.encoderinfo.get("duration", im.info.get("duration", 0)) loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) - disposal = im.encoderinfo.get( - "disposal", im.info.get("disposal", APNG_DISPOSE_OP_NONE) - ) - blend = im.encoderinfo.get("blend", im.info.get("blend", APNG_BLEND_OP_SOURCE)) + disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE)) + blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE)) if default_image: chain = itertools.chain(im.encoderinfo.get("append_images", [])) @@ -1094,10 +1100,10 @@ def _write_multiple_frames(im, fp, chunk, rawmode): previous = im_frames[-1] prev_disposal = previous["encoderinfo"].get("disposal") prev_blend = previous["encoderinfo"].get("blend") - if prev_disposal == APNG_DISPOSE_OP_PREVIOUS and len(im_frames) < 2: - prev_disposal = APNG_DISPOSE_OP_BACKGROUND + if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2: + prev_disposal = Disposal.OP_BACKGROUND - if prev_disposal == APNG_DISPOSE_OP_BACKGROUND: + if prev_disposal == Disposal.OP_BACKGROUND: base_im = previous["im"] dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0)) bbox = previous["bbox"] @@ -1106,7 +1112,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode): else: bbox = (0, 0) + im.size base_im.paste(dispose, bbox) - elif prev_disposal == APNG_DISPOSE_OP_PREVIOUS: + elif prev_disposal == Disposal.OP_PREVIOUS: base_im = im_frames[-2]["im"] else: base_im = previous["im"] diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 062af9f98..f21123c9b 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -316,7 +316,7 @@ if __name__ == "__main__": outfile = sys.argv[2] # perform some image operation - im = im.transpose(Image.FLIP_LEFT_RIGHT) + im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) print( f"saving a flipped version of {os.path.basename(filename)} " f"as {outfile} " diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index ed63da95f..59b89e988 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -152,7 +152,7 @@ class TgaImageFile(ImageFile.ImageFile): def load_end(self): if self._flip_horizontally: - self.im = self.im.transpose(Image.FLIP_LEFT_RIGHT) + self.im = self.im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) # diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index e54082fec..0980fb407 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1136,13 +1136,13 @@ class TiffImageFile(ImageFile.ImageFile): def load_end(self): if self._tile_orientation: method = { - 2: Image.FLIP_LEFT_RIGHT, - 3: Image.ROTATE_180, - 4: Image.FLIP_TOP_BOTTOM, - 5: Image.TRANSPOSE, - 6: Image.ROTATE_270, - 7: Image.TRANSVERSE, - 8: Image.ROTATE_90, + 2: Image.Transpose.FLIP_LEFT_RIGHT, + 3: Image.Transpose.ROTATE_180, + 4: Image.Transpose.FLIP_TOP_BOTTOM, + 5: Image.Transpose.TRANSPOSE, + 6: Image.Transpose.ROTATE_270, + 7: Image.Transpose.TRANSVERSE, + 8: Image.Transpose.ROTATE_90, }.get(self._tile_orientation) if method is not None: self.im = self.im.transpose(method) From ed8073e846dd585227ea5462381a62414f80629b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Jan 2022 10:07:07 +1100 Subject: [PATCH 298/633] Deprecated constants in favour of enums --- Tests/test_file_apng.py | 10 ++++++++ Tests/test_file_blp.py | 13 +++++++++- Tests/test_file_ftex.py | 13 +++++++++- Tests/test_image.py | 25 +++++++++++++++++++ Tests/test_imagecms.py | 10 ++++++++ Tests/test_imagefont.py | 9 +++++++ src/PIL/BlpImagePlugin.py | 31 +++++++++++++++++++---- src/PIL/FtexImagePlugin.py | 23 +++++++++++++++++- src/PIL/Image.py | 50 +++++++++++++++++++++++++++++++++----- src/PIL/ImageCms.py | 25 +++++++++++++++++-- src/PIL/ImageFont.py | 23 +++++++++++++++++- src/PIL/PngImagePlugin.py | 23 ++++++++++++++++-- 12 files changed, 236 insertions(+), 19 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index f5c767082..d1d5c85c1 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -635,3 +635,13 @@ def test_apng_save_blend(tmp_path): with Image.open(test_file) as im: im.seek(2) assert im.getpixel((0, 0)) == (0, 255, 0, 255) + + +def test_constants_deprecation(): + for enum, prefix in { + PngImagePlugin.Disposal: "APNG_DISPOSE_", + PngImagePlugin.Blend: "APNG_BLEND_", + }.items(): + for name in enum.__members__: + with pytest.warns(DeprecationWarning): + assert getattr(PngImagePlugin, prefix + name) == enum[name] diff --git a/Tests/test_file_blp.py b/Tests/test_file_blp.py index 15bd7e4f8..917f39530 100644 --- a/Tests/test_file_blp.py +++ b/Tests/test_file_blp.py @@ -1,6 +1,6 @@ import pytest -from PIL import Image +from PIL import BlpImagePlugin, Image from .helper import assert_image_equal_tofile @@ -37,3 +37,14 @@ def test_crashes(test_file): with Image.open(f) as im: with pytest.raises(OSError): im.load() + + +def test_constants_deprecation(): + for enum, prefix in { + BlpImagePlugin.Format: "BLP_FORMAT_", + BlpImagePlugin.Encoding: "BLP_ENCODING_", + BlpImagePlugin.AlphaEncoding: "BLP_ALPHA_ENCODING_", + }.items(): + for name in enum.__members__: + with pytest.warns(DeprecationWarning): + assert getattr(BlpImagePlugin, prefix + name) == enum[name] diff --git a/Tests/test_file_ftex.py b/Tests/test_file_ftex.py index f76fd895a..5447dc740 100644 --- a/Tests/test_file_ftex.py +++ b/Tests/test_file_ftex.py @@ -1,4 +1,6 @@ -from PIL import Image +import pytest + +from PIL import FtexImagePlugin, Image from .helper import assert_image_equal_tofile, assert_image_similar @@ -12,3 +14,12 @@ def test_load_dxt1(): with Image.open("Tests/images/ftex_dxt1.ftc") as im: with Image.open("Tests/images/ftex_dxt1.png") as target: assert_image_similar(im, target.convert("RGBA"), 15) + + +def test_constants_deprecation(): + for enum, prefix in { + FtexImagePlugin.Format: "FORMAT_", + }.items(): + for name in enum.__members__: + with pytest.warns(DeprecationWarning): + assert getattr(FtexImagePlugin, prefix + name) == enum[name] diff --git a/Tests/test_image.py b/Tests/test_image.py index 2d46d760d..1c30ca9fa 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -802,6 +802,31 @@ class TestImage: with pytest.warns(DeprecationWarning): assert Image.CONTAINER == 2 + def test_constants_deprecation(self): + with pytest.warns(DeprecationWarning): + assert Image.NEAREST == 0 + with pytest.warns(DeprecationWarning): + assert Image.NONE == 0 + + with pytest.warns(DeprecationWarning): + assert Image.LINEAR == Image.Resampling.BILINEAR + with pytest.warns(DeprecationWarning): + assert Image.CUBIC == Image.Resampling.BICUBIC + with pytest.warns(DeprecationWarning): + assert Image.ANTIALIAS == Image.Resampling.LANCZOS + + for enum in ( + Image.Transpose, + Image.Transform, + Image.Resampling, + Image.Dither, + Image.Palette, + Image.Quantize, + ): + for name in enum.__members__: + with pytest.warns(DeprecationWarning): + assert getattr(Image, name) == enum[name] + @pytest.mark.parametrize( "path", [ diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 09af11389..e0093739c 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -593,3 +593,13 @@ def test_auxiliary_channels_isolated(): ) assert_image_equal(test_image.convert(dst_format[2]), reference_image) + + +def test_constants_deprecation(): + for enum, prefix in { + ImageCms.Intent: "INTENT_", + ImageCms.Direction: "DIRECTION_", + }.items(): + for name in enum.__members__: + with pytest.warns(DeprecationWarning): + assert getattr(ImageCms, prefix + name) == enum[name] diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 8e2c61848..23bfda816 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1022,3 +1022,12 @@ def test_oom(test_file): font = ImageFont.truetype(BytesIO(f.read())) with pytest.raises(Image.DecompressionBombError): font.getmask("Test Text") + + +def test_constants_deprecation(): + for enum, prefix in { + ImageFont.Layout: "LAYOUT_", + }.items(): + for name in enum.__members__: + with pytest.warns(DeprecationWarning): + assert getattr(ImageFont, prefix + name) == enum[name] diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 111f03ae6..dc68b95b5 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -30,6 +30,7 @@ BLP files come in many different flavours: """ import struct +import warnings from enum import IntEnum from io import BytesIO @@ -52,11 +53,31 @@ class AlphaEncoding(IntEnum): DXT5 = 7 -globals().update({"BLP_FORMAT_" + k: v for k, v in Format.__members__.items()}) -globals().update({"BLP_ENCODING_" + k: v for k, v in Encoding.__members__.items()}) -globals().update( - {"BLP_ALPHA_ENCODING_" + k: v for k, v in AlphaEncoding.__members__.items()} -) +def __getattr__(name): + deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). " + for enum, prefix in { + Format: "BLP_FORMAT_", + Encoding: "BLP_ENCODING_", + AlphaEncoding: "BLP_ALPHA_ENCODING_", + }.items(): + if name.startswith(prefix): + name = name[len(prefix) :] + if name in enum.__members__: + warnings.warn( + prefix + + name + + " is " + + deprecated + + "Use " + + enum.__name__ + + "." + + name + + " instead.", + DeprecationWarning, + stacklevel=2, + ) + return enum[name] + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") def unpack_565(i): diff --git a/src/PIL/FtexImagePlugin.py b/src/PIL/FtexImagePlugin.py index de5859ba5..8629dcf64 100644 --- a/src/PIL/FtexImagePlugin.py +++ b/src/PIL/FtexImagePlugin.py @@ -52,6 +52,7 @@ Note: All data is stored in little-Endian (Intel) byte order. """ import struct +import warnings from enum import IntEnum from io import BytesIO @@ -65,7 +66,27 @@ class Format(IntEnum): UNCOMPRESSED = 1 -globals().update({"FORMAT_" + k: v for k, v in Format.__members__.items()}) +def __getattr__(name): + deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). " + for enum, prefix in {Format: "FORMAT_"}.items(): + if name.startswith(prefix): + name = name[len(prefix) :] + if name in enum.__members__: + warnings.warn( + prefix + + name + + " is " + + deprecated + + "Use " + + enum.__name__ + + "." + + name + + " instead.", + DeprecationWarning, + stacklevel=2, + ) + return enum[name] + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") class FtexImageFile(ImageFile.ImageFile): diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 30ee34c6b..c10e6d9cd 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -54,15 +54,57 @@ from ._util import deferred_error, isPath def __getattr__(name): + deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). " categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2} if name in categories: warnings.warn( - "Image categories are deprecated and will be removed in Pillow 10 " - "(2023-07-01). Use is_animated instead.", + "Image categories are " + deprecated + "Use is_animated instead.", DeprecationWarning, stacklevel=2, ) return categories[name] + elif name in ("NEAREST", "NONE"): + warnings.warn( + name + + " is " + + deprecated + + "Use Resampling.NEAREST or Dither.NONE instead.", + DeprecationWarning, + stacklevel=2, + ) + return 0 + old_resampling = { + "LINEAR": "BILINEAR", + "CUBIC": "BICUBIC", + "ANTIALIAS": "LANCZOS", + } + if name in old_resampling: + warnings.warn( + name + + " is " + + deprecated + + "Use Resampling." + + old_resampling[name] + + " instead.", + DeprecationWarning, + stacklevel=2, + ) + return Resampling[old_resampling[name]] + for enum in (Transpose, Transform, Resampling, Dither, Palette, Quantize): + if name in enum.__members__: + warnings.warn( + name + + " is " + + deprecated + + "Use " + + enum.__name__ + + "." + + name + + " instead.", + DeprecationWarning, + stacklevel=2, + ) + return enum[name] raise AttributeError(f"module '{__name__}' has no attribute '{name}'") @@ -199,10 +241,6 @@ class Quantize(IntEnum): LIBIMAGEQUANT = 3 -for enum in (Transpose, Transform, Resampling, Dither, Palette, Quantize): - globals().update(enum.__members__) -NEAREST = NONE = 0 - if hasattr(core, "DEFAULT_STRATEGY"): DEFAULT_STRATEGY = core.DEFAULT_STRATEGY FILTERED = core.FILTERED diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 1f104133d..ea328e149 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -16,6 +16,7 @@ # below for the original description. import sys +import warnings from enum import IntEnum from PIL import Image @@ -115,8 +116,28 @@ class Direction(IntEnum): PROOF = 2 -globals().update({"INTENT_" + k: v for k, v in Intent.__members__.items()}) -globals().update({"DIRECTION_" + k: v for k, v in Direction.__members__.items()}) +def __getattr__(name): + deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). " + for enum, prefix in {Intent: "INTENT_", Direction: "DIRECTION_"}.items(): + if name.startswith(prefix): + name = name[len(prefix) :] + if name in enum.__members__: + warnings.warn( + prefix + + name + + " is " + + deprecated + + "Use " + + enum.__name__ + + "." + + name + + " instead.", + DeprecationWarning, + stacklevel=2, + ) + return enum[name] + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + # # flags diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 9282d5279..84c9b430d 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -28,6 +28,7 @@ import base64 import os import sys +import warnings from enum import IntEnum from io import BytesIO @@ -40,7 +41,27 @@ class Layout(IntEnum): RAQM = 1 -globals().update({"LAYOUT_" + k: v for k, v in Layout.__members__.items()}) +def __getattr__(name): + deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). " + for enum, prefix in {Layout: "LAYOUT_"}.items(): + if name.startswith(prefix): + name = name[len(prefix) :] + if name in enum.__members__: + warnings.warn( + prefix + + name + + " is " + + deprecated + + "Use " + + enum.__name__ + + "." + + name + + " instead.", + DeprecationWarning, + stacklevel=2, + ) + return enum[name] + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") class _imagingft_not_installed: diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 94ca933b0..9d86afe34 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -130,8 +130,27 @@ class Blend(IntEnum): """ -globals().update({"APNG_DISPOSE_" + k: v for k, v in Disposal.__members__.items()}) -globals().update(Blend.__members__) +def __getattr__(name): + deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). " + for enum, prefix in {Disposal: "APNG_DISPOSE_", Blend: "APNG_BLEND_"}.items(): + if name.startswith(prefix): + name = name[len(prefix) :] + if name in enum.__members__: + warnings.warn( + prefix + + name + + " is " + + deprecated + + "Use " + + enum.__name__ + + "." + + name + + " instead.", + DeprecationWarning, + stacklevel=2, + ) + return enum[name] + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") def _safe_zlib_decompress(s): From 86944abbabad62e53e644bd7375b9a56d66c1675 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Jan 2022 16:08:37 +1100 Subject: [PATCH 299/633] Deprecated show_file "file" argument in favour of "path" --- Tests/test_imageshow.py | 15 +++++++++++ src/PIL/ImageShow.py | 59 +++++++++++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 5981e22c0..02edfdfa1 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -79,3 +79,18 @@ def test_ipythonviewer(): im = hopper() assert test_viewer.show(im) == 1 + + +@pytest.mark.skipif( + not on_ci() or is_win32(), + reason="Only run on CIs; hangs on Windows CIs", +) +def test_file_deprecated(): + for viewer in ImageShow._viewers: + with pytest.warns(DeprecationWarning): + try: + viewer.show_file(file="test.jpg") + except NotImplementedError: + pass + with pytest.raises(TypeError): + viewer.show_file() diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 2135293e5..068b7f784 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -16,6 +16,7 @@ import shutil import subprocess import sys import tempfile +import warnings from shlex import quote from PIL import Image @@ -105,9 +106,19 @@ class Viewer: """Display the given image.""" return self.show_file(self.save_image(image), **options) - def show_file(self, file, **options): - """Display the given file.""" - os.system(self.get_command(file, **options)) + def show_file(self, path=None, **options): + """Display given file.""" + if path is None: + if "file" in options: + warnings.warn( + "The 'file' argument is deprecated and will be removed in Pillow " + "10 (2023-07-01). Use 'path' instead.", + DeprecationWarning, + ) + path = options.pop("file") + else: + raise TypeError("Missing required argument: 'path'") + os.system(self.get_command(path, **options)) return 1 @@ -145,18 +156,28 @@ class MacViewer(Viewer): command = f"({command} {quote(file)}; sleep 20; rm -f {quote(file)})&" return command - def show_file(self, file, **options): + def show_file(self, path=None, **options): """Display given file""" - fd, path = tempfile.mkstemp() + if path is None: + if "file" in options: + warnings.warn( + "The 'file' argument is deprecated and will be removed in Pillow " + "10 (2023-07-01). Use 'path' instead.", + DeprecationWarning, + ) + path = options.pop("file") + else: + raise TypeError("Missing required argument: 'path'") + fd, temp_path = tempfile.mkstemp() with os.fdopen(fd, "w") as f: - f.write(file) - with open(path) as f: + f.write(path) + with open(temp_path) as f: subprocess.Popen( ["im=$(cat); open -a Preview.app $im; sleep 20; rm -f $im"], shell=True, stdin=f, ) - os.remove(path) + os.remove(temp_path) return 1 @@ -172,17 +193,27 @@ class UnixViewer(Viewer): command = self.get_command_ex(file, **options)[0] return f"({command} {quote(file)}; rm -f {quote(file)})&" - def show_file(self, file, **options): + def show_file(self, path=None, **options): """Display given file""" - fd, path = tempfile.mkstemp() + if path is None: + if "file" in options: + warnings.warn( + "The 'file' argument is deprecated and will be removed in Pillow " + "10 (2023-07-01). Use 'path' instead.", + DeprecationWarning, + ) + path = options.pop("file") + else: + raise TypeError("Missing required argument: 'path'") + fd, temp_path = tempfile.mkstemp() with os.fdopen(fd, "w") as f: - f.write(file) - with open(path) as f: - command = self.get_command_ex(file, **options)[0] + f.write(path) + with open(temp_path) as f: + command = self.get_command_ex(path, **options)[0] subprocess.Popen( ["im=$(cat);" + command + " $im; rm -f $im"], shell=True, stdin=f ) - os.remove(path) + os.remove(temp_path) return 1 From 26496a6c981bac4ab035369ee591616f31dcc67a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 16 Jan 2022 10:52:22 +1100 Subject: [PATCH 300/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 49ab72962..3e8c72dd8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.1.0 (unreleased) ------------------ +- Fixed Spider images for use with Bio-formats library #5956 + [radarhere] + - Ensure duplicated file pointer is closed #5946 [radarhere] From 5df83a57ff0f8362fc1d66180f777d5d79672342 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 16 Jan 2022 11:38:34 +1100 Subject: [PATCH 301/633] Documented deprecation --- docs/deprecations.rst | 12 ++++++++++++ src/PIL/ImageShow.py | 21 ++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index ce30fdf3b..dbe65dda1 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -53,6 +53,18 @@ Before Pillow 8.3.0, ``ImagePalette`` required palette data of particular length default, and the size parameter could be used to override that. Pillow 8.3.0 removed the default required length, also removing the need for the size parameter. +ImageShow.Viewer.show_file file argument +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 9.1.0 + +The ``file`` argument in :py:meth:`~PIL.ImageShow.Viewer.show_file()` has been +deprecated, replaced by ``path``. + +In effect, ``viewer.show_file("test.jpg")`` will continue to work unchanged. +``viewer.show_file(file="test.jpg")`` will raise a deprecation warning, and suggest +``viewer.show_file(path="test.jpg")`` instead. + Removed features ---------------- diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 068b7f784..e437bbade 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -107,7 +107,12 @@ class Viewer: return self.show_file(self.save_image(image), **options) def show_file(self, path=None, **options): - """Display given file.""" + """ + Display given file. + + Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated, + and ``path`` should be used instead. + """ if path is None: if "file" in options: warnings.warn( @@ -157,7 +162,12 @@ class MacViewer(Viewer): return command def show_file(self, path=None, **options): - """Display given file""" + """ + Display given file. + + Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated, + and ``path`` should be used instead. + """ if path is None: if "file" in options: warnings.warn( @@ -194,7 +204,12 @@ class UnixViewer(Viewer): return f"({command} {quote(file)}; rm -f {quote(file)})&" def show_file(self, path=None, **options): - """Display given file""" + """ + Display given file. + + Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated, + and ``path`` should be used instead. + """ if path is None: if "file" in options: warnings.warn( From 1ac96dc923a5f2360348b4e00b070856fff8a78e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 16 Jan 2022 14:09:12 +1100 Subject: [PATCH 302/633] Invoke pip using Python --- .ci/after_success.sh | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/after_success.sh b/.ci/after_success.sh index ff91b481e..53832c573 100755 --- a/.ci/after_success.sh +++ b/.ci/after_success.sh @@ -1,7 +1,7 @@ #!/bin/bash # gather the coverage data -pip3 install codecov +python3 -m pip install codecov if [[ $MATRIX_DOCKER ]]; then coverage xml --ignore-errors else diff --git a/Makefile b/Makefile index 44ab2ef1c..74a6a5ab2 100644 --- a/Makefile +++ b/Makefile @@ -105,7 +105,7 @@ test: .PHONY: valgrind valgrind: - python3 -c "import pytest_valgrind" || pip3 install pytest-valgrind + python3 -c "import pytest_valgrind" || python3 -m pip install pytest-valgrind PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \ --log-file=/tmp/valgrind-output \ python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output From 730e24e163ea6b891f24efe4dcb1869d85c2f63c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 17 Jan 2022 07:35:11 +1100 Subject: [PATCH 303/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3e8c72dd8..b3379c1ee 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,10 @@ Changelog (Pillow) 9.1.0 (unreleased) ------------------ -- Fixed Spider images for use with Bio-formats library #5956 +- Deprecated show_file "file" argument in favour of "path" #5959 + [radarhere] + +- Fixed SPIDER images for use with Bio-formats library #5956 [radarhere] - Ensure duplicated file pointer is closed #5946 From b53cb3f675a2987986ff4f794df123a44fa6181c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 17 Jan 2022 09:47:27 +1100 Subject: [PATCH 304/633] Updated macOS tested Pillow versions [ci skip] --- docs/installation.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index df21a7cdc..339ff966d 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -493,9 +493,13 @@ These platforms have been reported to work at the versions mentioned. | Operating system | | Tested Python | | Latest tested | | Tested | | | | versions | | Pillow version | | processors | +==================================+===========================+==================+==============+ -| macOS 11.0 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | +| macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10 | 9.0.0 |arm | ++----------------------------------+---------------------------+------------------+--------------+ +| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | | +---------------------------+------------------+--------------+ -| | 3.6, 3.7, 3.8, 3.9, 3.10 | 8.4.0 |x86-64 | +| | 3.7, 3.8, 3.9, 3.10 | 9.0.0 |x86-64 | +| +---------------------------+------------------+--------------+ +| | 3.6 | 8.4.0 |x86-64 | +----------------------------------+---------------------------+------------------+--------------+ | macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 | | +---------------------------+------------------+ | From c8d650f38303d17af0c4aefbb9ee9681e26fabea Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 17 Jan 2022 14:07:30 +1100 Subject: [PATCH 305/633] Added Debian 11 Bullseye --- .github/workflows/test-docker.yml | 1 + docs/installation.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 57396fddc..df04d0a6c 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -22,6 +22,7 @@ jobs: centos-8-amd64, centos-stream-8-amd64, debian-10-buster-x86, + debian-11-bullseye-x86, fedora-34-amd64, fedora-35-amd64, ubuntu-18.04-bionic-amd64, diff --git a/docs/installation.rst b/docs/installation.rst index df21a7cdc..3b249b368 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -458,6 +458,8 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Debian 10 Buster | 3.7 | x86 | +----------------------------------+----------------------------+---------------------+ +| Debian 11 Bullseye | 3.9 | x86 | ++----------------------------------+----------------------------+---------------------+ | Fedora 34 | 3.9 | x86-64 | +----------------------------------+----------------------------+---------------------+ | Fedora 35 | 3.10 | x86-64 | From 1be53c8bb3b729ddbd445a150efbf17b852a9703 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Jan 2022 09:08:50 +1100 Subject: [PATCH 306/633] Removed debugging --- src/PIL/BlpImagePlugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index f968ed851..3026ec49f 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -361,7 +361,6 @@ class BLP1Decoder(_BLPBaseDecoder): image.tile = [("jpeg", (0, 0) + self.size, 0, ("RGBA", ""))] b, g, r, a = image.split() - print(b, g, r, a) if not any( [a.getpixel((x, y)) for x in range(a.width) for y in range(a.height)] ): From acd33bf62acd2abb31fe5bc85c51f57406afc1ca Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Jan 2022 09:10:36 +1100 Subject: [PATCH 307/633] Removed unused images --- Tests/images/blp/blp1_jpeg.jpg | Bin 1651 -> 0 bytes Tests/images/blp/blp1_jpeg.png | Bin 270 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Tests/images/blp/blp1_jpeg.jpg delete mode 100644 Tests/images/blp/blp1_jpeg.png diff --git a/Tests/images/blp/blp1_jpeg.jpg b/Tests/images/blp/blp1_jpeg.jpg deleted file mode 100644 index c045c5945c1071f3d4bd4e3f0d878cec6899bc17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1651 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<uA z0}~@NGZPClD=P~NP<1U(o`FS>RY=j$kxe)-kzJ`!#HexNLJno8jR!@8E`CrkPAY2R z|V^&07y2J$~}^+4C1KUw!=a`ODXD-+%o41@afjpD+ON7@EHXf&OA*VPR%r2lxYE#}NLy#lXYN2#h>tK?Zwfl gP32wyWDa4_AkC=Dz<6e<1v|(Sp00i_>zopr0LjD?Hvj+t From ddb0a6393f3fcb4aeb78f0567a58d9058ba9ca72 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Jan 2022 11:46:29 +1100 Subject: [PATCH 308/633] Added test --- Tests/test_image.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index cf60f42af..db3e3063e 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -88,6 +88,17 @@ class TestImage: # with pytest.raises(MemoryError): # Image.new("L", (1000000, 1000000)) + def test_repr_pretty(self): + class Pretty: + def text(self, text): + self.pretty_output = text + + im = Image.new("L", (100, 100)) + + p = Pretty() + im._repr_pretty_(p, None) + assert p.pretty_output == "" + def test_open_formats(self): PNGFILE = "Tests/images/hopper.png" JPGFILE = "Tests/images/hopper.jpg" From c692fb42f80fe7d37fc7b9b91a884b12842e990b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Jan 2022 14:19:43 +1100 Subject: [PATCH 309/633] Fixed comparison warnings --- src/libImaging/TiffDecode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 38deb5360..f818f19d5 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -551,7 +551,7 @@ ImagingLibTiffDecode( uint16_t planarconfig = 0; int planes = 1; ImagingShuffler unpackers[4]; - UINT32 img_width, img_height; + INT32 img_width, img_height; memset(unpackers, 0, sizeof(ImagingShuffler) * 4); From a77babd6a459b9e1650bddc59ce6e3edf226f198 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Jan 2022 14:26:52 +1100 Subject: [PATCH 310/633] Updated links --- README.md | 4 ++-- docs/about.rst | 2 +- docs/index.rst | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 782b81f33..63671f405 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,10 @@ As of 2019, Pillow development is GitHub Actions wheels build status (Wheels) - Travis CI wheels build status (aarch64) - Code coverage Date: Tue, 18 Jan 2022 16:38:00 +1100 Subject: [PATCH 311/633] Raise an error when performing a negative crop --- Tests/test_decompression_bomb.py | 17 ++++------------- Tests/test_image_crop.py | 14 +++++--------- src/PIL/Image.py | 5 +++++ 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index d918ef941..1778491ab 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -86,21 +86,12 @@ class TestDecompressionCrop: pytest.warns(Image.DecompressionBombWarning, src.crop, box) def test_crop_decompression_checks(self): - im = Image.new("RGB", (100, 100)) - good_values = ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)) - - warning_values = ((-160, -160, 99, 99), (160, 160, -99, -99)) - - error_values = ((-99909, -99990, 99999, 99999), (99909, 99990, -99999, -99999)) - - for value in good_values: + for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)): assert im.crop(value).size == (9, 9) - for value in warning_values: - pytest.warns(Image.DecompressionBombWarning, im.crop, value) + pytest.warns(Image.DecompressionBombWarning, im.crop, (-160, -160, 99, 99)) - for value in error_values: - with pytest.raises(Image.DecompressionBombError): - im.crop(value) + with pytest.raises(Image.DecompressionBombError): + im.crop((-99909, -99990, 99999, 99999)) diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index e2228758c..6574e6efd 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -47,16 +47,12 @@ def test_wide_crop(): assert crop(-25, 75, 25, 125) == (1875, 625) -def test_negative_crop(): - # Check negative crop size (@PIL171) +@pytest.mark.parametrize("box", ((8, 2, 2, 8), (2, 8, 8, 2), (8, 8, 2, 2))) +def test_negative_crop(box): + im = Image.new("RGB", (10, 10)) - im = Image.new("L", (512, 512)) - im = im.crop((400, 400, 200, 200)) - - assert im.size == (0, 0) - assert len(im.getdata()) == 0 - with pytest.raises(IndexError): - im.getdata()[0] + with pytest.raises(ValueError): + im.crop(box) def test_crop_float(): diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 69089a290..439ffc65e 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1145,6 +1145,11 @@ class Image: if box is None: return self.copy() + if box[2] < box[0]: + raise ValueError("Region right less than region left") + elif box[3] < box[1]: + raise ValueError("Region lower less than region upper") + self.load() return self._new(self._crop(self.im, box)) From 67944cedc7293a2e1f568f47a1cce25ee7eaf148 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Jan 2022 19:40:57 +1100 Subject: [PATCH 312/633] Always save with contiguous planar configuration --- Tests/test_file_tiff.py | 11 +++++++++++ src/PIL/TiffImagePlugin.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 5801e1766..68409ad1e 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -594,6 +594,17 @@ class TestFileTiff: with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + def test_planar_configuration_save(self, tmp_path): + infile = "Tests/images/tiff_tiled_planar_raw.tif" + with Image.open(infile) as im: + assert im._planar_configuration == 2 + + outfile = str(tmp_path / "temp.tif") + im.save(outfile) + + with Image.open(outfile) as reloaded: + assert_image_equal_tofile(reloaded, infile) + def test_palette(self, tmp_path): def roundtrip(mode): outfile = str(tmp_path / "temp.tif") diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index e54082fec..39617c77d 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1528,7 +1528,7 @@ def _save(im, fp, filename): libtiff = WRITE_LIBTIFF or compression != "raw" # required for color libtiff images - ifd[PLANAR_CONFIGURATION] = getattr(im, "_planar_configuration", 1) + ifd[PLANAR_CONFIGURATION] = 1 ifd[IMAGEWIDTH] = im.size[0] ifd[IMAGELENGTH] = im.size[1] From ef99a73473aed8860d85b2979f2928e0f1e8c4ed Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 20 Jan 2022 10:04:35 +1100 Subject: [PATCH 313/633] Clarified that version numbers refer to Tk, not Pillow --- src/Tk/tkImaging.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index 9ae7edff1..5b3f18ace 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -29,7 +29,7 @@ * 1995-09-12 fl Created * 1996-04-08 fl Ready for release * 1997-05-09 fl Use command instead of image type - * 2001-03-18 fl Initialize alpha layer pointer (struct changed in 8.3) + * 2001-03-18 fl Initialize alpha layer pointer (struct changed in Tk 8.3) * 2003-04-23 fl Fixed building for Tk 8.4.1 and later (Jack Jansen) * 2004-06-24 fl Fixed building for Tk 8.4.6 and later. * @@ -116,7 +116,7 @@ PyImagingPhotoPut( block.offset[1] = 1; block.offset[2] = 2; if (strcmp(im->mode, "RGBA") == 0) { - block.offset[3] = 3; /* alpha (or reserved, under 8.2) */ + block.offset[3] = 3; /* alpha (or reserved, under Tk 8.2) */ } else { block.offset[3] = 0; /* no alpha */ } From 591231bbb408985d2b8ca58809fd237d704ffd1d Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Fri, 21 Jan 2022 08:19:27 +1100 Subject: [PATCH 314/633] Changed error wording Co-authored-by: Hugo van Kemenade --- src/PIL/Image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 439ffc65e..302bd638e 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1146,9 +1146,9 @@ class Image: return self.copy() if box[2] < box[0]: - raise ValueError("Region right less than region left") + raise ValueError("Coordinate 'right' is less than 'left'") elif box[3] < box[1]: - raise ValueError("Region lower less than region upper") + raise ValueError("Coordinate 'lower' is less than 'upper'") self.load() return self._new(self._crop(self.im, box)) From b894c8c73df04cb9ee2dc7b2e858278c59ece1be Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 21 Jan 2022 11:55:59 +1100 Subject: [PATCH 315/633] Connected discontiguous polygon corners --- .../discontiguous_corners_polygon.png | Bin 0 -> 486 bytes Tests/test_imagedraw.py | 12 +++++ src/libImaging/Draw.c | 47 +++++++++++++++--- 3 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 Tests/images/imagedraw/discontiguous_corners_polygon.png diff --git a/Tests/images/imagedraw/discontiguous_corners_polygon.png b/Tests/images/imagedraw/discontiguous_corners_polygon.png new file mode 100644 index 0000000000000000000000000000000000000000..509c42b26e0cbc5e01853915b083d367c1587579 GIT binary patch literal 486 zcmV@P)`!#sE^rA`<@Y%+RkqV?!_A|y&2pY*8)6=f4VlpVwn5}t^A6YuX03S#Y#g)7 zJQ^F#EHsbC_F-n5M`L?3Gt8s0{h7DSqp_oy7v|B}vCOaL^=6`p{x-yW@%sX@;oL`>6ss&}tQP{jy7s{L0??ngmnl7|5FWZZ5<^^5oX`ZzgZO!j@p}F~;z36X#*@aWg zPxj&<^KY|ooO$-7geQocksJTt*qn7e|89Ry{mlcOoKl@{@N4rK(#!8NvH9v#u!Q36 zd^nGC&Ywovw|zL9#`fS+c-x0_%4~m^#QgC7qU2=tJzr(chtir4_K?>8 z*}eohL7(jt&iOMT_E%jt+e0>wUv+tG52<#O=|9Yqe0QdtH`8R!&C>dOKMmcHy=s1@ cdrJ2_KmNq&!9TB{#sB~S07*qoM6N<$f>{CjhyVZp literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index b661494c7..3df4e7ca1 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1440,3 +1440,15 @@ def test_continuous_horizontal_edges_polygon(): assert_image_equal_tofile( img, expected, "continuous horizontal edges polygon failed" ) + + +def test_discontiguous_corners_polygon(): + img, draw = create_base_image_draw((84, 68)) + draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK) + draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK) + draw.polygon( + ((38, 66), (5, 49), (77, 49), (47, 66), (82, 63), (82, 47), (1, 47), (1, 63)), + BLACK, + ) + expected = os.path.join(IMAGES_PATH, "discontiguous_corners_polygon.png") + assert_image_similar_tofile(img, expected, 1) diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 0e4899b5b..86cd6c3a0 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -450,7 +450,8 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h int edge_count = 0; int ymin = im->ysize - 1; int ymax = 0; - int i; + int i, j, k; + float adjacent_line_x, adjacent_line_x_other_edge; if (n <= 0) { return 0; @@ -493,16 +494,48 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h return -1; } for (; ymin <= ymax; ymin++) { - int j = 0; + j = 0; for (i = 0; i < edge_count; i++) { Edge *current = edge_table[i]; if (ymin >= current->ymin && ymin <= current->ymax) { xx[j++] = (ymin - current->y0) * current->dx + current->x0; - } - /* Needed to draw consistent polygons */ - if (ymin == current->ymax && ymin < ymax) { - xx[j] = xx[j - 1]; - j++; + + if (ymin == current->ymax && ymin < ymax) { + // Needed to draw consistent polygons + xx[j] = xx[j - 1]; + j++; + } else if (current->dx != 0 && roundf(xx[j-1]) == xx[j-1]) { + // Connect discontiguous corners + for (k = 0; k < i; k++) { + Edge *other_edge = edge_table[k]; + if ((current->dx > 0 && other_edge->dx <= 0) || + (current->dx < 0 && other_edge->dx >= 0)) { + continue; + } + // Check if the two edges join to make a corner + if (xx[j-1] == (ymin - other_edge->y0) * other_edge->dx + other_edge->x0) { + // Determine points from the edges on the next row + // Or if this is the last row, check the previous row + int offset = ymin == ymax ? -1 : 1; + adjacent_line_x = (ymin + offset - current->y0) * current->dx + current->x0; + adjacent_line_x_other_edge = (ymin + offset - other_edge->y0) * other_edge->dx + other_edge->x0; + if (ymin == current->ymax) { + if (current->dx > 0) { + xx[k] = fmax(adjacent_line_x, adjacent_line_x_other_edge) + 1; + } else { + xx[k] = fmin(adjacent_line_x, adjacent_line_x_other_edge) - 1; + } + } else { + if (current->dx > 0) { + xx[k] = fmin(adjacent_line_x, adjacent_line_x_other_edge); + } else { + xx[k] = fmax(adjacent_line_x, adjacent_line_x_other_edge) + 1; + } + } + break; + } + } + } } } qsort(xx, j, sizeof(float), x_cmp); From 4eb80cb6bfb2f83dc039fb5bbce56a4416bac30f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Jan 2022 09:14:01 +1100 Subject: [PATCH 316/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b3379c1ee..d6cd9d50f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.1.0 (unreleased) ------------------ +- Raise an error when performing a negative crop #5972 + [radarhere, hugovk] + - Deprecated show_file "file" argument in favour of "path" #5959 [radarhere] From 54f85ddcade1c93edb84b47796cc178f37c7e8fd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Jan 2022 09:30:12 +1100 Subject: [PATCH 317/633] Updated libwebp to 1.2.2 --- depends/install_webp.sh | 2 +- winbuild/build_prepare.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/depends/install_webp.sh b/depends/install_webp.sh index 8a9c96804..a419a7646 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -1,7 +1,7 @@ #!/bin/bash # install webp -archive=libwebp-1.2.1 +archive=libwebp-1.2.2 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 0589baf21..3092c5a2b 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -154,9 +154,9 @@ deps = { # "bins": [r"libtiff\*.dll"], }, "libwebp": { - "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.2.1.tar.gz", - "filename": "libwebp-1.2.1.tar.gz", - "dir": "libwebp-1.2.1", + "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.2.2.tar.gz", + "filename": "libwebp-1.2.2.tar.gz", + "dir": "libwebp-1.2.2", "build": [ cmd_rmdir(r"output\release-static"), # clean cmd_nmake( From e05b8d74819fa18a908ea201a86138ea3168aba9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 23 Jan 2022 08:56:14 +1100 Subject: [PATCH 318/633] libwebp 1.2.2 fixed endian bugs --- Tests/test_file_webp_animated.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index 25ebffe02..cf14a4527 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -1,6 +1,7 @@ import pytest +from packaging.version import parse as parse_version -from PIL import Image +from PIL import Image, features from .helper import ( assert_image_equal, @@ -27,7 +28,6 @@ def test_n_frames(): assert im.is_animated -@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_write_animation_L(tmp_path): """ Convert an animated GIF to animated WebP, then compare the frame count, and first @@ -46,6 +46,11 @@ def test_write_animation_L(tmp_path): orig.load() im.load() assert_image_similar(im, orig.convert("RGBA"), 32.9) + + if is_big_endian(): + webp = parse_version(features.version_module("webp")) + if webp < parse_version("1.2.2"): + return orig.seek(orig.n_frames - 1) im.seek(im.n_frames - 1) orig.load() @@ -53,7 +58,6 @@ def test_write_animation_L(tmp_path): assert_image_similar(im, orig.convert("RGBA"), 32.9) -@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_write_animation_RGB(tmp_path): """ Write an animated WebP from RGB frames, and ensure the frames @@ -69,6 +73,10 @@ def test_write_animation_RGB(tmp_path): assert_image_equal(im, frame1.convert("RGBA")) # Compare second frame to original + if is_big_endian(): + webp = parse_version(features.version_module("webp")) + if webp < parse_version("1.2.2"): + return im.seek(1) im.load() assert_image_equal(im, frame2.convert("RGBA")) From eb1fc4ad9f0d6eba8b2b186360cd334740f3d52f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Jan 2022 07:47:44 +1100 Subject: [PATCH 319/633] Added pytest skip message --- Tests/test_file_webp_animated.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index cf14a4527..8606f6aaf 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -50,7 +50,7 @@ def test_write_animation_L(tmp_path): if is_big_endian(): webp = parse_version(features.version_module("webp")) if webp < parse_version("1.2.2"): - return + pytest.skip("Fails with libwebp earlier than 1.2.2") orig.seek(orig.n_frames - 1) im.seek(im.n_frames - 1) orig.load() @@ -76,7 +76,7 @@ def test_write_animation_RGB(tmp_path): if is_big_endian(): webp = parse_version(features.version_module("webp")) if webp < parse_version("1.2.2"): - return + pytest.skip("Fails with libwebp earlier than 1.2.2") im.seek(1) im.load() assert_image_equal(im, frame2.convert("RGBA")) From 31aa2ad98cffb8fd8abd05a34e56ff7f1094b553 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Jan 2022 11:06:41 +1100 Subject: [PATCH 320/633] Removed unused variables --- Tests/test_file_psd.py | 2 +- src/PIL/PsdImagePlugin.py | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index f50fe133f..a7f379e55 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -123,7 +123,7 @@ def test_no_icc_profile(): def test_combined_larger_than_size(): - # The 'combined' sizes of the individual parts is larger than the + # The combined size of the individual parts is larger than the # declared 'size' of the extra data field, resulting in a backwards seek. # If we instead take the 'size' of the extra data field as the source of truth, diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index 04b21e3de..550a333dd 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -195,7 +195,6 @@ def _layerinfo(fp, ct_bytes): x1 = i32(read(4)) # image info - info = [] mode = [] ct_types = i16(read(2)) types = list(range(ct_types)) @@ -211,8 +210,7 @@ def _layerinfo(fp, ct_bytes): m = "RGBA"[type] mode.append(m) - size = i32(read(4)) - info.append((m, size)) + read(4) # size # figure out the image mode mode.sort() @@ -229,26 +227,22 @@ def _layerinfo(fp, ct_bytes): read(12) # filler name = "" size = i32(read(4)) # length of the extra data field - combined = 0 if size: data_end = fp.tell() + size length = i32(read(4)) if length: fp.seek(length - 16, io.SEEK_CUR) - combined += length + 4 length = i32(read(4)) if length: fp.seek(length, io.SEEK_CUR) - combined += length + 4 length = i8(read(1)) if length: # Don't know the proper encoding, # Latin-1 should be a good guess name = read(length).decode("latin-1", "replace") - combined += length + 1 fp.seek(data_end) layers.append((name, mode, (x0, y0, x1, y1))) From 1d91f6dce5732c48710ad73f8489568471be449d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 26 Jan 2022 09:08:41 +1100 Subject: [PATCH 321/633] Document when file argument will be removed --- docs/deprecations.rst | 3 ++- src/PIL/ImageShow.py | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index dbe65dda1..a3abe81fa 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -59,7 +59,8 @@ ImageShow.Viewer.show_file file argument .. deprecated:: 9.1.0 The ``file`` argument in :py:meth:`~PIL.ImageShow.Viewer.show_file()` has been -deprecated, replaced by ``path``. +deprecated and will be removed in Pillow 10.0.0 (2023-07-01). It has been replaced by +``path``. In effect, ``viewer.show_file("test.jpg")`` will continue to work unchanged. ``viewer.show_file(file="test.jpg")`` will raise a deprecation warning, and suggest diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index e437bbade..2165da307 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -111,7 +111,8 @@ class Viewer: Display given file. Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated, - and ``path`` should be used instead. + and will be removed in Pillow 10.0.0 (2023-07-01). ``path`` should be used + instead. """ if path is None: if "file" in options: @@ -166,7 +167,8 @@ class MacViewer(Viewer): Display given file. Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated, - and ``path`` should be used instead. + and will be removed in Pillow 10.0.0 (2023-07-01). ``path`` should be used + instead. """ if path is None: if "file" in options: @@ -208,7 +210,8 @@ class UnixViewer(Viewer): Display given file. Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated, - and ``path`` should be used instead. + and will be removed in Pillow 10.0.0 (2023-07-01). ``path`` should be used + instead. """ if path is None: if "file" in options: From 93008c20957a7ae94fbd7b03da1f2980d4abb50c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 26 Jan 2022 12:23:43 +1100 Subject: [PATCH 322/633] Upgraded raqm to 0.8.0 --- .ci/install.sh | 2 +- depends/install_raqm.sh | 4 ++-- docs/installation.rst | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.ci/install.sh b/.ci/install.sh index c48acf9ee..efc57a641 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -19,7 +19,7 @@ set -e sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\ ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\ - cmake imagemagick libharfbuzz-dev libfribidi-dev + cmake meson imagemagick libharfbuzz-dev libfribidi-dev python3 -m pip install --upgrade pip python3 -m pip install --upgrade wheel diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index 3105465ec..39b998d05 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -2,13 +2,13 @@ # install raqm -archive=raqm-0.7.1 +archive=libraqm-0.8.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz pushd $archive -./configure --prefix=/usr && make -j4 && sudo make -j4 install +meson build --prefix=/usr && sudo ninja -C build install popd diff --git a/docs/installation.rst b/docs/installation.rst index 0699ef341..ea0e78500 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -394,7 +394,8 @@ Prerequisites for **Ubuntu 16.04 LTS - 20.04 LTS** are installed with:: libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ libharfbuzz-dev libfribidi-dev libxcb1-dev -Then see ``depends/install_raqm.sh`` to install libraqm. +To install libraqm, ``sudo apt-get install meson`` and then see +``depends/install_raqm.sh``. Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with:: From 5848726cadd40ff5be20497d7e8168cd4acf1d07 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 28 Jan 2022 11:23:51 +1100 Subject: [PATCH 323/633] Link directly to workflow pages --- README.md | 8 ++++---- docs/index.rst | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 63671f405..a0cb25d32 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,16 @@ As of 2019, Pillow development is tests - 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) Date: Fri, 28 Jan 2022 11:25:24 +1100 Subject: [PATCH 324/633] Added MinGW GitHub Actions badge --- README.md | 3 +++ docs/index.rst | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index a0cb25d32..7bff737a2 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ As of 2019, Pillow development is GitHub Actions build status (Test Windows) + GitHub Actions build status (Test MinGW) GitHub Actions build status (Test Docker) diff --git a/docs/index.rst b/docs/index.rst index c57a779df..f1a721c6a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -25,6 +25,10 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more Date: Fri, 28 Jan 2022 22:04:57 +1100 Subject: [PATCH 325/633] Updated raqm to 0.8.0 --- src/thirdparty/raqm/COPYING | 2 +- src/thirdparty/raqm/NEWS | 16 +- src/thirdparty/raqm/README.md | 27 +- src/thirdparty/raqm/raqm-version.h | 6 +- src/thirdparty/raqm/raqm.c | 401 ++++++++++++++++++++++------- src/thirdparty/raqm/raqm.h | 9 +- 6 files changed, 348 insertions(+), 113 deletions(-) diff --git a/src/thirdparty/raqm/COPYING b/src/thirdparty/raqm/COPYING index 196511ef6..78db981bb 100644 --- a/src/thirdparty/raqm/COPYING +++ b/src/thirdparty/raqm/COPYING @@ -1,7 +1,7 @@ The MIT License (MIT) Copyright © 2015 Information Technology Authority (ITA) -Copyright © 2016 Khaled Hosny +Copyright © 2016-2021 Khaled Hosny Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/thirdparty/raqm/NEWS b/src/thirdparty/raqm/NEWS index c49176a95..ae1128485 100644 --- a/src/thirdparty/raqm/NEWS +++ b/src/thirdparty/raqm/NEWS @@ -1,4 +1,18 @@ -Overview of changes leading to 0.7.1 +Overview of changes leading to 0.8.0 +Monday, December 13, 2021 +==================================== + +Remove autotools build. + +Support using SheenBiDi instead of FriBiDi for Unicode BiDi support. + +Fix running tests with Python <= 3.6. + +New API: + * raqm_get_par_resolved_direction + * raqm_get_direction_at_index + +Overview of changes leading to 0.7.2 Monday, September 27, 2021 ==================================== diff --git a/src/thirdparty/raqm/README.md b/src/thirdparty/raqm/README.md index 64937343a..17ce2efc9 100644 --- a/src/thirdparty/raqm/README.md +++ b/src/thirdparty/raqm/README.md @@ -6,26 +6,26 @@ Raqm Raqm is a small library that encapsulates the logic for complex text layout and provides a convenient API. -It currently provides bidirectional text support (using [FriBiDi][1]), shaping -(using [HarfBuzz][2]), and proper script itemization. As a result, -Raqm can support most writing systems covered by Unicode. +It currently provides bidirectional text support (using [FriBiDi][1] or +[SheenBidi][2]), shaping (using [HarfBuzz][3]), and proper script itemization. +As a result, Raqm can support most writing systems covered by Unicode. The documentation can be accessed on the web at: > http://host-oman.github.io/libraqm/ Raqm (Arabic: رَقْم) is writing, also number or digit and the Arabic word for -digital (رَقَمِيّ) shares the same root, so it is a play on “digital writing”. +digital (رَقَمِيّ) shares the same root, so it is a play on “digital writing”. Building -------- Raqm depends on the following libraries: -* [FreeType][3] -* [HarfBuzz][2] -* [FriBiDi][1] +* [FreeType][4] +* [HarfBuzz][3] +* [FriBiDi][1] or [SheenBidi][2] To build the documentation you will also need: -* [GTK-Doc][4] +* [GTK-Doc][5] To install dependencies on Fedora: @@ -48,11 +48,11 @@ directory: $ ninja -C build $ ninja -C build install -To build the documentation, pass `-Ddocs=enable` to the `meson`. +To build the documentation, pass `-Ddocs=true` to the `meson`. To run the tests: - $ ninja -C test + $ ninja -C build test Contributing ------------ @@ -78,6 +78,7 @@ The following projects have patches to support complex text layout using Raqm: [1]: http://fribidi.org -[2]: http://harfbuzz.org -[3]: https://www.freetype.org -[4]: https://www.gtk.org/gtk-doc +[2]: https://github.com/Tehreer/SheenBidi +[3]: http://harfbuzz.org +[4]: https://www.freetype.org +[5]: https://www.gtk.org/gtk-doc diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h index 8b115f612..e96b1aea5 100644 --- a/src/thirdparty/raqm/raqm-version.h +++ b/src/thirdparty/raqm/raqm-version.h @@ -32,10 +32,10 @@ #define _RAQM_VERSION_H_ #define RAQM_VERSION_MAJOR 0 -#define RAQM_VERSION_MINOR 7 -#define RAQM_VERSION_MICRO 2 +#define RAQM_VERSION_MINOR 8 +#define RAQM_VERSION_MICRO 0 -#define RAQM_VERSION_STRING "0.7.2" +#define RAQM_VERSION_STRING "0.8.0" #define RAQM_VERSION_ATLEAST(major,minor,micro) \ ((major)*10000+(minor)*100+(micro) <= \ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 31161c9d9..46890b9a7 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -1,6 +1,6 @@ /* * Copyright © 2015 Information Technology Authority (ITA) - * Copyright © 2016 Khaled Hosny + * Copyright © 2016-2021 Khaled Hosny * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -30,11 +30,18 @@ #include #include +#ifdef RAQM_SHEENBIDI +#include +#else #ifdef HAVE_FRIBIDI_SYSTEM #include #else #include "../fribidi-shim/fribidi.h" #endif +#if FRIBIDI_MAJOR_VERSION >= 1 +#define USE_FRIBIDI_EX_API +#endif +#endif #include #include @@ -56,10 +63,6 @@ #include "raqm.h" -#if FRIBIDI_MAJOR_VERSION >= 1 -#define USE_FRIBIDI_EX_API -#endif - /** * SECTION:raqm * @title: Raqm @@ -178,6 +181,15 @@ # define RAQM_TEST(...) #endif +#define RAQM_BIDI_LEVEL_IS_RTL(level) \ + ((level) & 1) + +#ifdef RAQM_SHEENBIDI + typedef SBLevel _raqm_bidi_level_t; +#else + typedef FriBidiLevel _raqm_bidi_level_t; +#endif + typedef enum { RAQM_FLAG_NONE = 0, RAQM_FLAG_UTF8 = 1 << 0 @@ -438,6 +450,53 @@ raqm_set_text (raqm_t *rq, return true; } +static void * +_raqm_get_utf8_codepoint (const void *str, + uint32_t *out_codepoint) +{ + const char *s = (const char *)str; + + if (0xf0 == (0xf8 & s[0])) + { + *out_codepoint = ((0x07 & s[0]) << 18) | ((0x3f & s[1]) << 12) | ((0x3f & s[2]) << 6) | (0x3f & s[3]); + s += 4; + } + else if (0xe0 == (0xf0 & s[0])) + { + *out_codepoint = ((0x0f & s[0]) << 12) | ((0x3f & s[1]) << 6) | (0x3f & s[2]); + s += 3; + } + else if (0xc0 == (0xe0 & s[0])) + { + *out_codepoint = ((0x1f & s[0]) << 6) | (0x3f & s[1]); + s += 2; + } + else + { + *out_codepoint = s[0]; + s += 1; + } + + return (void *)s; +} + +static size_t +_raqm_u8_to_u32 (const char *text, size_t len, uint32_t *unicode) +{ + size_t in_len = 0; + uint32_t *out_utf32 = unicode; + const char *in_utf8 = text; + + while ((*in_utf8 != '\0') && (in_len < len)) + { + in_utf8 = _raqm_get_utf8_codepoint (in_utf8, out_utf32); + ++out_utf32; + ++in_len; + } + + return (out_utf32 - unicode); +} + /** * raqm_set_text_utf8: * @rq: a #raqm_t. @@ -482,9 +541,7 @@ raqm_set_text_utf8 (raqm_t *rq, memcpy (rq->text_utf8, text, sizeof (char) * len); - ulen = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, - text, len, unicode); - + ulen = _raqm_u8_to_u32 (text, len, unicode); ok = raqm_set_text (rq, unicode, ulen); free (unicode); @@ -504,7 +561,7 @@ raqm_set_text_utf8 (raqm_t *rq, * * The default is #RAQM_DIRECTION_DEFAULT, which determines the paragraph * direction based on the first character with strong bidi type (see [rule - * P2](https://unicode.org/reports/tr9/#P2) in Unicode Bidirectional Algorithm), + * P2](http://unicode.org/reports/tr9/#P2) in Unicode Bidirectional Algorithm), * which can be good enough for many cases but has problems when a mainly * right-to-left paragraph starts with a left-to-right character and vice versa * as the detected paragraph direction will be the wrong one, or when text does @@ -971,17 +1028,78 @@ raqm_get_glyphs (raqm_t *rq, return rq->glyphs; } +/** + * raqm_get_par_resolved_direction: + * @rq: a #raqm_t. + * + * Gets the resolved direction of the paragraph; + * + * Return value: + * The #raqm_direction_t specifying the resolved direction of text, + * or #RAQM_DIRECTION_DEFAULT if raqm_layout() has not been called on @rq. + * + * Since: 0.8 + */ +RAQM_API raqm_direction_t +raqm_get_par_resolved_direction (raqm_t *rq) +{ + if (!rq) + return RAQM_DIRECTION_DEFAULT; + + return rq->resolved_dir; +} + +/** + * raqm_get_direction_at_index: + * @rq: a #raqm_t. + * @index: (in): character index. + * + * Gets the resolved direction of the character at specified index; + * + * Return value: + * The #raqm_direction_t specifying the resolved direction of text at the + * specified index, or #RAQM_DIRECTION_DEFAULT if raqm_layout() has not been + * called on @rq. + * + * Since: 0.8 + */ +RAQM_API raqm_direction_t +raqm_get_direction_at_index (raqm_t *rq, + size_t index) +{ + if (!rq) + return RAQM_DIRECTION_DEFAULT; + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + if (run->pos <= index && index < run->pos + run->len) { + switch (run->direction) { + case HB_DIRECTION_LTR: + return RAQM_DIRECTION_LTR; + case HB_DIRECTION_RTL: + return RAQM_DIRECTION_RTL; + case HB_DIRECTION_TTB: + return RAQM_DIRECTION_TTB; + default: + return RAQM_DIRECTION_DEFAULT; + } + } + } + + return RAQM_DIRECTION_DEFAULT; +} + static bool _raqm_resolve_scripts (raqm_t *rq); static hb_direction_t -_raqm_hb_dir (raqm_t *rq, FriBidiLevel level) +_raqm_hb_dir (raqm_t *rq, _raqm_bidi_level_t level) { hb_direction_t dir = HB_DIRECTION_LTR; if (rq->base_dir == RAQM_DIRECTION_TTB) dir = HB_DIRECTION_TTB; - else if (FRIBIDI_LEVEL_IS_RTL (level)) + else if (RAQM_BIDI_LEVEL_IS_RTL(level)) dir = HB_DIRECTION_RTL; return dir; @@ -990,9 +1108,65 @@ _raqm_hb_dir (raqm_t *rq, FriBidiLevel level) typedef struct { size_t pos; size_t len; - FriBidiLevel level; + _raqm_bidi_level_t level; } _raqm_bidi_run; +#ifdef RAQM_SHEENBIDI +static _raqm_bidi_run * +_raqm_bidi_itemize (raqm_t *rq, size_t *run_count) +{ + _raqm_bidi_run *runs; + SBAlgorithmRef bidi; + SBParagraphRef par; + SBUInteger par_len; + SBLineRef line; + + SBLevel base_level = SBLevelDefaultLTR; + SBCodepointSequence input = { + SBStringEncodingUTF32, + (void *) rq->text, + rq->text_len + }; + + if (rq->base_dir == RAQM_DIRECTION_RTL) + base_level = 1; + else if (rq->base_dir == RAQM_DIRECTION_LTR) + base_level = 0; + + /* paragraph */ + bidi = SBAlgorithmCreate (&input); + par = SBAlgorithmCreateParagraph (bidi, 0, INT32_MAX, base_level); + par_len = SBParagraphGetLength (par); + + /* lines */ + line = SBParagraphCreateLine (par, 0, par_len); + *run_count = SBLineGetRunCount (line); + + if (SBParagraphGetBaseLevel (par) == 0) + rq->resolved_dir = RAQM_DIRECTION_LTR; + else + rq->resolved_dir = RAQM_DIRECTION_RTL; + + runs = malloc (sizeof (_raqm_bidi_run) * (*run_count)); + if (runs) + { + const SBRun *sheenbidi_runs = SBLineGetRunsPtr(line); + + for (size_t i = 0; i < (*run_count); ++i) + { + runs[i].pos = sheenbidi_runs[i].offset; + runs[i].len = sheenbidi_runs[i].length; + runs[i].level = sheenbidi_runs[i].level; + } + } + + SBLineRelease (line); + SBParagraphRelease (par); + SBAlgorithmRelease (bidi); + + return runs; +} +#else static void _raqm_reverse_run (_raqm_bidi_run *run, const size_t len) { @@ -1093,19 +1267,78 @@ _raqm_reorder_runs (const FriBidiCharType *types, return runs; } -static bool -_raqm_itemize (raqm_t *rq) +static _raqm_bidi_run * +_raqm_bidi_itemize (raqm_t *rq, size_t *run_count) { FriBidiParType par_type = FRIBIDI_PAR_ON; + _raqm_bidi_run *runs = NULL; + FriBidiCharType *types; + _raqm_bidi_level_t *levels; + int max_level = 0; #ifdef USE_FRIBIDI_EX_API FriBidiBracketType *btypes; #endif - FriBidiLevel *levels; + + types = calloc (rq->text_len, sizeof (FriBidiCharType)); +#ifdef USE_FRIBIDI_EX_API + btypes = calloc (rq->text_len, sizeof (FriBidiBracketType)); +#endif + levels = calloc (rq->text_len, sizeof (_raqm_bidi_level_t)); + + if (!types || !levels +#ifdef USE_FRIBIDI_EX_API + || !btypes +#endif + ) + { + goto done; + } + + if (rq->base_dir == RAQM_DIRECTION_RTL) + par_type = FRIBIDI_PAR_RTL; + else if (rq->base_dir == RAQM_DIRECTION_LTR) + par_type = FRIBIDI_PAR_LTR; + + fribidi_get_bidi_types (rq->text, rq->text_len, types); +#ifdef USE_FRIBIDI_EX_API + fribidi_get_bracket_types (rq->text, rq->text_len, types, btypes); + max_level = fribidi_get_par_embedding_levels_ex (types, btypes, + rq->text_len, &par_type, + levels); +#else + max_level = fribidi_get_par_embedding_levels (types, rq->text_len, + &par_type, levels); +#endif + + if (par_type == FRIBIDI_PAR_LTR) + rq->resolved_dir = RAQM_DIRECTION_LTR; + else + rq->resolved_dir = RAQM_DIRECTION_RTL; + + if (max_level == 0) + goto done; + + /* Get the number of bidi runs */ + runs = _raqm_reorder_runs (types, rq->text_len, par_type, levels, run_count); + +done: + free (types); + free (levels); +#ifdef USE_FRIBIDI_EX_API + free (btypes); +#endif + + return runs; +} +#endif + +static bool +_raqm_itemize (raqm_t *rq) +{ _raqm_bidi_run *runs = NULL; raqm_run_t *last; - int max_level; - size_t run_count; + size_t run_count = 0; bool ok = true; #ifdef RAQM_TESTING @@ -1127,67 +1360,28 @@ _raqm_itemize (raqm_t *rq) } #endif - types = calloc (rq->text_len, sizeof (FriBidiCharType)); -#ifdef USE_FRIBIDI_EX_API - btypes = calloc (rq->text_len, sizeof (FriBidiBracketType)); -#endif - levels = calloc (rq->text_len, sizeof (FriBidiLevel)); - if (!types || !levels -#ifdef USE_FRIBIDI_EX_API - || !btypes -#endif - ) - { - ok = false; - goto done; - } - - if (rq->base_dir == RAQM_DIRECTION_RTL) - par_type = FRIBIDI_PAR_RTL; - else if (rq->base_dir == RAQM_DIRECTION_LTR) - par_type = FRIBIDI_PAR_LTR; - - if (rq->base_dir == RAQM_DIRECTION_TTB) - { - /* Treat every thing as LTR in vertical text */ - max_level = 1; - memset (types, FRIBIDI_TYPE_LTR, rq->text_len); - memset (levels, 0, rq->text_len); - rq->resolved_dir = RAQM_DIRECTION_LTR; - } - else - { - fribidi_get_bidi_types (rq->text, rq->text_len, types); -#ifdef USE_FRIBIDI_EX_API - fribidi_get_bracket_types (rq->text, rq->text_len, types, btypes); - max_level = fribidi_get_par_embedding_levels_ex (types, btypes, - rq->text_len, &par_type, - levels); -#else - max_level = fribidi_get_par_embedding_levels (types, rq->text_len, - &par_type, levels); -#endif - - if (par_type == FRIBIDI_PAR_LTR) - rq->resolved_dir = RAQM_DIRECTION_LTR; - else - rq->resolved_dir = RAQM_DIRECTION_RTL; - } - - if (max_level == 0) - { - ok = false; - goto done; - } - if (!_raqm_resolve_scripts (rq)) { ok = false; goto done; } - /* Get the number of bidi runs */ - runs = _raqm_reorder_runs (types, rq->text_len, par_type, levels, &run_count); + if (rq->base_dir == RAQM_DIRECTION_TTB) + { + /* Treat every thing as LTR in vertical text */ + run_count = 1; + rq->resolved_dir = RAQM_DIRECTION_TTB; + runs = malloc (sizeof (_raqm_bidi_run)); + if (runs) + { + runs->pos = 0; + runs->len = rq->text_len; + runs->level = 0; + } + } else { + runs = _raqm_bidi_itemize (rq, &run_count); + } + if (!runs) { ok = false; @@ -1197,7 +1391,7 @@ _raqm_itemize (raqm_t *rq) #ifdef RAQM_TESTING RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); - RAQM_TEST ("Fribidi Runs:\n"); + RAQM_TEST ("BiDi Runs:\n"); for (size_t i = 0; i < run_count; i++) { RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", @@ -1309,11 +1503,6 @@ _raqm_itemize (raqm_t *rq) done: free (runs); - free (types); -#ifdef USE_FRIBIDI_EX_API - free (btypes); -#endif - free (levels); return ok; } @@ -1328,7 +1517,7 @@ typedef struct { /* Special paired characters for script detection */ static size_t paired_len = 34; -static const FriBidiChar paired_chars[] = +static const uint32_t paired_chars[] = { 0x0028, 0x0029, /* ascii paired punctuation */ 0x003c, 0x003e, @@ -1431,7 +1620,7 @@ _raqm_stack_push (_raqm_stack_t *stack, } static int -_get_pair_index (const FriBidiChar ch) +_get_pair_index (const uint32_t ch) { int lower = 0; int upper = paired_len - 1; @@ -1569,6 +1758,7 @@ _raqm_resolve_scripts (raqm_t *rq) return true; } +#ifdef HAVE_FT_GET_TRANSFORM static void _raqm_ft_transform (int *x, int *y, @@ -1583,6 +1773,7 @@ _raqm_ft_transform (int *x, *x = vector.x; *y = vector.y; } +#endif static bool _raqm_shape (raqm_t *rq) @@ -1634,20 +1825,30 @@ _raqm_shape (raqm_t *rq) return true; } +/* Count equivalent UTF-8 bytes in codepoint */ +static size_t +_raqm_count_codepoint_utf8_bytes (uint32_t chr) +{ + if (0 == ((uint32_t) 0xffffff80 & chr)) + return 1; + else if (0 == ((uint32_t) 0xfffff800 & chr)) + return 2; + else if (0 == ((uint32_t) 0xffff0000 & chr)) + return 3; + else + return 4; +} + /* Convert index from UTF-32 to UTF-8 */ static uint32_t _raqm_u32_to_u8_index (raqm_t *rq, uint32_t index) { - FriBidiStrIndex length; - char *output = malloc ((sizeof (char) * 4 * index) + 1); + size_t length = 0; - length = fribidi_unicode_to_charset (FRIBIDI_CHAR_SET_UTF8, - rq->text, - index, - output); + for (uint32_t i = 0; i < index; ++i) + length += _raqm_count_codepoint_utf8_bytes (rq->text[i]); - free (output); return length; } @@ -1656,15 +1857,27 @@ static uint32_t _raqm_u8_to_u32_index (raqm_t *rq, uint32_t index) { - FriBidiStrIndex length; - uint32_t *output = malloc (sizeof (uint32_t) * (index + 1)); + const unsigned char *s = (const unsigned char *) rq->text_utf8; + const unsigned char *t = s; + size_t length = 0; - length = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, - rq->text_utf8, - index, - output); + while (((size_t) (s - t) < index) && ('\0' != *s)) + { + if (0xf0 == (0xf8 & *s)) + s += 4; + else if (0xe0 == (0xf0 & *s)) + s += 3; + else if (0xc0 == (0xe0 & *s)) + s += 2; + else + s += 1; + + length++; + } + + if ((size_t) (s-t) > index) + length--; - free (output); return length; } diff --git a/src/thirdparty/raqm/raqm.h b/src/thirdparty/raqm/raqm.h index 342afc8b2..faab85591 100644 --- a/src/thirdparty/raqm/raqm.h +++ b/src/thirdparty/raqm/raqm.h @@ -1,6 +1,6 @@ /* * Copyright © 2015 Information Technology Authority (ITA) - * Copyright © 2016 Khaled Hosny + * Copyright © 2016-2021 Khaled Hosny * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -156,6 +156,13 @@ RAQM_API raqm_glyph_t * raqm_get_glyphs (raqm_t *rq, size_t *length); +RAQM_API raqm_direction_t +raqm_get_par_resolved_direction (raqm_t *rq); + +RAQM_API raqm_direction_t +raqm_get_direction_at_index (raqm_t *rq, + size_t index); + RAQM_API bool raqm_index_to_position (raqm_t *rq, size_t *index, From 1b6a8c6122ad6b5e5794b04ca37660126675a8a4 Mon Sep 17 00:00:00 2001 From: DWesl <22566757+DWesl@users.noreply.github.com> Date: Sat, 18 Dec 2021 10:00:14 -0500 Subject: [PATCH 326/633] TST: Parametrize numpy roundtrip to find failing case Segfaults are annoying to debug. --- Tests/test_numpy.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index def7adf3f..936474fe8 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -189,8 +189,9 @@ def test_putdata(): assert len(im.getdata()) == len(arr) -def test_roundtrip_eye(): - for dtype in ( +@pytest.mark.parametrize( + "dtype", + ( bool, numpy.bool8, numpy.int8, @@ -202,9 +203,11 @@ def test_roundtrip_eye(): float, numpy.float32, numpy.float64, - ): - arr = numpy.eye(10, dtype=dtype) - numpy.testing.assert_array_equal(arr, numpy.array(Image.fromarray(arr))) + ), +) +def test_roundtrip_eye(dtype): + arr = numpy.eye(10, dtype=dtype) + numpy.testing.assert_array_equal(arr, numpy.array(Image.fromarray(arr))) def test_zero_size(): From 7ab973c4c95494cae655825cc36c5ba9abbfabd2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 30 Jan 2022 08:01:35 +1100 Subject: [PATCH 327/633] Updated lcms2 to 2.13 --- docs/installation.rst | 2 +- winbuild/build_prepare.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index ea0e78500..88aaf5834 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -169,7 +169,7 @@ Many of Pillow's features require external libraries: * **littlecms** provides color management * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and - above uses liblcms2. Tested with **1.19** and **2.7-2.12**. + above uses liblcms2. Tested with **1.19** and **2.7-2.13**. * **libwebp** provides the WebP format. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 3092c5a2b..78916987c 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -219,9 +219,9 @@ deps = { # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"], }, "lcms2": { - "url": SF_MIRROR + "/project/lcms/lcms/2.12/lcms2-2.12.tar.gz", - "filename": "lcms2-2.12.tar.gz", - "dir": "lcms2-2.12", + "url": SF_MIRROR + "/project/lcms/lcms/2.12/lcms2-2.13.tar.gz", + "filename": "lcms2-2.13.tar.gz", + "dir": "lcms2-2.13", "patch": { r"Projects\VC2017\lcms2_static\lcms2_static.vcxproj": { # default is /MD for x86 and /MT for x64, we need /MD always From d8e94c206ef69989642e48aa2795a2a69f9d0577 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 30 Jan 2022 14:40:30 +1100 Subject: [PATCH 328/633] Switched from windows-2019 to windows-latest --- .github/workflows/test-mingw.yml | 2 +- .github/workflows/test-windows.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index d94c7d537..8a9c1725d 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -4,7 +4,7 @@ on: [push, pull_request, workflow_dispatch] jobs: build: - runs-on: windows-2019 + runs-on: windows-latest strategy: fail-fast: false matrix: diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index c768838eb..c78f9fd24 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -4,7 +4,7 @@ on: [push, pull_request, workflow_dispatch] jobs: build: - runs-on: windows-2019 + runs-on: windows-latest strategy: fail-fast: false matrix: From 36f293071e76c9e0206708cf01087055f1dc0912 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 31 Jan 2022 08:05:01 +1100 Subject: [PATCH 329/633] Updated raqm to 0.9.0 --- depends/install_raqm.sh | 2 +- src/thirdparty/raqm/COPYING | 2 +- src/thirdparty/raqm/README.md | 5 +- src/thirdparty/raqm/raqm-version.h | 4 +- src/thirdparty/raqm/raqm.c | 472 ++++++++++++++++++----------- src/thirdparty/raqm/raqm.h | 11 +- 6 files changed, 311 insertions(+), 185 deletions(-) diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index 39b998d05..992503650 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -2,7 +2,7 @@ # install raqm -archive=libraqm-0.8.0 +archive=libraqm-0.9.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/src/thirdparty/raqm/COPYING b/src/thirdparty/raqm/COPYING index 78db981bb..c605a5dc6 100644 --- a/src/thirdparty/raqm/COPYING +++ b/src/thirdparty/raqm/COPYING @@ -1,7 +1,7 @@ The MIT License (MIT) Copyright © 2015 Information Technology Authority (ITA) -Copyright © 2016-2021 Khaled Hosny +Copyright © 2016-2022 Khaled Hosny Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/thirdparty/raqm/README.md b/src/thirdparty/raqm/README.md index 17ce2efc9..02e996e7a 100644 --- a/src/thirdparty/raqm/README.md +++ b/src/thirdparty/raqm/README.md @@ -68,6 +68,7 @@ Projects using Raqm 3. [FontView](https://github.com/googlei18n/fontview) 4. [Pillow](https://github.com/python-pillow) 5. [mplcairo](https://github.com/anntzer/mplcairo) +6. [CEGUI](https://github.com/cegui/cegui) The following projects have patches to support complex text layout using Raqm: @@ -77,8 +78,8 @@ The following projects have patches to support complex text layout using Raqm: -[1]: http://fribidi.org +[1]: https://github.com/fribidi/fribidi [2]: https://github.com/Tehreer/SheenBidi -[3]: http://harfbuzz.org +[3]: https://github.com/harfbuzz/harfbuzz [4]: https://www.freetype.org [5]: https://www.gtk.org/gtk-doc diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h index e96b1aea5..78b70a561 100644 --- a/src/thirdparty/raqm/raqm-version.h +++ b/src/thirdparty/raqm/raqm-version.h @@ -32,10 +32,10 @@ #define _RAQM_VERSION_H_ #define RAQM_VERSION_MAJOR 0 -#define RAQM_VERSION_MINOR 8 +#define RAQM_VERSION_MINOR 9 #define RAQM_VERSION_MICRO 0 -#define RAQM_VERSION_STRING "0.8.0" +#define RAQM_VERSION_STRING "0.9.0" #define RAQM_VERSION_ATLEAST(major,minor,micro) \ ((major)*10000+(minor)*100+(micro) <= \ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index 46890b9a7..f852542b4 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -1,6 +1,6 @@ /* * Copyright © 2015 Information Technology Authority (ITA) - * Copyright © 2016-2021 Khaled Hosny + * Copyright © 2016-2022 Khaled Hosny * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -24,7 +24,6 @@ #ifdef HAVE_CONFIG_H #include "config.h" -#undef HAVE_CONFIG_H // Workaround for Fribidi 1.0.5 and earlier #endif #include @@ -38,9 +37,6 @@ #else #include "../fribidi-shim/fribidi.h" #endif -#if FRIBIDI_MAJOR_VERSION >= 1 -#define USE_FRIBIDI_EX_API -#endif #endif #include @@ -190,13 +186,9 @@ typedef FriBidiLevel _raqm_bidi_level_t; #endif -typedef enum { - RAQM_FLAG_NONE = 0, - RAQM_FLAG_UTF8 = 1 << 0 -} _raqm_flags_t; - typedef struct { FT_Face ftface; + int ftloadflags; hb_language_t lang; hb_script_t script; } _raqm_text_info; @@ -209,6 +201,7 @@ struct _raqm { uint32_t *text; char *text_utf8; size_t text_len; + size_t text_capacity_bytes; _raqm_text_info *text_info; @@ -219,17 +212,17 @@ struct _raqm { size_t features_len; raqm_run_t *runs; + raqm_run_t *runs_pool; + raqm_glyph_t *glyphs; + size_t glyphs_capacity; - _raqm_flags_t flags; - - int ft_loadflags; int invisible_glyph; }; struct _raqm_run { - int pos; - int len; + uint32_t pos; + uint32_t len; hb_direction_t direction; hb_script_t script; @@ -243,31 +236,21 @@ static uint32_t _raqm_u8_to_u32_index (raqm_t *rq, uint32_t index); -static bool +static void _raqm_init_text_info (raqm_t *rq) { - hb_language_t default_lang; - - if (rq->text_info) - return true; - - rq->text_info = malloc (sizeof (_raqm_text_info) * rq->text_len); - if (!rq->text_info) - return false; - - default_lang = hb_language_get_default (); + hb_language_t default_lang = hb_language_get_default (); for (size_t i = 0; i < rq->text_len; i++) { rq->text_info[i].ftface = NULL; + rq->text_info[i].ftloadflags = -1; rq->text_info[i].lang = default_lang; rq->text_info[i].script = HB_SCRIPT_INVALID; } - - return true; } static void -_raqm_free_text_info (raqm_t *rq) +_raqm_release_text_info (raqm_t *rq) { if (!rq->text_info) return; @@ -277,9 +260,6 @@ _raqm_free_text_info (raqm_t *rq) if (rq->text_info[i].ftface) FT_Done_Face (rq->text_info[i].ftface); } - - free (rq->text_info); - rq->text_info = NULL; } static bool @@ -289,6 +269,9 @@ _raqm_compare_text_info (_raqm_text_info a, if (a.ftface != b.ftface) return false; + if (a.ftloadflags != b.ftloadflags) + return false; + if (a.lang != b.lang) return false; @@ -298,6 +281,88 @@ _raqm_compare_text_info (_raqm_text_info a, return true; } +static void +_raqm_free_text(raqm_t* rq) +{ + free (rq->text); + rq->text = NULL; + rq->text_info = NULL; + rq->text_utf8 = NULL; + rq->text_len = 0; + rq->text_capacity_bytes = 0; +} + +static bool +_raqm_alloc_text(raqm_t *rq, + size_t len, + bool need_utf8) +{ + /* Allocate contiguous memory block for texts and text_info */ + size_t mem_size = (sizeof (uint32_t) + sizeof (_raqm_text_info)) * len; + if (need_utf8) + mem_size += sizeof (char) * len; + + if (mem_size > rq->text_capacity_bytes) + { + void* new_mem = realloc (rq->text, mem_size); + if (!new_mem) + { + _raqm_free_text (rq); + return false; + } + + rq->text_capacity_bytes = mem_size; + rq->text = new_mem; + } + + rq->text_info = (_raqm_text_info*)(rq->text + len); + rq->text_utf8 = need_utf8 ? (char*)(rq->text_info + len) : NULL; + + return true; +} + +static raqm_run_t* +_raqm_alloc_run (raqm_t *rq) +{ + raqm_run_t *run = rq->runs_pool; + if (run) + { + rq->runs_pool = run->next; + } + else + { + run = malloc (sizeof (raqm_run_t)); + run->font = NULL; + run->buffer = NULL; + } + + run->pos = 0; + run->len = 0; + run->direction = HB_DIRECTION_INVALID; + run->script = HB_SCRIPT_INVALID; + run->next = NULL; + + return run; +} + +static void +_raqm_free_runs (raqm_run_t *runs) +{ + while (runs) + { + raqm_run_t *run = runs; + runs = runs->next; + + if (run->buffer) + hb_buffer_destroy (run->buffer); + + if (run->font) + hb_font_destroy (run->font); + + free (run); + } +} + /** * raqm_create: * @@ -322,26 +387,26 @@ raqm_create (void) rq->ref_count = 1; - rq->text = NULL; - rq->text_utf8 = NULL; - rq->text_len = 0; - - rq->text_info = NULL; - rq->base_dir = RAQM_DIRECTION_DEFAULT; rq->resolved_dir = RAQM_DIRECTION_DEFAULT; rq->features = NULL; rq->features_len = 0; - rq->runs = NULL; - rq->glyphs = NULL; - - rq->flags = RAQM_FLAG_NONE; - - rq->ft_loadflags = -1; rq->invisible_glyph = 0; + rq->text = NULL; + rq->text_utf8 = NULL; + rq->text_info = NULL; + rq->text_capacity_bytes = 0; + rq->text_len = 0; + + rq->runs = NULL; + rq->runs_pool = NULL; + + rq->glyphs = NULL; + rq->glyphs_capacity = 0; + return rq; } @@ -366,28 +431,13 @@ raqm_reference (raqm_t *rq) return rq; } -static void -_raqm_free_runs (raqm_t *rq) -{ - raqm_run_t *runs = rq->runs; - while (runs) - { - raqm_run_t *run = runs; - runs = runs->next; - - hb_buffer_destroy (run->buffer); - hb_font_destroy (run->font); - free (run); - } -} - /** * raqm_destroy: * @rq: a #raqm_t. * * Decreases the reference count on @rq by one. If the result is zero, then @rq * and all associated resources are freed. - * See cairo_reference(). + * See raqm_reference(). * * Since: 0.1 */ @@ -397,14 +447,60 @@ raqm_destroy (raqm_t *rq) if (!rq || --rq->ref_count != 0) return; - free (rq->text); - free (rq->text_utf8); - _raqm_free_text_info (rq); - _raqm_free_runs (rq); + _raqm_release_text_info (rq); + _raqm_free_text (rq); + _raqm_free_runs (rq->runs); + _raqm_free_runs (rq->runs_pool); free (rq->glyphs); + free (rq->features); free (rq); } +/** + * raqm_clear_contents: + * @rq: a #raqm_t. + * + * Clears internal state of previously used raqm_t object, making it ready + * for reuse and keeping some of allocated memory to increase performance. + * + * Since: 0.9 + */ +void +raqm_clear_contents (raqm_t *rq) +{ + if (!rq) + return; + + _raqm_release_text_info (rq); + + /* Return allocated runs to the pool, keep hb buffers for reuse */ + raqm_run_t *run = rq->runs; + while (run) + { + if (run->buffer) + hb_buffer_reset (run->buffer); + + if (run->font) + { + hb_font_destroy (run->font); + run->font = NULL; + } + + if (!run->next) + { + run->next = rq->runs_pool; + rq->runs_pool = rq->runs; + rq->runs = NULL; + break; + } + + run = run->next; + } + + rq->text_len = 0; + rq->resolved_dir = RAQM_DIRECTION_DEFAULT; +} + /** * raqm_set_text: * @rq: a #raqm_t. @@ -429,23 +525,20 @@ raqm_set_text (raqm_t *rq, if (!rq || !text) return false; - rq->text_len = len; + /* Call raqm_clear_contents to reuse this raqm_t */ + if (rq->text_len) + return false; /* Empty string, don’t fail but do nothing */ if (!len) return true; - free (rq->text); + if (!_raqm_alloc_text(rq, len, false)) + return false; - rq->text = malloc (sizeof (uint32_t) * rq->text_len); - if (!rq->text) - return false; - - _raqm_free_text_info (rq); - if (!_raqm_init_text_info (rq)) - return false; - - memcpy (rq->text, text, sizeof (uint32_t) * rq->text_len); + rq->text_len = len; + memcpy (rq->text, text, sizeof (uint32_t) * len); + _raqm_init_text_info (rq); return true; } @@ -511,41 +604,29 @@ _raqm_u8_to_u32 (const char *text, size_t len, uint32_t *unicode) * Since: 0.1 */ bool -raqm_set_text_utf8 (raqm_t *rq, - const char *text, - size_t len) +raqm_set_text_utf8 (raqm_t *rq, + const char *text, + size_t len) { - uint32_t *unicode; - size_t ulen; - bool ok; - if (!rq || !text) return false; + /* Call raqm_clear_contents to reuse this raqm_t */ + if (rq->text_len) + return false; + /* Empty string, don’t fail but do nothing */ if (!len) - { - rq->text_len = len; return true; - } - rq->flags |= RAQM_FLAG_UTF8; - - rq->text_utf8 = malloc (sizeof (char) * len); - if (!rq->text_utf8) - return false; - - unicode = malloc (sizeof (uint32_t) * len); - if (!unicode) - return false; + if (!_raqm_alloc_text(rq, len, true)) + return false; + rq->text_len = _raqm_u8_to_u32 (text, len, rq->text); memcpy (rq->text_utf8, text, sizeof (char) * len); + _raqm_init_text_info (rq); - ulen = _raqm_u8_to_u32 (text, len, unicode); - ok = raqm_set_text (rq, unicode, ulen); - - free (unicode); - return ok; + return true; } /** @@ -561,7 +642,7 @@ raqm_set_text_utf8 (raqm_t *rq, * * The default is #RAQM_DIRECTION_DEFAULT, which determines the paragraph * direction based on the first character with strong bidi type (see [rule - * P2](http://unicode.org/reports/tr9/#P2) in Unicode Bidirectional Algorithm), + * P2](https://unicode.org/reports/tr9/#P2) in Unicode Bidirectional Algorithm), * which can be good enough for many cases but has problems when a mainly * right-to-left paragraph starts with a left-to-right character and vice versa * as the detected paragraph direction will be the wrong one, or when text does @@ -629,7 +710,7 @@ raqm_set_language (raqm_t *rq, if (!rq->text_len) return true; - if (rq->flags & RAQM_FLAG_UTF8) + if (rq->text_utf8) { start = _raqm_u8_to_u32_index (rq, start); end = _raqm_u8_to_u32_index (rq, end); @@ -686,13 +767,14 @@ raqm_add_font_feature (raqm_t *rq, ok = hb_feature_from_string (feature, len, &fea); if (ok) { - rq->features_len++; - rq->features = realloc (rq->features, - sizeof (hb_feature_t) * (rq->features_len)); - if (!rq->features) + void* new_features = realloc (rq->features, + sizeof (hb_feature_t) * (rq->features_len + 1)); + if (!new_features) return false; - rq->features[rq->features_len - 1] = fea; + rq->features = new_features; + rq->features[rq->features_len] = fea; + rq->features_len++; } return ok; @@ -700,12 +782,13 @@ raqm_add_font_feature (raqm_t *rq, static hb_font_t * _raqm_create_hb_font (raqm_t *rq, - FT_Face face) + FT_Face face, + int loadflags) { hb_font_t *font = hb_ft_font_create_referenced (face); - if (rq->ft_loadflags >= 0) - hb_ft_font_set_load_flags (font, rq->ft_loadflags); + if (loadflags >= 0) + hb_ft_font_set_load_flags (font, loadflags); return font; } @@ -796,7 +879,7 @@ raqm_set_freetype_face_range (raqm_t *rq, if (!rq->text_len) return true; - if (rq->flags & RAQM_FLAG_UTF8) + if (rq->text_utf8) { start = _raqm_u8_to_u32_index (rq, start); end = _raqm_u8_to_u32_index (rq, end); @@ -805,6 +888,30 @@ raqm_set_freetype_face_range (raqm_t *rq, return _raqm_set_freetype_face (rq, face, start, end); } +static bool +_raqm_set_freetype_load_flags (raqm_t *rq, + int flags, + size_t start, + size_t end) +{ + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (start >= rq->text_len || end > rq->text_len) + return false; + + if (!rq->text_info) + return false; + + for (size_t i = start; i < end; i++) + rq->text_info[i].ftloadflags = flags; + + return true; +} + /** * raqm_set_freetype_load_flags: * @rq: a #raqm_t. @@ -823,14 +930,59 @@ raqm_set_freetype_face_range (raqm_t *rq, */ bool raqm_set_freetype_load_flags (raqm_t *rq, - int flags) + int flags) { + return _raqm_set_freetype_load_flags(rq, flags, 0, rq->text_len); +} + +/** + * raqm_set_freetype_load_flags_range: + * @rq: a #raqm_t. + * @flags: FreeType load flags. + * @start: index of first character that should use @flags. + * @len: number of characters using @flags. + * + * Sets the load flags passed to FreeType when loading glyphs for @len-number + * of characters staring at @start. Flags should be the same as used by the + * client when rendering corresponding FreeType glyphs. The @start and @len + * are input string array indices (i.e. counting bytes in UTF-8 and scaler + * values in UTF-32). + * + * This method can be used repeatedly to set different flags for different + * parts of the text. It is the responsibility of the client to make sure that + * flag ranges cover the whole text. + * + * This requires version of HarfBuzz that has hb_ft_font_set_load_flags(), for + * older version the flags will be ignored. + * + * See also raqm_set_freetype_load_flags(). + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.9 + */ +bool +raqm_set_freetype_load_flags_range (raqm_t *rq, + int flags, + size_t start, + size_t len) +{ + size_t end = start + len; + if (!rq) return false; - rq->ft_loadflags = flags; + if (!rq->text_len) + return true; - return true; + if (rq->text_utf8) + { + start = _raqm_u8_to_u32_index (rq, start); + end = _raqm_u8_to_u32_index (rq, end); + } + + return _raqm_set_freetype_load_flags (rq, flags, start, end); } /** @@ -841,17 +993,10 @@ raqm_set_freetype_load_flags (raqm_t *rq, * Sets the glyph id to be used for invisible glyhphs. * * If @gid is negative, invisible glyphs will be suppressed from the output. - * This requires HarfBuzz 1.8.0 or later. If raqm is used with an earlier - * HarfBuzz version, the return value will be %false and the shaping behavior - * does not change. * * If @gid is zero, invisible glyphs will be rendered as space. - * This works on all versions of HarfBuzz. * * If @gid is a positive number, it will be used for invisible glyphs. - * This requires a version of HarfBuzz that has - * hb_buffer_set_invisible_glyph(). For older versions, the return value - * will be %false and the shaping behavior does not change. * * Return value: * %true if no errors happened, %false otherwise. @@ -865,17 +1010,6 @@ raqm_set_invisible_glyph (raqm_t *rq, if (!rq) return false; -#ifndef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH - if (gid > 0) - return false; -#endif - -#if !defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) || \ - !HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES - if (gid < 0) - return false; -#endif - rq->invisible_glyph = gid; return true; } @@ -961,18 +1095,21 @@ raqm_get_glyphs (raqm_t *rq, for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) count += hb_buffer_get_length (run->buffer); - *length = count; - - if (rq->glyphs) - free (rq->glyphs); - - rq->glyphs = malloc (sizeof (raqm_glyph_t) * count); - if (!rq->glyphs) + if (count > rq->glyphs_capacity) { - *length = 0; - return NULL; + void* new_mem = realloc (rq->glyphs, sizeof (raqm_glyph_t) * count); + if (!new_mem) + { + *length = 0; + return NULL; + } + + rq->glyphs = new_mem; + rq->glyphs_capacity = count; } + *length = count; + RAQM_TEST ("Glyph information:\n"); count = 0; @@ -1005,7 +1142,7 @@ raqm_get_glyphs (raqm_t *rq, count += len; } - if (rq->flags & RAQM_FLAG_UTF8) + if (rq->text_utf8) { #ifdef RAQM_TESTING RAQM_TEST ("\nUTF-32 clusters:"); @@ -1276,24 +1413,14 @@ _raqm_bidi_itemize (raqm_t *rq, size_t *run_count) FriBidiCharType *types; _raqm_bidi_level_t *levels; int max_level = 0; -#ifdef USE_FRIBIDI_EX_API FriBidiBracketType *btypes; -#endif types = calloc (rq->text_len, sizeof (FriBidiCharType)); -#ifdef USE_FRIBIDI_EX_API btypes = calloc (rq->text_len, sizeof (FriBidiBracketType)); -#endif levels = calloc (rq->text_len, sizeof (_raqm_bidi_level_t)); - if (!types || !levels -#ifdef USE_FRIBIDI_EX_API - || !btypes -#endif - ) - { + if (!types || !levels || !btypes) goto done; - } if (rq->base_dir == RAQM_DIRECTION_RTL) par_type = FRIBIDI_PAR_RTL; @@ -1301,15 +1428,10 @@ _raqm_bidi_itemize (raqm_t *rq, size_t *run_count) par_type = FRIBIDI_PAR_LTR; fribidi_get_bidi_types (rq->text, rq->text_len, types); -#ifdef USE_FRIBIDI_EX_API fribidi_get_bracket_types (rq->text, rq->text_len, types, btypes); max_level = fribidi_get_par_embedding_levels_ex (types, btypes, rq->text_len, &par_type, levels); -#else - max_level = fribidi_get_par_embedding_levels (types, rq->text_len, - &par_type, levels); -#endif if (par_type == FRIBIDI_PAR_LTR) rq->resolved_dir = RAQM_DIRECTION_LTR; @@ -1325,9 +1447,7 @@ _raqm_bidi_itemize (raqm_t *rq, size_t *run_count) done: free (types); free (levels); -#ifdef USE_FRIBIDI_EX_API free (btypes); -#endif return runs; } @@ -1403,7 +1523,7 @@ _raqm_itemize (raqm_t *rq) last = NULL; for (size_t i = 0; i < run_count; i++) { - raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); + raqm_run_t *run = _raqm_alloc_run (rq); if (!run) { ok = false; @@ -1422,13 +1542,14 @@ _raqm_itemize (raqm_t *rq) { run->pos = runs[i].pos + runs[i].len - 1; run->script = rq->text_info[run->pos].script; - run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface, + rq->text_info[run->pos].ftloadflags); for (int j = runs[i].len - 1; j >= 0; j--) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) { - raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + raqm_run_t *newrun = _raqm_alloc_run (rq); if (!newrun) { ok = false; @@ -1438,7 +1559,8 @@ _raqm_itemize (raqm_t *rq) newrun->len = 1; newrun->direction = _raqm_hb_dir (rq, runs[i].level); newrun->script = info.script; - newrun->font = _raqm_create_hb_font (rq, info.ftface); + newrun->font = _raqm_create_hb_font (rq, info.ftface, + info.ftloadflags); run->next = newrun; run = newrun; } @@ -1453,13 +1575,14 @@ _raqm_itemize (raqm_t *rq) { run->pos = runs[i].pos; run->script = rq->text_info[run->pos].script; - run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface, + rq->text_info[run->pos].ftloadflags); for (size_t j = 0; j < runs[i].len; j++) { _raqm_text_info info = rq->text_info[runs[i].pos + j]; if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) { - raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + raqm_run_t *newrun = _raqm_alloc_run (rq); if (!newrun) { ok = false; @@ -1469,7 +1592,8 @@ _raqm_itemize (raqm_t *rq) newrun->len = 1; newrun->direction = _raqm_hb_dir (rq, runs[i].level); newrun->script = info.script; - newrun->font = _raqm_create_hb_font (rq, info.ftface); + newrun->font = _raqm_create_hb_font (rq, info.ftface, + info.ftloadflags); run->next = newrun; run = newrun; } @@ -1758,7 +1882,6 @@ _raqm_resolve_scripts (raqm_t *rq) return true; } -#ifdef HAVE_FT_GET_TRANSFORM static void _raqm_ft_transform (int *x, int *y, @@ -1773,22 +1896,19 @@ _raqm_ft_transform (int *x, *x = vector.x; *y = vector.y; } -#endif static bool _raqm_shape (raqm_t *rq) { hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; -#if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ - HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES if (rq->invisible_glyph < 0) hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; -#endif for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) { - run->buffer = hb_buffer_create (); + if (!run->buffer) + run->buffer = hb_buffer_create (); hb_buffer_add_utf32 (run->buffer, rq->text, rq->text_len, run->pos, run->len); @@ -1797,15 +1917,12 @@ _raqm_shape (raqm_t *rq) hb_buffer_set_direction (run->buffer, run->direction); hb_buffer_set_flags (run->buffer, hb_buffer_flags); -#ifdef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH if (rq->invisible_glyph > 0) hb_buffer_set_invisible_glyph (run->buffer, rq->invisible_glyph); -#endif hb_shape_full (run->font, run->buffer, rq->features, rq->features_len, NULL); -#ifdef HAVE_FT_GET_TRANSFORM { FT_Matrix matrix; hb_glyph_position_t *pos; @@ -1819,7 +1936,6 @@ _raqm_shape (raqm_t *rq) _raqm_ft_transform (&pos[i].x_offset, &pos[i].y_offset, matrix); } } -#endif } return true; @@ -1917,7 +2033,7 @@ raqm_index_to_position (raqm_t *rq, if (rq == NULL) return false; - if (rq->flags & RAQM_FLAG_UTF8) + if (rq->text_utf8) *index = _raqm_u8_to_u32_index (rq, *index); if (*index >= rq->text_len) @@ -1974,7 +2090,7 @@ raqm_index_to_position (raqm_t *rq, } found: - if (rq->flags & RAQM_FLAG_UTF8) + if (rq->text_utf8) *index = _raqm_u32_to_u8_index (rq, *index); RAQM_TEST ("The position is %d at index %zu\n",*x ,*index); return true; diff --git a/src/thirdparty/raqm/raqm.h b/src/thirdparty/raqm/raqm.h index faab85591..bdb5a50d8 100644 --- a/src/thirdparty/raqm/raqm.h +++ b/src/thirdparty/raqm/raqm.h @@ -1,6 +1,6 @@ /* * Copyright © 2015 Information Technology Authority (ITA) - * Copyright © 2016-2021 Khaled Hosny + * Copyright © 2016-2022 Khaled Hosny * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -106,6 +106,9 @@ raqm_reference (raqm_t *rq); RAQM_API void raqm_destroy (raqm_t *rq); +RAQM_API void +raqm_clear_contents (raqm_t *rq); + RAQM_API bool raqm_set_text (raqm_t *rq, const uint32_t *text, @@ -145,6 +148,12 @@ RAQM_API bool raqm_set_freetype_load_flags (raqm_t *rq, int flags); +RAQM_API bool +raqm_set_freetype_load_flags_range (raqm_t *rq, + int flags, + size_t start, + size_t len); + RAQM_API bool raqm_set_invisible_glyph (raqm_t *rq, int gid); From 999ef3e4eaf793612d9f3525fcbff2bac629e135 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 31 Jan 2022 00:11:47 +0000 Subject: [PATCH 330/633] Revert "detect FreeType / HarfBuzz features" This reverts commit 6565d13275cead21dc6f369204f0dc3d0b43bc18. --- src/thirdparty/raqm/raqm.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c index f852542b4..13f6e1f02 100644 --- a/src/thirdparty/raqm/raqm.c +++ b/src/thirdparty/raqm/raqm.c @@ -42,21 +42,6 @@ #include #include -#if FREETYPE_MAJOR > 2 || \ - FREETYPE_MAJOR == 2 && FREETYPE_MINOR >= 11 -#define HAVE_FT_GET_TRANSFORM -#endif - -#if HB_VERSION_ATLEAST(2, 0, 0) -#define HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH -#endif - -#if HB_VERSION_ATLEAST(1, 8, 0) -#define HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES 1 -#else -#define HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES 0 -#endif - #include "raqm.h" /** From 3ac2da533a1e74f4a1bb38e8e812190c49fbdc53 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 31 Jan 2022 18:56:52 +0200 Subject: [PATCH 331/633] Fix lcms2 URL --- winbuild/build_prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 78916987c..182146fbc 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -219,7 +219,7 @@ deps = { # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"], }, "lcms2": { - "url": SF_MIRROR + "/project/lcms/lcms/2.12/lcms2-2.13.tar.gz", + "url": SF_MIRROR + "/project/lcms/lcms/2.13/lcms2-2.13.tar.gz", "filename": "lcms2-2.13.tar.gz", "dir": "lcms2-2.13", "patch": { From cc4f9a2a7dcb3e69dd4ca4ab752ebcd0fba0bbb2 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 31 Jan 2022 20:38:47 +0200 Subject: [PATCH 332/633] Update harfbuzz to 3.3.0 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 3092c5a2b..e6bcbc6fc 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -278,9 +278,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/3.2.0.zip", - "filename": "harfbuzz-3.2.0.zip", - "dir": "harfbuzz-3.2.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/3.3.0.zip", + "filename": "harfbuzz-3.3.0.zip", + "dir": "harfbuzz-3.3.0", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 87ab18f804ed860f4315038980bcf20044ae352c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 31 Jan 2022 23:27:35 +0200 Subject: [PATCH 333/633] Update harfbuzz to 3.3.1 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index e6bcbc6fc..220956adc 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -278,9 +278,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/3.3.0.zip", - "filename": "harfbuzz-3.3.0.zip", - "dir": "harfbuzz-3.3.0", + "url": "https://github.com/harfbuzz/harfbuzz/archive/3.3.1.zip", + "filename": "harfbuzz-3.3.1.zip", + "dir": "harfbuzz-3.3.1", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 54e9decde30915e1f89ed7cda0bc49870707a02e Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 31 Jan 2022 23:41:16 +0200 Subject: [PATCH 334/633] Remove EOL CentOS 8 --- .github/workflows/test-docker.yml | 1 - docs/installation.rst | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index df04d0a6c..656df5e91 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -19,7 +19,6 @@ jobs: amazon-2-amd64, arch, centos-7-amd64, - centos-8-amd64, centos-stream-8-amd64, debian-10-buster-x86, debian-11-bullseye-x86, diff --git a/docs/installation.rst b/docs/installation.rst index 88aaf5834..984a689c2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -453,8 +453,6 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | CentOS 7 | 3.9 | x86-64 | +----------------------------------+----------------------------+---------------------+ -| CentOS 8 | 3.9 | x86-64 | -+----------------------------------+----------------------------+---------------------+ | CentOS Stream 8 | 3.9 | x86-64 | +----------------------------------+----------------------------+---------------------+ | Debian 10 Buster | 3.7 | x86 | @@ -530,6 +528,8 @@ These platforms have been reported to work at the versions mentioned. +----------------------------------+---------------------------+------------------+--------------+ | CentOS 6.3 | 2.7, 3.3 | |x86 | +----------------------------------+---------------------------+------------------+--------------+ +| CentOS 8 | 3.9 | 9.0.0 |x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ | Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 | +----------------------------------+---------------------------+------------------+--------------+ | Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 | From f22d3561fd764e9600fe4640856c50ad74a90eaf Mon Sep 17 00:00:00 2001 From: Gabor Kertesz Date: Fri, 29 Oct 2021 12:31:40 +0200 Subject: [PATCH 335/633] Windows: Enable ARM64 for MSVC This patch enables ARM64 as a new platform for Windows. Platform query and documentation is updated accordingly. --- winbuild/build.rst | 6 +++--- winbuild/build_prepare.py | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/winbuild/build.rst b/winbuild/build.rst index b30a94226..661c5a5ec 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -24,7 +24,7 @@ Download and install: * `CMake 3.12 or newer `_ (also available as Visual Studio component C++ CMake tools for Windows) -* `NASM `_ +* x86/x64: `NASM `_ Any version of Visual Studio 2017 or newer should be supported, including Visual Studio 2017 Community, or Build Tools for Visual Studio 2019. @@ -42,8 +42,8 @@ behaviour of ``build_prepare.py``: If ``PYTHON`` is unset, the version of Python used to run ``build_prepare.py`` will be used. If only ``PYTHON`` is set, ``EXECUTABLE`` defaults to ``python.exe``. -* ``ARCHITECTURE`` is used to select a ``x86`` or ``x64`` build. By default, - uses same architecture as the version of Python used to run ``build_prepare.py``. +* ``ARCHITECTURE`` is used to select a ``x86``, ``x64`` or ``ARM64``build. + By default, uses same architecture as the version of Python used to run ``build_prepare.py``. is used. * ``PILLOW_BUILD`` can be used to override the ``winbuild\build`` directory path, used to store generated build scripts and compiled libraries. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 4d9f79211..bebafff9f 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -1,4 +1,5 @@ import os +import platform import shutil import struct import subprocess @@ -93,6 +94,7 @@ SF_MIRROR = "http://iweb.dl.sourceforge.net" architectures = { "x86": {"vcvars_arch": "x86", "msbuild_arch": "Win32"}, "x64": {"vcvars_arch": "x86_amd64", "msbuild_arch": "x64"}, + "ARM64": {"vcvars_arch": "x86_arm64", "msbuild_arch": "ARM64"}, } header = [ @@ -490,7 +492,10 @@ if __name__ == "__main__": python_dir = os.environ.get("PYTHON") python_exe = os.environ.get("EXECUTABLE", "python.exe") architecture = os.environ.get( - "ARCHITECTURE", "x86" if struct.calcsize("P") == 4 else "x64" + "ARCHITECTURE", + "ARM64" + if platform.machine() == "ARM64" + else ("x86" if struct.calcsize("P") == 4 else "x64"), ) build_dir = os.environ.get("PILLOW_BUILD", os.path.join(winbuild_dir, "build")) sources_dir = "" From 22a1f6db5e097783885be99970ae45496fc296e8 Mon Sep 17 00:00:00 2001 From: Gabor Kertesz Date: Fri, 29 Oct 2021 12:20:56 +0200 Subject: [PATCH 336/633] lcms2: Update to VS2019 In order to enable win-arm64, VS2019 should be used, while other platforms should work with newer version as well. Tested on x64-win10. --- winbuild/build_prepare.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index bebafff9f..01f1bac29 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -225,21 +225,21 @@ deps = { "filename": "lcms2-2.13.tar.gz", "dir": "lcms2-2.13", "patch": { - r"Projects\VC2017\lcms2_static\lcms2_static.vcxproj": { + r"Projects\VC2019\lcms2_static\lcms2_static.vcxproj": { # default is /MD for x86 and /MT for x64, we need /MD always "MultiThreaded": "MultiThreadedDLL", # noqa: E501 # retarget to default toolset (selected by vcvarsall.bat) - "v141": "$(DefaultPlatformToolset)", # noqa: E501 + "v142": "$(DefaultPlatformToolset)", # noqa: E501 # retarget to latest (selected by vcvarsall.bat) - "10.0.17134.0": "$(WindowsSDKVersion)", # noqa: E501 + "10.0": "$(WindowsSDKVersion)", # noqa: E501 } }, "build": [ cmd_rmdir("Lib"), - cmd_rmdir(r"Projects\VC2017\Release"), - cmd_msbuild(r"Projects\VC2017\lcms2.sln", "Release", "Clean"), + cmd_rmdir(r"Projects\VC2019\Release"), + cmd_msbuild(r"Projects\VC2019\lcms2.sln", "Release", "Clean"), cmd_msbuild( - r"Projects\VC2017\lcms2.sln", "Release", "lcms2_static:Rebuild" + r"Projects\VC2019\lcms2.sln", "Release", "lcms2_static:Rebuild" ), cmd_xcopy("include", "{inc_dir}"), ], From a13ba2ee3b4b8719379bc86d3008512499381a97 Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Tue, 1 Feb 2022 19:49:45 +0000 Subject: [PATCH 337/633] Support Python distributions where _tkinter is compiled in In some cases, the Tk library may have been directly compiled in or bundled into the main executable by the time Pillow runs, in which case __file__ isn't available (nor would it make sense to use, anyway). If __file__ is missing, then set the library path to None and rely on our Tk loader being able to find the function pointers within the main binary - we know we probably have it because we've managed to import it already. --- src/PIL/_tkinter_finder.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/PIL/_tkinter_finder.py b/src/PIL/_tkinter_finder.py index ba4d045e6..5253f0759 100644 --- a/src/PIL/_tkinter_finder.py +++ b/src/PIL/_tkinter_finder.py @@ -5,10 +5,15 @@ import tkinter import warnings from tkinter import _tkinter as tk -if hasattr(sys, "pypy_find_executable"): - TKINTER_LIB = tk.tklib_cffi.__file__ -else: - TKINTER_LIB = tk.__file__ +try: + if hasattr(sys, "pypy_find_executable"): + TKINTER_LIB = tk.tklib_cffi.__file__ + else: + TKINTER_LIB = tk.__file__ +except AttributeError: + # _tkinter may be compiled directly into Python, in which case __file__ is + # not available. load_tkinter_funcs will check the binary first in any case. + TKINTER_LIB = None tk_version = str(tkinter.TkVersion) if tk_version == "8.4": From fb7edfda68a8972ec487eab845506fc08f25cae8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 2 Feb 2022 11:49:31 +1100 Subject: [PATCH 338/633] Improved consistency of returning an image access object from load() --- Tests/test_file_eps.py | 9 +++++++++ Tests/test_file_gbr.py | 22 +++++++++++++++------- Tests/test_file_icns.py | 8 ++++++++ Tests/test_file_ico.py | 5 +++++ Tests/test_file_wal.py | 16 ++++++++++------ Tests/test_file_wmf.py | 6 ++++++ src/PIL/EpsImagePlugin.py | 12 ++++++------ src/PIL/GbrImagePlugin.py | 10 ++++------ src/PIL/IcnsImagePlugin.py | 9 +++++---- src/PIL/IcoImagePlugin.py | 2 +- src/PIL/ImageFile.py | 1 + src/PIL/WalImageFile.py | 13 +++++-------- src/PIL/WmfImagePlugin.py | 2 +- 13 files changed, 76 insertions(+), 39 deletions(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 4c0b96f73..1790f4f77 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -58,6 +58,15 @@ def test_sanity(): assert image2_scale2.format == "EPS" +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +def test_load(): + with Image.open(FILE1) as im: + assert im.load()[0, 0] == (255, 255, 255) + + # Test again now that it has already been loaded once + assert im.load()[0, 0] == (255, 255, 255) + + def test_invalid_file(): invalid_file = "Tests/images/flower.jpg" diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index 8d7fcf147..1ea8af8ee 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -5,20 +5,28 @@ from PIL import GbrImagePlugin, Image from .helper import assert_image_equal_tofile -def test_invalid_file(): - invalid_file = "Tests/images/flower.jpg" - - with pytest.raises(SyntaxError): - GbrImagePlugin.GbrImageFile(invalid_file) - - def test_gbr_file(): with Image.open("Tests/images/gbr.gbr") as im: assert_image_equal_tofile(im, "Tests/images/gbr.png") +def test_load(): + with Image.open("Tests/images/gbr.gbr") as im: + assert im.load()[0, 0] == (0, 0, 0, 0) + + # Test again now that it has already been loaded once + assert im.load()[0, 0] == (0, 0, 0, 0) + + def test_multiple_load_operations(): with Image.open("Tests/images/gbr.gbr") as im: im.load() im.load() assert_image_equal_tofile(im, "Tests/images/gbr.png") + + +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" + + with pytest.raises(SyntaxError): + GbrImagePlugin.GbrImageFile(invalid_file) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 3afbbeaac..b492f6cb2 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -28,6 +28,14 @@ def test_sanity(): assert im.format == "ICNS" +def test_load(): + with Image.open(TEST_FILE) as im: + assert im.load()[0, 0] == (0, 0, 0, 0) + + # Test again now that it has already been loaded once + assert im.load()[0, 0] == (0, 0, 0, 0) + + def test_save(tmp_path): temp_file = str(tmp_path / "temp.icns") diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 317264db6..73ac6f742 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -18,6 +18,11 @@ def test_sanity(): assert im.get_format_mimetype() == "image/x-icon" +def test_load(): + with Image.open(TEST_ICO_FILE) as im: + assert im.load()[0, 0] == (1, 1, 9, 255) + + def test_mask(): with Image.open("Tests/images/hopper_mask.ico") as im: assert_image_equal_tofile(im, "Tests/images/hopper_mask.png") diff --git a/Tests/test_file_wal.py b/Tests/test_file_wal.py index f25b42fe0..4be46e9d6 100644 --- a/Tests/test_file_wal.py +++ b/Tests/test_file_wal.py @@ -2,15 +2,11 @@ from PIL import WalImageFile from .helper import assert_image_equal_tofile +TEST_FILE = "Tests/images/hopper.wal" + def test_open(): - # Arrange - TEST_FILE = "Tests/images/hopper.wal" - - # Act with WalImageFile.open(TEST_FILE) as im: - - # Assert assert im.format == "WAL" assert im.format_description == "Quake2 Texture" assert im.mode == "P" @@ -19,3 +15,11 @@ def test_open(): assert isinstance(im, WalImageFile.WalImageFile) assert_image_equal_tofile(im, "Tests/images/hopper_wal.png") + + +def test_load(): + with WalImageFile.open(TEST_FILE) as im: + assert im.load()[0, 0] == 122 + + # Test again now that it has already been loaded once + assert im.load()[0, 0] == 122 diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 3f8bc96cc..d6769a24b 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -24,6 +24,12 @@ def test_load_raw(): assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref.png", 2.0) +def test_load(): + with Image.open("Tests/images/drawing.emf") as im: + if hasattr(Image.core, "drawwmf"): + assert im.load()[0, 0] == (255, 255, 255) + + def test_register_handler(tmp_path): class TestHandler: methodCalled = False diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 5d9920280..b67363beb 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -329,12 +329,12 @@ class EpsImageFile(ImageFile.ImageFile): def load(self, scale=1, transparency=False): # Load EPS via Ghostscript - if not self.tile: - return - self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency) - self.mode = self.im.mode - self._size = self.im.size - self.tile = [] + if self.tile: + self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency) + self.mode = self.im.mode + self._size = self.im.size + self.tile = [] + return Image.Image.load(self) def load_seek(self, *args, **kwargs): # we can't incrementally load, so force ImageFile.parser to diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py index 0f230602d..3d8fc47b2 100644 --- a/src/PIL/GbrImagePlugin.py +++ b/src/PIL/GbrImagePlugin.py @@ -84,12 +84,10 @@ class GbrImageFile(ImageFile.ImageFile): self._data_size = width * height * color_depth def load(self): - if self.im: - # Already loaded - return - - self.im = Image.core.new(self.mode, self.size) - self.frombytes(self.fp.read(self._data_size)) + if not self.im: + self.im = Image.core.new(self.mode, self.size) + self.frombytes(self.fp.read(self._data_size)) + return Image.Image.load(self) # diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 6412d1cfb..069aff96b 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -286,21 +286,22 @@ class IcnsImageFile(ImageFile.ImageFile): self.best_size[1] * self.best_size[2], ) - Image.Image.load(self) + px = Image.Image.load(self) if self.im and self.im.size == self.size: # Already loaded - return + return px self.load_prepare() # This is likely NOT the best way to do it, but whatever. im = self.icns.getimage(self.best_size) # If this is a PNG or JPEG 2000, it won't be loaded yet - im.load() + px = im.load() self.im = im.im self.mode = im.mode self.size = im.size - self.load_end() + + return px def _save(im, fp, filename): diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index d9ff9b5e7..82837f307 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -306,7 +306,7 @@ class IcoImageFile(ImageFile.ImageFile): def load(self): if self.im and self.im.size == self.size: # Already loaded - return + return Image.Image.load(self) im = self.ico.getimage(self.size) # if tile is PNG, it won't really be loaded yet im.load() diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 3374a5b1d..331410f0e 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -328,6 +328,7 @@ class StubImageFile(ImageFile): # become the other object (!) self.__class__ = image.__class__ self.__dict__ = image.__dict__ + return image.load() def _load(self): """(Hook) Find actual image loader.""" diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index 1354ad32b..0dc695a88 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -51,14 +51,11 @@ class WalImageFile(ImageFile.ImageFile): self.info["next_name"] = next_name def load(self): - if self.im: - # Already loaded - return - - self.im = Image.core.new(self.mode, self.size) - self.frombytes(self.fp.read(self.size[0] * self.size[1])) - self.putpalette(quake2palette) - Image.Image.load(self) + if not self.im: + self.im = Image.core.new(self.mode, self.size) + self.frombytes(self.fp.read(self.size[0] * self.size[1])) + self.putpalette(quake2palette) + return Image.Image.load(self) def open(filename): diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 27f5d2f87..c32cc52f8 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -158,7 +158,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): (x1 - x0) * self.info["dpi"] // self._inch, (y1 - y0) * self.info["dpi"] // self._inch, ) - super().load() + return super().load() def _save(im, fp, filename): From 8e3878dec2b300f5b495dd81989a77089747abee Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 2 Feb 2022 14:33:19 +1100 Subject: [PATCH 339/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d6cd9d50f..66d417393 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 9.1.0 (unreleased) ------------------ +- Enable arm64 for MSVC on Windows #5811 + [gaborkertesz-linaro, gaborkertesz] + +- Keep IPython/Jupyter text/plain output stable #5891 + [shamrin, radarhere] + - Raise an error when performing a negative crop #5972 [radarhere, hugovk] From dd46100bdc7fbb6c2fb71008a49c40f081eb0c7c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Jan 2022 21:49:55 +1100 Subject: [PATCH 340/633] Restrict builtins within lambdas for ImageMath.eval --- Tests/test_imagemath.py | 12 ++++++++++-- src/PIL/ImageMath.py | 15 +++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index 25811aa89..39d91eade 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -52,9 +52,17 @@ def test_ops(): assert pixel(ImageMath.eval("float(B)**33", images)) == "F 8589934592.0" -def test_prevent_exec(): +@pytest.mark.parametrize( + "expression", + ( + "exec('pass')", + "(lambda: exec('pass'))()", + "(lambda: (lambda: exec('pass'))())()", + ), +) +def test_prevent_exec(expression): with pytest.raises(ValueError): - ImageMath.eval("exec('pass')") + ImageMath.eval(expression) def test_logical(): diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 4b6e4ccda..09d9898d7 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -240,11 +240,18 @@ def eval(expression, _dict={}, **kw): if hasattr(v, "im"): args[k] = _Operand(v) - code = compile(expression, "", "eval") - for name in code.co_names: - if name not in args and name != "abs": - raise ValueError(f"'{name}' not allowed") + compiled_code = compile(expression, "", "eval") + def scan(code): + for const in code.co_consts: + if type(const) == type(compiled_code): + scan(const) + + for name in code.co_names: + if name not in args and name != "abs": + raise ValueError(f"'{name}' not allowed") + + scan(compiled_code) out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args) try: return out.im From 8da80130dbc747f3954b4904247d26289fe722f9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 17 Jan 2022 08:59:17 +1100 Subject: [PATCH 341/633] In show_file, use os.remove to remove temporary images --- Tests/test_imageshow.py | 6 +- src/PIL/ImageShow.py | 145 +++++++++++++++++++++++++++++++--------- 2 files changed, 119 insertions(+), 32 deletions(-) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 02edfdfa1..bf19a6033 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -85,11 +85,13 @@ def test_ipythonviewer(): not on_ci() or is_win32(), reason="Only run on CIs; hangs on Windows CIs", ) -def test_file_deprecated(): +def test_file_deprecated(tmp_path): + f = str(tmp_path / "temp.jpg") for viewer in ImageShow._viewers: + hopper().save(f) with pytest.warns(DeprecationWarning): try: - viewer.show_file(file="test.jpg") + viewer.show_file(file=f) except NotImplementedError: pass with pytest.raises(TypeError): diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 2165da307..7212baa1c 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -15,7 +15,6 @@ import os import shutil import subprocess import sys -import tempfile import warnings from shlex import quote @@ -180,16 +179,15 @@ class MacViewer(Viewer): path = options.pop("file") else: raise TypeError("Missing required argument: 'path'") - fd, temp_path = tempfile.mkstemp() - with os.fdopen(fd, "w") as f: - f.write(path) - with open(temp_path) as f: - subprocess.Popen( - ["im=$(cat); open -a Preview.app $im; sleep 20; rm -f $im"], - shell=True, - stdin=f, - ) - os.remove(temp_path) + subprocess.call(["open", "-a", "Preview.app", path]) + subprocess.Popen( + [ + sys.executable, + "-c", + "import os, sys, time;time.sleep(20);os.remove(sys.argv[1])", + path, + ] + ) return 1 @@ -205,6 +203,16 @@ class UnixViewer(Viewer): command = self.get_command_ex(file, **options)[0] return f"({command} {quote(file)}; rm -f {quote(file)})&" + +class XDGViewer(UnixViewer): + """ + The freedesktop.org ``xdg-open`` command. + """ + + def get_command_ex(self, file, **options): + command = executable = "xdg-open" + return command, executable + def show_file(self, path=None, **options): """ Display given file. @@ -223,28 +231,11 @@ class UnixViewer(Viewer): path = options.pop("file") else: raise TypeError("Missing required argument: 'path'") - fd, temp_path = tempfile.mkstemp() - with os.fdopen(fd, "w") as f: - f.write(path) - with open(temp_path) as f: - command = self.get_command_ex(path, **options)[0] - subprocess.Popen( - ["im=$(cat);" + command + " $im; rm -f $im"], shell=True, stdin=f - ) - os.remove(temp_path) + subprocess.Popen(["xdg-open", path]) + os.remove(path) return 1 -class XDGViewer(UnixViewer): - """ - The freedesktop.org ``xdg-open`` command. - """ - - def get_command_ex(self, file, **options): - command = executable = "xdg-open" - return command, executable - - class DisplayViewer(UnixViewer): """ The ImageMagick ``display`` command. @@ -257,6 +248,32 @@ class DisplayViewer(UnixViewer): command += f" -name {quote(title)}" return command, executable + def show_file(self, path=None, **options): + """ + Display given file. + + Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated, + and ``path`` should be used instead. + """ + if path is None: + if "file" in options: + warnings.warn( + "The 'file' argument is deprecated and will be removed in Pillow " + "10 (2023-07-01). Use 'path' instead.", + DeprecationWarning, + ) + path = options.pop("file") + else: + raise TypeError("Missing required argument: 'path'") + args = ["display"] + if "title" in options: + args += ["-name", options["title"]] + args.append(path) + + subprocess.Popen(args) + os.remove(path) + return 1 + class GmDisplayViewer(UnixViewer): """The GraphicsMagick ``gm display`` command.""" @@ -266,6 +283,27 @@ class GmDisplayViewer(UnixViewer): command = "gm display" return command, executable + def show_file(self, path=None, **options): + """ + Display given file. + + Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated, + and ``path`` should be used instead. + """ + if path is None: + if "file" in options: + warnings.warn( + "The 'file' argument is deprecated and will be removed in Pillow " + "10 (2023-07-01). Use 'path' instead.", + DeprecationWarning, + ) + path = options.pop("file") + else: + raise TypeError("Missing required argument: 'path'") + subprocess.Popen(["gm", "display", path]) + os.remove(path) + return 1 + class EogViewer(UnixViewer): """The GNOME Image Viewer ``eog`` command.""" @@ -275,6 +313,27 @@ class EogViewer(UnixViewer): command = "eog -n" return command, executable + def show_file(self, path=None, **options): + """ + Display given file. + + Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated, + and ``path`` should be used instead. + """ + if path is None: + if "file" in options: + warnings.warn( + "The 'file' argument is deprecated and will be removed in Pillow " + "10 (2023-07-01). Use 'path' instead.", + DeprecationWarning, + ) + path = options.pop("file") + else: + raise TypeError("Missing required argument: 'path'") + subprocess.Popen(["eog", "-n", path]) + os.remove(path) + return 1 + class XVViewer(UnixViewer): """ @@ -290,6 +349,32 @@ class XVViewer(UnixViewer): command += f" -name {quote(title)}" return command, executable + def show_file(self, path=None, **options): + """ + Display given file. + + Before Pillow 9.1.0, the first argument was ``file``. This is now deprecated, + and ``path`` should be used instead. + """ + if path is None: + if "file" in options: + warnings.warn( + "The 'file' argument is deprecated and will be removed in Pillow " + "10 (2023-07-01). Use 'path' instead.", + DeprecationWarning, + ) + path = options.pop("file") + else: + raise TypeError("Missing required argument: 'path'") + args = ["xv"] + if "title" in options: + args += ["-name", options["title"]] + args.append(path) + + subprocess.Popen(args) + os.remove(path) + return 1 + if sys.platform not in ("win32", "darwin"): # unixoids if shutil.which("xdg-open"): From 143032103c9f2d55a0a7960bd3e630cb72549e8a Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 18 Jan 2022 11:24:01 +1100 Subject: [PATCH 342/633] Updated formatting Co-authored-by: Hugo van Kemenade --- src/PIL/ImageShow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 7212baa1c..ccdb0b2a0 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -184,7 +184,7 @@ class MacViewer(Viewer): [ sys.executable, "-c", - "import os, sys, time;time.sleep(20);os.remove(sys.argv[1])", + "import os, sys, time; time.sleep(20); os.remove(sys.argv[1])", path, ] ) From 10c4f75aaa383bd9671e923e3b91d391ea12d781 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 3 Feb 2022 08:58:12 +1100 Subject: [PATCH 343/633] Added delay after opening image with xdg-open --- src/PIL/ImageShow.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index ccdb0b2a0..f8829fc21 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -126,6 +126,16 @@ class Viewer: os.system(self.get_command(path, **options)) return 1 + def _remove_path_after_delay(self, path): + subprocess.Popen( + [ + sys.executable, + "-c", + "import os, sys, time; time.sleep(20); os.remove(sys.argv[1])", + path, + ] + ) + # -------------------------------------------------------------------- @@ -180,14 +190,7 @@ class MacViewer(Viewer): else: raise TypeError("Missing required argument: 'path'") subprocess.call(["open", "-a", "Preview.app", path]) - subprocess.Popen( - [ - sys.executable, - "-c", - "import os, sys, time; time.sleep(20); os.remove(sys.argv[1])", - path, - ] - ) + self._remove_path_after_delay(path) return 1 @@ -232,7 +235,7 @@ class XDGViewer(UnixViewer): else: raise TypeError("Missing required argument: 'path'") subprocess.Popen(["xdg-open", path]) - os.remove(path) + self._remove_path_after_delay(path) return 1 From 596eaf35cc983fe73e2408e3ca7f3e1431fd06e3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 3 Feb 2022 09:46:57 +1100 Subject: [PATCH 344/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 66d417393..fc9455652 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -32,6 +32,15 @@ Changelog (Pillow) - Remove readonly from Image.__eq__ #5930 [hugovk] +9.0.1 (2022-02-03) +------------------ + +- In show_file, use os.remove to remove temporary images. CVE-2022-24303 #6010 + [radarhere, hugovk] + +- Restrict builtins within lambdas for ImageMath.eval. CVE-2022-22817 #6009 + [radarhere] + 9.0.0 (2022-01-02) ------------------ From 8ef2d987ab652346126cf7e567eac7d2c0754940 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 3 Feb 2022 09:48:56 +1100 Subject: [PATCH 345/633] Added release notes for 9.0.1 --- docs/releasenotes/9.0.1.rst | 23 +++++++++++++++++++++++ docs/releasenotes/index.rst | 1 + 2 files changed, 24 insertions(+) create mode 100644 docs/releasenotes/9.0.1.rst diff --git a/docs/releasenotes/9.0.1.rst b/docs/releasenotes/9.0.1.rst new file mode 100644 index 000000000..5d1b246bc --- /dev/null +++ b/docs/releasenotes/9.0.1.rst @@ -0,0 +1,23 @@ +9.0.1 +----- + +Security +======== + +This release addresses several security problems. + +:cve:`CVE-2022-24303`: If the path to the temporary directory on Linux or macOS +contained a space, this would break removal of the temporary image file after +``im.show()`` (and related actions), and potentially remove an unrelated file. This +been present since PIL. + +:cve:`CVE-2022-22817`: While Pillow 9.0 restricted top-level builtins available to +:py:meth:`PIL.ImageMath.eval`, it did not prevent builtins available to lambda +expressions. These are now also restricted. + +Other Changes +============= + +Pillow 9.0 added support for ``xdg-open`` as an image viewer, but there have been +reports that the temporary image file was removed too quickly to be loaded into the +final application. A delay has been added. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 8d1ad7837..e9b11c220 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -14,6 +14,7 @@ expected to be backported to earlier versions. .. toctree:: :maxdepth: 2 + 9.0.1 9.0.0 8.4.0 8.3.2 From 9d8f173e39458bb73107f90813c745717d87ea78 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 3 Feb 2022 18:05:50 +1100 Subject: [PATCH 346/633] Updated libimagequant to 4.0.0 --- depends/install_imagequant.sh | 11 ++++++----- docs/installation.rst | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 774f26767..31fc2adaa 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,14 +1,15 @@ #!/bin/bash # install libimagequant -archive=libimagequant-2.17.0 +archive=libimagequant-4.0.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz -pushd $archive +pushd $archive/imagequant-sys -make shared -sudo cp libimagequant.so* /usr/lib/ -sudo cp libimagequant.h /usr/include/ +cargo install cargo-c +cargo cinstall --prefix=/usr --destdir=. +sudo cp usr/lib/libimagequant.so* /usr/lib/ +sudo cp usr/include/libimagequant.h /usr/include/ popd diff --git a/docs/installation.rst b/docs/installation.rst index 984a689c2..a6ed8f681 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -187,7 +187,7 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-2.17.0** + * Pillow has been tested with libimagequant **2.6-4.0** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. From caf6fc60cac8ed59b8b2c361df1e9705be3d0504 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 4 Feb 2022 03:19:49 +1100 Subject: [PATCH 347/633] Corrected sentence [ci skip] --- docs/releasenotes/9.0.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/9.0.1.rst b/docs/releasenotes/9.0.1.rst index 5d1b246bc..c1feee088 100644 --- a/docs/releasenotes/9.0.1.rst +++ b/docs/releasenotes/9.0.1.rst @@ -9,7 +9,7 @@ This release addresses several security problems. :cve:`CVE-2022-24303`: If the path to the temporary directory on Linux or macOS contained a space, this would break removal of the temporary image file after ``im.show()`` (and related actions), and potentially remove an unrelated file. This -been present since PIL. +has been present since PIL. :cve:`CVE-2022-22817`: While Pillow 9.0 restricted top-level builtins available to :py:meth:`PIL.ImageMath.eval`, it did not prevent builtins available to lambda From 943479941578b78ed3f8ea6bd1e3031b1244f006 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 4 Feb 2022 03:29:26 +1100 Subject: [PATCH 348/633] Updated lcms2 to 2.13.1 --- docs/installation.rst | 2 +- winbuild/build_prepare.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index a6ed8f681..023d27ddd 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -169,7 +169,7 @@ Many of Pillow's features require external libraries: * **littlecms** provides color management * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and - above uses liblcms2. Tested with **1.19** and **2.7-2.13**. + above uses liblcms2. Tested with **1.19** and **2.7-2.13.1**. * **libwebp** provides the WebP format. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 01f1bac29..6a37ff8bb 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -221,9 +221,9 @@ deps = { # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"], }, "lcms2": { - "url": SF_MIRROR + "/project/lcms/lcms/2.13/lcms2-2.13.tar.gz", - "filename": "lcms2-2.13.tar.gz", - "dir": "lcms2-2.13", + "url": SF_MIRROR + "/project/lcms/lcms/2.13/lcms2-2.13.1.tar.gz", + "filename": "lcms2-2.13.1.tar.gz", + "dir": "lcms2-2.13.1", "patch": { r"Projects\VC2019\lcms2_static\lcms2_static.vcxproj": { # default is /MD for x86 and /MT for x64, we need /MD always From f3762905e08a96902f20391289d487b028e68f98 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 6 Feb 2022 08:32:37 +1100 Subject: [PATCH 349/633] Upgraded Python 3.10 image to VS 2022 --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index d525e4cfc..f86500b48 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -12,7 +12,7 @@ environment: matrix: - PYTHON: C:/Python310 ARCHITECTURE: x86 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 - PYTHON: C:/Python37-x64 ARCHITECTURE: x64 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 From 718b72c0fd949ba6cf11bc5ffd936e772132231a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 7 Feb 2022 08:13:20 +1100 Subject: [PATCH 350/633] Updated harfbuzz to 3.3.2 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 01f1bac29..b00905b09 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -280,9 +280,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/3.3.1.zip", - "filename": "harfbuzz-3.3.1.zip", - "dir": "harfbuzz-3.3.1", + "url": "https://github.com/harfbuzz/harfbuzz/archive/3.3.2.zip", + "filename": "harfbuzz-3.3.2.zip", + "dir": "harfbuzz-3.3.2", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 85b872deb650847f9b087db53b6d4bb963679653 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 7 Feb 2022 10:18:14 +1100 Subject: [PATCH 351/633] Added unpacker from RGBA;15 to RGB --- src/libImaging/Unpack.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 5dac95c1d..4f9838fa8 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1529,6 +1529,7 @@ static struct { {"RGB", "RGBX", 32, copy4}, {"RGB", "RGBX;L", 32, unpackRGBAL}, {"RGB", "RGBA;L", 32, unpackRGBAL}, + {"RGB", "RGBA;15", 16, ImagingUnpackRGBA15}, {"RGB", "BGRX", 32, ImagingUnpackBGRX}, {"RGB", "XRGB", 32, ImagingUnpackXRGB}, {"RGB", "XBGR", 32, ImagingUnpackXBGR}, From c6b81d5989e3c0c01ccf683ed22df4c13e093165 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 7 Feb 2022 19:15:25 +1100 Subject: [PATCH 352/633] Ensure Tkinter hook is activated for getimage() --- Tests/test_imagetk.py | 5 ++-- src/PIL/ImageTk.py | 59 +++++++++++++++++++++++-------------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 928b8cbd1..5996183b3 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -75,8 +75,9 @@ def test_photoimage_blank(): assert im_tk.width() == 100 assert im_tk.height() == 100 - # reloaded = ImageTk.getimage(im_tk) - # assert_image_equal(reloaded, im) + im = Image.new(mode, (100, 100)) + reloaded = ImageTk.getimage(im_tk) + assert_image_equal(reloaded.convert(mode), im) def test_bitmapimage(): diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 62db7a717..8e540b05e 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -58,6 +58,35 @@ def _get_image_from_kw(kw): return Image.open(source) +def _pyimagingtkcall(command, photo, id): + tk = photo.tk + try: + tk.call(command, photo, id) + except tkinter.TclError: + # activate Tkinter hook + try: + from . import _imagingtk + + try: + if hasattr(tk, "interp"): + # Required for PyPy, which always has CFFI installed + from cffi import FFI + + ffi = FFI() + + # PyPy is using an FFI CDATA element + # (Pdb) self.tk.interp + # + _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1) + else: + _imagingtk.tkinit(tk.interpaddr(), 1) + except AttributeError: + _imagingtk.tkinit(id(tk), 0) + tk.call(command, photo, id) + except (ImportError, AttributeError, tkinter.TclError): + raise # configuration problem; cannot attach to Tkinter + + # -------------------------------------------------------------------- # PhotoImage @@ -170,33 +199,7 @@ class PhotoImage: block = image.new_block(self.__mode, im.size) image.convert2(block, image) # convert directly between buffers - tk = self.__photo.tk - - try: - tk.call("PyImagingPhoto", self.__photo, block.id) - except tkinter.TclError: - # activate Tkinter hook - try: - from . import _imagingtk - - try: - if hasattr(tk, "interp"): - # Required for PyPy, which always has CFFI installed - from cffi import FFI - - ffi = FFI() - - # PyPy is using an FFI CDATA element - # (Pdb) self.tk.interp - # - _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1) - else: - _imagingtk.tkinit(tk.interpaddr(), 1) - except AttributeError: - _imagingtk.tkinit(id(tk), 0) - tk.call("PyImagingPhoto", self.__photo, block.id) - except (ImportError, AttributeError, tkinter.TclError): - raise # configuration problem; cannot attach to Tkinter + _pyimagingtkcall("PyImagingPhoto", self.__photo, block.id) # -------------------------------------------------------------------- @@ -276,7 +279,7 @@ def getimage(photo): im = Image.new("RGBA", (photo.width(), photo.height())) block = im.im - photo.tk.call("PyImagingPhotoGet", photo, block.id) + _pyimagingtkcall("PyImagingPhotoGet", photo, block.id) return im From 3114064b1690c62467ff7cb401c683aecade8f93 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 7 Feb 2022 21:06:36 +1100 Subject: [PATCH 353/633] Removed redundant try catch --- src/PIL/ImageTk.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 8e540b05e..d151b9b0e 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -64,27 +64,25 @@ def _pyimagingtkcall(command, photo, id): tk.call(command, photo, id) except tkinter.TclError: # activate Tkinter hook + # may raise an error if it cannot attach to Tkinter + from . import _imagingtk + try: - from . import _imagingtk + if hasattr(tk, "interp"): + # Required for PyPy, which always has CFFI installed + from cffi import FFI - try: - if hasattr(tk, "interp"): - # Required for PyPy, which always has CFFI installed - from cffi import FFI + ffi = FFI() - ffi = FFI() - - # PyPy is using an FFI CDATA element - # (Pdb) self.tk.interp - # - _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1) - else: - _imagingtk.tkinit(tk.interpaddr(), 1) - except AttributeError: - _imagingtk.tkinit(id(tk), 0) - tk.call(command, photo, id) - except (ImportError, AttributeError, tkinter.TclError): - raise # configuration problem; cannot attach to Tkinter + # PyPy is using an FFI CDATA element + # (Pdb) self.tk.interp + # + _imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1) + else: + _imagingtk.tkinit(tk.interpaddr(), 1) + except AttributeError: + _imagingtk.tkinit(id(tk), 0) + tk.call(command, photo, id) # -------------------------------------------------------------------- From ecb64fe2103b9393fd5cf77d3965504e1749ce6d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 8 Feb 2022 09:12:01 +1100 Subject: [PATCH 354/633] Allow 1 mode images to be inverted --- Tests/test_imageops.py | 1 + src/PIL/ImageOps.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 6aa1cf35e..87fffa7b7 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -63,6 +63,7 @@ def test_sanity(): ImageOps.grayscale(hopper("L")) ImageOps.grayscale(hopper("RGB")) + ImageOps.invert(hopper("1")) ImageOps.invert(hopper("L")) ImageOps.invert(hopper("RGB")) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index b170e9d8c..86db777d9 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -523,7 +523,7 @@ def invert(image): lut = [] for i in range(256): lut.append(255 - i) - return _lut(image, lut) + return image.point(lut) if image.mode == "1" else _lut(image, lut) def mirror(image): From a278e0aa656745122e76c21d1589e9c9762ca743 Mon Sep 17 00:00:00 2001 From: nulano Date: Mon, 7 Feb 2022 23:57:35 +0000 Subject: [PATCH 355/633] issue warning if Raqm layout is requested, but Raqm is not available --- Tests/test_imagefont.py | 13 +++++++++++++ src/PIL/ImageFont.py | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 0d423aab7..3dcbf18d2 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1022,3 +1022,16 @@ def test_oom(test_file): font = ImageFont.truetype(BytesIO(f.read())) with pytest.raises(Image.DecompressionBombError): font.getmask("Test Text") + + +def test_raqm_missing_warning(monkeypatch): + monkeypatch.setattr(ImageFont.core, "HAVE_RAQM", False) + with pytest.warns(UserWarning) as record: + font = ImageFont.truetype( + FONT_PATH, FONT_SIZE, layout_engine=ImageFont.LAYOUT_RAQM + ) + assert font.layout_engine == ImageFont.LAYOUT_BASIC + assert str(record[-1].message) == ( + "Raqm layout was requested, but Raqm is not available. " + "Falling back to basic layout." + ) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 805c8fff9..58bf46a32 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -169,6 +169,12 @@ class FreeTypeFont: if core.HAVE_RAQM: layout_engine = LAYOUT_RAQM elif layout_engine == LAYOUT_RAQM and not core.HAVE_RAQM: + import warnings + + warnings.warn( + "Raqm layout was requested, but Raqm is not available. " + "Falling back to basic layout." + ) layout_engine = LAYOUT_BASIC self.layout_engine = layout_engine From 92371504316194883518dd5f0182ea01cbbab0f7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 8 Feb 2022 23:35:01 +1100 Subject: [PATCH 356/633] Added CentOS Stream 9 --- .github/workflows/test-docker.yml | 1 + docs/installation.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 656df5e91..db866774c 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -20,6 +20,7 @@ jobs: arch, centos-7-amd64, centos-stream-8-amd64, + centos-stream-9-amd64, debian-10-buster-x86, debian-11-bullseye-x86, fedora-34-amd64, diff --git a/docs/installation.rst b/docs/installation.rst index 023d27ddd..cf94f0c72 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -455,6 +455,8 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | CentOS Stream 8 | 3.9 | x86-64 | +----------------------------------+----------------------------+---------------------+ +| CentOS Stream 9 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ | Debian 10 Buster | 3.7 | x86 | +----------------------------------+----------------------------+---------------------+ | Debian 11 Bullseye | 3.9 | x86 | From d7922d1e85186951dbd1e1ac856a0cb45c4a7e06 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 9 Feb 2022 14:27:21 +1100 Subject: [PATCH 357/633] Updated macOS tested Pillow versions [ci skip] --- docs/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index cf94f0c72..141649107 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -496,11 +496,11 @@ These platforms have been reported to work at the versions mentioned. | Operating system | | Tested Python | | Latest tested | | Tested | | | | versions | | Pillow version | | processors | +==================================+===========================+==================+==============+ -| macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10 | 9.0.0 |arm | +| macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10 | 9.0.1 |arm | +----------------------------------+---------------------------+------------------+--------------+ | macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | | +---------------------------+------------------+--------------+ -| | 3.7, 3.8, 3.9, 3.10 | 9.0.0 |x86-64 | +| | 3.7, 3.8, 3.9, 3.10 | 9.0.1 |x86-64 | | +---------------------------+------------------+--------------+ | | 3.6 | 8.4.0 |x86-64 | +----------------------------------+---------------------------+------------------+--------------+ From 601c9d8515dba996af3f0b96d1a671619de37f10 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 9 Feb 2022 14:28:43 +0200 Subject: [PATCH 358/633] Fix return in docs --- src/PIL/JpegImagePlugin.py | 1 + src/PIL/PngImagePlugin.py | 1 + src/PIL/TiffImagePlugin.py | 1 + 3 files changed, 3 insertions(+) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index ccdcc20a8..b9999bdaf 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -482,6 +482,7 @@ class JpegImageFile(ImageFile.ImageFile): """ Returns a dictionary containing the XMP tags. Requires defusedxml to be installed. + :returns: XMP tags in a dictionary. """ diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 0f596f1fd..ae38b0ed3 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -982,6 +982,7 @@ class PngImageFile(ImageFile.ImageFile): """ Returns a dictionary containing the XMP tags. Requires defusedxml to be installed. + :returns: XMP tags in a dictionary. """ return ( diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index e54082fec..a3922d699 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1124,6 +1124,7 @@ class TiffImageFile(ImageFile.ImageFile): """ Returns a dictionary containing the XMP tags. Requires defusedxml to be installed. + :returns: XMP tags in a dictionary. """ return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {} From bb5a090f60ed08941e5bdd7077f9a53caec99b57 Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Wed, 9 Feb 2022 16:16:33 +0300 Subject: [PATCH 359/633] Drop excess values in BITSPERSAMPLE --- Tests/images/tiff_wrong_bits_per_sample_2.tiff | Bin 0 -> 1041 bytes Tests/test_file_tiff.py | 14 +++++++++----- src/PIL/TiffImagePlugin.py | 8 ++++++-- 3 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 Tests/images/tiff_wrong_bits_per_sample_2.tiff diff --git a/Tests/images/tiff_wrong_bits_per_sample_2.tiff b/Tests/images/tiff_wrong_bits_per_sample_2.tiff new file mode 100644 index 0000000000000000000000000000000000000000..d44176ce76ce43f738cf215db0b7974fe3e3ff2a GIT binary patch literal 1041 zcmebD)MDUZW*BkcKPmwFA1DAJAu^BopA1 len(bps_tuple) and len(bps_tuple) == 1: + # while should have more. Or have more values + # than expected. Fix it + bps_actual_count = len(bps_tuple) + if bps_count < bps_actual_count: + bps_tuple = bps_tuple[:bps_count] + elif bps_count > bps_actual_count and bps_actual_count == 1: bps_tuple = bps_tuple * bps_count samplesPerPixel = self.tag_v2.get( From 2bbf5f0981bbaa9a0ba8a5411b537389d3d6904f Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Wed, 9 Feb 2022 16:53:27 +0300 Subject: [PATCH 360/633] Lint --- Tests/test_file_tiff.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index fd526089a..55e2c8be3 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -90,10 +90,13 @@ class TestFileTiff: assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) - @pytest.mark.parametrize("file_name,mode,w,h,offset", [ - ("tiff_wrong_bits_per_sample.tiff","RGBA",52,53,160), - ("tiff_wrong_bits_per_sample_2.tiff","RGB",16,16,8), - ]) + @pytest.mark.parametrize( + "file_name,mode,w,h,offset", + [ + ("tiff_wrong_bits_per_sample.tiff", "RGBA", 52, 53, 160), + ("tiff_wrong_bits_per_sample_2.tiff", "RGB", 16, 16, 8), + ], + ) def test_wrong_bits_per_sample(self, file_name, mode, w, h, offset): with Image.open("Tests/images/" + file_name) as im: assert im.mode == mode From 4e65e2c29de489ca098399c5c179168bd73ef17c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Feb 2022 09:47:43 +1100 Subject: [PATCH 361/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fc9455652..900346f74 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.1.0 (unreleased) ------------------ +- Added unpacker from RGBA;15 to RGB #6031 + [radarhere] + - Enable arm64 for MSVC on Windows #5811 [gaborkertesz-linaro, gaborkertesz] From e098481279477c0091d7947d589276afd587983d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Feb 2022 11:02:13 +1100 Subject: [PATCH 362/633] Combined width and height into size --- Tests/test_file_tiff.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 55e2c8be3..d4ffe88bb 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -91,17 +91,17 @@ class TestFileTiff: assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) @pytest.mark.parametrize( - "file_name,mode,w,h,offset", + "file_name,mode,size,offset", [ - ("tiff_wrong_bits_per_sample.tiff", "RGBA", 52, 53, 160), - ("tiff_wrong_bits_per_sample_2.tiff", "RGB", 16, 16, 8), + ("tiff_wrong_bits_per_sample.tiff", "RGBA", (52, 53), 160), + ("tiff_wrong_bits_per_sample_2.tiff", "RGB", (16, 16), 8), ], ) - def test_wrong_bits_per_sample(self, file_name, mode, w, h, offset): + def test_wrong_bits_per_sample(self, file_name, mode, size, offset): with Image.open("Tests/images/" + file_name) as im: assert im.mode == mode - assert im.size == (w, h) - assert im.tile == [("raw", (0, 0, w, h), offset, (mode, 0, 1))] + assert im.size == size + assert im.tile == [("raw", (0, 0) + size, offset, (mode, 0, 1))] im.load() def test_set_legacy_api(self): From 1f82202998db41f135878e96c2dfed1321323361 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Feb 2022 11:08:25 +1100 Subject: [PATCH 363/633] Adjusted comments --- src/PIL/TiffImagePlugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 307a08cea..d6f23f11b 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1307,13 +1307,14 @@ class TiffImageFile(ImageFile.ImageFile): else: bps_count = 1 bps_count += len(extra_tuple) - # Some files have only one value in bps_tuple, - # while should have more. Or have more values - # than expected. Fix it bps_actual_count = len(bps_tuple) if bps_count < bps_actual_count: + # If a file has more values in bps_tuple than expected, + # remove the excess. bps_tuple = bps_tuple[:bps_count] elif bps_count > bps_actual_count and bps_actual_count == 1: + # If a file has only one value in bps_tuple, when it should have more, + # presume it is the same number of bits for all of the samples. bps_tuple = bps_tuple * bps_count samplesPerPixel = self.tag_v2.get( From 2ae70f144f144a9e24d0618e3d30375c6b46ed02 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Feb 2022 12:00:23 +1100 Subject: [PATCH 364/633] Added get_photoshop_blocks() to parse Photoshop tag --- Tests/test_file_tiff.py | 26 ++++++++++++++++++++++++++ src/PIL/TiffImagePlugin.py | 24 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 5801e1766..21033cc5d 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -685,6 +685,32 @@ class TestFileTiff: assert description[0]["format"] == "image/tiff" assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"] + def test_get_photoshop_blocks(self): + with Image.open("Tests/images/lab.tif") as im: + assert list(im.get_photoshop_blocks().keys()) == [ + 1061, + 1002, + 1005, + 1062, + 1037, + 1049, + 1011, + 1034, + 10000, + 1013, + 1016, + 1032, + 1054, + 1050, + 1064, + 1041, + 1044, + 1036, + 1057, + 4000, + 4001, + ] + def test_close_on_load_exclusive(self, tmp_path): # similar to test_fd_leak, but runs on unixlike os tmpfile = str(tmp_path / "temp.tif") diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a3922d699..764739e90 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -41,6 +41,7 @@ import io import itertools import logging +import math import os import struct import warnings @@ -49,6 +50,8 @@ from fractions import Fraction from numbers import Number, Rational from . import Image, ImageFile, ImageOps, ImagePalette, TiffTags +from ._binary import i16be as i16 +from ._binary import i32be as i32 from ._binary import o8 from .TiffTags import TYPES @@ -1129,6 +1132,27 @@ class TiffImageFile(ImageFile.ImageFile): """ return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {} + def get_photoshop_blocks(self): + """ + Returns a dictionary of Photoshop "Image Resource Blocks". + The keys are the image resource ID. For more information, see + https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1037727 + + :returns: Photoshop "Image Resource Blocks" in a dictionary. + """ + blocks = {} + val = self.tag_v2.get(0x8649) + if val: + while val[:4] == b"8BIM": + id = i16(val[4:6]) + n = math.ceil((val[6] + 1) / 2) * 2 + size = i32(val[6 + n : 10 + n]) + data = val[10 + n : 10 + n + size] + blocks[id] = {"data": data} + + val = val[math.ceil((10 + n + size) / 2) * 2 :] + return blocks + def load(self): if self.tile and self.use_load_libtiff: return self._load_libtiff() From ef1373838dd9e8c35e490136c42a45d0ab6c534b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Feb 2022 12:15:25 +1100 Subject: [PATCH 365/633] Added release notes --- docs/releasenotes/9.1.0.rst | 16 ++++++++++++++++ docs/releasenotes/index.rst | 1 + 2 files changed, 17 insertions(+) create mode 100644 docs/releasenotes/9.1.0.rst diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst new file mode 100644 index 000000000..9e4429b24 --- /dev/null +++ b/docs/releasenotes/9.1.0.rst @@ -0,0 +1,16 @@ +9.1.0 +----- + +API Additions +============= + +Added get_photoshop_blocks() to parse Photoshop TIFF tag +-------------------------------------------------------- + +:py:meth:`~PIL.TiffImagePlugin.TiffImageFile.get_photoshop_blocks` has been added, to +allow users to determine what Photoshop "Image Resource Blocks" are contained within an +image. The keys of the returned dictionary are the image resource IDs. + +At present, the information within each block is merely returned as a dictionary with a +"data" entry. This will allow more useful information to be added in the future without +breaking backwards compatibility. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index e9b11c220..656acef95 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -14,6 +14,7 @@ expected to be backported to earlier versions. .. toctree:: :maxdepth: 2 + 9.1.0 9.0.1 9.0.0 8.4.0 From 657ec4aa3dd253400ac72fdf1d8b44960d36df84 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Feb 2022 13:53:30 +1100 Subject: [PATCH 366/633] Added release notes for #5891 --- docs/releasenotes/9.1.0.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst index 9e4429b24..734eb1d99 100644 --- a/docs/releasenotes/9.1.0.rst +++ b/docs/releasenotes/9.1.0.rst @@ -14,3 +14,13 @@ image. The keys of the returned dictionary are the image resource IDs. At present, the information within each block is merely returned as a dictionary with a "data" entry. This will allow more useful information to be added in the future without breaking backwards compatibility. + +Other Changes +============= + +Image._repr_pretty_ +------------------- + +`im._repr_pretty_` has been added to provide a representation of an image without the +identity of the object. This allows Jupyter to describe an image and have that +description stay the same on subsequent executions of the same code. From f797137ab56eb13da1381ce9154637f5beebcbb7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Feb 2022 12:33:42 +1100 Subject: [PATCH 367/633] Added release notes for #5972 --- docs/releasenotes/9.1.0.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst index 734eb1d99..a7254c6f7 100644 --- a/docs/releasenotes/9.1.0.rst +++ b/docs/releasenotes/9.1.0.rst @@ -1,6 +1,16 @@ 9.1.0 ----- +API Changes +=========== + +Raise an error when performing a negative crop +---------------------------------------------- + +Performing a negative crop on an image previously just returned a `(0, 0)` image. Now +it will raise a `ValueError`, to help reduce confusion if a user has unintentionally +provided the wrong arguments. + API Additions ============= From 912296200c3017d51cee52a19b6c906081fe704e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Feb 2022 12:41:36 +1100 Subject: [PATCH 368/633] Added release notes for #5942 --- CHANGES.rst | 2 +- docs/releasenotes/9.1.0.rst | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 900346f74..678501122 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -26,7 +26,7 @@ Changelog (Pillow) - Ensure duplicated file pointer is closed #5946 [radarhere] -- Added specific error if ImagePath coordinate type is incorrect #5942 +- Added specific error if path coordinate type is incorrect #5942 [radarhere] - Return an empty bytestring from tobytes() for an empty image #5938 diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst index a7254c6f7..d0a8758bb 100644 --- a/docs/releasenotes/9.1.0.rst +++ b/docs/releasenotes/9.1.0.rst @@ -11,6 +11,13 @@ Performing a negative crop on an image previously just returned a `(0, 0)` image it will raise a `ValueError`, to help reduce confusion if a user has unintentionally provided the wrong arguments. +Added specific error if path coordinate type is incorrect +--------------------------------------------------------- + +Rather than returning a `SystemError`, passing the incorrect types of coordinates into +a path will now raise a more specific `ValueError`, with the message "incorrect +coordinate type". + API Additions ============= From 8b6ee688d806e0ec7f3342582c41d8635ccf0a15 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Feb 2022 13:56:27 +1100 Subject: [PATCH 369/633] Added release notes for #5959 --- docs/releasenotes/9.1.0.rst | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst index d0a8758bb..cbf9fe6e1 100644 --- a/docs/releasenotes/9.1.0.rst +++ b/docs/releasenotes/9.1.0.rst @@ -5,24 +5,37 @@ API Changes =========== Raise an error when performing a negative crop ----------------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Performing a negative crop on an image previously just returned a `(0, 0)` image. Now -it will raise a `ValueError`, to help reduce confusion if a user has unintentionally +Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now +it will raise a ``ValueError``, to help reduce confusion if a user has unintentionally provided the wrong arguments. Added specific error if path coordinate type is incorrect ---------------------------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Rather than returning a `SystemError`, passing the incorrect types of coordinates into -a path will now raise a more specific `ValueError`, with the message "incorrect +Rather than returning a ``SystemError``, passing the incorrect types of coordinates into +a path will now raise a more specific ``ValueError``, with the message "incorrect coordinate type". +Deprecations +^^^^^^^^^^^^ + +ImageShow.Viewer.show_file file argument +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``file`` argument in :py:meth:`~PIL.ImageShow.Viewer.show_file()` has been +deprecated, replaced by ``path``. + +In effect, ``viewer.show_file("test.jpg")`` will continue to work unchanged. +``viewer.show_file(file="test.jpg")`` will raise a deprecation warning, and suggest +``viewer.show_file(path="test.jpg")`` instead. + API Additions ============= Added get_photoshop_blocks() to parse Photoshop TIFF tag --------------------------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :py:meth:`~PIL.TiffImagePlugin.TiffImageFile.get_photoshop_blocks` has been added, to allow users to determine what Photoshop "Image Resource Blocks" are contained within an @@ -36,8 +49,8 @@ Other Changes ============= Image._repr_pretty_ -------------------- +^^^^^^^^^^^^^^^^^^^ -`im._repr_pretty_` has been added to provide a representation of an image without the +``im._repr_pretty_`` has been added to provide a representation of an image without the identity of the object. This allows Jupyter to describe an image and have that description stay the same on subsequent executions of the same code. From 164632650696429818fe9ff087b1189d38875b7b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 11 Feb 2022 08:23:04 +1100 Subject: [PATCH 370/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 678501122..12a303613 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 9.1.0 (unreleased) ------------------ +- Added get_photoshop_blocks() to parse Photoshop TIFF tag #6030 + [radarhere] + +- Drop excess values in BITSPERSAMPLE #6041 + [mikhail-iurkov] + - Added unpacker from RGBA;15 to RGB #6031 [radarhere] From 70a17080e49d5accad8ec5e665f07ea76f21074b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 12 Feb 2022 07:49:20 +1100 Subject: [PATCH 371/633] Updated return values to match docstring --- src/PIL/ImageShow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index f8829fc21..c6829ad4b 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -54,8 +54,8 @@ def show(image, title=None, **options): """ for viewer in _viewers: if viewer.show(image, title=title, **options): - return 1 - return 0 + return True + return False class Viewer: From 7f8df9d7125d4307421d066b1f44d5cc9ea6ab01 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 9 Feb 2022 16:21:47 +1100 Subject: [PATCH 372/633] Use "title" argument for display --- src/PIL/ImageShow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index c6829ad4b..eafbff875 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -248,7 +248,7 @@ class DisplayViewer(UnixViewer): def get_command_ex(self, file, title=None, **options): command = executable = "display" if title: - command += f" -name {quote(title)}" + command += f" -title {quote(title)}" return command, executable def show_file(self, path=None, **options): @@ -270,7 +270,7 @@ class DisplayViewer(UnixViewer): raise TypeError("Missing required argument: 'path'") args = ["display"] if "title" in options: - args += ["-name", options["title"]] + args += ["-title", options["title"]] args.append(path) subprocess.Popen(args) From e19447cbbbe8fe7832e2826f399c4aa77815e94c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 12 Feb 2022 08:04:40 +1100 Subject: [PATCH 373/633] Do not manually remove temporary files on Unix --- src/PIL/ImageShow.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index eafbff875..964ef57ea 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -126,16 +126,6 @@ class Viewer: os.system(self.get_command(path, **options)) return 1 - def _remove_path_after_delay(self, path): - subprocess.Popen( - [ - sys.executable, - "-c", - "import os, sys, time; time.sleep(20); os.remove(sys.argv[1])", - path, - ] - ) - # -------------------------------------------------------------------- @@ -190,7 +180,14 @@ class MacViewer(Viewer): else: raise TypeError("Missing required argument: 'path'") subprocess.call(["open", "-a", "Preview.app", path]) - self._remove_path_after_delay(path) + subprocess.Popen( + [ + sys.executable, + "-c", + "import os, sys, time; time.sleep(20); os.remove(sys.argv[1])", + path, + ] + ) return 1 @@ -235,7 +232,6 @@ class XDGViewer(UnixViewer): else: raise TypeError("Missing required argument: 'path'") subprocess.Popen(["xdg-open", path]) - self._remove_path_after_delay(path) return 1 @@ -274,7 +270,6 @@ class DisplayViewer(UnixViewer): args.append(path) subprocess.Popen(args) - os.remove(path) return 1 @@ -304,7 +299,6 @@ class GmDisplayViewer(UnixViewer): else: raise TypeError("Missing required argument: 'path'") subprocess.Popen(["gm", "display", path]) - os.remove(path) return 1 @@ -334,7 +328,6 @@ class EogViewer(UnixViewer): else: raise TypeError("Missing required argument: 'path'") subprocess.Popen(["eog", "-n", path]) - os.remove(path) return 1 @@ -375,7 +368,6 @@ class XVViewer(UnixViewer): args.append(path) subprocess.Popen(args) - os.remove(path) return 1 From 3ba9587675a80dcff958fa1c60de84f6b274b815 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 12 Feb 2022 09:07:17 +1100 Subject: [PATCH 374/633] Added test --- Tests/test_imageshow.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index bf19a6033..55d7c9479 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -51,6 +51,16 @@ def test_show(): assert ImageShow.show(im) +def test_show_without_viewers(): + viewers = ImageShow._viewers + ImageShow._viewers = [] + + im = hopper() + assert not ImageShow.show(im) + + ImageShow._viewers = viewers + + def test_viewer(): viewer = ImageShow.Viewer() From b818ad6103bed6d5231bf48a0f265c47bf99d2d1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 13 Feb 2022 21:58:46 +1100 Subject: [PATCH 375/633] Updated harfbuzz to 3.4.0 --- winbuild/build_prepare.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 71aed705a..ade620347 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -280,9 +280,9 @@ deps = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/3.3.2.zip", - "filename": "harfbuzz-3.3.2.zip", - "dir": "harfbuzz-3.3.2", + "url": "https://github.com/harfbuzz/harfbuzz/archive/3.4.0.zip", + "filename": "harfbuzz-3.4.0.zip", + "dir": "harfbuzz-3.4.0", "build": [ cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), cmd_nmake(target="clean"), From 993bb23ce0e9cd1ee4a2e6f05a1225ff0df20e4f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 14 Feb 2022 09:33:58 +1100 Subject: [PATCH 376/633] Do not manually remove temporary files on Unix in get_command() --- src/PIL/ImageShow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index 964ef57ea..f79206921 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -201,7 +201,7 @@ class UnixViewer(Viewer): def get_command(self, file, **options): command = self.get_command_ex(file, **options)[0] - return f"({command} {quote(file)}; rm -f {quote(file)})&" + return f"({command} {quote(file)}" class XDGViewer(UnixViewer): From 45534d130b2727d59681ae0dbb6ee60d484043d7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 14 Feb 2022 12:12:33 +1100 Subject: [PATCH 377/633] Only skip test if libimagequant is earlier than 4 on ppc64le --- Tests/test_image_quantize.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 53b6c9007..16cb8b41a 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -1,8 +1,9 @@ import pytest +from packaging.version import parse as parse_version -from PIL import Image +from PIL import Image, features -from .helper import assert_image_similar, hopper, is_ppc64le +from .helper import assert_image_similar, hopper, is_ppc64le, skip_unless_feature def test_sanity(): @@ -17,16 +18,14 @@ def test_sanity(): assert_image_similar(converted.convert("RGB"), image, 60) -@pytest.mark.xfail(is_ppc64le(), reason="failing on ppc64le on GHA") +@skip_unless_feature("libimagequant") def test_libimagequant_quantize(): image = hopper() - try: - converted = image.quantize(100, Image.LIBIMAGEQUANT) - except ValueError as ex: # pragma: no cover - if "dependency" in str(ex).lower(): - pytest.skip("libimagequant support not available") - else: - raise + if is_ppc64le(): + libimagequant = parse_version(features.version_feature("libimagequant")) + if libimagequant < parse_version("4"): + pytest.skip("Fails with libimagequant earlier than 4.0.0 on ppc64le") + converted = image.quantize(100, Image.LIBIMAGEQUANT) assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 15) assert len(converted.getcolors()) == 100 From 83d4f451fa0f38aea058084b38d0f32b74f93da3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 14 Feb 2022 16:10:59 +1100 Subject: [PATCH 378/633] Ensure image is opaque after converting P to PA with RGB palette --- Tests/test_image_convert.py | 7 ++++ src/libImaging/Convert.c | 82 ++++++++++++++++++------------------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index a5a95e962..4afb80a22 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -76,6 +76,13 @@ def test_16bit_workaround(): _test_float_conversion(im.convert("I")) +def test_opaque(): + alpha = hopper("P").convert("PA").getchannel("A") + + solid = Image.new("L", (128, 128), 255) + assert_image_equal(alpha, solid) + + def test_rgba_p(): im = hopper("RGBA") im.putalpha(hopper("L")) diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 517a4dbe3..c899a996c 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -991,115 +991,115 @@ static struct { /* ------------------- */ static void -p2bit(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; /* FIXME: precalculate greyscale palette? */ for (x = 0; x < xsize; x++) { - *out++ = (L(&palette[in[x] * 4]) >= 128000) ? 255 : 0; + *out++ = (L(&palette->palette[in[x] * 4]) >= 128000) ? 255 : 0; } } static void -pa2bit(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +pa2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; /* FIXME: precalculate greyscale palette? */ for (x = 0; x < xsize; x++, in += 4) { - *out++ = (L(&palette[in[0] * 4]) >= 128000) ? 255 : 0; + *out++ = (L(&palette->palette[in[0] * 4]) >= 128000) ? 255 : 0; } } static void -p2l(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +p2l(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; /* FIXME: precalculate greyscale palette? */ for (x = 0; x < xsize; x++) { - *out++ = L24(&palette[in[x] * 4]) >> 16; + *out++ = L24(&palette->palette[in[x] * 4]) >> 16; } } static void -pa2l(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +pa2l(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; /* FIXME: precalculate greyscale palette? */ for (x = 0; x < xsize; x++, in += 4) { - *out++ = L24(&palette[in[0] * 4]) >> 16; + *out++ = L24(&palette->palette[in[0] * 4]) >> 16; } } static void -p2pa(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; for (x = 0; x < xsize; x++, in++) { - const UINT8 *rgba = &palette[in[0]]; + const UINT8 *rgba = &palette->palette[in[0]]; *out++ = in[0]; *out++ = in[0]; *out++ = in[0]; - *out++ = rgba[3]; + *out++ = strcmp(palette->mode, "RGB") == 0 ? 255 : rgba[3]; } } static void -p2la(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +p2la(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; /* FIXME: precalculate greyscale palette? */ for (x = 0; x < xsize; x++, out += 4) { - const UINT8 *rgba = &palette[*in++ * 4]; + const UINT8 *rgba = &palette->palette[*in++ * 4]; out[0] = out[1] = out[2] = L24(rgba) >> 16; out[3] = rgba[3]; } } static void -pa2la(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +pa2la(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; /* FIXME: precalculate greyscale palette? */ for (x = 0; x < xsize; x++, in += 4, out += 4) { - out[0] = out[1] = out[2] = L24(&palette[in[0] * 4]) >> 16; + out[0] = out[1] = out[2] = L24(&palette->palette[in[0] * 4]) >> 16; out[3] = in[3]; } } static void -p2i(UINT8 *out_, const UINT8 *in, int xsize, const UINT8 *palette) { +p2i(UINT8 *out_, const UINT8 *in, int xsize, ImagingPalette palette) { int x; for (x = 0; x < xsize; x++, out_ += 4) { - INT32 v = L24(&palette[in[x] * 4]) >> 16; + INT32 v = L24(&palette->palette[in[x] * 4]) >> 16; memcpy(out_, &v, sizeof(v)); } } static void -pa2i(UINT8 *out_, const UINT8 *in, int xsize, const UINT8 *palette) { +pa2i(UINT8 *out_, const UINT8 *in, int xsize, ImagingPalette palette) { int x; INT32 *out = (INT32 *)out_; for (x = 0; x < xsize; x++, in += 4) { - *out++ = L24(&palette[in[0] * 4]) >> 16; + *out++ = L24(&palette->palette[in[0] * 4]) >> 16; } } static void -p2f(UINT8 *out_, const UINT8 *in, int xsize, const UINT8 *palette) { +p2f(UINT8 *out_, const UINT8 *in, int xsize, ImagingPalette palette) { int x; for (x = 0; x < xsize; x++, out_ += 4) { - FLOAT32 v = L(&palette[in[x] * 4]) / 1000.0F; + FLOAT32 v = L(&palette->palette[in[x] * 4]) / 1000.0F; memcpy(out_, &v, sizeof(v)); } } static void -pa2f(UINT8 *out_, const UINT8 *in, int xsize, const UINT8 *palette) { +pa2f(UINT8 *out_, const UINT8 *in, int xsize, ImagingPalette palette) { int x; FLOAT32 *out = (FLOAT32 *)out_; for (x = 0; x < xsize; x++, in += 4) { - *out++ = (float)L(&palette[in[0] * 4]) / 1000.0F; + *out++ = (float)L(&palette->palette[in[0] * 4]) / 1000.0F; } } static void -p2rgb(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +p2rgb(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; for (x = 0; x < xsize; x++) { - const UINT8 *rgb = &palette[*in++ * 4]; + const UINT8 *rgb = &palette->palette[*in++ * 4]; *out++ = rgb[0]; *out++ = rgb[1]; *out++ = rgb[2]; @@ -1108,10 +1108,10 @@ p2rgb(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { } static void -pa2rgb(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +pa2rgb(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; for (x = 0; x < xsize; x++, in += 4) { - const UINT8 *rgb = &palette[in[0] * 4]; + const UINT8 *rgb = &palette->palette[in[0] * 4]; *out++ = rgb[0]; *out++ = rgb[1]; *out++ = rgb[2]; @@ -1120,30 +1120,30 @@ pa2rgb(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { } static void -p2hsv(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +p2hsv(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; for (x = 0; x < xsize; x++, out += 4) { - const UINT8 *rgb = &palette[*in++ * 4]; + const UINT8 *rgb = &palette->palette[*in++ * 4]; rgb2hsv_row(out, rgb); out[3] = 255; } } static void -pa2hsv(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +pa2hsv(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; for (x = 0; x < xsize; x++, in += 4, out += 4) { - const UINT8 *rgb = &palette[in[0] * 4]; + const UINT8 *rgb = &palette->palette[in[0] * 4]; rgb2hsv_row(out, rgb); out[3] = 255; } } static void -p2rgba(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +p2rgba(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; for (x = 0; x < xsize; x++) { - const UINT8 *rgba = &palette[*in++ * 4]; + const UINT8 *rgba = &palette->palette[*in++ * 4]; *out++ = rgba[0]; *out++ = rgba[1]; *out++ = rgba[2]; @@ -1152,10 +1152,10 @@ p2rgba(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { } static void -pa2rgba(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +pa2rgba(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; for (x = 0; x < xsize; x++, in += 4) { - const UINT8 *rgb = &palette[in[0] * 4]; + const UINT8 *rgb = &palette->palette[in[0] * 4]; *out++ = rgb[0]; *out++ = rgb[1]; *out++ = rgb[2]; @@ -1164,25 +1164,25 @@ pa2rgba(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { } static void -p2cmyk(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +p2cmyk(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { p2rgb(out, in, xsize, palette); rgb2cmyk(out, out, xsize); } static void -pa2cmyk(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +pa2cmyk(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { pa2rgb(out, in, xsize, palette); rgb2cmyk(out, out, xsize); } static void -p2ycbcr(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +p2ycbcr(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { p2rgb(out, in, xsize, palette); ImagingConvertRGB2YCbCr(out, out, xsize); } static void -pa2ycbcr(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { +pa2ycbcr(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { pa2rgb(out, in, xsize, palette); ImagingConvertRGB2YCbCr(out, out, xsize); } @@ -1192,7 +1192,7 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) { ImagingSectionCookie cookie; int alpha; int y; - void (*convert)(UINT8 *, const UINT8 *, int, const UINT8 *); + void (*convert)(UINT8 *, const UINT8 *, int, ImagingPalette); /* Map palette image to L, RGB, RGBA, or CMYK */ @@ -1241,7 +1241,7 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) { (UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize, - imIn->palette->palette); + imIn->palette); } ImagingSectionLeave(&cookie); From 5411263d9276cb8af4a5ea6ab731f7c8cc1f037f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 14 Feb 2022 19:24:47 +1100 Subject: [PATCH 379/633] Simplified code --- src/libImaging/Convert.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 517a4dbe3..2cec2a578 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1216,9 +1216,7 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) { convert = alpha ? pa2f : p2f; } else if (strcmp(mode, "RGB") == 0) { convert = alpha ? pa2rgb : p2rgb; - } else if (strcmp(mode, "RGBA") == 0) { - convert = alpha ? pa2rgba : p2rgba; - } else if (strcmp(mode, "RGBX") == 0) { + } else if (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBX") == 0) { convert = alpha ? pa2rgba : p2rgba; } else if (strcmp(mode, "CMYK") == 0) { convert = alpha ? pa2cmyk : p2cmyk; From 9cdb0508b6cbd3a3061017760a5eab4d13c3924a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 14 Feb 2022 20:28:47 +1100 Subject: [PATCH 380/633] Attach RGBA palettes from putpalette() when suitable --- Tests/test_image_putpalette.py | 13 +++++++++++++ src/PIL/Image.py | 17 +++++++---------- src/_imaging.c | 7 ++++--- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index 012a57a09..725ecaade 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -62,3 +62,16 @@ def test_putpalette_with_alpha_values(): im.putpalette(palette_with_alpha_values, "RGBA") assert_image_equal(im.convert("RGBA"), expected) + + +@pytest.mark.parametrize( + "mode, palette", + ( + ("RGBA", (1, 2, 3, 4)), + ("RGBAX", (1, 2, 3, 4, 0)), + ), +) +def test_rgba_palette(mode, palette): + im = Image.new("P", (1, 1)) + im.putpalette(palette, mode) + assert im.palette.colors == {(1, 2, 3, 4): 0} diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 02b71e612..8d36e8871 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -821,12 +821,6 @@ class Image: if self.im and self.palette and self.palette.dirty: # realize palette mode, arr = self.palette.getdata() - if mode == "RGBA": - mode = "RGB" - self.info["transparency"] = arr[3::4] - arr = bytes( - value for (index, value) in enumerate(arr) if index % 4 != 3 - ) palette_length = self.im.putpalette(mode, arr) self.palette.dirty = 0 self.palette.rawmode = None @@ -837,8 +831,11 @@ class Image: self.im.putpalettealphas(self.info["transparency"]) self.palette.mode = "RGBA" else: - self.palette.mode = "RGB" - self.palette.palette = self.im.getpalette()[: palette_length * 3] + palette_mode = "RGBA" if mode.startswith("RGBA") else "RGB" + self.palette.mode = palette_mode + self.palette.palette = self.im.getpalette(palette_mode, palette_mode)[ + : palette_length * len(palette_mode) + ] if self.im: if cffi and USE_CFFI_ACCESS: @@ -1761,8 +1758,8 @@ class Image: Alternatively, an 8-bit string may be used instead of an integer sequence. :param data: A palette sequence (either a list or a string). - :param rawmode: The raw mode of the palette. Either "RGB", "RGBA", or a - mode that can be transformed to "RGB" (e.g. "R", "BGR;15", "RGBA;L"). + :param rawmode: The raw mode of the palette. Either "RGB", "RGBA", or a mode + that can be transformed to "RGB" or "RGBA" (e.g. "R", "BGR;15", "RGBA;L"). """ from . import ImagePalette diff --git a/src/_imaging.c b/src/_imaging.c index 2a42c0461..2ea517816 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -1641,7 +1641,7 @@ _putpalette(ImagingObject *self, PyObject *args) { ImagingShuffler unpack; int bits; - char *rawmode; + char *rawmode, *palette_mode; UINT8 *palette; Py_ssize_t palettesize; if (!PyArg_ParseTuple(args, "sy#", &rawmode, &palette, &palettesize)) { @@ -1654,7 +1654,8 @@ _putpalette(ImagingObject *self, PyObject *args) { return NULL; } - unpack = ImagingFindUnpacker("RGB", rawmode, &bits); + palette_mode = strncmp("RGBA", rawmode, 4) == 0 ? "RGBA" : "RGB"; + unpack = ImagingFindUnpacker(palette_mode, rawmode, &bits); if (!unpack) { PyErr_SetString(PyExc_ValueError, wrong_raw_mode); return NULL; @@ -1669,7 +1670,7 @@ _putpalette(ImagingObject *self, PyObject *args) { strcpy(self->image->mode, strlen(self->image->mode) == 2 ? "PA" : "P"); - self->image->palette = ImagingPaletteNew("RGB"); + self->image->palette = ImagingPaletteNew(palette_mode); unpack(self->image->palette->palette, palette, palettesize * 8 / bits); From 41a997537791c76463058da1ac6632fcf5a9f8bc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 14 Feb 2022 21:50:19 +1100 Subject: [PATCH 381/633] Moved strcmp outside of loop --- src/libImaging/Convert.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index c899a996c..57c66d2cc 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1029,12 +1029,13 @@ pa2l(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { static void p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) { int x; + int rgb = strcmp(palette->mode, "RGB"); for (x = 0; x < xsize; x++, in++) { const UINT8 *rgba = &palette->palette[in[0]]; *out++ = in[0]; *out++ = in[0]; *out++ = in[0]; - *out++ = strcmp(palette->mode, "RGB") == 0 ? 255 : rgba[3]; + *out++ = rgb == 0 ? 255 : rgba[3]; } } From dfdb17671d5579f5ca475e02bbb08e6f6f274695 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 15 Feb 2022 11:22:46 +1100 Subject: [PATCH 382/633] Added FITS reading --- Tests/images/hopper.fits | Bin 54720 -> 20160 bytes ...est_file_fitsstub.py => test_file_fits.py} | 32 +++---------- docs/handbook/image-file-formats.rst | 15 ++---- docs/reference/plugins.rst | 4 +- ...sStubImagePlugin.py => FitsImagePlugin.py} | 45 ++++-------------- src/PIL/__init__.py | 2 +- 6 files changed, 23 insertions(+), 75 deletions(-) rename Tests/{test_file_fitsstub.py => test_file_fits.py} (51%) rename src/PIL/{FitsStubImagePlugin.py => FitsImagePlugin.py} (63%) diff --git a/Tests/images/hopper.fits b/Tests/images/hopper.fits index 85afa4ac1671b9ce2b33fe50d148df9dc4604a46..7f28f75e5a892f1200baf2af6da5053f8d764457 100644 GIT binary patch literal 20160 zcmeHuXLwZSweGzqi9u8M-qZH<8I2kU0YY>PrW%9ky%+V~d+*X{G}1_;-g_5FAbKx0 z7=z;yJF%UFxZybN9SrE+_uC_!+~@Hm#(*b>xyOnr$4->&*Ws4=ggV^#<*YjdikDJTQ=hJf9f~$zjeKp%eF0B zziijPTyNI&Idh1D(C@$C5AJ$vHvG%={(Jg8w`S|Mjazmbg5%?(;}*`C5fX7YC;&ep zeqqz2VuOBxq5u4&RU20R=fCjlx4$y*D+9kW@GAqqGVm({|KBmtU$vN`1R{|@C=?2W zA~F7p#S{cjL>MP#5+jre#6kg|k6(a@DVmYU`9hf!C6UNvG6gG_P+~?VlgboIR>rc7 zmStrMg-Ioqs^l7lNT!g`5{XpeL{n1yEki>j0;xzUT-VZ5yGl%ngd$v=PXHt$r%61a zSWJLKz~uc#LQ5y{84=Wj5Cxzf&4{HGBch;}n3l*Dz>`WCsZ1_cD>MeRLdvR*YANfi zr{prZP9~A6Xqk$V2!sNHP5z#tq#_CB+t}H(RVb$T4*MOKrzH%<83|1(2_<)O5<@Xg z1i<5jBBt;Ze9%Nuj8vhNNx+FhCY38BDpsM=s1-_`mQ|>g3f4f&W$j60+JSRT!1La~170w=jxBqq+3E7hz_A~!8qZ#_9s zw`Jqz_>L=n)lIMOjq0_A?>V@2{nlOE_vg9Ollfv$Pc*=zf|fMb^>$fgKo>uWKcI5Z zfD!U}0ud#a8eF{QiXGZfoVg@CCF(7UmN*MITO?pzA(zV}?39HWM@O4BY}jn>yzspJ z#78Uk4%Nl&+PP!P=4~6c6d7otR7^1v4xeHajJBa+ptDIYppgJDm4p^gLN_9j(j(2DP4jIMX@@E$v6iN|xG$WNF{A8?*mMDF82Knwz+_^uo z!IGR&6Bbh(vD1IYq3zFa+Povh&52LLeg{4+Wu((<8izW1+{Fw$jq4Ns={W~tqM|a& z%Bvb$J9~#mdk4C@I$IizgnZ%=4jsOu#*W5rD4!~pGO!47uaQgStW3r#W^4#P`}IhO z+5c2Vd17DqndFesvaEyqcP3W1Z)Zd_aXoop7@0`+w7sFXw`(OW7JzPq9|^#9IY-`m zr#iK$p{c!VcyR3a(V@Pf?s{k9doJ%7kxD^nlxwb^Z!EDi(Q*+|S}B#XDy2e>D5RC1 zTY7#N3AY3e$L0I?_??LizgnEL>+t5Hp5`4C584tRk||qX%rx09Kn|_ z8v4WUuimXG9lY?;&6lp+c=7W26PN0?Oh%G&_!6j&bfXJzymPv!>FP$%E@kC%xkBl{ zS4tSo{^pP<->u2MfxF^2XIuAW1@GIiYOQ}n$Wls35*rF2{%8d)JzP`Y*U=RxrT|RP zA(K4&&%O1*%N@3!k<(+Rj-NhtWZ-DSMhRDSkn6NWC}lvl=kcq5d$&Kg)GU`NP+5oq ztO7wOW8`{9Dwoyo*tu)ho;`c_`;JVNG6FerOvFl^#9~76gehWKQdRXpdxwQfb$mf| z;2Fwac<}J`G<#ibd+*6pXOAE6A3V%Fg};P{$e#)sC99D5pLqBC&)#eZOZAqr3MKGm zqzD3EFA+<16l2}Cd+XLcyLbBS9@QZ@!I^|lQ#=7FWaKhpFd<;gmG8 ztXw7&YoL!@wQk|El`B`SSibVP7=TN7QV`700x``6I?(_%)>Knfd!(zaMai3pJ^)M> z<&~7CM(x}7+?**=M#z&;67gi26Hyt_FBA!sd|IkofBNlj9z1^hn!3T>&Q=dTg}Ocwe@+4wpO2D2 zQF0y}#h)w?$rZn45q$Us6bYEa8ndteb^P(8$K&7ru_e|dRj3gDETSL7p@LCpRSLCA zE~P}1#TaoQKUAwYcgH7DXfwS60_n_w{wR`Up^fC-C_^$?sT-2VDh{$s!SN zvK+1vG01z)|CCgqVwLjxD<3|3Fg`y1@WC4wmZ&utb2LhYLWZPfDWy!Rlu8iYe1!;w zLL?PSp%?}Vm_X{OV`xRmV>VaS*^hR0H!X#3Tu^Wf6RL@sgl_?#xSR_Ne2m|r7@kb6 zre;*$`Q-8V{l|~SzqxZ%rqa?X1*;_Pm#b7t7J>&Eu0S!QETck7xT1$9f?SK#axhE# zRh3tb_H{R{bGQVRpM1+HBa+Za6|O8!;Nx@dJ1HuTl$u)g%Kb+m;HU9VUwd9AM+HFs z5qu;eCx}TYY^4wkDEAYC4#pJD7J>&>fg1-`jR8~Q=8d=~i5&)0JAARxOQU+B~A_X+T zhadsCbVOSQe9(X)ic7Kro}6}F1e!H$MZ=;^Rez~}G?7JP)y{)x{8C;|Xb z=%s|T=ICF>$M28-^x(6<#4E+5uRt#d24oW0iIj&^ScP=L@o3FB1t>A0fcRA;aGGCU zS!N#_=x)f7a$!aS?q__5I|%;Il#ss?3mG{>u}VaF@a?Y<0goPi{nx!J466hm=)fWU z6`_Mg41fyggNm#mVF+-}2e>4k&)a3GD62Zz*WZ|};&Hy6;2M}<3BLnu5(5(@i)e&^ zrj;~b=p;e?%pSP$$2)Jlbh=qfOVLh|E`;DqFt(y0KwnH^z=4l$82BUzNc%)^gaUbL zX?aEEiSB{MOuYaj5TKy=gt?po4t%cjArV5{4{kML3N?_G$Y@p|_t2T90aA+o7u`4T z9Z|@Q1>D0EeDsHKii9C?LWy?FFk!Ay~a{TFF~WH z^e1Wop@pJVgI|36{rH3N2gMuL_?UDU=_vO*4^W2_6qpAxARNMJ;7CP>^D!Pz5P%Mn ztH|4mD~qkAX4|zAN1_#VHCFqYCMkkc%%F4>DmJ)MG5Q?Wr#1wflloS2{ zeJ&9$1UP)eA1MMNWG){qG?V(lSK|o4@5=KVtL=p)xdqi9-T#sF0S^Ym0KiCy+{N=m z8jzVG*PIf^pgR|9C@ty4pM8Qqm1bmUh3fW;o>J00hz}mI&-2mA*&$xdFTB3SmLIV*ZQpit z+O`iLJ^%sVWg&DNwUeYW%?*cOo>U{~f^sO6Vm(5SD3Co}Wwly4e5o84rs(c78-?s- z1lm9C=X~%pKKUH<^uG7thw-0Y^sDHwq{kjy?!SNAo}7g3?|<{?@%_(lNO&Yj;V(iK z=Gnmh8D2%Q5bdD`sUVw`S;66#l$0T`@whr`1n1Am@J`@bcF#l7kek3#*(s*{Mu@)8C{K5%(Z2Ww=~J@0<^zfAWvN|6>Jk0ng(>0hyeUC|Me#lSHAyxTsdKQ?_K*Rod(R16% z%dF){KcWBzWr~vOx&|%`^q;;kxOVO=Pm@|I;$iKH7>?8w^PNPL6D~Z-Rwco-k?-V$d4iZ>_y{bD5e$5K zc4k(iB`q~8CHl~T9ou$Cga-!3Bu2+)m#5$P{O^C0FtD7%$5ccul=&6zi6rl-4c<~%Pog`r6#7EA^K z6sB%8@5*P-tZi(_%}Y#*3EaMW!L=9P4Ra`(dp=mtke6)?L*Q9Dv zPLxI>UYuTCVXLmI0rMchUQt;o5c_4Uibvz(+Q@;@xlb?f z_L;k2!R#4RJ*Ihk&6q#m65V#OJ8+o?hG-Fzkb3Q}D@q$m@}q-8!h^O0eu~M}bKa_e z=mUomZMh96K1jsxk>OJ;=6M__YpN)#wcE-n;e9+f;2qva>raXg@G(x(LaabbHH+3R z@L4!_hR^&3bAazL+iRisg6fE#D@OtjZqHmVp#(g7(yiC#wbz;pV)ujv1+87+tXAq& zrs+#|hKB45wHDi}UrWa-4;I-3>ebnn>Kc2cy$bQhNfpEnrz7+^A+{625W!#~fq9k6 z;Osqr+6ifKRQC%*L5KI}?@ykEtpU-+v1ht#iZTNC@87Y}+e3$1 zAyzt1e=Z>G(1FAPdqt0BvJ5Q!ZgyU!)m~c-JnIvDQg+~T4xiLM2Rgw=`^*(fDJA!q zw_ujfoVl~Tr+ZGDIooqiU~_ugwa%DOzoNM8^gxx6*Liezb9q_v>Xpwty}+Qv&IhZ+ zg3q+)cJ6#WtkTljmcU>kN3yNL-eiZl2)ME*_>Qu};S+Gq_e2LYYFH*f+lQr7MrN8n zd-mM9b7oDSHqB?2&#cTkTm8kF^n}2?+>-dxbQv$7RK4@I-)_6bYFEx7ZqmZSJsqSN;85s zE}ye9t2={UkZQHsDr%A2<;XFT_6{LMf5Q75zGXrOFVT`>jm@^@#U{6de)4R{~##yYLO+AAnJ$>DE zqg_eqS=pI6IVp!0>@er%*lL^F`+8d&2d`c`f9cYhi|0=l@JZ66-ZRSyl=} z#D962ZNl*t=r>?GiF-u9*<5VK4_J_@8I}?A*obLLnFij6213T&d&;zJJ;z6m9v|rK zsvYgi%t*H6+NKa6+oNqqnW8w7t2ttRibyKx%1auA|Hl z!YAU53pm2}6MXod^Z!QS-guROtv-Zp1u-keL=pSF&R(8N?W4y=jvOE9?`b~W6%n5k zzszWy>b@brym73*rKqF2qQ1Va`^53?zP65r($d=go|4|v&CLzv2Y01Zbir;%kt0Jq znk)!H4PjTp=|L>Agdu{jVVBfK0-9yKQ+G}dtONPddw)E8}Z3!zo(pmmru8ePYIMmm4 z@k(pxX21COr`hcV&8@@j84+c+=IY|C!fbQTk*4w-tIbl;R@o3%)!W}!U$kyR_*iFI zVM%c@aWjGD@JZSc1supQe**vIt{@Gq;!~dMOe!Ov?BQaimhy~lX+LthzwhkHk)FnD zmsv%)QFqgufi7(@z{0uI1-CiYAOlymJ-RNv9Tq2BsS zr@FI>PF*{DW2CgYt-Ic4IkdF0wX1iyyZ^}1u93Ee#?Hph+U;IDLaj9wb=5tWjtmdl zi?OsrLf=Y!&e`u!!0f;$k6bk6oRorMtSeF?*+fb60C$OJ93MR$-I9vD(s{;=Lj;Hm|a^v#D+DY}<(; z5@!T>0-q@1z~l_)UJ`GVTD5YCt6a|fR)%p<&8^pvHEWqnD3c%V>>3#z9f+OCU(qiv(robO5S zahYFA>XAbMhu=x)gBq)q(y>wxM!=}}II*IXsFZpo2*8mDl)^PNM~)pCs=9K{Ts>G* z<(pMhP|<9wYHRPS4cO3>vE=FH>-X&0w&CE~UDbYGftH%`)|#fC#zt#ac42q@ll*&v zkI|PKc8C)22hfT2Wo1~XR;r|AqfV(Jdv`J=*{Z>k9F7=Ji`$M4kJ>KZD6Z;0Gtga< zm2a~ZH?;PToo+t3A$ardgCa%_>QCfCr)*>G?&@U4FdxiO|>OOW=pY!%<_np zcqqZ}&(#12GEsxX0QgsF6=E@muU2a`I&7TcbPq*gJzFg_<@Yq7c(He|vF+?&c2Yq> zWoN#*{p^LN!VtfXu?x3eyY>5*U%YiPJ31jCsGxV`ShpDqOC2XGlN(CST|fJA)YpIf!r5MXNl|6bnbr(zg~eQg3S7wH6Hmif zG6(~bWIoC>se@)SHtE!QRxDJI^HNHM5~sIVDNbFHW?0RaDxxe4n=a=!H5^Q_Wfs@g z76qi8e)F|Iy#3}Mu4kQj@y?w$U%!3lboR)#>$hJz**DbRTvuOc=@^O0D62xHFDxm0 zQg%xwe9zTEf?dRo0L8_?*BF#ypc3A}qnpR;2F}aNIogCOzdH7IX zL2Z3qYFb6#`J1o3_4^yI*K}OI_R5>D-+HZW_}a@iZ{E6e{KSdA=C=CuwxZ2ZZT9jq zOMZTF5lK42I+u^caP*! zpgkkIxTK%}{mukF>MCunI&RrbETDodd`DX=KN$)1-~^1_ATl$PtSymI|^`QX^?3k`Mo*6f1P;l7%> z+RDB+so@Jk6iBe9lY_{t-gVi^`%GqPF^|H zW$Es3Y-w)EORQVvq-tm}2KGrMtF=Mnr{Xhi%=o_St1?)&&FvoZJ%3SvU|!s1f-5d;k+6-{5UtQnbowP>IA-Z{B<5 z-mU3kg;A%}Jd+gS#*?!=ie+M~&I=lflj*Yh0 z6qFX{hn5vZF5Ea%<5&+W6eY)lYj&-I7`}+>&K3%*`|B=H})Q z$s=%id3c*k9&)mCa_}lY|M+LGyz^lH-v8Sl54y?K8nuC1=i^=8saNo^yP1|^ zkeUJu!*?v#&0g%IPrNu>mY!KtduYqj`0C47&y01Kg{O>~6MWn~7q3rAX?khN%ss1D z?Ta{AkXf9UQ;?OF4atH4jzfnkoJtO@a&tfa?%NOEoNr+K-}&m(Kc4r*CW%&~lu<(C zLN9b-QYtG;DNrqniw)SfdCzk*XKB@Xw<)fs=}+&?>brjZ%I)93c%mlpxo1OaQVXm# zsnvfE*UX$cd(DnzOX4dE^YXG%^RlxtGqbWXGr%~}BolUX0>!xJ_y6(U$9r6b)TR$U z4?X%pfljT_sufrflbAGEmXgW}5~R!|=fLR0A&FJZ71h-h*%AK!VaWxlRsEw!PQG;O z+GurQa==p8MOy>+@7v@#EoQ&h>Rkt>P2F3Vm6Do~k`CxhNCw9O!FLb>oxlyK`0}Io z|9VL8!awwWldB=1VWwKEQ_HY>B~fDiUL=(#XMqIWnvjr~3|nh+eN$6OVq|1=Tte;8 ziP7ORH?N#O)@jcV*tvi6l2uC=EPH0{`U403W4BGwuPw;Knn_MZMn*bOAR{v)gJ1%m zu#ujQk1{f|bMyZ8)t4Wfn<9~IcxR5Qn)WoQGw zs;<7izGj=*Tvp%Nd*;gJ^Ve=)xj5R@)RcJ8_dr;7%}DpLs>+u7_~iA|lnZlml2WoV zQvn49h#CYlGXv;=PfO!I2LqqHA8_mA!y57GBvyw8NXx4AI?|4)^;(@$rjle9iKzde zr!3vDE4{qDskgVOuCk<{sG{-YjdNkuxXTnu>CZQi|I;PIWXiHPxDnl2^-x zGt<)&lao_Y)6>(^ITG*;NyBUM78*b=$UkcJ-sVwfzQ&~0s+=(bY4m!uBNEo2#gSrG zl2#~?2*u8;w{8e1Y3e;Tdg1c5>zB`+I(g;#jXN)1ymsZxrSr!|+bc^dD_UA>n_D{? z?G?Fu4Sea;wAAGEq_nK`v{V9|hUe6@)RdG|0t~@B@^RWlXOpXH`BW*Z6r=EI^g7)H zzDdI>ad0KsDila2{nmBQzU^^MgU3croIHE+`b)R(+_-V`m6xu*c5B4@STT2cu!YMg@QbKHUe6j;GH8mwQ1#F}wCy~bqfeLA9&%0~gbna@J(JED} zi;K?L0DSN-$9jNDEN0Vlv7dwEA)cF`-C5ExdUE9W$qzwpwXm#)2d{e|D(x%|p2 zH!r>Z#;seY&JLXz9BOFDI=Dn6poQv`q}Y_C)a0LWlM<64+yi-$3{~Kr43$x1FuGz( z8s{dRmF{|j(Wp~k;KzYN4TgVdezF348aOS*stjxMx&{WjI*(qs_QH$T&R)B9>HL)o zmo8kmdgJExiz8#l1_y_`YO+_eIHQVoHYq+KAu%aA6}BfkOiv_&*F=rP1iVg2NJ?Q1 z8Y97%XymL>?qV=G8?|!8KP%D7RdT*MJxzk`3GC-!2VCN|+tvaV28TwDj|~hTX{~Rn zYi()k#yt5*e|LLFUt7*$S9%h5K(P-W8wVqkk_gwFwG)_}5_o`i@$vDA$qK#3=C?GIA zA)BoDT5aWdhi8d#vjD{>Tcy!)AUgpX;6GT0vETzX0~Oc_agf-!_(b44o807Ljfz$} ztBo!u;1l~bMwu2?E8|Ns7shFX--&5#!ttC$a)aA6pLz3VO`YPdL%6z6nK5_inoZkw zZ(1?Sz+fR8);kGlp#c|PP6;*e)17dN>`rIM($O0BEW z=&Wb4<47AdI8ZKDC0J=jNKQkDum#2wkR!P`qle9SY{--IFxWXDXYH_wf_*POPav2y znHD7hjE{_uiRBOp zBp8Rq(NR%Rusb$7lEYUJ{3&XgMk&#IEp*kmn4I+_|Lna=vDQZ;%S%?^Kno5ZbfIszxbq)FJpWS-&)Cb3T9-{Fuy%@-|=3I$SlL}XM<3_xM@&k`9P4%6{AIwme2 zb>HCas*tPn0=>trK$G({HxCu&pMGClxpdt}WVA5F5uEhavCuTCm`=9WNkoxUpDYYaV_~8*zFgBW)3^AI8glNwcP?71(bffEO$IxD>1z)CN(6vn6H&eqXooK;ik%Wg$lxG5iLPLY01CRk5L9oNa!@>v)Xdq&s0SS#b zo%7RG27?wSI8{>j>tDv_y?A?}YUYlbTYq`?m=~*ZYdV0TSb}X;p;#di(!Y~aA~mIA zaTkJ0?!xL>y^htoDV1)%xqo?Mle3%>(qi0d@D#H)ATTgEFeE%2*Z?LHMugJ@_Cqhk zWK4|A_{{H@8FV7KyVUjh7w`Y@&Bx(7^~?jMuYG>~)B!o`(zaKwa#rIEK7${zT%r;Q zRP*(Eqrs$;>Qqt}9Xs7L-Fxxs;js(LRP3RW`OZqLWwGM@p&@}`Asl2_7}iTdiG+oP zhj9u;Mv=$>1u>NC#!s^_^VUr{H2RnO-+uMpfR{5RVYN|T{N=jGO5kW$NbAftR^8N4cT-mF0QLXF!{L@c9 z|IEWJ ze}8z1+N5xs{?f3lDxG2*Y3i13EY;FdzV4z}w(Z zqE#61hwgp-=-Yeow6lqkPWi)|J2qc@b+a7zFL+zraVMefnf}$R+C{BXAO6!fpM}m9 z<37Lx(FPUck@@A{U+?(frw5n+@agBfrg|7mAf5i_( zDB_EdP^iP@XBtc@XTO1We;9xC`0MQ00RIShn-iE!m=AW? z9~cN%ka!^QAz|T>k!szwho9fOwpOo}t6fhWG_od~l+#UC$doEHK&quf%T+oTtjt9= z|M7>%U$>fLpEfGx29?{HKR3Mf;L+npKYrQxcBqH5$D)i&?>v0;&1*PQV8NArPcPgw*54S21=XV32}RyMypfESi^IJ zi`7O~gI1YTz3BUI9)AD+@bIgPuvq6F@%GC<{`AwsA09qN}tO`S?4l-Dl5ic#Qjj#vea; zBO@#(@^FG5M?QH7Kp_l78glVZpot(a@ksC$uDAEPP;$mqEtGjEbb1#P?%hE*tVR2; zS8LV|%uyLl&KlOg(;(jX!Tm=+j6c|+b~YxTd*|+V55D{9;e!u9d~@drbq|mB8unVAI zXeVWYVd8JM<={*R>xW|1=(osaf&GZ$Byjx6ymz-NaCctXnn07! zXODlpWs3+xfkmw+4koNaLb(D+V$a{#*AKoRERYxoMG?fY__u>oXUsbke)v#$ zRG@D#YzKZY7j^_4V5Ia0fPeshBAjAe=y3ypTp_{4my$V~lyWRR81$}29OTAPRRiuG zc9rkwovCGAT=kM&1I}u#(qx?e%B>k{#$fQMv#c?H8EZ`I-#ByX+(S_TKt^>Tbr<|Q zY$kRSKR}+OV*qhMU~o9s7m*eO#}k1smKvQ|8Ssq;R|CPt~^1-(pd4qDSqPwz6*8%6|B}pr_h>`{GJ(Dq0=jkUh?OTEM&AM zrTd(hKKts6_bfWssczcY?o($^U$!kMAl5e=&h_*44GeqYUxXOPct8NYAZ{nz<0U=~ z3<{6oCM1}RB;aONY&Ssk=s*WT9N+ae7R1G}5|L|+Ov5v#s@cF;!>-2wgJ%@ho{apKv{Cm++5eWJq*ZMPjj7B9%%d#=-Cy z4kJ(!PvI<={lg{4kmYNHn@)TTBX zxm*V-)mn*=FOkY*QZb(|5-LRE=7SgI9h;?7GCU*yED4}cCIN>s29!#bj7A`kfpHK( zB*-KZkw^e!M#Tu#d?AM;5Hl*ZMkW?B25Vaz3p0alo3?G+SZNhnbG=HZHMQ-m(`&V@ zY+5SSe6c_bc!`+c6cUk%*Zka5b2M^>D*{VWphET%A&<)!3WUVI5M-#uVm-qnbQ)4A zeJm8orOI}u3Nb_9#eB(SQbud@hl9%`pV4DSO&H?r+)r&`GpKFH9$mWi>SOm;Pp7_p z?9?2QR0Qt8Kk(&pVeW2D&#RoU0uRC>o-@)VivX68X znec$mFcKb~WlDaF<@3(vZM2p#e5elr5-_fYS5he^0r*O#&ZbT0KV?J!0fB$gPex-o z*0FyZMj^&rKvSg>nNn}rssHq&&mT1$IO9TtTeKaA}EzFJ;6$q1viVm)?WM%nY6vo4$H&*7^hsQVr4p z)-a@u(sbJDp-x^Zol4HgWQbE(@H!VP{GC zrHZ_5yZSLA3HS%TfG3j3r9$%sQDwPjdT5joy9iv>9WxV2qXS|uY_D3uD0Orq53&07C4X4#gPO%F47L=>*v zuquDv(TM1VvYN`Pm#$V9`;#}0vnbvEo504{?$C{zl85V44i!~aR9>hoD?WR&XzfToeorn*3QMQ z(x^3R*nmVNW6au4-yRjWBz%2LM*QxDn>KmHEnb4MxN2aaqQt^zpB7MjNh8L2|A>TtaD z>4U>DY4MDhk!z(OKuZF^6e(J2)Oz!rw4DbJ?muum=fM4*a*ei2J4*|VPV%cr3}mw@4&YcVQGtKR01_~jGipn@dRy9o{d@QBIecdK<90j|qtls7cSsq0=SZh@Q}-er}i3Pkw5Xxg)#X}O<&V8H*7y~CjY?Zn8hALWi9le+30t~ za=uc^m}Q=O-PH8y)@Dz4rBV&ou2gBXI!HhvGdD9eXidjW2@VYj4G9j7NYYK=q=D%2`Dq8y_()hP7_t=^y$DGX*URi+${iI|bX zmn)PKa#vCopDz~vxoB>1*@c254oamE+C}aKlrhcK5}s5d5X!k?F`uiVkj#+Z!5735 zxj>^*D>CyRHhl;Fx9>Mg`&$^45=?>;Y5_TjH3$tbMX64ze(W%!mrzz0dJ-y_ypzt5Fxb&^i070)4l zQ1IdMA+^pRQOQ+M0);|Jrl>+ZRIAmDSO`;9DHzxeMLkGZ2n}E(39N|68!^AjJ>c#SZr5Xu!y`;4k@Gy&h6o>jSE)`6@d@1vx-r=iiP zpj2XsLQZZ0_(T9aEP^+#DUry8ycW~v1_fTZkbfjt3tHh@p(qkEK^lOkjK=r|@971- zhxy2KI;FDvHfR7y`1a=EZY84?!VAL<8wnvwG#a&%fu>OoAts*(zeRIsT9R zyxIPNwHFJIq**EDPyizBpZF~QEIxgkr2-_ue@$nX>Htjy zQ9$_6VOXbti-;+X-gW+JSkU#laSRZc0ObQRIkZ6m9aJlg_zE%u(m~2zFhn9= z3xRidNKnAlqLQP@oiu7h00>1YW0mX&0R&0#40;4AISt5}5@GZO~at7WD6eHTn& zN~qy?X$ttwO?u7^@ec_oFDlr%%ti<5*zgLA0ewUY4NyU{X9a*cgA^nJgTu=(Mj(*z z6%6HSO0`yQ##3nY5_1*JvQYpK0UQvxhZ>SwQmJWT0D?RXA`}aGykBQVga&$tRGvM* z$IC{p0zNxf@jw8qmjogvo`hgD2E36NqTV!3RjA|?kr_sXh$rV;iX7A;y<92RP#j2i|>T30N`ArMs=Q zsoub}Df@y*5URo=h=KlN7>O_{0_zmzjL|c|{IUK}NCh0H;Baq0AJ2e_4ORw?($Gsu z?uk4EzJVV$N?}>fBZo^G$l9Morf66H^3yjq^nHEv;n{@){j41B!3}=Ach=a zWw1F`4_H!21L22pZTgYGP#H0&8B8zG%g@92jGYRRUOrbRkxK+3l~TZ$fC&n|U{}E9 z(sB`Ga9i+rSc6K`qhCC3`uY)WcW=RsXYbxWZ#atApOFcC8pp`QS~-J16C(n!?uMlT zQv!itfrw}%QK$`Sg{cW&JSQy3&&SKtE252D$`@%hNaK7No;oM^fbG}m^0*%;-)C@RK6-ZFZg3^O=7RV7J28qR1 zS}Q4E(&CSZ(4YXq@2XKK1XAs04<%0{;Uc*ZvI|6#KWq8e888|@{DzWIn-$!DLut>k zZKbv6j%~}`zI)fQ?});UU%x-=tWwbYk*ZQOqSX*`MOF{hIxUotO%N59NQN?2;_!sQ1x%!UGf z|M;R*L$W7pfeJu;Vt5-|f^spP&fL^guLBu6WLO*v&)GqN0lq%ozP@6)M5R-h9VpAO zF(6K32Z^#45+beC6b$AbI}^r?LL|4k^YTqoyhma=TYo}DXJeErn) z>it7|F(MKSm*$RK8G^M;i7Wss1D&~6qc=sGB^1cJghqvj1qS;2`FQ*JQEsA^+3Y!a zX}675YodY3;3R-YYRAKca6P<(L|~SpuJ`YMXnGXA@6_27r?TSLM6QgF&0K$KV^GGY zFK<6T$-wRc#Y;YNYfL2K4Wm%&bUKO!I=CV=ER!!Ey>L!gn17(ZzaRbu$b=H)h@JPG zJ6CvePh2|#B_ibO0mkQzr)*QBIGWpCP&wI7lQ zIrQ$^i%0i#3hZpbXJU1Q9V%A!)X2wSfuKbp6L6&Zkx}!)!)N=4__HG*0DDn9sqFV7 z7tfwMU3MdHkSW}OfZO6XUUR-k%9nHTdp@?!Km}jOXGYb%ZORNhdG6Su)7#d>W~FRB zwEy6~V|&-FUU~V`>&H)>7&$htNMprPwML=T>*N|lR=E;uU99=F{d^Wj&Y2St7VPgE z;P2;45XRsh24sc$HcPqY&^#V#~iK zghVZf42uX03keJ$Y+oM|fr=4uc&fHJxs}IHRNSgx9y!Nv{D5v&Dh69>VsVS+kflj; zp0EY>B(Z#3{-9~yvZ8{cN3#!QuStkc$U3ld%a$VtHtagFG5_4;4p@0XjpD*wO0s;(D9`IWqE4^@H&z16BQA=0FE{m8psMp`! zyR@@57q#GG=?eqkzym6^$|K({yC0}2KD_t9o-HW}%a$hW*tun6_P*?WIeD8Oe0zCM zqreImd#P|@LVO=tz2p~(+k$0LbHhWxdZ1rmP!KE+g9r!+2n^K9q=FVg+k?4hk7r*w zn4Az9wIXgsl;6xr0g?X0dbTx%3}qCBU|KS@{9@y{z)P3UoY}K$>*^J;QL`hGGt*Ny z?%BQj=&@tPP0yeHEh6bdW?1?rYSrR-CwFd2pWeZs!9pBb zEtOO~o3x?wVC z;unPYdW6qOOh~-Cwc*Rdb!p3X1$MB)_Pwz3?)a+nCy#Ggm6;glH{Py$$3ME+4VkfI z?YhME`KOEWUai7*FT$8OxbdK=iR0!TeC8AleUhqw$v!kpZBOee|&G+s^w9k?#_R9 zYirx4Tfd>fX<4ZWJ5HU?FWn$f^F+d5T6%~2g+xY1g!p)}_#h!9Bm}(sv-o89Kk?xu z;cfUrMq_64=a`_Vg)8F|VwNq8oE;fGcg2zVoI6c7vp20jcj(N4*luQuUHJ>j^YeB_ zg!p<+AJC^q&mKMg`mxbk7G6@|Ja0}^NN7ZOc!r)#>s2$(%6L9r3;tF#VlG{R&}B5d)cYOJMxQa^D4@+ zx_A9@qM{(lbAor|>b(bdu8oOLS-&nLEi*HIk>{vcy9$bu7^Pw2yrAHKQ1D5b7W_lq z0?Fq5jO-IUBENCS6qJu5WGfMEbcVLWJR(;n#4V0q5|=uE!@7n&+gAK6!uTTxZ@ zASdSUva1*N#x6-s+g@CLvE*XznPaC)E}T7EbY$&+I^gFg|v{+~`&5Nre@ahsv9(kZG0WUn{7r zFUbv!x_)+Z_VL0a#n-P@Uc7PrPW|PZcW&Nls5rG_N%G>vQ=9uG!pjE+dHef%d3yW$ zht2(oPt*QS@q>V$1KYt2;meTu@;O|Yy6d>GWy|Kyj#`la(LKsw<9`6<(^Y zx_aT#f|h6;w7e<85TfXYXm9nyun!D9^ zYK|5ZT&S(Re7Pub)ultZg*VEt)K}fPeeVf)8_w;n!hxc<1Y>EqYNPoF=$dHeF& z<>Km+^5c2okt?I;`T6_6zj=B228KrvJ{cWi{*M9G@HP^F^xH?wlZtsT!uB?LnMNX& z3Zyz)yXj%UQHu(%Up!xVp{6Ro{_>84XRg=WxK@@OmUy`G+{M%Nw<}67S6;Y$|M8vM zwO1~jExK5H?ON4?;&Z3N7RAnv4xb(5>kkw24-5$fJ}Y_%#0RXC-b4Gmu=2nUq##vG zRX7jP2X`{44UABQ?L}3G5y7Fe)34T?FRH4jt_S1OsX<(68tLcvksBui>N&@VLX$kmI5g%#B|>+&lK)*sHharuzHlY__Z zl1ue3?&jCuy>h9vI4|eGuB|%{AHQ(#;mz9fcW>P(&D|QYATB*AJR-;+b4vpMp>M3h zk-Uvmc+>Q=^FLlaBho^m&zvUKD#QvtvM!<2($PC)(fP8o`2}UQcNz+>HbKj|>TQ}^v_~=&6?YgqO%4^t) zE-T&_xpMc(U6Db)-mo}dFOQ#&J`nmxB>cel^dJlL_VuedGt5?NUvuHG$551*Hq6X)V~wEFt}uN5U_*Ge`ljZaKY-EnZ;>Wri% zF)KD7ExYxk>0U+o*`SrjkESm62>}H#GjA`*9jMR+JkvxF0W|d(c<)zovaeN3xvX#0 zXdO=_!vQf@uC(qlZ1t{^yxe0smFH_~a_evB$Ardi+Tz(K@<>(fqlVM_FJHNJrTBDF zLD`E>kFJ$eRFsrARMqXRef;P_WlrRR)inne_=W~~dw6(y5(tZL1ZF3Jpow`F|Js2d z8;!YG+IEno%G3m_5jX>bh5f3dM@|+TK3ZOLvnKy;L*CN)nHR3_a9NRCf3x=T_Ep8D zkMF&>`~KN`a)94Hynk}P{`$GZlA4CwWhE&)&+j>s7K+gCg?R?6EHV*6i0l&{{)vBJ zYhp_j03ch(9;rZr6+8}dRn|^PJB}VcmRC~UP;>U)^|Oho)z2P3&(5hTy>qd8@1l*> zw;#NE^ztd4fqnjX>)wNVb=B!HJMzn_>q`sYJuW)3Iy%(X3;qUfz$kK>4A$mJ{zMT= z3}$6u`z6Co)qK7gUydC_u@GxDoUcffHmmpT&N*_iy#89@-P)pU2OqwB`sv=;vTHZ1 z%PwqQa`oQBhtKXleD>nrtA?xB8ftIXqz7c|yiipNhyCu(^-Ehjeug6R~7s_QCVj^$q#=YLDb!uP&{= zbNOuPmFuOq>uZbSbD!U@zfrw^@uaYw=PS$W3g(6HFR7?1zj6KI)i(|ID{t(KoHYx( z3;tMXlI8nirR!@XfW4Gyr!hQuHj+r_Jq1qbHe8=jEh^bBz;L*^|qj071t_nR@dIFD9=4~ z>~!ssD34j5P=EkHv-pUB}#4%NdB4{aRMZX-?yvE9Kq?z?dH z_VGJ+x98VBy>akgWDGC^`bcu7C384x}w@WuL^k&8@FnZh1lV3$OUy*EZAm*b#E zHKp?Q%_9xBPL@2Zt-E z)Lbb&T~c@H#)H$bfnFXnJv?W!_=H5(4zd5l|49Y*H&H}_Rkl=ttztSxmSV3PtA8Bk z$^_ccvyMmZuNuPMY(5B6hFFCyk+B_J$vJ78{c9dvau1HpRXPryZ`3t`ww^P zZj_XsF0H$N>PA6au$Kp%FUvIG$>R_{Yxh6g080Y4wiuNZI~KslRwRz`Q5?lV-H{xn z5?V=@U6)T+KB&A?bLGXY>bwBg>JYt>hBPuIRV zdOGhwa)b|pF9HzDs29sSRG;7pd)6#^^zZ<@+MpB(uv3gvV2Q$Dp_Fi&VGm7?lX#)j zYSS4Tm(5uZu4Jt(D!qQS;^2wOd+%t!^=nhvbsPlH&hwQA_~3iv>xWOC--UnNUt5~K zulne^Xo^5HjHxCI?CnjAdwBwT=FC|%2wyH&8&o0@c1cZj*o@HPC`8agz(th;VzJ1O zdGwFoJC` zfiCG#pHZ2SB~>}eXR1nyE}YtX>D-O-Gl$MMHN84nhkf4dyB7m^;^gT+Bu(N5?sOyHGk-|H!=trEWol~R3)@wWG`BVX z1B*em!Qe1;{<_gPNs)(xX3HibFwD{%??5b{Is8vpFYD#*f6^`a)p^p zYF@s&i_hU;xm>GPs`}4g=0}BMVww5s;|@VbSFBvWCNp_^%7P_}qGm6M&dgkUs`Of2 z!xq$Fd^q{x)xEOQmvVCs?mK?5s;H#&`iTR3i!X((j}8d+_5h#8K|}z4>U03JuK`aV z%nw8PrdBNLRPn0j*`*Fx{^+!p&e6dgq*@(cqBdW9rl0%igIm@H&z>-K$o$ZhaYHBc z?&iK^b!5`I^8EXaU+!LMxUnyHXWEMBsO9Nddrndc=%78Io&u| zDu6$A>NE^`nvQo_eD~2m2PSM! z4;$@1+O6A?HJhW{2Rin5o;nvUxa!K;^Mz-3R+jD!T{N-Fu+ft}7B5fO78e=0XlMS~ z?2}R9Gp2h_2NK4VDbolKfW~)ICQq8ozMPtMdvM?BYbN)pdHmo(aVHC^W3kW<30rk$ zq+9@Nu*lxh-F`~)rsdva+y7zXxOMO0*qLKTyRW=daHizay$5&8F65j#xqtnbu>O-K zP6%2)$3Jk^q=8QU8&4E$3JjPvY4Xg;lP6E2F@@l!Ou^g9^e}1Sq)B)+b!z^{np-b- zo7+{qe)+UvMr)k$sLh!nJ-a7wGgo10YPoIOANtOb*~xRJ_H-HU)_?Qk+MI1W@{5yJ z%-&b=_3P_rH;cFIy?b=+n871_=WW|`=KiPwv!{&-SQ!x;9x`*n#4%$gOq@7z0tVn% zbdbW5WTa~H-&a+b~q@+$t{jxEdOjLMLmKcduJ<&rE5~!iurz_ zQ`{yD?9<0-khAkB&zM6E-@d{7e|dai=j@Q=+${%B7jDUETG-ji$#vAUk#4~YeWy(r zIefx|@#DsgA3ts^I0hBt$ANElfG+m=^~a|#Cbp8uM?L)*boAj;9pVt`nBe@()jSSg zV!n61B_r$8>5~`-iF}U8eEl{vIn!#=(iJHi4;K`cRFs|Cv0=let=mg(KD^uTqDg`{ZB!#hK@p9bwiIb;1 z|MKDCsa7(@(3^j?)=OF$Br=s!Dxi~ClyLDR`Zb5mgkrNEljg;4J9x6}YF&MGabDip zvf67;UcbWD&&!8(*RIwbU9&oA?SXT*s&3>Q&dW=S8TXfYC!d*P$4;6!cHEdTAYd%W z7&GRl9LC`deKZadc=yEn!h?w#k#mI3Os`cba0aVpkd7$S3Z+aWH*DIYlQviP80j@< z>xtv%uGE$m<>eeXmS1x3`I~pIUVnLW^}*8{H;PW4+L=>%`B7Ezxq{P&_9Tq7lD3>V zX%yH7?&vY}NY5Cf@tWR_8I8B}>fL&)&P#XcO>~w9y-JTNL77@3$6<#IN+=b}4OzRj zN{OQ57$3j5W5t#Ab&sCDc>Ut>{ktz-zJbes`}*FqS5F>aEzHS1Q(jq8cIi^VsZ-e@ z9Z`ioZQRI7W5-VzJ9-oWj~+96)Tq&;Mvfdsz!-Q(ACErQ%Ensxmlf7MBAo)Y9H`*a zARQ9R^e8YFh)mZW*5gn|^hdzlsEx%}u;X{{$;;^lxYOg00TM=!c57p3skOk~ zn?|XW>9q{XHjoaXI#LVn`GS_4cbUQ%)Y`v2J?8B_ccs4m?!(7VUw`=g^~0;Tuit+E zPGy+SzI}Q1_RiCr4{y}uoyuA`&Qd9F!JFtl7`P+;iR(Vx9fN(K7b8H*$Wdd)YRz>9 zQ%gB&iX>9K%uI!C04;;63mg|Japr8bZ)+>#S*}&PZd2D6R@78oZg~Fg!^gKz-hO)i z;@gqk8iXoY4we$mHf(d(A|IkIE3#G_|q|PS|nq$kF3D zM|Ng!PKis1O-Rh#wD)9w;qenEbFvq^w^oYL?I031Yd&_=DDXKF*sRpS>@b#RpaOf? z5Dd2=Lx%(3)Xa+EtCf5Pr=@!Gz7pUowPKYCS75#Fh(XH1d0KOsN}gF~WW5^Jh7VQ(3GM*A5P|6gjY*f>7L?qOyQKHPXTyt121|34GDG&+eW@cKY zMsF~+K*+W*7|hH~%`B~2+O%%d(%hg^qta7of)aI^xVe7fD0kN(gGUSn(}c@D0v!L{ zXbg6Bb#Zka>?Q;MrWV-wz+tIODl<^oARYg9S6FhJN#Xx?=><4c6Dd%2ClRA6g3G7+ zbt;v@@jluRXrQtKwPw^Xz~{F>*cR}bTaFtsV(>7RVUY1KVwuDaWN-}V!GrPOG6)2? zN>Qv!_)-OfA^|0eYqe?_&N`RaDK$cY&}!o*GaS5$C0v=5+Z=0hA)jl~f-8h;5DHKw zK}Y?l0Egd_Gh!}>$NlXeCO9e-^4g3YKFnq4prM1^SVV$^#KEzvi;D~Rc5@xX;!6nM z5`{aAP-FXtnZf}5$8HdlzRMudcU0=PueXFZlrmV1@wqKHyyjdUhbt7IM+T>|LdXPl z5+n&c;2pmO4y#c(A^Nq;*dc?R-3AV0pLX8X>A`qf@TqNeol+Bbl=0ho+47=6vm3+Pm z)h;}tn4{tF|AFmvsY$brqg-7E4j3?Skjvn~00q;=0ZIn;?~jjJlH7(U4JvJ0Gdiv3 z>TOGASX#DgX{!}0qaStxC8B5K}l_#l@7{Rg^$Syy5i=mb5Go{ev58iowv>00$rnJL9+ z)Uni^Khwdg&BT!kjoN0_lNaa0Eex&BtTNZC;eA9Z317(*=|u`if|1$U7}RQb47r-G zG*enySazD)%~EX;$^{&_ZD})38)QjN4$gxJ8Gr)`d%%GH{Xqm#;fjxG7Tk1ZJrfKD zG)keTp;htQh!q#h|I&6Ev$pEdquj2hhTjVonrcn4N1;T~EhAKLwQ{+I(%cLUP3GEG zdMmw^rKxoX3#(2(F%Rpy$qXa>rP&|-9qjDvoG=3z1K81@27N%YLO)P{SGjrb zi+#-uLS-vOE4R|d?_XZ{n`+xnjKBJ^vcN;H>s&bBTxp8xQod5da3y-3wMNy_v0b|k zZ9B9!ZDpzN(AK6`mp*oGWAe{ej_BAfB-6gNx!S;!{Ngyssjs7Be{hTeQ8?28yfY{u zwdhX-fC5QNwm14$p$UukQPL2R} zbZ~IQkCT(5qZ7dKpH)kufh3?ejC`NluV*_$_l(=$KYaW0F38%TYG%p|d2&7T;u5Q_ zr9tLWy-uxBnIJ%*C@;;oJ}z^ShUHzSyi&B^2N7@k5`3u>(saF@7?-N&AV#n~c20c0;pol&%AZFj-jN@FTln+G?2N@%HVy&&}O_Uo*jNBtgB zSyTORkKgPwl{L4+`V8(gGOcxRbf>?j9scyX&UeV*fk1R3nL`U5@MCWWDcIZf>*t7% z`u4T6@7K@H-g$tF>%k}Qn?61cmswfKm{ymohYvnkK18XOirX%+z>b)i>qSpXm7Y;q zL^n2l2z9n;W2yCwur}3niD~-w;Lxk4rmDwHO`biww(k_yse8L=d#jp0JScQ>cLu6c z|Nh|H{(r@{bA~`*81{XC;I~@!@ZIZI+iVSHdbL^MMzy*5gy|}gTBP}1q(n1=>!lgy zDxF+qx%3e#?N`ikwrOo@tyZ_PN;q`&^^>OeU+>&4tM%;AyJOdOT|V%eT(F!B$Jbfk~Ky`8E_{okR4G23Pgm>-K-8H@f6n_2o{>$Of zE>2D)UKYuY7zIA7gdoDn!LFZOzrKB-4E9dW0|u&EpMSk@*zR#!9m5#rSSm~nrb;d% z2r75c+G#QT{1{7}L9MdLJU;LS;agI$;HP^Uzl!~$ zS?s40pAjPm_a54Z#P49=uP-SYWbFuc@l2*bmT2G49wZUI%KE}+D|DS&sJLQFDYl+y z4Z`Om{gue{Isr1XU}>AmJ*{W_E)I`TH?q>s4gTZx?5KC&C9Plz_ztdY?Xhp@$A+6yy_G`S(Y>FlZD!krExO$h_2Q zGcB?&p#)7Jg`w6e^W+ zRV%}c!a-ItgQ?Ci=TdvMiZQqBa;2(gOMRO*T{a~TP5d~gRn*R5om+PrG11LsptFO+ z0Qg;~HTWlQPXkI%gIo?fV2yABE^bIjk&Yt&mI{E+6=`)+5%9HYgNE>B1{8A}26dTQ zHQHRMR#=#itsbCcbY?AW&o`}EdCcCjV@r!s%NCF5)2(A$v)=s&_lAeD>p!^vUywI~ zoTEMYd=?)%0H*Ew5bt;i>DxOE9O8}z0lN~C%h03-{X-)ui?7kEsTq^Os$Xk8*L!H` zWE-7K+uGEt(ow20*R<_*`2%)L;z5X&_IKO1f3)rCG;F|-za61lKyesAj+UYh>AsQk zJ~Zt{;o~JFl**pF*QE1-=OVOd7eyeC0|1im^RM_S8tM z$6gp=5bNF3tG_mWeqCm3fb4qx@%I2X7uWtys08c%caJ~&+V|<*mjWFteb(IU?fU@} zyhHYV@kyV)xqofMDNeh8(noukW8to=v252uyThL~<>E)ijkPrVjGSRNHllLIggDM#8u zD!~2(T1Kf$9xFj=Cu0;mfr5hu7}U5^TZI5Urs%Rj^*Cyt73SzPP|D3vQ=~_4CYM1` zGhMPdnxt`YtW|IUG}SXg*A469dsA;a))2TpiZ_k@{~{4SLm?*`4}L|3X%S! zu$0Be--{U7h(v)t73wIa_H(N6XBdq_t=B3Z7Rg_(z5c*S zw)yY94Xv&0%q-iRi#RQ~oMtA!G}mykbOW`-KSTmjnDormYXi}q%0t~cD_*KSXD1(g z0w0B}&3WSHX#Oz%mQ@W>46HSEe|K$;>UHWekfQ}uBNqvkt^bPLv0+IYi}vF)a;K(b zUK}z#D|+-_zgtCmy4pE-6iF#R-8_K6dBX8oFVYZggNe##&EM{Sn`Ef!;?&4EJj zB?j|0zhefds+gAiM%@&v26yXe;6RmV_Hk^A7Cyc})5>#A)n-G1^Q=YWA7JDQ;>yZa3FuUzWHrA{-5AOAXLtlyZq-ja`gbQ;8=`1f&s-rloUu1ZK- zleKBbo=safY{*)jVG2nQh17LMiYXQB%l7HN!kQ<+X*F#?=&|xc+l+*7`p2LRcasK( zjoK5k*lYEaqhXWR2aIZGYvnR8#Tk|M)D%G!qbE-&@0GT0?YgzSsk;PPNhAn`1Lp6l zuSpDBl$Nn>!;WqH_U_!VWyi)eGxT9W@US~6`u|a~=a`EspCxGm8Hg96Y7CoWFn=_E z%1!N8KV35_a@-b=Ah)F92fSt;4V=`ri>>GCbeG?LrN%mz0f`uUer~{Dvt}r&FWEUK z@4|H7QE}nS^~7b{athBDp3Kibm2>c9s#^=VC>EdE%{>kN#c2g54RoRG{DH&_mWvun# zL54=|1%hU_TaQ)VJhyJu#$AU{WqFL%VjJI=1WBBN>~UzjA;1h09mVInZiW%h*{I$-`4KveGk` zaH(g5WDg=ZhE@3=K9_~ZrzEf4bl~8Tefzg%Pn0w>A-6-WS1h3Zf7#RnwXYslq|FTP zjQby`us=aMh(;fU0k*6aiKGAMU}M|4Q|qqnlhK8U0$-_=E5$J@KmY_@gt!NVYDG%g zri}C?9uoea_4mKzY^A9kY;4=MZPU6{n+_Av{U!Em4->{o<5HBTZpbvai13i$A=!wT#mYt%CQWi$9shO6UCCGd9SJlM8U)~%8g7uls|W~^JcHlsa9$oXl1 z|LXteE6cjwE;@87?D`EcoI50zHM=nPta{675pRe*k}y$;A^P|Jf6^J& zD+6DO{(r5weZqx@U%!9+_O#%kn7oz1!rluxkY+t`N#gRhE`c+cYh&|HyRAw=kGm3Z;Pm&V4% z&y7zi2BL-?$k21N`2B3Uq<~0s4o61;4+ck9H3k=C71Y%x3Q9@M*s*a<%4iep*c#Of zSOCPJoW@8E;e!JDffo?K)U|4P=Fk~IF&>>If!Ovr`JOOSs-`;qDL%spXL&>z&v zAsXO@C=B0#vm4Z}Vvn5S06VDn8Yw9-VSt!^;+_&}x<0Nh;>!jkCB&vCZO_VD=_BQn zgNL4w!c&e(N`OIP!VjDGQ*b~K=IiZWgvxM0%~LR3ECCSz#Rd)%P_da3Sr|3RVi+9( zWCEqcBW8x?g>p55rx}(?W+x;k#-#6Bzde4iv>C~q0Fj%swo2<2f~U`M(!wU6Mm^L< zzHFR~T3J56j$Jy;rz_pCsyfL+XFP{`+<*N&;-!hi>63sS^Vr{<5VHeYj_fcSW62nnP~X^P6`=R z3S@!V>8yR7s zEWTvO&ClOHf1D-5iXAtd5~4_qXXtqM^K!KUH-e%9QX{YI{Np9)AvGjQh$VPp;!Yw_ z#;iz+k4ulq-Zj59M~D;mf8x_LP^QDqKJ*W7$qGn4G2|f)ufM-<{N$%>p+^phWtge7 zP3`x0w{6u_H2GtY#AzO22tV-26;Mba3!*7*la#zFDK05-|AxNJQERJ%Yh_uc`Jusr zk;*|~*e4cWELLrQ^6KsPPv1W+b9L@yj!jFIsO7~^jb9r>l*p=aLP6ulBhg@-eB?w% z0RWoS0@3hg$*U3*<5CVS!&-_b?Mwx?WSt}=vPITMu;xd^(A<-TNM)8)_uqc`*7)_~ zt0%YbUOSN4PX6ajxWTUVkU6DBAL#V5i8??2`ov|xos&Pk$hy?qDwe`x&jAUbVdLD9)W`;#Ai`;I)I z@xxg3{wuK_!W07*g><+UBtc36D(JRCEL9w}yyJqFwnRBNF%%0Hj1$7wT8lQGV#sewi%a}^ida^&l zrw2G`)&>N8y%MXhxp4{caj`Kmakv*5o!hR-7U+I6QIqR~0EW`MOinh3rL7gX^1 zE>~|iegFJ%&4d+8qeCY+d$~By@f%S0`Th4VO>gJ1?u(@!stnn)+Q(WX%>%Rm>7f$4 zn2fSlDl2~AOJyeH*f$?@;mN7&6$1=Vc8olMM++wUkjI4~3QR<@tv5en+x5h-c?olT zrn+`^cXb#TIBUSQ=byiQdUj4id5qCcu*xIXYcxo*2zrGr2NgDBRLb8MBt!g|{@7Tt z3?ZK*+gh+o2TE{PA}qi`0C54sz=E1=z&HzHuGr${lV?pYE{_ey?HeWy9oE@#xP8xQ z(_F^feEj9*+q-|ZFk#JxSb!oBDxwm*F{B{xphoH-Rhac%mX?Ix*2Dy;UmV?$N7!7o zVQ*fRRxJ4icb1{KCrp|MqYt78$VF?8MBMYit!Hm9x%qhedk2s1KeUT|fBQkMzN5SQ z-F*K1{rx0i3o;#KG4L9sgqVAZ#56RxISXW6kUQ|Ps~Sbucy@%_1H z*SAKg2XYyy2zU6QwJwMH|8Wxv(gu-0KKA;(=e|9{=6THwoG^0Wn1LfFjvPID`egTU zWA@*F`0!Cbx)~()ZMev*QzzA$hFzGSbplC#R&P69J@z35jvB2uggUpEiqkuM6#?lJc4(7|^uy z7!k)r>KhQ~+eg?8MKu%(8EI9+-`*)>HKtXdxnZc|fIGuN+PotcrI0`6e|DJdj>uut=^P&7lGy+G%&E&^vD zC^drjf$cMLE=QsWnv`;6-Gm(qMNKZZ>h zG_z*RfzLxGRrg&;3zGdW>>O5&RIl%%wbjFec>)#o_Z4%;uRgYEw3~xpUoOX_h2YoXi*1vZ&k3D6aQv9Dz1k_T(JWS3 zbaWa$a=@rLp$nH4hvQ};*!>op4O$$RmIje$lDX5!z(~iWlkz2DkpCeKL^guEQ9|pG zPooDIjsu=kh`7U`Cam@137Mxij2=87z{R^&GocCR#NOXimxlX|8tm5J&RWk%xW6?M zDlK}rk9KvL9uytDdU11X6-i9GFH28M%fK|#JR3)PI!${Li~ke*-|&%~^MCzSf_pXs zAGgP9!4)w)ne$?=(i6qN8GuuJWJZ9uP2lMpO|0x1c=Fkz}Bd3O9t{ zbDDFI!`RGSv24Xr6mpO951c?rqPgPs1i zRatv3Ne$yDti9t_#3!UDr$Pc?nRJdu8X5c#_9^oJ8$M1=;Drsi9WO|cQwK2i?Knuw zoL8pLUwLYg&(!gu9w7k%%e@9mH?Lpe)4z|4%lO&R(eoFChWG@`UlbWJ+k0f6E-n8E z$P5*9kBEs)NXkq|Oi4{oO-f4rNx(m)4>AZp?#Do`ik(mxjYf@gZ&VhNvqcvwDo{1P zaqH&CpI8zeFfC%n?1?_}0{z?nl^!*5O2BN7*(;VWm>(OH9JeqIj3q4#pXD&Px7*@* zZC2q9E-8qw@NjUtaC1iX@&2FUPekDR=RlSKd^l^`*hbA~#(g|Cq^sB09W)*HD7B4)E@DBncXmx=>p)@@z8W@A!T zg0KI~nZD@Yo7A>#q-Stg%)G@b=f%b)Y&)?3@ZPMgd-rB%ZCth>WMXLO!XUpT@o~v% zDNwzP3{ts7GI&sdy{4pu1cY76dtz3tib3v;|M5C1d*%GXZze%`nUKK-F(QZzQBl$@ zIyGzCx~w%ZThctdyuD_5drx&X{1xo)H*eAMSZMgtw7q+G?A*C!*N!b)H*b!MS`s?f zcj2I!iv|Eic6mI#?fjC)v{C_HU(FGCR@ zw}?XVB%|Q+yC-hlzGFw$+BNanS;2lY!+oQor&^o12F{O6Sukhm@}yOZ!zSVUdia20 zlY5=8Tw-xTv`c7cPihwB>c+VGC1}!;?0zSQWE)gj?XsteE%&@^W}gG@@h95$t0BAF93rJPIodH>E(_Ml(K{ ziMzYIySuv)4{>)#0|~*4g#cuc-5h*!yW8qI+UiQ0+m205j*L`!L^e;K zfI=|XAsljE!H*DIAYFKc-`_`O;NbAct&>h_vMO?Zr3fxpX2|1mov}@kN^>mOed6rt z<1^={rgqI;*_-F>m(-Ce5;cqr?|GxPc*o8{FC!}}D+_mj3v+8LBWtUW)Ebacjdk{S z+xWGP_3s@R>L0-IVq{D*WPCwKppTIeGIlb2(ufkSo-)GwzN|xiEL#z_C{ID$lZuKc ztM<_GGbc_g9y>O@d*RCNG@saVZr~J~ARj#_(U^KRBFDlc#N0*7FyFsTuF` zVAfZX6=aOcRG@&c0N$pr(Pb=>ta&9G^dZ zdUCRFWcNtdPG|Gb;`*-Ly&dOHj*gFX_w@~oV4;)9oD7`IHp!fW@QM9Px`E%gYS#LJ zG)RG`K*0?njt}bU*yqYA@^{UgK6m)!^x=bp^D_r4cAlC)b7iWoeRx-QL$$wNT^HV` zhsXEr8`#&|(K*yL*y2ogiL7mF?(W)q7Hk;<9Z)Xtfh(0fF@DkkIDLXIwF1n5VUTHC zF>v>!Kx1*hMJ9QyKqx)lv4`gN!L=Wr9=I?&(KUAB(7E$d)jJ2r+uA$xTmstryGOdm z`s)jJbT@UiRCT3m7=$F2G!KpqjvP5PcIt3f-{2rI_gDC&6HnA2iq%l0Ap{^tV z?cnPq+zuW9K9D?CKovAb`t;Pv6GxBq&R-ZFJh;1m|KYachTf6JvEJ4atE};n-hu9H zJGwz~eQRS^fhH%aq^7ZXaHM6*_obk<@^IEM*$aXY}|R$By(Y%(wMTc6Y^;m6tRPHn$B9jo znL7IWdpHJKdo%@5gUg$0ds};V_4hZHmR9W=8i4F!{G))D;A8cb`dy41hWN_+jgI0j z3eSVM+aT99xGBIT8o8%Y#PgK0vi-!#V<$T<-E8gNbNb-^`uyUimg?^QU6Y49eQm;> zyt9j&+dG?*LN)DKPBo)ry~F*TqumXi*`tS>_jdOUjgE|s4@;&!xAjpeReF4uU;7LT*8xHJhYM?+sc#v}VrSm5{7M4f)kBmXhsj^YOXz?#{rR`rPui&YH-) zn2O7rQcy8v+;} zlK4Wbhyek$#;CQ{CEl!LsJN-zL!KKArO{E!C7v9*D(K~nj`uuX^zQfCBdOFK$3QB7a?rrbvX{}Bj z8VPJ{8|;J&l8T?Ky=cVw6VU^|zrR<~2^AWH%cLtR8;&S3ILb6t9yr@^8srFq1we( zj0L0S2B=U=DwRSKonw>J6FT~`Q*6z&O+p*?4DM{(x##Ry$iB&Ut3 zf%3tzZfxr+GFtmb(cID*I(75bsr-rImXh4& z%94!ul+voQhOWI;IjL2R`UUkZz3tt7MBt>6UJ&@69;xcd*WRA)u8Km;JG_ACu6(}s z#XSQBbq+(F;aHU`-mXsBhPcn{W~S+IeP*J)kBN0G~=S3DUKp-Q+--uMqYq62Snrg4%sP*O|3iX zs&>TImc?i}@?~(v&9-t1D2(;=4BpY0SaYbQuep5)VkWJpM>^vOa8FNncXwB3SGVNz z_;XLIr13bL`|rQ^?y?SY9D#fq}8Vd+R+Vd z4Ain`CYy>%y1GJLjnms_=H58At1&TudS{-F2FJi5uW;;^wN9|3b5vSXb7N0e&maNn z=x9d+oCF;!5q{zBIwB<%kTkLwLZg6^J#%yCuidIT9`ry0oK84sRfp3}e zsR}v*M6v>33Aq?3S%a{el5T`qPzBCWsTGZVxKh~D-&9@Q&^@;I^u-G^^UD`593AcI zNbwH|N+|6-wEI|3_pYIWaz7oKeOr57LtATOD;fcB!*@wP@VT|6xtaWFL6^SzIOF~o zDQqRL3Uy?^qkbF7f5r7ZlZ|zds-arbqNwtkirCgGxU{unbl<+A{*L;pn%4f47cO7D zIJY=|`1H~Kjo+V(=)e$&H_`0dMzNdjOo2E`zsc4cz$rGNdzeh~{$id^sW-l%*%$_-U z^1{N^8<%G;%$}J&JvBMhTHnz=G~7LefQD}D5!iB6gT|)Drn;tfAQ2pN1BlH{O-%$C z4Il8exp|St;ZdFSu=L3xcmpYKsH6mZJQFf;3!tJ>+99t<-6q2i3-!tzo<4c(jW^EB zT)eWheC5jG&Bcog7nhf=TwR!-yEwmm1hISvMn{?(A}tiu6ofnL>l$z(Aeha~%}vcs zjg3uBjSUSAjsIG^P+p7BZxs~sRMi@87&}`pm%-lSf8|D#2H?LMG_Xm70MLV-_*)Qx*Y0efvhgO(QAas2WYek&Fn%zGV>?bmFcDpS8?~&A zT#q9jTw&XG6=4_-pc4lU9GpIS?7-Bak-ouR9I$pz9zHxZv3Gpef$^$Xp~@CnY@Vo; z${LKXzMcph{Q#&GQ_=~7T?c;Nn!0)lqF6L|$OT}i(YWe79v^A!=ndX2Im+8`7A~ko zD=I3hNd7-hld6n&Qbes%h2R$8l7J8^b%DGblR+tM!22g+I_L;4 zfIZ+zl-v%=lVlM9OJDoQ+Z4%^z;sn5J79)K=D&*H+g^HBH10;Xub2*w!!1LIom}RpAE{5@NTv>ro2t2~egU1~t5I7(p;vV)z-dQm% zA#FMG3Z#HAVaNhsw#$M}Ta990x}&PRc1I0#Ob`iVHIaE023|pa1TiqdSI78;S{mS1 zSLGo8pDEyTk^ir#GIW@#z_DPf?JUK85P5}0?JUGYDNjJ3QRpp``E<-mP~UxA-h#B`QOi=%`}J`}w}Tp?<@pi-Km z%Fg!#MMDi5l+F^8S3!}70F)RsRRkk%S5=o$#USOj$t%ez;;l$=+cp&xD%gtmBMk+0 zSv628E2+Ad6$7-ith@q41v)_{jeID>ZzW==s;Q;(G`L2{7oxF10dqHv$I<3%(ov2w z^~KWSO*46RZw@4mfJ-%G8lZ^P7R9Y9Dl+OyTja1rp=2X&Y~iOla(FYwc1b}7Ars1~ zTjl8#*An1ksARC@J79P00P{T&!Yg4wG_|#|EL}Uqk0S|>EnwWatjiKaha#kr-}wHA zmx~b7J(L7$HYM1$x`He+0~vCvO52c(#OEQPM;Y->46s(K@mOsAL{pBsh8&(Ql_?6i zQ+LlRC@C#2kr-Jya0zy4X-Nsj5B$n1VgoRZ8Yb6n6#3-{r=+WiZ@egNxV~aZGm6^1 z`tCb3`cx`^ELsKO)$($9*kzz%sSFiw1S%j4p{Q%1k_Z=P7CKwZV2ION-hUD-0CNT& z4^>rUX+d~1DJhgt`~$j_v=Y)F3eu-a@}~+-h3mNC%wfr^2-HNrw}1ZUx1Z!_(2bJ& zH@>+wpP;VJ-4mhC6llQvBI8M34Q~_*a!hM3m(Lf1Erp>f(4gq?^o;C0-Z+2RkxGg0 zv=%Z{HBiVTrMS2dq|YT1*y7@%V$w?RBP2o8QARp|9w-QdzG*pE78OhICFUS83E+kbxlw_FZ#{RQfNpKJ`=YT$?u`P1kk0d7gQq8tW`qTjj8=KSaLv!DFq#ZT8u z0@&s`w}1WSyU*``_fK-IZ?p}@)@tth`ZA0)&Dpuv`q{*gWupa*39um)m` z5*08^s(=0I;q@2>m*#)$+ZSK|{kaW~!P!Rf{A`eEQ(Zh5j{qMYc&LY{yVEP~LPv(~v73Y-|>?kZO1n(hW zLHr2fKlB8Qzk~uDjM(JMj*80JkAC>|x8Ke>(%5Q3!+U@G^oK8|I2?*R)$*Aa?ge=! zQ?3*Zkp_nm_WqY$EIFn0Bty|&o@;m8`|Z1Lo~73iwrheTaOk4TAMAhr3liRc{eJ5G zTrpc{U3cy6zmSpf)1TK1!MP0TNogy{gVG5sMlDeSVS+@jr1P<~OjYa7_l=>a<7o)p zYCBW-=h-;oVN7y&-0DlDqVD*`%rA7OEM8pTP##4oiv4p`x-Z$NQ)m6-A4f>Uk6FxIbS0`pa)WeBi7_=U6^_`1GT< z-r9WktM@Lx>!E34v>%!Ipricvv!D6ddTtqESww*(AYe_1~p+qL%=k=`3s0g3s79N z`})s+8~AGF`TfD4-hLL&G!kFI_+LJ2DJ(0^tPRNAFc*X5&EW4*en!^6>+6CiV-=9OcTH3wrU(r+_>cI(V$^#Sn$rDFT0s;wJ|%> z1F}zi_SuPysFqsqbb;C5|N8aeXerjbQsQ%q3ZZ(8wy>x~>VN>6my?qVHd2^{#2Vm- z$XbLKH`Gv82Gs=!n1Bys2k|hKCIBZTu0unQSkM__k%n^c5s?~2%+)r&a&(+U+xhE9 zlVAVzpWlCZ`Sa%5=gY=Aoi`_{a1#TCmXr|7g4vTnO3j{(72O~LfEX|#paK+UC?kHD zLRDjHFhF$2Lp??C53qz>3JINM@Q}z$RS!9BBH##^>YRv^A~q;uRgIp9>wpR@>Z^bM z@u=soQ-^;2^Pi`*_01CV)6&Z-3bOKnj{g9LSw_%_6+i*`MCxQlk}jYF#PkS!1;Qk8 z)K{VMP~in9ZZ=!UL$or3i3E6cHGx{#v_6Y26tWaUra7oB%VX<5Skb2|vDu=rF0YOk zCA^xGA*Q+(Nky3;8-~q+^A|=3Fp0mxxG`4J04iyD*}3>$SW*t(k5*ZQ7ayFsl-0Rh z%r_Qt*HFtH_!^q1=E!u{3OsFv8Oh))8=tVlmIFn0ul{qO{-h-hOql_7m0qTX#wG?~ z=|!oT*;!dd`mIuCDB5#S<3EL_8NWSD1m1AE_8uouQWjwp6fO!px(l`sT z_-tsO&f+uG7~Jv{yJ-g&m(JByaXxCTs=-qiny!5P?TZh)SORSk+gz+|X5bo?ms6Qt z1~Su}98gXZ{SpBa8zd%|hd((GI)NsXvqTH|B^BfhhVuaMDN3l0rbc12smT9F%_u&& zuCN=Wv8i-c)7jW#7ACPyTvfWsv@cJU6VSi%J$M&4^tBUGvZ4bclQYu6uv(CnmY0X& zm*i$-=H`OfHIIlKY`ZyGSp*YfCz>Z8bBX;Im7y3@Z5_@9Rn?%AVuE{tO1O7fEINya z@#8Iw!vzJ6TK=b#XC~*XXj(#LgX2DQ?W+4<{p*MC|MBgrsbg+ZT4q6RQC@apUIx(f zB>y;MkeizY%G*3*{~&+O%KRVEFa8H&d-z{4e96|6oWUxG8V$F>-IGHNj+>;$8N!Yc z6osb{Nu!|#7(<<8_DO$>r!zLmm@ERwhH6J)Q0(zEnDX&@K|^hocaNipXt*PGeT$ z{*6TRt7G(}42gf-LK3?^4 zdQo^CJPLT_MlU%$0UI|hT4v|gyL%#KaJWGbq!Lc8B$*!nGbtj4I5Z*zyh@|yC=H1P z1fL`kkgO8qIjPe0_)v_bFQDYZKg(bX^mL5v3=B0yx)!d!Vj}}zvA&}T6ZtM%Wwyw? z&P1X%>M}!k#L7T*BR0FU*3+B)+BO`n5b`OfL=u6JWP&lm2GJEf3X_}&B}vvfBrka) zCJcQfs$1aZsEoR;xH-eM86OIuYl!WgQyXfEEgZbdj;$6<9DXyg;?kZ>zi>B0BTF-f z0&T*=06DS1vWOzxLM_XUN4>qZ^4k#jCC&OEd16G?lvh;{N#ZO+q-XAcCt-q55_(8I zL2x1RA{^bcut!7DS11(K4Z#4Qp>Gge(NJb$@6&zqZq&r$H=b!rM|0i1olz!Nq?6C* zB14G$1EeS)ogvhAe)y!XyIXx5*luvD#(AEKvaVSERajbvmaahwxs~-jW4rfFPK}6& z0g%)lVsBh1nv*>2<(Q5s3-1NaOvoQsS5&$h-NHXGa7SWJ+Z%h^I}Q|AAE@>9_wqH+ z5Nhhg>yaA;lBKPIYiYtiE{>I$wmCJK8r)N)1>JWHn8n-k|v52M355Bv!zpsCa&SMHVTrGh>3?e#k7t-WZ zM8PNix?Hor`bOv8>MPZc>r&1&S3A3y=eBkwgBT9CoX{c!NaM0~D;~Vn(b?}UCpp_m z@im-h-gH){R@YGF^YWd8F-n+r5`4^MjzB14^xb=Jq5I(7NUl&U&=R3( zX_3nF8XPKxn|c`c(Mj#aWBY&@qD7V$w43%X>*8!#UJ1si&1l)qSg}_;^zn6%k+#zwlW8o6*TqjsvOkT zKwb=~cnKCyCf90y^R14?j;^h8nCGwXd9oc>v6USeM{v{CrL%{6+H%6EvOFA0AUH^Y z%A)dlJe|qa?_U1#(+7tOl8EF*B7vqrDBy80u?@6DLJl{`!5tJ!zP^FswRm9XsSo22ZhdvLxF3T)I$|V-+$}lmX^*wo-EQt@YW7|3PV;wLtdVTTC-{llwL$Ayb8FX z1 zXHlYmI!hgr=1s4D{U3}UeR%tphd>k5b(%a(Y5dD<3cLUxSIQb3H9nqpS-4;WIRG2} z2^nz+G9cM_nEPxxTete&d++tMc6VCH!tqNajA;NXBr%xq@Z{DJGJ)SEn*s9o5_}jt z-D>ZXzk_V;uRmY@^5h~*AkrYF2l{9x$Xt0`oNdP8d)Zw+*Ice;?bZis~}a0HL6dq`%LFNx9 z9XlT~atxB}enCe!ZZCojQ&{+~a7dU2kkA;N|Y2oN*r z0w04GiO8OWjvW#%>NN<38)+c5#`xiW7)o*)1{c?#KI`l4=*ZXt4=lmQ^NGkeF4#XJIpdD}A9RIsiTt@W~Qtj!wm(OkX&TRyVaBZ4OzF46%OG*9ZmGO?aBqi$?Id1tV* zyR}qqJJMeveZWfC1As3XzXV@0El3yeTR1=#W)_b}<_IH%TjWXcVUWOw z5kMTUK{Asxj3;kwy!HM_+dx|pZd~xdg3ST8l~SXVbU=!atABI=+9yjea5QOjzAE^B za70vL=n1vWBx?z=ZVvE?mU(U8PiI&kX&_qDi(%nDc zah)%n5JUse0gbLM^8dMBLk)r2OgeH0)!<^NB4sun4YY7T)&ONE#?KRw!9xT%&cYSJ z8;J1()(8lJ%frPMg{ftH`1-x|NAK_I-_sGG24*sJK!Q(H4FQlpL=}=bKxV-!|3lMk z|K(*iwhtsr60uuYk*F%#RDT{-2m%r|olJCsFIkX%Um3NSSZp$1@jGzo*Msm z8_*y#5J`#QL;if6!z3OEd!<(mgO?7U;8S@zk-m!^%1-v~#*uclyAVDW7 zhr|^T`2!p>k<1R@Gh)BQr2Dgo334?KxaxSY5Rui_{~6@5c_5~N?MwZSME?RV*%^t^ z7orRTPbMY`?a@U}*Ke(@tUP;oaI#8E7Uc61d<3In9hT@_Dt;_L#4d?SV4GyxaO%JM z4R>IFRd5tF8FaF-5z$RQc=_+Ij6f0$e=EVq_+bHPd>+Ig!PgRNVkIIDh)2roh@&1p zzIXr5`o{5H_OHn+P=ydO(8)eYYJ8HxOPwI`OXvZ?r!)BH|M}~`vGo5n5E*Q%D}uBs z`cJSEcv**g6A-Aw4`{&oktQIPFI7I)1E~P;y3gk_C=9KZ+fVP`TUpsW(}GY(6}mNu zRnRa8h~c3T?=N}G!gm}^lt74vjWdg7@ZNV=bx?E;rG7qseqq19s{JQqBk;$2Fl`3P zMvy34%t12o$($2PI)L*wKBGf8_F(a&v-M|IH=cn$f9e*^IE#*NmU>* z1SkM+o7=Z;!xF7Zr?Z98U%z+>n&uzgDyrXgYHs$8sg_S9w5%`xNX7mKJ_%Mx_DUcC zVzO{GprxfvCZG^5iFhO-{)(Z^GIGE9@X6E7^#>2Lc?@hW)!K9rX2WFQCtSBx&BQr5OIi>vZkJ6pc47mTmzQra1#MsAS5#Yk@ zYH4drtWOAo(89)}SE*5|J)1UtMb?* zq`|V`xYfcx{PM5ge*AXOza%du!dXAe(kv(5?$J-A5aDmFTeQ1Ai57P&%^ME2MR z5Z5ap?nkJlWvHobV3~P$b?xz!P2g+k@Ucl&nmFg9h+8hCB9kT=Kgl|mM{Rgg5rg=2 zIDHE3TT4=?~KK?U&f z*xImIYIM=ZKYahoM>o9^lVc*>EUanT)<$AKZ&Q!QpZ)psFJCw-qG%ae$Wf}Dtob6b zmIz)S14oGk0bdKf(K4}d%{+JW*6M@JCyzJR*B%%f8|mr6O7JSt1} zJ+M7xWJ01g9XT|qF`-(gufO>Fi$$Bz;NXA=CnFoWSkJ)9G{}|}_vCNi{qoHoux}_U zkvW8YI$N>}hSS4lP$(4Zh_&<$_4F+qgLd7%dF%el`r{{$AHn|24U9w_B}U(&BWF(^ zZsTFlxQ3970n0JWP)T;X9MqtI#2{qnMSlM2j~iO9;UP}Gfp%7w8VI^Hck;5Z5Y;{T z``6!m$p!T!)@!U4D2RX#!00e^tUg*g21aIPCWf|w<9AnX-`iMuwE6fk#=rj1$jCsG zt3V$(wExt}#XE-#>I*LDW3y%F*tk64fGNXi2R87uwczrD!NijHz|2zIhJ zb2GJbcXDy^ax!;tJoxrk-~Pjo93IHjg)hZAiz7Az(idnOLFu**4k0zi@7{g5zOlKv zx&HLYqxJQ*wTA{qMw%K5jNQAAOirDHMnow^N^jd zKwHNcQcoVbczfmH{ngdAN9&tUo}yvyJ$&$RV|~*^M_UBK(p`t9#@nX)!stwnP>6bP zxDX_D9%N;~4M6Ty$;B*%rgZkJlaz$wC|4(ts9W2b8(Vn50zADO9sQG|7e4*!Gtj)j zcMyEgO=#+Ai*+q^otqc#-n?^X<>BMU8=FryHl93v_Waq?jfeN|uWdYfWT0iBtwH1N zpPCw}8&A^afUJRRktr&CZJvS*96t%QLA3>t%i(ao{E3!sSwV1slZ%U;k(Hs2uA{Am zrHi+lgKt<$=kqr|K?nfWFtU}=;EBXK#+J5G!`GIs-d%g}^wHy|kJr~=a!=lT{?@Zc z8xQW^d$95Nv5weC4>z;9(=+3BwdPPU4W~O*MOkEGvNT238Zv0FV{3*EP$;r1&lPP8 ziXvStTpg`-O?5>AGcz3ndrxa)_rRo*BOiVg2R2$Vesv0qF0wSY$U2FFhz}nn!@&NBn?_UGtS46 zPI`ds5M)Lx-QN@jR>k`HxSG4z8|jO&>lN|EW^Q%{_Tk|zf-mX1@3Q*|Vpc4yO`j_IMLE2Y37M;o8IX$4{R=-h8lncje*6kxO+k$%w(@kdbk9o4ExK5uMB|`-EYqrJ_U8H{i2Kb)8ym1PROx}gA^vV{P5Sd1 z8vf$3rIop%CgMJM0s}*lm<_IAgpwi91R)0sN}A>7ZZZBefmW!Vr>qRT!PP^w^33Q! zJ98^ba|Y5R6=)Z%gAh-IA~Lz zl^Kj<44boU3nC5WWVeFalV@n8<&~3M(U_sAz~tyv-&p_P`J0c|*ViAd6CBcjxV{GY z<0C8rSDR==E~ciYdO{o@xjOn#|YUvaZ;O$~#qRm&9k@e4MD%j4m9lE{o?)x7G8%gk~G`^{&p|L)nfz1+!3Kk$W4UVEqU1x+zl%=tWp1r0fUDZS+ zc+Q&+hv5gYmQlRX;@7jZRKmPp9wR@}g?%#g^ zedm2GHcVxXsq%Og!b12aGoVFy`FJrTBXu3ny2t|B8j zH648?rpe9(TYKM7r@(~x@Q~CL1c6r6RTSl=g!$?ib4`mY&5oa1T6^ci&9%E^i?Q*D z7=ff=$fT`pV9_D#&j0Yu%ne0|{S|5(o0}Vo1$4Him`zwm)mg5H56^Im^-WH3^3WA> z=>oB%vP^WMn}1l8cXVQEQd~ZAJgVy22RbXWvm<@&0)iqc8upyOwfXGnqt#Wc^4MWM z`7gc%9bX7O`SU+~8#7ZbUcd!HGgAvAT>)Q>DiAVIa*N8W>KYoY2o8>`D)MsF6>73M zx+Zj9s#j=Oc65ANR8CG|>%idXKx1oHOKn+IMsk>AOmcN))3K{J9&D@-xBK`R9NmTl zX$?F5HLN%fB)Z48^FFZ!SmLIQuDPuNOM$MYX<}ezs4v2a3KyB`a>^9P_TjNzwQ(WQ zwWTh$raCy^3RsG!vHpHBiD@V+o1IZPh#btGmd@7d%JS;m_>{2t$SS|Qp(6`R_inFZ zz1w{HjBN2CdHlgvPtu4UV2RQWtgUaD7+4r$2h6rGGcmF<(ACw^5`g=iE~~;S8V7?% zeQH=}^$u9LIiJPT7g24zVuM0lQ_{nNV&h^msvD|`(=%ecy+YFR^I~%X;|uK~%A1Z| zdVrx~tk`I5ZoX1H*4ujz9^7Ar3LXIe!F}|Gh(O0g*I0|E&a1RDF|jog8|&+d5&8@k zTD#%tsfm3}X;D!%72b{p=9*>>1~M`(0l|TpVF9rTIT@+pu9lY8wuTmtKG6}O+35)} z(cyW1ZZSDyGdDNZHpqA%!qZ^9WZ-L9@6c8ypC!KnI>e$Y78!{JjGBG}LlaXok+HF% zHX^c_imEAxj!hrj*P0Ta+*0G?U}|q=gmqBeKO!(8KiM%ZDLfGLM?UTj=GGpbff>0O z84;-o$x%6R22SZc$4{-0{T1Md@X28B-zVTJE31$J`hwpPbfW)AN=}OolQ)s8VPtA( zsb^$n0(Z<|t7-2%eC)`7BLC4lWqGjEcNCG&H6(E8ZhDGCVmoy}Z4>srd@yp-y)FjN1k<1=&1o6nwXO00kk9u41!LzLp9510TF{71{( zIF+h|I2jw7+8P*Im|L0(nL-^-cISzcZ|pnJUy_~N)}8EY#O0yPld5-oZh2F^zqc_* zP2bif(A&z$Ho7uDAtN=)KR&j)v?PB=vWaWa@X?buS2vzMS-(#VUD|NefPgIqk9hyW~OHJ?Q0A%&~q*dq$tN^ z<`%a^_=P5f8q#pZCor?&bNOr)Cf(FMKC8H`C?zMGZC6&?d};aC-Hpd7HM6xeM`*iQdH0#KXAe!BZp%o>-hHsaSI^qR zhbm(hmY%sI)73mGHZL5vPFuSYJNXv-HwNJ4zx6`q8b}%>7=28sHrjO3dPwhKCm=l*Z zF;VYiON*!Hbr4*nN^eFGFh=6YhjhRDRi%F@i%$==3X%%ia#dyiZ^ zd+g}Jp6uA{{kt1HTq}FJh9i=)qD$kGZRB*5i>o_Zd)oRYhDSTvODk(D3)4gl^T7DD z;`Fq=!v(ebPF}vfh6zBlEX9}3Ky(4~ko-o{8SF+pjmPpV3|MSyeT>~1RV?hyw6O}Y z45t^)otc6OZ((Kq5hgqV0~(Yd?Eq$>#0534CZoGeC?}_Q2%;fC-<8@gjE!7N)bj4F61@|2vKQNYWMgjaYpTbi zm^iz7`MA0`Iy;ydXlv{03iWgfj!sSwWTj_K?k>!$DoQJ^%L)pKO%Kn{N(*E9*H#pl z6#DUH)I8I{Mi-&5#W6BIB`G&Qt#qKawytUprImk1uck)Urr zdSvG2YU`=RXsxVw4T8|fJs2y}Jy#35rxM{?704(&{jC6$NT!UOo8LRlrgo4z};;fv60N>D>?(+}V*H-Qj`AhM!_azco zB}11kiDdMTAKN>-yI5;6xrX+>0p6Z&s9<7aZj5?P`X<0h1 zZ3*^E$}Vs1Dm3O;^L2beGxGB?JZ$8}+jLTEYYKPdrxj<$r@Iw3ruLnL{o#apPcrS% z1+w)AwsbW_2c#R*%}r-_oMg2aY-0!CKtC^(Z1J|YGS)ZNH#IXf_<$u2JnHmNE|uWlI~>1=F6m38yLrta>Y`KhUyDRJ3VsRb3aCvQF6 zxVv(1^_AOsxCVcVeFs^4qzfMwAJQCMz1(eeHP}XWUVc6v?(ROmj#fsxM*1ce7A9H@ zUT$G_+0m1&;M;4gj`H?)56MaKH!^Z?vS%cBHFS<0J8*Dv*I0Y4S9xPiTU&8Jca|3PC-DEeGh?`?1Cj5v&~IA6mM}f;_-}ZJ$z9Z4K>l6Ee*8| z^i0g`O?3?!>OSeU2ana4r{;H-`#AgiMr8Vg74;35#Mqb=Hup`QK74p`-=Ur`H&08e zu(F!k!myb5*usu9_o~seHzDyy*m%9NcPN95UxE+IhYHC0yJ3av12=P#rn$YRzqgw! zDi*p}8H)9FElgks+QQc~aw-lTZ>SFmDlhhM3X6;lb1&Y1{M6J~tG7pO@8prmW5-X9 z`&s4G?5wJ6=x8a9j|__|?k@Gq88~uzbrmj9f=^}}>4p>@g22TS8WDk&JxY?|kxp!p_|+bC9ZXmJ@_lS-PcXxEd<&GJj%G9>8b1>A= zRnW zepZrqak*XViQ7v==0x}ZZ2$%?hL5|KSASva>=}ULg~0x}RbZ^AgDJ{_c^HY*`Dx9T zHd+R{N`ZkgrdEPTZzt#2d=GD9ZEc4b0t59t{r%nZ3f=6vY&E8BMtx~gdR%!|q0WxMeZxg%jTzB9 zE7}LziX#e3QxO3gdNiAkBIq^27G|7!XZn0gq^TND(MLks0L=h;jivC z-Ephe#wR?)UhI*#BTH4NtFLXK?iwQNR$#2}>Jt=@;-RaJ7X&SB7bpAB=;XvSy{f%? z>K&>|b7I2slB4}y+`}UM!{UpATx>#<=p~1*-Mxk7_EjT&0Q_CD2bFw={-KA}nmW4x zegC=}UM%Bh0 zQF&2*CB5w#A@K>e;eM`|v|6|f_fCyW4R*BiVsZP<-n_iLwoG}|9ewC+l9alE*uFjhxYAMjyQ!g;H=5oB2$UsL+)!Rt$`{ZIqvdI7mF3nVMTgk7j{3^XATy8lv`D&=n#epprs|}TR)D=j zczWEx-fOpR+*!VH8|@a_ojZV*3WLDkC6++6^89b#e*Te{AtUkrhwneT<80>W;pb_k z&16``gxVPD>&m*j;xbM@I@rn9+TDmk!BwatQb3iMX5rPNqXP#f2kKKj*&MIRq}<<>e(Qfhcnqofeu` z*V@prf8R($W=w!RQ%1ww%h}07nVKHTwhd3xGc7-Vb!qwL(hWf0M1ve41cFc6ZHOO@ zn7~)>z58CSp#>xDlW8+sus%3?1o+q)>FC>ed%Icb>MGhfZ^f;cW0;R$Ol}3@^-B`` zP|eZTH@B{>p|)f9@IXgZb_@>TdQ4o~YigQUcsToH1d6n>=WZUSd;{AbR z>E<1ioKfD`P@R_?pO{uy1(vApZ#8Ksu|m7;~Nqd?C%Z-Z+B-Qhedyq!iM`;NoocJ8tt`o}El9D`S5%?<%$%QHm|s|22IexpEH7QVwzPB& zKVL^9CW(H)Im9^mCCA1)8=1JGc4SmkSaftusDqBCjuoocdRmzC0{msY*7dV- zN-yv1YiaH38``yh^6-K2J$v>ZJv=rwwQuy;(TR!9?z)!Bih_n5SJ4(Zb&kjE+{~qm zSFQk4f_rW8+TzmU5wUK#a@ z9UO(eMEThoXqos1M>-o@nyI_^Zy_`gNdAwQV%((byRJiu=^$Cbb z%*e@413_x8A4g`3th$=4EQM=-^xUQS%ZoRF4HW==3GJ0IhzX!8=+E_=E`EMd32}CY zF3whNQDKpwQxErZHq|i+it+R^)z_!HMs5W&r>d%~I#o$_+iNm1uPLe{p+d|9j*5&3bhk1vbC2@1v9i=*xJ7N};7ygpL8*WhSXCBX*z)>TStMf0 z%Ypz|4$1d;Glp_rC!Fa@h~h?l9J(Oljk$%nE7xulp-TXzgC;)_;A__w7q2cZUR(0Q zOL|PCtr03{diV$WM~8(+1i4umnq_(h7;BrFP(4Dna}ZF0dN)W;Q32(^77P*bf(mGO zriNwUu|RJ7HWDg;NHrv7Q&BOCB8)pZcje;JwOg=zj2}oCE07n7Gzc)hUcvW8pAi4h z=tz4L2RBFW(D>lc;IOD*2V+Cy!>KklTH40^0B?CV2?~-2K_Dd0QbRO~tb&R(|6f@S zFY^kL@@=G0f;?F3kf5(BuRzyMJ8}N<#cPYV2^f&1kc;?!^~#kiSD}W-K;(0^TK^YL!$z%b#=6lUNzA&^t5G%xhvsKkwzgY7!UyPLPypv}y&Ro5Ei(p?~xP19CzF!6Q<;x2TmtWySG)sQ* z-o8;0?iS8o4tBAR1A}~m!+gzjdAzpIlPo3%9R$hF>O7RtR8f|tf{qMSc~oVTCubmB zozxmYJx6p!7Mb^oYFoD}Y-7l(ZbJ%%x|(MGiPINn=NGPCzOuLoP%>yq!*}qOVCYxj zmzHnb3<>x0P6)Ec?T?Xjz;`{~Uf|ZV=5y52U);EL-`&U{-A-A|rZT_?2VN0XrZQ8N z#oEq7ngO2X5iCXO{(&a%4oIZX2+?hFu9;lGN zRPvWDNwom`Bk*uV{{9K)UEG{)TwN`7?BDvUW6#Ggk{rC_hF9NOJ7%b5XIf;aYGy9x zBI6&t0^6ywNR#6z(bcyrE7B=iY%v?bz$}593OlOu(TW#C5d;~k;NGX1G#ov1?#!u~ zim#nu?wzRfeL7SSuYiRa-+S&@uFWIcy#ilouy5UA>YQ`$xKI!hJto-@)Q{*tuW@6u@l2Jp^mlzmH{r_Edxu-Sq8x& zGFyF)kM-_0um+2VMfA~&SLRT_arVO8+}s6xxriPBo8S|3gc^trmX~eJ)Bf`)D$Lh4 zV*33bKmY2pkBe%H6k5 zXLzzr6J|f%SYMvJc{yoda`)BaYTLAzm#e(Id(T{4n!hwNGjm~Xb{1Wjg$U4vOHc)w zfsntX2TRK)ZjHbH`OZd}yN^-$@vlC7|BDZTUF@86O`^Wr&9iUvacR>NI|uqWS|?rY zr(s)hbnd`#-rj}VXHEs$ZIJ{0KZS16KY4qjt3UbdT)&zVhkNknuP=1&o}2~#!p!-( zd5rzS#S3$Qzi{EgMJNJ&xFj)0NCCd_%OC#v<4>o2orCOMoi;!J@RxsHG`BR;wg~+$ z6#+v|_RS)ZTZEsNY1Ffi8suf+k;c;{8H~7lIl* zzIZP&{mJK9hGxd5;phBqZLDnFoA?6fFkesopvBMMixkLg-6G=|i-L!mN!RC&bUpd* zn=@GC9JpE>wJ>FdqI>t5ufO>I#Q-)Q^B2HO1T={M|HUVIAT~*+gaqGs=*u6!{pOIV zYp}1A(+3yy9o*{MEDi0gJW}m(e{s2ro*f6 zA`o)r!r*>ywiRDZ=5@c_4_^F?oc!tI$7ilxx;Q&CGkftOh79e`UXYG|b{49@Z;3U+ z7r_VV8?3+l`S17QyaL_byq{J(*!eg+IJg-)xu8$Zf zs`fS+Wg%BEdAat+uZa5l?pg6ntXN0SFzdvf?|%6E&p*F>cYONXmCKjrX3ox>htjbR zCHN3OkYNf?0BJdONMe*69VpTGV2<*x@O z&n{kGx^(e8tnbn#qJL<79!)XfELC_d!sF(O$faZ)D^bF9Dp`9f2o5UxZJZ#{^h3b9+(mWn^d= z=5OT`=^Yl87#AGie9lEg?RwaFI-u;0M`S2;)DKAd)L<6MiEB; zK|un98W1ED=SE1AIG6RVz23FG>%Dk=+uDf}(pJ3CB9O`hR4Of@kw_FNfYm}x+9}q?50Z{>kG?hTDsCY;uggN6#h+n{y=gpF(^Uay_oo{CL^BMtV z33dvDM3KvvO1-9!RhDY+5dG{xAm9%AM>QvJ=On++gWk3-*JQ&$FsRGTy+@kZPVN?d znzenX>8Z!w_~gcoW4BkYQ0f04MlBUtWeC zr!n(dGX9HCOAd3(UO}}miccR3gghL}<6n(*P3ogMrLoXuf7q^0 zH;QkH2L*a`;m7aJji|335?*bk?&IYL<%K9wcMi@9_(X!}p9BEgLDnH35K$IYIh`S% zgjc5Gi$;oZistq%Nn_25&o|`PRhG8crO==vT6^2IDNOt}=%`f~CixOgz%T%MR|vy(c@YR-4dmzWq`! zTB!#&wyL+n)eDiotDCc(*1*)vxkxmzl)98h#Vr=|ndLaauStzqFK~$!?IZ&jKqit( zv|6T2lmqx(nr^d5F1PN}6+DN-=|AKbX>*hM&aUSv&8O@!j`u6GZG-seXTSq3w z+_Z_YisSRyIFuDzOs>Sp(xTwQ{sDve*Xj;dp_YY^0+1n*(kj4Feb=w>mVTaNTDrVJ ziI)|pFEFTa7S^tf47y}~@|EsMLnFm`Lapsn{RYm(+00*V{`ULES)*OC^~b91Q@S!9yyWY0lNFN1&}HhkD&0o))P$mXw7UIV_J?c+G+1*1NqIdJ56 zhxYsIR;$D78}>-dskAz^yZ!J1r>XUc6SSet^7{N&ckgZ9y~)bcADwyc=vX*1cM*N7 zvBgw65l>uLNM_TJJz=1R54Wu`Pr^U|B$7ftvyv~60}xKb<+aOpwx8wEN*qKBktPe; z=s{iNZBGBlkfIuo{rcet@5WCW74hj^CybAqPHk-cdFQ8}f4MHaJR3QG{_~mma(*c~ zyO0EEJpRSZe5Qmr;!ycc008~31V|o&h)fap0LVRZBTymrtUl@t)ETHVP-mddK%Id) N19b-K4E$dV`~%WG*USI_ diff --git a/Tests/test_file_fitsstub.py b/Tests/test_file_fits.py similarity index 51% rename from Tests/test_file_fitsstub.py rename to Tests/test_file_fits.py index c77457947..68d708fb1 100644 --- a/Tests/test_file_fitsstub.py +++ b/Tests/test_file_fits.py @@ -2,7 +2,9 @@ from io import BytesIO import pytest -from PIL import FitsStubImagePlugin, Image +from PIL import FitsImagePlugin, Image + +from .helper import assert_image_equal, hopper TEST_FILE = "Tests/images/hopper.fits" @@ -16,6 +18,8 @@ def test_open(): assert im.size == (128, 128) assert im.mode == "L" + assert_image_equal(im, hopper("L")) + def test_invalid_file(): # Arrange @@ -23,23 +27,14 @@ def test_invalid_file(): # Act / Assert with pytest.raises(SyntaxError): - FitsStubImagePlugin.FITSStubImageFile(invalid_file) - - -def test_load(): - # Arrange - with Image.open(TEST_FILE) as im: - - # Act / Assert: stub cannot load without an implemented handler - with pytest.raises(OSError): - im.load() + FitsImagePlugin.FitsImageFile(invalid_file) def test_truncated_fits(): # No END to headers image_data = b"SIMPLE = T" + b" " * 50 + b"TRUNCATE" with pytest.raises(OSError): - FitsStubImagePlugin.FITSStubImageFile(BytesIO(image_data)) + FitsImagePlugin.FitsImageFile(BytesIO(image_data)) def test_naxis_zero(): @@ -48,16 +43,3 @@ def test_naxis_zero(): with pytest.raises(ValueError): with Image.open("Tests/images/hopper_naxis_zero.fits"): pass - - -def test_save(): - # Arrange - with Image.open(TEST_FILE) as im: - dummy_fp = None - dummy_filename = "dummy.filename" - - # Act / Assert: stub cannot save without an implemented handler - with pytest.raises(OSError): - im.save(dummy_filename) - with pytest.raises(OSError): - FitsStubImagePlugin._save(im, dummy_fp, dummy_filename) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index bd44f63a3..0a40a0b0b 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -1064,6 +1064,11 @@ is commonly used in fax applications. The DCX decoder can read files containing When the file is opened, only the first image is read. You can use :py:meth:`~PIL.Image.Image.seek` or :py:mod:`~PIL.ImageSequence` to read other images. +FITS +^^^^ + +Pillow identifies and reads FITS files, commonly used for astronomy. + FLI, FLC ^^^^^^^^ @@ -1354,16 +1359,6 @@ Pillow provides a stub driver for BUFR files. To add read or write support to your application, use :py:func:`PIL.BufrStubImagePlugin.register_handler`. -FITS -^^^^ - -.. versionadded:: 1.1.5 - -Pillow provides a stub driver for FITS files. - -To add read or write support to your application, use -:py:func:`PIL.FitsStubImagePlugin.register_handler`. - GRIB ^^^^ diff --git a/docs/reference/plugins.rst b/docs/reference/plugins.rst index 7094f8784..1ef5d7230 100644 --- a/docs/reference/plugins.rst +++ b/docs/reference/plugins.rst @@ -41,10 +41,10 @@ Plugin reference :undoc-members: :show-inheritance: -:mod:`~PIL.FitsStubImagePlugin` Module +:mod:`~PIL.FitsImagePlugin` Module -------------------------------------- -.. automodule:: PIL.FitsStubImagePlugin +.. automodule:: PIL.FitsImagePlugin :members: :undoc-members: :show-inheritance: diff --git a/src/PIL/FitsStubImagePlugin.py b/src/PIL/FitsImagePlugin.py similarity index 63% rename from src/PIL/FitsStubImagePlugin.py rename to src/PIL/FitsImagePlugin.py index a3a94cf4b..c16300efa 100644 --- a/src/PIL/FitsStubImagePlugin.py +++ b/src/PIL/FitsImagePlugin.py @@ -2,44 +2,28 @@ # The Python Imaging Library # $Id$ # -# FITS stub adapter +# FITS file handling # # Copyright (c) 1998-2003 by Fredrik Lundh # # See the README file for information on usage and redistribution. # +import math + from . import Image, ImageFile -_handler = None - - -def register_handler(handler): - """ - Install application-specific FITS image handler. - - :param handler: Handler object. - """ - global _handler - _handler = handler - - -# -------------------------------------------------------------------- -# Image adapter - def _accept(prefix): return prefix[:6] == b"SIMPLE" -class FITSStubImageFile(ImageFile.StubImageFile): +class FitsImageFile(ImageFile.ImageFile): format = "FITS" format_description = "FITS" def _open(self): - offset = self.fp.tell() - headers = {} while True: header = self.fp.read(80) @@ -75,26 +59,13 @@ class FITSStubImageFile(ImageFile.StubImageFile): self.mode = "F" # rawmode = "F" if number_of_bits == -32 else "F;64F" - self.fp.seek(offset) - - loader = self._load() - if loader: - loader.open(self) - - def _load(self): - return _handler - - -def _save(im, fp, filename): - if _handler is None or not hasattr("_handler", "save"): - raise OSError("FITS save handler not installed") - _handler.save(im, fp, filename) + offset = math.ceil(self.fp.tell() / 2880) * 2880 + self.tile = [("raw", (0, 0) + self.size, offset, (self.mode, 0, -1))] # -------------------------------------------------------------------- # Registry -Image.register_open(FITSStubImageFile.format, FITSStubImageFile, _accept) -Image.register_save(FITSStubImageFile.format, _save) +Image.register_open(FitsImageFile.format, FitsImageFile, _accept) -Image.register_extensions(FITSStubImageFile.format, [".fit", ".fits"]) +Image.register_extensions(FitsImageFile.format, [".fit", ".fits"]) diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index 45fef241e..6352e088f 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -30,7 +30,7 @@ _plugins = [ "DcxImagePlugin", "DdsImagePlugin", "EpsImagePlugin", - "FitsStubImagePlugin", + "FitsImagePlugin", "FliImagePlugin", "FpxImagePlugin", "FtexImagePlugin", From 95c17a8334f91f39669cb4333b3e850f552caa50 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 15 Feb 2022 17:39:56 +1100 Subject: [PATCH 383/633] Replaced _MODE_CONV extra with bands length --- src/PIL/Image.py | 55 ++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 02b71e612..ef27d6145 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -210,38 +210,39 @@ else: _MODE_CONV = { # official modes - "1": ("|b1", None), # Bits need to be extended to bytes - "L": ("|u1", None), - "LA": ("|u1", 2), - "I": (_ENDIAN + "i4", None), - "F": (_ENDIAN + "f4", None), - "P": ("|u1", None), - "RGB": ("|u1", 3), - "RGBX": ("|u1", 4), - "RGBA": ("|u1", 4), - "CMYK": ("|u1", 4), - "YCbCr": ("|u1", 3), - "LAB": ("|u1", 3), # UNDONE - unsigned |u1i1i1 - "HSV": ("|u1", 3), + "1": "|b1", # Bits need to be extended to bytes + "L": "|u1", + "LA": "|u1", + "I": _ENDIAN + "i4", + "F": _ENDIAN + "f4", + "P": "|u1", + "RGB": "|u1", + "RGBX": "|u1", + "RGBA": "|u1", + "CMYK": "|u1", + "YCbCr": "|u1", + "LAB": "|u1", # UNDONE - unsigned |u1i1i1 + "HSV": "|u1", # I;16 == I;16L, and I;32 == I;32L - "I;16": ("u2", None), - "I;16L": ("i2", None), - "I;16LS": ("u4", None), - "I;32L": ("i4", None), - "I;32LS": ("u2", + "I;16L": "i2", + "I;16LS": "u4", + "I;32L": "i4", + "I;32LS": " Date: Tue, 15 Feb 2022 18:01:02 +1100 Subject: [PATCH 384/633] Merged _MODE_CONV typ into ImageMode as typestr --- Tests/test_image_mode.py | 2 + src/PIL/Image.py | 42 +++------------------ src/PIL/ImageMode.py | 80 +++++++++++++++++++++++----------------- 3 files changed, 54 insertions(+), 70 deletions(-) diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py index 0232a5536..670b2f4eb 100644 --- a/Tests/test_image_mode.py +++ b/Tests/test_image_mode.py @@ -21,6 +21,7 @@ def test_sanity(): assert m.bands == ("1",) assert m.basemode == "L" assert m.basetype == "L" + assert m.typestr == "|b1" for mode in ( "I;16", @@ -45,6 +46,7 @@ def test_sanity(): assert m.bands == ("R", "G", "B") assert m.basemode == "RGB" assert m.basetype == "L" + assert m.typestr == "|u1" def test_properties(): diff --git a/src/PIL/Image.py b/src/PIL/Image.py index ef27d6145..2977cfd38 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -208,44 +208,14 @@ if sys.byteorder == "little": else: _ENDIAN = ">" -_MODE_CONV = { - # official modes - "1": "|b1", # Bits need to be extended to bytes - "L": "|u1", - "LA": "|u1", - "I": _ENDIAN + "i4", - "F": _ENDIAN + "f4", - "P": "|u1", - "RGB": "|u1", - "RGBX": "|u1", - "RGBA": "|u1", - "CMYK": "|u1", - "YCbCr": "|u1", - "LAB": "|u1", # UNDONE - unsigned |u1i1i1 - "HSV": "|u1", - # I;16 == I;16L, and I;32 == I;32L - "I;16": "u2", - "I;16L": "i2", - "I;16LS": "u4", - "I;32L": "i4", - "I;32LS": "u2", + "I;16BS": ">i2", + "I;16N": Image._ENDIAN + "u2", + "I;16NS": Image._ENDIAN + "i2", + "I;32": "u4", + "I;32L": "i4", + "I;32LS": " Date: Tue, 15 Feb 2022 21:50:20 +1100 Subject: [PATCH 385/633] Replaced absolute PIL import with relative import --- src/PIL/BlpImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 7b78597b4..367c4f479 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -347,7 +347,7 @@ class BLP1Decoder(_BLPBaseDecoder): ) def _decode_jpeg_stream(self): - from PIL.JpegImagePlugin import JpegImageFile + from .JpegImagePlugin import JpegImageFile (jpeg_header_size,) = struct.unpack(" Date: Tue, 15 Feb 2022 22:30:12 +1100 Subject: [PATCH 386/633] Avoid circular dependency --- src/PIL/ImageMode.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py index c76ad3993..318f13728 100644 --- a/src/PIL/ImageMode.py +++ b/src/PIL/ImageMode.py @@ -13,7 +13,7 @@ # See the README file for information on usage and redistribution. # -from PIL import Image +import sys # mode descriptor cache _modes = None @@ -39,13 +39,14 @@ def getmode(mode): if not _modes: # initialize mode cache modes = {} + endian = "<" if sys.byteorder == "little" else ">" for m, (basemode, basetype, bands, typestr) in { # core modes # Bits need to be extended to bytes "1": ("L", "L", ("1",), "|b1"), "L": ("L", "L", ("L",), "|u1"), - "I": ("L", "I", ("I",), Image._ENDIAN + "i4"), - "F": ("L", "F", ("F",), Image._ENDIAN + "f4"), + "I": ("L", "I", ("I",), endian + "i4"), + "F": ("L", "F", ("F",), endian + "f4"), "P": ("P", "L", ("P",), "|u1"), "RGB": ("RGB", "L", ("R", "G", "B"), "|u1"), "RGBX": ("RGB", "L", ("R", "G", "B", "X"), "|u1"), @@ -75,8 +76,8 @@ def getmode(mode): "I;16LS": "u2", "I;16BS": ">i2", - "I;16N": Image._ENDIAN + "u2", - "I;16NS": Image._ENDIAN + "i2", + "I;16N": endian + "u2", + "I;16NS": endian + "i2", "I;32": "u4", "I;32L": " Date: Tue, 15 Feb 2022 21:45:35 +1100 Subject: [PATCH 387/633] Use ternary operator --- src/PIL/Image.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2977cfd38..78149e091 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -203,10 +203,7 @@ ENCODERS = {} # -------------------------------------------------------------------- # Modes -if sys.byteorder == "little": - _ENDIAN = "<" -else: - _ENDIAN = ">" +_ENDIAN = "<" if sys.byteorder == "little" else ">" def _conv_type_shape(im): From fbd23bbf2839d70e3f82c2c35a7426fbdbf5db13 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 15 Feb 2022 21:47:12 +1100 Subject: [PATCH 388/633] Clarified code --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 78149e091..c13448dce 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -208,7 +208,7 @@ _ENDIAN = "<" if sys.byteorder == "little" else ">" def _conv_type_shape(im): m = ImageMode.getmode(im.mode) - shape = (im.size[1], im.size[0]) + shape = (im.height, im.width) extra = len(m.bands) if extra != 1: shape += (extra,) From 9d3c8d27142d44d1fca0d5096c6e19386cbe4602 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 15 Feb 2022 22:19:34 +1100 Subject: [PATCH 389/633] Added further typestr entries --- src/PIL/ImageMode.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py index 318f13728..0973536c9 100644 --- a/src/PIL/ImageMode.py +++ b/src/PIL/ImageMode.py @@ -57,13 +57,13 @@ def getmode(mode): "LAB": ("RGB", "L", ("L", "A", "B"), "|u1"), "HSV": ("RGB", "L", ("H", "S", "V"), "|u1"), # extra experimental modes - "RGBa": ("RGB", "L", ("R", "G", "B", "a"), None), - "BGR;15": ("RGB", "L", ("B", "G", "R"), None), - "BGR;16": ("RGB", "L", ("B", "G", "R"), None), - "BGR;24": ("RGB", "L", ("B", "G", "R"), None), - "BGR;32": ("RGB", "L", ("B", "G", "R"), None), + "RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"), + "BGR;15": ("RGB", "L", ("B", "G", "R"), endian + "u2"), + "BGR;16": ("RGB", "L", ("B", "G", "R"), endian + "u2"), + "BGR;24": ("RGB", "L", ("B", "G", "R"), endian + "u3"), + "BGR;32": ("RGB", "L", ("B", "G", "R"), endian + "u4"), "LA": ("L", "L", ("L", "A"), "|u1"), - "La": ("L", "L", ("L", "a"), None), + "La": ("L", "L", ("L", "a"), "|u1"), "PA": ("RGB", "L", ("P", "A"), "|u1"), }.items(): modes[m] = ModeDescriptor(m, bands, basemode, basetype, typestr) From 10e1731149ebd6dde5f9d4e9ca502ea8a17f9955 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 16 Feb 2022 07:39:35 +1100 Subject: [PATCH 390/633] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 12a303613..826caa783 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 9.1.0 (unreleased) ------------------ +- Attach RGBA palettes from putpalette() when suitable #6054 + [radarhere] + - Added get_photoshop_blocks() to parse Photoshop TIFF tag #6030 [radarhere] From 948c064b282f80492f5b88d3f06a599b76516edf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 16 Feb 2022 09:56:13 +1100 Subject: [PATCH 391/633] Allow getpalette() to return less than 256 colors --- Tests/test_image_putpalette.py | 1 + src/PIL/Image.py | 6 ++---- src/_imaging.c | 9 ++++++--- src/libImaging/Imaging.h | 1 + src/libImaging/Palette.c | 1 + 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index 725ecaade..3b29769a7 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -74,4 +74,5 @@ def test_putpalette_with_alpha_values(): def test_rgba_palette(mode, palette): im = Image.new("P", (1, 1)) im.putpalette(palette, mode) + assert im.getpalette() == [1, 2, 3] assert im.palette.colors == {(1, 2, 3, 4): 0} diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 8d36e8871..449d29c44 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -821,7 +821,7 @@ class Image: if self.im and self.palette and self.palette.dirty: # realize palette mode, arr = self.palette.getdata() - palette_length = self.im.putpalette(mode, arr) + self.im.putpalette(mode, arr) self.palette.dirty = 0 self.palette.rawmode = None if "transparency" in self.info and mode in ("LA", "PA"): @@ -833,9 +833,7 @@ class Image: else: palette_mode = "RGBA" if mode.startswith("RGBA") else "RGB" self.palette.mode = palette_mode - self.palette.palette = self.im.getpalette(palette_mode, palette_mode)[ - : palette_length * len(palette_mode) - ] + self.palette.palette = self.im.getpalette(palette_mode, palette_mode) if self.im: if cffi and USE_CFFI_ACCESS: diff --git a/src/_imaging.c b/src/_imaging.c index 2ea517816..0888188fb 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -1063,7 +1063,7 @@ _gaussian_blur(ImagingObject *self, PyObject *args) { static PyObject * _getpalette(ImagingObject *self, PyObject *args) { PyObject *palette; - int palettesize = 256; + int palettesize; int bits; ImagingShuffler pack; @@ -1084,6 +1084,7 @@ _getpalette(ImagingObject *self, PyObject *args) { return NULL; } + palettesize = self->image->palette->size; palette = PyBytes_FromStringAndSize(NULL, palettesize * bits / 8); if (!palette) { return NULL; @@ -1672,9 +1673,11 @@ _putpalette(ImagingObject *self, PyObject *args) { self->image->palette = ImagingPaletteNew(palette_mode); - unpack(self->image->palette->palette, palette, palettesize * 8 / bits); + self->image->palette->size = palettesize * 8 / bits; + unpack(self->image->palette->palette, palette, self->image->palette->size); - return PyLong_FromLong(palettesize * 8 / bits); + Py_INCREF(Py_None); + return Py_None; } static PyObject * diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 9b1c1024d..b65f8eadd 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -143,6 +143,7 @@ struct ImagingPaletteInstance { char mode[IMAGING_MODE_LENGTH]; /* Band names */ /* Data */ + int size; UINT8 palette[1024]; /* Palette data (same format as image data) */ INT16 *cache; /* Palette cache (used for predefined palettes) */ diff --git a/src/libImaging/Palette.c b/src/libImaging/Palette.c index 43bea61e3..174e58d34 100644 --- a/src/libImaging/Palette.c +++ b/src/libImaging/Palette.c @@ -40,6 +40,7 @@ ImagingPaletteNew(const char *mode) { palette->mode[IMAGING_MODE_LENGTH - 1] = 0; /* Initialize to ramp */ + palette->size = 256; for (i = 0; i < 256; i++) { palette->palette[i * 4 + 0] = palette->palette[i * 4 + 1] = palette->palette[i * 4 + 2] = (UINT8)i; From 54cb09d8b48cd6781a261621d596b614687c6246 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 16 Feb 2022 11:01:00 +1100 Subject: [PATCH 392/633] When converting to P, restrict colors to palette size --- Tests/test_image_quantize.py | 15 +++++++++++++++ src/libImaging/Palette.c | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 16cb8b41a..a36f789ff 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -108,3 +108,18 @@ def test_palette(method, color): converted = im.quantize(method=method) converted_px = converted.load() assert converted_px[0, 0] == converted.palette.colors[color] + + +def test_small_palette(): + # Arrange + im = hopper() + + colors = (255, 0, 0, 0, 0, 255) + p = Image.new("P", (1, 1)) + p.putpalette(colors) + + # Act + im = im.quantize(palette=p) + + # Assert + assert len(im.getcolors()) == 2 diff --git a/src/libImaging/Palette.c b/src/libImaging/Palette.c index 174e58d34..20c6bc84b 100644 --- a/src/libImaging/Palette.c +++ b/src/libImaging/Palette.c @@ -194,7 +194,7 @@ ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) { dmax = (unsigned int)~0; - for (i = 0; i < 256; i++) { + for (i = 0; i < palette->size; i++) { int r, g, b; unsigned int tmin, tmax; @@ -227,7 +227,7 @@ ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) { d[i] = (unsigned int)~0; } - for (i = 0; i < 256; i++) { + for (i = 0; i < palette->size; i++) { if (dmin[i] <= dmax) { int rd, gd, bd; int ri, gi, bi; From 852859476b07d4ca8244e5701377c1cc85df44a5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 17 Feb 2022 10:04:43 +1100 Subject: [PATCH 393/633] Added rawmode argument to getpalette() --- Tests/test_image_getpalette.py | 27 +++++++++++++++++++++++++++ src/PIL/Image.py | 6 ++++-- src/libImaging/Pack.c | 1 + 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_getpalette.py b/Tests/test_image_getpalette.py index 1818adca2..1690e411c 100644 --- a/Tests/test_image_getpalette.py +++ b/Tests/test_image_getpalette.py @@ -1,3 +1,5 @@ +from PIL import Image + from .helper import hopper @@ -17,3 +19,28 @@ def test_palette(): assert palette("RGBA") is None assert palette("CMYK") is None assert palette("YCbCr") is None + + +def test_palette_rawmode(): + im = Image.new("P", (1, 1)) + im.putpalette((1, 2, 3)) + + rgb = im.getpalette("RGB") + assert len(rgb) == 256 * 3 + assert rgb[:3] == [1, 2, 3] + + # Convert the RGB palette to RGBA + rgba = im.getpalette("RGBA") + assert len(rgba) == 256 * 4 + assert rgba[:4] == [1, 2, 3, 255] + + im.putpalette((1, 2, 3, 4), "RGBA") + + # Convert the RGBA palette to RGB + rgb = im.getpalette("RGB") + assert len(rgb) == 256 * 3 + assert rgb[:3] == [1, 2, 3] + + rgba = im.getpalette("RGBA") + assert len(rgba) == 256 * 4 + assert rgba[:4] == [1, 2, 3, 4] diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 8d36e8871..cabcf435a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1401,19 +1401,21 @@ class Image: self.load() return self.im.ptr - def getpalette(self): + def getpalette(self, rawmode="RGB"): """ Returns the image palette as a list. + :param rawmode: The mode in which to return the palette. :returns: A list of color values [r, g, b, ...], or None if the image has no palette. """ self.load() try: - return list(self.im.getpalette()) + mode = self.im.getpalettemode() except ValueError: return None # no palette + return list(self.im.getpalette(mode, rawmode)) def getpixel(self, xy): """ diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index 0c7c0497e..01760e742 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -574,6 +574,7 @@ static struct { /* true colour */ {"RGB", "RGB", 24, ImagingPackRGB}, {"RGB", "RGBX", 32, copy4}, + {"RGB", "RGBA", 32, copy4}, {"RGB", "XRGB", 32, ImagingPackXRGB}, {"RGB", "BGR", 24, ImagingPackBGR}, {"RGB", "BGRX", 32, ImagingPackBGRX}, From 6be87277f71948bc7e4b945c46660cac3e5ce919 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 17 Feb 2022 10:35:13 +1100 Subject: [PATCH 394/633] Allow rawmode None to return the palette in the current mode --- Tests/test_image_getpalette.py | 14 ++++++++------ src/PIL/Image.py | 5 ++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Tests/test_image_getpalette.py b/Tests/test_image_getpalette.py index 1690e411c..1a84b6928 100644 --- a/Tests/test_image_getpalette.py +++ b/Tests/test_image_getpalette.py @@ -25,9 +25,10 @@ def test_palette_rawmode(): im = Image.new("P", (1, 1)) im.putpalette((1, 2, 3)) - rgb = im.getpalette("RGB") - assert len(rgb) == 256 * 3 - assert rgb[:3] == [1, 2, 3] + for rawmode in ("RGB", None): + rgb = im.getpalette(rawmode) + assert len(rgb) == 256 * 3 + assert rgb[:3] == [1, 2, 3] # Convert the RGB palette to RGBA rgba = im.getpalette("RGBA") @@ -41,6 +42,7 @@ def test_palette_rawmode(): assert len(rgb) == 256 * 3 assert rgb[:3] == [1, 2, 3] - rgba = im.getpalette("RGBA") - assert len(rgba) == 256 * 4 - assert rgba[:4] == [1, 2, 3, 4] + for rawmode in ("RGBA", None): + rgba = im.getpalette(rawmode) + assert len(rgba) == 256 * 4 + assert rgba[:4] == [1, 2, 3, 4] diff --git a/src/PIL/Image.py b/src/PIL/Image.py index cabcf435a..305ef445b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1405,7 +1405,8 @@ class Image: """ Returns the image palette as a list. - :param rawmode: The mode in which to return the palette. + :param rawmode: The mode in which to return the palette. ``None`` will + return the palette in its current mode. :returns: A list of color values [r, g, b, ...], or None if the image has no palette. """ @@ -1415,6 +1416,8 @@ class Image: mode = self.im.getpalettemode() except ValueError: return None # no palette + if rawmode is None: + rawmode = mode return list(self.im.getpalette(mode, rawmode)) def getpixel(self, xy): From d4ee19199ca05839297749cc3f046fa5a4bc5192 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 17 Feb 2022 14:39:18 +1100 Subject: [PATCH 395/633] Replaced test image to avoid copyrighted color space --- Tests/images/pillow3.icns | Bin 1224971 -> 1333395 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Tests/images/pillow3.icns b/Tests/images/pillow3.icns index ef9b8917872e3790b9ae56456449645ebea6c420..49b691d90f29b8b8532f49feec08b083bc0e9d1f 100644 GIT binary patch literal 1333395 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≧!k5<=?c~%mTNF_#pPm^$sB!Er$2(d~Sfl;L*;! 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ȋ=zT*`a)+0bsG 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%0ACl3#rOi4rGuUlXDv;J%i@RNP@#KVCmdP&{R5 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

j6C^=K5>+UQPKTfrg(`5VH4YG|eBUCir@W~QmF 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