mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-01 00:17:27 +03:00 
			
		
		
		
	Merge branch 'main' into main
This commit is contained in:
		
						commit
						54c3c93987
					
				|  | @ -66,7 +66,7 @@ if [[ $(uname) != CYGWIN* ]]; then | ||||||
|     pushd depends && ./install_raqm.sh && popd |     pushd depends && ./install_raqm.sh && popd | ||||||
| 
 | 
 | ||||||
|     # libavif |     # libavif | ||||||
|     pushd depends && CMAKE_POLICY_VERSION_MINIMUM=3.5 ./install_libavif.sh && popd |     pushd depends && ./install_libavif.sh && popd | ||||||
| 
 | 
 | ||||||
|     # extra test images |     # extra test images | ||||||
|     pushd depends && ./install_extra_test_images.sh && popd |     pushd depends && ./install_extra_test_images.sh && popd | ||||||
|  |  | ||||||
|  | @ -1 +1 @@ | ||||||
| cibuildwheel==2.23.2 | cibuildwheel==2.23.3 | ||||||
|  |  | ||||||
							
								
								
									
										46
									
								
								.github/ISSUE_TEMPLATE/RELEASE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								.github/ISSUE_TEMPLATE/RELEASE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | ||||||
|  | --- | ||||||
|  | name: "Maintainers only: Release" | ||||||
|  | about: For maintainers to schedule a quarterly release | ||||||
|  | labels: Release | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | ## Main release | ||||||
|  | 
 | ||||||
|  | Released quarterly on January 2nd, April 1st, July 1st and October 15th. | ||||||
|  | 
 | ||||||
|  | * [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154 | ||||||
|  | * [ ] Develop and prepare release in `main` branch. | ||||||
|  |   * [ ] Add release notes e.g. https://github.com/python-pillow/Pillow/pull/8885 | ||||||
|  | * [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in `main` branch. | ||||||
|  | * [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them. | ||||||
|  | * [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py` | ||||||
|  | * [ ] Run pre-release check via `make release-test` in a freshly cloned repo. | ||||||
|  | * [ ] Create branch and tag for release e.g.: | ||||||
|  |   ```bash | ||||||
|  |   git branch [[MAJOR.MINOR]].x | ||||||
|  |   git tag [[MAJOR.MINOR]].0 | ||||||
|  |   git push --tags | ||||||
|  |   ``` | ||||||
|  | * [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) has passed, including the "Upload release to PyPI" job. This will have been triggered by the new tag. | ||||||
|  | * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases). | ||||||
|  | * [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then: | ||||||
|  |   ```bash | ||||||
|  |   git push --all | ||||||
|  |    ``` | ||||||
|  | 
 | ||||||
|  | ## Publicize release | ||||||
|  | 
 | ||||||
|  | * [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321 | ||||||
|  | 
 | ||||||
|  | ## Documentation | ||||||
|  | 
 | ||||||
|  | * [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes | ||||||
|  | 
 | ||||||
|  | ## Docker images | ||||||
|  | 
 | ||||||
|  | * [ ] Update Pillow in the Docker Images repository | ||||||
|  |   ```bash | ||||||
|  |   git clone https://github.com/python-pillow/docker-images | ||||||
|  |   cd docker-images | ||||||
|  |   ./update-pillow-tag.sh [[release tag]] | ||||||
|  |   ``` | ||||||
							
								
								
									
										6
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -84,7 +84,7 @@ jobs: | ||||||
|         python3 -m pip install --upgrade pip |         python3 -m pip install --upgrade pip | ||||||
| 
 | 
 | ||||||
|     - name: Install CPython dependencies |     - name: Install CPython dependencies | ||||||
|       if: "!contains(matrix.python-version, 'pypy') && matrix.architecture != 'x86'" |       if: "!contains(matrix.python-version, 'pypy') && !contains(matrix.python-version, '3.14') && matrix.architecture != 'x86'" | ||||||
|       run: | |       run: | | ||||||
|         python3 -m pip install PyQt6 |         python3 -m pip install PyQt6 | ||||||
| 
 | 
 | ||||||
|  | @ -98,8 +98,8 @@ jobs: | ||||||
|         choco install nasm --no-progress |         choco install nasm --no-progress | ||||||
|         echo "C:\Program Files\NASM" >> $env:GITHUB_PATH |         echo "C:\Program Files\NASM" >> $env:GITHUB_PATH | ||||||
| 
 | 
 | ||||||
|         choco install ghostscript --version=10.5.0 --no-progress |         choco install ghostscript --version=10.5.1 --no-progress | ||||||
|         echo "C:\Program Files\gs\gs10.05.0\bin" >> $env:GITHUB_PATH |         echo "C:\Program Files\gs\gs10.05.1\bin" >> $env:GITHUB_PATH | ||||||
| 
 | 
 | ||||||
|         # Install extra test images |         # Install extra test images | ||||||
|         xcopy /S /Y Tests\test-images\* Tests\images |         xcopy /S /Y Tests\test-images\* Tests\images | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							|  | @ -38,8 +38,8 @@ ARCHIVE_SDIR=pillow-depends-main | ||||||
| 
 | 
 | ||||||
| # Package versions for fresh source builds | # Package versions for fresh source builds | ||||||
| FREETYPE_VERSION=2.13.3 | FREETYPE_VERSION=2.13.3 | ||||||
| HARFBUZZ_VERSION=11.1.0 | HARFBUZZ_VERSION=11.2.1 | ||||||
| LIBPNG_VERSION=1.6.47 | LIBPNG_VERSION=1.6.48 | ||||||
| JPEGTURBO_VERSION=3.1.0 | JPEGTURBO_VERSION=3.1.0 | ||||||
| OPENJPEG_VERSION=2.5.3 | OPENJPEG_VERSION=2.5.3 | ||||||
| XZ_VERSION=5.8.1 | XZ_VERSION=5.8.1 | ||||||
|  |  | ||||||
							
								
								
									
										7
									
								
								.github/zizmor.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.github/zizmor.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | # Configuration for the zizmor static analysis tool, run via pre-commit in CI | ||||||
|  | # https://woodruffw.github.io/zizmor/configuration/ | ||||||
|  | rules: | ||||||
|  |   unpinned-uses: | ||||||
|  |     config: | ||||||
|  |       policies: | ||||||
|  |         "*": ref-pin | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| repos: | repos: | ||||||
|   - repo: https://github.com/astral-sh/ruff-pre-commit |   - repo: https://github.com/astral-sh/ruff-pre-commit | ||||||
|     rev: v0.11.4 |     rev: v0.11.8 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: ruff |       - id: ruff | ||||||
|         args: [--exit-non-zero-on-fix] |         args: [--exit-non-zero-on-fix] | ||||||
|  | @ -24,7 +24,7 @@ repos: | ||||||
|         exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) |         exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) | ||||||
| 
 | 
 | ||||||
|   - repo: https://github.com/pre-commit/mirrors-clang-format |   - repo: https://github.com/pre-commit/mirrors-clang-format | ||||||
|     rev: v20.1.0 |     rev: v20.1.3 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: clang-format |       - id: clang-format | ||||||
|         types: [c] |         types: [c] | ||||||
|  | @ -51,14 +51,14 @@ repos: | ||||||
|         exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ |         exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ | ||||||
| 
 | 
 | ||||||
|   - repo: https://github.com/python-jsonschema/check-jsonschema |   - repo: https://github.com/python-jsonschema/check-jsonschema | ||||||
|     rev: 0.32.1 |     rev: 0.33.0 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: check-github-workflows |       - id: check-github-workflows | ||||||
|       - id: check-readthedocs |       - id: check-readthedocs | ||||||
|       - id: check-renovate |       - id: check-renovate | ||||||
| 
 | 
 | ||||||
|   - repo: https://github.com/woodruffw/zizmor-pre-commit |   - repo: https://github.com/woodruffw/zizmor-pre-commit | ||||||
|     rev: v1.5.2 |     rev: v1.6.0 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: zizmor |       - id: zizmor | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -95,7 +95,7 @@ This library provides extensive file format support, an efficient internal repre | ||||||
| 
 | 
 | ||||||
| The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool. | The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool. | ||||||
| 
 | 
 | ||||||
| ## More Information | ## More information | ||||||
| 
 | 
 | ||||||
| - [Documentation](https://pillow.readthedocs.io/) | - [Documentation](https://pillow.readthedocs.io/) | ||||||
|   - [Installation](https://pillow.readthedocs.io/en/latest/installation/basic-installation.html) |   - [Installation](https://pillow.readthedocs.io/en/latest/installation/basic-installation.html) | ||||||
|  | @ -107,6 +107,6 @@ The core image library is designed for fast access to data stored in a few basic | ||||||
| - [Changelog](https://github.com/python-pillow/Pillow/releases) | - [Changelog](https://github.com/python-pillow/Pillow/releases) | ||||||
|   - [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork) |   - [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork) | ||||||
| 
 | 
 | ||||||
| ## Report a Vulnerability | ## Report a vulnerability | ||||||
| 
 | 
 | ||||||
| To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security). | To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security). | ||||||
|  |  | ||||||
							
								
								
									
										35
									
								
								RELEASING.md
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								RELEASING.md
									
									
									
									
									
								
							|  | @ -1,34 +1,15 @@ | ||||||
| # Release Checklist | # Release checklist | ||||||
| 
 | 
 | ||||||
| See https://pillow.readthedocs.io/en/stable/releasenotes/versioning.html for | See https://pillow.readthedocs.io/en/stable/releasenotes/versioning.html for | ||||||
| information about how the version numbers line up with releases. | information about how the version numbers line up with releases. | ||||||
| 
 | 
 | ||||||
| ## Main Release | ## Main release | ||||||
| 
 | 
 | ||||||
| Released quarterly on January 2nd, April 1st, July 1st and October 15th. | Released quarterly on January 2nd, April 1st, July 1st and October 15th. | ||||||
| 
 | 
 | ||||||
| * [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154 | * [ ] Create a new issue and select the "Maintainers only: Release" template. | ||||||
| * [ ] Develop and prepare release in `main` branch. | 
 | ||||||
| * [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in `main` branch. | ## Point release | ||||||
| * [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them. |  | ||||||
| * [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py` |  | ||||||
| * [ ] Run pre-release check via `make release-test` in a freshly cloned repo. |  | ||||||
| * [ ] Create branch and tag for release e.g.: |  | ||||||
|   ```bash |  | ||||||
|   git branch 5.2.x |  | ||||||
|   git tag 5.2.0 |  | ||||||
|   git push --tags |  | ||||||
|   ``` |  | ||||||
| * [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) |  | ||||||
|   has passed, including the "Upload release to PyPI" job. This will have been triggered |  | ||||||
|   by the new tag. |  | ||||||
| * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases). |  | ||||||
| * [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), |  | ||||||
|       increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then: |  | ||||||
|   ```bash |  | ||||||
|   git push --all |  | ||||||
|    ``` |  | ||||||
| ## Point Release |  | ||||||
| 
 | 
 | ||||||
| Released as needed for security, installation or critical bug fixes. | Released as needed for security, installation or critical bug fixes. | ||||||
| 
 | 
 | ||||||
|  | @ -58,7 +39,7 @@ Released as needed for security, installation or critical bug fixes. | ||||||
|   git push |   git push | ||||||
|   ``` |   ``` | ||||||
| 
 | 
 | ||||||
| ## Embargoed Release | ## Embargoed release | ||||||
| 
 | 
 | ||||||
| Released as needed privately to individual vendors for critical security-related bug fixes. | Released as needed privately to individual vendors for critical security-related bug fixes. | ||||||
| 
 | 
 | ||||||
|  | @ -82,7 +63,7 @@ Released as needed privately to individual vendors for critical security-related | ||||||
|   git push origin 2.5.x |   git push origin 2.5.x | ||||||
|   ``` |   ``` | ||||||
| 
 | 
 | ||||||
| ## Publicize Release | ## Publicize release | ||||||
| 
 | 
 | ||||||
| * [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321 | * [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321 | ||||||
| 
 | 
 | ||||||
|  | @ -90,7 +71,7 @@ Released as needed privately to individual vendors for critical security-related | ||||||
| 
 | 
 | ||||||
| * [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes | * [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes | ||||||
| 
 | 
 | ||||||
| ## Docker Images | ## Docker images | ||||||
| 
 | 
 | ||||||
| * [ ] Update Pillow in the Docker Images repository | * [ ] Update Pillow in the Docker Images repository | ||||||
|   ```bash |   ```bash | ||||||
|  |  | ||||||
|  | @ -161,6 +161,12 @@ def assert_tuple_approx_equal( | ||||||
|             pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets)) |             pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def timeout_unless_slower_valgrind(timeout: float) -> pytest.MarkDecorator: | ||||||
|  |     if "PILLOW_VALGRIND_TEST" in os.environ: | ||||||
|  |         return pytest.mark.pil_noop_mark() | ||||||
|  |     return pytest.mark.timeout(timeout) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def skip_unless_feature(feature: str) -> pytest.MarkDecorator: | def skip_unless_feature(feature: str) -> pytest.MarkDecorator: | ||||||
|     reason = f"{feature} not available" |     reason = f"{feature} not available" | ||||||
|     return pytest.mark.skipif(not features.check(feature), reason=reason) |     return pytest.mark.skipif(not features.check(feature), reason=reason) | ||||||
|  |  | ||||||
|  | @ -233,7 +233,7 @@ class TestFileAvif: | ||||||
|         with Image.open(out_gif) as reread: |         with Image.open(out_gif) as reread: | ||||||
|             reread_value = reread.convert("RGB").getpixel((1, 1)) |             reread_value = reread.convert("RGB").getpixel((1, 1)) | ||||||
|         difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)]) |         difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)]) | ||||||
|         assert difference <= 3 |         assert difference <= 6 | ||||||
| 
 | 
 | ||||||
|     def test_save_single_frame(self, tmp_path: Path) -> None: |     def test_save_single_frame(self, tmp_path: Path) -> None: | ||||||
|         temp_file = tmp_path / "temp.avif" |         temp_file = tmp_path / "temp.avif" | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ from .helper import ( | ||||||
|     is_win32, |     is_win32, | ||||||
|     mark_if_feature_version, |     mark_if_feature_version, | ||||||
|     skip_unless_feature, |     skip_unless_feature, | ||||||
|  |     timeout_unless_slower_valgrind, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript() | HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript() | ||||||
|  | @ -398,7 +399,7 @@ def test_emptyline() -> None: | ||||||
|     assert image.format == "EPS" |     assert image.format == "EPS" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.timeout(timeout=5) | @timeout_unless_slower_valgrind(5) | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|     "test_file", |     "test_file", | ||||||
|     ["Tests/images/eps/timeout-d675703545fee17acab56e5fec644c19979175de.eps"], |     ["Tests/images/eps/timeout-d675703545fee17acab56e5fec644c19979175de.eps"], | ||||||
|  |  | ||||||
|  | @ -7,7 +7,12 @@ import pytest | ||||||
| 
 | 
 | ||||||
| from PIL import FliImagePlugin, Image, ImageFile | from PIL import FliImagePlugin, Image, ImageFile | ||||||
| 
 | 
 | ||||||
| from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy | from .helper import ( | ||||||
|  |     assert_image_equal, | ||||||
|  |     assert_image_equal_tofile, | ||||||
|  |     is_pypy, | ||||||
|  |     timeout_unless_slower_valgrind, | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| # created as an export of a palette image from Gimp2.6 | # created as an export of a palette image from Gimp2.6 | ||||||
| # save as...-> hopper.fli, default options. | # save as...-> hopper.fli, default options. | ||||||
|  | @ -189,7 +194,7 @@ def test_seek() -> None: | ||||||
|         "Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli", |         "Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli", | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
| @pytest.mark.timeout(timeout=3) | @timeout_unless_slower_valgrind(3) | ||||||
| def test_timeouts(test_file: str) -> None: | def test_timeouts(test_file: str) -> None: | ||||||
|     with open(test_file, "rb") as f: |     with open(test_file, "rb") as f: | ||||||
|         with Image.open(f) as im: |         with Image.open(f) as im: | ||||||
|  |  | ||||||
|  | @ -32,6 +32,7 @@ from .helper import ( | ||||||
|     is_win32, |     is_win32, | ||||||
|     mark_if_feature_version, |     mark_if_feature_version, | ||||||
|     skip_unless_feature, |     skip_unless_feature, | ||||||
|  |     timeout_unless_slower_valgrind, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| ElementTree: ModuleType | None | ElementTree: ModuleType | None | ||||||
|  | @ -1033,7 +1034,7 @@ class TestFileJpeg: | ||||||
|         with pytest.raises(ValueError): |         with pytest.raises(ValueError): | ||||||
|             im.save(f, xmp=b"1" * 65505) |             im.save(f, xmp=b"1" * 65505) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.timeout(timeout=1) |     @timeout_unless_slower_valgrind(1) | ||||||
|     def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None: |     def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||||
|         # Even though this decoder never says that it is finished |         # Even though this decoder never says that it is finished | ||||||
|         # the image should still end when there is no new data |         # the image should still end when there is no new data | ||||||
|  |  | ||||||
|  | @ -13,7 +13,12 @@ import pytest | ||||||
| 
 | 
 | ||||||
| from PIL import Image, PdfParser, features | from PIL import Image, PdfParser, features | ||||||
| 
 | 
 | ||||||
| from .helper import hopper, mark_if_feature_version, skip_unless_feature | from .helper import ( | ||||||
|  |     hopper, | ||||||
|  |     mark_if_feature_version, | ||||||
|  |     skip_unless_feature, | ||||||
|  |     timeout_unless_slower_valgrind, | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str: | def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str: | ||||||
|  | @ -339,8 +344,7 @@ def test_pdf_append_to_bytesio() -> None: | ||||||
|     assert len(f.getvalue()) > initial_size |     assert len(f.getvalue()) > initial_size | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.timeout(1) | @timeout_unless_slower_valgrind(1) | ||||||
| @pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower") |  | ||||||
| @pytest.mark.parametrize("newline", (b"\r", b"\n")) | @pytest.mark.parametrize("newline", (b"\r", b"\n")) | ||||||
| def test_redos(newline: bytes) -> None: | def test_redos(newline: bytes) -> None: | ||||||
|     malicious = b" trailer<<>>" + newline * 3456 |     malicious = b" trailer<<>>" + newline * 3456 | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ from .helper import ( | ||||||
|     hopper, |     hopper, | ||||||
|     is_pypy, |     is_pypy, | ||||||
|     is_win32, |     is_win32, | ||||||
|  |     timeout_unless_slower_valgrind, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| ElementTree: ModuleType | None | ElementTree: ModuleType | None | ||||||
|  | @ -988,7 +989,7 @@ class TestFileTiff: | ||||||
|             with pytest.raises(OSError): |             with pytest.raises(OSError): | ||||||
|                 im.load() |                 im.load() | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.timeout(6) |     @timeout_unless_slower_valgrind(6) | ||||||
|     @pytest.mark.filterwarnings("ignore:Truncated File Read") |     @pytest.mark.filterwarnings("ignore:Truncated File Read") | ||||||
|     def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None: |     def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None: | ||||||
|         with Image.open("Tests/images/timeout-6646305047838720") as im: |         with Image.open("Tests/images/timeout-6646305047838720") as im: | ||||||
|  | @ -1001,7 +1002,7 @@ class TestFileTiff: | ||||||
|             "Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif", |             "Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif", | ||||||
|         ], |         ], | ||||||
|     ) |     ) | ||||||
|     @pytest.mark.timeout(2) |     @timeout_unless_slower_valgrind(2) | ||||||
|     def test_oom(self, test_file: str) -> None: |     def test_oom(self, test_file: str) -> None: | ||||||
|         with pytest.raises(UnidentifiedImageError): |         with pytest.raises(UnidentifiedImageError): | ||||||
|             with pytest.warns(UserWarning): |             with pytest.warns(UserWarning): | ||||||
|  |  | ||||||
|  | @ -34,6 +34,7 @@ from .helper import ( | ||||||
|     is_win32, |     is_win32, | ||||||
|     mark_if_feature_version, |     mark_if_feature_version, | ||||||
|     skip_unless_feature, |     skip_unless_feature, | ||||||
|  |     timeout_unless_slower_valgrind, | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| ElementTree: ModuleType | None | ElementTree: ModuleType | None | ||||||
|  | @ -572,10 +573,7 @@ class TestImage: | ||||||
|         i = Image.new("RGB", [1, 1]) |         i = Image.new("RGB", [1, 1]) | ||||||
|         assert isinstance(i.size, tuple) |         assert isinstance(i.size, tuple) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.timeout(0.75) |     @timeout_unless_slower_valgrind(0.75) | ||||||
|     @pytest.mark.skipif( |  | ||||||
|         "PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower" |  | ||||||
|     ) |  | ||||||
|     @pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0))) |     @pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0))) | ||||||
|     def test_empty_image(self, size: tuple[int, int]) -> None: |     def test_empty_image(self, size: tuple[int, int]) -> None: | ||||||
|         Image.new("RGB", size) |         Image.new("RGB", size) | ||||||
|  |  | ||||||
|  | @ -462,7 +462,7 @@ class TestCoreResampleBox: | ||||||
|         im.resize((32, 32), resample, (20, 20, 20, 100)) |         im.resize((32, 32), resample, (20, 20, 20, 100)) | ||||||
|         im.resize((32, 32), resample, (20, 20, 100, 20)) |         im.resize((32, 32), resample, (20, 20, 100, 20)) | ||||||
| 
 | 
 | ||||||
|         with pytest.raises(TypeError, match="must be sequence of length 4"): |         with pytest.raises(TypeError, match="must be (sequence|tuple) of length 4"): | ||||||
|             im.resize((32, 32), resample, (im.width, im.height))  # type: ignore[arg-type] |             im.resize((32, 32), resample, (im.width, im.height))  # type: ignore[arg-type] | ||||||
| 
 | 
 | ||||||
|         with pytest.raises(ValueError, match="can't be negative"): |         with pytest.raises(ValueError, match="can't be negative"): | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ import pytest | ||||||
| 
 | 
 | ||||||
| from PIL import Image, ImageDraw, ImageFont, _util, features | from PIL import Image, ImageDraw, ImageFont, _util, features | ||||||
| 
 | 
 | ||||||
| from .helper import assert_image_equal_tofile | from .helper import assert_image_equal_tofile, timeout_unless_slower_valgrind | ||||||
| 
 | 
 | ||||||
| fonts = [ImageFont.load_default_imagefont()] | fonts = [ImageFont.load_default_imagefont()] | ||||||
| if not features.check_module("freetype2"): | if not features.check_module("freetype2"): | ||||||
|  | @ -72,7 +72,7 @@ def test_decompression_bomb() -> None: | ||||||
|         font.getmask("A" * 1_000_000) |         font.getmask("A" * 1_000_000) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.timeout(4) | @timeout_unless_slower_valgrind(4) | ||||||
| def test_oom() -> None: | def test_oom() -> None: | ||||||
|     glyph = struct.pack( |     glyph = struct.pack( | ||||||
|         ">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767 |         ">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767 | ||||||
|  |  | ||||||
|  | @ -162,3 +162,13 @@ def test_pickle_font_file(tmp_path: Path, protocol: int) -> None: | ||||||
| 
 | 
 | ||||||
|     # Assert |     # Assert | ||||||
|     helper_assert_pickled_font_images(font, unpickled_font) |     helper_assert_pickled_font_images(font, unpickled_font) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_load_earlier_data() -> None: | ||||||
|  |     im = pickle.loads( | ||||||
|  |         b"\x80\x04\x95@\x00\x00\x00\x00\x00\x00\x00\x8c\x12PIL.PngImagePlugin" | ||||||
|  |         b"\x94\x8c\x0cPngImageFile\x94\x93\x94)\x81\x94]\x94(}\x94\x8c\x01L\x94K\x01" | ||||||
|  |         b"K\x01\x86\x94NC\x01\x00\x94eb." | ||||||
|  |     ) | ||||||
|  |     assert im.mode == "L" | ||||||
|  |     assert im.size == (1, 1) | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| #!/usr/bin/env bash | #!/usr/bin/env bash | ||||||
| set -eo pipefail | set -eo pipefail | ||||||
| 
 | 
 | ||||||
| version=1.2.1 | version=1.3.0 | ||||||
| 
 | 
 | ||||||
| ./download-and-extract.sh libavif-$version https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$version.tar.gz | ./download-and-extract.sh libavif-$version https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$version.tar.gz | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,17 +3,23 @@ | ||||||
| 
 | 
 | ||||||
| # You can set these variables from the command line.
 | # You can set these variables from the command line.
 | ||||||
| PYTHON        = python3 | PYTHON        = python3 | ||||||
| SPHINXOPTS    = |  | ||||||
| SPHINXBUILD   = $(PYTHON) -m sphinx.cmd.build | SPHINXBUILD   = $(PYTHON) -m sphinx.cmd.build | ||||||
| PAPER         = | SPHINXOPTS    = --fail-on-warning | ||||||
| BUILDDIR      = _build | BUILDDIR      = _build | ||||||
|  | BUILDER       = html | ||||||
|  | JOBS          = auto | ||||||
|  | PAPER         = | ||||||
| 
 | 
 | ||||||
| # Internal variables.
 | # Internal variables.
 | ||||||
| PAPEROPT_a4     = --define latex_paper_size=a4 | PAPEROPT_a4     = --define latex_paper_size=a4 | ||||||
| PAPEROPT_letter = --define latex_paper_size=letter | PAPEROPT_letter = --define latex_paper_size=letter | ||||||
| ALLSPHINXOPTS   = --doctree-dir $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | 
 | ||||||
| # the i18n builder cannot share the environment and doctrees with the others
 | ALLSPHINXOPTS   = --builder $(BUILDER) \
 | ||||||
| I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . |                   --doctree-dir $(BUILDDIR)/doctrees \
 | ||||||
|  |                   --jobs $(JOBS) \
 | ||||||
|  |                   $(PAPEROPT_$(PAPER)) \
 | ||||||
|  |                   $(SPHINXOPTS) \
 | ||||||
|  |                   . $(BUILDDIR)/$(BUILDER) | ||||||
| 
 | 
 | ||||||
| .PHONY: help | .PHONY: help | ||||||
| help: | help: | ||||||
|  | @ -36,31 +42,19 @@ install-sphinx: | ||||||
| .PHONY: html | .PHONY: html | ||||||
| html: | html: | ||||||
| 	$(MAKE) install-sphinx | 	$(MAKE) install-sphinx | ||||||
| 	$(SPHINXBUILD) --builder html --fail-on-warning --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html | 	$(SPHINXBUILD) $(ALLSPHINXOPTS) | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." |  | ||||||
| 
 | 
 | ||||||
| .PHONY: dirhtml | .PHONY: dirhtml | ||||||
| dirhtml: | dirhtml: BUILDER = dirhtml | ||||||
| 	$(MAKE) install-sphinx | dirhtml: html | ||||||
| 	$(SPHINXBUILD) --builder dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." |  | ||||||
| 
 | 
 | ||||||
| .PHONY: singlehtml | .PHONY: singlehtml | ||||||
| singlehtml: | singlehtml: BUILDER = singlehtml | ||||||
| 	$(MAKE) install-sphinx | singlehtml: html | ||||||
| 	$(SPHINXBUILD) --builder singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." |  | ||||||
| 
 | 
 | ||||||
| .PHONY: linkcheck | .PHONY: linkcheck | ||||||
| linkcheck: | linkcheck: BUILDER = linkcheck | ||||||
| 	$(MAKE) install-sphinx | linkcheck: html | ||||||
| 	$(SPHINXBUILD) --builder linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck -j auto |  | ||||||
| 	@echo |  | ||||||
| 	@echo "Link check complete; look for any errors in the above output " \
 |  | ||||||
| 	      "or in $(BUILDDIR)/linkcheck/output.txt." |  | ||||||
| 
 | 
 | ||||||
| .PHONY: htmlview | .PHONY: htmlview | ||||||
| htmlview: html | htmlview: html | ||||||
|  |  | ||||||
|  | @ -7,10 +7,8 @@ if "%SPHINXBUILD%" == "" ( | ||||||
| ) | ) | ||||||
| set BUILDDIR=_build | set BUILDDIR=_build | ||||||
| set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . | ||||||
| set I18NSPHINXOPTS=%SPHINXOPTS% . |  | ||||||
| if NOT "%PAPER%" == "" ( | if NOT "%PAPER%" == "" ( | ||||||
| 	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% | 	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% | ||||||
| 	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| if "%1" == "" goto help | if "%1" == "" goto help | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								setup.py
									
									
									
									
									
								
							|  | @ -46,7 +46,7 @@ WEBP_ROOT = None | ||||||
| ZLIB_ROOT = None | ZLIB_ROOT = None | ||||||
| FUZZING_BUILD = "LIB_FUZZING_ENGINE" in os.environ | FUZZING_BUILD = "LIB_FUZZING_ENGINE" in os.environ | ||||||
| 
 | 
 | ||||||
| if sys.platform == "win32" and sys.version_info >= (3, 14): | if sys.platform == "win32" and sys.version_info >= (3, 15): | ||||||
|     import atexit |     import atexit | ||||||
| 
 | 
 | ||||||
|     atexit.register( |     atexit.register( | ||||||
|  | @ -224,13 +224,14 @@ def _add_directory( | ||||||
|         path.insert(where, subdir) |         path.insert(where, subdir) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _find_include_file(self: pil_build_ext, include: str) -> int: | def _find_include_file(self: pil_build_ext, include: str) -> str | None: | ||||||
|     for directory in self.compiler.include_dirs: |     for directory in self.compiler.include_dirs: | ||||||
|         _dbg("Checking for include file %s in %s", (include, directory)) |         _dbg("Checking for include file %s in %s", (include, directory)) | ||||||
|         if os.path.isfile(os.path.join(directory, include)): |         path = os.path.join(directory, include) | ||||||
|  |         if os.path.isfile(path): | ||||||
|             _dbg("Found %s", include) |             _dbg("Found %s", include) | ||||||
|             return 1 |             return path | ||||||
|     return 0 |     return None | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _find_library_file(self: pil_build_ext, library: str) -> str | None: | def _find_library_file(self: pil_build_ext, library: str) -> str | None: | ||||||
|  | @ -852,9 +853,13 @@ class pil_build_ext(build_ext): | ||||||
| 
 | 
 | ||||||
|         if feature.want("avif"): |         if feature.want("avif"): | ||||||
|             _dbg("Looking for avif") |             _dbg("Looking for avif") | ||||||
|             if _find_include_file(self, "avif/avif.h"): |             if avif_h := _find_include_file(self, "avif/avif.h"): | ||||||
|                 if _find_library_file(self, "avif"): |                 with open(avif_h, "rb") as fp: | ||||||
|                     feature.set("avif", "avif") |                     major_version = int( | ||||||
|  |                         fp.read().split(b"#define AVIF_VERSION_MAJOR ")[1].split()[0] | ||||||
|  |                     ) | ||||||
|  |                     if major_version >= 1 and _find_library_file(self, "avif"): | ||||||
|  |                         feature.set("avif", "avif") | ||||||
| 
 | 
 | ||||||
|         for f in feature: |         for f in feature: | ||||||
|             if not feature.get(f) and feature.require(f): |             if not feature.get(f) and feature.require(f): | ||||||
|  |  | ||||||
|  | @ -16,7 +16,6 @@ except ImportError: | ||||||
| # Decoder options as module globals, until there is a way to pass parameters | # Decoder options as module globals, until there is a way to pass parameters | ||||||
| # to Image.open (see https://github.com/python-pillow/Pillow/issues/569) | # to Image.open (see https://github.com/python-pillow/Pillow/issues/569) | ||||||
| DECODE_CODEC_CHOICE = "auto" | DECODE_CODEC_CHOICE = "auto" | ||||||
| # Decoding is only affected by this for libavif **0.8.4** or greater. |  | ||||||
| DEFAULT_MAX_THREADS = 0 | DEFAULT_MAX_THREADS = 0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -257,7 +257,8 @@ class ImageFile(Image.Image): | ||||||
| 
 | 
 | ||||||
|     def __setstate__(self, state: list[Any]) -> None: |     def __setstate__(self, state: list[Any]) -> None: | ||||||
|         self.tile = [] |         self.tile = [] | ||||||
|         self.filename = state[5] |         if len(state) > 5: | ||||||
|  |             self.filename = state[5] | ||||||
|         super().__setstate__(state) |         super().__setstate__(state) | ||||||
| 
 | 
 | ||||||
|     def verify(self) -> None: |     def verify(self) -> None: | ||||||
|  |  | ||||||
|  | @ -81,7 +81,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): | ||||||
| 
 | 
 | ||||||
|     def _open(self) -> None: |     def _open(self) -> None: | ||||||
|         # check placable header |         # check placable header | ||||||
|         s = self.fp.read(80) |         s = self.fp.read(44) | ||||||
| 
 | 
 | ||||||
|         if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"): |         if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"): | ||||||
|             # placeable windows metafile |             # placeable windows metafile | ||||||
|  |  | ||||||
|  | @ -113,12 +113,12 @@ V = { | ||||||
|     "BROTLI": "1.1.0", |     "BROTLI": "1.1.0", | ||||||
|     "FREETYPE": "2.13.3", |     "FREETYPE": "2.13.3", | ||||||
|     "FRIBIDI": "1.0.16", |     "FRIBIDI": "1.0.16", | ||||||
|     "HARFBUZZ": "11.1.0", |     "HARFBUZZ": "11.2.1", | ||||||
|     "JPEGTURBO": "3.1.0", |     "JPEGTURBO": "3.1.0", | ||||||
|     "LCMS2": "2.17", |     "LCMS2": "2.17", | ||||||
|     "LIBAVIF": "1.2.1", |     "LIBAVIF": "1.3.0", | ||||||
|     "LIBIMAGEQUANT": "4.3.4", |     "LIBIMAGEQUANT": "4.3.4", | ||||||
|     "LIBPNG": "1.6.47", |     "LIBPNG": "1.6.48", | ||||||
|     "LIBWEBP": "1.5.0", |     "LIBWEBP": "1.5.0", | ||||||
|     "OPENJPEG": "2.5.3", |     "OPENJPEG": "2.5.3", | ||||||
|     "TIFF": "4.7.0", |     "TIFF": "4.7.0", | ||||||
|  | @ -389,6 +389,7 @@ DEPS: dict[str, dict[str, Any]] = { | ||||||
|         "filename": f"libavif-{V['LIBAVIF']}.zip", |         "filename": f"libavif-{V['LIBAVIF']}.zip", | ||||||
|         "license": "LICENSE", |         "license": "LICENSE", | ||||||
|         "build": [ |         "build": [ | ||||||
|  |             "rustup update", | ||||||
|             f"{sys.executable} -m pip install meson", |             f"{sys.executable} -m pip install meson", | ||||||
|             *cmds_cmake( |             *cmds_cmake( | ||||||
|                 "avif_static", |                 "avif_static", | ||||||
|  | @ -399,7 +400,6 @@ DEPS: dict[str, dict[str, Any]] = { | ||||||
|                 "-DAVIF_CODEC_DAV1D=LOCAL", |                 "-DAVIF_CODEC_DAV1D=LOCAL", | ||||||
|                 "-DAVIF_CODEC_RAV1E=LOCAL", |                 "-DAVIF_CODEC_RAV1E=LOCAL", | ||||||
|                 "-DAVIF_CODEC_SVT=LOCAL", |                 "-DAVIF_CODEC_SVT=LOCAL", | ||||||
|                 "-DCMAKE_POLICY_VERSION_MINIMUM=3.5", |  | ||||||
|             ), |             ), | ||||||
|             cmd_xcopy("include", "{inc_dir}"), |             cmd_xcopy("include", "{inc_dir}"), | ||||||
|         ], |         ], | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user