diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index b4e479f12..a07a27c46 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -67,10 +67,10 @@ jobs: mingw-w64-x86_64-python3-cffi \ mingw-w64-x86_64-python3-numpy \ mingw-w64-x86_64-python3-olefile \ - mingw-w64-x86_64-python3-pip \ mingw-w64-x86_64-python3-setuptools \ mingw-w64-x86_64-python-pyqt6 + python3 -m ensurepip python3 -m pip install pyroma pytest pytest-cov pytest-timeout pushd depends && ./install_extra_test_images.sh && popd diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 26bf2f6d6..cc8d7e085 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -19,7 +19,7 @@ FREETYPE_VERSION=2.13.2 HARFBUZZ_VERSION=8.3.0 LIBPNG_VERSION=1.6.40 JPEGTURBO_VERSION=3.0.1 -OPENJPEG_VERSION=2.5.0 +OPENJPEG_VERSION=2.5.2 XZ_VERSION=5.4.5 TIFF_VERSION=4.6.0 LCMS2_VERSION=2.16 @@ -40,7 +40,7 @@ BROTLI_VERSION=1.1.0 if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "x86_64" ]]; then function build_openjpeg { - local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz openjpeg-2.5.0.tar.gz) + local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz openjpeg-${OPENJPEG_VERSION}.tar.gz) (cd $out_dir \ && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ && make install) @@ -93,6 +93,9 @@ function build { done fi build_openjpeg + if [ -f /usr/local/lib64/libopenjp2.so ]; then + cp /usr/local/lib64/libopenjp2.so /usr/local/lib + fi ORIGINAL_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -O3 -DNDEBUG" diff --git a/CHANGES.rst b/CHANGES.rst index 205ffa294..7adcf1b40 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 10.3.0 (unreleased) ------------------- +- Handle truncated chunks at the end of PNG images #7709 + [lajiyuan, radarhere] + +- Match mask size to pasted image size in GifImagePlugin #7779 + [radarhere] + - Release GIL while calling ``WebPAnimDecoderGetNext`` #7782 [evanmiller, radarhere] diff --git a/README.md b/README.md index 9776c40e2..f142ef563 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,6 @@ As of 2019, Pillow development is Join the chat at https://gitter.im/python-pillow/Pillow - Follow on https://twitter.com/PythonPillow Follow on https://fosstodon.org/@pillow None: assert reread.n_frames == 10 +def test_append_different_size_image(tmp_path: Path) -> None: + out = str(tmp_path / "temp.gif") + + im = Image.new("RGB", (100, 100)) + bigger_im = Image.new("RGB", (200, 200), "#f00") + + im.save(out, save_all=True, append_images=[bigger_im]) + + with Image.open(out) as reread: + assert reread.size == (100, 100) + + reread.seek(1) + assert reread.size == (100, 100) + + def test_transparent_optimize(tmp_path: Path) -> None: # From issue #2195, if the transparent color is incorrectly optimized out, GIF loses # transparency. diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index c51f56ce7..334839f5c 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -783,6 +783,18 @@ class TestFilePng: with Image.open(mystdout) as reloaded: assert_image_equal_tofile(reloaded, TEST_PNG_FILE) + def test_truncated_end_chunk(self) -> None: + with Image.open("Tests/images/truncated_end_chunk.png") as im: + with pytest.raises(OSError): + im.load() + + ImageFile.LOAD_TRUNCATED_IMAGES = True + try: + with Image.open("Tests/images/truncated_end_chunk.png") as im: + assert_image_equal_tofile(im, "Tests/images/hopper.png") + finally: + ImageFile.LOAD_TRUNCATED_IMAGES = False + @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") @skip_unless_feature("zlib") diff --git a/Tests/test_image.py b/Tests/test_image.py index 4c04e0da4..2a4d453e2 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -685,15 +685,18 @@ class TestImage: _make_new(im, blank_p, ImagePalette.ImagePalette()) _make_new(im, blank_pa, ImagePalette.ImagePalette()) - def test_p_from_rgb_rgba(self) -> None: - for mode, color in [ + @pytest.mark.parametrize( + "mode, color", + ( ("RGB", "#DDEEFF"), ("RGB", (221, 238, 255)), ("RGBA", (221, 238, 255, 255)), - ]: - im = Image.new("P", (100, 100), color) - expected = Image.new(mode, (100, 100), color) - assert_image_equal(im.convert(mode), expected) + ), + ) + def test_p_from_rgb_rgba(self, mode: str, color: str | tuple[int, ...]) -> None: + im = Image.new("P", (100, 100), color) + expected = Image.new(mode, (100, 100), color) + assert_image_equal(im.convert(mode), expected) def test_no_resource_warning_on_save(self, tmp_path: Path) -> None: # https://github.com/python-pillow/Pillow/issues/835 diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index 4f4b81a62..8c2967bc2 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -1,7 +1,7 @@ #!/bin/bash # install openjpeg -archive=openjpeg-2.5.0 +archive=openjpeg-2.5.2 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 205fcb9ab..8877ccdeb 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -504,3 +504,27 @@ PIL.OleFileIO the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError` in 5.0.0 (2018-01). The deprecated file has now been removed from Pillow. If needed, install from PyPI (eg. ``python3 -m pip install olefile``). + +import _imaging +~~~~~~~~~~~~~~~ + +.. versionremoved:: 2.1.0 + +Pillow >= 2.1.0 no longer supports ``import _imaging``. +Please use ``from PIL.Image import core as _imaging`` instead. + +Pillow and PIL +~~~~~~~~~~~~~~ + +.. versionremoved:: 1.0.0 + +Pillow and PIL cannot co-exist in the same environment. +Before installing Pillow, please uninstall PIL. + +import Image +~~~~~~~~~~~~ + +.. versionremoved:: 1.0.0 + +Pillow >= 1.0 no longer supports ``import Image``. +Please use ``from PIL import Image`` instead. diff --git a/docs/index.rst b/docs/index.rst index bf2feea9a..f4e81e45d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -73,10 +73,6 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more -Warnings --------- - -.. warning:: Pillow and PIL cannot co-exist in the same environment. Before installing Pillow, please uninstall PIL. - -.. warning:: Pillow >= 1.0 no longer supports ``import Image``. Please use ``from PIL import Image`` instead. - -.. warning:: Pillow >= 2.1.0 no longer supports ``import _imaging``. Please use ``from PIL.Image import core as _imaging`` instead. - Python Support -------------- @@ -186,7 +177,7 @@ Many of Pillow's features require external libraries: * **openjpeg** provides JPEG 2000 functionality. * Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**, - **2.4.0** and **2.5.0**. + **2.4.0**, **2.5.0** and **2.5.2**. * Pillow does **not** support the earlier **1.5** series which ships with Debian Jessie. diff --git a/docs/reference/ImageOps.rst b/docs/reference/ImageOps.rst index 475253078..051fdcfc9 100644 --- a/docs/reference/ImageOps.rst +++ b/docs/reference/ImageOps.rst @@ -14,6 +14,8 @@ only work on L and RGB images. .. autofunction:: colorize .. autofunction:: crop .. autofunction:: scale +.. autoclass:: SupportsGetMesh + :show-inheritance: .. autofunction:: deform .. autofunction:: equalize .. autofunction:: expand diff --git a/pyproject.toml b/pyproject.toml index 58c2464bc..518facc34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,6 @@ Homepage = "https://python-pillow.org" Mastodon = "https://fosstodon.org/@pillow" "Release notes" = "https://pillow.readthedocs.io/en/stable/releasenotes/index.html" Source = "https://github.com/python-pillow/Pillow" -Twitter = "https://twitter.com/PythonPillow" [tool.setuptools] packages = ["PIL"] diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index dc842d7a3..b38b43013 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -649,9 +649,7 @@ def _write_multiple_frames(im, fp, palette): if "transparency" in encoderinfo: # When the delta is zero, fill the image with transparency diff_frame = im_frame.copy() - fill = Image.new( - "P", diff_frame.size, encoderinfo["transparency"] - ) + fill = Image.new("P", delta.size, encoderinfo["transparency"]) if delta.mode == "RGBA": r, g, b, a = delta.split() mask = ImageMath.eval( diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 6218c723f..33db8fa50 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -411,7 +411,15 @@ def scale( return image.resize(size, resample) -class _SupportsGetMesh(Protocol): +class SupportsGetMesh(Protocol): + """ + An object that supports the ``getmesh`` method, taking an image as an + argument, and returning a list of tuples. Each tuple contains two tuples, + the source box as a tuple of 4 integers, and a tuple of 8 integers for the + final quadrilateral, in order of top left, bottom left, bottom right, top + right. + """ + def getmesh( self, image: Image.Image ) -> list[ @@ -421,7 +429,7 @@ class _SupportsGetMesh(Protocol): def deform( image: Image.Image, - deformer: _SupportsGetMesh, + deformer: SupportsGetMesh, resample: int = Image.Resampling.BILINEAR, ) -> Image.Image: """ diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 823f12492..1248fb785 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -981,7 +981,13 @@ class PngImageFile(ImageFile.ImageFile): except EOFError: if cid == b"fdAT": length -= 4 - ImageFile._safe_read(self.fp, length) + try: + ImageFile._safe_read(self.fp, length) + except OSError as e: + if ImageFile.LOAD_TRUNCATED_IMAGES: + break + else: + raise e except AttributeError: logger.debug("%r %s %s (unknown)", cid, pos, length) s = ImageFile._safe_read(self.fp, length) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index df33ea493..2ee9872e6 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -308,21 +308,16 @@ DEPS = { "libs": [r"Lib\MS\*.lib"], }, "openjpeg": { - "url": "https://github.com/uclouvain/openjpeg/archive/v2.5.0.tar.gz", - "filename": "openjpeg-2.5.0.tar.gz", - "dir": "openjpeg-2.5.0", + "url": "https://github.com/uclouvain/openjpeg/archive/v2.5.2.tar.gz", + "filename": "openjpeg-2.5.2.tar.gz", + "dir": "openjpeg-2.5.2", "license": "LICENSE", - "patch": { - r"src\lib\openjp2\ht_dec.c": { - "#ifdef OPJ_COMPILER_MSVC\n return (OPJ_UINT32)__popcnt(val);": "#if defined(OPJ_COMPILER_MSVC) && (defined(_M_IX86) || defined(_M_AMD64))\n return (OPJ_UINT32)__popcnt(val);", # noqa: E501 - } - }, "build": [ *cmds_cmake( "openjp2", "-DBUILD_CODEC:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF" ), - cmd_mkdir(r"{inc_dir}\openjpeg-2.5.0"), - cmd_copy(r"src\lib\openjp2\*.h", r"{inc_dir}\openjpeg-2.5.0"), + cmd_mkdir(r"{inc_dir}\openjpeg-2.5.2"), + cmd_copy(r"src\lib\openjp2\*.h", r"{inc_dir}\openjpeg-2.5.2"), ], "libs": [r"bin\*.lib"], },