mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-30 09:59:50 +03:00
Merge branch 'main' into sgi
This commit is contained in:
commit
5609d1144b
|
@ -66,7 +66,7 @@ if [[ $(uname) != CYGWIN* ]]; then
|
|||
pushd depends && ./install_raqm.sh && popd
|
||||
|
||||
# libavif
|
||||
pushd depends && CMAKE_POLICY_VERSION_MINIMUM=3.5 ./install_libavif.sh && popd
|
||||
pushd depends && ./install_libavif.sh && popd
|
||||
|
||||
# extra test images
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
|
|
@ -1 +1 @@
|
|||
cibuildwheel==2.23.2
|
||||
cibuildwheel==3.0.0
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
mypy==1.15.0
|
||||
mypy==1.16.1
|
||||
IceSpringPySideStubs-PyQt6
|
||||
IceSpringPySideStubs-PySide6
|
||||
ipython
|
||||
numpy
|
||||
packaging
|
||||
pyarrow-stubs
|
||||
pytest
|
||||
sphinx
|
||||
types-atheris
|
||||
|
|
46
.github/ISSUE_TEMPLATE/RELEASE.md
vendored
Normal file
46
.github/ISSUE_TEMPLATE/RELEASE.md
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
name: "Maintainers only: Release"
|
||||
about: For maintainers to schedule a quarterly release
|
||||
labels: Release
|
||||
---
|
||||
|
||||
## Main release
|
||||
|
||||
Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||
|
||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||
* [ ] Develop and prepare release in `main` branch.
|
||||
* [ ] Add release notes e.g. https://github.com/python-pillow/Pillow/pull/8885
|
||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in `main` branch.
|
||||
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
|
||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||
* [ ] Create branch and tag for release e.g.:
|
||||
```bash
|
||||
git branch [[MAJOR.MINOR]].x
|
||||
git tag [[MAJOR.MINOR]].0
|
||||
git push --tags
|
||||
```
|
||||
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) has passed, including the "Upload release to PyPI" job. This will have been triggered by the new tag.
|
||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases).
|
||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
||||
```bash
|
||||
git push --all
|
||||
```
|
||||
|
||||
## Publicize release
|
||||
|
||||
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
|
||||
|
||||
## Documentation
|
||||
|
||||
* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
|
||||
|
||||
## Docker images
|
||||
|
||||
* [ ] Update Pillow in the Docker Images repository
|
||||
```bash
|
||||
git clone https://github.com/python-pillow/docker-images
|
||||
cd docker-images
|
||||
./update-pillow-tag.sh [[release tag]]
|
||||
```
|
1
.github/workflows/test-docker.yml
vendored
1
.github/workflows/test-docker.yml
vendored
|
@ -48,6 +48,7 @@ jobs:
|
|||
debian-12-bookworm-x86,
|
||||
debian-12-bookworm-amd64,
|
||||
fedora-41-amd64,
|
||||
fedora-42-amd64,
|
||||
gentoo,
|
||||
ubuntu-22.04-jammy-amd64,
|
||||
ubuntu-24.04-noble-amd64,
|
||||
|
|
60
.github/workflows/test-valgrind-memory.yml
vendored
Normal file
60
.github/workflows/test-valgrind-memory.yml
vendored
Normal 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
|
13
.github/workflows/test-windows.yml
vendored
13
.github/workflows/test-windows.yml
vendored
|
@ -31,16 +31,15 @@ env:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
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"]
|
||||
os: ["windows-latest"]
|
||||
include:
|
||||
# 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
|
||||
|
||||
|
@ -84,7 +83,7 @@ jobs:
|
|||
python3 -m pip install --upgrade pip
|
||||
|
||||
- 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: |
|
||||
python3 -m pip install PyQt6
|
||||
|
||||
|
@ -98,8 +97,8 @@ jobs:
|
|||
choco install nasm --no-progress
|
||||
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||
|
||||
choco install ghostscript --version=10.5.0 --no-progress
|
||||
echo "C:\Program Files\gs\gs10.05.0\bin" >> $env:GITHUB_PATH
|
||||
choco install ghostscript --version=10.5.1 --no-progress
|
||||
echo "C:\Program Files\gs\gs10.05.1\bin" >> $env:GITHUB_PATH
|
||||
|
||||
# Install extra test images
|
||||
xcopy /S /Y Tests\test-images\* Tests\images
|
||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -43,6 +43,7 @@ jobs:
|
|||
python-version: [
|
||||
"pypy3.11",
|
||||
"pypy3.10",
|
||||
"3.14t",
|
||||
"3.14",
|
||||
"3.13t",
|
||||
"3.13",
|
||||
|
@ -55,6 +56,7 @@ jobs:
|
|||
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
||||
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
|
||||
# Free-threaded
|
||||
- { python-version: "3.14t", disable-gil: true }
|
||||
- { python-version: "3.13t", disable-gil: true }
|
||||
# M1 only available for 3.10+
|
||||
- { os: "macos-13", python-version: "3.9" }
|
||||
|
|
63
.github/workflows/wheels-dependencies.sh
vendored
63
.github/workflows/wheels-dependencies.sh
vendored
|
@ -38,9 +38,9 @@ ARCHIVE_SDIR=pillow-depends-main
|
|||
|
||||
# Package versions for fresh source builds
|
||||
FREETYPE_VERSION=2.13.3
|
||||
HARFBUZZ_VERSION=11.0.1
|
||||
LIBPNG_VERSION=1.6.47
|
||||
JPEGTURBO_VERSION=3.1.0
|
||||
HARFBUZZ_VERSION=11.2.1
|
||||
LIBPNG_VERSION=1.6.49
|
||||
JPEGTURBO_VERSION=3.1.1
|
||||
OPENJPEG_VERSION=2.5.3
|
||||
XZ_VERSION=5.8.1
|
||||
TIFF_VERSION=4.7.0
|
||||
|
@ -51,6 +51,7 @@ LIBWEBP_VERSION=1.5.0
|
|||
BZIP2_VERSION=1.0.8
|
||||
LIBXCB_VERSION=1.17.0
|
||||
BROTLI_VERSION=1.1.0
|
||||
LIBAVIF_VERSION=1.3.0
|
||||
|
||||
function build_pkg_config {
|
||||
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)
|
||||
(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 \
|
||||
&& meson install)
|
||||
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 {
|
||||
build_xz
|
||||
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
|
||||
|
@ -132,6 +186,7 @@ function build {
|
|||
build_tiff
|
||||
fi
|
||||
|
||||
build_libavif
|
||||
build_libpng
|
||||
build_lcms2
|
||||
build_openjpeg
|
||||
|
|
16
.github/workflows/wheels-test.ps1
vendored
16
.github/workflows/wheels-test.ps1
vendored
|
@ -9,17 +9,21 @@ if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
|
|||
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
|
||||
}
|
||||
$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
|
||||
if ("$venv" -like "*\cibw-run-*-win_amd64\*") {
|
||||
& python -m pip install numpy
|
||||
& $venv\Scripts\$python -m pip install numpy
|
||||
}
|
||||
cd $pillow
|
||||
& python -VV
|
||||
& $venv\Scripts\$python -VV
|
||||
if (!$?) { exit $LASTEXITCODE }
|
||||
& python selftest.py
|
||||
& $venv\Scripts\$python selftest.py
|
||||
if (!$?) { exit $LASTEXITCODE }
|
||||
& python -m pytest -vx Tests\check_wheel.py
|
||||
& $venv\Scripts\$python -m pytest -vx Tests\check_wheel.py
|
||||
if (!$?) { exit $LASTEXITCODE }
|
||||
& python -m pytest -vx Tests
|
||||
& $venv\Scripts\$python -m pytest -vx Tests
|
||||
if (!$?) { exit $LASTEXITCODE }
|
||||
|
|
11
.github/workflows/wheels.yml
vendored
11
.github/workflows/wheels.yml
vendored
|
@ -58,7 +58,7 @@ jobs:
|
|||
- name: "macOS 10.13 x86_64"
|
||||
os: macos-13
|
||||
cibw_arch: x86_64
|
||||
build: "cp3{12,13}*"
|
||||
build: "cp3{12,13,14}*"
|
||||
macosx_deployment_target: "10.13"
|
||||
- name: "macOS 10.15 x86_64"
|
||||
os: macos-13
|
||||
|
@ -110,7 +110,6 @@ jobs:
|
|||
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_SKIP: pp39-*
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
@ -121,14 +120,17 @@ jobs:
|
|||
windows:
|
||||
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
|
||||
name: Windows ${{ matrix.cibw_arch }}
|
||||
runs-on: windows-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- cibw_arch: x86
|
||||
os: windows-latest
|
||||
- cibw_arch: AMD64
|
||||
os: windows-latest
|
||||
- cibw_arch: ARM64
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
@ -157,7 +159,7 @@ jobs:
|
|||
# Install extra test 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
|
||||
|
||||
- name: Build wheels
|
||||
|
@ -185,7 +187,6 @@ jobs:
|
|||
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
||||
CIBW_CACHE_PATH: "C:\\cibw"
|
||||
CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
|
||||
CIBW_SKIP: pp39-*
|
||||
CIBW_TEST_SKIP: "*-win_arm64"
|
||||
CIBW_TEST_COMMAND: 'docker run --rm
|
||||
-v {project}:C:\pillow
|
||||
|
|
7
.github/zizmor.yml
vendored
Normal file
7
.github/zizmor.yml
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
|
||||
# https://woodruffw.github.io/zizmor/configuration/
|
||||
rules:
|
||||
unpinned-uses:
|
||||
config:
|
||||
policies:
|
||||
"*": ref-pin
|
|
@ -1,8 +1,8 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.11.4
|
||||
rev: v0.11.12
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-check
|
||||
args: [--exit-non-zero-on-fix]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
|
@ -24,7 +24,7 @@ repos:
|
|||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v20.1.0
|
||||
rev: v20.1.5
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types: [c]
|
||||
|
@ -51,14 +51,14 @@ repos:
|
|||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.32.1
|
||||
rev: 0.33.0
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
- id: check-readthedocs
|
||||
- id: check-renovate
|
||||
|
||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||
rev: v1.5.2
|
||||
rev: v1.9.0
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
|
@ -68,7 +68,7 @@ repos:
|
|||
- id: sphinx-lint
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: v2.5.1
|
||||
rev: v2.6.0
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
|
|
21
Makefile
21
Makefile
|
@ -23,6 +23,10 @@ doc html:
|
|||
htmlview:
|
||||
$(MAKE) -C docs htmlview
|
||||
|
||||
.PHONY: htmllive
|
||||
htmllive:
|
||||
$(MAKE) -C docs htmllive
|
||||
|
||||
.PHONY: doccheck
|
||||
doccheck:
|
||||
$(MAKE) doc
|
||||
|
@ -43,6 +47,7 @@ help:
|
|||
@echo " docserve run an HTTP server on the docs directory"
|
||||
@echo " html make HTML docs"
|
||||
@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-coverage make and install with C coverage"
|
||||
@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 -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
|
||||
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 \
|
||||
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
|
||||
readme:
|
||||
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2
|
||||
|
|
|
@ -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.
|
||||
|
||||
## More Information
|
||||
## More information
|
||||
|
||||
- [Documentation](https://pillow.readthedocs.io/)
|
||||
- [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)
|
||||
- [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).
|
||||
|
|
35
RELEASING.md
35
RELEASING.md
|
@ -1,34 +1,15 @@
|
|||
# Release Checklist
|
||||
# Release checklist
|
||||
|
||||
See https://pillow.readthedocs.io/en/stable/releasenotes/versioning.html for
|
||||
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.
|
||||
|
||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||
* [ ] Develop and prepare release in `main` branch.
|
||||
* [ ] 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 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
|
||||
* [ ] Create a new issue and select the "Maintainers only: Release" template.
|
||||
|
||||
## Point release
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## Embargoed Release
|
||||
## Embargoed release
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## Publicize Release
|
||||
## Publicize release
|
||||
|
||||
* [ ] 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
|
||||
|
||||
## Docker Images
|
||||
## Docker images
|
||||
|
||||
* [ ] Update Pillow in the Docker Images repository
|
||||
```bash
|
||||
|
|
|
@ -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``.
|
||||
|
|
|
@ -9,15 +9,20 @@ from .helper import is_pypy
|
|||
|
||||
|
||||
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
|
||||
try:
|
||||
import tkinter
|
||||
if sys.platform == "win32":
|
||||
# tkinter is not available in cibuildwheel installed CPython on Windows
|
||||
try:
|
||||
import tkinter
|
||||
|
||||
assert tkinter
|
||||
except ImportError:
|
||||
expected_modules.remove("tkinter")
|
||||
assert tkinter
|
||||
except ImportError:
|
||||
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
|
||||
|
||||
|
|
|
@ -161,6 +161,12 @@ def assert_tuple_approx_equal(
|
|||
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:
|
||||
reason = f"{feature} not available"
|
||||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||
|
|
Binary file not shown.
BIN
Tests/images/op_index.qoi
Normal file
BIN
Tests/images/op_index.qoi
Normal file
Binary file not shown.
BIN
Tests/images/p_4_planes.pcx
Normal file
BIN
Tests/images/p_4_planes.pcx
Normal file
Binary file not shown.
|
@ -14,3 +14,23 @@
|
|||
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
|
||||
...
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@ def test_unknown_version() -> None:
|
|||
],
|
||||
)
|
||||
def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
|
||||
expected = r""
|
||||
with pytest.raises(RuntimeError, match=expected):
|
||||
_deprecate.deprecate(deprecated, 1, plural=plural)
|
||||
|
||||
|
|
|
@ -233,7 +233,7 @@ class TestFileAvif:
|
|||
with Image.open(out_gif) as reread:
|
||||
reread_value = reread.convert("RGB").getpixel((1, 1))
|
||||
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:
|
||||
temp_file = tmp_path / "temp.avif"
|
||||
|
@ -254,7 +254,9 @@ class TestFileAvif:
|
|||
assert_image(im, "RGBA", (64, 64))
|
||||
|
||||
# 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:
|
||||
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))
|
||||
|
|
|
@ -7,9 +7,8 @@ import pytest
|
|||
from PIL import BlpImagePlugin, Image
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
assert_image_equal_tofile,
|
||||
assert_image_similar,
|
||||
assert_image_similar_tofile,
|
||||
hopper,
|
||||
)
|
||||
|
||||
|
@ -52,18 +51,16 @@ def test_save(tmp_path: Path) -> None:
|
|||
im = hopper("P")
|
||||
im.save(f, blp_version=version)
|
||||
|
||||
with Image.open(f) as reloaded:
|
||||
assert_image_equal(im.convert("RGB"), reloaded)
|
||||
assert_image_equal_tofile(im.convert("RGB"), f)
|
||||
|
||||
with Image.open("Tests/images/transparent.png") as im:
|
||||
f = tmp_path / "temp.blp"
|
||||
im.convert("P").save(f, blp_version=version)
|
||||
|
||||
with Image.open(f) as reloaded:
|
||||
assert_image_similar(im, reloaded, 8)
|
||||
assert_image_similar_tofile(im, f, 8)
|
||||
|
||||
im = hopper()
|
||||
with pytest.raises(ValueError):
|
||||
with pytest.raises(ValueError, match="Unsupported BLP image mode"):
|
||||
im.save(f)
|
||||
|
||||
|
||||
|
|
|
@ -190,9 +190,9 @@ def test_rle8() -> None:
|
|||
# Signal end of bitmap before the image is finished
|
||||
with open("Tests/images/bmp/g/pal8rle.bmp", "rb") as fp:
|
||||
data = fp.read(1063) + b"\x01"
|
||||
with Image.open(io.BytesIO(data)) as im:
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
with Image.open(io.BytesIO(data)) as im:
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
||||
|
||||
def test_rle4() -> None:
|
||||
|
@ -214,9 +214,9 @@ def test_rle4() -> None:
|
|||
def test_rle8_eof(file_name: str, length: int) -> None:
|
||||
with open(file_name, "rb") as fp:
|
||||
data = fp.read(length)
|
||||
with Image.open(io.BytesIO(data)) as im:
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
with Image.open(io.BytesIO(data)) as im:
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
||||
|
||||
def test_offset() -> None:
|
||||
|
|
|
@ -15,6 +15,7 @@ from .helper import (
|
|||
is_win32,
|
||||
mark_if_feature_version,
|
||||
skip_unless_feature,
|
||||
timeout_unless_slower_valgrind,
|
||||
)
|
||||
|
||||
HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript()
|
||||
|
@ -398,7 +399,7 @@ def test_emptyline() -> None:
|
|||
assert image.format == "EPS"
|
||||
|
||||
|
||||
@pytest.mark.timeout(timeout=5)
|
||||
@timeout_unless_slower_valgrind(5)
|
||||
@pytest.mark.parametrize(
|
||||
"test_file",
|
||||
["Tests/images/eps/timeout-d675703545fee17acab56e5fec644c19979175de.eps"],
|
||||
|
|
|
@ -7,7 +7,12 @@ import pytest
|
|||
|
||||
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
|
||||
# save as...-> hopper.fli, default options.
|
||||
|
@ -189,7 +194,7 @@ def test_seek() -> None:
|
|||
"Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli",
|
||||
],
|
||||
)
|
||||
@pytest.mark.timeout(timeout=3)
|
||||
@timeout_unless_slower_valgrind(3)
|
||||
def test_timeouts(test_file: str) -> None:
|
||||
with open(test_file, "rb") as f:
|
||||
with Image.open(f) as im:
|
||||
|
|
|
@ -540,7 +540,9 @@ def test_dispose_background_transparency() -> None:
|
|||
img.seek(2)
|
||||
px = img.load()
|
||||
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(
|
||||
|
@ -1422,7 +1424,9 @@ def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
def test_lzw_bits() -> None:
|
||||
# see https://github.com/python-pillow/Pillow/issues/2811
|
||||
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
|
||||
im.load()
|
||||
|
||||
|
@ -1477,7 +1481,11 @@ def test_saving_rgba(tmp_path: Path) -> None:
|
|||
|
||||
with Image.open(out) as reloaded:
|
||||
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}))
|
||||
|
|
|
@ -32,6 +32,7 @@ from .helper import (
|
|||
is_win32,
|
||||
mark_if_feature_version,
|
||||
skip_unless_feature,
|
||||
timeout_unless_slower_valgrind,
|
||||
)
|
||||
|
||||
ElementTree: ModuleType | None
|
||||
|
@ -129,30 +130,26 @@ class TestFileJpeg:
|
|||
def test_cmyk(self) -> None:
|
||||
# Test CMYK handling. Thanks to Tim and Charlie for test data,
|
||||
# Michael for getting me to look one more time.
|
||||
f = "Tests/images/pil_sample_cmyk.jpg"
|
||||
with Image.open(f) as im:
|
||||
# the source image has red pixels in the upper left corner.
|
||||
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))
|
||||
def check(im: ImageFile.ImageFile) -> None:
|
||||
cmyk = im.getpixel((0, 0))
|
||||
assert isinstance(cmyk, tuple)
|
||||
c, m, y, k = (x / 255.0 for x in cmyk)
|
||||
assert c == 0.0
|
||||
assert m > 0.8
|
||||
assert y > 0.8
|
||||
assert k == 0.0
|
||||
# the opposite corner is black
|
||||
c, m, y, k = (
|
||||
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))
|
||||
)
|
||||
cmyk = im.getpixel((im.size[0] - 1, im.size[1] - 1))
|
||||
assert isinstance(cmyk, tuple)
|
||||
k = cmyk[3] / 255.0
|
||||
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
|
||||
im = 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
|
||||
check(self.roundtrip(im))
|
||||
|
||||
def test_rgb(self) -> None:
|
||||
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, ...]:
|
||||
|
@ -1033,7 +1030,7 @@ class TestFileJpeg:
|
|||
with pytest.raises(ValueError):
|
||||
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:
|
||||
# Even though this decoder never says that it is finished
|
||||
# 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":
|
||||
assert marker in data[1]
|
||||
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 not in data[2]
|
||||
|
||||
# SOF0, SOS, APP0 (JFIF header)
|
||||
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
|
||||
assert marker not in data[1]
|
||||
|
|
|
@ -457,8 +457,8 @@ def test_comment() -> None:
|
|||
# Test an image that is truncated partway through a codestream
|
||||
with open("Tests/images/comment.jp2", "rb") as fp:
|
||||
b = BytesIO(fp.read(130))
|
||||
with Image.open(b) as im:
|
||||
pass
|
||||
with Image.open(b) as im:
|
||||
pass
|
||||
|
||||
|
||||
def test_save_comment(card: ImageFile.ImageFile) -> None:
|
||||
|
|
|
@ -81,7 +81,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
s = io.BytesIO()
|
||||
with open(test_file, "rb") as f:
|
||||
s.write(f.read())
|
||||
s.seek(0)
|
||||
s.seek(0)
|
||||
with Image.open(s) as im:
|
||||
assert im.size == (500, 500)
|
||||
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:
|
||||
data = fp.read()
|
||||
|
||||
# Set EXIF Orientation to 2
|
||||
data = data[:102] + b"\x02" + data[103:]
|
||||
# Set EXIF Orientation to 2
|
||||
data = data[:102] + b"\x02" + data[103:]
|
||||
|
||||
with Image.open(io.BytesIO(data)) as im:
|
||||
im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
||||
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
||||
with Image.open(io.BytesIO(data)) as im:
|
||||
im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
||||
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
|
||||
|
||||
def test_open_missing_samplesperpixel(self) -> None:
|
||||
with Image.open(
|
||||
|
|
|
@ -32,7 +32,7 @@ class TestFileLibTiffSmall(LibTiffTestCase):
|
|||
s = BytesIO()
|
||||
with open(test_file, "rb") as f:
|
||||
s.write(f.read())
|
||||
s.seek(0)
|
||||
s.seek(0)
|
||||
with Image.open(s) as im:
|
||||
assert im.size == (128, 128)
|
||||
self._assert_noerr(tmp_path, im)
|
||||
|
|
|
@ -156,6 +156,7 @@ def test_reload_exif_after_seek() -> None:
|
|||
def test_mp(test_file: str) -> None:
|
||||
with Image.open(test_file) as im:
|
||||
mpinfo = im._getmp()
|
||||
assert mpinfo is not None
|
||||
assert mpinfo[45056] == b"0100"
|
||||
assert mpinfo[45057] == 2
|
||||
|
||||
|
@ -165,6 +166,7 @@ def test_mp_offset() -> None:
|
|||
# in APP2 data, in contrast to normal 8
|
||||
with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im:
|
||||
mpinfo = im._getmp()
|
||||
assert mpinfo is not None
|
||||
assert mpinfo[45056] == b"0100"
|
||||
assert mpinfo[45057] == 2
|
||||
|
||||
|
@ -181,6 +183,7 @@ def test_mp_no_data() -> None:
|
|||
def test_mp_attribute(test_file: str) -> None:
|
||||
with Image.open(test_file) as im:
|
||||
mpinfo = im._getmp()
|
||||
assert mpinfo is not None
|
||||
for frame_number, mpentry in enumerate(mpinfo[0xB002]):
|
||||
mpattr = mpentry["Attribute"]
|
||||
if frame_number:
|
||||
|
|
|
@ -37,6 +37,11 @@ def test_sanity(tmp_path: Path) -> None:
|
|||
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:
|
||||
with open("Tests/images/pil184.pcx", "rb") as fp:
|
||||
data = fp.read()
|
||||
|
|
|
@ -13,7 +13,12 @@ import pytest
|
|||
|
||||
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:
|
||||
|
@ -339,8 +344,7 @@ def test_pdf_append_to_bytesio() -> None:
|
|||
assert len(f.getvalue()) > initial_size
|
||||
|
||||
|
||||
@pytest.mark.timeout(1)
|
||||
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
|
||||
@timeout_unless_slower_valgrind(1)
|
||||
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
|
||||
def test_redos(newline: bytes) -> None:
|
||||
malicious = b" trailer<<>>" + newline * 3456
|
||||
|
|
|
@ -100,11 +100,11 @@ class TestFilePng:
|
|||
assert im.format == "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.save(test_file)
|
||||
with Image.open(test_file) as reloaded:
|
||||
if mode in ("I", "I;16B"):
|
||||
if mode == "I;16B":
|
||||
reloaded = reloaded.convert(mode)
|
||||
assert_image_equal(reloaded, im)
|
||||
|
||||
|
@ -801,6 +801,16 @@ class TestFilePng:
|
|||
with Image.open("Tests/images/truncated_end_chunk.png") as im:
|
||||
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")
|
||||
@skip_unless_feature("zlib")
|
||||
|
|
|
@ -288,14 +288,16 @@ def test_non_integer_token(tmp_path: Path) -> None:
|
|||
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"
|
||||
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):
|
||||
pass
|
||||
assert "Token too long in file header: " in repr(e)
|
||||
|
||||
|
||||
def test_truncated_file(tmp_path: Path) -> None:
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, QoiImagePlugin
|
||||
|
||||
from .helper import assert_image_equal_tofile
|
||||
from .helper import assert_image_equal_tofile, hopper
|
||||
|
||||
|
||||
def test_sanity() -> None:
|
||||
|
@ -28,3 +30,28 @@ def test_invalid_file() -> None:
|
|||
|
||||
with pytest.raises(SyntaxError):
|
||||
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)
|
||||
|
|
|
@ -220,12 +220,16 @@ def test_horizontal_orientations() -> None:
|
|||
with Image.open("Tests/images/rgb32rle_top_right.tga") as im:
|
||||
px = im.load()
|
||||
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:
|
||||
px = im.load()
|
||||
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:
|
||||
|
|
|
@ -14,6 +14,7 @@ from PIL import (
|
|||
ImageFile,
|
||||
JpegImagePlugin,
|
||||
TiffImagePlugin,
|
||||
TiffTags,
|
||||
UnidentifiedImageError,
|
||||
)
|
||||
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
|
||||
|
@ -26,6 +27,7 @@ from .helper import (
|
|||
hopper,
|
||||
is_pypy,
|
||||
is_win32,
|
||||
timeout_unless_slower_valgrind,
|
||||
)
|
||||
|
||||
ElementTree: ModuleType | None
|
||||
|
@ -47,25 +49,10 @@ class TestFileTiff:
|
|||
assert im.size == (128, 128)
|
||||
assert im.format == "TIFF"
|
||||
|
||||
hopper("1").save(filename)
|
||||
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
|
||||
for mode in ("1", "L", "P", "RGB", "I", "I;16", "I;16L"):
|
||||
hopper(mode).save(filename)
|
||||
with Image.open(filename):
|
||||
pass
|
||||
|
||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
|
||||
def test_unclosed_file(self) -> None:
|
||||
|
@ -899,6 +886,29 @@ class TestFileTiff:
|
|||
assert description[0]["format"] == "image/tiff"
|
||||
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:
|
||||
with Image.open("Tests/images/lab.tif") as im:
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
|
@ -988,7 +998,7 @@ class TestFileTiff:
|
|||
with pytest.raises(OSError):
|
||||
im.load()
|
||||
|
||||
@pytest.mark.timeout(6)
|
||||
@timeout_unless_slower_valgrind(6)
|
||||
@pytest.mark.filterwarnings("ignore:Truncated File Read")
|
||||
def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
with Image.open("Tests/images/timeout-6646305047838720") as im:
|
||||
|
@ -1001,7 +1011,7 @@ class TestFileTiff:
|
|||
"Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif",
|
||||
],
|
||||
)
|
||||
@pytest.mark.timeout(2)
|
||||
@timeout_unless_slower_valgrind(2)
|
||||
def test_oom(self, test_file: str) -> None:
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
with pytest.warns(UserWarning):
|
||||
|
|
|
@ -219,6 +219,7 @@ class TestFileWebp:
|
|||
# Save P mode GIF with background
|
||||
with Image.open("Tests/images/chi.gif") as im:
|
||||
original_value = im.convert("RGB").getpixel((1, 1))
|
||||
assert isinstance(original_value, tuple)
|
||||
|
||||
# Save as WEBP
|
||||
im.save(out_webp, save_all=True)
|
||||
|
@ -230,6 +231,7 @@ class TestFileWebp:
|
|||
|
||||
with Image.open(out_gif) as reread:
|
||||
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))
|
||||
assert difference < 5
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ from .helper import (
|
|||
is_win32,
|
||||
mark_if_feature_version,
|
||||
skip_unless_feature,
|
||||
timeout_unless_slower_valgrind,
|
||||
)
|
||||
|
||||
ElementTree: ModuleType | None
|
||||
|
@ -572,10 +573,7 @@ class TestImage:
|
|||
i = Image.new("RGB", [1, 1])
|
||||
assert isinstance(i.size, tuple)
|
||||
|
||||
@pytest.mark.timeout(0.75)
|
||||
@pytest.mark.skipif(
|
||||
"PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower"
|
||||
)
|
||||
@timeout_unless_slower_valgrind(0.75)
|
||||
@pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
|
||||
def test_empty_image(self, size: tuple[int, int]) -> None:
|
||||
Image.new("RGB", size)
|
||||
|
@ -673,6 +671,7 @@ class TestImage:
|
|||
im_remapped = im.remap_palette(list(range(256)))
|
||||
assert_image_equal(im, im_remapped)
|
||||
assert im.palette is not None
|
||||
assert im_remapped.palette is not None
|
||||
assert im.palette.palette == im_remapped.palette.palette
|
||||
|
||||
# Test illegal image mode
|
||||
|
@ -975,6 +974,11 @@ class TestImage:
|
|||
assert tag not in exif.get_ifd(0x8769)
|
||||
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:
|
||||
with Image.open("Tests/images/hopper.gif") as im:
|
||||
if ElementTree is None:
|
||||
|
@ -991,7 +995,7 @@ class TestImage:
|
|||
im = Image.new("RGB", (1, 1))
|
||||
im.info["xmp"] = (
|
||||
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:
|
||||
with pytest.warns(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
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))
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
import numpy.typing as npt
|
||||
|
||||
|
@ -101,7 +102,8 @@ def test_fromarray_strides_without_tobytes() -> None:
|
|||
|
||||
with pytest.raises(ValueError):
|
||||
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:
|
||||
|
@ -110,7 +112,8 @@ def test_fromarray_palette() -> None:
|
|||
a = numpy.array(i)
|
||||
|
||||
# Act
|
||||
out = Image.fromarray(a, "P")
|
||||
with pytest.warns(DeprecationWarning):
|
||||
out = Image.fromarray(a, "P")
|
||||
|
||||
# Assert that the Python and C palettes match
|
||||
assert out.palette is not None
|
||||
|
|
|
@ -462,7 +462,7 @@ class TestCoreResampleBox:
|
|||
im.resize((32, 32), resample, (20, 20, 20, 100))
|
||||
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]
|
||||
|
||||
with pytest.raises(ValueError, match="can't be negative"):
|
||||
|
|
|
@ -783,9 +783,10 @@ def test_rectangle_I16(bbox: Coords) -> None:
|
|||
draw = ImageDraw.Draw(im)
|
||||
|
||||
# Act
|
||||
draw.rectangle(bbox, outline=0xFFFF)
|
||||
draw.rectangle(bbox, outline=0xCDEF)
|
||||
|
||||
# Assert
|
||||
assert im.getpixel((X0, Y0)) == 0xCDEF
|
||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff")
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import pytest
|
|||
|
||||
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()]
|
||||
if not features.check_module("freetype2"):
|
||||
|
@ -72,7 +72,7 @@ def test_decompression_bomb() -> None:
|
|||
font.getmask("A" * 1_000_000)
|
||||
|
||||
|
||||
@pytest.mark.timeout(4)
|
||||
@timeout_unless_slower_valgrind(4)
|
||||
def test_oom() -> None:
|
||||
glyph = struct.pack(
|
||||
">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767
|
||||
|
|
|
@ -43,6 +43,7 @@ class TestImageGrab:
|
|||
if (
|
||||
sys.platform not in ("win32", "darwin")
|
||||
and not shutil.which("gnome-screenshot")
|
||||
and not shutil.which("grim")
|
||||
and not shutil.which("spectacle")
|
||||
):
|
||||
with pytest.raises(OSError) as e:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -9,6 +8,7 @@ from PIL import Image, _typing
|
|||
|
||||
from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
import numpy
|
||||
import numpy.typing as npt
|
||||
|
|
|
@ -162,3 +162,13 @@ def test_pickle_font_file(tmp_path: Path, protocol: int) -> None:
|
|||
|
||||
# Assert
|
||||
helper_assert_pickled_font_images(font, unpickled_font)
|
||||
|
||||
|
||||
def test_load_earlier_data() -> None:
|
||||
im = pickle.loads(
|
||||
b"\x80\x04\x95@\x00\x00\x00\x00\x00\x00\x00\x8c\x12PIL.PngImagePlugin"
|
||||
b"\x94\x8c\x0cPngImageFile\x94\x93\x94)\x81\x94]\x94(}\x94\x8c\x01L\x94K\x01"
|
||||
b"K\x01\x86\x94NC\x01\x00\x94eb."
|
||||
)
|
||||
assert im.mode == "L"
|
||||
assert im.size == (1, 1)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any # undone
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -10,30 +10,73 @@ from .helper import (
|
|||
assert_deep_equal,
|
||||
assert_image_equal,
|
||||
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)
|
||||
|
||||
|
||||
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:
|
||||
assert img.height * img.width == len(arr)
|
||||
assert img.height * img.width * elts_per_pixel == len(arr)
|
||||
px = img.load()
|
||||
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 y in range(0, img.size[1], int(img.size[1] / 10)):
|
||||
if mask:
|
||||
pixel = px[x, y]
|
||||
assert isinstance(pixel, tuple)
|
||||
for ix, elt in enumerate(mask):
|
||||
pixel = px[x, y]
|
||||
assert isinstance(pixel, tuple)
|
||||
assert pixel[ix] == arr[y * img.width + x].as_py()[elt]
|
||||
if elts_per_pixel == 1:
|
||||
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:
|
||||
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
|
||||
fl_uint8_4_type = pyarrow.field(
|
||||
"_", 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]),
|
||||
),
|
||||
)
|
||||
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)
|
||||
|
||||
# Resize to non-square
|
||||
img = img.crop((3, 0, 124, 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)
|
||||
assert arr.type == dtype
|
||||
|
||||
|
@ -79,8 +122,8 @@ def test_lifetime() -> None:
|
|||
|
||||
img = hopper("L")
|
||||
|
||||
arr_1 = pyarrow.array(img)
|
||||
arr_2 = pyarrow.array(img)
|
||||
arr_1 = pyarrow.array(img) # type: ignore[call-overload]
|
||||
arr_2 = pyarrow.array(img) # type: ignore[call-overload]
|
||||
|
||||
del img
|
||||
|
||||
|
@ -97,8 +140,8 @@ def test_lifetime2() -> None:
|
|||
|
||||
img = hopper("L")
|
||||
|
||||
arr_1 = pyarrow.array(img)
|
||||
arr_2 = pyarrow.array(img)
|
||||
arr_1 = pyarrow.array(img) # type: ignore[call-overload]
|
||||
arr_2 = pyarrow.array(img) # type: ignore[call-overload]
|
||||
|
||||
assert arr_1.sum().as_py() > 0
|
||||
del arr_1
|
||||
|
@ -110,3 +153,94 @@ def test_lifetime2() -> None:
|
|||
px = img2.load()
|
||||
assert px # make mypy happy
|
||||
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)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Union
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -9,6 +9,7 @@ from PIL import Image, ImageQt
|
|||
|
||||
from .helper import assert_image_equal_tofile, assert_image_similar, hopper
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
import PyQt6
|
||||
import PySide6
|
||||
|
|
|
@ -52,3 +52,17 @@ def test_tiff_crashes(test_file: str) -> None:
|
|||
pytest.skip("test image not found")
|
||||
except OSError:
|
||||
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")
|
||||
|
|
11
depends/docker-test-valgrind-memory.sh
Executable file
11
depends/docker-test-valgrind-memory.sh
Executable 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
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
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
|
||||
|
||||
|
|
|
@ -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"))
|
186
docs/Makefile
186
docs/Makefile
|
@ -3,43 +3,34 @@
|
|||
|
||||
# You can set these variables from the command line.
|
||||
PYTHON = python3
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = $(PYTHON) -m sphinx.cmd.build
|
||||
PAPER =
|
||||
SPHINXOPTS = --fail-on-warning
|
||||
BUILDDIR = _build
|
||||
BUILDER = html
|
||||
JOBS = auto
|
||||
PAPER =
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = --define latex_paper_size=a4
|
||||
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
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
ALLSPHINXOPTS = --builder $(BUILDER) \
|
||||
--doctree-dir $(BUILDDIR)/doctrees \
|
||||
--jobs $(JOBS) \
|
||||
$(PAPEROPT_$(PAPER)) \
|
||||
$(SPHINXOPTS) \
|
||||
. $(BUILDDIR)/$(BUILDER)
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@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 " 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 " 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 " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
@ -51,159 +42,28 @@ install-sphinx:
|
|||
.PHONY: html
|
||||
html:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) --builder html --fail-on-warning --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
$(SPHINXBUILD) $(ALLSPHINXOPTS)
|
||||
|
||||
.PHONY: dirhtml
|
||||
dirhtml:
|
||||
$(MAKE) install-sphinx
|
||||
$(SPHINXBUILD) --builder dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
dirhtml: BUILDER = dirhtml
|
||||
dirhtml: html
|
||||
|
||||
.PHONY: singlehtml
|
||||
singlehtml:
|
||||
$(MAKE) install-sphinx
|
||||
$(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."
|
||||
singlehtml: BUILDER = singlehtml
|
||||
singlehtml: html
|
||||
|
||||
.PHONY: linkcheck
|
||||
linkcheck:
|
||||
$(MAKE) install-sphinx
|
||||
$(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."
|
||||
linkcheck: BUILDER = linkcheck
|
||||
linkcheck: html
|
||||
|
||||
.PHONY: htmlview
|
||||
htmlview: html
|
||||
$(PYTHON) -c "import os, webbrowser; webbrowser.open('file://' + os.path.realpath('$(BUILDDIR)/html/index.html'))"
|
||||
|
||||
.PHONY: livehtml
|
||||
livehtml: html
|
||||
livereload $(BUILDDIR)/html -p 33233
|
||||
.PHONY: htmllive
|
||||
htmllive: SPHINXBUILD = $(PYTHON) -m sphinx_autobuild
|
||||
htmllive: SPHINXOPTS = --open-browser --delay 0
|
||||
htmllive: html
|
||||
|
||||
.PHONY: serve
|
||||
serve:
|
||||
|
|
32
docs/PIL.rst
32
docs/PIL.rst
|
@ -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
|
||||
can be found here.
|
||||
|
||||
:mod:`PIL` Module
|
||||
:mod:`PIL` module
|
||||
-----------------
|
||||
|
||||
.. py:module:: PIL
|
||||
|
@ -12,7 +12,7 @@ can be found here.
|
|||
.. autoexception:: UnidentifiedImageError
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.BdfFontFile` Module
|
||||
:mod:`~PIL.BdfFontFile` module
|
||||
------------------------------
|
||||
|
||||
.. automodule:: PIL.BdfFontFile
|
||||
|
@ -20,7 +20,7 @@ can be found here.
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.ContainerIO` Module
|
||||
:mod:`~PIL.ContainerIO` module
|
||||
------------------------------
|
||||
|
||||
.. automodule:: PIL.ContainerIO
|
||||
|
@ -28,7 +28,7 @@ can be found here.
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.FontFile` Module
|
||||
:mod:`~PIL.FontFile` module
|
||||
---------------------------
|
||||
|
||||
.. automodule:: PIL.FontFile
|
||||
|
@ -36,7 +36,7 @@ can be found here.
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.GdImageFile` Module
|
||||
:mod:`~PIL.GdImageFile` module
|
||||
------------------------------
|
||||
|
||||
.. automodule:: PIL.GdImageFile
|
||||
|
@ -44,7 +44,7 @@ can be found here.
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.GimpGradientFile` Module
|
||||
:mod:`~PIL.GimpGradientFile` module
|
||||
-----------------------------------
|
||||
|
||||
.. automodule:: PIL.GimpGradientFile
|
||||
|
@ -52,7 +52,7 @@ can be found here.
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.GimpPaletteFile` Module
|
||||
:mod:`~PIL.GimpPaletteFile` module
|
||||
----------------------------------
|
||||
|
||||
.. automodule:: PIL.GimpPaletteFile
|
||||
|
@ -60,7 +60,7 @@ can be found here.
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.ImageDraw2` Module
|
||||
:mod:`~PIL.ImageDraw2` module
|
||||
-----------------------------
|
||||
|
||||
.. automodule:: PIL.ImageDraw2
|
||||
|
@ -69,7 +69,7 @@ can be found here.
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.ImageMode` Module
|
||||
:mod:`~PIL.ImageMode` module
|
||||
----------------------------
|
||||
|
||||
.. automodule:: PIL.ImageMode
|
||||
|
@ -77,7 +77,7 @@ can be found here.
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.PaletteFile` Module
|
||||
:mod:`~PIL.PaletteFile` module
|
||||
------------------------------
|
||||
|
||||
.. automodule:: PIL.PaletteFile
|
||||
|
@ -85,7 +85,7 @@ can be found here.
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.PcfFontFile` Module
|
||||
:mod:`~PIL.PcfFontFile` module
|
||||
------------------------------
|
||||
|
||||
.. automodule:: PIL.PcfFontFile
|
||||
|
@ -93,7 +93,7 @@ can be found here.
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:class:`.PngImagePlugin.iTXt` Class
|
||||
:class:`.PngImagePlugin.iTXt` class
|
||||
-----------------------------------
|
||||
|
||||
.. autoclass:: PIL.PngImagePlugin.iTXt
|
||||
|
@ -107,7 +107,7 @@ can be found here.
|
|||
:param lang: language code
|
||||
:param tkey: UTF-8 version of the key name
|
||||
|
||||
:class:`.PngImagePlugin.PngInfo` Class
|
||||
:class:`.PngImagePlugin.PngInfo` class
|
||||
--------------------------------------
|
||||
|
||||
.. autoclass:: PIL.PngImagePlugin.PngInfo
|
||||
|
@ -116,7 +116,7 @@ can be found here.
|
|||
:show-inheritance:
|
||||
|
||||
|
||||
:mod:`~PIL.TarIO` Module
|
||||
:mod:`~PIL.TarIO` module
|
||||
------------------------
|
||||
|
||||
.. automodule:: PIL.TarIO
|
||||
|
@ -124,7 +124,7 @@ can be found here.
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.WalImageFile` Module
|
||||
:mod:`~PIL.WalImageFile` module
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: PIL.WalImageFile
|
||||
|
|
91
docs/conf.py
91
docs/conf.py
|
@ -229,97 +229,6 @@ html_js_files = [
|
|||
# implements a search results scorer. If empty, the default will be used.
|
||||
# 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 = {
|
||||
r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*",
|
||||
|
|
|
@ -8,8 +8,8 @@ from __future__ import annotations
|
|||
|
||||
import re
|
||||
import subprocess
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from sphinx.application import Sphinx
|
||||
|
||||
|
|
|
@ -155,7 +155,7 @@ JpegImageFile.huffman_ac and JpegImageFile.huffman_dc
|
|||
The ``huffman_ac`` and ``huffman_dc`` dictionaries on JPEG images were unused. They
|
||||
have been deprecated, and will be removed in Pillow 12 (2025-10-15).
|
||||
|
||||
Specific WebP Feature Checks
|
||||
Specific WebP feature checks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. 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
|
||||
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
|
||||
----------------
|
||||
|
||||
|
|
|
@ -8,4 +8,5 @@ Appendices
|
|||
|
||||
image-file-formats
|
||||
text-anchors
|
||||
third-party-plugins
|
||||
writing-your-own-image-plugin
|
||||
|
|
|
@ -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
|
||||
supports the following standard modes:
|
||||
|
||||
* ``1`` (1-bit pixels, black and white, stored with one pixel per byte)
|
||||
* ``L`` (8-bit pixels, grayscale)
|
||||
* ``P`` (8-bit pixels, mapped to any other mode using a color palette)
|
||||
* ``RGB`` (3x8-bit pixels, true color)
|
||||
* ``RGBA`` (4x8-bit pixels, true color with transparency mask)
|
||||
* ``CMYK`` (4x8-bit pixels, color separation)
|
||||
* ``YCbCr`` (3x8-bit pixels, color video format)
|
||||
* ``1`` (1-bit pixels, black and white, stored with one pixel per byte)
|
||||
* ``L`` (8-bit pixels, grayscale)
|
||||
* ``P`` (8-bit pixels, mapped to any other mode using a color palette)
|
||||
* ``RGB`` (3x8-bit pixels, true color)
|
||||
* ``RGBA`` (4x8-bit pixels, true color with transparency mask)
|
||||
* ``CMYK`` (4x8-bit pixels, color separation)
|
||||
* ``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)
|
||||
* ``HSV`` (3x8-bit pixels, Hue, Saturation, Value color space)
|
||||
* ``LAB`` (3x8-bit pixels, the L*a*b 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)
|
||||
* ``F`` (32-bit floating point pixels)
|
||||
* ``I`` (32-bit signed integer pixels)
|
||||
* ``F`` (32-bit floating point pixels)
|
||||
|
||||
Pillow also provides limited support for a few additional modes, including:
|
||||
|
||||
* ``LA`` (L with alpha)
|
||||
* ``PA`` (P with alpha)
|
||||
* ``RGBX`` (true color with padding)
|
||||
* ``RGBa`` (true color with premultiplied alpha)
|
||||
* ``La`` (L with premultiplied alpha)
|
||||
* ``I;16`` (16-bit unsigned integer pixels)
|
||||
* ``I;16L`` (16-bit little endian unsigned integer pixels)
|
||||
* ``I;16B`` (16-bit big endian unsigned integer pixels)
|
||||
* ``I;16N`` (16-bit native endian unsigned integer pixels)
|
||||
* ``LA`` (L with alpha)
|
||||
* ``PA`` (P with alpha)
|
||||
* ``RGBX`` (true color with padding)
|
||||
* ``RGBa`` (true color with premultiplied alpha)
|
||||
* ``La`` (L with premultiplied alpha)
|
||||
* ``I;16`` (16-bit unsigned integer pixels)
|
||||
* ``I;16L`` (16-bit little endian unsigned integer pixels)
|
||||
* ``I;16B`` (16-bit big 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
|
||||
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
|
||||
-----------------
|
||||
|
||||
The Python Imaging Library uses a Cartesian pixel coordinate system, with (0,0)
|
||||
|
|
|
@ -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.
|
||||
|
||||
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
|
||||
^^^
|
||||
|
||||
|
@ -1222,7 +1242,7 @@ numbers are returned as a tuple of ``(numerator, denominator)``.
|
|||
|
||||
.. 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
|
||||
|
@ -1578,15 +1598,6 @@ PSD
|
|||
|
||||
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
|
||||
^^^
|
||||
|
||||
|
@ -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
|
||||
transparent.
|
||||
|
||||
XV Thumbnails
|
||||
XV thumbnails
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Pillow can read XV thumbnail files.
|
||||
|
|
|
@ -13,7 +13,7 @@ processing tool.
|
|||
|
||||
Let’s 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
|
||||
|
@ -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
|
||||
presentation formats.
|
||||
|
||||
Image Display
|
||||
Image display
|
||||
-------------
|
||||
|
||||
The current release includes Tk :py:class:`~PIL.ImageTk.PhotoImage` and
|
||||
|
@ -36,7 +36,7 @@ support.
|
|||
For debugging, there’s also a :py:meth:`~PIL.Image.Image.show` method which saves an image to
|
||||
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.
|
||||
|
|
18
docs/handbook/third-party-plugins.rst
Normal file
18
docs/handbook/third-party-plugins.rst
Normal 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.
|
|
@ -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. Here’s a simple script to quickly
|
||||
identify a set of image files:
|
||||
|
||||
Identify Image Files
|
||||
Identify image files
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
::
|
||||
|
@ -399,7 +399,7 @@ Applying filters
|
|||
.. image:: enhanced_hopper.webp
|
||||
:align: center
|
||||
|
||||
Point Operations
|
||||
Point operations
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
The :py:meth:`~PIL.Image.Image.point` method can be used to translate the pixel
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.. _image-plugins:
|
||||
|
||||
Writing Your Own Image Plugin
|
||||
Writing your own image plugin
|
||||
=============================
|
||||
|
||||
Pillow uses a plugin model which allows you to add your own
|
||||
|
@ -329,7 +329,7 @@ The fields are used as follows:
|
|||
|
||||
.. _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:
|
||||
|
@ -414,7 +414,7 @@ memory and release any resources from external libraries.
|
|||
|
||||
.. _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
|
||||
|
|
|
@ -3,27 +3,27 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
Basic Installation
|
||||
Basic installation
|
||||
------------------
|
||||
|
||||
.. 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.
|
||||
|
||||
Platform Support
|
||||
Platform support
|
||||
----------------
|
||||
|
||||
.. 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.
|
||||
|
||||
Old Versions
|
||||
Old versions
|
||||
------------
|
||||
|
||||
.. Note:: This section has moved to :ref:`old-versions`. Please update references accordingly.
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
.. _basic-installation:
|
||||
|
||||
Basic Installation
|
||||
Basic installation
|
||||
==================
|
||||
|
||||
.. note::
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
|
||||
.. _building-from-source:
|
||||
|
||||
Building From Source
|
||||
Building from source
|
||||
====================
|
||||
|
||||
.. _external-libraries:
|
||||
|
||||
External Libraries
|
||||
External libraries
|
||||
------------------
|
||||
|
||||
.. note::
|
||||
|
@ -194,9 +194,9 @@ Many of Pillow's features require external libraries:
|
|||
|
||||
pacman -S \
|
||||
mingw-w64-x86_64-gcc \
|
||||
mingw-w64-x86_64-python3 \
|
||||
mingw-w64-x86_64-python3-pip \
|
||||
mingw-w64-x86_64-python3-setuptools
|
||||
mingw-w64-x86_64-python \
|
||||
mingw-w64-x86_64-python-pip \
|
||||
mingw-w64-x86_64-python-setuptools
|
||||
|
||||
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
|
||||
|
||||
Build Options
|
||||
Build options
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
* Config setting: ``-C parallel=n``. Can also be given
|
||||
|
@ -319,7 +319,7 @@ Sample usage::
|
|||
|
||||
.. _old-versions:
|
||||
|
||||
Old Versions
|
||||
Old versions
|
||||
============
|
||||
|
||||
You can download old distributions from the `release history at PyPI
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.. _platform-support:
|
||||
|
||||
Platform Support
|
||||
Platform support
|
||||
================
|
||||
|
||||
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
|
||||
Windows.
|
||||
|
||||
Continuous Integration Targets
|
||||
Continuous integration targets
|
||||
------------------------------
|
||||
|
||||
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 42 | 3.13 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Gentoo | 3.12 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 13 Ventura | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| 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 |
|
||||
| | 3.12, 3.13, PyPy3 | |
|
||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 24.04 LTS (Noble) | 3.12 | x86-64, arm64v8, |
|
||||
| | | ppc64le, s390x |
|
||||
| Ubuntu Linux 24.04 LTS (Noble) | 3.9, 3.10, 3.11, | x86-64 |
|
||||
| | 3.12, 3.13, 3.14, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.12 | arm64v8, ppc64le, |
|
||||
| | | s390x |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2019 | 3.9 | x86 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2022 | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
||||
| | PyPy3 | |
|
||||
| Windows Server 2022 | 3.9 | x86 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
||||
| | 3.14, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.. _python-support:
|
||||
|
||||
Python Support
|
||||
Python support
|
||||
==============
|
||||
|
||||
Pillow supports these Python versions.
|
||||
|
|
125
docs/make.bat
125
docs/make.bat
|
@ -7,10 +7,8 @@ if "%SPHINXBUILD%" == "" (
|
|||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
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. dirhtml to make HTML files named index.html in directories
|
||||
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. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
|
@ -80,107 +65,6 @@ if "%1" == "singlehtml" (
|
|||
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" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
|
@ -190,13 +74,4 @@ or in %BUILDDIR%/linkcheck/output.txt.
|
|||
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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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`
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
@ -113,7 +113,7 @@ Registering plugins
|
|||
.. autofunction:: register_decoder
|
||||
.. autofunction:: register_encoder
|
||||
|
||||
The Image Class
|
||||
The Image class
|
||||
---------------
|
||||
|
||||
.. autoclass:: PIL.Image.Image
|
||||
|
@ -261,7 +261,7 @@ method. ::
|
|||
.. automethod:: PIL.Image.Image.load
|
||||
.. automethod:: PIL.Image.Image.close
|
||||
|
||||
Image Attributes
|
||||
Image attributes
|
||||
----------------
|
||||
|
||||
Instances of the :py:class:`Image` class have the following attributes:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
@ -11,7 +11,7 @@ others.
|
|||
|
||||
.. _color-names:
|
||||
|
||||
Color Names
|
||||
Color names
|
||||
-----------
|
||||
|
||||
The ImageColor module supports the following string formats:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
@ -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 don’t draw with
|
||||
more than 256 colors.
|
||||
|
||||
Color Names
|
||||
Color names
|
||||
^^^^^^^^^^^
|
||||
|
||||
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
|
||||
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()
|
||||
|
||||
Example: Draw Multiline Text
|
||||
Example: Draw multiline text
|
||||
----------------------------
|
||||
|
||||
::
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
@ -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
|
||||
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::
|
||||
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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
fallback if they are installed. To disable this behaviour, pass ``xdisplay=""``
|
||||
instead.
|
||||
a snapshot of the screen, ``gnome-screenshot``, ``grim`` or ``spectacle`` will be
|
||||
used as a fallback if they are installed. To disable this behaviour, pass
|
||||
``xdisplay=""`` instead.
|
||||
|
||||
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
@ -86,7 +86,7 @@ Expression syntax
|
|||
It is not recommended to process expressions without considering this.
|
||||
:py:meth:`lambda_eval` is a more secure alternative.
|
||||
|
||||
Standard Operators
|
||||
Standard operators
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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()``
|
||||
functions described below.
|
||||
|
||||
Bitwise Operators
|
||||
Bitwise operators
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The module also provides operations that operate on individual bits. This
|
||||
|
@ -116,7 +116,7 @@ mask off unwanted bits.
|
|||
|
||||
Bitwise operators don’t work on floating point images.
|
||||
|
||||
Logical Operators
|
||||
Logical operators
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
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,
|
||||
while not always returns a boolean value.
|
||||
|
||||
Built-in Functions
|
||||
Built-in functions
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
These functions are applied to each individual pixel.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
.. py:module:: 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.
|
||||
|
||||
.. autofunction:: PIL.ImageShow.show
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.. py:currentmodule:: PIL.JpegPresets
|
||||
|
||||
:py:mod:`~PIL.JpegPresets` Module
|
||||
:py:mod:`~PIL.JpegPresets` module
|
||||
=================================
|
||||
|
||||
.. automodule:: PIL.JpegPresets
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.. py:module:: 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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user