Merge branch 'main' into init
|
@ -10,11 +10,11 @@ environment:
|
||||||
TEST_OPTIONS:
|
TEST_OPTIONS:
|
||||||
DEPLOY: YES
|
DEPLOY: YES
|
||||||
matrix:
|
matrix:
|
||||||
- PYTHON: C:/Python311
|
- PYTHON: C:/Python312
|
||||||
ARCHITECTURE: x86
|
ARCHITECTURE: x86
|
||||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||||
- PYTHON: C:/Python38-x64
|
- PYTHON: C:/Python38-x64
|
||||||
ARCHITECTURE: x64
|
ARCHITECTURE: AMD64
|
||||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ build_script:
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- cd c:\pillow
|
- cd c:\pillow
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout'
|
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout defusedxml numpy olefile pyroma'
|
||||||
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
||||||
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
|
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
|
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
|
||||||
|
|
|
@ -28,7 +28,8 @@ fi
|
||||||
|
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python3 -m pip install --upgrade wheel
|
python3 -m pip install --upgrade wheel
|
||||||
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
# TODO Update condition when cffi supports 3.13
|
||||||
|
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
|
||||||
python3 -m pip install coverage
|
python3 -m pip install coverage
|
||||||
python3 -m pip install defusedxml
|
python3 -m pip install defusedxml
|
||||||
python3 -m pip install olefile
|
python3 -m pip install olefile
|
||||||
|
@ -38,7 +39,8 @@ python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
if [[ $(uname) != CYGWIN* ]]; then
|
||||||
python3 -m pip install numpy
|
# TODO Update condition when NumPy supports 3.13
|
||||||
|
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi
|
||||||
|
|
||||||
# PyQt6 doesn't support PyPy3
|
# PyQt6 doesn't support PyPy3
|
||||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||||
|
@ -46,6 +48,16 @@ if [[ $(uname) != CYGWIN* ]]; then
|
||||||
python3 -m pip install pyqt6
|
python3 -m pip install pyqt6
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Pyroma uses non-isolated build and fails with old setuptools
|
||||||
|
if [[
|
||||||
|
$GHA_PYTHON_VERSION == pypy3.9
|
||||||
|
|| $GHA_PYTHON_VERSION == 3.8
|
||||||
|
|| $GHA_PYTHON_VERSION == 3.9
|
||||||
|
]]; then
|
||||||
|
# To match pyproject.toml
|
||||||
|
python3 -m pip install "setuptools>=67.8"
|
||||||
|
fi
|
||||||
|
|
||||||
# webp
|
# webp
|
||||||
pushd depends && ./install_webp.sh && popd
|
pushd depends && ./install_webp.sh && popd
|
||||||
|
|
||||||
|
|
1
.ci/requirements-cibw.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
cibuildwheel==2.16.5
|
|
@ -2,15 +2,14 @@
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
# Regexes for lines to exclude from consideration
|
# Regexes for lines to exclude from consideration
|
||||||
exclude_lines =
|
exclude_also =
|
||||||
# Have to re-enable the standard pragma:
|
# Don't complain if non-runnable code isn't run
|
||||||
pragma: no cover
|
|
||||||
|
|
||||||
# Don't complain if non-runnable code isn't run:
|
|
||||||
if 0:
|
if 0:
|
||||||
if __name__ == .__main__.:
|
if __name__ == .__main__.:
|
||||||
# Don't complain about debug code
|
# Don't complain about debug code
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
# Don't complain about compatibility code for missing optional dependencies
|
||||||
|
except ImportError
|
||||||
|
|
||||||
[run]
|
[run]
|
||||||
omit =
|
omit =
|
||||||
|
|
|
@ -13,7 +13,7 @@ indent_style = space
|
||||||
|
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.yml]
|
[*.{toml,yml}]
|
||||||
# Two-space indentation
|
# Two-space indentation
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
|
6
.git-blame-ignore-revs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Flake8
|
||||||
|
8de95676e0fd89f2326b3953488ab66ff29cd2d0
|
||||||
|
# Format with Black
|
||||||
|
53a7e3500437a9fd5826bc04758f7116bd7e52dc
|
||||||
|
# Format the C code with ClangFormat
|
||||||
|
46b7e86bab79450ec0a2866c6c0c679afb659d17
|
18
.github/problem-matchers/gcc.json
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"__comment": "Based on vscode-cpptools' Extension/package.json gcc rule",
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "gcc-problem-matcher",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^\\s*(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||||
|
"file": 1,
|
||||||
|
"line": 2,
|
||||||
|
"column": 3,
|
||||||
|
"severity": 4,
|
||||||
|
"message": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
2
.github/release-drafter.yml
vendored
|
@ -13,6 +13,8 @@ categories:
|
||||||
label: "Removal"
|
label: "Removal"
|
||||||
- title: "Testing"
|
- title: "Testing"
|
||||||
label: "Testing"
|
label: "Testing"
|
||||||
|
- title: "Type hints"
|
||||||
|
label: "Type hints"
|
||||||
|
|
||||||
exclude-labels:
|
exclude-labels:
|
||||||
- "changelog: skip"
|
- "changelog: skip"
|
||||||
|
|
4
.github/workflows/cifuzz.yml
vendored
|
@ -42,13 +42,13 @@ jobs:
|
||||||
language: python
|
language: python
|
||||||
dry-run: false
|
dry-run: false
|
||||||
- name: Upload New Crash
|
- name: Upload New Crash
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure() && steps.build.outcome == 'success'
|
if: failure() && steps.build.outcome == 'success'
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
name: artifacts
|
||||||
path: ./out/artifacts
|
path: ./out/artifacts
|
||||||
- name: Upload Legacy Crash
|
- name: Upload Legacy Crash
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: steps.run.outcome == 'success'
|
if: steps.run.outcome == 'success'
|
||||||
with:
|
with:
|
||||||
name: crash
|
name: crash
|
||||||
|
|
14
.github/workflows/docs.yml
vendored
|
@ -33,20 +33,30 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: ".ci/*.sh"
|
cache-dependency-path: |
|
||||||
|
".ci/*.sh"
|
||||||
|
"pyproject.toml"
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
|
- name: Cache libimagequant
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: cache-libimagequant
|
||||||
|
with:
|
||||||
|
path: ~/cache-libimagequant
|
||||||
|
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
|
||||||
|
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
run: |
|
run: |
|
||||||
.ci/install.sh
|
.ci/install.sh
|
||||||
env:
|
env:
|
||||||
GHA_PYTHON_VERSION: "3.x"
|
GHA_PYTHON_VERSION: "3.x"
|
||||||
|
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
|
10
.github/workflows/lint.yml
vendored
|
@ -2,6 +2,9 @@ name: Lint
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
@ -20,7 +23,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: pre-commit cache
|
- name: pre-commit cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pre-commit
|
path: ~/.cache/pre-commit
|
||||||
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
||||||
|
@ -28,7 +31,7 @@ jobs:
|
||||||
lint-pre-commit-
|
lint-pre-commit-
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
cache: pip
|
cache: pip
|
||||||
|
@ -46,3 +49,6 @@ jobs:
|
||||||
run: tox -e lint
|
run: tox -e lint
|
||||||
env:
|
env:
|
||||||
PRE_COMMIT_COLOR: always
|
PRE_COMMIT_COLOR: always
|
||||||
|
|
||||||
|
- name: Mypy
|
||||||
|
run: tox -e mypy
|
||||||
|
|
18
.github/workflows/macos-install.sh
vendored
|
@ -2,10 +2,21 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
|
brew install \
|
||||||
|
freetype \
|
||||||
|
ghostscript \
|
||||||
|
libimagequant \
|
||||||
|
libjpeg \
|
||||||
|
libraqm \
|
||||||
|
libtiff \
|
||||||
|
little-cms2 \
|
||||||
|
openjpeg \
|
||||||
|
webp
|
||||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||||
|
|
||||||
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
# TODO Update condition when cffi supports 3.13
|
||||||
|
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
|
||||||
|
|
||||||
python3 -m pip install coverage
|
python3 -m pip install coverage
|
||||||
python3 -m pip install defusedxml
|
python3 -m pip install defusedxml
|
||||||
python3 -m pip install olefile
|
python3 -m pip install olefile
|
||||||
|
@ -14,7 +25,8 @@ python3 -m pip install -U pytest-cov
|
||||||
python3 -m pip install -U pytest-timeout
|
python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
|
|
||||||
python3 -m pip install numpy
|
# TODO Update condition when NumPy supports 3.13
|
||||||
|
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
2
.github/workflows/stale.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Check issues"
|
- name: "Check issues"
|
||||||
uses: actions/stale@v8
|
uses: actions/stale@v9
|
||||||
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/system-info.py
vendored
|
@ -6,6 +6,8 @@ This sort of info is missing from GitHub Actions.
|
||||||
Requested here:
|
Requested here:
|
||||||
https://github.com/actions/virtual-environments/issues/79
|
https://github.com/actions/virtual-environments/issues/79
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
|
|
11
.github/workflows/test-cygwin.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -16,7 +15,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -49,7 +47,7 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Cygwin
|
- name: Install Cygwin
|
||||||
uses: cygwin/cygwin-install-action@v4
|
uses: egor-tensin/setup-cygwin@v4
|
||||||
with:
|
with:
|
||||||
platform: x86_64
|
platform: x86_64
|
||||||
packages: >
|
packages: >
|
||||||
|
@ -71,6 +69,7 @@ jobs:
|
||||||
make
|
make
|
||||||
netpbm
|
netpbm
|
||||||
perl
|
perl
|
||||||
|
python39=3.9.16-1
|
||||||
python3${{ matrix.python-minor-version }}-cffi
|
python3${{ matrix.python-minor-version }}-cffi
|
||||||
python3${{ matrix.python-minor-version }}-cython
|
python3${{ matrix.python-minor-version }}-cython
|
||||||
python3${{ matrix.python-minor-version }}-devel
|
python3${{ matrix.python-minor-version }}-devel
|
||||||
|
@ -88,7 +87,7 @@ jobs:
|
||||||
|
|
||||||
- name: Select Python version
|
- name: Select Python version
|
||||||
run: |
|
run: |
|
||||||
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
|
ln -sf c:/tools/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/tools/cygwin/bin/python3
|
||||||
|
|
||||||
- name: Get latest NumPy version
|
- name: Get latest NumPy version
|
||||||
id: latest-numpy
|
id: latest-numpy
|
||||||
|
@ -97,7 +96,7 @@ jobs:
|
||||||
python3 -m pip list --outdated | grep numpy | sed -r 's/ +/ /g' | cut -d ' ' -f 3 | sed 's/^/version=/' >> $GITHUB_OUTPUT
|
python3 -m pip list --outdated | grep numpy | sed -r 's/ +/ /g' | cut -d ' ' -f 3 | sed 's/^/version=/' >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: pip cache
|
- name: pip cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
||||||
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }}
|
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }}
|
||||||
|
@ -132,7 +131,7 @@ jobs:
|
||||||
dash.exe -c "mkdir -p Tests/errors"
|
dash.exe -c "mkdir -p Tests/errors"
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: errors
|
name: errors
|
||||||
|
|
4
.github/workflows/test-docker.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -16,7 +15,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -51,8 +49,8 @@ jobs:
|
||||||
debian-11-bullseye-amd64,
|
debian-11-bullseye-amd64,
|
||||||
debian-12-bookworm-x86,
|
debian-12-bookworm-x86,
|
||||||
debian-12-bookworm-amd64,
|
debian-12-bookworm-amd64,
|
||||||
fedora-37-amd64,
|
|
||||||
fedora-38-amd64,
|
fedora-38-amd64,
|
||||||
|
fedora-39-amd64,
|
||||||
gentoo,
|
gentoo,
|
||||||
ubuntu-20.04-focal-amd64,
|
ubuntu-20.04-focal-amd64,
|
||||||
ubuntu-22.04-jammy-amd64,
|
ubuntu-22.04-jammy-amd64,
|
||||||
|
|
2
.github/workflows/test-mingw.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -16,7 +15,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
65
.github/workflows/test-windows.yml
vendored
|
@ -2,11 +2,12 @@ name: Test Windows
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -14,7 +15,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -32,7 +32,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12"]
|
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
|
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
@ -56,25 +56,26 @@ jobs:
|
||||||
|
|
||||||
# sets env: pythonLocation
|
# sets env: pythonLocation
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
allow-prereleases: true
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: ".github/workflows/test-windows.yml"
|
cache-dependency-path: ".github/workflows/test-windows.yml"
|
||||||
|
|
||||||
- name: Print build system information
|
- name: Print build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
- name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml
|
- name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma
|
||||||
run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml
|
run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
id: install
|
id: install
|
||||||
run: |
|
run: |
|
||||||
7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
choco install nasm --no-progress
|
||||||
echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH
|
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
choco install ghostscript --version=10.0.0.20230317
|
choco install ghostscript --version=10.0.0.20230317 --no-progress
|
||||||
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
|
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
# Install extra test images
|
# Install extra test images
|
||||||
|
@ -88,7 +89,7 @@ jobs:
|
||||||
|
|
||||||
- name: Cache build
|
- name: Cache build
|
||||||
id: build-cache
|
id: build-cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: winbuild\build
|
path: winbuild\build
|
||||||
key:
|
key:
|
||||||
|
@ -166,7 +167,6 @@ jobs:
|
||||||
- name: Build Pillow
|
- name: Build Pillow
|
||||||
run: |
|
run: |
|
||||||
$FLAGS="-C raqm=vendor -C fribidi=vendor"
|
$FLAGS="-C raqm=vendor -C fribidi=vendor"
|
||||||
if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS+=" -C imagequant=disable" }
|
|
||||||
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
|
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
|
||||||
& $env:pythonLocation\python.exe selftest.py --installed
|
& $env:pythonLocation\python.exe selftest.py --installed
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
@ -190,7 +190,7 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: errors
|
name: errors
|
||||||
|
@ -208,47 +208,6 @@ jobs:
|
||||||
flags: GHA_Windows
|
flags: GHA_Windows
|
||||||
name: ${{ runner.os }} Python ${{ matrix.python-version }}
|
name: ${{ runner.os }} Python ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Build wheel
|
|
||||||
id: wheel
|
|
||||||
if: "github.event_name != 'pull_request'"
|
|
||||||
run: |
|
|
||||||
mkdir fribidi
|
|
||||||
copy winbuild\build\bin\fribidi* fribidi
|
|
||||||
setlocal EnableDelayedExpansion
|
|
||||||
for %%f in (winbuild\build\license\*) do (
|
|
||||||
set x=%%~nf
|
|
||||||
rem Skip FriBiDi license, it is not included in the wheel.
|
|
||||||
set fribidi=!x:~0,7!
|
|
||||||
if NOT !fribidi!==fribidi (
|
|
||||||
rem Skip imagequant license, it is not included in the wheel.
|
|
||||||
set libimagequant=!x:~0,13!
|
|
||||||
if NOT !libimagequant!==libimagequant (
|
|
||||||
echo. >> LICENSE
|
|
||||||
echo ===== %%~nf ===== >> LICENSE
|
|
||||||
echo. >> LICENSE
|
|
||||||
type %%f >> LICENSE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo dist=dist-%%a >> %GITHUB_OUTPUT%
|
|
||||||
call winbuild\\build\\build_env.cmd
|
|
||||||
%pythonLocation%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor -C imagequant=disable .
|
|
||||||
shell: cmd
|
|
||||||
|
|
||||||
- name: Upload wheel
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
if: "github.event_name != 'pull_request'"
|
|
||||||
with:
|
|
||||||
name: ${{ steps.wheel.outputs.dist }}
|
|
||||||
path: "*.whl"
|
|
||||||
|
|
||||||
- name: Upload fribidi.dll
|
|
||||||
if: "github.event_name != 'pull_request' && matrix.python-version == 3.11"
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: fribidi
|
|
||||||
path: fribidi\*
|
|
||||||
|
|
||||||
success:
|
success:
|
||||||
permissions:
|
permissions:
|
||||||
contents: none
|
contents: none
|
||||||
|
|
46
.github/workflows/test.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -16,7 +15,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -28,6 +26,9 @@ concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
|
@ -35,12 +36,13 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [
|
os: [
|
||||||
"macos-latest",
|
"macos-14",
|
||||||
"ubuntu-latest",
|
"ubuntu-latest",
|
||||||
]
|
]
|
||||||
python-version: [
|
python-version: [
|
||||||
"pypy3.10",
|
"pypy3.10",
|
||||||
"pypy3.9",
|
"pypy3.9",
|
||||||
|
"3.13",
|
||||||
"3.12",
|
"3.12",
|
||||||
"3.11",
|
"3.11",
|
||||||
"3.10",
|
"3.10",
|
||||||
|
@ -48,11 +50,21 @@ jobs:
|
||||||
"3.8",
|
"3.8",
|
||||||
]
|
]
|
||||||
include:
|
include:
|
||||||
- python-version: "3.9"
|
- python-version: "3.11"
|
||||||
PYTHONOPTIMIZE: 1
|
PYTHONOPTIMIZE: 1
|
||||||
REVERSE: "--reverse"
|
REVERSE: "--reverse"
|
||||||
- python-version: "3.8"
|
- python-version: "3.10"
|
||||||
PYTHONOPTIMIZE: 2
|
PYTHONOPTIMIZE: 2
|
||||||
|
# M1 only available for 3.10+
|
||||||
|
- os: "macos-latest"
|
||||||
|
python-version: "3.9"
|
||||||
|
- os: "macos-latest"
|
||||||
|
python-version: "3.8"
|
||||||
|
exclude:
|
||||||
|
- os: "macos-14"
|
||||||
|
python-version: "3.9"
|
||||||
|
- os: "macos-14"
|
||||||
|
python-version: "3.8"
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||||
|
@ -61,21 +73,33 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
allow-prereleases: true
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: ".ci/*.sh"
|
cache-dependency-path: |
|
||||||
|
".ci/*.sh"
|
||||||
|
"pyproject.toml"
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
|
- name: Cache libimagequant
|
||||||
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: cache-libimagequant
|
||||||
|
with:
|
||||||
|
path: ~/cache-libimagequant
|
||||||
|
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
|
||||||
|
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
if: startsWith(matrix.os, 'ubuntu')
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
run: |
|
run: |
|
||||||
.ci/install.sh
|
.ci/install.sh
|
||||||
env:
|
env:
|
||||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||||
|
|
||||||
- name: Install macOS dependencies
|
- name: Install macOS dependencies
|
||||||
if: startsWith(matrix.os, 'macOS')
|
if: startsWith(matrix.os, 'macOS')
|
||||||
|
@ -84,6 +108,10 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Register gcc problem matcher
|
||||||
|
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'"
|
||||||
|
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
.ci/build.sh
|
.ci/build.sh
|
||||||
|
@ -110,7 +138,7 @@ jobs:
|
||||||
mkdir -p Tests/errors
|
mkdir -p Tests/errors
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: errors
|
name: errors
|
||||||
|
@ -123,7 +151,7 @@ jobs:
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
|
flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }}
|
||||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||||
gcov: true
|
gcov: true
|
||||||
|
|
||||||
|
|
40
.github/workflows/wheels-build.sh
vendored
|
@ -1,40 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
|
||||||
# webp, zstd, xz, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb
|
|
||||||
# libxdmcp causes an issue on macOS < 11
|
|
||||||
# curl from brew requires zstd, use system curl
|
|
||||||
# if php is installed, brew tries to reinstall these after installing openblas
|
|
||||||
# remove lcms2 and libpng to fix building openjpeg on arm64
|
|
||||||
brew remove --ignore-dependencies webp zstd xz libpng libtiff libxcb libxdmcp curl php lcms2 ghostscript
|
|
||||||
|
|
||||||
brew install pkg-config
|
|
||||||
|
|
||||||
if [[ "$PLAT" == "arm64" ]]; then
|
|
||||||
export MACOSX_DEPLOYMENT_TARGET="11.0"
|
|
||||||
else
|
|
||||||
export MACOSX_DEPLOYMENT_TARGET="10.10"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$MB_PYTHON_VERSION" == pypy3* ]]; then
|
|
||||||
MB_PYTHON_OSX_VER="10.9"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "::group::Install a virtualenv"
|
|
||||||
source wheels/multibuild/common_utils.sh
|
|
||||||
source wheels/multibuild/travis_steps.sh
|
|
||||||
python3 -m pip install virtualenv
|
|
||||||
before_install
|
|
||||||
echo "::endgroup::"
|
|
||||||
|
|
||||||
echo "::group::Build wheel"
|
|
||||||
build_wheel
|
|
||||||
ls -l "${GITHUB_WORKSPACE}/${WHEEL_SDIR}/"
|
|
||||||
echo "::endgroup::"
|
|
||||||
|
|
||||||
if [[ $MACOSX_DEPLOYMENT_TARGET != "11.0" ]]; then
|
|
||||||
echo "::group::Test wheel"
|
|
||||||
install_run
|
|
||||||
echo "::endgroup::"
|
|
||||||
fi
|
|
151
.github/workflows/wheels-dependencies.sh
vendored
Executable file
|
@ -0,0 +1,151 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Define custom utilities
|
||||||
|
# Test for macOS with [ -n "$IS_MACOS" ]
|
||||||
|
if [ -z "$IS_MACOS" ]; then
|
||||||
|
export MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
|
||||||
|
export MB_ML_VER=${AUDITWHEEL_POLICY:9}
|
||||||
|
fi
|
||||||
|
export PLAT=$CIBW_ARCHS
|
||||||
|
source wheels/multibuild/common_utils.sh
|
||||||
|
source wheels/multibuild/library_builders.sh
|
||||||
|
if [ -z "$IS_MACOS" ]; then
|
||||||
|
source wheels/multibuild/manylinux_utils.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARCHIVE_SDIR=pillow-depends-main
|
||||||
|
|
||||||
|
# Package versions for fresh source builds
|
||||||
|
FREETYPE_VERSION=2.13.2
|
||||||
|
HARFBUZZ_VERSION=8.3.0
|
||||||
|
LIBPNG_VERSION=1.6.40
|
||||||
|
JPEGTURBO_VERSION=3.0.1
|
||||||
|
OPENJPEG_VERSION=2.5.0
|
||||||
|
XZ_VERSION=5.4.5
|
||||||
|
TIFF_VERSION=4.6.0
|
||||||
|
LCMS2_VERSION=2.16
|
||||||
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
|
GIFLIB_VERSION=5.1.4
|
||||||
|
else
|
||||||
|
GIFLIB_VERSION=5.2.1
|
||||||
|
fi
|
||||||
|
if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then
|
||||||
|
ZLIB_VERSION=1.3
|
||||||
|
else
|
||||||
|
ZLIB_VERSION=1.2.8
|
||||||
|
fi
|
||||||
|
LIBWEBP_VERSION=1.3.2
|
||||||
|
BZIP2_VERSION=1.0.8
|
||||||
|
LIBXCB_VERSION=1.16
|
||||||
|
BROTLI_VERSION=1.1.0
|
||||||
|
|
||||||
|
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "x86_64" ]]; then
|
||||||
|
function build_openjpeg {
|
||||||
|
local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz openjpeg-2.5.0.tar.gz)
|
||||||
|
(cd $out_dir \
|
||||||
|
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
|
||||||
|
&& make install)
|
||||||
|
touch openjpeg-stamp
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
function build_brotli {
|
||||||
|
local cmake=$(get_modern_cmake)
|
||||||
|
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-1.1.0.tar.gz)
|
||||||
|
(cd $out_dir \
|
||||||
|
&& $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
|
||||||
|
&& make install)
|
||||||
|
if [[ "$MB_ML_LIBC" == "manylinux" ]]; then
|
||||||
|
cp /usr/local/lib64/libbrotli* /usr/local/lib
|
||||||
|
cp /usr/local/lib64/pkgconfig/libbrotli* /usr/local/lib/pkgconfig
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function build {
|
||||||
|
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||||
|
export BUILD_PREFIX="/usr/local"
|
||||||
|
fi
|
||||||
|
build_xz
|
||||||
|
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
|
||||||
|
yum remove -y zlib-devel
|
||||||
|
fi
|
||||||
|
build_new_zlib
|
||||||
|
|
||||||
|
build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto
|
||||||
|
if [ -n "$IS_MACOS" ]; then
|
||||||
|
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||||
|
build_simple xorgproto 2023.2 https://www.x.org/pub/individual/proto
|
||||||
|
build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib
|
||||||
|
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
|
||||||
|
if [ -f /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc ]; then
|
||||||
|
cp /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc /Library/Frameworks/Python.framework/Versions/Current/lib/pkgconfig/xcb-proto.pc
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc
|
||||||
|
fi
|
||||||
|
build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
|
||||||
|
|
||||||
|
build_libjpeg_turbo
|
||||||
|
build_tiff
|
||||||
|
build_libpng
|
||||||
|
build_lcms2
|
||||||
|
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||||
|
for dylib in libjpeg.dylib libtiff.dylib liblcms2.dylib; do
|
||||||
|
cp $BUILD_PREFIX/lib/$dylib /opt/arm64-builds/lib
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
build_openjpeg
|
||||||
|
|
||||||
|
ORIGINAL_CFLAGS=$CFLAGS
|
||||||
|
CFLAGS="$CFLAGS -O3 -DNDEBUG"
|
||||||
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
|
CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names"
|
||||||
|
fi
|
||||||
|
build_libwebp
|
||||||
|
CFLAGS=$ORIGINAL_CFLAGS
|
||||||
|
|
||||||
|
build_brotli
|
||||||
|
|
||||||
|
if [ -n "$IS_MACOS" ]; then
|
||||||
|
# Custom freetype build
|
||||||
|
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
|
||||||
|
else
|
||||||
|
build_freetype
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$IS_MACOS" ]; then
|
||||||
|
export FREETYPE_LIBS=-lfreetype
|
||||||
|
export FREETYPE_CFLAGS=-I/usr/local/include/freetype2/
|
||||||
|
fi
|
||||||
|
build_simple harfbuzz $HARFBUZZ_VERSION https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION tar.xz --with-freetype=yes --with-glib=no
|
||||||
|
if [ -z "$IS_MACOS" ]; then
|
||||||
|
export FREETYPE_LIBS=""
|
||||||
|
export FREETYPE_CFLAGS=""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Any stuff that you need to do before you start building the wheels
|
||||||
|
# Runs in the root directory of this repository.
|
||||||
|
curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
|
||||||
|
untar pillow-depends-main.zip
|
||||||
|
|
||||||
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
|
# webp, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb
|
||||||
|
# libxdmcp causes an issue on macOS < 11
|
||||||
|
# if php is installed, brew tries to reinstall these after installing openblas
|
||||||
|
# remove cairo to fix building harfbuzz on arm64
|
||||||
|
# remove lcms2 and libpng to fix building openjpeg on arm64
|
||||||
|
# remove zstd to avoid inclusion on x86_64
|
||||||
|
# curl from brew requires zstd, use system curl
|
||||||
|
brew remove --ignore-dependencies webp libpng libtiff libxcb libxdmcp curl php cairo lcms2 ghostscript zstd
|
||||||
|
|
||||||
|
brew install pkg-config
|
||||||
|
fi
|
||||||
|
|
||||||
|
wrap_wheel_builder build
|
||||||
|
|
||||||
|
# Append licenses
|
||||||
|
for filename in wheels/dependency_licenses/*; do
|
||||||
|
echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE
|
||||||
|
cat $filename >> LICENSE
|
||||||
|
done
|
69
.github/workflows/wheels-linux.yml
vendored
|
@ -1,69 +0,0 @@
|
||||||
name: Build Linux wheels
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
artifacts-name:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
env:
|
|
||||||
CONFIG_PATH: "wheels/config.sh"
|
|
||||||
REPO_DIR: "."
|
|
||||||
TEST_DEPENDS: "pytest pytest-timeout"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: ${{ matrix.python }} ${{ matrix.mb-ml-libc }}${{ matrix.mb-ml-ver }}
|
|
||||||
runs-on: "ubuntu-latest"
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
python: [
|
|
||||||
"pypy3.9-7.3.13",
|
|
||||||
"pypy3.10-7.3.13",
|
|
||||||
"3.8",
|
|
||||||
"3.9",
|
|
||||||
"3.10",
|
|
||||||
"3.11",
|
|
||||||
"3.12",
|
|
||||||
]
|
|
||||||
mb-ml-libc: [ "manylinux" ]
|
|
||||||
mb-ml-ver: [ 2014, "_2_28" ]
|
|
||||||
include:
|
|
||||||
- python: "3.8"
|
|
||||||
mb-ml-libc: "musllinux"
|
|
||||||
mb-ml-ver: "_1_1"
|
|
||||||
- python: "3.9"
|
|
||||||
mb-ml-libc: "musllinux"
|
|
||||||
mb-ml-ver: "_1_1"
|
|
||||||
- python: "3.10"
|
|
||||||
mb-ml-libc: "musllinux"
|
|
||||||
mb-ml-ver: "_1_1"
|
|
||||||
- python: "3.11"
|
|
||||||
mb-ml-libc: "musllinux"
|
|
||||||
mb-ml-ver: "_1_1"
|
|
||||||
- python: "3.12"
|
|
||||||
mb-ml-libc: "musllinux"
|
|
||||||
mb-ml-ver: "_1_1"
|
|
||||||
env:
|
|
||||||
MB_PYTHON_VERSION: ${{ matrix.python }}
|
|
||||||
MB_ML_LIBC: ${{ matrix.mb-ml-libc }}
|
|
||||||
MB_ML_VER: ${{ matrix.mb-ml-ver }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: "3.x"
|
|
||||||
- name: Build Wheel
|
|
||||||
run: .github/workflows/wheels-build.sh
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: ${{ inputs.artifacts-name }}
|
|
||||||
path: wheelhouse/*.whl
|
|
||||||
# Uncomment to get SSH access for testing
|
|
||||||
# - name: Setup tmate session
|
|
||||||
# if: failure()
|
|
||||||
# uses: mxschmitt/action-tmate@v3
|
|
57
.github/workflows/wheels-macos.yml
vendored
|
@ -1,57 +0,0 @@
|
||||||
name: Build macOS wheels
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
artifacts-name:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
env:
|
|
||||||
CONFIG_PATH: "wheels/config.sh"
|
|
||||||
REPO_DIR: "."
|
|
||||||
TEST_DEPENDS: "pytest pytest-timeout"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: ${{ matrix.python }} ${{ matrix.platform }}
|
|
||||||
runs-on: "macos-latest"
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
python: [
|
|
||||||
"pypy3.9-7.3.13",
|
|
||||||
"pypy3.10-7.3.13",
|
|
||||||
"3.8",
|
|
||||||
"3.9",
|
|
||||||
"3.10",
|
|
||||||
"3.11",
|
|
||||||
"3.12",
|
|
||||||
]
|
|
||||||
platform: [ "x86_64", "arm64" ]
|
|
||||||
exclude:
|
|
||||||
- python: "pypy3.9-7.3.13"
|
|
||||||
platform: "arm64"
|
|
||||||
- python: "pypy3.10-7.3.13"
|
|
||||||
platform: "arm64"
|
|
||||||
env:
|
|
||||||
PLAT: ${{ matrix.platform }}
|
|
||||||
MB_PYTHON_VERSION: ${{ matrix.python }}
|
|
||||||
TRAVIS_OS_NAME: "osx"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: "3.x"
|
|
||||||
- name: Build Wheel
|
|
||||||
run: .github/workflows/wheels-build.sh
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: ${{ inputs.artifacts-name }}
|
|
||||||
path: wheelhouse/*.whl
|
|
||||||
# Uncomment to get SSH access for testing
|
|
||||||
# - name: Setup tmate session
|
|
||||||
# if: failure()
|
|
||||||
# uses: mxschmitt/action-tmate@v3
|
|
22
.github/workflows/wheels-test.ps1
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
param ([string]$venv, [string]$pillow="C:\pillow")
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
Set-PSDebug -Trace 1
|
||||||
|
if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
|
||||||
|
# unlike CPython, PyPy requires Visual C++ Redistributable to be installed
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
Invoke-WebRequest -Uri 'https://aka.ms/vs/15/release/vc_redist.x64.exe' -OutFile 'vc_redist.x64.exe'
|
||||||
|
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
|
||||||
|
}
|
||||||
|
$env:path += ";$pillow\winbuild\build\bin\"
|
||||||
|
& "$venv\Scripts\activate.ps1"
|
||||||
|
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
|
||||||
|
cd $pillow
|
||||||
|
& python -VV
|
||||||
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
|
& python selftest.py
|
||||||
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
|
& python -m pytest -vx Tests\check_wheel.py
|
||||||
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
|
& python -m pytest -vx Tests
|
||||||
|
if (!$?) { exit $LASTEXITCODE }
|
25
.github/workflows/wheels-test.sh
vendored
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
brew install fribidi
|
||||||
|
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||||
|
elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
|
||||||
|
apk add curl fribidi
|
||||||
|
else
|
||||||
|
yum install -y fribidi
|
||||||
|
fi
|
||||||
|
if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; then
|
||||||
|
python3 -m pip install numpy
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "test-images-main" ]; then
|
||||||
|
curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
|
||||||
|
unzip pillow-test-images.zip
|
||||||
|
mv test-images-main/* Tests/images
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Runs tests
|
||||||
|
python3 selftest.py
|
||||||
|
python3 -m pytest Tests/check_wheel.py
|
||||||
|
python3 -m pytest
|
260
.github/workflows/wheels.yml
vendored
|
@ -3,14 +3,20 @@ name: Wheels
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/wheels*.yml"
|
- ".ci/requirements-cibw.txt"
|
||||||
|
- ".github/workflows/wheel*"
|
||||||
- "wheels/*"
|
- "wheels/*"
|
||||||
|
- "winbuild/build_prepare.py"
|
||||||
|
- "winbuild/fribidi.cmake"
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- "*"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/wheels*.yml"
|
- ".ci/requirements-cibw.txt"
|
||||||
|
- ".github/workflows/wheel*"
|
||||||
- "wheels/*"
|
- "wheels/*"
|
||||||
|
- "winbuild/build_prepare.py"
|
||||||
|
- "winbuild/fribidi.cmake"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -20,23 +26,239 @@ concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
macos:
|
build-1-QEMU-emulated-wheels:
|
||||||
uses: ./.github/workflows/wheels-macos.yml
|
name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }}
|
||||||
with:
|
|
||||||
artifacts-name: "wheels"
|
|
||||||
|
|
||||||
linux:
|
|
||||||
uses: ./.github/workflows/wheels-linux.yml
|
|
||||||
with:
|
|
||||||
artifacts-name: "wheels"
|
|
||||||
|
|
||||||
success:
|
|
||||||
permissions:
|
|
||||||
contents: none
|
|
||||||
needs: [macos, linux]
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Wheels Successful
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version:
|
||||||
|
- pp39
|
||||||
|
- pp310
|
||||||
|
- cp38
|
||||||
|
- cp39
|
||||||
|
- cp310
|
||||||
|
- cp311
|
||||||
|
- cp312
|
||||||
|
spec:
|
||||||
|
- manylinux2014
|
||||||
|
- manylinux_2_28
|
||||||
|
- musllinux
|
||||||
|
exclude:
|
||||||
|
- { python-version: pp39, spec: musllinux }
|
||||||
|
- { python-version: pp310, spec: musllinux }
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Success
|
- uses: actions/checkout@v4
|
||||||
run: echo Wheels Successful
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
# https://github.com/docker/setup-qemu-action
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Install cibuildwheel
|
||||||
|
run: |
|
||||||
|
python3 -m pip install -r .ci/requirements-cibw.txt
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
run: |
|
||||||
|
python3 -m cibuildwheel --output-dir wheelhouse
|
||||||
|
env:
|
||||||
|
# Build only the currently selected Linux architecture (so we can
|
||||||
|
# parallelise for speed).
|
||||||
|
CIBW_ARCHS: "aarch64"
|
||||||
|
# Likewise, select only one Python version per job to speed this up.
|
||||||
|
CIBW_BUILD: "${{ matrix.python-version }}-${{ matrix.spec == 'musllinux' && 'musllinux' || 'manylinux' }}*"
|
||||||
|
# Extra options for manylinux.
|
||||||
|
CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.spec }}
|
||||||
|
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.spec }}
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist-qemu-${{ matrix.python-version }}-${{ matrix.spec }}
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
|
build-2-native-wheels:
|
||||||
|
name: ${{ matrix.name }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: "macOS x86_64"
|
||||||
|
os: macos-latest
|
||||||
|
cibw_arch: x86_64
|
||||||
|
macosx_deployment_target: "10.10"
|
||||||
|
- name: "macOS arm64"
|
||||||
|
os: macos-latest
|
||||||
|
cibw_arch: arm64
|
||||||
|
macosx_deployment_target: "11.0"
|
||||||
|
- name: "manylinux2014 and musllinux x86_64"
|
||||||
|
os: ubuntu-latest
|
||||||
|
cibw_arch: x86_64
|
||||||
|
- name: "manylinux_2_28 x86_64"
|
||||||
|
os: ubuntu-latest
|
||||||
|
cibw_arch: x86_64
|
||||||
|
build: "*manylinux*"
|
||||||
|
manylinux: "manylinux_2_28"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- name: Install cibuildwheel
|
||||||
|
run: |
|
||||||
|
python3 -m pip install -r .ci/requirements-cibw.txt
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
run: |
|
||||||
|
python3 -m cibuildwheel --output-dir wheelhouse
|
||||||
|
env:
|
||||||
|
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||||
|
CIBW_BUILD: ${{ matrix.build }}
|
||||||
|
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
|
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
|
CIBW_SKIP: pp38-*
|
||||||
|
CIBW_TEST_SKIP: "*-macosx_arm64"
|
||||||
|
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist-${{ matrix.os }}-${{ matrix.cibw_arch }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }}
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
|
windows:
|
||||||
|
name: Windows ${{ matrix.cibw_arch }}
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- cibw_arch: x86
|
||||||
|
- cibw_arch: AMD64
|
||||||
|
- cibw_arch: ARM64
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Checkout extra test images
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: python-pillow/test-images
|
||||||
|
path: Tests\test-images
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- name: Install cibuildwheel
|
||||||
|
run: |
|
||||||
|
python.exe -m pip install -r .ci/requirements-cibw.txt
|
||||||
|
|
||||||
|
- name: Prepare for build
|
||||||
|
run: |
|
||||||
|
choco install nasm --no-progress
|
||||||
|
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
|
# Install extra test images
|
||||||
|
xcopy /S /Y Tests\test-images\* Tests\images
|
||||||
|
|
||||||
|
& python.exe winbuild\build_prepare.py -v --no-imagequant --architecture=${{ matrix.cibw_arch }}
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
run: |
|
||||||
|
setlocal EnableDelayedExpansion
|
||||||
|
for %%f in (winbuild\build\license\*) do (
|
||||||
|
set x=%%~nf
|
||||||
|
rem Skip FriBiDi license, it is not included in the wheel.
|
||||||
|
set fribidi=!x:~0,7!
|
||||||
|
if NOT !fribidi!==fribidi (
|
||||||
|
rem Skip imagequant license, it is not included in the wheel.
|
||||||
|
set libimagequant=!x:~0,13!
|
||||||
|
if NOT !libimagequant!==libimagequant (
|
||||||
|
echo. >> LICENSE
|
||||||
|
echo ===== %%~nf ===== >> LICENSE
|
||||||
|
echo. >> LICENSE
|
||||||
|
type %%f >> LICENSE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
call winbuild\\build\\build_env.cmd
|
||||||
|
%pythonLocation%\python.exe -m cibuildwheel . --output-dir wheelhouse
|
||||||
|
env:
|
||||||
|
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||||
|
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
||||||
|
CIBW_CACHE_PATH: "C:\\cibw"
|
||||||
|
CIBW_SKIP: pp38-*
|
||||||
|
CIBW_TEST_SKIP: "*-win_arm64"
|
||||||
|
CIBW_TEST_COMMAND: 'docker run --rm
|
||||||
|
-v {project}:C:\pillow
|
||||||
|
-v C:\cibw:C:\cibw
|
||||||
|
-v %CD%\..\venv-test:%CD%\..\venv-test
|
||||||
|
-e CI -e GITHUB_ACTIONS
|
||||||
|
mcr.microsoft.com/windows/servercore:ltsc2022
|
||||||
|
powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test'
|
||||||
|
shell: cmd
|
||||||
|
|
||||||
|
- name: Upload wheels
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist-windows-${{ matrix.cibw_arch }}
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
|
- name: Upload fribidi.dll
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: fribidi-windows-${{ matrix.cibw_arch }}
|
||||||
|
path: winbuild\build\bin\fribidi*
|
||||||
|
|
||||||
|
sdist:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
cache: pip
|
||||||
|
cache-dependency-path: "Makefile"
|
||||||
|
|
||||||
|
- run: make sdist
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist-sdist
|
||||||
|
path: dist/*.tar.gz
|
||||||
|
|
||||||
|
pypi-publish:
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||||
|
needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Upload release to PyPI
|
||||||
|
environment:
|
||||||
|
name: release-pypi
|
||||||
|
url: https://pypi.org/p/Pillow
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: dist-*
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Publish to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
|
|
@ -1,56 +1,38 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v3.13.0
|
rev: v0.1.9
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: ruff
|
||||||
args: [--py38-plus]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
rev: 23.9.1
|
rev: 23.12.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--target-version=py38]
|
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/isort
|
|
||||||
rev: 5.12.0
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/bandit
|
- repo: https://github.com/PyCQA/bandit
|
||||||
rev: 1.7.5
|
rev: 1.7.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: bandit
|
- id: bandit
|
||||||
args: [--severity-level=high]
|
args: [--severity-level=high]
|
||||||
files: ^src/
|
files: ^src/
|
||||||
|
|
||||||
- repo: https://github.com/asottile/yesqa
|
|
||||||
rev: v1.5.0
|
|
||||||
hooks:
|
|
||||||
- id: yesqa
|
|
||||||
|
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||||
rev: v1.5.4
|
rev: v1.5.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/flake8
|
|
||||||
rev: 6.1.0
|
|
||||||
hooks:
|
|
||||||
- id: flake8
|
|
||||||
additional_dependencies:
|
|
||||||
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat, flake8-logging]
|
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||||
rev: v1.10.0
|
rev: v1.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: python-check-blanket-noqa
|
|
||||||
- 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: v4.4.0
|
rev: v4.5.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-executables-have-shebangs
|
- id: check-executables-have-shebangs
|
||||||
|
- id: check-shebang-scripts-are-executable
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-json
|
- id: check-json
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
|
@ -61,17 +43,17 @@ repos:
|
||||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||||
|
|
||||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||||
rev: v0.6.8
|
rev: v0.9.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: sphinx-lint
|
- id: sphinx-lint
|
||||||
|
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
rev: 1.2.0
|
rev: 1.5.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
|
|
||||||
- repo: https://github.com/abravalheri/validate-pyproject
|
- repo: https://github.com/abravalheri/validate-pyproject
|
||||||
rev: v0.14
|
rev: v0.15
|
||||||
hooks:
|
hooks:
|
||||||
- id: validate-pyproject
|
- id: validate-pyproject
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ formats: [pdf]
|
||||||
build:
|
build:
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
tools:
|
tools:
|
||||||
python: "3.11"
|
python: "3"
|
||||||
|
|
||||||
python:
|
python:
|
||||||
install:
|
install:
|
||||||
|
|
135
.travis.yml
|
@ -1,135 +0,0 @@
|
||||||
if: tag IS present OR type = api
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- CONFIG_PATH=wheels/config.sh
|
|
||||||
- REPO_DIR=.
|
|
||||||
- PLAT=aarch64
|
|
||||||
- TEST_DEPENDS=pytest-timeout
|
|
||||||
|
|
||||||
language: python
|
|
||||||
# Default Python version is usually 3.6
|
|
||||||
python: "3.11"
|
|
||||||
dist: focal
|
|
||||||
services: docker
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- name: "3.8 Focal manylinux2014 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER=2014
|
|
||||||
- MB_PYTHON_VERSION=3.8
|
|
||||||
- name: "3.8 Focal manylinux_2_28 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER="_2_28"
|
|
||||||
- MB_PYTHON_VERSION=3.8
|
|
||||||
- name: "3.8 musllinux_1_1 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER="_1_1"
|
|
||||||
- MB_ML_LIBC="musllinux"
|
|
||||||
- MB_PYTHON_VERSION=3.8
|
|
||||||
- name: "3.9 Focal manylinux2014 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER=2014
|
|
||||||
- MB_PYTHON_VERSION=3.9
|
|
||||||
- name: "3.9 Focal manylinux_2_28 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER="_2_28"
|
|
||||||
- MB_PYTHON_VERSION=3.9
|
|
||||||
- name: "3.9 musllinux_1_1 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER="_1_1"
|
|
||||||
- MB_ML_LIBC="musllinux"
|
|
||||||
- MB_PYTHON_VERSION=3.9
|
|
||||||
- name: "3.10 Focal manylinux2014 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER=2014
|
|
||||||
- MB_PYTHON_VERSION=3.10
|
|
||||||
- name: "3.10 Focal manylinux_2_28 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER="_2_28"
|
|
||||||
- MB_PYTHON_VERSION=3.10
|
|
||||||
- name: "3.10 musllinux_1_1 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER="_1_1"
|
|
||||||
- MB_ML_LIBC="musllinux"
|
|
||||||
- MB_PYTHON_VERSION=3.10
|
|
||||||
- name: "3.11 Focal manylinux_2_28 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER=2014
|
|
||||||
- MB_PYTHON_VERSION=3.11
|
|
||||||
- name: "3.11 Focal manylinux_2_28 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER="_2_28"
|
|
||||||
- MB_PYTHON_VERSION=3.11
|
|
||||||
- name: "3.11 musllinux_1_1 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER="_1_1"
|
|
||||||
- MB_ML_LIBC="musllinux"
|
|
||||||
- MB_PYTHON_VERSION=3.11
|
|
||||||
- name: "3.12 Focal manylinux_2_28 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER=2014
|
|
||||||
- MB_PYTHON_VERSION=3.12
|
|
||||||
- name: "3.12 Focal manylinux_2_28 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER="_2_28"
|
|
||||||
- MB_PYTHON_VERSION=3.12
|
|
||||||
- name: "3.12 musllinux_1_1 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- MB_ML_VER="_1_1"
|
|
||||||
- MB_ML_LIBC="musllinux"
|
|
||||||
- MB_PYTHON_VERSION=3.12
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- source wheels/multibuild/common_utils.sh
|
|
||||||
- source wheels/multibuild/travis_steps.sh
|
|
||||||
- before_install
|
|
||||||
|
|
||||||
install:
|
|
||||||
- build_multilinux aarch64 build_wheel
|
|
||||||
- ls -l "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/"
|
|
||||||
|
|
||||||
script:
|
|
||||||
- install_run
|
|
||||||
|
|
||||||
# Upload wheels to GitHub Releases
|
|
||||||
deploy:
|
|
||||||
provider: releases
|
|
||||||
api_key: $GITHUB_RELEASE_TOKEN
|
|
||||||
file_glob: true
|
|
||||||
file: "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/*.whl"
|
|
||||||
on:
|
|
||||||
repo: python-pillow/Pillow
|
|
||||||
tags: true
|
|
||||||
skip_cleanup: true
|
|
147
CHANGES.rst
|
@ -2,6 +2,141 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
10.3.0 (unreleased)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Do not support using test-image-results to upload images after test failures #7739
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Changed ImageMath.ops to be static #7721
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix APNG info after seeking backwards more than twice #7701
|
||||||
|
[esoma, radarhere]
|
||||||
|
|
||||||
|
- Deprecate ImageCms constants and versions() function #7702
|
||||||
|
[nulano, radarhere]
|
||||||
|
|
||||||
|
- Added PerspectiveTransform #7699
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Add support for reading and writing grayscale PFM images #7696
|
||||||
|
[nulano, hugovk]
|
||||||
|
|
||||||
|
- Add LCMS2 flags to ImageCms #7676
|
||||||
|
[nulano, radarhere, hugovk]
|
||||||
|
|
||||||
|
- Rename x64 to AMD64 in winbuild #7693
|
||||||
|
[nulano]
|
||||||
|
|
||||||
|
10.2.0 (2024-01-02)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Add ``keep_rgb`` option when saving JPEG to prevent conversion of RGB colorspace #7553
|
||||||
|
[bgilbert, radarhere]
|
||||||
|
|
||||||
|
- Trim glyph size in ImageFont.getmask() #7669, #7672
|
||||||
|
[radarhere, nulano]
|
||||||
|
|
||||||
|
- Deprecate IptcImagePlugin helpers #7664
|
||||||
|
[nulano, hugovk, radarhere]
|
||||||
|
|
||||||
|
- Allow uncompressed TIFF images to be saved in chunks #7650
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Concatenate multiple JPEG EXIF markers #7496
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Changed IPTC tile tuple to match other plugins #7661
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not assign new fp attribute when exiting context manager #7566
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support arbitrary masks for uncompressed RGB DDS images #7589
|
||||||
|
[radarhere, akx]
|
||||||
|
|
||||||
|
- Support setting ROWSPERSTRIP tag #7654
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Apply ImageFont.MAX_STRING_LENGTH to ImageFont.getmask() #7662
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Optimise ``ImageColor`` using ``functools.lru_cache`` #7657
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Restricted environment keys for ImageMath.eval() #7655
|
||||||
|
[wiredfool, radarhere]
|
||||||
|
|
||||||
|
- Optimise ``ImageMode.getmode`` using ``functools.lru_cache`` #7641
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Fix incorrect color blending for overlapping glyphs #7497
|
||||||
|
[ZachNagengast, nulano, radarhere]
|
||||||
|
|
||||||
|
- Attempt memory mapping when tile args is a string #7565
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fill identical pixels with transparency in subsequent frames when saving GIF #7568
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Corrected duration when combining multiple GIF frames into single frame #7521
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Handle disposing GIF background from outside palette #7515
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Seek past the data when skipping a PSD layer #7483
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Import plugins relative to the module #7576
|
||||||
|
[deliangyang, jaxx0n]
|
||||||
|
|
||||||
|
- Translate encoder error codes to strings; deprecate ``ImageFile.raise_oserror()`` #7609
|
||||||
|
[bgilbert, radarhere]
|
||||||
|
|
||||||
|
- Support reading BC4U and DX10 BC1 images #6486
|
||||||
|
[REDxEYE, radarhere, hugovk]
|
||||||
|
|
||||||
|
- Optimize ImageStat.Stat.extrema #7593
|
||||||
|
[florath, radarhere]
|
||||||
|
|
||||||
|
- Handle pathlib.Path in FreeTypeFont #7578
|
||||||
|
[radarhere, hugovk, nulano]
|
||||||
|
|
||||||
|
- Added support for reading DX10 BC4 DDS images #7603
|
||||||
|
[sambvfx, radarhere]
|
||||||
|
|
||||||
|
- Optimized ImageStat.Stat.count #7599
|
||||||
|
[florath]
|
||||||
|
|
||||||
|
- Correct PDF palette size when saving #7555
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed closing file pointer with olefile 0.47 #7594
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Raise ValueError when TrueType font size is not greater than zero #7584, #7587
|
||||||
|
[akx, radarhere]
|
||||||
|
|
||||||
|
- If absent, do not try to close fp when closing image #7557
|
||||||
|
[RaphaelVRossi, radarhere]
|
||||||
|
|
||||||
|
- Allow configuring JPEG restart marker interval on save #7488
|
||||||
|
[bgilbert, radarhere]
|
||||||
|
|
||||||
|
- Decrement reference count for PyObject #7549
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Implement ``streamtype=1`` option for tables-only JPEG encoding #7491
|
||||||
|
[bgilbert, radarhere]
|
||||||
|
|
||||||
|
- If save_all PNG only has one frame, do not create animated image #7522
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed frombytes() for images with a zero dimension #7493
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
10.1.0 (2023-10-15)
|
10.1.0 (2023-10-15)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -2191,7 +2326,7 @@ Changelog (Pillow)
|
||||||
- Cache EXIF information #3498
|
- Cache EXIF information #3498
|
||||||
[Glandos]
|
[Glandos]
|
||||||
|
|
||||||
- Added transparency for all PNG greyscale modes #3744
|
- Added transparency for all PNG grayscale modes #3744
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Fix deprecation warnings in Python 3.8 #3749
|
- Fix deprecation warnings in Python 3.8 #3749
|
||||||
|
@ -4693,7 +4828,7 @@ Changelog (Pillow)
|
||||||
- Fix Bicubic interpolation #970
|
- Fix Bicubic interpolation #970
|
||||||
[homm]
|
[homm]
|
||||||
|
|
||||||
- Support for 4-bit greyscale TIFF images #980
|
- Support for 4-bit grayscale TIFF images #980
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
- Updated manifest #957
|
- Updated manifest #957
|
||||||
|
@ -6768,7 +6903,7 @@ The test suite includes 750 individual tests.
|
||||||
|
|
||||||
- You can now convert directly between all modes supported by
|
- You can now convert directly between all modes supported by
|
||||||
PIL. When converting colour images to "P", PIL defaults to
|
PIL. When converting colour images to "P", PIL defaults to
|
||||||
a "web" palette and dithering. When converting greyscale
|
a "web" palette and dithering. When converting grayscale
|
||||||
images to "1", PIL uses a thresholding and dithering.
|
images to "1", PIL uses a thresholding and dithering.
|
||||||
|
|
||||||
- Added a "dither" option to "convert". By default, "convert"
|
- Added a "dither" option to "convert". By default, "convert"
|
||||||
|
@ -6846,13 +6981,13 @@ The test suite includes 530 individual tests.
|
||||||
- Fixed "paste" to allow a mask also for mode "F" images.
|
- Fixed "paste" to allow a mask also for mode "F" images.
|
||||||
|
|
||||||
- The BMP driver now saves mode "1" images. When loading images, the mode
|
- The BMP driver now saves mode "1" images. When loading images, the mode
|
||||||
is set to "L" for 8-bit files with greyscale palettes, and to "P" for
|
is set to "L" for 8-bit files with grayscale palettes, and to "P" for
|
||||||
other 8-bit files.
|
other 8-bit files.
|
||||||
|
|
||||||
- The IM driver now reads and saves "1" images (file modes "0 1" or "L 1").
|
- The IM driver now reads and saves "1" images (file modes "0 1" or "L 1").
|
||||||
|
|
||||||
- The JPEG and GIF drivers now saves "1" images. For JPEG, the image
|
- The JPEG and GIF drivers now saves "1" images. For JPEG, the image
|
||||||
is saved as 8-bit greyscale (it will load as mode "L"). For GIF, the
|
is saved as 8-bit grayscale (it will load as mode "L"). For GIF, the
|
||||||
image will be loaded as a "P" image.
|
image will be loaded as a "P" image.
|
||||||
|
|
||||||
- Fixed a potential buffer overrun in the GIF encoder.
|
- Fixed a potential buffer overrun in the GIF encoder.
|
||||||
|
@ -7156,7 +7291,7 @@ The test suite includes 400 individual tests.
|
||||||
drawing capabilities can be used to render vector and metafile
|
drawing capabilities can be used to render vector and metafile
|
||||||
formats.
|
formats.
|
||||||
|
|
||||||
- Added restricted drivers for images from Image Tools (greyscale
|
- Added restricted drivers for images from Image Tools (grayscale
|
||||||
only) and LabEye/IFUNC (common interchange modes only).
|
only) and LabEye/IFUNC (common interchange modes only).
|
||||||
|
|
||||||
- Some minor improvements to the sample scripts provided in the
|
- Some minor improvements to the sample scripts provided in the
|
||||||
|
|
2
LICENSE
|
@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
|
||||||
|
|
||||||
Pillow is the friendly PIL fork. It is
|
Pillow is the friendly PIL fork. It is
|
||||||
|
|
||||||
Copyright © 2010-2023 by Jeffrey A. Clark (Alex) and contributors.
|
Copyright © 2010-2024 by Jeffrey A. Clark (Alex) and contributors.
|
||||||
|
|
||||||
Like PIL, Pillow is licensed under the open source HPND License:
|
Like PIL, Pillow is licensed under the open source HPND License:
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,10 @@ include *.md
|
||||||
include *.py
|
include *.py
|
||||||
include *.rst
|
include *.rst
|
||||||
include *.sh
|
include *.sh
|
||||||
|
include *.toml
|
||||||
include *.txt
|
include *.txt
|
||||||
include *.yaml
|
include *.yaml
|
||||||
|
include .flake8
|
||||||
include LICENSE
|
include LICENSE
|
||||||
include Makefile
|
include Makefile
|
||||||
include tox.ini
|
include tox.ini
|
||||||
|
|
8
Makefile
|
@ -49,7 +49,7 @@ help:
|
||||||
@echo " install make and install"
|
@echo " install make and install"
|
||||||
@echo " install-coverage make and install with C coverage"
|
@echo " install-coverage make and install with C coverage"
|
||||||
@echo " lint run the lint checks"
|
@echo " lint run the lint checks"
|
||||||
@echo " lint-fix run Black and isort to (mostly) fix lint issues"
|
@echo " lint-fix run Ruff to (mostly) fix lint issues"
|
||||||
@echo " release-test run code and package tests before release"
|
@echo " release-test run code and package tests before release"
|
||||||
@echo " test run tests on installed Pillow"
|
@echo " test run tests on installed Pillow"
|
||||||
|
|
||||||
|
@ -118,6 +118,6 @@ lint:
|
||||||
.PHONY: lint-fix
|
.PHONY: lint-fix
|
||||||
lint-fix:
|
lint-fix:
|
||||||
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
|
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
|
||||||
python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort
|
python3 -m black .
|
||||||
python3 -m black --target-version py38 .
|
python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff
|
||||||
python3 -m isort .
|
python3 -m ruff --fix .
|
||||||
|
|
|
@ -48,9 +48,6 @@ As of 2019, Pillow development is
|
||||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
|
||||||
alt="GitHub Actions build status (Wheels)"
|
alt="GitHub Actions build status (Wheels)"
|
||||||
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
|
||||||
<a href="https://app.travis-ci.com/github/python-pillow/Pillow"><img
|
|
||||||
alt="Travis CI wheels build status (aarch64)"
|
|
||||||
src="https://img.shields.io/travis/com/python-pillow/Pillow/main.svg?label=aarch64%20wheels"></a>
|
|
||||||
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
|
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
|
||||||
alt="Code coverage"
|
alt="Code coverage"
|
||||||
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
||||||
|
@ -68,10 +65,10 @@ As of 2019, Pillow development is
|
||||||
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
|
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
|
||||||
alt="Tidelift"
|
alt="Tidelift"
|
||||||
src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
|
src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
|
||||||
<a href="https://pypi.org/project/Pillow/"><img
|
<a href="https://pypi.org/project/pillow/"><img
|
||||||
alt="Newest PyPI version"
|
alt="Newest PyPI version"
|
||||||
src="https://img.shields.io/pypi/v/pillow.svg"></a>
|
src="https://img.shields.io/pypi/v/pillow.svg"></a>
|
||||||
<a href="https://pypi.org/project/Pillow/"><img
|
<a href="https://pypi.org/project/pillow/"><img
|
||||||
alt="Number of PyPI downloads"
|
alt="Number of PyPI downloads"
|
||||||
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
||||||
<a href="https://www.bestpractices.dev/projects/6331"><img
|
<a href="https://www.bestpractices.dev/projects/6331"><img
|
||||||
|
|
48
RELEASING.md
|
@ -10,7 +10,7 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||||
* [ ] Develop and prepare release in `main` branch.
|
* [ ] Develop and prepare release in `main` branch.
|
||||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
|
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
|
||||||
* [ ] Check that all of the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) and [Travis CI](https://app.travis-ci.com/github/python-pillow/pillow) jobs by manually triggering them.
|
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
|
||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||||
* [ ] Update `CHANGES.rst`.
|
* [ ] Update `CHANGES.rst`.
|
||||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||||
|
@ -20,16 +20,7 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
git tag 5.2.0
|
git tag 5.2.0
|
||||||
git push --tags
|
git push --tags
|
||||||
```
|
```
|
||||||
* [ ] Create and check source distribution:
|
* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||||
```bash
|
|
||||||
make sdist
|
|
||||||
```
|
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
|
|
||||||
* [ ] Check and upload all binaries and source distributions e.g.:
|
|
||||||
```bash
|
|
||||||
python3 -m twine check --strict dist/*
|
|
||||||
python3 -m twine upload dist/Pillow-5.2.0*
|
|
||||||
```
|
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
|
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
|
||||||
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
||||||
|
@ -59,12 +50,7 @@ Released as needed for security, installation or critical bug fixes.
|
||||||
```bash
|
```bash
|
||||||
make sdist
|
make sdist
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
|
* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||||
* [ ] Check and upload all binaries and source distributions e.g.:
|
|
||||||
```bash
|
|
||||||
python3 -m twine check --strict dist/*
|
|
||||||
python3 -m twine upload dist/Pillow-5.2.1*
|
|
||||||
```
|
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
||||||
```bash
|
```bash
|
||||||
git push
|
git push
|
||||||
|
@ -86,35 +72,17 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
git tag 2.5.3
|
git tag 2.5.3
|
||||||
git push origin --tags
|
git push origin --tags
|
||||||
```
|
```
|
||||||
* [ ] Create and check source distribution:
|
* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||||
```bash
|
|
||||||
make sdist
|
|
||||||
```
|
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
|
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
||||||
```bash
|
```bash
|
||||||
git push origin 2.5.x
|
git push origin 2.5.x
|
||||||
```
|
```
|
||||||
|
|
||||||
## Binary Distributions
|
## Source and Binary Distributions
|
||||||
|
|
||||||
### macOS and Linux
|
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||||
* [ ] Download wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
has passed, including the "Upload release to PyPI" job. This will have been triggered
|
||||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
|
by the new tag.
|
||||||
```bash
|
|
||||||
gh run download --dir dist
|
|
||||||
# select wheels
|
|
||||||
```
|
|
||||||
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
|
|
||||||
and copy into `dist`.
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
|
|
||||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
|
|
||||||
```bash
|
|
||||||
gh run download --dir dist
|
|
||||||
# select dist-x.y.z
|
|
||||||
```
|
|
||||||
|
|
||||||
## Publicize Release
|
## Publicize Release
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from PIL import PyAccess
|
from PIL import PyAccess
|
||||||
|
@ -7,21 +9,21 @@ from .helper import hopper
|
||||||
# Not running this test by default. No DOS against CI.
|
# Not running this test by default. No DOS against CI.
|
||||||
|
|
||||||
|
|
||||||
def iterate_get(size, access):
|
def iterate_get(size, access) -> None:
|
||||||
(w, h) = size
|
(w, h) = size
|
||||||
for x in range(w):
|
for x in range(w):
|
||||||
for y in range(h):
|
for y in range(h):
|
||||||
access[(x, y)]
|
access[(x, y)]
|
||||||
|
|
||||||
|
|
||||||
def iterate_set(size, access):
|
def iterate_set(size, access) -> None:
|
||||||
(w, h) = size
|
(w, h) = size
|
||||||
for x in range(w):
|
for x in range(w):
|
||||||
for y in range(h):
|
for y in range(h):
|
||||||
access[(x, y)] = (x % 256, y % 256, 0)
|
access[(x, y)] = (x % 256, y % 256, 0)
|
||||||
|
|
||||||
|
|
||||||
def timer(func, label, *args):
|
def timer(func, label, *args) -> None:
|
||||||
iterations = 5000
|
iterations = 5000
|
||||||
starttime = time.time()
|
starttime = time.time()
|
||||||
for x in range(iterations):
|
for x in range(iterations):
|
||||||
|
@ -36,7 +38,7 @@ def timer(func, label, *args):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_direct():
|
def test_direct() -> None:
|
||||||
im = hopper()
|
im = hopper()
|
||||||
im.load()
|
im.load()
|
||||||
# im = Image.new("RGB", (2000, 2000), (1, 3, 2))
|
# im = Image.new("RGB", (2000, 2000), (1, 3, 2))
|
||||||
|
@ -45,7 +47,7 @@ def test_direct():
|
||||||
|
|
||||||
assert caccess[(0, 0)] == access[(0, 0)]
|
assert caccess[(0, 0)] == access[(0, 0)]
|
||||||
|
|
||||||
print("Size: %sx%s" % im.size)
|
print(f"Size: {im.width}x{im.height}")
|
||||||
timer(iterate_get, "PyAccess - get", im.size, access)
|
timer(iterate_get, "PyAccess - get", im.size, access)
|
||||||
timer(iterate_set, "PyAccess - set", im.size, access)
|
timer(iterate_set, "PyAccess - set", im.size, access)
|
||||||
timer(iterate_get, "C-api - get", im.size, caccess)
|
timer(iterate_get, "C-api - get", im.size, caccess)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env python3
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
TEST_FILE = "Tests/images/fli_overflow.fli"
|
TEST_FILE = "Tests/images/fli_overflow.fli"
|
||||||
|
|
||||||
|
|
||||||
def test_fli_overflow():
|
def test_fli_overflow() -> None:
|
||||||
# this should not crash with a malloc error or access violation
|
# this should not crash with a malloc error or access violation
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Tests potential DOS of IcnsImagePlugin with 0 length block.
|
# Tests potential DOS of IcnsImagePlugin with 0 length block.
|
||||||
# Run from anywhere that PIL is importable.
|
# Run from anywhere that PIL is importable.
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
@ -11,31 +15,34 @@ max_iterations = 10000
|
||||||
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
||||||
|
|
||||||
|
|
||||||
def _get_mem_usage():
|
def _get_mem_usage() -> float:
|
||||||
from resource import RUSAGE_SELF, getpagesize, getrusage
|
from resource import RUSAGE_SELF, getpagesize, getrusage
|
||||||
|
|
||||||
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
||||||
return mem * getpagesize() / 1024 / 1024
|
return mem * getpagesize() / 1024 / 1024
|
||||||
|
|
||||||
|
|
||||||
def _test_leak(min_iterations, max_iterations, fn, *args, **kwargs):
|
def _test_leak(
|
||||||
|
min_iterations: int, max_iterations: int, fn: Callable[..., None], *args: Any
|
||||||
|
) -> None:
|
||||||
mem_limit = None
|
mem_limit = None
|
||||||
for i in range(max_iterations):
|
for i in range(max_iterations):
|
||||||
fn(*args, **kwargs)
|
fn(*args)
|
||||||
mem = _get_mem_usage()
|
mem = _get_mem_usage()
|
||||||
if i < min_iterations:
|
if i < min_iterations:
|
||||||
mem_limit = mem + 1
|
mem_limit = mem + 1
|
||||||
continue
|
continue
|
||||||
msg = f"memory usage limit exceeded after {i + 1} iterations"
|
msg = f"memory usage limit exceeded after {i + 1} iterations"
|
||||||
|
assert mem_limit is not None
|
||||||
assert mem <= mem_limit, msg
|
assert mem <= mem_limit, msg
|
||||||
|
|
||||||
|
|
||||||
def test_leak_putdata():
|
def test_leak_putdata() -> None:
|
||||||
im = Image.new("RGB", (25, 25))
|
im = Image.new("RGB", (25, 25))
|
||||||
_test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
|
_test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
|
||||||
|
|
||||||
|
|
||||||
def test_leak_getlist():
|
def test_leak_getlist() -> None:
|
||||||
im = Image.new("P", (25, 25))
|
im = Image.new("P", (25, 25))
|
||||||
_test_leak(
|
_test_leak(
|
||||||
min_iterations,
|
min_iterations,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Tests potential DOS of Jpeg2kImagePlugin with 0 length block.
|
# Tests potential DOS of Jpeg2kImagePlugin with 0 length block.
|
||||||
# Run from anywhere that PIL is importable.
|
# Run from anywhere that PIL is importable.
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -18,7 +20,7 @@ pytestmark = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_leak_load():
|
def test_leak_load() -> None:
|
||||||
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
||||||
|
|
||||||
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||||
|
@ -28,7 +30,7 @@ def test_leak_load():
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
def test_leak_save():
|
def test_leak_save() -> None:
|
||||||
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
||||||
|
|
||||||
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
def test_j2k_overflow(tmp_path):
|
def test_j2k_overflow(tmp_path: Path) -> None:
|
||||||
im = Image.new("RGBA", (1024, 131584))
|
im = Image.new("RGBA", (1024, 131584))
|
||||||
target = str(tmp_path / "temp.jpc")
|
target = str(tmp_path / "temp.jpc")
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
|
|
4
Tests/check_jp2_overflow.py
Executable file → Normal file
|
@ -1,5 +1,3 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Reproductions/tests for OOB read errors in FliDecode.c
|
# Reproductions/tests for OOB read errors in FliDecode.c
|
||||||
|
|
||||||
# When run in python, all of these images should fail for
|
# When run in python, all of these images should fail for
|
||||||
|
@ -12,7 +10,7 @@
|
||||||
# the output should be empty. There may be python issues
|
# the output should be empty. There may be python issues
|
||||||
# in the valgrind especially if run in a debug python
|
# in the valgrind especially if run in a debug python
|
||||||
# version.
|
# version.
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -109,14 +111,14 @@ standard_chrominance_qtable = (
|
||||||
[standard_l_qtable, standard_chrominance_qtable],
|
[standard_l_qtable, standard_chrominance_qtable],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_qtables_leak(qtables):
|
def test_qtables_leak(qtables: tuple[tuple[int, ...]] | list[tuple[int, ...]]) -> None:
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
for _ in range(iterations):
|
for _ in range(iterations):
|
||||||
test_output = BytesIO()
|
test_output = BytesIO()
|
||||||
im.save(test_output, "JPEG", qtables=qtables)
|
im.save(test_output, "JPEG", qtables=qtables)
|
||||||
|
|
||||||
|
|
||||||
def test_exif_leak():
|
def test_exif_leak() -> None:
|
||||||
"""
|
"""
|
||||||
pre patch:
|
pre patch:
|
||||||
|
|
||||||
|
@ -179,7 +181,7 @@ def test_exif_leak():
|
||||||
im.save(test_output, "JPEG", exif=exif)
|
im.save(test_output, "JPEG", exif=exif)
|
||||||
|
|
||||||
|
|
||||||
def test_base_save():
|
def test_base_save() -> None:
|
||||||
"""
|
"""
|
||||||
base case:
|
base case:
|
||||||
MB
|
MB
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -14,6 +18,7 @@ from PIL import Image
|
||||||
# 2.7 and 3.2.
|
# 2.7 and 3.2.
|
||||||
|
|
||||||
|
|
||||||
|
numpy: ModuleType | None
|
||||||
try:
|
try:
|
||||||
import numpy
|
import numpy
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -26,23 +31,24 @@ XDIM = 48000
|
||||||
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
||||||
|
|
||||||
|
|
||||||
def _write_png(tmp_path, xdim, ydim):
|
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
|
||||||
f = str(tmp_path / "temp.png")
|
f = str(tmp_path / "temp.png")
|
||||||
im = Image.new("L", (xdim, ydim), 0)
|
im = Image.new("L", (xdim, ydim), 0)
|
||||||
im.save(f)
|
im.save(f)
|
||||||
|
|
||||||
|
|
||||||
def test_large(tmp_path):
|
def test_large(tmp_path: Path) -> None:
|
||||||
"""succeeded prepatch"""
|
"""succeeded prepatch"""
|
||||||
_write_png(tmp_path, XDIM, YDIM)
|
_write_png(tmp_path, XDIM, YDIM)
|
||||||
|
|
||||||
|
|
||||||
def test_2gpx(tmp_path):
|
def test_2gpx(tmp_path: Path) -> None:
|
||||||
"""failed prepatch"""
|
"""failed prepatch"""
|
||||||
_write_png(tmp_path, XDIM, XDIM)
|
_write_png(tmp_path, XDIM, XDIM)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(numpy is None, reason="Numpy is not installed")
|
@pytest.mark.skipif(numpy is None, reason="Numpy is not installed")
|
||||||
def test_size_greater_than_int():
|
def test_size_greater_than_int() -> None:
|
||||||
|
assert numpy is not None
|
||||||
arr = numpy.ndarray(shape=(16394, 16394))
|
arr = numpy.ndarray(shape=(16394, 16394))
|
||||||
Image.fromarray(arr)
|
Image.fromarray(arr)
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -22,7 +25,7 @@ XDIM = 48000
|
||||||
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
||||||
|
|
||||||
|
|
||||||
def _write_png(tmp_path, xdim, ydim):
|
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
|
||||||
dtype = np.uint8
|
dtype = np.uint8
|
||||||
a = np.zeros((xdim, ydim), dtype=dtype)
|
a = np.zeros((xdim, ydim), dtype=dtype)
|
||||||
f = str(tmp_path / "temp.png")
|
f = str(tmp_path / "temp.png")
|
||||||
|
@ -30,11 +33,11 @@ def _write_png(tmp_path, xdim, ydim):
|
||||||
im.save(f)
|
im.save(f)
|
||||||
|
|
||||||
|
|
||||||
def test_large(tmp_path):
|
def test_large(tmp_path: Path) -> None:
|
||||||
"""succeeded prepatch"""
|
"""succeeded prepatch"""
|
||||||
_write_png(tmp_path, XDIM, YDIM)
|
_write_png(tmp_path, XDIM, YDIM)
|
||||||
|
|
||||||
|
|
||||||
def test_2gpx(tmp_path):
|
def test_2gpx(tmp_path: Path) -> None:
|
||||||
"""failed prepatch"""
|
"""failed prepatch"""
|
||||||
_write_png(tmp_path, XDIM, XDIM)
|
_write_png(tmp_path, XDIM, XDIM)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
@ -5,7 +7,7 @@ from PIL import Image
|
||||||
TEST_FILE = "Tests/images/libtiff_segfault.tif"
|
TEST_FILE = "Tests/images/libtiff_segfault.tif"
|
||||||
|
|
||||||
|
|
||||||
def test_libtiff_segfault():
|
def test_libtiff_segfault() -> None:
|
||||||
"""This test should not segfault. It will on Pillow <= 3.1.0 and
|
"""This test should not segfault. It will on Pillow <= 3.1.0 and
|
||||||
libtiff >= 4.0.0
|
libtiff >= 4.0.0
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import zlib
|
import zlib
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
@ -6,7 +8,7 @@ from PIL import Image, ImageFile, PngImagePlugin
|
||||||
TEST_FILE = "Tests/images/png_decompression_dos.png"
|
TEST_FILE = "Tests/images/png_decompression_dos.png"
|
||||||
|
|
||||||
|
|
||||||
def test_ignore_dos_text():
|
def test_ignore_dos_text() -> None:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -22,7 +24,7 @@ def test_ignore_dos_text():
|
||||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||||
|
|
||||||
|
|
||||||
def test_dos_text():
|
def test_dos_text() -> None:
|
||||||
try:
|
try:
|
||||||
im = Image.open(TEST_FILE)
|
im = Image.open(TEST_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
|
@ -34,7 +36,7 @@ def test_dos_text():
|
||||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||||
|
|
||||||
|
|
||||||
def test_dos_total_memory():
|
def test_dos_total_memory() -> None:
|
||||||
im = Image.new("L", (1, 1))
|
im = Image.new("L", (1, 1))
|
||||||
compressed_data = zlib.compress(b"a" * 1024 * 1023)
|
compressed_data = zlib.compress(b"a" * 1024 * 1023)
|
||||||
|
|
||||||
|
@ -51,7 +53,7 @@ def test_dos_total_memory():
|
||||||
try:
|
try:
|
||||||
im2 = Image.open(b)
|
im2 = Image.open(b)
|
||||||
except ValueError as msg:
|
except ValueError as msg:
|
||||||
assert "Too much memory" in msg
|
assert "Too much memory" in str(msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
total_len = 0
|
total_len = 0
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
43
Tests/check_wheel.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PIL import features
|
||||||
|
|
||||||
|
|
||||||
|
def test_wheel_modules() -> None:
|
||||||
|
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
|
||||||
|
|
||||||
|
# tkinter is not available in cibuildwheel installed CPython on Windows
|
||||||
|
try:
|
||||||
|
import tkinter
|
||||||
|
|
||||||
|
assert tkinter
|
||||||
|
except ImportError:
|
||||||
|
expected_modules.remove("tkinter")
|
||||||
|
|
||||||
|
assert set(features.get_supported_modules()) == expected_modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_wheel_codecs() -> None:
|
||||||
|
expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
|
||||||
|
|
||||||
|
assert set(features.get_supported_codecs()) == expected_codecs
|
||||||
|
|
||||||
|
|
||||||
|
def test_wheel_features() -> None:
|
||||||
|
expected_features = {
|
||||||
|
"webp_anim",
|
||||||
|
"webp_mux",
|
||||||
|
"transp_webp",
|
||||||
|
"raqm",
|
||||||
|
"fribidi",
|
||||||
|
"harfbuzz",
|
||||||
|
"libjpeg_turbo",
|
||||||
|
"xcb",
|
||||||
|
}
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
expected_features.remove("xcb")
|
||||||
|
|
||||||
|
assert set(features.get_supported_features()) == expected_features
|
|
@ -1,7 +1,11 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
def pytest_report_header(config):
|
|
||||||
|
def pytest_report_header(config: pytest.Config) -> str:
|
||||||
try:
|
try:
|
||||||
from PIL import features
|
from PIL import features
|
||||||
|
|
||||||
|
@ -12,7 +16,7 @@ def pytest_report_header(config):
|
||||||
return f"pytest_report_header failed: {e}"
|
return f"pytest_report_header failed: {e}"
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config: pytest.Config) -> None:
|
||||||
config.addinivalue_line(
|
config.addinivalue_line(
|
||||||
"markers",
|
"markers",
|
||||||
"pil_noop_mark: A conditional mark where nothing special happens",
|
"pil_noop_mark: A conditional mark where nothing special happens",
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
BIN
Tests/fonts/CBDTTestFont.ttf
Normal file
BIN
Tests/fonts/EBDTTestFont.ttf
Normal file
|
@ -2,7 +2,6 @@
|
||||||
NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts
|
NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts
|
||||||
NotoSans-Regular.ttf, from https://www.google.com/get/noto/
|
NotoSans-Regular.ttf, from https://www.google.com/get/noto/
|
||||||
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
|
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
|
||||||
NotoColorEmoji.ttf, from https://github.com/googlefonts/noto-emoji
|
|
||||||
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype
|
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype
|
||||||
TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
|
TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
|
||||||
ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
|
ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
|
||||||
|
@ -25,3 +24,5 @@ FreeMono.ttf is licensed under GPLv3.
|
||||||
10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base
|
10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base
|
||||||
|
|
||||||
"Public domain font. Share and enjoy."
|
"Public domain font. Share and enjoy."
|
||||||
|
|
||||||
|
CBDTTestFont.ttf and EBDTTestFont.ttf from https://github.com/nulano/font-tests are public domain.
|
||||||
|
|
162
Tests/helper.py
|
@ -1,14 +1,17 @@
|
||||||
"""
|
"""
|
||||||
Helper functions.
|
Helper functions.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import tempfile
|
import tempfile
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from typing import Any, Callable, Sequence
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
from packaging.version import parse as parse_version
|
||||||
|
@ -17,42 +20,31 @@ from PIL import Image, ImageMath, features
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
uploader = None
|
||||||
HAS_UPLOADER = False
|
|
||||||
|
|
||||||
if os.environ.get("SHOW_ERRORS"):
|
if os.environ.get("SHOW_ERRORS"):
|
||||||
# local img.show for errors.
|
uploader = "show"
|
||||||
HAS_UPLOADER = True
|
elif "GITHUB_ACTIONS" in os.environ:
|
||||||
|
uploader = "github_actions"
|
||||||
|
|
||||||
class test_image_results:
|
|
||||||
@staticmethod
|
def upload(a: Image.Image, b: Image.Image) -> str | None:
|
||||||
def upload(a, b):
|
if uploader == "show":
|
||||||
|
# local img.show for errors.
|
||||||
a.show()
|
a.show()
|
||||||
b.show()
|
b.show()
|
||||||
|
elif uploader == "github_actions":
|
||||||
elif "GITHUB_ACTIONS" in os.environ:
|
|
||||||
HAS_UPLOADER = True
|
|
||||||
|
|
||||||
class test_image_results:
|
|
||||||
@staticmethod
|
|
||||||
def upload(a, b):
|
|
||||||
dir_errors = os.path.join(os.path.dirname(__file__), "errors")
|
dir_errors = os.path.join(os.path.dirname(__file__), "errors")
|
||||||
os.makedirs(dir_errors, exist_ok=True)
|
os.makedirs(dir_errors, exist_ok=True)
|
||||||
tmpdir = tempfile.mkdtemp(dir=dir_errors)
|
tmpdir = tempfile.mkdtemp(dir=dir_errors)
|
||||||
a.save(os.path.join(tmpdir, "a.png"))
|
a.save(os.path.join(tmpdir, "a.png"))
|
||||||
b.save(os.path.join(tmpdir, "b.png"))
|
b.save(os.path.join(tmpdir, "b.png"))
|
||||||
return tmpdir
|
return tmpdir
|
||||||
|
return None
|
||||||
else:
|
|
||||||
try:
|
|
||||||
import test_image_results
|
|
||||||
|
|
||||||
HAS_UPLOADER = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_comparable(a, b):
|
def convert_to_comparable(
|
||||||
|
a: Image.Image, b: Image.Image
|
||||||
|
) -> tuple[Image.Image, Image.Image]:
|
||||||
new_a, new_b = a, b
|
new_a, new_b = a, b
|
||||||
if a.mode == "P":
|
if a.mode == "P":
|
||||||
new_a = Image.new("L", a.size)
|
new_a = Image.new("L", a.size)
|
||||||
|
@ -65,14 +57,18 @@ def convert_to_comparable(a, b):
|
||||||
return new_a, new_b
|
return new_a, new_b
|
||||||
|
|
||||||
|
|
||||||
def assert_deep_equal(a, b, msg=None):
|
def assert_deep_equal(
|
||||||
|
a: Sequence[Any], b: Sequence[Any], msg: str | None = None
|
||||||
|
) -> None:
|
||||||
try:
|
try:
|
||||||
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
|
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
|
||||||
except Exception:
|
except Exception:
|
||||||
assert a == b, msg
|
assert a == b, msg
|
||||||
|
|
||||||
|
|
||||||
def assert_image(im, mode, size, msg=None):
|
def assert_image(
|
||||||
|
im: Image.Image, mode: str, size: tuple[int, int], msg: str | None = None
|
||||||
|
) -> None:
|
||||||
if mode is not None:
|
if mode is not None:
|
||||||
assert im.mode == mode, (
|
assert im.mode == mode, (
|
||||||
msg or f"got mode {repr(im.mode)}, expected {repr(mode)}"
|
msg or f"got mode {repr(im.mode)}, expected {repr(mode)}"
|
||||||
|
@ -84,28 +80,32 @@ def assert_image(im, mode, size, msg=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def assert_image_equal(a, b, msg=None):
|
def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) -> None:
|
||||||
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
||||||
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
||||||
if a.tobytes() != b.tobytes():
|
if a.tobytes() != b.tobytes():
|
||||||
if HAS_UPLOADER:
|
|
||||||
try:
|
try:
|
||||||
url = test_image_results.upload(a, b)
|
url = upload(a, b)
|
||||||
|
if url:
|
||||||
logger.error("URL for test images: %s", url)
|
logger.error("URL for test images: %s", url)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert False, msg or "got different content"
|
pytest.fail(msg or "got different content")
|
||||||
|
|
||||||
|
|
||||||
def assert_image_equal_tofile(a, filename, msg=None, mode=None):
|
def assert_image_equal_tofile(
|
||||||
|
a: Image.Image, filename: str, msg: str | None = None, mode: str | None = None
|
||||||
|
) -> None:
|
||||||
with Image.open(filename) as img:
|
with Image.open(filename) as img:
|
||||||
if mode:
|
if mode:
|
||||||
img = img.convert(mode)
|
img = img.convert(mode)
|
||||||
assert_image_equal(a, img, msg)
|
assert_image_equal(a, img, msg)
|
||||||
|
|
||||||
|
|
||||||
def assert_image_similar(a, b, epsilon, msg=None):
|
def assert_image_similar(
|
||||||
|
a: Image.Image, b: Image.Image, epsilon: float, msg: str | None = None
|
||||||
|
) -> None:
|
||||||
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
||||||
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
||||||
|
|
||||||
|
@ -123,55 +123,68 @@ def assert_image_similar(a, b, epsilon, msg=None):
|
||||||
+ f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}"
|
+ f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if HAS_UPLOADER:
|
|
||||||
try:
|
try:
|
||||||
url = test_image_results.upload(a, b)
|
url = upload(a, b)
|
||||||
|
if url:
|
||||||
logger.exception("URL for test images: %s", url)
|
logger.exception("URL for test images: %s", url)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def assert_image_similar_tofile(a, filename, epsilon, msg=None, mode=None):
|
def assert_image_similar_tofile(
|
||||||
|
a: Image.Image,
|
||||||
|
filename: str,
|
||||||
|
epsilon: float,
|
||||||
|
msg: str | None = None,
|
||||||
|
mode: str | None = None,
|
||||||
|
) -> None:
|
||||||
with Image.open(filename) as img:
|
with Image.open(filename) as img:
|
||||||
if mode:
|
if mode:
|
||||||
img = img.convert(mode)
|
img = img.convert(mode)
|
||||||
assert_image_similar(a, img, epsilon, msg)
|
assert_image_similar(a, img, epsilon, msg)
|
||||||
|
|
||||||
|
|
||||||
def assert_all_same(items, msg=None):
|
def assert_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||||
assert items.count(items[0]) == len(items), msg
|
assert items.count(items[0]) == len(items), msg
|
||||||
|
|
||||||
|
|
||||||
def assert_not_all_same(items, msg=None):
|
def assert_not_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||||
assert items.count(items[0]) != len(items), msg
|
assert items.count(items[0]) != len(items), msg
|
||||||
|
|
||||||
|
|
||||||
def assert_tuple_approx_equal(actuals, targets, threshold, msg):
|
def assert_tuple_approx_equal(
|
||||||
|
actuals: Sequence[int], targets: tuple[int, ...], threshold: int, msg: str
|
||||||
|
) -> None:
|
||||||
"""Tests if actuals has values within threshold from targets"""
|
"""Tests if actuals has values within threshold from targets"""
|
||||||
value = True
|
|
||||||
for i, target in enumerate(targets):
|
for i, target in enumerate(targets):
|
||||||
value *= target - threshold <= actuals[i] <= target + threshold
|
if not (target - threshold <= actuals[i] <= target + threshold):
|
||||||
|
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
|
||||||
assert value, msg + ": " + repr(actuals) + " != " + repr(targets)
|
|
||||||
|
|
||||||
|
|
||||||
def skip_unless_feature(feature):
|
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
||||||
reason = f"{feature} not available"
|
reason = f"{feature} not available"
|
||||||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||||
|
|
||||||
|
|
||||||
def skip_unless_feature_version(feature, version_required, reason=None):
|
def skip_unless_feature_version(
|
||||||
|
feature: str, required: str, reason: str | None = None
|
||||||
|
) -> pytest.MarkDecorator:
|
||||||
if not features.check(feature):
|
if not features.check(feature):
|
||||||
return pytest.mark.skip(f"{feature} not available")
|
return pytest.mark.skip(f"{feature} not available")
|
||||||
if reason is None:
|
if reason is None:
|
||||||
reason = f"{feature} is older than {version_required}"
|
reason = f"{feature} is older than {required}"
|
||||||
version_required = parse_version(version_required)
|
version_required = parse_version(required)
|
||||||
version_available = parse_version(features.version(feature))
|
version_available = parse_version(features.version(feature))
|
||||||
return pytest.mark.skipif(version_available < version_required, reason=reason)
|
return pytest.mark.skipif(version_available < version_required, reason=reason)
|
||||||
|
|
||||||
|
|
||||||
def mark_if_feature_version(mark, feature, version_blacklist, reason=None):
|
def mark_if_feature_version(
|
||||||
|
mark: pytest.MarkDecorator,
|
||||||
|
feature: str,
|
||||||
|
version_blacklist: str,
|
||||||
|
reason: str | None = None,
|
||||||
|
) -> pytest.MarkDecorator:
|
||||||
if not features.check(feature):
|
if not features.check(feature):
|
||||||
return pytest.mark.pil_noop_mark()
|
return pytest.mark.pil_noop_mark()
|
||||||
if reason is None:
|
if reason is None:
|
||||||
|
@ -192,7 +205,7 @@ class PillowLeakTestCase:
|
||||||
iterations = 100 # count
|
iterations = 100 # count
|
||||||
mem_limit = 512 # k
|
mem_limit = 512 # k
|
||||||
|
|
||||||
def _get_mem_usage(self):
|
def _get_mem_usage(self) -> float:
|
||||||
"""
|
"""
|
||||||
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
||||||
between macOS and Linux rss reporting
|
between macOS and Linux rss reporting
|
||||||
|
@ -214,7 +227,7 @@ class PillowLeakTestCase:
|
||||||
# This is the maximum resident set size used (in kilobytes).
|
# This is the maximum resident set size used (in kilobytes).
|
||||||
return mem # Kb
|
return mem # Kb
|
||||||
|
|
||||||
def _test_leak(self, core):
|
def _test_leak(self, core: Callable[[], None]) -> None:
|
||||||
start_mem = self._get_mem_usage()
|
start_mem = self._get_mem_usage()
|
||||||
for cycle in range(self.iterations):
|
for cycle in range(self.iterations):
|
||||||
core()
|
core()
|
||||||
|
@ -226,17 +239,17 @@ class PillowLeakTestCase:
|
||||||
# helpers
|
# helpers
|
||||||
|
|
||||||
|
|
||||||
def fromstring(data):
|
def fromstring(data: bytes) -> Image.Image:
|
||||||
return Image.open(BytesIO(data))
|
return Image.open(BytesIO(data))
|
||||||
|
|
||||||
|
|
||||||
def tostring(im, string_format, **options):
|
def tostring(im: Image.Image, string_format: str, **options: dict[str, Any]) -> bytes:
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im.save(out, string_format, **options)
|
im.save(out, string_format, **options)
|
||||||
return out.getvalue()
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def hopper(mode=None, cache={}):
|
def hopper(mode: str | None = None, cache: dict[str, Image.Image] = {}) -> Image.Image:
|
||||||
if mode is None:
|
if mode is None:
|
||||||
# Always return fresh not-yet-loaded version of image.
|
# Always return fresh not-yet-loaded version of image.
|
||||||
# Operations on not-yet-loaded images is separate class of errors
|
# Operations on not-yet-loaded images is separate class of errors
|
||||||
|
@ -257,19 +270,31 @@ def hopper(mode=None, cache={}):
|
||||||
return im.copy()
|
return im.copy()
|
||||||
|
|
||||||
|
|
||||||
def djpeg_available():
|
def djpeg_available() -> bool:
|
||||||
return bool(shutil.which("djpeg"))
|
if shutil.which("djpeg"):
|
||||||
|
try:
|
||||||
|
subprocess.check_call(["djpeg", "-version"])
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError: # pragma: no cover
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def cjpeg_available():
|
def cjpeg_available() -> bool:
|
||||||
return bool(shutil.which("cjpeg"))
|
if shutil.which("cjpeg"):
|
||||||
|
try:
|
||||||
|
subprocess.check_call(["cjpeg", "-version"])
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError: # pragma: no cover
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def netpbm_available():
|
def netpbm_available() -> bool:
|
||||||
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
||||||
|
|
||||||
|
|
||||||
def magick_command():
|
def magick_command() -> list[str] | None:
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
magickhome = os.environ.get("MAGICK_HOME")
|
magickhome = os.environ.get("MAGICK_HOME")
|
||||||
if magickhome:
|
if magickhome:
|
||||||
|
@ -286,47 +311,48 @@ def magick_command():
|
||||||
return imagemagick
|
return imagemagick
|
||||||
if graphicsmagick and shutil.which(graphicsmagick[0]):
|
if graphicsmagick and shutil.which(graphicsmagick[0]):
|
||||||
return graphicsmagick
|
return graphicsmagick
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def on_appveyor():
|
def on_appveyor() -> bool:
|
||||||
return "APPVEYOR" in os.environ
|
return "APPVEYOR" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def on_github_actions():
|
def on_github_actions() -> bool:
|
||||||
return "GITHUB_ACTIONS" in os.environ
|
return "GITHUB_ACTIONS" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def on_ci():
|
def on_ci() -> bool:
|
||||||
# GitHub Actions and AppVeyor have "CI"
|
# GitHub Actions and AppVeyor have "CI"
|
||||||
return "CI" in os.environ
|
return "CI" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def is_big_endian():
|
def is_big_endian() -> bool:
|
||||||
return sys.byteorder == "big"
|
return sys.byteorder == "big"
|
||||||
|
|
||||||
|
|
||||||
def is_ppc64le():
|
def is_ppc64le() -> bool:
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
return platform.machine() == "ppc64le"
|
return platform.machine() == "ppc64le"
|
||||||
|
|
||||||
|
|
||||||
def is_win32():
|
def is_win32() -> bool:
|
||||||
return sys.platform.startswith("win32")
|
return sys.platform.startswith("win32")
|
||||||
|
|
||||||
|
|
||||||
def is_pypy():
|
def is_pypy() -> bool:
|
||||||
return hasattr(sys, "pypy_translation_info")
|
return hasattr(sys, "pypy_translation_info")
|
||||||
|
|
||||||
|
|
||||||
def is_mingw():
|
def is_mingw() -> bool:
|
||||||
return sysconfig.get_platform() == "mingw"
|
return sysconfig.get_platform() == "mingw"
|
||||||
|
|
||||||
|
|
||||||
class CachedProperty:
|
class CachedProperty:
|
||||||
def __init__(self, func):
|
def __init__(self, func: Callable[[Any], None]) -> None:
|
||||||
self.func = func
|
self.func = func
|
||||||
|
|
||||||
def __get__(self, instance, cls=None):
|
def __get__(self, instance: Any, cls: type[Any] | None = None) -> Any:
|
||||||
result = instance.__dict__[self.func.__name__] = self.func(instance)
|
result = instance.__dict__[self.func.__name__] = self.func(instance)
|
||||||
return result
|
return result
|
||||||
|
|
BIN
Tests/images/apng/different_durations.png
Normal file
After Width: | Height: | Size: 233 B |
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 331 B |
Before Width: | Height: | Size: 668 B After Width: | Height: | Size: 668 B |
BIN
Tests/images/background_outside_palette.gif
Normal file
After Width: | Height: | Size: 82 B |
BIN
Tests/images/bc1.dds
Executable file
BIN
Tests/images/bc1_typeless.dds
Executable file
BIN
Tests/images/bc4_typeless.dds
Normal file
BIN
Tests/images/bc4_unorm.dds
Normal file
BIN
Tests/images/bc4_unorm.png
Normal file
After Width: | Height: | Size: 982 B |
BIN
Tests/images/bc4u.dds
Normal file
BIN
Tests/images/bgr15.dds
Normal file
BIN
Tests/images/bgr15.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
Tests/images/bitmap_font_blend.png
Normal file
After Width: | Height: | Size: 387 B |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.7 KiB |
BIN
Tests/images/cbdt.png
Normal file
After Width: | Height: | Size: 348 B |
BIN
Tests/images/cbdt_mask.png
Normal file
After Width: | Height: | Size: 367 B |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.5 KiB |
|
@ -1,5 +1,3 @@
|
||||||
#!/usr/bin/gnuplot
|
|
||||||
|
|
||||||
#This is the script that was used to create our sample EPS files
|
#This is the script that was used to create our sample EPS files
|
||||||
#We used the following version of the gnuplot program
|
#We used the following version of the gnuplot program
|
||||||
#G N U P L O T
|
#G N U P L O T
|
||||||
|
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
BIN
Tests/images/five_channels.psd
Normal file
BIN
Tests/images/hopper.pfm
Normal file
BIN
Tests/images/hopper_be.pfm
Normal file
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
BIN
Tests/images/multiple_exif.jpg
Normal file
After Width: | Height: | Size: 364 B |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
BIN
Tests/images/unsupported_bitcount.dds
Normal file
|
@ -15,7 +15,7 @@
|
||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
python3 setup.py build --build-base=/tmp/build install
|
python3 -m pip install .
|
||||||
|
|
||||||
# Build fuzzers in $OUT.
|
# Build fuzzers in $OUT.
|
||||||
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
|
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
|
||||||
|
|
|
@ -23,7 +23,7 @@ with atheris.instrument_imports():
|
||||||
import fuzzers
|
import fuzzers
|
||||||
|
|
||||||
|
|
||||||
def TestOneInput(data):
|
def TestOneInput(data: bytes) -> None:
|
||||||
try:
|
try:
|
||||||
fuzzers.fuzz_font(data)
|
fuzzers.fuzz_font(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -32,7 +32,7 @@ def TestOneInput(data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
fuzzers.enable_decompressionbomb_error()
|
fuzzers.enable_decompressionbomb_error()
|
||||||
atheris.Setup(sys.argv, TestOneInput)
|
atheris.Setup(sys.argv, TestOneInput)
|
||||||
atheris.Fuzz()
|
atheris.Fuzz()
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
# Copyright 2020 Google LLC
|
# Copyright 2020 Google LLC
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -23,7 +21,7 @@ with atheris.instrument_imports():
|
||||||
import fuzzers
|
import fuzzers
|
||||||
|
|
||||||
|
|
||||||
def TestOneInput(data):
|
def TestOneInput(data: bytes) -> None:
|
||||||
try:
|
try:
|
||||||
fuzzers.fuzz_image(data)
|
fuzzers.fuzz_image(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -32,7 +30,7 @@ def TestOneInput(data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
fuzzers.enable_decompressionbomb_error()
|
fuzzers.enable_decompressionbomb_error()
|
||||||
atheris.Setup(sys.argv, TestOneInput)
|
atheris.Setup(sys.argv, TestOneInput)
|
||||||
atheris.Fuzz()
|
atheris.Fuzz()
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
|
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
|
||||||
|
|
||||||
|
|
||||||
def enable_decompressionbomb_error():
|
def enable_decompressionbomb_error() -> None:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
warnings.filterwarnings("ignore")
|
warnings.filterwarnings("ignore")
|
||||||
warnings.simplefilter("error", Image.DecompressionBombWarning)
|
warnings.simplefilter("error", Image.DecompressionBombWarning)
|
||||||
|
|
||||||
|
|
||||||
def disable_decompressionbomb_error():
|
def disable_decompressionbomb_error() -> None:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||||
warnings.resetwarnings()
|
warnings.resetwarnings()
|
||||||
|
|
||||||
|
|
||||||
def fuzz_image(data):
|
def fuzz_image(data: bytes) -> None:
|
||||||
# This will fail on some images in the corpus, as we have many
|
# This will fail on some images in the corpus, as we have many
|
||||||
# invalid images in the test suite.
|
# invalid images in the test suite.
|
||||||
with Image.open(io.BytesIO(data)) as im:
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
|
@ -24,7 +26,7 @@ def fuzz_image(data):
|
||||||
im.save(io.BytesIO(), "BMP")
|
im.save(io.BytesIO(), "BMP")
|
||||||
|
|
||||||
|
|
||||||
def fuzz_font(data):
|
def fuzz_font(data: bytes) -> None:
|
||||||
wrapper = io.BytesIO(data)
|
wrapper = io.BytesIO(data)
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(wrapper)
|
font = ImageFont.truetype(wrapper)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -22,7 +24,7 @@ if features.check("libjpeg_turbo"):
|
||||||
"path",
|
"path",
|
||||||
subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"),
|
subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"),
|
||||||
)
|
)
|
||||||
def test_fuzz_images(path):
|
def test_fuzz_images(path: str) -> None:
|
||||||
fuzzers.enable_decompressionbomb_error()
|
fuzzers.enable_decompressionbomb_error()
|
||||||
try:
|
try:
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
|
@ -53,7 +55,7 @@ def test_fuzz_images(path):
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
|
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
|
||||||
)
|
)
|
||||||
def test_fuzz_fonts(path):
|
def test_fuzz_fonts(path: str) -> None:
|
||||||
if not path:
|
if not path:
|
||||||
return
|
return
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
def test_sanity() -> None:
|
||||||
# Make sure we have the binary extension
|
# Make sure we have the binary extension
|
||||||
Image.core.new("L", (100, 100))
|
Image.core.new("L", (100, 100))
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import _binary
|
from PIL import _binary
|
||||||
|
|
||||||
|
|
||||||
def test_standard():
|
def test_standard() -> None:
|
||||||
assert _binary.i8(b"*") == 42
|
assert _binary.i8(b"*") == 42
|
||||||
assert _binary.o8(42) == b"*"
|
assert _binary.o8(42) == b"*"
|
||||||
|
|
||||||
|
|
||||||
def test_little_endian():
|
def test_little_endian() -> None:
|
||||||
assert _binary.i16le(b"\xff\xff\x00\x00") == 65535
|
assert _binary.i16le(b"\xff\xff\x00\x00") == 65535
|
||||||
assert _binary.i32le(b"\xff\xff\x00\x00") == 65535
|
assert _binary.i32le(b"\xff\xff\x00\x00") == 65535
|
||||||
|
|
||||||
|
@ -14,7 +16,7 @@ def test_little_endian():
|
||||||
assert _binary.o32le(65535) == b"\xff\xff\x00\x00"
|
assert _binary.o32le(65535) == b"\xff\xff\x00\x00"
|
||||||
|
|
||||||
|
|
||||||
def test_big_endian():
|
def test_big_endian() -> None:
|
||||||
assert _binary.i16be(b"\x00\x00\xff\xff") == 0
|
assert _binary.i16be(b"\x00\x00\xff\xff") == 0
|
||||||
assert _binary.i32be(b"\x00\x00\xff\xff") == 65535
|
assert _binary.i32be(b"\x00\x00\xff\xff") == 65535
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
@ -8,13 +10,13 @@ from .helper import assert_image_similar
|
||||||
base = os.path.join("Tests", "images", "bmp")
|
base = os.path.join("Tests", "images", "bmp")
|
||||||
|
|
||||||
|
|
||||||
def get_files(d, ext=".bmp"):
|
def get_files(d, ext: str = ".bmp"):
|
||||||
return [
|
return [
|
||||||
os.path.join(base, d, f) for f in os.listdir(os.path.join(base, d)) if ext in f
|
os.path.join(base, d, f) for f in os.listdir(os.path.join(base, d)) if ext in f
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_bad():
|
def test_bad() -> None:
|
||||||
"""These shouldn't crash/dos, but they shouldn't return anything
|
"""These shouldn't crash/dos, but they shouldn't return anything
|
||||||
either"""
|
either"""
|
||||||
for f in get_files("b"):
|
for f in get_files("b"):
|
||||||
|
@ -54,7 +56,7 @@ def test_questionable():
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def test_good():
|
def test_good() -> None:
|
||||||
"""These should all work. There's a set of target files in the
|
"""These should all work. There's a set of target files in the
|
||||||
html directory that we can compare against."""
|
html directory that we can compare against."""
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageFilter
|
from PIL import Image, ImageFilter
|
||||||
|
@ -14,18 +16,18 @@ sample.putdata(sum([
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
def test_imageops_box_blur():
|
def test_imageops_box_blur() -> None:
|
||||||
i = sample.filter(ImageFilter.BoxBlur(1))
|
i = sample.filter(ImageFilter.BoxBlur(1))
|
||||||
assert i.mode == sample.mode
|
assert i.mode == sample.mode
|
||||||
assert i.size == sample.size
|
assert i.size == sample.size
|
||||||
assert isinstance(i, Image.Image)
|
assert isinstance(i, Image.Image)
|
||||||
|
|
||||||
|
|
||||||
def box_blur(image, radius=1, n=1):
|
def box_blur(image, radius: int = 1, n: int = 1):
|
||||||
return image._new(image.im.box_blur((radius, radius), n))
|
return image._new(image.im.box_blur((radius, radius), n))
|
||||||
|
|
||||||
|
|
||||||
def assert_image(im, data, delta=0):
|
def assert_image(im, data, delta: int = 0) -> None:
|
||||||
it = iter(im.getdata())
|
it = iter(im.getdata())
|
||||||
for data_row in data:
|
for data_row in data:
|
||||||
im_row = [next(it) for _ in range(im.size[0])]
|
im_row = [next(it) for _ in range(im.size[0])]
|
||||||
|
@ -35,7 +37,7 @@ def assert_image(im, data, delta=0):
|
||||||
next(it)
|
next(it)
|
||||||
|
|
||||||
|
|
||||||
def assert_blur(im, radius, data, passes=1, delta=0):
|
def assert_blur(im, radius, data, passes: int = 1, delta: int = 0) -> None:
|
||||||
# check grayscale image
|
# check grayscale image
|
||||||
assert_image(box_blur(im, radius, passes), data, delta)
|
assert_image(box_blur(im, radius, passes), data, delta)
|
||||||
rgba = Image.merge("RGBA", (im, im, im, im))
|
rgba = Image.merge("RGBA", (im, im, im, im))
|
||||||
|
@ -43,7 +45,7 @@ def assert_blur(im, radius, data, passes=1, delta=0):
|
||||||
assert_image(band, data, delta)
|
assert_image(band, data, delta)
|
||||||
|
|
||||||
|
|
||||||
def test_color_modes():
|
def test_color_modes() -> None:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
box_blur(sample.convert("1"))
|
box_blur(sample.convert("1"))
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
|
@ -63,7 +65,7 @@ def test_color_modes():
|
||||||
box_blur(sample.convert("YCbCr"))
|
box_blur(sample.convert("YCbCr"))
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0():
|
def test_radius_0() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0,
|
0,
|
||||||
|
@ -79,7 +81,7 @@ def test_radius_0():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_02():
|
def test_radius_0_02() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.02,
|
0.02,
|
||||||
|
@ -96,7 +98,7 @@ def test_radius_0_02():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_05():
|
def test_radius_0_05() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.05,
|
0.05,
|
||||||
|
@ -113,7 +115,7 @@ def test_radius_0_05():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_1():
|
def test_radius_0_1() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.1,
|
0.1,
|
||||||
|
@ -130,7 +132,7 @@ def test_radius_0_1():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_5():
|
def test_radius_0_5() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.5,
|
0.5,
|
||||||
|
@ -147,7 +149,7 @@ def test_radius_0_5():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_radius_1():
|
def test_radius_1() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1,
|
1,
|
||||||
|
@ -164,7 +166,7 @@ def test_radius_1():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_radius_1_5():
|
def test_radius_1_5() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1.5,
|
1.5,
|
||||||
|
@ -181,7 +183,7 @@ def test_radius_1_5():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_radius_bigger_then_half():
|
def test_radius_bigger_then_half() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
3,
|
3,
|
||||||
|
@ -198,7 +200,7 @@ def test_radius_bigger_then_half():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_radius_bigger_then_width():
|
def test_radius_bigger_then_width() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
10,
|
10,
|
||||||
|
@ -213,7 +215,7 @@ def test_radius_bigger_then_width():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_extreme_large_radius():
|
def test_extreme_large_radius() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
600,
|
600,
|
||||||
|
@ -228,7 +230,7 @@ def test_extreme_large_radius():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_two_passes():
|
def test_two_passes() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1,
|
1,
|
||||||
|
@ -246,7 +248,7 @@ def test_two_passes():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_three_passes():
|
def test_three_passes() -> None:
|
||||||
assert_blur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1,
|
1,
|
||||||
|
|