mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-10-22 19:54:46 +03:00
Merge branch 'main' into rgba_pa
This commit is contained in:
commit
762bdce34f
|
@ -51,10 +51,10 @@ pushd depends && ./install_webp.sh && popd
|
||||||
pushd depends && ./install_imagequant.sh && popd
|
pushd depends && ./install_imagequant.sh && popd
|
||||||
|
|
||||||
# raqm
|
# raqm
|
||||||
pushd depends && ./install_raqm.sh && popd
|
pushd depends && sudo ./install_raqm.sh && popd
|
||||||
|
|
||||||
# libavif
|
# libavif
|
||||||
pushd depends && ./install_libavif.sh && popd
|
pushd depends && sudo ./install_libavif.sh && popd
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
cibuildwheel==3.1.3
|
cibuildwheel==3.2.0
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mypy==1.17.1
|
mypy==1.18.2
|
||||||
IceSpringPySideStubs-PyQt6
|
IceSpringPySideStubs-PyQt6
|
||||||
IceSpringPySideStubs-PySide6
|
IceSpringPySideStubs-PySide6
|
||||||
ipython
|
ipython
|
||||||
|
|
4
.github/workflows/docs.yml
vendored
4
.github/workflows/docs.yml
vendored
|
@ -32,12 +32,12 @@ jobs:
|
||||||
name: Docs
|
name: Docs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
||||||
name: Lint
|
name: Lint
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ jobs:
|
||||||
lint-pre-commit-
|
lint-pre-commit-
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|
10
.github/workflows/macos-install.sh
vendored
10
.github/workflows/macos-install.sh
vendored
|
@ -4,11 +4,19 @@ set -e
|
||||||
|
|
||||||
if [[ "$ImageOS" == "macos13" ]]; then
|
if [[ "$ImageOS" == "macos13" ]]; then
|
||||||
brew uninstall gradle maven
|
brew uninstall gradle maven
|
||||||
|
|
||||||
|
wget https://raw.githubusercontent.com/python-pillow/pillow-depends/main/freetype-2.14.1.tar.gz
|
||||||
|
tar -xvzf freetype-2.14.1.tar.gz
|
||||||
|
(cd freetype-2.14.1 \
|
||||||
|
&& ./configure \
|
||||||
|
&& make -j4 \
|
||||||
|
&& make install)
|
||||||
|
else
|
||||||
|
brew install freetype
|
||||||
fi
|
fi
|
||||||
brew install \
|
brew install \
|
||||||
aom \
|
aom \
|
||||||
dav1d \
|
dav1d \
|
||||||
freetype \
|
|
||||||
ghostscript \
|
ghostscript \
|
||||||
jpeg-turbo \
|
jpeg-turbo \
|
||||||
libimagequant \
|
libimagequant \
|
||||||
|
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Check issues"
|
- name: "Check issues"
|
||||||
uses: actions/stale@v9
|
uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
only-labels: "Awaiting OP Action"
|
only-labels: "Awaiting OP Action"
|
||||||
|
|
2
.github/workflows/test-docker.yml
vendored
2
.github/workflows/test-docker.yml
vendored
|
@ -68,7 +68,7 @@ jobs:
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
|
|
2
.github/workflows/test-mingw.yml
vendored
2
.github/workflows/test-mingw.yml
vendored
|
@ -45,7 +45,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Pillow
|
- name: Checkout Pillow
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
|
|
2
.github/workflows/test-valgrind-memory.yml
vendored
2
.github/workflows/test-valgrind-memory.yml
vendored
|
@ -41,7 +41,7 @@ jobs:
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
|
|
2
.github/workflows/test-valgrind.yml
vendored
2
.github/workflows/test-valgrind.yml
vendored
|
@ -39,7 +39,7 @@ jobs:
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
|
|
12
.github/workflows/test-windows.yml
vendored
12
.github/workflows/test-windows.yml
vendored
|
@ -47,19 +47,19 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Pillow
|
- name: Checkout Pillow
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Checkout cached dependencies
|
- name: Checkout cached dependencies
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
repository: python-pillow/pillow-depends
|
repository: python-pillow/pillow-depends
|
||||||
path: winbuild\depends
|
path: winbuild\depends
|
||||||
|
|
||||||
- name: Checkout extra test images
|
- name: Checkout extra test images
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
repository: python-pillow/test-images
|
repository: python-pillow/test-images
|
||||||
|
@ -67,7 +67,7 @@ jobs:
|
||||||
|
|
||||||
# sets env: pythonLocation
|
# sets env: pythonLocation
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
allow-prereleases: true
|
allow-prereleases: true
|
||||||
|
@ -97,8 +97,8 @@ jobs:
|
||||||
choco install nasm --no-progress
|
choco install nasm --no-progress
|
||||||
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
choco install ghostscript --version=10.5.1 --no-progress
|
choco install ghostscript --version=10.6.0 --no-progress
|
||||||
echo "C:\Program Files\gs\gs10.05.1\bin" >> $env:GITHUB_PATH
|
echo "C:\Program Files\gs\gs10.06.0\bin" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
# Install extra test images
|
# Install extra test images
|
||||||
xcopy /S /Y Tests\test-images\* Tests\images
|
xcopy /S /Y Tests\test-images\* Tests\images
|
||||||
|
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
@ -65,12 +65,12 @@ jobs:
|
||||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
allow-prereleases: true
|
allow-prereleases: true
|
||||||
|
|
33
.github/workflows/wheels-dependencies.sh
vendored
33
.github/workflows/wheels-dependencies.sh
vendored
|
@ -93,16 +93,20 @@ ARCHIVE_SDIR=pillow-depends-main
|
||||||
# Package versions for fresh source builds. Version numbers with "Patched"
|
# Package versions for fresh source builds. Version numbers with "Patched"
|
||||||
# annotations have a source code patch that is required for some platforms. If
|
# annotations have a source code patch that is required for some platforms. If
|
||||||
# you change those versions, ensure the patch is also updated.
|
# you change those versions, ensure the patch is also updated.
|
||||||
|
if [[ -n "$IOS_SDK" ]]; then
|
||||||
FREETYPE_VERSION=2.13.3
|
FREETYPE_VERSION=2.13.3
|
||||||
HARFBUZZ_VERSION=11.3.3
|
else
|
||||||
|
FREETYPE_VERSION=2.14.1
|
||||||
|
fi
|
||||||
|
HARFBUZZ_VERSION=11.5.0
|
||||||
LIBPNG_VERSION=1.6.50
|
LIBPNG_VERSION=1.6.50
|
||||||
JPEGTURBO_VERSION=3.1.1
|
JPEGTURBO_VERSION=3.1.2
|
||||||
OPENJPEG_VERSION=2.5.3
|
OPENJPEG_VERSION=2.5.4
|
||||||
XZ_VERSION=5.8.1
|
XZ_VERSION=5.8.1
|
||||||
TIFF_VERSION=4.7.0
|
ZSTD_VERSION=1.5.7
|
||||||
|
TIFF_VERSION=4.7.1
|
||||||
LCMS2_VERSION=2.17
|
LCMS2_VERSION=2.17
|
||||||
ZLIB_VERSION=1.3.1
|
ZLIB_NG_VERSION=2.2.5
|
||||||
ZLIB_NG_VERSION=2.2.4
|
|
||||||
LIBWEBP_VERSION=1.6.0
|
LIBWEBP_VERSION=1.6.0
|
||||||
BZIP2_VERSION=1.0.8
|
BZIP2_VERSION=1.0.8
|
||||||
LIBXCB_VERSION=1.17.0
|
LIBXCB_VERSION=1.17.0
|
||||||
|
@ -254,16 +258,20 @@ function build_libavif {
|
||||||
touch libavif-stamp
|
touch libavif-stamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function build_zstd {
|
||||||
|
if [ -e zstd-stamp ]; then return; fi
|
||||||
|
local out_dir=$(fetch_unpack https://github.com/facebook/zstd/releases/download/v$ZSTD_VERSION/zstd-$ZSTD_VERSION.tar.gz)
|
||||||
|
(cd $out_dir \
|
||||||
|
&& make -j4 install)
|
||||||
|
touch zstd-stamp
|
||||||
|
}
|
||||||
|
|
||||||
function build {
|
function build {
|
||||||
build_xz
|
build_xz
|
||||||
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
|
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
|
||||||
yum remove -y zlib-devel
|
yum remove -y zlib-devel
|
||||||
fi
|
fi
|
||||||
if [[ -n "$IS_MACOS" ]] && [[ "$MACOSX_DEPLOYMENT_TARGET" == "10.10" || "$MACOSX_DEPLOYMENT_TARGET" == "10.13" ]]; then
|
|
||||||
build_new_zlib
|
|
||||||
else
|
|
||||||
build_zlib_ng
|
build_zlib_ng
|
||||||
fi
|
|
||||||
|
|
||||||
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
|
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
|
||||||
if [[ -n "$IS_MACOS" ]]; then
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
|
@ -285,6 +293,7 @@ function build {
|
||||||
--with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \
|
--with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \
|
||||||
--disable-webp --disable-libdeflate --disable-zstd
|
--disable-webp --disable-libdeflate --disable-zstd
|
||||||
else
|
else
|
||||||
|
build_zstd
|
||||||
build_tiff
|
build_tiff
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -309,6 +318,10 @@ function build {
|
||||||
|
|
||||||
if [[ -n "$IS_MACOS" ]]; then
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
# Custom freetype build
|
# Custom freetype build
|
||||||
|
if [[ -z "$IOS_SDK" ]]; then
|
||||||
|
build_simple sed 4.9 https://mirrors.middlendian.com/gnu/sed
|
||||||
|
fi
|
||||||
|
|
||||||
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
|
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
|
||||||
else
|
else
|
||||||
build_freetype
|
build_freetype
|
||||||
|
|
18
.github/workflows/wheels.yml
vendored
18
.github/workflows/wheels.yml
vendored
|
@ -54,7 +54,7 @@ jobs:
|
||||||
platform: macos
|
platform: macos
|
||||||
os: macos-13
|
os: macos-13
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
build: "cp3{9,10,11}*"
|
build: "cp3{10,11}*"
|
||||||
macosx_deployment_target: "10.10"
|
macosx_deployment_target: "10.10"
|
||||||
- name: "macOS 10.13 x86_64"
|
- name: "macOS 10.13 x86_64"
|
||||||
platform: macos
|
platform: macos
|
||||||
|
@ -99,19 +99,19 @@ jobs:
|
||||||
cibw_arch: arm64_iphoneos
|
cibw_arch: arm64_iphoneos
|
||||||
- name: "iOS arm64 simulator"
|
- name: "iOS arm64 simulator"
|
||||||
platform: ios
|
platform: ios
|
||||||
os: macos-latest
|
os: macos-14
|
||||||
cibw_arch: arm64_iphonesimulator
|
cibw_arch: arm64_iphonesimulator
|
||||||
- name: "iOS x86_64 simulator"
|
- name: "iOS x86_64 simulator"
|
||||||
platform: ios
|
platform: ios
|
||||||
os: macos-13
|
os: macos-13
|
||||||
cibw_arch: x86_64_iphonesimulator
|
cibw_arch: x86_64_iphonesimulator
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
|
|
||||||
|
@ -153,18 +153,18 @@ jobs:
|
||||||
- cibw_arch: ARM64
|
- cibw_arch: ARM64
|
||||||
os: windows-11-arm
|
os: windows-11-arm
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Checkout extra test images
|
- name: Checkout extra test images
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
repository: python-pillow/test-images
|
repository: python-pillow/test-images
|
||||||
path: Tests\test-images
|
path: Tests\test-images
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
|
|
||||||
|
@ -234,12 +234,12 @@ jobs:
|
||||||
if: github.event_name != 'schedule'
|
if: github.event_name != 'schedule'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.12.7
|
rev: v0.12.11
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
args: [--exit-non-zero-on-fix]
|
args: [--exit-non-zero-on-fix]
|
||||||
|
@ -24,7 +24,7 @@ repos:
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: v20.1.8
|
rev: v21.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
types: [c]
|
types: [c]
|
||||||
|
@ -36,7 +36,7 @@ repos:
|
||||||
- id: rst-backticks
|
- id: rst-backticks
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v5.0.0
|
rev: v6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-executables-have-shebangs
|
- id: check-executables-have-shebangs
|
||||||
- id: check-shebang-scripts-are-executable
|
- id: check-shebang-scripts-are-executable
|
||||||
|
@ -51,14 +51,14 @@ repos:
|
||||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
|
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
|
||||||
|
|
||||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||||
rev: 0.33.2
|
rev: 0.33.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-github-workflows
|
- id: check-github-workflows
|
||||||
- id: check-readthedocs
|
- id: check-readthedocs
|
||||||
- id: check-renovate
|
- id: check-renovate
|
||||||
|
|
||||||
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
||||||
rev: v1.11.0
|
rev: v1.12.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: zizmor
|
- id: zizmor
|
||||||
|
|
||||||
|
|
|
@ -175,6 +175,14 @@ def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
||||||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||||
|
|
||||||
|
|
||||||
|
def has_feature_version(feature: str, required: str) -> bool:
|
||||||
|
version = features.version(feature)
|
||||||
|
assert version is not None
|
||||||
|
version_required = parse_version(required)
|
||||||
|
version_available = parse_version(version)
|
||||||
|
return version_available >= version_required
|
||||||
|
|
||||||
|
|
||||||
def skip_unless_feature_version(
|
def skip_unless_feature_version(
|
||||||
feature: str, required: str, reason: str | None = None
|
feature: str, required: str, reason: str | None = None
|
||||||
) -> pytest.MarkDecorator:
|
) -> pytest.MarkDecorator:
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.5 KiB |
BIN
Tests/images/colr_bungee_older.png
Normal file
BIN
Tests/images/colr_bungee_older.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,8 +1,10 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import GbrImagePlugin, Image
|
from PIL import GbrImagePlugin, Image, _binary
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import assert_image_equal_tofile
|
||||||
|
|
||||||
|
@ -31,8 +33,49 @@ def test_multiple_load_operations() -> None:
|
||||||
assert_image_equal_tofile(im, "Tests/images/gbr.png")
|
assert_image_equal_tofile(im, "Tests/images/gbr.png")
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file() -> None:
|
def create_gbr_image(info: dict[str, int] = {}, magic_number=b"") -> BytesIO:
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
return BytesIO(
|
||||||
|
b"".join(
|
||||||
|
_binary.o32be(i)
|
||||||
|
for i in [
|
||||||
|
info.get("header_size", 20),
|
||||||
|
info.get("version", 1),
|
||||||
|
info.get("width", 1),
|
||||||
|
info.get("height", 1),
|
||||||
|
info.get("color_depth", 1),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
+ magic_number
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(SyntaxError):
|
|
||||||
|
def test_invalid_file() -> None:
|
||||||
|
for f in [
|
||||||
|
create_gbr_image({"header_size": 0}),
|
||||||
|
create_gbr_image({"width": 0}),
|
||||||
|
create_gbr_image({"height": 0}),
|
||||||
|
]:
|
||||||
|
with pytest.raises(SyntaxError, match="not a GIMP brush"):
|
||||||
|
GbrImagePlugin.GbrImageFile(f)
|
||||||
|
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
with pytest.raises(SyntaxError, match="Unsupported GIMP brush version"):
|
||||||
GbrImagePlugin.GbrImageFile(invalid_file)
|
GbrImagePlugin.GbrImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsupported_gimp_brush() -> None:
|
||||||
|
f = create_gbr_image({"color_depth": 2})
|
||||||
|
with pytest.raises(SyntaxError, match="Unsupported GIMP brush color depth: 2"):
|
||||||
|
GbrImagePlugin.GbrImageFile(f)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bad_magic_number() -> None:
|
||||||
|
f = create_gbr_image({"version": 2}, magic_number=b"badm")
|
||||||
|
with pytest.raises(SyntaxError, match="not a GIMP brush, bad magic number"):
|
||||||
|
GbrImagePlugin.GbrImageFile(f)
|
||||||
|
|
||||||
|
|
||||||
|
def test_L() -> None:
|
||||||
|
f = create_gbr_image()
|
||||||
|
with Image.open(f) as im:
|
||||||
|
assert im.mode == "L"
|
||||||
|
|
|
@ -2,6 +2,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags
|
from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
@ -9,21 +11,78 @@ from .helper import assert_image_equal, hopper
|
||||||
TEST_FILE = "Tests/images/iptc.jpg"
|
TEST_FILE = "Tests/images/iptc.jpg"
|
||||||
|
|
||||||
|
|
||||||
|
def create_iptc_image(info: dict[str, int] = {}) -> BytesIO:
|
||||||
|
def field(tag, value):
|
||||||
|
return bytes((0x1C,) + tag + (0, len(value))) + value
|
||||||
|
|
||||||
|
data = field((3, 60), bytes((info.get("layers", 1), info.get("component", 0))))
|
||||||
|
data += field((3, 120), bytes((info.get("compression", 1),)))
|
||||||
|
if "band" in info:
|
||||||
|
data += field((3, 65), bytes((info["band"] + 1,)))
|
||||||
|
data += field((3, 20), b"\x01") # width
|
||||||
|
data += field((3, 30), b"\x01") # height
|
||||||
|
data += field(
|
||||||
|
(8, 10),
|
||||||
|
bytes((info.get("data", 0),)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return BytesIO(data)
|
||||||
|
|
||||||
|
|
||||||
def test_open() -> None:
|
def test_open() -> None:
|
||||||
expected = Image.new("L", (1, 1))
|
expected = Image.new("L", (1, 1))
|
||||||
|
|
||||||
f = BytesIO(
|
f = create_iptc_image()
|
||||||
b"\x1c\x03<\x00\x02\x01\x00\x1c\x03x\x00\x01\x01\x1c\x03\x14\x00\x01\x01"
|
|
||||||
b"\x1c\x03\x1e\x00\x01\x01\x1c\x08\n\x00\x01\x00"
|
|
||||||
)
|
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")]
|
assert im.tile == [("iptc", (0, 0, 1, 1), 25, ("raw", None))]
|
||||||
assert_image_equal(im, expected)
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
assert im.load() is not None
|
assert im.load() is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_field_length() -> None:
|
||||||
|
f = create_iptc_image()
|
||||||
|
f.seek(28)
|
||||||
|
f.write(b"\xff")
|
||||||
|
with pytest.raises(OSError, match="illegal field length in IPTC/NAA file"):
|
||||||
|
with Image.open(f):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("layers, mode", ((3, "RGB"), (4, "CMYK")))
|
||||||
|
def test_layers(layers: int, mode: str) -> None:
|
||||||
|
for band in range(-1, layers):
|
||||||
|
info = {"layers": layers, "component": 1, "data": 5}
|
||||||
|
if band != -1:
|
||||||
|
info["band"] = band
|
||||||
|
f = create_iptc_image(info)
|
||||||
|
with Image.open(f) as im:
|
||||||
|
assert im.mode == mode
|
||||||
|
|
||||||
|
data = [0] * layers
|
||||||
|
data[max(band, 0)] = 5
|
||||||
|
assert im.getpixel((0, 0)) == tuple(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_compression() -> None:
|
||||||
|
f = create_iptc_image({"compression": 2})
|
||||||
|
with pytest.raises(OSError, match="Unknown IPTC image compression"):
|
||||||
|
with Image.open(f):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_getiptcinfo() -> None:
|
||||||
|
f = create_iptc_image()
|
||||||
|
with Image.open(f) as im:
|
||||||
|
assert IptcImagePlugin.getiptcinfo(im) == {
|
||||||
|
(3, 60): b"\x01\x00",
|
||||||
|
(3, 120): b"\x01",
|
||||||
|
(3, 20): b"\x01",
|
||||||
|
(3, 30): b"\x01",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_getiptcinfo_jpg_none() -> None:
|
def test_getiptcinfo_jpg_none() -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
with hopper() as im:
|
with hopper() as im:
|
||||||
|
|
|
@ -365,7 +365,6 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
|
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
|
||||||
if 700 in reloaded.tag_v2:
|
|
||||||
assert reloaded.tag_v2[700] == b"xmlpacket tag"
|
assert reloaded.tag_v2[700] == b"xmlpacket tag"
|
||||||
|
|
||||||
def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
def test_load_raw() -> None:
|
def test_load_raw() -> None:
|
||||||
with Image.open("Tests/images/hopper.pcd") as im:
|
with Image.open("Tests/images/hopper.pcd") as im:
|
||||||
|
assert im.size == (768, 512)
|
||||||
im.load() # should not segfault.
|
im.load() # should not segfault.
|
||||||
|
|
||||||
# Note that this image was created with a resized hopper
|
# Note that this image was created with a resized hopper
|
||||||
|
@ -15,3 +20,13 @@ def test_load_raw() -> None:
|
||||||
|
|
||||||
# target = hopper().resize((768,512))
|
# target = hopper().resize((768,512))
|
||||||
# assert_image_similar(im, target, 10)
|
# assert_image_similar(im, target, 10)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("orientation", (1, 3))
|
||||||
|
def test_rotated(orientation: int) -> None:
|
||||||
|
with open("Tests/images/hopper.pcd", "rb") as fp:
|
||||||
|
data = bytearray(fp.read())
|
||||||
|
data[2048 + 1538] = orientation
|
||||||
|
f = BytesIO(data)
|
||||||
|
with Image.open(f) as im:
|
||||||
|
assert im.size == (512, 768)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import WalImageFile
|
from PIL import WalImageFile
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import assert_image_equal_tofile
|
||||||
|
@ -13,12 +15,22 @@ def test_open() -> None:
|
||||||
assert im.format_description == "Quake2 Texture"
|
assert im.format_description == "Quake2 Texture"
|
||||||
assert im.mode == "P"
|
assert im.mode == "P"
|
||||||
assert im.size == (128, 128)
|
assert im.size == (128, 128)
|
||||||
|
assert "next_name" not in im.info
|
||||||
|
|
||||||
assert isinstance(im, WalImageFile.WalImageFile)
|
assert isinstance(im, WalImageFile.WalImageFile)
|
||||||
|
|
||||||
assert_image_equal_tofile(im, "Tests/images/hopper_wal.png")
|
assert_image_equal_tofile(im, "Tests/images/hopper_wal.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_next_name() -> None:
|
||||||
|
with open(TEST_FILE, "rb") as fp:
|
||||||
|
data = bytearray(fp.read())
|
||||||
|
data[56:60] = b"Test"
|
||||||
|
f = BytesIO(data)
|
||||||
|
with WalImageFile.open(f) as im:
|
||||||
|
assert im.info["next_name"] == b"Test"
|
||||||
|
|
||||||
|
|
||||||
def test_load() -> None:
|
def test_load() -> None:
|
||||||
with WalImageFile.open(TEST_FILE) as im:
|
with WalImageFile.open(TEST_FILE) as im:
|
||||||
px = im.load()
|
px = im.load()
|
||||||
|
|
|
@ -4,13 +4,13 @@ from collections.abc import Generator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
|
||||||
|
|
||||||
from PIL import GifImagePlugin, Image, WebPImagePlugin, features
|
from PIL import GifImagePlugin, Image, WebPImagePlugin
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
|
has_feature_version,
|
||||||
is_big_endian,
|
is_big_endian,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
)
|
)
|
||||||
|
@ -53,10 +53,7 @@ def test_write_animation_L(tmp_path: Path) -> None:
|
||||||
im.load()
|
im.load()
|
||||||
assert_image_similar(im, orig.convert("RGBA"), 32.9)
|
assert_image_similar(im, orig.convert("RGBA"), 32.9)
|
||||||
|
|
||||||
if is_big_endian():
|
if is_big_endian() and not has_feature_version("webp", "1.2.2"):
|
||||||
version = features.version_module("webp")
|
|
||||||
assert version is not None
|
|
||||||
if parse_version(version) < parse_version("1.2.2"):
|
|
||||||
pytest.skip("Fails with libwebp earlier than 1.2.2")
|
pytest.skip("Fails with libwebp earlier than 1.2.2")
|
||||||
orig.seek(orig.n_frames - 1)
|
orig.seek(orig.n_frames - 1)
|
||||||
im.seek(im.n_frames - 1)
|
im.seek(im.n_frames - 1)
|
||||||
|
@ -81,10 +78,7 @@ def test_write_animation_RGB(tmp_path: Path) -> None:
|
||||||
assert_image_equal(im, frame1.convert("RGBA"))
|
assert_image_equal(im, frame1.convert("RGBA"))
|
||||||
|
|
||||||
# Compare second frame to original
|
# Compare second frame to original
|
||||||
if is_big_endian():
|
if is_big_endian() and not has_feature_version("webp", "1.2.2"):
|
||||||
version = features.version_module("webp")
|
|
||||||
assert version is not None
|
|
||||||
if parse_version(version) < parse_version("1.2.2"):
|
|
||||||
pytest.skip("Fails with libwebp earlier than 1.2.2")
|
pytest.skip("Fails with libwebp earlier than 1.2.2")
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
|
@ -9,7 +9,8 @@ from .helper import skip_unless_feature
|
||||||
|
|
||||||
class TestFontCrash:
|
class TestFontCrash:
|
||||||
def _fuzz_font(self, font: ImageFont.FreeTypeFont) -> None:
|
def _fuzz_font(self, font: ImageFont.FreeTypeFont) -> None:
|
||||||
# from fuzzers.fuzz_font
|
# Copy of the code from fuzz_font() in Tests/oss-fuzz/fuzzers.py
|
||||||
|
# that triggered a problem when fuzzing
|
||||||
font.getbbox("ABC")
|
font.getbbox("ABC")
|
||||||
font.getmask("test text")
|
font.getmask("test text")
|
||||||
with Image.new(mode="RGBA", size=(200, 200)) as im:
|
with Image.new(mode="RGBA", size=(200, 200)) as im:
|
||||||
|
|
|
@ -19,6 +19,7 @@ from PIL import (
|
||||||
ImageDraw,
|
ImageDraw,
|
||||||
ImageFile,
|
ImageFile,
|
||||||
ImagePalette,
|
ImagePalette,
|
||||||
|
ImageShow,
|
||||||
UnidentifiedImageError,
|
UnidentifiedImageError,
|
||||||
features,
|
features,
|
||||||
)
|
)
|
||||||
|
@ -283,33 +284,6 @@ class TestImage:
|
||||||
assert item is not None
|
assert item is not None
|
||||||
assert item != num
|
assert item != num
|
||||||
|
|
||||||
def test_expand_x(self) -> None:
|
|
||||||
# Arrange
|
|
||||||
im = hopper()
|
|
||||||
orig_size = im.size
|
|
||||||
xmargin = 5
|
|
||||||
|
|
||||||
# Act
|
|
||||||
im = im._expand(xmargin)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert im.size[0] == orig_size[0] + 2 * xmargin
|
|
||||||
assert im.size[1] == orig_size[1] + 2 * xmargin
|
|
||||||
|
|
||||||
def test_expand_xy(self) -> None:
|
|
||||||
# Arrange
|
|
||||||
im = hopper()
|
|
||||||
orig_size = im.size
|
|
||||||
xmargin = 5
|
|
||||||
ymargin = 3
|
|
||||||
|
|
||||||
# Act
|
|
||||||
im = im._expand(xmargin, ymargin)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert im.size[0] == orig_size[0] + 2 * xmargin
|
|
||||||
assert im.size[1] == orig_size[1] + 2 * ymargin
|
|
||||||
|
|
||||||
def test_getbands(self) -> None:
|
def test_getbands(self) -> None:
|
||||||
# Assert
|
# Assert
|
||||||
assert hopper("RGB").getbands() == ("R", "G", "B")
|
assert hopper("RGB").getbands() == ("R", "G", "B")
|
||||||
|
@ -388,6 +362,37 @@ class TestImage:
|
||||||
assert img_colors is not None
|
assert img_colors is not None
|
||||||
assert sorted(img_colors) == expected_colors
|
assert sorted(img_colors) == expected_colors
|
||||||
|
|
||||||
|
def test_alpha_composite_la(self) -> None:
|
||||||
|
# Arrange
|
||||||
|
expected_colors = sorted(
|
||||||
|
[
|
||||||
|
(3300, (255, 255)),
|
||||||
|
(1156, (170, 192)),
|
||||||
|
(1122, (128, 255)),
|
||||||
|
(1089, (0, 0)),
|
||||||
|
(1122, (255, 128)),
|
||||||
|
(1122, (0, 128)),
|
||||||
|
(1089, (0, 255)),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
dst = Image.new("LA", size=(100, 100), color=(0, 255))
|
||||||
|
draw = ImageDraw.Draw(dst)
|
||||||
|
draw.rectangle((0, 33, 100, 66), fill=(0, 128))
|
||||||
|
draw.rectangle((0, 67, 100, 100), fill=(0, 0))
|
||||||
|
src = Image.new("LA", size=(100, 100), color=(255, 255))
|
||||||
|
draw = ImageDraw.Draw(src)
|
||||||
|
draw.rectangle((33, 0, 66, 100), fill=(255, 128))
|
||||||
|
draw.rectangle((67, 0, 100, 100), fill=(255, 0))
|
||||||
|
|
||||||
|
# Act
|
||||||
|
img = Image.alpha_composite(dst, src)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
img_colors = img.getcolors()
|
||||||
|
assert img_colors is not None
|
||||||
|
assert sorted(img_colors) == expected_colors
|
||||||
|
|
||||||
def test_alpha_inplace(self) -> None:
|
def test_alpha_inplace(self) -> None:
|
||||||
src = Image.new("RGBA", (128, 128), "blue")
|
src = Image.new("RGBA", (128, 128), "blue")
|
||||||
|
|
||||||
|
@ -922,6 +927,17 @@ class TestImage:
|
||||||
reloaded_exif.load(exif.tobytes())
|
reloaded_exif.load(exif.tobytes())
|
||||||
assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769)
|
assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769)
|
||||||
|
|
||||||
|
def test_delete_ifd_tag(self) -> None:
|
||||||
|
with Image.open("Tests/images/flower.jpg") as im:
|
||||||
|
exif = im.getexif()
|
||||||
|
exif.get_ifd(0x8769)
|
||||||
|
assert 0x8769 in exif
|
||||||
|
del exif[0x8769]
|
||||||
|
|
||||||
|
reloaded_exif = Image.Exif()
|
||||||
|
reloaded_exif.load(exif.tobytes())
|
||||||
|
assert 0x8769 not in reloaded_exif
|
||||||
|
|
||||||
def test_exif_load_from_fp(self) -> None:
|
def test_exif_load_from_fp(self) -> None:
|
||||||
with Image.open("Tests/images/flower.jpg") as im:
|
with Image.open("Tests/images/flower.jpg") as im:
|
||||||
data = im.info["exif"]
|
data = im.info["exif"]
|
||||||
|
@ -1005,6 +1021,13 @@ class TestImage:
|
||||||
with pytest.warns(DeprecationWarning, match="Image.Image.get_child_images"):
|
with pytest.warns(DeprecationWarning, match="Image.Image.get_child_images"):
|
||||||
assert im.get_child_images() == []
|
assert im.get_child_images() == []
|
||||||
|
|
||||||
|
def test_show(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setattr(ImageShow, "_viewers", [])
|
||||||
|
|
||||||
|
im = Image.new("RGB", (1, 1))
|
||||||
|
with pytest.warns(DeprecationWarning, match="Image._show"):
|
||||||
|
Image._show(im)
|
||||||
|
|
||||||
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
|
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
|
||||||
def test_zero_tobytes(self, size: tuple[int, int]) -> None:
|
def test_zero_tobytes(self, size: tuple[int, int]) -> None:
|
||||||
im = Image.new("RGB", size)
|
im = Image.new("RGB", size)
|
||||||
|
|
|
@ -101,8 +101,7 @@ def test_fromarray_strides_without_tobytes() -> None:
|
||||||
self.__array_interface__ = arr_params
|
self.__array_interface__ = arr_params
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)})
|
wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1), "typestr": "|u1"})
|
||||||
with pytest.warns(DeprecationWarning, match="'mode' parameter"):
|
|
||||||
Image.fromarray(wrapped, "L")
|
Image.fromarray(wrapped, "L")
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,9 +111,16 @@ def test_fromarray_palette() -> None:
|
||||||
a = numpy.array(i)
|
a = numpy.array(i)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
with pytest.warns(DeprecationWarning, match="'mode' parameter"):
|
|
||||||
out = Image.fromarray(a, "P")
|
out = Image.fromarray(a, "P")
|
||||||
|
|
||||||
# Assert that the Python and C palettes match
|
# Assert that the Python and C palettes match
|
||||||
assert out.palette is not None
|
assert out.palette is not None
|
||||||
assert len(out.palette.colors) == len(out.im.getpalette()) / 3
|
assert len(out.palette.colors) == len(out.im.getpalette()) / 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_deprecation() -> None:
|
||||||
|
a = numpy.array(im.convert("L"))
|
||||||
|
with pytest.warns(
|
||||||
|
DeprecationWarning, match="'mode' parameter for changing data types"
|
||||||
|
):
|
||||||
|
Image.fromarray(a, "1")
|
||||||
|
|
|
@ -97,6 +97,13 @@ def test_opaque() -> None:
|
||||||
assert_image_equal(alpha, solid)
|
assert_image_equal(alpha, solid)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rgba() -> None:
|
||||||
|
with Image.open("Tests/images/transparent.png") as im:
|
||||||
|
assert im.mode == "RGBA"
|
||||||
|
|
||||||
|
assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
|
||||||
|
|
||||||
|
|
||||||
def test_rgba_p() -> None:
|
def test_rgba_p() -> None:
|
||||||
im = hopper("RGBA")
|
im = hopper("RGBA")
|
||||||
im.putalpha(hopper("L"))
|
im.putalpha(hopper("L"))
|
||||||
|
@ -114,11 +121,12 @@ def test_rgba_pa() -> None:
|
||||||
assert_image_similar(im, expected, 9.3)
|
assert_image_similar(im, expected, 9.3)
|
||||||
|
|
||||||
|
|
||||||
def test_rgba() -> None:
|
def test_pa() -> None:
|
||||||
with Image.open("Tests/images/transparent.png") as im:
|
im = hopper().convert("PA")
|
||||||
assert im.mode == "RGBA"
|
|
||||||
|
|
||||||
assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
|
palette = im.palette
|
||||||
|
assert palette is not None
|
||||||
|
assert palette.colors != {}
|
||||||
|
|
||||||
|
|
||||||
def test_trns_p(tmp_path: Path) -> None:
|
def test_trns_p(tmp_path: Path) -> None:
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
|
||||||
|
|
||||||
from PIL import Image, features
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import assert_image_similar, hopper, is_ppc64le, skip_unless_feature
|
from .helper import (
|
||||||
|
assert_image_similar,
|
||||||
|
has_feature_version,
|
||||||
|
hopper,
|
||||||
|
is_ppc64le,
|
||||||
|
skip_unless_feature,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_sanity() -> None:
|
def test_sanity() -> None:
|
||||||
|
@ -23,10 +28,7 @@ def test_sanity() -> None:
|
||||||
@skip_unless_feature("libimagequant")
|
@skip_unless_feature("libimagequant")
|
||||||
def test_libimagequant_quantize() -> None:
|
def test_libimagequant_quantize() -> None:
|
||||||
image = hopper()
|
image = hopper()
|
||||||
if is_ppc64le():
|
if is_ppc64le() and not has_feature_version("libimagequant", "4"):
|
||||||
version = features.version_feature("libimagequant")
|
|
||||||
assert version is not None
|
|
||||||
if parse_version(version) < parse_version("4"):
|
|
||||||
pytest.skip("Fails with libimagequant earlier than 4.0.0 on ppc64le")
|
pytest.skip("Fails with libimagequant earlier than 4.0.0 on ppc64le")
|
||||||
converted = image.quantize(100, Image.Quantize.LIBIMAGEQUANT)
|
converted = image.quantize(100, Image.Quantize.LIBIMAGEQUANT)
|
||||||
assert converted.mode == "P"
|
assert converted.mode == "P"
|
||||||
|
|
|
@ -1494,7 +1494,9 @@ def test_default_font_size() -> None:
|
||||||
|
|
||||||
def draw_text() -> None:
|
def draw_text() -> None:
|
||||||
draw.text((0, 0), text, font_size=16)
|
draw.text((0, 0), text, font_size=16)
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
|
assert_image_similar_tofile(
|
||||||
|
im, "Tests/images/imagedraw_default_font_size.png", 1
|
||||||
|
)
|
||||||
|
|
||||||
check(draw_text)
|
check(draw_text)
|
||||||
|
|
||||||
|
@ -1513,7 +1515,9 @@ def test_default_font_size() -> None:
|
||||||
|
|
||||||
def draw_multiline_text() -> None:
|
def draw_multiline_text() -> None:
|
||||||
draw.multiline_text((0, 0), text, font_size=16)
|
draw.multiline_text((0, 0), text, font_size=16)
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
|
assert_image_similar_tofile(
|
||||||
|
im, "Tests/images/imagedraw_default_font_size.png", 1
|
||||||
|
)
|
||||||
|
|
||||||
check(draw_multiline_text)
|
check(draw_multiline_text)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
assert_image_equal_tofile,
|
assert_image_equal_tofile,
|
||||||
assert_image_similar_tofile,
|
assert_image_similar_tofile,
|
||||||
|
has_feature_version,
|
||||||
is_win32,
|
is_win32,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
skip_unless_feature_version,
|
skip_unless_feature_version,
|
||||||
|
@ -492,6 +493,11 @@ def test_stroke_mask() -> None:
|
||||||
assert mask.getpixel((42, 5)) == 255
|
assert mask.getpixel((42, 5)) == 255
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_invalid_file() -> None:
|
||||||
|
with pytest.raises(SyntaxError, match="Not a PILfont file"):
|
||||||
|
ImageFont.load("Tests/images/1_trns.png")
|
||||||
|
|
||||||
|
|
||||||
def test_load_when_image_not_found() -> None:
|
def test_load_when_image_not_found() -> None:
|
||||||
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
||||||
pass
|
pass
|
||||||
|
@ -549,7 +555,7 @@ def test_default_font() -> None:
|
||||||
draw.text((10, 60), txt, font=larger_default_font)
|
draw.text((10, 60), txt, font=larger_default_font)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, "Tests/images/default_font_freetype.png")
|
assert_image_similar_tofile(im, "Tests/images/default_font_freetype.png", 0.13)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("", "1", "RGBA"))
|
@pytest.mark.parametrize("mode", ("", "1", "RGBA"))
|
||||||
|
@ -1055,7 +1061,10 @@ def test_colr(layout_engine: ImageFont.Layout) -> None:
|
||||||
|
|
||||||
d.text((15, 5), "Bungee", font=font, embedded_color=True)
|
d.text((15, 5), "Bungee", font=font, embedded_color=True)
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21)
|
if has_feature_version("freetype2", "2.14.0"):
|
||||||
|
assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 6.1)
|
||||||
|
else:
|
||||||
|
assert_image_similar_tofile(im, "Tests/images/colr_bungee_older.png", 21)
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature_version("freetype2", "2.10.0")
|
@skip_unless_feature_version("freetype2", "2.10.0")
|
||||||
|
@ -1071,7 +1080,7 @@ def test_colr_mask(layout_engine: ImageFont.Layout) -> None:
|
||||||
|
|
||||||
d.text((15, 5), "Bungee", "black", font=font)
|
d.text((15, 5), "Bungee", "black", font=font)
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22)
|
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 14.1)
|
||||||
|
|
||||||
|
|
||||||
def test_woff2(layout_engine: ImageFont.Layout) -> None:
|
def test_woff2(layout_engine: ImageFont.Layout) -> None:
|
||||||
|
|
|
@ -7,6 +7,7 @@ from PIL import Image, ImageDraw, ImageFont
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal_tofile,
|
assert_image_equal_tofile,
|
||||||
assert_image_similar_tofile,
|
assert_image_similar_tofile,
|
||||||
|
has_feature_version,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -104,11 +105,9 @@ def test_text_direction_ttb() -> None:
|
||||||
|
|
||||||
im = Image.new(mode="RGB", size=(100, 300))
|
im = Image.new(mode="RGB", size=(100, 300))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
try:
|
if not has_feature_version("raqm", "0.7"):
|
||||||
draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb")
|
|
||||||
except ValueError as ex:
|
|
||||||
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
|
|
||||||
pytest.skip("libraqm 0.7 or greater not available")
|
pytest.skip("libraqm 0.7 or greater not available")
|
||||||
|
draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb")
|
||||||
|
|
||||||
target = "Tests/images/test_direction_ttb.png"
|
target = "Tests/images/test_direction_ttb.png"
|
||||||
assert_image_similar_tofile(im, target, 2.8)
|
assert_image_similar_tofile(im, target, 2.8)
|
||||||
|
@ -119,7 +118,8 @@ def test_text_direction_ttb_stroke() -> None:
|
||||||
|
|
||||||
im = Image.new(mode="RGB", size=(100, 300))
|
im = Image.new(mode="RGB", size=(100, 300))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
try:
|
if not has_feature_version("raqm", "0.7"):
|
||||||
|
pytest.skip("libraqm 0.7 or greater not available")
|
||||||
draw.text(
|
draw.text(
|
||||||
(27, 27),
|
(27, 27),
|
||||||
"あい",
|
"あい",
|
||||||
|
@ -129,9 +129,6 @@ def test_text_direction_ttb_stroke() -> None:
|
||||||
stroke_width=2,
|
stroke_width=2,
|
||||||
stroke_fill="#0f0",
|
stroke_fill="#0f0",
|
||||||
)
|
)
|
||||||
except ValueError as ex:
|
|
||||||
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
|
|
||||||
pytest.skip("libraqm 0.7 or greater not available")
|
|
||||||
|
|
||||||
target = "Tests/images/test_direction_ttb_stroke.png"
|
target = "Tests/images/test_direction_ttb_stroke.png"
|
||||||
assert_image_similar_tofile(im, target, 19.4)
|
assert_image_similar_tofile(im, target, 19.4)
|
||||||
|
@ -186,7 +183,7 @@ def test_x_max_and_y_offset() -> None:
|
||||||
draw.text((0, 0), "لح", font=ttf, fill=500)
|
draw.text((0, 0), "لح", font=ttf, fill=500)
|
||||||
|
|
||||||
target = "Tests/images/test_x_max_and_y_offset.png"
|
target = "Tests/images/test_x_max_and_y_offset.png"
|
||||||
assert_image_similar_tofile(im, target, 0.5)
|
assert_image_similar_tofile(im, target, 3.8)
|
||||||
|
|
||||||
|
|
||||||
def test_language() -> None:
|
def test_language() -> None:
|
||||||
|
@ -219,14 +216,9 @@ def test_getlength(
|
||||||
im = Image.new(mode, (1, 1), 0)
|
im = Image.new(mode, (1, 1), 0)
|
||||||
d = ImageDraw.Draw(im)
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
try:
|
if direction == "ttb" and not has_feature_version("raqm", "0.7"):
|
||||||
assert d.textlength(text, ttf, direction) == expected
|
|
||||||
except ValueError as ex:
|
|
||||||
if (
|
|
||||||
direction == "ttb"
|
|
||||||
and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction"
|
|
||||||
):
|
|
||||||
pytest.skip("libraqm 0.7 or greater not available")
|
pytest.skip("libraqm 0.7 or greater not available")
|
||||||
|
assert d.textlength(text, ttf, direction) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("L", "1"))
|
@pytest.mark.parametrize("mode", ("L", "1"))
|
||||||
|
@ -242,17 +234,12 @@ def test_getlength_combine(mode: str, direction: str, text: str) -> None:
|
||||||
|
|
||||||
ttf = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
|
ttf = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
|
||||||
|
|
||||||
try:
|
if direction == "ttb" and not has_feature_version("raqm", "0.7"):
|
||||||
|
pytest.skip("libraqm 0.7 or greater not available")
|
||||||
target = ttf.getlength("ii", mode, direction)
|
target = ttf.getlength("ii", mode, direction)
|
||||||
actual = ttf.getlength(text, mode, direction)
|
actual = ttf.getlength(text, mode, direction)
|
||||||
|
|
||||||
assert actual == target
|
assert actual == target
|
||||||
except ValueError as ex:
|
|
||||||
if (
|
|
||||||
direction == "ttb"
|
|
||||||
and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction"
|
|
||||||
):
|
|
||||||
pytest.skip("libraqm 0.7 or greater not available")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm"))
|
@pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm"))
|
||||||
|
@ -265,11 +252,9 @@ def test_anchor_ttb(anchor: str) -> None:
|
||||||
d = ImageDraw.Draw(im)
|
d = ImageDraw.Draw(im)
|
||||||
d.line(((0, 200), (200, 200)), "gray")
|
d.line(((0, 200), (200, 200)), "gray")
|
||||||
d.line(((100, 0), (100, 400)), "gray")
|
d.line(((100, 0), (100, 400)), "gray")
|
||||||
try:
|
if not has_feature_version("raqm", "0.7"):
|
||||||
d.text((100, 200), text, fill="black", anchor=anchor, direction="ttb", font=f)
|
|
||||||
except ValueError as ex:
|
|
||||||
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
|
|
||||||
pytest.skip("libraqm 0.7 or greater not available")
|
pytest.skip("libraqm 0.7 or greater not available")
|
||||||
|
d.text((100, 200), text, fill="black", anchor=anchor, direction="ttb", font=f)
|
||||||
|
|
||||||
assert_image_similar_tofile(im, path, 1) # fails at 5
|
assert_image_similar_tofile(im, path, 1) # fails at 5
|
||||||
|
|
||||||
|
@ -310,10 +295,12 @@ combine_tests = (
|
||||||
|
|
||||||
# this tests various combining characters for anchor alignment and clipping
|
# this tests various combining characters for anchor alignment and clipping
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests]
|
"name, text, anchor, direction, epsilon",
|
||||||
|
combine_tests,
|
||||||
|
ids=[r[0] for r in combine_tests],
|
||||||
)
|
)
|
||||||
def test_combine(
|
def test_combine(
|
||||||
name: str, text: str, dir: str | None, anchor: str | None, epsilon: float
|
name: str, text: str, direction: str | None, anchor: str | None, epsilon: float
|
||||||
) -> None:
|
) -> None:
|
||||||
path = f"Tests/images/test_combine_{name}.png"
|
path = f"Tests/images/test_combine_{name}.png"
|
||||||
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
|
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
|
||||||
|
@ -322,11 +309,9 @@ def test_combine(
|
||||||
d = ImageDraw.Draw(im)
|
d = ImageDraw.Draw(im)
|
||||||
d.line(((0, 200), (400, 200)), "gray")
|
d.line(((0, 200), (400, 200)), "gray")
|
||||||
d.line(((200, 0), (200, 400)), "gray")
|
d.line(((200, 0), (200, 400)), "gray")
|
||||||
try:
|
if direction == "ttb" and not has_feature_version("raqm", "0.7"):
|
||||||
d.text((200, 200), text, fill="black", anchor=anchor, direction=dir, font=f)
|
|
||||||
except ValueError as ex:
|
|
||||||
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
|
|
||||||
pytest.skip("libraqm 0.7 or greater not available")
|
pytest.skip("libraqm 0.7 or greater not available")
|
||||||
|
d.text((200, 200), text, fill="black", anchor=anchor, direction=direction, font=f)
|
||||||
|
|
||||||
assert_image_similar_tofile(im, path, epsilon)
|
assert_image_similar_tofile(im, path, epsilon)
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,14 @@ def test_default_font(font: ImageFont.ImageFont) -> None:
|
||||||
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_mode() -> None:
|
||||||
|
font = ImageFont.ImageFont()
|
||||||
|
fp = BytesIO()
|
||||||
|
with Image.open("Tests/images/hopper.png") as im:
|
||||||
|
with pytest.raises(TypeError, match="invalid font image mode"):
|
||||||
|
font._load_pilfont_data(fp, im)
|
||||||
|
|
||||||
|
|
||||||
def test_without_freetype() -> None:
|
def test_without_freetype() -> None:
|
||||||
original_core = ImageFont.core
|
original_core = ImageFont.core
|
||||||
if features.check_module("freetype2"):
|
if features.check_module("freetype2"):
|
||||||
|
|
|
@ -7,7 +7,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageMorph, _imagingmorph
|
from PIL import Image, ImageMorph, _imagingmorph
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile, hopper
|
from .helper import assert_image_equal_tofile, hopper, timeout_unless_slower_valgrind
|
||||||
|
|
||||||
|
|
||||||
def string_to_img(image_string: str) -> Image.Image:
|
def string_to_img(image_string: str) -> Image.Image:
|
||||||
|
@ -266,16 +266,18 @@ def test_unknown_pattern() -> None:
|
||||||
ImageMorph.LutBuilder(op_name="unknown")
|
ImageMorph.LutBuilder(op_name="unknown")
|
||||||
|
|
||||||
|
|
||||||
def test_pattern_syntax_error() -> None:
|
@pytest.mark.parametrize(
|
||||||
|
"pattern", ("a pattern with a syntax error", "4:(" + "X" * 30000)
|
||||||
|
)
|
||||||
|
@timeout_unless_slower_valgrind(1)
|
||||||
|
def test_pattern_syntax_error(pattern: str) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
lb = ImageMorph.LutBuilder(op_name="corner")
|
lb = ImageMorph.LutBuilder(op_name="corner")
|
||||||
new_patterns = ["a pattern with a syntax error"]
|
new_patterns = [pattern]
|
||||||
lb.add_patterns(new_patterns)
|
lb.add_patterns(new_patterns)
|
||||||
|
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
with pytest.raises(
|
with pytest.raises(Exception, match='Syntax error in pattern "'):
|
||||||
Exception, match='Syntax error in pattern "a pattern with a syntax error"'
|
|
||||||
):
|
|
||||||
lb.build_lut()
|
lb.build_lut()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -59,15 +59,12 @@ def test_show(mode: str) -> None:
|
||||||
assert ImageShow.show(im)
|
assert ImageShow.show(im)
|
||||||
|
|
||||||
|
|
||||||
def test_show_without_viewers() -> None:
|
def test_show_without_viewers(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
viewers = ImageShow._viewers
|
monkeypatch.setattr(ImageShow, "_viewers", [])
|
||||||
ImageShow._viewers = []
|
|
||||||
|
|
||||||
with hopper() as im:
|
with hopper() as im:
|
||||||
assert not ImageShow.show(im)
|
assert not ImageShow.show(im)
|
||||||
|
|
||||||
ImageShow._viewers = viewers
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"viewer",
|
"viewer",
|
||||||
|
|
|
@ -57,3 +57,13 @@ def test_constant() -> None:
|
||||||
assert st.rms[0] == 128
|
assert st.rms[0] == 128
|
||||||
assert st.var[0] == 0
|
assert st.var[0] == 0
|
||||||
assert st.stddev[0] == 0
|
assert st.stddev[0] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_zero_count() -> None:
|
||||||
|
im = Image.new("L", (0, 0))
|
||||||
|
|
||||||
|
st = ImageStat.Stat(im)
|
||||||
|
|
||||||
|
assert st.mean == [0]
|
||||||
|
assert st.rms == [0]
|
||||||
|
assert st.var == [0]
|
||||||
|
|
|
@ -28,15 +28,13 @@ def test_numpy_to_image() -> None:
|
||||||
a = numpy.array(data, dtype=dtype)
|
a = numpy.array(data, dtype=dtype)
|
||||||
a.shape = TEST_IMAGE_SIZE
|
a.shape = TEST_IMAGE_SIZE
|
||||||
i = Image.fromarray(a)
|
i = Image.fromarray(a)
|
||||||
if list(i.getdata()) != data:
|
assert list(i.getdata()) == data
|
||||||
print("data mismatch for", dtype)
|
|
||||||
else:
|
else:
|
||||||
data = list(range(100))
|
data = list(range(100))
|
||||||
a = numpy.array([[x] * bands for x in data], dtype=dtype)
|
a = numpy.array([[x] * bands for x in data], dtype=dtype)
|
||||||
a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands
|
a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands
|
||||||
i = Image.fromarray(a)
|
i = Image.fromarray(a)
|
||||||
if list(i.getchannel(0).getdata()) != list(range(100)):
|
assert list(i.getchannel(0).getdata()) == list(range(100))
|
||||||
print("data mismatch for", dtype)
|
|
||||||
return i
|
return i
|
||||||
|
|
||||||
# Check supported 1-bit integer formats
|
# Check supported 1-bit integer formats
|
||||||
|
|
|
@ -9,7 +9,7 @@ from PIL import __version__
|
||||||
pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed")
|
pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed")
|
||||||
|
|
||||||
|
|
||||||
def map_metadata_keys(metadata):
|
def map_metadata_keys(md):
|
||||||
# Convert installed wheel metadata into canonical Core Metadata 2.4 format.
|
# Convert installed wheel metadata into canonical Core Metadata 2.4 format.
|
||||||
# This was a utility method in pyroma 4.3.3; it was removed in 5.0.
|
# This was a utility method in pyroma 4.3.3; it was removed in 5.0.
|
||||||
# This implementation is constructed from the relevant logic from
|
# This implementation is constructed from the relevant logic from
|
||||||
|
@ -17,8 +17,8 @@ def map_metadata_keys(metadata):
|
||||||
# upstream to Pyroma as https://github.com/regebro/pyroma/pull/116,
|
# upstream to Pyroma as https://github.com/regebro/pyroma/pull/116,
|
||||||
# so it may be possible to simplify this test in future.
|
# so it may be possible to simplify this test in future.
|
||||||
data = {}
|
data = {}
|
||||||
for key in set(metadata.keys()):
|
for key in set(md.keys()):
|
||||||
value = metadata.get_all(key)
|
value = md.get_all(key)
|
||||||
key = pyroma.projectdata.normalize(key)
|
key = pyroma.projectdata.normalize(key)
|
||||||
|
|
||||||
if len(value) == 1:
|
if len(value) == 1:
|
||||||
|
|
|
@ -4,7 +4,6 @@ import platform
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PIL import features
|
from PIL import features
|
||||||
from Tests.helper import is_pypy
|
|
||||||
|
|
||||||
|
|
||||||
def test_wheel_modules() -> None:
|
def test_wheel_modules() -> None:
|
||||||
|
@ -48,8 +47,6 @@ def test_wheel_features() -> None:
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
expected_features.remove("xcb")
|
expected_features.remove("xcb")
|
||||||
elif sys.platform == "darwin" and not is_pypy() and platform.processor() != "arm":
|
|
||||||
expected_features.remove("zlib_ng")
|
|
||||||
elif sys.platform == "ios":
|
elif sys.platform == "ios":
|
||||||
# Can't distribute raqm due to licensing, and there's no system version;
|
# Can't distribute raqm due to licensing, and there's no system version;
|
||||||
# fribidi and harfbuzz won't be available if raqm isn't available.
|
# fribidi and harfbuzz won't be available if raqm isn't available.
|
||||||
|
|
|
@ -59,6 +59,6 @@ cmake \
|
||||||
"${LIBAVIF_CMAKE_FLAGS[@]}" \
|
"${LIBAVIF_CMAKE_FLAGS[@]}" \
|
||||||
.
|
.
|
||||||
|
|
||||||
sudo make install
|
make install
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install openjpeg
|
# install openjpeg
|
||||||
|
|
||||||
archive=openjpeg-2.5.3
|
archive=openjpeg-2.5.4
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
# install raqm
|
# install raqm
|
||||||
|
|
||||||
|
|
||||||
archive=libraqm-0.10.2
|
archive=libraqm-0.10.3
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||||
|
|
||||||
pushd $archive
|
pushd $archive
|
||||||
|
|
||||||
meson build --prefix=/usr && sudo ninja -C build install
|
meson build --prefix=/usr && ninja -C build install
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
|
@ -35,8 +35,12 @@ Image.fromarray mode parameter
|
||||||
|
|
||||||
.. deprecated:: 11.3.0
|
.. deprecated:: 11.3.0
|
||||||
|
|
||||||
The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
|
Using the ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` was deprecated in
|
||||||
mode can be automatically determined from the object's shape and type instead.
|
Pillow 11.3.0. In Pillow 12.0.0, this was partially reverted, and it is now only
|
||||||
|
deprecated when changing data types. Since pixel values do not contain information
|
||||||
|
about palettes or color spaces, the parameter can still be used to place grayscale L
|
||||||
|
mode data within a P mode image, or read RGB data as YCbCr for example. If omitted, the
|
||||||
|
mode will be automatically determined from the object's shape and type.
|
||||||
|
|
||||||
Saving I mode images as PNG
|
Saving I mode images as PNG
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
@ -61,6 +65,14 @@ ImageCms.ImageCmsProfile.product_name and .product_info
|
||||||
``.product_info`` attributes have been deprecated, and will be removed in
|
``.product_info`` attributes have been deprecated, and will be removed in
|
||||||
Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
|
Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
|
||||||
|
|
||||||
|
Image._show
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 12.0.0
|
||||||
|
|
||||||
|
``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15).
|
||||||
|
Use :py:meth:`~PIL.ImageShow.show` instead.
|
||||||
|
|
||||||
Removed features
|
Removed features
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
* **libtiff** provides compressed TIFF functionality
|
* **libtiff** provides compressed TIFF functionality
|
||||||
|
|
||||||
* Pillow has been tested with libtiff versions **4.0-4.7.0**
|
* Pillow has been tested with libtiff versions **4.0-4.7.1**
|
||||||
|
|
||||||
* **libfreetype** provides type related services
|
* **libfreetype** provides type related services
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ Many of Pillow's features require external libraries:
|
||||||
* **openjpeg** provides JPEG 2000 functionality.
|
* **openjpeg** provides JPEG 2000 functionality.
|
||||||
|
|
||||||
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
|
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
|
||||||
**2.4.0**, **2.5.0**, **2.5.2** and **2.5.3**.
|
**2.4.0**, **2.5.0**, **2.5.2**, **2.5.3** and **2.5.4**.
|
||||||
* Pillow does **not** support the earlier **1.5** series which ships
|
* Pillow does **not** support the earlier **1.5** series which ships
|
||||||
with Debian Jessie.
|
with Debian Jessie.
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| macOS 13 Ventura | 3.10 | x86-64 |
|
| macOS 13 Ventura | 3.10 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| macOS 14 Sonoma | 3.11, 3.12, 3.13, 3.14 | arm64 |
|
| macOS 15 Sequoia | 3.11, 3.12, 3.13, 3.14 | arm64 |
|
||||||
| | PyPy3 | |
|
| | PyPy3 | |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
||||||
|
@ -75,6 +75,8 @@ These platforms have been reported to work at the versions mentioned.
|
||||||
| Operating system | | Tested Python | | Latest tested | | Tested |
|
| Operating system | | Tested Python | | Latest tested | | Tested |
|
||||||
| | | versions | | Pillow version | | processors |
|
| | | versions | | Pillow version | | processors |
|
||||||
+==================================+============================+==================+==============+
|
+==================================+============================+==================+==============+
|
||||||
|
| macOS 26 Tahoe | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
|
||||||
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
|
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
|
||||||
| +----------------------------+------------------+ |
|
| +----------------------------+------------------+ |
|
||||||
| | 3.8 | 10.4.0 | |
|
| | 3.8 | 10.4.0 | |
|
||||||
|
|
|
@ -74,5 +74,6 @@ Constants
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. autodata:: PIL.ImageFile.LOAD_TRUNCATED_IMAGES
|
.. autodata:: PIL.ImageFile.LOAD_TRUNCATED_IMAGES
|
||||||
|
.. autodata:: PIL.ImageFile.MAXBLOCK
|
||||||
.. autodata:: PIL.ImageFile.ERRORS
|
.. autodata:: PIL.ImageFile.ERRORS
|
||||||
:annotation:
|
:annotation:
|
||||||
|
|
|
@ -20,7 +20,9 @@ or the clipboard to a PIL image memory.
|
||||||
used as a fallback if they are installed. To disable this behaviour, pass
|
used as a fallback if they are installed. To disable this behaviour, pass
|
||||||
``xdisplay=""`` instead.
|
``xdisplay=""`` instead.
|
||||||
|
|
||||||
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux)
|
.. versionadded:: 1.1.3 Windows support
|
||||||
|
.. versionadded:: 3.0.0 macOS support
|
||||||
|
.. versionadded:: 7.1.0 Linux support
|
||||||
|
|
||||||
:param bbox: What region to copy. Default is the entire screen.
|
:param bbox: What region to copy. Default is the entire screen.
|
||||||
On macOS, this is not increased to 2x for Retina screens, so the full
|
On macOS, this is not increased to 2x for Retina screens, so the full
|
||||||
|
@ -53,7 +55,9 @@ or the clipboard to a PIL image memory.
|
||||||
|
|
||||||
On Linux, ``wl-paste`` or ``xclip`` is required.
|
On Linux, ``wl-paste`` or ``xclip`` is required.
|
||||||
|
|
||||||
.. versionadded:: 1.1.4 (Windows), 3.3.0 (macOS), 9.4.0 (Linux)
|
.. versionadded:: 1.1.4 Windows support
|
||||||
|
.. versionadded:: 3.3.0 macOS support
|
||||||
|
.. versionadded:: 9.4.0 Linux support
|
||||||
|
|
||||||
:return: On Windows, an image, a list of filenames,
|
:return: On Windows, an image, a list of filenames,
|
||||||
or None if the clipboard does not contain image data or filenames.
|
or None if the clipboard does not contain image data or filenames.
|
||||||
|
|
|
@ -29,6 +29,13 @@ Image.fromarray mode parameter
|
||||||
The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
|
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.
|
mode can be automatically determined from the object's shape and type instead.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Since pixel values do not contain information about palettes or color spaces, part
|
||||||
|
of this functionality was restored in Pillow 12.0.0. The parameter can be used to
|
||||||
|
place grayscale L mode data within a P mode image, or read RGB data as YCbCr for
|
||||||
|
example.
|
||||||
|
|
||||||
Saving I mode images as PNG
|
Saving I mode images as PNG
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,12 @@ vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
Image._show
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15).
|
||||||
|
Use :py:meth:`~PIL.ImageShow.show` instead.
|
||||||
|
|
||||||
ImageCms.ImageCmsProfile.product_name and .product_info
|
ImageCms.ImageCmsProfile.product_name and .product_info
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -150,3 +156,19 @@ others prepare for 3.14, and to ensure Pillow could be used immediately at the r
|
||||||
of 3.14.0 final (2025-10-07, :pep:`745`).
|
of 3.14.0 final (2025-10-07, :pep:`745`).
|
||||||
|
|
||||||
Pillow 12.0.0 now officially supports Python 3.14.
|
Pillow 12.0.0 now officially supports Python 3.14.
|
||||||
|
|
||||||
|
Image.fromarray mode parameter
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In Pillow 11.3.0, the ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` was
|
||||||
|
deprecated. Part of this functionality has been restored in Pillow 12.0.0. Since pixel
|
||||||
|
values do not contain information about palettes or color spaces, the parameter can be
|
||||||
|
used to place grayscale L mode data within a P mode image, or read RGB data as YCbCr
|
||||||
|
for example.
|
||||||
|
|
||||||
|
ImageMorph operations must have length 1
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Valid ImageMorph operations are 4, N, 1 and M. By limiting the length to 1 character
|
||||||
|
within Pillow, long execution times can be avoided if a user provided long pattern
|
||||||
|
strings. Reported by `Jang Choi <https://github.com/uko3211>`__.
|
||||||
|
|
|
@ -30,7 +30,7 @@ from ._util import DeferredError
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
return (
|
return (
|
||||||
len(prefix) >= 6
|
len(prefix) >= 16
|
||||||
and i16(prefix, 4) in [0xAF11, 0xAF12]
|
and i16(prefix, 4) in [0xAF11, 0xAF12]
|
||||||
and i16(prefix, 14) in [0, 3] # flags
|
and i16(prefix, 14) in [0, 3] # flags
|
||||||
)
|
)
|
||||||
|
@ -49,7 +49,12 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
def _open(self) -> None:
|
def _open(self) -> None:
|
||||||
# HEAD
|
# HEAD
|
||||||
s = self.fp.read(128)
|
s = self.fp.read(128)
|
||||||
if not (_accept(s) and s[20:22] == b"\x00\x00"):
|
if not (
|
||||||
|
_accept(s)
|
||||||
|
and s[20:22] == b"\x00" * 2
|
||||||
|
and s[42:80] == b"\x00" * 38
|
||||||
|
and s[88:] == b"\x00" * 40
|
||||||
|
):
|
||||||
msg = "not an FLI/FLC file"
|
msg = "not an FLI/FLC file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
width = i32(self.fp.read(4))
|
width = i32(self.fp.read(4))
|
||||||
height = i32(self.fp.read(4))
|
height = i32(self.fp.read(4))
|
||||||
color_depth = i32(self.fp.read(4))
|
color_depth = i32(self.fp.read(4))
|
||||||
if width <= 0 or height <= 0:
|
if width == 0 or height == 0:
|
||||||
msg = "not a GIMP brush"
|
msg = "not a GIMP brush"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
if color_depth not in (1, 4):
|
if color_depth not in (1, 4):
|
||||||
|
@ -71,7 +71,7 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
self.info["spacing"] = i32(self.fp.read(4))
|
self.info["spacing"] = i32(self.fp.read(4))
|
||||||
|
|
||||||
comment = self.fp.read(comment_length)[:-1]
|
self.info["comment"] = self.fp.read(comment_length)[:-1]
|
||||||
|
|
||||||
if color_depth == 1:
|
if color_depth == 1:
|
||||||
self._mode = "L"
|
self._mode = "L"
|
||||||
|
@ -80,8 +80,6 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self._size = width, height
|
self._size = width, height
|
||||||
|
|
||||||
self.info["comment"] = comment
|
|
||||||
|
|
||||||
# Image might not be small
|
# Image might not be small
|
||||||
Image._decompression_bomb_check(self.size)
|
Image._decompression_bomb_check(self.size)
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ def register_handler(handler: ImageFile.StubHandler | None) -> None:
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
return prefix.startswith(b"GRIB") and prefix[7] == 1
|
return len(prefix) >= 8 and prefix.startswith(b"GRIB") and prefix[7] == 1
|
||||||
|
|
||||||
|
|
||||||
class GribStubImageFile(ImageFile.StubImageFile):
|
class GribStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
|
@ -103,7 +103,6 @@ try:
|
||||||
raise ImportError(msg)
|
raise ImportError(msg)
|
||||||
|
|
||||||
except ImportError as v:
|
except ImportError as v:
|
||||||
core = DeferredError.new(ImportError("The _imaging C module is not installed."))
|
|
||||||
# Explanations for ways that we know we might have an import error
|
# Explanations for ways that we know we might have an import error
|
||||||
if str(v).startswith("Module use of python"):
|
if str(v).startswith("Module use of python"):
|
||||||
# The _imaging C module is present, but not compiled for
|
# The _imaging C module is present, but not compiled for
|
||||||
|
@ -1149,7 +1148,7 @@ class Image:
|
||||||
raise ValueError(msg) from e
|
raise ValueError(msg) from e
|
||||||
|
|
||||||
new_im = self._new(im)
|
new_im = self._new(im)
|
||||||
if mode == "P" and palette != Palette.ADAPTIVE:
|
if mode in ("P", "PA") and palette != Palette.ADAPTIVE:
|
||||||
from . import ImagePalette
|
from . import ImagePalette
|
||||||
|
|
||||||
new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB"))
|
new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB"))
|
||||||
|
@ -1343,12 +1342,6 @@ class Image:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _expand(self, xmargin: int, ymargin: int | None = None) -> Image:
|
|
||||||
if ymargin is None:
|
|
||||||
ymargin = xmargin
|
|
||||||
self.load()
|
|
||||||
return self._new(self.im.expand(xmargin, ymargin))
|
|
||||||
|
|
||||||
def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image:
|
def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image:
|
||||||
"""
|
"""
|
||||||
Filters this image using the given filter. For a list of
|
Filters this image using the given filter. For a list of
|
||||||
|
@ -2639,7 +2632,9 @@ class Image:
|
||||||
:param title: Optional title to use for the image window, where possible.
|
:param title: Optional title to use for the image window, where possible.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_show(self, title=title)
|
from . import ImageShow
|
||||||
|
|
||||||
|
ImageShow.show(self, title)
|
||||||
|
|
||||||
def split(self) -> tuple[Image, ...]:
|
def split(self) -> tuple[Image, ...]:
|
||||||
"""
|
"""
|
||||||
|
@ -3264,19 +3259,10 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
|
||||||
transferred. This means that P and PA mode images will lose their palette.
|
transferred. This means that P and PA mode images will lose their palette.
|
||||||
|
|
||||||
:param obj: Object with array interface
|
:param obj: Object with array interface
|
||||||
:param mode: Optional mode to use when reading ``obj``. Will be determined from
|
:param mode: Optional mode to use when reading ``obj``. Since pixel values do not
|
||||||
type if ``None``. Deprecated.
|
contain information about palettes or color spaces, this can be used to place
|
||||||
|
grayscale L mode data within a P mode image, or read RGB data as YCbCr for
|
||||||
This will not be used to convert the data after reading, but will be used to
|
example.
|
||||||
change how the data is read::
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
import numpy as np
|
|
||||||
a = np.full((1, 1), 300)
|
|
||||||
im = Image.fromarray(a, mode="L")
|
|
||||||
im.getpixel((0, 0)) # 44
|
|
||||||
im = Image.fromarray(a, mode="RGB")
|
|
||||||
im.getpixel((0, 0)) # (44, 1, 0)
|
|
||||||
|
|
||||||
See: :ref:`concept-modes` for general information about modes.
|
See: :ref:`concept-modes` for general information about modes.
|
||||||
:returns: An image object.
|
:returns: An image object.
|
||||||
|
@ -3287,21 +3273,28 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
|
||||||
shape = arr["shape"]
|
shape = arr["shape"]
|
||||||
ndim = len(shape)
|
ndim = len(shape)
|
||||||
strides = arr.get("strides", None)
|
strides = arr.get("strides", None)
|
||||||
if mode is None:
|
|
||||||
try:
|
try:
|
||||||
typekey = (1, 1) + shape[2:], arr["typestr"]
|
typekey = (1, 1) + shape[2:], arr["typestr"]
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
|
if mode is not None:
|
||||||
|
typekey = None
|
||||||
|
color_modes: list[str] = []
|
||||||
|
else:
|
||||||
msg = "Cannot handle this data type"
|
msg = "Cannot handle this data type"
|
||||||
raise TypeError(msg) from e
|
raise TypeError(msg) from e
|
||||||
|
if typekey is not None:
|
||||||
try:
|
try:
|
||||||
mode, rawmode = _fromarray_typemap[typekey]
|
typemode, rawmode, color_modes = _fromarray_typemap[typekey]
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
typekey_shape, typestr = typekey
|
typekey_shape, typestr = typekey
|
||||||
msg = f"Cannot handle this data type: {typekey_shape}, {typestr}"
|
msg = f"Cannot handle this data type: {typekey_shape}, {typestr}"
|
||||||
raise TypeError(msg) from e
|
raise TypeError(msg) from e
|
||||||
else:
|
if mode is not None:
|
||||||
deprecate("'mode' parameter", 13)
|
if mode != typemode and mode not in color_modes:
|
||||||
|
deprecate("'mode' parameter for changing data types", 13)
|
||||||
rawmode = mode
|
rawmode = mode
|
||||||
|
else:
|
||||||
|
mode = typemode
|
||||||
if mode in ["1", "L", "I", "P", "F"]:
|
if mode in ["1", "L", "I", "P", "F"]:
|
||||||
ndmax = 2
|
ndmax = 2
|
||||||
elif mode == "RGB":
|
elif mode == "RGB":
|
||||||
|
@ -3398,29 +3391,29 @@ def fromqpixmap(im: ImageQt.QPixmap) -> ImageFile.ImageFile:
|
||||||
|
|
||||||
|
|
||||||
_fromarray_typemap = {
|
_fromarray_typemap = {
|
||||||
# (shape, typestr) => mode, rawmode
|
# (shape, typestr) => mode, rawmode, color modes
|
||||||
# first two members of shape are set to one
|
# first two members of shape are set to one
|
||||||
((1, 1), "|b1"): ("1", "1;8"),
|
((1, 1), "|b1"): ("1", "1;8", []),
|
||||||
((1, 1), "|u1"): ("L", "L"),
|
((1, 1), "|u1"): ("L", "L", ["P"]),
|
||||||
((1, 1), "|i1"): ("I", "I;8"),
|
((1, 1), "|i1"): ("I", "I;8", []),
|
||||||
((1, 1), "<u2"): ("I", "I;16"),
|
((1, 1), "<u2"): ("I", "I;16", []),
|
||||||
((1, 1), ">u2"): ("I", "I;16B"),
|
((1, 1), ">u2"): ("I", "I;16B", []),
|
||||||
((1, 1), "<i2"): ("I", "I;16S"),
|
((1, 1), "<i2"): ("I", "I;16S", []),
|
||||||
((1, 1), ">i2"): ("I", "I;16BS"),
|
((1, 1), ">i2"): ("I", "I;16BS", []),
|
||||||
((1, 1), "<u4"): ("I", "I;32"),
|
((1, 1), "<u4"): ("I", "I;32", []),
|
||||||
((1, 1), ">u4"): ("I", "I;32B"),
|
((1, 1), ">u4"): ("I", "I;32B", []),
|
||||||
((1, 1), "<i4"): ("I", "I;32S"),
|
((1, 1), "<i4"): ("I", "I;32S", []),
|
||||||
((1, 1), ">i4"): ("I", "I;32BS"),
|
((1, 1), ">i4"): ("I", "I;32BS", []),
|
||||||
((1, 1), "<f4"): ("F", "F;32F"),
|
((1, 1), "<f4"): ("F", "F;32F", []),
|
||||||
((1, 1), ">f4"): ("F", "F;32BF"),
|
((1, 1), ">f4"): ("F", "F;32BF", []),
|
||||||
((1, 1), "<f8"): ("F", "F;64F"),
|
((1, 1), "<f8"): ("F", "F;64F", []),
|
||||||
((1, 1), ">f8"): ("F", "F;64BF"),
|
((1, 1), ">f8"): ("F", "F;64BF", []),
|
||||||
((1, 1, 2), "|u1"): ("LA", "LA"),
|
((1, 1, 2), "|u1"): ("LA", "LA", ["La", "PA"]),
|
||||||
((1, 1, 3), "|u1"): ("RGB", "RGB"),
|
((1, 1, 3), "|u1"): ("RGB", "RGB", ["YCbCr", "LAB", "HSV"]),
|
||||||
((1, 1, 4), "|u1"): ("RGBA", "RGBA"),
|
((1, 1, 4), "|u1"): ("RGBA", "RGBA", ["RGBa", "RGBX", "CMYK"]),
|
||||||
# shortcuts:
|
# shortcuts:
|
||||||
((1, 1), f"{_ENDIAN}i4"): ("I", "I"),
|
((1, 1), f"{_ENDIAN}i4"): ("I", "I", []),
|
||||||
((1, 1), f"{_ENDIAN}f4"): ("F", "F"),
|
((1, 1), f"{_ENDIAN}f4"): ("F", "F", []),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3577,9 +3570,8 @@ def alpha_composite(im1: Image, im2: Image) -> Image:
|
||||||
"""
|
"""
|
||||||
Alpha composite im2 over im1.
|
Alpha composite im2 over im1.
|
||||||
|
|
||||||
:param im1: The first image. Must have mode RGBA.
|
:param im1: The first image. Must have mode RGBA or LA.
|
||||||
:param im2: The second image. Must have mode RGBA, and the same size as
|
:param im2: The second image. Must have the same mode and size as the first image.
|
||||||
the first image.
|
|
||||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -3805,6 +3797,7 @@ def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None:
|
||||||
def _show(image: Image, **options: Any) -> None:
|
def _show(image: Image, **options: Any) -> None:
|
||||||
from . import ImageShow
|
from . import ImageShow
|
||||||
|
|
||||||
|
deprecate("Image._show", 13, "ImageShow.show")
|
||||||
ImageShow.show(image, **options)
|
ImageShow.show(image, **options)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4226,6 +4219,8 @@ class Exif(_ExifBase):
|
||||||
del self._info[tag]
|
del self._info[tag]
|
||||||
else:
|
else:
|
||||||
del self._data[tag]
|
del self._data[tag]
|
||||||
|
if tag in self._ifds:
|
||||||
|
del self._ifds[tag]
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[int]:
|
def __iter__(self) -> Iterator[int]:
|
||||||
keys = set(self._data)
|
keys = set(self._data)
|
||||||
|
|
|
@ -46,6 +46,18 @@ if TYPE_CHECKING:
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
MAXBLOCK = 65536
|
MAXBLOCK = 65536
|
||||||
|
"""
|
||||||
|
By default, Pillow processes image data in blocks. This helps to prevent excessive use
|
||||||
|
of resources. Codecs may disable this behaviour with ``_pulls_fd`` or ``_pushes_fd``.
|
||||||
|
|
||||||
|
When reading an image, this is the number of bytes to read at once.
|
||||||
|
|
||||||
|
When writing an image, this is the number of bytes to write at once.
|
||||||
|
If the image width times 4 is greater, then that will be used instead.
|
||||||
|
Plugins may also set a greater number.
|
||||||
|
|
||||||
|
User code may set this to another number.
|
||||||
|
"""
|
||||||
|
|
||||||
SAFEBLOCK = 1024 * 1024
|
SAFEBLOCK = 1024 * 1024
|
||||||
|
|
||||||
|
|
|
@ -125,11 +125,16 @@ class ImageFont:
|
||||||
image.close()
|
image.close()
|
||||||
|
|
||||||
def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None:
|
def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None:
|
||||||
|
# check image
|
||||||
|
if image.mode not in ("1", "L"):
|
||||||
|
msg = "invalid font image mode"
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
# read PILfont header
|
# read PILfont header
|
||||||
if file.readline() != b"PILfont\n":
|
if file.read(8) != b"PILfont\n":
|
||||||
msg = "Not a PILfont file"
|
msg = "Not a PILfont file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
file.readline().split(b";")
|
file.readline()
|
||||||
self.info = [] # FIXME: should be a dictionary
|
self.info = [] # FIXME: should be a dictionary
|
||||||
while True:
|
while True:
|
||||||
s = file.readline()
|
s = file.readline()
|
||||||
|
@ -140,11 +145,6 @@ class ImageFont:
|
||||||
# read PILfont metrics
|
# read PILfont metrics
|
||||||
data = file.read(256 * 20)
|
data = file.read(256 * 20)
|
||||||
|
|
||||||
# check image
|
|
||||||
if image.mode not in ("1", "L"):
|
|
||||||
msg = "invalid font image mode"
|
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
image.load()
|
image.load()
|
||||||
|
|
||||||
self.font = Image.core.font(image.im, data)
|
self.font = Image.core.font(image.im, data)
|
||||||
|
@ -671,11 +671,7 @@ class FreeTypeFont:
|
||||||
:returns: A list of the named styles in a variation font.
|
:returns: A list of the named styles in a variation font.
|
||||||
:exception OSError: If the font is not a variation font.
|
:exception OSError: If the font is not a variation font.
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
names = self.font.getvarnames()
|
names = self.font.getvarnames()
|
||||||
except AttributeError as e:
|
|
||||||
msg = "FreeType 2.9.1 or greater is required"
|
|
||||||
raise NotImplementedError(msg) from e
|
|
||||||
return [name.replace(b"\x00", b"") for name in names]
|
return [name.replace(b"\x00", b"") for name in names]
|
||||||
|
|
||||||
def set_variation_by_name(self, name: str | bytes) -> None:
|
def set_variation_by_name(self, name: str | bytes) -> None:
|
||||||
|
@ -702,11 +698,7 @@ class FreeTypeFont:
|
||||||
:returns: A list of the axes in a variation font.
|
:returns: A list of the axes in a variation font.
|
||||||
:exception OSError: If the font is not a variation font.
|
:exception OSError: If the font is not a variation font.
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
axes = self.font.getvaraxes()
|
axes = self.font.getvaraxes()
|
||||||
except AttributeError as e:
|
|
||||||
msg = "FreeType 2.9.1 or greater is required"
|
|
||||||
raise NotImplementedError(msg) from e
|
|
||||||
for axis in axes:
|
for axis in axes:
|
||||||
if axis["name"]:
|
if axis["name"]:
|
||||||
axis["name"] = axis["name"].replace(b"\x00", b"")
|
axis["name"] = axis["name"].replace(b"\x00", b"")
|
||||||
|
@ -717,11 +709,7 @@ class FreeTypeFont:
|
||||||
:param axes: A list of values for each axis.
|
:param axes: A list of values for each axis.
|
||||||
:exception OSError: If the font is not a variation font.
|
:exception OSError: If the font is not a variation font.
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
self.font.setvaraxes(axes)
|
self.font.setvaraxes(axes)
|
||||||
except AttributeError as e:
|
|
||||||
msg = "FreeType 2.9.1 or greater is required"
|
|
||||||
raise NotImplementedError(msg) from e
|
|
||||||
|
|
||||||
|
|
||||||
class TransposedFont:
|
class TransposedFont:
|
||||||
|
|
|
@ -150,7 +150,7 @@ class LutBuilder:
|
||||||
|
|
||||||
# Parse and create symmetries of the patterns strings
|
# Parse and create symmetries of the patterns strings
|
||||||
for p in self.patterns:
|
for p in self.patterns:
|
||||||
m = re.search(r"(\w*):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", ""))
|
m = re.search(r"(\w):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", ""))
|
||||||
if not m:
|
if not m:
|
||||||
msg = 'Syntax error in pattern "' + p + '"'
|
msg = 'Syntax error in pattern "' + p + '"'
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
|
|
|
@ -120,7 +120,7 @@ class Stat:
|
||||||
@cached_property
|
@cached_property
|
||||||
def mean(self) -> list[float]:
|
def mean(self) -> list[float]:
|
||||||
"""Average (arithmetic mean) pixel level for each band in the image."""
|
"""Average (arithmetic mean) pixel level for each band in the image."""
|
||||||
return [self.sum[i] / self.count[i] for i in self.bands]
|
return [self.sum[i] / self.count[i] if self.count[i] else 0 for i in self.bands]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def median(self) -> list[int]:
|
def median(self) -> list[int]:
|
||||||
|
@ -141,13 +141,20 @@ class Stat:
|
||||||
@cached_property
|
@cached_property
|
||||||
def rms(self) -> list[float]:
|
def rms(self) -> list[float]:
|
||||||
"""RMS (root-mean-square) for each band in the image."""
|
"""RMS (root-mean-square) for each band in the image."""
|
||||||
return [math.sqrt(self.sum2[i] / self.count[i]) for i in self.bands]
|
return [
|
||||||
|
math.sqrt(self.sum2[i] / self.count[i]) if self.count[i] else 0
|
||||||
|
for i in self.bands
|
||||||
|
]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def var(self) -> list[float]:
|
def var(self) -> list[float]:
|
||||||
"""Variance for each band in the image."""
|
"""Variance for each band in the image."""
|
||||||
return [
|
return [
|
||||||
|
(
|
||||||
(self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i]
|
(self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i]
|
||||||
|
if self.count[i]
|
||||||
|
else 0
|
||||||
|
)
|
||||||
for i in self.bands
|
for i in self.bands
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,6 @@ def _i(c: bytes) -> int:
|
||||||
return i32((b"\0\0\0\0" + c)[-4:])
|
return i32((b"\0\0\0\0" + c)[-4:])
|
||||||
|
|
||||||
|
|
||||||
def _i8(c: int | bytes) -> int:
|
|
||||||
return c if isinstance(c, int) else c[0]
|
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
|
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
|
||||||
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
|
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
|
||||||
|
@ -100,16 +96,18 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
# mode
|
# mode
|
||||||
layers = self.info[(3, 60)][0]
|
layers = self.info[(3, 60)][0]
|
||||||
component = self.info[(3, 60)][1]
|
component = self.info[(3, 60)][1]
|
||||||
if (3, 65) in self.info:
|
|
||||||
id = self.info[(3, 65)][0] - 1
|
|
||||||
else:
|
|
||||||
id = 0
|
|
||||||
if layers == 1 and not component:
|
if layers == 1 and not component:
|
||||||
self._mode = "L"
|
self._mode = "L"
|
||||||
elif layers == 3 and component:
|
band = None
|
||||||
self._mode = "RGB"[id]
|
else:
|
||||||
|
if layers == 3 and component:
|
||||||
|
self._mode = "RGB"
|
||||||
elif layers == 4 and component:
|
elif layers == 4 and component:
|
||||||
self._mode = "CMYK"[id]
|
self._mode = "CMYK"
|
||||||
|
if (3, 65) in self.info:
|
||||||
|
band = self.info[(3, 65)][0] - 1
|
||||||
|
else:
|
||||||
|
band = 0
|
||||||
|
|
||||||
# size
|
# size
|
||||||
self._size = self.getint((3, 20)), self.getint((3, 30))
|
self._size = self.getint((3, 20)), self.getint((3, 30))
|
||||||
|
@ -124,16 +122,16 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
# tile
|
# tile
|
||||||
if tag == (8, 10):
|
if tag == (8, 10):
|
||||||
self.tile = [
|
self.tile = [
|
||||||
ImageFile._Tile("iptc", (0, 0) + self.size, offset, compression)
|
ImageFile._Tile("iptc", (0, 0) + self.size, offset, (compression, band))
|
||||||
]
|
]
|
||||||
|
|
||||||
def load(self) -> Image.core.PixelAccess | None:
|
def load(self) -> Image.core.PixelAccess | None:
|
||||||
if len(self.tile) != 1 or self.tile[0][0] != "iptc":
|
if self.tile:
|
||||||
return ImageFile.ImageFile.load(self)
|
args = self.tile[0].args
|
||||||
|
assert isinstance(args, tuple)
|
||||||
|
compression, band = args
|
||||||
|
|
||||||
offset, compression = self.tile[0][2:]
|
self.fp.seek(self.tile[0].offset)
|
||||||
|
|
||||||
self.fp.seek(offset)
|
|
||||||
|
|
||||||
# Copy image data to temporary file
|
# Copy image data to temporary file
|
||||||
o = BytesIO()
|
o = BytesIO()
|
||||||
|
@ -153,10 +151,15 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
size -= len(s)
|
size -= len(s)
|
||||||
|
|
||||||
with Image.open(o) as _im:
|
with Image.open(o) as _im:
|
||||||
|
if band is not None:
|
||||||
|
bands = [Image.new("L", _im.size)] * Image.getmodebands(self.mode)
|
||||||
|
bands[band] = _im
|
||||||
|
_im = Image.merge(self.mode, bands)
|
||||||
|
else:
|
||||||
_im.load()
|
_im.load()
|
||||||
self.im = _im.im
|
self.im = _im.im
|
||||||
self.tile = []
|
self.tile = []
|
||||||
return Image.Image.load(self)
|
return ImageFile.ImageFile.load(self)
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(IptcImageFile.format, IptcImageFile)
|
Image.register_open(IptcImageFile.format, IptcImageFile)
|
||||||
|
|
|
@ -32,7 +32,7 @@ class PcdImageFile(ImageFile.ImageFile):
|
||||||
assert self.fp is not None
|
assert self.fp is not None
|
||||||
|
|
||||||
self.fp.seek(2048)
|
self.fp.seek(2048)
|
||||||
s = self.fp.read(2048)
|
s = self.fp.read(1539)
|
||||||
|
|
||||||
if not s.startswith(b"PCD_"):
|
if not s.startswith(b"PCD_"):
|
||||||
msg = "not a PCD file"
|
msg = "not a PCD file"
|
||||||
|
@ -46,14 +46,13 @@ class PcdImageFile(ImageFile.ImageFile):
|
||||||
self.tile_post_rotate = -90
|
self.tile_post_rotate = -90
|
||||||
|
|
||||||
self._mode = "RGB"
|
self._mode = "RGB"
|
||||||
self._size = 768, 512 # FIXME: not correct for rotated images!
|
self._size = (512, 768) if orientation in (1, 3) else (768, 512)
|
||||||
self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
|
self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
|
||||||
|
|
||||||
def load_end(self) -> None:
|
def load_end(self) -> None:
|
||||||
if self.tile_post_rotate:
|
if self.tile_post_rotate:
|
||||||
# Handle rotated PCDs
|
# Handle rotated PCDs
|
||||||
self.im = self.im.rotate(self.tile_post_rotate)
|
self.im = self.im.rotate(self.tile_post_rotate)
|
||||||
self._size = self.im.size
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -39,7 +39,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]
|
return len(prefix) >= 2 and prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -47,7 +47,7 @@ MODES = {
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
return prefix.startswith(b"P") and prefix[1] in b"0123456fy"
|
return len(prefix) >= 2 and prefix.startswith(b"P") and prefix[1] in b"0123456fy"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -49,8 +49,7 @@ class WalImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# strings are null-terminated
|
# strings are null-terminated
|
||||||
self.info["name"] = header[:32].split(b"\0", 1)[0]
|
self.info["name"] = header[:32].split(b"\0", 1)[0]
|
||||||
next_name = header[56 : 56 + 32].split(b"\0", 1)[0]
|
if next_name := header[56 : 56 + 32].split(b"\0", 1)[0]:
|
||||||
if next_name:
|
|
||||||
self.info["next_name"] = next_name
|
self.info["next_name"] = next_name
|
||||||
|
|
||||||
def load(self) -> Image.core.PixelAccess | None:
|
def load(self) -> Image.core.PixelAccess | None:
|
||||||
|
|
|
@ -1221,8 +1221,6 @@ glyph_error:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
|
|
||||||
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
font_getvarnames(FontObject *self) {
|
font_getvarnames(FontObject *self) {
|
||||||
int error;
|
int error;
|
||||||
|
@ -1432,7 +1430,6 @@ font_setvaraxes(FontObject *self, PyObject *args) {
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
font_dealloc(FontObject *self) {
|
font_dealloc(FontObject *self) {
|
||||||
|
@ -1451,13 +1448,10 @@ static PyMethodDef font_methods[] = {
|
||||||
{"render", (PyCFunction)font_render, METH_VARARGS},
|
{"render", (PyCFunction)font_render, METH_VARARGS},
|
||||||
{"getsize", (PyCFunction)font_getsize, METH_VARARGS},
|
{"getsize", (PyCFunction)font_getsize, METH_VARARGS},
|
||||||
{"getlength", (PyCFunction)font_getlength, METH_VARARGS},
|
{"getlength", (PyCFunction)font_getlength, METH_VARARGS},
|
||||||
#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
|
|
||||||
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
|
||||||
{"getvarnames", (PyCFunction)font_getvarnames, METH_NOARGS},
|
{"getvarnames", (PyCFunction)font_getvarnames, METH_NOARGS},
|
||||||
{"getvaraxes", (PyCFunction)font_getvaraxes, METH_NOARGS},
|
{"getvaraxes", (PyCFunction)font_getvaraxes, METH_NOARGS},
|
||||||
{"setvarname", (PyCFunction)font_setvarname, METH_VARARGS},
|
{"setvarname", (PyCFunction)font_setvarname, METH_VARARGS},
|
||||||
{"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS},
|
{"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS},
|
||||||
#endif
|
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -870,8 +870,6 @@ PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (strcmp(format, "j2k") == 0) {
|
if (strcmp(format, "j2k") == 0) {
|
||||||
codec_format = OPJ_CODEC_J2K;
|
codec_format = OPJ_CODEC_J2K;
|
||||||
} else if (strcmp(format, "jpt") == 0) {
|
|
||||||
codec_format = OPJ_CODEC_JPT;
|
|
||||||
} else if (strcmp(format, "jp2") == 0) {
|
} else if (strcmp(format, "jp2") == 0) {
|
||||||
codec_format = OPJ_CODEC_JP2;
|
codec_format = OPJ_CODEC_JP2;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -25,13 +25,12 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) {
|
||||||
int x, y;
|
int x, y;
|
||||||
|
|
||||||
/* Check arguments */
|
/* Check arguments */
|
||||||
if (!imDst || !imSrc || strcmp(imDst->mode, "RGBA") ||
|
if (!imDst || !imSrc ||
|
||||||
imDst->type != IMAGING_TYPE_UINT8 || imDst->bands != 4) {
|
(strcmp(imDst->mode, "RGBA") && strcmp(imDst->mode, "LA"))) {
|
||||||
return ImagingError_ModeError();
|
return ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(imDst->mode, imSrc->mode) || imDst->type != imSrc->type ||
|
if (strcmp(imDst->mode, imSrc->mode) || imDst->xsize != imSrc->xsize ||
|
||||||
imDst->bands != imSrc->bands || imDst->xsize != imSrc->xsize ||
|
|
||||||
imDst->ysize != imSrc->ysize) {
|
imDst->ysize != imSrc->ysize) {
|
||||||
return ImagingError_Mismatch();
|
return ImagingError_Mismatch();
|
||||||
}
|
}
|
||||||
|
|
2
src/thirdparty/raqm/COPYING
vendored
2
src/thirdparty/raqm/COPYING
vendored
|
@ -1,7 +1,7 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om>
|
Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om>
|
||||||
Copyright © 2016-2023 Khaled Hosny <khaled@aliftype.com>
|
Copyright © 2016-2025 Khaled Hosny <khaled@aliftype.com>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
16
src/thirdparty/raqm/NEWS
vendored
16
src/thirdparty/raqm/NEWS
vendored
|
@ -1,3 +1,19 @@
|
||||||
|
Overview of changes leading to 0.10.3
|
||||||
|
Tuesday, August 5, 2025
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Fix raqm_set_text_utf8/utf16 reading beyond len for multibyte.
|
||||||
|
|
||||||
|
Support building against SheenBidi 2.9.
|
||||||
|
|
||||||
|
Fix deprecation warning with latest HarfBuzz.
|
||||||
|
|
||||||
|
Overview of changes leading to 0.10.2
|
||||||
|
Sunday, September 22, 2024
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Fix Unicode codepoint conversion from UTF-16.
|
||||||
|
|
||||||
Overview of changes leading to 0.10.1
|
Overview of changes leading to 0.10.1
|
||||||
Wednesday, April 12, 2023
|
Wednesday, April 12, 2023
|
||||||
====================================
|
====================================
|
||||||
|
|
4
src/thirdparty/raqm/raqm-version.h
vendored
4
src/thirdparty/raqm/raqm-version.h
vendored
|
@ -33,9 +33,9 @@
|
||||||
|
|
||||||
#define RAQM_VERSION_MAJOR 0
|
#define RAQM_VERSION_MAJOR 0
|
||||||
#define RAQM_VERSION_MINOR 10
|
#define RAQM_VERSION_MINOR 10
|
||||||
#define RAQM_VERSION_MICRO 1
|
#define RAQM_VERSION_MICRO 3
|
||||||
|
|
||||||
#define RAQM_VERSION_STRING "0.10.1"
|
#define RAQM_VERSION_STRING "0.10.3"
|
||||||
|
|
||||||
#define RAQM_VERSION_ATLEAST(major,minor,micro) \
|
#define RAQM_VERSION_ATLEAST(major,minor,micro) \
|
||||||
((major)*10000+(minor)*100+(micro) <= \
|
((major)*10000+(minor)*100+(micro) <= \
|
||||||
|
|
82
src/thirdparty/raqm/raqm.c
vendored
82
src/thirdparty/raqm/raqm.c
vendored
|
@ -30,7 +30,11 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#ifdef RAQM_SHEENBIDI
|
#ifdef RAQM_SHEENBIDI
|
||||||
|
#ifdef RAQM_SHEENBIDI_GT_2_9
|
||||||
|
#include <SheenBidi/SheenBidi.h>
|
||||||
|
#else
|
||||||
#include <SheenBidi.h>
|
#include <SheenBidi.h>
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#ifdef HAVE_FRIBIDI_SYSTEM
|
#ifdef HAVE_FRIBIDI_SYSTEM
|
||||||
#include <fribidi.h>
|
#include <fribidi.h>
|
||||||
|
@ -546,34 +550,32 @@ raqm_set_text (raqm_t *rq,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *
|
static const char *
|
||||||
_raqm_get_utf8_codepoint (const void *str,
|
_raqm_get_utf8_codepoint (const char *str,
|
||||||
uint32_t *out_codepoint)
|
uint32_t *out_codepoint)
|
||||||
{
|
{
|
||||||
const char *s = (const char *)str;
|
if (0xf0 == (0xf8 & str[0]))
|
||||||
|
|
||||||
if (0xf0 == (0xf8 & s[0]))
|
|
||||||
{
|
{
|
||||||
*out_codepoint = ((0x07 & s[0]) << 18) | ((0x3f & s[1]) << 12) | ((0x3f & s[2]) << 6) | (0x3f & s[3]);
|
*out_codepoint = ((0x07 & str[0]) << 18) | ((0x3f & str[1]) << 12) | ((0x3f & str[2]) << 6) | (0x3f & str[3]);
|
||||||
s += 4;
|
str += 4;
|
||||||
}
|
}
|
||||||
else if (0xe0 == (0xf0 & s[0]))
|
else if (0xe0 == (0xf0 & str[0]))
|
||||||
{
|
{
|
||||||
*out_codepoint = ((0x0f & s[0]) << 12) | ((0x3f & s[1]) << 6) | (0x3f & s[2]);
|
*out_codepoint = ((0x0f & str[0]) << 12) | ((0x3f & str[1]) << 6) | (0x3f & str[2]);
|
||||||
s += 3;
|
str += 3;
|
||||||
}
|
}
|
||||||
else if (0xc0 == (0xe0 & s[0]))
|
else if (0xc0 == (0xe0 & str[0]))
|
||||||
{
|
{
|
||||||
*out_codepoint = ((0x1f & s[0]) << 6) | (0x3f & s[1]);
|
*out_codepoint = ((0x1f & str[0]) << 6) | (0x3f & str[1]);
|
||||||
s += 2;
|
str += 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
*out_codepoint = s[0];
|
*out_codepoint = str[0];
|
||||||
s += 1;
|
str += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (void *)s;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
|
@ -585,42 +587,41 @@ _raqm_u8_to_u32 (const char *text, size_t len, uint32_t *unicode)
|
||||||
|
|
||||||
while ((*in_utf8 != '\0') && (in_len < len))
|
while ((*in_utf8 != '\0') && (in_len < len))
|
||||||
{
|
{
|
||||||
in_utf8 = _raqm_get_utf8_codepoint (in_utf8, out_utf32);
|
const char *out_utf8 = _raqm_get_utf8_codepoint (in_utf8, out_utf32);
|
||||||
|
in_len += out_utf8 - in_utf8;
|
||||||
|
in_utf8 = out_utf8;
|
||||||
++out_utf32;
|
++out_utf32;
|
||||||
++in_len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (out_utf32 - unicode);
|
return (out_utf32 - unicode);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *
|
static const uint16_t *
|
||||||
_raqm_get_utf16_codepoint (const void *str,
|
_raqm_get_utf16_codepoint (const uint16_t *str,
|
||||||
uint32_t *out_codepoint)
|
uint32_t *out_codepoint)
|
||||||
{
|
{
|
||||||
const uint16_t *s = (const uint16_t *)str;
|
if (str[0] >= 0xD800 && str[0] <= 0xDBFF)
|
||||||
|
|
||||||
if (s[0] > 0xD800 && s[0] < 0xDBFF)
|
|
||||||
{
|
{
|
||||||
if (s[1] > 0xDC00 && s[1] < 0xDFFF)
|
if (str[1] >= 0xDC00 && str[1] <= 0xDFFF)
|
||||||
{
|
{
|
||||||
uint32_t X = ((s[0] & ((1 << 6) -1)) << 10) | (s[1] & ((1 << 10) -1));
|
uint32_t X = ((str[0] & ((1 << 6) -1)) << 10) | (str[1] & ((1 << 10) -1));
|
||||||
uint32_t W = (s[0] >> 6) & ((1 << 5) - 1);
|
uint32_t W = (str[0] >> 6) & ((1 << 5) - 1);
|
||||||
*out_codepoint = (W+1) << 16 | X;
|
*out_codepoint = (W+1) << 16 | X;
|
||||||
s += 2;
|
str += 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* A single high surrogate, this is an error. */
|
/* A single high surrogate, this is an error. */
|
||||||
*out_codepoint = s[0];
|
*out_codepoint = str[0];
|
||||||
s += 1;
|
str += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
*out_codepoint = s[0];
|
*out_codepoint = str[0];
|
||||||
s += 1;
|
str += 1;
|
||||||
}
|
}
|
||||||
return (void *)s;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
|
@ -632,9 +633,10 @@ _raqm_u16_to_u32 (const uint16_t *text, size_t len, uint32_t *unicode)
|
||||||
|
|
||||||
while ((*in_utf16 != '\0') && (in_len < len))
|
while ((*in_utf16 != '\0') && (in_len < len))
|
||||||
{
|
{
|
||||||
in_utf16 = _raqm_get_utf16_codepoint (in_utf16, out_utf32);
|
const uint16_t *out_utf16 = _raqm_get_utf16_codepoint (in_utf16, out_utf32);
|
||||||
|
in_len += (out_utf16 - in_utf16);
|
||||||
|
in_utf16 = out_utf16;
|
||||||
++out_utf32;
|
++out_utf32;
|
||||||
++in_len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (out_utf32 - unicode);
|
return (out_utf32 - unicode);
|
||||||
|
@ -1114,12 +1116,12 @@ _raqm_set_spacing (raqm_t *rq,
|
||||||
{
|
{
|
||||||
if (_raqm_allowed_grapheme_boundary (rq->text[i], rq->text[i+1]))
|
if (_raqm_allowed_grapheme_boundary (rq->text[i], rq->text[i+1]))
|
||||||
{
|
{
|
||||||
/* CSS word seperators, word spacing is only applied on these.*/
|
/* CSS word separators, word spacing is only applied on these.*/
|
||||||
if (rq->text[i] == 0x0020 || /* Space */
|
if (rq->text[i] == 0x0020 || /* Space */
|
||||||
rq->text[i] == 0x00A0 || /* No Break Space */
|
rq->text[i] == 0x00A0 || /* No Break Space */
|
||||||
rq->text[i] == 0x1361 || /* Ethiopic Word Space */
|
rq->text[i] == 0x1361 || /* Ethiopic Word Space */
|
||||||
rq->text[i] == 0x10100 || /* Aegean Word Seperator Line */
|
rq->text[i] == 0x10100 || /* Aegean Word Separator Line */
|
||||||
rq->text[i] == 0x10101 || /* Aegean Word Seperator Dot */
|
rq->text[i] == 0x10101 || /* Aegean Word Separator Dot */
|
||||||
rq->text[i] == 0x1039F || /* Ugaric Word Divider */
|
rq->text[i] == 0x1039F || /* Ugaric Word Divider */
|
||||||
rq->text[i] == 0x1091F) /* Phoenician Word Separator */
|
rq->text[i] == 0x1091F) /* Phoenician Word Separator */
|
||||||
{
|
{
|
||||||
|
@ -2167,6 +2169,10 @@ _raqm_ft_transform (int *x,
|
||||||
*y = vector.y;
|
*y = vector.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !HB_VERSION_ATLEAST (10, 4, 0)
|
||||||
|
# define hb_ft_font_get_ft_face hb_ft_font_get_face
|
||||||
|
#endif
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
_raqm_shape (raqm_t *rq)
|
_raqm_shape (raqm_t *rq)
|
||||||
{
|
{
|
||||||
|
@ -2199,7 +2205,7 @@ _raqm_shape (raqm_t *rq)
|
||||||
hb_glyph_position_t *pos;
|
hb_glyph_position_t *pos;
|
||||||
unsigned int len;
|
unsigned int len;
|
||||||
|
|
||||||
FT_Get_Transform (hb_ft_font_get_face (run->font), &matrix, NULL);
|
FT_Get_Transform (hb_ft_font_get_ft_face (run->font), &matrix, NULL);
|
||||||
pos = hb_buffer_get_glyph_positions (run->buffer, &len);
|
pos = hb_buffer_get_glyph_positions (run->buffer, &len);
|
||||||
info = hb_buffer_get_glyph_infos (run->buffer, &len);
|
info = hb_buffer_get_glyph_infos (run->buffer, &len);
|
||||||
|
|
||||||
|
|
30
wheels/dependency_licenses/ZSTD.txt
Normal file
30
wheels/dependency_licenses/ZSTD.txt
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
BSD License
|
||||||
|
|
||||||
|
For Zstandard software
|
||||||
|
|
||||||
|
Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name Facebook, nor Meta, nor the names of its contributors may
|
||||||
|
be used to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1 +1 @@
|
||||||
Subproject commit 42d761728d141d8462cd9943f4329f12fe62b155
|
Subproject commit 64739327166fcad1fa41ad9b23fa910fa244c84f
|
|
@ -16,7 +16,7 @@ For more extensive info, see the [Windows build instructions](build.rst).
|
||||||
Here's an example script to build on Windows:
|
Here's an example script to build on Windows:
|
||||||
|
|
||||||
```
|
```
|
||||||
set PYTHON=C:\Python39\bin
|
set PYTHON=C:\Python310\bin
|
||||||
cd /D C:\Pillow\winbuild
|
cd /D C:\Pillow\winbuild
|
||||||
%PYTHON%\python.exe build_prepare.py -v --depends=C:\pillow-depends
|
%PYTHON%\python.exe build_prepare.py -v --depends=C:\pillow-depends
|
||||||
build\build_dep_all.cmd
|
build\build_dep_all.cmd
|
||||||
|
|
|
@ -115,7 +115,7 @@ Example
|
||||||
|
|
||||||
Here's an example script to build on Windows::
|
Here's an example script to build on Windows::
|
||||||
|
|
||||||
set PYTHON=C:\Python39\bin
|
set PYTHON=C:\Python310\bin
|
||||||
cd /D C:\Pillow\winbuild
|
cd /D C:\Pillow\winbuild
|
||||||
%PYTHON%\python.exe build_prepare.py -v --depends C:\pillow-depends
|
%PYTHON%\python.exe build_prepare.py -v --depends C:\pillow-depends
|
||||||
build\build_dep_all.cmd
|
build\build_dep_all.cmd
|
||||||
|
|
|
@ -114,19 +114,19 @@ ARCHITECTURES = {
|
||||||
|
|
||||||
V = {
|
V = {
|
||||||
"BROTLI": "1.1.0",
|
"BROTLI": "1.1.0",
|
||||||
"FREETYPE": "2.13.3",
|
"FREETYPE": "2.14.1",
|
||||||
"FRIBIDI": "1.0.16",
|
"FRIBIDI": "1.0.16",
|
||||||
"HARFBUZZ": "11.3.3",
|
"HARFBUZZ": "11.5.0",
|
||||||
"JPEGTURBO": "3.1.1",
|
"JPEGTURBO": "3.1.2",
|
||||||
"LCMS2": "2.17",
|
"LCMS2": "2.17",
|
||||||
"LIBAVIF": "1.3.0",
|
"LIBAVIF": "1.3.0",
|
||||||
"LIBIMAGEQUANT": "4.4.0",
|
"LIBIMAGEQUANT": "4.4.0",
|
||||||
"LIBPNG": "1.6.50",
|
"LIBPNG": "1.6.50",
|
||||||
"LIBWEBP": "1.6.0",
|
"LIBWEBP": "1.6.0",
|
||||||
"OPENJPEG": "2.5.3",
|
"OPENJPEG": "2.5.4",
|
||||||
"TIFF": "4.7.0",
|
"TIFF": "4.7.1",
|
||||||
"XZ": "5.8.1",
|
"XZ": "5.8.1",
|
||||||
"ZLIBNG": "2.2.4",
|
"ZLIBNG": "2.2.5",
|
||||||
}
|
}
|
||||||
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
|
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
|
||||||
|
|
||||||
|
@ -228,12 +228,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||||
# link against libwebp.lib
|
# link against libwebp.lib
|
||||||
"#ifdef WEBP_SUPPORT": '#ifdef WEBP_SUPPORT\n#pragma comment(lib, "libwebp.lib")', # noqa: E501
|
"#ifdef WEBP_SUPPORT": '#ifdef WEBP_SUPPORT\n#pragma comment(lib, "libwebp.lib")', # noqa: E501
|
||||||
},
|
},
|
||||||
r"test\CMakeLists.txt": {
|
|
||||||
"add_executable(test_write_read_tags ../placeholder.h)": "",
|
|
||||||
"target_sources(test_write_read_tags PRIVATE test_write_read_tags.c)": "", # noqa: E501
|
|
||||||
"target_link_libraries(test_write_read_tags PRIVATE tiff)": "",
|
|
||||||
"list(APPEND simple_tests test_write_read_tags)": "",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"build": [
|
"build": [
|
||||||
*cmds_cmake(
|
*cmds_cmake(
|
||||||
|
@ -241,7 +235,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||||
"-DBUILD_SHARED_LIBS:BOOL=OFF",
|
"-DBUILD_SHARED_LIBS:BOOL=OFF",
|
||||||
"-DWebP_LIBRARY=libwebp",
|
"-DWebP_LIBRARY=libwebp",
|
||||||
'-DCMAKE_C_FLAGS="-nologo -DLZMA_API_STATIC"',
|
'-DCMAKE_C_FLAGS="-nologo -DLZMA_API_STATIC"',
|
||||||
"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
"headers": [r"libtiff\tiff*.h"],
|
"headers": [r"libtiff\tiff*.h"],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user