Merge branch 'main' into init
|
@ -10,11 +10,11 @@ environment:
|
|||
TEST_OPTIONS:
|
||||
DEPLOY: YES
|
||||
matrix:
|
||||
- PYTHON: C:/Python311
|
||||
- PYTHON: C:/Python312
|
||||
ARCHITECTURE: x86
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||
- PYTHON: C:/Python38-x64
|
||||
ARCHITECTURE: x64
|
||||
ARCHITECTURE: AMD64
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
|
||||
|
||||
|
@ -43,7 +43,7 @@ build_script:
|
|||
|
||||
test_script:
|
||||
- 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%
|
||||
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
|
||||
- '%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 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 defusedxml
|
||||
python3 -m pip install olefile
|
||||
|
@ -38,7 +39,8 @@ python3 -m pip install -U pytest-timeout
|
|||
python3 -m pip install pyroma
|
||||
|
||||
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
|
||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||
|
@ -46,6 +48,16 @@ if [[ $(uname) != CYGWIN* ]]; then
|
|||
python3 -m pip install pyqt6
|
||||
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
|
||||
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]
|
||||
# Regexes for lines to exclude from consideration
|
||||
exclude_lines =
|
||||
# Have to re-enable the standard pragma:
|
||||
pragma: no cover
|
||||
|
||||
# Don't complain if non-runnable code isn't run:
|
||||
exclude_also =
|
||||
# Don't complain if non-runnable code isn't run
|
||||
if 0:
|
||||
if __name__ == .__main__.:
|
||||
# Don't complain about debug code
|
||||
if DEBUG:
|
||||
# Don't complain about compatibility code for missing optional dependencies
|
||||
except ImportError
|
||||
|
||||
[run]
|
||||
omit =
|
||||
|
|
|
@ -13,7 +13,7 @@ indent_style = space
|
|||
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.yml]
|
||||
[*.{toml,yml}]
|
||||
# Two-space indentation
|
||||
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"
|
||||
- title: "Testing"
|
||||
label: "Testing"
|
||||
- title: "Type hints"
|
||||
label: "Type hints"
|
||||
|
||||
exclude-labels:
|
||||
- "changelog: skip"
|
||||
|
|
4
.github/workflows/cifuzz.yml
vendored
|
@ -42,13 +42,13 @@ jobs:
|
|||
language: python
|
||||
dry-run: false
|
||||
- name: Upload New Crash
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
||||
- name: Upload Legacy Crash
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: steps.run.outcome == 'success'
|
||||
with:
|
||||
name: crash
|
||||
|
|
14
.github/workflows/docs.yml
vendored
|
@ -33,20 +33,30 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
cache: pip
|
||||
cache-dependency-path: ".ci/*.sh"
|
||||
cache-dependency-path: |
|
||||
".ci/*.sh"
|
||||
"pyproject.toml"
|
||||
|
||||
- name: Build system information
|
||||
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
|
||||
run: |
|
||||
.ci/install.sh
|
||||
env:
|
||||
GHA_PYTHON_VERSION: "3.x"
|
||||
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
|
10
.github/workflows/lint.yml
vendored
|
@ -2,6 +2,9 @@ name: Lint
|
|||
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
@ -20,7 +23,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: pre-commit cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
||||
|
@ -28,7 +31,7 @@ jobs:
|
|||
lint-pre-commit-
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
cache: pip
|
||||
|
@ -46,3 +49,6 @@ jobs:
|
|||
run: tox -e lint
|
||||
env:
|
||||
PRE_COMMIT_COLOR: always
|
||||
|
||||
- name: Mypy
|
||||
run: tox -e mypy
|
||||
|
|
18
.github/workflows/macos-install.sh
vendored
|
@ -2,10 +2,21 @@
|
|||
|
||||
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"
|
||||
|
||||
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 defusedxml
|
||||
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 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
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
|
2
.github/workflows/stale.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: "Check issues"
|
||||
uses: actions/stale@v8
|
||||
uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
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:
|
||||
https://github.com/actions/virtual-environments/issues/79
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
|
|
11
.github/workflows/test-cygwin.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
|
@ -16,7 +15,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
@ -49,7 +47,7 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Cygwin
|
||||
uses: cygwin/cygwin-install-action@v4
|
||||
uses: egor-tensin/setup-cygwin@v4
|
||||
with:
|
||||
platform: x86_64
|
||||
packages: >
|
||||
|
@ -71,6 +69,7 @@ jobs:
|
|||
make
|
||||
netpbm
|
||||
perl
|
||||
python39=3.9.16-1
|
||||
python3${{ matrix.python-minor-version }}-cffi
|
||||
python3${{ matrix.python-minor-version }}-cython
|
||||
python3${{ matrix.python-minor-version }}-devel
|
||||
|
@ -88,7 +87,7 @@ jobs:
|
|||
|
||||
- name: Select Python version
|
||||
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
|
||||
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
|
||||
|
||||
- name: pip cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
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') }}
|
||||
|
@ -132,7 +131,7 @@ jobs:
|
|||
dash.exe -c "mkdir -p Tests/errors"
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
|
|
4
.github/workflows/test-docker.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
|
@ -16,7 +15,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
@ -51,8 +49,8 @@ jobs:
|
|||
debian-11-bullseye-amd64,
|
||||
debian-12-bookworm-x86,
|
||||
debian-12-bookworm-amd64,
|
||||
fedora-37-amd64,
|
||||
fedora-38-amd64,
|
||||
fedora-39-amd64,
|
||||
gentoo,
|
||||
ubuntu-20.04-focal-amd64,
|
||||
ubuntu-22.04-jammy-amd64,
|
||||
|
|
2
.github/workflows/test-mingw.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
|
@ -16,7 +15,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
|
65
.github/workflows/test-windows.yml
vendored
|
@ -2,11 +2,12 @@ name: Test Windows
|
|||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
|
@ -14,7 +15,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
@ -32,7 +32,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
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
|
||||
|
||||
|
@ -56,25 +56,26 @@ jobs:
|
|||
|
||||
# sets env: pythonLocation
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
allow-prereleases: true
|
||||
cache: pip
|
||||
cache-dependency-path: ".github/workflows/test-windows.yml"
|
||||
|
||||
- name: Print build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml
|
||||
run: 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 olefile pyroma
|
||||
|
||||
- name: Install dependencies
|
||||
id: install
|
||||
run: |
|
||||
7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
||||
echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH
|
||||
choco install nasm --no-progress
|
||||
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
|
||||
|
||||
# Install extra test images
|
||||
|
@ -88,7 +89,7 @@ jobs:
|
|||
|
||||
- name: Cache build
|
||||
id: build-cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: winbuild\build
|
||||
key:
|
||||
|
@ -166,7 +167,6 @@ jobs:
|
|||
- name: Build Pillow
|
||||
run: |
|
||||
$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 ."
|
||||
& $env:pythonLocation\python.exe selftest.py --installed
|
||||
shell: pwsh
|
||||
|
@ -190,7 +190,7 @@ jobs:
|
|||
shell: bash
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
|
@ -208,47 +208,6 @@ jobs:
|
|||
flags: GHA_Windows
|
||||
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:
|
||||
permissions:
|
||||
contents: none
|
||||
|
|
46
.github/workflows/test.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
|
@ -16,7 +15,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
@ -28,6 +26,9 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
|
@ -35,12 +36,13 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
os: [
|
||||
"macos-latest",
|
||||
"macos-14",
|
||||
"ubuntu-latest",
|
||||
]
|
||||
python-version: [
|
||||
"pypy3.10",
|
||||
"pypy3.9",
|
||||
"3.13",
|
||||
"3.12",
|
||||
"3.11",
|
||||
"3.10",
|
||||
|
@ -48,11 +50,21 @@ jobs:
|
|||
"3.8",
|
||||
]
|
||||
include:
|
||||
- python-version: "3.9"
|
||||
- python-version: "3.11"
|
||||
PYTHONOPTIMIZE: 1
|
||||
REVERSE: "--reverse"
|
||||
- python-version: "3.8"
|
||||
- python-version: "3.10"
|
||||
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 }}
|
||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
|
@ -61,21 +73,33 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
allow-prereleases: true
|
||||
cache: pip
|
||||
cache-dependency-path: ".ci/*.sh"
|
||||
cache-dependency-path: |
|
||||
".ci/*.sh"
|
||||
"pyproject.toml"
|
||||
|
||||
- name: Build system information
|
||||
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
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
.ci/install.sh
|
||||
env:
|
||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||
|
||||
- name: Install macOS dependencies
|
||||
if: startsWith(matrix.os, 'macOS')
|
||||
|
@ -84,6 +108,10 @@ jobs:
|
|||
env:
|
||||
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
|
||||
run: |
|
||||
.ci/build.sh
|
||||
|
@ -110,7 +138,7 @@ jobs:
|
|||
mkdir -p Tests/errors
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
|
@ -123,7 +151,7 @@ jobs:
|
|||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
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 }}
|
||||
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:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/wheels*.yml"
|
||||
- ".ci/requirements-cibw.txt"
|
||||
- ".github/workflows/wheel*"
|
||||
- "wheels/*"
|
||||
- "winbuild/build_prepare.py"
|
||||
- "winbuild/fribidi.cmake"
|
||||
tags:
|
||||
- "*"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/wheels*.yml"
|
||||
- ".ci/requirements-cibw.txt"
|
||||
- ".github/workflows/wheel*"
|
||||
- "wheels/*"
|
||||
- "winbuild/build_prepare.py"
|
||||
- "winbuild/fribidi.cmake"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
@ -20,23 +26,239 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
macos:
|
||||
uses: ./.github/workflows/wheels-macos.yml
|
||||
with:
|
||||
artifacts-name: "wheels"
|
||||
|
||||
linux:
|
||||
uses: ./.github/workflows/wheels-linux.yml
|
||||
with:
|
||||
artifacts-name: "wheels"
|
||||
|
||||
success:
|
||||
permissions:
|
||||
contents: none
|
||||
needs: [macos, linux]
|
||||
build-1-QEMU-emulated-wheels:
|
||||
name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }}
|
||||
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:
|
||||
- name: Success
|
||||
run: echo Wheels Successful
|
||||
- uses: actions/checkout@v4
|
||||
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:
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.13.0
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.9
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 23.9.1
|
||||
rev: 23.12.1
|
||||
hooks:
|
||||
- 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
|
||||
rev: 1.7.5
|
||||
rev: 1.7.6
|
||||
hooks:
|
||||
- id: bandit
|
||||
args: [--severity-level=high]
|
||||
files: ^src/
|
||||
|
||||
- repo: https://github.com/asottile/yesqa
|
||||
rev: v1.5.0
|
||||
hooks:
|
||||
- id: yesqa
|
||||
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||
rev: v1.5.4
|
||||
hooks:
|
||||
- id: remove-tabs
|
||||
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
|
||||
rev: v1.10.0
|
||||
hooks:
|
||||
- id: python-check-blanket-noqa
|
||||
- id: rst-backticks
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-shebang-scripts-are-executable
|
||||
- id: check-merge-conflict
|
||||
- id: check-json
|
||||
- id: check-toml
|
||||
|
@ -61,17 +43,17 @@ repos:
|
|||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||
|
||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||
rev: v0.6.8
|
||||
rev: v0.9.1
|
||||
hooks:
|
||||
- id: sphinx-lint
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: 1.2.0
|
||||
rev: 1.5.3
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.14
|
||||
rev: v0.15
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ formats: [pdf]
|
|||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
python: "3"
|
||||
|
||||
python:
|
||||
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)
|
||||
==================
|
||||
|
||||
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)
|
||||
-------------------
|
||||
|
||||
|
@ -2191,7 +2326,7 @@ Changelog (Pillow)
|
|||
- Cache EXIF information #3498
|
||||
[Glandos]
|
||||
|
||||
- Added transparency for all PNG greyscale modes #3744
|
||||
- Added transparency for all PNG grayscale modes #3744
|
||||
[radarhere]
|
||||
|
||||
- Fix deprecation warnings in Python 3.8 #3749
|
||||
|
@ -4693,7 +4828,7 @@ Changelog (Pillow)
|
|||
- Fix Bicubic interpolation #970
|
||||
[homm]
|
||||
|
||||
- Support for 4-bit greyscale TIFF images #980
|
||||
- Support for 4-bit grayscale TIFF images #980
|
||||
[hugovk]
|
||||
|
||||
- Updated manifest #957
|
||||
|
@ -6768,7 +6903,7 @@ The test suite includes 750 individual tests.
|
|||
|
||||
- You can now convert directly between all modes supported by
|
||||
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.
|
||||
|
||||
- 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.
|
||||
|
||||
- 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.
|
||||
|
||||
- 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
|
||||
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.
|
||||
|
||||
- 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
|
||||
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).
|
||||
|
||||
- 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
|
||||
|
||||
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:
|
||||
|
||||
|
|
|
@ -5,8 +5,10 @@ include *.md
|
|||
include *.py
|
||||
include *.rst
|
||||
include *.sh
|
||||
include *.toml
|
||||
include *.txt
|
||||
include *.yaml
|
||||
include .flake8
|
||||
include LICENSE
|
||||
include Makefile
|
||||
include tox.ini
|
||||
|
|
8
Makefile
|
@ -49,7 +49,7 @@ help:
|
|||
@echo " install make and install"
|
||||
@echo " install-coverage make and install with C coverage"
|
||||
@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 " test run tests on installed Pillow"
|
||||
|
||||
|
@ -118,6 +118,6 @@ lint:
|
|||
.PHONY: lint-fix
|
||||
lint-fix:
|
||||
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 --target-version py38 .
|
||||
python3 -m isort .
|
||||
python3 -m black .
|
||||
python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff
|
||||
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
|
||||
alt="GitHub Actions build status (Wheels)"
|
||||
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
|
||||
alt="Code coverage"
|
||||
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
|
||||
alt="Tidelift"
|
||||
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"
|
||||
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"
|
||||
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
||||
<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
|
||||
* [ ] 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 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`
|
||||
* [ ] Update `CHANGES.rst`.
|
||||
* [ ] 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 push --tags
|
||||
```
|
||||
* [ ] Create and check source distribution:
|
||||
```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*
|
||||
```
|
||||
* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
|
||||
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
||||
|
@ -59,12 +50,7 @@ Released as needed for security, installation or critical bug fixes.
|
|||
```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.1*
|
||||
```
|
||||
* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
||||
```bash
|
||||
git push
|
||||
|
@ -86,35 +72,17 @@ Released as needed privately to individual vendors for critical security-related
|
|||
git tag 2.5.3
|
||||
git push origin --tags
|
||||
```
|
||||
* [ ] Create and check source distribution:
|
||||
```bash
|
||||
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)
|
||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
||||
```bash
|
||||
git push origin 2.5.x
|
||||
```
|
||||
|
||||
## Binary Distributions
|
||||
## Source and Binary Distributions
|
||||
|
||||
### macOS and Linux
|
||||
* [ ] Download wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
|
||||
```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
|
||||
```
|
||||
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||
has passed, including the "Upload release to PyPI" job. This will have been triggered
|
||||
by the new tag.
|
||||
|
||||
## Publicize Release
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
from PIL import PyAccess
|
||||
|
@ -7,21 +9,21 @@ from .helper import hopper
|
|||
# Not running this test by default. No DOS against CI.
|
||||
|
||||
|
||||
def iterate_get(size, access):
|
||||
def iterate_get(size, access) -> None:
|
||||
(w, h) = size
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
access[(x, y)]
|
||||
|
||||
|
||||
def iterate_set(size, access):
|
||||
def iterate_set(size, access) -> None:
|
||||
(w, h) = size
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
access[(x, y)] = (x % 256, y % 256, 0)
|
||||
|
||||
|
||||
def timer(func, label, *args):
|
||||
def timer(func, label, *args) -> None:
|
||||
iterations = 5000
|
||||
starttime = time.time()
|
||||
for x in range(iterations):
|
||||
|
@ -36,7 +38,7 @@ def timer(func, label, *args):
|
|||
)
|
||||
|
||||
|
||||
def test_direct():
|
||||
def test_direct() -> None:
|
||||
im = hopper()
|
||||
im.load()
|
||||
# im = Image.new("RGB", (2000, 2000), (1, 3, 2))
|
||||
|
@ -45,7 +47,7 @@ def test_direct():
|
|||
|
||||
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_set, "PyAccess - set", im.size, access)
|
||||
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
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from PIL import Image
|
||||
|
||||
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
|
||||
with Image.open(TEST_FILE) as im:
|
||||
im.load()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Tests potential DOS of IcnsImagePlugin with 0 length block.
|
||||
# Run from anywhere that PIL is importable.
|
||||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
@ -11,31 +15,34 @@ max_iterations = 10000
|
|||
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
|
||||
|
||||
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
||||
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
|
||||
for i in range(max_iterations):
|
||||
fn(*args, **kwargs)
|
||||
fn(*args)
|
||||
mem = _get_mem_usage()
|
||||
if i < min_iterations:
|
||||
mem_limit = mem + 1
|
||||
continue
|
||||
msg = f"memory usage limit exceeded after {i + 1} iterations"
|
||||
assert mem_limit is not None
|
||||
assert mem <= mem_limit, msg
|
||||
|
||||
|
||||
def test_leak_putdata():
|
||||
def test_leak_putdata() -> None:
|
||||
im = Image.new("RGB", (25, 25))
|
||||
_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))
|
||||
_test_leak(
|
||||
min_iterations,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Tests potential DOS of Jpeg2kImagePlugin with 0 length block.
|
||||
# Run from anywhere that PIL is importable.
|
||||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
@ -18,7 +20,7 @@ pytestmark = [
|
|||
]
|
||||
|
||||
|
||||
def test_leak_load():
|
||||
def test_leak_load() -> None:
|
||||
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
||||
|
||||
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||
|
@ -28,7 +30,7 @@ def test_leak_load():
|
|||
im.load()
|
||||
|
||||
|
||||
def test_leak_save():
|
||||
def test_leak_save() -> None:
|
||||
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
||||
|
||||
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def test_j2k_overflow(tmp_path):
|
||||
def test_j2k_overflow(tmp_path: Path) -> None:
|
||||
im = Image.new("RGBA", (1024, 131584))
|
||||
target = str(tmp_path / "temp.jpc")
|
||||
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
|
||||
|
||||
# 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
|
||||
# in the valgrind especially if run in a debug python
|
||||
# version.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
@ -109,14 +111,14 @@ 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")
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", qtables=qtables)
|
||||
|
||||
|
||||
def test_exif_leak():
|
||||
def test_exif_leak() -> None:
|
||||
"""
|
||||
pre patch:
|
||||
|
||||
|
@ -179,7 +181,7 @@ def test_exif_leak():
|
|||
im.save(test_output, "JPEG", exif=exif)
|
||||
|
||||
|
||||
def test_base_save():
|
||||
def test_base_save() -> None:
|
||||
"""
|
||||
base case:
|
||||
MB
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -14,6 +18,7 @@ from PIL import Image
|
|||
# 2.7 and 3.2.
|
||||
|
||||
|
||||
numpy: ModuleType | None
|
||||
try:
|
||||
import numpy
|
||||
except ImportError:
|
||||
|
@ -26,23 +31,24 @@ XDIM = 48000
|
|||
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")
|
||||
im = Image.new("L", (xdim, ydim), 0)
|
||||
im.save(f)
|
||||
|
||||
|
||||
def test_large(tmp_path):
|
||||
def test_large(tmp_path: Path) -> None:
|
||||
"""succeeded prepatch"""
|
||||
_write_png(tmp_path, XDIM, YDIM)
|
||||
|
||||
|
||||
def test_2gpx(tmp_path):
|
||||
def test_2gpx(tmp_path: Path) -> None:
|
||||
"""failed prepatch"""
|
||||
_write_png(tmp_path, XDIM, XDIM)
|
||||
|
||||
|
||||
@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))
|
||||
Image.fromarray(arr)
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -22,7 +25,7 @@ XDIM = 48000
|
|||
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
|
||||
a = np.zeros((xdim, ydim), dtype=dtype)
|
||||
f = str(tmp_path / "temp.png")
|
||||
|
@ -30,11 +33,11 @@ def _write_png(tmp_path, xdim, ydim):
|
|||
im.save(f)
|
||||
|
||||
|
||||
def test_large(tmp_path):
|
||||
def test_large(tmp_path: Path) -> None:
|
||||
"""succeeded prepatch"""
|
||||
_write_png(tmp_path, XDIM, YDIM)
|
||||
|
||||
|
||||
def test_2gpx(tmp_path):
|
||||
def test_2gpx(tmp_path: Path) -> None:
|
||||
"""failed prepatch"""
|
||||
_write_png(tmp_path, XDIM, XDIM)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
@ -5,7 +7,7 @@ from PIL import Image
|
|||
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
|
||||
libtiff >= 4.0.0
|
||||
"""
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import zlib
|
||||
from io import BytesIO
|
||||
|
||||
|
@ -6,7 +8,7 @@ from PIL import Image, ImageFile, PngImagePlugin
|
|||
TEST_FILE = "Tests/images/png_decompression_dos.png"
|
||||
|
||||
|
||||
def test_ignore_dos_text():
|
||||
def test_ignore_dos_text() -> None:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
|
||||
try:
|
||||
|
@ -22,7 +24,7 @@ def test_ignore_dos_text():
|
|||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||
|
||||
|
||||
def test_dos_text():
|
||||
def test_dos_text() -> None:
|
||||
try:
|
||||
im = Image.open(TEST_FILE)
|
||||
im.load()
|
||||
|
@ -34,7 +36,7 @@ def test_dos_text():
|
|||
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))
|
||||
compressed_data = zlib.compress(b"a" * 1024 * 1023)
|
||||
|
||||
|
@ -51,7 +53,7 @@ def test_dos_total_memory():
|
|||
try:
|
||||
im2 = Image.open(b)
|
||||
except ValueError as msg:
|
||||
assert "Too much memory" in msg
|
||||
assert "Too much memory" in str(msg)
|
||||
return
|
||||
|
||||
total_len = 0
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
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 pytest
|
||||
|
||||
def pytest_report_header(config):
|
||||
|
||||
def pytest_report_header(config: pytest.Config) -> str:
|
||||
try:
|
||||
from PIL import features
|
||||
|
||||
|
@ -12,7 +16,7 @@ def pytest_report_header(config):
|
|||
return f"pytest_report_header failed: {e}"
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
def pytest_configure(config: pytest.Config) -> None:
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"pil_noop_mark: A conditional mark where nothing special happens",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
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
|
||||
NotoSans-Regular.ttf, from https://www.google.com/get/noto/
|
||||
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
|
||||
TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
|
||||
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
|
||||
|
||||
"Public domain font. Share and enjoy."
|
||||
|
||||
CBDTTestFont.ttf and EBDTTestFont.ttf from https://github.com/nulano/font-tests are public domain.
|
||||
|
|
190
Tests/helper.py
|
@ -1,14 +1,17 @@
|
|||
"""
|
||||
Helper functions.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import tempfile
|
||||
from io import BytesIO
|
||||
from typing import Any, Callable, Sequence
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
@ -17,42 +20,31 @@ from PIL import Image, ImageMath, features
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
HAS_UPLOADER = False
|
||||
|
||||
uploader = None
|
||||
if os.environ.get("SHOW_ERRORS"):
|
||||
# local img.show for errors.
|
||||
HAS_UPLOADER = True
|
||||
|
||||
class test_image_results:
|
||||
@staticmethod
|
||||
def upload(a, b):
|
||||
a.show()
|
||||
b.show()
|
||||
|
||||
uploader = "show"
|
||||
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")
|
||||
os.makedirs(dir_errors, exist_ok=True)
|
||||
tmpdir = tempfile.mkdtemp(dir=dir_errors)
|
||||
a.save(os.path.join(tmpdir, "a.png"))
|
||||
b.save(os.path.join(tmpdir, "b.png"))
|
||||
return tmpdir
|
||||
|
||||
else:
|
||||
try:
|
||||
import test_image_results
|
||||
|
||||
HAS_UPLOADER = True
|
||||
except ImportError:
|
||||
pass
|
||||
uploader = "github_actions"
|
||||
|
||||
|
||||
def convert_to_comparable(a, b):
|
||||
def upload(a: Image.Image, b: Image.Image) -> str | None:
|
||||
if uploader == "show":
|
||||
# local img.show for errors.
|
||||
a.show()
|
||||
b.show()
|
||||
elif uploader == "github_actions":
|
||||
dir_errors = os.path.join(os.path.dirname(__file__), "errors")
|
||||
os.makedirs(dir_errors, exist_ok=True)
|
||||
tmpdir = tempfile.mkdtemp(dir=dir_errors)
|
||||
a.save(os.path.join(tmpdir, "a.png"))
|
||||
b.save(os.path.join(tmpdir, "b.png"))
|
||||
return tmpdir
|
||||
return None
|
||||
|
||||
|
||||
def convert_to_comparable(
|
||||
a: Image.Image, b: Image.Image
|
||||
) -> tuple[Image.Image, Image.Image]:
|
||||
new_a, new_b = a, b
|
||||
if a.mode == "P":
|
||||
new_a = Image.new("L", a.size)
|
||||
|
@ -65,14 +57,18 @@ def convert_to_comparable(a, 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:
|
||||
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
|
||||
except Exception:
|
||||
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:
|
||||
assert im.mode == 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.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
||||
if a.tobytes() != b.tobytes():
|
||||
if HAS_UPLOADER:
|
||||
try:
|
||||
url = test_image_results.upload(a, b)
|
||||
try:
|
||||
url = upload(a, b)
|
||||
if url:
|
||||
logger.error("URL for test images: %s", url)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
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:
|
||||
if mode:
|
||||
img = img.convert(mode)
|
||||
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.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}"
|
||||
)
|
||||
except Exception as e:
|
||||
if HAS_UPLOADER:
|
||||
try:
|
||||
url = test_image_results.upload(a, b)
|
||||
try:
|
||||
url = upload(a, b)
|
||||
if url:
|
||||
logger.exception("URL for test images: %s", url)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
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:
|
||||
if mode:
|
||||
img = img.convert(mode)
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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"""
|
||||
value = True
|
||||
for i, target in enumerate(targets):
|
||||
value *= target - threshold <= actuals[i] <= target + threshold
|
||||
|
||||
assert value, msg + ": " + repr(actuals) + " != " + repr(targets)
|
||||
if not (target - threshold <= actuals[i] <= target + threshold):
|
||||
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
|
||||
|
||||
|
||||
def skip_unless_feature(feature):
|
||||
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
||||
reason = f"{feature} not available"
|
||||
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):
|
||||
return pytest.mark.skip(f"{feature} not available")
|
||||
if reason is None:
|
||||
reason = f"{feature} is older than {version_required}"
|
||||
version_required = parse_version(version_required)
|
||||
reason = f"{feature} is older than {required}"
|
||||
version_required = parse_version(required)
|
||||
version_available = parse_version(features.version(feature))
|
||||
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):
|
||||
return pytest.mark.pil_noop_mark()
|
||||
if reason is None:
|
||||
|
@ -192,7 +205,7 @@ class PillowLeakTestCase:
|
|||
iterations = 100 # count
|
||||
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
|
||||
between macOS and Linux rss reporting
|
||||
|
@ -214,7 +227,7 @@ class PillowLeakTestCase:
|
|||
# This is the maximum resident set size used (in kilobytes).
|
||||
return mem # Kb
|
||||
|
||||
def _test_leak(self, core):
|
||||
def _test_leak(self, core: Callable[[], None]) -> None:
|
||||
start_mem = self._get_mem_usage()
|
||||
for cycle in range(self.iterations):
|
||||
core()
|
||||
|
@ -226,17 +239,17 @@ class PillowLeakTestCase:
|
|||
# helpers
|
||||
|
||||
|
||||
def fromstring(data):
|
||||
def fromstring(data: bytes) -> Image.Image:
|
||||
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()
|
||||
im.save(out, string_format, **options)
|
||||
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:
|
||||
# Always return fresh not-yet-loaded version of image.
|
||||
# Operations on not-yet-loaded images is separate class of errors
|
||||
|
@ -257,19 +270,31 @@ def hopper(mode=None, cache={}):
|
|||
return im.copy()
|
||||
|
||||
|
||||
def djpeg_available():
|
||||
return bool(shutil.which("djpeg"))
|
||||
def djpeg_available() -> bool:
|
||||
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():
|
||||
return bool(shutil.which("cjpeg"))
|
||||
def cjpeg_available() -> bool:
|
||||
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"))
|
||||
|
||||
|
||||
def magick_command():
|
||||
def magick_command() -> list[str] | None:
|
||||
if sys.platform == "win32":
|
||||
magickhome = os.environ.get("MAGICK_HOME")
|
||||
if magickhome:
|
||||
|
@ -286,47 +311,48 @@ def magick_command():
|
|||
return imagemagick
|
||||
if graphicsmagick and shutil.which(graphicsmagick[0]):
|
||||
return graphicsmagick
|
||||
return None
|
||||
|
||||
|
||||
def on_appveyor():
|
||||
def on_appveyor() -> bool:
|
||||
return "APPVEYOR" in os.environ
|
||||
|
||||
|
||||
def on_github_actions():
|
||||
def on_github_actions() -> bool:
|
||||
return "GITHUB_ACTIONS" in os.environ
|
||||
|
||||
|
||||
def on_ci():
|
||||
def on_ci() -> bool:
|
||||
# GitHub Actions and AppVeyor have "CI"
|
||||
return "CI" in os.environ
|
||||
|
||||
|
||||
def is_big_endian():
|
||||
def is_big_endian() -> bool:
|
||||
return sys.byteorder == "big"
|
||||
|
||||
|
||||
def is_ppc64le():
|
||||
def is_ppc64le() -> bool:
|
||||
import platform
|
||||
|
||||
return platform.machine() == "ppc64le"
|
||||
|
||||
|
||||
def is_win32():
|
||||
def is_win32() -> bool:
|
||||
return sys.platform.startswith("win32")
|
||||
|
||||
|
||||
def is_pypy():
|
||||
def is_pypy() -> bool:
|
||||
return hasattr(sys, "pypy_translation_info")
|
||||
|
||||
|
||||
def is_mingw():
|
||||
def is_mingw() -> bool:
|
||||
return sysconfig.get_platform() == "mingw"
|
||||
|
||||
|
||||
class CachedProperty:
|
||||
def __init__(self, func):
|
||||
def __init__(self, func: Callable[[Any], None]) -> None:
|
||||
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)
|
||||
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
|
||||
#We used the following version of the gnuplot program
|
||||
#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.
|
||||
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
|
||||
|
|
|
@ -23,7 +23,7 @@ with atheris.instrument_imports():
|
|||
import fuzzers
|
||||
|
||||
|
||||
def TestOneInput(data):
|
||||
def TestOneInput(data: bytes) -> None:
|
||||
try:
|
||||
fuzzers.fuzz_font(data)
|
||||
except Exception:
|
||||
|
@ -32,7 +32,7 @@ def TestOneInput(data):
|
|||
pass
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
fuzzers.enable_decompressionbomb_error()
|
||||
atheris.Setup(sys.argv, TestOneInput)
|
||||
atheris.Fuzz()
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -23,7 +21,7 @@ with atheris.instrument_imports():
|
|||
import fuzzers
|
||||
|
||||
|
||||
def TestOneInput(data):
|
||||
def TestOneInput(data: bytes) -> None:
|
||||
try:
|
||||
fuzzers.fuzz_image(data)
|
||||
except Exception:
|
||||
|
@ -32,7 +30,7 @@ def TestOneInput(data):
|
|||
pass
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
fuzzers.enable_decompressionbomb_error()
|
||||
atheris.Setup(sys.argv, TestOneInput)
|
||||
atheris.Fuzz()
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import warnings
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
|
||||
|
||||
|
||||
def enable_decompressionbomb_error():
|
||||
def enable_decompressionbomb_error() -> None:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
warnings.filterwarnings("ignore")
|
||||
warnings.simplefilter("error", Image.DecompressionBombWarning)
|
||||
|
||||
|
||||
def disable_decompressionbomb_error():
|
||||
def disable_decompressionbomb_error() -> None:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||
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
|
||||
# invalid images in the test suite.
|
||||
with Image.open(io.BytesIO(data)) as im:
|
||||
|
@ -24,7 +26,7 @@ def fuzz_image(data):
|
|||
im.save(io.BytesIO(), "BMP")
|
||||
|
||||
|
||||
def fuzz_font(data):
|
||||
def fuzz_font(data: bytes) -> None:
|
||||
wrapper = io.BytesIO(data)
|
||||
try:
|
||||
font = ImageFont.truetype(wrapper)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
@ -22,7 +24,7 @@ if features.check("libjpeg_turbo"):
|
|||
"path",
|
||||
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()
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
|
@ -53,7 +55,7 @@ def test_fuzz_images(path):
|
|||
@pytest.mark.parametrize(
|
||||
"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:
|
||||
return
|
||||
with open(path, "rb") as f:
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def test_sanity():
|
||||
def test_sanity() -> None:
|
||||
# Make sure we have the binary extension
|
||||
Image.core.new("L", (100, 100))
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from PIL import _binary
|
||||
|
||||
|
||||
def test_standard():
|
||||
def test_standard() -> None:
|
||||
assert _binary.i8(b"*") == 42
|
||||
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.i32le(b"\xff\xff\x00\x00") == 65535
|
||||
|
||||
|
@ -14,7 +16,7 @@ def test_little_endian():
|
|||
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.i32be(b"\x00\x00\xff\xff") == 65535
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import warnings
|
||||
|
||||
|
@ -8,13 +10,13 @@ from .helper import assert_image_similar
|
|||
base = os.path.join("Tests", "images", "bmp")
|
||||
|
||||
|
||||
def get_files(d, ext=".bmp"):
|
||||
def get_files(d, ext: str = ".bmp"):
|
||||
return [
|
||||
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
|
||||
either"""
|
||||
for f in get_files("b"):
|
||||
|
@ -54,7 +56,7 @@ def test_questionable():
|
|||
raise
|
||||
|
||||
|
||||
def test_good():
|
||||
def test_good() -> None:
|
||||
"""These should all work. There's a set of target files in the
|
||||
html directory that we can compare against."""
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageFilter
|
||||
|
@ -14,18 +16,18 @@ sample.putdata(sum([
|
|||
# fmt: on
|
||||
|
||||
|
||||
def test_imageops_box_blur():
|
||||
def test_imageops_box_blur() -> None:
|
||||
i = sample.filter(ImageFilter.BoxBlur(1))
|
||||
assert i.mode == sample.mode
|
||||
assert i.size == sample.size
|
||||
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))
|
||||
|
||||
|
||||
def assert_image(im, data, delta=0):
|
||||
def assert_image(im, data, delta: int = 0) -> None:
|
||||
it = iter(im.getdata())
|
||||
for data_row in data:
|
||||
im_row = [next(it) for _ in range(im.size[0])]
|
||||
|
@ -35,7 +37,7 @@ def assert_image(im, data, delta=0):
|
|||
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
|
||||
assert_image(box_blur(im, radius, passes), data, delta)
|
||||
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)
|
||||
|
||||
|
||||
def test_color_modes():
|
||||
def test_color_modes() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
box_blur(sample.convert("1"))
|
||||
with pytest.raises(ValueError):
|
||||
|
@ -63,7 +65,7 @@ def test_color_modes():
|
|||
box_blur(sample.convert("YCbCr"))
|
||||
|
||||
|
||||
def test_radius_0():
|
||||
def test_radius_0() -> None:
|
||||
assert_blur(
|
||||
sample,
|
||||
0,
|
||||
|
@ -79,7 +81,7 @@ def test_radius_0():
|
|||
)
|
||||
|
||||
|
||||
def test_radius_0_02():
|
||||
def test_radius_0_02() -> None:
|
||||
assert_blur(
|
||||
sample,
|
||||
0.02,
|
||||
|
@ -96,7 +98,7 @@ def test_radius_0_02():
|
|||
)
|
||||
|
||||
|
||||
def test_radius_0_05():
|
||||
def test_radius_0_05() -> None:
|
||||
assert_blur(
|
||||
sample,
|
||||
0.05,
|
||||
|
@ -113,7 +115,7 @@ def test_radius_0_05():
|
|||
)
|
||||
|
||||
|
||||
def test_radius_0_1():
|
||||
def test_radius_0_1() -> None:
|
||||
assert_blur(
|
||||
sample,
|
||||
0.1,
|
||||
|
@ -130,7 +132,7 @@ def test_radius_0_1():
|
|||
)
|
||||
|
||||
|
||||
def test_radius_0_5():
|
||||
def test_radius_0_5() -> None:
|
||||
assert_blur(
|
||||
sample,
|
||||
0.5,
|
||||
|
@ -147,7 +149,7 @@ def test_radius_0_5():
|
|||
)
|
||||
|
||||
|
||||
def test_radius_1():
|
||||
def test_radius_1() -> None:
|
||||
assert_blur(
|
||||
sample,
|
||||
1,
|
||||
|
@ -164,7 +166,7 @@ def test_radius_1():
|
|||
)
|
||||
|
||||
|
||||
def test_radius_1_5():
|
||||
def test_radius_1_5() -> None:
|
||||
assert_blur(
|
||||
sample,
|
||||
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(
|
||||
sample,
|
||||
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(
|
||||
sample,
|
||||
10,
|
||||
|
@ -213,7 +215,7 @@ def test_radius_bigger_then_width():
|
|||
)
|
||||
|
||||
|
||||
def test_extreme_large_radius():
|
||||
def test_extreme_large_radius() -> None:
|
||||
assert_blur(
|
||||
sample,
|
||||
600,
|
||||
|
@ -228,7 +230,7 @@ def test_extreme_large_radius():
|
|||
)
|
||||
|
||||
|
||||
def test_two_passes():
|
||||
def test_two_passes() -> None:
|
||||
assert_blur(
|
||||
sample,
|
||||
1,
|
||||
|
@ -246,7 +248,7 @@ def test_two_passes():
|
|||
)
|
||||
|
||||
|
||||
def test_three_passes():
|
||||
def test_three_passes() -> None:
|
||||
assert_blur(
|
||||
sample,
|
||||
1,
|
||||
|
|