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
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||
- PYTHON: C:/Python38-x64
|
||||
ARCHITECTURE: x64
|
||||
ARCHITECTURE: AMD64
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
|
||||
|
||||
|
|
|
@ -2,15 +2,14 @@
|
|||
|
||||
[report]
|
||||
# Regexes for lines to exclude from consideration
|
||||
exclude_lines =
|
||||
# Have to re-enable the standard pragma:
|
||||
pragma: no cover
|
||||
|
||||
# Don't complain if non-runnable code isn't run:
|
||||
exclude_also =
|
||||
# Don't complain if non-runnable code isn't run
|
||||
if 0:
|
||||
if __name__ == .__main__.:
|
||||
# Don't complain about debug code
|
||||
if DEBUG:
|
||||
# Don't complain about compatibility code for missing optional dependencies
|
||||
except ImportError
|
||||
|
||||
[run]
|
||||
omit =
|
||||
|
|
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"
|
||||
- title: "Testing"
|
||||
label: "Testing"
|
||||
- title: "Type hints"
|
||||
label: "Type hints"
|
||||
|
||||
exclude-labels:
|
||||
- "changelog: skip"
|
||||
|
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: pre-commit cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
||||
|
|
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/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
|
@ -16,7 +15,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
@ -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
|
||||
|
||||
- name: pip cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
||||
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }}
|
||||
|
|
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/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
|
@ -16,7 +15,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
|
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/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
|
@ -16,7 +15,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
|
6
.github/workflows/test-windows.yml
vendored
6
.github/workflows/test-windows.yml
vendored
|
@ -2,11 +2,12 @@ name: Test Windows
|
|||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
|
@ -14,7 +15,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
@ -89,7 +89,7 @@ jobs:
|
|||
|
||||
- name: Cache build
|
||||
id: build-cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: winbuild\build
|
||||
key:
|
||||
|
|
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
|
@ -8,7 +8,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
|
@ -16,7 +15,6 @@ on:
|
|||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
@ -86,6 +84,10 @@ jobs:
|
|||
env:
|
||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
||||
- name: Register gcc problem matcher
|
||||
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'"
|
||||
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
.ci/build.sh
|
||||
|
|
146
.github/workflows/wheels.yml
vendored
146
.github/workflows/wheels.yml
vendored
|
@ -30,7 +30,64 @@ env:
|
|||
FORCE_COLOR: 1
|
||||
|
||||
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 }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
|
@ -39,18 +96,18 @@ jobs:
|
|||
include:
|
||||
- name: "macOS x86_64"
|
||||
os: macos-latest
|
||||
archs: x86_64
|
||||
cibw_arch: x86_64
|
||||
macosx_deployment_target: "10.10"
|
||||
- name: "macOS arm64"
|
||||
os: macos-latest
|
||||
archs: arm64
|
||||
cibw_arch: arm64
|
||||
macosx_deployment_target: "11.0"
|
||||
- name: "manylinux2014 and musllinux x86_64"
|
||||
os: ubuntu-latest
|
||||
archs: x86_64
|
||||
cibw_arch: x86_64
|
||||
- name: "manylinux_2_28 x86_64"
|
||||
os: ubuntu-latest
|
||||
archs: x86_64
|
||||
cibw_arch: x86_64
|
||||
build: "*manylinux*"
|
||||
manylinux: "manylinux_2_28"
|
||||
steps:
|
||||
|
@ -62,12 +119,15 @@ jobs:
|
|||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Build wheels
|
||||
- name: Install cibuildwheel
|
||||
run: |
|
||||
python3 -m pip install -r .ci/requirements-cibw.txt
|
||||
|
||||
- name: Build wheels
|
||||
run: |
|
||||
python3 -m cibuildwheel --output-dir wheelhouse
|
||||
env:
|
||||
CIBW_ARCHS: ${{ matrix.archs }}
|
||||
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||
CIBW_BUILD: ${{ matrix.build }}
|
||||
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
|
@ -75,24 +135,21 @@ jobs:
|
|||
CIBW_TEST_SKIP: "*-macosx_arm64"
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
name: dist-${{ matrix.os }}-${{ matrix.cibw_arch }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
windows:
|
||||
name: Windows ${{ matrix.arch }}
|
||||
name: Windows ${{ matrix.cibw_arch }}
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: x86
|
||||
cibw_arch: x86
|
||||
- arch: x64
|
||||
cibw_arch: AMD64
|
||||
- arch: ARM64
|
||||
cibw_arch: ARM64
|
||||
- cibw_arch: x86
|
||||
- cibw_arch: AMD64
|
||||
- cibw_arch: ARM64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
@ -106,6 +163,10 @@ jobs:
|
|||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install cibuildwheel
|
||||
run: |
|
||||
python.exe -m pip install -r .ci/requirements-cibw.txt
|
||||
|
||||
- name: Prepare for build
|
||||
run: |
|
||||
choco install nasm --no-progress
|
||||
|
@ -114,12 +175,7 @@ jobs:
|
|||
# Install extra test images
|
||||
xcopy /S /Y Tests\test-images\* Tests\images
|
||||
|
||||
& python.exe -m pip install -r .ci/requirements-cibw.txt
|
||||
|
||||
# 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
|
||||
& python.exe winbuild\build_prepare.py -v --no-imagequant --architecture=${{ matrix.cibw_arch }}
|
||||
shell: pwsh
|
||||
|
||||
- name: Build wheels
|
||||
|
@ -146,6 +202,7 @@ jobs:
|
|||
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
||||
CIBW_CACHE_PATH: "C:\\cibw"
|
||||
CIBW_SKIP: pp38-*
|
||||
CIBW_TEST_SKIP: "*-win_arm64"
|
||||
CIBW_TEST_COMMAND: 'docker run --rm
|
||||
-v {project}:C:\pillow
|
||||
|
@ -157,24 +214,16 @@ jobs:
|
|||
shell: cmd
|
||||
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
name: dist-windows-${{ matrix.cibw_arch }}
|
||||
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
|
||||
if: "matrix.arch != 'ARM64'"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fribidi
|
||||
path: fribidi\*
|
||||
name: fribidi-windows-${{ matrix.cibw_arch }}
|
||||
path: winbuild\build\bin\fribidi*
|
||||
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -190,17 +239,26 @@ jobs:
|
|||
|
||||
- run: make sdist
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
name: dist-sdist
|
||||
path: dist/*.tar.gz
|
||||
|
||||
success:
|
||||
permissions:
|
||||
contents: none
|
||||
needs: [build, windows, sdist]
|
||||
pypi-publish:
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist]
|
||||
runs-on: ubuntu-latest
|
||||
name: Wheels Successful
|
||||
name: Upload release to PyPI
|
||||
environment:
|
||||
name: release-pypi
|
||||
url: https://pypi.org/p/Pillow
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Success
|
||||
run: echo Wheels Successful
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.7
|
||||
rev: v0.1.9
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 23.12.0
|
||||
rev: 23.12.1
|
||||
hooks:
|
||||
- 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)
|
||||
==================
|
||||
|
||||
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
|
||||
[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
|
||||
|
||||
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:
|
||||
|
||||
|
|
|
@ -48,9 +48,6 @@ As of 2019, Pillow development is
|
|||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
|
||||
alt="GitHub Actions build status (Wheels)"
|
||||
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
|
||||
<a href="https://app.travis-ci.com/github/python-pillow/Pillow"><img
|
||||
alt="Travis CI wheels build status (aarch64)"
|
||||
src="https://img.shields.io/travis/com/python-pillow/Pillow/main.svg?label=aarch64%20wheels"></a>
|
||||
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
|
||||
alt="Code coverage"
|
||||
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
||||
|
@ -68,10 +65,10 @@ As of 2019, Pillow development is
|
|||
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
|
||||
alt="Tidelift"
|
||||
src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
|
||||
<a href="https://pypi.org/project/Pillow/"><img
|
||||
<a href="https://pypi.org/project/pillow/"><img
|
||||
alt="Newest PyPI version"
|
||||
src="https://img.shields.io/pypi/v/pillow.svg"></a>
|
||||
<a href="https://pypi.org/project/Pillow/"><img
|
||||
<a href="https://pypi.org/project/pillow/"><img
|
||||
alt="Number of PyPI downloads"
|
||||
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
||||
<a href="https://www.bestpractices.dev/projects/6331"><img
|
||||
|
|
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
|
||||
* [ ] Develop and prepare release in `main` branch.
|
||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
|
||||
* [ ] Check that all of the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) and [Travis CI](https://app.travis-ci.com/github/python-pillow/pillow) jobs by manually triggering them.
|
||||
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
|
||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||
* [ ] Update `CHANGES.rst`.
|
||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||
|
@ -20,12 +20,7 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
|||
git tag 5.2.0
|
||||
git push --tags
|
||||
```
|
||||
* [ ] Create [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*
|
||||
```
|
||||
* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
|
||||
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
||||
|
@ -55,12 +50,7 @@ Released as needed for security, installation or critical bug fixes.
|
|||
```bash
|
||||
make sdist
|
||||
```
|
||||
* [ ] Create [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*
|
||||
```
|
||||
* [ ] Create and upload all [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
||||
```bash
|
||||
git push
|
||||
|
@ -82,11 +72,7 @@ Released as needed privately to individual vendors for critical security-related
|
|||
git tag 2.5.3
|
||||
git push origin --tags
|
||||
```
|
||||
* [ ] Create and check source distribution:
|
||||
```bash
|
||||
make sdist
|
||||
```
|
||||
* [ ] Create [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)
|
||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
||||
```bash
|
||||
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
|
||||
|
||||
* [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
|
||||
```bash
|
||||
gh run download --dir dist
|
||||
# select dist
|
||||
```
|
||||
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
|
||||
and copy into `dist`.
|
||||
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||
has passed, including the "Upload release to PyPI" job. This will have been triggered
|
||||
by the new tag.
|
||||
|
||||
## Publicize Release
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
from PIL import PyAccess
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from PIL import Image
|
||||
|
||||
TEST_FILE = "Tests/images/fli_overflow.fli"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
# version.
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
from PIL import Image
|
||||
|
||||
repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import zlib
|
||||
from io import BytesIO
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from PIL import features
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import os
|
||||
|
||||
|
|
169
Tests/helper.py
169
Tests/helper.py
|
@ -11,6 +11,7 @@ import sys
|
|||
import sysconfig
|
||||
import tempfile
|
||||
from io import BytesIO
|
||||
from typing import Any, Callable, Sequence
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
@ -19,42 +20,40 @@ from PIL import Image, ImageMath, features
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
HAS_UPLOADER = False
|
||||
|
||||
uploader = None
|
||||
if os.environ.get("SHOW_ERRORS"):
|
||||
# local img.show for errors.
|
||||
HAS_UPLOADER = True
|
||||
|
||||
class test_image_results:
|
||||
@staticmethod
|
||||
def upload(a, b):
|
||||
a.show()
|
||||
b.show()
|
||||
|
||||
uploader = "show"
|
||||
elif "GITHUB_ACTIONS" in os.environ:
|
||||
HAS_UPLOADER = True
|
||||
|
||||
class test_image_results:
|
||||
@staticmethod
|
||||
def upload(a, b):
|
||||
dir_errors = os.path.join(os.path.dirname(__file__), "errors")
|
||||
os.makedirs(dir_errors, exist_ok=True)
|
||||
tmpdir = tempfile.mkdtemp(dir=dir_errors)
|
||||
a.save(os.path.join(tmpdir, "a.png"))
|
||||
b.save(os.path.join(tmpdir, "b.png"))
|
||||
return tmpdir
|
||||
|
||||
uploader = "github_actions"
|
||||
else:
|
||||
try:
|
||||
import test_image_results
|
||||
|
||||
HAS_UPLOADER = True
|
||||
uploader = "aws"
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def convert_to_comparable(a, b):
|
||||
def upload(a: Image.Image, b: Image.Image) -> str | None:
|
||||
if uploader == "show":
|
||||
# local img.show for errors.
|
||||
a.show()
|
||||
b.show()
|
||||
elif uploader == "github_actions":
|
||||
dir_errors = os.path.join(os.path.dirname(__file__), "errors")
|
||||
os.makedirs(dir_errors, exist_ok=True)
|
||||
tmpdir = tempfile.mkdtemp(dir=dir_errors)
|
||||
a.save(os.path.join(tmpdir, "a.png"))
|
||||
b.save(os.path.join(tmpdir, "b.png"))
|
||||
return tmpdir
|
||||
elif uploader == "aws":
|
||||
return test_image_results.upload(a, b)
|
||||
return None
|
||||
|
||||
|
||||
def convert_to_comparable(
|
||||
a: Image.Image, b: Image.Image
|
||||
) -> tuple[Image.Image, Image.Image]:
|
||||
new_a, new_b = a, b
|
||||
if a.mode == "P":
|
||||
new_a = Image.new("L", a.size)
|
||||
|
@ -67,14 +66,18 @@ def convert_to_comparable(a, b):
|
|||
return new_a, new_b
|
||||
|
||||
|
||||
def assert_deep_equal(a, b, msg=None):
|
||||
def assert_deep_equal(
|
||||
a: Sequence[Any], b: Sequence[Any], msg: str | None = None
|
||||
) -> None:
|
||||
try:
|
||||
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
|
||||
except Exception:
|
||||
assert a == b, msg
|
||||
|
||||
|
||||
def assert_image(im, mode, size, msg=None):
|
||||
def assert_image(
|
||||
im: Image.Image, mode: str, size: tuple[int, int], msg: str | None = None
|
||||
) -> None:
|
||||
if mode is not None:
|
||||
assert im.mode == mode, (
|
||||
msg or f"got mode {repr(im.mode)}, expected {repr(mode)}"
|
||||
|
@ -86,28 +89,32 @@ def assert_image(im, mode, size, msg=None):
|
|||
)
|
||||
|
||||
|
||||
def assert_image_equal(a, b, msg=None):
|
||||
def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) -> None:
|
||||
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
||||
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
||||
if a.tobytes() != b.tobytes():
|
||||
if HAS_UPLOADER:
|
||||
try:
|
||||
url = test_image_results.upload(a, b)
|
||||
try:
|
||||
url = upload(a, b)
|
||||
if url:
|
||||
logger.error("URL for test images: %s", url)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
pytest.fail(msg or "got different content")
|
||||
|
||||
|
||||
def assert_image_equal_tofile(a, filename, msg=None, mode=None):
|
||||
def assert_image_equal_tofile(
|
||||
a: Image.Image, filename: str, msg: str | None = None, mode: str | None = None
|
||||
) -> None:
|
||||
with Image.open(filename) as img:
|
||||
if mode:
|
||||
img = img.convert(mode)
|
||||
assert_image_equal(a, img, msg)
|
||||
|
||||
|
||||
def assert_image_similar(a, b, epsilon, msg=None):
|
||||
def assert_image_similar(
|
||||
a: Image.Image, b: Image.Image, epsilon: float, msg: str | None = None
|
||||
) -> None:
|
||||
assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
|
||||
assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
|
||||
|
||||
|
@ -125,55 +132,68 @@ def assert_image_similar(a, b, epsilon, msg=None):
|
|||
+ f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}"
|
||||
)
|
||||
except Exception as e:
|
||||
if HAS_UPLOADER:
|
||||
try:
|
||||
url = test_image_results.upload(a, b)
|
||||
try:
|
||||
url = upload(a, b)
|
||||
if url:
|
||||
logger.exception("URL for test images: %s", url)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
raise e
|
||||
|
||||
|
||||
def assert_image_similar_tofile(a, filename, epsilon, msg=None, mode=None):
|
||||
def assert_image_similar_tofile(
|
||||
a: Image.Image,
|
||||
filename: str,
|
||||
epsilon: float,
|
||||
msg: str | None = None,
|
||||
mode: str | None = None,
|
||||
) -> None:
|
||||
with Image.open(filename) as img:
|
||||
if mode:
|
||||
img = img.convert(mode)
|
||||
assert_image_similar(a, img, epsilon, msg)
|
||||
|
||||
|
||||
def assert_all_same(items, msg=None):
|
||||
def assert_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||
assert items.count(items[0]) == len(items), msg
|
||||
|
||||
|
||||
def assert_not_all_same(items, msg=None):
|
||||
def assert_not_all_same(items: Sequence[Any], msg: str | None = None) -> None:
|
||||
assert items.count(items[0]) != len(items), msg
|
||||
|
||||
|
||||
def assert_tuple_approx_equal(actuals, targets, threshold, msg):
|
||||
def assert_tuple_approx_equal(
|
||||
actuals: Sequence[int], targets: tuple[int, ...], threshold: int, msg: str
|
||||
) -> None:
|
||||
"""Tests if actuals has values within threshold from targets"""
|
||||
value = True
|
||||
for i, target in enumerate(targets):
|
||||
value *= target - threshold <= actuals[i] <= target + threshold
|
||||
|
||||
assert value, msg + ": " + repr(actuals) + " != " + repr(targets)
|
||||
if not (target - threshold <= actuals[i] <= target + threshold):
|
||||
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
|
||||
|
||||
|
||||
def skip_unless_feature(feature):
|
||||
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
||||
reason = f"{feature} not available"
|
||||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||
|
||||
|
||||
def skip_unless_feature_version(feature, version_required, reason=None):
|
||||
def skip_unless_feature_version(
|
||||
feature: str, required: str, reason: str | None = None
|
||||
) -> pytest.MarkDecorator:
|
||||
if not features.check(feature):
|
||||
return pytest.mark.skip(f"{feature} not available")
|
||||
if reason is None:
|
||||
reason = f"{feature} is older than {version_required}"
|
||||
version_required = parse_version(version_required)
|
||||
reason = f"{feature} is older than {required}"
|
||||
version_required = parse_version(required)
|
||||
version_available = parse_version(features.version(feature))
|
||||
return pytest.mark.skipif(version_available < version_required, reason=reason)
|
||||
|
||||
|
||||
def mark_if_feature_version(mark, feature, version_blacklist, reason=None):
|
||||
def mark_if_feature_version(
|
||||
mark: pytest.MarkDecorator,
|
||||
feature: str,
|
||||
version_blacklist: str,
|
||||
reason: str | None = None,
|
||||
) -> pytest.MarkDecorator:
|
||||
if not features.check(feature):
|
||||
return pytest.mark.pil_noop_mark()
|
||||
if reason is None:
|
||||
|
@ -194,7 +214,7 @@ class PillowLeakTestCase:
|
|||
iterations = 100 # count
|
||||
mem_limit = 512 # k
|
||||
|
||||
def _get_mem_usage(self):
|
||||
def _get_mem_usage(self) -> float:
|
||||
"""
|
||||
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
||||
between macOS and Linux rss reporting
|
||||
|
@ -216,7 +236,7 @@ class PillowLeakTestCase:
|
|||
# This is the maximum resident set size used (in kilobytes).
|
||||
return mem # Kb
|
||||
|
||||
def _test_leak(self, core):
|
||||
def _test_leak(self, core: Callable[[], None]) -> None:
|
||||
start_mem = self._get_mem_usage()
|
||||
for cycle in range(self.iterations):
|
||||
core()
|
||||
|
@ -228,17 +248,17 @@ class PillowLeakTestCase:
|
|||
# helpers
|
||||
|
||||
|
||||
def fromstring(data):
|
||||
def fromstring(data: bytes) -> Image.Image:
|
||||
return Image.open(BytesIO(data))
|
||||
|
||||
|
||||
def tostring(im, string_format, **options):
|
||||
def tostring(im: Image.Image, string_format: str, **options: dict[str, Any]) -> bytes:
|
||||
out = BytesIO()
|
||||
im.save(out, string_format, **options)
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
def hopper(mode=None, cache={}):
|
||||
def hopper(mode: str | None = None, cache: dict[str, Image.Image] = {}) -> Image.Image:
|
||||
if mode is None:
|
||||
# Always return fresh not-yet-loaded version of image.
|
||||
# Operations on not-yet-loaded images is separate class of errors
|
||||
|
@ -259,29 +279,31 @@ def hopper(mode=None, cache={}):
|
|||
return im.copy()
|
||||
|
||||
|
||||
def djpeg_available():
|
||||
def djpeg_available() -> bool:
|
||||
if shutil.which("djpeg"):
|
||||
try:
|
||||
subprocess.check_call(["djpeg", "-version"])
|
||||
return True
|
||||
except subprocess.CalledProcessError: # pragma: no cover
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def cjpeg_available():
|
||||
def cjpeg_available() -> bool:
|
||||
if shutil.which("cjpeg"):
|
||||
try:
|
||||
subprocess.check_call(["cjpeg", "-version"])
|
||||
return True
|
||||
except subprocess.CalledProcessError: # pragma: no cover
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def netpbm_available():
|
||||
def netpbm_available() -> bool:
|
||||
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
||||
|
||||
|
||||
def magick_command():
|
||||
def magick_command() -> list[str] | None:
|
||||
if sys.platform == "win32":
|
||||
magickhome = os.environ.get("MAGICK_HOME")
|
||||
if magickhome:
|
||||
|
@ -298,47 +320,48 @@ def magick_command():
|
|||
return imagemagick
|
||||
if graphicsmagick and shutil.which(graphicsmagick[0]):
|
||||
return graphicsmagick
|
||||
return None
|
||||
|
||||
|
||||
def on_appveyor():
|
||||
def on_appveyor() -> bool:
|
||||
return "APPVEYOR" in os.environ
|
||||
|
||||
|
||||
def on_github_actions():
|
||||
def on_github_actions() -> bool:
|
||||
return "GITHUB_ACTIONS" in os.environ
|
||||
|
||||
|
||||
def on_ci():
|
||||
def on_ci() -> bool:
|
||||
# GitHub Actions and AppVeyor have "CI"
|
||||
return "CI" in os.environ
|
||||
|
||||
|
||||
def is_big_endian():
|
||||
def is_big_endian() -> bool:
|
||||
return sys.byteorder == "big"
|
||||
|
||||
|
||||
def is_ppc64le():
|
||||
def is_ppc64le() -> bool:
|
||||
import platform
|
||||
|
||||
return platform.machine() == "ppc64le"
|
||||
|
||||
|
||||
def is_win32():
|
||||
def is_win32() -> bool:
|
||||
return sys.platform.startswith("win32")
|
||||
|
||||
|
||||
def is_pypy():
|
||||
def is_pypy() -> bool:
|
||||
return hasattr(sys, "pypy_translation_info")
|
||||
|
||||
|
||||
def is_mingw():
|
||||
def is_mingw() -> bool:
|
||||
return sysconfig.get_platform() == "mingw"
|
||||
|
||||
|
||||
class CachedProperty:
|
||||
def __init__(self, func):
|
||||
def __init__(self, func: Callable[[Any], None]) -> None:
|
||||
self.func = func
|
||||
|
||||
def __get__(self, instance, cls=None):
|
||||
def __get__(self, instance: Any, cls: type[Any] | None = None) -> Any:
|
||||
result = instance.__dict__[self.func.__name__] = self.func(instance)
|
||||
return result
|
||||
|
|
BIN
Tests/images/apng/different_durations.png
Normal file
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.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
import atheris
|
||||
|
@ -24,7 +23,7 @@ with atheris.instrument_imports():
|
|||
import fuzzers
|
||||
|
||||
|
||||
def TestOneInput(data):
|
||||
def TestOneInput(data: bytes) -> None:
|
||||
try:
|
||||
fuzzers.fuzz_font(data)
|
||||
except Exception:
|
||||
|
@ -33,7 +32,7 @@ def TestOneInput(data):
|
|||
pass
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
fuzzers.enable_decompressionbomb_error()
|
||||
atheris.Setup(sys.argv, TestOneInput)
|
||||
atheris.Fuzz()
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
import atheris
|
||||
|
@ -24,7 +23,7 @@ with atheris.instrument_imports():
|
|||
import fuzzers
|
||||
|
||||
|
||||
def TestOneInput(data):
|
||||
def TestOneInput(data: bytes) -> None:
|
||||
try:
|
||||
fuzzers.fuzz_image(data)
|
||||
except Exception:
|
||||
|
@ -33,7 +32,7 @@ def TestOneInput(data):
|
|||
pass
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
fuzzers.enable_decompressionbomb_error()
|
||||
atheris.Setup(sys.argv, TestOneInput)
|
||||
atheris.Fuzz()
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import warnings
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
|
||||
|
||||
|
||||
def enable_decompressionbomb_error():
|
||||
def enable_decompressionbomb_error() -> None:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
warnings.filterwarnings("ignore")
|
||||
warnings.simplefilter("error", Image.DecompressionBombWarning)
|
||||
|
||||
|
||||
def disable_decompressionbomb_error():
|
||||
def disable_decompressionbomb_error() -> None:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||
warnings.resetwarnings()
|
||||
|
||||
|
||||
def fuzz_image(data):
|
||||
def fuzz_image(data: bytes) -> None:
|
||||
# This will fail on some images in the corpus, as we have many
|
||||
# invalid images in the test suite.
|
||||
with Image.open(io.BytesIO(data)) as im:
|
||||
|
@ -25,7 +26,7 @@ def fuzz_image(data):
|
|||
im.save(io.BytesIO(), "BMP")
|
||||
|
||||
|
||||
def fuzz_font(data):
|
||||
def fuzz_font(data: bytes) -> None:
|
||||
wrapper = io.BytesIO(data)
|
||||
try:
|
||||
font = ImageFont.truetype(wrapper)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
@ -23,7 +24,7 @@ if features.check("libjpeg_turbo"):
|
|||
"path",
|
||||
subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"),
|
||||
)
|
||||
def test_fuzz_images(path):
|
||||
def test_fuzz_images(path: str) -> None:
|
||||
fuzzers.enable_decompressionbomb_error()
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
|
@ -54,7 +55,7 @@ def test_fuzz_images(path):
|
|||
@pytest.mark.parametrize(
|
||||
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
|
||||
)
|
||||
def test_fuzz_fonts(path):
|
||||
def test_fuzz_fonts(path: str) -> None:
|
||||
if not path:
|
||||
return
|
||||
with open(path, "rb") as f:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from PIL import _binary
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import warnings
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageFilter
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from array import array
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import _deprecate
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import re
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import BufrStubImagePlugin, Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import ContainerIO, Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Test DdsImagePlugin"""
|
||||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
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_WITH_ALPHA = "Tests/images/uncompressed_la.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"
|
||||
|
||||
|
||||
|
@ -249,6 +251,7 @@ def test_dx10_r8g8b8a8_unorm_srgb():
|
|||
("L", (128, 128), TEST_FILE_UNCOMPRESSED_L),
|
||||
("LA", (128, 128), TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA),
|
||||
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_RGB),
|
||||
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_BGR15),
|
||||
("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")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_file",
|
||||
(
|
||||
"Tests/images/unsupported_bitcount_rgb.dds",
|
||||
"Tests/images/unsupported_bitcount_luminance.dds",
|
||||
),
|
||||
)
|
||||
def test_unsupported_bitcount(test_file):
|
||||
def test_unsupported_bitcount():
|
||||
with pytest.raises(OSError):
|
||||
with Image.open(test_file):
|
||||
with Image.open("Tests/images/unsupported_bitcount.dds"):
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
@ -270,7 +271,7 @@ def test_render_scale1():
|
|||
image1_scale1_compare.load()
|
||||
assert_image_similar(image1_scale1, image1_scale1_compare, 5)
|
||||
|
||||
# Non-Zero bounding box
|
||||
# Non-zero bounding box
|
||||
with Image.open(FILE2) as image2_scale1:
|
||||
image2_scale1.load()
|
||||
with Image.open(FILE2_COMPARE) as image2_scale1_compare:
|
||||
|
@ -292,7 +293,7 @@ def test_render_scale2():
|
|||
image1_scale2_compare.load()
|
||||
assert_image_similar(image1_scale2, image1_scale2_compare, 5)
|
||||
|
||||
# Non-Zero bounding box
|
||||
# Non-zero bounding box
|
||||
with Image.open(FILE2) as image2_scale2:
|
||||
image2_scale2.load(scale=2)
|
||||
with Image.open(FILE2_COMPARE_SCALE2) as image2_scale2_compare:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import FtexImagePlugin, Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import GbrImagePlugin, Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import GdImageFile, UnidentifiedImageError
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from io import BytesIO
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from PIL import GimpGradientFile, ImagePalette
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL.GimpPaletteFile import GimpPaletteFile
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import GribStubImagePlugin, Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Hdf5StubImagePlugin, Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import warnings
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import filecmp
|
||||
import warnings
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from io import BytesIO, StringIO
|
||||
|
||||
|
@ -6,11 +7,23 @@ import pytest
|
|||
|
||||
from PIL import Image, IptcImagePlugin
|
||||
|
||||
from .helper import hopper
|
||||
from .helper import assert_image_equal, hopper
|
||||
|
||||
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():
|
||||
# Arrange
|
||||
with hopper() as im:
|
||||
|
@ -78,24 +91,28 @@ def test_i():
|
|||
c = b"a"
|
||||
|
||||
# Act
|
||||
ret = IptcImagePlugin.i(c)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
ret = IptcImagePlugin.i(c)
|
||||
|
||||
# Assert
|
||||
assert ret == 97
|
||||
|
||||
|
||||
def test_dump():
|
||||
def test_dump(monkeypatch):
|
||||
# Arrange
|
||||
c = b"abc"
|
||||
# Temporarily redirect stdout
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = mystdout = StringIO()
|
||||
mystdout = StringIO()
|
||||
monkeypatch.setattr(sys, "stdout", mystdout)
|
||||
|
||||
# Act
|
||||
IptcImagePlugin.dump(c)
|
||||
|
||||
# Reset stdout
|
||||
sys.stdout = old_stdout
|
||||
with pytest.warns(DeprecationWarning):
|
||||
IptcImagePlugin.dump(c)
|
||||
|
||||
# Assert
|
||||
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
|
||||
|
||||
import os
|
||||
import re
|
||||
import warnings
|
||||
|
@ -142,6 +143,19 @@ class TestFileJpeg:
|
|||
)
|
||||
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(
|
||||
"test_image_path",
|
||||
[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]
|
||||
|
||||
# experimental API
|
||||
im = self.roundtrip(hopper(), subsampling=-1) # default
|
||||
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
||||
im = self.roundtrip(hopper(), subsampling=0) # 4:4:4
|
||||
assert getsampling(im) == (1, 1, 1, 1, 1, 1)
|
||||
im = self.roundtrip(hopper(), subsampling=1) # 4:2:2
|
||||
assert getsampling(im) == (2, 1, 1, 1, 1, 1)
|
||||
im = self.roundtrip(hopper(), subsampling=2) # 4:2:0
|
||||
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
||||
im = self.roundtrip(hopper(), subsampling=3) # default (undefined)
|
||||
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
||||
for subsampling in (-1, 3): # (default, invalid)
|
||||
im = self.roundtrip(hopper(), subsampling=subsampling)
|
||||
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
||||
for subsampling in (0, "4:4:4"):
|
||||
im = self.roundtrip(hopper(), subsampling=subsampling)
|
||||
assert getsampling(im) == (1, 1, 1, 1, 1, 1)
|
||||
for subsampling in (1, "4:2:2"):
|
||||
im = self.roundtrip(hopper(), subsampling=subsampling)
|
||||
assert getsampling(im) == (2, 1, 1, 1, 1, 1)
|
||||
for subsampling in (2, "4:2:0", "4:1:1"):
|
||||
im = self.roundtrip(hopper(), subsampling=subsampling)
|
||||
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
|
||||
|
||||
im = self.roundtrip(hopper(), subsampling="4:4:4")
|
||||
assert getsampling(im) == (1, 1, 1, 1, 1, 1)
|
||||
im = self.roundtrip(hopper(), subsampling="4:2:2")
|
||||
assert getsampling(im) == (2, 1, 1, 1, 1, 1)
|
||||
im = self.roundtrip(hopper(), subsampling="4:2:0")
|
||||
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)
|
||||
# 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)
|
||||
for subsampling in (1, "4:2:2", 2, "4:2:0", 3):
|
||||
with pytest.raises(OSError):
|
||||
self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
self.roundtrip(hopper(), subsampling="1:1:1")
|
||||
|
@ -840,6 +857,10 @@ class TestFileJpeg:
|
|||
# Act / Assert
|
||||
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(
|
||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
from io import BytesIO
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import io
|
||||
import itertools
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, McIdasImagePlugin
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImagePalette
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from io import BytesIO
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
import subprocess
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageFile, PcxImagePlugin
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import os.path
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, PixarImagePlugin
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from io import BytesIO
|
||||
|
||||
|
@ -6,7 +7,12 @@ import pytest
|
|||
|
||||
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
|
||||
TEST_FILE = "Tests/images/hopper.ppm"
|
||||
|
@ -84,20 +90,58 @@ def test_16bit_pgm():
|
|||
|
||||
def test_16bit_pgm_write(tmp_path):
|
||||
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
||||
f = str(tmp_path / "temp.pgm")
|
||||
im.save(f, "PPM")
|
||||
filename = str(tmp_path / "temp.pgm")
|
||||
im.save(filename, "PPM")
|
||||
|
||||
assert_image_equal_tofile(im, f)
|
||||
assert_image_equal_tofile(im, filename)
|
||||
|
||||
|
||||
def test_pnm(tmp_path):
|
||||
with Image.open("Tests/images/hopper.pnm") as im:
|
||||
assert_image_similar(im, hopper(), 0.0001)
|
||||
|
||||
f = str(tmp_path / "temp.pnm")
|
||||
im.save(f)
|
||||
filename = str(tmp_path / "temp.pnm")
|
||||
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(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, QoiImagePlugin
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, SgiImagePlugin
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import warnings
|
||||
from io import BytesIO
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
|
||||
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