From d431c97ba3b19d40d4090763ca25499536287141 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Apr 2024 19:28:52 +1000 Subject: [PATCH 1/3] Deprecate BGR;15, BGR;16 and BGR;24 --- Tests/helper.py | 7 ++++++- Tests/test_image.py | 30 +++++++++++++++++++++++++----- Tests/test_image_access.py | 6 +++++- Tests/test_image_putdata.py | 3 ++- Tests/test_lib_pack.py | 13 ++++++++----- docs/deprecations.rst | 7 +++++++ docs/handbook/concepts.rst | 3 --- src/PIL/Image.py | 7 +++++++ src/PIL/ImageMode.py | 4 ++++ 9 files changed, 64 insertions(+), 16 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index c1399e89bf8..213d994270d 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -273,7 +273,12 @@ def _cached_hopper(mode: str) -> Image.Image: im = hopper("L") else: im = hopper() - return im.convert(mode) + if mode.startswith("BGR;"): + with pytest.warns(DeprecationWarning): + im = im.convert(mode) + else: + im = im.convert(mode) + return im def djpeg_available() -> bool: diff --git a/Tests/test_image.py b/Tests/test_image.py index 941ec40d9bc..ed80be503fb 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -66,7 +66,11 @@ class TestImage: @pytest.mark.parametrize("mode", image_mode_names) def test_image_modes_success(self, mode: str) -> None: - Image.new(mode, (1, 1)) + if mode.startswith("BGR;"): + with pytest.warns(DeprecationWarning): + Image.new(mode, (1, 1)) + else: + Image.new(mode, (1, 1)) @pytest.mark.parametrize("mode", ("", "bad", "very very long")) def test_image_modes_fail(self, mode: str) -> None: @@ -1050,7 +1054,11 @@ def test_roundtrip_bytes_constructor(self, mode: str) -> None: im = hopper(mode) source_bytes = im.tobytes() - reloaded = Image.frombytes(mode, im.size, source_bytes) + if mode.startswith("BGR;"): + with pytest.warns(DeprecationWarning): + reloaded = Image.frombytes(mode, im.size, source_bytes) + else: + reloaded = Image.frombytes(mode, im.size, source_bytes) assert reloaded.tobytes() == source_bytes @pytest.mark.parametrize("mode", image_mode_names) @@ -1058,17 +1066,29 @@ def test_roundtrip_bytes_method(self, mode: str) -> None: im = hopper(mode) source_bytes = im.tobytes() - reloaded = Image.new(mode, im.size) + if mode.startswith("BGR;"): + with pytest.warns(DeprecationWarning): + reloaded = Image.new(mode, im.size) + else: + reloaded = Image.new(mode, im.size) reloaded.frombytes(source_bytes) assert reloaded.tobytes() == source_bytes @pytest.mark.parametrize(("mode", "pixelsize"), image_modes) def test_getdata_putdata(self, mode: str, pixelsize: int) -> None: - im = Image.new(mode, (2, 2)) + if mode.startswith("BGR;"): + with pytest.warns(DeprecationWarning): + im = Image.new(mode, (2, 2)) + else: + im = Image.new(mode, (2, 2)) source_bytes = bytes(range(im.width * im.height * pixelsize)) im.frombytes(source_bytes) - reloaded = Image.new(mode, im.size) + if mode.startswith("BGR;"): + with pytest.warns(DeprecationWarning): + reloaded = Image.new(mode, im.size) + else: + reloaded = Image.new(mode, im.size) reloaded.putdata(im.getdata()) assert_image_equal(im, reloaded) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 8c42da57a37..8bb90710aa3 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -229,7 +229,11 @@ def check(self, mode: str, expected_color_int: int | None = None) -> None: ), ) def test_basic(self, mode: str) -> None: - self.check(mode) + if mode.startswith("BGR;"): + with pytest.warns(DeprecationWarning): + self.check(mode) + else: + self.check(mode) def test_list(self) -> None: im = hopper() diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 73145faac15..dad26ef144c 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -81,7 +81,8 @@ def test_mode_F() -> None: @pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24")) def test_mode_BGR(mode: str) -> None: data = [(16, 32, 49), (32, 32, 98)] - im = Image.new(mode, (1, 2)) + with pytest.warns(DeprecationWarning): + im = Image.new(mode, (1, 2)) im.putdata(data) assert list(im.getdata()) == data diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 6a0e704b89a..f80c5b78c9c 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -359,11 +359,14 @@ def test_RGB(self) -> None: ) def test_BGR(self) -> None: - self.assert_unpack("BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8)) - self.assert_unpack( - "BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0) - ) - self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) + with pytest.warns(DeprecationWarning): + self.assert_unpack( + "BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8) + ) + self.assert_unpack( + "BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0) + ) + self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) def test_RGBA(self) -> None: self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index c3d1ba4f028..da4e9e597f5 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -100,6 +100,13 @@ ImageMath eval() ``ImageMath.eval()`` has been deprecated. Use :py:meth:`~PIL.ImageMath.lambda_eval` or :py:meth:`~PIL.ImageMath.unsafe_eval` instead. +BGR;15, BGR 16 and BGR;24 +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 10.4.0 + +The experimental BGR;15, BGR;16 and BGR;24 modes have been deprecated. + Removed features ---------------- diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst index e0975a12132..5094dbf3f27 100644 --- a/docs/handbook/concepts.rst +++ b/docs/handbook/concepts.rst @@ -59,9 +59,6 @@ Pillow also provides limited support for a few additional modes, including: * ``I;16L`` (16-bit little endian unsigned integer pixels) * ``I;16B`` (16-bit big endian unsigned integer pixels) * ``I;16N`` (16-bit native endian unsigned integer pixels) - * ``BGR;15`` (15-bit reversed true colour) - * ``BGR;16`` (16-bit reversed true colour) - * ``BGR;24`` (24-bit reversed true colour) Premultiplied alpha is where the values for each other channel have been multiplied by the alpha. For example, an RGBA pixel of ``(10, 20, 30, 127)`` diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 3ae90106087..26be427798a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -55,6 +55,7 @@ _plugins, ) from ._binary import i32le, o32be, o32le +from ._deprecate import deprecate from ._typing import StrOrBytesPath, TypeGuard from ._util import DeferredError, is_path @@ -939,6 +940,9 @@ def convert( :returns: An :py:class:`~PIL.Image.Image` object. """ + if mode in ("BGR;15", "BGR;16", "BGR;24"): + deprecate(mode, 12) + self.load() has_transparency = "transparency" in self.info @@ -2956,6 +2960,9 @@ def new( :returns: An :py:class:`~PIL.Image.Image` object. """ + if mode in ("BGR;15", "BGR;16", "BGR;24"): + deprecate(mode, 12) + _check_size(size) if color is None: diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py index 5e05c5f43ed..7bd2afcf2fa 100644 --- a/src/PIL/ImageMode.py +++ b/src/PIL/ImageMode.py @@ -18,6 +18,8 @@ from functools import lru_cache from typing import NamedTuple +from ._deprecate import deprecate + class ModeDescriptor(NamedTuple): """Wrapper for mode strings.""" @@ -63,6 +65,8 @@ def getmode(mode: str) -> ModeDescriptor: "PA": ("RGB", "L", ("P", "A"), "|u1"), } if mode in modes: + if mode in ("BGR;15", "BGR;16", "BGR;24"): + deprecate(mode, 12) base_mode, base_type, bands, type_str = modes[mode] return ModeDescriptor(mode, bands, base_mode, base_type, type_str) From 02db41119018f313df060c254a22f44a95057e15 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Apr 2024 09:14:48 +1000 Subject: [PATCH 2/3] Added release notes --- docs/releasenotes/10.4.0.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/releasenotes/10.4.0.rst b/docs/releasenotes/10.4.0.rst index 0c2926732e4..3150bf4e02f 100644 --- a/docs/releasenotes/10.4.0.rst +++ b/docs/releasenotes/10.4.0.rst @@ -23,6 +23,11 @@ TODO Deprecations ============ +BGR;15, BGR 16 and BGR;24 +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The experimental BGR;15, BGR;16 and BGR;24 modes have been deprecated. + Support for LibTIFF earlier than 4 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 0099de0ed904c2021850fbb5a19908c36f908890 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:00:14 +0300 Subject: [PATCH 3/3] Add deprecation helper for Image.new with BGR; modes --- Tests/test_image.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index ed80be503fb..e8339424d99 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -63,14 +63,19 @@ image_mode_names = [name for name, _ in image_modes] +# Deprecation helper +def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image: + if mode.startswith("BGR;"): + with pytest.warns(DeprecationWarning): + return Image.new(mode, size) + else: + return Image.new(mode, size) + + class TestImage: @pytest.mark.parametrize("mode", image_mode_names) def test_image_modes_success(self, mode: str) -> None: - if mode.startswith("BGR;"): - with pytest.warns(DeprecationWarning): - Image.new(mode, (1, 1)) - else: - Image.new(mode, (1, 1)) + helper_image_new(mode, (1, 1)) @pytest.mark.parametrize("mode", ("", "bad", "very very long")) def test_image_modes_fail(self, mode: str) -> None: @@ -1066,29 +1071,17 @@ def test_roundtrip_bytes_method(self, mode: str) -> None: im = hopper(mode) source_bytes = im.tobytes() - if mode.startswith("BGR;"): - with pytest.warns(DeprecationWarning): - reloaded = Image.new(mode, im.size) - else: - reloaded = Image.new(mode, im.size) + reloaded = helper_image_new(mode, im.size) reloaded.frombytes(source_bytes) assert reloaded.tobytes() == source_bytes @pytest.mark.parametrize(("mode", "pixelsize"), image_modes) def test_getdata_putdata(self, mode: str, pixelsize: int) -> None: - if mode.startswith("BGR;"): - with pytest.warns(DeprecationWarning): - im = Image.new(mode, (2, 2)) - else: - im = Image.new(mode, (2, 2)) + im = helper_image_new(mode, (2, 2)) source_bytes = bytes(range(im.width * im.height * pixelsize)) im.frombytes(source_bytes) - if mode.startswith("BGR;"): - with pytest.warns(DeprecationWarning): - reloaded = Image.new(mode, im.size) - else: - reloaded = Image.new(mode, im.size) + reloaded = helper_image_new(mode, im.size) reloaded.putdata(im.getdata()) assert_image_equal(im, reloaded)