From 8fab24c8ab3d401e8473700f5d1a599b77f4c98e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Feb 2020 14:17:38 +1100 Subject: [PATCH 1/6] Added convert_mode param when saving --- Tests/images/pil123rgba_red.jpg | Bin 0 -> 4666 bytes Tests/test_file_gif.py | 12 +++++++++ Tests/test_file_jpeg.py | 16 +++++++++-- Tests/test_file_png.py | 8 ++++++ Tests/test_file_webp.py | 8 ++++++ Tests/test_image.py | 16 ++++++++++- src/PIL/GifImagePlugin.py | 7 +++++ src/PIL/Image.py | 46 ++++++++++++++++++++++++++++---- src/PIL/JpegImagePlugin.py | 11 ++++++++ src/PIL/PngImagePlugin.py | 6 +++++ src/PIL/WebPImagePlugin.py | 13 +++++++++ 11 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 Tests/images/pil123rgba_red.jpg diff --git a/Tests/images/pil123rgba_red.jpg b/Tests/images/pil123rgba_red.jpg new file mode 100644 index 0000000000000000000000000000000000000000..073a654366dd60e2420e99ad61ec88e17cf76c49 GIT binary patch literal 4666 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 z Date: Tue, 9 Nov 2021 22:09:17 +1100 Subject: [PATCH 2/6] Allow plugins to specify their supported modes --- Tests/test_file_jpeg.py | 3 +- Tests/test_file_webp.py | 4 +-- Tests/test_image.py | 67 +++++++++++++++++++++++++++++++------- src/PIL/GifImagePlugin.py | 7 ++-- src/PIL/Image.py | 65 +++++++++++++++++++++++++----------- src/PIL/JpegImagePlugin.py | 11 ++----- src/PIL/PngImagePlugin.py | 6 ++-- src/PIL/WebPImagePlugin.py | 27 ++++++++------- 8 files changed, 126 insertions(+), 64 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 9f755e8c6..9086c9b0e 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -672,8 +672,7 @@ class TestFileJpeg: img.save(temp_file, convert_mode=True, fill_color="red") with Image.open(temp_file) as reloaded: - with Image.open("Tests/images/pil123rgba_red.jpg") as target: - assert_image_similar(reloaded, target, 4) + assert_image_similar_tofile(reloaded, "Tests/images/pil123rgba_red.jpg", 4) def test_save_tiff_with_dpi(self, tmp_path): # Arrange diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 339580bde..e87cc8503 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -15,8 +15,6 @@ from .helper import ( skip_unless_feature, ) -from io import BytesIO - try: from PIL import _webp @@ -91,7 +89,7 @@ class TestFileWebp: assert_image_similar(image, target, epsilon) def test_save_convert_mode(self): - out = BytesIO() + out = io.BytesIO() for mode in ["CMYK", "I", "L", "LA", "P"]: img = Image.new(mode, (20, 20)) img.save(out, "WEBP", convert_mode=True) diff --git a/Tests/test_image.py b/Tests/test_image.py index 91ecd3c4f..b9d9d7451 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -7,13 +7,7 @@ import warnings import pytest -from PIL import ( - Image, - ImageDraw, - ImagePalette, - TiffImagePlugin, - UnidentifiedImageError, -) +from PIL import Image, ImageDraw, ImagePalette, TiffImagePlugin, UnidentifiedImageError from .helper import ( assert_image_equal, @@ -138,8 +132,6 @@ class TestImage: im.size = (3, 4) def test_invalid_image(self): - import io - im = io.BytesIO(b"") with pytest.raises(UnidentifiedImageError): with Image.open(im): @@ -430,14 +422,67 @@ class TestImage: for ext in [".cur", ".icns", ".tif", ".tiff"]: assert ext in extensions - def test_no_convert_mode(self, tmp_path): - assert not hasattr(TiffImagePlugin, "_convert_mode") + def test_supported_modes(self): + for format in Image.MIME.keys(): + try: + save_handler = Image.SAVE[format] + except KeyError: + continue + plugin = sys.modules[save_handler.__module__] + if not hasattr(plugin, "_supported_modes"): + continue + + # Check that the supported modes list is accurate + supported_modes = plugin._supported_modes() + for mode in [ + "1", + "L", + "P", + "RGB", + "RGBA", + "CMYK", + "YCbCr", + "LAB", + "HSV", + "I", + "F", + "LA", + "La", + "RGBX", + "RGBa", + ]: + out = io.BytesIO() + im = Image.new(mode, (100, 100)) + if mode in supported_modes: + im.save(out, format) + else: + with pytest.raises(Exception): + im.save(out, format) + + def test_no_supported_modes_method(self, tmp_path): + assert not hasattr(TiffImagePlugin, "_supported_modes") temp_file = str(tmp_path / "temp.tiff") im = hopper() im.save(temp_file, convert_mode=True) + def test_convert_mode(self): + for mode, modes in [["P", []], ["P", ["P"]]]: # no modes, same mode + im = Image.new(mode, (100, 100)) + assert im._convert_mode(modes) is None + + for mode, modes in [ + ["P", ["RGB"]], + ["P", ["L"]], # converting to a non-preferred mode + ["LA", ["P"]], + ["I", ["L"]], + ["RGB", ["L"]], + ["RGB", ["CMYK"]], + ]: + im = Image.new(mode, (100, 100)) + assert im._convert_mode(modes) is not None + def test_effect_mandelbrot(self): # Arrange size = (512, 512) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 4de387509..e1d2e82cb 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -1022,11 +1022,8 @@ def getdata(im, offset=(0, 0), **params): return fp.data -def _convert_mode(im): - return { - 'LA':'P', - 'CMYK':'RGB' - }.get(im.mode) +def _supported_modes(): + return ["RGB", "RGBA", "P", "I", "F", "LA", "L", "1"] # -------------------------------------------------------------------- diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 266044d49..ffe6df0e3 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2277,16 +2277,18 @@ class Image: if format.upper() not in SAVE: init() - if params.pop('save_all', False): + if params.pop("save_all", False): save_handler = SAVE_ALL[format.upper()] else: save_handler = SAVE[format.upper()] - if params.get('convert_mode'): + if params.get("convert_mode"): plugin = sys.modules[save_handler.__module__] - converted_im = self._convert_mode(plugin, params) - if converted_im: - return converted_im.save(fp, format, **params) + if hasattr(plugin, "_supported_modes"): + modes = plugin._supported_modes() + converted_im = self._convert_mode(modes, params) + if converted_im: + return converted_im.save(fp, format, **params) self.encoderinfo = params self.encoderconfig = () @@ -2315,32 +2317,57 @@ class Image: if open_fp: fp.close() - def _convert_mode(self, plugin, params): - if not hasattr(plugin, '_convert_mode'): + def _convert_mode(self, modes, params={}): + if not modes or self.mode in modes: return - new_mode = plugin._convert_mode(self) - if self.mode == 'LA' and new_mode == 'P': - alpha = self.getchannel('A') + if self.mode == "P": + preferred_modes = [] + if "A" in self.im.getpalettemode(): + preferred_modes.append("RGBA") + preferred_modes.append("RGB") + else: + preferred_modes = { + "CMYK": ["RGB"], + "RGB": ["CMYK"], + "RGBX": ["RGB"], + "RGBa": ["RGBA", "RGB"], + "RGBA": ["RGB"], + "LA": ["RGBA", "P", "L"], + "La": ["LA", "L"], + "L": ["RGB"], + "F": ["I"], + "I": ["L", "RGB"], + "1": ["L"], + "YCbCr": ["RGB"], + "LAB": ["RGB"], + "HSV": ["RGB"], + }.get(self.mode, []) + for new_mode in preferred_modes: + if new_mode in modes: + break + else: + new_mode = modes[0] + if self.mode == "LA" and new_mode == "P": + alpha = self.getchannel("A") # Convert the image into P mode but only use 255 colors # in the palette out of 256. - im = self.convert('L') \ - .convert('P', palette=Palette.ADAPTIVE, colors=255) + im = self.convert("L").convert("P", palette=Palette.ADAPTIVE, colors=255) # Set all pixel values below 128 to 255, and the rest to 0. mask = eval(alpha, lambda px: 255 if px < 128 else 0) # Paste the color of index 255 and use alpha as a mask. im.paste(255, mask) # The transparency index is 255. - im.info['transparency'] = 255 + im.info["transparency"] = 255 return im - elif self.mode == 'I': - im = self.point([i//256 for i in range(65536)], 'L') - return im.convert(new_mode) if new_mode != 'L' else im + elif self.mode == "I": + im = self.point([i // 256 for i in range(65536)], "L") + return im.convert(new_mode) if new_mode != "L" else im - elif self.mode in ('RGBA', 'LA') and new_mode in ('RGB', 'L'): - fill_color = params.get('fill_color', 'white') + elif self.mode in ("RGBA", "LA") and new_mode in ("RGB", "L"): + fill_color = params.get("fill_color", "white") background = new(new_mode, self.size, fill_color) - background.paste(self, self.getchannel('A')) + background.paste(self, self.getchannel("A")) return background elif new_mode: diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 618b9996a..ebc56ebf3 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -819,15 +819,8 @@ def jpeg_factory(fp=None, filename=None): return im -def _convert_mode(im): - mode = im.mode - if mode == 'P': - return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB' - return { - 'RGBA':'RGB', - 'LA':'L', - 'I':'L' - }.get(mode) +def _supported_modes(): + return ["RGB", "CMYK", "YCbCr", "RGBX", "L", "1"] # --------------------------------------------------------------------- diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 740727bbb..4c3e7143b 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1420,10 +1420,8 @@ def getchunks(im, **params): return fp.data -def _convert_mode(im): - return { - 'CMYK':'RGB' - }.get(im.mode) +def _supported_modes(): + return ["RGB", "RGBA", "P", "I", "LA", "L", "1"] # -------------------------------------------------------------------- diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index d81bc99a4..665896f55 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -344,17 +344,22 @@ def _save(im, fp, filename): fp.write(data) -def _convert_mode(im): - mode = im.mode - if mode == 'P': - return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB' - return { - # Pillow doesn't support L modes for webp for now. - 'L':'RGB', - 'LA':'RGBA', - 'I':'RGB', - 'CMYK':'RGB' - }.get(mode) +def _supported_modes(): + return [ + "RGB", + "RGBA", + "RGBa", + "RGBX", + "CMYK", + "YCbCr", + "HSV", + "I", + "F", + "P", + "LA", + "L", + "1", + ] Image.register_open(WebPImageFile.format, WebPImageFile, _accept) From 9ac4d5392d4a2585a29b6adb831444d7b276f58f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Jun 2022 12:55:34 +0000 Subject: [PATCH 3/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_image.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 3a7bd9d43..19986e29d 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -7,7 +7,14 @@ import warnings import pytest -from PIL import Image, ImageDraw, ImagePalette, TiffImagePlugin, UnidentifiedImageError, features +from PIL import ( + Image, + ImageDraw, + ImagePalette, + TiffImagePlugin, + UnidentifiedImageError, + features, +) from .helper import ( assert_image_equal, From 7440b710324650c256ce7ac5aaf0f7b567fb700e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 23 Dec 2022 11:21:55 +1100 Subject: [PATCH 4/6] Updated supported modes after #6647 --- src/PIL/WebPImagePlugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index e53885edb..1c38c4a41 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -369,6 +369,7 @@ def _supported_modes(): "F", "P", "LA", + "LAB", "L", "1", ] From 741da90906e004ab3203625cb648dbe509277eb4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 28 Dec 2024 07:37:57 +1100 Subject: [PATCH 5/6] Added type hints --- Tests/test_image.py | 38 ++++++++++++++++++++++++-------------- src/PIL/GifImagePlugin.py | 2 +- src/PIL/Image.py | 8 ++++++-- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/PngImagePlugin.py | 2 +- src/PIL/WebPImagePlugin.py | 2 +- 6 files changed, 34 insertions(+), 20 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index ca522cf38..3f28d4e38 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -514,21 +514,31 @@ class TestImage: im = hopper() im.save(temp_file, convert_mode=True) - def test_convert_mode(self) -> None: - for mode, modes in [["P", []], ["P", ["P"]]]: # no modes, same mode - im = Image.new(mode, (100, 100)) - assert im._convert_mode(modes) is None + @pytest.mark.parametrize( + "mode, modes", + ( + ("P", ["RGB"]), + ("P", ["L"]), # converting to a non-preferred mode + ("LA", ["P"]), + ("I", ["L"]), + ("RGB", ["L"]), + ("RGB", ["CMYK"]), + ), + ) + def test_convert_mode(self, mode: str, modes: list[str]) -> None: + im = Image.new(mode, (100, 100)) + assert im._convert_mode(modes) is not None - for mode, modes in [ - ["P", ["RGB"]], - ["P", ["L"]], # converting to a non-preferred mode - ["LA", ["P"]], - ["I", ["L"]], - ["RGB", ["L"]], - ["RGB", ["CMYK"]], - ]: - im = Image.new(mode, (100, 100)) - assert im._convert_mode(modes) is not None + @pytest.mark.parametrize( + "mode, modes", + ( + ("P", []), # no mode + ("P", ["P"]), # same mode + ), + ) + def test_convert_mode_noop(self, mode: str, modes: list[str]) -> None: + im = Image.new(mode, (100, 100)) + assert im._convert_mode(modes) is None def test_effect_mandelbrot(self) -> None: # Arrange diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 09f1fb7f4..74d48ea0e 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -1181,7 +1181,7 @@ def getdata( return fp.data -def _supported_modes(): +def _supported_modes() -> list[str]: return ["RGB", "RGBA", "P", "I", "F", "LA", "L", "1"] diff --git a/src/PIL/Image.py b/src/PIL/Image.py index a5ada6c50..d2eff91d7 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2618,9 +2618,11 @@ class Image: if open_fp: fp.close() - def _convert_mode(self, modes, params={}): + def _convert_mode( + self, modes: list[str], params: dict[str, Any] = {} + ) -> Image | None: if not modes or self.mode in modes: - return + return None if self.mode == "P": preferred_modes = [] if "A" in self.im.getpalettemode(): @@ -2674,6 +2676,8 @@ class Image: elif new_mode: return self.convert(new_mode) + return None + def seek(self, frame: int) -> None: """ Seeks to the given frame in this sequence file. If you seek diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 97fc5747f..cc02b29c9 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -891,7 +891,7 @@ def jpeg_factory( return im -def _supported_modes(): +def _supported_modes() -> list[str]: return ["RGB", "CMYK", "YCbCr", "RGBX", "L", "1"] diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 244db4545..543a7c170 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1532,7 +1532,7 @@ def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes] return chunks -def _supported_modes(): +def _supported_modes() -> list[str]: return ["RGB", "RGBA", "P", "I", "LA", "L", "1"] diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 2d951d23e..ee7c9e747 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -314,7 +314,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: fp.write(data) -def _supported_modes(): +def _supported_modes() -> list[str]: return [ "RGB", "RGBA", From 86b251f40e4c5157282da2f7be5e903416e9db2c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Mar 2025 20:40:29 +1100 Subject: [PATCH 6/6] Simplified code --- Tests/test_file_jpeg.py | 2 +- Tests/test_image.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index c316a1dd8..c7da07caf 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -764,7 +764,7 @@ class TestFileJpeg: img = Image.new(mode, (20, 20)) img.save(out, "JPEG", convert_mode=True) - temp_file = str(tmp_path / "temp.jpg") + temp_file = tmp_path / "temp.jpg" with Image.open("Tests/images/pil123rgba.png") as img: img.save(temp_file, convert_mode=True, fill_color="red") diff --git a/Tests/test_image.py b/Tests/test_image.py index 3e90c5801..ea8b957ac 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -507,7 +507,7 @@ class TestImage: def test_no_supported_modes_method(self, tmp_path: Path) -> None: assert not hasattr(TiffImagePlugin, "_supported_modes") - temp_file = str(tmp_path / "temp.tiff") + temp_file = tmp_path / "temp.tiff" im = hopper() im.save(temp_file, convert_mode=True)