Merge branch 'main' into sgi

This commit is contained in:
Andrew Murray 2025-06-27 08:37:59 +10:00
commit 5609d1144b
215 changed files with 2213 additions and 1295 deletions

View File

@ -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

View File

@ -1 +1 @@
cibuildwheel==2.23.2 cibuildwheel==3.0.0

View File

@ -1,9 +1,10 @@
mypy==1.15.0 mypy==1.16.1
IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6 IceSpringPySideStubs-PySide6
ipython ipython
numpy numpy
packaging packaging
pyarrow-stubs
pytest pytest
sphinx sphinx
types-atheris types-atheris

46
.github/ISSUE_TEMPLATE/RELEASE.md vendored Normal file
View 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]]
```

View File

@ -48,6 +48,7 @@ jobs:
debian-12-bookworm-x86, debian-12-bookworm-x86,
debian-12-bookworm-amd64, debian-12-bookworm-amd64,
fedora-41-amd64, fedora-41-amd64,
fedora-42-amd64,
gentoo, gentoo,
ubuntu-22.04-jammy-amd64, ubuntu-22.04-jammy-amd64,
ubuntu-24.04-noble-amd64, ubuntu-24.04-noble-amd64,

View File

@ -0,0 +1,60 @@
name: Test Valgrind Memory Leaks
# like the Docker tests, but running valgrind only on *.c/*.h changes.
# this is very expensive. Only run on the pull request.
on:
# push:
# branches:
# - "**"
# paths:
# - ".github/workflows/test-valgrind.yml"
# - "**.c"
# - "**.h"
pull_request:
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"
- "**.h"
- "depends/docker-test-valgrind-memory.sh"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
docker: [
ubuntu-22.04-jammy-amd64-valgrind,
]
dockerTag: [main]
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Docker pull
run: |
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
- name: Build and Run Valgrind
run: |
# The Pillow user in the docker container is UID 1001
sudo chown -R 1001 $GITHUB_WORKSPACE
docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} /Pillow/depends/docker-test-valgrind-memory.sh
sudo chown -R runner $GITHUB_WORKSPACE

View File

@ -31,16 +31,15 @@ env:
jobs: jobs:
build: build:
runs-on: ${{ matrix.os }} runs-on: windows-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"] python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"]
architecture: ["x64"] architecture: ["x64"]
os: ["windows-latest"]
include: include:
# Test the oldest Python on 32-bit # Test the oldest Python on 32-bit
- { python-version: "3.9", architecture: "x86", os: "windows-2019" } - { python-version: "3.9", architecture: "x86" }
timeout-minutes: 45 timeout-minutes: 45
@ -84,7 +83,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 +97,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

View File

@ -43,6 +43,7 @@ jobs:
python-version: [ python-version: [
"pypy3.11", "pypy3.11",
"pypy3.10", "pypy3.10",
"3.14t",
"3.14", "3.14",
"3.13t", "3.13t",
"3.13", "3.13",
@ -55,6 +56,7 @@ jobs:
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" } - { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
- { python-version: "3.10", PYTHONOPTIMIZE: 2 } - { python-version: "3.10", PYTHONOPTIMIZE: 2 }
# Free-threaded # Free-threaded
- { python-version: "3.14t", disable-gil: true }
- { python-version: "3.13t", disable-gil: true } - { python-version: "3.13t", disable-gil: true }
# M1 only available for 3.10+ # M1 only available for 3.10+
- { os: "macos-13", python-version: "3.9" } - { os: "macos-13", python-version: "3.9" }

View File

@ -38,9 +38,9 @@ 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.0.1 HARFBUZZ_VERSION=11.2.1
LIBPNG_VERSION=1.6.47 LIBPNG_VERSION=1.6.49
JPEGTURBO_VERSION=3.1.0 JPEGTURBO_VERSION=3.1.1
OPENJPEG_VERSION=2.5.3 OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.8.1 XZ_VERSION=5.8.1
TIFF_VERSION=4.7.0 TIFF_VERSION=4.7.0
@ -51,6 +51,7 @@ LIBWEBP_VERSION=1.5.0
BZIP2_VERSION=1.0.8 BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0 LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0 BROTLI_VERSION=1.1.0
LIBAVIF_VERSION=1.3.0
function build_pkg_config { function build_pkg_config {
if [ -e pkg-config-stamp ]; then return; fi if [ -e pkg-config-stamp ]; then return; fi
@ -92,12 +93,65 @@ function build_harfbuzz {
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
(cd $out_dir \ (cd $out_dir \
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=release -Dfreetype=enabled -Dglib=disabled -Dtests=disabled) && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled)
(cd $out_dir/build \ (cd $out_dir/build \
&& meson install) && meson install)
touch harfbuzz-stamp touch harfbuzz-stamp
} }
function build_libavif {
if [ -e libavif-stamp ]; then return; fi
python3 -m pip install meson ninja
if [[ "$PLAT" == "x86_64" ]] || [ -n "$SANITIZER" ]; then
build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
fi
local build_type=MinSizeRel
local lto=ON
local libavif_cmake_flags
if [ -n "$IS_MACOS" ]; then
lto=OFF
libavif_cmake_flags=(
-DCMAKE_C_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_CXX_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,-S,-x,-dead_strip_dylibs" \
)
else
if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "x86_64" ]]; then
build_type=Release
fi
libavif_cmake_flags=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now")
fi
local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz)
# CONFIG_AV1_HIGHBITDEPTH=0 is a flag for libaom (included as a subproject
# of libavif) that disables support for encoding high bit depth images.
(cd $out_dir \
&& cmake \
-DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \
-DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib \
-DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \
-DBUILD_SHARED_LIBS=ON \
-DAVIF_LIBSHARPYUV=LOCAL \
-DAVIF_LIBYUV=LOCAL \
-DAVIF_CODEC_AOM=LOCAL \
-DCONFIG_AV1_HIGHBITDEPTH=0 \
-DAVIF_CODEC_AOM_DECODE=OFF \
-DAVIF_CODEC_DAV1D=LOCAL \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=$lto \
-DCMAKE_C_VISIBILITY_PRESET=hidden \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden \
-DCMAKE_BUILD_TYPE=$build_type \
"${libavif_cmake_flags[@]}" \
. \
&& make install)
touch libavif-stamp
}
function build { function build {
build_xz build_xz
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
@ -132,6 +186,7 @@ function build {
build_tiff build_tiff
fi fi
build_libavif
build_libpng build_libpng
build_lcms2 build_lcms2
build_openjpeg build_openjpeg

View File

@ -9,17 +9,21 @@ if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
} }
$env:path += ";$pillow\winbuild\build\bin\" $env:path += ";$pillow\winbuild\build\bin\"
& "$venv\Scripts\activate.ps1" if (Test-Path $venv\Scripts\pypy.exe) {
$python = "pypy.exe"
} else {
$python = "python.exe"
}
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f & reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
if ("$venv" -like "*\cibw-run-*-win_amd64\*") { if ("$venv" -like "*\cibw-run-*-win_amd64\*") {
& python -m pip install numpy & $venv\Scripts\$python -m pip install numpy
} }
cd $pillow cd $pillow
& python -VV & $venv\Scripts\$python -VV
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }
& python selftest.py & $venv\Scripts\$python selftest.py
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }
& python -m pytest -vx Tests\check_wheel.py & $venv\Scripts\$python -m pytest -vx Tests\check_wheel.py
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }
& python -m pytest -vx Tests & $venv\Scripts\$python -m pytest -vx Tests
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }

View File

@ -58,7 +58,7 @@ jobs:
- name: "macOS 10.13 x86_64" - name: "macOS 10.13 x86_64"
os: macos-13 os: macos-13
cibw_arch: x86_64 cibw_arch: x86_64
build: "cp3{12,13}*" build: "cp3{12,13,14}*"
macosx_deployment_target: "10.13" macosx_deployment_target: "10.13"
- name: "macOS 10.15 x86_64" - name: "macOS 10.15 x86_64"
os: macos-13 os: macos-13
@ -110,7 +110,6 @@ jobs:
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_SKIP: pp39-*
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
@ -121,14 +120,17 @@ jobs:
windows: windows:
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow' if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
name: Windows ${{ matrix.cibw_arch }} name: Windows ${{ matrix.cibw_arch }}
runs-on: windows-latest runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- cibw_arch: x86 - cibw_arch: x86
os: windows-latest
- cibw_arch: AMD64 - cibw_arch: AMD64
os: windows-latest
- cibw_arch: ARM64 - cibw_arch: ARM64
os: windows-11-arm
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@ -157,7 +159,7 @@ jobs:
# Install extra test images # Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images xcopy /S /Y Tests\test-images\* Tests\images
& python.exe winbuild\build_prepare.py -v --no-imagequant --no-avif --architecture=${{ matrix.cibw_arch }} & python.exe winbuild\build_prepare.py -v --no-imagequant --architecture=${{ matrix.cibw_arch }}
shell: pwsh shell: pwsh
- name: Build wheels - name: Build wheels
@ -185,7 +187,6 @@ jobs:
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
CIBW_CACHE_PATH: "C:\\cibw" CIBW_CACHE_PATH: "C:\\cibw"
CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
CIBW_SKIP: pp39-*
CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_COMMAND: 'docker run --rm CIBW_TEST_COMMAND: 'docker run --rm
-v {project}:C:\pillow -v {project}:C:\pillow

7
.github/zizmor.yml vendored Normal file
View 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

View File

@ -1,8 +1,8 @@
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.12
hooks: hooks:
- id: ruff - id: ruff-check
args: [--exit-non-zero-on-fix] args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror - repo: https://github.com/psf/black-pre-commit-mirror
@ -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.5
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.9.0
hooks: hooks:
- id: zizmor - id: zizmor
@ -68,7 +68,7 @@ repos:
- id: sphinx-lint - id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt - repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.5.1 rev: v2.6.0
hooks: hooks:
- id: pyproject-fmt - id: pyproject-fmt

View File

@ -23,6 +23,10 @@ doc html:
htmlview: htmlview:
$(MAKE) -C docs htmlview $(MAKE) -C docs htmlview
.PHONY: htmllive
htmllive:
$(MAKE) -C docs htmllive
.PHONY: doccheck .PHONY: doccheck
doccheck: doccheck:
$(MAKE) doc $(MAKE) doc
@ -43,6 +47,7 @@ help:
@echo " docserve run an HTTP server on the docs directory" @echo " docserve run an HTTP server on the docs directory"
@echo " html make HTML docs" @echo " html make HTML docs"
@echo " htmlview open the index page built by the html target in your browser" @echo " htmlview open the index page built by the html target in your browser"
@echo " htmllive rebuild and reload HTML files in your browser"
@echo " install make and install" @echo " install make and install"
@echo " install-coverage make and install with C coverage" @echo " install-coverage make and install with C coverage"
@echo " lint run the lint checks" @echo " lint run the lint checks"
@ -92,13 +97,27 @@ test:
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
python3 -m pytest -qq python3 -m pytest -qq
.PHONY: test-p
test-p:
python3 -c "import xdist" > /dev/null 2>&1 || python3 -m pip install pytest-xdist
python3 -m pytest -qq -n auto
.PHONY: valgrind .PHONY: valgrind
valgrind: valgrind:
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \ PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
--log-file=/tmp/valgrind-output \ --log-file=/tmp/valgrind-output \
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
.PHONY: valgrind-leak
valgrind-leak:
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp \
--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite \
--log-file=/tmp/valgrind-output \
python3 -m pytest -vv --valgrind --valgrind-log=/tmp/valgrind-output
.PHONY: readme .PHONY: readme
readme: readme:
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2 python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2

View File

@ -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).

View File

@ -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

View File

@ -1,4 +1,4 @@
Pillow Tests Pillow tests
============ ============
Test scripts are named ``test_xxx.py``. Helper classes and functions can be found in ``helper.py``. Test scripts are named ``test_xxx.py``. Helper classes and functions can be found in ``helper.py``.

View File

@ -9,15 +9,20 @@ from .helper import is_pypy
def test_wheel_modules() -> None: def test_wheel_modules() -> None:
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"} expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp", "avif"}
# tkinter is not available in cibuildwheel installed CPython on Windows if sys.platform == "win32":
try: # tkinter is not available in cibuildwheel installed CPython on Windows
import tkinter try:
import tkinter
assert tkinter assert tkinter
except ImportError: except ImportError:
expected_modules.remove("tkinter") expected_modules.remove("tkinter")
# libavif is not available on Windows for ARM64 architectures
if platform.machine() == "ARM64":
expected_modules.remove("avif")
assert set(features.get_supported_modules()) == expected_modules assert set(features.get_supported_modules()) == expected_modules

View File

@ -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)

BIN
Tests/images/op_index.qoi Normal file

Binary file not shown.

BIN
Tests/images/p_4_planes.pcx Normal file

Binary file not shown.

View File

@ -14,3 +14,23 @@
fun:_TIFFReadEncodedTileAndAllocBuffer fun:_TIFFReadEncodedTileAndAllocBuffer
... ...
} }
{
<python_alloc_possible_leak>
Memcheck:Leak
match-leak-kinds: all
fun:malloc
fun:_PyMem_RawMalloc
fun:PyObject_Malloc
...
}
{
<python_realloc_possible_leak>
Memcheck:Leak
match-leak-kinds: all
fun:malloc
fun:_PyMem_RawRealloc
fun:PyMem_Realloc
...
}

View File

@ -47,7 +47,6 @@ def test_unknown_version() -> None:
], ],
) )
def test_old_version(deprecated: str, plural: bool, expected: str) -> None: def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
expected = r""
with pytest.raises(RuntimeError, match=expected): with pytest.raises(RuntimeError, match=expected):
_deprecate.deprecate(deprecated, 1, plural=plural) _deprecate.deprecate(deprecated, 1, plural=plural)

View File

@ -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"
@ -254,7 +254,9 @@ class TestFileAvif:
assert_image(im, "RGBA", (64, 64)) assert_image(im, "RGBA", (64, 64))
# image has 876 transparent pixels # image has 876 transparent pixels
assert im.getchannel("A").getcolors()[0] == (876, 0) colors = im.getchannel("A").getcolors()
assert colors is not None
assert colors[0] == (876, 0)
def test_save_transparent(self, tmp_path: Path) -> None: def test_save_transparent(self, tmp_path: Path) -> None:
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0)) im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))

View File

@ -7,9 +7,8 @@ import pytest
from PIL import BlpImagePlugin, Image from PIL import BlpImagePlugin, Image
from .helper import ( from .helper import (
assert_image_equal,
assert_image_equal_tofile, assert_image_equal_tofile,
assert_image_similar, assert_image_similar_tofile,
hopper, hopper,
) )
@ -52,18 +51,16 @@ def test_save(tmp_path: Path) -> None:
im = hopper("P") im = hopper("P")
im.save(f, blp_version=version) im.save(f, blp_version=version)
with Image.open(f) as reloaded: assert_image_equal_tofile(im.convert("RGB"), f)
assert_image_equal(im.convert("RGB"), reloaded)
with Image.open("Tests/images/transparent.png") as im: with Image.open("Tests/images/transparent.png") as im:
f = tmp_path / "temp.blp" f = tmp_path / "temp.blp"
im.convert("P").save(f, blp_version=version) im.convert("P").save(f, blp_version=version)
with Image.open(f) as reloaded: assert_image_similar_tofile(im, f, 8)
assert_image_similar(im, reloaded, 8)
im = hopper() im = hopper()
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Unsupported BLP image mode"):
im.save(f) im.save(f)

View File

@ -190,9 +190,9 @@ def test_rle8() -> None:
# Signal end of bitmap before the image is finished # Signal end of bitmap before the image is finished
with open("Tests/images/bmp/g/pal8rle.bmp", "rb") as fp: with open("Tests/images/bmp/g/pal8rle.bmp", "rb") as fp:
data = fp.read(1063) + b"\x01" data = fp.read(1063) + b"\x01"
with Image.open(io.BytesIO(data)) as im: with Image.open(io.BytesIO(data)) as im:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.load() im.load()
def test_rle4() -> None: def test_rle4() -> None:
@ -214,9 +214,9 @@ def test_rle4() -> None:
def test_rle8_eof(file_name: str, length: int) -> None: def test_rle8_eof(file_name: str, length: int) -> None:
with open(file_name, "rb") as fp: with open(file_name, "rb") as fp:
data = fp.read(length) data = fp.read(length)
with Image.open(io.BytesIO(data)) as im: with Image.open(io.BytesIO(data)) as im:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.load() im.load()
def test_offset() -> None: def test_offset() -> None:

View File

@ -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"],

View File

@ -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:

View File

@ -540,7 +540,9 @@ def test_dispose_background_transparency() -> None:
img.seek(2) img.seek(2)
px = img.load() px = img.load()
assert px is not None assert px is not None
assert px[35, 30][3] == 0 value = px[35, 30]
assert isinstance(value, tuple)
assert value[3] == 0
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -1422,7 +1424,9 @@ def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None:
def test_lzw_bits() -> None: def test_lzw_bits() -> None:
# see https://github.com/python-pillow/Pillow/issues/2811 # see https://github.com/python-pillow/Pillow/issues/2811
with Image.open("Tests/images/issue_2811.gif") as im: with Image.open("Tests/images/issue_2811.gif") as im:
assert im.tile[0][3][0] == 11 # LZW bits args = im.tile[0][3]
assert isinstance(args, tuple)
assert args[0] == 11 # LZW bits
# codec error prepatch # codec error prepatch
im.load() im.load()
@ -1477,7 +1481,11 @@ def test_saving_rgba(tmp_path: Path) -> None:
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
reloaded_rgba = reloaded.convert("RGBA") reloaded_rgba = reloaded.convert("RGBA")
assert reloaded_rgba.load()[0, 0][3] == 0 px = reloaded_rgba.load()
assert px is not None
value = px[0, 0]
assert isinstance(value, tuple)
assert value[3] == 0
@pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False})) @pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False}))

View File

@ -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
@ -129,30 +130,26 @@ class TestFileJpeg:
def test_cmyk(self) -> None: def test_cmyk(self) -> None:
# Test CMYK handling. Thanks to Tim and Charlie for test data, # Test CMYK handling. Thanks to Tim and Charlie for test data,
# Michael for getting me to look one more time. # Michael for getting me to look one more time.
f = "Tests/images/pil_sample_cmyk.jpg" def check(im: ImageFile.ImageFile) -> None:
with Image.open(f) as im: cmyk = im.getpixel((0, 0))
# the source image has red pixels in the upper left corner. assert isinstance(cmyk, tuple)
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) c, m, y, k = (x / 255.0 for x in cmyk)
assert c == 0.0 assert c == 0.0
assert m > 0.8 assert m > 0.8
assert y > 0.8 assert y > 0.8
assert k == 0.0 assert k == 0.0
# the opposite corner is black # the opposite corner is black
c, m, y, k = ( cmyk = im.getpixel((im.size[0] - 1, im.size[1] - 1))
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) assert isinstance(cmyk, tuple)
) k = cmyk[3] / 255.0
assert k > 0.9 assert k > 0.9
with Image.open("Tests/images/pil_sample_cmyk.jpg") as im:
# the source image has red pixels in the upper left corner.
check(im)
# roundtrip, and check again # roundtrip, and check again
im = self.roundtrip(im) check(self.roundtrip(im))
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))
assert c == 0.0
assert m > 0.8
assert y > 0.8
assert k == 0.0
c, m, y, k = (
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))
)
assert k > 0.9
def test_rgb(self) -> None: def test_rgb(self) -> None:
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, ...]: def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, ...]:
@ -1033,7 +1030,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
@ -1064,10 +1061,16 @@ class TestFileJpeg:
for marker in b"\xff\xd8", b"\xff\xd9": for marker in b"\xff\xd8", b"\xff\xd9":
assert marker in data[1] assert marker in data[1]
assert marker in data[2] assert marker in data[2]
# DHT, DQT
for marker in b"\xff\xc4", b"\xff\xdb": # DQT
markers = [b"\xff\xdb"]
if features.check_feature("libjpeg_turbo"):
# DHT
markers.append(b"\xff\xc4")
for marker in markers:
assert marker in data[1] assert marker in data[1]
assert marker not in data[2] assert marker not in data[2]
# SOF0, SOS, APP0 (JFIF header) # SOF0, SOS, APP0 (JFIF header)
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0": for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
assert marker not in data[1] assert marker not in data[1]

View File

@ -457,8 +457,8 @@ def test_comment() -> None:
# Test an image that is truncated partway through a codestream # Test an image that is truncated partway through a codestream
with open("Tests/images/comment.jp2", "rb") as fp: with open("Tests/images/comment.jp2", "rb") as fp:
b = BytesIO(fp.read(130)) b = BytesIO(fp.read(130))
with Image.open(b) as im: with Image.open(b) as im:
pass pass
def test_save_comment(card: ImageFile.ImageFile) -> None: def test_save_comment(card: ImageFile.ImageFile) -> None:

View File

@ -81,7 +81,7 @@ class TestFileLibTiff(LibTiffTestCase):
s = io.BytesIO() s = io.BytesIO()
with open(test_file, "rb") as f: with open(test_file, "rb") as f:
s.write(f.read()) s.write(f.read())
s.seek(0) s.seek(0)
with Image.open(s) as im: with Image.open(s) as im:
assert im.size == (500, 500) assert im.size == (500, 500)
self._assert_noerr(tmp_path, im) self._assert_noerr(tmp_path, im)
@ -1050,12 +1050,12 @@ class TestFileLibTiff(LibTiffTestCase):
with open("Tests/images/old-style-jpeg-compression.tif", "rb") as fp: with open("Tests/images/old-style-jpeg-compression.tif", "rb") as fp:
data = fp.read() data = fp.read()
# Set EXIF Orientation to 2 # Set EXIF Orientation to 2
data = data[:102] + b"\x02" + data[103:] data = data[:102] + b"\x02" + data[103:]
with Image.open(io.BytesIO(data)) as im: with Image.open(io.BytesIO(data)) as im:
im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png") assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
def test_open_missing_samplesperpixel(self) -> None: def test_open_missing_samplesperpixel(self) -> None:
with Image.open( with Image.open(

View File

@ -32,7 +32,7 @@ class TestFileLibTiffSmall(LibTiffTestCase):
s = BytesIO() s = BytesIO()
with open(test_file, "rb") as f: with open(test_file, "rb") as f:
s.write(f.read()) s.write(f.read())
s.seek(0) s.seek(0)
with Image.open(s) as im: with Image.open(s) as im:
assert im.size == (128, 128) assert im.size == (128, 128)
self._assert_noerr(tmp_path, im) self._assert_noerr(tmp_path, im)

View File

@ -156,6 +156,7 @@ def test_reload_exif_after_seek() -> None:
def test_mp(test_file: str) -> None: def test_mp(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None
assert mpinfo[45056] == b"0100" assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2 assert mpinfo[45057] == 2
@ -165,6 +166,7 @@ def test_mp_offset() -> None:
# in APP2 data, in contrast to normal 8 # in APP2 data, in contrast to normal 8
with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im: with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im:
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None
assert mpinfo[45056] == b"0100" assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2 assert mpinfo[45057] == 2
@ -181,6 +183,7 @@ def test_mp_no_data() -> None:
def test_mp_attribute(test_file: str) -> None: def test_mp_attribute(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None
for frame_number, mpentry in enumerate(mpinfo[0xB002]): for frame_number, mpentry in enumerate(mpinfo[0xB002]):
mpattr = mpentry["Attribute"] mpattr = mpentry["Attribute"]
if frame_number: if frame_number:

View File

@ -37,6 +37,11 @@ def test_sanity(tmp_path: Path) -> None:
im.save(f) im.save(f)
def test_p_4_planes() -> None:
with Image.open("Tests/images/p_4_planes.pcx") as im:
assert im.getpixel((0, 0)) == 3
def test_bad_image_size() -> None: def test_bad_image_size() -> None:
with open("Tests/images/pil184.pcx", "rb") as fp: with open("Tests/images/pil184.pcx", "rb") as fp:
data = fp.read() data = fp.read()

View File

@ -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

View File

@ -100,11 +100,11 @@ class TestFilePng:
assert im.format == "PNG" assert im.format == "PNG"
assert im.get_format_mimetype() == "image/png" assert im.get_format_mimetype() == "image/png"
for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]: for mode in ["1", "L", "P", "RGB", "I;16", "I;16B"]:
im = hopper(mode) im = hopper(mode)
im.save(test_file) im.save(test_file)
with Image.open(test_file) as reloaded: with Image.open(test_file) as reloaded:
if mode in ("I", "I;16B"): if mode == "I;16B":
reloaded = reloaded.convert(mode) reloaded = reloaded.convert(mode)
assert_image_equal(reloaded, im) assert_image_equal(reloaded, im)
@ -801,6 +801,16 @@ class TestFilePng:
with Image.open("Tests/images/truncated_end_chunk.png") as im: with Image.open("Tests/images/truncated_end_chunk.png") as im:
assert_image_equal_tofile(im, "Tests/images/hopper.png") assert_image_equal_tofile(im, "Tests/images/hopper.png")
def test_deprecation(self, tmp_path: Path) -> None:
test_file = tmp_path / "out.png"
im = hopper("I")
with pytest.warns(DeprecationWarning):
im.save(test_file)
with Image.open(test_file) as reloaded:
assert_image_equal(im, reloaded.convert("I"))
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
@skip_unless_feature("zlib") @skip_unless_feature("zlib")

View File

@ -288,14 +288,16 @@ def test_non_integer_token(tmp_path: Path) -> None:
pass pass
def test_header_token_too_long(tmp_path: Path) -> None: @pytest.mark.parametrize("data", (b"P3\x0cAAAAAAAAAA\xee", b"P6\n 01234567890"))
def test_header_token_too_long(tmp_path: Path, data: bytes) -> None:
path = tmp_path / "temp.ppm" path = tmp_path / "temp.ppm"
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(b"P6\n 01234567890") f.write(data)
with pytest.raises(ValueError, match="Token too long in file header: 01234567890"): with pytest.raises(ValueError) as e:
with Image.open(path): with Image.open(path):
pass pass
assert "Token too long in file header: " in repr(e)
def test_truncated_file(tmp_path: Path) -> None: def test_truncated_file(tmp_path: Path) -> None:

View File

@ -1,10 +1,12 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from PIL import Image, QoiImagePlugin from PIL import Image, QoiImagePlugin
from .helper import assert_image_equal_tofile from .helper import assert_image_equal_tofile, hopper
def test_sanity() -> None: def test_sanity() -> None:
@ -28,3 +30,28 @@ def test_invalid_file() -> None:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
QoiImagePlugin.QoiImageFile(invalid_file) QoiImagePlugin.QoiImageFile(invalid_file)
def test_op_index() -> None:
# QOI_OP_INDEX as the first chunk
with Image.open("Tests/images/op_index.qoi") as im:
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
def test_save(tmp_path: Path) -> None:
f = tmp_path / "temp.qoi"
im = hopper()
im.save(f, colorspace="sRGB")
assert_image_equal_tofile(im, f)
for path in ("Tests/images/default_font.png", "Tests/images/pil123rgba.png"):
with Image.open(path) as im:
im.save(f)
assert_image_equal_tofile(im, f)
im = hopper("P")
with pytest.raises(ValueError, match="Unsupported QOI image mode"):
im.save(f)

View File

@ -220,12 +220,16 @@ def test_horizontal_orientations() -> None:
with Image.open("Tests/images/rgb32rle_top_right.tga") as im: with Image.open("Tests/images/rgb32rle_top_right.tga") as im:
px = im.load() px = im.load()
assert px is not None assert px is not None
assert px[90, 90][:3] == (0, 0, 0) value = px[90, 90]
assert isinstance(value, tuple)
assert value[:3] == (0, 0, 0)
with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im: with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im:
px = im.load() px = im.load()
assert px is not None assert px is not None
assert px[90, 90][:3] == (0, 255, 0) value = px[90, 90]
assert isinstance(value, tuple)
assert value[:3] == (0, 255, 0)
def test_save_rle(tmp_path: Path) -> None: def test_save_rle(tmp_path: Path) -> None:

View File

@ -14,6 +14,7 @@ from PIL import (
ImageFile, ImageFile,
JpegImagePlugin, JpegImagePlugin,
TiffImagePlugin, TiffImagePlugin,
TiffTags,
UnidentifiedImageError, UnidentifiedImageError,
) )
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
@ -26,6 +27,7 @@ from .helper import (
hopper, hopper,
is_pypy, is_pypy,
is_win32, is_win32,
timeout_unless_slower_valgrind,
) )
ElementTree: ModuleType | None ElementTree: ModuleType | None
@ -47,25 +49,10 @@ class TestFileTiff:
assert im.size == (128, 128) assert im.size == (128, 128)
assert im.format == "TIFF" assert im.format == "TIFF"
hopper("1").save(filename) for mode in ("1", "L", "P", "RGB", "I", "I;16", "I;16L"):
with Image.open(filename): hopper(mode).save(filename)
pass with Image.open(filename):
pass
hopper("L").save(filename)
with Image.open(filename):
pass
hopper("P").save(filename)
with Image.open(filename):
pass
hopper("RGB").save(filename)
with Image.open(filename):
pass
hopper("I").save(filename)
with Image.open(filename):
pass
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file(self) -> None: def test_unclosed_file(self) -> None:
@ -899,6 +886,29 @@ class TestFileTiff:
assert description[0]["format"] == "image/tiff" assert description[0]["format"] == "image/tiff"
assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"] assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
def test_getxmp_undefined(self, tmp_path: Path) -> None:
tmpfile = tmp_path / "temp.tif"
im = Image.new("L", (1, 1))
ifd = TiffImagePlugin.ImageFileDirectory_v2()
ifd.tagtype[700] = TiffTags.UNDEFINED
with Image.open("Tests/images/lab.tif") as im_xmp:
ifd[700] = im_xmp.info["xmp"]
im.save(tmpfile, tiffinfo=ifd)
with Image.open(tmpfile) as im_reloaded:
if ElementTree is None:
with pytest.warns(
UserWarning,
match="XMP data cannot be read without defusedxml dependency",
):
assert im_reloaded.getxmp() == {}
else:
assert "xmp" in im_reloaded.info
xmp = im_reloaded.getxmp()
description = xmp["xmpmeta"]["RDF"]["Description"]
assert description[0]["format"] == "image/tiff"
def test_get_photoshop_blocks(self) -> None: def test_get_photoshop_blocks(self) -> None:
with Image.open("Tests/images/lab.tif") as im: with Image.open("Tests/images/lab.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile) assert isinstance(im, TiffImagePlugin.TiffImageFile)
@ -988,7 +998,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 +1011,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):

View File

@ -219,6 +219,7 @@ class TestFileWebp:
# Save P mode GIF with background # Save P mode GIF with background
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
original_value = im.convert("RGB").getpixel((1, 1)) original_value = im.convert("RGB").getpixel((1, 1))
assert isinstance(original_value, tuple)
# Save as WEBP # Save as WEBP
im.save(out_webp, save_all=True) im.save(out_webp, save_all=True)
@ -230,6 +231,7 @@ class TestFileWebp:
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))
assert isinstance(reread_value, tuple)
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 < 5 assert difference < 5

View File

@ -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)
@ -673,6 +671,7 @@ class TestImage:
im_remapped = im.remap_palette(list(range(256))) im_remapped = im.remap_palette(list(range(256)))
assert_image_equal(im, im_remapped) assert_image_equal(im, im_remapped)
assert im.palette is not None assert im.palette is not None
assert im_remapped.palette is not None
assert im.palette.palette == im_remapped.palette.palette assert im.palette.palette == im_remapped.palette.palette
# Test illegal image mode # Test illegal image mode
@ -975,6 +974,11 @@ class TestImage:
assert tag not in exif.get_ifd(0x8769) assert tag not in exif.get_ifd(0x8769)
assert exif.get_ifd(0xA005) assert exif.get_ifd(0xA005)
def test_exif_from_xmp_bytes(self) -> None:
im = Image.new("RGB", (1, 1))
im.info["xmp"] = b'\xff tiff:Orientation="2"'
assert im.getexif()[274] == 2
def test_empty_xmp(self) -> None: def test_empty_xmp(self) -> None:
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
if ElementTree is None: if ElementTree is None:
@ -991,7 +995,7 @@ class TestImage:
im = Image.new("RGB", (1, 1)) im = Image.new("RGB", (1, 1))
im.info["xmp"] = ( im.info["xmp"] = (
b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n' b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00' b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00 '
) )
if ElementTree is None: if ElementTree is None:
with pytest.warns( with pytest.warns(

View File

@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Any from typing import Any
import pytest import pytest
from packaging.version import parse as parse_version from packaging.version import parse as parse_version
@ -13,6 +13,7 @@ numpy = pytest.importorskip("numpy", reason="NumPy not installed")
im = hopper().resize((128, 100)) im = hopper().resize((128, 100))
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
import numpy.typing as npt import numpy.typing as npt
@ -101,7 +102,8 @@ def test_fromarray_strides_without_tobytes() -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)}) wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)})
Image.fromarray(wrapped, "L") with pytest.warns(DeprecationWarning):
Image.fromarray(wrapped, "L")
def test_fromarray_palette() -> None: def test_fromarray_palette() -> None:
@ -110,7 +112,8 @@ def test_fromarray_palette() -> None:
a = numpy.array(i) a = numpy.array(i)
# Act # Act
out = Image.fromarray(a, "P") with pytest.warns(DeprecationWarning):
out = Image.fromarray(a, "P")
# Assert that the Python and C palettes match # Assert that the Python and C palettes match
assert out.palette is not None assert out.palette is not None

View File

@ -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"):

View File

@ -783,9 +783,10 @@ def test_rectangle_I16(bbox: Coords) -> None:
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
# Act # Act
draw.rectangle(bbox, outline=0xFFFF) draw.rectangle(bbox, outline=0xCDEF)
# Assert # Assert
assert im.getpixel((X0, Y0)) == 0xCDEF
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff") assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff")

View File

@ -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

View File

@ -43,6 +43,7 @@ class TestImageGrab:
if ( if (
sys.platform not in ("win32", "darwin") sys.platform not in ("win32", "darwin")
and not shutil.which("gnome-screenshot") and not shutil.which("gnome-screenshot")
and not shutil.which("grim")
and not shutil.which("spectacle") and not shutil.which("spectacle")
): ):
with pytest.raises(OSError) as e: with pytest.raises(OSError) as e:

View File

@ -1,7 +1,6 @@
from __future__ import annotations from __future__ import annotations
import warnings import warnings
from typing import TYPE_CHECKING
import pytest import pytest
@ -9,6 +8,7 @@ from PIL import Image, _typing
from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
import numpy import numpy
import numpy.typing as npt import numpy.typing as npt

View File

@ -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)

View File

@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import Any # undone from typing import Any, NamedTuple
import pytest import pytest
@ -10,30 +10,73 @@ from .helper import (
assert_deep_equal, assert_deep_equal,
assert_image_equal, assert_image_equal,
hopper, hopper,
is_big_endian,
) )
pyarrow = pytest.importorskip("pyarrow", reason="PyArrow not installed") TYPE_CHECKING = False
if TYPE_CHECKING:
import pyarrow
else:
pyarrow = pytest.importorskip("pyarrow", reason="PyArrow not installed")
TEST_IMAGE_SIZE = (10, 10) TEST_IMAGE_SIZE = (10, 10)
def _test_img_equals_pyarray( def _test_img_equals_pyarray(
img: Image.Image, arr: Any, mask: list[int] | None img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
) -> None: ) -> None:
assert img.height * img.width == len(arr) assert img.height * img.width * elts_per_pixel == len(arr)
px = img.load() px = img.load()
assert px is not None assert px is not None
if elts_per_pixel > 1 and mask is None:
# have to do element-wise comparison when we're comparing
# flattened r,g,b,a to a pixel.
mask = list(range(elts_per_pixel))
for x in range(0, img.size[0], int(img.size[0] / 10)): for x in range(0, img.size[0], int(img.size[0] / 10)):
for y in range(0, img.size[1], int(img.size[1] / 10)): for y in range(0, img.size[1], int(img.size[1] / 10)):
if mask: if mask:
pixel = px[x, y]
assert isinstance(pixel, tuple)
for ix, elt in enumerate(mask): for ix, elt in enumerate(mask):
pixel = px[x, y] if elts_per_pixel == 1:
assert isinstance(pixel, tuple) assert pixel[ix] == arr[y * img.width + x].as_py()[elt]
assert pixel[ix] == arr[y * img.width + x].as_py()[elt] else:
assert (
pixel[ix]
== arr[(y * img.width + x) * elts_per_pixel + elt].as_py()
)
else: else:
assert_deep_equal(px[x, y], arr[y * img.width + x].as_py()) assert_deep_equal(px[x, y], arr[y * img.width + x].as_py())
def _test_img_equals_int32_pyarray(
img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
) -> None:
assert img.height * img.width * elts_per_pixel == len(arr)
px = img.load()
assert px is not None
if mask is None:
# have to do element-wise comparison when we're comparing
# flattened rgba in an uint32 to a pixel.
mask = list(range(elts_per_pixel))
for x in range(0, img.size[0], int(img.size[0] / 10)):
for y in range(0, img.size[1], int(img.size[1] / 10)):
pixel = px[x, y]
assert isinstance(pixel, tuple)
arr_pixel_int = arr[y * img.width + x].as_py()
arr_pixel_tuple = (
arr_pixel_int % 256,
(arr_pixel_int // 256) % 256,
(arr_pixel_int // 256**2) % 256,
(arr_pixel_int // 256**3),
)
if is_big_endian():
arr_pixel_tuple = arr_pixel_tuple[::-1]
for ix, elt in enumerate(mask):
assert pixel[ix] == arr_pixel_tuple[elt]
# really hard to get a non-nullable list type # really hard to get a non-nullable list type
fl_uint8_4_type = pyarrow.field( fl_uint8_4_type = pyarrow.field(
"_", pyarrow.list_(pyarrow.field("_", pyarrow.uint8()).with_nullable(False), 4) "_", pyarrow.list_(pyarrow.field("_", pyarrow.uint8()).with_nullable(False), 4)
@ -55,14 +98,14 @@ fl_uint8_4_type = pyarrow.field(
("HSV", fl_uint8_4_type, [0, 1, 2]), ("HSV", fl_uint8_4_type, [0, 1, 2]),
), ),
) )
def test_to_array(mode: str, dtype: Any, mask: list[int] | None) -> None: def test_to_array(mode: str, dtype: pyarrow.DataType, mask: list[int] | None) -> None:
img = hopper(mode) img = hopper(mode)
# Resize to non-square # Resize to non-square
img = img.crop((3, 0, 124, 127)) img = img.crop((3, 0, 124, 127))
assert img.size == (121, 127) assert img.size == (121, 127)
arr = pyarrow.array(img) arr = pyarrow.array(img) # type: ignore[call-overload]
_test_img_equals_pyarray(img, arr, mask) _test_img_equals_pyarray(img, arr, mask)
assert arr.type == dtype assert arr.type == dtype
@ -79,8 +122,8 @@ def test_lifetime() -> None:
img = hopper("L") img = hopper("L")
arr_1 = pyarrow.array(img) arr_1 = pyarrow.array(img) # type: ignore[call-overload]
arr_2 = pyarrow.array(img) arr_2 = pyarrow.array(img) # type: ignore[call-overload]
del img del img
@ -97,8 +140,8 @@ def test_lifetime2() -> None:
img = hopper("L") img = hopper("L")
arr_1 = pyarrow.array(img) arr_1 = pyarrow.array(img) # type: ignore[call-overload]
arr_2 = pyarrow.array(img) arr_2 = pyarrow.array(img) # type: ignore[call-overload]
assert arr_1.sum().as_py() > 0 assert arr_1.sum().as_py() > 0
del arr_1 del arr_1
@ -110,3 +153,94 @@ def test_lifetime2() -> None:
px = img2.load() px = img2.load()
assert px # make mypy happy assert px # make mypy happy
assert isinstance(px[0, 0], int) assert isinstance(px[0, 0], int)
class DataShape(NamedTuple):
dtype: pyarrow.DataType
# Strictly speaking, elt should be a pixel or pixel component, so
# list[uint8][4], float, int, uint32, uint8, etc. But more
# correctly, it should be exactly the dtype from the line above.
elt: Any
elts_per_pixel: int
UINT_ARR = DataShape(
dtype=fl_uint8_4_type,
elt=[1, 2, 3, 4], # array of 4 uint8 per pixel
elts_per_pixel=1, # only one array per pixel
)
UINT = DataShape(
dtype=pyarrow.uint8(),
elt=3, # one uint8,
elts_per_pixel=4, # but repeated 4x per pixel
)
UINT32 = DataShape(
dtype=pyarrow.uint32(),
elt=0xABCDEF45, # one packed int, doesn't fit in a int32 > 0x80000000
elts_per_pixel=1, # one per pixel
)
INT32 = DataShape(
dtype=pyarrow.uint32(),
elt=0x12CDEF45, # one packed int
elts_per_pixel=1, # one per pixel
)
@pytest.mark.parametrize(
"mode, data_tp, mask",
(
("L", DataShape(pyarrow.uint8(), 3, 1), None),
("I", DataShape(pyarrow.int32(), 1 << 24, 1), None),
("F", DataShape(pyarrow.float32(), 3.14159, 1), None),
("LA", UINT_ARR, [0, 3]),
("LA", UINT, [0, 3]),
("RGB", UINT_ARR, [0, 1, 2]),
("RGBA", UINT_ARR, None),
("CMYK", UINT_ARR, None),
("YCbCr", UINT_ARR, [0, 1, 2]),
("HSV", UINT_ARR, [0, 1, 2]),
("RGB", UINT, [0, 1, 2]),
("RGBA", UINT, None),
("CMYK", UINT, None),
("YCbCr", UINT, [0, 1, 2]),
("HSV", UINT, [0, 1, 2]),
),
)
def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
(dtype, elt, elts_per_pixel) = data_tp
ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
arr = pyarrow.array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
_test_img_equals_pyarray(img, arr, mask, elts_per_pixel)
@pytest.mark.parametrize(
"mode, data_tp, mask",
(
("LA", UINT32, [0, 3]),
("RGB", UINT32, [0, 1, 2]),
("RGBA", UINT32, None),
("CMYK", UINT32, None),
("YCbCr", UINT32, [0, 1, 2]),
("HSV", UINT32, [0, 1, 2]),
("LA", INT32, [0, 3]),
("RGB", INT32, [0, 1, 2]),
("RGBA", INT32, None),
("CMYK", INT32, None),
("YCbCr", INT32, [0, 1, 2]),
("HSV", INT32, [0, 1, 2]),
),
)
def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
(dtype, elt, elts_per_pixel) = data_tp
ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
arr = pyarrow.array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
_test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel)

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Union from typing import Union
import pytest import pytest
@ -9,6 +9,7 @@ from PIL import Image, ImageQt
from .helper import assert_image_equal_tofile, assert_image_similar, hopper from .helper import assert_image_equal_tofile, assert_image_similar, hopper
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
import PyQt6 import PyQt6
import PySide6 import PySide6

View File

@ -52,3 +52,17 @@ def test_tiff_crashes(test_file: str) -> None:
pytest.skip("test image not found") pytest.skip("test image not found")
except OSError: except OSError:
pass pass
def test_tiff_mmap() -> None:
try:
with Image.open("Tests/images/crash_mmap.tif") as im:
im.seek(1)
im.load()
im.seek(0)
im.load()
except FileNotFoundError:
if on_ci():
raise
pytest.skip("test image not found")

View File

@ -0,0 +1,11 @@
#!/bin/bash
## Run this as the test script in the Docker valgrind image.
## Note -- can be included directly into the Docker image,
## but requires the current python.supp.
source /vpy3/bin/activate
cd /Pillow
make clean
make install
make valgrind-leak

View File

@ -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

View File

@ -1,10 +0,0 @@
#!/usr/bin/env python3
from __future__ import annotations
from livereload.compiler import shell
from livereload.task import Task
Task.add("*.rst", shell("make html"))
Task.add("*/*.rst", shell("make html"))
Task.add("Makefile", shell("make html"))
Task.add("conf.py", shell("make html"))

View File

@ -3,43 +3,34 @@
# 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:
@echo "Please use \`make <target>' where <target> is one of" @echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files" @echo " html to make standalone HTML files"
@echo " htmlview to open the index page built by the html target in your browser" @echo " htmlview to open the index page built by the html target in your browser"
@echo " htmllive to rebuild and reload HTML files in your browser"
@echo " serve to start a local server for viewing docs" @echo " serve to start a local server for viewing docs"
@echo " livehtml to start a local server for viewing docs and auto-reload on change"
@echo " dirhtml to make HTML files named index.html in directories" @echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file" @echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity" @echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
.PHONY: clean .PHONY: clean
clean: clean:
@ -51,159 +42,28 @@ 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: pickle
pickle:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PillowPILfork.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PillowPILfork.qhc"
.PHONY: devhelp
devhelp:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/PillowPILfork"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PillowPILfork"
@echo "# devhelp"
.PHONY: epub
epub:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: latex
latex:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
info:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.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: doctest
doctest:
$(MAKE) install-sphinx
$(SPHINXBUILD) --builder doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: htmlview .PHONY: htmlview
htmlview: html htmlview: html
$(PYTHON) -c "import os, webbrowser; webbrowser.open('file://' + os.path.realpath('$(BUILDDIR)/html/index.html'))" $(PYTHON) -c "import os, webbrowser; webbrowser.open('file://' + os.path.realpath('$(BUILDDIR)/html/index.html'))"
.PHONY: livehtml .PHONY: htmllive
livehtml: html htmllive: SPHINXBUILD = $(PYTHON) -m sphinx_autobuild
livereload $(BUILDDIR)/html -p 33233 htmllive: SPHINXOPTS = --open-browser --delay 0
htmllive: html
.PHONY: serve .PHONY: serve
serve: serve:

View File

@ -1,10 +1,10 @@
PIL Package (autodoc of remaining modules) PIL package (autodoc of remaining modules)
========================================== ==========================================
Reference for modules whose documentation has not yet been ported or written Reference for modules whose documentation has not yet been ported or written
can be found here. can be found here.
:mod:`PIL` Module :mod:`PIL` module
----------------- -----------------
.. py:module:: PIL .. py:module:: PIL
@ -12,7 +12,7 @@ can be found here.
.. autoexception:: UnidentifiedImageError .. autoexception:: UnidentifiedImageError
:show-inheritance: :show-inheritance:
:mod:`~PIL.BdfFontFile` Module :mod:`~PIL.BdfFontFile` module
------------------------------ ------------------------------
.. automodule:: PIL.BdfFontFile .. automodule:: PIL.BdfFontFile
@ -20,7 +20,7 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`~PIL.ContainerIO` Module :mod:`~PIL.ContainerIO` module
------------------------------ ------------------------------
.. automodule:: PIL.ContainerIO .. automodule:: PIL.ContainerIO
@ -28,7 +28,7 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`~PIL.FontFile` Module :mod:`~PIL.FontFile` module
--------------------------- ---------------------------
.. automodule:: PIL.FontFile .. automodule:: PIL.FontFile
@ -36,7 +36,7 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`~PIL.GdImageFile` Module :mod:`~PIL.GdImageFile` module
------------------------------ ------------------------------
.. automodule:: PIL.GdImageFile .. automodule:: PIL.GdImageFile
@ -44,7 +44,7 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`~PIL.GimpGradientFile` Module :mod:`~PIL.GimpGradientFile` module
----------------------------------- -----------------------------------
.. automodule:: PIL.GimpGradientFile .. automodule:: PIL.GimpGradientFile
@ -52,7 +52,7 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`~PIL.GimpPaletteFile` Module :mod:`~PIL.GimpPaletteFile` module
---------------------------------- ----------------------------------
.. automodule:: PIL.GimpPaletteFile .. automodule:: PIL.GimpPaletteFile
@ -60,7 +60,7 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`~PIL.ImageDraw2` Module :mod:`~PIL.ImageDraw2` module
----------------------------- -----------------------------
.. automodule:: PIL.ImageDraw2 .. automodule:: PIL.ImageDraw2
@ -69,7 +69,7 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`~PIL.ImageMode` Module :mod:`~PIL.ImageMode` module
---------------------------- ----------------------------
.. automodule:: PIL.ImageMode .. automodule:: PIL.ImageMode
@ -77,7 +77,7 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`~PIL.PaletteFile` Module :mod:`~PIL.PaletteFile` module
------------------------------ ------------------------------
.. automodule:: PIL.PaletteFile .. automodule:: PIL.PaletteFile
@ -85,7 +85,7 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`~PIL.PcfFontFile` Module :mod:`~PIL.PcfFontFile` module
------------------------------ ------------------------------
.. automodule:: PIL.PcfFontFile .. automodule:: PIL.PcfFontFile
@ -93,7 +93,7 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:class:`.PngImagePlugin.iTXt` Class :class:`.PngImagePlugin.iTXt` class
----------------------------------- -----------------------------------
.. autoclass:: PIL.PngImagePlugin.iTXt .. autoclass:: PIL.PngImagePlugin.iTXt
@ -107,7 +107,7 @@ can be found here.
:param lang: language code :param lang: language code
:param tkey: UTF-8 version of the key name :param tkey: UTF-8 version of the key name
:class:`.PngImagePlugin.PngInfo` Class :class:`.PngImagePlugin.PngInfo` class
-------------------------------------- --------------------------------------
.. autoclass:: PIL.PngImagePlugin.PngInfo .. autoclass:: PIL.PngImagePlugin.PngInfo
@ -116,7 +116,7 @@ can be found here.
:show-inheritance: :show-inheritance:
:mod:`~PIL.TarIO` Module :mod:`~PIL.TarIO` module
------------------------ ------------------------
.. automodule:: PIL.TarIO .. automodule:: PIL.TarIO
@ -124,7 +124,7 @@ can be found here.
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
:mod:`~PIL.WalImageFile` Module :mod:`~PIL.WalImageFile` module
------------------------------- -------------------------------
.. automodule:: PIL.WalImageFile .. automodule:: PIL.WalImageFile

View File

@ -229,97 +229,6 @@ html_js_files = [
# implements a search results scorer. If empty, the default will be used. # implements a search results scorer. If empty, the default will be used.
# html_search_scorer = 'scorer.js' # html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = "PillowPILForkdoc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements: dict[str, str] = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
# Latex figure (float) alignment
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(
master_doc,
"PillowPILFork.tex",
"Pillow (PIL Fork) Documentation",
"Jeffrey A. Clark",
"manual",
)
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, "pillowpilfork", "Pillow (PIL Fork) Documentation", [author], 1)
]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
master_doc,
"PillowPILFork",
"Pillow (PIL Fork) Documentation",
author,
"PillowPILFork",
"Pillow is the friendly PIL fork by Jeffrey A. Clark and contributors.",
"Miscellaneous",
)
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
# texinfo_no_detailmenu = False
linkcheck_allowed_redirects = { linkcheck_allowed_redirects = {
r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*", r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*",

View File

@ -8,8 +8,8 @@ from __future__ import annotations
import re import re
import subprocess import subprocess
from typing import TYPE_CHECKING
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
from sphinx.application import Sphinx from sphinx.application import Sphinx

View File

@ -155,7 +155,7 @@ JpegImageFile.huffman_ac and JpegImageFile.huffman_dc
The ``huffman_ac`` and ``huffman_dc`` dictionaries on JPEG images were unused. They 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). have been deprecated, and will be removed in Pillow 12 (2025-10-15).
Specific WebP Feature Checks Specific WebP feature checks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 11.0.0 .. deprecated:: 11.0.0
@ -193,6 +193,28 @@ Image.Image.get_child_images()
method uses an image's file pointer, and so child images could only be retrieved from method uses an image's file pointer, and so child images could only be retrieved from
an :py:class:`PIL.ImageFile.ImageFile` instance. an :py:class:`PIL.ImageFile.ImageFile` instance.
Image.fromarray mode parameter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 11.3.0
The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
mode can be automatically determined from the object's shape and type instead.
Saving I mode images as PNG
^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 11.3.0
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
changing the data, this is now deprecated. Instead, the image can be converted to
another mode before saving::
from PIL import Image
im = Image.new("I", (1, 1))
im.convert("I;16").save("out.png")
Removed features Removed features
---------------- ----------------

View File

@ -8,4 +8,5 @@ Appendices
image-file-formats image-file-formats
text-anchors text-anchors
third-party-plugins
writing-your-own-image-plugin writing-your-own-image-plugin

View File

@ -30,35 +30,35 @@ image. Each pixel uses the full range of the bit depth. So a 1-bit pixel has a r
INT32 and a 32-bit floating point pixel has the range of FLOAT32. The current release INT32 and a 32-bit floating point pixel has the range of FLOAT32. The current release
supports the following standard modes: supports the following standard modes:
* ``1`` (1-bit pixels, black and white, stored with one pixel per byte) * ``1`` (1-bit pixels, black and white, stored with one pixel per byte)
* ``L`` (8-bit pixels, grayscale) * ``L`` (8-bit pixels, grayscale)
* ``P`` (8-bit pixels, mapped to any other mode using a color palette) * ``P`` (8-bit pixels, mapped to any other mode using a color palette)
* ``RGB`` (3x8-bit pixels, true color) * ``RGB`` (3x8-bit pixels, true color)
* ``RGBA`` (4x8-bit pixels, true color with transparency mask) * ``RGBA`` (4x8-bit pixels, true color with transparency mask)
* ``CMYK`` (4x8-bit pixels, color separation) * ``CMYK`` (4x8-bit pixels, color separation)
* ``YCbCr`` (3x8-bit pixels, color video format) * ``YCbCr`` (3x8-bit pixels, color video format)
* Note that this refers to the JPEG, and not the ITU-R BT.2020, standard * Note that this refers to the JPEG, and not the ITU-R BT.2020, standard
* ``LAB`` (3x8-bit pixels, the L*a*b color space) * ``LAB`` (3x8-bit pixels, the L*a*b color space)
* ``HSV`` (3x8-bit pixels, Hue, Saturation, Value color space) * ``HSV`` (3x8-bit pixels, Hue, Saturation, Value color space)
* Hue's range of 0-255 is a scaled version of 0 degrees <= Hue < 360 degrees * Hue's range of 0-255 is a scaled version of 0 degrees <= Hue < 360 degrees
* ``I`` (32-bit signed integer pixels) * ``I`` (32-bit signed integer pixels)
* ``F`` (32-bit floating point pixels) * ``F`` (32-bit floating point pixels)
Pillow also provides limited support for a few additional modes, including: Pillow also provides limited support for a few additional modes, including:
* ``LA`` (L with alpha) * ``LA`` (L with alpha)
* ``PA`` (P with alpha) * ``PA`` (P with alpha)
* ``RGBX`` (true color with padding) * ``RGBX`` (true color with padding)
* ``RGBa`` (true color with premultiplied alpha) * ``RGBa`` (true color with premultiplied alpha)
* ``La`` (L with premultiplied alpha) * ``La`` (L with premultiplied alpha)
* ``I;16`` (16-bit unsigned integer pixels) * ``I;16`` (16-bit unsigned integer pixels)
* ``I;16L`` (16-bit little endian unsigned integer pixels) * ``I;16L`` (16-bit little endian unsigned integer pixels)
* ``I;16B`` (16-bit big endian unsigned integer pixels) * ``I;16B`` (16-bit big endian unsigned integer pixels)
* ``I;16N`` (16-bit native endian unsigned integer pixels) * ``I;16N`` (16-bit native endian unsigned integer pixels)
Premultiplied alpha is where the values for each other channel have been Premultiplied alpha is where the values for each other channel have been
multiplied by the alpha. For example, an RGBA pixel of ``(10, 20, 30, 127)`` multiplied by the alpha. For example, an RGBA pixel of ``(10, 20, 30, 127)``
@ -84,7 +84,7 @@ pixels.
.. _coordinate-system: .. _coordinate-system:
Coordinate System Coordinate system
----------------- -----------------
The Python Imaging Library uses a Cartesian pixel coordinate system, with (0,0) The Python Imaging Library uses a Cartesian pixel coordinate system, with (0,0)

View File

@ -1082,6 +1082,26 @@ Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I
Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well. Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well.
QOI
^^^
.. versionadded:: 9.5.0
Pillow reads and writes images in Quite OK Image format using a Python codec. If you
wish to write code specifically for this format, :pypi:`qoi` is an alternative library
that uses C to decode the image and interfaces with NumPy.
.. _qoi-saving:
Saving
~~~~~~
The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
**colorspace**
If set to "sRGB", the colorspace will be written as sRGB with linear alpha, instead
of all channels being linear.
SGI SGI
^^^ ^^^
@ -1222,7 +1242,7 @@ numbers are returned as a tuple of ``(numerator, denominator)``.
.. deprecated:: 3.0.0 .. deprecated:: 3.0.0
Reading Multi-frame TIFF Images Reading multi-frame TIFF images
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The TIFF loader supports the :py:meth:`~PIL.Image.Image.seek` and The TIFF loader supports the :py:meth:`~PIL.Image.Image.seek` and
@ -1578,15 +1598,6 @@ PSD
Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0. Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.
QOI
^^^
.. versionadded:: 9.5.0
Pillow reads images in Quite OK Image format using a Python decoder. If you wish to
write code specifically for this format, :pypi:`qoi` is an alternative library that
uses C to decode the image and interfaces with NumPy.
SUN SUN
^^^ ^^^
@ -1664,7 +1675,7 @@ The :py:meth:`~PIL.Image.open` method sets the following
Transparency color index. This key is omitted if the image is not Transparency color index. This key is omitted if the image is not
transparent. transparent.
XV Thumbnails XV thumbnails
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
Pillow can read XV thumbnail files. Pillow can read XV thumbnail files.

View File

@ -13,7 +13,7 @@ processing tool.
Lets look at a few possible uses of this library. Lets look at a few possible uses of this library.
Image Archives Image archives
-------------- --------------
The Python Imaging Library is ideal for image archival and batch processing The Python Imaging Library is ideal for image archival and batch processing
@ -24,7 +24,7 @@ The current version identifies and reads a large number of formats. Write
support is intentionally restricted to the most commonly used interchange and support is intentionally restricted to the most commonly used interchange and
presentation formats. presentation formats.
Image Display Image display
------------- -------------
The current release includes Tk :py:class:`~PIL.ImageTk.PhotoImage` and The current release includes Tk :py:class:`~PIL.ImageTk.PhotoImage` and
@ -36,7 +36,7 @@ support.
For debugging, theres also a :py:meth:`~PIL.Image.Image.show` method which saves an image to For debugging, theres also a :py:meth:`~PIL.Image.Image.show` method which saves an image to
disk, and calls an external display utility. disk, and calls an external display utility.
Image Processing Image processing
---------------- ----------------
The library contains basic image processing functionality, including point operations, filtering with a set of built-in convolution kernels, and colour space conversions. The library contains basic image processing functionality, including point operations, filtering with a set of built-in convolution kernels, and colour space conversions.

View File

@ -0,0 +1,18 @@
Third-party plugins
===================
Pillow uses a plugin model which allows users to add their own
decoders and encoders to the library, without any changes to the library
itself.
Here is a list of PyPI projects that offer additional plugins:
* :pypi:`DjvuRleImagePlugin`: Plugin for the DjVu RLE image format as defined in the DjVuLibre docs.
* :pypi:`heif-image-plugin`: Simple HEIF/HEIC images plugin, based on the pyheif library.
* :pypi:`jxlpy`: Introduces reading and writing support for JPEG XL.
* :pypi:`pillow-heif`: Python bindings to libheif for working with HEIF images.
* :pypi:`pillow-jpls`: Plugin for the JPEG-LS codec, based on the Charls JPEG-LS implemetation. Python bindings implemented using pybind11.
* :pypi:`pillow-jxl-plugin`: Plugin for JPEG-XL, using Rust for bindings.
* :pypi:`pillow-mbm`: Adds support for KSP's proprietary MBM texture format.
* :pypi:`pillow-svg`: Implements basic SVG read support. Supports basic paths, shapes, and text.
* :pypi:`raw-pillow-opener`: Simple camera raw opener, based on the rawpy library.

View File

@ -122,7 +122,7 @@ This means that opening an image file is a fast operation, which is independent
of the file size and compression type. Heres a simple script to quickly of the file size and compression type. Heres a simple script to quickly
identify a set of image files: identify a set of image files:
Identify Image Files Identify image files
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
:: ::
@ -399,7 +399,7 @@ Applying filters
.. image:: enhanced_hopper.webp .. image:: enhanced_hopper.webp
:align: center :align: center
Point Operations Point operations
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
The :py:meth:`~PIL.Image.Image.point` method can be used to translate the pixel The :py:meth:`~PIL.Image.Image.point` method can be used to translate the pixel

View File

@ -1,6 +1,6 @@
.. _image-plugins: .. _image-plugins:
Writing Your Own Image Plugin Writing your own image plugin
============================= =============================
Pillow uses a plugin model which allows you to add your own Pillow uses a plugin model which allows you to add your own
@ -329,7 +329,7 @@ The fields are used as follows:
.. _file-codecs: .. _file-codecs:
Writing Your Own File Codec in C Writing your own file codec in C
================================ ================================
There are 3 stages in a file codec's lifetime: There are 3 stages in a file codec's lifetime:
@ -414,7 +414,7 @@ memory and release any resources from external libraries.
.. _file-codecs-py: .. _file-codecs-py:
Writing Your Own File Codec in Python Writing your own file codec in Python
===================================== =====================================
Python file decoders and encoders should derive from Python file decoders and encoders should derive from

View File

@ -3,27 +3,27 @@
Installation Installation
============ ============
Basic Installation Basic installation
------------------ ------------------
.. Note:: This section has moved to :ref:`basic-installation`. Please update references accordingly. .. Note:: This section has moved to :ref:`basic-installation`. Please update references accordingly.
Python Support Python support
-------------- --------------
.. Note:: This section has moved to :ref:`python-support`. Please update references accordingly. .. Note:: This section has moved to :ref:`python-support`. Please update references accordingly.
Platform Support Platform support
---------------- ----------------
.. Note:: This section has moved to :ref:`platform-support`. Please update references accordingly. .. Note:: This section has moved to :ref:`platform-support`. Please update references accordingly.
Building From Source Building from source
-------------------- --------------------
.. Note:: This section has moved to :ref:`building-from-source`. Please update references accordingly. .. Note:: This section has moved to :ref:`building-from-source`. Please update references accordingly.
Old Versions Old versions
------------ ------------
.. Note:: This section has moved to :ref:`old-versions`. Please update references accordingly. .. Note:: This section has moved to :ref:`old-versions`. Please update references accordingly.

View File

@ -8,7 +8,7 @@
.. _basic-installation: .. _basic-installation:
Basic Installation Basic installation
================== ==================
.. note:: .. note::

View File

@ -8,12 +8,12 @@
.. _building-from-source: .. _building-from-source:
Building From Source Building from source
==================== ====================
.. _external-libraries: .. _external-libraries:
External Libraries External libraries
------------------ ------------------
.. note:: .. note::
@ -194,9 +194,9 @@ Many of Pillow's features require external libraries:
pacman -S \ pacman -S \
mingw-w64-x86_64-gcc \ mingw-w64-x86_64-gcc \
mingw-w64-x86_64-python3 \ mingw-w64-x86_64-python \
mingw-w64-x86_64-python3-pip \ mingw-w64-x86_64-python-pip \
mingw-w64-x86_64-python3-setuptools mingw-w64-x86_64-python-setuptools
Prerequisites are installed on **MSYS2 MinGW 64-bit** with:: Prerequisites are installed on **MSYS2 MinGW 64-bit** with::
@ -271,7 +271,7 @@ After navigating to the Pillow directory, run::
.. _compressed archive from PyPI: https://pypi.org/project/pillow/#files .. _compressed archive from PyPI: https://pypi.org/project/pillow/#files
Build Options Build options
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
* Config setting: ``-C parallel=n``. Can also be given * Config setting: ``-C parallel=n``. Can also be given
@ -319,7 +319,7 @@ Sample usage::
.. _old-versions: .. _old-versions:
Old Versions Old versions
============ ============
You can download old distributions from the `release history at PyPI You can download old distributions from the `release history at PyPI

View File

@ -1,6 +1,6 @@
.. _platform-support: .. _platform-support:
Platform Support Platform support
================ ================
Current platform support for Pillow. Binary distributions are Current platform support for Pillow. Binary distributions are
@ -9,7 +9,7 @@ should compile and run everywhere platform support is listed. In
general, we aim to support all current versions of Linux, macOS, and general, we aim to support all current versions of Linux, macOS, and
Windows. Windows.
Continuous Integration Targets Continuous integration targets
------------------------------ ------------------------------
These platforms are built and tested for every change. These platforms are built and tested for every change.
@ -33,23 +33,27 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Fedora 41 | 3.13 | x86-64 | | Fedora 41 | 3.13 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Fedora 42 | 3.13 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Gentoo | 3.12 | x86-64 | | Gentoo | 3.12 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| macOS 13 Ventura | 3.9 | x86-64 | | macOS 13 Ventura | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 | | macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
| | PyPy3 | | | | 3.14, PyPy3 | |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 22.04 LTS (Jammy) | 3.9, 3.10, 3.11, | x86-64 | | Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
| | 3.12, 3.13, PyPy3 | |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 24.04 LTS (Noble) | 3.12 | x86-64, arm64v8, | | Ubuntu Linux 24.04 LTS (Noble) | 3.9, 3.10, 3.11, | x86-64 |
| | | ppc64le, s390x | | | 3.12, 3.13, 3.14, PyPy3 | |
| +----------------------------+---------------------+
| | 3.12 | arm64v8, ppc64le, |
| | | s390x |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Windows Server 2019 | 3.9 | x86 | | Windows Server 2022 | 3.9 | x86 |
+----------------------------------+----------------------------+---------------------+ | +----------------------------+---------------------+
| Windows Server 2022 | 3.10, 3.11, 3.12, 3.13, | x86-64 | | | 3.10, 3.11, 3.12, 3.13, | x86-64 |
| | PyPy3 | | | | 3.14, PyPy3 | |
| +----------------------------+---------------------+ | +----------------------------+---------------------+
| | 3.12 (MinGW) | x86-64 | | | 3.12 (MinGW) | x86-64 |
| +----------------------------+---------------------+ | +----------------------------+---------------------+
@ -57,7 +61,7 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
Other Platforms Other platforms
--------------- ---------------
These platforms have been reported to work at the versions mentioned. These platforms have been reported to work at the versions mentioned.

View File

@ -1,6 +1,6 @@
.. _python-support: .. _python-support:
Python Support Python support
============== ==============
Pillow supports these Python versions. Pillow supports these Python versions.

View File

@ -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
@ -22,20 +20,7 @@ if "%1" == "help" (
echo. htmlview to open the index page built by the html target in your browser echo. htmlview to open the index page built by the html target in your browser
echo. dirhtml to make HTML files named index.html in directories echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end goto end
) )
@ -80,107 +65,6 @@ if "%1" == "singlehtml" (
goto end goto end
) )
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PillowPILfork.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PillowPILfork.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" ( if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1 if errorlevel 1 exit /b 1
@ -190,13 +74,4 @@ or in %BUILDDIR%/linkcheck/output.txt.
goto end goto end
) )
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end :end

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ExifTags .. py:module:: PIL.ExifTags
.. py:currentmodule:: PIL.ExifTags .. py:currentmodule:: PIL.ExifTags
:py:mod:`~PIL.ExifTags` Module :py:mod:`~PIL.ExifTags` module
============================== ==============================
The :py:mod:`~PIL.ExifTags` module exposes several :py:class:`enum.IntEnum` The :py:mod:`~PIL.ExifTags` module exposes several :py:class:`enum.IntEnum`

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.Image .. py:module:: PIL.Image
.. py:currentmodule:: PIL.Image .. py:currentmodule:: PIL.Image
:py:mod:`~PIL.Image` Module :py:mod:`~PIL.Image` module
=========================== ===========================
The :py:mod:`~PIL.Image` module provides a class with the same name which is The :py:mod:`~PIL.Image` module provides a class with the same name which is
@ -113,7 +113,7 @@ Registering plugins
.. autofunction:: register_decoder .. autofunction:: register_decoder
.. autofunction:: register_encoder .. autofunction:: register_encoder
The Image Class The Image class
--------------- ---------------
.. autoclass:: PIL.Image.Image .. autoclass:: PIL.Image.Image
@ -261,7 +261,7 @@ method. ::
.. automethod:: PIL.Image.Image.load .. automethod:: PIL.Image.Image.load
.. automethod:: PIL.Image.Image.close .. automethod:: PIL.Image.Image.close
Image Attributes Image attributes
---------------- ----------------
Instances of the :py:class:`Image` class have the following attributes: Instances of the :py:class:`Image` class have the following attributes:

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageChops .. py:module:: PIL.ImageChops
.. py:currentmodule:: PIL.ImageChops .. py:currentmodule:: PIL.ImageChops
:py:mod:`~PIL.ImageChops` ("Channel Operations") Module :py:mod:`~PIL.ImageChops` ("channel operations") module
======================================================= =======================================================
The :py:mod:`~PIL.ImageChops` module contains a number of arithmetical image The :py:mod:`~PIL.ImageChops` module contains a number of arithmetical image

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageCms .. py:module:: PIL.ImageCms
.. py:currentmodule:: PIL.ImageCms .. py:currentmodule:: PIL.ImageCms
:py:mod:`~PIL.ImageCms` Module :py:mod:`~PIL.ImageCms` module
============================== ==============================
The :py:mod:`~PIL.ImageCms` module provides color profile management The :py:mod:`~PIL.ImageCms` module provides color profile management

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageColor .. py:module:: PIL.ImageColor
.. py:currentmodule:: PIL.ImageColor .. py:currentmodule:: PIL.ImageColor
:py:mod:`~PIL.ImageColor` Module :py:mod:`~PIL.ImageColor` module
================================ ================================
The :py:mod:`~PIL.ImageColor` module contains color tables and converters from The :py:mod:`~PIL.ImageColor` module contains color tables and converters from
@ -11,7 +11,7 @@ others.
.. _color-names: .. _color-names:
Color Names Color names
----------- -----------
The ImageColor module supports the following string formats: The ImageColor module supports the following string formats:

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageDraw .. py:module:: PIL.ImageDraw
.. py:currentmodule:: PIL.ImageDraw .. py:currentmodule:: PIL.ImageDraw
:py:mod:`~PIL.ImageDraw` Module :py:mod:`~PIL.ImageDraw` module
=============================== ===============================
The :py:mod:`~PIL.ImageDraw` module provides simple 2D graphics for The :py:mod:`~PIL.ImageDraw` module provides simple 2D graphics for
@ -54,7 +54,7 @@ later, you can also use RGB 3-tuples or color names (see below). The drawing
layer will automatically assign color indexes, as long as you dont draw with layer will automatically assign color indexes, as long as you dont draw with
more than 256 colors. more than 256 colors.
Color Names Color names
^^^^^^^^^^^ ^^^^^^^^^^^
See :ref:`color-names` for the color names supported by Pillow. See :ref:`color-names` for the color names supported by Pillow.
@ -75,7 +75,7 @@ To load a OpenType/TrueType font, use the truetype function in the
:py:mod:`~PIL.ImageFont` module. Note that this function depends on third-party :py:mod:`~PIL.ImageFont` module. Note that this function depends on third-party
libraries, and may not available in all PIL builds. libraries, and may not available in all PIL builds.
Example: Draw Partial Opacity Text Example: Draw partial opacity text
---------------------------------- ----------------------------------
:: ::
@ -102,7 +102,7 @@ Example: Draw Partial Opacity Text
out.show() out.show()
Example: Draw Multiline Text Example: Draw multiline text
---------------------------- ----------------------------
:: ::

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageEnhance .. py:module:: PIL.ImageEnhance
.. py:currentmodule:: PIL.ImageEnhance .. py:currentmodule:: PIL.ImageEnhance
:py:mod:`~PIL.ImageEnhance` Module :py:mod:`~PIL.ImageEnhance` module
================================== ==================================
The :py:mod:`~PIL.ImageEnhance` module contains a number of classes that can be used The :py:mod:`~PIL.ImageEnhance` module contains a number of classes that can be used

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageFile .. py:module:: PIL.ImageFile
.. py:currentmodule:: PIL.ImageFile .. py:currentmodule:: PIL.ImageFile
:py:mod:`~PIL.ImageFile` Module :py:mod:`~PIL.ImageFile` module
=============================== ===============================
The :py:mod:`~PIL.ImageFile` module provides support functions for the image open The :py:mod:`~PIL.ImageFile` module provides support functions for the image open

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageFilter .. py:module:: PIL.ImageFilter
.. py:currentmodule:: PIL.ImageFilter .. py:currentmodule:: PIL.ImageFilter
:py:mod:`~PIL.ImageFilter` Module :py:mod:`~PIL.ImageFilter` module
================================= =================================
The :py:mod:`~PIL.ImageFilter` module contains definitions for a pre-defined set of The :py:mod:`~PIL.ImageFilter` module contains definitions for a pre-defined set of

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageFont .. py:module:: PIL.ImageFont
.. py:currentmodule:: PIL.ImageFont .. py:currentmodule:: PIL.ImageFont
:py:mod:`~PIL.ImageFont` Module :py:mod:`~PIL.ImageFont` module
=============================== ===============================
The :py:mod:`~PIL.ImageFont` module defines a class with the same name. Instances of The :py:mod:`~PIL.ImageFont` module defines a class with the same name. Instances of
@ -18,6 +18,9 @@ OpenType fonts (as well as other font formats supported by the FreeType
library). For earlier versions, TrueType support is only available as part of library). For earlier versions, TrueType support is only available as part of
the imToolkit package. the imToolkit package.
When measuring text sizes, this module will not break at newline characters. For
multiline text, see the :py:mod:`~PIL.ImageDraw` module.
.. warning:: .. warning::
To protect against potential DOS attacks when using arbitrary strings as To protect against potential DOS attacks when using arbitrary strings as
text input, Pillow will raise a :py:exc:`ValueError` if the number of characters text input, Pillow will raise a :py:exc:`ValueError` if the number of characters

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageGrab .. py:module:: PIL.ImageGrab
.. py:currentmodule:: PIL.ImageGrab .. py:currentmodule:: PIL.ImageGrab
:py:mod:`~PIL.ImageGrab` Module :py:mod:`~PIL.ImageGrab` module
=============================== ===============================
The :py:mod:`~PIL.ImageGrab` module can be used to copy the contents of the screen The :py:mod:`~PIL.ImageGrab` module can be used to copy the contents of the screen
@ -16,9 +16,9 @@ or the clipboard to a PIL image memory.
the entire screen is copied, and on macOS, it will be at 2x if on a Retina screen. the entire screen is copied, and on macOS, it will be at 2x if on a Retina screen.
On Linux, if ``xdisplay`` is ``None`` and the default X11 display does not return On Linux, if ``xdisplay`` is ``None`` and the default X11 display does not return
a snapshot of the screen, ``gnome-screenshot`` or ``spectacle`` will be used as a a snapshot of the screen, ``gnome-screenshot``, ``grim`` or ``spectacle`` will be
fallback if they are installed. To disable this behaviour, pass ``xdisplay=""`` used as a fallback if they are installed. To disable this behaviour, pass
instead. ``xdisplay=""`` instead.
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux) .. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux)

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageMath .. py:module:: PIL.ImageMath
.. py:currentmodule:: PIL.ImageMath .. py:currentmodule:: PIL.ImageMath
:py:mod:`~PIL.ImageMath` Module :py:mod:`~PIL.ImageMath` module
=============================== ===============================
The :py:mod:`~PIL.ImageMath` module can be used to evaluate “image expressions”, that The :py:mod:`~PIL.ImageMath` module can be used to evaluate “image expressions”, that
@ -86,7 +86,7 @@ Expression syntax
It is not recommended to process expressions without considering this. It is not recommended to process expressions without considering this.
:py:meth:`lambda_eval` is a more secure alternative. :py:meth:`lambda_eval` is a more secure alternative.
Standard Operators Standard operators
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
You can use standard arithmetical operators for addition (+), subtraction (-), You can use standard arithmetical operators for addition (+), subtraction (-),
@ -102,7 +102,7 @@ an 8-bit image, the result will be a 32-bit floating point image.
You can force conversion using the ``convert()``, ``float()``, and ``int()`` You can force conversion using the ``convert()``, ``float()``, and ``int()``
functions described below. functions described below.
Bitwise Operators Bitwise operators
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
The module also provides operations that operate on individual bits. This The module also provides operations that operate on individual bits. This
@ -116,7 +116,7 @@ mask off unwanted bits.
Bitwise operators dont work on floating point images. Bitwise operators dont work on floating point images.
Logical Operators Logical operators
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
Logical operators like ``and``, ``or``, and ``not`` work Logical operators like ``and``, ``or``, and ``not`` work
@ -128,7 +128,7 @@ treated as true.
Note that ``and`` and ``or`` return the last evaluated operand, Note that ``and`` and ``or`` return the last evaluated operand,
while not always returns a boolean value. while not always returns a boolean value.
Built-in Functions Built-in functions
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
These functions are applied to each individual pixel. These functions are applied to each individual pixel.

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageMorph .. py:module:: PIL.ImageMorph
.. py:currentmodule:: PIL.ImageMorph .. py:currentmodule:: PIL.ImageMorph
:py:mod:`~PIL.ImageMorph` Module :py:mod:`~PIL.ImageMorph` module
================================ ================================
The :py:mod:`~PIL.ImageMorph` module provides morphology operations on images. The :py:mod:`~PIL.ImageMorph` module provides morphology operations on images.

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageOps .. py:module:: PIL.ImageOps
.. py:currentmodule:: PIL.ImageOps .. py:currentmodule:: PIL.ImageOps
:py:mod:`~PIL.ImageOps` Module :py:mod:`~PIL.ImageOps` module
============================== ==============================
The :py:mod:`~PIL.ImageOps` module contains a number of ready-made image The :py:mod:`~PIL.ImageOps` module contains a number of ready-made image

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImagePalette .. py:module:: PIL.ImagePalette
.. py:currentmodule:: PIL.ImagePalette .. py:currentmodule:: PIL.ImagePalette
:py:mod:`~PIL.ImagePalette` Module :py:mod:`~PIL.ImagePalette` module
================================== ==================================
The :py:mod:`~PIL.ImagePalette` module contains a class of the same name to The :py:mod:`~PIL.ImagePalette` module contains a class of the same name to

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImagePath .. py:module:: PIL.ImagePath
.. py:currentmodule:: PIL.ImagePath .. py:currentmodule:: PIL.ImagePath
:py:mod:`~PIL.ImagePath` Module :py:mod:`~PIL.ImagePath` module
=============================== ===============================
The :py:mod:`~PIL.ImagePath` module is used to store and manipulate 2-dimensional The :py:mod:`~PIL.ImagePath` module is used to store and manipulate 2-dimensional

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageQt .. py:module:: PIL.ImageQt
.. py:currentmodule:: PIL.ImageQt .. py:currentmodule:: PIL.ImageQt
:py:mod:`~PIL.ImageQt` Module :py:mod:`~PIL.ImageQt` module
============================= =============================
The :py:mod:`~PIL.ImageQt` module contains support for creating PyQt6 or PySide6 The :py:mod:`~PIL.ImageQt` module contains support for creating PyQt6 or PySide6

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageSequence .. py:module:: PIL.ImageSequence
.. py:currentmodule:: PIL.ImageSequence .. py:currentmodule:: PIL.ImageSequence
:py:mod:`~PIL.ImageSequence` Module :py:mod:`~PIL.ImageSequence` module
=================================== ===================================
The :py:mod:`~PIL.ImageSequence` module contains a wrapper class that lets you The :py:mod:`~PIL.ImageSequence` module contains a wrapper class that lets you

View File

@ -1,10 +1,10 @@
.. py:module:: PIL.ImageShow .. py:module:: PIL.ImageShow
.. py:currentmodule:: PIL.ImageShow .. py:currentmodule:: PIL.ImageShow
:py:mod:`~PIL.ImageShow` Module :py:mod:`~PIL.ImageShow` module
=============================== ===============================
The :py:mod:`~PIL.ImageShow` Module is used to display images. The :py:mod:`~PIL.ImageShow` module is used to display images.
All default viewers convert the image to be shown to PNG format. All default viewers convert the image to be shown to PNG format.
.. autofunction:: PIL.ImageShow.show .. autofunction:: PIL.ImageShow.show

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageStat .. py:module:: PIL.ImageStat
.. py:currentmodule:: PIL.ImageStat .. py:currentmodule:: PIL.ImageStat
:py:mod:`~PIL.ImageStat` Module :py:mod:`~PIL.ImageStat` module
=============================== ===============================
The :py:mod:`~PIL.ImageStat` module calculates global statistics for an image, or The :py:mod:`~PIL.ImageStat` module calculates global statistics for an image, or

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageTk .. py:module:: PIL.ImageTk
.. py:currentmodule:: PIL.ImageTk .. py:currentmodule:: PIL.ImageTk
:py:mod:`~PIL.ImageTk` Module :py:mod:`~PIL.ImageTk` module
============================= =============================
The :py:mod:`~PIL.ImageTk` module contains support to create and modify Tkinter The :py:mod:`~PIL.ImageTk` module contains support to create and modify Tkinter

View File

@ -2,7 +2,7 @@
.. py:module:: PIL.ImageTransform .. py:module:: PIL.ImageTransform
.. py:currentmodule:: PIL.ImageTransform .. py:currentmodule:: PIL.ImageTransform
:py:mod:`~PIL.ImageTransform` Module :py:mod:`~PIL.ImageTransform` module
==================================== ====================================
The :py:mod:`~PIL.ImageTransform` module contains implementations of The :py:mod:`~PIL.ImageTransform` module contains implementations of

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.ImageWin .. py:module:: PIL.ImageWin
.. py:currentmodule:: PIL.ImageWin .. py:currentmodule:: PIL.ImageWin
:py:mod:`~PIL.ImageWin` Module (Windows-only) :py:mod:`~PIL.ImageWin` module (Windows-only)
============================================= =============================================
The :py:mod:`~PIL.ImageWin` module contains support to create and display images on The :py:mod:`~PIL.ImageWin` module contains support to create and display images on

View File

@ -1,6 +1,6 @@
.. py:currentmodule:: PIL.JpegPresets .. py:currentmodule:: PIL.JpegPresets
:py:mod:`~PIL.JpegPresets` Module :py:mod:`~PIL.JpegPresets` module
================================= =================================
.. automodule:: PIL.JpegPresets .. automodule:: PIL.JpegPresets

View File

@ -1,7 +1,7 @@
.. py:module:: PIL.PSDraw .. py:module:: PIL.PSDraw
.. py:currentmodule:: PIL.PSDraw .. py:currentmodule:: PIL.PSDraw
:py:mod:`~PIL.PSDraw` Module :py:mod:`~PIL.PSDraw` module
============================ ============================
The :py:mod:`~PIL.PSDraw` module provides simple print support for PostScript The :py:mod:`~PIL.PSDraw` module provides simple print support for PostScript

Some files were not shown because too many files have changed in this diff Show More