From ad6c4f82f3d85df5e51d99916fd68f0d8b180244 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Feb 2025 09:27:16 +1100 Subject: [PATCH 01/24] Updated lcms2 to 2.17 --- .github/workflows/wheels-dependencies.sh | 2 +- docs/installation/building-from-source.rst | 2 +- winbuild/build_prepare.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index edf5ba937..155e5fb13 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -44,7 +44,7 @@ JPEGTURBO_VERSION=3.1.0 OPENJPEG_VERSION=2.5.3 XZ_VERSION=5.6.4 TIFF_VERSION=4.6.0 -LCMS2_VERSION=2.16 +LCMS2_VERSION=2.17 ZLIB_NG_VERSION=2.2.4 LIBWEBP_VERSION=1.5.0 BZIP2_VERSION=1.0.8 diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 46a4c1245..b400a3436 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -51,7 +51,7 @@ Many of Pillow's features require external libraries: * **littlecms** provides color management * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and - above uses liblcms2. Tested with **1.19** and **2.7-2.16**. + above uses liblcms2. Tested with **1.19** and **2.7-2.17**. * **libwebp** provides the WebP format. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index f942716cb..e3509aee6 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -115,7 +115,7 @@ V = { "FRIBIDI": "1.0.16", "HARFBUZZ": "10.2.0", "JPEGTURBO": "3.1.0", - "LCMS2": "2.16", + "LCMS2": "2.17", "LIBIMAGEQUANT": "4.3.4", "LIBPNG": "1.6.46", "LIBWEBP": "1.5.0", From 4b7e75be2d9305ffa25e113740aa08ff5d4fed74 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Feb 2025 20:47:17 +1100 Subject: [PATCH 02/24] Test errors --- Tests/test_file_sun.py | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 6cfff8730..ebb069379 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -1,10 +1,11 @@ from __future__ import annotations +import io import os import pytest -from PIL import Image, SunImagePlugin +from PIL import Image, SunImagePlugin, _binary from .helper import assert_image_equal_tofile, assert_image_similar, hopper @@ -33,6 +34,44 @@ def test_im1() -> None: assert_image_equal_tofile(im, "Tests/images/sunraster.im1.png") +def _sun_header( + depth: int = 0, file_type: int = 0, palette_length: int = 0 +) -> io.BytesIO: + return io.BytesIO( + _binary.o32be(0x59A66A95) + + b"\x00" * 8 + + _binary.o32be(depth) + + b"\x00" * 4 + + _binary.o32be(file_type) + + b"\x00" * 4 + + _binary.o32be(palette_length) + ) + + +def test_unsupported_mode_bit_depth() -> None: + with pytest.raises(SyntaxError, match="Unsupported Mode/Bit Depth"): + with SunImagePlugin.SunImageFile(_sun_header()): + pass + + +def test_unsupported_color_palette_length() -> None: + with pytest.raises(SyntaxError, match="Unsupported Color Palette Length"): + with SunImagePlugin.SunImageFile(_sun_header(depth=1, palette_length=1025)): + pass + + +def test_unsupported_palette_type() -> None: + with pytest.raises(SyntaxError, match="Unsupported Palette Type"): + with SunImagePlugin.SunImageFile(_sun_header(depth=1, palette_length=1)): + pass + + +def test_unsupported_file_type() -> None: + with pytest.raises(SyntaxError, match="Unsupported Sun Raster file type"): + with SunImagePlugin.SunImageFile(_sun_header(depth=1, file_type=6)): + pass + + @pytest.mark.skipif( not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" ) From 5d40e6aead98fd7154c889b027c79f3e15c06661 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Feb 2025 20:29:35 +1100 Subject: [PATCH 03/24] Test RGBX raw mode --- Tests/test_file_sun.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index ebb069379..c2f162cf9 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -72,6 +72,22 @@ def test_unsupported_file_type() -> None: pass +@pytest.mark.skipif( + not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" +) +def test_rgbx() -> None: + with open(os.path.join(EXTRA_DIR, "32bpp.ras"), "rb") as fp: + data = fp.read() + + # Set file type to 3 + data = data[:20] + _binary.o32be(3) + data[24:] + + with Image.open(io.BytesIO(data)) as im: + r, g, b = im.split() + im = Image.merge("RGB", (b, g, r)) + assert_image_equal_tofile(im, os.path.join(EXTRA_DIR, "32bpp.png")) + + @pytest.mark.skipif( not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" ) From b096018fdd5c6c36211023fd372c16a66b7a52f3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Feb 2025 22:27:13 +1100 Subject: [PATCH 04/24] Update Sphinx to 8.2 to remove nitpick ignore --- docs/conf.py | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index e1e3f1b8f..bfbcf9151 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ import PIL # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = "8.1" +needs_sphinx = "8.2" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -121,7 +121,7 @@ nitpicky = True # generating warnings in “nitpicky mode”. Note that type should include the domain name # if present. Example entries would be ('py:func', 'int') or # ('envvar', 'LD_LIBRARY_PATH'). -nitpick_ignore = [("py:class", "_io.BytesIO"), ("py:class", "_CmsProfileCompatible")] +nitpick_ignore = [("py:class", "_CmsProfileCompatible")] # -- Options for HTML output ---------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index aaaba0032..2ffd9faca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ dynamic = [ optional-dependencies.docs = [ "furo", "olefile", - "sphinx>=8.1", + "sphinx>=8.2", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph", From 4415b4ad3631a96fb70610ba6672e5c14dcfa174 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Feb 2025 08:47:04 +1100 Subject: [PATCH 05/24] Updated libpng to 1.6.47 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index f0c96d160..0f8eac5bb 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -39,7 +39,7 @@ ARCHIVE_SDIR=pillow-depends-main # Package versions for fresh source builds FREETYPE_VERSION=2.13.3 HARFBUZZ_VERSION=10.2.0 -LIBPNG_VERSION=1.6.46 +LIBPNG_VERSION=1.6.47 JPEGTURBO_VERSION=3.1.0 OPENJPEG_VERSION=2.5.3 XZ_VERSION=5.6.4 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index f942716cb..5665abaab 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -117,7 +117,7 @@ V = { "JPEGTURBO": "3.1.0", "LCMS2": "2.16", "LIBIMAGEQUANT": "4.3.4", - "LIBPNG": "1.6.46", + "LIBPNG": "1.6.47", "LIBWEBP": "1.5.0", "OPENJPEG": "2.5.3", "TIFF": "4.6.0", From dc94d1d8bba208cf7f24e207a3536ac6eaf22fa9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Feb 2025 18:27:05 +1100 Subject: [PATCH 06/24] Test opening file with plugin directly --- Tests/test_file_mpo.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index ab8f2d5a1..6b4f6423b 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -29,12 +29,17 @@ def roundtrip(im: Image.Image, **options: Any) -> ImageFile.ImageFile: @pytest.mark.parametrize("test_file", test_files) def test_sanity(test_file: str) -> None: - with Image.open(test_file) as im: + def check(im: ImageFile.ImageFile) -> None: im.load() assert im.mode == "RGB" assert im.size == (640, 480) assert im.format == "MPO" + with Image.open(test_file) as im: + check(im) + with MpoImagePlugin.MpoImageFile(test_file) as im: + check(im) + @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: From ae6bb4cac2f666715666a05b46fa43942ef19201 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 19 Feb 2025 23:28:25 +1100 Subject: [PATCH 07/24] Test invalid texture compression format --- Tests/test_file_ftex.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Tests/test_file_ftex.py b/Tests/test_file_ftex.py index 0c544245a..fdd7b3757 100644 --- a/Tests/test_file_ftex.py +++ b/Tests/test_file_ftex.py @@ -1,5 +1,8 @@ from __future__ import annotations +import io +import struct + import pytest from PIL import FtexImagePlugin, Image @@ -23,3 +26,15 @@ def test_invalid_file() -> None: with pytest.raises(SyntaxError): FtexImagePlugin.FtexImageFile(invalid_file) + + +def test_invalid_texture() -> None: + with open("Tests/images/ftex_dxt1.ftc", "rb") as fp: + data = fp.read() + + # Change texture compression format + data = data[:24] + struct.pack(" Date: Thu, 20 Feb 2025 07:57:10 +1100 Subject: [PATCH 08/24] Only set mode when necessary --- src/PIL/FtexImagePlugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/FtexImagePlugin.py b/src/PIL/FtexImagePlugin.py index 26e5bd4a6..d60e75bb6 100644 --- a/src/PIL/FtexImagePlugin.py +++ b/src/PIL/FtexImagePlugin.py @@ -79,8 +79,6 @@ class FtexImageFile(ImageFile.ImageFile): self._size = struct.unpack("<2i", self.fp.read(8)) mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8)) - self._mode = "RGB" - # Only support single-format files. # I don't know of any multi-format file. assert format_count == 1 @@ -95,6 +93,7 @@ class FtexImageFile(ImageFile.ImageFile): self._mode = "RGBA" self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))] elif format == Format.UNCOMPRESSED: + self._mode = "RGB" self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, "RGB")] else: msg = f"Invalid texture compression format: {repr(format)}" From ae7c4920c9fba33b93c8938d07a9c16495dd6d69 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Feb 2025 08:09:44 +1100 Subject: [PATCH 09/24] Test that subsequent compile() calls do not change anything --- Tests/test_fontfile.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Tests/test_fontfile.py b/Tests/test_fontfile.py index 206499a04..575dada86 100644 --- a/Tests/test_fontfile.py +++ b/Tests/test_fontfile.py @@ -4,7 +4,20 @@ from pathlib import Path import pytest -from PIL import FontFile +from PIL import FontFile, Image + + +def test_compile() -> None: + font = FontFile.FontFile() + font.glyph[0] = ((0, 0), (0, 0, 0, 0), (0, 0, 0, 1), Image.new("L", (0, 0))) + font.compile() + assert font.ysize == 1 + + font.ysize = 2 + font.compile() + + # Assert that compiling again did not change anything + assert font.ysize == 2 def test_save(tmp_path: Path) -> None: From 85f439f575a1ae5e3e11262e9b0a6d838f541aa6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 25 Feb 2025 18:46:22 +1100 Subject: [PATCH 10/24] _seek_check already raises an EOFError --- src/PIL/MicImagePlugin.py | 7 +------ src/PIL/PsdImagePlugin.py | 14 +++++--------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index bbddd972e..9ce38c427 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -73,12 +73,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): def seek(self, frame: int) -> None: if not self._seek_check(frame): return - try: - filename = self.images[frame] - except IndexError as e: - msg = "no such frame" - raise EOFError(msg) from e - + filename = self.images[frame] self.fp = self.ole.openstream(filename) TiffImagePlugin.TiffImageFile._open(self) diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index c59d302e5..0aada8a06 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -169,15 +169,11 @@ class PsdImageFile(ImageFile.ImageFile): return # seek to given layer (1..max) - try: - _, mode, _, tile = self.layers[layer - 1] - self._mode = mode - self.tile = tile - self.frame = layer - self.fp = self._fp - except IndexError as e: - msg = "no such layer" - raise EOFError(msg) from e + _, mode, _, tile = self.layers[layer - 1] + self._mode = mode + self.tile = tile + self.frame = layer + self.fp = self._fp def tell(self) -> int: # return layer number (0=image, 1..max=layers) From 153fd4801c2344a41260fe8b4b83bd491e51f535 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Feb 2025 22:24:48 +1100 Subject: [PATCH 11/24] Revert "Do not install libimagequant" This reverts commit 1e115987afbc92aef02b489ed8fea1875821d174. --- .github/workflows/test-mingw.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index 045926482..bb6d7dc37 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -60,6 +60,7 @@ jobs: mingw-w64-x86_64-gcc \ mingw-w64-x86_64-ghostscript \ mingw-w64-x86_64-lcms2 \ + mingw-w64-x86_64-libimagequant \ mingw-w64-x86_64-libjpeg-turbo \ mingw-w64-x86_64-libraqm \ mingw-w64-x86_64-libtiff \ From d6b94421d0eff83249adf0c4191d3b3eda2b5b90 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Mar 2025 11:37:49 +1100 Subject: [PATCH 12/24] Updated harfbuzz to 10.4.0 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index f0c96d160..50b7ad488 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -38,7 +38,7 @@ ARCHIVE_SDIR=pillow-depends-main # Package versions for fresh source builds FREETYPE_VERSION=2.13.3 -HARFBUZZ_VERSION=10.2.0 +HARFBUZZ_VERSION=10.4.0 LIBPNG_VERSION=1.6.46 JPEGTURBO_VERSION=3.1.0 OPENJPEG_VERSION=2.5.3 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index f942716cb..c21258cb9 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -113,7 +113,7 @@ V = { "BROTLI": "1.1.0", "FREETYPE": "2.13.3", "FRIBIDI": "1.0.16", - "HARFBUZZ": "10.2.0", + "HARFBUZZ": "10.4.0", "JPEGTURBO": "3.1.0", "LCMS2": "2.16", "LIBIMAGEQUANT": "4.3.4", From ff4f5d4cb68f6cfe9713dd72ad343e505b57b1f5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Mar 2025 21:41:30 +1100 Subject: [PATCH 13/24] Test ValueError --- Tests/test_font_bdf.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index 136070f9e..8d78019b3 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -1,5 +1,7 @@ from __future__ import annotations +import io + import pytest from PIL import BdfFontFile, FontFile @@ -8,13 +10,20 @@ filename = "Tests/images/courB08.bdf" def test_sanity() -> None: - with open(filename, "rb") as test_file: - font = BdfFontFile.BdfFontFile(test_file) + with open(filename, "rb") as fp: + font = BdfFontFile.BdfFontFile(fp) assert isinstance(font, FontFile.FontFile) assert len([_f for _f in font.glyph if _f]) == 190 +def test_valueerror() -> None: + with open(filename, "rb") as fp: + data = fp.read() + data = data[:2650] + b"\x00\x00" + data[2652:] + BdfFontFile.BdfFontFile(io.BytesIO(data)) + + def test_invalid_file() -> None: with open("Tests/images/flower.jpg", "rb") as fp: with pytest.raises(SyntaxError): From 397f8c752b583cd781fa21fe03149c8781035247 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 1 Mar 2025 20:50:23 +0000 Subject: [PATCH 14/24] Update dependency cibuildwheel to v2.23.0 --- .ci/requirements-cibw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index 833aca23d..2fd3eb6ff 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==2.22.0 +cibuildwheel==2.23.0 From db4534a8cf35bd7d3a531f5e19e889c7b7c63c30 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 2 Mar 2025 12:00:26 +0200 Subject: [PATCH 15/24] Build PyPy3.11 wheel for macOS 10.15 x86_64 --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index db8e4d58b..1fe6badae 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -63,7 +63,7 @@ jobs: - name: "macOS 10.15 x86_64" os: macos-13 cibw_arch: x86_64 - build: "pp310*" + build: "pp3*" macosx_deployment_target: "10.15" - name: "macOS arm64" os: macos-latest From c60682af67a364fe185c3b9eea0477980af07cdd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 2 Mar 2025 22:34:58 +1100 Subject: [PATCH 16/24] JPEG comments are from the COM marker --- docs/handbook/image-file-formats.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index a915ee4e2..991cadaa2 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -454,7 +454,8 @@ The :py:meth:`~PIL.Image.open` method may set the following Raw EXIF data from the image. **comment** - A comment about the image. + A comment about the image, from the COM marker. This is separate from the + UserComment tag that may be stored in the EXIF data. .. versionadded:: 7.1.0 From ebc7a17d86a7789119e1b9c4ea5d186306ed276c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Mar 2025 07:24:13 +1100 Subject: [PATCH 17/24] Removed _show --- src/PIL/ImageTk.py | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index bf29fdba5..e6a9d8eea 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -28,7 +28,7 @@ from __future__ import annotations import tkinter from io import BytesIO -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any from . import Image, ImageFile @@ -263,28 +263,3 @@ def getimage(photo: PhotoImage) -> Image.Image: _pyimagingtkcall("PyImagingPhotoGet", photo, im.getim()) return im - - -def _show(image: Image.Image, title: str | None) -> None: - """Helper for the Image.show method.""" - - class UI(tkinter.Label): - def __init__(self, master: tkinter.Toplevel, im: Image.Image) -> None: - self.image: BitmapImage | PhotoImage - if im.mode == "1": - self.image = BitmapImage(im, foreground="white", master=master) - else: - self.image = PhotoImage(im, master=master) - if TYPE_CHECKING: - image = cast(tkinter._Image, self.image) - else: - image = self.image - super().__init__(master, image=image, bg="black", bd=0) - - if not getattr(tkinter, "_default_root"): - msg = "tkinter not initialized" - raise OSError(msg) - top = tkinter.Toplevel() - if title: - top.title(title) - UI(top, image).pack() From 2d97521aa3a7a5f4ab114354e881e05916fae483 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 02:38:52 +0000 Subject: [PATCH 18/24] Update dependency mypy to v1.15.0 --- .ci/requirements-mypy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index 10e59b885..2e3610478 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -1,4 +1,4 @@ -mypy==1.14.1 +mypy==1.15.0 IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PySide6 ipython From d6272297fc6c8e2e796c264b4229e5d20045aa9c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Mar 2025 14:48:00 +1100 Subject: [PATCH 19/24] Ignore override --- src/PIL/TiffImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 0454038e8..3d36d1abc 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -404,7 +404,7 @@ class IFDRational(Rational): def __repr__(self) -> str: return str(float(self._val)) - def __hash__(self) -> int: + def __hash__(self) -> int: # type: ignore[override] return self._val.__hash__() def __eq__(self, other: object) -> bool: From 4161bb1645fc66c9d587aafc53214f797831fa52 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Mar 2025 19:10:55 +1100 Subject: [PATCH 20/24] Corrected error when XMP is tuple --- Tests/test_imageops.py | 9 +++++++++ src/PIL/ImageOps.py | 14 +++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 3621aa50f..9f2fd5ba2 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -448,6 +448,15 @@ def test_exif_transpose() -> None: assert 0x0112 not in transposed_im.getexif() +def test_exif_transpose_with_xmp_tuple() -> None: + with Image.open("Tests/images/xmp_tags_orientation.png") as im: + assert im.getexif()[0x0112] == 3 + + im.info["xmp"] = (b"test",) + transposed_im = ImageOps.exif_transpose(im) + assert 0x0112 not in transposed_im.getexif() + + def test_exif_transpose_xml_without_xmp() -> None: with Image.open("Tests/images/xmp_tags_orientation.png") as im: assert im.getexif()[0x0112] == 3 diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index fef1d7328..75dfbee22 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -729,11 +729,15 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image r"([0-9])", ): value = exif_image.info[key] - exif_image.info[key] = ( - re.sub(pattern, "", value) - if isinstance(value, str) - else re.sub(pattern.encode(), b"", value) - ) + if isinstance(value, str): + value = re.sub(pattern, "", value) + elif isinstance(value, tuple): + value = tuple( + re.sub(pattern.encode(), b"", v) for v in value + ) + else: + value = re.sub(pattern.encode(), b"", value) + exif_image.info[key] = value if not in_place: return transposed_image elif not in_place: From 51183c22042303e464d86a26245c272f733f35f8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Mar 2025 21:58:29 +1100 Subject: [PATCH 21/24] Fixed loading images --- Tests/test_file_gd.py | 3 +++ src/PIL/GdImageFile.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py index d512df284..806532c17 100644 --- a/Tests/test_file_gd.py +++ b/Tests/test_file_gd.py @@ -4,6 +4,8 @@ import pytest from PIL import GdImageFile, UnidentifiedImageError +from .helper import assert_image_similar_tofile + TEST_GD_FILE = "Tests/images/hopper.gd" @@ -11,6 +13,7 @@ def test_sanity() -> None: with GdImageFile.open(TEST_GD_FILE) as im: assert im.size == (128, 128) assert im.format == "GD" + assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.jpg", 14) def test_bad_mode() -> None: diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index fc4801e9d..891225ce2 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -56,7 +56,7 @@ class GdImageFile(ImageFile.ImageFile): msg = "Not a valid GD 2.x .gd file" raise SyntaxError(msg) - self._mode = "L" # FIXME: "P" + self._mode = "P" self._size = i16(s, 2), i16(s, 4) true_color = s[6] @@ -68,14 +68,14 @@ class GdImageFile(ImageFile.ImageFile): self.info["transparency"] = tindex self.palette = ImagePalette.raw( - "XBGR", s[7 + true_color_offset + 4 : 7 + true_color_offset + 4 + 256 * 4] + "RGBX", s[7 + true_color_offset + 6 : 7 + true_color_offset + 6 + 256 * 4] ) self.tile = [ ImageFile._Tile( "raw", (0, 0) + self.size, - 7 + true_color_offset + 4 + 256 * 4, + 7 + true_color_offset + 6 + 256 * 4, "L", ) ] From a1a467bda2d79baa70775c6cd0d52ddcc1496ee8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Mar 2025 23:55:19 +1100 Subject: [PATCH 22/24] Image.core.outline will no longer raise an AttributeError --- Tests/test_imagedraw.py | 4 ---- src/PIL/ImageDraw.py | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index d127175eb..232cbb16c 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -448,7 +448,6 @@ def test_shape1() -> None: x3, y3 = 95, 5 # Act - assert ImageDraw.Outline is not None s = ImageDraw.Outline() s.move(x0, y0) s.curve(x1, y1, x2, y2, x3, y3) @@ -470,7 +469,6 @@ def test_shape2() -> None: x3, y3 = 5, 95 # Act - assert ImageDraw.Outline is not None s = ImageDraw.Outline() s.move(x0, y0) s.curve(x1, y1, x2, y2, x3, y3) @@ -489,7 +487,6 @@ def test_transform() -> None: draw = ImageDraw.Draw(im) # Act - assert ImageDraw.Outline is not None s = ImageDraw.Outline() s.line(0, 0) s.transform((0, 0, 0, 0, 0, 0)) @@ -1526,7 +1523,6 @@ def test_same_color_outline(bbox: Coords) -> None: x2, y2 = 95, 50 x3, y3 = 95, 5 - assert ImageDraw.Outline is not None s = ImageDraw.Outline() s.move(x0, y0) s.curve(x1, y1, x2, y2, x3, y3) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 742b5f587..c4ebc5931 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -42,11 +42,7 @@ from ._deprecate import deprecate from ._typing import Coords # experimental access to the outline API -Outline: Callable[[], Image.core._Outline] | None -try: - Outline = Image.core.outline -except AttributeError: - Outline = None +Outline: Callable[[], Image.core._Outline] = Image.core.outline if TYPE_CHECKING: from . import ImageDraw2, ImageFont From c1703f53307e668a1eef54e78350f22bb5cbe194 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 17:15:48 +0000 Subject: [PATCH 23/24] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.4 → v0.9.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.4...v0.9.9) - [github.com/PyCQA/bandit: 1.8.2 → 1.8.3](https://github.com/PyCQA/bandit/compare/1.8.2...1.8.3) - [github.com/python-jsonschema/check-jsonschema: 0.31.1 → 0.31.2](https://github.com/python-jsonschema/check-jsonschema/compare/0.31.1...0.31.2) - [github.com/woodruffw/zizmor-pre-commit: v1.3.0 → v1.4.1](https://github.com/woodruffw/zizmor-pre-commit/compare/v1.3.0...v1.4.1) - [github.com/tox-dev/pyproject-fmt: v2.5.0 → v2.5.1](https://github.com/tox-dev/pyproject-fmt/compare/v2.5.0...v2.5.1) --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8c8cee15..5ff947d41 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.9.4 + rev: v0.9.9 hooks: - id: ruff args: [--exit-non-zero-on-fix] @@ -11,7 +11,7 @@ repos: - id: black - repo: https://github.com/PyCQA/bandit - rev: 1.8.2 + rev: 1.8.3 hooks: - id: bandit args: [--severity-level=high] @@ -50,14 +50,14 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.31.1 + rev: 0.31.2 hooks: - id: check-github-workflows - id: check-readthedocs - id: check-renovate - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.3.0 + rev: v1.4.1 hooks: - id: zizmor @@ -67,7 +67,7 @@ repos: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.5.0 + rev: v2.5.1 hooks: - id: pyproject-fmt From 5ce8929ed467712025c82311f0cdfa196224a06a Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 4 Mar 2025 07:48:12 +1100 Subject: [PATCH 24/24] Updated test name Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Tests/test_font_bdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index 8d78019b3..2ece5457a 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -17,7 +17,7 @@ def test_sanity() -> None: assert len([_f for _f in font.glyph if _f]) == 190 -def test_valueerror() -> None: +def test_zero_width_chars() -> None: with open(filename, "rb") as fp: data = fp.read() data = data[:2650] + b"\x00\x00" + data[2652:]