mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-12 01:20:53 +03:00
Merge branch 'main' into main
This commit is contained in:
commit
4128e8cc2c
|
@ -14,7 +14,7 @@ environment:
|
||||||
ARCHITECTURE: x86
|
ARCHITECTURE: x86
|
||||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||||
- PYTHON: C:/Python38-x64
|
- PYTHON: C:/Python38-x64
|
||||||
ARCHITECTURE: x64
|
ARCHITECTURE: AMD64
|
||||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,14 @@
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
# Regexes for lines to exclude from consideration
|
# Regexes for lines to exclude from consideration
|
||||||
exclude_lines =
|
exclude_also =
|
||||||
# Have to re-enable the standard pragma:
|
# Don't complain if non-runnable code isn't run
|
||||||
pragma: no cover
|
|
||||||
|
|
||||||
# Don't complain if non-runnable code isn't run:
|
|
||||||
if 0:
|
if 0:
|
||||||
if __name__ == .__main__.:
|
if __name__ == .__main__.:
|
||||||
# Don't complain about debug code
|
# Don't complain about debug code
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
# Don't complain about compatibility code for missing optional dependencies
|
||||||
|
except ImportError
|
||||||
|
|
||||||
[run]
|
[run]
|
||||||
omit =
|
omit =
|
||||||
|
|
18
.github/problem-matchers/gcc.json
vendored
Normal file
18
.github/problem-matchers/gcc.json
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"__comment": "Based on vscode-cpptools' Extension/package.json gcc rule",
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "gcc-problem-matcher",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^\\s*(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||||
|
"file": 1,
|
||||||
|
"line": 2,
|
||||||
|
"column": 3,
|
||||||
|
"severity": 4,
|
||||||
|
"message": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
2
.github/release-drafter.yml
vendored
2
.github/release-drafter.yml
vendored
|
@ -13,6 +13,8 @@ categories:
|
||||||
label: "Removal"
|
label: "Removal"
|
||||||
- title: "Testing"
|
- title: "Testing"
|
||||||
label: "Testing"
|
label: "Testing"
|
||||||
|
- title: "Type hints"
|
||||||
|
label: "Type hints"
|
||||||
|
|
||||||
exclude-labels:
|
exclude-labels:
|
||||||
- "changelog: skip"
|
- "changelog: skip"
|
||||||
|
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: pre-commit cache
|
- name: pre-commit cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pre-commit
|
path: ~/.cache/pre-commit
|
||||||
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
||||||
|
|
4
.github/workflows/test-cygwin.yml
vendored
4
.github/workflows/test-cygwin.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -16,7 +15,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -97,7 +95,7 @@ jobs:
|
||||||
python3 -m pip list --outdated | grep numpy | sed -r 's/ +/ /g' | cut -d ' ' -f 3 | sed 's/^/version=/' >> $GITHUB_OUTPUT
|
python3 -m pip list --outdated | grep numpy | sed -r 's/ +/ /g' | cut -d ' ' -f 3 | sed 's/^/version=/' >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: pip cache
|
- name: pip cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
||||||
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }}
|
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }}
|
||||||
|
|
2
.github/workflows/test-docker.yml
vendored
2
.github/workflows/test-docker.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -16,7 +15,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
2
.github/workflows/test-mingw.yml
vendored
2
.github/workflows/test-mingw.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -16,7 +15,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
6
.github/workflows/test-windows.yml
vendored
6
.github/workflows/test-windows.yml
vendored
|
@ -2,11 +2,12 @@ name: Test Windows
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -14,7 +15,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -89,7 +89,7 @@ jobs:
|
||||||
|
|
||||||
- name: Cache build
|
- name: Cache build
|
||||||
id: build-cache
|
id: build-cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: winbuild\build
|
path: winbuild\build
|
||||||
key:
|
key:
|
||||||
|
|
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -16,7 +15,6 @@ on:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- ".github/workflows/wheels*"
|
- ".github/workflows/wheels*"
|
||||||
- ".gitmodules"
|
- ".gitmodules"
|
||||||
- ".travis.yml"
|
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- "wheels/**"
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
@ -86,6 +84,10 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Register gcc problem matcher
|
||||||
|
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'"
|
||||||
|
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
.ci/build.sh
|
.ci/build.sh
|
||||||
|
|
146
.github/workflows/wheels.yml
vendored
146
.github/workflows/wheels.yml
vendored
|
@ -30,7 +30,64 @@ env:
|
||||||
FORCE_COLOR: 1
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-1-QEMU-emulated-wheels:
|
||||||
|
name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version:
|
||||||
|
- pp39
|
||||||
|
- pp310
|
||||||
|
- cp38
|
||||||
|
- cp39
|
||||||
|
- cp310
|
||||||
|
- cp311
|
||||||
|
- cp312
|
||||||
|
spec:
|
||||||
|
- manylinux2014
|
||||||
|
- manylinux_2_28
|
||||||
|
- musllinux
|
||||||
|
exclude:
|
||||||
|
- { python-version: pp39, spec: musllinux }
|
||||||
|
- { python-version: pp310, spec: musllinux }
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
# https://github.com/docker/setup-qemu-action
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Install cibuildwheel
|
||||||
|
run: |
|
||||||
|
python3 -m pip install -r .ci/requirements-cibw.txt
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
run: |
|
||||||
|
python3 -m cibuildwheel --output-dir wheelhouse
|
||||||
|
env:
|
||||||
|
# Build only the currently selected Linux architecture (so we can
|
||||||
|
# parallelise for speed).
|
||||||
|
CIBW_ARCHS: "aarch64"
|
||||||
|
# Likewise, select only one Python version per job to speed this up.
|
||||||
|
CIBW_BUILD: "${{ matrix.python-version }}-${{ matrix.spec == 'musllinux' && 'musllinux' || 'manylinux' }}*"
|
||||||
|
# Extra options for manylinux.
|
||||||
|
CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.spec }}
|
||||||
|
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.spec }}
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist-qemu-${{ matrix.python-version }}-${{ matrix.spec }}
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
|
build-2-native-wheels:
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -39,18 +96,18 @@ jobs:
|
||||||
include:
|
include:
|
||||||
- name: "macOS x86_64"
|
- name: "macOS x86_64"
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
archs: x86_64
|
cibw_arch: x86_64
|
||||||
macosx_deployment_target: "10.10"
|
macosx_deployment_target: "10.10"
|
||||||
- name: "macOS arm64"
|
- name: "macOS arm64"
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
archs: arm64
|
cibw_arch: arm64
|
||||||
macosx_deployment_target: "11.0"
|
macosx_deployment_target: "11.0"
|
||||||
- name: "manylinux2014 and musllinux x86_64"
|
- name: "manylinux2014 and musllinux x86_64"
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
archs: x86_64
|
cibw_arch: x86_64
|
||||||
- name: "manylinux_2_28 x86_64"
|
- name: "manylinux_2_28 x86_64"
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
archs: x86_64
|
cibw_arch: x86_64
|
||||||
build: "*manylinux*"
|
build: "*manylinux*"
|
||||||
manylinux: "manylinux_2_28"
|
manylinux: "manylinux_2_28"
|
||||||
steps:
|
steps:
|
||||||
|
@ -62,12 +119,15 @@ jobs:
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Install cibuildwheel
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install -r .ci/requirements-cibw.txt
|
python3 -m pip install -r .ci/requirements-cibw.txt
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
run: |
|
||||||
python3 -m cibuildwheel --output-dir wheelhouse
|
python3 -m cibuildwheel --output-dir wheelhouse
|
||||||
env:
|
env:
|
||||||
CIBW_ARCHS: ${{ matrix.archs }}
|
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||||
CIBW_BUILD: ${{ matrix.build }}
|
CIBW_BUILD: ${{ matrix.build }}
|
||||||
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
|
@ -75,24 +135,21 @@ jobs:
|
||||||
CIBW_TEST_SKIP: "*-macosx_arm64"
|
CIBW_TEST_SKIP: "*-macosx_arm64"
|
||||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist-${{ matrix.os }}-${{ matrix.cibw_arch }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }}
|
||||||
path: ./wheelhouse/*.whl
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
name: Windows ${{ matrix.arch }}
|
name: Windows ${{ matrix.cibw_arch }}
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- arch: x86
|
- cibw_arch: x86
|
||||||
cibw_arch: x86
|
- cibw_arch: AMD64
|
||||||
- arch: x64
|
- cibw_arch: ARM64
|
||||||
cibw_arch: AMD64
|
|
||||||
- arch: ARM64
|
|
||||||
cibw_arch: ARM64
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
@ -106,6 +163,10 @@ jobs:
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- name: Install cibuildwheel
|
||||||
|
run: |
|
||||||
|
python.exe -m pip install -r .ci/requirements-cibw.txt
|
||||||
|
|
||||||
- name: Prepare for build
|
- name: Prepare for build
|
||||||
run: |
|
run: |
|
||||||
choco install nasm --no-progress
|
choco install nasm --no-progress
|
||||||
|
@ -114,12 +175,7 @@ jobs:
|
||||||
# Install extra test images
|
# Install extra test images
|
||||||
xcopy /S /Y Tests\test-images\* Tests\images
|
xcopy /S /Y Tests\test-images\* Tests\images
|
||||||
|
|
||||||
& python.exe -m pip install -r .ci/requirements-cibw.txt
|
& python.exe winbuild\build_prepare.py -v --no-imagequant --architecture=${{ matrix.cibw_arch }}
|
||||||
|
|
||||||
# Cannot cross-compile FriBiDi (only used for tests)
|
|
||||||
$FLAGS = ("--no-imagequant", "--architecture=${{ matrix.arch }}")
|
|
||||||
if ('${{ matrix.arch }}' -eq 'ARM64') { $FLAGS += "--no-fribidi" }
|
|
||||||
& python.exe winbuild\build_prepare.py -v @FLAGS
|
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
|
@ -146,6 +202,7 @@ jobs:
|
||||||
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||||
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
||||||
CIBW_CACHE_PATH: "C:\\cibw"
|
CIBW_CACHE_PATH: "C:\\cibw"
|
||||||
|
CIBW_SKIP: pp38-*
|
||||||
CIBW_TEST_SKIP: "*-win_arm64"
|
CIBW_TEST_SKIP: "*-win_arm64"
|
||||||
CIBW_TEST_COMMAND: 'docker run --rm
|
CIBW_TEST_COMMAND: 'docker run --rm
|
||||||
-v {project}:C:\pillow
|
-v {project}:C:\pillow
|
||||||
|
@ -157,24 +214,16 @@ jobs:
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
|
||||||
- name: Upload wheels
|
- name: Upload wheels
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist-windows-${{ matrix.cibw_arch }}
|
||||||
path: ./wheelhouse/*.whl
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
- name: Prepare to upload FriBiDi
|
|
||||||
if: "matrix.arch != 'ARM64'"
|
|
||||||
run: |
|
|
||||||
mkdir fribidi\${{ matrix.arch }}
|
|
||||||
copy winbuild\build\bin\fribidi* fribidi\${{ matrix.arch }}
|
|
||||||
shell: cmd
|
|
||||||
|
|
||||||
- name: Upload fribidi.dll
|
- name: Upload fribidi.dll
|
||||||
if: "matrix.arch != 'ARM64'"
|
uses: actions/upload-artifact@v4
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
with:
|
||||||
name: fribidi
|
name: fribidi-windows-${{ matrix.cibw_arch }}
|
||||||
path: fribidi\*
|
path: winbuild\build\bin\fribidi*
|
||||||
|
|
||||||
sdist:
|
sdist:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -190,17 +239,26 @@ jobs:
|
||||||
|
|
||||||
- run: make sdist
|
- run: make sdist
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist-sdist
|
||||||
path: dist/*.tar.gz
|
path: dist/*.tar.gz
|
||||||
|
|
||||||
success:
|
pypi-publish:
|
||||||
permissions:
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||||
contents: none
|
needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist]
|
||||||
needs: [build, windows, sdist]
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Wheels Successful
|
name: Upload release to PyPI
|
||||||
|
environment:
|
||||||
|
name: release-pypi
|
||||||
|
url: https://pypi.org/p/Pillow
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Success
|
- uses: actions/download-artifact@v4
|
||||||
run: echo Wheels Successful
|
with:
|
||||||
|
pattern: dist-*
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Publish to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.1.7
|
rev: v0.1.9
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
rev: 23.12.0
|
rev: 23.12.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
|
|
52
.travis.yml
52
.travis.yml
|
@ -1,52 +0,0 @@
|
||||||
if: tag IS present OR type = api
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- CIBW_ARCHS=aarch64
|
|
||||||
- CIBW_SKIP=pp38-*
|
|
||||||
|
|
||||||
language: python
|
|
||||||
# Default Python version is usually 3.6
|
|
||||||
python: "3.12"
|
|
||||||
dist: jammy
|
|
||||||
services: docker
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- name: "manylinux2014 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- CIBW_BUILD="*manylinux*"
|
|
||||||
- CIBW_MANYLINUX_AARCH64_IMAGE=manylinux2014
|
|
||||||
- CIBW_MANYLINUX_PYPY_AARCH64_IMAGE=manylinux2014
|
|
||||||
- name: "manylinux_2_28 aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- CIBW_BUILD="*manylinux*"
|
|
||||||
- CIBW_MANYLINUX_AARCH64_IMAGE=manylinux_2_28
|
|
||||||
- CIBW_MANYLINUX_PYPY_AARCH64_IMAGE=manylinux_2_28
|
|
||||||
- name: "musllinux aarch64"
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
env:
|
|
||||||
- CIBW_BUILD="*musllinux*"
|
|
||||||
|
|
||||||
install:
|
|
||||||
- python3 -m pip install -r .ci/requirements-cibw.txt
|
|
||||||
|
|
||||||
script:
|
|
||||||
- python3 -m cibuildwheel --output-dir wheelhouse
|
|
||||||
- ls -l "${TRAVIS_BUILD_DIR}/wheelhouse/"
|
|
||||||
|
|
||||||
# Upload wheels to GitHub Releases
|
|
||||||
deploy:
|
|
||||||
provider: releases
|
|
||||||
api_key: $GITHUB_RELEASE_TOKEN
|
|
||||||
file_glob: true
|
|
||||||
file: "${TRAVIS_BUILD_DIR}/wheelhouse/*.whl"
|
|
||||||
on:
|
|
||||||
repo: python-pillow/Pillow
|
|
||||||
tags: true
|
|
||||||
skip_cleanup: true
|
|
62
CHANGES.rst
62
CHANGES.rst
|
@ -2,9 +2,69 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
10.2.0 (unreleased)
|
10.3.0 (unreleased)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
- Fix APNG info after seeking backwards more than twice #7701
|
||||||
|
[esoma, radarhere]
|
||||||
|
|
||||||
|
- Deprecate ImageCms constants and versions() function #7702
|
||||||
|
[nulano, radarhere]
|
||||||
|
|
||||||
|
- Added PerspectiveTransform #7699
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Add support for reading and writing grayscale PFM images #7696
|
||||||
|
[nulano, hugovk]
|
||||||
|
|
||||||
|
- Add LCMS2 flags to ImageCms #7676
|
||||||
|
[nulano, radarhere, hugovk]
|
||||||
|
|
||||||
|
- Rename x64 to AMD64 in winbuild #7693
|
||||||
|
[nulano]
|
||||||
|
|
||||||
|
10.2.0 (2024-01-02)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Add ``keep_rgb`` option when saving JPEG to prevent conversion of RGB colorspace #7553
|
||||||
|
[bgilbert, radarhere]
|
||||||
|
|
||||||
|
- Trim glyph size in ImageFont.getmask() #7669, #7672
|
||||||
|
[radarhere, nulano]
|
||||||
|
|
||||||
|
- Deprecate IptcImagePlugin helpers #7664
|
||||||
|
[nulano, hugovk, radarhere]
|
||||||
|
|
||||||
|
- Allow uncompressed TIFF images to be saved in chunks #7650
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Concatenate multiple JPEG EXIF markers #7496
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Changed IPTC tile tuple to match other plugins #7661
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not assign new fp attribute when exiting context manager #7566
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support arbitrary masks for uncompressed RGB DDS images #7589
|
||||||
|
[radarhere, akx]
|
||||||
|
|
||||||
|
- Support setting ROWSPERSTRIP tag #7654
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Apply ImageFont.MAX_STRING_LENGTH to ImageFont.getmask() #7662
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Optimise ``ImageColor`` using ``functools.lru_cache`` #7657
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Restricted environment keys for ImageMath.eval() #7655
|
||||||
|
[wiredfool, radarhere]
|
||||||
|
|
||||||
|
- Optimise ``ImageMode.getmode`` using ``functools.lru_cache`` #7641
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
- Fix incorrect color blending for overlapping glyphs #7497
|
- Fix incorrect color blending for overlapping glyphs #7497
|
||||||
[ZachNagengast, nulano, radarhere]
|
[ZachNagengast, nulano, radarhere]
|
||||||
|
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
|
||||||
|
|
||||||
Pillow is the friendly PIL fork. It is
|
Pillow is the friendly PIL fork. It is
|
||||||
|
|
||||||
Copyright © 2010-2023 by Jeffrey A. Clark (Alex) and contributors.
|
Copyright © 2010-2024 by Jeffrey A. Clark (Alex) and contributors.
|
||||||
|
|
||||||
Like PIL, Pillow is licensed under the open source HPND License:
|
Like PIL, Pillow is licensed under the open source HPND License:
|
||||||
|
|
||||||
|
|
|
@ -48,9 +48,6 @@ As of 2019, Pillow development is
|
||||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
|
||||||
alt="GitHub Actions build status (Wheels)"
|
alt="GitHub Actions build status (Wheels)"
|
||||||
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
|
||||||
<a href="https://app.travis-ci.com/github/python-pillow/Pillow"><img
|
|
||||||
alt="Travis CI wheels build status (aarch64)"
|
|
||||||
src="https://img.shields.io/travis/com/python-pillow/Pillow/main.svg?label=aarch64%20wheels"></a>
|
|
||||||
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
|
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
|
||||||
alt="Code coverage"
|
alt="Code coverage"
|
||||||
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
||||||
|
@ -68,10 +65,10 @@ As of 2019, Pillow development is
|
||||||
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
|
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
|
||||||
alt="Tidelift"
|
alt="Tidelift"
|
||||||
src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
|
src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
|
||||||
<a href="https://pypi.org/project/Pillow/"><img
|
<a href="https://pypi.org/project/pillow/"><img
|
||||||
alt="Newest PyPI version"
|
alt="Newest PyPI version"
|
||||||
src="https://img.shields.io/pypi/v/pillow.svg"></a>
|
src="https://img.shields.io/pypi/v/pillow.svg"></a>
|
||||||
<a href="https://pypi.org/project/Pillow/"><img
|
<a href="https://pypi.org/project/pillow/"><img
|
||||||
alt="Number of PyPI downloads"
|
alt="Number of PyPI downloads"
|
||||||
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
||||||
<a href="https://www.bestpractices.dev/projects/6331"><img
|
<a href="https://www.bestpractices.dev/projects/6331"><img
|
||||||
|
|
33
RELEASING.md
33
RELEASING.md
|
@ -10,7 +10,7 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||||
* [ ] Develop and prepare release in `main` branch.
|
* [ ] Develop and prepare release in `main` branch.
|
||||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
|
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
|
||||||
* [ ] Check that all of the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) and [Travis CI](https://app.travis-ci.com/github/python-pillow/pillow) jobs by manually triggering them.
|
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
|
||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||||
* [ ] Update `CHANGES.rst`.
|
* [ ] Update `CHANGES.rst`.
|
||||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||||
|
@ -20,12 +20,7 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
git tag 5.2.0
|
git tag 5.2.0
|
||||||
git push --tags
|
git push --tags
|
||||||
```
|
```
|
||||||
* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||||
* [ ] Check and upload all source and binary distributions e.g.:
|
|
||||||
```bash
|
|
||||||
python3 -m twine check --strict dist/*
|
|
||||||
python3 -m twine upload dist/Pillow-5.2.0*
|
|
||||||
```
|
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
|
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
|
||||||
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
||||||
|
@ -55,12 +50,7 @@ Released as needed for security, installation or critical bug fixes.
|
||||||
```bash
|
```bash
|
||||||
make sdist
|
make sdist
|
||||||
```
|
```
|
||||||
* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||||
* [ ] Check and upload all source and binary distributions e.g.:
|
|
||||||
```bash
|
|
||||||
python3 -m twine check --strict dist/*
|
|
||||||
python3 -m twine upload dist/Pillow-5.2.1*
|
|
||||||
```
|
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
||||||
```bash
|
```bash
|
||||||
git push
|
git push
|
||||||
|
@ -82,11 +72,7 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
git tag 2.5.3
|
git tag 2.5.3
|
||||||
git push origin --tags
|
git push origin --tags
|
||||||
```
|
```
|
||||||
* [ ] Create and check source distribution:
|
* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||||
```bash
|
|
||||||
make sdist
|
|
||||||
```
|
|
||||||
* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
||||||
```bash
|
```bash
|
||||||
git push origin 2.5.x
|
git push origin 2.5.x
|
||||||
|
@ -94,14 +80,9 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
|
|
||||||
## Source and Binary Distributions
|
## Source and Binary Distributions
|
||||||
|
|
||||||
* [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
|
has passed, including the "Upload release to PyPI" job. This will have been triggered
|
||||||
```bash
|
by the new tag.
|
||||||
gh run download --dir dist
|
|
||||||
# select dist
|
|
||||||
```
|
|
||||||
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
|
|
||||||
and copy into `dist`.
|
|
||||||
|
|
||||||
## Publicize Release
|
## Publicize Release
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from PIL import PyAccess
|
from PIL import PyAccess
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
TEST_FILE = "Tests/images/fli_overflow.fli"
|
TEST_FILE = "Tests/images/fli_overflow.fli"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
# version.
|
# version.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2")
|
repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2")
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import zlib
|
import zlib
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PIL import features
|
from PIL import features
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
153
Tests/helper.py
153
Tests/helper.py
|
@ -11,6 +11,7 @@ import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import tempfile
|
import tempfile
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from typing import Any, Callable, Sequence
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
from packaging.version import parse as parse_version
|
||||||
|
@ -19,42 +20,40 @@ from PIL import Image, ImageMath, features
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
uploader = None
|
||||||
HAS_UPLOADER = False
|
|
||||||
|
|
||||||
if os.environ.get("SHOW_ERRORS"):
|
if os.environ.get("SHOW_ERRORS"):
|
||||||
# local img.show for errors.
|
uploader = "show"
|
||||||
HAS_UPLOADER = True
|
elif "GITHUB_ACTIONS" in os.environ:
|
||||||
|
uploader = "github_actions"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
import test_image_results
|
||||||
|
|
||||||
class test_image_results:
|
uploader = "aws"
|
||||||
@staticmethod
|
except ImportError:
|
||||||
def upload(a, b):
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def upload(a: Image.Image, b: Image.Image) -> str | None:
|
||||||
|
if uploader == "show":
|
||||||
|
# local img.show for errors.
|
||||||
a.show()
|
a.show()
|
||||||
b.show()
|
b.show()
|
||||||
|
elif uploader == "github_actions":
|
||||||
elif "GITHUB_ACTIONS" in os.environ:
|
|
||||||
HAS_UPLOADER = True
|
|
||||||
|
|
||||||
class test_image_results:
|
|
||||||
@staticmethod
|
|
||||||
def upload(a, b):
|
|
||||||
dir_errors = os.path.join(os.path.dirname(__file__), "errors")
|
dir_errors = os.path.join(os.path.dirname(__file__), "errors")
|
||||||
os.makedirs(dir_errors, exist_ok=True)
|
os.makedirs(dir_errors, exist_ok=True)
|
||||||
tmpdir = tempfile.mkdtemp(dir=dir_errors)
|
tmpdir = tempfile.mkdtemp(dir=dir_errors)
|
||||||
a.save(os.path.join(tmpdir, "a.png"))
|
a.save(os.path.join(tmpdir, "a.png"))
|
||||||
b.save(os.path.join(tmpdir, "b.png"))
|
b.save(os.path.join(tmpdir, "b.png"))
|
||||||
return tmpdir
|
return tmpdir
|
||||||
|
elif uploader == "aws":
|
||||||
else:
|
return test_image_results.upload(a, b)
|
||||||
try:
|
return None
|
||||||
import test_image_results
|
|
||||||
|
|
||||||
HAS_UPLOADER = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_comparable(a, b):
|
def convert_to_comparable(
|
||||||
|
a: Image.Image, b: Image.Image
|
||||||
|
) -> tuple[Image.Image, Image.Image]:
|
||||||
new_a, new_b = a, b
|
new_a, new_b = a, b
|
||||||
if a.mode == "P":
|
if a.mode == "P":
|
||||||
new_a = Image.new("L", a.size)
|
new_a = Image.new("L", a.size)
|
||||||
|
@ -67,14 +66,18 @@ def convert_to_comparable(a, b):
|
||||||
return new_a, new_b
|
return new_a, new_b
|
||||||
|
|
||||||
|
|
||||||
def assert_deep_equal(a, b, msg=None):
|
def assert_deep_equal(
|
||||||
|
a: Sequence[Any], b: Sequence[Any], msg: str | None = None
|
||||||
|
) -> None:
|
||||||
try:
|
try:
|
||||||
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
|
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
|
||||||
except Exception:
|
except Exception:
|
||||||
assert a == b, msg
|
assert a == b, msg
|
||||||
|
|
||||||
|
|
||||||
def assert_image(im, mode, size, msg=None):
|
def assert_image(
|
||||||
|
im: Image.Image, mode: str, size: tuple[int, int], msg: str | None = None
|
||||||
|
) -> None:
|
||||||
if mode is not None:
|
if mode is not None:
|
||||||
assert im.mode == mode, (
|
assert im.mode == mode, (
|
||||||
msg or f"got mode {repr(im.mode)}, expected {repr(mode)}"
|
msg or f"got mode {repr(im.mode)}, expected {repr(mode)}"
|
||||||
|
@ -86,13 +89,13 @@ def assert_image(im, mode, size, msg=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def assert_image_equal(a, b, msg=None):
|
def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) -> None:
|
||||||
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
||||||
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
||||||
if a.tobytes() != b.tobytes():
|
if a.tobytes() != b.tobytes():
|
||||||
if HAS_UPLOADER:
|
|
||||||
try:
|
try:
|
||||||
url = test_image_results.upload(a, b)
|
url = upload(a, b)
|
||||||
|
if url:
|
||||||
logger.error("URL for test images: %s", url)
|
logger.error("URL for test images: %s", url)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
@ -100,14 +103,18 @@ def assert_image_equal(a, b, msg=None):
|
||||||
pytest.fail(msg or "got different content")
|
pytest.fail(msg or "got different content")
|
||||||
|
|
||||||
|
|
||||||
def assert_image_equal_tofile(a, filename, msg=None, mode=None):
|
def assert_image_equal_tofile(
|
||||||
|
a: Image.Image, filename: str, msg: str | None = None, mode: str | None = None
|
||||||
|
) -> None:
|
||||||
with Image.open(filename) as img:
|
with Image.open(filename) as img:
|
||||||
if mode:
|
if mode:
|
||||||
img = img.convert(mode)
|
img = img.convert(mode)
|
||||||
assert_image_equal(a, img, msg)
|
assert_image_equal(a, img, msg)
|
||||||
|
|
||||||
|
|
||||||
def assert_image_similar(a, b, epsilon, msg=None):
|
def assert_image_similar(
|
||||||
|
a: Image.Image, b: Image.Image, epsilon: float, msg: str | None = None
|
||||||
|
) -> None:
|
||||||
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
||||||
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
||||||
|
|
||||||
|
@ -125,55 +132,68 @@ def assert_image_similar(a, b, epsilon, msg=None):
|
||||||
+ f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}"
|
+ f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if HAS_UPLOADER:
|
|
||||||
try:
|
try:
|
||||||
url = test_image_results.upload(a, b)
|
url = upload(a, b)
|
||||||
|
if url:
|
||||||
logger.exception("URL for test images: %s", url)
|
logger.exception("URL for test images: %s", url)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def assert_image_similar_tofile(a, filename, epsilon, msg=None, mode=None):
|
def assert_image_similar_tofile(
|
||||||
|
a: Image.Image,
|
||||||
|
filename: str,
|
||||||
|
epsilon: float,
|
||||||
|
msg: str | None = None,
|
||||||
|
mode: str | None = None,
|
||||||
|
) -> None:
|
||||||
with Image.open(filename) as img:
|
with Image.open(filename) as img:
|
||||||
if mode:
|
if mode:
|
||||||
img = img.convert(mode)
|
img = img.convert(mode)
|
||||||
assert_image_similar(a, img, epsilon, msg)
|
assert_image_similar(a, img, epsilon, msg)
|
||||||
|
|
||||||
|
|
||||||
def assert_all_same(items, msg=None):
|
def assert_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||||
assert items.count(items[0]) == len(items), msg
|
assert items.count(items[0]) == len(items), msg
|
||||||
|
|
||||||
|
|
||||||
def assert_not_all_same(items, msg=None):
|
def assert_not_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||||
assert items.count(items[0]) != len(items), msg
|
assert items.count(items[0]) != len(items), msg
|
||||||
|
|
||||||
|
|
||||||
def assert_tuple_approx_equal(actuals, targets, threshold, msg):
|
def assert_tuple_approx_equal(
|
||||||
|
actuals: Sequence[int], targets: tuple[int, ...], threshold: int, msg: str
|
||||||
|
) -> None:
|
||||||
"""Tests if actuals has values within threshold from targets"""
|
"""Tests if actuals has values within threshold from targets"""
|
||||||
value = True
|
|
||||||
for i, target in enumerate(targets):
|
for i, target in enumerate(targets):
|
||||||
value *= target - threshold <= actuals[i] <= target + threshold
|
if not (target - threshold <= actuals[i] <= target + threshold):
|
||||||
|
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
|
||||||
assert value, msg + ": " + repr(actuals) + " != " + repr(targets)
|
|
||||||
|
|
||||||
|
|
||||||
def skip_unless_feature(feature):
|
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
||||||
reason = f"{feature} not available"
|
reason = f"{feature} not available"
|
||||||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||||
|
|
||||||
|
|
||||||
def skip_unless_feature_version(feature, version_required, reason=None):
|
def skip_unless_feature_version(
|
||||||
|
feature: str, required: str, reason: str | None = None
|
||||||
|
) -> pytest.MarkDecorator:
|
||||||
if not features.check(feature):
|
if not features.check(feature):
|
||||||
return pytest.mark.skip(f"{feature} not available")
|
return pytest.mark.skip(f"{feature} not available")
|
||||||
if reason is None:
|
if reason is None:
|
||||||
reason = f"{feature} is older than {version_required}"
|
reason = f"{feature} is older than {required}"
|
||||||
version_required = parse_version(version_required)
|
version_required = parse_version(required)
|
||||||
version_available = parse_version(features.version(feature))
|
version_available = parse_version(features.version(feature))
|
||||||
return pytest.mark.skipif(version_available < version_required, reason=reason)
|
return pytest.mark.skipif(version_available < version_required, reason=reason)
|
||||||
|
|
||||||
|
|
||||||
def mark_if_feature_version(mark, feature, version_blacklist, reason=None):
|
def mark_if_feature_version(
|
||||||
|
mark: pytest.MarkDecorator,
|
||||||
|
feature: str,
|
||||||
|
version_blacklist: str,
|
||||||
|
reason: str | None = None,
|
||||||
|
) -> pytest.MarkDecorator:
|
||||||
if not features.check(feature):
|
if not features.check(feature):
|
||||||
return pytest.mark.pil_noop_mark()
|
return pytest.mark.pil_noop_mark()
|
||||||
if reason is None:
|
if reason is None:
|
||||||
|
@ -194,7 +214,7 @@ class PillowLeakTestCase:
|
||||||
iterations = 100 # count
|
iterations = 100 # count
|
||||||
mem_limit = 512 # k
|
mem_limit = 512 # k
|
||||||
|
|
||||||
def _get_mem_usage(self):
|
def _get_mem_usage(self) -> float:
|
||||||
"""
|
"""
|
||||||
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
||||||
between macOS and Linux rss reporting
|
between macOS and Linux rss reporting
|
||||||
|
@ -216,7 +236,7 @@ class PillowLeakTestCase:
|
||||||
# This is the maximum resident set size used (in kilobytes).
|
# This is the maximum resident set size used (in kilobytes).
|
||||||
return mem # Kb
|
return mem # Kb
|
||||||
|
|
||||||
def _test_leak(self, core):
|
def _test_leak(self, core: Callable[[], None]) -> None:
|
||||||
start_mem = self._get_mem_usage()
|
start_mem = self._get_mem_usage()
|
||||||
for cycle in range(self.iterations):
|
for cycle in range(self.iterations):
|
||||||
core()
|
core()
|
||||||
|
@ -228,17 +248,17 @@ class PillowLeakTestCase:
|
||||||
# helpers
|
# helpers
|
||||||
|
|
||||||
|
|
||||||
def fromstring(data):
|
def fromstring(data: bytes) -> Image.Image:
|
||||||
return Image.open(BytesIO(data))
|
return Image.open(BytesIO(data))
|
||||||
|
|
||||||
|
|
||||||
def tostring(im, string_format, **options):
|
def tostring(im: Image.Image, string_format: str, **options: dict[str, Any]) -> bytes:
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im.save(out, string_format, **options)
|
im.save(out, string_format, **options)
|
||||||
return out.getvalue()
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def hopper(mode=None, cache={}):
|
def hopper(mode: str | None = None, cache: dict[str, Image.Image] = {}) -> Image.Image:
|
||||||
if mode is None:
|
if mode is None:
|
||||||
# Always return fresh not-yet-loaded version of image.
|
# Always return fresh not-yet-loaded version of image.
|
||||||
# Operations on not-yet-loaded images is separate class of errors
|
# Operations on not-yet-loaded images is separate class of errors
|
||||||
|
@ -259,29 +279,31 @@ def hopper(mode=None, cache={}):
|
||||||
return im.copy()
|
return im.copy()
|
||||||
|
|
||||||
|
|
||||||
def djpeg_available():
|
def djpeg_available() -> bool:
|
||||||
if shutil.which("djpeg"):
|
if shutil.which("djpeg"):
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(["djpeg", "-version"])
|
subprocess.check_call(["djpeg", "-version"])
|
||||||
return True
|
return True
|
||||||
except subprocess.CalledProcessError: # pragma: no cover
|
except subprocess.CalledProcessError: # pragma: no cover
|
||||||
return False
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def cjpeg_available():
|
def cjpeg_available() -> bool:
|
||||||
if shutil.which("cjpeg"):
|
if shutil.which("cjpeg"):
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(["cjpeg", "-version"])
|
subprocess.check_call(["cjpeg", "-version"])
|
||||||
return True
|
return True
|
||||||
except subprocess.CalledProcessError: # pragma: no cover
|
except subprocess.CalledProcessError: # pragma: no cover
|
||||||
return False
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def netpbm_available():
|
def netpbm_available() -> bool:
|
||||||
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
||||||
|
|
||||||
|
|
||||||
def magick_command():
|
def magick_command() -> list[str] | None:
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
magickhome = os.environ.get("MAGICK_HOME")
|
magickhome = os.environ.get("MAGICK_HOME")
|
||||||
if magickhome:
|
if magickhome:
|
||||||
|
@ -298,47 +320,48 @@ def magick_command():
|
||||||
return imagemagick
|
return imagemagick
|
||||||
if graphicsmagick and shutil.which(graphicsmagick[0]):
|
if graphicsmagick and shutil.which(graphicsmagick[0]):
|
||||||
return graphicsmagick
|
return graphicsmagick
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def on_appveyor():
|
def on_appveyor() -> bool:
|
||||||
return "APPVEYOR" in os.environ
|
return "APPVEYOR" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def on_github_actions():
|
def on_github_actions() -> bool:
|
||||||
return "GITHUB_ACTIONS" in os.environ
|
return "GITHUB_ACTIONS" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def on_ci():
|
def on_ci() -> bool:
|
||||||
# GitHub Actions and AppVeyor have "CI"
|
# GitHub Actions and AppVeyor have "CI"
|
||||||
return "CI" in os.environ
|
return "CI" in os.environ
|
||||||
|
|
||||||
|
|
||||||
def is_big_endian():
|
def is_big_endian() -> bool:
|
||||||
return sys.byteorder == "big"
|
return sys.byteorder == "big"
|
||||||
|
|
||||||
|
|
||||||
def is_ppc64le():
|
def is_ppc64le() -> bool:
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
return platform.machine() == "ppc64le"
|
return platform.machine() == "ppc64le"
|
||||||
|
|
||||||
|
|
||||||
def is_win32():
|
def is_win32() -> bool:
|
||||||
return sys.platform.startswith("win32")
|
return sys.platform.startswith("win32")
|
||||||
|
|
||||||
|
|
||||||
def is_pypy():
|
def is_pypy() -> bool:
|
||||||
return hasattr(sys, "pypy_translation_info")
|
return hasattr(sys, "pypy_translation_info")
|
||||||
|
|
||||||
|
|
||||||
def is_mingw():
|
def is_mingw() -> bool:
|
||||||
return sysconfig.get_platform() == "mingw"
|
return sysconfig.get_platform() == "mingw"
|
||||||
|
|
||||||
|
|
||||||
class CachedProperty:
|
class CachedProperty:
|
||||||
def __init__(self, func):
|
def __init__(self, func: Callable[[Any], None]) -> None:
|
||||||
self.func = func
|
self.func = func
|
||||||
|
|
||||||
def __get__(self, instance, cls=None):
|
def __get__(self, instance: Any, cls: type[Any] | None = None) -> Any:
|
||||||
result = instance.__dict__[self.func.__name__] = self.func(instance)
|
result = instance.__dict__[self.func.__name__] = self.func(instance)
|
||||||
return result
|
return result
|
||||||
|
|
BIN
Tests/images/apng/different_durations.png
Normal file
BIN
Tests/images/apng/different_durations.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 233 B |
BIN
Tests/images/bgr15.dds
Normal file
BIN
Tests/images/bgr15.dds
Normal file
Binary file not shown.
BIN
Tests/images/bgr15.png
Normal file
BIN
Tests/images/bgr15.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
Tests/images/hopper.pfm
Normal file
BIN
Tests/images/hopper.pfm
Normal file
Binary file not shown.
BIN
Tests/images/hopper_be.pfm
Normal file
BIN
Tests/images/hopper_be.pfm
Normal file
Binary file not shown.
BIN
Tests/images/multiple_exif.jpg
Normal file
BIN
Tests/images/multiple_exif.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 364 B |
Binary file not shown.
|
@ -13,7 +13,6 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
import atheris
|
import atheris
|
||||||
|
@ -24,7 +23,7 @@ with atheris.instrument_imports():
|
||||||
import fuzzers
|
import fuzzers
|
||||||
|
|
||||||
|
|
||||||
def TestOneInput(data):
|
def TestOneInput(data: bytes) -> None:
|
||||||
try:
|
try:
|
||||||
fuzzers.fuzz_font(data)
|
fuzzers.fuzz_font(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -33,7 +32,7 @@ def TestOneInput(data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
fuzzers.enable_decompressionbomb_error()
|
fuzzers.enable_decompressionbomb_error()
|
||||||
atheris.Setup(sys.argv, TestOneInput)
|
atheris.Setup(sys.argv, TestOneInput)
|
||||||
atheris.Fuzz()
|
atheris.Fuzz()
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
import atheris
|
import atheris
|
||||||
|
@ -24,7 +23,7 @@ with atheris.instrument_imports():
|
||||||
import fuzzers
|
import fuzzers
|
||||||
|
|
||||||
|
|
||||||
def TestOneInput(data):
|
def TestOneInput(data: bytes) -> None:
|
||||||
try:
|
try:
|
||||||
fuzzers.fuzz_image(data)
|
fuzzers.fuzz_image(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -33,7 +32,7 @@ def TestOneInput(data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
fuzzers.enable_decompressionbomb_error()
|
fuzzers.enable_decompressionbomb_error()
|
||||||
atheris.Setup(sys.argv, TestOneInput)
|
atheris.Setup(sys.argv, TestOneInput)
|
||||||
atheris.Fuzz()
|
atheris.Fuzz()
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
|
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
|
||||||
|
|
||||||
|
|
||||||
def enable_decompressionbomb_error():
|
def enable_decompressionbomb_error() -> None:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
warnings.filterwarnings("ignore")
|
warnings.filterwarnings("ignore")
|
||||||
warnings.simplefilter("error", Image.DecompressionBombWarning)
|
warnings.simplefilter("error", Image.DecompressionBombWarning)
|
||||||
|
|
||||||
|
|
||||||
def disable_decompressionbomb_error():
|
def disable_decompressionbomb_error() -> None:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||||
warnings.resetwarnings()
|
warnings.resetwarnings()
|
||||||
|
|
||||||
|
|
||||||
def fuzz_image(data):
|
def fuzz_image(data: bytes) -> None:
|
||||||
# This will fail on some images in the corpus, as we have many
|
# This will fail on some images in the corpus, as we have many
|
||||||
# invalid images in the test suite.
|
# invalid images in the test suite.
|
||||||
with Image.open(io.BytesIO(data)) as im:
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
|
@ -25,7 +26,7 @@ def fuzz_image(data):
|
||||||
im.save(io.BytesIO(), "BMP")
|
im.save(io.BytesIO(), "BMP")
|
||||||
|
|
||||||
|
|
||||||
def fuzz_font(data):
|
def fuzz_font(data: bytes) -> None:
|
||||||
wrapper = io.BytesIO(data)
|
wrapper = io.BytesIO(data)
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype(wrapper)
|
font = ImageFont.truetype(wrapper)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ if features.check("libjpeg_turbo"):
|
||||||
"path",
|
"path",
|
||||||
subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"),
|
subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"),
|
||||||
)
|
)
|
||||||
def test_fuzz_images(path):
|
def test_fuzz_images(path: str) -> None:
|
||||||
fuzzers.enable_decompressionbomb_error()
|
fuzzers.enable_decompressionbomb_error()
|
||||||
try:
|
try:
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
|
@ -54,7 +55,7 @@ def test_fuzz_images(path):
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
|
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
|
||||||
)
|
)
|
||||||
def test_fuzz_fonts(path):
|
def test_fuzz_fonts(path: str) -> None:
|
||||||
if not path:
|
if not path:
|
||||||
return
|
return
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import _binary
|
from PIL import _binary
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageFilter
|
from PIL import Image, ImageFilter
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from array import array
|
from array import array
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import _deprecate
|
from PIL import _deprecate
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageSequence, PngImagePlugin
|
from PIL import Image, ImageSequence, PngImagePlugin
|
||||||
|
@ -689,3 +690,12 @@ def test_different_modes_in_later_frames(mode, default_image, duplicate, tmp_pat
|
||||||
)
|
)
|
||||||
with Image.open(test_file) as reloaded:
|
with Image.open(test_file) as reloaded:
|
||||||
assert reloaded.mode == mode
|
assert reloaded.mode == mode
|
||||||
|
|
||||||
|
|
||||||
|
def test_apng_repeated_seeks_give_correct_info() -> None:
|
||||||
|
with Image.open("Tests/images/apng/different_durations.png") as im:
|
||||||
|
for i in range(3):
|
||||||
|
im.seek(0)
|
||||||
|
assert im.info["duration"] == 4000
|
||||||
|
im.seek(1)
|
||||||
|
assert im.info["duration"] == 1000
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import BufrStubImagePlugin, Image
|
from PIL import BufrStubImagePlugin, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import ContainerIO, Image
|
from PIL import ContainerIO, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Test DdsImagePlugin"""
|
"""Test DdsImagePlugin"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -32,6 +33,7 @@ TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB = "Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SR
|
||||||
TEST_FILE_UNCOMPRESSED_L = "Tests/images/uncompressed_l.dds"
|
TEST_FILE_UNCOMPRESSED_L = "Tests/images/uncompressed_l.dds"
|
||||||
TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA = "Tests/images/uncompressed_la.dds"
|
TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA = "Tests/images/uncompressed_la.dds"
|
||||||
TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/hopper.dds"
|
TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/hopper.dds"
|
||||||
|
TEST_FILE_UNCOMPRESSED_BGR15 = "Tests/images/bgr15.dds"
|
||||||
TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
|
TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
|
||||||
|
|
||||||
|
|
||||||
|
@ -249,6 +251,7 @@ def test_dx10_r8g8b8a8_unorm_srgb():
|
||||||
("L", (128, 128), TEST_FILE_UNCOMPRESSED_L),
|
("L", (128, 128), TEST_FILE_UNCOMPRESSED_L),
|
||||||
("LA", (128, 128), TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA),
|
("LA", (128, 128), TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA),
|
||||||
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_RGB),
|
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_RGB),
|
||||||
|
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_BGR15),
|
||||||
("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA),
|
("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -341,16 +344,9 @@ def test_palette():
|
||||||
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
|
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
def test_unsupported_bitcount():
|
||||||
"test_file",
|
|
||||||
(
|
|
||||||
"Tests/images/unsupported_bitcount_rgb.dds",
|
|
||||||
"Tests/images/unsupported_bitcount_luminance.dds",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_unsupported_bitcount(test_file):
|
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
with Image.open(test_file):
|
with Image.open("Tests/images/unsupported_bitcount.dds"):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -270,7 +271,7 @@ def test_render_scale1():
|
||||||
image1_scale1_compare.load()
|
image1_scale1_compare.load()
|
||||||
assert_image_similar(image1_scale1, image1_scale1_compare, 5)
|
assert_image_similar(image1_scale1, image1_scale1_compare, 5)
|
||||||
|
|
||||||
# Non-Zero bounding box
|
# Non-zero bounding box
|
||||||
with Image.open(FILE2) as image2_scale1:
|
with Image.open(FILE2) as image2_scale1:
|
||||||
image2_scale1.load()
|
image2_scale1.load()
|
||||||
with Image.open(FILE2_COMPARE) as image2_scale1_compare:
|
with Image.open(FILE2_COMPARE) as image2_scale1_compare:
|
||||||
|
@ -292,7 +293,7 @@ def test_render_scale2():
|
||||||
image1_scale2_compare.load()
|
image1_scale2_compare.load()
|
||||||
assert_image_similar(image1_scale2, image1_scale2_compare, 5)
|
assert_image_similar(image1_scale2, image1_scale2_compare, 5)
|
||||||
|
|
||||||
# Non-Zero bounding box
|
# Non-zero bounding box
|
||||||
with Image.open(FILE2) as image2_scale2:
|
with Image.open(FILE2) as image2_scale2:
|
||||||
image2_scale2.load(scale=2)
|
image2_scale2.load(scale=2)
|
||||||
with Image.open(FILE2_COMPARE_SCALE2) as image2_scale2_compare:
|
with Image.open(FILE2_COMPARE_SCALE2) as image2_scale2_compare:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import FtexImagePlugin, Image
|
from PIL import FtexImagePlugin, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import GbrImagePlugin, Image
|
from PIL import GbrImagePlugin, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import GdImageFile, UnidentifiedImageError
|
from PIL import GdImageFile, UnidentifiedImageError
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import GimpGradientFile, ImagePalette
|
from PIL import GimpGradientFile, ImagePalette
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL.GimpPaletteFile import GimpPaletteFile
|
from PIL.GimpPaletteFile import GimpPaletteFile
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import GribStubImagePlugin, Image
|
from PIL import GribStubImagePlugin, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Hdf5StubImagePlugin, Image
|
from PIL import Hdf5StubImagePlugin, Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import filecmp
|
import filecmp
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
|
|
||||||
|
@ -6,11 +7,23 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, IptcImagePlugin
|
from PIL import Image, IptcImagePlugin
|
||||||
|
|
||||||
from .helper import hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
TEST_FILE = "Tests/images/iptc.jpg"
|
TEST_FILE = "Tests/images/iptc.jpg"
|
||||||
|
|
||||||
|
|
||||||
|
def test_open():
|
||||||
|
expected = Image.new("L", (1, 1))
|
||||||
|
|
||||||
|
f = BytesIO(
|
||||||
|
b"\x1c\x03<\x00\x02\x01\x00\x1c\x03x\x00\x01\x01\x1c\x03\x14\x00\x01\x01"
|
||||||
|
b"\x1c\x03\x1e\x00\x01\x01\x1c\x08\n\x00\x01\x00"
|
||||||
|
)
|
||||||
|
with Image.open(f) as im:
|
||||||
|
assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")]
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_getiptcinfo_jpg_none():
|
def test_getiptcinfo_jpg_none():
|
||||||
# Arrange
|
# Arrange
|
||||||
with hopper() as im:
|
with hopper() as im:
|
||||||
|
@ -78,24 +91,28 @@ def test_i():
|
||||||
c = b"a"
|
c = b"a"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
ret = IptcImagePlugin.i(c)
|
ret = IptcImagePlugin.i(c)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert ret == 97
|
assert ret == 97
|
||||||
|
|
||||||
|
|
||||||
def test_dump():
|
def test_dump(monkeypatch):
|
||||||
# Arrange
|
# Arrange
|
||||||
c = b"abc"
|
c = b"abc"
|
||||||
# Temporarily redirect stdout
|
# Temporarily redirect stdout
|
||||||
old_stdout = sys.stdout
|
mystdout = StringIO()
|
||||||
sys.stdout = mystdout = StringIO()
|
monkeypatch.setattr(sys, "stdout", mystdout)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
IptcImagePlugin.dump(c)
|
IptcImagePlugin.dump(c)
|
||||||
|
|
||||||
# Reset stdout
|
|
||||||
sys.stdout = old_stdout
|
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert mystdout.getvalue() == "61 62 63 \n"
|
assert mystdout.getvalue() == "61 62 63 \n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_pad_deprecation():
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert IptcImagePlugin.PAD == b"\0\0\0\0"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -142,6 +143,19 @@ class TestFileJpeg:
|
||||||
)
|
)
|
||||||
assert k > 0.9
|
assert k > 0.9
|
||||||
|
|
||||||
|
def test_rgb(self):
|
||||||
|
def getchannels(im):
|
||||||
|
return tuple(v[0] for v in im.layer)
|
||||||
|
|
||||||
|
im = hopper()
|
||||||
|
im_ycbcr = self.roundtrip(im)
|
||||||
|
assert getchannels(im_ycbcr) == (1, 2, 3)
|
||||||
|
assert_image_similar(im, im_ycbcr, 17)
|
||||||
|
|
||||||
|
im_rgb = self.roundtrip(im, keep_rgb=True)
|
||||||
|
assert getchannels(im_rgb) == (ord("R"), ord("G"), ord("B"))
|
||||||
|
assert_image_similar(im, im_rgb, 12)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_image_path",
|
"test_image_path",
|
||||||
[TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"],
|
[TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"],
|
||||||
|
@ -423,25 +437,28 @@ class TestFileJpeg:
|
||||||
return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]
|
return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]
|
||||||
|
|
||||||
# experimental API
|
# experimental API
|
||||||
im = self.roundtrip(hopper(), subsampling=-1) # default
|
for subsampling in (-1, 3): # (default, invalid)
|
||||||
|
im = self.roundtrip(hopper(), subsampling=subsampling)
|
||||||
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
||||||
im = self.roundtrip(hopper(), subsampling=0) # 4:4:4
|
for subsampling in (0, "4:4:4"):
|
||||||
|
im = self.roundtrip(hopper(), subsampling=subsampling)
|
||||||
assert getsampling(im) == (1, 1, 1, 1, 1, 1)
|
assert getsampling(im) == (1, 1, 1, 1, 1, 1)
|
||||||
im = self.roundtrip(hopper(), subsampling=1) # 4:2:2
|
for subsampling in (1, "4:2:2"):
|
||||||
|
im = self.roundtrip(hopper(), subsampling=subsampling)
|
||||||
assert getsampling(im) == (2, 1, 1, 1, 1, 1)
|
assert getsampling(im) == (2, 1, 1, 1, 1, 1)
|
||||||
im = self.roundtrip(hopper(), subsampling=2) # 4:2:0
|
for subsampling in (2, "4:2:0", "4:1:1"):
|
||||||
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
im = self.roundtrip(hopper(), subsampling=subsampling)
|
||||||
im = self.roundtrip(hopper(), subsampling=3) # default (undefined)
|
|
||||||
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
||||||
|
|
||||||
im = self.roundtrip(hopper(), subsampling="4:4:4")
|
# RGB colorspace
|
||||||
|
for subsampling in (-1, 0, "4:4:4"):
|
||||||
|
# "4:4:4" doesn't really make sense for RGB, but the conversion
|
||||||
|
# to an integer happens at a higher level
|
||||||
|
im = self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling)
|
||||||
assert getsampling(im) == (1, 1, 1, 1, 1, 1)
|
assert getsampling(im) == (1, 1, 1, 1, 1, 1)
|
||||||
im = self.roundtrip(hopper(), subsampling="4:2:2")
|
for subsampling in (1, "4:2:2", 2, "4:2:0", 3):
|
||||||
assert getsampling(im) == (2, 1, 1, 1, 1, 1)
|
with pytest.raises(OSError):
|
||||||
im = self.roundtrip(hopper(), subsampling="4:2:0")
|
self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling)
|
||||||
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
|
||||||
im = self.roundtrip(hopper(), subsampling="4:1:1")
|
|
||||||
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
self.roundtrip(hopper(), subsampling="1:1:1")
|
self.roundtrip(hopper(), subsampling="1:1:1")
|
||||||
|
@ -840,6 +857,10 @@ class TestFileJpeg:
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
assert im._getexif()[306] == "2017:03:13 23:03:09"
|
assert im._getexif()[306] == "2017:03:13 23:03:09"
|
||||||
|
|
||||||
|
def test_multiple_exif(self):
|
||||||
|
with Image.open("Tests/images/multiple_exif.jpg") as im:
|
||||||
|
assert im.info["exif"] == b"Exif\x00\x00firstsecond"
|
||||||
|
|
||||||
@mark_if_feature_version(
|
@mark_if_feature_version(
|
||||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, McIdasImagePlugin
|
from PIL import Image, McIdasImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImagePalette
|
from PIL import Image, ImagePalette
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageFile, PcxImagePlugin
|
from PIL import Image, ImageFile, PcxImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, PixarImagePlugin
|
from PIL import Image, PixarImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
@ -6,7 +7,12 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, PpmImagePlugin
|
from PIL import Image, PpmImagePlugin
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile, assert_image_similar, hopper
|
from .helper import (
|
||||||
|
assert_image_equal,
|
||||||
|
assert_image_equal_tofile,
|
||||||
|
assert_image_similar,
|
||||||
|
hopper,
|
||||||
|
)
|
||||||
|
|
||||||
# sample ppm stream
|
# sample ppm stream
|
||||||
TEST_FILE = "Tests/images/hopper.ppm"
|
TEST_FILE = "Tests/images/hopper.ppm"
|
||||||
|
@ -84,20 +90,58 @@ def test_16bit_pgm():
|
||||||
|
|
||||||
def test_16bit_pgm_write(tmp_path):
|
def test_16bit_pgm_write(tmp_path):
|
||||||
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
||||||
f = str(tmp_path / "temp.pgm")
|
filename = str(tmp_path / "temp.pgm")
|
||||||
im.save(f, "PPM")
|
im.save(filename, "PPM")
|
||||||
|
|
||||||
assert_image_equal_tofile(im, f)
|
assert_image_equal_tofile(im, filename)
|
||||||
|
|
||||||
|
|
||||||
def test_pnm(tmp_path):
|
def test_pnm(tmp_path):
|
||||||
with Image.open("Tests/images/hopper.pnm") as im:
|
with Image.open("Tests/images/hopper.pnm") as im:
|
||||||
assert_image_similar(im, hopper(), 0.0001)
|
assert_image_similar(im, hopper(), 0.0001)
|
||||||
|
|
||||||
f = str(tmp_path / "temp.pnm")
|
filename = str(tmp_path / "temp.pnm")
|
||||||
im.save(f)
|
im.save(filename)
|
||||||
|
|
||||||
assert_image_equal_tofile(im, f)
|
assert_image_equal_tofile(im, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pfm(tmp_path):
|
||||||
|
with Image.open("Tests/images/hopper.pfm") as im:
|
||||||
|
assert im.info["scale"] == 1.0
|
||||||
|
assert_image_equal(im, hopper("F"))
|
||||||
|
|
||||||
|
filename = str(tmp_path / "tmp.pfm")
|
||||||
|
im.save(filename)
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pfm_big_endian(tmp_path):
|
||||||
|
with Image.open("Tests/images/hopper_be.pfm") as im:
|
||||||
|
assert im.info["scale"] == 2.5
|
||||||
|
assert_image_equal(im, hopper("F"))
|
||||||
|
|
||||||
|
filename = str(tmp_path / "tmp.pfm")
|
||||||
|
im.save(filename)
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, filename)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"data",
|
||||||
|
[
|
||||||
|
b"Pf 1 1 NaN \0\0\0\0",
|
||||||
|
b"Pf 1 1 inf \0\0\0\0",
|
||||||
|
b"Pf 1 1 -inf \0\0\0\0",
|
||||||
|
b"Pf 1 1 0.0 \0\0\0\0",
|
||||||
|
b"Pf 1 1 -0.0 \0\0\0\0",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_pfm_invalid(data):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
with Image.open(BytesIO(data)):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, QoiImagePlugin
|
from PIL import Image, QoiImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, SgiImagePlugin
|
from PIL import Image, SgiImagePlugin
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user