diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d5fd964f1..75c7d3632 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.0 + rev: v0.12.2 hooks: - id: ruff-check args: [--exit-non-zero-on-fix] @@ -11,7 +11,7 @@ repos: - id: black - repo: https://github.com/PyCQA/bandit - rev: 1.8.5 + rev: 1.8.6 hooks: - id: bandit args: [--severity-level=high] @@ -24,7 +24,7 @@ repos: exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$) - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v20.1.6 + rev: v20.1.7 hooks: - id: clang-format types: [c] @@ -51,14 +51,14 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.33.1 + rev: 0.33.2 hooks: - id: check-github-workflows - id: check-readthedocs - id: check-renovate - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.9.0 + rev: v1.11.0 hooks: - id: zizmor diff --git a/Tests/helper.py b/Tests/helper.py index e71b4665b..34e4d6e75 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -271,17 +271,13 @@ def _cached_hopper(mode: str) -> Image.Image: im = hopper("L") else: im = hopper() - if mode.startswith("BGR;"): - with pytest.warns(DeprecationWarning, match="BGR;"): - im = im.convert(mode) - else: - try: - im = im.convert(mode) - except ImportError: - if mode == "LAB": - im = Image.open("Tests/images/hopper.Lab.tif") - else: - raise + try: + im = im.convert(mode) + except ImportError: + if mode == "LAB": + im = Image.open("Tests/images/hopper.Lab.tif") + else: + raise return im diff --git a/Tests/test_deprecate.py b/Tests/test_deprecate.py index 88479ff0d..1e98ecfff 100644 --- a/Tests/test_deprecate.py +++ b/Tests/test_deprecate.py @@ -9,9 +9,9 @@ from PIL import _deprecate "version, expected", [ ( - 12, - "Old thing is deprecated and will be removed in Pillow 12 " - r"\(2025-10-15\)\. Use new thing instead\.", + 13, + "Old thing is deprecated and will be removed in Pillow 13 " + r"\(2026-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 12 \(2025-10-15\)\. " + r"Old things are deprecated and will be removed in Pillow 13 \(2026-10-15\)\. " r"Use new thing instead\." ) with pytest.warns(DeprecationWarning, match=expected): - _deprecate.deprecate("Old things", 12, "new thing", plural=True) + _deprecate.deprecate("Old things", 13, "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", 12, replacement="new thing", action="Upgrade to new thing" + "Old thing", 13, 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 12 \(2025-10-15\)\. " + r"Old thing is deprecated and will be removed in Pillow 13 \(2026-10-15\)\. " r"Upgrade to new thing\." ) with pytest.warns(DeprecationWarning, match=expected): - _deprecate.deprecate("Old thing", 12, action=action) + _deprecate.deprecate("Old thing", 13, action=action) def test_no_replacement_or_action() -> None: expected = ( - r"Old thing is deprecated and will be removed in Pillow 12 \(2025-10-15\)" + r"Old thing is deprecated and will be removed in Pillow 13 \(2026-10-15\)" ) with pytest.warns(DeprecationWarning, match=expected): - _deprecate.deprecate("Old thing", 12) + _deprecate.deprecate("Old thing", 13) diff --git a/Tests/test_features.py b/Tests/test_features.py index d06fb4d84..520c25b46 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -55,21 +55,6 @@ def test_version() -> None: test(feature, features.version_feature) -def test_webp_transparency() -> None: - with pytest.warns(DeprecationWarning, match="transp_webp"): - assert (features.check("transp_webp") or False) == features.check_module("webp") - - -def test_webp_mux() -> None: - with pytest.warns(DeprecationWarning, match="webp_mux"): - assert (features.check("webp_mux") or False) == features.check_module("webp") - - -def test_webp_anim() -> None: - with pytest.warns(DeprecationWarning, match="webp_anim"): - assert (features.check("webp_anim") or False) == features.check_module("webp") - - @skip_unless_feature("libjpeg_turbo") def test_libjpeg_turbo_version() -> None: version = features.version("libjpeg_turbo") diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 8ff59161f..b9b818506 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -93,21 +93,11 @@ def test_sizes() -> None: with Image.open(TEST_FILE) as im: assert isinstance(im, IcnsImagePlugin.IcnsImageFile) for w, h, r in im.info["sizes"]: - wr = w * r - hr = h * r - with pytest.warns( - DeprecationWarning, match=r"Setting size to \(width, height, scale\)" - ): - im.size = (w, h, r) - im.load() - assert im.mode == "RGBA" - assert im.size == (wr, hr) - # Test using load() with scale im.size = (w, h) im.load(scale=r) assert im.mode == "RGBA" - assert im.size == (wr, hr) + assert im.size == (w * r, h * r) # Check that we cannot load an incorrect size with pytest.raises(ValueError): diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index 5dca3da21..3c4c892c8 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -1,9 +1,6 @@ from __future__ import annotations -import sys -from io import BytesIO, StringIO - -import pytest +from io import BytesIO from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags @@ -101,35 +98,3 @@ def test_getiptcinfo_tiff_none() -> None: # Assert assert iptc is None - - -def test_i() -> None: - # Arrange - c = b"a" - - # Act - with pytest.warns(DeprecationWarning, match="IptcImagePlugin.i"): - ret = IptcImagePlugin.i(c) - - # Assert - assert ret == 97 - - -def test_dump(monkeypatch: pytest.MonkeyPatch) -> None: - # Arrange - c = b"abc" - # Temporarily redirect stdout - mystdout = StringIO() - monkeypatch.setattr(sys, "stdout", mystdout) - - # Act - with pytest.warns(DeprecationWarning, match="IptcImagePlugin.dump"): - IptcImagePlugin.dump(c) - - # Assert - assert mystdout.getvalue() == "61 62 63 \n" - - -def test_pad_deprecation() -> None: - with pytest.warns(DeprecationWarning, match="IptcImagePlugin.PAD"): - assert IptcImagePlugin.PAD == b"\0\0\0\0" diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 5afae0412..08e879807 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -1115,14 +1115,6 @@ class TestFileJpeg: assert im._repr_jpeg_() is None - def test_deprecation(self) -> None: - with Image.open(TEST_FILE) as im: - assert isinstance(im, JpegImagePlugin.JpegImageFile) - with pytest.warns(DeprecationWarning, match="huffman_ac"): - assert im.huffman_ac == {} - with pytest.warns(DeprecationWarning, match="huffman_dc"): - assert im.huffman_dc == {} - @pytest.mark.skipif(not is_win32(), reason="Windows only") @skip_unless_feature("jpg") diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 1ec39eba5..c245a5a9b 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -256,19 +256,7 @@ class TestFileLibTiff(LibTiffTestCase): im.save(out, tiffinfo=new_ifd) - @pytest.mark.parametrize( - "libtiff", - ( - pytest.param( - True, - marks=pytest.mark.skipif( - not getattr(Image.core, "libtiff_support_custom_tags", False), - reason="Custom tags not supported by older libtiff", - ), - ), - False, - ), - ) + @pytest.mark.parametrize("libtiff", (True, False)) def test_custom_metadata( self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool ) -> None: @@ -724,8 +712,7 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as reloaded: assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) - if Image.core.libtiff_support_custom_tags: - assert reloaded.tag_v2[34665] == 125456 + assert reloaded.tag_v2[34665] == 125456 def test_crashing_metadata( self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path @@ -777,19 +764,7 @@ class TestFileLibTiff(LibTiffTestCase): assert icc_libtiff is not None assert icc == icc_libtiff - @pytest.mark.parametrize( - "libtiff", - ( - pytest.param( - True, - marks=pytest.mark.skipif( - not getattr(Image.core, "libtiff_support_custom_tags", False), - reason="Custom tags not supported by older libtiff", - ), - ), - False, - ), - ) + @pytest.mark.parametrize("libtiff", (True, False)) def test_write_icc( self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool ) -> None: diff --git a/Tests/test_image.py b/Tests/test_image.py index e4c25693a..ffa15b02a 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -30,7 +30,6 @@ from .helper import ( assert_image_similar_tofile, assert_not_all_same, hopper, - is_big_endian, is_win32, mark_if_feature_version, skip_unless_feature, @@ -50,19 +49,10 @@ except ImportError: PrettyPrinter = None -# Deprecation helper -def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image: - if mode.startswith("BGR;"): - with pytest.warns(DeprecationWarning, match="BGR;"): - return Image.new(mode, size) - else: - return Image.new(mode, size) - - class TestImage: - @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"]) + @pytest.mark.parametrize("mode", Image.MODES) def test_image_modes_success(self, mode: str) -> None: - helper_image_new(mode, (1, 1)) + Image.new(mode, (1, 1)) @pytest.mark.parametrize("mode", ("", "bad", "very very long")) def test_image_modes_fail(self, mode: str) -> None: @@ -1173,39 +1163,29 @@ class TestImage: assert len(caplog.records) == 0 assert im.fp is None - def test_deprecation(self) -> None: - with pytest.warns(DeprecationWarning, match="Image.isImageType"): - assert not Image.isImageType(None) - class TestImageBytes: - @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"]) + @pytest.mark.parametrize("mode", Image.MODES) def test_roundtrip_bytes_constructor(self, mode: str) -> None: im = hopper(mode) source_bytes = im.tobytes() - if mode.startswith("BGR;"): - with pytest.warns(DeprecationWarning, match=mode): - reloaded = Image.frombytes(mode, im.size, source_bytes) - else: - reloaded = Image.frombytes(mode, im.size, source_bytes) + reloaded = Image.frombytes(mode, im.size, source_bytes) assert reloaded.tobytes() == source_bytes - @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"]) + @pytest.mark.parametrize("mode", Image.MODES) def test_roundtrip_bytes_method(self, mode: str) -> None: im = hopper(mode) source_bytes = im.tobytes() - reloaded = helper_image_new(mode, im.size) + reloaded = Image.new(mode, im.size) reloaded.frombytes(source_bytes) assert reloaded.tobytes() == source_bytes - @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"]) + @pytest.mark.parametrize("mode", Image.MODES) 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 = helper_image_new(mode, im.size) + 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 66412a035..b3de5c13d 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -123,10 +123,6 @@ class TestImageGetPixel: bands = Image.getmodebands(mode) if bands == 1: return 1 - if mode in ("BGR;15", "BGR;16"): - # These modes have less than 8 bits per band, - # so (1, 2, 3) cannot be roundtripped. - return (16, 32, 49) return tuple(range(1, bands + 1)) def check(self, mode: str, expected_color_int: int | None = None) -> None: @@ -191,11 +187,6 @@ class TestImageGetPixel: def test_basic(self, mode: str) -> None: self.check(mode) - @pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24")) - def test_deprecated(self, mode: str) -> None: - with pytest.warns(DeprecationWarning, match="BGR;"): - self.check(mode) - def test_list(self) -> None: im = hopper() assert im.getpixel([0, 0]) == (20, 20, 70) @@ -218,7 +209,7 @@ class TestImageGetPixel: class TestImagePutPixelError: - IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"] + IMAGE_MODES1 = ["LA", "RGB", "RGBA"] IMAGE_MODES2 = ["L", "I", "I;16"] INVALID_TYPES = ["foo", 1.0, None] @@ -234,11 +225,6 @@ class TestImagePutPixelError: ( ("L", (0, 2), "color must be int or single-element tuple"), ("LA", (0, 3), "color must be int, or tuple of one or two elements"), - ( - "BGR;15", - (0, 2), - "color must be int, or tuple of one or three elements", - ), ( "RGB", (0, 2, 5), diff --git a/Tests/test_image_getim.py b/Tests/test_image_getim.py index 7b5f7a589..07612e587 100644 --- a/Tests/test_image_getim.py +++ b/Tests/test_image_getim.py @@ -1,7 +1,5 @@ from __future__ import annotations -import pytest - from .helper import hopper @@ -10,10 +8,3 @@ def test_sanity() -> None: type_repr = repr(type(im.getim())) assert "PyCapsule" in type_repr - - with pytest.warns(DeprecationWarning, match="id property"): - assert isinstance(im.im.id, int) - - with pytest.warns(DeprecationWarning, match="unsafe_ptrs property"): - ptrs = dict(im.im.unsafe_ptrs) - assert ptrs.keys() == {"image8", "image32", "image"} diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 34c1763b8..bf8e89b53 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -78,16 +78,6 @@ def test_mode_F() -> None: assert list(im.getdata()) == target -@pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24")) -def test_mode_BGR(mode: str) -> None: - data = [(16, 32, 49), (32, 32, 98)] - with pytest.warns(DeprecationWarning, match=mode): - im = Image.new(mode, (1, 2)) - im.putdata(data) - - assert list(im.getdata()) == data - - def test_array_B() -> None: # shouldn't segfault # see https://github.com/python-pillow/Pillow/issues/1008 diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index f700d20c0..270500a44 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -324,7 +324,7 @@ class TestImageResize: im = hopper(mode) assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20)) - @pytest.mark.parametrize("mode", ("1", "P", "BGR;15", "BGR;16")) + @pytest.mark.parametrize("mode", ("1", "P")) def test_default_filter_nearest(self, mode: str) -> None: im = hopper(mode) assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20)) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index b6db0ab5c..55a4a87fb 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -54,10 +54,6 @@ def skip_missing() -> None: def test_sanity() -> None: # basic smoke test. # this mostly follows the cms_test outline. - with pytest.warns(DeprecationWarning, match="PIL.ImageCms.versions"): - v = ImageCms.versions() # should return four strings - assert v[0] == "1.0.0 pil" - assert list(map(type, v)) == [str, str, str, str] # internal version number version = features.version_module("littlecms2") @@ -677,12 +673,6 @@ def test_auxiliary_channels_isolated() -> None: assert_image_equal(test_image.convert(dst_format[2]), reference_image) -def test_long_modes() -> None: - p = ImageCms.getOpenProfile("Tests/icc/sGrey-v2-nano.icc") - with pytest.warns(DeprecationWarning, match="ABCDEFGHI"): - ImageCms.buildTransform(p, p, "ABCDEFGHI", "ABCDEFGHI") - - @pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX")) def test_rgb_lab(mode: str) -> None: im = Image.new(mode, (1, 1)) @@ -700,18 +690,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: - with pytest.warns(DeprecationWarning, match="ImageCms.DESCRIPTION"): - assert ImageCms.DESCRIPTION.strip().startswith("pyCMS") - with pytest.warns(DeprecationWarning, match="ImageCms.VERSION"): - assert ImageCms.VERSION == "1.0.0 pil" - with pytest.warns(DeprecationWarning, match="ImageCms.FLAGS"): - assert isinstance(ImageCms.FLAGS, dict) - - profile = ImageCmsProfile(ImageCms.createProfile("sRGB")) - with pytest.warns(DeprecationWarning, match="RGBA;16B"): - ImageCms.ImageCmsTransform(profile, profile, "RGBA;16B", "RGB") - with pytest.warns(DeprecationWarning, match="RGBA;16B"): - ImageCms.ImageCmsTransform(profile, profile, "RGB", "RGBA;16B") diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 881f9c85d..e1dcbc52c 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1732,8 +1732,3 @@ def test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None: draw.rectangle(xy) with pytest.raises(ValueError): draw.rounded_rectangle(xy) - - -def test_getdraw() -> None: - with pytest.warns(DeprecationWarning, match="'hints' parameter"): - ImageDraw.getdraw(None, []) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index a9444c26d..d4dfb1b6d 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -151,11 +151,6 @@ class TestImageFile: # Despite multiple tiles, assert only one tile caused a read of maxblock size assert reads.count(im.decodermaxblock) == 1 - def test_raise_oserror(self) -> None: - with pytest.warns(DeprecationWarning, match="raise_oserror"): - with pytest.raises(OSError): - ImageFile.raise_oserror(1) - def test_raise_typeerror(self) -> None: with pytest.raises(TypeError): parser = ImageFile.Parser() diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 9334d30e4..4565d35ba 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -11,7 +11,6 @@ from pathlib import Path from typing import Any, BinaryIO import pytest -from packaging.version import parse as parse_version from PIL import Image, ImageDraw, ImageFont, features from PIL._typing import StrOrBytesPath @@ -691,16 +690,6 @@ def test_complex_font_settings() -> None: def test_variation_get(font: ImageFont.FreeTypeFont) -> None: - version = features.version_module("freetype2") - assert version is not None - freetype = parse_version(version) - if freetype < parse_version("2.9.1"): - with pytest.raises(NotImplementedError): - font.get_variation_names() - with pytest.raises(NotImplementedError): - font.get_variation_axes() - return - with pytest.raises(OSError): font.get_variation_names() with pytest.raises(OSError): @@ -763,14 +752,6 @@ def _check_text(font: ImageFont.FreeTypeFont, path: str, epsilon: float) -> None def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None: - version = features.version_module("freetype2") - assert version is not None - freetype = parse_version(version) - if freetype < parse_version("2.9.1"): - with pytest.raises(NotImplementedError): - font.set_variation_by_name("Bold") - return - with pytest.raises(OSError): font.set_variation_by_name("Bold") @@ -790,14 +771,6 @@ def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None: def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None: - version = features.version_module("freetype2") - assert version is not None - freetype = parse_version(version) - if freetype < parse_version("2.9.1"): - with pytest.raises(NotImplementedError): - font.set_variation_by_axes([100]) - return - with pytest.raises(OSError): font.set_variation_by_axes([500, 50]) @@ -1209,15 +1182,3 @@ def test_invalid_truetype_sizes_raise_valueerror( ) -> None: with pytest.raises(ValueError): ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine) - - -def test_freetype_deprecation(monkeypatch: pytest.MonkeyPatch) -> None: - # Arrange: mock features.version_module to return fake FreeType version - def fake_version_module(module: str) -> str: - return "2.9.0" - - monkeypatch.setattr(features, "version_module", fake_version_module) - - # Act / Assert - with pytest.warns(DeprecationWarning, match="FreeType 2.9.0"): - ImageFont.truetype(FONT_PATH, FONT_SIZE) diff --git a/Tests/test_imagemath_lambda_eval.py b/Tests/test_imagemath_lambda_eval.py index eec76118a..26c04b9a0 100644 --- a/Tests/test_imagemath_lambda_eval.py +++ b/Tests/test_imagemath_lambda_eval.py @@ -2,8 +2,6 @@ from __future__ import annotations from typing import Any -import pytest - from PIL import Image, ImageMath @@ -55,11 +53,6 @@ def test_sanity() -> None: ) -def test_options_deprecated() -> None: - with pytest.warns(DeprecationWarning, match="ImageMath.lambda_eval options"): - assert ImageMath.lambda_eval(lambda args: 1, images) == 1 - - def test_ops() -> None: assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, **images)) == "I -1" diff --git a/Tests/test_imagemath_unsafe_eval.py b/Tests/test_imagemath_unsafe_eval.py index 60ad6aafa..5e141a55b 100644 --- a/Tests/test_imagemath_unsafe_eval.py +++ b/Tests/test_imagemath_unsafe_eval.py @@ -35,16 +35,6 @@ def test_sanity() -> None: assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", **images)) == "I 3" -def test_eval_deprecated() -> None: - with pytest.warns(DeprecationWarning, match="ImageMath.eval"): - assert ImageMath.eval("1") == 1 - - -def test_options_deprecated() -> None: - with pytest.warns(DeprecationWarning, match="ImageMath.unsafe_eval options"): - assert ImageMath.unsafe_eval("1", images) == 1 - - def test_ops() -> None: assert pixel(ImageMath.unsafe_eval("-A", **images)) == "I -1" assert pixel(ImageMath.unsafe_eval("+B", **images)) == "L 2" diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 2d6af70eb..da6157d4e 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -361,18 +361,6 @@ class TestLibUnpack: "RGB", "CMYK", 4, (250, 249, 248), (242, 241, 240), (234, 233, 233) ) - def test_BGR(self) -> None: - with pytest.warns(DeprecationWarning, match="BGR;15"): - self.assert_unpack( - "BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8) - ) - with pytest.warns(DeprecationWarning, match="BGR;16"): - self.assert_unpack( - "BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0) - ) - with pytest.warns(DeprecationWarning, match="BGR;24"): - 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)) self.assert_unpack( diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index 9669f485a..a161d3f05 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -25,11 +25,5 @@ def test_pyroma() -> None: ) else: - # Should have a perfect score, but pyroma does not support PEP 639 yet. - assert rating == ( - 9, - [ - "Your package does neither have a license field " - "nor any license classifiers." - ], - ) + # Should have a perfect score + assert rating == (10, []) diff --git a/checks/check_wheel.py b/checks/check_wheel.py index c89d32ed7..3d806eb71 100644 --- a/checks/check_wheel.py +++ b/checks/check_wheel.py @@ -39,9 +39,6 @@ def test_wheel_codecs() -> None: def test_wheel_features() -> None: expected_features = { - "webp_anim", - "webp_mux", - "transp_webp", "raqm", "fribidi", "harfbuzz", diff --git a/docs/deprecations.rst b/docs/deprecations.rst index def98b80a..236554565 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -12,96 +12,6 @@ Deprecated features Below are features which are considered deprecated. Where appropriate, a :py:exc:`DeprecationWarning` is issued. -ImageFile.raise_oserror -~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 10.2.0 - -``ImageFile.raise_oserror()`` has been deprecated and will be removed in Pillow -12.0.0 (2025-10-15). The function is undocumented and is only useful for translating -error codes returned by a codec's ``decode()`` method, which ImageFile already does -automatically. - -IptcImageFile helper functions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 10.2.0 - -The functions ``IptcImageFile.dump`` and ``IptcImageFile.i``, and the constant -``IptcImageFile.PAD`` have been deprecated and will be removed in Pillow -12.0.0 (2025-10-15). These are undocumented helper functions intended -for internal use, so there is no replacement. They can each be replaced -by a single line of code using builtin functions in Python. - -ImageCms constants and versions() function -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 10.3.0 - -A number of constants and a function in :py:mod:`.ImageCms` have been deprecated. -This includes a table of flags based on LittleCMS version 1 which has been -replaced with a new class :py:class:`.ImageCms.Flags` based on LittleCMS 2 flags. - -============================================ ==================================================== -Deprecated Use instead -============================================ ==================================================== -``ImageCms.DESCRIPTION`` No replacement -``ImageCms.VERSION`` ``PIL.__version__`` -``ImageCms.FLAGS["MATRIXINPUT"]`` :py:attr:`.ImageCms.Flags.CLUT_POST_LINEARIZATION` -``ImageCms.FLAGS["MATRIXOUTPUT"]`` :py:attr:`.ImageCms.Flags.FORCE_CLUT` -``ImageCms.FLAGS["MATRIXONLY"]`` No replacement -``ImageCms.FLAGS["NOWHITEONWHITEFIXUP"]`` :py:attr:`.ImageCms.Flags.NOWHITEONWHITEFIXUP` -``ImageCms.FLAGS["NOPRELINEARIZATION"]`` :py:attr:`.ImageCms.Flags.CLUT_PRE_LINEARIZATION` -``ImageCms.FLAGS["GUESSDEVICECLASS"]`` :py:attr:`.ImageCms.Flags.GUESSDEVICECLASS` -``ImageCms.FLAGS["NOTCACHE"]`` :py:attr:`.ImageCms.Flags.NOCACHE` -``ImageCms.FLAGS["NOTPRECALC"]`` :py:attr:`.ImageCms.Flags.NOOPTIMIZE` -``ImageCms.FLAGS["NULLTRANSFORM"]`` :py:attr:`.ImageCms.Flags.NULLTRANSFORM` -``ImageCms.FLAGS["HIGHRESPRECALC"]`` :py:attr:`.ImageCms.Flags.HIGHRESPRECALC` -``ImageCms.FLAGS["LOWRESPRECALC"]`` :py:attr:`.ImageCms.Flags.LOWRESPRECALC` -``ImageCms.FLAGS["GAMUTCHECK"]`` :py:attr:`.ImageCms.Flags.GAMUTCHECK` -``ImageCms.FLAGS["WHITEBLACKCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION` -``ImageCms.FLAGS["BLACKPOINTCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION` -``ImageCms.FLAGS["SOFTPROOFING"]`` :py:attr:`.ImageCms.Flags.SOFTPROOFING` -``ImageCms.FLAGS["PRESERVEBLACK"]`` :py:attr:`.ImageCms.Flags.NONEGATIVES` -``ImageCms.FLAGS["NODEFAULTRESOURCEDEF"]`` :py:attr:`.ImageCms.Flags.NODEFAULTRESOURCEDEF` -``ImageCms.FLAGS["GRIDPOINTS"]`` :py:attr:`.ImageCms.Flags.GRIDPOINTS()` -``ImageCms.versions()`` :py:func:`PIL.features.version_module` with - ``feature="littlecms2"``, :py:data:`sys.version` or - :py:data:`sys.version_info`, and ``PIL.__version__`` -============================================ ==================================================== - -ImageMath eval() -^^^^^^^^^^^^^^^^ - -.. deprecated:: 10.3.0 - -``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. - -Non-image modes in ImageCms -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. deprecated:: 10.4.0 - -The use in :py:mod:`.ImageCms` of input modes and output modes that are not Pillow -image modes has been deprecated. Defaulting to "L" or "1" if the mode cannot be mapped -is also deprecated. - -Support for LibTIFF earlier than 4 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. deprecated:: 10.4.0 - -Support for LibTIFF earlier than version 4 has been deprecated. -Upgrade to a newer version of LibTIFF instead. - ImageDraw.getdraw hints parameter ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -109,72 +19,6 @@ ImageDraw.getdraw hints parameter The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. -FreeType 2.9.0 -^^^^^^^^^^^^^^ - -.. deprecated:: 11.0.0 - -Support for FreeType 2.9.0 is deprecated and will be removed in Pillow 12.0.0 -(2025-10-15), when FreeType 2.9.1 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:`2020-15999`). - -.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/ - -ICNS (width, height, scale) sizes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. deprecated:: 11.0.0 - -Setting an ICNS image size to ``(width, height, scale)`` before loading has been -deprecated. Instead, ``load(scale)`` can be used. - -Image isImageType() -^^^^^^^^^^^^^^^^^^^ - -.. deprecated:: 11.0.0 - -``Image.isImageType(im)`` has been deprecated. Use ``isinstance(im, Image.Image)`` -instead. - -ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. deprecated:: 11.0.0 - -The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and -:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more keyword -arguments can be used instead. - -JpegImageFile.huffman_ac and JpegImageFile.huffman_dc -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. deprecated:: 11.0.0 - -The ``huffman_ac`` and ``huffman_dc`` dictionaries on JPEG images were unused. They -have been deprecated, and will be removed in Pillow 12 (2025-10-15). - -Specific WebP feature checks -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. deprecated:: 11.0.0 - -``features.check("transp_webp")``, ``features.check("webp_mux")`` and -``features.check("webp_anim")`` are now deprecated. They will always return -``True`` if the WebP module is installed, until they are removed in Pillow -12.0.0 (2025-10-15). - -Get internal pointers to objects -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. deprecated:: 11.0.0 - -``Image.core.ImagingCore.id`` and ``Image.core.ImagingCore.unsafe_ptrs`` have been -deprecated and will be removed in Pillow 12 (2025-10-15). They were used for obtaining -raw pointers to ``ImagingCore`` internals. To interact with C code, you can use -``Image.Image.getim()``, which returns a ``Capsule`` object. - ExifTags.IFD.Makernote ^^^^^^^^^^^^^^^^^^^^^^ @@ -221,12 +65,185 @@ Removed features Deprecated features are only removed in major releases after an appropriate period of deprecation has passed. +ImageFile.raise_oserror +^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 10.2.0 +.. versionremoved:: 12.0.0 + +``ImageFile.raise_oserror()`` has been removed. The function was undocumented and was +only useful for translating error codes returned by a codec's ``decode()`` method, +which ImageFile already did automatically. + +IptcImageFile helper functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 10.2.0 +.. versionremoved:: 12.0.0 + +The functions ``IptcImageFile.dump`` and ``IptcImageFile.i``, and the constant +``IptcImageFile.PAD`` have been removed. These were undocumented helper functions +intended for internal use, so there is no replacement. They can each be replaced by a +single line of code using builtin functions in Python. + +ImageCms constants and versions() function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 10.3.0 +.. versionremoved:: 12.0.0 + +A number of constants and a function in :py:mod:`.ImageCms` have been removed. This +includes a table of flags based on LittleCMS version 1 which has been replaced with a +new class :py:class:`.ImageCms.Flags` based on LittleCMS 2 flags. + +============================================ ==================================================== +Deprecated Use instead +============================================ ==================================================== +``ImageCms.DESCRIPTION`` No replacement +``ImageCms.VERSION`` ``PIL.__version__`` +``ImageCms.FLAGS["MATRIXINPUT"]`` :py:attr:`.ImageCms.Flags.CLUT_POST_LINEARIZATION` +``ImageCms.FLAGS["MATRIXOUTPUT"]`` :py:attr:`.ImageCms.Flags.FORCE_CLUT` +``ImageCms.FLAGS["MATRIXONLY"]`` No replacement +``ImageCms.FLAGS["NOWHITEONWHITEFIXUP"]`` :py:attr:`.ImageCms.Flags.NOWHITEONWHITEFIXUP` +``ImageCms.FLAGS["NOPRELINEARIZATION"]`` :py:attr:`.ImageCms.Flags.CLUT_PRE_LINEARIZATION` +``ImageCms.FLAGS["GUESSDEVICECLASS"]`` :py:attr:`.ImageCms.Flags.GUESSDEVICECLASS` +``ImageCms.FLAGS["NOTCACHE"]`` :py:attr:`.ImageCms.Flags.NOCACHE` +``ImageCms.FLAGS["NOTPRECALC"]`` :py:attr:`.ImageCms.Flags.NOOPTIMIZE` +``ImageCms.FLAGS["NULLTRANSFORM"]`` :py:attr:`.ImageCms.Flags.NULLTRANSFORM` +``ImageCms.FLAGS["HIGHRESPRECALC"]`` :py:attr:`.ImageCms.Flags.HIGHRESPRECALC` +``ImageCms.FLAGS["LOWRESPRECALC"]`` :py:attr:`.ImageCms.Flags.LOWRESPRECALC` +``ImageCms.FLAGS["GAMUTCHECK"]`` :py:attr:`.ImageCms.Flags.GAMUTCHECK` +``ImageCms.FLAGS["WHITEBLACKCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION` +``ImageCms.FLAGS["BLACKPOINTCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION` +``ImageCms.FLAGS["SOFTPROOFING"]`` :py:attr:`.ImageCms.Flags.SOFTPROOFING` +``ImageCms.FLAGS["PRESERVEBLACK"]`` :py:attr:`.ImageCms.Flags.NONEGATIVES` +``ImageCms.FLAGS["NODEFAULTRESOURCEDEF"]`` :py:attr:`.ImageCms.Flags.NODEFAULTRESOURCEDEF` +``ImageCms.FLAGS["GRIDPOINTS"]`` :py:attr:`.ImageCms.Flags.GRIDPOINTS()` +``ImageCms.versions()`` :py:func:`PIL.features.version_module` with + ``feature="littlecms2"``, :py:data:`sys.version` or + :py:data:`sys.version_info`, and ``PIL.__version__`` +============================================ ==================================================== + +ImageMath eval() +^^^^^^^^^^^^^^^^ + +.. deprecated:: 10.3.0 +.. versionremoved:: 12.0.0 + +``ImageMath.eval()`` has been removed. 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 +.. versionremoved:: 12.0.0 + +The experimental BGR;15, BGR;16 and BGR;24 modes have been removed. + +Non-image modes in ImageCms +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 10.4.0 +.. versionremoved:: 12.0.0 + +The use in :py:mod:`.ImageCms` of input modes and output modes that are not Pillow +image modes has been removed. Defaulting to "L" or "1" if the mode cannot be mapped has +also been removed. + +Support for LibTIFF earlier than 4 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 10.4.0 +.. versionremoved:: 12.0.0 + +Support for LibTIFF earlier than version 4 has been removed. +Upgrade to a newer version of LibTIFF instead. + +ImageDraw.getdraw hints parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 10.4.0 +.. versionremoved:: 12.0.0 + +The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been removed. + +FreeType 2.9.0 +^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 +.. versionremoved:: 12.0.0 + +Support for FreeType 2.9.0 has been removed. FreeType 2.9.1 is the minimum version +supported. + +We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe +vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`). + +.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/ + +ICNS (width, height, scale) sizes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 + +Setting an ICNS image size to ``(width, height, scale)`` before loading has been +removed. Instead, ``load(scale)`` can be used. + +Image isImageType() +^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 +.. versionremoved:: 12.0.0 + +``Image.isImageType(im)`` has been removed. Use ``isinstance(im, Image.Image)`` +instead. + +ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 +.. versionremoved:: 12.0.0 + +The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and +:py:meth:`~PIL.ImageMath.unsafe_eval()` has been removed. One or more keyword +arguments can be used instead. + +JpegImageFile.huffman_ac and JpegImageFile.huffman_dc +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 +.. versionremoved:: 12.0.0 + +The ``huffman_ac`` and ``huffman_dc`` dictionaries on JPEG images were unused. They +have been removed. + +Specific WebP feature checks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 +.. versionremoved:: 12.0.0 + +``features.check("transp_webp")``, ``features.check("webp_mux")`` and +``features.check("webp_anim")`` have been removed. + +Get internal pointers to objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 +.. versionremoved:: 12.0.0 + +``Image.core.ImagingCore.id`` and ``Image.core.ImagingCore.unsafe_ptrs`` have been +removed. They were used for obtaining raw pointers to ``ImagingCore`` internals. To +interact with C code, you can use ``Image.Image.getim()``, which returns a ``Capsule`` +object. + TiffImagePlugin IFD_LEGACY_API ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionremoved:: 11.0.0 -``TiffImagePlugin.IFD_LEGACY_API`` was removed, as it was an unused setting. +``TiffImagePlugin.IFD_LEGACY_API`` has been removed, as it was an unused setting. PSFile ~~~~~~ diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index a56f94316..c2227f1d2 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -75,7 +75,7 @@ These platforms have been reported to work at the versions mentioned. | Operating system | | Tested Python | | Latest tested | | Tested | | | | versions | | Pillow version | | processors | +==================================+============================+==================+==============+ -| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.2.1 |arm | +| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm | | +----------------------------+------------------+ | | | 3.8 | 10.4.0 | | +----------------------------------+----------------------------+------------------+--------------+ diff --git a/docs/reference/ImageCms.rst b/docs/reference/ImageCms.rst index 238390e75..4a2123677 100644 --- a/docs/reference/ImageCms.rst +++ b/docs/reference/ImageCms.rst @@ -56,7 +56,6 @@ Functions .. autofunction:: get_display_profile .. autofunction:: isIntentSupported .. autofunction:: profileToProfile -.. autofunction:: versions CmsProfile ---------- diff --git a/docs/reference/arrow_support.rst b/docs/reference/arrow_support.rst index 063046d8c..8e8b86c8e 100644 --- a/docs/reference/arrow_support.rst +++ b/docs/reference/arrow_support.rst @@ -21,9 +21,7 @@ with any Arrow provider or consumer in the Python ecosystem. Data formats ============ -Pillow currently supports exporting Arrow images in all modes -**except** for ``BGR;15``, ``BGR;16`` and ``BGR;24``. This is due to -line-length packing in these modes making for non-continuous memory. +Pillow currently supports exporting Arrow images in all modes. For single-band images, the exported array is width*height elements, with each pixel corresponding to the appropriate Arrow type. diff --git a/docs/reference/features.rst b/docs/reference/features.rst index 381d7830a..45067ba35 100644 --- a/docs/reference/features.rst +++ b/docs/reference/features.rst @@ -60,9 +60,6 @@ Support for the following features can be checked: * ``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. -* ``transp_webp``: Deprecated. Always ``True`` if WebP module is installed. -* ``webp_mux``: Deprecated. Always ``True`` if WebP module is installed. -* ``webp_anim``: Deprecated. Always ``True`` if WebP module is installed. .. autofunction:: PIL.features.check_feature .. autofunction:: PIL.features.version_feature diff --git a/docs/releasenotes/11.3.0.rst b/docs/releasenotes/11.3.0.rst index 4af1f68ed..409d50295 100644 --- a/docs/releasenotes/11.3.0.rst +++ b/docs/releasenotes/11.3.0.rst @@ -69,12 +69,14 @@ AVIF support in wheels Support for reading and writing AVIF images is now included in Pillow's wheels, except for Windows ARM64 and iOS. libaom is available as an encoder and dav1d as a decoder. +(Thank you Frankie Dintino and Andrew Murray!) iOS ^^^ Pillow now provides wheels that can be used on iOS ARM64 devices, and on the iOS simulator on ARM64 and x86_64. Currently, only Python 3.13 wheels are available. +(Thank you Russell Keith-Magee and Andrew Murray!) Python 3.14 beta ^^^^^^^^^^^^^^^^ diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst new file mode 100644 index 000000000..68b664443 --- /dev/null +++ b/docs/releasenotes/12.0.0.rst @@ -0,0 +1,140 @@ +12.0.0 +------ + +Security +======== + +TODO +^^^^ + +TODO + +:cve:`YYYY-XXXXX`: TODO +^^^^^^^^^^^^^^^^^^^^^^^ + +TODO + +Backwards incompatible changes +============================== + +ImageFile.raise_oserror +^^^^^^^^^^^^^^^^^^^^^^^ + +``ImageFile.raise_oserror()`` has been removed. The function was undocumented and was +only useful for translating error codes returned by a codec's ``decode()`` method, +which ImageFile already did automatically. + +IptcImageFile helper functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The functions ``IptcImageFile.dump`` and ``IptcImageFile.i``, and the constant +``IptcImageFile.PAD`` have been removed. These were undocumented helper functions +intended for internal use, so there is no replacement. They can each be replaced by a +single line of code using builtin functions in Python. + +ImageCms constants and versions() function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A number of constants and a function in :py:mod:`.ImageCms` have been removed. This +includes a table of flags based on LittleCMS version 1 which has been replaced with a +new class :py:class:`.ImageCms.Flags` based on LittleCMS 2 flags. + +============================================ ==================================================== +Deprecated Use instead +============================================ ==================================================== +``ImageCms.DESCRIPTION`` No replacement +``ImageCms.VERSION`` ``PIL.__version__`` +``ImageCms.FLAGS["MATRIXINPUT"]`` :py:attr:`.ImageCms.Flags.CLUT_POST_LINEARIZATION` +``ImageCms.FLAGS["MATRIXOUTPUT"]`` :py:attr:`.ImageCms.Flags.FORCE_CLUT` +``ImageCms.FLAGS["MATRIXONLY"]`` No replacement +``ImageCms.FLAGS["NOWHITEONWHITEFIXUP"]`` :py:attr:`.ImageCms.Flags.NOWHITEONWHITEFIXUP` +``ImageCms.FLAGS["NOPRELINEARIZATION"]`` :py:attr:`.ImageCms.Flags.CLUT_PRE_LINEARIZATION` +``ImageCms.FLAGS["GUESSDEVICECLASS"]`` :py:attr:`.ImageCms.Flags.GUESSDEVICECLASS` +``ImageCms.FLAGS["NOTCACHE"]`` :py:attr:`.ImageCms.Flags.NOCACHE` +``ImageCms.FLAGS["NOTPRECALC"]`` :py:attr:`.ImageCms.Flags.NOOPTIMIZE` +``ImageCms.FLAGS["NULLTRANSFORM"]`` :py:attr:`.ImageCms.Flags.NULLTRANSFORM` +``ImageCms.FLAGS["HIGHRESPRECALC"]`` :py:attr:`.ImageCms.Flags.HIGHRESPRECALC` +``ImageCms.FLAGS["LOWRESPRECALC"]`` :py:attr:`.ImageCms.Flags.LOWRESPRECALC` +``ImageCms.FLAGS["GAMUTCHECK"]`` :py:attr:`.ImageCms.Flags.GAMUTCHECK` +``ImageCms.FLAGS["WHITEBLACKCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION` +``ImageCms.FLAGS["BLACKPOINTCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION` +``ImageCms.FLAGS["SOFTPROOFING"]`` :py:attr:`.ImageCms.Flags.SOFTPROOFING` +``ImageCms.FLAGS["PRESERVEBLACK"]`` :py:attr:`.ImageCms.Flags.NONEGATIVES` +``ImageCms.FLAGS["NODEFAULTRESOURCEDEF"]`` :py:attr:`.ImageCms.Flags.NODEFAULTRESOURCEDEF` +``ImageCms.FLAGS["GRIDPOINTS"]`` :py:attr:`.ImageCms.Flags.GRIDPOINTS()` +``ImageCms.versions()`` :py:func:`PIL.features.version_module` with + ``feature="littlecms2"``, :py:data:`sys.version` or + :py:data:`sys.version_info`, and ``PIL.__version__`` +============================================ ==================================================== + +ImageMath eval() +^^^^^^^^^^^^^^^^ + +``ImageMath.eval()`` has been removed. Use :py:meth:`~PIL.ImageMath.lambda_eval` or +:py:meth:`~PIL.ImageMath.unsafe_eval` instead. + +BGR;15, BGR 16 and BGR;24 +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The experimental BGR;15, BGR;16 and BGR;24 modes have been removed. + +Non-image modes in ImageCms +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The use in :py:mod:`.ImageCms` of input modes and output modes that are not Pillow +image modes has been removed. Defaulting to "L" or "1" if the mode cannot be mapped has +also been removed. + +Support for LibTIFF earlier than 4 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Support for LibTIFF earlier than version 4 has been removed. +Upgrade to a newer version of LibTIFF instead. + +ImageDraw.getdraw hints parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been removed. + +FreeType 2.9.0 +^^^^^^^^^^^^^^ + +Support for FreeType 2.9.0 has been removed. FreeType 2.9.1 is the minimum version +supported. + +We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe +vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`). + +.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/ + +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 a85f1e075..f66240c89 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 + 12.0.0 11.3.0 11.2.1 11.1.0 diff --git a/docs/releasenotes/template.rst b/docs/releasenotes/template.rst index a453d2a43..b603a9938 100644 --- a/docs/releasenotes/template.rst +++ b/docs/releasenotes/template.rst @@ -20,6 +20,8 @@ Backwards incompatible changes TODO ^^^^ +TODO + Deprecations ============ diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 5a88429e5..197ea7a2b 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -25,7 +25,6 @@ import sys from typing import IO from . import Image, ImageFile, PngImagePlugin, features -from ._deprecate import deprecate enable_jpeg2k = features.check_codec("jpg_2000") if enable_jpeg2k: @@ -275,34 +274,25 @@ class IcnsImageFile(ImageFile.ImageFile): self.best_size[1] * self.best_size[2], ) - @property # type: ignore[override] - def size(self) -> tuple[int, int] | tuple[int, int, int]: + @property + def size(self) -> tuple[int, int]: return self._size @size.setter - def size(self, value: tuple[int, int] | tuple[int, int, int]) -> None: - if len(value) == 3: - deprecate("Setting size to (width, height, scale)", 12, "load(scale)") - if value in self.info["sizes"]: - self._size = value # type: ignore[assignment] + def size(self, value: tuple[int, int]) -> None: + # Check that a matching size exists, + # or that there is a scale that would create a size that matches + for size in self.info["sizes"]: + simple_size = size[0] * size[2], size[1] * size[2] + scale = simple_size[0] // value[0] + if simple_size[1] / value[1] == scale: + self._size = value return - else: - # Check that a matching size exists, - # or that there is a scale that would create a size that matches - for size in self.info["sizes"]: - simple_size = size[0] * size[2], size[1] * size[2] - scale = simple_size[0] // value[0] - if simple_size[1] / value[1] == scale: - self._size = value - return msg = "This is not one of the allowed sizes of this image" raise ValueError(msg) def load(self, scale: int | None = None) -> Image.core.PixelAccess | None: - if scale is not None or len(self.size) == 3: - if scale is None and len(self.size) == 3: - scale = self.size[2] - assert scale is not None + if scale is not None: width, height = self.size[:2] self.size = width * scale, height * scale self.best_size = width, height, scale diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f4f1eea79..9da0011db 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -115,21 +115,6 @@ except ImportError as v: raise -def isImageType(t: Any) -> TypeGuard[Image]: - """ - Checks if an object is an image object. - - .. warning:: - - This function is for internal use only. - - :param t: object to check if it's an image - :returns: True if the object is an image - """ - deprecate("Image.isImageType(im)", 12, "isinstance(im, Image.Image)") - return hasattr(t, "im") - - # # Constants @@ -219,7 +204,7 @@ if TYPE_CHECKING: from IPython.lib.pretty import PrettyPrinter from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin - from ._typing import CapsuleType, NumpyArray, StrOrBytesPath, TypeGuard + from ._typing import CapsuleType, NumpyArray, StrOrBytesPath ID: list[str] = [] OPEN: dict[ str, @@ -980,9 +965,6 @@ class Image: :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 @@ -2229,8 +2211,6 @@ class Image: :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`, :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`. If the image has mode "1" or "P", it is always set to - :py:data:`Resampling.NEAREST`. If the image mode is "BGR;15", - "BGR;16" or "BGR;24", then the default filter is :py:data:`Resampling.NEAREST`. Otherwise, the default filter is :py:data:`Resampling.BICUBIC`. See: :ref:`concept-filters`. :param box: An optional 4-tuple of floats providing @@ -2253,8 +2233,7 @@ class Image: """ if resample is None: - bgr = self.mode.startswith("BGR;") - resample = Resampling.NEAREST if bgr else Resampling.BICUBIC + resample = Resampling.BICUBIC elif resample not in ( Resampling.NEAREST, Resampling.BILINEAR, @@ -3085,9 +3064,6 @@ 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/ImageCms.py b/src/PIL/ImageCms.py index a1584f111..d3555694a 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -23,10 +23,9 @@ import operator 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, __version__ -from ._deprecate import deprecate +from . import Image from ._typing import SupportsRead try: @@ -108,20 +107,6 @@ pyCMS _VERSION = "1.0.0 pil" -def __getattr__(name: str) -> Any: - if name == "DESCRIPTION": - deprecate("PIL.ImageCms.DESCRIPTION", 12) - return _DESCRIPTION - elif name == "VERSION": - deprecate("PIL.ImageCms.VERSION", 12) - return _VERSION - elif name == "FLAGS": - deprecate("PIL.ImageCms.FLAGS", 12, "PIL.ImageCms.Flags") - return _FLAGS - msg = f"module '{__name__}' has no attribute '{name}'" - raise AttributeError(msg) - - # --------------------------------------------------------------------. @@ -301,31 +286,6 @@ class ImageCmsTransform(Image.ImagePointHandler): proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC, flags: Flags = Flags.NONE, ): - supported_modes = ( - "RGB", - "RGBA", - "RGBX", - "CMYK", - "I;16", - "I;16L", - "I;16B", - "YCbCr", - "LAB", - "L", - "1", - ) - for mode in (input_mode, output_mode): - if mode not in supported_modes: - deprecate( - mode, - 12, - { - "L;16": "I;16 or I;16L", - "L:16B": "I;16B", - "YCCA": "YCbCr", - "YCC": "YCbCr", - }.get(mode), - ) if proof is None: self.transform = core.buildTransform( input.profile, output.profile, input_mode, output_mode, intent, flags @@ -1108,16 +1068,3 @@ def isIntentSupported( return -1 except (AttributeError, OSError, TypeError, ValueError) as v: raise PyCMSError(v) from v - - -def versions() -> tuple[str, str | None, str, str]: - """ - (pyCMS) Fetches versions. - """ - - deprecate( - "PIL.ImageCms.versions()", - 12, - '(PIL.features.version("littlecms2"), sys.version, PIL.__version__)', - ) - return _VERSION, core.littlecms_version, sys.version.split()[0], __version__ diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 6cf1ee626..e95fa91f8 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -38,7 +38,6 @@ from types import ModuleType from typing import Any, AnyStr, Callable, Union, cast from . import Image, ImageColor -from ._deprecate import deprecate from ._typing import Coords # experimental access to the outline API @@ -1009,16 +1008,11 @@ def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw: return ImageDraw(im, mode) -def getdraw( - im: Image.Image | None = None, hints: list[str] | None = None -) -> tuple[ImageDraw2.Draw | None, ModuleType]: +def getdraw(im: Image.Image | None = None) -> tuple[ImageDraw2.Draw | None, ModuleType]: """ :param im: The image to draw in. - :param hints: An optional list of hints. Deprecated. :returns: A (drawing context, drawing resource factory) tuple. """ - if hints is not None: - deprecate("'hints' parameter", 12) from . import ImageDraw2 draw = ImageDraw2.Draw(im) if im is not None else None diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index bf556a2c6..27b27127e 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -37,7 +37,6 @@ import struct from typing import IO, Any, NamedTuple, cast from . import ExifTags, Image -from ._deprecate import deprecate from ._util import DeferredError, is_path TYPE_CHECKING = False @@ -83,16 +82,6 @@ def _get_oserror(error: int, *, encoder: bool) -> OSError: return OSError(msg) -def raise_oserror(error: int) -> OSError: - deprecate( - "raise_oserror", - 12, - action="It is only useful for translating error codes returned by a codec's " - "decode() method, which ImageFile already does automatically.", - ) - raise _get_oserror(error, encoder=False) - - def _tilesort(t: _Tile) -> int: # sort on offset return t[2] diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 329c463ff..bf3f471f5 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -36,7 +36,7 @@ from io import BytesIO from types import ModuleType from typing import IO, Any, BinaryIO, TypedDict, cast -from . import Image, features +from . import Image from ._typing import StrOrBytesPath from ._util import DeferredError, is_path @@ -236,21 +236,6 @@ class FreeTypeFont: self.index = index self.encoding = encoding - try: - from packaging.version import parse as parse_version - except ImportError: - pass - else: - if freetype_version := features.version_module("freetype2"): - if parse_version(freetype_version) < parse_version("2.9.1"): - warnings.warn( - "Support for FreeType 2.9.0 is deprecated and will be removed " - "in Pillow 12 (2025-10-15). Please upgrade to FreeType 2.9.1 " - "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/PIL/ImageMath.py b/src/PIL/ImageMath.py index c33809ced..d2504b1ae 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -21,7 +21,6 @@ from types import CodeType from typing import Any, Callable from . import Image, _imagingmath -from ._deprecate import deprecate class _Operand: @@ -233,11 +232,7 @@ ops = { } -def lambda_eval( - expression: Callable[[dict[str, Any]], Any], - options: dict[str, Any] = {}, - **kw: Any, -) -> Any: +def lambda_eval(expression: Callable[[dict[str, Any]], Any], **kw: Any) -> Any: """ Returns the result of an image function. @@ -246,23 +241,13 @@ def lambda_eval( :py:func:`~PIL.Image.merge` function. :param expression: A function that receives a dictionary. - :param options: Values to add to the function's dictionary. Deprecated. - You can instead use one or more keyword arguments. :param **kw: Values to add to the function's dictionary. :return: The expression result. This is usually an image object, but can also be an integer, a floating point value, or a pixel tuple, depending on the expression. """ - if options: - deprecate( - "ImageMath.lambda_eval options", - 12, - "ImageMath.lambda_eval keyword arguments", - ) - args: dict[str, Any] = ops.copy() - args.update(options) args.update(kw) for k, v in args.items(): if isinstance(v, Image.Image): @@ -275,11 +260,7 @@ def lambda_eval( return out -def unsafe_eval( - expression: str, - options: dict[str, Any] = {}, - **kw: Any, -) -> Any: +def unsafe_eval(expression: str, **kw: Any) -> Any: """ Evaluates an image expression. This uses Python's ``eval()`` function to process the expression string, and carries the security risks of doing so. It is not @@ -291,29 +272,19 @@ def unsafe_eval( :py:func:`~PIL.Image.merge` function. :param expression: A string containing a Python-style expression. - :param options: Values to add to the evaluation context. Deprecated. - You can instead use one or more keyword arguments. :param **kw: Values to add to the evaluation context. :return: The evaluated expression. This is usually an image object, but can also be an integer, a floating point value, or a pixel tuple, depending on the expression. """ - if options: - deprecate( - "ImageMath.unsafe_eval options", - 12, - "ImageMath.unsafe_eval keyword arguments", - ) - # build execution namespace args: dict[str, Any] = ops.copy() - for k in [*options, *kw]: + for k in kw: if "__" in k or hasattr(builtins, k): msg = f"'{k}' not allowed" raise ValueError(msg) - args.update(options) args.update(kw) for k, v in args.items(): if isinstance(v, Image.Image): @@ -337,32 +308,3 @@ def unsafe_eval( return out.im except AttributeError: return out - - -def eval( - expression: str, - _dict: dict[str, Any] = {}, - **kw: Any, -) -> Any: - """ - Evaluates an image expression. - - Deprecated. Use lambda_eval() or unsafe_eval() instead. - - :param expression: A string containing a Python-style expression. - :param _dict: Values to add to the evaluation context. You - can either use a dictionary, or one or more keyword - arguments. - :return: The evaluated expression. This is usually an image object, but can - also be an integer, a floating point value, or a pixel tuple, - depending on the expression. - - .. deprecated:: 10.3.0 - """ - - deprecate( - "ImageMath.eval", - 12, - "ImageMath.lambda_eval or ImageMath.unsafe_eval", - ) - return unsafe_eval(expression, _dict, **kw) diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py index 92a08d2cb..b7c6c8636 100644 --- a/src/PIL/ImageMode.py +++ b/src/PIL/ImageMode.py @@ -18,8 +18,6 @@ import sys from functools import lru_cache from typing import NamedTuple -from ._deprecate import deprecate - class ModeDescriptor(NamedTuple): """Wrapper for mode strings.""" @@ -57,16 +55,11 @@ def getmode(mode: str) -> ModeDescriptor: "HSV": ("RGB", "L", ("H", "S", "V"), "|u1"), # extra experimental modes "RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"), - "BGR;15": ("RGB", "L", ("B", "G", "R"), "|u1"), - "BGR;16": ("RGB", "L", ("B", "G", "R"), "|u1"), - "BGR;24": ("RGB", "L", ("B", "G", "R"), "|u1"), "LA": ("L", "L", ("L", "A"), "|u1"), "La": ("L", "L", ("L", "a"), "|u1"), "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) diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index fc024d668..b1fbb1bf1 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -16,26 +16,16 @@ # from __future__ import annotations -from collections.abc import Sequence from io import BytesIO from typing import cast from . import Image, ImageFile from ._binary import i16be as i16 from ._binary import i32be as i32 -from ._deprecate import deprecate COMPRESSION = {1: "raw", 5: "jpeg"} -def __getattr__(name: str) -> bytes: - if name == "PAD": - deprecate("IptcImagePlugin.PAD", 12) - return b"\0\0\0\0" - msg = f"module '{__name__}' has no attribute '{name}'" - raise AttributeError(msg) - - # # Helpers @@ -48,20 +38,6 @@ def _i8(c: int | bytes) -> int: return c if isinstance(c, int) else c[0] -def i(c: bytes) -> int: - """.. deprecated:: 10.2.0""" - deprecate("IptcImagePlugin.i", 12) - return _i(c) - - -def dump(c: Sequence[int | bytes]) -> None: - """.. deprecated:: 10.2.0""" - deprecate("IptcImagePlugin.dump", 12) - for i in c: - print(f"{_i8(i):02x}", end=" ") - print() - - ## # Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields # from TIFF and JPEG files, use the getiptcinfo function. diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index defe9f773..082f3551a 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -49,7 +49,6 @@ from ._binary import i16be as i16 from ._binary import i32be as i32 from ._binary import o8 from ._binary import o16be as o16 -from ._deprecate import deprecate from .JpegPresets import presets TYPE_CHECKING = False @@ -393,12 +392,6 @@ class JpegImageFile(ImageFile.ImageFile): self._read_dpi_from_exif() - def __getattr__(self, name: str) -> Any: - if name in ("huffman_ac", "huffman_dc"): - deprecate(name, 12) - return getattr(self, "_" + name) - raise AttributeError(name) - def __getstate__(self) -> list[Any]: return super().__getstate__() + [self.layers, self.layer] diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index daf20f2e8..c1850f084 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -56,7 +56,6 @@ from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags from ._binary import i16be as i16 from ._binary import i32be as i32 from ._binary import o8 -from ._deprecate import deprecate from ._typing import StrOrBytesPath from ._util import DeferredError, is_path from .TiffTags import TYPES @@ -284,9 +283,6 @@ PREFIXES = [ b"II\x2b\x00", # BigTIFF with little-endian byte order ] -if not getattr(Image.core, "libtiff_support_custom_tags", True): - deprecate("Support for LibTIFF earlier than version 4", 12) - def _accept(prefix: bytes) -> bool: return prefix.startswith(tuple(PREFIXES)) @@ -1934,9 +1930,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: # Custom items are supported for int, float, unicode, string and byte # values. Other types and tuples require a tagtype. if tag not in TiffTags.LIBTIFF_CORE: - if not getattr(Image.core, "libtiff_support_custom_tags", False): - continue - if tag in TiffTags.TAGS_V2_GROUPS: types[tag] = TiffTags.LONG8 elif tag in ifd.tagtype: diff --git a/src/PIL/_deprecate.py b/src/PIL/_deprecate.py index 170d44490..616a9aace 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 == 12: - removed = "Pillow 12 (2025-10-15)" elif when == 13: removed = "Pillow 13 (2026-10-15)" else: diff --git a/src/PIL/features.py b/src/PIL/features.py index 573f1d412..984f7532c 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -121,9 +121,6 @@ def get_supported_codecs() -> list[str]: features: dict[str, tuple[str, str | bool, str | None]] = { - "webp_anim": ("PIL._webp", True, None), - "webp_mux": ("PIL._webp", True, None), - "transp_webp": ("PIL._webp", True, None), "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"), "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"), "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"), diff --git a/src/_imaging.c b/src/_imaging.c index 6f13834a9..fbfc0e41a 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -681,30 +681,6 @@ getink(PyObject *color, Imaging im, char *ink) { } else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) { return NULL; } - if (!strcmp(im->mode, "BGR;15")) { - UINT16 v = ((((UINT16)r) << 7) & 0x7c00) + - ((((UINT16)g) << 2) & 0x03e0) + - ((((UINT16)b) >> 3) & 0x001f); - - ink[0] = (UINT8)v; - ink[1] = (UINT8)(v >> 8); - ink[2] = ink[3] = 0; - return ink; - } else if (!strcmp(im->mode, "BGR;16")) { - UINT16 v = ((((UINT16)r) << 8) & 0xf800) + - ((((UINT16)g) << 3) & 0x07e0) + - ((((UINT16)b) >> 3) & 0x001f); - ink[0] = (UINT8)v; - ink[1] = (UINT8)(v >> 8); - ink[2] = ink[3] = 0; - return ink; - } else if (!strcmp(im->mode, "BGR;24")) { - ink[0] = (UINT8)b; - ink[1] = (UINT8)g; - ink[2] = (UINT8)r; - ink[3] = 0; - return ink; - } } } @@ -1650,54 +1626,33 @@ _putdata(ImagingObject *self, PyObject *args) { return NULL; } double value; - if (image->bands == 1) { - int bigendian = 0; - if (image->type == IMAGING_TYPE_SPECIAL) { - // I;16* - if ( - strcmp(image->mode, "I;16B") == 0 + int bigendian = 0; + if (image->type == IMAGING_TYPE_SPECIAL) { + // I;16* + if ( + strcmp(image->mode, "I;16B") == 0 #ifdef WORDS_BIGENDIAN - || strcmp(image->mode, "I;16N") == 0 + || strcmp(image->mode, "I;16N") == 0 #endif - ) { - bigendian = 1; - } + ) { + bigendian = 1; } - for (i = x = y = 0; i < n; i++) { - set_value_to_item(seq, i); - if (scale != 1.0 || offset != 0.0) { - value = value * scale + offset; - } - if (image->type == IMAGING_TYPE_SPECIAL) { - image->image8[y][x * 2 + (bigendian ? 1 : 0)] = - CLIP8((int)value % 256); - image->image8[y][x * 2 + (bigendian ? 0 : 1)] = - CLIP8((int)value >> 8); - } else { - image->image8[y][x] = (UINT8)CLIP8(value); - } - if (++x >= (int)image->xsize) { - x = 0, y++; - } + } + for (i = x = y = 0; i < n; i++) { + set_value_to_item(seq, i); + if (scale != 1.0 || offset != 0.0) { + value = value * scale + offset; } - } else { - // BGR;* - int b; - for (i = x = y = 0; i < n; i++) { - char ink[4]; - - op = PySequence_Fast_GET_ITEM(seq, i); - if (!op || !getink(op, image, ink)) { - Py_DECREF(seq); - return NULL; - } - /* FIXME: what about scale and offset? */ - for (b = 0; b < image->pixelsize; b++) { - image->image8[y][x * image->pixelsize + b] = ink[b]; - } - if (++x >= (int)image->xsize) { - x = 0, y++; - } + if (image->type == IMAGING_TYPE_SPECIAL) { + image->image8[y][x * 2 + (bigendian ? 1 : 0)] = + CLIP8((int)value % 256); + image->image8[y][x * 2 + (bigendian ? 0 : 1)] = + CLIP8((int)value >> 8); + } else { + image->image8[y][x] = (UINT8)CLIP8(value); + } + if (++x >= (int)image->xsize) { + x = 0, y++; } } PyErr_Clear(); /* Avoid weird exceptions */ @@ -3769,18 +3724,6 @@ _getattr_bands(ImagingObject *self, void *closure) { return PyLong_FromLong(self->image->bands); } -static PyObject * -_getattr_id(ImagingObject *self, void *closure) { - if (PyErr_WarnEx( - PyExc_DeprecationWarning, - "id property is deprecated and will be removed in Pillow 12 (2025-10-15)", - 1 - ) < 0) { - return NULL; - } - return PyLong_FromSsize_t((Py_ssize_t)self->image); -} - static void _ptr_destructor(PyObject *capsule) { PyObject *self = (PyObject *)PyCapsule_GetContext(capsule); @@ -3795,27 +3738,6 @@ _getattr_ptr(ImagingObject *self, void *closure) { return capsule; } -static PyObject * -_getattr_unsafe_ptrs(ImagingObject *self, void *closure) { - if (PyErr_WarnEx( - PyExc_DeprecationWarning, - "unsafe_ptrs property is deprecated and will be removed in Pillow 12 " - "(2025-10-15)", - 1 - ) < 0) { - return NULL; - } - return Py_BuildValue( - "(sn)(sn)(sn)", - "image8", - self->image->image8, - "image32", - self->image->image32, - "image", - self->image->image - ); -} - static PyObject * _getattr_readonly(ImagingObject *self, void *closure) { return PyLong_FromLong(self->image->read_only); @@ -3825,9 +3747,7 @@ static struct PyGetSetDef getsetters[] = { {"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {"bands", (getter)_getattr_bands}, - {"id", (getter)_getattr_id}, {"ptr", (getter)_getattr_ptr}, - {"unsafe_ptrs", (getter)_getattr_unsafe_ptrs}, {"readonly", (getter)_getattr_readonly}, {NULL} }; @@ -4432,16 +4352,6 @@ setup_module(PyObject *m) { PyObject *v = PyUnicode_FromString(ImagingTiffVersion()); PyDict_SetItemString(d, "libtiff_version", v ? v : Py_None); Py_XDECREF(v); - - // Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7 - PyObject *support_custom_tags; -#if TIFFLIB_VERSION >= 20111221 && TIFFLIB_VERSION != 20120218 && \ - TIFFLIB_VERSION != 20120922 - support_custom_tags = Py_True; -#else - support_custom_tags = Py_False; -#endif - PyDict_SetItemString(d, "libtiff_support_custom_tags", support_custom_tags); } #endif diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 1c1937105..3db52377e 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -82,31 +82,6 @@ get_pixel_16B(Imaging im, int x, int y, void *color) { #endif } -static void -get_pixel_BGR15(Imaging im, int x, int y, void *color) { - UINT8 *in = (UINT8 *)&im->image8[y][x * 2]; - UINT16 pixel = in[0] + (in[1] << 8); - char *out = color; - out[0] = (pixel & 31) * 255 / 31; - out[1] = ((pixel >> 5) & 31) * 255 / 31; - out[2] = ((pixel >> 10) & 31) * 255 / 31; -} - -static void -get_pixel_BGR16(Imaging im, int x, int y, void *color) { - UINT8 *in = (UINT8 *)&im->image8[y][x * 2]; - UINT16 pixel = in[0] + (in[1] << 8); - char *out = color; - out[0] = (pixel & 31) * 255 / 31; - out[1] = ((pixel >> 5) & 63) * 255 / 63; - out[2] = ((pixel >> 11) & 31) * 255 / 31; -} - -static void -get_pixel_BGR24(Imaging im, int x, int y, void *color) { - memcpy(color, &im->image8[y][x * 3], sizeof(UINT8) * 3); -} - static void get_pixel_32(Imaging im, int x, int y, void *color) { memcpy(color, &im->image32[y][x], sizeof(INT32)); @@ -154,16 +129,6 @@ put_pixel_16B(Imaging im, int x, int y, const void *color) { out[1] = in[0]; } -static void -put_pixel_BGR1516(Imaging im, int x, int y, const void *color) { - memcpy(&im->image8[y][x * 2], color, 2); -} - -static void -put_pixel_BGR24(Imaging im, int x, int y, const void *color) { - memcpy(&im->image8[y][x * 3], color, 3); -} - static void put_pixel_32L(Imaging im, int x, int y, const void *color) { memcpy(&im->image8[y][x * 4], color, 4); @@ -212,9 +177,6 @@ ImagingAccessInit(void) { ADD("F", get_pixel_32, put_pixel_32); ADD("P", get_pixel_8, put_pixel_8); ADD("PA", get_pixel_32_2bands, put_pixel_32); - ADD("BGR;15", get_pixel_BGR15, put_pixel_BGR1516); - ADD("BGR;16", get_pixel_BGR16, put_pixel_BGR1516); - ADD("BGR;24", get_pixel_BGR24, put_pixel_BGR24); ADD("RGB", get_pixel_32, put_pixel_32); ADD("RGBA", get_pixel_32, put_pixel_32); ADD("RGBa", get_pixel_32, put_pixel_32); diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index c8f234261..9a2c9ff16 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -277,38 +277,6 @@ rgb2f(UINT8 *out_, const UINT8 *in, int xsize) { } } -static void -rgb2bgr15(UINT8 *out_, const UINT8 *in, int xsize) { - int x; - for (x = 0; x < xsize; x++, in += 4, out_ += 2) { - UINT16 v = ((((UINT16)in[0]) << 7) & 0x7c00) + - ((((UINT16)in[1]) << 2) & 0x03e0) + - ((((UINT16)in[2]) >> 3) & 0x001f); - memcpy(out_, &v, sizeof(v)); - } -} - -static void -rgb2bgr16(UINT8 *out_, const UINT8 *in, int xsize) { - int x; - for (x = 0; x < xsize; x++, in += 4, out_ += 2) { - UINT16 v = ((((UINT16)in[0]) << 8) & 0xf800) + - ((((UINT16)in[1]) << 3) & 0x07e0) + - ((((UINT16)in[2]) >> 3) & 0x001f); - memcpy(out_, &v, sizeof(v)); - } -} - -static void -rgb2bgr24(UINT8 *out, const UINT8 *in, int xsize) { - int x; - for (x = 0; x < xsize; x++, in += 4) { - *out++ = in[2]; - *out++ = in[1]; - *out++ = in[0]; - } -} - static void rgb2hsv_row(UINT8 *out, const UINT8 *in) { // following colorsys.py float h, s, rc, gc, bc, cr; @@ -971,9 +939,6 @@ static struct { {"RGB", "I;16N", rgb2i16l}, #endif {"RGB", "F", rgb2f}, - {"RGB", "BGR;15", rgb2bgr15}, - {"RGB", "BGR;16", rgb2bgr16}, - {"RGB", "BGR;24", rgb2bgr24}, {"RGB", "RGBA", rgb2rgba}, {"RGB", "RGBa", rgb2rgba}, {"RGB", "RGBX", rgb2rgba}, diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index c29473d90..7f8a50d19 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -471,12 +471,6 @@ copy2(UINT8 *out, const UINT8 *in, int pixels) { memcpy(out, in, pixels * 2); } -static void -copy3(UINT8 *out, const UINT8 *in, int pixels) { - /* BGR;24, etc */ - memcpy(out, in, pixels * 3); -} - static void copy4(UINT8 *out, const UINT8 *in, int pixels) { /* RGBA, CMYK quadruples */ @@ -657,9 +651,6 @@ static struct { {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian. {"I;16L", "I;16N", 16, packI16N_I16}, {"I;16B", "I;16N", 16, packI16N_I16B}, - {"BGR;15", "BGR;15", 16, copy2}, - {"BGR;16", "BGR;16", 16, copy2}, - {"BGR;24", "BGR;24", 24, copy3}, {NULL} /* sentinel */ }; diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c index 6fe26e1bd..4640f078a 100644 --- a/src/libImaging/Storage.c +++ b/src/libImaging/Storage.c @@ -151,36 +151,6 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { strcpy(im->band_names[2], "B"); strcpy(im->band_names[3], "X"); - } else if (strcmp(mode, "BGR;15") == 0) { - /* EXPERIMENTAL */ - /* 15-bit reversed true colour */ - im->bands = 3; - im->pixelsize = 2; - im->linesize = (xsize * 2 + 3) & -4; - im->type = IMAGING_TYPE_SPECIAL; - /* not allowing arrow due to line length packing */ - strcpy(im->arrow_band_format, ""); - - } else if (strcmp(mode, "BGR;16") == 0) { - /* EXPERIMENTAL */ - /* 16-bit reversed true colour */ - im->bands = 3; - im->pixelsize = 2; - im->linesize = (xsize * 2 + 3) & -4; - im->type = IMAGING_TYPE_SPECIAL; - /* not allowing arrow due to line length packing */ - strcpy(im->arrow_band_format, ""); - - } else if (strcmp(mode, "BGR;24") == 0) { - /* EXPERIMENTAL */ - /* 24-bit reversed true colour */ - im->bands = 3; - im->pixelsize = 3; - im->linesize = (xsize * 3 + 3) & -4; - im->type = IMAGING_TYPE_SPECIAL; - /* not allowing arrow due to line length packing */ - strcpy(im->arrow_band_format, ""); - } else if (strcmp(mode, "RGBX") == 0) { /* 32-bit true colour images with padding */ im->bands = im->pixelsize = 4; diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index e289ce405..71516fd1b 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -884,7 +884,6 @@ ImagingLibTiffMergeFieldInfo( // Refer to libtiff docs (http://www.simplesystems.org/libtiff/addingtags.html) TIFFSTATE *clientstate = (TIFFSTATE *)state->context; uint32_t n; - int status = 0; // custom fields added with ImagingLibTiffMergeFieldInfo are only used for // decoding, ignore readcount; @@ -907,14 +906,7 @@ ImagingLibTiffMergeFieldInfo( n = sizeof(info) / sizeof(info[0]); - // Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7 -#if TIFFLIB_VERSION >= 20111221 && TIFFLIB_VERSION != 20120218 && \ - TIFFLIB_VERSION != 20120922 - status = TIFFMergeFieldInfo(clientstate->tiff, info, n); -#else - TIFFMergeFieldInfo(clientstate->tiff, info, n); -#endif - return status; + return TIFFMergeFieldInfo(clientstate->tiff, info, n); } int diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 9c3ee2665..976baa726 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1284,12 +1284,6 @@ copy2(UINT8 *out, const UINT8 *in, int pixels) { memcpy(out, in, pixels * 2); } -static void -copy3(UINT8 *out, const UINT8 *in, int pixels) { - /* BGR;24 */ - memcpy(out, in, pixels * 3); -} - static void copy4(UINT8 *out, const UINT8 *in, int pixels) { /* RGBA, CMYK quadruples */ @@ -1649,10 +1643,6 @@ static struct { {"RGB", "B;16B", 16, band216B}, {"RGB", "CMYK", 32, cmyk2rgb}, - {"BGR;15", "BGR;15", 16, copy2}, - {"BGR;16", "BGR;16", 16, copy2}, - {"BGR;24", "BGR;24", 24, copy3}, - /* true colour w. alpha */ {"RGBA", "LA", 16, unpackRGBALA}, {"RGBA", "LA;16B", 32, unpackRGBALA16B},