Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Tests/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,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:
Expand Down
21 changes: 17 additions & 4 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,19 @@
)


# 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", modes)
def test_image_modes_success(self, mode: str) -> None:
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:
Expand Down Expand Up @@ -1023,15 +1032,19 @@ 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", modes)
def test_roundtrip_bytes_method(self, mode: str) -> None:
im = hopper(mode)
source_bytes = im.tobytes()

reloaded = Image.new(mode, im.size)
reloaded = helper_image_new(mode, im.size)
reloaded.frombytes(source_bytes)
assert reloaded.tobytes() == source_bytes

Expand All @@ -1040,7 +1053,7 @@ def test_getdata_putdata(self, mode: str) -> None:
if is_big_endian and mode == "BGR;15":
pytest.xfail("Known failure of BGR;15 on big-endian")
im = hopper(mode)
reloaded = Image.new(mode, im.size)
reloaded = helper_image_new(mode, im.size)
reloaded.putdata(im.getdata())
assert_image_equal(im, reloaded)

Expand Down
6 changes: 5 additions & 1 deletion Tests/test_image_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,11 @@ def check(self, mode: str, expected_color_int: int | None = None) -> None:

@pytest.mark.parametrize("mode", modes)
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()
Expand Down
3 changes: 2 additions & 1 deletion Tests/test_image_putdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 8 additions & 5 deletions Tests/test_lib_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,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))
Expand Down
7 changes: 7 additions & 0 deletions docs/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Support for LibTIFF earlier than 4
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
3 changes: 0 additions & 3 deletions docs/handbook/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)``
Expand Down
5 changes: 5 additions & 0 deletions docs/releasenotes/10.4.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
7 changes: 7 additions & 0 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions src/PIL/ImageMode.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from functools import lru_cache
from typing import NamedTuple

from ._deprecate import deprecate


class ModeDescriptor(NamedTuple):
"""Wrapper for mode strings."""
Expand Down Expand Up @@ -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)

Expand Down