Merge branch 'main' into msys
|
@ -1,3 +1,10 @@
|
||||||
|
skip_commits:
|
||||||
|
files:
|
||||||
|
- ".github/**/*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- "docs/**/*"
|
||||||
|
- "wheels/**/*"
|
||||||
|
|
||||||
version: '{build}'
|
version: '{build}'
|
||||||
clone_folder: c:\pillow
|
clone_folder: c:\pillow
|
||||||
init:
|
init:
|
||||||
|
@ -6,50 +13,48 @@ init:
|
||||||
# Uncomment previous line to get RDP access during the build.
|
# Uncomment previous line to get RDP access during the build.
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
COVERAGE_CORE: sysmon
|
||||||
EXECUTABLE: python.exe
|
EXECUTABLE: python.exe
|
||||||
TEST_OPTIONS:
|
TEST_OPTIONS:
|
||||||
DEPLOY: YES
|
DEPLOY: YES
|
||||||
matrix:
|
matrix:
|
||||||
- PYTHON: C:/Python311-x64
|
- PYTHON: C:/Python313
|
||||||
ARCHITECTURE: x64
|
ARCHITECTURE: x86
|
||||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||||
- PYTHON: C:/Python38-x64
|
- PYTHON: C:/Python39-x64
|
||||||
ARCHITECTURE: x64
|
ARCHITECTURE: AMD64
|
||||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||||
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- '%PYTHON%\%EXECUTABLE% --version'
|
- '%PYTHON%\%EXECUTABLE% --version'
|
||||||
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
|
- '%PYTHON%\%EXECUTABLE% -m pip install --upgrade pip'
|
||||||
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
|
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
|
||||||
- 7z x pillow-depends.zip -oc:\
|
|
||||||
- 7z x pillow-test-images.zip -oc:\
|
- 7z x pillow-test-images.zip -oc:\
|
||||||
- mv c:\pillow-depends-main c:\pillow-depends
|
|
||||||
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
|
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
|
||||||
- 7z x ..\pillow-depends\nasm-2.16.01-win64.zip -oc:\
|
- curl -fsSL -o nasm-win64.zip https://raw.githubusercontent.com/python-pillow/pillow-depends/main/nasm-2.16.03-win64.zip
|
||||||
- choco install ghostscript --version=10.0.0.20230317
|
- 7z x nasm-win64.zip -oc:\
|
||||||
- path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH%
|
- choco install ghostscript --version=10.4.0
|
||||||
|
- path c:\nasm-2.16.03;C:\Program Files\gs\gs10.04.0\bin;%PATH%
|
||||||
- cd c:\pillow\winbuild\
|
- cd c:\pillow\winbuild\
|
||||||
- ps: |
|
- ps: |
|
||||||
c:\python38\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
|
c:\python39\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
|
||||||
c:\pillow\winbuild\build\build_dep_all.cmd
|
c:\pillow\winbuild\build\build_dep_all.cmd
|
||||||
$host.SetShouldExit(0)
|
$host.SetShouldExit(0)
|
||||||
- path C:\pillow\winbuild\build\bin;%PATH%
|
- path C:\pillow\winbuild\build\bin;%PATH%
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- ps: |
|
|
||||||
c:\pillow\winbuild\build\build_pillow.cmd install
|
|
||||||
$host.SetShouldExit(0)
|
|
||||||
- cd c:\pillow
|
- cd c:\pillow
|
||||||
|
- winbuild\build\build_env.cmd
|
||||||
|
- '%PYTHON%\%EXECUTABLE% -m pip install -v -C raqm=vendor -C fribidi=vendor .'
|
||||||
- '%PYTHON%\%EXECUTABLE% selftest.py --installed'
|
- '%PYTHON%\%EXECUTABLE% selftest.py --installed'
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- cd c:\pillow
|
- cd c:\pillow
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout'
|
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout defusedxml ipython numpy olefile pyroma'
|
||||||
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
||||||
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
|
- path %PYTHON%;%PATH%
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
|
- .ci\test.cmd
|
||||||
#- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest?
|
|
||||||
|
|
||||||
after_test:
|
after_test:
|
||||||
- curl -Os https://uploader.codecov.io/latest/windows/codecov.exe
|
- curl -Os https://uploader.codecov.io/latest/windows/codecov.exe
|
||||||
|
@ -62,18 +67,15 @@ cache:
|
||||||
- '%LOCALAPPDATA%\pip\Cache'
|
- '%LOCALAPPDATA%\pip\Cache'
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
- path: pillow\dist\*.egg
|
- path: pillow\*.egg
|
||||||
name: egg
|
name: egg
|
||||||
- path: pillow\dist\*.wheel
|
- path: pillow\*.whl
|
||||||
name: wheel
|
name: wheel
|
||||||
|
|
||||||
before_deploy:
|
before_deploy:
|
||||||
- cd c:\pillow
|
- cd c:\pillow
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pip install wheel'
|
- '%PYTHON%\%EXECUTABLE% -m pip wheel -v -C raqm=vendor -C fribidi=vendor .'
|
||||||
- cd c:\pillow\winbuild\
|
- ps: Get-ChildItem .\*.whl | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
|
||||||
- c:\pillow\winbuild\build\build_pillow.cmd bdist_wheel
|
|
||||||
- cd c:\pillow
|
|
||||||
- ps: Get-ChildItem .\dist\*.* | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
provider: S3
|
provider: S3
|
||||||
|
|
|
@ -21,16 +21,16 @@ set -e
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
if [[ $(uname) != CYGWIN* ]]; then
|
||||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
||||||
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
ghostscript libjpeg-turbo-progs libopenjp2-7-dev\
|
||||||
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
||||||
sway wl-clipboard
|
sway wl-clipboard libopenblas-dev
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python3 -m pip install --upgrade wheel
|
python3 -m pip install --upgrade wheel
|
||||||
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
|
||||||
python3 -m pip install coverage
|
python3 -m pip install coverage
|
||||||
python3 -m pip install defusedxml
|
python3 -m pip install defusedxml
|
||||||
|
python3 -m pip install ipython
|
||||||
python3 -m pip install olefile
|
python3 -m pip install olefile
|
||||||
python3 -m pip install -U pytest
|
python3 -m pip install -U pytest
|
||||||
python3 -m pip install -U pytest-cov
|
python3 -m pip install -U pytest-cov
|
||||||
|
@ -38,13 +38,19 @@ python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
if [[ $(uname) != CYGWIN* ]]; then
|
||||||
# TODO Remove condition when NumPy supports 3.12
|
python3 -m pip install numpy
|
||||||
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
|
|
||||||
|
|
||||||
# PyQt6 doesn't support PyPy3
|
# PyQt6 doesn't support PyPy3
|
||||||
if [[ "$GHA_PYTHON_VERSION" != "3.12-dev" && $GHA_PYTHON_VERSION == 3.* ]]; then
|
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||||
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
||||||
python3 -m pip install pyqt6
|
# TODO Update condition when pyqt6 supports free-threading
|
||||||
|
if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pyroma uses non-isolated build and fails with old setuptools
|
||||||
|
if [[ $GHA_PYTHON_VERSION == 3.9 ]]; then
|
||||||
|
# To match pyproject.toml
|
||||||
|
python3 -m pip install "setuptools>=67.8"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# webp
|
# webp
|
||||||
|
|
1
.ci/requirements-cibw.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
cibuildwheel==2.21.3
|
12
.ci/requirements-mypy.txt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
mypy==1.13.0
|
||||||
|
IceSpringPySideStubs-PyQt6
|
||||||
|
IceSpringPySideStubs-PySide6
|
||||||
|
ipython
|
||||||
|
numpy
|
||||||
|
packaging
|
||||||
|
pytest
|
||||||
|
sphinx
|
||||||
|
types-atheris
|
||||||
|
types-defusedxml
|
||||||
|
types-olefile
|
||||||
|
types-setuptools
|
3
.ci/test.cmd
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
python.exe -c "from PIL import Image"
|
||||||
|
IF ERRORLEVEL 1 EXIT /B
|
||||||
|
python.exe -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests
|
|
@ -4,4 +4,4 @@ set -e
|
||||||
|
|
||||||
python3 -c "from PIL import Image"
|
python3 -c "from PIL import Image"
|
||||||
|
|
||||||
python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests $REVERSE
|
python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests $REVERSE
|
||||||
|
|
|
@ -3,12 +3,13 @@
|
||||||
BasedOnStyle: Google
|
BasedOnStyle: Google
|
||||||
AlwaysBreakAfterReturnType: All
|
AlwaysBreakAfterReturnType: All
|
||||||
AllowShortIfStatementsOnASingleLine: false
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
AlignAfterOpenBracket: AlwaysBreak
|
AlignAfterOpenBracket: BlockIndent
|
||||||
BinPackArguments: false
|
BinPackArguments: false
|
||||||
BinPackParameters: false
|
BinPackParameters: false
|
||||||
BreakBeforeBraces: Attach
|
BreakBeforeBraces: Attach
|
||||||
ColumnLimit: 88
|
ColumnLimit: 88
|
||||||
DerivePointerAlignment: false
|
DerivePointerAlignment: false
|
||||||
|
IndentGotoLabels: false
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
Language: Cpp
|
Language: Cpp
|
||||||
PointerAlignment: Right
|
PointerAlignment: Right
|
||||||
|
|
15
.coveragerc
|
@ -2,19 +2,22 @@
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
# Regexes for lines to exclude from consideration
|
# Regexes for lines to exclude from consideration
|
||||||
exclude_lines =
|
exclude_also =
|
||||||
# Have to re-enable the standard pragma:
|
# Don't complain if non-runnable code isn't run
|
||||||
pragma: no cover
|
|
||||||
|
|
||||||
# Don't complain if non-runnable code isn't run:
|
|
||||||
if 0:
|
if 0:
|
||||||
if __name__ == .__main__.:
|
if __name__ == .__main__.:
|
||||||
# Don't complain about debug code
|
# Don't complain about debug code
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
# Don't complain about compatibility code for missing optional dependencies
|
||||||
|
except ImportError
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
@abc.abstractmethod
|
||||||
|
# Empty bodies in protocols or abstract methods
|
||||||
|
^\s*def [a-zA-Z0-9_]+\(.*\)(\s*->.*)?:\s*\.\.\.(\s*#.*)?$
|
||||||
|
^\s*\.\.\.(\s*#.*)?$
|
||||||
|
|
||||||
[run]
|
[run]
|
||||||
omit =
|
omit =
|
||||||
Tests/32bit_segfault_check.py
|
Tests/32bit_segfault_check.py
|
||||||
Tests/bench_cffi_access.py
|
|
||||||
Tests/check_*.py
|
Tests/check_*.py
|
||||||
Tests/createfontdatachunk.py
|
Tests/createfontdatachunk.py
|
||||||
|
|
|
@ -13,7 +13,7 @@ indent_style = space
|
||||||
|
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.yml]
|
[*.{toml,yml}]
|
||||||
# Two-space indentation
|
# Two-space indentation
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
|
6
.git-blame-ignore-revs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Flake8
|
||||||
|
8de95676e0fd89f2326b3953488ab66ff29cd2d0
|
||||||
|
# Format with Black
|
||||||
|
53a7e3500437a9fd5826bc04758f7116bd7e52dc
|
||||||
|
# Format the C code with ClangFormat
|
||||||
|
46b7e86bab79450ec0a2866c6c0c679afb659d17
|
1
.github/CONTRIBUTING.md
vendored
|
@ -19,7 +19,6 @@ Please send a pull request to the `main` branch. Please include [documentation](
|
||||||
- Follow PEP 8.
|
- Follow PEP 8.
|
||||||
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor.
|
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor.
|
||||||
- Include [release notes](https://github.com/python-pillow/Pillow/tree/main/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests.
|
- Include [release notes](https://github.com/python-pillow/Pillow/tree/main/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests.
|
||||||
- Do not add to the [changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) for proposed changes, as that is updated after changes are merged.
|
|
||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
|
|
2
.github/FUNDING.yml
vendored
|
@ -1 +1 @@
|
||||||
tidelift: "pypi/Pillow"
|
tidelift: "pypi/pillow"
|
||||||
|
|
15
.github/ISSUE_TEMPLATE/ISSUE_REPORT.md
vendored
|
@ -48,6 +48,21 @@ Thank you.
|
||||||
* Python:
|
* Python:
|
||||||
* Pillow:
|
* Pillow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Please paste here the output of running:
|
||||||
|
|
||||||
|
python3 -m PIL.report
|
||||||
|
or
|
||||||
|
python3 -m PIL --report
|
||||||
|
|
||||||
|
Or the output of the following Python code:
|
||||||
|
|
||||||
|
from PIL import report
|
||||||
|
# or
|
||||||
|
from PIL import features
|
||||||
|
features.pilinfo(supported_formats=False)
|
||||||
|
```
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
|
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
|
||||||
|
|
||||||
|
|
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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
13
.github/release-drafter.yml
vendored
|
@ -3,16 +3,19 @@ tag-template: "$NEXT_MINOR_VERSION"
|
||||||
change-template: '- $TITLE #$NUMBER [@$AUTHOR]'
|
change-template: '- $TITLE #$NUMBER [@$AUTHOR]'
|
||||||
|
|
||||||
categories:
|
categories:
|
||||||
- title: "Dependencies"
|
- title: "Removals"
|
||||||
label: "Dependency"
|
label: "Removal"
|
||||||
- title: "Deprecations"
|
- title: "Deprecations"
|
||||||
label: "Deprecation"
|
label: "Deprecation"
|
||||||
- title: "Documentation"
|
- title: "Documentation"
|
||||||
label: "Documentation"
|
label: "Documentation"
|
||||||
- title: "Removals"
|
- title: "Dependencies"
|
||||||
label: "Removal"
|
label: "Dependency"
|
||||||
- title: "Testing"
|
- title: "Testing"
|
||||||
label: "Testing"
|
label: "Testing"
|
||||||
|
- title: "Type hints"
|
||||||
|
label: "Type hints"
|
||||||
|
- title: "Other changes"
|
||||||
|
|
||||||
exclude-labels:
|
exclude-labels:
|
||||||
- "changelog: skip"
|
- "changelog: skip"
|
||||||
|
@ -21,6 +24,4 @@ template: |
|
||||||
|
|
||||||
https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html
|
https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html
|
||||||
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
$CHANGES
|
$CHANGES
|
||||||
|
|
12
.github/renovate.json
vendored
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": [
|
||||||
"config:base"
|
"config:recommended"
|
||||||
],
|
],
|
||||||
"labels": [
|
"labels": [
|
||||||
"Dependency"
|
"Dependency"
|
||||||
|
@ -9,9 +9,13 @@
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
{
|
||||||
"groupName": "github-actions",
|
"groupName": "github-actions",
|
||||||
"matchManagers": ["github-actions"],
|
"matchManagers": [
|
||||||
"separateMajorMinor": "false"
|
"github-actions"
|
||||||
|
],
|
||||||
|
"separateMajorMinor": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"schedule": ["on the 3rd day of the month"]
|
"schedule": [
|
||||||
|
"on the 3rd day of the month"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
8
.github/workflows/cifuzz.yml
vendored
|
@ -2,13 +2,17 @@ name: CIFuzz
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/cifuzz.yml"
|
- ".github/workflows/cifuzz.yml"
|
||||||
|
- ".github/workflows/wheels-dependencies.sh"
|
||||||
- "**.c"
|
- "**.c"
|
||||||
- "**.h"
|
- "**.h"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/cifuzz.yml"
|
- ".github/workflows/cifuzz.yml"
|
||||||
|
- ".github/workflows/wheels-dependencies.sh"
|
||||||
- "**.c"
|
- "**.c"
|
||||||
- "**.h"
|
- "**.h"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -40,13 +44,13 @@ jobs:
|
||||||
language: python
|
language: python
|
||||||
dry-run: false
|
dry-run: false
|
||||||
- name: Upload New Crash
|
- name: Upload New Crash
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure() && steps.build.outcome == 'success'
|
if: failure() && steps.build.outcome == 'success'
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
name: artifacts
|
||||||
path: ./out/artifacts
|
path: ./out/artifacts
|
||||||
- name: Upload Legacy Crash
|
- name: Upload Legacy Crash
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: steps.run.outcome == 'success'
|
if: steps.run.outcome == 'success'
|
||||||
with:
|
with:
|
||||||
name: crash
|
name: crash
|
||||||
|
|
22
.github/workflows/docs.yml
vendored
|
@ -2,13 +2,17 @@ name: Docs
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "src/PIL/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "src/PIL/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -28,23 +32,35 @@ jobs:
|
||||||
name: Docs
|
name: Docs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: ".ci/*.sh"
|
cache-dependency-path: |
|
||||||
|
".ci/*.sh"
|
||||||
|
"pyproject.toml"
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
|
- name: Cache libimagequant
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: cache-libimagequant
|
||||||
|
with:
|
||||||
|
path: ~/cache-libimagequant
|
||||||
|
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
|
||||||
|
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
run: |
|
run: |
|
||||||
.ci/install.sh
|
.ci/install.sh
|
||||||
env:
|
env:
|
||||||
GHA_PYTHON_VERSION: "3.x"
|
GHA_PYTHON_VERSION: "3.x"
|
||||||
|
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
|
14
.github/workflows/lint.yml
vendored
|
@ -2,6 +2,9 @@ name: Lint
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
@ -17,10 +20,12 @@ jobs:
|
||||||
name: Lint
|
name: Lint
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: pre-commit cache
|
- name: pre-commit cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pre-commit
|
path: ~/.cache/pre-commit
|
||||||
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
||||||
|
@ -28,7 +33,7 @@ jobs:
|
||||||
lint-pre-commit-
|
lint-pre-commit-
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
cache: pip
|
cache: pip
|
||||||
|
@ -46,3 +51,6 @@ jobs:
|
||||||
run: tox -e lint
|
run: tox -e lint
|
||||||
env:
|
env:
|
||||||
PRE_COMMIT_COLOR: always
|
PRE_COMMIT_COLOR: always
|
||||||
|
|
||||||
|
- name: Mypy
|
||||||
|
run: tox -e mypy
|
||||||
|
|
25
.github/workflows/macos-install.sh
vendored
|
@ -2,19 +2,34 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
|
if [[ "$ImageOS" == "macos13" ]]; then
|
||||||
|
brew uninstall gradle maven
|
||||||
|
fi
|
||||||
|
brew install \
|
||||||
|
freetype \
|
||||||
|
ghostscript \
|
||||||
|
libimagequant \
|
||||||
|
libjpeg \
|
||||||
|
libtiff \
|
||||||
|
little-cms2 \
|
||||||
|
openjpeg \
|
||||||
|
webp
|
||||||
|
if [[ "$ImageOS" == "macos13" ]]; then
|
||||||
|
brew install --ignore-dependencies libraqm
|
||||||
|
else
|
||||||
|
brew install libraqm
|
||||||
|
fi
|
||||||
|
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||||
|
|
||||||
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
|
||||||
python3 -m pip install coverage
|
python3 -m pip install coverage
|
||||||
python3 -m pip install defusedxml
|
python3 -m pip install defusedxml
|
||||||
|
python3 -m pip install ipython
|
||||||
python3 -m pip install olefile
|
python3 -m pip install olefile
|
||||||
python3 -m pip install -U pytest
|
python3 -m pip install -U pytest
|
||||||
python3 -m pip install -U pytest-cov
|
python3 -m pip install -U pytest-cov
|
||||||
python3 -m pip install -U pytest-timeout
|
python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
|
python3 -m pip install numpy
|
||||||
# TODO Remove condition when NumPy supports 3.12
|
|
||||||
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
|
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
4
.github/workflows/release-drafter.yml
vendored
|
@ -10,7 +10,7 @@ on:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
@ -23,6 +23,6 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
# Drafts your next release notes as pull requests are merged into "main"
|
# Drafts your next release notes as pull requests are merged into "main"
|
||||||
- uses: release-drafter/release-drafter@v5
|
- uses: release-drafter/release-drafter@v6
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
8
.github/workflows/stale.yml
vendored
|
@ -6,21 +6,23 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
contents: read
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
if: github.repository_owner == 'python-pillow'
|
if: github.repository_owner == 'python-pillow'
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Check issues"
|
- name: "Check issues"
|
||||||
uses: actions/stale@v8
|
uses: actions/stale@v9
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
only-labels: "Awaiting OP Action"
|
only-labels: "Awaiting OP Action"
|
||||||
|
|
3
.github/workflows/system-info.py
vendored
|
@ -6,6 +6,9 @@ This sort of info is missing from GitHub Actions.
|
||||||
Requested here:
|
Requested here:
|
||||||
https://github.com/actions/virtual-environments/issues/79
|
https://github.com/actions/virtual-environments/issues/79
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
|
|
46
.github/workflows/test-cygwin.yml
vendored
|
@ -2,13 +2,21 @@ name: Test Cygwin
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -18,13 +26,16 @@ concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
COVERAGE_CORE: sysmon
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-minor-version: [8, 9]
|
python-minor-version: [9]
|
||||||
|
|
||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
|
|
||||||
|
@ -36,15 +47,17 @@ jobs:
|
||||||
git config --global core.autocrlf input
|
git config --global core.autocrlf input
|
||||||
|
|
||||||
- name: Checkout Pillow
|
- name: Checkout Pillow
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Cygwin
|
- name: Install Cygwin
|
||||||
uses: cygwin/cygwin-install-action@v4
|
uses: cygwin/cygwin-install-action@v4
|
||||||
with:
|
with:
|
||||||
platform: x86_64
|
|
||||||
packages: >
|
packages: >
|
||||||
gcc-g++
|
gcc-g++
|
||||||
ghostscript
|
ghostscript
|
||||||
|
git
|
||||||
ImageMagick
|
ImageMagick
|
||||||
jpeg
|
jpeg
|
||||||
libfreetype-devel
|
libfreetype-devel
|
||||||
|
@ -61,9 +74,9 @@ jobs:
|
||||||
make
|
make
|
||||||
netpbm
|
netpbm
|
||||||
perl
|
perl
|
||||||
python3${{ matrix.python-minor-version }}-cffi
|
|
||||||
python3${{ matrix.python-minor-version }}-cython
|
python3${{ matrix.python-minor-version }}-cython
|
||||||
python3${{ matrix.python-minor-version }}-devel
|
python3${{ matrix.python-minor-version }}-devel
|
||||||
|
python3${{ matrix.python-minor-version }}-ipython
|
||||||
python3${{ matrix.python-minor-version }}-numpy
|
python3${{ matrix.python-minor-version }}-numpy
|
||||||
python3${{ matrix.python-minor-version }}-sip
|
python3${{ matrix.python-minor-version }}-sip
|
||||||
python3${{ matrix.python-minor-version }}-tkinter
|
python3${{ matrix.python-minor-version }}-tkinter
|
||||||
|
@ -72,22 +85,22 @@ jobs:
|
||||||
zlib-devel
|
zlib-devel
|
||||||
|
|
||||||
- name: Add Lapack to PATH
|
- name: Add Lapack to PATH
|
||||||
uses: egor-tensin/cleanup-path@v3
|
uses: egor-tensin/cleanup-path@v4
|
||||||
with:
|
with:
|
||||||
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
|
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
|
||||||
|
|
||||||
|
- name: Select Python version
|
||||||
|
run: |
|
||||||
|
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
|
||||||
|
|
||||||
- name: pip cache
|
- name: pip cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
||||||
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }}
|
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
|
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
|
||||||
|
|
||||||
- name: Select Python version
|
|
||||||
run: |
|
|
||||||
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
|
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: |
|
run: |
|
||||||
dash.exe -c "python3 .github/workflows/system-info.py"
|
dash.exe -c "python3 .github/workflows/system-info.py"
|
||||||
|
@ -96,11 +109,6 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
bash.exe .ci/install.sh
|
bash.exe .ci/install.sh
|
||||||
|
|
||||||
- name: Install a different NumPy
|
|
||||||
shell: dash.exe -l "{0}"
|
|
||||||
run: |
|
|
||||||
python3 -m pip install -U numpy
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: bash.exe -eo pipefail -o igncr "{0}"
|
shell: bash.exe -eo pipefail -o igncr "{0}"
|
||||||
run: |
|
run: |
|
||||||
|
@ -116,7 +124,7 @@ jobs:
|
||||||
dash.exe -c "mkdir -p Tests/errors"
|
dash.exe -c "mkdir -p Tests/errors"
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: errors
|
name: errors
|
||||||
|
@ -125,13 +133,15 @@ jobs:
|
||||||
- name: After success
|
- name: After success
|
||||||
run: |
|
run: |
|
||||||
bash.exe .ci/after_success.sh
|
bash.exe .ci/after_success.sh
|
||||||
|
rm C:\cygwin\bin\bash.EXE
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
file: ./coverage.xml
|
files: ./coverage.xml
|
||||||
flags: GHA_Cygwin
|
flags: GHA_Cygwin
|
||||||
name: Cygwin Python 3.${{ matrix.python-minor-version }}
|
name: Cygwin Python 3.${{ matrix.python-minor-version }}
|
||||||
|
token: ${{ secrets.CODECOV_ORG_TOKEN }}
|
||||||
|
|
||||||
success:
|
success:
|
||||||
permissions:
|
permissions:
|
||||||
|
|
38
.github/workflows/test-docker.yml
vendored
|
@ -2,13 +2,21 @@ name: Test Docker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -28,37 +36,37 @@ jobs:
|
||||||
docker: [
|
docker: [
|
||||||
# Run slower jobs first to give them a headstart and reduce waiting time
|
# Run slower jobs first to give them a headstart and reduce waiting time
|
||||||
ubuntu-22.04-jammy-arm64v8,
|
ubuntu-22.04-jammy-arm64v8,
|
||||||
ubuntu-22.04-jammy-ppc64le,
|
ubuntu-24.04-noble-ppc64le,
|
||||||
ubuntu-22.04-jammy-s390x,
|
ubuntu-24.04-noble-s390x,
|
||||||
# Then run the remainder
|
# Then run the remainder
|
||||||
alpine,
|
alpine,
|
||||||
amazon-2-amd64,
|
amazon-2-amd64,
|
||||||
amazon-2023-amd64,
|
amazon-2023-amd64,
|
||||||
arch,
|
arch,
|
||||||
centos-7-amd64,
|
|
||||||
centos-stream-8-amd64,
|
|
||||||
centos-stream-9-amd64,
|
centos-stream-9-amd64,
|
||||||
debian-11-bullseye-amd64,
|
debian-12-bookworm-x86,
|
||||||
debian-12-bookworm-amd64,
|
debian-12-bookworm-amd64,
|
||||||
fedora-37-amd64,
|
fedora-40-amd64,
|
||||||
fedora-38-amd64,
|
fedora-41-amd64,
|
||||||
gentoo,
|
gentoo,
|
||||||
ubuntu-20.04-focal-amd64,
|
|
||||||
ubuntu-22.04-jammy-amd64,
|
ubuntu-22.04-jammy-amd64,
|
||||||
|
ubuntu-24.04-noble-amd64,
|
||||||
]
|
]
|
||||||
dockerTag: [main]
|
dockerTag: [main]
|
||||||
include:
|
include:
|
||||||
- docker: "ubuntu-22.04-jammy-arm64v8"
|
- docker: "ubuntu-22.04-jammy-arm64v8"
|
||||||
qemu-arch: "aarch64"
|
qemu-arch: "aarch64"
|
||||||
- docker: "ubuntu-22.04-jammy-ppc64le"
|
- docker: "ubuntu-24.04-noble-ppc64le"
|
||||||
qemu-arch: "ppc64le"
|
qemu-arch: "ppc64le"
|
||||||
- docker: "ubuntu-22.04-jammy-s390x"
|
- docker: "ubuntu-24.04-noble-s390x"
|
||||||
qemu-arch: "s390x"
|
qemu-arch: "s390x"
|
||||||
|
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
@ -74,8 +82,8 @@ jobs:
|
||||||
|
|
||||||
- name: Docker build
|
- name: Docker build
|
||||||
run: |
|
run: |
|
||||||
# The Pillow user in the docker container is UID 1000
|
# The Pillow user in the docker container is UID 1001
|
||||||
sudo chown -R 1000 $GITHUB_WORKSPACE
|
sudo chown -R 1001 $GITHUB_WORKSPACE
|
||||||
docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
||||||
sudo chown -R runner $GITHUB_WORKSPACE
|
sudo chown -R runner $GITHUB_WORKSPACE
|
||||||
|
|
||||||
|
@ -92,11 +100,11 @@ jobs:
|
||||||
MATRIX_DOCKER: ${{ matrix.docker }}
|
MATRIX_DOCKER: ${{ matrix.docker }}
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
flags: GHA_Docker
|
flags: GHA_Docker
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
gcov: true
|
token: ${{ secrets.CODECOV_ORG_TOKEN }}
|
||||||
|
|
||||||
success:
|
success:
|
||||||
permissions:
|
permissions:
|
||||||
|
|
23
.github/workflows/test-msys2.yml
vendored
|
@ -2,13 +2,21 @@ name: Test MSYS2
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -18,6 +26,9 @@ concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
COVERAGE_CORE: sysmon
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
@ -42,7 +53,9 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Pillow
|
- name: Checkout Pillow
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up shell
|
- name: Set up shell
|
||||||
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
||||||
|
@ -66,11 +79,9 @@ jobs:
|
||||||
libtiff:p \
|
libtiff:p \
|
||||||
libwebp:p \
|
libwebp:p \
|
||||||
openjpeg2:p \
|
openjpeg2:p \
|
||||||
python3-cffi:p \
|
|
||||||
python3-numpy:p \
|
python3-numpy:p \
|
||||||
python3-olefile:p \
|
python3-olefile:p \
|
||||||
python3-pip:p \
|
python3-pip:p \
|
||||||
python3-setuptools:p \
|
|
||||||
python-pyqt6:p
|
python-pyqt6:p
|
||||||
|
|
||||||
python3 -m pip install pyroma pytest pytest-cov pytest-timeout
|
python3 -m pip install pyroma pytest pytest-cov pytest-timeout
|
||||||
|
@ -78,7 +89,7 @@ jobs:
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
||||||
- name: Build Pillow
|
- name: Build Pillow
|
||||||
run: SETUPTOOLS_USE_DISTUTILS="stdlib" .ci/build.sh
|
run: .ci/build.sh
|
||||||
|
|
||||||
- name: Test Pillow
|
- name: Test Pillow
|
||||||
run: .ci/test.sh
|
run: .ci/test.sh
|
||||||
|
@ -87,9 +98,9 @@ jobs:
|
||||||
run: .ci/after_success.sh
|
run: .ci/after_success.sh
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
file: ./coverage.xml
|
files: ./coverage.xml
|
||||||
flags: GHA_Windows
|
flags: GHA_Windows
|
||||||
name: MSYS2 ${{ matrix.msystem }}
|
name: MSYS2 ${{ matrix.msystem }}
|
||||||
|
|
||||||
|
|
12
.github/workflows/test-valgrind.yml
vendored
|
@ -1,9 +1,11 @@
|
||||||
name: Test Valgrind
|
name: Test Valgrind
|
||||||
|
|
||||||
# like the docker tests, but running valgrind only on *.c/*.h changes.
|
# like the Docker tests, but running valgrind only on *.c/*.h changes.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/test-valgrind.yml"
|
- ".github/workflows/test-valgrind.yml"
|
||||||
- "**.c"
|
- "**.c"
|
||||||
|
@ -37,7 +39,9 @@ jobs:
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
@ -48,7 +52,7 @@ jobs:
|
||||||
|
|
||||||
- name: Build and Run Valgrind
|
- name: Build and Run Valgrind
|
||||||
run: |
|
run: |
|
||||||
# The Pillow user in the docker container is UID 1000
|
# The Pillow user in the docker container is UID 1001
|
||||||
sudo chown -R 1000 $GITHUB_WORKSPACE
|
sudo chown -R 1001 $GITHUB_WORKSPACE
|
||||||
docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
||||||
sudo chown -R runner $GITHUB_WORKSPACE
|
sudo chown -R runner $GITHUB_WORKSPACE
|
||||||
|
|
104
.github/workflows/test-windows.yml
vendored
|
@ -2,13 +2,21 @@ name: Test Windows
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -18,13 +26,16 @@ concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
COVERAGE_CORE: sysmon
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
|
python-version: ["pypy3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
|
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
@ -32,42 +43,53 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Pillow
|
- name: Checkout Pillow
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Checkout cached dependencies
|
- name: Checkout cached dependencies
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
persist-credentials: false
|
||||||
repository: python-pillow/pillow-depends
|
repository: python-pillow/pillow-depends
|
||||||
path: winbuild\depends
|
path: winbuild\depends
|
||||||
|
|
||||||
- name: Checkout extra test images
|
- name: Checkout extra test images
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
persist-credentials: false
|
||||||
repository: python-pillow/test-images
|
repository: python-pillow/test-images
|
||||||
path: Tests\test-images
|
path: Tests\test-images
|
||||||
|
|
||||||
# sets env: pythonLocation
|
# sets env: pythonLocation
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
allow-prereleases: true
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: ".github/workflows/test-windows.yml"
|
cache-dependency-path: ".github/workflows/test-windows.yml"
|
||||||
|
|
||||||
- name: Print build system information
|
- name: Print build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
- name: python3 -m pip install setuptools wheel pytest pytest-cov pytest-timeout defusedxml
|
- name: Upgrade pip
|
||||||
run: python3 -m pip install setuptools wheel pytest pytest-cov pytest-timeout defusedxml
|
run: |
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
|
||||||
|
- name: Install CPython dependencies
|
||||||
|
if: "!contains(matrix.python-version, 'pypy')"
|
||||||
|
run: |
|
||||||
|
python3 -m pip install PyQt6
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
id: install
|
id: install
|
||||||
run: |
|
run: |
|
||||||
7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
choco install nasm --no-progress
|
||||||
echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH
|
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
choco install ghostscript --version=10.0.0.20230317
|
choco install ghostscript --version=10.4.0 --no-progress
|
||||||
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
|
echo "C:\Program Files\gs\gs10.04.0\bin" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
# Install extra test images
|
# Install extra test images
|
||||||
xcopy /S /Y Tests\test-images\* Tests\images
|
xcopy /S /Y Tests\test-images\* Tests\images
|
||||||
|
@ -80,7 +102,7 @@ jobs:
|
||||||
|
|
||||||
- name: Cache build
|
- name: Cache build
|
||||||
id: build-cache
|
id: build-cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: winbuild\build
|
path: winbuild\build
|
||||||
key:
|
key:
|
||||||
|
@ -89,7 +111,7 @@ jobs:
|
||||||
- name: Prepare build
|
- name: Prepare build
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
& python.exe winbuild\build_prepare.py -v --python $env:pythonLocation
|
& python.exe winbuild\build_prepare.py -v
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Build dependencies / libjpeg-turbo
|
- name: Build dependencies / libjpeg-turbo
|
||||||
|
@ -157,9 +179,8 @@ jobs:
|
||||||
|
|
||||||
- name: Build Pillow
|
- name: Build Pillow
|
||||||
run: |
|
run: |
|
||||||
$FLAGS=""
|
$FLAGS="-C raqm=vendor -C fribidi=vendor"
|
||||||
if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS="--disable-imagequant" }
|
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS .[tests]"
|
||||||
& winbuild\build\build_pillow.cmd $FLAGS install
|
|
||||||
& $env:pythonLocation\python.exe selftest.py --installed
|
& $env:pythonLocation\python.exe selftest.py --installed
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
|
@ -171,8 +192,8 @@ jobs:
|
||||||
|
|
||||||
- name: Test Pillow
|
- name: Test Pillow
|
||||||
run: |
|
run: |
|
||||||
path %GITHUB_WORKSPACE%\\winbuild\\build\\bin;%PATH%
|
path %GITHUB_WORKSPACE%\winbuild\build\bin;%PATH%
|
||||||
python.exe -m pytest -vx -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests
|
.ci\test.cmd
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
|
||||||
- name: Prepare to upload errors
|
- name: Prepare to upload errors
|
||||||
|
@ -182,7 +203,7 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: errors
|
name: errors
|
||||||
|
@ -194,51 +215,12 @@ jobs:
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
file: ./coverage.xml
|
files: ./coverage.xml
|
||||||
flags: GHA_Windows
|
flags: GHA_Windows
|
||||||
name: ${{ runner.os }} Python ${{ matrix.python-version }}
|
name: ${{ runner.os }} Python ${{ matrix.python-version }}
|
||||||
|
token: ${{ secrets.CODECOV_ORG_TOKEN }}
|
||||||
- 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%
|
|
||||||
winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel
|
|
||||||
shell: cmd
|
|
||||||
|
|
||||||
- name: Upload wheel
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
if: "github.event_name != 'pull_request'"
|
|
||||||
with:
|
|
||||||
name: ${{ steps.wheel.outputs.dist }}
|
|
||||||
path: dist\*.whl
|
|
||||||
|
|
||||||
- name: Upload fribidi.dll
|
|
||||||
if: "github.event_name != 'pull_request' && matrix.python-version == 3.11"
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: fribidi
|
|
||||||
path: fribidi\*
|
|
||||||
|
|
||||||
success:
|
success:
|
||||||
permissions:
|
permissions:
|
||||||
|
|
68
.github/workflows/test.yml
vendored
|
@ -2,13 +2,21 @@ name: Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -18,6 +26,10 @@ concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
COVERAGE_CORE: sysmon
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
|
@ -30,42 +42,64 @@ jobs:
|
||||||
]
|
]
|
||||||
python-version: [
|
python-version: [
|
||||||
"pypy3.10",
|
"pypy3.10",
|
||||||
"pypy3.9",
|
"3.13t",
|
||||||
"3.12-dev",
|
"3.13",
|
||||||
|
"3.12",
|
||||||
"3.11",
|
"3.11",
|
||||||
"3.10",
|
"3.10",
|
||||||
"3.9",
|
"3.9",
|
||||||
"3.8",
|
|
||||||
]
|
]
|
||||||
include:
|
include:
|
||||||
- python-version: "3.9"
|
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
||||||
PYTHONOPTIMIZE: 1
|
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
|
||||||
REVERSE: "--reverse"
|
# Free-threaded
|
||||||
- python-version: "3.8"
|
- { python-version: "3.13t", disable-gil: true }
|
||||||
PYTHONOPTIMIZE: 2
|
# M1 only available for 3.10+
|
||||||
|
- { os: "macos-13", python-version: "3.9" }
|
||||||
|
exclude:
|
||||||
|
- { os: "macos-latest", python-version: "3.9" }
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4
|
uses: Quansight-Labs/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
allow-prereleases: true
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: ".ci/*.sh"
|
cache-dependency-path: |
|
||||||
|
".ci/*.sh"
|
||||||
|
"pyproject.toml"
|
||||||
|
|
||||||
|
- name: Set PYTHON_GIL
|
||||||
|
if: "${{ matrix.disable-gil }}"
|
||||||
|
run: |
|
||||||
|
echo "PYTHON_GIL=0" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
|
- name: Cache libimagequant
|
||||||
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: cache-libimagequant
|
||||||
|
with:
|
||||||
|
path: ~/cache-libimagequant
|
||||||
|
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
|
||||||
|
|
||||||
- name: Install Linux dependencies
|
- name: Install Linux dependencies
|
||||||
if: startsWith(matrix.os, 'ubuntu')
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
run: |
|
run: |
|
||||||
.ci/install.sh
|
.ci/install.sh
|
||||||
env:
|
env:
|
||||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||||
|
|
||||||
- name: Install macOS dependencies
|
- name: Install macOS dependencies
|
||||||
if: startsWith(matrix.os, 'macOS')
|
if: startsWith(matrix.os, 'macOS')
|
||||||
|
@ -74,6 +108,10 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Register gcc problem matcher
|
||||||
|
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'"
|
||||||
|
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
.ci/build.sh
|
.ci/build.sh
|
||||||
|
@ -100,7 +138,7 @@ jobs:
|
||||||
mkdir -p Tests/errors
|
mkdir -p Tests/errors
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: errors
|
name: errors
|
||||||
|
@ -111,11 +149,11 @@ jobs:
|
||||||
.ci/after_success.sh
|
.ci/after_success.sh
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
|
flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }}
|
||||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||||
gcov: true
|
token: ${{ secrets.CODECOV_ORG_TOKEN }}
|
||||||
|
|
||||||
success:
|
success:
|
||||||
permissions:
|
permissions:
|
||||||
|
|
197
.github/workflows/wheels-dependencies.sh
vendored
Executable file
|
@ -0,0 +1,197 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Setup that needs to be done before multibuild utils are invoked
|
||||||
|
PROJECTDIR=$(pwd)
|
||||||
|
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||||
|
# Safety check - macOS builds require that CIBW_ARCHS is set, and that it
|
||||||
|
# only contains a single value (even though cibuildwheel allows multiple
|
||||||
|
# values in CIBW_ARCHS).
|
||||||
|
if [[ -z "$CIBW_ARCHS" ]]; then
|
||||||
|
echo "ERROR: Pillow macOS builds require CIBW_ARCHS be defined."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "$CIBW_ARCHS" == *" "* ]]; then
|
||||||
|
echo "ERROR: Pillow macOS builds only support a single architecture in CIBW_ARCHS."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build macOS dependencies in `build/darwin`
|
||||||
|
# Install them into `build/deps/darwin`
|
||||||
|
WORKDIR=$(pwd)/build/darwin
|
||||||
|
BUILD_PREFIX=$(pwd)/build/deps/darwin
|
||||||
|
else
|
||||||
|
# Build prefix will default to /usr/local
|
||||||
|
WORKDIR=$(pwd)/build
|
||||||
|
MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
|
||||||
|
MB_ML_VER=${AUDITWHEEL_POLICY:9}
|
||||||
|
fi
|
||||||
|
PLAT=$CIBW_ARCHS
|
||||||
|
|
||||||
|
# Define custom utilities
|
||||||
|
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=10.0.1
|
||||||
|
LIBPNG_VERSION=1.6.44
|
||||||
|
JPEGTURBO_VERSION=3.0.4
|
||||||
|
OPENJPEG_VERSION=2.5.2
|
||||||
|
XZ_VERSION=5.6.3
|
||||||
|
TIFF_VERSION=4.6.0
|
||||||
|
LCMS2_VERSION=2.16
|
||||||
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
|
GIFLIB_VERSION=5.2.2
|
||||||
|
else
|
||||||
|
GIFLIB_VERSION=5.2.1
|
||||||
|
fi
|
||||||
|
if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then
|
||||||
|
ZLIB_VERSION=1.3.1
|
||||||
|
else
|
||||||
|
ZLIB_VERSION=1.2.8
|
||||||
|
fi
|
||||||
|
LIBWEBP_VERSION=1.4.0
|
||||||
|
BZIP2_VERSION=1.0.8
|
||||||
|
LIBXCB_VERSION=1.17.0
|
||||||
|
BROTLI_VERSION=1.1.0
|
||||||
|
|
||||||
|
function build_pkg_config {
|
||||||
|
if [ -e pkg-config-stamp ]; then return; fi
|
||||||
|
# This essentially duplicates the Homebrew recipe
|
||||||
|
ORIGINAL_CFLAGS=$CFLAGS
|
||||||
|
CFLAGS="$CFLAGS -Wno-int-conversion"
|
||||||
|
build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
|
||||||
|
--disable-debug --disable-host-tool --with-internal-glib \
|
||||||
|
--with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \
|
||||||
|
--with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include
|
||||||
|
CFLAGS=$ORIGINAL_CFLAGS
|
||||||
|
export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
|
||||||
|
touch pkg-config-stamp
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_brotli {
|
||||||
|
if [ -e brotli-stamp ]; then return; fi
|
||||||
|
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
|
||||||
|
(cd $out_dir \
|
||||||
|
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
|
||||||
|
&& make install)
|
||||||
|
touch brotli-stamp
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_harfbuzz {
|
||||||
|
if [ -e harfbuzz-stamp ]; then return; fi
|
||||||
|
python3 -m pip install meson ninja
|
||||||
|
|
||||||
|
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
|
||||||
|
(cd $out_dir \
|
||||||
|
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=release -Dfreetype=enabled -Dglib=disabled)
|
||||||
|
(cd $out_dir/build \
|
||||||
|
&& meson install)
|
||||||
|
touch harfbuzz-stamp
|
||||||
|
}
|
||||||
|
|
||||||
|
function build {
|
||||||
|
build_xz
|
||||||
|
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
|
||||||
|
yum remove -y zlib-devel
|
||||||
|
fi
|
||||||
|
build_new_zlib
|
||||||
|
|
||||||
|
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
|
||||||
|
if [ -n "$IS_MACOS" ]; then
|
||||||
|
build_simple xorgproto 2024.1 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
|
||||||
|
else
|
||||||
|
sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
|
||||||
|
fi
|
||||||
|
build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
|
||||||
|
|
||||||
|
build_libjpeg_turbo
|
||||||
|
if [ -n "$IS_MACOS" ]; then
|
||||||
|
# Custom tiff build to include jpeg; by default, configure won't include
|
||||||
|
# headers/libs in the custom macOS prefix. Explicitly disable webp,
|
||||||
|
# libdeflate and zstd, because on x86_64 macs, it will pick up the
|
||||||
|
# Homebrew versions of those libraries from /usr/local.
|
||||||
|
build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \
|
||||||
|
--with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \
|
||||||
|
--disable-webp --disable-libdeflate --disable-zstd
|
||||||
|
else
|
||||||
|
build_tiff
|
||||||
|
fi
|
||||||
|
|
||||||
|
build_libpng
|
||||||
|
build_lcms2
|
||||||
|
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
|
||||||
|
|
||||||
|
build_harfbuzz
|
||||||
|
}
|
||||||
|
|
||||||
|
# Perform all dependency builds in the build subfolder.
|
||||||
|
mkdir -p $WORKDIR
|
||||||
|
pushd $WORKDIR > /dev/null
|
||||||
|
|
||||||
|
# Any stuff that you need to do before you start building the wheels
|
||||||
|
# Runs in the root directory of this repository.
|
||||||
|
if [[ ! -d $WORKDIR/pillow-depends-main ]]; then
|
||||||
|
if [[ ! -f $PROJECTDIR/pillow-depends-main.zip ]]; then
|
||||||
|
echo "Download pillow dependency sources..."
|
||||||
|
curl -fSL -o $PROJECTDIR/pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
|
||||||
|
fi
|
||||||
|
echo "Unpacking pillow dependency sources..."
|
||||||
|
untar $PROJECTDIR/pillow-depends-main.zip
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
|
# Homebrew (or similar packaging environments) install can contain some of
|
||||||
|
# the libraries that we're going to build. However, they may be compiled
|
||||||
|
# with a MACOSX_DEPLOYMENT_TARGET that doesn't match what we want to use,
|
||||||
|
# and they may bring in other dependencies that we don't want. The same will
|
||||||
|
# be true of any other locations on the path. To avoid conflicts, strip the
|
||||||
|
# path down to the bare minimum (which, on macOS, won't include any
|
||||||
|
# development dependencies).
|
||||||
|
export PATH="$BUILD_PREFIX/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
|
||||||
|
export CMAKE_PREFIX_PATH=$BUILD_PREFIX
|
||||||
|
|
||||||
|
# Ensure the basic structure of the build prefix directory exists.
|
||||||
|
mkdir -p "$BUILD_PREFIX/bin"
|
||||||
|
mkdir -p "$BUILD_PREFIX/lib"
|
||||||
|
|
||||||
|
# Ensure pkg-config is available
|
||||||
|
build_pkg_config
|
||||||
|
# Ensure cmake is available
|
||||||
|
python3 -m pip install cmake
|
||||||
|
fi
|
||||||
|
|
||||||
|
wrap_wheel_builder build
|
||||||
|
|
||||||
|
# Return to the project root to finish the build
|
||||||
|
popd > /dev/null
|
||||||
|
|
||||||
|
# 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
|
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 }
|
39
.github/workflows/wheels-test.sh
vendored
Executable file
|
@ -0,0 +1,39 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Ensure fribidi is installed by the system.
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# If Homebrew is on the path during the build, it may leak into the wheels.
|
||||||
|
# However, we *do* need Homebrew to provide a copy of fribidi for
|
||||||
|
# testing purposes so that we can verify the fribidi shim works as expected.
|
||||||
|
if [[ "$(uname -m)" == "x86_64" ]]; then
|
||||||
|
HOMEBREW_PREFIX=/usr/local
|
||||||
|
else
|
||||||
|
HOMEBREW_PREFIX=/opt/homebrew
|
||||||
|
fi
|
||||||
|
$HOMEBREW_PREFIX/bin/brew install fribidi
|
||||||
|
|
||||||
|
# Add the lib folder for fribidi so that the vendored library can be found.
|
||||||
|
# Don't use $HOMEWBREW_PREFIX/lib directly - use the lib folder where the
|
||||||
|
# installed copy of fribidi is cellared. This ensures we don't pick up the
|
||||||
|
# Homebrew version of any other library that we're dependent on (most notably,
|
||||||
|
# freetype).
|
||||||
|
export DYLD_LIBRARY_PATH=$(dirname $(realpath $HOMEBREW_PREFIX/lib/libfribidi.dylib))
|
||||||
|
elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
|
||||||
|
apk add curl fribidi
|
||||||
|
else
|
||||||
|
yum install -y fribidi
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 -m pip install numpy
|
||||||
|
|
||||||
|
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
|
314
.github/workflows/wheels.yml
vendored
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
name: Wheels
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# ┌───────────── minute (0 - 59)
|
||||||
|
# │ ┌───────────── hour (0 - 23)
|
||||||
|
# │ │ ┌───────────── day of the month (1 - 31)
|
||||||
|
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||||
|
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||||
|
# │ │ │ │ │
|
||||||
|
- cron: "42 1 * * 0,3"
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- ".ci/requirements-cibw.txt"
|
||||||
|
- ".github/workflows/wheel*"
|
||||||
|
- "setup.py"
|
||||||
|
- "wheels/*"
|
||||||
|
- "winbuild/build_prepare.py"
|
||||||
|
- "winbuild/fribidi.cmake"
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".ci/requirements-cibw.txt"
|
||||||
|
- ".github/workflows/wheel*"
|
||||||
|
- "setup.py"
|
||||||
|
- "wheels/*"
|
||||||
|
- "winbuild/build_prepare.py"
|
||||||
|
- "winbuild/fribidi.cmake"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-1-QEMU-emulated-wheels:
|
||||||
|
if: github.event_name != 'schedule'
|
||||||
|
name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version:
|
||||||
|
- pp310
|
||||||
|
- cp3{9,10,11}
|
||||||
|
- cp3{12,13}
|
||||||
|
spec:
|
||||||
|
- manylinux2014
|
||||||
|
- manylinux_2_28
|
||||||
|
- musllinux
|
||||||
|
exclude:
|
||||||
|
- { python-version: pp310, spec: musllinux }
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
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' }}*"
|
||||||
|
CIBW_PRERELEASE_PYTHONS: True
|
||||||
|
# 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:
|
||||||
|
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
|
||||||
|
name: ${{ matrix.name }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: "macOS 10.10 x86_64"
|
||||||
|
os: macos-13
|
||||||
|
cibw_arch: x86_64
|
||||||
|
build: "cp3{9,10,11}*"
|
||||||
|
macosx_deployment_target: "10.10"
|
||||||
|
- name: "macOS 10.13 x86_64"
|
||||||
|
os: macos-13
|
||||||
|
cibw_arch: x86_64
|
||||||
|
build: "cp3{12,13}*"
|
||||||
|
macosx_deployment_target: "10.13"
|
||||||
|
- name: "macOS 10.15 x86_64"
|
||||||
|
os: macos-13
|
||||||
|
cibw_arch: x86_64
|
||||||
|
build: "pp310*"
|
||||||
|
macosx_deployment_target: "10.15"
|
||||||
|
- 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:
|
||||||
|
persist-credentials: false
|
||||||
|
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_FREE_THREADED_SUPPORT: True
|
||||||
|
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
|
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
|
CIBW_PRERELEASE_PYTHONS: True
|
||||||
|
CIBW_SKIP: pp39-*
|
||||||
|
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist-${{ matrix.os }}${{ matrix.macosx_deployment_target && format('-{0}', matrix.macosx_deployment_target) }}-${{ matrix.cibw_arch }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }}
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
|
windows:
|
||||||
|
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
|
||||||
|
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
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Checkout extra test images
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
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_FREE_THREADED_SUPPORT: True
|
||||||
|
CIBW_PRERELEASE_PYTHONS: True
|
||||||
|
CIBW_SKIP: pp39-*
|
||||||
|
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:
|
||||||
|
if: github.event_name != 'schedule'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
scientific-python-nightly-wheels-publish:
|
||||||
|
if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||||
|
needs: [build-2-native-wheels, windows]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Upload wheels to scientific-python-nightly-wheels
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: dist-*
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Upload wheels to scientific-python-nightly-wheels
|
||||||
|
uses: scientific-python/upload-nightly-action@82396a2ed4269ba06c6b2988bb4fd568ef3c3d6b # 0.6.1
|
||||||
|
with:
|
||||||
|
artifacts_path: dist
|
||||||
|
anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}
|
||||||
|
|
||||||
|
pypi-publish:
|
||||||
|
if: github.repository_owner == 'python-pillow' && 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
|
||||||
|
with:
|
||||||
|
attestations: true
|
5
.gitignore
vendored
|
@ -19,6 +19,7 @@ lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
|
wheelhouse/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
|
@ -90,5 +91,9 @@ Tests/images/msp
|
||||||
Tests/images/picins
|
Tests/images/picins
|
||||||
Tests/images/sunraster
|
Tests/images/sunraster
|
||||||
|
|
||||||
|
# Test and dependency downloads
|
||||||
|
pillow-depends-main.zip
|
||||||
|
pillow-test-images.zip
|
||||||
|
|
||||||
# pyinstaller
|
# pyinstaller
|
||||||
*.spec
|
*.spec
|
||||||
|
|
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "multibuild"]
|
||||||
|
path = wheels/multibuild
|
||||||
|
url = https://github.com/multi-build/multibuild.git
|
|
@ -1,62 +1,86 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: 23.3.0
|
rev: v0.7.2
|
||||||
|
hooks:
|
||||||
|
- id: ruff
|
||||||
|
args: [--exit-non-zero-on-fix]
|
||||||
|
|
||||||
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
|
rev: 24.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--target-version=py38]
|
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/isort
|
|
||||||
rev: 5.12.0
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/bandit
|
- repo: https://github.com/PyCQA/bandit
|
||||||
rev: 1.7.5
|
rev: 1.7.10
|
||||||
hooks:
|
hooks:
|
||||||
- id: bandit
|
- id: bandit
|
||||||
args: [--severity-level=high]
|
args: [--severity-level=high]
|
||||||
files: ^src/
|
files: ^src/
|
||||||
|
|
||||||
- repo: https://github.com/asottile/yesqa
|
|
||||||
rev: v1.4.0
|
|
||||||
hooks:
|
|
||||||
- id: yesqa
|
|
||||||
|
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||||
rev: v1.5.1
|
rev: v1.5.5
|
||||||
hooks:
|
hooks:
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: 6.0.0
|
rev: v19.1.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: clang-format
|
||||||
additional_dependencies:
|
types: [c]
|
||||||
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat]
|
exclude: ^src/thirdparty/
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||||
rev: v1.10.0
|
rev: v1.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: python-check-blanket-noqa
|
|
||||||
- id: rst-backticks
|
- id: rst-backticks
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v5.0.0
|
||||||
hooks:
|
hooks:
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
|
- id: check-shebang-scripts-are-executable
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-json
|
- id: check-json
|
||||||
|
- id: check-toml
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
exclude: ^Tests/images/
|
||||||
|
- id: trailing-whitespace
|
||||||
|
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||||
|
|
||||||
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||||
|
rev: 0.29.4
|
||||||
|
hooks:
|
||||||
|
- id: check-github-workflows
|
||||||
|
- id: check-readthedocs
|
||||||
|
- id: check-renovate
|
||||||
|
|
||||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||||
rev: v0.6.7
|
rev: v1.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: sphinx-lint
|
- id: sphinx-lint
|
||||||
|
|
||||||
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
|
rev: v2.5.0
|
||||||
|
hooks:
|
||||||
|
- id: pyproject-fmt
|
||||||
|
|
||||||
|
- repo: https://github.com/abravalheri/validate-pyproject
|
||||||
|
rev: v0.22
|
||||||
|
hooks:
|
||||||
|
- id: validate-pyproject
|
||||||
|
additional_dependencies: [trove-classifiers>=2024.10.12]
|
||||||
|
|
||||||
- repo: https://github.com/tox-dev/tox-ini-fmt
|
- repo: https://github.com/tox-dev/tox-ini-fmt
|
||||||
rev: 1.3.0
|
rev: 1.4.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: tox-ini-fmt
|
- id: tox-ini-fmt
|
||||||
|
|
||||||
|
- repo: meta
|
||||||
|
hooks:
|
||||||
|
- id: check-hooks-apply
|
||||||
|
- id: check-useless-excludes
|
||||||
|
|
||||||
ci:
|
ci:
|
||||||
autoupdate_schedule: monthly
|
autoupdate_schedule: monthly
|
||||||
|
|
|
@ -3,9 +3,13 @@ version: 2
|
||||||
formats: [pdf]
|
formats: [pdf]
|
||||||
|
|
||||||
build:
|
build:
|
||||||
os: ubuntu-22.04
|
os: ubuntu-lts-latest
|
||||||
tools:
|
tools:
|
||||||
python: "3.11"
|
python: "3"
|
||||||
|
jobs:
|
||||||
|
post_checkout:
|
||||||
|
- git remote add upstream https://github.com/python-pillow/Pillow.git # For forks
|
||||||
|
- git fetch upstream --tags
|
||||||
|
|
||||||
python:
|
python:
|
||||||
install:
|
install:
|
||||||
|
|
727
CHANGES.rst
|
@ -2,12 +2,688 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
10.0.0 (unreleased)
|
11.1.0 and newer
|
||||||
|
----------------
|
||||||
|
|
||||||
|
See GitHub Releases:
|
||||||
|
|
||||||
|
- https://github.com/python-pillow/Pillow/releases
|
||||||
|
|
||||||
|
11.0.0 (2024-10-15)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
- Removed support for 32-bit #7228
|
- Update licence to MIT-CMU #8460
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Conditionally define ImageCms type hint to avoid requiring core #8197
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support writing LONG8 offsets in AppendingTiffWriter #8417
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use ImageFile.MAXBLOCK when saving TIFF images #8461
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not close provided file handles with libtiff when saving #8458
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support ImageFilter.BuiltinFilter for I;16* images #8438
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use ImagingCore.ptr instead of ImagingCore.id #8341
|
||||||
|
[homm, radarhere, hugovk]
|
||||||
|
|
||||||
|
- Updated EPS mode when opening images without transparency #8281
|
||||||
|
[Yay295, radarhere]
|
||||||
|
|
||||||
|
- Use transparency when combining P frames from APNGs #8443
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support all resampling filters when resizing I;16* images #8422
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Free memory on early return #8413
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Cast int before potentially exceeding INT_MAX #8402
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Check image value before use #8400
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved copying imagequant libraries #8420
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use Capsule for WebP saving #8386
|
||||||
|
[homm, radarhere]
|
||||||
|
|
||||||
|
- Fixed writing multiple StripOffsets to TIFF #8317
|
||||||
|
[Yay295, radarhere]
|
||||||
|
|
||||||
|
- Fix dereference before checking for NULL in ImagingTransformAffine #8398
|
||||||
|
[PavlNekrasov]
|
||||||
|
|
||||||
|
- Use transposed size after opening for TIFF images #8390
|
||||||
|
[radarhere, homm]
|
||||||
|
|
||||||
|
- Improve ImageFont error messages #8338
|
||||||
|
[yngvem, radarhere, hugovk]
|
||||||
|
|
||||||
|
- Mention MAX_TEXT_CHUNK limit in PNG error message #8391
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Cast Dib handle to int #8385
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Accept float stroke widths #8369
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Deprecate ICNS (width, height, scale) sizes in favour of load(scale) #8352
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved handling of RGBA palettes when saving GIF images #8366
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Deprecate isImageType #8364
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support converting more modes to LAB by converting to RGBA first #8358
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Deprecate support for FreeType 2.9.0 #8356
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Removed unused TiffImagePlugin IFD_LEGACY_API #8355
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Handle duplicate EXIF header #8350
|
||||||
|
[zakajd, radarhere]
|
||||||
|
|
||||||
|
- Return early from BoxBlur if either width or height is zero #8347
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Check text is either string or bytes #8308
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added writing XMP bytes to JPEG #8286
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support JPEG2000 RGBA palettes #8256
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Expand C image to match GIF frame image size #8237
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow saving I;16 images as PPM #8231
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- When IFD is missing, connect get_ifd() dictionary to Exif #8230
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Skip truncated ICO mask if LOAD_TRUNCATED_IMAGES is enabled #8180
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Treat unknown JPEG2000 colorspace as unspecified #8343
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Updated error message when saving WebP with invalid width or height #8322
|
||||||
[radarhere, hugovk]
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Remove warning if NumPy failed to raise an error during conversion #8326
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- If left and right sides meet in ImageDraw.rounded_rectangle(), do not draw rectangle to fill gap #8304
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Remove WebP support without anim, mux/demux, and with buggy alpha #8213
|
||||||
|
[homm, radarhere]
|
||||||
|
|
||||||
|
- Add missing TIFF CMYK;16B reader #8298
|
||||||
|
[homm]
|
||||||
|
|
||||||
|
- Remove all WITH_* flags from _imaging.c and other flags #8211
|
||||||
|
[homm]
|
||||||
|
|
||||||
|
- Improve ImageDraw2 shape methods #8265
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Lock around usages of imaging memory arenas #8238
|
||||||
|
[lysnikolaou]
|
||||||
|
|
||||||
|
- Deprecate JpegImageFile huffman_ac and huffman_dc #8274
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Deprecate ImageMath lambda_eval and unsafe_eval options argument #8242
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Changed ContainerIO to subclass IO #8240
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Move away from APIs that use borrowed references under the free-threaded build #8216
|
||||||
|
[hugovk, lysnikolaou]
|
||||||
|
|
||||||
|
- Allow size argument to resize() to be a NumPy array #8201
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Drop support for Python 3.8 #8183
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Add support for Python 3.13 #8181
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Fix incompatibility with NumPy 1.20 #8187
|
||||||
|
[neutrinoceros, radarhere]
|
||||||
|
|
||||||
|
- Remove PSFile, PyAccess and USE_CFFI_ACCESS #8182
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
10.4.0 (2024-07-01)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Raise FileNotFoundError if show_file() path does not exist #8178
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved reading 16-bit TGA images with colour #7965
|
||||||
|
[Yay295, radarhere]
|
||||||
|
|
||||||
|
- Deprecate non-image ImageCms modes #8031
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed processing multiple JPEG EXIF markers #8127
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not preserve EXIFIFD tag by default when saving TIFF images #8110
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added ImageFont.load_default_imagefont() #8086
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added Image.WARN_POSSIBLE_FORMATS #8063
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Remove zero-byte end padding when parsing any XMP data #8171
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not detect Ultra HDR images as MPO #8056
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Raise SyntaxError specific to JP2 #8146
|
||||||
|
[Yay295, radarhere]
|
||||||
|
|
||||||
|
- Do not use first frame duration for other frames when saving APNG images #8104
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Consider I;16 pixel size when using a 1 mode mask #8112
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- When saving multiple PNG frames, convert to mode rather than raw mode #8087
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added byte support to FreeTypeFont #8141
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow float center for rotate operations #8114
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not read layers immediately when opening PSD images #8039
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Restore original thread state #8065
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Read IM and TIFF images as RGB, rather than RGBX #7997
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Only preserve TIFF IPTC_NAA_CHUNK tag if type is BYTE or UNDEFINED #7948
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Clarify ImageDraw2 error message when size is missing #8165
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support unpacking more rawmodes to RGBA palettes #7966
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Removed support for Qt 5 #8159
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improve ``ImageFont.freetype`` support for XDG directories on Linux #8135
|
||||||
|
[mamg22, radarhere]
|
||||||
|
|
||||||
|
- Improved consistency of XMP handling #8069
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use pkg-config to help find libwebp and raqm #8142
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Accept 't' suffix for libtiff version #8126, #8129
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Deprecate ImageDraw.getdraw hints parameter #8124
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Added ImageDraw circle() #8085
|
||||||
|
[void4, hugovk, radarhere]
|
||||||
|
|
||||||
|
- Add mypy target to Makefile #8077
|
||||||
|
[Yay295]
|
||||||
|
|
||||||
|
- Added more modes to Image.MODES #7984
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Deprecate BGR;15, BGR;16 and BGR;24 modes #7978
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Fix ImagingAccess for I;16N on big-endian #7921
|
||||||
|
[Yay295, radarhere]
|
||||||
|
|
||||||
|
- Support reading P mode TIFF images with padding #7996
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Deprecate support for libtiff < 4 #7998
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Corrected ImageShow UnixViewer command #7987
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use functools.cached_property in ImageStat #7952
|
||||||
|
[nulano, hugovk, radarhere]
|
||||||
|
|
||||||
|
- Add support for reading BITMAPV2INFOHEADER and BITMAPV3INFOHEADER #7956
|
||||||
|
[Cirras, radarhere]
|
||||||
|
|
||||||
|
- Support reading CMYK JPEG2000 images #7947
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
10.3.0 (2024-04-01)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- CVE-2024-28219: Use ``strncpy`` to avoid buffer overflow #7928
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Deprecate ``eval()``, replacing it with ``lambda_eval()`` and ``unsafe_eval()`` #7927
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Raise ``ValueError`` if seeking to greater than offset-sized integer in TIFF #7883
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Add ``--report`` argument to ``__main__.py`` to omit supported formats #7818
|
||||||
|
[nulano, radarhere, hugovk]
|
||||||
|
|
||||||
|
- Added RGB to I;16, I;16L, I;16B and I;16N conversion #7918, #7920
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix editable installation with custom build backend and configuration options #7658
|
||||||
|
[nulano, radarhere]
|
||||||
|
|
||||||
|
- Fix putdata() for I;16N on big-endian #7209
|
||||||
|
[Yay295, hugovk, radarhere]
|
||||||
|
|
||||||
|
- Determine MPO size from markers, not EXIF data #7884
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved conversion from RGB to RGBa, LA and La #7888
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support FITS images with GZIP_1 compression #7894
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use I;16 mode for 9-bit JPEG 2000 images #7900
|
||||||
|
[scaramallion, radarhere]
|
||||||
|
|
||||||
|
- Raise ValueError if kmeans is negative #7891
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Remove TIFF tag OSUBFILETYPE when saving using libtiff #7893
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Raise ValueError for negative values when loading P1-P3 PPM images #7882
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added reading of JPEG2000 palettes #7870
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added alpha_quality argument when saving WebP images #7872
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed joined corners for ImageDraw rounded_rectangle() non-integer dimensions #7881
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Stop reading EPS image at EOF marker #7753
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- PSD layer co-ordinates may be negative #7706
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use subprocess with CREATE_NO_WINDOW flag in ImageShow WindowsViewer #7791
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- When saving GIF frame that restores to background color, do not fill identical pixels #7788
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed reading PNG iCCP compression method #7823
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow writing IFDRational to UNDEFINED tag #7840
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix logged tag name when loading Exif data #7842
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use maximum frame size in IHDR chunk when saving APNG images #7821
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Prevent opening P TGA images without a palette #7797
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use palette when loading ICO images #7798
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use consistent arguments for load_read and load_seek #7713
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Turn off nullability warnings for macOS SDK #7827
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix shift-sign issue in Convert.c #7838
|
||||||
|
[r-barnes, radarhere]
|
||||||
|
|
||||||
|
- Open 16-bit grayscale PNGs as I;16 #7849
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Handle truncated chunks at the end of PNG images #7709
|
||||||
|
[lajiyuan, radarhere]
|
||||||
|
|
||||||
|
- Match mask size to pasted image size in GifImagePlugin #7779
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Release GIL while calling ``WebPAnimDecoderGetNext`` #7782
|
||||||
|
[evanmiller, radarhere]
|
||||||
|
|
||||||
|
- Fixed reading FLI/FLC images with a prefix chunk #7804
|
||||||
|
[twolife]
|
||||||
|
|
||||||
|
- Update wl-paste handling and return None for some errors in grabclipboard() on Linux #7745
|
||||||
|
[nik012003, radarhere]
|
||||||
|
|
||||||
|
- Remove execute bit from ``setup.py`` #7760
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Added TrueType default font to allow for different sizes #7354
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed invalid argument warning #7442
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added ImageOps cover method #7412
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Catch struct.error from truncated EXIF when reading JPEG DPI #7458
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Consider default image when selecting mode for PNG save_all #7437
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support BGR;15, BGR;16 and BGR;24 access, unpacking and putdata #7303
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added CMYK to RGB unpacker #7310
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved flexibility of XMP parsing #7274
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support reading 8-bit YCbCr TIFF images #7415
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow saving I;16B images as PNG #7302
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Corrected drawing I;16 points and writing I;16 text #7257
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Set blue channel to 128 for BC5S #7413
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Increase flexibility when reading IPTC fields #7319
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Set C palette to be empty by default #7289
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added gs_binary to control Ghostscript use on all platforms #7392
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Read bounding box information from the trailer of EPS files if specified #7382
|
||||||
|
[nopperl, radarhere]
|
||||||
|
|
||||||
|
- Added reading 8-bit color DDS images #7426
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added has_transparency_data #7420
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Fixed bug when reading BC5S DDS images #7401
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Prevent TIFF orientation from being applied more than once #7383
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use previous pixel alpha for QOI_OP_RGB #7357
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added BC5U reading #7358
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow getpixel() to accept a list #7355
|
||||||
|
[radarhere, homm]
|
||||||
|
|
||||||
|
- Allow GaussianBlur and BoxBlur to accept a sequence of x and y radii #7336
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Expand JPEG buffer size when saving optimized or progressive #7345
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added session type check for Linux in ImageGrab.grabclipboard() #7332
|
||||||
|
[TheNooB2706, radarhere, hugovk]
|
||||||
|
|
||||||
|
- Allow "loop=None" when saving GIF images #7329
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed transparency when saving P mode images to PDF #7323
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added saving LA images as PDFs #7299
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Set SMaskInData to 1 for PDFs with alpha #7316, #7317
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Changed Image mode property to be read-only by default #7307
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Silence exceptions in _repr_jpeg_ and _repr_png_ #7266
|
||||||
|
[mtreinish, radarhere]
|
||||||
|
|
||||||
|
- Do not use transparency when saving GIF if it has been removed when normalizing mode #7284
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix missing symbols when libtiff depends on libjpeg #7270
|
||||||
|
[heitbaum]
|
||||||
|
|
||||||
|
10.0.1 (2023-09-15)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Updated libwebp to 1.3.2 #7395
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Updated zlib to 1.3 #7344
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
10.0.0 (2023-07-01)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Fixed deallocating mask images #7246
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added ImageFont.MAX_STRING_LENGTH #7244
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Fix Windows build with pyproject.toml #7230
|
||||||
|
[hugovk, nulano, radarhere]
|
||||||
|
|
||||||
|
- Do not close provided file handles with libtiff #7199
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Convert to HSV if mode is HSV in getcolor() #7226
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added alpha_only argument to getbbox() #7123
|
||||||
|
[radarhere. hugovk]
|
||||||
|
|
||||||
|
- Prioritise speed in _repr_png_ #7242
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not use CFFI access by default on PyPy #7236
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Limit size even if one dimension is zero in decompression bomb check #7235
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Use --config-settings instead of deprecated --global-option #7171
|
- Use --config-settings instead of deprecated --global-option #7171
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
@ -2053,7 +2729,7 @@ Changelog (Pillow)
|
||||||
- Cache EXIF information #3498
|
- Cache EXIF information #3498
|
||||||
[Glandos]
|
[Glandos]
|
||||||
|
|
||||||
- Added transparency for all PNG greyscale modes #3744
|
- Added transparency for all PNG grayscale modes #3744
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Fix deprecation warnings in Python 3.8 #3749
|
- Fix deprecation warnings in Python 3.8 #3749
|
||||||
|
@ -3962,7 +4638,7 @@ Changelog (Pillow)
|
||||||
- Documentation changes, URL update, transpose, release checklist
|
- Documentation changes, URL update, transpose, release checklist
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Fixed saving to nonexistant files specified by pathlib.Path objects #1748 (fixes #1747)
|
- Fixed saving to nonexistent files specified by pathlib.Path objects #1748 (fixes #1747)
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Round Image.crop arguments to the nearest integer #1745 (fixes #1744)
|
- Round Image.crop arguments to the nearest integer #1745 (fixes #1744)
|
||||||
|
@ -4555,7 +5231,7 @@ Changelog (Pillow)
|
||||||
- Fix Bicubic interpolation #970
|
- Fix Bicubic interpolation #970
|
||||||
[homm]
|
[homm]
|
||||||
|
|
||||||
- Support for 4-bit greyscale TIFF images #980
|
- Support for 4-bit grayscale TIFF images #980
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
- Updated manifest #957
|
- Updated manifest #957
|
||||||
|
@ -5705,8 +6381,8 @@ http://svn.effbot.org/public/pil/
|
||||||
a polyline, independent of line angle.
|
a polyline, independent of line angle.
|
||||||
|
|
||||||
- Fixed bearing calculation and clipping in the ImageFont truetype
|
- Fixed bearing calculation and clipping in the ImageFont truetype
|
||||||
renderer; this could lead to clipped text, or crashes in the low-
|
renderer; this could lead to clipped text, or crashes in the low-level
|
||||||
level _imagingft module. (based on input from Adam Twardoch and
|
_imagingft module. (based on input from Adam Twardoch and
|
||||||
others).
|
others).
|
||||||
|
|
||||||
- Added ImageQt wrapper module, for converting PIL Image objects to
|
- Added ImageQt wrapper module, for converting PIL Image objects to
|
||||||
|
@ -5787,8 +6463,7 @@ http://svn.effbot.org/public/pil/
|
||||||
1.1.5c2 and 1.1.5 final
|
1.1.5c2 and 1.1.5 final
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
- Added experimental PERSPECTIVE transform method (from Jeff Breiden-
|
- Added experimental PERSPECTIVE transform method (from Jeff Breidenbach).
|
||||||
bach).
|
|
||||||
|
|
||||||
1.1.5c1
|
1.1.5c1
|
||||||
-------
|
-------
|
||||||
|
@ -5860,8 +6535,8 @@ http://svn.effbot.org/public/pil/
|
||||||
|
|
||||||
- Fixed BILINEAR/BICUBIC/ANTIALIAS filtering for mode "LA".
|
- Fixed BILINEAR/BICUBIC/ANTIALIAS filtering for mode "LA".
|
||||||
|
|
||||||
- Added "getcolors()" method. This is similar to the existing histo-
|
- Added "getcolors()" method. This is similar to the existing histogram
|
||||||
gram method, but looks at color values instead of individual layers,
|
method, but looks at color values instead of individual layers,
|
||||||
and returns an unsorted list of (count, color) tuples.
|
and returns an unsorted list of (count, color) tuples.
|
||||||
|
|
||||||
By default, the method returns None if finds more than 256 colors.
|
By default, the method returns None if finds more than 256 colors.
|
||||||
|
@ -6077,8 +6752,8 @@ http://svn.effbot.org/public/pil/
|
||||||
|
|
||||||
- Added limited support for "bitfield compression" in BMP files
|
- Added limited support for "bitfield compression" in BMP files
|
||||||
and DIB buffers, for 15-bit, 16-bit, and 32-bit images. This
|
and DIB buffers, for 15-bit, 16-bit, and 32-bit images. This
|
||||||
also fixes a problem with ImageGrab module when copying screen-
|
also fixes a problem with ImageGrab module when copying screendumps
|
||||||
dumps from the clipboard on 15/16/32-bit displays.
|
from the clipboard on 15/16/32-bit displays.
|
||||||
|
|
||||||
- Added experimental WAL (Quake 2 textures) loader. To use this
|
- Added experimental WAL (Quake 2 textures) loader. To use this
|
||||||
loader, import WalImageFile and call the "open" method in that
|
loader, import WalImageFile and call the "open" method in that
|
||||||
|
@ -6189,8 +6864,8 @@ http://svn.effbot.org/public/pil/
|
||||||
1.1.3 final
|
1.1.3 final
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- Made setup.py look for old versions of zlib. For some back-
|
- Made setup.py look for old versions of zlib. For some background,
|
||||||
ground, see: https://zlib.net/advisory-2002-03-11.txt
|
see: https://zlib.net/advisory-2002-03-11.txt
|
||||||
|
|
||||||
1.1.3c2
|
1.1.3c2
|
||||||
-------
|
-------
|
||||||
|
@ -6381,8 +7056,8 @@ http://svn.effbot.org/public/pil/
|
||||||
supports all major PIL image modes (including F and I).
|
supports all major PIL image modes (including F and I).
|
||||||
|
|
||||||
- The ImageFile module now includes a Parser class, which can
|
- The ImageFile module now includes a Parser class, which can
|
||||||
be used to incrementally decode an image file (while down-
|
be used to incrementally decode an image file (while downloading
|
||||||
loading it from the net, for example). See the handbook for
|
it from the net, for example). See the handbook for
|
||||||
details.
|
details.
|
||||||
|
|
||||||
- "show" now converts non-standard modes to "L" or "RGB" (as
|
- "show" now converts non-standard modes to "L" or "RGB" (as
|
||||||
|
@ -6520,8 +7195,8 @@ http://svn.effbot.org/public/pil/
|
||||||
|
|
||||||
- The Image "transform" method now supports Image.QUAD transforms.
|
- The Image "transform" method now supports Image.QUAD transforms.
|
||||||
The data argument is an 8-tuple giving the upper left, lower
|
The data argument is an 8-tuple giving the upper left, lower
|
||||||
left, lower right, and upper right corner of the source quadri-
|
left, lower right, and upper right corner of the source quadrilateral.
|
||||||
lateral. Also added Image.MESH transform which takes a list
|
Also added Image.MESH transform which takes a list
|
||||||
of quadrilaterals.
|
of quadrilaterals.
|
||||||
|
|
||||||
- The Image "resize", "rotate", and "transform" methods now support
|
- The Image "resize", "rotate", and "transform" methods now support
|
||||||
|
@ -6631,7 +7306,7 @@ The test suite includes 750 individual tests.
|
||||||
|
|
||||||
- You can now convert directly between all modes supported by
|
- You can now convert directly between all modes supported by
|
||||||
PIL. When converting colour images to "P", PIL defaults to
|
PIL. When converting colour images to "P", PIL defaults to
|
||||||
a "web" palette and dithering. When converting greyscale
|
a "web" palette and dithering. When converting grayscale
|
||||||
images to "1", PIL uses a thresholding and dithering.
|
images to "1", PIL uses a thresholding and dithering.
|
||||||
|
|
||||||
- Added a "dither" option to "convert". By default, "convert"
|
- Added a "dither" option to "convert". By default, "convert"
|
||||||
|
@ -6709,13 +7384,13 @@ The test suite includes 530 individual tests.
|
||||||
- Fixed "paste" to allow a mask also for mode "F" images.
|
- Fixed "paste" to allow a mask also for mode "F" images.
|
||||||
|
|
||||||
- The BMP driver now saves mode "1" images. When loading images, the mode
|
- The BMP driver now saves mode "1" images. When loading images, the mode
|
||||||
is set to "L" for 8-bit files with greyscale palettes, and to "P" for
|
is set to "L" for 8-bit files with grayscale palettes, and to "P" for
|
||||||
other 8-bit files.
|
other 8-bit files.
|
||||||
|
|
||||||
- The IM driver now reads and saves "1" images (file modes "0 1" or "L 1").
|
- The IM driver now reads and saves "1" images (file modes "0 1" or "L 1").
|
||||||
|
|
||||||
- The JPEG and GIF drivers now saves "1" images. For JPEG, the image
|
- The JPEG and GIF drivers now saves "1" images. For JPEG, the image
|
||||||
is saved as 8-bit greyscale (it will load as mode "L"). For GIF, the
|
is saved as 8-bit grayscale (it will load as mode "L"). For GIF, the
|
||||||
image will be loaded as a "P" image.
|
image will be loaded as a "P" image.
|
||||||
|
|
||||||
- Fixed a potential buffer overrun in the GIF encoder.
|
- Fixed a potential buffer overrun in the GIF encoder.
|
||||||
|
@ -6746,8 +7421,8 @@ The test suite includes 400 individual tests.
|
||||||
neither "short", "int" nor "long" are 32-bit wide.
|
neither "short", "int" nor "long" are 32-bit wide.
|
||||||
|
|
||||||
- Added file= and data= keyword arguments to PhotoImage and BitmapImage.
|
- Added file= and data= keyword arguments to PhotoImage and BitmapImage.
|
||||||
This allows you to use them as drop-in replacements for the corre-
|
This allows you to use them as drop-in replacements for the corresponding
|
||||||
sponding Tkinter classes.
|
Tkinter classes.
|
||||||
|
|
||||||
- Removed bogus references to the crack coder (ImagingCrack).
|
- Removed bogus references to the crack coder (ImagingCrack).
|
||||||
|
|
||||||
|
@ -7019,7 +7694,7 @@ The test suite includes 400 individual tests.
|
||||||
drawing capabilities can be used to render vector and metafile
|
drawing capabilities can be used to render vector and metafile
|
||||||
formats.
|
formats.
|
||||||
|
|
||||||
- Added restricted drivers for images from Image Tools (greyscale
|
- Added restricted drivers for images from Image Tools (grayscale
|
||||||
only) and LabEye/IFUNC (common interchange modes only).
|
only) and LabEye/IFUNC (common interchange modes only).
|
||||||
|
|
||||||
- Some minor improvements to the sample scripts provided in the
|
- Some minor improvements to the sample scripts provided in the
|
||||||
|
@ -7174,7 +7849,7 @@ The test suite includes 400 individual tests.
|
||||||
- A handbook is available (distributed separately).
|
- A handbook is available (distributed separately).
|
||||||
|
|
||||||
- The coordinate system is changed so that (0,0) is now located
|
- The coordinate system is changed so that (0,0) is now located
|
||||||
in the upper left corner. This is in compliancy with ISO 12087
|
in the upper left corner. This is in compliance with ISO 12087
|
||||||
and 90% of all other image processing and graphics libraries.
|
and 90% of all other image processing and graphics libraries.
|
||||||
|
|
||||||
- Modes "1" (bilevel) and "P" (palette) have been introduced. Note
|
- Modes "1" (bilevel) and "P" (palette) have been introduced. Note
|
||||||
|
|
6
LICENSE
|
@ -1,13 +1,13 @@
|
||||||
The Python Imaging Library (PIL) is
|
The Python Imaging Library (PIL) is
|
||||||
|
|
||||||
Copyright © 1997-2011 by Secret Labs AB
|
Copyright © 1997-2011 by Secret Labs AB
|
||||||
Copyright © 1995-2011 by Fredrik Lundh
|
Copyright © 1995-2011 by Fredrik Lundh and contributors
|
||||||
|
|
||||||
Pillow is the friendly PIL fork. It is
|
Pillow is the friendly PIL fork. It is
|
||||||
|
|
||||||
Copyright © 2010-2023 by Jeffrey A. Clark (Alex) and contributors.
|
Copyright © 2010-2024 by Jeffrey A. Clark and contributors
|
||||||
|
|
||||||
Like PIL, Pillow is licensed under the open source HPND License:
|
Like PIL, Pillow is licensed under the open source MIT-CMU License:
|
||||||
|
|
||||||
By obtaining, using, and/or copying this software and/or its associated
|
By obtaining, using, and/or copying this software and/or its associated
|
||||||
documentation, you agree that you have read, understood, and will comply
|
documentation, you agree that you have read, understood, and will comply
|
||||||
|
|
|
@ -5,8 +5,10 @@ include *.md
|
||||||
include *.py
|
include *.py
|
||||||
include *.rst
|
include *.rst
|
||||||
include *.sh
|
include *.sh
|
||||||
|
include *.toml
|
||||||
include *.txt
|
include *.txt
|
||||||
include *.yaml
|
include *.yaml
|
||||||
|
include .flake8
|
||||||
include LICENSE
|
include LICENSE
|
||||||
include Makefile
|
include Makefile
|
||||||
include tox.ini
|
include tox.ini
|
||||||
|
@ -29,3 +31,4 @@ global-exclude .git*
|
||||||
global-exclude *.pyc
|
global-exclude *.pyc
|
||||||
global-exclude *.so
|
global-exclude *.so
|
||||||
prune .ci
|
prune .ci
|
||||||
|
prune wheels
|
||||||
|
|
18
Makefile
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
python3 setup.py clean
|
|
||||||
rm src/PIL/*.so || true
|
rm src/PIL/*.so || true
|
||||||
rm -r build || true
|
rm -r build || true
|
||||||
find . -name __pycache__ | xargs rm -r || true
|
find . -name __pycache__ | xargs rm -r || true
|
||||||
|
@ -18,12 +17,10 @@ coverage:
|
||||||
.PHONY: doc
|
.PHONY: doc
|
||||||
.PHONY: html
|
.PHONY: html
|
||||||
doc html:
|
doc html:
|
||||||
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
|
|
||||||
$(MAKE) -C docs html
|
$(MAKE) -C docs html
|
||||||
|
|
||||||
.PHONY: htmlview
|
.PHONY: htmlview
|
||||||
htmlview:
|
htmlview:
|
||||||
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
|
|
||||||
$(MAKE) -C docs htmlview
|
$(MAKE) -C docs htmlview
|
||||||
|
|
||||||
.PHONY: doccheck
|
.PHONY: doccheck
|
||||||
|
@ -49,7 +46,7 @@ help:
|
||||||
@echo " install make and install"
|
@echo " install make and install"
|
||||||
@echo " install-coverage make and install with C coverage"
|
@echo " install-coverage make and install with C coverage"
|
||||||
@echo " lint run the lint checks"
|
@echo " lint run the lint checks"
|
||||||
@echo " lint-fix run Black and isort to (mostly) fix lint issues"
|
@echo " lint-fix run Ruff to (mostly) fix lint issues"
|
||||||
@echo " release-test run code and package tests before release"
|
@echo " release-test run code and package tests before release"
|
||||||
@echo " test run tests on installed Pillow"
|
@echo " test run tests on installed Pillow"
|
||||||
|
|
||||||
|
@ -78,8 +75,6 @@ release-test:
|
||||||
python3 selftest.py
|
python3 selftest.py
|
||||||
python3 -m pytest Tests
|
python3 -m pytest Tests
|
||||||
python3 -m pip install .
|
python3 -m pip install .
|
||||||
-rm dist/*.egg
|
|
||||||
-rmdir dist
|
|
||||||
python3 -m pytest -qq
|
python3 -m pytest -qq
|
||||||
python3 -m check_manifest
|
python3 -m check_manifest
|
||||||
python3 -m pyroma .
|
python3 -m pyroma .
|
||||||
|
@ -118,6 +113,11 @@ lint:
|
||||||
.PHONY: lint-fix
|
.PHONY: lint-fix
|
||||||
lint-fix:
|
lint-fix:
|
||||||
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
|
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
|
||||||
python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort
|
python3 -m black .
|
||||||
python3 -m black --target-version py38 .
|
python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff
|
||||||
python3 -m isort .
|
python3 -m ruff check --fix .
|
||||||
|
|
||||||
|
.PHONY: mypy
|
||||||
|
mypy:
|
||||||
|
python3 -c "import tox" > /dev/null 2>&1 || python3 -m pip install tox
|
||||||
|
python3 -m tox -e mypy
|
||||||
|
|
32
README.md
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
## Python Imaging Library (Fork)
|
## Python Imaging Library (Fork)
|
||||||
|
|
||||||
Pillow is the friendly PIL fork by [Jeffrey A. Clark (Alex) and
|
Pillow is the friendly PIL fork by [Jeffrey A. Clark and
|
||||||
contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
|
contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
|
||||||
PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
PIL is the Python Imaging Library by Fredrik Lundh and contributors.
|
||||||
As of 2019, Pillow development is
|
As of 2019, Pillow development is
|
||||||
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
|
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
|
||||||
|
|
||||||
|
@ -45,16 +45,13 @@ As of 2019, Pillow development is
|
||||||
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
|
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
|
||||||
alt="AppVeyor CI build status (Windows)"
|
alt="AppVeyor CI build status (Windows)"
|
||||||
src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
|
src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
|
||||||
<a href="https://github.com/python-pillow/pillow-wheels/actions"><img
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
|
||||||
alt="GitHub Actions wheels build status (Wheels)"
|
alt="GitHub Actions build status (Wheels)"
|
||||||
src="https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
|
||||||
<a href="https://app.travis-ci.com/github/python-pillow/pillow-wheels"><img
|
|
||||||
alt="Travis CI wheels build status (aarch64)"
|
|
||||||
src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels"></a>
|
|
||||||
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
|
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
|
||||||
alt="Code coverage"
|
alt="Code coverage"
|
||||||
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
||||||
<a href="https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:pillow"><img
|
<a href="https://issues.oss-fuzz.com/issues?q=title:pillow"><img
|
||||||
alt="Fuzzing Status"
|
alt="Fuzzing Status"
|
||||||
src="https://oss-fuzz-build-logs.storage.googleapis.com/badges/pillow.svg"></a>
|
src="https://oss-fuzz-build-logs.storage.googleapis.com/badges/pillow.svg"></a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -67,16 +64,16 @@ As of 2019, Pillow development is
|
||||||
src="https://zenodo.org/badge/17549/python-pillow/Pillow.svg"></a>
|
src="https://zenodo.org/badge/17549/python-pillow/Pillow.svg"></a>
|
||||||
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
|
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
|
||||||
alt="Tidelift"
|
alt="Tidelift"
|
||||||
src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
|
src="https://tidelift.com/badges/package/pypi/pillow?style=flat"></a>
|
||||||
<a href="https://pypi.org/project/Pillow/"><img
|
<a href="https://pypi.org/project/pillow/"><img
|
||||||
alt="Newest PyPI version"
|
alt="Newest PyPI version"
|
||||||
src="https://img.shields.io/pypi/v/pillow.svg"></a>
|
src="https://img.shields.io/pypi/v/pillow.svg"></a>
|
||||||
<a href="https://pypi.org/project/Pillow/"><img
|
<a href="https://pypi.org/project/pillow/"><img
|
||||||
alt="Number of PyPI downloads"
|
alt="Number of PyPI downloads"
|
||||||
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
||||||
<a href="https://bestpractices.coreinfrastructure.org/projects/6331"><img
|
<a href="https://www.bestpractices.dev/projects/6331"><img
|
||||||
alt="OpenSSF Best Practices"
|
alt="OpenSSF Best Practices"
|
||||||
src="https://bestpractices.coreinfrastructure.org/projects/6331/badge"></a>
|
src="https://www.bestpractices.dev/projects/6331/badge"></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -85,9 +82,6 @@ As of 2019, Pillow development is
|
||||||
<a href="https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img
|
<a href="https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img
|
||||||
alt="Join the chat at https://gitter.im/python-pillow/Pillow"
|
alt="Join the chat at https://gitter.im/python-pillow/Pillow"
|
||||||
src="https://badges.gitter.im/python-pillow/Pillow.svg"></a>
|
src="https://badges.gitter.im/python-pillow/Pillow.svg"></a>
|
||||||
<a href="https://twitter.com/PythonPillow"><img
|
|
||||||
alt="Follow on https://twitter.com/PythonPillow"
|
|
||||||
src="https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg"></a>
|
|
||||||
<a href="https://fosstodon.org/@pillow"><img
|
<a href="https://fosstodon.org/@pillow"><img
|
||||||
alt="Follow on https://fosstodon.org/@pillow"
|
alt="Follow on https://fosstodon.org/@pillow"
|
||||||
src="https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg"
|
src="https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg"
|
||||||
|
@ -107,13 +101,13 @@ The core image library is designed for fast access to data stored in a few basic
|
||||||
## More Information
|
## More Information
|
||||||
|
|
||||||
- [Documentation](https://pillow.readthedocs.io/)
|
- [Documentation](https://pillow.readthedocs.io/)
|
||||||
- [Installation](https://pillow.readthedocs.io/en/latest/installation.html)
|
- [Installation](https://pillow.readthedocs.io/en/latest/installation/basic-installation.html)
|
||||||
- [Handbook](https://pillow.readthedocs.io/en/latest/handbook/index.html)
|
- [Handbook](https://pillow.readthedocs.io/en/latest/handbook/index.html)
|
||||||
- [Contribute](https://github.com/python-pillow/Pillow/blob/main/.github/CONTRIBUTING.md)
|
- [Contribute](https://github.com/python-pillow/Pillow/blob/main/.github/CONTRIBUTING.md)
|
||||||
- [Issues](https://github.com/python-pillow/Pillow/issues)
|
- [Issues](https://github.com/python-pillow/Pillow/issues)
|
||||||
- [Pull requests](https://github.com/python-pillow/Pillow/pulls)
|
- [Pull requests](https://github.com/python-pillow/Pillow/pulls)
|
||||||
- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
|
- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
|
||||||
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
|
- [Changelog](https://github.com/python-pillow/Pillow/releases)
|
||||||
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
|
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
|
||||||
|
|
||||||
## Report a Vulnerability
|
## Report a Vulnerability
|
||||||
|
|
61
RELEASING.md
|
@ -10,9 +10,8 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||||
* [ ] Develop and prepare release in `main` branch.
|
* [ ] Develop and prepare release in `main` branch.
|
||||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
|
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
|
||||||
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI and GitHub Actions.
|
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
|
||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||||
* [ ] Update `CHANGES.rst`.
|
|
||||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||||
* [ ] Create branch and tag for release e.g.:
|
* [ ] Create branch and tag for release e.g.:
|
||||||
```bash
|
```bash
|
||||||
|
@ -20,17 +19,10 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
git tag 5.2.0
|
git tag 5.2.0
|
||||||
git push --tags
|
git push --tags
|
||||||
```
|
```
|
||||||
* [ ] Create and check source distribution:
|
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||||
```bash
|
has passed, including the "Upload release to PyPI" job. This will have been triggered
|
||||||
make sdist
|
by the new tag.
|
||||||
```
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases).
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
|
|
||||||
* [ ] Check and upload all binaries and source distributions e.g.:
|
|
||||||
```bash
|
|
||||||
python3 -m twine check --strict dist/*
|
|
||||||
python3 -m twine upload dist/Pillow-5.2.0*
|
|
||||||
```
|
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
|
||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
|
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
|
||||||
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
||||||
```bash
|
```bash
|
||||||
|
@ -41,7 +33,6 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
Released as needed for security, installation or critical bug fixes.
|
Released as needed for security, installation or critical bug fixes.
|
||||||
|
|
||||||
* [ ] Make necessary changes in `main` branch.
|
* [ ] Make necessary changes in `main` branch.
|
||||||
* [ ] Update `CHANGES.rst`.
|
|
||||||
* [ ] Check out release branch e.g.:
|
* [ ] Check out release branch e.g.:
|
||||||
```bash
|
```bash
|
||||||
git checkout -t remotes/origin/5.2.x
|
git checkout -t remotes/origin/5.2.x
|
||||||
|
@ -59,12 +50,9 @@ Released as needed for security, installation or critical bug fixes.
|
||||||
```bash
|
```bash
|
||||||
make sdist
|
make sdist
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
|
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||||
* [ ] Check and upload all binaries and source distributions e.g.:
|
has passed, including the "Upload release to PyPI" job. This will have been triggered
|
||||||
```bash
|
by the new tag.
|
||||||
python3 -m twine check --strict dist/*
|
|
||||||
python3 -m twine upload dist/Pillow-5.2.1*
|
|
||||||
```
|
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
||||||
```bash
|
```bash
|
||||||
git push
|
git push
|
||||||
|
@ -86,42 +74,17 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
git tag 2.5.3
|
git tag 2.5.3
|
||||||
git push origin --tags
|
git push origin --tags
|
||||||
```
|
```
|
||||||
* [ ] Create and check source distribution:
|
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||||
```bash
|
has passed, including the "Upload release to PyPI" job. This will have been triggered
|
||||||
make sdist
|
by the new tag.
|
||||||
```
|
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
|
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
||||||
```bash
|
```bash
|
||||||
git push origin 2.5.x
|
git push origin 2.5.x
|
||||||
```
|
```
|
||||||
|
|
||||||
## Binary Distributions
|
|
||||||
|
|
||||||
### macOS and Linux
|
|
||||||
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/python-pillow/pillow-wheels
|
|
||||||
cd pillow-wheels
|
|
||||||
./update-pillow-tag.sh [[release tag]]
|
|
||||||
```
|
|
||||||
* [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases)
|
|
||||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli) from the main repo:
|
|
||||||
```bash
|
|
||||||
gh release download --dir dist --pattern "*.whl" --repo python-pillow/pillow-wheels
|
|
||||||
```
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
|
|
||||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
|
|
||||||
```bash
|
|
||||||
gh run download --dir dist
|
|
||||||
# select dist-x.y.z
|
|
||||||
```
|
|
||||||
|
|
||||||
## Publicize Release
|
## Publicize Release
|
||||||
|
|
||||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
|
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
|
9
Tests/32bit_segfault_check.py
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
if sys.maxsize < 2**32:
|
||||||
|
im = Image.new("L", (999999, 999999), 0)
|
|
@ -1,52 +0,0 @@
|
||||||
import time
|
|
||||||
|
|
||||||
from PIL import PyAccess
|
|
||||||
|
|
||||||
from .helper import hopper
|
|
||||||
|
|
||||||
# Not running this test by default. No DOS against CI.
|
|
||||||
|
|
||||||
|
|
||||||
def iterate_get(size, access):
|
|
||||||
(w, h) = size
|
|
||||||
for x in range(w):
|
|
||||||
for y in range(h):
|
|
||||||
access[(x, y)]
|
|
||||||
|
|
||||||
|
|
||||||
def iterate_set(size, access):
|
|
||||||
(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):
|
|
||||||
iterations = 5000
|
|
||||||
starttime = time.time()
|
|
||||||
for x in range(iterations):
|
|
||||||
func(*args)
|
|
||||||
if time.time() - starttime > 10:
|
|
||||||
break
|
|
||||||
endtime = time.time()
|
|
||||||
print(
|
|
||||||
"{}: completed {} iterations in {:.4f}s, {:.6f}s per iteration".format(
|
|
||||||
label, x + 1, endtime - starttime, (endtime - starttime) / (x + 1.0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_direct():
|
|
||||||
im = hopper()
|
|
||||||
im.load()
|
|
||||||
# im = Image.new("RGB", (2000, 2000), (1, 3, 2))
|
|
||||||
caccess = im.im.pixel_access(False)
|
|
||||||
access = PyAccess.new(im, False)
|
|
||||||
|
|
||||||
assert caccess[(0, 0)] == access[(0, 0)]
|
|
||||||
|
|
||||||
print("Size: %sx%s" % im.size)
|
|
||||||
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)
|
|
||||||
timer(iterate_set, "C-api - set", im.size, caccess)
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env python3
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
TEST_FILE = "Tests/images/fli_overflow.fli"
|
TEST_FILE = "Tests/images/fli_overflow.fli"
|
||||||
|
|
||||||
|
|
||||||
def test_fli_overflow():
|
def test_fli_overflow() -> None:
|
||||||
# this should not crash with a malloc error or access violation
|
# this should not crash with a malloc error or access violation
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Tests potential DOS of IcnsImagePlugin with 0 length block.
|
# Tests potential DOS of IcnsImagePlugin with 0 length block.
|
||||||
# Run from anywhere that PIL is importable.
|
# Run from anywhere that PIL is importable.
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
@ -11,31 +15,37 @@ max_iterations = 10000
|
||||||
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
||||||
|
|
||||||
|
|
||||||
def _get_mem_usage():
|
def _get_mem_usage() -> float:
|
||||||
from resource import RUSAGE_SELF, getpagesize, getrusage
|
from resource import RUSAGE_SELF, getpagesize, getrusage
|
||||||
|
|
||||||
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
||||||
return mem * getpagesize() / 1024 / 1024
|
return mem * getpagesize() / 1024 / 1024
|
||||||
|
|
||||||
|
|
||||||
def _test_leak(min_iterations, max_iterations, fn, *args, **kwargs):
|
def _test_leak(
|
||||||
|
min_iterations: int,
|
||||||
|
max_iterations: int,
|
||||||
|
fn: Callable[..., Image.Image | None],
|
||||||
|
*args: Any,
|
||||||
|
) -> None:
|
||||||
mem_limit = None
|
mem_limit = None
|
||||||
for i in range(max_iterations):
|
for i in range(max_iterations):
|
||||||
fn(*args, **kwargs)
|
fn(*args)
|
||||||
mem = _get_mem_usage()
|
mem = _get_mem_usage()
|
||||||
if i < min_iterations:
|
if i < min_iterations:
|
||||||
mem_limit = mem + 1
|
mem_limit = mem + 1
|
||||||
continue
|
continue
|
||||||
msg = f"memory usage limit exceeded after {i + 1} iterations"
|
msg = f"memory usage limit exceeded after {i + 1} iterations"
|
||||||
|
assert mem_limit is not None
|
||||||
assert mem <= mem_limit, msg
|
assert mem <= mem_limit, msg
|
||||||
|
|
||||||
|
|
||||||
def test_leak_putdata():
|
def test_leak_putdata() -> None:
|
||||||
im = Image.new("RGB", (25, 25))
|
im = Image.new("RGB", (25, 25))
|
||||||
_test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
|
_test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
|
||||||
|
|
||||||
|
|
||||||
def test_leak_getlist():
|
def test_leak_getlist() -> None:
|
||||||
im = Image.new("P", (25, 25))
|
im = Image.new("P", (25, 25))
|
||||||
_test_leak(
|
_test_leak(
|
||||||
min_iterations,
|
min_iterations,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Tests potential DOS of Jpeg2kImagePlugin with 0 length block.
|
# Tests potential DOS of Jpeg2kImagePlugin with 0 length block.
|
||||||
# Run from anywhere that PIL is importable.
|
# Run from anywhere that PIL is importable.
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
6
Tests/check_j2k_leaks.py
Executable file → Normal file
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -18,7 +20,7 @@ pytestmark = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_leak_load():
|
def test_leak_load() -> None:
|
||||||
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
||||||
|
|
||||||
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||||
|
@ -28,7 +30,7 @@ def test_leak_load():
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
def test_leak_save():
|
def test_leak_save() -> None:
|
||||||
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
|
||||||
|
|
||||||
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
def test_j2k_overflow(tmp_path):
|
def test_j2k_overflow(tmp_path: Path) -> None:
|
||||||
im = Image.new("RGBA", (1024, 131584))
|
im = Image.new("RGBA", (1024, 131584))
|
||||||
target = str(tmp_path / "temp.jpc")
|
target = str(tmp_path / "temp.jpc")
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
|
|
4
Tests/check_jp2_overflow.py
Executable file → Normal file
|
@ -1,5 +1,3 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Reproductions/tests for OOB read errors in FliDecode.c
|
# Reproductions/tests for OOB read errors in FliDecode.c
|
||||||
|
|
||||||
# When run in python, all of these images should fail for
|
# When run in python, all of these images should fail for
|
||||||
|
@ -12,7 +10,7 @@
|
||||||
# the output should be empty. There may be python issues
|
# the output should be empty. There may be python issues
|
||||||
# in the valgrind especially if run in a debug python
|
# in the valgrind especially if run in a debug python
|
||||||
# version.
|
# version.
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -109,14 +111,14 @@ standard_chrominance_qtable = (
|
||||||
[standard_l_qtable, standard_chrominance_qtable],
|
[standard_l_qtable, standard_chrominance_qtable],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_qtables_leak(qtables):
|
def test_qtables_leak(qtables: tuple[tuple[int, ...]] | list[tuple[int, ...]]) -> None:
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
for _ in range(iterations):
|
for _ in range(iterations):
|
||||||
test_output = BytesIO()
|
test_output = BytesIO()
|
||||||
im.save(test_output, "JPEG", qtables=qtables)
|
im.save(test_output, "JPEG", qtables=qtables)
|
||||||
|
|
||||||
|
|
||||||
def test_exif_leak():
|
def test_exif_leak() -> None:
|
||||||
"""
|
"""
|
||||||
pre patch:
|
pre patch:
|
||||||
|
|
||||||
|
@ -179,7 +181,7 @@ def test_exif_leak():
|
||||||
im.save(test_output, "JPEG", exif=exif)
|
im.save(test_output, "JPEG", exif=exif)
|
||||||
|
|
||||||
|
|
||||||
def test_base_save():
|
def test_base_save() -> None:
|
||||||
"""
|
"""
|
||||||
base case:
|
base case:
|
||||||
MB
|
MB
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
@ -12,6 +18,7 @@ from PIL import Image
|
||||||
# 2.7 and 3.2.
|
# 2.7 and 3.2.
|
||||||
|
|
||||||
|
|
||||||
|
numpy: ModuleType | None
|
||||||
try:
|
try:
|
||||||
import numpy
|
import numpy
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -21,23 +28,27 @@ YDIM = 32769
|
||||||
XDIM = 48000
|
XDIM = 48000
|
||||||
|
|
||||||
|
|
||||||
def _write_png(tmp_path, xdim, ydim):
|
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
||||||
|
|
||||||
|
|
||||||
|
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
|
||||||
f = str(tmp_path / "temp.png")
|
f = str(tmp_path / "temp.png")
|
||||||
im = Image.new("L", (xdim, ydim), 0)
|
im = Image.new("L", (xdim, ydim), 0)
|
||||||
im.save(f)
|
im.save(f)
|
||||||
|
|
||||||
|
|
||||||
def test_large(tmp_path):
|
def test_large(tmp_path: Path) -> None:
|
||||||
"""succeeded prepatch"""
|
"""succeeded prepatch"""
|
||||||
_write_png(tmp_path, XDIM, YDIM)
|
_write_png(tmp_path, XDIM, YDIM)
|
||||||
|
|
||||||
|
|
||||||
def test_2gpx(tmp_path):
|
def test_2gpx(tmp_path: Path) -> None:
|
||||||
"""failed prepatch"""
|
"""failed prepatch"""
|
||||||
_write_png(tmp_path, XDIM, XDIM)
|
_write_png(tmp_path, XDIM, XDIM)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(numpy is None, reason="Numpy is not installed")
|
@pytest.mark.skipif(numpy is None, reason="Numpy is not installed")
|
||||||
def test_size_greater_than_int():
|
def test_size_greater_than_int() -> None:
|
||||||
|
assert numpy is not None
|
||||||
arr = numpy.ndarray(shape=(16394, 16394))
|
arr = numpy.ndarray(shape=(16394, 16394))
|
||||||
Image.fromarray(arr)
|
Image.fromarray(arr)
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
@ -17,7 +22,10 @@ YDIM = 32769
|
||||||
XDIM = 48000
|
XDIM = 48000
|
||||||
|
|
||||||
|
|
||||||
def _write_png(tmp_path, xdim, ydim):
|
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
||||||
|
|
||||||
|
|
||||||
|
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
|
||||||
dtype = np.uint8
|
dtype = np.uint8
|
||||||
a = np.zeros((xdim, ydim), dtype=dtype)
|
a = np.zeros((xdim, ydim), dtype=dtype)
|
||||||
f = str(tmp_path / "temp.png")
|
f = str(tmp_path / "temp.png")
|
||||||
|
@ -25,11 +33,11 @@ def _write_png(tmp_path, xdim, ydim):
|
||||||
im.save(f)
|
im.save(f)
|
||||||
|
|
||||||
|
|
||||||
def test_large(tmp_path):
|
def test_large(tmp_path: Path) -> None:
|
||||||
"""succeeded prepatch"""
|
"""succeeded prepatch"""
|
||||||
_write_png(tmp_path, XDIM, YDIM)
|
_write_png(tmp_path, XDIM, YDIM)
|
||||||
|
|
||||||
|
|
||||||
def test_2gpx(tmp_path):
|
def test_2gpx(tmp_path: Path) -> None:
|
||||||
"""failed prepatch"""
|
"""failed prepatch"""
|
||||||
_write_png(tmp_path, XDIM, XDIM)
|
_write_png(tmp_path, XDIM, XDIM)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
@ -5,7 +7,7 @@ from PIL import Image
|
||||||
TEST_FILE = "Tests/images/libtiff_segfault.tif"
|
TEST_FILE = "Tests/images/libtiff_segfault.tif"
|
||||||
|
|
||||||
|
|
||||||
def test_libtiff_segfault():
|
def test_libtiff_segfault() -> None:
|
||||||
"""This test should not segfault. It will on Pillow <= 3.1.0 and
|
"""This test should not segfault. It will on Pillow <= 3.1.0 and
|
||||||
libtiff >= 4.0.0
|
libtiff >= 4.0.0
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import zlib
|
import zlib
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
@ -6,7 +8,7 @@ from PIL import Image, ImageFile, PngImagePlugin
|
||||||
TEST_FILE = "Tests/images/png_decompression_dos.png"
|
TEST_FILE = "Tests/images/png_decompression_dos.png"
|
||||||
|
|
||||||
|
|
||||||
def test_ignore_dos_text():
|
def test_ignore_dos_text() -> None:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -15,6 +17,7 @@ def test_ignore_dos_text():
|
||||||
finally:
|
finally:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||||
|
|
||||||
|
assert isinstance(im, PngImagePlugin.PngImageFile)
|
||||||
for s in im.text.values():
|
for s in im.text.values():
|
||||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||||
|
|
||||||
|
@ -22,7 +25,7 @@ def test_ignore_dos_text():
|
||||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||||
|
|
||||||
|
|
||||||
def test_dos_text():
|
def test_dos_text() -> None:
|
||||||
try:
|
try:
|
||||||
im = Image.open(TEST_FILE)
|
im = Image.open(TEST_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
|
@ -30,11 +33,12 @@ def test_dos_text():
|
||||||
assert msg, "Decompressed Data Too Large"
|
assert msg, "Decompressed Data Too Large"
|
||||||
return
|
return
|
||||||
|
|
||||||
|
assert isinstance(im, PngImagePlugin.PngImageFile)
|
||||||
for s in im.text.values():
|
for s in im.text.values():
|
||||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||||
|
|
||||||
|
|
||||||
def test_dos_total_memory():
|
def test_dos_total_memory() -> None:
|
||||||
im = Image.new("L", (1, 1))
|
im = Image.new("L", (1, 1))
|
||||||
compressed_data = zlib.compress(b"a" * 1024 * 1023)
|
compressed_data = zlib.compress(b"a" * 1024 * 1023)
|
||||||
|
|
||||||
|
@ -51,10 +55,11 @@ def test_dos_total_memory():
|
||||||
try:
|
try:
|
||||||
im2 = Image.open(b)
|
im2 = Image.open(b)
|
||||||
except ValueError as msg:
|
except ValueError as msg:
|
||||||
assert "Too much memory" in msg
|
assert "Too much memory" in str(msg)
|
||||||
return
|
return
|
||||||
|
|
||||||
total_len = 0
|
total_len = 0
|
||||||
|
assert isinstance(im2, PngImagePlugin.PngImageFile)
|
||||||
for txt in im2.text.values():
|
for txt in im2.text.values():
|
||||||
total_len += len(txt)
|
total_len += len(txt)
|
||||||
assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M"
|
assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
43
Tests/check_wheel.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PIL import features
|
||||||
|
|
||||||
|
|
||||||
|
def test_wheel_modules() -> None:
|
||||||
|
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
|
||||||
|
|
||||||
|
# tkinter is not available in cibuildwheel installed CPython on Windows
|
||||||
|
try:
|
||||||
|
import tkinter
|
||||||
|
|
||||||
|
assert tkinter
|
||||||
|
except ImportError:
|
||||||
|
expected_modules.remove("tkinter")
|
||||||
|
|
||||||
|
assert set(features.get_supported_modules()) == expected_modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_wheel_codecs() -> None:
|
||||||
|
expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
|
||||||
|
|
||||||
|
assert set(features.get_supported_codecs()) == expected_codecs
|
||||||
|
|
||||||
|
|
||||||
|
def test_wheel_features() -> None:
|
||||||
|
expected_features = {
|
||||||
|
"webp_anim",
|
||||||
|
"webp_mux",
|
||||||
|
"transp_webp",
|
||||||
|
"raqm",
|
||||||
|
"fribidi",
|
||||||
|
"harfbuzz",
|
||||||
|
"libjpeg_turbo",
|
||||||
|
"xcb",
|
||||||
|
}
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
expected_features.remove("xcb")
|
||||||
|
|
||||||
|
assert set(features.get_supported_features()) == expected_features
|
|
@ -1,7 +1,11 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
def pytest_report_header(config):
|
|
||||||
|
def pytest_report_header(config: pytest.Config) -> str:
|
||||||
try:
|
try:
|
||||||
from PIL import features
|
from PIL import features
|
||||||
|
|
||||||
|
@ -12,7 +16,7 @@ def pytest_report_header(config):
|
||||||
return f"pytest_report_header failed: {e}"
|
return f"pytest_report_header failed: {e}"
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config: pytest.Config) -> None:
|
||||||
config.addinivalue_line(
|
config.addinivalue_line(
|
||||||
"markers",
|
"markers",
|
||||||
"pil_noop_mark: A conditional mark where nothing special happens",
|
"pil_noop_mark: A conditional mark where nothing special happens",
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
BIN
Tests/fonts/CBDTTestFont.ttf
Normal file
|
@ -37,4 +37,4 @@ The Font Software may be sold as part of a larger software package but no copy o
|
||||||
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
|
||||||
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.
|
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.
|
||||||
|
|
BIN
Tests/fonts/EBDTTestFont.ttf
Normal file
|
@ -2,7 +2,6 @@
|
||||||
NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts
|
NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts
|
||||||
NotoSans-Regular.ttf, from https://www.google.com/get/noto/
|
NotoSans-Regular.ttf, from https://www.google.com/get/noto/
|
||||||
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
|
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
|
||||||
NotoColorEmoji.ttf, from https://github.com/googlefonts/noto-emoji
|
|
||||||
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype
|
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype
|
||||||
TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
|
TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
|
||||||
ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
|
ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
|
||||||
|
@ -25,3 +24,5 @@ FreeMono.ttf is licensed under GPLv3.
|
||||||
10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base
|
10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base
|
||||||
|
|
||||||
"Public domain font. Share and enjoy."
|
"Public domain font. Share and enjoy."
|
||||||
|
|
||||||
|
CBDTTestFont.ttf and EBDTTestFont.ttf from https://github.com/nulano/font-tests are public domain.
|
||||||
|
|
269
Tests/helper.py
|
@ -2,57 +2,52 @@
|
||||||
Helper functions.
|
Helper functions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from collections.abc import Sequence
|
||||||
|
from functools import lru_cache
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
from packaging.version import parse as parse_version
|
||||||
|
|
||||||
from PIL import Image, ImageMath, features
|
from PIL import Image, ImageFile, ImageMath, features
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
uploader = None
|
||||||
HAS_UPLOADER = False
|
|
||||||
|
|
||||||
if os.environ.get("SHOW_ERRORS"):
|
if os.environ.get("SHOW_ERRORS"):
|
||||||
# local img.show for errors.
|
uploader = "show"
|
||||||
HAS_UPLOADER = True
|
|
||||||
|
|
||||||
class test_image_results:
|
|
||||||
@staticmethod
|
|
||||||
def upload(a, b):
|
|
||||||
a.show()
|
|
||||||
b.show()
|
|
||||||
|
|
||||||
elif "GITHUB_ACTIONS" in os.environ:
|
elif "GITHUB_ACTIONS" in os.environ:
|
||||||
HAS_UPLOADER = True
|
uploader = "github_actions"
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
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
|
new_a, new_b = a, b
|
||||||
if a.mode == "P":
|
if a.mode == "P":
|
||||||
new_a = Image.new("L", a.size)
|
new_a = Image.new("L", a.size)
|
||||||
|
@ -65,14 +60,16 @@ def convert_to_comparable(a, b):
|
||||||
return new_a, new_b
|
return new_a, new_b
|
||||||
|
|
||||||
|
|
||||||
def assert_deep_equal(a, b, msg=None):
|
def assert_deep_equal(a: Any, b: Any, msg: str | None = None) -> None:
|
||||||
try:
|
try:
|
||||||
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
|
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
|
||||||
except Exception:
|
except Exception:
|
||||||
assert a == b, msg
|
assert a == b, msg
|
||||||
|
|
||||||
|
|
||||||
def assert_image(im, mode, size, msg=None):
|
def assert_image(
|
||||||
|
im: Image.Image, mode: str, size: tuple[int, int], msg: str | None = None
|
||||||
|
) -> None:
|
||||||
if mode is not None:
|
if mode is not None:
|
||||||
assert im.mode == mode, (
|
assert im.mode == mode, (
|
||||||
msg or f"got mode {repr(im.mode)}, expected {repr(mode)}"
|
msg or f"got mode {repr(im.mode)}, expected {repr(mode)}"
|
||||||
|
@ -84,28 +81,32 @@ def assert_image(im, mode, size, msg=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def assert_image_equal(a, b, msg=None):
|
def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) -> None:
|
||||||
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
||||||
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
||||||
if a.tobytes() != b.tobytes():
|
if a.tobytes() != b.tobytes():
|
||||||
if HAS_UPLOADER:
|
try:
|
||||||
try:
|
url = upload(a, b)
|
||||||
url = test_image_results.upload(a, b)
|
if url:
|
||||||
logger.error(f"Url for test images: {url}")
|
logger.error("URL for test images: %s", url)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert False, msg or "got different content"
|
pytest.fail(msg or "got different content")
|
||||||
|
|
||||||
|
|
||||||
def assert_image_equal_tofile(a, filename, msg=None, mode=None):
|
def assert_image_equal_tofile(
|
||||||
|
a: Image.Image, filename: str, msg: str | None = None, mode: str | None = None
|
||||||
|
) -> None:
|
||||||
with Image.open(filename) as img:
|
with Image.open(filename) as img:
|
||||||
if mode:
|
if mode:
|
||||||
img = img.convert(mode)
|
img = img.convert(mode)
|
||||||
assert_image_equal(a, img, msg)
|
assert_image_equal(a, img, msg)
|
||||||
|
|
||||||
|
|
||||||
def assert_image_similar(a, b, epsilon, msg=None):
|
def assert_image_similar(
|
||||||
|
a: Image.Image, b: Image.Image, epsilon: float, msg: str | None = None
|
||||||
|
) -> None:
|
||||||
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
||||||
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
||||||
|
|
||||||
|
@ -113,7 +114,9 @@ def assert_image_similar(a, b, epsilon, msg=None):
|
||||||
|
|
||||||
diff = 0
|
diff = 0
|
||||||
for ach, bch in zip(a.split(), b.split()):
|
for ach, bch in zip(a.split(), b.split()):
|
||||||
chdiff = ImageMath.eval("abs(a - b)", a=ach, b=bch).convert("L")
|
chdiff = ImageMath.lambda_eval(
|
||||||
|
lambda args: abs(args["a"] - args["b"]), a=ach, b=bch
|
||||||
|
).convert("L")
|
||||||
diff += sum(i * num for i, num in enumerate(chdiff.histogram()))
|
diff += sum(i * num for i, num in enumerate(chdiff.histogram()))
|
||||||
|
|
||||||
ave_diff = diff / (a.size[0] * a.size[1])
|
ave_diff = diff / (a.size[0] * a.size[1])
|
||||||
|
@ -123,61 +126,76 @@ def assert_image_similar(a, b, epsilon, msg=None):
|
||||||
+ f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}"
|
+ f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if HAS_UPLOADER:
|
try:
|
||||||
try:
|
url = upload(a, b)
|
||||||
url = test_image_results.upload(a, b)
|
if url:
|
||||||
logger.error(f"Url for test images: {url}")
|
logger.exception("URL for test images: %s", url)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def assert_image_similar_tofile(a, filename, epsilon, msg=None, mode=None):
|
def assert_image_similar_tofile(
|
||||||
|
a: Image.Image,
|
||||||
|
filename: str,
|
||||||
|
epsilon: float,
|
||||||
|
msg: str | None = None,
|
||||||
|
mode: str | None = None,
|
||||||
|
) -> None:
|
||||||
with Image.open(filename) as img:
|
with Image.open(filename) as img:
|
||||||
if mode:
|
if mode:
|
||||||
img = img.convert(mode)
|
img = img.convert(mode)
|
||||||
assert_image_similar(a, img, epsilon, msg)
|
assert_image_similar(a, img, epsilon, msg)
|
||||||
|
|
||||||
|
|
||||||
def assert_all_same(items, msg=None):
|
def assert_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||||
assert items.count(items[0]) == len(items), msg
|
assert items.count(items[0]) == len(items), msg
|
||||||
|
|
||||||
|
|
||||||
def assert_not_all_same(items, msg=None):
|
def assert_not_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||||
assert items.count(items[0]) != len(items), msg
|
assert items.count(items[0]) != len(items), msg
|
||||||
|
|
||||||
|
|
||||||
def assert_tuple_approx_equal(actuals, targets, threshold, msg):
|
def assert_tuple_approx_equal(
|
||||||
|
actuals: Sequence[int], targets: tuple[int, ...], threshold: int, msg: str
|
||||||
|
) -> None:
|
||||||
"""Tests if actuals has values within threshold from targets"""
|
"""Tests if actuals has values within threshold from targets"""
|
||||||
value = True
|
|
||||||
for i, target in enumerate(targets):
|
for i, target in enumerate(targets):
|
||||||
value *= target - threshold <= actuals[i] <= target + threshold
|
if not (target - threshold <= actuals[i] <= target + threshold):
|
||||||
|
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
|
||||||
assert value, msg + ": " + repr(actuals) + " != " + repr(targets)
|
|
||||||
|
|
||||||
|
|
||||||
def skip_unless_feature(feature):
|
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
||||||
reason = f"{feature} not available"
|
reason = f"{feature} not available"
|
||||||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||||
|
|
||||||
|
|
||||||
def skip_unless_feature_version(feature, version_required, reason=None):
|
def skip_unless_feature_version(
|
||||||
if not features.check(feature):
|
feature: str, required: str, reason: str | None = None
|
||||||
|
) -> pytest.MarkDecorator:
|
||||||
|
version = features.version(feature)
|
||||||
|
if version is None:
|
||||||
return pytest.mark.skip(f"{feature} not available")
|
return pytest.mark.skip(f"{feature} not available")
|
||||||
if reason is None:
|
if reason is None:
|
||||||
reason = f"{feature} is older than {version_required}"
|
reason = f"{feature} is older than {required}"
|
||||||
version_required = parse_version(version_required)
|
version_required = parse_version(required)
|
||||||
version_available = parse_version(features.version(feature))
|
version_available = parse_version(version)
|
||||||
return pytest.mark.skipif(version_available < version_required, reason=reason)
|
return pytest.mark.skipif(version_available < version_required, reason=reason)
|
||||||
|
|
||||||
|
|
||||||
def mark_if_feature_version(mark, feature, version_blacklist, reason=None):
|
def mark_if_feature_version(
|
||||||
if not features.check(feature):
|
mark: pytest.MarkDecorator,
|
||||||
|
feature: str,
|
||||||
|
version_blacklist: str,
|
||||||
|
reason: str | None = None,
|
||||||
|
) -> pytest.MarkDecorator:
|
||||||
|
version = features.version(feature)
|
||||||
|
if version is None:
|
||||||
return pytest.mark.pil_noop_mark()
|
return pytest.mark.pil_noop_mark()
|
||||||
if reason is None:
|
if reason is None:
|
||||||
reason = f"{feature} is {version_blacklist}"
|
reason = f"{feature} is {version_blacklist}"
|
||||||
version_required = parse_version(version_blacklist)
|
version_required = parse_version(version_blacklist)
|
||||||
version_available = parse_version(features.version(feature))
|
version_available = parse_version(version)
|
||||||
if (
|
if (
|
||||||
version_available.major == version_required.major
|
version_available.major == version_required.major
|
||||||
and version_available.minor == version_required.minor
|
and version_available.minor == version_required.minor
|
||||||
|
@ -192,7 +210,7 @@ class PillowLeakTestCase:
|
||||||
iterations = 100 # count
|
iterations = 100 # count
|
||||||
mem_limit = 512 # k
|
mem_limit = 512 # k
|
||||||
|
|
||||||
def _get_mem_usage(self):
|
def _get_mem_usage(self) -> float:
|
||||||
"""
|
"""
|
||||||
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
||||||
between macOS and Linux rss reporting
|
between macOS and Linux rss reporting
|
||||||
|
@ -203,18 +221,13 @@ class PillowLeakTestCase:
|
||||||
from resource import RUSAGE_SELF, getrusage
|
from resource import RUSAGE_SELF, getrusage
|
||||||
|
|
||||||
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
||||||
if sys.platform == "darwin":
|
# man 2 getrusage:
|
||||||
# man 2 getrusage:
|
# ru_maxrss
|
||||||
# ru_maxrss
|
# This is the maximum resident set size utilized
|
||||||
# This is the maximum resident set size utilized (in bytes).
|
# in bytes on macOS, in kilobytes on Linux
|
||||||
return mem / 1024 # Kb
|
return mem / 1024 if sys.platform == "darwin" else mem
|
||||||
# linux
|
|
||||||
# man 2 getrusage
|
|
||||||
# ru_maxrss (since Linux 2.6.32)
|
|
||||||
# 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()
|
start_mem = self._get_mem_usage()
|
||||||
for cycle in range(self.iterations):
|
for cycle in range(self.iterations):
|
||||||
core()
|
core()
|
||||||
|
@ -226,50 +239,75 @@ class PillowLeakTestCase:
|
||||||
# helpers
|
# helpers
|
||||||
|
|
||||||
|
|
||||||
def fromstring(data):
|
def fromstring(data: bytes) -> ImageFile.ImageFile:
|
||||||
return Image.open(BytesIO(data))
|
return Image.open(BytesIO(data))
|
||||||
|
|
||||||
|
|
||||||
def tostring(im, string_format, **options):
|
def tostring(im: Image.Image, string_format: str, **options: Any) -> bytes:
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im.save(out, string_format, **options)
|
im.save(out, string_format, **options)
|
||||||
return out.getvalue()
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def hopper(mode=None, cache={}):
|
def hopper(mode: str | None = None) -> Image.Image:
|
||||||
|
# Use caching to reduce reading from disk, but return a copy
|
||||||
|
# so that the cached image isn't modified by the tests
|
||||||
|
# (for fast, isolated, repeatable tests).
|
||||||
|
|
||||||
if mode is None:
|
if mode is None:
|
||||||
# Always return fresh not-yet-loaded version of image.
|
# Always return fresh not-yet-loaded version of image.
|
||||||
# Operations on not-yet-loaded images is separate class of errors
|
# Operations on not-yet-loaded images are a separate class of errors
|
||||||
# what we should catch.
|
# that we should catch.
|
||||||
return Image.open("Tests/images/hopper.ppm")
|
return Image.open("Tests/images/hopper.ppm")
|
||||||
# Use caching to reduce reading from disk but so an original copy is
|
|
||||||
# returned each time and the cached image isn't modified by tests
|
return _cached_hopper(mode).copy()
|
||||||
# (for fast, isolated, repeatable tests).
|
|
||||||
im = cache.get(mode)
|
|
||||||
if im is None:
|
|
||||||
if mode == "F":
|
|
||||||
im = hopper("L").convert(mode)
|
|
||||||
elif mode[:4] == "I;16":
|
|
||||||
im = hopper("I").convert(mode)
|
|
||||||
else:
|
|
||||||
im = hopper().convert(mode)
|
|
||||||
cache[mode] = im
|
|
||||||
return im.copy()
|
|
||||||
|
|
||||||
|
|
||||||
def djpeg_available():
|
@lru_cache
|
||||||
return bool(shutil.which("djpeg"))
|
def _cached_hopper(mode: str) -> Image.Image:
|
||||||
|
if mode == "F":
|
||||||
|
im = hopper("L")
|
||||||
|
else:
|
||||||
|
im = hopper()
|
||||||
|
if mode.startswith("BGR;"):
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
im = im.convert(mode)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
im = im.convert(mode)
|
||||||
|
except ImportError:
|
||||||
|
if mode == "LAB":
|
||||||
|
im = Image.open("Tests/images/hopper.Lab.tif")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
return im
|
||||||
|
|
||||||
|
|
||||||
def cjpeg_available():
|
def djpeg_available() -> bool:
|
||||||
return bool(shutil.which("cjpeg"))
|
if shutil.which("djpeg"):
|
||||||
|
try:
|
||||||
|
subprocess.check_call(["djpeg", "-version"])
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError: # pragma: no cover
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def netpbm_available():
|
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() -> bool:
|
||||||
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
||||||
|
|
||||||
|
|
||||||
def magick_command():
|
def magick_command() -> list[str] | None:
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
magickhome = os.environ.get("MAGICK_HOME")
|
magickhome = os.environ.get("MAGICK_HOME")
|
||||||
if magickhome:
|
if magickhome:
|
||||||
|
@ -286,47 +324,48 @@ def magick_command():
|
||||||
return imagemagick
|
return imagemagick
|
||||||
if graphicsmagick and shutil.which(graphicsmagick[0]):
|
if graphicsmagick and shutil.which(graphicsmagick[0]):
|
||||||
return graphicsmagick
|
return graphicsmagick
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def on_appveyor():
|
def on_appveyor() -> bool:
|
||||||
return "APPVEYOR" in os.environ
|
return "APPVEYOR" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def on_github_actions():
|
def on_github_actions() -> bool:
|
||||||
return "GITHUB_ACTIONS" in os.environ
|
return "GITHUB_ACTIONS" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def on_ci():
|
def on_ci() -> bool:
|
||||||
# GitHub Actions and AppVeyor have "CI"
|
# GitHub Actions and AppVeyor have "CI"
|
||||||
return "CI" in os.environ
|
return "CI" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def is_big_endian():
|
def is_big_endian() -> bool:
|
||||||
return sys.byteorder == "big"
|
return sys.byteorder == "big"
|
||||||
|
|
||||||
|
|
||||||
def is_ppc64le():
|
def is_ppc64le() -> bool:
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
return platform.machine() == "ppc64le"
|
return platform.machine() == "ppc64le"
|
||||||
|
|
||||||
|
|
||||||
def is_win32():
|
def is_win32() -> bool:
|
||||||
return sys.platform.startswith("win32")
|
return sys.platform.startswith("win32")
|
||||||
|
|
||||||
|
|
||||||
def is_pypy():
|
def is_pypy() -> bool:
|
||||||
return hasattr(sys, "pypy_translation_info")
|
return hasattr(sys, "pypy_translation_info")
|
||||||
|
|
||||||
|
|
||||||
def is_mingw():
|
def is_mingw() -> bool:
|
||||||
return sysconfig.get_platform() == "mingw"
|
return sysconfig.get_platform() == "mingw"
|
||||||
|
|
||||||
|
|
||||||
class CachedProperty:
|
class CachedProperty:
|
||||||
def __init__(self, func):
|
def __init__(self, func: Callable[[Any], Any]) -> None:
|
||||||
self.func = func
|
self.func = func
|
||||||
|
|
||||||
def __get__(self, instance, cls=None):
|
def __get__(self, instance: Any, cls: type[Any] | None = None) -> Any:
|
||||||
result = instance.__dict__[self.func.__name__] = self.func(instance)
|
result = instance.__dict__[self.func.__name__] = self.func(instance)
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -22,4 +22,3 @@ and that the name of ICC shall not be used in advertising or publicity
|
||||||
pertaining to distribution of the software without specific, written
|
pertaining to distribution of the software without specific, written
|
||||||
prior permission. ICC makes no representations about the suitability
|
prior permission. ICC makes no representations about the suitability
|
||||||
of this software for any purpose.
|
of this software for any purpose.
|
||||||
|
|
||||||
|
|
BIN
Tests/icc/sGrey-v2-nano.icc
Normal file
Before Width: | Height: | Size: 578 B |
BIN
Tests/images/16_bit_binary_pgm.tiff
Normal file
BIN
Tests/images/2422.flc
Normal file
BIN
Tests/images/9bit.j2k
Normal file
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
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 95 KiB |
BIN
Tests/images/bc5u.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/bmp/q/rgb32h52.bmp
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
Tests/images/bmp/q/rgba32h56.bmp
Normal file
After Width: | Height: | Size: 32 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 |
Before Width: | Height: | Size: 298 KiB |
BIN
Tests/images/cmx3g8_wv_1998.260_0745_mcidas.tiff
Normal file
BIN
Tests/images/default_font_freetype.png
Normal file
After Width: | Height: | Size: 3.6 KiB |