diff --git a/Tests/test_deprecate.py b/Tests/test_deprecate.py index 1e98ecfff37..5f2fa36eaa6 100644 --- a/Tests/test_deprecate.py +++ b/Tests/test_deprecate.py @@ -9,9 +9,9 @@ "version, expected", [ ( - 13, - "Old thing is deprecated and will be removed in Pillow 13 " - r"\(2026-10-15\)\. Use new thing instead\.", + 14, + "Old thing is deprecated and will be removed in Pillow 14 " + r"\(2027-10-15\)\. Use new thing instead\.", ), ( None, @@ -53,18 +53,18 @@ def test_old_version(deprecated: str, plural: bool, expected: str) -> None: def test_plural() -> None: expected = ( - r"Old things are deprecated and will be removed in Pillow 13 \(2026-10-15\)\. " + r"Old things are deprecated and will be removed in Pillow 14 \(2027-10-15\)\. " r"Use new thing instead\." ) with pytest.warns(DeprecationWarning, match=expected): - _deprecate.deprecate("Old things", 13, "new thing", plural=True) + _deprecate.deprecate("Old things", 14, "new thing", plural=True) def test_replacement_and_action() -> None: expected = "Use only one of 'replacement' and 'action'" with pytest.raises(ValueError, match=expected): _deprecate.deprecate( - "Old thing", 13, replacement="new thing", action="Upgrade to new thing" + "Old thing", 14, replacement="new thing", action="Upgrade to new thing" ) @@ -77,16 +77,16 @@ def test_replacement_and_action() -> None: ) def test_action(action: str) -> None: expected = ( - r"Old thing is deprecated and will be removed in Pillow 13 \(2026-10-15\)\. " + r"Old thing is deprecated and will be removed in Pillow 14 \(2027-10-15\)\. " r"Upgrade to new thing\." ) with pytest.warns(DeprecationWarning, match=expected): - _deprecate.deprecate("Old thing", 13, action=action) + _deprecate.deprecate("Old thing", 14, action=action) def test_no_replacement_or_action() -> None: expected = ( - r"Old thing is deprecated and will be removed in Pillow 13 \(2026-10-15\)" + r"Old thing is deprecated and will be removed in Pillow 14 \(2027-10-15\)" ) with pytest.warns(DeprecationWarning, match=expected): - _deprecate.deprecate("Old thing", 13) + _deprecate.deprecate("Old thing", 14) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index c9ffc54cd53..9f81f3288a7 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -537,11 +537,6 @@ def test_trns_invalid(self, tmp_path: Path) -> None: ): im.save(out, transparency="invalid") - im = Image.new("I", (1, 1)) - with pytest.warns(DeprecationWarning, match="Saving I mode images as PNG"): - with pytest.raises(ValueError): - im.save(out, transparency="invalid") - im = Image.new("P", (1, 1)) with pytest.raises( ValueError, match="transparency for P must be an integer or bytes" @@ -880,16 +875,6 @@ def test_truncated_end_chunk(self, monkeypatch: pytest.MonkeyPatch) -> None: with Image.open("Tests/images/truncated_end_chunk.png") as im: assert_image_equal_tofile(im, "Tests/images/hopper.png") - def test_deprecation(self, tmp_path: Path) -> None: - test_file = tmp_path / "out.png" - - im = hopper("I") - with pytest.warns(DeprecationWarning, match="Saving I mode images as PNG"): - im.save(test_file) - - with Image.open(test_file) as reloaded: - assert_image_equal(im, reloaded.convert("I")) - @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") @skip_unless_feature("zlib") diff --git a/Tests/test_image.py b/Tests/test_image.py index c42b37a2fb4..300ea81e00e 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -19,7 +19,6 @@ ImageDraw, ImageFile, ImagePalette, - ImageShow, UnidentifiedImageError, features, ) @@ -1024,18 +1023,6 @@ def test_getxmp_padded(self) -> None: else: assert im.getxmp() == {"xmpmeta": None} - def test_get_child_images(self) -> None: - im = Image.new("RGB", (1, 1)) - with pytest.warns(DeprecationWarning, match="Image.Image.get_child_images"): - assert im.get_child_images() == [] - - def test_show(self, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setattr(ImageShow, "_viewers", []) - - im = Image.new("RGB", (1, 1)) - with pytest.warns(DeprecationWarning, match="Image._show"): - Image._show(im) - @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) def test_zero_tobytes(self, size: tuple[int, int]) -> None: im = Image.new("RGB", size) diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 099c08dbb48..f5ca2b6b25f 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -122,11 +122,3 @@ def test_fromarray_palette() -> None: # Assert that the Python and C palettes match assert out.palette is not None assert len(out.palette.colors) == len(out.im.getpalette()) / 3 - - -def test_deprecation() -> None: - a = numpy.array(im.convert("L")) - with pytest.warns( - DeprecationWarning, match="'mode' parameter for changing data types" - ): - Image.fromarray(a, "1") diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 3382a34e47c..7b8051486d4 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -698,17 +698,3 @@ def test_cmyk_lab() -> None: im = Image.new("CMYK", (1, 1)) converted_im = im.convert("LAB") assert converted_im.getpixel((0, 0)) == (255, 128, 128) - - -def test_deprecation() -> None: - profile = ImageCmsProfile(ImageCms.createProfile("sRGB")) - with pytest.warns( - DeprecationWarning, match="ImageCms.ImageCmsProfile.product_name" - ): - profile.product_name - with pytest.warns( - DeprecationWarning, match="ImageCms.ImageCmsProfile.product_info" - ): - profile.product_info - with pytest.raises(AttributeError): - profile.this_attribute_does_not_exist diff --git a/docs/deprecations.rst b/docs/deprecations.rst index c73e6b9296b..70745104483 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -20,74 +20,79 @@ ExifTags.IFD.Makernote ``ExifTags.IFD.Makernote`` has been deprecated. Instead, use ``ExifTags.IFD.MakerNote``. -Image.Image.get_child_images() -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Image getdata() +~~~~~~~~~~~~~~~ -.. deprecated:: 11.2.1 +.. deprecated:: 12.1.0 -``Image.Image.get_child_images()`` has been deprecated, and will be removed in Pillow -13 (2026-10-15). It will be moved to ``ImageFile.ImageFile.get_child_images()``. The -method uses an image's file pointer, and so child images could only be retrieved from -an :py:class:`PIL.ImageFile.ImageFile` instance. +:py:meth:`~PIL.Image.Image.getdata` has been deprecated. +:py:meth:`~PIL.Image.Image.get_flattened_data` can be used instead. This new method is +identical, except that it returns a tuple of pixel values, instead of an internal +Pillow data type. -Image.fromarray mode parameter -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Removed features +---------------- -.. deprecated:: 11.3.0 +Deprecated features are only removed in major releases after an appropriate +period of deprecation has passed. -Using the ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` was deprecated in -Pillow 11.3.0. In Pillow 12.0.0, this was partially reverted, and it is now only -deprecated when changing data types. Since pixel values do not contain information -about palettes or color spaces, the parameter can still be used to place grayscale L -mode data within a P mode image, or read RGB data as YCbCr for example. If omitted, the -mode will be automatically determined from the object's shape and type. +Image._show +^^^^^^^^^^^ + +.. deprecated:: 12.0.0 +.. versionremoved:: 13.0.0 + +``Image._show`` has been removed. Use :py:meth:`~PIL.ImageShow.show` instead. + +ImageCms.ImageCmsProfile.product_name and .product_info +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 12.0.0 +.. versionremoved:: 13.0.0 + +``ImageCms.ImageCmsProfile.product_name`` and the corresponding +``.product_info`` attributes have been removed. They were set to ``None`` since Pillow +2.3.0. Saving I mode images as PNG ^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. deprecated:: 11.3.0 +.. versionremoved:: 13.0.0 In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly -changing the data, this is now deprecated. Instead, the image can be converted to -another mode before saving:: +changing the data, this is now removed. Instead, the image can be converted to another +mode before saving:: from PIL import Image im = Image.new("I", (1, 1)) im.convert("I;16").save("out.png") -ImageCms.ImageCmsProfile.product_name and .product_info -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. deprecated:: 12.0.0 - -``ImageCms.ImageCmsProfile.product_name`` and the corresponding -``.product_info`` attributes have been deprecated, and will be removed in -Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0. - -Image._show -~~~~~~~~~~~ - -.. deprecated:: 12.0.0 - -``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15). -Use :py:meth:`~PIL.ImageShow.show` instead. +Image.fromarray mode parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Image getdata() -~~~~~~~~~~~~~~~ +.. deprecated:: 11.3.0 +.. versionremoved:: 13.0.0 -.. deprecated:: 12.1.0 +Using the ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` was deprecated in +Pillow 11.3.0. In Pillow 12.0.0, this was partially reverted, and now the only +functionality removed is when the mode changes data types. Since pixel values do not +contain information about palettes or color spaces, the parameter can still be used to +place grayscale L mode data within a P mode image, or read RGB data as YCbCr for +example. If omitted, the mode will be automatically determined from the object's shape +and type. -:py:meth:`~PIL.Image.Image.getdata` has been deprecated. -:py:meth:`~PIL.Image.Image.get_flattened_data` can be used instead. This new method is -identical, except that it returns a tuple of pixel values, instead of an internal -Pillow data type. +Image.Image.get_child_images() +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Removed features ----------------- +.. deprecated:: 11.2.1 +.. versionremoved:: 13.0.0 -Deprecated features are only removed in major releases after an appropriate -period of deprecation has passed. +``Image.Image.get_child_images()`` has been moved to +``ImageFile.ImageFile.get_child_images()``. The method uses an image's file pointer, +and so child images could only be retrieved from an :py:class:`PIL.ImageFile.ImageFile` +instance. ImageFile.raise_oserror ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst new file mode 100644 index 00000000000..d8721fb12d4 --- /dev/null +++ b/docs/releasenotes/13.0.0.rst @@ -0,0 +1,93 @@ +13.0.0 +------ + +Security +======== + +TODO +^^^^ + +TODO + +:cve:`YYYY-XXXXX`: TODO +^^^^^^^^^^^^^^^^^^^^^^^ + +TODO + +Backwards incompatible changes +============================== + +Saving I mode images as PNG +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain +at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly +changing the data, this is now removed. Instead, the image can be converted to another +mode before saving:: + + from PIL import Image + im = Image.new("I", (1, 1)) + im.convert("I;16").save("out.png") + +Image.fromarray mode parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Using the ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` was deprecated in +Pillow 11.3.0. In Pillow 12.0.0, this was partially reverted, and now the only +functionality removed is when the mode changes data types. Since pixel values do not +contain information about palettes or color spaces, the parameter can still be used to +place grayscale L mode data within a P mode image, or read RGB data as YCbCr for +example. If omitted, the mode will be automatically determined from the object's shape +and type. + +Image.Image.get_child_images() +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``Image.Image.get_child_images()`` has been moved to +``ImageFile.ImageFile.get_child_images()``. The method uses an image's file pointer, +and so child images could only be retrieved from an :py:class:`PIL.ImageFile.ImageFile` +instance. + +Image._show +~~~~~~~~~~~ + +``Image._show`` has been removed. Use :py:meth:`~PIL.ImageShow.show` instead. + +ImageCms.ImageCmsProfile.product_name and .product_info +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``ImageCms.ImageCmsProfile.product_name`` and the corresponding +``.product_info`` attributes have been removed. They were set to ``None`` since Pillow +2.3.0. + +Deprecations +============ + +TODO +^^^^ + +TODO + +API changes +=========== + +TODO +^^^^ + +TODO + +API additions +============= + +TODO +^^^^ + +TODO + +Other changes +============= + +TODO +^^^^ + +TODO diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 7cae29d1825..e45919afb07 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -15,6 +15,7 @@ expected to be backported to earlier versions. :maxdepth: 2 versioning + 13.0.0 12.3.0 12.2.0 12.1.1 diff --git a/src/PIL/Image.py b/src/PIL/Image.py index ebbd8fd35f8..b7a43a824f5 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1665,12 +1665,6 @@ def _reload_exif(self) -> None: self._exif._loaded = False self.getexif() - def get_child_images(self) -> list[ImageFile.ImageFile]: - from . import ImageFile - - deprecate("Image.Image.get_child_images", 13) - return ImageFile.ImageFile.get_child_images(self) # type: ignore[arg-type] - def getim(self) -> CapsuleType: """ Returns a capsule that points to the internal image memory. @@ -3435,7 +3429,8 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image: raise TypeError(msg) from e if mode is not None: if mode != typemode and mode not in color_modes: - deprecate("'mode' parameter for changing data types", 13) + msg = "Invalid mode for data type" + raise ValueError(msg) rawmode = mode else: mode = typemode @@ -3943,17 +3938,6 @@ def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None: ENCODERS[name] = encoder -# -------------------------------------------------------------------- -# Simple display support. - - -def _show(image: Image, **options: Any) -> None: - from . import ImageShow - - deprecate("Image._show", 13, "ImageShow.show") - ImageShow.show(image, **options) - - # -------------------------------------------------------------------- # Effects diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 388a9296f8b..9bcf8cb7fb7 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -23,10 +23,9 @@ import sys from enum import IntEnum, IntFlag from functools import reduce -from typing import Any, Literal, SupportsFloat, SupportsInt, Union +from typing import Literal, SupportsFloat, SupportsInt, Union from . import Image -from ._deprecate import deprecate from ._typing import SupportsRead try: @@ -255,13 +254,6 @@ def __init__(self, profile: str | SupportsRead[bytes] | core.CmsProfile) -> None msg = "Invalid type for Profile" # type: ignore[unreachable] raise TypeError(msg) - def __getattr__(self, name: str) -> Any: - if name in ("product_name", "product_info"): - deprecate(f"ImageCms.ImageCmsProfile.{name}", 13) - return None - msg = f"'{self.__class__.__name__}' object has no attribute '{name}'" - raise AttributeError(msg) - def tobytes(self) -> bytes: """ Returns the profile in a format suitable for embedding in diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 058fb831c9e..c777b4d3162 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -48,7 +48,6 @@ from ._binary import o8 from ._binary import o16be as o16 from ._binary import o32be as o32 -from ._deprecate import deprecate from ._util import DeferredError TYPE_CHECKING = False @@ -1112,7 +1111,6 @@ def getexif(self) -> Image.Exif: "L;4": ("L;4", b"\x04", b"\x00"), "L": ("L", b"\x08", b"\x00"), "LA": ("LA", b"\x08", b"\x04"), - "I": ("I;16B", b"\x10", b"\x00"), "I;16": ("I;16B", b"\x10", b"\x00"), "I;16B": ("I;16B", b"\x10", b"\x00"), "P;1": ("P;1", b"\x01", b"\x03"), @@ -1387,8 +1385,6 @@ def _save( except KeyError as e: msg = f"cannot write mode {mode} as PNG" raise OSError(msg) from e - if outmode == "I": - deprecate("Saving I mode images as PNG", 13, stacklevel=4) # # write minimal PNG file diff --git a/src/PIL/_deprecate.py b/src/PIL/_deprecate.py index 711c62ab2b1..ff18bbea2e5 100644 --- a/src/PIL/_deprecate.py +++ b/src/PIL/_deprecate.py @@ -46,8 +46,6 @@ def deprecate( elif when <= int(__version__.split(".")[0]): msg = f"{deprecated} {is_} deprecated and should be removed." raise RuntimeError(msg) - elif when == 13: - removed = "Pillow 13 (2026-10-15)" elif when == 14: removed = "Pillow 14 (2027-10-15)" else: