Merge branch 'main' into master

This commit is contained in:
Andrew Murray 2022-04-17 12:23:19 +10:00 committed by GitHub
commit 0bfbea0ab9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
322 changed files with 8177 additions and 5928 deletions

View File

@ -10,29 +10,29 @@ environment:
TEST_OPTIONS:
DEPLOY: YES
matrix:
- PYTHON: C:/Python39
- PYTHON: C:/Python310
ARCHITECTURE: x86
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- PYTHON: C:/Python36-x64
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
- PYTHON: C:/Python37-x64
ARCHITECTURE: x64
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
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:\
- 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
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
- ..\pillow-depends\gs9540w32.exe /S
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.54.0\bin;%PATH%
- ..\pillow-depends\gs9561w32.exe /S
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.56.1\bin;%PATH%
- cd c:\pillow\winbuild\
- ps: |
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
c:\pillow\winbuild\build\build_dep_all.cmd
$host.SetShouldExit(0)
- path C:\pillow\winbuild\build\bin;%PATH%
- '%PYTHON%\%EXECUTABLE% -m pip install -U setuptools'
build_script:
- ps: |
@ -43,7 +43,7 @@ build_script:
test_script:
- 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%
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
@ -84,7 +84,7 @@ deploy:
artifact: /.*egg|wheel/
on:
APPVEYOR_REPO_NAME: python-pillow/Pillow
branch: master
branch: main
deploy: YES

View File

@ -1,7 +1,7 @@
#!/bin/bash
# gather the coverage data
pip3 install codecov
python3 -m pip install codecov
if [[ $MATRIX_DOCKER ]]; then
coverage xml --ignore-errors
else

View File

@ -19,7 +19,7 @@ set -e
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
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 wheel
@ -32,8 +32,7 @@ python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
python3 -m pip install test-image-results
# TODO Remove condition when numpy supports 3.10
if ! [ "$GHA_PYTHON_VERSION" == "3.10-dev" ]; then python3 -m pip install numpy ; fi
python3 -m pip install numpy
# PyQt5 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then

View File

@ -4,13 +4,13 @@ Bug fixes, feature additions, tests, documentation and more can be contributed v
## 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.
- Create a branch from master.
- Create a branch from `main`.
- 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.
- 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
@ -18,7 +18,7 @@ Please send a pull request to the master branch. Please include [documentation](
- Provide tests for any newly added code.
- Follow PEP 8.
- 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
@ -35,4 +35,4 @@ The best reproductions are self-contained scripts with minimal dependencies. If
## 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
View File

@ -7,6 +7,7 @@ pull_request_rules:
- status-success=Test Successful
- status-success=Docker Test Successful
- status-success=Windows Test Successful
- status-success=MinGW Test Successful
- status-success=continuous-integration/appveyor/pr
actions:
merge:

View File

@ -1,4 +1,5 @@
name: CIFuzz
on:
push:
paths:
@ -8,6 +9,7 @@ on:
paths:
- "**.c"
- "**.h"
workflow_dispatch:
jobs:
Fuzzing:
@ -29,13 +31,13 @@ jobs:
language: python
dry-run: false
- name: Upload New Crash
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
- name: Upload Legacy Crash
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: steps.run.outcome == 'success'
with:
name: crash

View File

@ -1,6 +1,6 @@
name: Lint
on: [push, pull_request]
on: [push, pull_request, workflow_dispatch]
jobs:
build:
@ -10,15 +10,7 @@ jobs:
name: Lint
steps:
- uses: actions/checkout@v2
- name: pip cache
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: lint-pip-${{ hashFiles('**/setup.py') }}
restore-keys: |
lint-pip-
- uses: actions/checkout@v3
- name: pre-commit cache
uses: actions/cache@v2
@ -29,9 +21,11 @@ jobs:
lint-pre-commit-
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v3
with:
python-version: 3.8
python-version: "3.10"
cache: pip
cache-dependency-path: "setup.py"
- name: Build system information
run: python3 .github/workflows/system-info.py
@ -45,4 +39,3 @@ jobs:
run: tox -e lint
env:
PRE_COMMIT_COLOR: always

View File

@ -4,14 +4,15 @@ on:
push:
# branches to consider in the event; optional, defaults to all
branches:
- master
- main
workflow_dispatch:
jobs:
update_release_draft:
if: github.repository == 'python-pillow/Pillow'
runs-on: ubuntu-latest
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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

27
.github/workflows/stale.yml vendored Normal file
View 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"

View File

@ -1,6 +1,6 @@
name: Test Docker
on: [push, pull_request]
on: [push, pull_request, workflow_dispatch]
jobs:
build:
@ -19,14 +19,16 @@ jobs:
amazon-2-amd64,
arch,
centos-7-amd64,
centos-8-amd64,
centos-stream-8-amd64,
centos-stream-9-amd64,
debian-10-buster-x86,
fedora-33-amd64,
fedora-34-amd64,
debian-11-bullseye-x86,
fedora-35-amd64,
gentoo,
ubuntu-18.04-bionic-amd64,
ubuntu-20.04-focal-amd64,
]
dockerTag: [master]
dockerTag: [main]
include:
- docker: "ubuntu-20.04-focal-arm64v8"
qemu-arch: "aarch64"
@ -38,7 +40,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build system information
run: python3 .github/workflows/system-info.py

85
.github/workflows/test-mingw.yml vendored Normal file
View 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

View File

@ -11,6 +11,7 @@ on:
paths:
- "**.c"
- "**.h"
workflow_dispatch:
jobs:
build:
@ -22,12 +23,12 @@ jobs:
docker: [
ubuntu-20.04-focal-amd64-valgrind,
]
dockerTag: [master]
dockerTag: [main]
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build system information
run: python3 .github/workflows/system-info.py
@ -42,11 +43,3 @@ jobs:
sudo chown -R 1000 $GITHUB_WORKSPACE
docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
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

View File

@ -1,52 +1,44 @@
name: Test Windows
on: [push, pull_request]
on: [push, pull_request, workflow_dispatch]
jobs:
build:
runs-on: windows-2019
runs-on: windows-latest
strategy:
fail-fast: false
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"]
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
- python-version: "pypy-3.7"
architecture: "x64"
- python-version: "pypy-3.8"
architecture: "x64"
timeout-minutes: 30
name: Python ${{ matrix.python-version }} ${{ matrix.architecture }}
steps:
- name: Checkout Pillow
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Checkout cached dependencies
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
repository: python-pillow/pillow-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
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }}
cache: pip
cache-dependency-path: ".github/workflows/test-windows.yml"
- name: Print build system information
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\"
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
winbuild\depends\gs9540w32.exe /S
echo "C:\Program Files (x86)\gs\gs9.54.0\bin" >> $env:GITHUB_PATH
winbuild\depends\gs9561w32.exe /S
echo "C:\Program Files (x86)\gs\gs9.56.1\bin" >> $env:GITHUB_PATH
xcopy /S /Y winbuild\depends\test_images\* Tests\images\
@ -140,15 +132,16 @@ jobs:
- name: Build Pillow
run: |
$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
& $env:pythonLocation\python.exe selftest.py --installed
shell: pwsh
# failing with PyPy3
# skip PyPy for speed
- name: Enable heap verification
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
run: |
@ -163,7 +156,7 @@ jobs:
shell: bash
- name: Upload errors
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: failure()
with:
name: errors
@ -183,92 +176,20 @@ jobs:
- name: Build wheel
id: wheel
if: "github.event_name == 'push'"
if: "github.event_name != 'pull_request'"
run: |
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
shell: cmd
- uses: actions/upload-artifact@v2
if: "github.event_name == 'push'"
- uses: actions/upload-artifact@v3
if: "github.event_name != 'pull_request'"
with:
name: ${{ steps.wheel.outputs.dist }}
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:
needs: [build, msys]
needs: build
runs-on: ubuntu-latest
name: Windows Test Successful
steps:

View File

@ -1,6 +1,6 @@
name: Test
on: [push, pull_request]
on: [push, pull_request, workflow_dispatch]
jobs:
build:
@ -9,54 +9,41 @@ jobs:
fail-fast: false
matrix:
os: [
"macos-latest",
"ubuntu-latest",
"macOS-latest",
]
python-version: [
"pypy-3.8",
"pypy-3.7",
"pypy-3.6",
"3.10-dev",
"3.10",
"3.9",
"3.8",
"3.7",
"3.6",
]
include:
- python-version: "3.6"
- python-version: "3.7"
PYTHONOPTIMIZE: 1
REVERSE: "--reverse"
- python-version: "3.7"
- python-version: "3.8"
PYTHONOPTIMIZE: 2
# Include new variables for Codecov
- os: ubuntu-latest
codecov-flag: GHA_Ubuntu
- os: macOS-latest
- os: macos-latest
codecov-flag: GHA_macOS
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Get pip cache dir
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 }}-
cache: pip
cache-dependency-path: ".ci/*.sh"
- name: Build system information
run: python3 .github/workflows/system-info.py
@ -97,16 +84,16 @@ jobs:
mkdir -p Tests/errors
- name: Upload errors
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: failure()
with:
name: errors
path: Tests/errors
- name: Docs
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.9
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.10
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
- name: After success

26
.github/workflows/tidelift.yml vendored Normal file
View 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
View File

@ -83,6 +83,7 @@ docs/_build/
Tests/images/README.md
Tests/images/crash_1.tif
Tests/images/crash_2.tif
Tests/images/crash-81154a65438ba5aaeca73fd502fa4850fbde60f8.tif
Tests/images/string_dimension.tiff
Tests/images/jpeg2000
Tests/images/msp

View File

@ -1,43 +1,43 @@
repos:
- repo: https://github.com/psf/black
rev: e3000ace2fd1fcb1c181bb7a8285f1f976bcbdc7 # frozen: 21.7b0
rev: 22.3.0
hooks:
- id: black
args: ["--target-version", "py36"]
args: ["--target-version", "py37"]
# Only .py files, until https://github.com/psf/black/issues/402 resolved
files: \.py$
types: []
- repo: https://github.com/PyCQA/isort
rev: fd5ba70665a37ec301a1f714ed09336048b3be63 # frozen: 5.9.3
rev: 5.10.1
hooks:
- id: isort
- repo: https://github.com/asottile/yesqa
rev: 644ede78511c02fc6f8e03e014cc1ddcfbf1e1f5 # frozen: v1.2.3
rev: v1.3.0
hooks:
- id: yesqa
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: 3592548bbd98528887eeed63486cf6c9bae00b98 # frozen: v1.1.10
rev: v1.1.13
hooks:
- id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
- repo: https://gitlab.com/pycqa/flake8
rev: dcd740bc0ebaf2b3d43e59a0060d157c97de13f3 # frozen: 3.9.2
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
hooks:
- id: flake8
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: 6f51a66bba59954917140ec2eeeaa4d5e630e6ce # frozen: v1.9.0
rev: v1.9.0
hooks:
- id: python-check-blanket-noqa
- id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 38b88246ccc552bffaaf54259d064beeee434539 # frozen: v4.0.1
rev: v4.1.0
hooks:
- id: check-merge-conflict
- id: check-yaml

View File

@ -1,2 +1,8 @@
version: 2
python:
pip_install: true
install:
- method: pip
path: .
extra_requirements:
- docs

View File

@ -2,9 +2,300 @@
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
[radarhere]
@ -59,12 +350,30 @@ Changelog (Pillow)
- Fixed ImageOps expand with tuple border on P image #5615
[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
[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)
------------------
@ -338,7 +647,7 @@ Changelog (Pillow)
- Changed Image.open formats parameter to be case-insensitive #5250
[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]
- Added tk version to pilinfo #5226

View File

@ -5,7 +5,7 @@ The Python Imaging Library (PIL) 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:

View File

@ -1,6 +1,7 @@
include *.c
include *.h
include *.in
include *.lock
include *.md
include *.py
include *.rst
@ -9,6 +10,7 @@ include *.txt
include *.yaml
include LICENSE
include Makefile
include Pipfile
include tox.ini
graft Tests
graft src

View File

@ -9,9 +9,11 @@ clean:
.PHONY: 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
coverage report
python3 -c "import coverage" > /dev/null 2>&1 || python3 -m pip install coverage
python3 -m coverage report
.PHONY: doc
doc:
@ -33,33 +35,29 @@ help:
@echo "Welcome to Pillow development. Please use \`make <target>\` where <target> is one of"
@echo " clean remove build products"
@echo " coverage run coverage test (in progress)"
@echo " doc make html docs"
@echo " docserve run an http server on the docs directory"
@echo " doc make HTML docs"
@echo " docserve run an HTTP server on the docs directory"
@echo " html to make standalone HTML files"
@echo " inplace make inplace extension"
@echo " install make and install"
@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-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 " 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"
@echo " test run tests on installed Pillow"
.PHONY: inplace
inplace: clean
python3 setup.py develop build_ext --inplace
python3 -m pip install -e --global-option="build_ext" --global-option="--inplace" .
.PHONY: install
install:
python3 setup.py install
python3 -m pip install .
python3 selftest.py
.PHONY: 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
.PHONY: debug
@ -68,58 +66,52 @@ debug:
# for our stuff, kills optimization, and redirects to dev null so we
# see any build failures.
make clean > /dev/null
CFLAGS='-g -O0' python3 setup.py build_ext install > /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
CFLAGS='-g -O0' python3 -m pip install --global-option="build_ext" . > /dev/null
.PHONY: release-test
release-test:
$(MAKE) install-req
python3 setup.py develop
python3 -m pip install -e .[tests]
python3 selftest.py
python3 -m pytest Tests
python3 setup.py install
python3 -m pip install .
-rm dist/*.egg
-rmdir dist
python3 -m pytest -qq
check-manifest
pyroma .
python3 -m check_manifest
python3 -m pyroma .
$(MAKE) readme
.PHONY: 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
test:
pytest -qq
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
python3 -m pytest -qq
.PHONY: 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 \
--log-file=/tmp/valgrind-output \
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
.PHONY: 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
lint:
tox --help > /dev/null || python3 -m pip install tox
tox -e lint
python3 -c "import tox" > /dev/null 2>&1 || python3 -m pip install tox
python3 -m tox -e lint
.PHONY: lint-fix
lint-fix:
black --target-version py36 .
isort .
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort
python3 -m black --target-version py37 .
python3 -m isort .

22
Pipfile Normal file
View 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
View 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": {}
}

View File

@ -1,5 +1,5 @@
<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>
# Pillow
@ -24,30 +24,36 @@ As of 2019, Pillow development is
<tr>
<th>tests</th>
<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)"
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)"
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)"
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)"
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
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
alt="GitHub Actions wheels build status (Wheels)"
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)"
src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/master.svg?label=aarch64%20wheels"></a>
<a href="https://codecov.io/gh/python-pillow/Pillow"><img
src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels"></a>
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
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>
</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/)
- [Installation](https://pillow.readthedocs.io/en/latest/installation.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)
- [Pull requests](https://github.com/python-pillow/Pillow/pulls)
- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
- [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst)
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
## Report a Vulnerability

View File

@ -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.
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
* [ ] Develop and prepare release 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 `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 `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.
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
* [ ] Update `CHANGES.rst`.
@ -24,13 +24,13 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
* [ ] Create and check source distribution:
```bash
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.:
```bash
twine check dist/*
twine upload dist/Pillow-5.2.0*
python3 -m twine check --strict dist/*
python3 -m twine upload dist/Pillow-5.2.0*
```
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
* [ ] In compliance with [PEP 440](https://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.
* [ ] Make necessary changes in `master` branch.
* [ ] Make necessary changes in `main` branch.
* [ ] Update `CHANGES.rst`.
* [ ] Check out release branch e.g.:
```bash
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:
```bash
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.:
```bash
twine check dist/*
twine upload dist/Pillow-5.2.1*
python3 -m twine check --strict dist/*
python3 -m twine upload dist/Pillow-5.2.1*
```
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
@ -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.
* [ ] 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.
* [ ] Privately send to distros.
* [ ] 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:
```bash
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)
## Binary Distributions

View File

@ -4,5 +4,5 @@ import sys
from PIL import Image
if sys.maxsize < 2 ** 32:
if sys.maxsize < 2**32:
im = Image.new("L", (999999, 999999), 0)

View File

@ -61,7 +61,7 @@ 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:
im.load()
except Exception as msg:

View File

@ -19,7 +19,7 @@ from PIL import Image
repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2")
for path in repro:
im = Image.open(path)
with Image.open(path) as im:
try:
im.load()
except Exception as msg:

View File

@ -23,7 +23,7 @@ YDIM = 32769
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):

View File

@ -19,7 +19,7 @@ YDIM = 32769
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):

View File

@ -30,7 +30,6 @@ if os.environ.get("SHOW_ERRORS", None):
a.show()
b.show()
elif "GITHUB_ACTIONS" in os.environ:
HAS_UPLOADER = True
@ -44,7 +43,6 @@ elif "GITHUB_ACTIONS" in os.environ:
b.save(os.path.join(tmpdir, "b.png"))
return tmpdir
else:
try:
import test_image_results
@ -326,7 +324,7 @@ def is_mingw():
return sysconfig.get_platform() == "mingw"
class cached_property:
class CachedProperty:
def __init__(self, func):
self.func = func

BIN
Tests/images/16bit.r.tif Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
Tests/images/no_palette.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -22,7 +22,7 @@ for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
fuzzer_basename=$(basename -s .py $fuzzer)
fuzzer_package=${fuzzer_basename}.pkg
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/liblcms2.so.2:. \
--add-binary /usr/local/lib/libopenjp2.so.7:. \

View File

@ -14,10 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import atheris_no_libfuzzer as atheris
import fuzzers
import atheris
with atheris.instrument_imports():
import sys
import fuzzers
def TestOneInput(data):
@ -26,13 +29,12 @@ def TestOneInput(data):
except Exception:
# We're catching all exceptions because Pillow's exceptions are
# directly inheriting from Exception.
return
return
pass
def main():
fuzzers.enable_decompressionbomb_error()
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
fuzzers.disable_decompressionbomb_error()

View File

@ -14,10 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import atheris_no_libfuzzer as atheris
import fuzzers
import atheris
with atheris.instrument_imports():
import sys
import fuzzers
def TestOneInput(data):
@ -26,13 +29,12 @@ def TestOneInput(data):
except Exception:
# We're catching all exceptions because Pillow's exceptions are
# directly inheriting from Exception.
return
return
pass
def main():
fuzzers.enable_decompressionbomb_error()
atheris.Setup(sys.argv, TestOneInput, enable_python_coverage=True)
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
fuzzers.disable_decompressionbomb_error()

View File

@ -1,6 +1,5 @@
import os
import pytest
import warnings
from PIL import Image
@ -20,16 +19,14 @@ def test_bad():
either"""
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:
with Image.open(f) as im:
im.load()
except Exception: # as msg:
pass
# Assert that there is no unclosed file warning
assert not record
def test_questionable():
"""These shouldn't crash/dos, but it's not well defined that these
@ -43,6 +40,7 @@ def test_questionable():
"rgb32fakealpha.bmp",
"rgb24largepal.bmp",
"pal8os2sp.bmp",
"pal8rletrns.bmp",
"rgb32bf-xbgr.bmp",
]
for f in get_files("q"):

View File

@ -25,7 +25,7 @@ def box_blur(image, radius=1, n=1):
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())
for data_row in data:
im_row = [next(it) for _ in range(im.size[0])]
@ -35,12 +35,12 @@ def assertImage(im, data, delta=0):
next(it)
def assertBlur(im, radius, data, passes=1, delta=0):
def assert_blur(im, radius, data, passes=1, delta=0):
# 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))
for band in box_blur(rgba, radius, passes).split():
assertImage(band, data, delta)
assert_image(band, data, delta)
def test_color_modes():
@ -64,7 +64,7 @@ def test_color_modes():
def test_radius_0():
assertBlur(
assert_blur(
sample,
0,
[
@ -80,7 +80,7 @@ def test_radius_0():
def test_radius_0_02():
assertBlur(
assert_blur(
sample,
0.02,
[
@ -97,7 +97,7 @@ def test_radius_0_02():
def test_radius_0_05():
assertBlur(
assert_blur(
sample,
0.05,
[
@ -114,7 +114,7 @@ def test_radius_0_05():
def test_radius_0_1():
assertBlur(
assert_blur(
sample,
0.1,
[
@ -131,7 +131,7 @@ def test_radius_0_1():
def test_radius_0_5():
assertBlur(
assert_blur(
sample,
0.5,
[
@ -148,7 +148,7 @@ def test_radius_0_5():
def test_radius_1():
assertBlur(
assert_blur(
sample,
1,
[
@ -165,7 +165,7 @@ def test_radius_1():
def test_radius_1_5():
assertBlur(
assert_blur(
sample,
1.5,
[
@ -182,7 +182,7 @@ def test_radius_1_5():
def test_radius_bigger_then_half():
assertBlur(
assert_blur(
sample,
3,
[
@ -199,7 +199,7 @@ def test_radius_bigger_then_half():
def test_radius_bigger_then_width():
assertBlur(
assert_blur(
sample,
10,
[
@ -214,7 +214,7 @@ def test_radius_bigger_then_width():
def test_extreme_large_radius():
assertBlur(
assert_blur(
sample,
600,
[
@ -229,7 +229,7 @@ def test_extreme_large_radius():
def test_two_passes():
assertBlur(
assert_blur(
sample,
1,
[
@ -247,7 +247,7 @@ def test_two_passes():
def test_three_passes():
assertBlur(
assert_blur(
sample,
1,
[

View File

@ -15,27 +15,27 @@ except ImportError:
class TestColorLut3DCoreAPI:
def generate_identity_table(self, channels, size):
if isinstance(size, tuple):
size1D, size2D, size3D = size
size_1d, size_2d, size_3d = size
else:
size1D, size2D, size3D = (size, size, size)
size_1d, size_2d, size_3d = (size, size, size)
table = [
[
r / (size1D - 1) if size1D != 1 else 0,
g / (size2D - 1) if size2D != 1 else 0,
b / (size3D - 1) if size3D != 1 else 0,
r / (size1D - 1) if size1D != 1 else 0,
g / (size2D - 1) if size2D != 1 else 0,
r / (size_1d - 1) if size_1d != 1 else 0,
g / (size_2d - 1) if size_2d != 1 else 0,
b / (size_3d - 1) if size_3d != 1 else 0,
r / (size_1d - 1) if size_1d != 1 else 0,
g / (size_2d - 1) if size_2d != 1 else 0,
][:channels]
for b in range(size3D)
for g in range(size2D)
for r in range(size1D)
for b in range(size_3d)
for g in range(size_2d)
for r in range(size_1d)
]
return (
channels,
size1D,
size2D,
size3D,
size_1d,
size_2d,
size_3d,
[item for sublist in table for item in sublist],
)
@ -43,107 +43,158 @@ class TestColorLut3DCoreAPI:
im = Image.new("RGB", (10, 10), 0)
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"):
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"):
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(
"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"):
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"):
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"):
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):
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):
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):
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(
"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(
"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(
"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(
"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):
with pytest.raises(ValueError, match="wrong mode"):
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(
"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"):
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):
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.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.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.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):
g = Image.linear_gradient("L")
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
@ -152,7 +203,9 @@ class TestColorLut3DCoreAPI:
im,
im._new(
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._new(
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):
g = Image.linear_gradient("L")
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
@ -178,7 +238,9 @@ class TestColorLut3DCoreAPI:
Image.merge("RGBA", (im.split() * 2)[:4]),
im._new(
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",
[
g,
g.transpose(Image.ROTATE_90),
g.transpose(Image.ROTATE_180),
g.transpose(Image.ROTATE_270),
g.transpose(Image.Transpose.ROTATE_90),
g.transpose(Image.Transpose.ROTATE_180),
g.transpose(Image.Transpose.ROTATE_270),
],
)
@ -199,7 +261,9 @@ class TestColorLut3DCoreAPI:
im,
im._new(
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):
g = Image.linear_gradient("L")
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
# fmt: off
assert_image_equal(
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, [
0, 0, 0, 0, 0, 1,
0, 1, 0, 0, 1, 1,
@ -227,11 +296,16 @@ class TestColorLut3DCoreAPI:
def test_overflow(self):
g = Image.linear_gradient("L")
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
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,
[
-1, -1, -1, 2, -1, -1,
@ -251,7 +325,7 @@ class TestColorLut3DCoreAPI:
assert transformed[205, 205] == (255, 255, 0)
# 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, -3, -3, 5, -3, -3,
@ -354,7 +428,12 @@ class TestColorLut3DFilter:
def test_numpy_formats(self):
g = Image.linear_gradient("L")
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))
@ -445,7 +524,12 @@ class TestGenerateColorLut3D:
g = Image.linear_gradient("L")
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)

View File

@ -110,9 +110,9 @@ class TestCoreMemory:
with pytest.raises(ValueError):
Image.core.set_blocks_max(-1)
if sys.maxsize < 2 ** 32:
if sys.maxsize < 2**32:
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")
def test_set_blocks_max_stats(self):

View File

@ -78,7 +78,7 @@ class TestDecompressionCrop:
def teardown_class(self):
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
def testEnlargeCrop(self):
def test_enlarge_crop(self):
# Crops can extend the extents, therefore we should have the
# same decompression bomb warnings on them.
with hopper() as src:
@ -86,21 +86,12 @@ class TestDecompressionCrop:
pytest.warns(Image.DecompressionBombWarning, src.crop, box)
def test_crop_decompression_checks(self):
im = Image.new("RGB", (100, 100))
good_values = ((-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:
for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)):
assert im.crop(value).size == (9, 9)
for value in warning_values:
pytest.warns(Image.DecompressionBombWarning, im.crop, value)
pytest.warns(Image.DecompressionBombWarning, im.crop, (-160, -160, 99, 99))
for value in error_values:
with pytest.raises(Image.DecompressionBombError):
im.crop(value)
im.crop((-99909, -99990, 99999, 99999))

91
Tests/test_deprecate.py Normal file
View 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)

View File

@ -120,9 +120,9 @@ def test_apng_dispose_op_previous_frame():
# save_all=True,
# append_images=[green, blue],
# disposal=[
# PngImagePlugin.APNG_DISPOSE_OP_NONE,
# PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
# PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS
# PngImagePlugin.Disposal.OP_NONE,
# PngImagePlugin.Disposal.OP_PREVIOUS,
# PngImagePlugin.Disposal.OP_PREVIOUS
# ],
# )
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.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):
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))
transparent = Image.new("RGBA", size, (0, 0, 0, 0))
# test APNG_DISPOSE_OP_NONE
# test OP_NONE
red.save(
test_file,
save_all=True,
append_images=[green, transparent],
disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
disposal=PngImagePlugin.Disposal.OP_NONE,
blend=PngImagePlugin.Blend.OP_OVER,
)
with Image.open(test_file) as im:
im.seek(2)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
# test APNG_DISPOSE_OP_BACKGROUND
# test OP_BACKGROUND
disposal = [
PngImagePlugin.APNG_DISPOSE_OP_NONE,
PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND,
PngImagePlugin.APNG_DISPOSE_OP_NONE,
PngImagePlugin.Disposal.OP_NONE,
PngImagePlugin.Disposal.OP_BACKGROUND,
PngImagePlugin.Disposal.OP_NONE,
]
red.save(
test_file,
save_all=True,
append_images=[red, transparent],
disposal=disposal,
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
blend=PngImagePlugin.Blend.OP_OVER,
)
with Image.open(test_file) as im:
im.seek(2)
@ -481,26 +487,26 @@ def test_apng_save_disposal(tmp_path):
assert im.getpixel((64, 32)) == (0, 0, 0, 0)
disposal = [
PngImagePlugin.APNG_DISPOSE_OP_NONE,
PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND,
PngImagePlugin.Disposal.OP_NONE,
PngImagePlugin.Disposal.OP_BACKGROUND,
]
red.save(
test_file,
save_all=True,
append_images=[green],
disposal=disposal,
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
blend=PngImagePlugin.Blend.OP_OVER,
)
with Image.open(test_file) as im:
im.seek(1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
# test APNG_DISPOSE_OP_PREVIOUS
# test OP_PREVIOUS
disposal = [
PngImagePlugin.APNG_DISPOSE_OP_NONE,
PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
PngImagePlugin.APNG_DISPOSE_OP_NONE,
PngImagePlugin.Disposal.OP_NONE,
PngImagePlugin.Disposal.OP_PREVIOUS,
PngImagePlugin.Disposal.OP_NONE,
]
red.save(
test_file,
@ -508,7 +514,7 @@ def test_apng_save_disposal(tmp_path):
append_images=[green, red, transparent],
default_image=True,
disposal=disposal,
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
blend=PngImagePlugin.Blend.OP_OVER,
)
with Image.open(test_file) as im:
im.seek(3)
@ -516,21 +522,32 @@ def test_apng_save_disposal(tmp_path):
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
disposal = [
PngImagePlugin.APNG_DISPOSE_OP_NONE,
PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
PngImagePlugin.Disposal.OP_NONE,
PngImagePlugin.Disposal.OP_PREVIOUS,
]
red.save(
test_file,
save_all=True,
append_images=[green],
disposal=disposal,
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
blend=PngImagePlugin.Blend.OP_OVER,
)
with Image.open(test_file) as im:
im.seek(1)
assert im.getpixel((0, 0)) == (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):
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))
green = Image.new("RGBA", size, (0, 255, 0, 255))
# test APNG_DISPOSE_OP_NONE
# test OP_NONE
transparent.save(
test_file,
save_all=True,
append_images=[red, green],
disposal=PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
disposal=PngImagePlugin.Disposal.OP_PREVIOUS,
)
with Image.open(test_file) as im:
im.seek(2)
@ -559,17 +576,17 @@ def test_apng_save_blend(tmp_path):
green = Image.new("RGBA", size, (0, 255, 0, 255))
transparent = Image.new("RGBA", size, (0, 0, 0, 0))
# test APNG_BLEND_OP_SOURCE on solid color
# test OP_SOURCE on solid color
blend = [
PngImagePlugin.APNG_BLEND_OP_OVER,
PngImagePlugin.APNG_BLEND_OP_SOURCE,
PngImagePlugin.Blend.OP_OVER,
PngImagePlugin.Blend.OP_SOURCE,
]
red.save(
test_file,
save_all=True,
append_images=[red, green],
default_image=True,
disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
disposal=PngImagePlugin.Disposal.OP_NONE,
blend=blend,
)
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((64, 32)) == (0, 255, 0, 255)
# test APNG_BLEND_OP_SOURCE on transparent color
# test OP_SOURCE on transparent color
blend = [
PngImagePlugin.APNG_BLEND_OP_OVER,
PngImagePlugin.APNG_BLEND_OP_SOURCE,
PngImagePlugin.Blend.OP_OVER,
PngImagePlugin.Blend.OP_SOURCE,
]
red.save(
test_file,
save_all=True,
append_images=[red, transparent],
default_image=True,
disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
disposal=PngImagePlugin.Disposal.OP_NONE,
blend=blend,
)
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((64, 32)) == (0, 0, 0, 0)
# test APNG_BLEND_OP_OVER
# test OP_OVER
red.save(
test_file,
save_all=True,
append_images=[green, transparent],
default_image=True,
disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE,
blend=PngImagePlugin.APNG_BLEND_OP_OVER,
disposal=PngImagePlugin.Disposal.OP_NONE,
blend=PngImagePlugin.Blend.OP_OVER,
)
with Image.open(test_file) as im:
im.seek(1)
@ -611,3 +628,20 @@ def test_apng_save_blend(tmp_path):
im.seek(2)
assert im.getpixel((0, 0)) == (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]

View File

@ -1,8 +1,18 @@
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():
@ -20,6 +30,28 @@ def test_load_blp2_dxt1a():
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(
"test_file",
[
@ -37,3 +69,14 @@ def test_crashes(test_file):
with Image.open(f) as im:
with pytest.raises(OSError):
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]

View File

@ -4,7 +4,12 @@ import pytest
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):
@ -123,3 +128,46 @@ def test_rgba_bitfields():
im = Image.merge("RGB", (r, g, b))
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")

View File

@ -45,3 +45,35 @@ def test_save(tmp_path):
# Act / Assert: stub cannot save without an implemented handler
with pytest.raises(OSError):
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

View File

@ -1,3 +1,5 @@
import warnings
import pytest
from PIL import DcxImagePlugin, Image
@ -31,21 +33,17 @@ def test_unclosed_file():
def test_closed_file():
with pytest.warns(None) as record:
with warnings.catch_warnings():
im = Image.open(TEST_FILE)
im.load()
im.close()
assert not record
def test_context_manager():
with pytest.warns(None) as record:
with warnings.catch_warnings():
with Image.open(TEST_FILE) as im:
im.load()
assert not record
def test_invalid_file():
with open("Tests/images/flower.jpg", "rb") as fp:

View File

@ -196,6 +196,13 @@ def test__accept_false():
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():
"""Check a short header"""
with open(TEST_FILE_DXT5, "rb") as f:

View File

@ -58,6 +58,15 @@ def test_sanity():
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():
invalid_file = "Tests/images/flower.jpg"

80
Tests/test_file_fits.py Normal file
View 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,
)

View File

@ -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)

View File

@ -1,3 +1,5 @@
import warnings
import pytest
from PIL import FliImagePlugin, Image
@ -38,21 +40,17 @@ def test_unclosed_file():
def test_closed_file():
with pytest.warns(None) as record:
with warnings.catch_warnings():
im = Image.open(static_test_file)
im.load()
im.close()
assert not record
def test_context_manager():
with pytest.warns(None) as record:
with warnings.catch_warnings():
with Image.open(static_test_file) as im:
im.load()
assert not record
def test_tell():
# Arrange
@ -138,3 +136,16 @@ def test_timeouts(test_file):
with Image.open(f) as im:
with pytest.raises(OSError):
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()

View File

@ -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
@ -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.png") as target:
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]

View File

@ -5,20 +5,28 @@ from PIL import GbrImagePlugin, Image
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():
with Image.open("Tests/images/gbr.gbr") as im:
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():
with Image.open("Tests/images/gbr.gbr") as im:
im.load()
im.load()
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)

View File

@ -1,3 +1,4 @@
import warnings
from io import BytesIO
import pytest
@ -39,21 +40,17 @@ def test_unclosed_file():
def test_closed_file():
with pytest.warns(None) as record:
with warnings.catch_warnings():
im = Image.open(TEST_GIF)
im.load()
im.close()
assert not record
def test_context_manager():
with pytest.warns(None) as record:
with warnings.catch_warnings():
with Image.open(TEST_GIF) as im:
im.load()
assert not record
def test_invalid_file():
invalid_file = "Tests/images/flower.jpg"
@ -62,6 +59,51 @@ def test_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_grayscale(optimize):
im = Image.new("L", (1, 1), 0)
@ -163,6 +205,32 @@ def test_roundtrip_save_all(tmp_path):
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):
important_headers = ["background", "version", "duration", "loop"]
# Multiframe image
@ -184,8 +252,8 @@ def test_palette_handling(tmp_path):
with Image.open(TEST_GIF) as im:
im = im.convert("RGB")
im = im.resize((100, 100), Image.LANCZOS)
im2 = im.convert("P", palette=Image.ADAPTIVE, colors=256)
im = im.resize((100, 100), Image.Resampling.LANCZOS)
im2 = im.convert("P", palette=Image.Palette.ADAPTIVE, colors=256)
f = str(tmp_path / "temp.gif")
im2.save(f, optimize=True)
@ -285,6 +353,22 @@ def test_n_frames():
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():
with Image.open(TEST_GIF) as im:
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:
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():
@ -337,14 +421,45 @@ def test_dispose_background():
pass
def test_transparent_dispose():
expected_colors = [(2, 1, 2), (0, 1, 0), (2, 1, 2)]
def test_dispose_background_transparency():
with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img:
img.seek(2)
px = img.load()
assert px[35, 30][3] == 0
@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():
@ -361,7 +476,7 @@ def test_dispose_previous_first_frame():
with Image.open("Tests/images/dispose_prev_first_frame.gif") as im:
im.seek(1)
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:
im.seek(1)
assert im.getpixel((0, 0)) == 0
assert im.getpixel((0, 0)) == (255, 0, 0)
def test_transparency_in_second_frame():
@ -510,9 +625,9 @@ def test_transparency_in_second_frame():
# Seek to the second frame
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():
@ -684,31 +799,31 @@ def test_zero_comment_subblocks():
def test_version(tmp_path):
out = str(tmp_path / "temp.gif")
def assertVersionAfterSave(im, version):
def assert_version_after_save(im, version):
im.save(out)
with Image.open(out) as reread:
assert reread.info["version"] == version
# Test that GIF87a is used by default
im = Image.new("L", (100, 100), "#000")
assertVersionAfterSave(im, b"GIF87a")
assert_version_after_save(im, b"GIF87a")
# Test setting the version to 89a
im = Image.new("L", (100, 100), "#000")
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
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
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
im.info["version"] = b"GIF89a"
assertVersionAfterSave(im, b"GIF87a")
assert_version_after_save(im, b"GIF87a")
def test_append_images(tmp_path):
@ -723,10 +838,10 @@ def test_append_images(tmp_path):
assert reread.n_frames == 3
# Tests appending using a generator
def imGenerator(ims):
def im_generator(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:
assert reread.n_frames == 3
@ -781,6 +896,17 @@ def test_rgb_transparency(tmp_path):
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):
out = str(tmp_path / "temp.gif")
@ -811,7 +937,7 @@ def test_palette_save_P(tmp_path):
# Forcing a non-straight grayscale palette.
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")
im.save(out, palette=palette)
@ -856,7 +982,7 @@ def test_palette_save_ImagePalette(tmp_path):
with Image.open(out) as reloaded:
im.putpalette(palette)
assert_image_equal(reloaded, im)
assert_image_equal(reloaded.convert("RGB"), im.convert("RGB"))
def test_save_I(tmp_path):
@ -874,11 +1000,11 @@ def test_save_I(tmp_path):
def test_getdata():
# Test getheader/getdata against legacy values.
# 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.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
try:
@ -910,6 +1036,11 @@ def test_lzw_bits():
def test_extents():
with Image.open("Tests/images/test_extents.gif") as im:
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)
assert im.size == (150, 150)
@ -919,4 +1050,14 @@ def test_missing_background():
# but the disposal method is "Restore to background color"
with Image.open("Tests/images/missing_background.gif") as im:
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

View File

@ -45,3 +45,35 @@ def test_save(tmp_path):
# Act / Assert: stub cannot save without an implemented handler
with pytest.raises(OSError):
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

View File

@ -46,3 +46,35 @@ def test_save():
im.save(dummy_filename)
with pytest.raises(OSError):
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

Some files were not shown because too many files have changed in this diff Show More