Merge branch 'main' into master
|
@ -10,29 +10,29 @@ environment:
|
||||||
TEST_OPTIONS:
|
TEST_OPTIONS:
|
||||||
DEPLOY: YES
|
DEPLOY: YES
|
||||||
matrix:
|
matrix:
|
||||||
- PYTHON: C:/Python39
|
- PYTHON: C:/Python310
|
||||||
ARCHITECTURE: x86
|
ARCHITECTURE: x86
|
||||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||||
- PYTHON: C:/Python36-x64
|
- PYTHON: C:/Python37-x64
|
||||||
ARCHITECTURE: x64
|
ARCHITECTURE: x64
|
||||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||||
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/master.zip
|
- '%PYTHON%\%EXECUTABLE% --version'
|
||||||
|
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
|
||||||
- 7z x pillow-depends.zip -oc:\
|
- 7z x pillow-depends.zip -oc:\
|
||||||
- mv c:\pillow-depends-master c:\pillow-depends
|
- mv c:\pillow-depends-main c:\pillow-depends
|
||||||
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
|
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
|
||||||
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
|
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
|
||||||
- ..\pillow-depends\gs9540w32.exe /S
|
- ..\pillow-depends\gs9561w32.exe /S
|
||||||
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.54.0\bin;%PATH%
|
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.56.1\bin;%PATH%
|
||||||
- cd c:\pillow\winbuild\
|
- cd c:\pillow\winbuild\
|
||||||
- ps: |
|
- ps: |
|
||||||
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
|
c:\python37\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%
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pip install -U setuptools'
|
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- ps: |
|
- ps: |
|
||||||
|
@ -43,7 +43,7 @@ build_script:
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- cd c:\pillow
|
- cd c:\pillow
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov'
|
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout'
|
||||||
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
||||||
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
|
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
|
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
|
||||||
|
@ -84,7 +84,7 @@ deploy:
|
||||||
artifact: /.*egg|wheel/
|
artifact: /.*egg|wheel/
|
||||||
on:
|
on:
|
||||||
APPVEYOR_REPO_NAME: python-pillow/Pillow
|
APPVEYOR_REPO_NAME: python-pillow/Pillow
|
||||||
branch: master
|
branch: main
|
||||||
deploy: YES
|
deploy: YES
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# gather the coverage data
|
# gather the coverage data
|
||||||
pip3 install codecov
|
python3 -m pip install codecov
|
||||||
if [[ $MATRIX_DOCKER ]]; then
|
if [[ $MATRIX_DOCKER ]]; then
|
||||||
coverage xml --ignore-errors
|
coverage xml --ignore-errors
|
||||||
else
|
else
|
||||||
|
|
|
@ -19,7 +19,7 @@ set -e
|
||||||
|
|
||||||
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 libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
||||||
cmake imagemagick libharfbuzz-dev libfribidi-dev
|
cmake meson imagemagick libharfbuzz-dev libfribidi-dev
|
||||||
|
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python3 -m pip install --upgrade wheel
|
python3 -m pip install --upgrade wheel
|
||||||
|
@ -32,8 +32,7 @@ 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 test-image-results
|
python3 -m pip install test-image-results
|
||||||
# TODO Remove condition when numpy supports 3.10
|
python3 -m pip install numpy
|
||||||
if ! [ "$GHA_PYTHON_VERSION" == "3.10-dev" ]; then python3 -m pip install numpy ; fi
|
|
||||||
|
|
||||||
# PyQt5 doesn't support PyPy3
|
# PyQt5 doesn't support PyPy3
|
||||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||||
|
|
10
.github/CONTRIBUTING.md
vendored
|
@ -4,13 +4,13 @@ Bug fixes, feature additions, tests, documentation and more can be contributed v
|
||||||
|
|
||||||
## Bug fixes, feature additions, etc.
|
## Bug fixes, feature additions, etc.
|
||||||
|
|
||||||
Please send a pull request to the master branch. Please include [documentation](https://pillow.readthedocs.io) and [tests](../Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new), [Gitter](https://gitter.im/python-pillow/Pillow) or irc://irc.freenode.net#pil
|
Please send a pull request to the `main` branch. Please include [documentation](https://pillow.readthedocs.io) and [tests](../Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new), [Gitter](https://gitter.im/python-pillow/Pillow) or irc://irc.freenode.net#pil
|
||||||
|
|
||||||
- Fork the Pillow repository.
|
- Fork the Pillow repository.
|
||||||
- Create a branch from master.
|
- Create a branch from `main`.
|
||||||
- Develop bug fixes, features, tests, etc.
|
- Develop bug fixes, features, tests, etc.
|
||||||
- Run the test suite. You can enable GitHub Actions (https://github.com/MY-USERNAME/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
|
- Run the test suite. You can enable GitHub Actions (https://github.com/MY-USERNAME/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
|
||||||
- Create a pull request to pull the changes from your branch to the Pillow master.
|
- Create a pull request to pull the changes from your branch to the Pillow `main`.
|
||||||
|
|
||||||
### Guidelines
|
### Guidelines
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ Please send a pull request to the master branch. Please include [documentation](
|
||||||
- Provide tests for any newly added code.
|
- Provide tests for any newly added code.
|
||||||
- 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/master/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.
|
||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
|
@ -35,4 +35,4 @@ The best reproductions are self-contained scripts with minimal dependencies. If
|
||||||
|
|
||||||
## Security vulnerabilities
|
## Security vulnerabilities
|
||||||
|
|
||||||
Please see our [security policy](https://github.com/python-pillow/Pillow/blob/master/.github/SECURITY.md).
|
Please see our [security policy](https://github.com/python-pillow/Pillow/blob/main/.github/SECURITY.md).
|
||||||
|
|
1
.github/mergify.yml
vendored
|
@ -7,6 +7,7 @@ pull_request_rules:
|
||||||
- status-success=Test Successful
|
- status-success=Test Successful
|
||||||
- status-success=Docker Test Successful
|
- status-success=Docker Test Successful
|
||||||
- status-success=Windows Test Successful
|
- status-success=Windows Test Successful
|
||||||
|
- status-success=MinGW Test Successful
|
||||||
- status-success=continuous-integration/appveyor/pr
|
- status-success=continuous-integration/appveyor/pr
|
||||||
actions:
|
actions:
|
||||||
merge:
|
merge:
|
||||||
|
|
6
.github/workflows/cifuzz.yml
vendored
|
@ -1,4 +1,5 @@
|
||||||
name: CIFuzz
|
name: CIFuzz
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
|
@ -8,6 +9,7 @@ on:
|
||||||
paths:
|
paths:
|
||||||
- "**.c"
|
- "**.c"
|
||||||
- "**.h"
|
- "**.h"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Fuzzing:
|
Fuzzing:
|
||||||
|
@ -29,13 +31,13 @@ jobs:
|
||||||
language: python
|
language: python
|
||||||
dry-run: false
|
dry-run: false
|
||||||
- name: Upload New Crash
|
- name: Upload New Crash
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
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@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: steps.run.outcome == 'success'
|
if: steps.run.outcome == 'success'
|
||||||
with:
|
with:
|
||||||
name: crash
|
name: crash
|
||||||
|
|
19
.github/workflows/lint.yml
vendored
|
@ -1,6 +1,6 @@
|
||||||
name: Lint
|
name: Lint
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -10,15 +10,7 @@ jobs:
|
||||||
name: Lint
|
name: Lint
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: pip cache
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ~/.cache/pip
|
|
||||||
key: lint-pip-${{ hashFiles('**/setup.py') }}
|
|
||||||
restore-keys: |
|
|
||||||
lint-pip-
|
|
||||||
|
|
||||||
- name: pre-commit cache
|
- name: pre-commit cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
|
@ -29,9 +21,11 @@ jobs:
|
||||||
lint-pre-commit-
|
lint-pre-commit-
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: "3.10"
|
||||||
|
cache: pip
|
||||||
|
cache-dependency-path: "setup.py"
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
@ -45,4 +39,3 @@ jobs:
|
||||||
run: tox -e lint
|
run: tox -e lint
|
||||||
env:
|
env:
|
||||||
PRE_COMMIT_COLOR: always
|
PRE_COMMIT_COLOR: always
|
||||||
|
|
||||||
|
|
5
.github/workflows/release-drafter.yml
vendored
|
@ -4,14 +4,15 @@ on:
|
||||||
push:
|
push:
|
||||||
# branches to consider in the event; optional, defaults to all
|
# branches to consider in the event; optional, defaults to all
|
||||||
branches:
|
branches:
|
||||||
- master
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update_release_draft:
|
update_release_draft:
|
||||||
if: github.repository == 'python-pillow/Pillow'
|
if: github.repository == 'python-pillow/Pillow'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
# Drafts your next release notes as pull requests are merged into "master"
|
# Drafts your next release notes as pull requests are merged into "main"
|
||||||
- uses: release-drafter/release-drafter@v5
|
- uses: release-drafter/release-drafter@v5
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
27
.github/workflows/stale.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
name: Close stale issues
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "10 0 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
if: github.repository_owner == 'python-pillow'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Check issues"
|
||||||
|
uses: actions/stale@v5
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
only-labels: "Awaiting OP Action"
|
||||||
|
close-issue-message: "Closing this issue as no feedback has been received."
|
||||||
|
days-before-stale: 7
|
||||||
|
days-before-issue-close: 0
|
||||||
|
days-before-pr-close: -1
|
||||||
|
labels-to-remove-when-unstale: "Awaiting OP Action"
|
14
.github/workflows/test-docker.yml
vendored
|
@ -1,6 +1,6 @@
|
||||||
name: Test Docker
|
name: Test Docker
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -19,14 +19,16 @@ jobs:
|
||||||
amazon-2-amd64,
|
amazon-2-amd64,
|
||||||
arch,
|
arch,
|
||||||
centos-7-amd64,
|
centos-7-amd64,
|
||||||
centos-8-amd64,
|
centos-stream-8-amd64,
|
||||||
|
centos-stream-9-amd64,
|
||||||
debian-10-buster-x86,
|
debian-10-buster-x86,
|
||||||
fedora-33-amd64,
|
debian-11-bullseye-x86,
|
||||||
fedora-34-amd64,
|
fedora-35-amd64,
|
||||||
|
gentoo,
|
||||||
ubuntu-18.04-bionic-amd64,
|
ubuntu-18.04-bionic-amd64,
|
||||||
ubuntu-20.04-focal-amd64,
|
ubuntu-20.04-focal-amd64,
|
||||||
]
|
]
|
||||||
dockerTag: [master]
|
dockerTag: [main]
|
||||||
include:
|
include:
|
||||||
- docker: "ubuntu-20.04-focal-arm64v8"
|
- docker: "ubuntu-20.04-focal-arm64v8"
|
||||||
qemu-arch: "aarch64"
|
qemu-arch: "aarch64"
|
||||||
|
@ -38,7 +40,7 @@ jobs:
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
85
.github/workflows/test-mingw.yml
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
name: Test MinGW
|
||||||
|
|
||||||
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
mingw: ["MINGW32", "MINGW64"]
|
||||||
|
include:
|
||||||
|
- mingw: "MINGW32"
|
||||||
|
name: "MSYS2 MinGW 32-bit"
|
||||||
|
package: "mingw-w64-i686"
|
||||||
|
- mingw: "MINGW64"
|
||||||
|
name: "MSYS2 MinGW 64-bit"
|
||||||
|
package: "mingw-w64-x86_64"
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash.exe --login -eo pipefail "{0}"
|
||||||
|
env:
|
||||||
|
MSYSTEM: ${{ matrix.mingw }}
|
||||||
|
CHERE_INVOKING: 1
|
||||||
|
|
||||||
|
timeout-minutes: 30
|
||||||
|
name: ${{ matrix.name }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Pillow
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up shell
|
||||||
|
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pacman -S --noconfirm \
|
||||||
|
${{ matrix.package }}-python3-cffi \
|
||||||
|
${{ matrix.package }}-python3-numpy \
|
||||||
|
${{ matrix.package }}-python3-olefile \
|
||||||
|
${{ matrix.package }}-python3-pip \
|
||||||
|
${{ matrix.package }}-python-pyqt6 \
|
||||||
|
${{ matrix.package }}-python3-setuptools \
|
||||||
|
${{ matrix.package }}-freetype \
|
||||||
|
${{ matrix.package }}-gcc \
|
||||||
|
${{ matrix.package }}-ghostscript \
|
||||||
|
${{ matrix.package }}-lcms2 \
|
||||||
|
${{ matrix.package }}-libimagequant \
|
||||||
|
${{ matrix.package }}-libjpeg-turbo \
|
||||||
|
${{ matrix.package }}-libraqm \
|
||||||
|
${{ matrix.package }}-libtiff \
|
||||||
|
${{ matrix.package }}-libwebp \
|
||||||
|
${{ matrix.package }}-openjpeg2 \
|
||||||
|
subversion
|
||||||
|
|
||||||
|
python3 -m pip install pyroma pytest pytest-cov pytest-timeout
|
||||||
|
|
||||||
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
||||||
|
- name: Build Pillow
|
||||||
|
run: CFLAGS="-coverage" python3 -m pip install --global-option="build_ext" .
|
||||||
|
|
||||||
|
- name: Test Pillow
|
||||||
|
run: |
|
||||||
|
python3 selftest.py --installed
|
||||||
|
python3 -c "from PIL import Image"
|
||||||
|
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
|
||||||
|
|
||||||
|
- name: Upload coverage
|
||||||
|
run: |
|
||||||
|
python3 -m pip install codecov
|
||||||
|
bash <(curl -s https://codecov.io/bash) -F GHA_Windows
|
||||||
|
env:
|
||||||
|
CODECOV_NAME: ${{ matrix.name }}
|
||||||
|
|
||||||
|
success:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: MinGW Test Successful
|
||||||
|
steps:
|
||||||
|
- name: Success
|
||||||
|
run: echo MinGW Test Successful
|
13
.github/workflows/test-valgrind.yml
vendored
|
@ -11,6 +11,7 @@ on:
|
||||||
paths:
|
paths:
|
||||||
- "**.c"
|
- "**.c"
|
||||||
- "**.h"
|
- "**.h"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -22,12 +23,12 @@ jobs:
|
||||||
docker: [
|
docker: [
|
||||||
ubuntu-20.04-focal-amd64-valgrind,
|
ubuntu-20.04-focal-amd64-valgrind,
|
||||||
]
|
]
|
||||||
dockerTag: [master]
|
dockerTag: [main]
|
||||||
|
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
@ -42,11 +43,3 @@ jobs:
|
||||||
sudo chown -R 1000 $GITHUB_WORKSPACE
|
sudo chown -R 1000 $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
|
||||||
|
|
||||||
success:
|
|
||||||
needs: build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Valgrind Test Successful
|
|
||||||
steps:
|
|
||||||
- name: Success
|
|
||||||
run: echo Valgrind Test Successful
|
|
||||||
|
|
123
.github/workflows/test-windows.yml
vendored
|
@ -1,52 +1,44 @@
|
||||||
name: Test Windows
|
name: Test Windows
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: windows-2019
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10-dev"]
|
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||||
architecture: ["x86", "x64"]
|
architecture: ["x86", "x64"]
|
||||||
include:
|
include:
|
||||||
# PyPy3.6 only ships 32-bit binaries for Windows
|
|
||||||
- python-version: "pypy-3.6"
|
|
||||||
architecture: "x86"
|
|
||||||
# PyPy 7.3.4+ only ships 64-bit binaries for Windows
|
# PyPy 7.3.4+ only ships 64-bit binaries for Windows
|
||||||
- python-version: "pypy-3.7"
|
- python-version: "pypy-3.7"
|
||||||
architecture: "x64"
|
architecture: "x64"
|
||||||
|
- python-version: "pypy-3.8"
|
||||||
|
architecture: "x64"
|
||||||
|
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|
||||||
name: Python ${{ matrix.python-version }} ${{ matrix.architecture }}
|
name: Python ${{ matrix.python-version }} ${{ matrix.architecture }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Pillow
|
- name: Checkout Pillow
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Checkout cached dependencies
|
- name: Checkout cached dependencies
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: python-pillow/pillow-depends
|
repository: python-pillow/pillow-depends
|
||||||
path: winbuild\depends
|
path: winbuild\depends
|
||||||
|
|
||||||
- name: Cache pip
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ~\AppData\Local\pip\Cache
|
|
||||||
key:
|
|
||||||
${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.architecture }}-${{ hashFiles('**/.github/workflows/test-windows.yml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.architecture }}-
|
|
||||||
${{ runner.os }}-${{ matrix.python-version }}-
|
|
||||||
|
|
||||||
# sets env: pythonLocation
|
# sets env: pythonLocation
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
architecture: ${{ matrix.architecture }}
|
architecture: ${{ matrix.architecture }}
|
||||||
|
cache: pip
|
||||||
|
cache-dependency-path: ".github/workflows/test-windows.yml"
|
||||||
|
|
||||||
- name: Print build system information
|
- name: Print build system information
|
||||||
run: python .github/workflows/system-info.py
|
run: python .github/workflows/system-info.py
|
||||||
|
@ -60,8 +52,8 @@ jobs:
|
||||||
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
||||||
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
|
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
winbuild\depends\gs9540w32.exe /S
|
winbuild\depends\gs9561w32.exe /S
|
||||||
echo "C:\Program Files (x86)\gs\gs9.54.0\bin" >> $env:GITHUB_PATH
|
echo "C:\Program Files (x86)\gs\gs9.56.1\bin" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
xcopy /S /Y winbuild\depends\test_images\* Tests\images\
|
xcopy /S /Y winbuild\depends\test_images\* Tests\images\
|
||||||
|
|
||||||
|
@ -140,15 +132,16 @@ jobs:
|
||||||
- name: Build Pillow
|
- name: Build Pillow
|
||||||
run: |
|
run: |
|
||||||
$FLAGS=""
|
$FLAGS=""
|
||||||
if ('${{ github.event_name }}' -eq 'push') { $FLAGS="--disable-imagequant" }
|
if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS="--disable-imagequant" }
|
||||||
& winbuild\build\build_pillow.cmd $FLAGS install
|
& 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
|
||||||
|
|
||||||
# failing with PyPy3
|
# skip PyPy for speed
|
||||||
- name: Enable heap verification
|
- name: Enable heap verification
|
||||||
if: "!contains(matrix.python-version, 'pypy')"
|
if: "!contains(matrix.python-version, 'pypy')"
|
||||||
run: "& 'C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86\\gflags.exe' /p /enable $env:pythonLocation\\python.exe"
|
run: |
|
||||||
|
& reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
|
||||||
|
|
||||||
- name: Test Pillow
|
- name: Test Pillow
|
||||||
run: |
|
run: |
|
||||||
|
@ -163,7 +156,7 @@ jobs:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: errors
|
name: errors
|
||||||
|
@ -183,92 +176,20 @@ jobs:
|
||||||
|
|
||||||
- name: Build wheel
|
- name: Build wheel
|
||||||
id: wheel
|
id: wheel
|
||||||
if: "github.event_name == 'push'"
|
if: "github.event_name != 'pull_request'"
|
||||||
run: |
|
run: |
|
||||||
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ::set-output name=dist::dist-%%a
|
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ::set-output name=dist::dist-%%a
|
||||||
winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel
|
winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v3
|
||||||
if: "github.event_name == 'push'"
|
if: "github.event_name != 'pull_request'"
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.wheel.outputs.dist }}
|
name: ${{ steps.wheel.outputs.dist }}
|
||||||
path: dist\*.whl
|
path: dist\*.whl
|
||||||
|
|
||||||
msys:
|
|
||||||
runs-on: windows-2019
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
mingw: ["MINGW32", "MINGW64"]
|
|
||||||
include:
|
|
||||||
- mingw: "MINGW32"
|
|
||||||
name: "MSYS2 MinGW 32-bit"
|
|
||||||
package: "mingw-w64-i686"
|
|
||||||
- mingw: "MINGW64"
|
|
||||||
name: "MSYS2 MinGW 64-bit"
|
|
||||||
package: "mingw-w64-x86_64"
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash.exe --login -eo pipefail "{0}"
|
|
||||||
env:
|
|
||||||
MSYSTEM: ${{ matrix.mingw }}
|
|
||||||
CHERE_INVOKING: 1
|
|
||||||
|
|
||||||
timeout-minutes: 30
|
|
||||||
name: ${{ matrix.name }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Set up shell
|
|
||||||
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
pacman -S --noconfirm \
|
|
||||||
${{ matrix.package }}-python3-cffi \
|
|
||||||
${{ matrix.package }}-python3-numpy \
|
|
||||||
${{ matrix.package }}-python3-olefile \
|
|
||||||
${{ matrix.package }}-python3-pip \
|
|
||||||
${{ matrix.package }}-python3-pyqt5 \
|
|
||||||
${{ matrix.package }}-python3-setuptools \
|
|
||||||
${{ matrix.package }}-freetype \
|
|
||||||
${{ matrix.package }}-ghostscript \
|
|
||||||
${{ matrix.package }}-lcms2 \
|
|
||||||
${{ matrix.package }}-libimagequant \
|
|
||||||
${{ matrix.package }}-libjpeg-turbo \
|
|
||||||
${{ matrix.package }}-libraqm \
|
|
||||||
${{ matrix.package }}-libtiff \
|
|
||||||
${{ matrix.package }}-libwebp \
|
|
||||||
${{ matrix.package }}-openjpeg2 \
|
|
||||||
subversion
|
|
||||||
|
|
||||||
python3 -m pip install pyroma pytest pytest-cov
|
|
||||||
|
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
|
||||||
|
|
||||||
- name: Build Pillow
|
|
||||||
run: CFLAGS="-coverage" python3 setup.py build_ext install
|
|
||||||
|
|
||||||
- name: Test Pillow
|
|
||||||
run: |
|
|
||||||
python3 selftest.py --installed
|
|
||||||
python3 -c "from PIL import Image"
|
|
||||||
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
|
|
||||||
|
|
||||||
- name: Upload coverage
|
|
||||||
run: |
|
|
||||||
python3 -m pip install codecov
|
|
||||||
bash <(curl -s https://codecov.io/bash) -F GHA_Windows
|
|
||||||
env:
|
|
||||||
CODECOV_NAME: ${{ matrix.name }}
|
|
||||||
|
|
||||||
success:
|
success:
|
||||||
needs: [build, msys]
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Windows Test Successful
|
name: Windows Test Successful
|
||||||
steps:
|
steps:
|
||||||
|
|
41
.github/workflows/test.yml
vendored
|
@ -1,6 +1,6 @@
|
||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -9,54 +9,41 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [
|
os: [
|
||||||
|
"macos-latest",
|
||||||
"ubuntu-latest",
|
"ubuntu-latest",
|
||||||
"macOS-latest",
|
|
||||||
]
|
]
|
||||||
python-version: [
|
python-version: [
|
||||||
|
"pypy-3.8",
|
||||||
"pypy-3.7",
|
"pypy-3.7",
|
||||||
"pypy-3.6",
|
"3.10",
|
||||||
"3.10-dev",
|
|
||||||
"3.9",
|
"3.9",
|
||||||
"3.8",
|
"3.8",
|
||||||
"3.7",
|
"3.7",
|
||||||
"3.6",
|
|
||||||
]
|
]
|
||||||
include:
|
include:
|
||||||
- python-version: "3.6"
|
- python-version: "3.7"
|
||||||
PYTHONOPTIMIZE: 1
|
PYTHONOPTIMIZE: 1
|
||||||
REVERSE: "--reverse"
|
REVERSE: "--reverse"
|
||||||
- python-version: "3.7"
|
- python-version: "3.8"
|
||||||
PYTHONOPTIMIZE: 2
|
PYTHONOPTIMIZE: 2
|
||||||
# Include new variables for Codecov
|
# Include new variables for Codecov
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
codecov-flag: GHA_Ubuntu
|
codecov-flag: GHA_Ubuntu
|
||||||
- os: macOS-latest
|
- os: macos-latest
|
||||||
codecov-flag: GHA_macOS
|
codecov-flag: GHA_macOS
|
||||||
|
|
||||||
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@v2
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
cache: pip
|
||||||
- name: Get pip cache dir
|
cache-dependency-path: ".ci/*.sh"
|
||||||
id: pip-cache
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=dir::$(python3 -m pip cache dir)"
|
|
||||||
|
|
||||||
- name: pip cache
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pip-cache.outputs.dir }}
|
|
||||||
key:
|
|
||||||
${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.ci/*.sh') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ matrix.os }}-${{ matrix.python-version }}-
|
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
@ -97,16 +84,16 @@ jobs:
|
||||||
mkdir -p Tests/errors
|
mkdir -p Tests/errors
|
||||||
|
|
||||||
- name: Upload errors
|
- name: Upload errors
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: errors
|
name: errors
|
||||||
path: Tests/errors
|
path: Tests/errors
|
||||||
|
|
||||||
- name: Docs
|
- name: Docs
|
||||||
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.9
|
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.10
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install sphinx-copybutton sphinx-issues sphinx-removed-in sphinx-rtd-theme sphinxext-opengraph
|
python3 -m pip install furo sphinx-copybutton sphinx-issues sphinx-removed-in sphinxext-opengraph
|
||||||
make doccheck
|
make doccheck
|
||||||
|
|
||||||
- name: After success
|
- name: After success
|
||||||
|
|
26
.github/workflows/tidelift.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
name: Tidelift Align
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 2 * * *" # daily at 02:30 UTC
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/tidelift.yml"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/tidelift.yml"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
if: github.repository_owner == 'python-pillow'
|
||||||
|
name: Run Tidelift to ensure approved open source packages are in use
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Scan
|
||||||
|
uses: tidelift/alignment-action@main
|
||||||
|
env:
|
||||||
|
TIDELIFT_API_KEY: ${{ secrets.TIDELIFT_API_KEY }}
|
||||||
|
TIDELIFT_ORGANIZATION: team/aclark4life
|
||||||
|
TIDELIFT_PROJECT: pillow
|
1
.gitignore
vendored
|
@ -83,6 +83,7 @@ docs/_build/
|
||||||
Tests/images/README.md
|
Tests/images/README.md
|
||||||
Tests/images/crash_1.tif
|
Tests/images/crash_1.tif
|
||||||
Tests/images/crash_2.tif
|
Tests/images/crash_2.tif
|
||||||
|
Tests/images/crash-81154a65438ba5aaeca73fd502fa4850fbde60f8.tif
|
||||||
Tests/images/string_dimension.tiff
|
Tests/images/string_dimension.tiff
|
||||||
Tests/images/jpeg2000
|
Tests/images/jpeg2000
|
||||||
Tests/images/msp
|
Tests/images/msp
|
||||||
|
|
|
@ -1,43 +1,43 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: e3000ace2fd1fcb1c181bb7a8285f1f976bcbdc7 # frozen: 21.7b0
|
rev: 22.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: ["--target-version", "py36"]
|
args: ["--target-version", "py37"]
|
||||||
# Only .py files, until https://github.com/psf/black/issues/402 resolved
|
# Only .py files, until https://github.com/psf/black/issues/402 resolved
|
||||||
files: \.py$
|
files: \.py$
|
||||||
types: []
|
types: []
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/isort
|
- repo: https://github.com/PyCQA/isort
|
||||||
rev: fd5ba70665a37ec301a1f714ed09336048b3be63 # frozen: 5.9.3
|
rev: 5.10.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
|
|
||||||
- repo: https://github.com/asottile/yesqa
|
- repo: https://github.com/asottile/yesqa
|
||||||
rev: 644ede78511c02fc6f8e03e014cc1ddcfbf1e1f5 # frozen: v1.2.3
|
rev: v1.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: yesqa
|
- id: yesqa
|
||||||
|
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||||
rev: 3592548bbd98528887eeed63486cf6c9bae00b98 # frozen: v1.1.10
|
rev: v1.1.13
|
||||||
hooks:
|
hooks:
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
|
||||||
|
|
||||||
- repo: https://gitlab.com/pycqa/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: dcd740bc0ebaf2b3d43e59a0060d157c97de13f3 # frozen: 3.9.2
|
rev: 4.0.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
|
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||||
rev: 6f51a66bba59954917140ec2eeeaa4d5e630e6ce # frozen: v1.9.0
|
rev: v1.9.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: python-check-blanket-noqa
|
- 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: 38b88246ccc552bffaaf54259d064beeee434539 # frozen: v4.0.1
|
rev: v4.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
|
|
|
@ -1,2 +1,8 @@
|
||||||
|
version: 2
|
||||||
|
|
||||||
python:
|
python:
|
||||||
pip_install: true
|
install:
|
||||||
|
- method: pip
|
||||||
|
path: .
|
||||||
|
extra_requirements:
|
||||||
|
- docs
|
||||||
|
|
319
CHANGES.rst
|
@ -2,9 +2,300 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
8.4.0 (unreleased)
|
9.2.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Round lut values where necessary #6188
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Load before getting size in resize() #6190
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Load image before performing size calculations in thumbnail() #6186
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Deprecated PhotoImage.paste() box parameter #6178
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
9.1.0 (2022-04-01)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Add support for multiple component transformation to JPEG2000 #5500
|
||||||
|
[scaramallion, radarhere, hugovk]
|
||||||
|
|
||||||
|
- Fix loading FriBiDi on Alpine #6165
|
||||||
|
[nulano]
|
||||||
|
|
||||||
|
- Added setting for converting GIF P frames to RGB #6150
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow 1 mode images to be inverted #6034
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Raise ValueError when trying to save empty JPEG #6159
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Always save TIFF with contiguous planar configuration #5973
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Connected discontiguous polygon corners #5980
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Ensure Tkinter hook is activated for getimage() #6032
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use screencapture arguments to crop on macOS #6152
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not mark L mode JPEG as 1 bit in PDF #6151
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added support for reading I;16R TIFF images #6132
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- If an error occurs after creating a file, remove the file #6134
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed calling DisplayViewer or XVViewer without a title #6136
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Retain RGBA transparency when saving multiple GIF frames #6128
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Save additional ICO frames with other bit depths if supplied #6122
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Handle EXIF data truncated to just the header #6124
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added support for reading BMP images with RLE8 compression #6102
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support Python distributions where _tkinter is compiled in #6006
|
||||||
|
[lukegb]
|
||||||
|
|
||||||
|
- Added support for PPM arbitrary maxval #6119
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added BigTIFF reading #6097
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- When converting, clip I;16 to be unsigned, not signed #6112
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed loading L mode GIF with transparency #6086
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved handling of PPM header #5121
|
||||||
|
[Piolie, radarhere]
|
||||||
|
|
||||||
|
- Reset size when seeking away from "Large Thumbnail" MPO frame #6101
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Replace requirements.txt with extras #6072
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Added PyEncoder and support BLP saving #6069
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Handle TGA images with packets that cross scan lines #6087
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added FITS reading #6056
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Added rawmode argument to Image.getpalette() #6061
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed BUFR, GRIB and HDF5 stub saving #6071
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not automatically remove temporary ImageShow files on Unix #6045
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Correctly read JPEG compressed BLP images #4685
|
||||||
|
[Meithal, radarhere]
|
||||||
|
|
||||||
|
- Merged _MODE_CONV typ into ImageMode as typestr #6057
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Consider palette size when converting and in getpalette() #6060
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added enums #5954
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Ensure image is opaque after converting P to PA with RGB palette #6052
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Attach RGBA palettes from putpalette() when suitable #6054
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added get_photoshop_blocks() to parse Photoshop TIFF tag #6030
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Drop excess values in BITSPERSAMPLE #6041
|
||||||
|
[mikhail-iurkov]
|
||||||
|
|
||||||
|
- Added unpacker from RGBA;15 to RGB #6031
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Enable arm64 for MSVC on Windows #5811
|
||||||
|
[gaborkertesz-linaro, gaborkertesz]
|
||||||
|
|
||||||
|
- Keep IPython/Jupyter text/plain output stable #5891
|
||||||
|
[shamrin, radarhere]
|
||||||
|
|
||||||
|
- Raise an error when performing a negative crop #5972
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Deprecated show_file "file" argument in favour of "path" #5959
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed SPIDER images for use with Bio-formats library #5956
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Ensure duplicated file pointer is closed #5946
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added specific error if path coordinate type is incorrect #5942
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Return an empty bytestring from tobytes() for an empty image #5938
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Remove readonly from Image.__eq__ #5930
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
9.0.1 (2022-02-03)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- In show_file, use os.remove to remove temporary images. CVE-2022-24303 #6010
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Restrict builtins within lambdas for ImageMath.eval. CVE-2022-22817 #6009
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
9.0.0 (2022-01-02)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Restrict builtins for ImageMath.eval(). CVE-2022-22817 #5923
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Ensure JpegImagePlugin stops at the end of a truncated file #5921
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed ImagePath.Path array handling. CVE-2022-22815, CVE-2022-22816 #5920
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Remove consecutive duplicate tiles that only differ by their offset #5919
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved I;16 operations on big endian #5901
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Limit quantized palette to number of colors #5879
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed palette index for zeroed color in FASTOCTREE quantize #5869
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- When saving RGBA to GIF, make use of first transparent palette entry #5859
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Pass SAMPLEFORMAT to libtiff #5848
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added rounding when converting P and PA #5824
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved putdata() documentation and data handling #5910
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Exclude carriage return in PDF regex to help prevent ReDoS #5912
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Fixed freeing pointer in ImageDraw.Outline.transform #5909
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added ImageShow support for xdg-open #5897
|
||||||
|
[m-shinder, radarhere]
|
||||||
|
|
||||||
|
- Support 16-bit grayscale ImageQt conversion #5856
|
||||||
|
[cmbruns, radarhere]
|
||||||
|
|
||||||
|
- Convert subsequent GIF frames to RGB or RGBA #5857
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not prematurely return in ImageFile when saving to stdout #5665
|
||||||
|
[infmagic2047, radarhere]
|
||||||
|
|
||||||
|
- Added support for top right and bottom right TGA orientations #5829
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Corrected ICNS file length in header #5845
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Block tile TIFF tags when saving #5839
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added line width argument to polygon #5694
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not redeclare class each time when converting to NumPy #5844
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Only prevent repeated polygon pixels when drawing with transparency #5835
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Add support for pickling TrueType fonts #5826
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Only prefer command line tools SDK on macOS over default MacOSX SDK #5828
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Drop support for soon-EOL Python 3.6 #5768
|
||||||
|
[hugovk, nulano, radarhere]
|
||||||
|
|
||||||
|
- Fix compilation on 64-bit Termux #5793
|
||||||
|
[landfillbaby]
|
||||||
|
|
||||||
|
- Use title for display in ImageShow #5788
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Remove support for FreeType 2.7 and older #5777
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Fix for PyQt6 #5775
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Removed deprecated PILLOW_VERSION, Image.show command parameter, Image._showxv and ImageFile.raise_ioerror #5776
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
8.4.0 (2021-10-15)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Prefer global transparency in GIF when replacing with background color #5756
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added "exif" keyword argument to TIFF saving #5575
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Copy Python palette to new image in quantize() #5696
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Read ICO AND mask from end #5667
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Actually check the framesize in FliDecode.c #5659
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Determine JPEG2000 mode purely from ihdr header box #5654
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed using info dictionary when writing multiple APNG frames #5611
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Allow saving 1 and L mode TIFF with PhotometricInterpretation 0 #5655
|
- Allow saving 1 and L mode TIFF with PhotometricInterpretation 0 #5655
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
@ -59,12 +350,30 @@ Changelog (Pillow)
|
||||||
- Fixed ImageOps expand with tuple border on P image #5615
|
- Fixed ImageOps expand with tuple border on P image #5615
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Ensure TIFF RowsPerStrip is multiple of 8 for JPEG compression #5588
|
|
||||||
[kmilos, radarhere]
|
|
||||||
|
|
||||||
- Fixed error saving APNG with duplicate frames and different duration times #5609
|
- Fixed error saving APNG with duplicate frames and different duration times #5609
|
||||||
[thak1411, radarhere]
|
[thak1411, radarhere]
|
||||||
|
|
||||||
|
8.3.2 (2021-09-02)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- CVE-2021-23437 Raise ValueError if color specifier is too long
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Fix 6-byte OOB read in FliDecode
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Add support for Python 3.10 #5569, #5570
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Ensure TIFF ``RowsPerStrip`` is multiple of 8 for JPEG compression #5588
|
||||||
|
[kmilos, radarhere]
|
||||||
|
|
||||||
|
- Updates for ``ImagePalette`` channel order #5599
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Hide FriBiDi shim symbols to avoid conflict with real FriBiDi library #5651
|
||||||
|
[nulano]
|
||||||
|
|
||||||
8.3.1 (2021-07-06)
|
8.3.1 (2021-07-06)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -338,7 +647,7 @@ Changelog (Pillow)
|
||||||
- Changed Image.open formats parameter to be case-insensitive #5250
|
- Changed Image.open formats parameter to be case-insensitive #5250
|
||||||
[Piolie, radarhere]
|
[Piolie, radarhere]
|
||||||
|
|
||||||
- Deprecate Tk/Tcl 8.4, to be removed in Pillow 10 (2023-01-02) #5216
|
- Deprecate Tk/Tcl 8.4, to be removed in Pillow 10 (2023-07-01) #5216
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Added tk version to pilinfo #5226
|
- Added tk version to pilinfo #5226
|
||||||
|
|
2
LICENSE
|
@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
|
||||||
|
|
||||||
Pillow is the friendly PIL fork. It is
|
Pillow is the friendly PIL fork. It is
|
||||||
|
|
||||||
Copyright © 2010-2021 by Alex Clark and contributors
|
Copyright © 2010-2022 by Alex Clark and contributors
|
||||||
|
|
||||||
Like PIL, Pillow is licensed under the open source HPND License:
|
Like PIL, Pillow is licensed under the open source HPND License:
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
include *.c
|
include *.c
|
||||||
include *.h
|
include *.h
|
||||||
include *.in
|
include *.in
|
||||||
|
include *.lock
|
||||||
include *.md
|
include *.md
|
||||||
include *.py
|
include *.py
|
||||||
include *.rst
|
include *.rst
|
||||||
|
@ -9,6 +10,7 @@ include *.txt
|
||||||
include *.yaml
|
include *.yaml
|
||||||
include LICENSE
|
include LICENSE
|
||||||
include Makefile
|
include Makefile
|
||||||
|
include Pipfile
|
||||||
include tox.ini
|
include tox.ini
|
||||||
graft Tests
|
graft Tests
|
||||||
graft src
|
graft src
|
||||||
|
|
66
Makefile
|
@ -9,9 +9,11 @@ clean:
|
||||||
|
|
||||||
.PHONY: coverage
|
.PHONY: coverage
|
||||||
coverage:
|
coverage:
|
||||||
pytest -qq
|
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
|
||||||
|
python3 -m pytest -qq
|
||||||
rm -r htmlcov || true
|
rm -r htmlcov || true
|
||||||
coverage report
|
python3 -c "import coverage" > /dev/null 2>&1 || python3 -m pip install coverage
|
||||||
|
python3 -m coverage report
|
||||||
|
|
||||||
.PHONY: doc
|
.PHONY: doc
|
||||||
doc:
|
doc:
|
||||||
|
@ -33,33 +35,29 @@ help:
|
||||||
@echo "Welcome to Pillow development. Please use \`make <target>\` where <target> is one of"
|
@echo "Welcome to Pillow development. Please use \`make <target>\` where <target> is one of"
|
||||||
@echo " clean remove build products"
|
@echo " clean remove build products"
|
||||||
@echo " coverage run coverage test (in progress)"
|
@echo " coverage run coverage test (in progress)"
|
||||||
@echo " doc make html docs"
|
@echo " doc make HTML docs"
|
||||||
@echo " docserve run an http server on the docs directory"
|
@echo " docserve run an HTTP server on the docs directory"
|
||||||
@echo " html to make standalone HTML files"
|
@echo " html to make standalone HTML files"
|
||||||
@echo " inplace make inplace extension"
|
@echo " inplace make inplace extension"
|
||||||
@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 " install-req install documentation and test dependencies"
|
|
||||||
@echo " install-venv (deprecated) install in virtualenv"
|
|
||||||
@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 Black and isort 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"
|
||||||
@echo " upload build and upload sdists to PyPI"
|
|
||||||
@echo " upload-test build and upload sdists to test.pythonpackages.com"
|
|
||||||
|
|
||||||
.PHONY: inplace
|
.PHONY: inplace
|
||||||
inplace: clean
|
inplace: clean
|
||||||
python3 setup.py develop build_ext --inplace
|
python3 -m pip install -e --global-option="build_ext" --global-option="--inplace" .
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
install:
|
install:
|
||||||
python3 setup.py install
|
python3 -m pip install .
|
||||||
python3 selftest.py
|
python3 selftest.py
|
||||||
|
|
||||||
.PHONY: install-coverage
|
.PHONY: install-coverage
|
||||||
install-coverage:
|
install-coverage:
|
||||||
CFLAGS="-coverage -Werror=implicit-function-declaration" python3 setup.py build_ext install
|
CFLAGS="-coverage -Werror=implicit-function-declaration" python3 -m pip install --global-option="build_ext" .
|
||||||
python3 selftest.py
|
python3 selftest.py
|
||||||
|
|
||||||
.PHONY: debug
|
.PHONY: debug
|
||||||
|
@ -68,58 +66,52 @@ debug:
|
||||||
# for our stuff, kills optimization, and redirects to dev null so we
|
# for our stuff, kills optimization, and redirects to dev null so we
|
||||||
# see any build failures.
|
# see any build failures.
|
||||||
make clean > /dev/null
|
make clean > /dev/null
|
||||||
CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null
|
CFLAGS='-g -O0' python3 -m pip install --global-option="build_ext" . > /dev/null
|
||||||
|
|
||||||
.PHONY: install-req
|
|
||||||
install-req:
|
|
||||||
python3 -m pip install -r requirements.txt
|
|
||||||
|
|
||||||
.PHONY: install-venv
|
|
||||||
install-venv:
|
|
||||||
echo "'install-venv' is deprecated and will be removed in a future Pillow release"
|
|
||||||
virtualenv .
|
|
||||||
bin/pip install -r requirements.txt
|
|
||||||
|
|
||||||
.PHONY: release-test
|
.PHONY: release-test
|
||||||
release-test:
|
release-test:
|
||||||
$(MAKE) install-req
|
python3 -m pip install -e .[tests]
|
||||||
python3 setup.py develop
|
|
||||||
python3 selftest.py
|
python3 selftest.py
|
||||||
python3 -m pytest Tests
|
python3 -m pytest Tests
|
||||||
python3 setup.py install
|
python3 -m pip install .
|
||||||
-rm dist/*.egg
|
-rm dist/*.egg
|
||||||
-rmdir dist
|
-rmdir dist
|
||||||
python3 -m pytest -qq
|
python3 -m pytest -qq
|
||||||
check-manifest
|
python3 -m check_manifest
|
||||||
pyroma .
|
python3 -m pyroma .
|
||||||
$(MAKE) readme
|
$(MAKE) readme
|
||||||
|
|
||||||
.PHONY: sdist
|
.PHONY: sdist
|
||||||
sdist:
|
sdist:
|
||||||
python3 setup.py sdist --format=gztar
|
python3 -m build --help > /dev/null 2>&1 || python3 -m pip install build
|
||||||
|
python3 -m build --sdist
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
pytest -qq
|
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
|
||||||
|
python3 -m pytest -qq
|
||||||
|
|
||||||
.PHONY: valgrind
|
.PHONY: valgrind
|
||||||
valgrind:
|
valgrind:
|
||||||
python3 -c "import pytest_valgrind" || pip3 install pytest-valgrind
|
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
|
||||||
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
|
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
|
||||||
--log-file=/tmp/valgrind-output \
|
--log-file=/tmp/valgrind-output \
|
||||||
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
||||||
|
|
||||||
.PHONY: readme
|
.PHONY: readme
|
||||||
readme:
|
readme:
|
||||||
python3 setup.py --long-description | markdown2 > .long-description.html && open .long-description.html
|
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2
|
||||||
|
python3 -m markdown2 README.md > .long-description.html && open .long-description.html
|
||||||
|
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
tox --help > /dev/null || python3 -m pip install tox
|
python3 -c "import tox" > /dev/null 2>&1 || python3 -m pip install tox
|
||||||
tox -e lint
|
python3 -m tox -e lint
|
||||||
|
|
||||||
.PHONY: lint-fix
|
.PHONY: lint-fix
|
||||||
lint-fix:
|
lint-fix:
|
||||||
black --target-version py36 .
|
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
|
||||||
isort .
|
python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort
|
||||||
|
python3 -m black --target-version py37 .
|
||||||
|
python3 -m isort .
|
||||||
|
|
22
Pipfile
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[[source]]
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
black = "*"
|
||||||
|
check-manifest = "*"
|
||||||
|
coverage = "*"
|
||||||
|
defusedxml = "*"
|
||||||
|
packaging = "*"
|
||||||
|
markdown2 = "*"
|
||||||
|
olefile = "*"
|
||||||
|
pyroma = "*"
|
||||||
|
pytest = "*"
|
||||||
|
pytest-cov = "*"
|
||||||
|
pytest-timeout = "*"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.9"
|
324
Pipfile.lock
generated
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "e5cad23bf4187647d53b613a64dc4792b7064bf86b08dfb5737580e32943f54d"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.9"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"attrs": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
|
||||||
|
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||||
|
"version": "==21.2.0"
|
||||||
|
},
|
||||||
|
"black": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3",
|
||||||
|
"sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==21.12b0"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1aaadcd69338252ade4f7ec1265e1a19184bf916d84c9b7df095f423948cb89f",
|
||||||
|
"sha256:21b7ebbd1b22499c4dac536abc7606696ea4d909fd755e00f09f3c0f2c05e3c8"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==0.7.0"
|
||||||
|
},
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
|
||||||
|
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
|
||||||
|
],
|
||||||
|
"version": "==2021.10.8"
|
||||||
|
},
|
||||||
|
"charset-normalizer": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721",
|
||||||
|
"sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3'",
|
||||||
|
"version": "==2.0.9"
|
||||||
|
},
|
||||||
|
"check-manifest": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:365c94d65de4c927d9d8b505371d08ee19f9f369c86b9ac3db97c2754c827c95",
|
||||||
|
"sha256:56dadd260a9c7d550b159796d2894b6d0bcc176a94cbc426d9bb93e5e48d12ce"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.47"
|
||||||
|
},
|
||||||
|
"click": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3",
|
||||||
|
"sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==8.0.3"
|
||||||
|
},
|
||||||
|
"coverage": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0",
|
||||||
|
"sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd",
|
||||||
|
"sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884",
|
||||||
|
"sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48",
|
||||||
|
"sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76",
|
||||||
|
"sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0",
|
||||||
|
"sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64",
|
||||||
|
"sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685",
|
||||||
|
"sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47",
|
||||||
|
"sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d",
|
||||||
|
"sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840",
|
||||||
|
"sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f",
|
||||||
|
"sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971",
|
||||||
|
"sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c",
|
||||||
|
"sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a",
|
||||||
|
"sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de",
|
||||||
|
"sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17",
|
||||||
|
"sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4",
|
||||||
|
"sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521",
|
||||||
|
"sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57",
|
||||||
|
"sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b",
|
||||||
|
"sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282",
|
||||||
|
"sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644",
|
||||||
|
"sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475",
|
||||||
|
"sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d",
|
||||||
|
"sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da",
|
||||||
|
"sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953",
|
||||||
|
"sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2",
|
||||||
|
"sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e",
|
||||||
|
"sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c",
|
||||||
|
"sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc",
|
||||||
|
"sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64",
|
||||||
|
"sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74",
|
||||||
|
"sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617",
|
||||||
|
"sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3",
|
||||||
|
"sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d",
|
||||||
|
"sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa",
|
||||||
|
"sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739",
|
||||||
|
"sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8",
|
||||||
|
"sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8",
|
||||||
|
"sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781",
|
||||||
|
"sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58",
|
||||||
|
"sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9",
|
||||||
|
"sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c",
|
||||||
|
"sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd",
|
||||||
|
"sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e",
|
||||||
|
"sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==6.2"
|
||||||
|
},
|
||||||
|
"defusedxml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
|
||||||
|
"sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.7.1"
|
||||||
|
},
|
||||||
|
"docutils": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c",
|
||||||
|
"sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||||
|
"version": "==0.18.1"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
||||||
|
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3'",
|
||||||
|
"version": "==3.3"
|
||||||
|
},
|
||||||
|
"iniconfig": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
|
||||||
|
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
|
||||||
|
],
|
||||||
|
"version": "==1.1.1"
|
||||||
|
},
|
||||||
|
"markdown2": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8f4ac8d9a124ab408c67361090ed512deda746c04362c36c2ec16190c720c2b0",
|
||||||
|
"sha256:91113caf23aa662570fe21984f08fe74f814695c0a0ea8e863a8b4c4f63f9f6e"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.4.2"
|
||||||
|
},
|
||||||
|
"mypy-extensions": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
|
||||||
|
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
|
||||||
|
],
|
||||||
|
"version": "==0.4.3"
|
||||||
|
},
|
||||||
|
"olefile": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.46"
|
||||||
|
},
|
||||||
|
"packaging": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
|
||||||
|
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==21.3"
|
||||||
|
},
|
||||||
|
"pathspec": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
|
||||||
|
"sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
|
||||||
|
],
|
||||||
|
"version": "==0.9.0"
|
||||||
|
},
|
||||||
|
"pep517": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0",
|
||||||
|
"sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161"
|
||||||
|
],
|
||||||
|
"version": "==0.12.0"
|
||||||
|
},
|
||||||
|
"platformdirs": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2",
|
||||||
|
"sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==2.4.0"
|
||||||
|
},
|
||||||
|
"pluggy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
|
||||||
|
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==1.0.0"
|
||||||
|
},
|
||||||
|
"py": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719",
|
||||||
|
"sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||||
|
"version": "==1.11.0"
|
||||||
|
},
|
||||||
|
"pygments": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380",
|
||||||
|
"sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
|
"version": "==2.10.0"
|
||||||
|
},
|
||||||
|
"pyparsing": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4",
|
||||||
|
"sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==3.0.6"
|
||||||
|
},
|
||||||
|
"pyroma": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0fba67322913026091590e68e0d9e0d4fbd6420fcf34d315b2ad6985ab104d65",
|
||||||
|
"sha256:f8c181e0d5d292f11791afc18f7d0218a83c85cf64d6f8fb1571ce9d29a24e4a"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.2"
|
||||||
|
},
|
||||||
|
"pytest": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
|
||||||
|
"sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==6.2.5"
|
||||||
|
},
|
||||||
|
"pytest-cov": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6",
|
||||||
|
"sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.0.0"
|
||||||
|
},
|
||||||
|
"pytest-timeout": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:e6f98b54dafde8d70e4088467ff621260b641eb64895c4195b6e5c8f45638112",
|
||||||
|
"sha256:fe9c3d5006c053bb9e062d60f641e6a76d6707aedb645350af9593e376fcc717"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.0.2"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
|
||||||
|
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||||
|
"version": "==2.26.0"
|
||||||
|
},
|
||||||
|
"setuptools": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:5ec2bbb534ed160b261acbbdd1b463eb3cf52a8d223d96a8ab9981f63798e85c",
|
||||||
|
"sha256:75fd345a47ce3d79595b27bf57e6f49c2ca7904f3c7ce75f8a87012046c86b0b"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==60.0.0"
|
||||||
|
},
|
||||||
|
"toml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||||
|
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
|
||||||
|
"version": "==0.10.2"
|
||||||
|
},
|
||||||
|
"tomli": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f",
|
||||||
|
"sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==1.2.3"
|
||||||
|
},
|
||||||
|
"typing-extensions": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e",
|
||||||
|
"sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==4.0.1"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
|
||||||
|
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||||
|
"version": "==1.26.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
32
README.md
|
@ -1,5 +1,5 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img width="248" height="250" src="https://raw.githubusercontent.com/python-pillow/pillow-logo/master/pillow-logo-248x250.png" alt="Pillow logo">
|
<img width="248" height="250" src="https://raw.githubusercontent.com/python-pillow/pillow-logo/main/pillow-logo-248x250.png" alt="Pillow logo">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Pillow
|
# Pillow
|
||||||
|
@ -24,30 +24,36 @@ As of 2019, Pillow development is
|
||||||
<tr>
|
<tr>
|
||||||
<th>tests</th>
|
<th>tests</th>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint"><img
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/lint.yml"><img
|
||||||
alt="GitHub Actions build status (Lint)"
|
alt="GitHub Actions build status (Lint)"
|
||||||
src="https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg"></a>
|
||||||
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3ATest"><img
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test.yml"><img
|
||||||
alt="GitHub Actions build status (Test Linux and macOS)"
|
alt="GitHub Actions build status (Test Linux and macOS)"
|
||||||
src="https://github.com/python-pillow/Pillow/workflows/Test/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Test/badge.svg"></a>
|
||||||
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Windows%22"><img
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml"><img
|
||||||
alt="GitHub Actions build status (Test Windows)"
|
alt="GitHub Actions build status (Test Windows)"
|
||||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg"></a>
|
||||||
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Docker%22"><img
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml"><img
|
||||||
|
alt="GitHub Actions build status (Test MinGW)"
|
||||||
|
src="https://github.com/python-pillow/Pillow/workflows/Test%20MinGW/badge.svg"></a>
|
||||||
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml"><img
|
||||||
alt="GitHub Actions build status (Test Docker)"
|
alt="GitHub Actions build status (Test Docker)"
|
||||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
|
||||||
<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/master.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-wheels/actions"><img
|
||||||
alt="GitHub Actions wheels build status (Wheels)"
|
alt="GitHub Actions wheels build status (Wheels)"
|
||||||
src="https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg"></a>
|
src="https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg"></a>
|
||||||
<a href="https://travis-ci.com/github/python-pillow/pillow-wheels"><img
|
<a href="https://app.travis-ci.com/github/python-pillow/pillow-wheels"><img
|
||||||
alt="Travis CI wheels build status (aarch64)"
|
alt="Travis CI wheels build status (aarch64)"
|
||||||
src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/master.svg?label=aarch64%20wheels"></a>
|
src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels"></a>
|
||||||
<a href="https://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/master/graph/badge.svg"></a>
|
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
||||||
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml"><img
|
||||||
|
alt="Tidelift Align"
|
||||||
|
src="https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml/badge.svg"></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -93,12 +99,12 @@ The core image library is designed for fast access to data stored in a few basic
|
||||||
- [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.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/master/.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/master/CHANGES.rst)
|
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
|
||||||
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork)
|
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
|
||||||
|
|
||||||
## Report a Vulnerability
|
## Report a Vulnerability
|
||||||
|
|
||||||
|
|
30
RELEASING.md
|
@ -8,8 +8,8 @@ information about how the version numbers line up with releases.
|
||||||
Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
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 `master` 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 `master` 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 of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI and GitHub Actions.
|
||||||
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
|
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||||
* [ ] Update `CHANGES.rst`.
|
* [ ] Update `CHANGES.rst`.
|
||||||
|
@ -24,13 +24,13 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
* [ ] Create and check source distribution:
|
* [ ] Create and check source distribution:
|
||||||
```bash
|
```bash
|
||||||
make sdist
|
make sdist
|
||||||
twine check dist/*
|
python3 -m twine check --strict dist/*
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions)
|
* [ ] 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.:
|
* [ ] Check and upload all binaries and source distributions e.g.:
|
||||||
```bash
|
```bash
|
||||||
twine check dist/*
|
python3 -m twine check --strict dist/*
|
||||||
twine upload dist/Pillow-5.2.0*
|
python3 -m twine upload dist/Pillow-5.2.0*
|
||||||
```
|
```
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
||||||
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py`
|
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py`
|
||||||
|
@ -39,13 +39,13 @@ 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 `master` branch.
|
* [ ] Make necessary changes in `main` branch.
|
||||||
* [ ] Update `CHANGES.rst`.
|
* [ ] 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
|
||||||
```
|
```
|
||||||
* [ ] Cherry pick individual commits from `master` branch to release branch e.g. `5.2.x`, then `git push`.
|
* [ ] Cherry pick individual commits from `main` branch to release branch e.g. `5.2.x`, then `git push`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,13 +61,13 @@ Released as needed for security, installation or critical bug fixes.
|
||||||
* [ ] Create and check source distribution:
|
* [ ] Create and check source distribution:
|
||||||
```bash
|
```bash
|
||||||
make sdist
|
make sdist
|
||||||
twine check dist/*
|
python3 -m twine check --strict dist/*
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions)
|
* [ ] 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.:
|
* [ ] Check and upload all binaries and source distributions e.g.:
|
||||||
```bash
|
```bash
|
||||||
twine check dist/*
|
python3 -m twine check --strict dist/*
|
||||||
twine upload dist/Pillow-5.2.1*
|
python3 -m twine upload dist/Pillow-5.2.1*
|
||||||
```
|
```
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ Released as needed for security, installation or critical bug fixes.
|
||||||
Released as needed privately to individual vendors for critical security-related bug fixes.
|
Released as needed privately to individual vendors for critical security-related bug fixes.
|
||||||
|
|
||||||
* [ ] Prepare patch for all versions that will get a fix. Test against local installations.
|
* [ ] Prepare patch for all versions that will get a fix. Test against local installations.
|
||||||
* [ ] Commit against master, cherry pick to affected release branches.
|
* [ ] Commit against `main`, cherry pick to affected release branches.
|
||||||
* [ ] Run local test matrix on each release & Python version.
|
* [ ] Run local test matrix on each release & Python version.
|
||||||
* [ ] Privately send to distros.
|
* [ ] Privately send to distros.
|
||||||
* [ ] Run pre-release check via `make release-test`
|
* [ ] Run pre-release check via `make release-test`
|
||||||
|
@ -91,9 +91,9 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
* [ ] Create and check source distribution:
|
* [ ] Create and check source distribution:
|
||||||
```bash
|
```bash
|
||||||
make sdist
|
make sdist
|
||||||
twine check dist/*
|
python3 -m twine check --strict dist/*
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions)
|
* [ ] 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)
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
||||||
|
|
||||||
## Binary Distributions
|
## Binary Distributions
|
||||||
|
|
|
@ -4,5 +4,5 @@ import sys
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
if sys.maxsize < 2 ** 32:
|
if sys.maxsize < 2**32:
|
||||||
im = Image.new("L", (999999, 999999), 0)
|
im = Image.new("L", (999999, 999999), 0)
|
||||||
|
|
|
@ -61,8 +61,8 @@ repro_copy = (
|
||||||
|
|
||||||
|
|
||||||
for path in repro_ss2 + repro_lc + repro_advance + repro_brun + repro_copy:
|
for path in repro_ss2 + repro_lc + repro_advance + repro_brun + repro_copy:
|
||||||
im = Image.open(path)
|
with Image.open(path) as im:
|
||||||
try:
|
try:
|
||||||
im.load()
|
im.load()
|
||||||
except Exception as msg:
|
except Exception as msg:
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
|
@ -19,8 +19,8 @@ from PIL import Image
|
||||||
repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2")
|
repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2")
|
||||||
|
|
||||||
for path in repro:
|
for path in repro:
|
||||||
im = Image.open(path)
|
with Image.open(path) as im:
|
||||||
try:
|
try:
|
||||||
im.load()
|
im.load()
|
||||||
except Exception as msg:
|
except Exception as msg:
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
|
@ -23,7 +23,7 @@ YDIM = 32769
|
||||||
XDIM = 48000
|
XDIM = 48000
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system")
|
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
||||||
|
|
||||||
|
|
||||||
def _write_png(tmp_path, xdim, ydim):
|
def _write_png(tmp_path, xdim, ydim):
|
||||||
|
|
|
@ -19,7 +19,7 @@ YDIM = 32769
|
||||||
XDIM = 48000
|
XDIM = 48000
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system")
|
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
|
||||||
|
|
||||||
|
|
||||||
def _write_png(tmp_path, xdim, ydim):
|
def _write_png(tmp_path, xdim, ydim):
|
||||||
|
|
|
@ -30,7 +30,6 @@ if os.environ.get("SHOW_ERRORS", None):
|
||||||
a.show()
|
a.show()
|
||||||
b.show()
|
b.show()
|
||||||
|
|
||||||
|
|
||||||
elif "GITHUB_ACTIONS" in os.environ:
|
elif "GITHUB_ACTIONS" in os.environ:
|
||||||
HAS_UPLOADER = True
|
HAS_UPLOADER = True
|
||||||
|
|
||||||
|
@ -44,7 +43,6 @@ elif "GITHUB_ACTIONS" in os.environ:
|
||||||
b.save(os.path.join(tmpdir, "b.png"))
|
b.save(os.path.join(tmpdir, "b.png"))
|
||||||
return tmpdir
|
return tmpdir
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
import test_image_results
|
import test_image_results
|
||||||
|
@ -326,7 +324,7 @@ def is_mingw():
|
||||||
return sysconfig.get_platform() == "mingw"
|
return sysconfig.get_platform() == "mingw"
|
||||||
|
|
||||||
|
|
||||||
class cached_property:
|
class CachedProperty:
|
||||||
def __init__(self, func):
|
def __init__(self, func):
|
||||||
self.func = func
|
self.func = func
|
||||||
|
|
||||||
|
|
BIN
Tests/images/16bit.r.tif
Normal file
BIN
Tests/images/balloon_eciRGBv2_aware.jp2
Normal file
BIN
Tests/images/bitmap_font_stroke_basic.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
Tests/images/bitmap_font_stroke_raqm.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
Tests/images/blp/blp1_jpeg.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
Tests/images/crash-5762152299364352.fli
Normal file
BIN
Tests/images/cross_scan_line.png
Normal file
After Width: | Height: | Size: 71 B |
BIN
Tests/images/cross_scan_line.tga
Normal file
Before Width: | Height: | Size: 3.0 KiB |
BIN
Tests/images/different_transparency_merged.png
Normal file
After Width: | Height: | Size: 333 B |
BIN
Tests/images/dispose_bgnd_rgba.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/dispose_bgnd_transparency.gif
Normal file
After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 17 KiB |
BIN
Tests/images/dispose_none_load_end_second.png
Normal file
After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 1.0 KiB |
BIN
Tests/images/dispose_prev_first_frame_seeked.png
Normal file
After Width: | Height: | Size: 208 B |
BIN
Tests/images/hopper_bigtiff.tif
Normal file
BIN
Tests/images/hopper_mask.ico
Normal file
After Width: | Height: | Size: 262 B |
BIN
Tests/images/hopper_mask.png
Normal file
After Width: | Height: | Size: 208 B |
BIN
Tests/images/hopper_rle8.bmp
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
Tests/images/hopper_rle8_row_overflow.bmp
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
Tests/images/imagedraw/discontiguous_corners_polygon.png
Normal file
After Width: | Height: | Size: 486 B |
BIN
Tests/images/imagedraw/triangle_right_width.png
Normal file
After Width: | Height: | Size: 472 B |
BIN
Tests/images/imagedraw/triangle_right_width_no_fill.png
Normal file
After Width: | Height: | Size: 479 B |
BIN
Tests/images/imagedraw_polygon_translucent.png
Normal file
After Width: | Height: | Size: 385 B |
Before Width: | Height: | Size: 950 B |
BIN
Tests/images/missing_background_first_frame.png
Normal file
After Width: | Height: | Size: 382 B |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
Tests/images/no_palette.gif
Normal file
After Width: | Height: | Size: 48 B |
BIN
Tests/images/no_palette_with_background.gif
Normal file
After Width: | Height: | Size: 54 B |
BIN
Tests/images/no_palette_with_transparency.gif
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
Tests/images/pal8_offset.bmp
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
Tests/images/palette_negative.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
Tests/images/palette_sepia.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
Tests/images/palette_wedge.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
Tests/images/rgb32rle_bottom_right.tga
Normal file
BIN
Tests/images/rgb32rle_top_right.tga
Normal file
BIN
Tests/images/tiff_wrong_bits_per_sample_2.tiff
Normal file
BIN
Tests/images/timeout-6646305047838720
Normal file
|
@ -22,7 +22,7 @@ for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
|
||||||
fuzzer_basename=$(basename -s .py $fuzzer)
|
fuzzer_basename=$(basename -s .py $fuzzer)
|
||||||
fuzzer_package=${fuzzer_basename}.pkg
|
fuzzer_package=${fuzzer_basename}.pkg
|
||||||
pyinstaller \
|
pyinstaller \
|
||||||
--add-binary /usr/local/lib/libjpeg.so.9:. \
|
--add-binary /usr/local/lib/libjpeg.so.62.3.0:. \
|
||||||
--add-binary /usr/local/lib/libfreetype.so.6:. \
|
--add-binary /usr/local/lib/libfreetype.so.6:. \
|
||||||
--add-binary /usr/local/lib/liblcms2.so.2:. \
|
--add-binary /usr/local/lib/liblcms2.so.2:. \
|
||||||
--add-binary /usr/local/lib/libopenjp2.so.7:. \
|
--add-binary /usr/local/lib/libopenjp2.so.7:. \
|
||||||
|
|
|
@ -14,10 +14,13 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import atheris_no_libfuzzer as atheris
|
import atheris
|
||||||
import fuzzers
|
|
||||||
|
with atheris.instrument_imports():
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import fuzzers
|
||||||
|
|
||||||
|
|
||||||
def TestOneInput(data):
|
def TestOneInput(data):
|
||||||
|
@ -26,13 +29,12 @@ def TestOneInput(data):
|
||||||
except Exception:
|
except Exception:
|
||||||
# We're catching all exceptions because Pillow's exceptions are
|
# We're catching all exceptions because Pillow's exceptions are
|
||||||
# directly inheriting from Exception.
|
# directly inheriting from Exception.
|
||||||
return
|
pass
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
fuzzers.enable_decompressionbomb_error()
|
fuzzers.enable_decompressionbomb_error()
|
||||||
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
|
atheris.Setup(sys.argv, TestOneInput)
|
||||||
atheris.Fuzz()
|
atheris.Fuzz()
|
||||||
fuzzers.disable_decompressionbomb_error()
|
fuzzers.disable_decompressionbomb_error()
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,13 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import atheris_no_libfuzzer as atheris
|
import atheris
|
||||||
import fuzzers
|
|
||||||
|
with atheris.instrument_imports():
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import fuzzers
|
||||||
|
|
||||||
|
|
||||||
def TestOneInput(data):
|
def TestOneInput(data):
|
||||||
|
@ -26,13 +29,12 @@ def TestOneInput(data):
|
||||||
except Exception:
|
except Exception:
|
||||||
# We're catching all exceptions because Pillow's exceptions are
|
# We're catching all exceptions because Pillow's exceptions are
|
||||||
# directly inheriting from Exception.
|
# directly inheriting from Exception.
|
||||||
return
|
pass
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
fuzzers.enable_decompressionbomb_error()
|
fuzzers.enable_decompressionbomb_error()
|
||||||
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
|
atheris.Setup(sys.argv, TestOneInput)
|
||||||
atheris.Fuzz()
|
atheris.Fuzz()
|
||||||
fuzzers.disable_decompressionbomb_error()
|
fuzzers.disable_decompressionbomb_error()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import warnings
|
||||||
import pytest
|
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
@ -20,16 +19,14 @@ def test_bad():
|
||||||
either"""
|
either"""
|
||||||
for f in get_files("b"):
|
for f in get_files("b"):
|
||||||
|
|
||||||
with pytest.warns(None) as record:
|
# Assert that there is no unclosed file warning
|
||||||
|
with warnings.catch_warnings():
|
||||||
try:
|
try:
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
im.load()
|
im.load()
|
||||||
except Exception: # as msg:
|
except Exception: # as msg:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Assert that there is no unclosed file warning
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_questionable():
|
def test_questionable():
|
||||||
"""These shouldn't crash/dos, but it's not well defined that these
|
"""These shouldn't crash/dos, but it's not well defined that these
|
||||||
|
@ -43,6 +40,7 @@ def test_questionable():
|
||||||
"rgb32fakealpha.bmp",
|
"rgb32fakealpha.bmp",
|
||||||
"rgb24largepal.bmp",
|
"rgb24largepal.bmp",
|
||||||
"pal8os2sp.bmp",
|
"pal8os2sp.bmp",
|
||||||
|
"pal8rletrns.bmp",
|
||||||
"rgb32bf-xbgr.bmp",
|
"rgb32bf-xbgr.bmp",
|
||||||
]
|
]
|
||||||
for f in get_files("q"):
|
for f in get_files("q"):
|
||||||
|
|
|
@ -25,7 +25,7 @@ def box_blur(image, radius=1, n=1):
|
||||||
return image._new(image.im.box_blur(radius, n))
|
return image._new(image.im.box_blur(radius, n))
|
||||||
|
|
||||||
|
|
||||||
def assertImage(im, data, delta=0):
|
def assert_image(im, data, delta=0):
|
||||||
it = iter(im.getdata())
|
it = iter(im.getdata())
|
||||||
for data_row in data:
|
for data_row in data:
|
||||||
im_row = [next(it) for _ in range(im.size[0])]
|
im_row = [next(it) for _ in range(im.size[0])]
|
||||||
|
@ -35,12 +35,12 @@ def assertImage(im, data, delta=0):
|
||||||
next(it)
|
next(it)
|
||||||
|
|
||||||
|
|
||||||
def assertBlur(im, radius, data, passes=1, delta=0):
|
def assert_blur(im, radius, data, passes=1, delta=0):
|
||||||
# check grayscale image
|
# check grayscale image
|
||||||
assertImage(box_blur(im, radius, passes), data, delta)
|
assert_image(box_blur(im, radius, passes), data, delta)
|
||||||
rgba = Image.merge("RGBA", (im, im, im, im))
|
rgba = Image.merge("RGBA", (im, im, im, im))
|
||||||
for band in box_blur(rgba, radius, passes).split():
|
for band in box_blur(rgba, radius, passes).split():
|
||||||
assertImage(band, data, delta)
|
assert_image(band, data, delta)
|
||||||
|
|
||||||
|
|
||||||
def test_color_modes():
|
def test_color_modes():
|
||||||
|
@ -64,7 +64,7 @@ def test_color_modes():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0():
|
def test_radius_0():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0,
|
0,
|
||||||
[
|
[
|
||||||
|
@ -80,7 +80,7 @@ def test_radius_0():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_02():
|
def test_radius_0_02():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.02,
|
0.02,
|
||||||
[
|
[
|
||||||
|
@ -97,7 +97,7 @@ def test_radius_0_02():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_05():
|
def test_radius_0_05():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.05,
|
0.05,
|
||||||
[
|
[
|
||||||
|
@ -114,7 +114,7 @@ def test_radius_0_05():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_1():
|
def test_radius_0_1():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.1,
|
0.1,
|
||||||
[
|
[
|
||||||
|
@ -131,7 +131,7 @@ def test_radius_0_1():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_5():
|
def test_radius_0_5():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.5,
|
0.5,
|
||||||
[
|
[
|
||||||
|
@ -148,7 +148,7 @@ def test_radius_0_5():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_1():
|
def test_radius_1():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1,
|
1,
|
||||||
[
|
[
|
||||||
|
@ -165,7 +165,7 @@ def test_radius_1():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_1_5():
|
def test_radius_1_5():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1.5,
|
1.5,
|
||||||
[
|
[
|
||||||
|
@ -182,7 +182,7 @@ def test_radius_1_5():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_bigger_then_half():
|
def test_radius_bigger_then_half():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
3,
|
3,
|
||||||
[
|
[
|
||||||
|
@ -199,7 +199,7 @@ def test_radius_bigger_then_half():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_bigger_then_width():
|
def test_radius_bigger_then_width():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
10,
|
10,
|
||||||
[
|
[
|
||||||
|
@ -214,7 +214,7 @@ def test_radius_bigger_then_width():
|
||||||
|
|
||||||
|
|
||||||
def test_extreme_large_radius():
|
def test_extreme_large_radius():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
600,
|
600,
|
||||||
[
|
[
|
||||||
|
@ -229,7 +229,7 @@ def test_extreme_large_radius():
|
||||||
|
|
||||||
|
|
||||||
def test_two_passes():
|
def test_two_passes():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1,
|
1,
|
||||||
[
|
[
|
||||||
|
@ -247,7 +247,7 @@ def test_two_passes():
|
||||||
|
|
||||||
|
|
||||||
def test_three_passes():
|
def test_three_passes():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1,
|
1,
|
||||||
[
|
[
|
||||||
|
|
|
@ -15,27 +15,27 @@ except ImportError:
|
||||||
class TestColorLut3DCoreAPI:
|
class TestColorLut3DCoreAPI:
|
||||||
def generate_identity_table(self, channels, size):
|
def generate_identity_table(self, channels, size):
|
||||||
if isinstance(size, tuple):
|
if isinstance(size, tuple):
|
||||||
size1D, size2D, size3D = size
|
size_1d, size_2d, size_3d = size
|
||||||
else:
|
else:
|
||||||
size1D, size2D, size3D = (size, size, size)
|
size_1d, size_2d, size_3d = (size, size, size)
|
||||||
|
|
||||||
table = [
|
table = [
|
||||||
[
|
[
|
||||||
r / (size1D - 1) if size1D != 1 else 0,
|
r / (size_1d - 1) if size_1d != 1 else 0,
|
||||||
g / (size2D - 1) if size2D != 1 else 0,
|
g / (size_2d - 1) if size_2d != 1 else 0,
|
||||||
b / (size3D - 1) if size3D != 1 else 0,
|
b / (size_3d - 1) if size_3d != 1 else 0,
|
||||||
r / (size1D - 1) if size1D != 1 else 0,
|
r / (size_1d - 1) if size_1d != 1 else 0,
|
||||||
g / (size2D - 1) if size2D != 1 else 0,
|
g / (size_2d - 1) if size_2d != 1 else 0,
|
||||||
][:channels]
|
][:channels]
|
||||||
for b in range(size3D)
|
for b in range(size_3d)
|
||||||
for g in range(size2D)
|
for g in range(size_2d)
|
||||||
for r in range(size1D)
|
for r in range(size_1d)
|
||||||
]
|
]
|
||||||
return (
|
return (
|
||||||
channels,
|
channels,
|
||||||
size1D,
|
size_1d,
|
||||||
size2D,
|
size_2d,
|
||||||
size3D,
|
size_3d,
|
||||||
[item for sublist in table for item in sublist],
|
[item for sublist in table for item in sublist],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,107 +43,158 @@ class TestColorLut3DCoreAPI:
|
||||||
im = Image.new("RGB", (10, 10), 0)
|
im = Image.new("RGB", (10, 10), 0)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="filter"):
|
with pytest.raises(ValueError, match="filter"):
|
||||||
im.im.color_lut_3d("RGB", Image.CUBIC, *self.generate_identity_table(3, 3))
|
im.im.color_lut_3d(
|
||||||
|
"RGB", Image.Resampling.BICUBIC, *self.generate_identity_table(3, 3)
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="image mode"):
|
with pytest.raises(ValueError, match="image mode"):
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"wrong", Image.LINEAR, *self.generate_identity_table(3, 3)
|
"wrong", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3)
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="table_channels"):
|
with pytest.raises(ValueError, match="table_channels"):
|
||||||
im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(5, 3))
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="table_channels"):
|
|
||||||
im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(1, 3))
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="table_channels"):
|
|
||||||
im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(2, 3))
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Table size"):
|
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGB", Image.LINEAR, *self.generate_identity_table(3, (1, 3, 3))
|
"RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(5, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="table_channels"):
|
||||||
|
im.im.color_lut_3d(
|
||||||
|
"RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(1, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="table_channels"):
|
||||||
|
im.im.color_lut_3d(
|
||||||
|
"RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(2, 3)
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Table size"):
|
with pytest.raises(ValueError, match="Table size"):
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGB", Image.LINEAR, *self.generate_identity_table(3, (66, 3, 3))
|
"RGB",
|
||||||
|
Image.Resampling.BILINEAR,
|
||||||
|
*self.generate_identity_table(3, (1, 3, 3)),
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Table size"):
|
||||||
|
im.im.color_lut_3d(
|
||||||
|
"RGB",
|
||||||
|
Image.Resampling.BILINEAR,
|
||||||
|
*self.generate_identity_table(3, (66, 3, 3)),
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
|
with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
|
||||||
im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 7)
|
im.im.color_lut_3d(
|
||||||
|
"RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, 0] * 7
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
|
with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
|
||||||
im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 9)
|
im.im.color_lut_3d(
|
||||||
|
"RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, 0] * 9
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, "0"] * 8)
|
im.im.color_lut_3d(
|
||||||
|
"RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, "0"] * 8
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, 16)
|
im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, 16)
|
||||||
|
|
||||||
def test_correct_args(self):
|
def test_correct_args(self):
|
||||||
im = Image.new("RGB", (10, 10), 0)
|
im = Image.new("RGB", (10, 10), 0)
|
||||||
|
|
||||||
im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(3, 3))
|
|
||||||
|
|
||||||
im.im.color_lut_3d("CMYK", Image.LINEAR, *self.generate_identity_table(4, 3))
|
|
||||||
|
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGB", Image.LINEAR, *self.generate_identity_table(3, (2, 3, 3))
|
"RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3)
|
||||||
)
|
)
|
||||||
|
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGB", Image.LINEAR, *self.generate_identity_table(3, (65, 3, 3))
|
"CMYK", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3)
|
||||||
)
|
)
|
||||||
|
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGB", Image.LINEAR, *self.generate_identity_table(3, (3, 65, 3))
|
"RGB",
|
||||||
|
Image.Resampling.BILINEAR,
|
||||||
|
*self.generate_identity_table(3, (2, 3, 3)),
|
||||||
)
|
)
|
||||||
|
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGB", Image.LINEAR, *self.generate_identity_table(3, (3, 3, 65))
|
"RGB",
|
||||||
|
Image.Resampling.BILINEAR,
|
||||||
|
*self.generate_identity_table(3, (65, 3, 3)),
|
||||||
|
)
|
||||||
|
|
||||||
|
im.im.color_lut_3d(
|
||||||
|
"RGB",
|
||||||
|
Image.Resampling.BILINEAR,
|
||||||
|
*self.generate_identity_table(3, (3, 65, 3)),
|
||||||
|
)
|
||||||
|
|
||||||
|
im.im.color_lut_3d(
|
||||||
|
"RGB",
|
||||||
|
Image.Resampling.BILINEAR,
|
||||||
|
*self.generate_identity_table(3, (3, 3, 65)),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_wrong_mode(self):
|
def test_wrong_mode(self):
|
||||||
with pytest.raises(ValueError, match="wrong mode"):
|
with pytest.raises(ValueError, match="wrong mode"):
|
||||||
im = Image.new("L", (10, 10), 0)
|
im = Image.new("L", (10, 10), 0)
|
||||||
im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(3, 3))
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="wrong mode"):
|
|
||||||
im = Image.new("RGB", (10, 10), 0)
|
|
||||||
im.im.color_lut_3d("L", Image.LINEAR, *self.generate_identity_table(3, 3))
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="wrong mode"):
|
|
||||||
im = Image.new("L", (10, 10), 0)
|
|
||||||
im.im.color_lut_3d("L", Image.LINEAR, *self.generate_identity_table(3, 3))
|
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="wrong mode"):
|
|
||||||
im = Image.new("RGB", (10, 10), 0)
|
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGBA", Image.LINEAR, *self.generate_identity_table(3, 3)
|
"RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3)
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="wrong mode"):
|
with pytest.raises(ValueError, match="wrong mode"):
|
||||||
im = Image.new("RGB", (10, 10), 0)
|
im = Image.new("RGB", (10, 10), 0)
|
||||||
im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(4, 3))
|
im.im.color_lut_3d(
|
||||||
|
"L", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="wrong mode"):
|
||||||
|
im = Image.new("L", (10, 10), 0)
|
||||||
|
im.im.color_lut_3d(
|
||||||
|
"L", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="wrong mode"):
|
||||||
|
im = Image.new("RGB", (10, 10), 0)
|
||||||
|
im.im.color_lut_3d(
|
||||||
|
"RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="wrong mode"):
|
||||||
|
im = Image.new("RGB", (10, 10), 0)
|
||||||
|
im.im.color_lut_3d(
|
||||||
|
"RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3)
|
||||||
|
)
|
||||||
|
|
||||||
def test_correct_mode(self):
|
def test_correct_mode(self):
|
||||||
im = Image.new("RGBA", (10, 10), 0)
|
im = Image.new("RGBA", (10, 10), 0)
|
||||||
im.im.color_lut_3d("RGBA", Image.LINEAR, *self.generate_identity_table(3, 3))
|
im.im.color_lut_3d(
|
||||||
|
"RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3)
|
||||||
|
)
|
||||||
|
|
||||||
im = Image.new("RGBA", (10, 10), 0)
|
im = Image.new("RGBA", (10, 10), 0)
|
||||||
im.im.color_lut_3d("RGBA", Image.LINEAR, *self.generate_identity_table(4, 3))
|
im.im.color_lut_3d(
|
||||||
|
"RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3)
|
||||||
|
)
|
||||||
|
|
||||||
im = Image.new("RGB", (10, 10), 0)
|
im = Image.new("RGB", (10, 10), 0)
|
||||||
im.im.color_lut_3d("HSV", Image.LINEAR, *self.generate_identity_table(3, 3))
|
im.im.color_lut_3d(
|
||||||
|
"HSV", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3)
|
||||||
|
)
|
||||||
|
|
||||||
im = Image.new("RGB", (10, 10), 0)
|
im = Image.new("RGB", (10, 10), 0)
|
||||||
im.im.color_lut_3d("RGBA", Image.LINEAR, *self.generate_identity_table(4, 3))
|
im.im.color_lut_3d(
|
||||||
|
"RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3)
|
||||||
|
)
|
||||||
|
|
||||||
def test_identities(self):
|
def test_identities(self):
|
||||||
g = Image.linear_gradient("L")
|
g = Image.linear_gradient("L")
|
||||||
im = Image.merge(
|
im = Image.merge(
|
||||||
"RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)]
|
"RGB",
|
||||||
|
[
|
||||||
|
g,
|
||||||
|
g.transpose(Image.Transpose.ROTATE_90),
|
||||||
|
g.transpose(Image.Transpose.ROTATE_180),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fast test with small cubes
|
# Fast test with small cubes
|
||||||
|
@ -152,7 +203,9 @@ class TestColorLut3DCoreAPI:
|
||||||
im,
|
im,
|
||||||
im._new(
|
im._new(
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGB", Image.LINEAR, *self.generate_identity_table(3, size)
|
"RGB",
|
||||||
|
Image.Resampling.BILINEAR,
|
||||||
|
*self.generate_identity_table(3, size),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -162,7 +215,9 @@ class TestColorLut3DCoreAPI:
|
||||||
im,
|
im,
|
||||||
im._new(
|
im._new(
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGB", Image.LINEAR, *self.generate_identity_table(3, (2, 2, 65))
|
"RGB",
|
||||||
|
Image.Resampling.BILINEAR,
|
||||||
|
*self.generate_identity_table(3, (2, 2, 65)),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -170,7 +225,12 @@ class TestColorLut3DCoreAPI:
|
||||||
def test_identities_4_channels(self):
|
def test_identities_4_channels(self):
|
||||||
g = Image.linear_gradient("L")
|
g = Image.linear_gradient("L")
|
||||||
im = Image.merge(
|
im = Image.merge(
|
||||||
"RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)]
|
"RGB",
|
||||||
|
[
|
||||||
|
g,
|
||||||
|
g.transpose(Image.Transpose.ROTATE_90),
|
||||||
|
g.transpose(Image.Transpose.ROTATE_180),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Red channel copied to alpha
|
# Red channel copied to alpha
|
||||||
|
@ -178,7 +238,9 @@ class TestColorLut3DCoreAPI:
|
||||||
Image.merge("RGBA", (im.split() * 2)[:4]),
|
Image.merge("RGBA", (im.split() * 2)[:4]),
|
||||||
im._new(
|
im._new(
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGBA", Image.LINEAR, *self.generate_identity_table(4, 17)
|
"RGBA",
|
||||||
|
Image.Resampling.BILINEAR,
|
||||||
|
*self.generate_identity_table(4, 17),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -189,9 +251,9 @@ class TestColorLut3DCoreAPI:
|
||||||
"RGBA",
|
"RGBA",
|
||||||
[
|
[
|
||||||
g,
|
g,
|
||||||
g.transpose(Image.ROTATE_90),
|
g.transpose(Image.Transpose.ROTATE_90),
|
||||||
g.transpose(Image.ROTATE_180),
|
g.transpose(Image.Transpose.ROTATE_180),
|
||||||
g.transpose(Image.ROTATE_270),
|
g.transpose(Image.Transpose.ROTATE_270),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -199,7 +261,9 @@ class TestColorLut3DCoreAPI:
|
||||||
im,
|
im,
|
||||||
im._new(
|
im._new(
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGBA", Image.LINEAR, *self.generate_identity_table(3, 17)
|
"RGBA",
|
||||||
|
Image.Resampling.BILINEAR,
|
||||||
|
*self.generate_identity_table(3, 17),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -207,14 +271,19 @@ class TestColorLut3DCoreAPI:
|
||||||
def test_channels_order(self):
|
def test_channels_order(self):
|
||||||
g = Image.linear_gradient("L")
|
g = Image.linear_gradient("L")
|
||||||
im = Image.merge(
|
im = Image.merge(
|
||||||
"RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)]
|
"RGB",
|
||||||
|
[
|
||||||
|
g,
|
||||||
|
g.transpose(Image.Transpose.ROTATE_90),
|
||||||
|
g.transpose(Image.Transpose.ROTATE_180),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reverse channels by splitting and using table
|
# Reverse channels by splitting and using table
|
||||||
# fmt: off
|
# fmt: off
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
Image.merge('RGB', im.split()[::-1]),
|
Image.merge('RGB', im.split()[::-1]),
|
||||||
im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
|
im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
|
||||||
3, 2, 2, 2, [
|
3, 2, 2, 2, [
|
||||||
0, 0, 0, 0, 0, 1,
|
0, 0, 0, 0, 0, 1,
|
||||||
0, 1, 0, 0, 1, 1,
|
0, 1, 0, 0, 1, 1,
|
||||||
|
@ -227,11 +296,16 @@ class TestColorLut3DCoreAPI:
|
||||||
def test_overflow(self):
|
def test_overflow(self):
|
||||||
g = Image.linear_gradient("L")
|
g = Image.linear_gradient("L")
|
||||||
im = Image.merge(
|
im = Image.merge(
|
||||||
"RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)]
|
"RGB",
|
||||||
|
[
|
||||||
|
g,
|
||||||
|
g.transpose(Image.Transpose.ROTATE_90),
|
||||||
|
g.transpose(Image.Transpose.ROTATE_180),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
|
transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
|
||||||
3, 2, 2, 2,
|
3, 2, 2, 2,
|
||||||
[
|
[
|
||||||
-1, -1, -1, 2, -1, -1,
|
-1, -1, -1, 2, -1, -1,
|
||||||
|
@ -251,7 +325,7 @@ class TestColorLut3DCoreAPI:
|
||||||
assert transformed[205, 205] == (255, 255, 0)
|
assert transformed[205, 205] == (255, 255, 0)
|
||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR,
|
transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
|
||||||
3, 2, 2, 2,
|
3, 2, 2, 2,
|
||||||
[
|
[
|
||||||
-3, -3, -3, 5, -3, -3,
|
-3, -3, -3, 5, -3, -3,
|
||||||
|
@ -354,7 +428,12 @@ class TestColorLut3DFilter:
|
||||||
def test_numpy_formats(self):
|
def test_numpy_formats(self):
|
||||||
g = Image.linear_gradient("L")
|
g = Image.linear_gradient("L")
|
||||||
im = Image.merge(
|
im = Image.merge(
|
||||||
"RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)]
|
"RGB",
|
||||||
|
[
|
||||||
|
g,
|
||||||
|
g.transpose(Image.Transpose.ROTATE_90),
|
||||||
|
g.transpose(Image.Transpose.ROTATE_180),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b))
|
lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b))
|
||||||
|
@ -445,7 +524,12 @@ class TestGenerateColorLut3D:
|
||||||
|
|
||||||
g = Image.linear_gradient("L")
|
g = Image.linear_gradient("L")
|
||||||
im = Image.merge(
|
im = Image.merge(
|
||||||
"RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)]
|
"RGB",
|
||||||
|
[
|
||||||
|
g,
|
||||||
|
g.transpose(Image.Transpose.ROTATE_90),
|
||||||
|
g.transpose(Image.Transpose.ROTATE_180),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
assert im == im.filter(lut)
|
assert im == im.filter(lut)
|
||||||
|
|
||||||
|
|
|
@ -110,9 +110,9 @@ class TestCoreMemory:
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Image.core.set_blocks_max(-1)
|
Image.core.set_blocks_max(-1)
|
||||||
if sys.maxsize < 2 ** 32:
|
if sys.maxsize < 2**32:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Image.core.set_blocks_max(2 ** 29)
|
Image.core.set_blocks_max(2**29)
|
||||||
|
|
||||||
@pytest.mark.skipif(is_pypy(), reason="Images not collected")
|
@pytest.mark.skipif(is_pypy(), reason="Images not collected")
|
||||||
def test_set_blocks_max_stats(self):
|
def test_set_blocks_max_stats(self):
|
||||||
|
|
|
@ -78,7 +78,7 @@ class TestDecompressionCrop:
|
||||||
def teardown_class(self):
|
def teardown_class(self):
|
||||||
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
|
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
|
||||||
|
|
||||||
def testEnlargeCrop(self):
|
def test_enlarge_crop(self):
|
||||||
# Crops can extend the extents, therefore we should have the
|
# Crops can extend the extents, therefore we should have the
|
||||||
# same decompression bomb warnings on them.
|
# same decompression bomb warnings on them.
|
||||||
with hopper() as src:
|
with hopper() as src:
|
||||||
|
@ -86,21 +86,12 @@ class TestDecompressionCrop:
|
||||||
pytest.warns(Image.DecompressionBombWarning, src.crop, box)
|
pytest.warns(Image.DecompressionBombWarning, src.crop, box)
|
||||||
|
|
||||||
def test_crop_decompression_checks(self):
|
def test_crop_decompression_checks(self):
|
||||||
|
|
||||||
im = Image.new("RGB", (100, 100))
|
im = Image.new("RGB", (100, 100))
|
||||||
|
|
||||||
good_values = ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990))
|
for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)):
|
||||||
|
|
||||||
warning_values = ((-160, -160, 99, 99), (160, 160, -99, -99))
|
|
||||||
|
|
||||||
error_values = ((-99909, -99990, 99999, 99999), (99909, 99990, -99999, -99999))
|
|
||||||
|
|
||||||
for value in good_values:
|
|
||||||
assert im.crop(value).size == (9, 9)
|
assert im.crop(value).size == (9, 9)
|
||||||
|
|
||||||
for value in warning_values:
|
pytest.warns(Image.DecompressionBombWarning, im.crop, (-160, -160, 99, 99))
|
||||||
pytest.warns(Image.DecompressionBombWarning, im.crop, value)
|
|
||||||
|
|
||||||
for value in error_values:
|
with pytest.raises(Image.DecompressionBombError):
|
||||||
with pytest.raises(Image.DecompressionBombError):
|
im.crop((-99909, -99990, 99999, 99999))
|
||||||
im.crop(value)
|
|
||||||
|
|
91
Tests/test_deprecate.py
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import _deprecate
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"version, expected",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
10,
|
||||||
|
"Old thing is deprecated and will be removed in Pillow 10 "
|
||||||
|
r"\(2023-07-01\)\. Use new thing instead\.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
None,
|
||||||
|
r"Old thing is deprecated and will be removed in a future version\. "
|
||||||
|
r"Use new thing instead\.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_version(version, expected):
|
||||||
|
with pytest.warns(DeprecationWarning, match=expected):
|
||||||
|
_deprecate.deprecate("Old thing", version, "new thing")
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_version():
|
||||||
|
expected = r"Unknown removal version, update PIL\._deprecate\?"
|
||||||
|
with pytest.raises(ValueError, match=expected):
|
||||||
|
_deprecate.deprecate("Old thing", 12345, "new thing")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"deprecated, plural, expected",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"Old thing",
|
||||||
|
False,
|
||||||
|
r"Old thing is deprecated and should be removed\.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Old things",
|
||||||
|
True,
|
||||||
|
r"Old things are deprecated and should be removed\.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_old_version(deprecated, plural, expected):
|
||||||
|
expected = r""
|
||||||
|
with pytest.raises(RuntimeError, match=expected):
|
||||||
|
_deprecate.deprecate(deprecated, 1, plural=plural)
|
||||||
|
|
||||||
|
|
||||||
|
def test_plural():
|
||||||
|
expected = (
|
||||||
|
r"Old things are deprecated and will be removed in Pillow 10 \(2023-07-01\)\. "
|
||||||
|
r"Use new thing instead\."
|
||||||
|
)
|
||||||
|
with pytest.warns(DeprecationWarning, match=expected):
|
||||||
|
_deprecate.deprecate("Old things", 10, "new thing", plural=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_replacement_and_action():
|
||||||
|
expected = "Use only one of 'replacement' and 'action'"
|
||||||
|
with pytest.raises(ValueError, match=expected):
|
||||||
|
_deprecate.deprecate(
|
||||||
|
"Old thing", 10, replacement="new thing", action="Upgrade to new thing"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"action",
|
||||||
|
[
|
||||||
|
"Upgrade to new thing",
|
||||||
|
"Upgrade to new thing.",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_action(action):
|
||||||
|
expected = (
|
||||||
|
r"Old thing is deprecated and will be removed in Pillow 10 \(2023-07-01\)\. "
|
||||||
|
r"Upgrade to new thing\."
|
||||||
|
)
|
||||||
|
with pytest.warns(DeprecationWarning, match=expected):
|
||||||
|
_deprecate.deprecate("Old thing", 10, action=action)
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_replacement_or_action():
|
||||||
|
expected = (
|
||||||
|
r"Old thing is deprecated and will be removed in Pillow 10 \(2023-07-01\)"
|
||||||
|
)
|
||||||
|
with pytest.warns(DeprecationWarning, match=expected):
|
||||||
|
_deprecate.deprecate("Old thing", 10)
|
|
@ -120,9 +120,9 @@ def test_apng_dispose_op_previous_frame():
|
||||||
# save_all=True,
|
# save_all=True,
|
||||||
# append_images=[green, blue],
|
# append_images=[green, blue],
|
||||||
# disposal=[
|
# disposal=[
|
||||||
# PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
# PngImagePlugin.Disposal.OP_NONE,
|
||||||
# PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
|
# PngImagePlugin.Disposal.OP_PREVIOUS,
|
||||||
# PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS
|
# PngImagePlugin.Disposal.OP_PREVIOUS
|
||||||
# ],
|
# ],
|
||||||
# )
|
# )
|
||||||
with Image.open("Tests/images/apng/dispose_op_previous_frame.png") as im:
|
with Image.open("Tests/images/apng/dispose_op_previous_frame.png") as im:
|
||||||
|
@ -441,6 +441,12 @@ def test_apng_save_duration_loop(tmp_path):
|
||||||
assert im.n_frames == 1
|
assert im.n_frames == 1
|
||||||
assert im.info.get("duration") == 750
|
assert im.info.get("duration") == 750
|
||||||
|
|
||||||
|
# test info duration
|
||||||
|
frame.info["duration"] = 750
|
||||||
|
frame.save(test_file, save_all=True)
|
||||||
|
with Image.open(test_file) as im:
|
||||||
|
assert im.info.get("duration") == 750
|
||||||
|
|
||||||
|
|
||||||
def test_apng_save_disposal(tmp_path):
|
def test_apng_save_disposal(tmp_path):
|
||||||
test_file = str(tmp_path / "temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
|
@ -449,31 +455,31 @@ def test_apng_save_disposal(tmp_path):
|
||||||
green = Image.new("RGBA", size, (0, 255, 0, 255))
|
green = Image.new("RGBA", size, (0, 255, 0, 255))
|
||||||
transparent = Image.new("RGBA", size, (0, 0, 0, 0))
|
transparent = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||||
|
|
||||||
# test APNG_DISPOSE_OP_NONE
|
# test OP_NONE
|
||||||
red.save(
|
red.save(
|
||||||
test_file,
|
test_file,
|
||||||
save_all=True,
|
save_all=True,
|
||||||
append_images=[green, transparent],
|
append_images=[green, transparent],
|
||||||
disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
disposal=PngImagePlugin.Disposal.OP_NONE,
|
||||||
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
|
blend=PngImagePlugin.Blend.OP_OVER,
|
||||||
)
|
)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.seek(2)
|
im.seek(2)
|
||||||
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
# test APNG_DISPOSE_OP_BACKGROUND
|
# test OP_BACKGROUND
|
||||||
disposal = [
|
disposal = [
|
||||||
PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
PngImagePlugin.Disposal.OP_NONE,
|
||||||
PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND,
|
PngImagePlugin.Disposal.OP_BACKGROUND,
|
||||||
PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
PngImagePlugin.Disposal.OP_NONE,
|
||||||
]
|
]
|
||||||
red.save(
|
red.save(
|
||||||
test_file,
|
test_file,
|
||||||
save_all=True,
|
save_all=True,
|
||||||
append_images=[red, transparent],
|
append_images=[red, transparent],
|
||||||
disposal=disposal,
|
disposal=disposal,
|
||||||
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
|
blend=PngImagePlugin.Blend.OP_OVER,
|
||||||
)
|
)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.seek(2)
|
im.seek(2)
|
||||||
|
@ -481,26 +487,26 @@ def test_apng_save_disposal(tmp_path):
|
||||||
assert im.getpixel((64, 32)) == (0, 0, 0, 0)
|
assert im.getpixel((64, 32)) == (0, 0, 0, 0)
|
||||||
|
|
||||||
disposal = [
|
disposal = [
|
||||||
PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
PngImagePlugin.Disposal.OP_NONE,
|
||||||
PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND,
|
PngImagePlugin.Disposal.OP_BACKGROUND,
|
||||||
]
|
]
|
||||||
red.save(
|
red.save(
|
||||||
test_file,
|
test_file,
|
||||||
save_all=True,
|
save_all=True,
|
||||||
append_images=[green],
|
append_images=[green],
|
||||||
disposal=disposal,
|
disposal=disposal,
|
||||||
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
|
blend=PngImagePlugin.Blend.OP_OVER,
|
||||||
)
|
)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
# test APNG_DISPOSE_OP_PREVIOUS
|
# test OP_PREVIOUS
|
||||||
disposal = [
|
disposal = [
|
||||||
PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
PngImagePlugin.Disposal.OP_NONE,
|
||||||
PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
|
PngImagePlugin.Disposal.OP_PREVIOUS,
|
||||||
PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
PngImagePlugin.Disposal.OP_NONE,
|
||||||
]
|
]
|
||||||
red.save(
|
red.save(
|
||||||
test_file,
|
test_file,
|
||||||
|
@ -508,7 +514,7 @@ def test_apng_save_disposal(tmp_path):
|
||||||
append_images=[green, red, transparent],
|
append_images=[green, red, transparent],
|
||||||
default_image=True,
|
default_image=True,
|
||||||
disposal=disposal,
|
disposal=disposal,
|
||||||
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
|
blend=PngImagePlugin.Blend.OP_OVER,
|
||||||
)
|
)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.seek(3)
|
im.seek(3)
|
||||||
|
@ -516,21 +522,32 @@ def test_apng_save_disposal(tmp_path):
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
disposal = [
|
disposal = [
|
||||||
PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
PngImagePlugin.Disposal.OP_NONE,
|
||||||
PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
|
PngImagePlugin.Disposal.OP_PREVIOUS,
|
||||||
]
|
]
|
||||||
red.save(
|
red.save(
|
||||||
test_file,
|
test_file,
|
||||||
save_all=True,
|
save_all=True,
|
||||||
append_images=[green],
|
append_images=[green],
|
||||||
disposal=disposal,
|
disposal=disposal,
|
||||||
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
|
blend=PngImagePlugin.Blend.OP_OVER,
|
||||||
)
|
)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
|
# test info disposal
|
||||||
|
red.info["disposal"] = PngImagePlugin.Disposal.OP_BACKGROUND
|
||||||
|
red.save(
|
||||||
|
test_file,
|
||||||
|
save_all=True,
|
||||||
|
append_images=[Image.new("RGBA", (10, 10), (0, 255, 0, 255))],
|
||||||
|
)
|
||||||
|
with Image.open(test_file) as im:
|
||||||
|
im.seek(1)
|
||||||
|
assert im.getpixel((64, 32)) == (0, 0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
def test_apng_save_disposal_previous(tmp_path):
|
def test_apng_save_disposal_previous(tmp_path):
|
||||||
test_file = str(tmp_path / "temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
|
@ -539,12 +556,12 @@ def test_apng_save_disposal_previous(tmp_path):
|
||||||
red = Image.new("RGBA", size, (255, 0, 0, 255))
|
red = Image.new("RGBA", size, (255, 0, 0, 255))
|
||||||
green = Image.new("RGBA", size, (0, 255, 0, 255))
|
green = Image.new("RGBA", size, (0, 255, 0, 255))
|
||||||
|
|
||||||
# test APNG_DISPOSE_OP_NONE
|
# test OP_NONE
|
||||||
transparent.save(
|
transparent.save(
|
||||||
test_file,
|
test_file,
|
||||||
save_all=True,
|
save_all=True,
|
||||||
append_images=[red, green],
|
append_images=[red, green],
|
||||||
disposal=PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
|
disposal=PngImagePlugin.Disposal.OP_PREVIOUS,
|
||||||
)
|
)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.seek(2)
|
im.seek(2)
|
||||||
|
@ -559,17 +576,17 @@ def test_apng_save_blend(tmp_path):
|
||||||
green = Image.new("RGBA", size, (0, 255, 0, 255))
|
green = Image.new("RGBA", size, (0, 255, 0, 255))
|
||||||
transparent = Image.new("RGBA", size, (0, 0, 0, 0))
|
transparent = Image.new("RGBA", size, (0, 0, 0, 0))
|
||||||
|
|
||||||
# test APNG_BLEND_OP_SOURCE on solid color
|
# test OP_SOURCE on solid color
|
||||||
blend = [
|
blend = [
|
||||||
PngImagePlugin.APNG_BLEND_OP_OVER,
|
PngImagePlugin.Blend.OP_OVER,
|
||||||
PngImagePlugin.APNG_BLEND_OP_SOURCE,
|
PngImagePlugin.Blend.OP_SOURCE,
|
||||||
]
|
]
|
||||||
red.save(
|
red.save(
|
||||||
test_file,
|
test_file,
|
||||||
save_all=True,
|
save_all=True,
|
||||||
append_images=[red, green],
|
append_images=[red, green],
|
||||||
default_image=True,
|
default_image=True,
|
||||||
disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
disposal=PngImagePlugin.Disposal.OP_NONE,
|
||||||
blend=blend,
|
blend=blend,
|
||||||
)
|
)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
|
@ -577,17 +594,17 @@ def test_apng_save_blend(tmp_path):
|
||||||
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
# test APNG_BLEND_OP_SOURCE on transparent color
|
# test OP_SOURCE on transparent color
|
||||||
blend = [
|
blend = [
|
||||||
PngImagePlugin.APNG_BLEND_OP_OVER,
|
PngImagePlugin.Blend.OP_OVER,
|
||||||
PngImagePlugin.APNG_BLEND_OP_SOURCE,
|
PngImagePlugin.Blend.OP_SOURCE,
|
||||||
]
|
]
|
||||||
red.save(
|
red.save(
|
||||||
test_file,
|
test_file,
|
||||||
save_all=True,
|
save_all=True,
|
||||||
append_images=[red, transparent],
|
append_images=[red, transparent],
|
||||||
default_image=True,
|
default_image=True,
|
||||||
disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
disposal=PngImagePlugin.Disposal.OP_NONE,
|
||||||
blend=blend,
|
blend=blend,
|
||||||
)
|
)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
|
@ -595,14 +612,14 @@ def test_apng_save_blend(tmp_path):
|
||||||
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
|
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
|
||||||
assert im.getpixel((64, 32)) == (0, 0, 0, 0)
|
assert im.getpixel((64, 32)) == (0, 0, 0, 0)
|
||||||
|
|
||||||
# test APNG_BLEND_OP_OVER
|
# test OP_OVER
|
||||||
red.save(
|
red.save(
|
||||||
test_file,
|
test_file,
|
||||||
save_all=True,
|
save_all=True,
|
||||||
append_images=[green, transparent],
|
append_images=[green, transparent],
|
||||||
default_image=True,
|
default_image=True,
|
||||||
disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
disposal=PngImagePlugin.Disposal.OP_NONE,
|
||||||
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
|
blend=PngImagePlugin.Blend.OP_OVER,
|
||||||
)
|
)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
|
@ -611,3 +628,20 @@ def test_apng_save_blend(tmp_path):
|
||||||
im.seek(2)
|
im.seek(2)
|
||||||
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
|
# test info blend
|
||||||
|
red.info["blend"] = PngImagePlugin.Blend.OP_OVER
|
||||||
|
red.save(test_file, save_all=True, append_images=[green, transparent])
|
||||||
|
with Image.open(test_file) as im:
|
||||||
|
im.seek(2)
|
||||||
|
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def test_constants_deprecation():
|
||||||
|
for enum, prefix in {
|
||||||
|
PngImagePlugin.Disposal: "APNG_DISPOSE_",
|
||||||
|
PngImagePlugin.Blend: "APNG_BLEND_",
|
||||||
|
}.items():
|
||||||
|
for name in enum.__members__:
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert getattr(PngImagePlugin, prefix + name) == enum[name]
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import BlpImagePlugin, Image
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import (
|
||||||
|
assert_image_equal,
|
||||||
|
assert_image_equal_tofile,
|
||||||
|
assert_image_similar,
|
||||||
|
hopper,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_blp1():
|
||||||
|
with Image.open("Tests/images/blp/blp1_jpeg.blp") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
|
||||||
|
|
||||||
|
|
||||||
def test_load_blp2_raw():
|
def test_load_blp2_raw():
|
||||||
|
@ -20,6 +30,28 @@ def test_load_blp2_dxt1a():
|
||||||
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
|
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_save(tmp_path):
|
||||||
|
f = str(tmp_path / "temp.blp")
|
||||||
|
|
||||||
|
for version in ("BLP1", "BLP2"):
|
||||||
|
im = hopper("P")
|
||||||
|
im.save(f, blp_version=version)
|
||||||
|
|
||||||
|
with Image.open(f) as reloaded:
|
||||||
|
assert_image_equal(im.convert("RGB"), reloaded)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/transparent.png") as im:
|
||||||
|
f = str(tmp_path / "temp.blp")
|
||||||
|
im.convert("P").save(f, blp_version=version)
|
||||||
|
|
||||||
|
with Image.open(f) as reloaded:
|
||||||
|
assert_image_similar(im, reloaded, 8)
|
||||||
|
|
||||||
|
im = hopper()
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im.save(f)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_file",
|
"test_file",
|
||||||
[
|
[
|
||||||
|
@ -37,3 +69,14 @@ def test_crashes(test_file):
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
|
def test_constants_deprecation():
|
||||||
|
for enum, prefix in {
|
||||||
|
BlpImagePlugin.Format: "BLP_FORMAT_",
|
||||||
|
BlpImagePlugin.Encoding: "BLP_ENCODING_",
|
||||||
|
BlpImagePlugin.AlphaEncoding: "BLP_ALPHA_ENCODING_",
|
||||||
|
}.items():
|
||||||
|
for name in enum.__members__:
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert getattr(BlpImagePlugin, prefix + name) == enum[name]
|
||||||
|
|
|
@ -4,7 +4,12 @@ import pytest
|
||||||
|
|
||||||
from PIL import BmpImagePlugin, Image
|
from PIL import BmpImagePlugin, Image
|
||||||
|
|
||||||
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
from .helper import (
|
||||||
|
assert_image_equal,
|
||||||
|
assert_image_equal_tofile,
|
||||||
|
assert_image_similar_tofile,
|
||||||
|
hopper,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_sanity(tmp_path):
|
def test_sanity(tmp_path):
|
||||||
|
@ -123,3 +128,46 @@ def test_rgba_bitfields():
|
||||||
im = Image.merge("RGB", (r, g, b))
|
im = Image.merge("RGB", (r, g, b))
|
||||||
|
|
||||||
assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")
|
assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")
|
||||||
|
|
||||||
|
|
||||||
|
def test_rle8():
|
||||||
|
with Image.open("Tests/images/hopper_rle8.bmp") as im:
|
||||||
|
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
||||||
|
|
||||||
|
# This test image has been manually hexedited
|
||||||
|
# to have rows with too much data
|
||||||
|
with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im:
|
||||||
|
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
||||||
|
|
||||||
|
# Signal end of bitmap before the image is finished
|
||||||
|
with open("Tests/images/bmp/g/pal8rle.bmp", "rb") as fp:
|
||||||
|
data = fp.read(1063) + b"\x01"
|
||||||
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"file_name,length",
|
||||||
|
(
|
||||||
|
# EOF immediately after the header
|
||||||
|
("Tests/images/hopper_rle8.bmp", 1078),
|
||||||
|
# EOF during delta
|
||||||
|
("Tests/images/bmp/q/pal8rletrns.bmp", 3670),
|
||||||
|
# EOF when reading data in absolute mode
|
||||||
|
("Tests/images/bmp/g/pal8rle.bmp", 1064),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_rle8_eof(file_name, length):
|
||||||
|
with open(file_name, "rb") as fp:
|
||||||
|
data = fp.read(length)
|
||||||
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
|
def test_offset():
|
||||||
|
# This image has been hexedited
|
||||||
|
# to exclude the palette size from the pixel data offset
|
||||||
|
with Image.open("Tests/images/pal8_offset.bmp") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/bmp/g/pal8.bmp")
|
||||||
|
|
|
@ -45,3 +45,35 @@ def test_save(tmp_path):
|
||||||
# Act / Assert: stub cannot save without an implemented handler
|
# Act / Assert: stub cannot save without an implemented handler
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
im.save(tmpfile)
|
im.save(tmpfile)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handler(tmp_path):
|
||||||
|
class TestHandler:
|
||||||
|
opened = False
|
||||||
|
loaded = False
|
||||||
|
saved = False
|
||||||
|
|
||||||
|
def open(self, im):
|
||||||
|
self.opened = True
|
||||||
|
|
||||||
|
def load(self, im):
|
||||||
|
self.loaded = True
|
||||||
|
return Image.new("RGB", (1, 1))
|
||||||
|
|
||||||
|
def save(self, im, fp, filename):
|
||||||
|
self.saved = True
|
||||||
|
|
||||||
|
handler = TestHandler()
|
||||||
|
BufrStubImagePlugin.register_handler(handler)
|
||||||
|
with Image.open(TEST_FILE) as im:
|
||||||
|
assert handler.opened
|
||||||
|
assert not handler.loaded
|
||||||
|
|
||||||
|
im.load()
|
||||||
|
assert handler.loaded
|
||||||
|
|
||||||
|
temp_file = str(tmp_path / "temp.bufr")
|
||||||
|
im.save(temp_file)
|
||||||
|
assert handler.saved
|
||||||
|
|
||||||
|
BufrStubImagePlugin._handler = None
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import DcxImagePlugin, Image
|
from PIL import DcxImagePlugin, Image
|
||||||
|
@ -31,21 +33,17 @@ def test_unclosed_file():
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im = Image.open(TEST_FILE)
|
im = Image.open(TEST_FILE)
|
||||||
im.load()
|
im.load()
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_context_manager():
|
def test_context_manager():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
with open("Tests/images/flower.jpg", "rb") as fp:
|
with open("Tests/images/flower.jpg", "rb") as fp:
|
||||||
|
|
|
@ -196,6 +196,13 @@ def test__accept_false():
|
||||||
assert not output
|
assert not output
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_file():
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
DdsImagePlugin.DdsImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
def test_short_header():
|
def test_short_header():
|
||||||
"""Check a short header"""
|
"""Check a short header"""
|
||||||
with open(TEST_FILE_DXT5, "rb") as f:
|
with open(TEST_FILE_DXT5, "rb") as f:
|
||||||
|
|
|
@ -58,6 +58,15 @@ def test_sanity():
|
||||||
assert image2_scale2.format == "EPS"
|
assert image2_scale2.format == "EPS"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
|
||||||
|
def test_load():
|
||||||
|
with Image.open(FILE1) as im:
|
||||||
|
assert im.load()[0, 0] == (255, 255, 255)
|
||||||
|
|
||||||
|
# Test again now that it has already been loaded once
|
||||||
|
assert im.load()[0, 0] == (255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
|
80
Tests/test_file_fits.py
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import FitsImagePlugin, FitsStubImagePlugin, Image
|
||||||
|
|
||||||
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
TEST_FILE = "Tests/images/hopper.fits"
|
||||||
|
|
||||||
|
|
||||||
|
def test_open():
|
||||||
|
# Act
|
||||||
|
with Image.open(TEST_FILE) as im:
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert im.format == "FITS"
|
||||||
|
assert im.size == (128, 128)
|
||||||
|
assert im.mode == "L"
|
||||||
|
|
||||||
|
assert_image_equal(im, hopper("L"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_file():
|
||||||
|
# Arrange
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
FitsImagePlugin.FitsImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_truncated_fits():
|
||||||
|
# No END to headers
|
||||||
|
image_data = b"SIMPLE = T" + b" " * 50 + b"TRUNCATE"
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
FitsImagePlugin.FitsImageFile(BytesIO(image_data))
|
||||||
|
|
||||||
|
|
||||||
|
def test_naxis_zero():
|
||||||
|
# This test image has been manually hexedited
|
||||||
|
# to set the number of data axes to zero
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
with Image.open("Tests/images/hopper_naxis_zero.fits"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_stub_deprecated():
|
||||||
|
class Handler:
|
||||||
|
opened = False
|
||||||
|
loaded = False
|
||||||
|
|
||||||
|
def open(self, im):
|
||||||
|
self.opened = True
|
||||||
|
|
||||||
|
def load(self, im):
|
||||||
|
self.loaded = True
|
||||||
|
return Image.new("RGB", (1, 1))
|
||||||
|
|
||||||
|
handler = Handler()
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
FitsStubImagePlugin.register_handler(handler)
|
||||||
|
|
||||||
|
with Image.open(TEST_FILE) as im:
|
||||||
|
assert im.format == "FITS"
|
||||||
|
assert im.size == (128, 128)
|
||||||
|
assert im.mode == "L"
|
||||||
|
|
||||||
|
assert handler.opened
|
||||||
|
assert not handler.loaded
|
||||||
|
|
||||||
|
im.load()
|
||||||
|
assert handler.loaded
|
||||||
|
|
||||||
|
FitsStubImagePlugin._handler = None
|
||||||
|
Image.register_open(
|
||||||
|
FitsImagePlugin.FitsImageFile.format,
|
||||||
|
FitsImagePlugin.FitsImageFile,
|
||||||
|
FitsImagePlugin._accept,
|
||||||
|
)
|
|
@ -1,63 +0,0 @@
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from PIL import FitsStubImagePlugin, Image
|
|
||||||
|
|
||||||
TEST_FILE = "Tests/images/hopper.fits"
|
|
||||||
|
|
||||||
|
|
||||||
def test_open():
|
|
||||||
# Act
|
|
||||||
with Image.open(TEST_FILE) as im:
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert im.format == "FITS"
|
|
||||||
assert im.size == (128, 128)
|
|
||||||
assert im.mode == "L"
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
|
||||||
# Arrange
|
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
with pytest.raises(SyntaxError):
|
|
||||||
FitsStubImagePlugin.FITSStubImageFile(invalid_file)
|
|
||||||
|
|
||||||
|
|
||||||
def test_load():
|
|
||||||
# Arrange
|
|
||||||
with Image.open(TEST_FILE) as im:
|
|
||||||
|
|
||||||
# Act / Assert: stub cannot load without an implemented handler
|
|
||||||
with pytest.raises(OSError):
|
|
||||||
im.load()
|
|
||||||
|
|
||||||
|
|
||||||
def test_truncated_fits():
|
|
||||||
# No END to headers
|
|
||||||
image_data = b"SIMPLE = T" + b" " * 50 + b"TRUNCATE"
|
|
||||||
with pytest.raises(OSError):
|
|
||||||
FitsStubImagePlugin.FITSStubImageFile(BytesIO(image_data))
|
|
||||||
|
|
||||||
|
|
||||||
def test_naxis_zero():
|
|
||||||
# This test image has been manually hexedited
|
|
||||||
# to set the number of data axes to zero
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
with Image.open("Tests/images/hopper_naxis_zero.fits"):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def test_save():
|
|
||||||
# Arrange
|
|
||||||
with Image.open(TEST_FILE) as im:
|
|
||||||
dummy_fp = None
|
|
||||||
dummy_filename = "dummy.filename"
|
|
||||||
|
|
||||||
# Act / Assert: stub cannot save without an implemented handler
|
|
||||||
with pytest.raises(OSError):
|
|
||||||
im.save(dummy_filename)
|
|
||||||
with pytest.raises(OSError):
|
|
||||||
FitsStubImagePlugin._save(im, dummy_fp, dummy_filename)
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import FliImagePlugin, Image
|
from PIL import FliImagePlugin, Image
|
||||||
|
@ -38,21 +40,17 @@ def test_unclosed_file():
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im = Image.open(static_test_file)
|
im = Image.open(static_test_file)
|
||||||
im.load()
|
im.load()
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_context_manager():
|
def test_context_manager():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with Image.open(static_test_file) as im:
|
with Image.open(static_test_file) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_tell():
|
def test_tell():
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -138,3 +136,16 @@ def test_timeouts(test_file):
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"test_file",
|
||||||
|
[
|
||||||
|
"Tests/images/crash-5762152299364352.fli",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_crash(test_file):
|
||||||
|
with open(test_file, "rb") as f:
|
||||||
|
with Image.open(f) as im:
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
im.load()
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from PIL import Image
|
import pytest
|
||||||
|
|
||||||
|
from PIL import FtexImagePlugin, Image
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile, assert_image_similar
|
from .helper import assert_image_equal_tofile, assert_image_similar
|
||||||
|
|
||||||
|
@ -12,3 +14,19 @@ def test_load_dxt1():
|
||||||
with Image.open("Tests/images/ftex_dxt1.ftc") as im:
|
with Image.open("Tests/images/ftex_dxt1.ftc") as im:
|
||||||
with Image.open("Tests/images/ftex_dxt1.png") as target:
|
with Image.open("Tests/images/ftex_dxt1.png") as target:
|
||||||
assert_image_similar(im, target.convert("RGBA"), 15)
|
assert_image_similar(im, target.convert("RGBA"), 15)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_file():
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
FtexImagePlugin.FtexImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_constants_deprecation():
|
||||||
|
for enum, prefix in {
|
||||||
|
FtexImagePlugin.Format: "FORMAT_",
|
||||||
|
}.items():
|
||||||
|
for name in enum.__members__:
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert getattr(FtexImagePlugin, prefix + name) == enum[name]
|
||||||
|
|
|
@ -5,20 +5,28 @@ from PIL import GbrImagePlugin, Image
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import assert_image_equal_tofile
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
|
||||||
|
|
||||||
with pytest.raises(SyntaxError):
|
|
||||||
GbrImagePlugin.GbrImageFile(invalid_file)
|
|
||||||
|
|
||||||
|
|
||||||
def test_gbr_file():
|
def test_gbr_file():
|
||||||
with Image.open("Tests/images/gbr.gbr") as im:
|
with Image.open("Tests/images/gbr.gbr") as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/gbr.png")
|
assert_image_equal_tofile(im, "Tests/images/gbr.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_load():
|
||||||
|
with Image.open("Tests/images/gbr.gbr") as im:
|
||||||
|
assert im.load()[0, 0] == (0, 0, 0, 0)
|
||||||
|
|
||||||
|
# Test again now that it has already been loaded once
|
||||||
|
assert im.load()[0, 0] == (0, 0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_load_operations():
|
def test_multiple_load_operations():
|
||||||
with Image.open("Tests/images/gbr.gbr") as im:
|
with Image.open("Tests/images/gbr.gbr") as im:
|
||||||
im.load()
|
im.load()
|
||||||
im.load()
|
im.load()
|
||||||
assert_image_equal_tofile(im, "Tests/images/gbr.png")
|
assert_image_equal_tofile(im, "Tests/images/gbr.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_file():
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
with pytest.raises(SyntaxError):
|
||||||
|
GbrImagePlugin.GbrImageFile(invalid_file)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -39,21 +40,17 @@ def test_unclosed_file():
|
||||||
|
|
||||||
|
|
||||||
def test_closed_file():
|
def test_closed_file():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
im = Image.open(TEST_GIF)
|
im = Image.open(TEST_GIF)
|
||||||
im.load()
|
im.load()
|
||||||
im.close()
|
im.close()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_context_manager():
|
def test_context_manager():
|
||||||
with pytest.warns(None) as record:
|
with warnings.catch_warnings():
|
||||||
with Image.open(TEST_GIF) as im:
|
with Image.open(TEST_GIF) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert not record
|
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
@ -62,6 +59,51 @@ def test_invalid_file():
|
||||||
GifImagePlugin.GifImageFile(invalid_file)
|
GifImagePlugin.GifImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_l_mode_transparency():
|
||||||
|
with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
|
||||||
|
assert im.mode == "L"
|
||||||
|
assert im.load()[0, 0] == 128
|
||||||
|
assert im.info["transparency"] == 255
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
assert im.mode == "L"
|
||||||
|
assert im.load()[0, 0] == 128
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy():
|
||||||
|
with Image.open("Tests/images/chi.gif") as im:
|
||||||
|
expected_zero = im.convert("RGB")
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
expected_one = im.convert("RGB")
|
||||||
|
|
||||||
|
try:
|
||||||
|
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
|
||||||
|
with Image.open("Tests/images/chi.gif") as im:
|
||||||
|
assert im.mode == "RGB"
|
||||||
|
assert_image_equal(im, expected_zero)
|
||||||
|
|
||||||
|
GifImagePlugin.LOADING_STRATEGY = (
|
||||||
|
GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
||||||
|
)
|
||||||
|
# Stay in P mode with only a global palette
|
||||||
|
with Image.open("Tests/images/chi.gif") as im:
|
||||||
|
assert im.mode == "P"
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
assert im.mode == "P"
|
||||||
|
assert_image_equal(im.convert("RGB"), expected_one)
|
||||||
|
|
||||||
|
# Change to RGB mode when a frame has an individual palette
|
||||||
|
with Image.open("Tests/images/iss634.gif") as im:
|
||||||
|
assert im.mode == "P"
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
assert im.mode == "RGB"
|
||||||
|
finally:
|
||||||
|
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
|
||||||
|
|
||||||
|
|
||||||
def test_optimize():
|
def test_optimize():
|
||||||
def test_grayscale(optimize):
|
def test_grayscale(optimize):
|
||||||
im = Image.new("L", (1, 1), 0)
|
im = Image.new("L", (1, 1), 0)
|
||||||
|
@ -163,6 +205,32 @@ def test_roundtrip_save_all(tmp_path):
|
||||||
assert reread.n_frames == 5
|
assert reread.n_frames == 5
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path, mode",
|
||||||
|
(
|
||||||
|
("Tests/images/dispose_bgnd.gif", "RGB"),
|
||||||
|
# Hexeditted copy of dispose_bgnd to add transparency
|
||||||
|
("Tests/images/dispose_bgnd_rgba.gif", "RGBA"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_loading_multiple_palettes(path, mode):
|
||||||
|
with Image.open(path) as im:
|
||||||
|
assert im.mode == "P"
|
||||||
|
first_frame_colors = im.palette.colors.keys()
|
||||||
|
original_color = im.convert("RGB").load()[0, 0]
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
assert im.mode == mode
|
||||||
|
if mode == "RGBA":
|
||||||
|
im = im.convert("RGB")
|
||||||
|
|
||||||
|
# Check a color only from the old palette
|
||||||
|
assert im.load()[0, 0] == original_color
|
||||||
|
|
||||||
|
# Check a color from the new palette
|
||||||
|
assert im.load()[24, 24] not in first_frame_colors
|
||||||
|
|
||||||
|
|
||||||
def test_headers_saving_for_animated_gifs(tmp_path):
|
def test_headers_saving_for_animated_gifs(tmp_path):
|
||||||
important_headers = ["background", "version", "duration", "loop"]
|
important_headers = ["background", "version", "duration", "loop"]
|
||||||
# Multiframe image
|
# Multiframe image
|
||||||
|
@ -184,8 +252,8 @@ def test_palette_handling(tmp_path):
|
||||||
with Image.open(TEST_GIF) as im:
|
with Image.open(TEST_GIF) as im:
|
||||||
im = im.convert("RGB")
|
im = im.convert("RGB")
|
||||||
|
|
||||||
im = im.resize((100, 100), Image.LANCZOS)
|
im = im.resize((100, 100), Image.Resampling.LANCZOS)
|
||||||
im2 = im.convert("P", palette=Image.ADAPTIVE, colors=256)
|
im2 = im.convert("P", palette=Image.Palette.ADAPTIVE, colors=256)
|
||||||
|
|
||||||
f = str(tmp_path / "temp.gif")
|
f = str(tmp_path / "temp.gif")
|
||||||
im2.save(f, optimize=True)
|
im2.save(f, optimize=True)
|
||||||
|
@ -285,6 +353,22 @@ def test_n_frames():
|
||||||
assert im.is_animated == (n_frames != 1)
|
assert im.is_animated == (n_frames != 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_change():
|
||||||
|
# Test n_frames does not change the image
|
||||||
|
with Image.open("Tests/images/dispose_bgnd.gif") as im:
|
||||||
|
im.seek(1)
|
||||||
|
expected = im.copy()
|
||||||
|
assert im.n_frames == 5
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
# Test is_animated does not change the image
|
||||||
|
with Image.open("Tests/images/dispose_bgnd.gif") as im:
|
||||||
|
im.seek(3)
|
||||||
|
expected = im.copy()
|
||||||
|
assert im.is_animated
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_eoferror():
|
def test_eoferror():
|
||||||
with Image.open(TEST_GIF) as im:
|
with Image.open(TEST_GIF) as im:
|
||||||
n_frames = im.n_frames
|
n_frames = im.n_frames
|
||||||
|
@ -324,7 +408,7 @@ def test_dispose_none_load_end():
|
||||||
with Image.open("Tests/images/dispose_none_load_end.gif") as img:
|
with Image.open("Tests/images/dispose_none_load_end.gif") as img:
|
||||||
img.seek(1)
|
img.seek(1)
|
||||||
|
|
||||||
assert_image_equal_tofile(img, "Tests/images/dispose_none_load_end_second.gif")
|
assert_image_equal_tofile(img, "Tests/images/dispose_none_load_end_second.png")
|
||||||
|
|
||||||
|
|
||||||
def test_dispose_background():
|
def test_dispose_background():
|
||||||
|
@ -337,14 +421,45 @@ def test_dispose_background():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_transparent_dispose():
|
def test_dispose_background_transparency():
|
||||||
expected_colors = [(2, 1, 2), (0, 1, 0), (2, 1, 2)]
|
with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img:
|
||||||
with Image.open("Tests/images/transparent_dispose.gif") as img:
|
img.seek(2)
|
||||||
for frame in range(3):
|
px = img.load()
|
||||||
img.seek(frame)
|
assert px[35, 30][3] == 0
|
||||||
for x in range(3):
|
|
||||||
color = img.getpixel((x, 0))
|
|
||||||
assert color == expected_colors[frame][x]
|
@pytest.mark.parametrize(
|
||||||
|
"loading_strategy, expected_colors",
|
||||||
|
(
|
||||||
|
(
|
||||||
|
GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST,
|
||||||
|
(
|
||||||
|
(2, 1, 2),
|
||||||
|
((0, 255, 24, 255), (0, 0, 255, 255), (0, 255, 24, 255)),
|
||||||
|
((0, 0, 0, 0), (0, 0, 255, 255), (0, 0, 0, 0)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY,
|
||||||
|
(
|
||||||
|
(2, 1, 2),
|
||||||
|
(0, 1, 0),
|
||||||
|
(2, 1, 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_transparent_dispose(loading_strategy, expected_colors):
|
||||||
|
GifImagePlugin.LOADING_STRATEGY = loading_strategy
|
||||||
|
try:
|
||||||
|
with Image.open("Tests/images/transparent_dispose.gif") as img:
|
||||||
|
for frame in range(3):
|
||||||
|
img.seek(frame)
|
||||||
|
for x in range(3):
|
||||||
|
color = img.getpixel((x, 0))
|
||||||
|
assert color == expected_colors[frame][x]
|
||||||
|
finally:
|
||||||
|
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
|
||||||
|
|
||||||
|
|
||||||
def test_dispose_previous():
|
def test_dispose_previous():
|
||||||
|
@ -361,7 +476,7 @@ def test_dispose_previous_first_frame():
|
||||||
with Image.open("Tests/images/dispose_prev_first_frame.gif") as im:
|
with Image.open("Tests/images/dispose_prev_first_frame.gif") as im:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert_image_equal_tofile(
|
assert_image_equal_tofile(
|
||||||
im, "Tests/images/dispose_prev_first_frame_seeked.gif"
|
im, "Tests/images/dispose_prev_first_frame_seeked.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -501,7 +616,7 @@ def test_dispose2_background(tmp_path):
|
||||||
|
|
||||||
with Image.open(out) as im:
|
with Image.open(out) as im:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.getpixel((0, 0)) == 0
|
assert im.getpixel((0, 0)) == (255, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
def test_transparency_in_second_frame():
|
def test_transparency_in_second_frame():
|
||||||
|
@ -510,9 +625,9 @@ def test_transparency_in_second_frame():
|
||||||
|
|
||||||
# Seek to the second frame
|
# Seek to the second frame
|
||||||
im.seek(im.tell() + 1)
|
im.seek(im.tell() + 1)
|
||||||
assert im.info["transparency"] == 0
|
assert "transparency" not in im.info
|
||||||
|
|
||||||
assert_image_equal_tofile(im, "Tests/images/different_transparency_merged.gif")
|
assert_image_equal_tofile(im, "Tests/images/different_transparency_merged.png")
|
||||||
|
|
||||||
|
|
||||||
def test_no_transparency_in_second_frame():
|
def test_no_transparency_in_second_frame():
|
||||||
|
@ -684,31 +799,31 @@ def test_zero_comment_subblocks():
|
||||||
def test_version(tmp_path):
|
def test_version(tmp_path):
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
|
|
||||||
def assertVersionAfterSave(im, version):
|
def assert_version_after_save(im, version):
|
||||||
im.save(out)
|
im.save(out)
|
||||||
with Image.open(out) as reread:
|
with Image.open(out) as reread:
|
||||||
assert reread.info["version"] == version
|
assert reread.info["version"] == version
|
||||||
|
|
||||||
# Test that GIF87a is used by default
|
# Test that GIF87a is used by default
|
||||||
im = Image.new("L", (100, 100), "#000")
|
im = Image.new("L", (100, 100), "#000")
|
||||||
assertVersionAfterSave(im, b"GIF87a")
|
assert_version_after_save(im, b"GIF87a")
|
||||||
|
|
||||||
# Test setting the version to 89a
|
# Test setting the version to 89a
|
||||||
im = Image.new("L", (100, 100), "#000")
|
im = Image.new("L", (100, 100), "#000")
|
||||||
im.info["version"] = b"89a"
|
im.info["version"] = b"89a"
|
||||||
assertVersionAfterSave(im, b"GIF89a")
|
assert_version_after_save(im, b"GIF89a")
|
||||||
|
|
||||||
# Test that adding a GIF89a feature changes the version
|
# Test that adding a GIF89a feature changes the version
|
||||||
im.info["transparency"] = 1
|
im.info["transparency"] = 1
|
||||||
assertVersionAfterSave(im, b"GIF89a")
|
assert_version_after_save(im, b"GIF89a")
|
||||||
|
|
||||||
# Test that a GIF87a image is also saved in that format
|
# Test that a GIF87a image is also saved in that format
|
||||||
with Image.open("Tests/images/test.colors.gif") as im:
|
with Image.open("Tests/images/test.colors.gif") as im:
|
||||||
assertVersionAfterSave(im, b"GIF87a")
|
assert_version_after_save(im, b"GIF87a")
|
||||||
|
|
||||||
# Test that a GIF89a image is also saved in that format
|
# Test that a GIF89a image is also saved in that format
|
||||||
im.info["version"] = b"GIF89a"
|
im.info["version"] = b"GIF89a"
|
||||||
assertVersionAfterSave(im, b"GIF87a")
|
assert_version_after_save(im, b"GIF87a")
|
||||||
|
|
||||||
|
|
||||||
def test_append_images(tmp_path):
|
def test_append_images(tmp_path):
|
||||||
|
@ -723,10 +838,10 @@ def test_append_images(tmp_path):
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
|
||||||
# Tests appending using a generator
|
# Tests appending using a generator
|
||||||
def imGenerator(ims):
|
def im_generator(ims):
|
||||||
yield from ims
|
yield from ims
|
||||||
|
|
||||||
im.save(out, save_all=True, append_images=imGenerator(ims))
|
im.save(out, save_all=True, append_images=im_generator(ims))
|
||||||
|
|
||||||
with Image.open(out) as reread:
|
with Image.open(out) as reread:
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
@ -781,6 +896,17 @@ def test_rgb_transparency(tmp_path):
|
||||||
assert "transparency" not in reloaded.info
|
assert "transparency" not in reloaded.info
|
||||||
|
|
||||||
|
|
||||||
|
def test_rgba_transparency(tmp_path):
|
||||||
|
out = str(tmp_path / "temp.gif")
|
||||||
|
|
||||||
|
im = hopper("P")
|
||||||
|
im.save(out, save_all=True, append_images=[Image.new("RGBA", im.size)])
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
reloaded.seek(1)
|
||||||
|
assert_image_equal(hopper("P").convert("RGB"), reloaded)
|
||||||
|
|
||||||
|
|
||||||
def test_bbox(tmp_path):
|
def test_bbox(tmp_path):
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
|
|
||||||
|
@ -811,7 +937,7 @@ def test_palette_save_P(tmp_path):
|
||||||
# Forcing a non-straight grayscale palette.
|
# Forcing a non-straight grayscale palette.
|
||||||
|
|
||||||
im = hopper("P")
|
im = hopper("P")
|
||||||
palette = bytes([255 - i // 3 for i in range(768)])
|
palette = bytes(255 - i // 3 for i in range(768))
|
||||||
|
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
im.save(out, palette=palette)
|
im.save(out, palette=palette)
|
||||||
|
@ -856,7 +982,7 @@ def test_palette_save_ImagePalette(tmp_path):
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
im.putpalette(palette)
|
im.putpalette(palette)
|
||||||
assert_image_equal(reloaded, im)
|
assert_image_equal(reloaded.convert("RGB"), im.convert("RGB"))
|
||||||
|
|
||||||
|
|
||||||
def test_save_I(tmp_path):
|
def test_save_I(tmp_path):
|
||||||
|
@ -874,11 +1000,11 @@ def test_save_I(tmp_path):
|
||||||
def test_getdata():
|
def test_getdata():
|
||||||
# Test getheader/getdata against legacy values.
|
# Test getheader/getdata against legacy values.
|
||||||
# Create a 'P' image with holes in the palette.
|
# Create a 'P' image with holes in the palette.
|
||||||
im = Image._wedge().resize((16, 16), Image.NEAREST)
|
im = Image._wedge().resize((16, 16), Image.Resampling.NEAREST)
|
||||||
im.putpalette(ImagePalette.ImagePalette("RGB"))
|
im.putpalette(ImagePalette.ImagePalette("RGB"))
|
||||||
im.info = {"background": 0}
|
im.info = {"background": 0}
|
||||||
|
|
||||||
passed_palette = bytes([255 - i // 3 for i in range(768)])
|
passed_palette = bytes(255 - i // 3 for i in range(768))
|
||||||
|
|
||||||
GifImagePlugin._FORCE_OPTIMIZE = True
|
GifImagePlugin._FORCE_OPTIMIZE = True
|
||||||
try:
|
try:
|
||||||
|
@ -910,6 +1036,11 @@ def test_lzw_bits():
|
||||||
def test_extents():
|
def test_extents():
|
||||||
with Image.open("Tests/images/test_extents.gif") as im:
|
with Image.open("Tests/images/test_extents.gif") as im:
|
||||||
assert im.size == (100, 100)
|
assert im.size == (100, 100)
|
||||||
|
|
||||||
|
# Check that n_frames does not change the size
|
||||||
|
assert im.n_frames == 2
|
||||||
|
assert im.size == (100, 100)
|
||||||
|
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.size == (150, 150)
|
assert im.size == (150, 150)
|
||||||
|
|
||||||
|
@ -919,4 +1050,14 @@ def test_missing_background():
|
||||||
# but the disposal method is "Restore to background color"
|
# but the disposal method is "Restore to background color"
|
||||||
with Image.open("Tests/images/missing_background.gif") as im:
|
with Image.open("Tests/images/missing_background.gif") as im:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert_image_equal_tofile(im, "Tests/images/missing_background_first_frame.gif")
|
assert_image_equal_tofile(im, "Tests/images/missing_background_first_frame.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_saving_rgba(tmp_path):
|
||||||
|
out = str(tmp_path / "temp.gif")
|
||||||
|
with Image.open("Tests/images/transparent.png") as im:
|
||||||
|
im.save(out)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
reloaded_rgba = reloaded.convert("RGBA")
|
||||||
|
assert reloaded_rgba.load()[0, 0][3] == 0
|
||||||
|
|
|
@ -45,3 +45,35 @@ def test_save(tmp_path):
|
||||||
# Act / Assert: stub cannot save without an implemented handler
|
# Act / Assert: stub cannot save without an implemented handler
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
im.save(tmpfile)
|
im.save(tmpfile)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handler(tmp_path):
|
||||||
|
class TestHandler:
|
||||||
|
opened = False
|
||||||
|
loaded = False
|
||||||
|
saved = False
|
||||||
|
|
||||||
|
def open(self, im):
|
||||||
|
self.opened = True
|
||||||
|
|
||||||
|
def load(self, im):
|
||||||
|
self.loaded = True
|
||||||
|
return Image.new("RGB", (1, 1))
|
||||||
|
|
||||||
|
def save(self, im, fp, filename):
|
||||||
|
self.saved = True
|
||||||
|
|
||||||
|
handler = TestHandler()
|
||||||
|
GribStubImagePlugin.register_handler(handler)
|
||||||
|
with Image.open(TEST_FILE) as im:
|
||||||
|
assert handler.opened
|
||||||
|
assert not handler.loaded
|
||||||
|
|
||||||
|
im.load()
|
||||||
|
assert handler.loaded
|
||||||
|
|
||||||
|
temp_file = str(tmp_path / "temp.grib")
|
||||||
|
im.save(temp_file)
|
||||||
|
assert handler.saved
|
||||||
|
|
||||||
|
GribStubImagePlugin._handler = None
|
||||||
|
|
|
@ -46,3 +46,35 @@ def test_save():
|
||||||
im.save(dummy_filename)
|
im.save(dummy_filename)
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
Hdf5StubImagePlugin._save(im, dummy_fp, dummy_filename)
|
Hdf5StubImagePlugin._save(im, dummy_fp, dummy_filename)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handler(tmp_path):
|
||||||
|
class TestHandler:
|
||||||
|
opened = False
|
||||||
|
loaded = False
|
||||||
|
saved = False
|
||||||
|
|
||||||
|
def open(self, im):
|
||||||
|
self.opened = True
|
||||||
|
|
||||||
|
def load(self, im):
|
||||||
|
self.loaded = True
|
||||||
|
return Image.new("RGB", (1, 1))
|
||||||
|
|
||||||
|
def save(self, im, fp, filename):
|
||||||
|
self.saved = True
|
||||||
|
|
||||||
|
handler = TestHandler()
|
||||||
|
Hdf5StubImagePlugin.register_handler(handler)
|
||||||
|
with Image.open(TEST_FILE) as im:
|
||||||
|
assert handler.opened
|
||||||
|
assert not handler.loaded
|
||||||
|
|
||||||
|
im.load()
|
||||||
|
assert handler.loaded
|
||||||
|
|
||||||
|
temp_file = str(tmp_path / "temp.h5")
|
||||||
|
im.save(temp_file)
|
||||||
|
assert handler.saved
|
||||||
|
|
||||||
|
Hdf5StubImagePlugin._handler = None
|
||||||
|
|