Merge branch 'main' into build-editable

This commit is contained in:
Andrew Murray 2024-02-15 11:12:17 +11:00 committed by GitHub
commit bf9ba35b20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
282 changed files with 4125 additions and 2951 deletions

View File

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

View File

@ -1 +1 @@
cibuildwheel==2.16.2
cibuildwheel==2.16.5

View File

@ -10,6 +10,11 @@ exclude_also =
if DEBUG:
# Don't complain about compatibility code for missing optional dependencies
except ImportError
if TYPE_CHECKING:
@abc.abstractmethod
# Empty bodies in protocols or abstract methods
^\s*def [a-zA-Z0-9_]+\(.*\)(\s*->.*)?:\s*\.\.\.(\s*#.*)?$
^\s*\.\.\.(\s*#.*)?$
[run]
omit =

View File

@ -37,16 +37,26 @@ jobs:
with:
python-version: "3.x"
cache: pip
cache-dependency-path: ".ci/*.sh"
cache-dependency-path: |
".ci/*.sh"
"pyproject.toml"
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Cache libimagequant
uses: actions/cache@v4
id: cache-libimagequant
with:
path: ~/cache-libimagequant
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
- name: Install Linux dependencies
run: |
.ci/install.sh
env:
GHA_PYTHON_VERSION: "3.x"
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
- name: Build
run: |

View File

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

View File

@ -2,7 +2,16 @@
set -e
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
brew install \
freetype \
ghostscript \
libimagequant \
libjpeg \
libraqm \
libtiff \
little-cms2 \
openjpeg \
webp
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
# TODO Update condition when cffi supports 3.13

View File

@ -23,6 +23,6 @@ jobs:
runs-on: ubuntu-latest
steps:
# Drafts your next release notes as pull requests are merged into "main"
- uses: release-drafter/release-drafter@v5
- uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -6,6 +6,7 @@ This sort of info is missing from GitHub Actions.
Requested here:
https://github.com/actions/virtual-environments/issues/79
"""
from __future__ import annotations
import os

View File

@ -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:
@ -49,9 +47,8 @@ jobs:
uses: actions/checkout@v4
- name: Install Cygwin
uses: cygwin/cygwin-install-action@v4
uses: egor-tensin/setup-cygwin@v4
with:
platform: x86_64
packages: >
gcc-g++
ghostscript
@ -71,6 +68,7 @@ jobs:
make
netpbm
perl
python39=3.9.16-1
python3${{ matrix.python-minor-version }}-cffi
python3${{ matrix.python-minor-version }}-cython
python3${{ matrix.python-minor-version }}-devel
@ -82,13 +80,13 @@ jobs:
zlib-devel
- name: Add Lapack to PATH
uses: egor-tensin/cleanup-path@v3
uses: egor-tensin/cleanup-path@v4
with:
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
- name: Select Python version
run: |
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
ln -sf c:/tools/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/tools/cygwin/bin/python3
- name: Get latest NumPy version
id: latest-numpy
@ -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') }}
@ -143,7 +141,7 @@ jobs:
bash.exe .ci/after_success.sh
- name: Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v3.1.5
with:
file: ./coverage.xml
flags: GHA_Cygwin

View File

@ -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:
@ -103,7 +101,7 @@ jobs:
MATRIX_DOCKER: ${{ matrix.docker }}
- name: Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v3.1.5
with:
flags: GHA_Docker
name: ${{ matrix.docker }}

View File

@ -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:
@ -84,7 +82,7 @@ jobs:
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
- name: Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v3.1.5
with:
file: ./coverage.xml
flags: GHA_Windows

View File

@ -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:
@ -202,7 +202,7 @@ jobs:
shell: pwsh
- name: Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v3.1.5
with:
file: ./coverage.xml
flags: GHA_Windows

View File

@ -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:
@ -28,6 +26,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
build:
@ -35,7 +36,7 @@ jobs:
fail-fast: false
matrix:
os: [
"macos-latest",
"macos-14",
"ubuntu-latest",
]
python-version: [
@ -49,11 +50,21 @@ jobs:
"3.8",
]
include:
- python-version: "3.9"
- python-version: "3.11"
PYTHONOPTIMIZE: 1
REVERSE: "--reverse"
- python-version: "3.8"
- python-version: "3.10"
PYTHONOPTIMIZE: 2
# M1 only available for 3.10+
- os: "macos-latest"
python-version: "3.9"
- os: "macos-latest"
python-version: "3.8"
exclude:
- os: "macos-14"
python-version: "3.9"
- os: "macos-14"
python-version: "3.8"
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
@ -67,17 +78,28 @@ jobs:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: pip
cache-dependency-path: ".ci/*.sh"
cache-dependency-path: |
".ci/*.sh"
"pyproject.toml"
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Cache libimagequant
if: startsWith(matrix.os, 'ubuntu')
uses: actions/cache@v4
id: cache-libimagequant
with:
path: ~/cache-libimagequant
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
- name: Install Linux dependencies
if: startsWith(matrix.os, 'ubuntu')
run: |
.ci/install.sh
env:
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
- name: Install macOS dependencies
if: startsWith(matrix.os, 'macOS')
@ -127,9 +149,9 @@ jobs:
.ci/after_success.sh
- name: Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v3.1.5
with:
flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
gcov: true

View File

@ -72,14 +72,12 @@ function build {
build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto
if [ -n "$IS_MACOS" ]; then
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
build_simple xorgproto 2023.2 https://www.x.org/pub/individual/proto
build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
if [ -f /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc ]; then
cp /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc /Library/Frameworks/Python.framework/Versions/Current/lib/pkgconfig/xcb-proto.pc
fi
fi
else
sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc
fi
@ -131,13 +129,13 @@ untar pillow-depends-main.zip
if [[ -n "$IS_MACOS" ]]; then
# webp, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb
# libxdmcp causes an issue on macOS < 11
# libxau and libxdmcp cause an issue on macOS < 11
# if php is installed, brew tries to reinstall these after installing openblas
# remove cairo to fix building harfbuzz on arm64
# remove lcms2 and libpng to fix building openjpeg on arm64
# remove zstd to avoid inclusion on x86_64
# curl from brew requires zstd, use system curl
brew remove --ignore-dependencies webp libpng libtiff libxcb libxdmcp curl php cairo lcms2 ghostscript zstd
brew remove --ignore-dependencies webp libpng libtiff libxcb libxau libxdmcp curl php cairo lcms2 ghostscript zstd
brew install pkg-config
fi

View File

@ -32,7 +32,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:
@ -41,18 +98,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:
@ -64,12 +121,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 }}
@ -79,22 +139,19 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.os }}-${{ matrix.archs }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }}
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
@ -108,6 +165,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
@ -116,9 +177,7 @@ jobs:
# Install extra test 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.arch }}
& python.exe winbuild\build_prepare.py -v --no-imagequant --architecture=${{ matrix.cibw_arch }}
shell: pwsh
- name: Build wheels
@ -159,13 +218,13 @@ jobs:
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: dist-windows-${{ matrix.arch }}
name: dist-windows-${{ matrix.cibw_arch }}
path: ./wheelhouse/*.whl
- name: Upload fribidi.dll
uses: actions/upload-artifact@v4
with:
name: fribidi-windows-${{ matrix.arch }}
name: fribidi-windows-${{ matrix.cibw_arch }}
path: winbuild\build\bin\fribidi*
sdist:
@ -189,7 +248,7 @@ jobs:
pypi-publish:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
needs: [build, windows, sdist]
needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist]
runs-on: ubuntu-latest
name: Upload release to PyPI
environment:

View File

@ -1,17 +1,17 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.9
rev: v0.2.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.12.1
rev: 24.1.1
hooks:
- id: black
- repo: https://github.com/PyCQA/bandit
rev: 1.7.6
rev: 1.7.7
hooks:
- id: bandit
args: [--severity-level=high]
@ -32,6 +32,7 @@ repos:
rev: v4.5.0
hooks:
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: check-merge-conflict
- id: check-json
- id: check-toml
@ -47,12 +48,12 @@ repos:
- id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt
rev: 1.5.3
rev: 1.7.0
hooks:
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.15
rev: v0.16
hooks:
- id: validate-pyproject

View File

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

View File

@ -2,6 +2,39 @@
Changelog (Pillow)
==================
10.3.0 (unreleased)
-------------------
- Update wl-paste handling and return None for some errors in grabclipboard() on Linux #7745
[nik012003, radarhere]
- Remove execute bit from ``setup.py`` #7760
[hugovk]
- Do not support using test-image-results to upload images after test failures #7739
[radarhere]
- Changed ImageMath.ops to be static #7721
[radarhere]
- Fix APNG info after seeking backwards more than twice #7701
[esoma, radarhere]
- Deprecate ImageCms constants and versions() function #7702
[nulano, radarhere]
- Added PerspectiveTransform #7699
[radarhere]
- Add support for reading and writing grayscale PFM images #7696
[nulano, hugovk]
- Add LCMS2 flags to ImageCms #7676
[nulano, radarhere, hugovk]
- Rename x64 to AMD64 in winbuild #7693
[nulano]
10.2.0 (2024-01-02)
-------------------

View File

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

View File

@ -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.
@ -83,12 +83,6 @@ Released as needed privately to individual vendors for critical security-related
* [ ] 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.
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
and copy into `dist`. Check and upload them e.g.:
```bash
python3 -m twine check --strict dist/*
python3 -m twine upload dist/pillow-5.2.0*
```
## Publicize Release

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import time
from PIL import PyAccess
@ -8,21 +9,21 @@ from .helper import hopper
# Not running this test by default. No DOS against CI.
def iterate_get(size, access):
def iterate_get(size, access) -> None:
(w, h) = size
for x in range(w):
for y in range(h):
access[(x, y)]
def iterate_set(size, access):
def iterate_set(size, access) -> None:
(w, h) = size
for x in range(w):
for y in range(h):
access[(x, y)] = (x % 256, y % 256, 0)
def timer(func, label, *args):
def timer(func, label, *args) -> None:
iterations = 5000
starttime = time.time()
for x in range(iterations):
@ -37,7 +38,7 @@ def timer(func, label, *args):
)
def test_direct():
def test_direct() -> None:
im = hopper()
im.load()
# im = Image.new("RGB", (2000, 2000), (1, 3, 2))

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python3
from __future__ import annotations
from PIL import Image

View File

@ -1,10 +1,11 @@
from __future__ import annotations
from PIL import Image
TEST_FILE = "Tests/images/fli_overflow.fli"
def test_fli_overflow():
def test_fli_overflow() -> None:
# this should not crash with a malloc error or access violation
with Image.open(TEST_FILE) as im:
im.load()

View File

@ -1,5 +1,8 @@
#!/usr/bin/env python3
from __future__ import annotations
from typing import Any, Callable
import pytest
from PIL import Image
@ -12,31 +15,34 @@ max_iterations = 10000
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
def _get_mem_usage():
def _get_mem_usage() -> float:
from resource import RUSAGE_SELF, getpagesize, getrusage
mem = getrusage(RUSAGE_SELF).ru_maxrss
return mem * getpagesize() / 1024 / 1024
def _test_leak(min_iterations, max_iterations, fn, *args, **kwargs):
def _test_leak(
min_iterations: int, max_iterations: int, fn: Callable[..., None], *args: Any
) -> None:
mem_limit = None
for i in range(max_iterations):
fn(*args, **kwargs)
fn(*args)
mem = _get_mem_usage()
if i < min_iterations:
mem_limit = mem + 1
continue
msg = f"memory usage limit exceeded after {i + 1} iterations"
assert mem_limit is not None
assert mem <= mem_limit, msg
def test_leak_putdata():
def test_leak_putdata() -> None:
im = Image.new("RGB", (25, 25))
_test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
def test_leak_getlist():
def test_leak_getlist() -> None:
im = Image.new("P", (25, 25))
_test_leak(
min_iterations,

View File

@ -1,4 +1,5 @@
from __future__ import annotations
from io import BytesIO
import pytest
@ -19,7 +20,7 @@ pytestmark = [
]
def test_leak_load():
def test_leak_load() -> None:
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
setrlimit(RLIMIT_STACK, (stack_size, stack_size))
@ -29,7 +30,7 @@ def test_leak_load():
im.load()
def test_leak_save():
def test_leak_save() -> None:
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
setrlimit(RLIMIT_STACK, (stack_size, stack_size))

View File

@ -1,10 +1,13 @@
from __future__ import annotations
from pathlib import Path
import pytest
from PIL import Image
def test_j2k_overflow(tmp_path):
def test_j2k_overflow(tmp_path: Path) -> None:
im = Image.new("RGBA", (1024, 131584))
target = str(tmp_path / "temp.jpc")
with pytest.raises(OSError):

3
Tests/check_jp2_overflow.py Executable file → Normal file
View File

@ -1,5 +1,3 @@
#!/usr/bin/env python3
# Reproductions/tests for OOB read errors in FliDecode.c
# When run in python, all of these images should fail for
@ -14,7 +12,6 @@
# version.
from __future__ import annotations
from PIL import Image
repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2")

View File

@ -1,4 +1,5 @@
from __future__ import annotations
from io import BytesIO
import pytest
@ -110,14 +111,14 @@ standard_chrominance_qtable = (
[standard_l_qtable, standard_chrominance_qtable],
),
)
def test_qtables_leak(qtables):
def test_qtables_leak(qtables: tuple[tuple[int, ...]] | list[tuple[int, ...]]) -> None:
im = hopper("RGB")
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables)
def test_exif_leak():
def test_exif_leak() -> None:
"""
pre patch:
@ -180,7 +181,7 @@ def test_exif_leak():
im.save(test_output, "JPEG", exif=exif)
def test_base_save():
def test_base_save() -> None:
"""
base case:
MB

View File

@ -1,5 +1,8 @@
from __future__ import annotations
import sys
from pathlib import Path
from types import ModuleType
import pytest
@ -15,6 +18,7 @@ from PIL import Image
# 2.7 and 3.2.
numpy: ModuleType | None
try:
import numpy
except ImportError:
@ -27,23 +31,24 @@ XDIM = 48000
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
def _write_png(tmp_path, xdim, ydim):
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
f = str(tmp_path / "temp.png")
im = Image.new("L", (xdim, ydim), 0)
im.save(f)
def test_large(tmp_path):
def test_large(tmp_path: Path) -> None:
"""succeeded prepatch"""
_write_png(tmp_path, XDIM, YDIM)
def test_2gpx(tmp_path):
def test_2gpx(tmp_path: Path) -> None:
"""failed prepatch"""
_write_png(tmp_path, XDIM, XDIM)
@pytest.mark.skipif(numpy is None, reason="Numpy is not installed")
def test_size_greater_than_int():
def test_size_greater_than_int() -> None:
assert numpy is not None
arr = numpy.ndarray(shape=(16394, 16394))
Image.fromarray(arr)

View File

@ -1,5 +1,7 @@
from __future__ import annotations
import sys
from pathlib import Path
import pytest
@ -23,7 +25,7 @@ XDIM = 48000
pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit system")
def _write_png(tmp_path, xdim, ydim):
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
dtype = np.uint8
a = np.zeros((xdim, ydim), dtype=dtype)
f = str(tmp_path / "temp.png")
@ -31,11 +33,11 @@ def _write_png(tmp_path, xdim, ydim):
im.save(f)
def test_large(tmp_path):
def test_large(tmp_path: Path) -> None:
"""succeeded prepatch"""
_write_png(tmp_path, XDIM, YDIM)
def test_2gpx(tmp_path):
def test_2gpx(tmp_path: Path) -> None:
"""failed prepatch"""
_write_png(tmp_path, XDIM, XDIM)

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import Image
@ -6,7 +7,7 @@ from PIL import Image
TEST_FILE = "Tests/images/libtiff_segfault.tif"
def test_libtiff_segfault():
def test_libtiff_segfault() -> None:
"""This test should not segfault. It will on Pillow <= 3.1.0 and
libtiff >= 4.0.0
"""

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import zlib
from io import BytesIO
@ -7,7 +8,7 @@ from PIL import Image, ImageFile, PngImagePlugin
TEST_FILE = "Tests/images/png_decompression_dos.png"
def test_ignore_dos_text():
def test_ignore_dos_text() -> None:
ImageFile.LOAD_TRUNCATED_IMAGES = True
try:
@ -23,7 +24,7 @@ def test_ignore_dos_text():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
def test_dos_text():
def test_dos_text() -> None:
try:
im = Image.open(TEST_FILE)
im.load()
@ -35,7 +36,7 @@ def test_dos_text():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
def test_dos_total_memory():
def test_dos_total_memory() -> None:
im = Image.new("L", (1, 1))
compressed_data = zlib.compress(b"a" * 1024 * 1023)
@ -52,7 +53,7 @@ def test_dos_total_memory():
try:
im2 = Image.open(b)
except ValueError as msg:
assert "Too much memory" in msg
assert "Too much memory" in str(msg)
return
total_len = 0

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import sys
from pathlib import Path

View File

@ -1,10 +1,11 @@
from __future__ import annotations
import sys
from PIL import features
def test_wheel_modules():
def test_wheel_modules() -> None:
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
# tkinter is not available in cibuildwheel installed CPython on Windows
@ -18,13 +19,13 @@ def test_wheel_modules():
assert set(features.get_supported_modules()) == expected_modules
def test_wheel_codecs():
def test_wheel_codecs() -> None:
expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
assert set(features.get_supported_codecs()) == expected_codecs
def test_wheel_features():
def test_wheel_features() -> None:
expected_features = {
"webp_anim",
"webp_mux",

View File

@ -1,8 +1,11 @@
from __future__ import annotations
import io
import pytest
def pytest_report_header(config):
def pytest_report_header(config: pytest.Config) -> str:
try:
from PIL import features
@ -13,7 +16,7 @@ def pytest_report_header(config):
return f"pytest_report_header failed: {e}"
def pytest_configure(config):
def pytest_configure(config: pytest.Config) -> None:
config.addinivalue_line(
"markers",
"pil_noop_mark: A conditional mark where nothing special happens",

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3
from __future__ import annotations
import base64
import os

View File

@ -1,6 +1,7 @@
"""
Helper functions.
"""
from __future__ import annotations
import logging
@ -11,6 +12,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 +21,31 @@ 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
uploader = "show"
elif "GITHUB_ACTIONS" in os.environ:
uploader = "github_actions"
class test_image_results:
@staticmethod
def upload(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 "GITHUB_ACTIONS" in os.environ:
HAS_UPLOADER = True
class test_image_results:
@staticmethod
def upload(a, b):
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
else:
try:
import test_image_results
HAS_UPLOADER = True
except ImportError:
pass
return None
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
if a.mode == "P":
new_a = Image.new("L", a.size)
@ -67,14 +58,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,13 +81,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.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)
url = upload(a, b)
if url:
logger.error("URL for test images: %s", url)
except Exception:
pass
@ -100,14 +95,18 @@ def assert_image_equal(a, b, msg=None):
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 +124,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)
url = upload(a, b)
if url:
logger.exception("URL for test images: %s", url)
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 +206,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 +228,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 +240,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: 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 +271,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 +312,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

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

View File

@ -1,5 +1,3 @@
#!/usr/bin/gnuplot
#This is the script that was used to create our sample EPS files
#We used the following version of the gnuplot program
#G N U P L O T

BIN
Tests/images/hopper.pfm Normal file

Binary file not shown.

BIN
Tests/images/hopper_be.pfm Normal file

Binary file not shown.

View File

@ -23,7 +23,7 @@ with atheris.instrument_imports():
import fuzzers
def TestOneInput(data):
def TestOneInput(data: bytes) -> None:
try:
fuzzers.fuzz_font(data)
except Exception:
@ -32,7 +32,7 @@ def TestOneInput(data):
pass
def main():
def main() -> None:
fuzzers.enable_decompressionbomb_error()
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()

View File

@ -1,5 +1,3 @@
#!/usr/bin/python3
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -23,7 +21,7 @@ with atheris.instrument_imports():
import fuzzers
def TestOneInput(data):
def TestOneInput(data: bytes) -> None:
try:
fuzzers.fuzz_image(data)
except Exception:
@ -32,7 +30,7 @@ def TestOneInput(data):
pass
def main():
def main() -> None:
fuzzers.enable_decompressionbomb_error()
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()

View File

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

View File

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

View File

@ -1,8 +1,9 @@
from __future__ import annotations
from PIL import Image
def test_sanity():
def test_sanity() -> None:
# Make sure we have the binary extension
Image.core.new("L", (100, 100))

View File

@ -1,13 +1,14 @@
from __future__ import annotations
from PIL import _binary
def test_standard():
def test_standard() -> None:
assert _binary.i8(b"*") == 42
assert _binary.o8(42) == b"*"
def test_little_endian():
def test_little_endian() -> None:
assert _binary.i16le(b"\xff\xff\x00\x00") == 65535
assert _binary.i32le(b"\xff\xff\x00\x00") == 65535
@ -15,7 +16,7 @@ def test_little_endian():
assert _binary.o32le(65535) == b"\xff\xff\x00\x00"
def test_big_endian():
def test_big_endian() -> None:
assert _binary.i16be(b"\x00\x00\xff\xff") == 0
assert _binary.i32be(b"\x00\x00\xff\xff") == 65535

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import os
import warnings
@ -9,13 +10,13 @@ from .helper import assert_image_similar
base = os.path.join("Tests", "images", "bmp")
def get_files(d, ext=".bmp"):
def get_files(d: str, ext: str = ".bmp") -> list[str]:
return [
os.path.join(base, d, f) for f in os.listdir(os.path.join(base, d)) if ext in f
]
def test_bad():
def test_bad() -> None:
"""These shouldn't crash/dos, but they shouldn't return anything
either"""
for f in get_files("b"):
@ -28,7 +29,7 @@ def test_bad():
pass
def test_questionable():
def test_questionable() -> None:
"""These shouldn't crash/dos, but it's not well defined that these
are in spec"""
supported = [
@ -55,7 +56,7 @@ def test_questionable():
raise
def test_good():
def test_good() -> None:
"""These should all work. There's a set of target files in the
html directory that we can compare against."""
@ -79,7 +80,7 @@ def test_good():
"rgb32bf.bmp": "rgb24.png",
}
def get_compare(f):
def get_compare(f: str) -> str:
name = os.path.split(f)[1]
if name in file_map:
return os.path.join(base, "html", file_map[name])

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import Image, ImageFilter
@ -15,18 +16,18 @@ sample.putdata(sum([
# fmt: on
def test_imageops_box_blur():
def test_imageops_box_blur() -> None:
i = sample.filter(ImageFilter.BoxBlur(1))
assert i.mode == sample.mode
assert i.size == sample.size
assert isinstance(i, Image.Image)
def box_blur(image, radius=1, n=1):
def box_blur(image: Image.Image, radius: float = 1, n: int = 1) -> Image.Image:
return image._new(image.im.box_blur((radius, radius), n))
def assert_image(im, data, delta=0):
def assert_image(im: Image.Image, data: list[list[int]], delta: int = 0) -> None:
it = iter(im.getdata())
for data_row in data:
im_row = [next(it) for _ in range(im.size[0])]
@ -36,7 +37,13 @@ def assert_image(im, data, delta=0):
next(it)
def assert_blur(im, radius, data, passes=1, delta=0):
def assert_blur(
im: Image.Image,
radius: float,
data: list[list[int]],
passes: int = 1,
delta: int = 0,
) -> None:
# check grayscale image
assert_image(box_blur(im, radius, passes), data, delta)
rgba = Image.merge("RGBA", (im, im, im, im))
@ -44,7 +51,7 @@ def assert_blur(im, radius, data, passes=1, delta=0):
assert_image(band, data, delta)
def test_color_modes():
def test_color_modes() -> None:
with pytest.raises(ValueError):
box_blur(sample.convert("1"))
with pytest.raises(ValueError):
@ -64,7 +71,7 @@ def test_color_modes():
box_blur(sample.convert("YCbCr"))
def test_radius_0():
def test_radius_0() -> None:
assert_blur(
sample,
0,
@ -80,7 +87,7 @@ def test_radius_0():
)
def test_radius_0_02():
def test_radius_0_02() -> None:
assert_blur(
sample,
0.02,
@ -97,7 +104,7 @@ def test_radius_0_02():
)
def test_radius_0_05():
def test_radius_0_05() -> None:
assert_blur(
sample,
0.05,
@ -114,7 +121,7 @@ def test_radius_0_05():
)
def test_radius_0_1():
def test_radius_0_1() -> None:
assert_blur(
sample,
0.1,
@ -131,7 +138,7 @@ def test_radius_0_1():
)
def test_radius_0_5():
def test_radius_0_5() -> None:
assert_blur(
sample,
0.5,
@ -148,7 +155,7 @@ def test_radius_0_5():
)
def test_radius_1():
def test_radius_1() -> None:
assert_blur(
sample,
1,
@ -165,7 +172,7 @@ def test_radius_1():
)
def test_radius_1_5():
def test_radius_1_5() -> None:
assert_blur(
sample,
1.5,
@ -182,7 +189,7 @@ def test_radius_1_5():
)
def test_radius_bigger_then_half():
def test_radius_bigger_then_half() -> None:
assert_blur(
sample,
3,
@ -199,7 +206,7 @@ def test_radius_bigger_then_half():
)
def test_radius_bigger_then_width():
def test_radius_bigger_then_width() -> None:
assert_blur(
sample,
10,
@ -214,7 +221,7 @@ def test_radius_bigger_then_width():
)
def test_extreme_large_radius():
def test_extreme_large_radius() -> None:
assert_blur(
sample,
600,
@ -229,7 +236,7 @@ def test_extreme_large_radius():
)
def test_two_passes():
def test_two_passes() -> None:
assert_blur(
sample,
1,
@ -247,7 +254,7 @@ def test_two_passes():
)
def test_three_passes():
def test_three_passes() -> None:
assert_blur(
sample,
1,

View File

@ -1,5 +1,7 @@
from __future__ import annotations
from array import array
from types import ModuleType
import pytest
@ -7,6 +9,7 @@ from PIL import Image, ImageFilter
from .helper import assert_image_equal
numpy: ModuleType | None
try:
import numpy
except ImportError:
@ -14,7 +17,9 @@ except ImportError:
class TestColorLut3DCoreAPI:
def generate_identity_table(self, channels, size):
def generate_identity_table(
self, channels: int, size: int | tuple[int, int, int]
) -> tuple[int, int, int, int, list[float]]:
if isinstance(size, tuple):
size_1d, size_2d, size_3d = size
else:
@ -40,7 +45,7 @@ class TestColorLut3DCoreAPI:
[item for sublist in table for item in sublist],
)
def test_wrong_args(self):
def test_wrong_args(self) -> None:
im = Image.new("RGB", (10, 10), 0)
with pytest.raises(ValueError, match="filter"):
@ -100,7 +105,7 @@ class TestColorLut3DCoreAPI:
with pytest.raises(TypeError):
im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, 16)
def test_correct_args(self):
def test_correct_args(self) -> None:
im = Image.new("RGB", (10, 10), 0)
im.im.color_lut_3d(
@ -135,7 +140,7 @@ class TestColorLut3DCoreAPI:
*self.generate_identity_table(3, (3, 3, 65)),
)
def test_wrong_mode(self):
def test_wrong_mode(self) -> None:
with pytest.raises(ValueError, match="wrong mode"):
im = Image.new("L", (10, 10), 0)
im.im.color_lut_3d(
@ -166,7 +171,7 @@ class TestColorLut3DCoreAPI:
"RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3)
)
def test_correct_mode(self):
def test_correct_mode(self) -> None:
im = Image.new("RGBA", (10, 10), 0)
im.im.color_lut_3d(
"RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3)
@ -187,7 +192,7 @@ class TestColorLut3DCoreAPI:
"RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3)
)
def test_identities(self):
def test_identities(self) -> None:
g = Image.linear_gradient("L")
im = Image.merge(
"RGB",
@ -223,7 +228,7 @@ class TestColorLut3DCoreAPI:
),
)
def test_identities_4_channels(self):
def test_identities_4_channels(self) -> None:
g = Image.linear_gradient("L")
im = Image.merge(
"RGB",
@ -246,7 +251,7 @@ class TestColorLut3DCoreAPI:
),
)
def test_copy_alpha_channel(self):
def test_copy_alpha_channel(self) -> None:
g = Image.linear_gradient("L")
im = Image.merge(
"RGBA",
@ -269,7 +274,7 @@ class TestColorLut3DCoreAPI:
),
)
def test_channels_order(self):
def test_channels_order(self) -> None:
g = Image.linear_gradient("L")
im = Image.merge(
"RGB",
@ -294,7 +299,7 @@ class TestColorLut3DCoreAPI:
])))
# fmt: on
def test_overflow(self):
def test_overflow(self) -> None:
g = Image.linear_gradient("L")
im = Image.merge(
"RGB",
@ -347,7 +352,7 @@ class TestColorLut3DCoreAPI:
class TestColorLut3DFilter:
def test_wrong_args(self):
def test_wrong_args(self) -> None:
with pytest.raises(ValueError, match="should be either an integer"):
ImageFilter.Color3DLUT("small", [1])
@ -375,7 +380,7 @@ class TestColorLut3DFilter:
with pytest.raises(ValueError, match="Only 3 or 4 output"):
ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8, channels=2)
def test_convert_table(self):
def test_convert_table(self) -> None:
lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8)
assert tuple(lut.size) == (2, 2, 2)
assert lut.name == "Color 3D LUT"
@ -393,7 +398,8 @@ class TestColorLut3DFilter:
assert lut.table == list(range(4)) * 8
@pytest.mark.skipif(numpy is None, reason="NumPy not installed")
def test_numpy_sources(self):
def test_numpy_sources(self) -> None:
assert numpy is not None
table = numpy.ones((5, 6, 7, 3), dtype=numpy.float16)
with pytest.raises(ValueError, match="should have either channels"):
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
@ -426,7 +432,8 @@ class TestColorLut3DFilter:
assert lut.table[0] == 33
@pytest.mark.skipif(numpy is None, reason="NumPy not installed")
def test_numpy_formats(self):
def test_numpy_formats(self) -> None:
assert numpy is not None
g = Image.linear_gradient("L")
im = Image.merge(
"RGB",
@ -465,7 +472,7 @@ class TestColorLut3DFilter:
lut.table = numpy.array(lut.table, dtype=numpy.int8)
im.filter(lut)
def test_repr(self):
def test_repr(self) -> None:
lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8)
assert repr(lut) == "<Color3DLUT from list size=2x2x2 channels=3>"
@ -483,7 +490,7 @@ class TestColorLut3DFilter:
class TestGenerateColorLut3D:
def test_wrong_channels_count(self):
def test_wrong_channels_count(self) -> None:
with pytest.raises(ValueError, match="3 or 4 output channels"):
ImageFilter.Color3DLUT.generate(
5, channels=2, callback=lambda r, g, b: (r, g, b)
@ -497,7 +504,7 @@ class TestGenerateColorLut3D:
5, channels=4, callback=lambda r, g, b: (r, g, b)
)
def test_3_channels(self):
def test_3_channels(self) -> None:
lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
assert tuple(lut.size) == (5, 5, 5)
assert lut.name == "Color 3D LUT"
@ -507,7 +514,7 @@ class TestGenerateColorLut3D:
1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0]
# fmt: on
def test_4_channels(self):
def test_4_channels(self) -> None:
lut = ImageFilter.Color3DLUT.generate(
5, channels=4, callback=lambda r, g, b: (b, r, g, (r + g + b) / 2)
)
@ -520,7 +527,7 @@ class TestGenerateColorLut3D:
]
# fmt: on
def test_apply(self):
def test_apply(self) -> None:
lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
g = Image.linear_gradient("L")
@ -536,7 +543,7 @@ class TestGenerateColorLut3D:
class TestTransformColorLut3D:
def test_wrong_args(self):
def test_wrong_args(self) -> None:
source = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
with pytest.raises(ValueError, match="Only 3 or 4 output"):
@ -551,7 +558,7 @@ class TestTransformColorLut3D:
with pytest.raises(TypeError):
source.transform(lambda r, g, b, a: (r, g, b))
def test_target_mode(self):
def test_target_mode(self) -> None:
source = ImageFilter.Color3DLUT.generate(
2, lambda r, g, b: (r, g, b), target_mode="HSV"
)
@ -562,7 +569,7 @@ class TestTransformColorLut3D:
lut = source.transform(lambda r, g, b: (r, g, b), target_mode="RGB")
assert lut.mode == "RGB"
def test_3_to_3_channels(self):
def test_3_to_3_channels(self) -> None:
source = ImageFilter.Color3DLUT.generate((3, 4, 5), lambda r, g, b: (r, g, b))
lut = source.transform(lambda r, g, b: (r * r, g * g, b * b))
assert tuple(lut.size) == tuple(source.size)
@ -570,7 +577,7 @@ class TestTransformColorLut3D:
assert lut.table != source.table
assert lut.table[:10] == [0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]
def test_3_to_4_channels(self):
def test_3_to_4_channels(self) -> None:
source = ImageFilter.Color3DLUT.generate((6, 5, 4), lambda r, g, b: (r, g, b))
lut = source.transform(lambda r, g, b: (r * r, g * g, b * b, 1), channels=4)
assert tuple(lut.size) == tuple(source.size)
@ -582,7 +589,7 @@ class TestTransformColorLut3D:
0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1]
# fmt: on
def test_4_to_3_channels(self):
def test_4_to_3_channels(self) -> None:
source = ImageFilter.Color3DLUT.generate(
(3, 6, 5), lambda r, g, b: (r, g, b, 1), channels=4
)
@ -598,7 +605,7 @@ class TestTransformColorLut3D:
1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0]
# fmt: on
def test_4_to_4_channels(self):
def test_4_to_4_channels(self) -> None:
source = ImageFilter.Color3DLUT.generate(
(6, 5, 4), lambda r, g, b: (r, g, b, 1), channels=4
)
@ -612,7 +619,7 @@ class TestTransformColorLut3D:
0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5]
# fmt: on
def test_with_normals_3_channels(self):
def test_with_normals_3_channels(self) -> None:
source = ImageFilter.Color3DLUT.generate(
(6, 5, 4), lambda r, g, b: (r * r, g * g, b * b)
)
@ -628,7 +635,7 @@ class TestTransformColorLut3D:
0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0]
# fmt: on
def test_with_normals_4_channels(self):
def test_with_normals_4_channels(self) -> None:
source = ImageFilter.Color3DLUT.generate(
(3, 6, 5), lambda r, g, b: (r * r, g * g, b * b, 1), channels=4
)

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import sys
import pytest
@ -8,7 +9,7 @@ from PIL import Image
from .helper import is_pypy
def test_get_stats():
def test_get_stats() -> None:
# Create at least one image
Image.new("RGB", (10, 10))
@ -21,7 +22,7 @@ def test_get_stats():
assert "blocks_cached" in stats
def test_reset_stats():
def test_reset_stats() -> None:
Image.core.reset_stats()
stats = Image.core.get_stats()
@ -34,19 +35,19 @@ def test_reset_stats():
class TestCoreMemory:
def teardown_method(self):
def teardown_method(self) -> None:
# Restore default values
Image.core.set_alignment(1)
Image.core.set_block_size(1024 * 1024)
Image.core.set_blocks_max(0)
Image.core.clear_cache()
def test_get_alignment(self):
def test_get_alignment(self) -> None:
alignment = Image.core.get_alignment()
assert alignment > 0
def test_set_alignment(self):
def test_set_alignment(self) -> None:
for i in [1, 2, 4, 8, 16, 32]:
Image.core.set_alignment(i)
alignment = Image.core.get_alignment()
@ -62,12 +63,12 @@ class TestCoreMemory:
with pytest.raises(ValueError):
Image.core.set_alignment(3)
def test_get_block_size(self):
def test_get_block_size(self) -> None:
block_size = Image.core.get_block_size()
assert block_size >= 4096
def test_set_block_size(self):
def test_set_block_size(self) -> None:
for i in [4096, 2 * 4096, 3 * 4096]:
Image.core.set_block_size(i)
block_size = Image.core.get_block_size()
@ -83,7 +84,7 @@ class TestCoreMemory:
with pytest.raises(ValueError):
Image.core.set_block_size(4000)
def test_set_block_size_stats(self):
def test_set_block_size_stats(self) -> None:
Image.core.reset_stats()
Image.core.set_blocks_max(0)
Image.core.set_block_size(4096)
@ -95,12 +96,12 @@ class TestCoreMemory:
if not is_pypy():
assert stats["freed_blocks"] >= 64
def test_get_blocks_max(self):
def test_get_blocks_max(self) -> None:
blocks_max = Image.core.get_blocks_max()
assert blocks_max >= 0
def test_set_blocks_max(self):
def test_set_blocks_max(self) -> None:
for i in [0, 1, 10]:
Image.core.set_blocks_max(i)
blocks_max = Image.core.get_blocks_max()
@ -116,7 +117,7 @@ class TestCoreMemory:
Image.core.set_blocks_max(2**29)
@pytest.mark.skipif(is_pypy(), reason="Images not collected")
def test_set_blocks_max_stats(self):
def test_set_blocks_max_stats(self) -> None:
Image.core.reset_stats()
Image.core.set_blocks_max(128)
Image.core.set_block_size(4096)
@ -131,7 +132,7 @@ class TestCoreMemory:
assert stats["blocks_cached"] == 64
@pytest.mark.skipif(is_pypy(), reason="Images not collected")
def test_clear_cache_stats(self):
def test_clear_cache_stats(self) -> None:
Image.core.reset_stats()
Image.core.clear_cache()
Image.core.set_blocks_max(128)
@ -148,7 +149,7 @@ class TestCoreMemory:
assert stats["freed_blocks"] >= 48
assert stats["blocks_cached"] == 16
def test_large_images(self):
def test_large_images(self) -> None:
Image.core.reset_stats()
Image.core.set_blocks_max(0)
Image.core.set_block_size(4096)
@ -165,14 +166,14 @@ class TestCoreMemory:
class TestEnvVars:
def teardown_method(self):
def teardown_method(self) -> None:
# Restore default values
Image.core.set_alignment(1)
Image.core.set_block_size(1024 * 1024)
Image.core.set_blocks_max(0)
Image.core.clear_cache()
def test_units(self):
def test_units(self) -> None:
Image._apply_env_variables({"PILLOW_BLOCKS_MAX": "2K"})
assert Image.core.get_blocks_max() == 2 * 1024
Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"})
@ -186,6 +187,6 @@ class TestEnvVars:
{"PILLOW_BLOCKS_MAX": "wat"},
),
)
def test_warnings(self, var):
def test_warnings(self, var: dict[str, str]) -> None:
with pytest.warns(UserWarning):
Image._apply_env_variables(var)

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import Image
@ -11,16 +12,16 @@ ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS
class TestDecompressionBomb:
def teardown_method(self, method):
def teardown_method(self, method) -> None:
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
def test_no_warning_small_file(self):
def test_no_warning_small_file(self) -> None:
# Implicit assert: no warning.
# A warning would cause a failure.
with Image.open(TEST_FILE):
pass
def test_no_warning_no_limit(self):
def test_no_warning_no_limit(self) -> None:
# Arrange
# Turn limit off
Image.MAX_IMAGE_PIXELS = None
@ -32,7 +33,7 @@ class TestDecompressionBomb:
with Image.open(TEST_FILE):
pass
def test_warning(self):
def test_warning(self) -> None:
# Set limit to trigger warning on the test file
Image.MAX_IMAGE_PIXELS = 128 * 128 - 1
assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1
@ -41,7 +42,7 @@ class TestDecompressionBomb:
with Image.open(TEST_FILE):
pass
def test_exception(self):
def test_exception(self) -> None:
# Set limit to trigger exception on the test file
Image.MAX_IMAGE_PIXELS = 64 * 128 - 1
assert Image.MAX_IMAGE_PIXELS == 64 * 128 - 1
@ -50,22 +51,22 @@ class TestDecompressionBomb:
with Image.open(TEST_FILE):
pass
def test_exception_ico(self):
def test_exception_ico(self) -> None:
with pytest.raises(Image.DecompressionBombError):
with Image.open("Tests/images/decompression_bomb.ico"):
pass
def test_exception_gif(self):
def test_exception_gif(self) -> None:
with pytest.raises(Image.DecompressionBombError):
with Image.open("Tests/images/decompression_bomb.gif"):
pass
def test_exception_gif_extents(self):
def test_exception_gif_extents(self) -> None:
with Image.open("Tests/images/decompression_bomb_extents.gif") as im:
with pytest.raises(Image.DecompressionBombError):
im.seek(1)
def test_exception_gif_zero_width(self):
def test_exception_gif_zero_width(self) -> None:
# Set limit to trigger exception on the test file
Image.MAX_IMAGE_PIXELS = 4 * 64 * 128
assert Image.MAX_IMAGE_PIXELS == 4 * 64 * 128
@ -74,7 +75,7 @@ class TestDecompressionBomb:
with Image.open("Tests/images/zero_width.gif"):
pass
def test_exception_bmp(self):
def test_exception_bmp(self) -> None:
with pytest.raises(Image.DecompressionBombError):
with Image.open("Tests/images/bmp/b/reallybig.bmp"):
pass
@ -82,15 +83,15 @@ class TestDecompressionBomb:
class TestDecompressionCrop:
@classmethod
def setup_class(cls):
def setup_class(cls) -> None:
width, height = 128, 128
Image.MAX_IMAGE_PIXELS = height * width * 4 - 1
@classmethod
def teardown_class(cls):
def teardown_class(cls) -> None:
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
def test_enlarge_crop(self):
def test_enlarge_crop(self) -> None:
# Crops can extend the extents, therefore we should have the
# same decompression bomb warnings on them.
with hopper() as src:
@ -98,7 +99,7 @@ class TestDecompressionCrop:
with pytest.warns(Image.DecompressionBombWarning):
src.crop(box)
def test_crop_decompression_checks(self):
def test_crop_decompression_checks(self) -> None:
im = Image.new("RGB", (100, 100))
for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)):

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import _deprecate
@ -19,12 +20,12 @@ from PIL import _deprecate
),
],
)
def test_version(version, expected):
def test_version(version, expected) -> None:
with pytest.warns(DeprecationWarning, match=expected):
_deprecate.deprecate("Old thing", version, "new thing")
def test_unknown_version():
def test_unknown_version() -> None:
expected = r"Unknown removal version: 12345. Update PIL\._deprecate\?"
with pytest.raises(ValueError, match=expected):
_deprecate.deprecate("Old thing", 12345, "new thing")
@ -45,13 +46,13 @@ def test_unknown_version():
),
],
)
def test_old_version(deprecated, plural, expected):
def test_old_version(deprecated, plural, expected) -> None:
expected = r""
with pytest.raises(RuntimeError, match=expected):
_deprecate.deprecate(deprecated, 1, plural=plural)
def test_plural():
def test_plural() -> None:
expected = (
r"Old things are deprecated and will be removed in Pillow 11 \(2024-10-15\)\. "
r"Use new thing instead\."
@ -60,7 +61,7 @@ def test_plural():
_deprecate.deprecate("Old things", 11, "new thing", plural=True)
def test_replacement_and_action():
def test_replacement_and_action() -> None:
expected = "Use only one of 'replacement' and 'action'"
with pytest.raises(ValueError, match=expected):
_deprecate.deprecate(
@ -75,7 +76,7 @@ def test_replacement_and_action():
"Upgrade to new thing.",
],
)
def test_action(action):
def test_action(action) -> None:
expected = (
r"Old thing is deprecated and will be removed in Pillow 11 \(2024-10-15\)\. "
r"Upgrade to new thing\."
@ -84,7 +85,7 @@ def test_action(action):
_deprecate.deprecate("Old thing", 11, action=action)
def test_no_replacement_or_action():
def test_no_replacement_or_action() -> None:
expected = (
r"Old thing is deprecated and will be removed in Pillow 11 \(2024-10-15\)"
)

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import io
import re
@ -14,7 +15,7 @@ except ImportError:
pass
def test_check():
def test_check() -> None:
# Check the correctness of the convenience function
for module in features.modules:
assert features.check_module(module) == features.check(module)
@ -24,11 +25,11 @@ def test_check():
assert features.check_feature(feature) == features.check(feature)
def test_version():
def test_version() -> None:
# Check the correctness of the convenience function
# and the format of version numbers
def test(name, function):
def test(name, function) -> None:
version = features.version(name)
if not features.check(name):
assert version is None
@ -46,56 +47,56 @@ def test_version():
@skip_unless_feature("webp")
def test_webp_transparency():
def test_webp_transparency() -> None:
assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha()
assert features.check("transp_webp") == _webp.HAVE_TRANSPARENCY
@skip_unless_feature("webp")
def test_webp_mux():
def test_webp_mux() -> None:
assert features.check("webp_mux") == _webp.HAVE_WEBPMUX
@skip_unless_feature("webp")
def test_webp_anim():
def test_webp_anim() -> None:
assert features.check("webp_anim") == _webp.HAVE_WEBPANIM
@skip_unless_feature("libjpeg_turbo")
def test_libjpeg_turbo_version():
def test_libjpeg_turbo_version() -> None:
assert re.search(r"\d+\.\d+\.\d+$", features.version("libjpeg_turbo"))
@skip_unless_feature("libimagequant")
def test_libimagequant_version():
def test_libimagequant_version() -> None:
assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant"))
@pytest.mark.parametrize("feature", features.modules)
def test_check_modules(feature):
def test_check_modules(feature) -> None:
assert features.check_module(feature) in [True, False]
@pytest.mark.parametrize("feature", features.codecs)
def test_check_codecs(feature):
def test_check_codecs(feature) -> None:
assert features.check_codec(feature) in [True, False]
def test_check_warns_on_nonexistent():
def test_check_warns_on_nonexistent() -> None:
with pytest.warns(UserWarning) as cm:
has_feature = features.check("typo")
assert has_feature is False
assert str(cm[-1].message) == "Unknown feature 'typo'."
def test_supported_modules():
def test_supported_modules() -> None:
assert isinstance(features.get_supported_modules(), list)
assert isinstance(features.get_supported_codecs(), list)
assert isinstance(features.get_supported_features(), list)
assert isinstance(features.get_supported(), list)
def test_unsupported_codec():
def test_unsupported_codec() -> None:
# Arrange
codec = "unsupported_codec"
# Act / Assert
@ -105,7 +106,7 @@ def test_unsupported_codec():
features.version_codec(codec)
def test_unsupported_module():
def test_unsupported_module() -> None:
# Arrange
module = "unsupported_module"
# Act / Assert
@ -115,7 +116,7 @@ def test_unsupported_module():
features.version_module(module)
def test_pilinfo():
def test_pilinfo() -> None:
buf = io.StringIO()
features.pilinfo(buf)
out = buf.getvalue()

View File

@ -1,4 +1,7 @@
from __future__ import annotations
from pathlib import Path
import pytest
from PIL import Image, ImageSequence, PngImagePlugin
@ -7,7 +10,7 @@ from PIL import Image, ImageSequence, PngImagePlugin
# APNG browser support tests and fixtures via:
# https://philip.html5.org/tests/apng/tests.html
# (referenced from https://wiki.mozilla.org/APNG_Specification)
def test_apng_basic():
def test_apng_basic() -> None:
with Image.open("Tests/images/apng/single_frame.png") as im:
assert not im.is_animated
assert im.n_frames == 1
@ -44,14 +47,14 @@ def test_apng_basic():
"filename",
("Tests/images/apng/split_fdat.png", "Tests/images/apng/split_fdat_zero_chunk.png"),
)
def test_apng_fdat(filename):
def test_apng_fdat(filename: str) -> None:
with Image.open(filename) as im:
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_dispose():
def test_apng_dispose() -> None:
with Image.open("Tests/images/apng/dispose_op_none.png") as im:
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
@ -83,7 +86,7 @@ def test_apng_dispose():
assert im.getpixel((64, 32)) == (0, 0, 0, 0)
def test_apng_dispose_region():
def test_apng_dispose_region() -> None:
with Image.open("Tests/images/apng/dispose_op_none_region.png") as im:
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
@ -105,7 +108,7 @@ def test_apng_dispose_region():
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_dispose_op_previous_frame():
def test_apng_dispose_op_previous_frame() -> None:
# Test that the dispose settings being used are from the previous frame
#
# Image created with:
@ -130,14 +133,14 @@ def test_apng_dispose_op_previous_frame():
assert im.getpixel((0, 0)) == (255, 0, 0, 255)
def test_apng_dispose_op_background_p_mode():
def test_apng_dispose_op_background_p_mode() -> None:
with Image.open("Tests/images/apng/dispose_op_background_p_mode.png") as im:
im.seek(1)
im.load()
assert im.size == (128, 64)
def test_apng_blend():
def test_apng_blend() -> None:
with Image.open("Tests/images/apng/blend_op_source_solid.png") as im:
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
@ -164,20 +167,20 @@ def test_apng_blend():
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_blend_transparency():
def test_apng_blend_transparency() -> None:
with Image.open("Tests/images/blend_transparency.png") as im:
im.seek(1)
assert im.getpixel((0, 0)) == (255, 0, 0)
def test_apng_chunk_order():
def test_apng_chunk_order() -> None:
with Image.open("Tests/images/apng/fctl_actl.png") as im:
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_delay():
def test_apng_delay() -> None:
with Image.open("Tests/images/apng/delay.png") as im:
im.seek(1)
assert im.info.get("duration") == 500.0
@ -217,7 +220,7 @@ def test_apng_delay():
assert im.info.get("duration") == 1000.0
def test_apng_num_plays():
def test_apng_num_plays() -> None:
with Image.open("Tests/images/apng/num_plays.png") as im:
assert im.info.get("loop") == 0
@ -225,7 +228,7 @@ def test_apng_num_plays():
assert im.info.get("loop") == 1
def test_apng_mode():
def test_apng_mode() -> None:
with Image.open("Tests/images/apng/mode_16bit.png") as im:
assert im.mode == "RGBA"
im.seek(im.n_frames - 1)
@ -266,7 +269,7 @@ def test_apng_mode():
assert im.getpixel((64, 32)) == (0, 0, 255, 128)
def test_apng_chunk_errors():
def test_apng_chunk_errors() -> None:
with Image.open("Tests/images/apng/chunk_no_actl.png") as im:
assert not im.is_animated
@ -291,7 +294,7 @@ def test_apng_chunk_errors():
im.seek(im.n_frames - 1)
def test_apng_syntax_errors():
def test_apng_syntax_errors() -> None:
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im:
assert not im.is_animated
@ -335,14 +338,14 @@ def test_apng_syntax_errors():
"sequence_fdat_fctl.png",
),
)
def test_apng_sequence_errors(test_file):
def test_apng_sequence_errors(test_file: str) -> None:
with pytest.raises(SyntaxError):
with Image.open(f"Tests/images/apng/{test_file}") as im:
im.seek(im.n_frames - 1)
im.load()
def test_apng_save(tmp_path):
def test_apng_save(tmp_path: Path) -> None:
with Image.open("Tests/images/apng/single_frame.png") as im:
test_file = str(tmp_path / "temp.png")
im.save(test_file, save_all=True)
@ -373,7 +376,7 @@ def test_apng_save(tmp_path):
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_save_alpha(tmp_path):
def test_apng_save_alpha(tmp_path: Path) -> None:
test_file = str(tmp_path / "temp.png")
im = Image.new("RGBA", (1, 1), (255, 0, 0, 255))
@ -387,7 +390,7 @@ def test_apng_save_alpha(tmp_path):
assert reloaded.getpixel((0, 0)) == (255, 0, 0, 127)
def test_apng_save_split_fdat(tmp_path):
def test_apng_save_split_fdat(tmp_path: Path) -> None:
# test to make sure we do not generate sequence errors when writing
# frames with image data spanning multiple fdAT chunks (in this case
# both the default image and first animation frame will span multiple
@ -411,7 +414,7 @@ def test_apng_save_split_fdat(tmp_path):
assert exception is None
def test_apng_save_duration_loop(tmp_path):
def test_apng_save_duration_loop(tmp_path: Path) -> None:
test_file = str(tmp_path / "temp.png")
with Image.open("Tests/images/apng/delay.png") as im:
frames = []
@ -474,7 +477,7 @@ def test_apng_save_duration_loop(tmp_path):
assert im.info["duration"] == 600
def test_apng_save_disposal(tmp_path):
def test_apng_save_disposal(tmp_path: Path) -> None:
test_file = str(tmp_path / "temp.png")
size = (128, 64)
red = Image.new("RGBA", size, (255, 0, 0, 255))
@ -575,7 +578,7 @@ def test_apng_save_disposal(tmp_path):
assert im.getpixel((64, 32)) == (0, 0, 0, 0)
def test_apng_save_disposal_previous(tmp_path):
def test_apng_save_disposal_previous(tmp_path: Path) -> None:
test_file = str(tmp_path / "temp.png")
size = (128, 64)
blue = Image.new("RGBA", size, (0, 0, 255, 255))
@ -597,7 +600,7 @@ def test_apng_save_disposal_previous(tmp_path):
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_save_blend(tmp_path):
def test_apng_save_blend(tmp_path: Path) -> None:
test_file = str(tmp_path / "temp.png")
size = (128, 64)
red = Image.new("RGBA", size, (255, 0, 0, 255))
@ -665,7 +668,7 @@ def test_apng_save_blend(tmp_path):
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
def test_seek_after_close():
def test_seek_after_close() -> None:
im = Image.open("Tests/images/apng/delay.png")
im.seek(1)
im.close()
@ -677,7 +680,9 @@ def test_seek_after_close():
@pytest.mark.parametrize("mode", ("RGBA", "RGB", "P"))
@pytest.mark.parametrize("default_image", (True, False))
@pytest.mark.parametrize("duplicate", (True, False))
def test_different_modes_in_later_frames(mode, default_image, duplicate, tmp_path):
def test_different_modes_in_later_frames(
mode: str, default_image: bool, duplicate: bool, tmp_path: Path
) -> None:
test_file = str(tmp_path / "temp.png")
im = Image.new("L", (1, 1))
@ -689,3 +694,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

View File

@ -1,4 +1,7 @@
from __future__ import annotations
from pathlib import Path
import pytest
from PIL import Image
@ -11,7 +14,7 @@ from .helper import (
)
def test_load_blp1():
def test_load_blp1() -> None:
with Image.open("Tests/images/blp/blp1_jpeg.blp") as im:
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
@ -19,22 +22,22 @@ def test_load_blp1():
im.load()
def test_load_blp2_raw():
def test_load_blp2_raw() -> None:
with Image.open("Tests/images/blp/blp2_raw.blp") as im:
assert_image_equal_tofile(im, "Tests/images/blp/blp2_raw.png")
def test_load_blp2_dxt1():
def test_load_blp2_dxt1() -> None:
with Image.open("Tests/images/blp/blp2_dxt1.blp") as im:
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1.png")
def test_load_blp2_dxt1a():
def test_load_blp2_dxt1a() -> None:
with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im:
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
def test_save(tmp_path):
def test_save(tmp_path: Path) -> None:
f = str(tmp_path / "temp.blp")
for version in ("BLP1", "BLP2"):
@ -68,7 +71,7 @@ def test_save(tmp_path):
"Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp",
],
)
def test_crashes(test_file):
def test_crashes(test_file) -> None:
with open(test_file, "rb") as f:
with Image.open(f) as im:
with pytest.raises(OSError):

View File

@ -1,5 +1,7 @@
from __future__ import annotations
import io
from pathlib import Path
import pytest
@ -13,8 +15,8 @@ from .helper import (
)
def test_sanity(tmp_path):
def roundtrip(im):
def test_sanity(tmp_path: Path) -> None:
def roundtrip(im) -> None:
outfile = str(tmp_path / "temp.bmp")
im.save(outfile, "BMP")
@ -34,20 +36,20 @@ def test_sanity(tmp_path):
roundtrip(hopper("RGB"))
def test_invalid_file():
def test_invalid_file() -> None:
with open("Tests/images/flower.jpg", "rb") as fp:
with pytest.raises(SyntaxError):
BmpImagePlugin.BmpImageFile(fp)
def test_fallback_if_mmap_errors():
def test_fallback_if_mmap_errors() -> None:
# This image has been truncated,
# so that the buffer is not large enough when using mmap
with Image.open("Tests/images/mmap_error.bmp") as im:
assert_image_equal_tofile(im, "Tests/images/pal8_offset.bmp")
def test_save_to_bytes():
def test_save_to_bytes() -> None:
output = io.BytesIO()
im = hopper()
im.save(output, "BMP")
@ -59,7 +61,7 @@ def test_save_to_bytes():
assert reloaded.format == "BMP"
def test_small_palette(tmp_path):
def test_small_palette(tmp_path: Path) -> None:
im = Image.new("P", (1, 1))
colors = [0, 0, 0, 125, 125, 125, 255, 255, 255]
im.putpalette(colors)
@ -71,7 +73,7 @@ def test_small_palette(tmp_path):
assert reloaded.getpalette() == colors
def test_save_too_large(tmp_path):
def test_save_too_large(tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.bmp")
with Image.new("RGB", (1, 1)) as im:
im._size = (37838, 37838)
@ -79,7 +81,7 @@ def test_save_too_large(tmp_path):
im.save(outfile)
def test_dpi():
def test_dpi() -> None:
dpi = (72, 72)
output = io.BytesIO()
@ -91,7 +93,7 @@ def test_dpi():
assert reloaded.info["dpi"] == (72.008961115161, 72.008961115161)
def test_save_bmp_with_dpi(tmp_path):
def test_save_bmp_with_dpi(tmp_path: Path) -> None:
# Test for #1301
# Arrange
outfile = str(tmp_path / "temp.jpg")
@ -109,7 +111,7 @@ def test_save_bmp_with_dpi(tmp_path):
assert reloaded.format == "JPEG"
def test_save_float_dpi(tmp_path):
def test_save_float_dpi(tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.bmp")
with Image.open("Tests/images/hopper.bmp") as im:
im.save(outfile, dpi=(72.21216100543306, 72.21216100543306))
@ -117,7 +119,7 @@ def test_save_float_dpi(tmp_path):
assert reloaded.info["dpi"] == (72.21216100543306, 72.21216100543306)
def test_load_dib():
def test_load_dib() -> None:
# test for #1293, Imagegrab returning Unsupported Bitfields Format
with Image.open("Tests/images/clipboard.dib") as im:
assert im.format == "DIB"
@ -126,7 +128,7 @@ def test_load_dib():
assert_image_equal_tofile(im, "Tests/images/clipboard_target.png")
def test_save_dib(tmp_path):
def test_save_dib(tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.dib")
with Image.open("Tests/images/clipboard.dib") as im:
@ -138,7 +140,7 @@ def test_save_dib(tmp_path):
assert_image_equal(im, reloaded)
def test_rgba_bitfields():
def test_rgba_bitfields() -> None:
# This test image has been manually hexedited
# to change the bitfield compression in the header from XBGR to RGBA
with Image.open("Tests/images/rgb32bf-rgba.bmp") as im:
@ -156,7 +158,7 @@ def test_rgba_bitfields():
)
def test_rle8():
def test_rle8() -> None:
with Image.open("Tests/images/hopper_rle8.bmp") as im:
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
@ -176,7 +178,7 @@ def test_rle8():
im.load()
def test_rle4():
def test_rle4() -> None:
with Image.open("Tests/images/bmp/g/pal4rle.bmp") as im:
assert_image_similar_tofile(im, "Tests/images/bmp/g/pal4.bmp", 12)
@ -192,7 +194,7 @@ def test_rle4():
("Tests/images/bmp/g/pal8rle.bmp", 1064),
),
)
def test_rle8_eof(file_name, length):
def test_rle8_eof(file_name, length) -> None:
with open(file_name, "rb") as fp:
data = fp.read(length)
with Image.open(io.BytesIO(data)) as im:
@ -200,7 +202,7 @@ def test_rle8_eof(file_name, length):
im.load()
def test_offset():
def test_offset() -> None:
# This image has been hexedited
# to exclude the palette size from the pixel data offset
with Image.open("Tests/images/pal8_offset.bmp") as im:

View File

@ -1,4 +1,7 @@
from __future__ import annotations
from pathlib import Path
import pytest
from PIL import BufrStubImagePlugin, Image
@ -8,7 +11,7 @@ from .helper import hopper
TEST_FILE = "Tests/images/gfs.t06z.rassda.tm00.bufr_d"
def test_open():
def test_open() -> None:
# Act
with Image.open(TEST_FILE) as im:
# Assert
@ -19,7 +22,7 @@ def test_open():
assert im.size == (1, 1)
def test_invalid_file():
def test_invalid_file() -> None:
# Arrange
invalid_file = "Tests/images/flower.jpg"
@ -28,7 +31,7 @@ def test_invalid_file():
BufrStubImagePlugin.BufrStubImageFile(invalid_file)
def test_load():
def test_load() -> None:
# Arrange
with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler
@ -36,7 +39,7 @@ def test_load():
im.load()
def test_save(tmp_path):
def test_save(tmp_path: Path) -> None:
# Arrange
im = hopper()
tmpfile = str(tmp_path / "temp.bufr")
@ -46,13 +49,13 @@ def test_save(tmp_path):
im.save(tmpfile)
def test_handler(tmp_path):
def test_handler(tmp_path: Path) -> None:
class TestHandler:
opened = False
loaded = False
saved = False
def open(self, im):
def open(self, im) -> None:
self.opened = True
def load(self, im):
@ -60,7 +63,7 @@ def test_handler(tmp_path):
im.fp.close()
return Image.new("RGB", (1, 1))
def save(self, im, fp, filename):
def save(self, im, fp, filename) -> None:
self.saved = True
handler = TestHandler()

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import ContainerIO, Image
@ -8,19 +9,19 @@ from .helper import hopper
TEST_FILE = "Tests/images/dummy.container"
def test_sanity():
def test_sanity() -> None:
dir(Image)
dir(ContainerIO)
def test_isatty():
def test_isatty() -> None:
with hopper() as im:
container = ContainerIO.ContainerIO(im, 0, 0)
assert container.isatty() is False
def test_seek_mode_0():
def test_seek_mode_0() -> None:
# Arrange
mode = 0
with open(TEST_FILE, "rb") as fh:
@ -34,7 +35,7 @@ def test_seek_mode_0():
assert container.tell() == 33
def test_seek_mode_1():
def test_seek_mode_1() -> None:
# Arrange
mode = 1
with open(TEST_FILE, "rb") as fh:
@ -48,7 +49,7 @@ def test_seek_mode_1():
assert container.tell() == 66
def test_seek_mode_2():
def test_seek_mode_2() -> None:
# Arrange
mode = 2
with open(TEST_FILE, "rb") as fh:
@ -63,7 +64,7 @@ def test_seek_mode_2():
@pytest.mark.parametrize("bytesmode", (True, False))
def test_read_n0(bytesmode):
def test_read_n0(bytesmode: bool) -> None:
# Arrange
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)
@ -79,7 +80,7 @@ def test_read_n0(bytesmode):
@pytest.mark.parametrize("bytesmode", (True, False))
def test_read_n(bytesmode):
def test_read_n(bytesmode: bool) -> None:
# Arrange
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)
@ -95,7 +96,7 @@ def test_read_n(bytesmode):
@pytest.mark.parametrize("bytesmode", (True, False))
def test_read_eof(bytesmode):
def test_read_eof(bytesmode: bool) -> None:
# Arrange
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)
@ -111,7 +112,7 @@ def test_read_eof(bytesmode):
@pytest.mark.parametrize("bytesmode", (True, False))
def test_readline(bytesmode):
def test_readline(bytesmode: bool) -> None:
# Arrange
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 0, 120)
@ -126,7 +127,7 @@ def test_readline(bytesmode):
@pytest.mark.parametrize("bytesmode", (True, False))
def test_readlines(bytesmode):
def test_readlines(bytesmode: bool) -> None:
# Arrange
expected = [
"This is line 1\n",

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import CurImagePlugin, Image
@ -6,7 +7,7 @@ from PIL import CurImagePlugin, Image
TEST_FILE = "Tests/images/deerstalker.cur"
def test_sanity():
def test_sanity() -> None:
with Image.open(TEST_FILE) as im:
assert im.size == (32, 32)
assert isinstance(im, CurImagePlugin.CurImageFile)
@ -16,7 +17,7 @@ def test_sanity():
assert im.getpixel((16, 16)) == (84, 87, 86, 255)
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import warnings
import pytest
@ -11,7 +12,7 @@ from .helper import assert_image_equal, hopper, is_pypy
TEST_FILE = "Tests/images/hopper.dcx"
def test_sanity():
def test_sanity() -> None:
# Arrange
# Act
@ -24,8 +25,8 @@ def test_sanity():
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file():
def open():
def test_unclosed_file() -> None:
def open() -> None:
im = Image.open(TEST_FILE)
im.load()
@ -33,26 +34,26 @@ def test_unclosed_file():
open()
def test_closed_file():
def test_closed_file() -> None:
with warnings.catch_warnings():
im = Image.open(TEST_FILE)
im.load()
im.close()
def test_context_manager():
def test_context_manager() -> None:
with warnings.catch_warnings():
with Image.open(TEST_FILE) as im:
im.load()
def test_invalid_file():
def test_invalid_file() -> None:
with open("Tests/images/flower.jpg", "rb") as fp:
with pytest.raises(SyntaxError):
DcxImagePlugin.DcxImageFile(fp)
def test_tell():
def test_tell() -> None:
# Arrange
with Image.open(TEST_FILE) as im:
# Act
@ -62,13 +63,13 @@ def test_tell():
assert frame == 0
def test_n_frames():
def test_n_frames() -> None:
with Image.open(TEST_FILE) as im:
assert im.n_frames == 1
assert not im.is_animated
def test_eoferror():
def test_eoferror() -> None:
with Image.open(TEST_FILE) as im:
n_frames = im.n_frames
@ -81,7 +82,7 @@ def test_eoferror():
im.seek(n_frames - 1)
def test_seek_too_far():
def test_seek_too_far() -> None:
# Arrange
with Image.open(TEST_FILE) as im:
frame = 999 # too big on purpose

View File

@ -1,6 +1,9 @@
"""Test DdsImagePlugin"""
from __future__ import annotations
from io import BytesIO
from pathlib import Path
import pytest
@ -45,7 +48,7 @@ TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
TEST_FILE_DX10_BC1_TYPELESS,
),
)
def test_sanity_dxt1_bc1(image_path):
def test_sanity_dxt1_bc1(image_path: str) -> None:
"""Check DXT1 and BC1 images can be opened"""
with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target:
target = target.convert("RGBA")
@ -59,7 +62,7 @@ def test_sanity_dxt1_bc1(image_path):
assert_image_equal(im, target)
def test_sanity_dxt3():
def test_sanity_dxt3() -> None:
"""Check DXT3 images can be opened"""
with Image.open(TEST_FILE_DXT3) as im:
@ -72,7 +75,7 @@ def test_sanity_dxt3():
assert_image_equal_tofile(im, TEST_FILE_DXT3.replace(".dds", ".png"))
def test_sanity_dxt5():
def test_sanity_dxt5() -> None:
"""Check DXT5 images can be opened"""
with Image.open(TEST_FILE_DXT5) as im:
@ -93,7 +96,7 @@ def test_sanity_dxt5():
TEST_FILE_BC4U,
),
)
def test_sanity_ati1_bc4u(image_path):
def test_sanity_ati1_bc4u(image_path: str) -> None:
"""Check ATI1 and BC4U images can be opened"""
with Image.open(image_path) as im:
@ -114,7 +117,7 @@ def test_sanity_ati1_bc4u(image_path):
TEST_FILE_DX10_BC4_TYPELESS,
),
)
def test_dx10_bc4(image_path):
def test_dx10_bc4(image_path: str) -> None:
"""Check DX10 BC4 images can be opened"""
with Image.open(image_path) as im:
@ -135,7 +138,7 @@ def test_dx10_bc4(image_path):
TEST_FILE_BC5U,
),
)
def test_sanity_ati2_bc5u(image_path):
def test_sanity_ati2_bc5u(image_path: str) -> None:
"""Check ATI2 and BC5U images can be opened"""
with Image.open(image_path) as im:
@ -159,7 +162,7 @@ def test_sanity_ati2_bc5u(image_path):
(TEST_FILE_BC5S, TEST_FILE_BC5S),
),
)
def test_dx10_bc5(image_path, expected_path):
def test_dx10_bc5(image_path: str, expected_path: str) -> None:
"""Check DX10 BC5 images can be opened"""
with Image.open(image_path) as im:
@ -173,7 +176,7 @@ def test_dx10_bc5(image_path, expected_path):
@pytest.mark.parametrize("image_path", (TEST_FILE_BC6H, TEST_FILE_BC6HS))
def test_dx10_bc6h(image_path):
def test_dx10_bc6h(image_path: str) -> None:
"""Check DX10 BC6H/BC6HS images can be opened"""
with Image.open(image_path) as im:
@ -186,7 +189,7 @@ def test_dx10_bc6h(image_path):
assert_image_equal_tofile(im, image_path.replace(".dds", ".png"))
def test_dx10_bc7():
def test_dx10_bc7() -> None:
"""Check DX10 images can be opened"""
with Image.open(TEST_FILE_DX10_BC7) as im:
@ -199,7 +202,7 @@ def test_dx10_bc7():
assert_image_equal_tofile(im, TEST_FILE_DX10_BC7.replace(".dds", ".png"))
def test_dx10_bc7_unorm_srgb():
def test_dx10_bc7_unorm_srgb() -> None:
"""Check DX10 unsigned normalized integer images can be opened"""
with Image.open(TEST_FILE_DX10_BC7_UNORM_SRGB) as im:
@ -215,7 +218,7 @@ def test_dx10_bc7_unorm_srgb():
)
def test_dx10_r8g8b8a8():
def test_dx10_r8g8b8a8() -> None:
"""Check DX10 images can be opened"""
with Image.open(TEST_FILE_DX10_R8G8B8A8) as im:
@ -228,7 +231,7 @@ def test_dx10_r8g8b8a8():
assert_image_equal_tofile(im, TEST_FILE_DX10_R8G8B8A8.replace(".dds", ".png"))
def test_dx10_r8g8b8a8_unorm_srgb():
def test_dx10_r8g8b8a8_unorm_srgb() -> None:
"""Check DX10 unsigned normalized integer images can be opened"""
with Image.open(TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB) as im:
@ -254,7 +257,7 @@ def test_dx10_r8g8b8a8_unorm_srgb():
("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA),
],
)
def test_uncompressed(mode, size, test_file):
def test_uncompressed(mode: str, size: tuple[int, int], test_file: str) -> None:
"""Check uncompressed images can be opened"""
with Image.open(test_file) as im:
@ -265,7 +268,7 @@ def test_uncompressed(mode, size, test_file):
assert_image_equal_tofile(im, test_file.replace(".dds", ".png"))
def test__accept_true():
def test__accept_true() -> None:
"""Check valid prefix"""
# Arrange
prefix = b"DDS etc"
@ -277,7 +280,7 @@ def test__accept_true():
assert output
def test__accept_false():
def test__accept_false() -> None:
"""Check invalid prefix"""
# Arrange
prefix = b"something invalid"
@ -289,19 +292,19 @@ def test__accept_false():
assert not output
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
DdsImagePlugin.DdsImageFile(invalid_file)
def test_short_header():
def test_short_header() -> None:
"""Check a short header"""
with open(TEST_FILE_DXT5, "rb") as f:
img_file = f.read()
def short_header():
def short_header() -> None:
with Image.open(BytesIO(img_file[:119])):
pass # pragma: no cover
@ -309,13 +312,13 @@ def test_short_header():
short_header()
def test_short_file():
def test_short_file() -> None:
"""Check that the appropriate error is thrown for a short file"""
with open(TEST_FILE_DXT5, "rb") as f:
img_file = f.read()
def short_file():
def short_file() -> None:
with Image.open(BytesIO(img_file[:-100])) as im:
im.load()
@ -323,7 +326,7 @@ def test_short_file():
short_file()
def test_dxt5_colorblock_alpha_issue_4142():
def test_dxt5_colorblock_alpha_issue_4142() -> None:
"""Check that colorblocks are decoded correctly in DXT5"""
with Image.open("Tests/images/dxt5-colorblock-alpha-issue-4142.dds") as im:
@ -338,12 +341,12 @@ def test_dxt5_colorblock_alpha_issue_4142():
assert px[2] != 0
def test_palette():
def test_palette() -> None:
with Image.open("Tests/images/palette.dds") as im:
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
def test_unsupported_bitcount():
def test_unsupported_bitcount() -> None:
with pytest.raises(OSError):
with Image.open("Tests/images/unsupported_bitcount.dds"):
pass
@ -356,13 +359,13 @@ def test_unsupported_bitcount():
"Tests/images/unimplemented_pfflags.dds",
),
)
def test_not_implemented(test_file):
def test_not_implemented(test_file: str) -> None:
with pytest.raises(NotImplementedError):
with Image.open(test_file):
pass
def test_save_unsupported_mode(tmp_path):
def test_save_unsupported_mode(tmp_path: Path) -> None:
out = str(tmp_path / "temp.dds")
im = hopper("HSV")
with pytest.raises(OSError):
@ -378,7 +381,7 @@ def test_save_unsupported_mode(tmp_path):
("RGBA", "Tests/images/pil123rgba.png"),
],
)
def test_save(mode, test_file, tmp_path):
def test_save(mode: str, test_file: str, tmp_path: Path) -> None:
out = str(tmp_path / "temp.dds")
with Image.open(test_file) as im:
assert im.mode == mode

View File

@ -1,5 +1,7 @@
from __future__ import annotations
import io
from pathlib import Path
import pytest
@ -82,7 +84,7 @@ simple_eps_file_with_long_binary_data = (
("filename", "size"), ((FILE1, (460, 352)), (FILE2, (360, 252)))
)
@pytest.mark.parametrize("scale", (1, 2))
def test_sanity(filename, size, scale):
def test_sanity(filename: str, size: tuple[int, int], scale: int) -> None:
expected_size = tuple(s * scale for s in size)
with Image.open(filename) as image:
image.load(scale=scale)
@ -92,7 +94,7 @@ def test_sanity(filename, size, scale):
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_load():
def test_load() -> None:
with Image.open(FILE1) as im:
assert im.load()[0, 0] == (255, 255, 255)
@ -100,7 +102,7 @@ def test_load():
assert im.load()[0, 0] == (255, 255, 255)
def test_binary():
def test_binary() -> None:
if HAS_GHOSTSCRIPT:
assert EpsImagePlugin.gs_binary is not None
else:
@ -114,41 +116,41 @@ def test_binary():
assert EpsImagePlugin.gs_windows_binary is not None
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
EpsImagePlugin.EpsImageFile(invalid_file)
def test_binary_header_only():
def test_binary_header_only() -> None:
data = io.BytesIO(simple_binary_header)
with pytest.raises(SyntaxError, match='EPS header missing "%!PS-Adobe" comment'):
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_missing_version_comment(prefix):
def test_missing_version_comment(prefix: bytes) -> None:
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_version))
with pytest.raises(SyntaxError):
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_missing_boundingbox_comment(prefix):
def test_missing_boundingbox_comment(prefix: bytes) -> None:
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_boundingbox))
with pytest.raises(SyntaxError, match='EPS header missing "%%BoundingBox" comment'):
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_invalid_boundingbox_comment(prefix):
def test_invalid_boundingbox_comment(prefix: bytes) -> None:
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox))
with pytest.raises(OSError, match="cannot determine EPS bounding box"):
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix):
def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix: bytes) -> None:
data = io.BytesIO(
prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox_valid_imagedata)
)
@ -159,21 +161,21 @@ def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix):
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_ascii_comment_too_long(prefix):
def test_ascii_comment_too_long(prefix: bytes) -> None:
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_ascii_comment))
with pytest.raises(SyntaxError, match="not an EPS file"):
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_long_binary_data(prefix):
def test_long_binary_data(prefix: bytes) -> None:
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data))
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_load_long_binary_data(prefix):
def test_load_long_binary_data(prefix: bytes) -> None:
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data))
with Image.open(data) as img:
img.load()
@ -186,7 +188,7 @@ def test_load_long_binary_data(prefix):
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_cmyk():
def test_cmyk() -> None:
with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image:
assert cmyk_image.mode == "CMYK"
assert cmyk_image.size == (100, 100)
@ -202,7 +204,7 @@ def test_cmyk():
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_showpage():
def test_showpage() -> None:
# See https://github.com/python-pillow/Pillow/issues/2615
with Image.open("Tests/images/reqd_showpage.eps") as plot_image:
with Image.open("Tests/images/reqd_showpage.png") as target:
@ -213,7 +215,7 @@ def test_showpage():
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_transparency():
def test_transparency() -> None:
with Image.open("Tests/images/reqd_showpage.eps") as plot_image:
plot_image.load(transparency=True)
assert plot_image.mode == "RGBA"
@ -224,7 +226,7 @@ def test_transparency():
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_file_object(tmp_path):
def test_file_object(tmp_path: Path) -> None:
# issue 479
with Image.open(FILE1) as image1:
with open(str(tmp_path / "temp.eps"), "wb") as fh:
@ -232,7 +234,7 @@ def test_file_object(tmp_path):
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_bytesio_object():
def test_bytesio_object() -> None:
with open(FILE1, "rb") as f:
img_bytes = io.BytesIO(f.read())
@ -245,12 +247,12 @@ def test_bytesio_object():
assert_image_similar(img, image1_scale1_compare, 5)
def test_1_mode():
def test_1_mode() -> None:
with Image.open("Tests/images/1.eps") as im:
assert im.mode == "1"
def test_image_mode_not_supported(tmp_path):
def test_image_mode_not_supported(tmp_path: Path) -> None:
im = hopper("RGBA")
tmpfile = str(tmp_path / "temp.eps")
with pytest.raises(ValueError):
@ -259,7 +261,7 @@ def test_image_mode_not_supported(tmp_path):
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@skip_unless_feature("zlib")
def test_render_scale1():
def test_render_scale1() -> None:
# We need png support for these render test
# Zero bounding box
@ -270,7 +272,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:
@ -281,7 +283,7 @@ def test_render_scale1():
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@skip_unless_feature("zlib")
def test_render_scale2():
def test_render_scale2() -> None:
# We need png support for these render test
# Zero bounding box
@ -292,7 +294,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:
@ -303,7 +305,7 @@ def test_render_scale2():
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@pytest.mark.parametrize("filename", (FILE1, FILE2, "Tests/images/illu10_preview.eps"))
def test_resize(filename):
def test_resize(filename: str) -> None:
with Image.open(filename) as im:
new_size = (100, 100)
im = im.resize(new_size)
@ -312,7 +314,7 @@ def test_resize(filename):
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@pytest.mark.parametrize("filename", (FILE1, FILE2))
def test_thumbnail(filename):
def test_thumbnail(filename: str) -> None:
# Issue #619
with Image.open(filename) as im:
new_size = (100, 100)
@ -320,20 +322,20 @@ def test_thumbnail(filename):
assert max(im.size) == max(new_size)
def test_read_binary_preview():
def test_read_binary_preview() -> None:
# Issue 302
# open image with binary preview
with Image.open(FILE3):
pass
def test_readline_psfile(tmp_path):
def test_readline_psfile(tmp_path: Path) -> None:
# check all the freaking line endings possible from the spec
# test_string = u'something\r\nelse\n\rbaz\rbif\n'
line_endings = ["\r\n", "\n", "\n\r", "\r"]
strings = ["something", "else", "baz", "bif"]
def _test_readline(t, ending):
def _test_readline(t: EpsImagePlugin.PSFile, ending: str) -> None:
ending = "Failure with line ending: %s" % (
"".join("%s" % ord(s) for s in ending)
)
@ -342,13 +344,13 @@ def test_readline_psfile(tmp_path):
assert t.readline().strip("\r\n") == "baz", ending
assert t.readline().strip("\r\n") == "bif", ending
def _test_readline_io_psfile(test_string, ending):
def _test_readline_io_psfile(test_string: str, ending: str) -> None:
f = io.BytesIO(test_string.encode("latin-1"))
with pytest.warns(DeprecationWarning):
t = EpsImagePlugin.PSFile(f)
_test_readline(t, ending)
def _test_readline_file_psfile(test_string, ending):
def _test_readline_file_psfile(test_string: str, ending: str) -> None:
f = str(tmp_path / "temp.txt")
with open(f, "wb") as w:
w.write(test_string.encode("latin-1"))
@ -364,7 +366,7 @@ def test_readline_psfile(tmp_path):
_test_readline_file_psfile(s, ending)
def test_psfile_deprecation():
def test_psfile_deprecation() -> None:
with pytest.warns(DeprecationWarning):
EpsImagePlugin.PSFile(None)
@ -374,7 +376,7 @@ def test_psfile_deprecation():
"line_ending",
(b"\r\n", b"\n", b"\n\r", b"\r"),
)
def test_readline(prefix, line_ending):
def test_readline(prefix: bytes, line_ending: bytes) -> None:
simple_file = prefix + line_ending.join(simple_eps_file_with_comments)
data = io.BytesIO(simple_file)
test_file = EpsImagePlugin.EpsImageFile(data)
@ -392,14 +394,14 @@ def test_readline(prefix, line_ending):
"Tests/images/illuCS6_preview.eps",
),
)
def test_open_eps(filename):
def test_open_eps(filename: str) -> None:
# https://github.com/python-pillow/Pillow/issues/1104
with Image.open(filename) as img:
assert img.mode == "RGB"
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_emptyline():
def test_emptyline() -> None:
# Test file includes an empty line in the header data
emptyline_file = "Tests/images/zero_bb_emptyline.eps"
@ -415,14 +417,14 @@ def test_emptyline():
"test_file",
["Tests/images/timeout-d675703545fee17acab56e5fec644c19979175de.eps"],
)
def test_timeout(test_file):
def test_timeout(test_file: str) -> None:
with open(test_file, "rb") as f:
with pytest.raises(Image.UnidentifiedImageError):
with Image.open(f):
pass
def test_bounding_box_in_trailer():
def test_bounding_box_in_trailer() -> None:
# Check bounding boxes are parsed in the same way
# when specified in the header and the trailer
with Image.open("Tests/images/zero_bb_trailer.eps") as trailer_image, Image.open(
@ -431,7 +433,7 @@ def test_bounding_box_in_trailer():
assert trailer_image.size == header_image.size
def test_eof_before_bounding_box():
def test_eof_before_bounding_box() -> None:
with pytest.raises(OSError):
with Image.open("Tests/images/zero_bb_eof_before_boundingbox.eps"):
pass

View File

@ -1,4 +1,5 @@
from __future__ import annotations
from io import BytesIO
import pytest
@ -10,7 +11,7 @@ from .helper import assert_image_equal, hopper
TEST_FILE = "Tests/images/hopper.fits"
def test_open():
def test_open() -> None:
# Act
with Image.open(TEST_FILE) as im:
# Assert
@ -21,7 +22,7 @@ def test_open():
assert_image_equal(im, hopper("L"))
def test_invalid_file():
def test_invalid_file() -> None:
# Arrange
invalid_file = "Tests/images/flower.jpg"
@ -30,14 +31,14 @@ def test_invalid_file():
FitsImagePlugin.FitsImageFile(invalid_file)
def test_truncated_fits():
def test_truncated_fits() -> None:
# No END to headers
image_data = b"SIMPLE = T" + b" " * 50 + b"TRUNCATE"
with pytest.raises(OSError):
FitsImagePlugin.FitsImageFile(BytesIO(image_data))
def test_naxis_zero():
def test_naxis_zero() -> None:
# This test image has been manually hexedited
# to set the number of data axes to zero
with pytest.raises(ValueError):
@ -45,7 +46,7 @@ def test_naxis_zero():
pass
def test_comment():
def test_comment() -> None:
image_data = b"SIMPLE = T / comment string"
with pytest.raises(OSError):
FitsImagePlugin.FitsImageFile(BytesIO(image_data))

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import warnings
import pytest
@ -15,7 +16,7 @@ static_test_file = "Tests/images/hopper.fli"
animated_test_file = "Tests/images/a.fli"
def test_sanity():
def test_sanity() -> None:
with Image.open(static_test_file) as im:
im.load()
assert im.mode == "P"
@ -32,8 +33,8 @@ def test_sanity():
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file():
def open():
def test_unclosed_file() -> None:
def open() -> None:
im = Image.open(static_test_file)
im.load()
@ -41,14 +42,14 @@ def test_unclosed_file():
open()
def test_closed_file():
def test_closed_file() -> None:
with warnings.catch_warnings():
im = Image.open(static_test_file)
im.load()
im.close()
def test_seek_after_close():
def test_seek_after_close() -> None:
im = Image.open(animated_test_file)
im.seek(1)
im.close()
@ -57,13 +58,13 @@ def test_seek_after_close():
im.seek(0)
def test_context_manager():
def test_context_manager() -> None:
with warnings.catch_warnings():
with Image.open(static_test_file) as im:
im.load()
def test_tell():
def test_tell() -> None:
# Arrange
with Image.open(static_test_file) as im:
# Act
@ -73,20 +74,20 @@ def test_tell():
assert frame == 0
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
FliImagePlugin.FliImageFile(invalid_file)
def test_palette_chunk_second():
def test_palette_chunk_second() -> None:
with Image.open("Tests/images/hopper_palette_chunk_second.fli") as im:
with Image.open(static_test_file) as expected:
assert_image_equal(im.convert("RGB"), expected.convert("RGB"))
def test_n_frames():
def test_n_frames() -> None:
with Image.open(static_test_file) as im:
assert im.n_frames == 1
assert not im.is_animated
@ -96,7 +97,7 @@ def test_n_frames():
assert im.is_animated
def test_eoferror():
def test_eoferror() -> None:
with Image.open(animated_test_file) as im:
n_frames = im.n_frames
@ -109,7 +110,7 @@ def test_eoferror():
im.seek(n_frames - 1)
def test_seek_tell():
def test_seek_tell() -> None:
with Image.open(animated_test_file) as im:
layer_number = im.tell()
assert layer_number == 0
@ -131,7 +132,7 @@ def test_seek_tell():
assert layer_number == 1
def test_seek():
def test_seek() -> None:
with Image.open(animated_test_file) as im:
im.seek(50)
@ -146,7 +147,7 @@ def test_seek():
],
)
@pytest.mark.timeout(timeout=3)
def test_timeouts(test_file):
def test_timeouts(test_file: str) -> None:
with open(test_file, "rb") as f:
with Image.open(f) as im:
with pytest.raises(OSError):
@ -159,7 +160,7 @@ def test_timeouts(test_file):
"Tests/images/crash-5762152299364352.fli",
],
)
def test_crash(test_file):
def test_crash(test_file: str) -> None:
with open(test_file, "rb") as f:
with Image.open(f) as im:
with pytest.raises(OSError):

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import Image
@ -10,7 +11,7 @@ FpxImagePlugin = pytest.importorskip(
)
def test_sanity():
def test_sanity() -> None:
with Image.open("Tests/images/input_bw_one_band.fpx") as im:
assert im.mode == "L"
assert im.size == (70, 46)
@ -19,7 +20,7 @@ def test_sanity():
assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png")
def test_close():
def test_close() -> None:
with Image.open("Tests/images/input_bw_one_band.fpx") as im:
pass
assert im.ole.fp.closed
@ -29,7 +30,7 @@ def test_close():
assert im.ole.fp.closed
def test_invalid_file():
def test_invalid_file() -> None:
# Test an invalid OLE file
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
@ -41,7 +42,7 @@ def test_invalid_file():
FpxImagePlugin.FpxImageFile(ole_file)
def test_fpx_invalid_number_of_bands():
def test_fpx_invalid_number_of_bands() -> None:
with pytest.raises(OSError, match="Invalid number of bands"):
with Image.open("Tests/images/input_bw_five_bands.fpx"):
pass

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import FtexImagePlugin, Image
@ -6,18 +7,18 @@ from PIL import FtexImagePlugin, Image
from .helper import assert_image_equal_tofile, assert_image_similar
def test_load_raw():
def test_load_raw() -> None:
with Image.open("Tests/images/ftex_uncompressed.ftu") as im:
assert_image_equal_tofile(im, "Tests/images/ftex_uncompressed.png")
def test_load_dxt1():
def test_load_dxt1() -> None:
with Image.open("Tests/images/ftex_dxt1.ftc") as im:
with Image.open("Tests/images/ftex_dxt1.png") as target:
assert_image_similar(im, target.convert("RGBA"), 15)
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import GbrImagePlugin, Image
@ -6,12 +7,12 @@ from PIL import GbrImagePlugin, Image
from .helper import assert_image_equal_tofile
def test_gbr_file():
def test_gbr_file() -> None:
with Image.open("Tests/images/gbr.gbr") as im:
assert_image_equal_tofile(im, "Tests/images/gbr.png")
def test_load():
def test_load() -> None:
with Image.open("Tests/images/gbr.gbr") as im:
assert im.load()[0, 0] == (0, 0, 0, 0)
@ -19,14 +20,14 @@ def test_load():
assert im.load()[0, 0] == (0, 0, 0, 0)
def test_multiple_load_operations():
def test_multiple_load_operations() -> None:
with Image.open("Tests/images/gbr.gbr") as im:
im.load()
im.load()
assert_image_equal_tofile(im, "Tests/images/gbr.png")
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import GdImageFile, UnidentifiedImageError
@ -6,18 +7,18 @@ from PIL import GdImageFile, UnidentifiedImageError
TEST_GD_FILE = "Tests/images/hopper.gd"
def test_sanity():
def test_sanity() -> None:
with GdImageFile.open(TEST_GD_FILE) as im:
assert im.size == (128, 128)
assert im.format == "GD"
def test_bad_mode():
def test_bad_mode() -> None:
with pytest.raises(ValueError):
GdImageFile.open(TEST_GD_FILE, "bad mode")
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(UnidentifiedImageError):

View File

@ -1,6 +1,9 @@
from __future__ import annotations
import warnings
from io import BytesIO
from pathlib import Path
from typing import Generator
import pytest
@ -22,7 +25,7 @@ with open(TEST_GIF, "rb") as f:
data = f.read()
def test_sanity():
def test_sanity() -> None:
with Image.open(TEST_GIF) as im:
im.load()
assert im.mode == "P"
@ -32,8 +35,8 @@ def test_sanity():
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file():
def open():
def test_unclosed_file() -> None:
def open() -> None:
im = Image.open(TEST_GIF)
im.load()
@ -41,14 +44,14 @@ def test_unclosed_file():
open()
def test_closed_file():
def test_closed_file() -> None:
with warnings.catch_warnings():
im = Image.open(TEST_GIF)
im.load()
im.close()
def test_seek_after_close():
def test_seek_after_close() -> None:
im = Image.open("Tests/images/iss634.gif")
im.load()
im.close()
@ -61,20 +64,20 @@ def test_seek_after_close():
im.seek(1)
def test_context_manager():
def test_context_manager() -> None:
with warnings.catch_warnings():
with Image.open(TEST_GIF) as im:
im.load()
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
GifImagePlugin.GifImageFile(invalid_file)
def test_l_mode_transparency():
def test_l_mode_transparency() -> None:
with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
assert im.mode == "L"
assert im.load()[0, 0] == 128
@ -85,7 +88,7 @@ def test_l_mode_transparency():
assert im.load()[0, 0] == 128
def test_l_mode_after_rgb():
def test_l_mode_after_rgb() -> None:
with Image.open("Tests/images/no_palette_after_rgb.gif") as im:
im.seek(1)
assert im.mode == "RGB"
@ -94,13 +97,13 @@ def test_l_mode_after_rgb():
assert im.mode == "RGB"
def test_palette_not_needed_for_second_frame():
def test_palette_not_needed_for_second_frame() -> None:
with Image.open("Tests/images/palette_not_needed_for_second_frame.gif") as im:
im.seek(1)
assert_image_similar(im, hopper("L").convert("RGB"), 8)
def test_strategy():
def test_strategy() -> None:
with Image.open("Tests/images/iss634.gif") as im:
expected_rgb_always = im.convert("RGB")
@ -141,14 +144,14 @@ def test_strategy():
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
def test_optimize():
def test_grayscale(optimize):
def test_optimize() -> None:
def test_grayscale(optimize: int) -> int:
im = Image.new("L", (1, 1), 0)
filename = BytesIO()
im.save(filename, "GIF", optimize=optimize)
return len(filename.getvalue())
def test_bilevel(optimize):
def test_bilevel(optimize: int) -> int:
im = Image.new("1", (1, 1), 0)
test_file = BytesIO()
im.save(test_file, "GIF", optimize=optimize)
@ -176,7 +179,9 @@ def test_optimize():
(4, 513, 256),
),
)
def test_optimize_correctness(colors, size, expected_palette_length):
def test_optimize_correctness(
colors: int, size: int, expected_palette_length: int
) -> None:
# 256 color Palette image, posterize to > 128 and < 128 levels.
# Size bigger and smaller than 512x512.
# Check the palette for number of colors allocated.
@ -198,14 +203,14 @@ def test_optimize_correctness(colors, size, expected_palette_length):
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
def test_optimize_full_l():
def test_optimize_full_l() -> None:
im = Image.frombytes("L", (16, 16), bytes(range(256)))
test_file = BytesIO()
im.save(test_file, "GIF", optimize=True)
assert im.mode == "L"
def test_optimize_if_palette_can_be_reduced_by_half():
def test_optimize_if_palette_can_be_reduced_by_half() -> None:
im = Image.new("P", (8, 1))
im.palette = ImagePalette.raw("RGB", bytes((0, 0, 0) * 150))
for i in range(8):
@ -218,7 +223,7 @@ def test_optimize_if_palette_can_be_reduced_by_half():
assert len(reloaded.palette.palette) // 3 == colors
def test_full_palette_second_frame(tmp_path):
def test_full_palette_second_frame(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = Image.new("P", (1, 256))
@ -239,7 +244,7 @@ def test_full_palette_second_frame(tmp_path):
reloaded.getpixel((0, i)) == i
def test_roundtrip(tmp_path):
def test_roundtrip(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = hopper()
im.save(out)
@ -247,7 +252,7 @@ def test_roundtrip(tmp_path):
assert_image_similar(reread.convert("RGB"), im, 50)
def test_roundtrip2(tmp_path):
def test_roundtrip2(tmp_path: Path) -> None:
# see https://github.com/python-pillow/Pillow/issues/403
out = str(tmp_path / "temp.gif")
with Image.open(TEST_GIF) as im:
@ -257,7 +262,7 @@ def test_roundtrip2(tmp_path):
assert_image_similar(reread.convert("RGB"), hopper(), 50)
def test_roundtrip_save_all(tmp_path):
def test_roundtrip_save_all(tmp_path: Path) -> None:
# Single frame image
out = str(tmp_path / "temp.gif")
im = hopper()
@ -274,7 +279,7 @@ def test_roundtrip_save_all(tmp_path):
assert reread.n_frames == 5
def test_roundtrip_save_all_1(tmp_path):
def test_roundtrip_save_all_1(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = Image.new("1", (1, 1))
im2 = Image.new("1", (1, 1), 1)
@ -295,7 +300,7 @@ def test_roundtrip_save_all_1(tmp_path):
("Tests/images/dispose_bgnd_rgba.gif", "RGBA"),
),
)
def test_loading_multiple_palettes(path, mode):
def test_loading_multiple_palettes(path: str, mode: str) -> None:
with Image.open(path) as im:
assert im.mode == "P"
first_frame_colors = im.palette.colors.keys()
@ -313,7 +318,7 @@ def test_loading_multiple_palettes(path, mode):
assert im.load()[24, 24] not in first_frame_colors
def test_headers_saving_for_animated_gifs(tmp_path):
def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None:
important_headers = ["background", "version", "duration", "loop"]
# Multiframe image
with Image.open("Tests/images/dispose_bgnd.gif") as im:
@ -326,7 +331,7 @@ def test_headers_saving_for_animated_gifs(tmp_path):
assert info[header] == reread.info[header]
def test_palette_handling(tmp_path):
def test_palette_handling(tmp_path: Path) -> None:
# see https://github.com/python-pillow/Pillow/issues/513
with Image.open(TEST_GIF) as im:
@ -342,12 +347,12 @@ def test_palette_handling(tmp_path):
assert_image_similar(im, reloaded.convert("RGB"), 10)
def test_palette_434(tmp_path):
def test_palette_434(tmp_path: Path) -> None:
# see https://github.com/python-pillow/Pillow/issues/434
def roundtrip(im, *args, **kwargs):
def roundtrip(im: Image.Image, **kwargs: bool) -> Image.Image:
out = str(tmp_path / "temp.gif")
im.copy().save(out, *args, **kwargs)
im.copy().save(out, **kwargs)
reloaded = Image.open(out)
return reloaded
@ -367,7 +372,7 @@ def test_palette_434(tmp_path):
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_bmp_mode(tmp_path):
def test_save_netpbm_bmp_mode(tmp_path: Path) -> None:
with Image.open(TEST_GIF) as img:
img = img.convert("RGB")
@ -378,7 +383,7 @@ def test_save_netpbm_bmp_mode(tmp_path):
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_l_mode(tmp_path):
def test_save_netpbm_l_mode(tmp_path: Path) -> None:
with Image.open(TEST_GIF) as img:
img = img.convert("L")
@ -388,7 +393,7 @@ def test_save_netpbm_l_mode(tmp_path):
assert_image_similar(img, reloaded.convert("L"), 0)
def test_seek():
def test_seek() -> None:
with Image.open("Tests/images/dispose_none.gif") as img:
frame_count = 0
try:
@ -399,7 +404,7 @@ def test_seek():
assert frame_count == 5
def test_seek_info():
def test_seek_info() -> None:
with Image.open("Tests/images/iss634.gif") as im:
info = im.info.copy()
@ -409,7 +414,7 @@ def test_seek_info():
assert im.info == info
def test_seek_rewind():
def test_seek_rewind() -> None:
with Image.open("Tests/images/iss634.gif") as im:
im.seek(2)
im.seek(1)
@ -427,7 +432,7 @@ def test_seek_rewind():
("Tests/images/iss634.gif", 42),
),
)
def test_n_frames(path, n_frames):
def test_n_frames(path: str, n_frames: int) -> None:
# Test is_animated before n_frames
with Image.open(path) as im:
assert im.is_animated == (n_frames != 1)
@ -438,7 +443,7 @@ def test_n_frames(path, n_frames):
assert im.is_animated == (n_frames != 1)
def test_no_change():
def test_no_change() -> None:
# Test n_frames does not change the image
with Image.open("Tests/images/dispose_bgnd.gif") as im:
im.seek(1)
@ -459,7 +464,7 @@ def test_no_change():
assert_image_equal(im, expected)
def test_eoferror():
def test_eoferror() -> None:
with Image.open(TEST_GIF) as im:
n_frames = im.n_frames
@ -472,13 +477,13 @@ def test_eoferror():
im.seek(n_frames - 1)
def test_first_frame_transparency():
def test_first_frame_transparency() -> None:
with Image.open("Tests/images/first_frame_transparency.gif") as im:
px = im.load()
assert px[0, 0] == im.info["transparency"]
def test_dispose_none():
def test_dispose_none() -> None:
with Image.open("Tests/images/dispose_none.gif") as img:
try:
while True:
@ -488,7 +493,7 @@ def test_dispose_none():
pass
def test_dispose_none_load_end():
def test_dispose_none_load_end() -> None:
# Test image created with:
#
# im = Image.open("transparent.gif")
@ -501,7 +506,7 @@ def test_dispose_none_load_end():
assert_image_equal_tofile(img, "Tests/images/dispose_none_load_end_second.png")
def test_dispose_background():
def test_dispose_background() -> None:
with Image.open("Tests/images/dispose_bgnd.gif") as img:
try:
while True:
@ -511,7 +516,7 @@ def test_dispose_background():
pass
def test_dispose_background_transparency():
def test_dispose_background_transparency() -> None:
with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img:
img.seek(2)
px = img.load()
@ -539,7 +544,10 @@ def test_dispose_background_transparency():
),
),
)
def test_transparent_dispose(loading_strategy, expected_colors):
def test_transparent_dispose(
loading_strategy: GifImagePlugin.LoadingStrategy,
expected_colors: tuple[tuple[int | tuple[int, int, int, int], ...]],
) -> None:
GifImagePlugin.LOADING_STRATEGY = loading_strategy
try:
with Image.open("Tests/images/transparent_dispose.gif") as img:
@ -552,7 +560,7 @@ def test_transparent_dispose(loading_strategy, expected_colors):
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
def test_dispose_previous():
def test_dispose_previous() -> None:
with Image.open("Tests/images/dispose_prev.gif") as img:
try:
while True:
@ -562,7 +570,7 @@ def test_dispose_previous():
pass
def test_dispose_previous_first_frame():
def test_dispose_previous_first_frame() -> None:
with Image.open("Tests/images/dispose_prev_first_frame.gif") as im:
im.seek(1)
assert_image_equal_tofile(
@ -570,7 +578,7 @@ def test_dispose_previous_first_frame():
)
def test_previous_frame_loaded():
def test_previous_frame_loaded() -> None:
with Image.open("Tests/images/dispose_none.gif") as img:
img.load()
img.seek(1)
@ -581,7 +589,7 @@ def test_previous_frame_loaded():
assert_image_equal(img_skipped, img)
def test_save_dispose(tmp_path):
def test_save_dispose(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im_list = [
Image.new("L", (100, 100), "#000"),
@ -609,7 +617,7 @@ def test_save_dispose(tmp_path):
assert img.disposal_method == i + 1
def test_dispose2_palette(tmp_path):
def test_dispose2_palette(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
# Four colors: white, gray, black, red
@ -640,7 +648,7 @@ def test_dispose2_palette(tmp_path):
assert rgb_img.getpixel((50, 50)) == circle
def test_dispose2_diff(tmp_path):
def test_dispose2_diff(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
# 4 frames: red/blue, red/red, blue/blue, red/blue
@ -682,7 +690,7 @@ def test_dispose2_diff(tmp_path):
assert rgb_img.getpixel((1, 1)) == (255, 255, 255, 0)
def test_dispose2_background(tmp_path):
def test_dispose2_background(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im_list = []
@ -708,7 +716,7 @@ def test_dispose2_background(tmp_path):
assert im.getpixel((0, 0)) == (255, 0, 0)
def test_dispose2_background_frame(tmp_path):
def test_dispose2_background_frame(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im_list = [Image.new("RGBA", (1, 20))]
@ -726,7 +734,7 @@ def test_dispose2_background_frame(tmp_path):
assert im.n_frames == 3
def test_transparency_in_second_frame(tmp_path):
def test_transparency_in_second_frame(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/different_transparency.gif") as im:
assert im.info["transparency"] == 0
@ -746,7 +754,7 @@ def test_transparency_in_second_frame(tmp_path):
)
def test_no_transparency_in_second_frame():
def test_no_transparency_in_second_frame() -> None:
with Image.open("Tests/images/iss634.gif") as img:
# Seek to the second frame
img.seek(img.tell() + 1)
@ -756,7 +764,7 @@ def test_no_transparency_in_second_frame():
assert img.histogram()[255] == 0
def test_remapped_transparency(tmp_path):
def test_remapped_transparency(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = Image.new("P", (1, 2))
@ -772,7 +780,7 @@ def test_remapped_transparency(tmp_path):
assert reloaded.info["transparency"] == reloaded.getpixel((0, 1))
def test_duration(tmp_path):
def test_duration(tmp_path: Path) -> None:
duration = 1000
out = str(tmp_path / "temp.gif")
@ -786,7 +794,7 @@ def test_duration(tmp_path):
assert reread.info["duration"] == duration
def test_multiple_duration(tmp_path):
def test_multiple_duration(tmp_path: Path) -> None:
duration_list = [1000, 2000, 3000]
out = str(tmp_path / "temp.gif")
@ -821,7 +829,7 @@ def test_multiple_duration(tmp_path):
pass
def test_roundtrip_info_duration(tmp_path):
def test_roundtrip_info_duration(tmp_path: Path) -> None:
duration_list = [100, 500, 500]
out = str(tmp_path / "temp.gif")
@ -838,7 +846,7 @@ def test_roundtrip_info_duration(tmp_path):
] == duration_list
def test_roundtrip_info_duration_combined(tmp_path):
def test_roundtrip_info_duration_combined(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/duplicate_frame.gif") as im:
assert [frame.info["duration"] for frame in ImageSequence.Iterator(im)] == [
@ -854,7 +862,7 @@ def test_roundtrip_info_duration_combined(tmp_path):
] == [1000, 2000]
def test_identical_frames(tmp_path):
def test_identical_frames(tmp_path: Path) -> None:
duration_list = [1000, 1500, 2000, 4000]
out = str(tmp_path / "temp.gif")
@ -887,7 +895,9 @@ def test_identical_frames(tmp_path):
1500,
),
)
def test_identical_frames_to_single_frame(duration, tmp_path):
def test_identical_frames_to_single_frame(
duration: int | list[int], tmp_path: Path
) -> None:
out = str(tmp_path / "temp.gif")
im_list = [
Image.new("L", (100, 100), "#000"),
@ -904,7 +914,7 @@ def test_identical_frames_to_single_frame(duration, tmp_path):
assert reread.info["duration"] == 4500
def test_loop_none(tmp_path):
def test_loop_none(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = Image.new("L", (100, 100), "#000")
im.save(out, loop=None)
@ -912,7 +922,7 @@ def test_loop_none(tmp_path):
assert "loop" not in reread.info
def test_number_of_loops(tmp_path):
def test_number_of_loops(tmp_path: Path) -> None:
number_of_loops = 2
out = str(tmp_path / "temp.gif")
@ -930,7 +940,7 @@ def test_number_of_loops(tmp_path):
assert im.info["loop"] == 2
def test_background(tmp_path):
def test_background(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = Image.new("L", (100, 100), "#000")
im.info["background"] = 1
@ -939,7 +949,7 @@ def test_background(tmp_path):
assert reread.info["background"] == im.info["background"]
def test_webp_background(tmp_path):
def test_webp_background(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
# Test opaque WebP background
@ -954,7 +964,7 @@ def test_webp_background(tmp_path):
im.save(out)
def test_comment(tmp_path):
def test_comment(tmp_path: Path) -> None:
with Image.open(TEST_GIF) as im:
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0"
@ -974,7 +984,7 @@ def test_comment(tmp_path):
assert reread.info["version"] == b"GIF89a"
def test_comment_over_255(tmp_path):
def test_comment_over_255(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = Image.new("L", (100, 100), "#000")
comment = b"Test comment text"
@ -989,18 +999,18 @@ def test_comment_over_255(tmp_path):
assert reread.info["version"] == b"GIF89a"
def test_zero_comment_subblocks():
def test_zero_comment_subblocks() -> None:
with Image.open("Tests/images/hopper_zero_comment_subblocks.gif") as im:
assert_image_equal_tofile(im, TEST_GIF)
def test_read_multiple_comment_blocks():
def test_read_multiple_comment_blocks() -> None:
with Image.open("Tests/images/multiple_comments.gif") as im:
# Multiple comment blocks in a frame are separated not concatenated
assert im.info["comment"] == b"Test comment 1\nTest comment 2"
def test_empty_string_comment(tmp_path):
def test_empty_string_comment(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/chi.gif") as im:
assert "comment" in im.info
@ -1013,7 +1023,7 @@ def test_empty_string_comment(tmp_path):
assert "comment" not in frame.info
def test_retain_comment_in_subsequent_frames(tmp_path):
def test_retain_comment_in_subsequent_frames(tmp_path: Path) -> None:
# Test that a comment block at the beginning is kept
with Image.open("Tests/images/chi.gif") as im:
for frame in ImageSequence.Iterator(im):
@ -1044,10 +1054,10 @@ def test_retain_comment_in_subsequent_frames(tmp_path):
assert frame.info["comment"] == b"Test"
def test_version(tmp_path):
def test_version(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
def assert_version_after_save(im, version):
def assert_version_after_save(im: Image.Image, version: bytes) -> None:
im.save(out)
with Image.open(out) as reread:
assert reread.info["version"] == version
@ -1074,7 +1084,7 @@ def test_version(tmp_path):
assert_version_after_save(im, b"GIF87a")
def test_append_images(tmp_path):
def test_append_images(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
# Test appending single frame images
@ -1086,7 +1096,7 @@ def test_append_images(tmp_path):
assert reread.n_frames == 3
# Tests appending using a generator
def im_generator(ims):
def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
yield from ims
im.save(out, save_all=True, append_images=im_generator(ims))
@ -1103,7 +1113,7 @@ def test_append_images(tmp_path):
assert reread.n_frames == 10
def test_transparent_optimize(tmp_path):
def test_transparent_optimize(tmp_path: Path) -> None:
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
# transparency.
# Need a palette that isn't using the 0 color,
@ -1123,7 +1133,7 @@ def test_transparent_optimize(tmp_path):
assert reloaded.info["transparency"] == reloaded.getpixel((252, 0))
def test_removed_transparency(tmp_path):
def test_removed_transparency(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = Image.new("RGB", (256, 1))
@ -1138,7 +1148,7 @@ def test_removed_transparency(tmp_path):
assert "transparency" not in reloaded.info
def test_rgb_transparency(tmp_path):
def test_rgb_transparency(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
# Single frame
@ -1160,7 +1170,7 @@ def test_rgb_transparency(tmp_path):
assert "transparency" not in reloaded.info
def test_rgba_transparency(tmp_path):
def test_rgba_transparency(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = hopper("P")
@ -1171,13 +1181,13 @@ def test_rgba_transparency(tmp_path):
assert_image_equal(hopper("P").convert("RGB"), reloaded)
def test_background_outside_palettte(tmp_path):
def test_background_outside_palettte(tmp_path: Path) -> None:
with Image.open("Tests/images/background_outside_palette.gif") as im:
im.seek(1)
assert im.info["background"] == 255
def test_bbox(tmp_path):
def test_bbox(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = Image.new("RGB", (100, 100), "#fff")
@ -1188,7 +1198,7 @@ def test_bbox(tmp_path):
assert reread.n_frames == 2
def test_bbox_alpha(tmp_path):
def test_bbox_alpha(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = Image.new("RGBA", (1, 2), (255, 0, 0, 255))
@ -1200,7 +1210,7 @@ def test_bbox_alpha(tmp_path):
assert reread.n_frames == 2
def test_palette_save_L(tmp_path):
def test_palette_save_L(tmp_path: Path) -> None:
# Generate an L mode image with a separate palette
im = hopper("P")
@ -1214,7 +1224,7 @@ def test_palette_save_L(tmp_path):
assert_image_equal(reloaded.convert("RGB"), im.convert("RGB"))
def test_palette_save_P(tmp_path):
def test_palette_save_P(tmp_path: Path) -> None:
im = Image.new("P", (1, 2))
im.putpixel((0, 1), 1)
@ -1228,7 +1238,7 @@ def test_palette_save_P(tmp_path):
assert reloaded_rgb.getpixel((0, 1)) == (4, 5, 6)
def test_palette_save_duplicate_entries(tmp_path):
def test_palette_save_duplicate_entries(tmp_path: Path) -> None:
im = Image.new("P", (1, 2))
im.putpixel((0, 1), 1)
@ -1241,7 +1251,7 @@ def test_palette_save_duplicate_entries(tmp_path):
assert reloaded.convert("RGB").getpixel((0, 1)) == (0, 0, 0)
def test_palette_save_all_P(tmp_path):
def test_palette_save_all_P(tmp_path: Path) -> None:
frames = []
colors = ((255, 0, 0), (0, 255, 0))
for color in colors:
@ -1264,7 +1274,7 @@ def test_palette_save_all_P(tmp_path):
assert im.palette.palette == im.global_palette.palette
def test_palette_save_ImagePalette(tmp_path):
def test_palette_save_ImagePalette(tmp_path: Path) -> None:
# Pass in a different palette, as an ImagePalette.ImagePalette
# effectively the same as test_palette_save_P
@ -1279,7 +1289,7 @@ def test_palette_save_ImagePalette(tmp_path):
assert_image_equal(reloaded.convert("RGB"), im.convert("RGB"))
def test_save_I(tmp_path):
def test_save_I(tmp_path: Path) -> None:
# Test saving something that would trigger the auto-convert to 'L'
im = hopper("I")
@ -1291,7 +1301,7 @@ def test_save_I(tmp_path):
assert_image_equal(reloaded.convert("L"), im.convert("L"))
def test_getdata():
def test_getdata() -> None:
# Test getheader/getdata against legacy values.
# Create a 'P' image with holes in the palette.
im = Image._wedge().resize((16, 16), Image.Resampling.NEAREST)
@ -1319,7 +1329,7 @@ def test_getdata():
GifImagePlugin._FORCE_OPTIMIZE = False
def test_lzw_bits():
def test_lzw_bits() -> None:
# see https://github.com/python-pillow/Pillow/issues/2811
with Image.open("Tests/images/issue_2811.gif") as im:
assert im.tile[0][3][0] == 11 # LZW bits
@ -1327,7 +1337,7 @@ def test_lzw_bits():
im.load()
def test_extents():
def test_extents() -> None:
with Image.open("Tests/images/test_extents.gif") as im:
assert im.size == (100, 100)
@ -1339,7 +1349,7 @@ def test_extents():
assert im.size == (150, 150)
def test_missing_background():
def test_missing_background() -> None:
# The Global Color Table Flag isn't set, so there is no background color index,
# but the disposal method is "Restore to background color"
with Image.open("Tests/images/missing_background.gif") as im:
@ -1347,7 +1357,7 @@ def test_missing_background():
assert_image_equal_tofile(im, "Tests/images/missing_background_first_frame.png")
def test_saving_rgba(tmp_path):
def test_saving_rgba(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/transparent.png") as im:
im.save(out)

View File

@ -1,8 +1,9 @@
from __future__ import annotations
from PIL import GimpGradientFile, ImagePalette
def test_linear_pos_le_middle():
def test_linear_pos_le_middle() -> None:
# Arrange
middle = 0.5
pos = 0.25
@ -14,7 +15,7 @@ def test_linear_pos_le_middle():
assert ret == 0.25
def test_linear_pos_le_small_middle():
def test_linear_pos_le_small_middle() -> None:
# Arrange
middle = 1e-11
pos = 1e-12
@ -26,7 +27,7 @@ def test_linear_pos_le_small_middle():
assert ret == 0.0
def test_linear_pos_gt_middle():
def test_linear_pos_gt_middle() -> None:
# Arrange
middle = 0.5
pos = 0.75
@ -38,7 +39,7 @@ def test_linear_pos_gt_middle():
assert ret == 0.75
def test_linear_pos_gt_small_middle():
def test_linear_pos_gt_small_middle() -> None:
# Arrange
middle = 1 - 1e-11
pos = 1 - 1e-12
@ -50,7 +51,7 @@ def test_linear_pos_gt_small_middle():
assert ret == 1.0
def test_curved():
def test_curved() -> None:
# Arrange
middle = 0.5
pos = 0.75
@ -62,7 +63,7 @@ def test_curved():
assert ret == 0.75
def test_sine():
def test_sine() -> None:
# Arrange
middle = 0.5
pos = 0.75
@ -74,7 +75,7 @@ def test_sine():
assert ret == 0.8535533905932737
def test_sphere_increasing():
def test_sphere_increasing() -> None:
# Arrange
middle = 0.5
pos = 0.75
@ -86,7 +87,7 @@ def test_sphere_increasing():
assert round(abs(ret - 0.9682458365518543), 7) == 0
def test_sphere_decreasing():
def test_sphere_decreasing() -> None:
# Arrange
middle = 0.5
pos = 0.75
@ -98,7 +99,7 @@ def test_sphere_decreasing():
assert ret == 0.3385621722338523
def test_load_via_imagepalette():
def test_load_via_imagepalette() -> None:
# Arrange
test_file = "Tests/images/gimp_gradient.ggr"
@ -111,7 +112,7 @@ def test_load_via_imagepalette():
assert palette[1] == "RGBA"
def test_load_1_3_via_imagepalette():
def test_load_1_3_via_imagepalette() -> None:
# Arrange
# GIMP 1.3 gradient files contain a name field
test_file = "Tests/images/gimp_gradient_with_name.ggr"

View File

@ -1,10 +1,11 @@
from __future__ import annotations
import pytest
from PIL.GimpPaletteFile import GimpPaletteFile
def test_sanity():
def test_sanity() -> None:
with open("Tests/images/test.gpl", "rb") as fp:
GimpPaletteFile(fp)
@ -21,7 +22,7 @@ def test_sanity():
GimpPaletteFile(fp)
def test_get_palette():
def test_get_palette() -> None:
# Arrange
with open("Tests/images/custom_gimp_palette.gpl", "rb") as fp:
palette_file = GimpPaletteFile(fp)

View File

@ -1,4 +1,8 @@
from __future__ import annotations
from pathlib import Path
from typing import IO
import pytest
from PIL import GribStubImagePlugin, Image
@ -8,7 +12,7 @@ from .helper import hopper
TEST_FILE = "Tests/images/WAlaska.wind.7days.grb"
def test_open():
def test_open() -> None:
# Act
with Image.open(TEST_FILE) as im:
# Assert
@ -19,7 +23,7 @@ def test_open():
assert im.size == (1, 1)
def test_invalid_file():
def test_invalid_file() -> None:
# Arrange
invalid_file = "Tests/images/flower.jpg"
@ -28,7 +32,7 @@ def test_invalid_file():
GribStubImagePlugin.GribStubImageFile(invalid_file)
def test_load():
def test_load() -> None:
# Arrange
with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler
@ -36,7 +40,7 @@ def test_load():
im.load()
def test_save(tmp_path):
def test_save(tmp_path: Path) -> None:
# Arrange
im = hopper()
tmpfile = str(tmp_path / "temp.grib")
@ -46,21 +50,21 @@ def test_save(tmp_path):
im.save(tmpfile)
def test_handler(tmp_path):
def test_handler(tmp_path: Path) -> None:
class TestHandler:
opened = False
loaded = False
saved = False
def open(self, im):
def open(self, im: Image.Image) -> None:
self.opened = True
def load(self, im):
def load(self, im: Image.Image) -> Image.Image:
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))
def save(self, im, fp, filename):
def save(self, im: Image.Image, fp: IO[bytes], filename: str) -> None:
self.saved = True
handler = TestHandler()

View File

@ -1,4 +1,8 @@
from __future__ import annotations
from pathlib import Path
from typing import IO
import pytest
from PIL import Hdf5StubImagePlugin, Image
@ -6,7 +10,7 @@ from PIL import Hdf5StubImagePlugin, Image
TEST_FILE = "Tests/images/hdf5.h5"
def test_open():
def test_open() -> None:
# Act
with Image.open(TEST_FILE) as im:
# Assert
@ -17,7 +21,7 @@ def test_open():
assert im.size == (1, 1)
def test_invalid_file():
def test_invalid_file() -> None:
# Arrange
invalid_file = "Tests/images/flower.jpg"
@ -26,7 +30,7 @@ def test_invalid_file():
Hdf5StubImagePlugin.HDF5StubImageFile(invalid_file)
def test_load():
def test_load() -> None:
# Arrange
with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler
@ -34,7 +38,7 @@ def test_load():
im.load()
def test_save():
def test_save() -> None:
# Arrange
with Image.open(TEST_FILE) as im:
dummy_fp = None
@ -47,21 +51,21 @@ def test_save():
Hdf5StubImagePlugin._save(im, dummy_fp, dummy_filename)
def test_handler(tmp_path):
def test_handler(tmp_path: Path) -> None:
class TestHandler:
opened = False
loaded = False
saved = False
def open(self, im):
def open(self, im: Image.Image) -> None:
self.opened = True
def load(self, im):
def load(self, im: Image.Image) -> Image.Image:
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))
def save(self, im, fp, filename):
def save(self, im: Image.Image, fp: IO[bytes], filename: str) -> None:
self.saved = True
handler = TestHandler()

View File

@ -1,7 +1,9 @@
from __future__ import annotations
import io
import os
import warnings
from pathlib import Path
import pytest
@ -13,7 +15,7 @@ from .helper import assert_image_equal, assert_image_similar_tofile, skip_unless
TEST_FILE = "Tests/images/pillow.icns"
def test_sanity():
def test_sanity() -> None:
# Loading this icon by default should result in the largest size
# (512x512@2x) being loaded
with Image.open(TEST_FILE) as im:
@ -26,7 +28,7 @@ def test_sanity():
assert im.format == "ICNS"
def test_load():
def test_load() -> None:
with Image.open(TEST_FILE) as im:
assert im.load()[0, 0] == (0, 0, 0, 0)
@ -34,7 +36,7 @@ def test_load():
assert im.load()[0, 0] == (0, 0, 0, 0)
def test_save(tmp_path):
def test_save(tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.icns")
with Image.open(TEST_FILE) as im:
@ -51,7 +53,7 @@ def test_save(tmp_path):
assert _binary.i32be(fp.read(4)) == file_length
def test_save_append_images(tmp_path):
def test_save_append_images(tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.icns")
provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128))
@ -66,7 +68,7 @@ def test_save_append_images(tmp_path):
assert_image_equal(reread, provided_im)
def test_save_fp():
def test_save_fp() -> None:
fp = io.BytesIO()
with Image.open(TEST_FILE) as im:
@ -78,7 +80,7 @@ def test_save_fp():
assert reread.format == "ICNS"
def test_sizes():
def test_sizes() -> None:
# Check that we can load all of the sizes, and that the final pixel
# dimensions are as expected
with Image.open(TEST_FILE) as im:
@ -95,7 +97,7 @@ def test_sizes():
im.size = (1, 1)
def test_older_icon():
def test_older_icon() -> None:
# This icon was made with Icon Composer rather than iconutil; it still
# uses PNG rather than JP2, however (since it was made on 10.9).
with Image.open("Tests/images/pillow2.icns") as im:
@ -110,7 +112,7 @@ def test_older_icon():
@skip_unless_feature("jpg_2000")
def test_jp2_icon():
def test_jp2_icon() -> None:
# This icon uses JPEG 2000 images instead of the PNG images.
# The advantage of doing this is that OS X 10.5 supports JPEG 2000
# but not PNG; some commercial software therefore does just this.
@ -126,7 +128,7 @@ def test_jp2_icon():
assert im2.size == (wr, hr)
def test_getimage():
def test_getimage() -> None:
with open(TEST_FILE, "rb") as fp:
icns_file = IcnsImagePlugin.IcnsFile(fp)
@ -139,14 +141,14 @@ def test_getimage():
assert im.size == (512, 512)
def test_not_an_icns_file():
def test_not_an_icns_file() -> None:
with io.BytesIO(b"invalid\n") as fp:
with pytest.raises(SyntaxError):
IcnsImagePlugin.IcnsFile(fp)
@skip_unless_feature("jpg_2000")
def test_icns_decompression_bomb():
def test_icns_decompression_bomb() -> None:
with Image.open(
"Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns"
) as im:

View File

@ -1,6 +1,8 @@
from __future__ import annotations
import io
import os
from pathlib import Path
import pytest
@ -11,7 +13,7 @@ from .helper import assert_image_equal, assert_image_equal_tofile, hopper
TEST_ICO_FILE = "Tests/images/hopper.ico"
def test_sanity():
def test_sanity() -> None:
with Image.open(TEST_ICO_FILE) as im:
im.load()
assert im.mode == "RGBA"
@ -20,29 +22,29 @@ def test_sanity():
assert im.get_format_mimetype() == "image/x-icon"
def test_load():
def test_load() -> None:
with Image.open(TEST_ICO_FILE) as im:
assert im.load()[0, 0] == (1, 1, 9, 255)
def test_mask():
def test_mask() -> None:
with Image.open("Tests/images/hopper_mask.ico") as im:
assert_image_equal_tofile(im, "Tests/images/hopper_mask.png")
def test_black_and_white():
def test_black_and_white() -> None:
with Image.open("Tests/images/black_and_white.ico") as im:
assert im.mode == "RGBA"
assert im.size == (16, 16)
def test_invalid_file():
def test_invalid_file() -> None:
with open("Tests/images/flower.jpg", "rb") as fp:
with pytest.raises(SyntaxError):
IcoImagePlugin.IcoImageFile(fp)
def test_save_to_bytes():
def test_save_to_bytes() -> None:
output = io.BytesIO()
im = hopper()
im.save(output, "ico", sizes=[(32, 32), (64, 64)])
@ -72,7 +74,7 @@ def test_save_to_bytes():
)
def test_getpixel(tmp_path):
def test_getpixel(tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.ico")
im = hopper()
@ -85,7 +87,7 @@ def test_getpixel(tmp_path):
assert reloaded.getpixel((0, 0)) == (18, 20, 62)
def test_no_duplicates(tmp_path):
def test_no_duplicates(tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.ico")
temp_file2 = str(tmp_path / "temp2.ico")
@ -99,7 +101,7 @@ def test_no_duplicates(tmp_path):
assert os.path.getsize(temp_file) == os.path.getsize(temp_file2)
def test_different_bit_depths(tmp_path):
def test_different_bit_depths(tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.ico")
temp_file2 = str(tmp_path / "temp2.ico")
@ -133,7 +135,7 @@ def test_different_bit_depths(tmp_path):
@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB", "RGBA"))
def test_save_to_bytes_bmp(mode):
def test_save_to_bytes_bmp(mode) -> None:
output = io.BytesIO()
im = hopper(mode)
im.save(output, "ico", bitmap_format="bmp", sizes=[(32, 32), (64, 64)])
@ -161,13 +163,13 @@ def test_save_to_bytes_bmp(mode):
assert_image_equal(reloaded, im)
def test_incorrect_size():
def test_incorrect_size() -> None:
with Image.open(TEST_ICO_FILE) as im:
with pytest.raises(ValueError):
im.size = (1, 1)
def test_save_256x256(tmp_path):
def test_save_256x256(tmp_path: Path) -> None:
"""Issue #2264 https://github.com/python-pillow/Pillow/issues/2264"""
# Arrange
with Image.open("Tests/images/hopper_256x256.ico") as im:
@ -180,7 +182,7 @@ def test_save_256x256(tmp_path):
assert im_saved.size == (256, 256)
def test_only_save_relevant_sizes(tmp_path):
def test_only_save_relevant_sizes(tmp_path: Path) -> None:
"""Issue #2266 https://github.com/python-pillow/Pillow/issues/2266
Should save in 16x16, 24x24, 32x32, 48x48 sizes
and not in 16x16, 24x24, 32x32, 48x48, 48x48, 48x48, 48x48 sizes
@ -196,7 +198,7 @@ def test_only_save_relevant_sizes(tmp_path):
assert im_saved.info["sizes"] == {(16, 16), (24, 24), (32, 32), (48, 48)}
def test_save_append_images(tmp_path):
def test_save_append_images(tmp_path: Path) -> None:
# append_images should be used for scaled down versions of the image
im = hopper("RGBA")
provided_im = Image.new("RGBA", (32, 32), (255, 0, 0))
@ -210,7 +212,7 @@ def test_save_append_images(tmp_path):
assert_image_equal(reread, provided_im)
def test_unexpected_size():
def test_unexpected_size() -> None:
# This image has been manually hexedited to state that it is 16x32
# while the image within is still 16x16
with pytest.warns(UserWarning):
@ -218,7 +220,7 @@ def test_unexpected_size():
assert im.size == (16, 16)
def test_draw_reloaded(tmp_path):
def test_draw_reloaded(tmp_path: Path) -> None:
with Image.open(TEST_ICO_FILE) as im:
outfile = str(tmp_path / "temp_saved_hopper_draw.ico")

View File

@ -1,6 +1,8 @@
from __future__ import annotations
import filecmp
import warnings
from pathlib import Path
import pytest
@ -12,7 +14,7 @@ from .helper import assert_image_equal_tofile, hopper, is_pypy
TEST_IM = "Tests/images/hopper.im"
def test_sanity():
def test_sanity() -> None:
with Image.open(TEST_IM) as im:
im.load()
assert im.mode == "RGB"
@ -20,7 +22,7 @@ def test_sanity():
assert im.format == "IM"
def test_name_limit(tmp_path):
def test_name_limit(tmp_path: Path) -> None:
out = str(tmp_path / ("name_limit_test" * 7 + ".im"))
with Image.open(TEST_IM) as im:
im.save(out)
@ -28,8 +30,8 @@ def test_name_limit(tmp_path):
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file():
def open():
def test_unclosed_file() -> None:
def open() -> None:
im = Image.open(TEST_IM)
im.load()
@ -37,20 +39,20 @@ def test_unclosed_file():
open()
def test_closed_file():
def test_closed_file() -> None:
with warnings.catch_warnings():
im = Image.open(TEST_IM)
im.load()
im.close()
def test_context_manager():
def test_context_manager() -> None:
with warnings.catch_warnings():
with Image.open(TEST_IM) as im:
im.load()
def test_tell():
def test_tell() -> None:
# Arrange
with Image.open(TEST_IM) as im:
# Act
@ -60,13 +62,13 @@ def test_tell():
assert frame == 0
def test_n_frames():
def test_n_frames() -> None:
with Image.open(TEST_IM) as im:
assert im.n_frames == 1
assert not im.is_animated
def test_eoferror():
def test_eoferror() -> None:
with Image.open(TEST_IM) as im:
n_frames = im.n_frames
@ -80,14 +82,14 @@ def test_eoferror():
@pytest.mark.parametrize("mode", ("RGB", "P", "PA"))
def test_roundtrip(mode, tmp_path):
def test_roundtrip(mode, tmp_path: Path) -> None:
out = str(tmp_path / "temp.im")
im = hopper(mode)
im.save(out)
assert_image_equal_tofile(im, out)
def test_small_palette(tmp_path):
def test_small_palette(tmp_path: Path) -> None:
im = Image.new("P", (1, 1))
colors = [0, 1, 2]
im.putpalette(colors)
@ -99,19 +101,19 @@ def test_small_palette(tmp_path):
assert reloaded.getpalette() == colors + [0] * 765
def test_save_unsupported_mode(tmp_path):
def test_save_unsupported_mode(tmp_path: Path) -> None:
out = str(tmp_path / "temp.im")
im = hopper("HSV")
with pytest.raises(ValueError):
im.save(out)
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
ImImagePlugin.ImImageFile(invalid_file)
def test_number():
def test_number() -> None:
assert ImImagePlugin.number("1.2") == 1.2

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import io
import pytest
@ -8,13 +9,13 @@ from PIL import Image, ImtImagePlugin
from .helper import assert_image_equal_tofile
def test_sanity():
def test_sanity() -> None:
with Image.open("Tests/images/bw_gradient.imt") as im:
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
@pytest.mark.parametrize("data", (b"\n", b"\n-", b"width 1\n"))
def test_invalid_file(data):
def test_invalid_file(data: bytes) -> None:
with io.BytesIO(data) as fp:
with pytest.raises(SyntaxError):
ImtImagePlugin.ImtImageFile(fp)

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import sys
from io import BytesIO, StringIO
@ -11,7 +12,7 @@ from .helper import assert_image_equal, hopper
TEST_FILE = "Tests/images/iptc.jpg"
def test_open():
def test_open() -> None:
expected = Image.new("L", (1, 1))
f = BytesIO(
@ -23,7 +24,7 @@ def test_open():
assert_image_equal(im, expected)
def test_getiptcinfo_jpg_none():
def test_getiptcinfo_jpg_none() -> None:
# Arrange
with hopper() as im:
# Act
@ -33,7 +34,7 @@ def test_getiptcinfo_jpg_none():
assert iptc is None
def test_getiptcinfo_jpg_found():
def test_getiptcinfo_jpg_found() -> None:
# Arrange
with Image.open(TEST_FILE) as im:
# Act
@ -45,7 +46,7 @@ def test_getiptcinfo_jpg_found():
assert iptc[(2, 101)] == b"Hungary"
def test_getiptcinfo_fotostation():
def test_getiptcinfo_fotostation() -> None:
# Arrange
with open(TEST_FILE, "rb") as fp:
data = bytearray(fp.read())
@ -62,7 +63,7 @@ def test_getiptcinfo_fotostation():
pytest.fail("FotoStation tag not found")
def test_getiptcinfo_zero_padding():
def test_getiptcinfo_zero_padding() -> None:
# Arrange
with Image.open(TEST_FILE) as im:
im.info["photoshop"][0x0404] += b"\x00\x00\x00"
@ -75,7 +76,7 @@ def test_getiptcinfo_zero_padding():
assert len(iptc) == 3
def test_getiptcinfo_tiff_none():
def test_getiptcinfo_tiff_none() -> None:
# Arrange
with Image.open("Tests/images/hopper.tif") as im:
# Act
@ -85,7 +86,7 @@ def test_getiptcinfo_tiff_none():
assert iptc is None
def test_i():
def test_i() -> None:
# Arrange
c = b"a"
@ -97,7 +98,7 @@ def test_i():
assert ret == 97
def test_dump(monkeypatch):
def test_dump(monkeypatch) -> None:
# Arrange
c = b"abc"
# Temporarily redirect stdout
@ -112,6 +113,6 @@ def test_dump(monkeypatch):
assert mystdout.getvalue() == "61 62 63 \n"
def test_pad_deprecation():
def test_pad_deprecation() -> None:
with pytest.warns(DeprecationWarning):
assert IptcImagePlugin.PAD == b"\0\0\0\0"

View File

@ -1,8 +1,12 @@
from __future__ import annotations
import os
import re
import warnings
from io import BytesIO
from pathlib import Path
from types import ModuleType
from typing import Any
import pytest
@ -30,6 +34,7 @@ from .helper import (
skip_unless_feature,
)
ElementTree: ModuleType | None
try:
from defusedxml import ElementTree
except ImportError:
@ -40,7 +45,7 @@ TEST_FILE = "Tests/images/hopper.jpg"
@skip_unless_feature("jpg")
class TestFileJpeg:
def roundtrip(self, im, **options):
def roundtrip(self, im: Image.Image, **options: Any) -> Image.Image:
out = BytesIO()
im.save(out, "JPEG", **options)
test_bytes = out.tell()
@ -49,7 +54,7 @@ class TestFileJpeg:
im.bytes = test_bytes # for testing only
return im
def gen_random_image(self, size, mode="RGB"):
def gen_random_image(self, size: tuple[int, int], mode: str = "RGB") -> Image.Image:
"""Generates a very hard to compress file
:param size: tuple
:param mode: optional image mode
@ -57,7 +62,7 @@ class TestFileJpeg:
"""
return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode)))
def test_sanity(self):
def test_sanity(self) -> None:
# internal version number
assert re.search(r"\d+\.\d+$", features.version_codec("jpg"))
@ -69,13 +74,13 @@ class TestFileJpeg:
assert im.get_format_mimetype() == "image/jpeg"
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
def test_zero(self, size, tmp_path):
def test_zero(self, size: tuple[int, int], tmp_path: Path) -> None:
f = str(tmp_path / "temp.jpg")
im = Image.new("RGB", size)
with pytest.raises(ValueError):
im.save(f)
def test_app(self):
def test_app(self) -> None:
# Test APP/COM reader (@PIL135)
with Image.open(TEST_FILE) as im:
assert im.applist[0] == ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00")
@ -88,7 +93,7 @@ class TestFileJpeg:
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
assert im.app["COM"] == im.info["comment"]
def test_comment_write(self):
def test_comment_write(self) -> None:
with Image.open(TEST_FILE) as im:
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
@ -106,15 +111,13 @@ class TestFileJpeg:
assert "comment" not in reloaded.info
# Test that a comment argument overrides the default comment
for comment in ("Test comment text", b"Text comment text"):
for comment in ("Test comment text", b"Test comment text"):
out = BytesIO()
im.save(out, format="JPEG", comment=comment)
with Image.open(out) as reloaded:
if not isinstance(comment, bytes):
comment = comment.encode()
assert reloaded.info["comment"] == comment
assert reloaded.info["comment"] == b"Test comment text"
def test_cmyk(self):
def test_cmyk(self) -> None:
# Test CMYK handling. Thanks to Tim and Charlie for test data,
# Michael for getting me to look one more time.
f = "Tests/images/pil_sample_cmyk.jpg"
@ -142,8 +145,8 @@ class TestFileJpeg:
)
assert k > 0.9
def test_rgb(self):
def getchannels(im):
def test_rgb(self) -> None:
def getchannels(im: Image.Image) -> tuple[int, int, int]:
return tuple(v[0] for v in im.layer)
im = hopper()
@ -159,8 +162,8 @@ class TestFileJpeg:
"test_image_path",
[TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"],
)
def test_dpi(self, test_image_path):
def test(xdpi, ydpi=None):
def test_dpi(self, test_image_path: str) -> None:
def test(xdpi: int, ydpi: int | None = None):
with Image.open(test_image_path) as im:
im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))
return im.info.get("dpi")
@ -173,7 +176,7 @@ class TestFileJpeg:
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_icc(self, tmp_path):
def test_icc(self, tmp_path: Path) -> None:
# Test ICC support
with Image.open("Tests/images/rgb.jpg") as im1:
icc_profile = im1.info["icc_profile"]
@ -205,7 +208,7 @@ class TestFileJpeg:
ImageFile.MAXBLOCK * 4 + 3, # large block
),
)
def test_icc_big(self, n):
def test_icc_big(self, n: int) -> None:
# Make sure that the "extra" support handles large blocks
# The ICC APP marker can store 65519 bytes per marker, so
# using a 4-byte test code should allow us to detect out of
@ -218,7 +221,7 @@ class TestFileJpeg:
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_large_icc_meta(self, tmp_path):
def test_large_icc_meta(self, tmp_path: Path) -> None:
# https://github.com/python-pillow/Pillow/issues/148
# Sometimes the meta data on the icc_profile block is bigger than
# Image.MAXBLOCK or the image size.
@ -242,7 +245,7 @@ class TestFileJpeg:
f = str(tmp_path / "temp3.jpg")
im.save(f, progressive=True, quality=94, exif=b" " * 43668)
def test_optimize(self):
def test_optimize(self) -> None:
im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), optimize=0)
im3 = self.roundtrip(hopper(), optimize=1)
@ -251,14 +254,14 @@ class TestFileJpeg:
assert im1.bytes >= im2.bytes
assert im1.bytes >= im3.bytes
def test_optimize_large_buffer(self, tmp_path):
def test_optimize_large_buffer(self, tmp_path: Path) -> None:
# https://github.com/python-pillow/Pillow/issues/148
f = str(tmp_path / "temp.jpg")
# this requires ~ 1.5x Image.MAXBLOCK
im = Image.new("RGB", (4096, 4096), 0xFF3333)
im.save(f, format="JPEG", optimize=True)
def test_progressive(self):
def test_progressive(self) -> None:
im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), progressive=False)
im3 = self.roundtrip(hopper(), progressive=True)
@ -269,25 +272,25 @@ class TestFileJpeg:
assert_image_equal(im1, im3)
assert im1.bytes >= im3.bytes
def test_progressive_large_buffer(self, tmp_path):
def test_progressive_large_buffer(self, tmp_path: Path) -> None:
f = str(tmp_path / "temp.jpg")
# this requires ~ 1.5x Image.MAXBLOCK
im = Image.new("RGB", (4096, 4096), 0xFF3333)
im.save(f, format="JPEG", progressive=True)
def test_progressive_large_buffer_highest_quality(self, tmp_path):
def test_progressive_large_buffer_highest_quality(self, tmp_path: Path) -> None:
f = str(tmp_path / "temp.jpg")
im = self.gen_random_image((255, 255))
# this requires more bytes than pixels in the image
im.save(f, format="JPEG", progressive=True, quality=100)
def test_progressive_cmyk_buffer(self):
def test_progressive_cmyk_buffer(self) -> None:
# Issue 2272, quality 90 cmyk image is tripping the large buffer bug.
f = BytesIO()
im = self.gen_random_image((256, 256), "CMYK")
im.save(f, format="JPEG", progressive=True, quality=94)
def test_large_exif(self, tmp_path):
def test_large_exif(self, tmp_path: Path) -> None:
# https://github.com/python-pillow/Pillow/issues/148
f = str(tmp_path / "temp.jpg")
im = hopper()
@ -296,12 +299,12 @@ class TestFileJpeg:
with pytest.raises(ValueError):
im.save(f, "JPEG", quality=90, exif=b"1" * 65534)
def test_exif_typeerror(self):
def test_exif_typeerror(self) -> None:
with Image.open("Tests/images/exif_typeerror.jpg") as im:
# Should not raise a TypeError
im._getexif()
def test_exif_gps(self, tmp_path):
def test_exif_gps(self, tmp_path: Path) -> None:
expected_exif_gps = {
0: b"\x00\x00\x00\x01",
2: 4294967295,
@ -326,7 +329,7 @@ class TestFileJpeg:
exif = reloaded._getexif()
assert exif[gps_index] == expected_exif_gps
def test_empty_exif_gps(self):
def test_empty_exif_gps(self) -> None:
with Image.open("Tests/images/empty_gps_ifd.jpg") as im:
exif = im.getexif()
del exif[0x8769]
@ -344,7 +347,7 @@ class TestFileJpeg:
# Assert that it was transposed
assert 0x0112 not in exif
def test_exif_equality(self):
def test_exif_equality(self) -> None:
# In 7.2.0, Exif rationals were changed to be read as
# TiffImagePlugin.IFDRational. This class had a bug in __eq__,
# breaking the self-equality of Exif data
@ -354,7 +357,7 @@ class TestFileJpeg:
exifs.append(im._getexif())
assert exifs[0] == exifs[1]
def test_exif_rollback(self):
def test_exif_rollback(self) -> None:
# rolling back exif support in 3.1 to pre-3.0 formatting.
# expected from 2.9, with b/u qualifiers switched for 3.2 compatibility
# this test passes on 2.9 and 3.1, but not 3.0
@ -389,12 +392,12 @@ class TestFileJpeg:
for tag, value in expected_exif.items():
assert value == exif[tag]
def test_exif_gps_typeerror(self):
def test_exif_gps_typeerror(self) -> None:
with Image.open("Tests/images/exif_gps_typeerror.jpg") as im:
# Should not raise a TypeError
im._getexif()
def test_progressive_compat(self):
def test_progressive_compat(self) -> None:
im1 = self.roundtrip(hopper())
assert not im1.info.get("progressive")
assert not im1.info.get("progression")
@ -415,7 +418,7 @@ class TestFileJpeg:
assert im3.info.get("progressive")
assert im3.info.get("progression")
def test_quality(self):
def test_quality(self) -> None:
im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), quality=50)
assert_image(im1, im2.mode, im2.size)
@ -425,13 +428,13 @@ class TestFileJpeg:
assert_image(im1, im3.mode, im3.size)
assert im2.bytes > im3.bytes
def test_smooth(self):
def test_smooth(self) -> None:
im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), smooth=100)
assert_image(im1, im2.mode, im2.size)
def test_subsampling(self):
def getsampling(im):
def test_subsampling(self) -> None:
def getsampling(im: Image.Image):
layer = im.layer
return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]
@ -439,46 +442,46 @@ class TestFileJpeg:
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)
for subsampling1 in (0, "4:4:4"):
im = self.roundtrip(hopper(), subsampling=subsampling1)
assert getsampling(im) == (1, 1, 1, 1, 1, 1)
for subsampling in (1, "4:2:2"):
im = self.roundtrip(hopper(), subsampling=subsampling)
for subsampling1 in (1, "4:2:2"):
im = self.roundtrip(hopper(), subsampling=subsampling1)
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)
for subsampling1 in (2, "4:2:0", "4:1:1"):
im = self.roundtrip(hopper(), subsampling=subsampling1)
assert getsampling(im) == (2, 2, 1, 1, 1, 1)
# RGB colorspace
for subsampling in (-1, 0, "4:4:4"):
for subsampling1 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)
im = self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling1)
assert getsampling(im) == (1, 1, 1, 1, 1, 1)
for subsampling in (1, "4:2:2", 2, "4:2:0", 3):
for subsampling1 in (1, "4:2:2", 2, "4:2:0", 3):
with pytest.raises(OSError):
self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling)
self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling1)
with pytest.raises(TypeError):
self.roundtrip(hopper(), subsampling="1:1:1")
def test_exif(self):
def test_exif(self) -> None:
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
info = im._getexif()
assert info[305] == "Adobe Photoshop CS Macintosh"
def test_get_child_images(self):
def test_get_child_images(self) -> None:
with Image.open("Tests/images/flower.jpg") as im:
ims = im.get_child_images()
assert len(ims) == 1
assert_image_similar_tofile(ims[0], "Tests/images/flower_thumbnail.png", 2.1)
def test_mp(self):
def test_mp(self) -> None:
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
assert im._getmp() is None
def test_quality_keep(self, tmp_path):
def test_quality_keep(self, tmp_path: Path) -> None:
# RGB
with Image.open("Tests/images/hopper.jpg") as im:
f = str(tmp_path / "temp.jpg")
@ -492,13 +495,13 @@ class TestFileJpeg:
f = str(tmp_path / "temp.jpg")
im.save(f, quality="keep")
def test_junk_jpeg_header(self):
def test_junk_jpeg_header(self) -> None:
# https://github.com/python-pillow/Pillow/issues/630
filename = "Tests/images/junk_jpeg_header.jpg"
with Image.open(filename):
pass
def test_ff00_jpeg_header(self):
def test_ff00_jpeg_header(self) -> None:
filename = "Tests/images/jpeg_ff00_header.jpg"
with Image.open(filename):
pass
@ -506,7 +509,7 @@ class TestFileJpeg:
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_truncated_jpeg_should_read_all_the_data(self):
def test_truncated_jpeg_should_read_all_the_data(self) -> None:
filename = "Tests/images/truncated_jpeg.jpg"
ImageFile.LOAD_TRUNCATED_IMAGES = True
with Image.open(filename) as im:
@ -514,7 +517,7 @@ class TestFileJpeg:
ImageFile.LOAD_TRUNCATED_IMAGES = False
assert im.getbbox() is not None
def test_truncated_jpeg_throws_oserror(self):
def test_truncated_jpeg_throws_oserror(self) -> None:
filename = "Tests/images/truncated_jpeg.jpg"
with Image.open(filename) as im:
with pytest.raises(OSError):
@ -527,8 +530,8 @@ class TestFileJpeg:
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_qtables(self, tmp_path):
def _n_qtables_helper(n, test_file):
def test_qtables(self, tmp_path: Path) -> None:
def _n_qtables_helper(n: int, test_file: str) -> None:
with Image.open(test_file) as im:
f = str(tmp_path / "temp.jpg")
im.save(f, qtables=[[n] * 64] * n)
@ -636,24 +639,24 @@ class TestFileJpeg:
with pytest.raises(ValueError):
self.roundtrip(im, qtables=[[1, 2, 3, 4]])
def test_load_16bit_qtables(self):
def test_load_16bit_qtables(self) -> None:
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
assert len(im.quantization) == 2
assert len(im.quantization[0]) == 64
assert max(im.quantization[0]) > 255
def test_save_multiple_16bit_qtables(self):
def test_save_multiple_16bit_qtables(self) -> None:
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
im2 = self.roundtrip(im, qtables="keep")
assert im.quantization == im2.quantization
def test_save_single_16bit_qtable(self):
def test_save_single_16bit_qtable(self) -> None:
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
im2 = self.roundtrip(im, qtables={0: im.quantization[0]})
assert len(im2.quantization) == 1
assert im2.quantization[0] == im.quantization[0]
def test_save_low_quality_baseline_qtables(self):
def test_save_low_quality_baseline_qtables(self) -> None:
with Image.open(TEST_FILE) as im:
im2 = self.roundtrip(im, quality=10)
assert len(im2.quantization) == 2
@ -664,7 +667,7 @@ class TestFileJpeg:
"blocks, rows, markers",
((0, 0, 0), (1, 0, 15), (3, 0, 5), (8, 0, 1), (0, 1, 3), (0, 2, 1)),
)
def test_restart_markers(self, blocks, rows, markers):
def test_restart_markers(self, blocks: int, rows: int, markers: int) -> None:
im = Image.new("RGB", (32, 32)) # 16 MCUs
out = BytesIO()
im.save(
@ -678,20 +681,20 @@ class TestFileJpeg:
assert len(re.findall(b"\xff[\xd0-\xd7]", out.getvalue())) == markers
@pytest.mark.skipif(not djpeg_available(), reason="djpeg not available")
def test_load_djpeg(self):
def test_load_djpeg(self) -> None:
with Image.open(TEST_FILE) as img:
img.load_djpeg()
assert_image_similar_tofile(img, TEST_FILE, 5)
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
def test_save_cjpeg(self, tmp_path):
def test_save_cjpeg(self, tmp_path: Path) -> None:
with Image.open(TEST_FILE) as img:
tempfile = str(tmp_path / "temp.jpg")
JpegImagePlugin._save_cjpeg(img, 0, tempfile)
# Default save quality is 75%, so a tiny bit of difference is alright
assert_image_similar_tofile(img, tempfile, 17)
def test_no_duplicate_0x1001_tag(self):
def test_no_duplicate_0x1001_tag(self) -> None:
# Arrange
tag_ids = {v: k for k, v in ExifTags.TAGS.items()}
@ -699,7 +702,7 @@ class TestFileJpeg:
assert tag_ids["RelatedImageWidth"] == 0x1001
assert tag_ids["RelatedImageLength"] == 0x1002
def test_MAXBLOCK_scaling(self, tmp_path):
def test_MAXBLOCK_scaling(self, tmp_path: Path) -> None:
im = self.gen_random_image((512, 512))
f = str(tmp_path / "temp.jpeg")
im.save(f, quality=100, optimize=True)
@ -710,7 +713,7 @@ class TestFileJpeg:
reloaded.save(f, quality="keep", progressive=True)
reloaded.save(f, quality="keep", optimize=True)
def test_bad_mpo_header(self):
def test_bad_mpo_header(self) -> None:
"""Treat unknown MPO as JPEG"""
# Arrange
@ -722,20 +725,20 @@ class TestFileJpeg:
assert im.format == "JPEG"
@pytest.mark.parametrize("mode", ("1", "L", "RGB", "RGBX", "CMYK", "YCbCr"))
def test_save_correct_modes(self, mode):
def test_save_correct_modes(self, mode: str) -> None:
out = BytesIO()
img = Image.new(mode, (20, 20))
img.save(out, "JPEG")
@pytest.mark.parametrize("mode", ("LA", "La", "RGBA", "RGBa", "P"))
def test_save_wrong_modes(self, mode):
def test_save_wrong_modes(self, mode: str) -> None:
# ref https://github.com/python-pillow/Pillow/issues/2005
out = BytesIO()
img = Image.new(mode, (20, 20))
with pytest.raises(OSError):
img.save(out, "JPEG")
def test_save_tiff_with_dpi(self, tmp_path):
def test_save_tiff_with_dpi(self, tmp_path: Path) -> None:
# Arrange
outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/hopper.tif") as im:
@ -747,7 +750,7 @@ class TestFileJpeg:
reloaded.load()
assert im.info["dpi"] == reloaded.info["dpi"]
def test_save_dpi_rounding(self, tmp_path):
def test_save_dpi_rounding(self, tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.jpg")
with Image.open("Tests/images/hopper.jpg") as im:
im.save(outfile, dpi=(72.2, 72.2))
@ -760,7 +763,7 @@ class TestFileJpeg:
with Image.open(outfile) as reloaded:
assert reloaded.info["dpi"] == (73, 73)
def test_dpi_tuple_from_exif(self):
def test_dpi_tuple_from_exif(self) -> None:
# Arrange
# This Photoshop CC 2017 image has DPI in EXIF not metadata
# EXIF XResolution is (2000000, 10000)
@ -768,7 +771,7 @@ class TestFileJpeg:
# Act / Assert
assert im.info.get("dpi") == (200, 200)
def test_dpi_int_from_exif(self):
def test_dpi_int_from_exif(self) -> None:
# Arrange
# This image has DPI in EXIF not metadata
# EXIF XResolution is 72
@ -776,7 +779,7 @@ class TestFileJpeg:
# Act / Assert
assert im.info.get("dpi") == (72, 72)
def test_dpi_from_dpcm_exif(self):
def test_dpi_from_dpcm_exif(self) -> None:
# Arrange
# This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:
# exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg
@ -784,7 +787,7 @@ class TestFileJpeg:
# Act / Assert
assert im.info.get("dpi") == (508, 508)
def test_dpi_exif_zero_division(self):
def test_dpi_exif_zero_division(self) -> None:
# Arrange
# This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:
# exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg
@ -793,7 +796,7 @@ class TestFileJpeg:
# This should return the default, and not raise a ZeroDivisionError
assert im.info.get("dpi") == (72, 72)
def test_dpi_exif_string(self):
def test_dpi_exif_string(self) -> None:
# Arrange
# 0x011A tag in this exif contains string '300300\x02'
with Image.open("Tests/images/broken_exif_dpi.jpg") as im:
@ -801,14 +804,14 @@ class TestFileJpeg:
# This should return the default
assert im.info.get("dpi") == (72, 72)
def test_dpi_exif_truncated(self):
def test_dpi_exif_truncated(self) -> None:
# Arrange
with Image.open("Tests/images/truncated_exif_dpi.jpg") as im:
# Act / Assert
# This should return the default
assert im.info.get("dpi") == (72, 72)
def test_no_dpi_in_exif(self):
def test_no_dpi_in_exif(self) -> None:
# Arrange
# This is photoshop-200dpi.jpg with resolution removed from EXIF:
# exiftool "-*resolution*"= photoshop-200dpi.jpg
@ -818,7 +821,7 @@ class TestFileJpeg:
# https://exiv2.org/tags.html
assert im.info.get("dpi") == (72, 72)
def test_invalid_exif(self):
def test_invalid_exif(self) -> None:
# This is no-dpi-in-exif with the tiff header of the exif block
# hexedited from MM * to FF FF FF FF
with Image.open("Tests/images/invalid-exif.jpg") as im:
@ -829,7 +832,7 @@ class TestFileJpeg:
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_exif_x_resolution(self, tmp_path):
def test_exif_x_resolution(self, tmp_path: Path) -> None:
with Image.open("Tests/images/flower.jpg") as im:
exif = im.getexif()
assert exif[282] == 180
@ -841,14 +844,14 @@ class TestFileJpeg:
with Image.open(out) as reloaded:
assert reloaded.getexif()[282] == 180
def test_invalid_exif_x_resolution(self):
def test_invalid_exif_x_resolution(self) -> None:
# When no x or y resolution is defined in EXIF
with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im:
# This should return the default, and not a ValueError or
# OSError for an unidentified image.
assert im.info.get("dpi") == (72, 72)
def test_ifd_offset_exif(self):
def test_ifd_offset_exif(self) -> None:
# Arrange
# This image has been manually hexedited to have an IFD offset of 10,
# in contrast to normal 8
@ -856,14 +859,14 @@ class TestFileJpeg:
# Act / Assert
assert im._getexif()[306] == "2017:03:13 23:03:09"
def test_multiple_exif(self):
def test_multiple_exif(self) -> None:
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"
)
def test_photoshop(self):
def test_photoshop(self) -> None:
with Image.open("Tests/images/photoshop-200dpi.jpg") as im:
assert im.info["photoshop"][0x03ED] == {
"XResolution": 200.0,
@ -880,14 +883,14 @@ class TestFileJpeg:
with Image.open("Tests/images/app13.jpg") as im:
assert "photoshop" not in im.info
def test_photoshop_malformed_and_multiple(self):
def test_photoshop_malformed_and_multiple(self) -> None:
with Image.open("Tests/images/app13-multiple.jpg") as im:
assert "photoshop" in im.info
assert 24 == len(im.info["photoshop"])
apps_13_lengths = [len(v) for k, v in im.applist if k == "APP13"]
assert [65504, 24] == apps_13_lengths
def test_adobe_transform(self):
def test_adobe_transform(self) -> None:
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
assert im.info["adobe_transform"] == 1
@ -901,11 +904,11 @@ class TestFileJpeg:
assert "adobe" in im.info
assert "adobe_transform" not in im.info
def test_icc_after_SOF(self):
def test_icc_after_SOF(self) -> None:
with Image.open("Tests/images/icc-after-SOF.jpg") as im:
assert im.info["icc_profile"] == b"profile"
def test_jpeg_magic_number(self):
def test_jpeg_magic_number(self) -> None:
size = 4097
buffer = BytesIO(b"\xFF" * size) # Many xFF bytes
buffer.max_pos = 0
@ -924,7 +927,7 @@ class TestFileJpeg:
# Assert the entire file has not been read
assert 0 < buffer.max_pos < size
def test_getxmp(self):
def test_getxmp(self) -> None:
with Image.open("Tests/images/xmp_test.jpg") as im:
if ElementTree is None:
with pytest.warns(
@ -953,7 +956,7 @@ class TestFileJpeg:
with Image.open("Tests/images/hopper.jpg") as im:
assert im.getxmp() == {}
def test_getxmp_no_prefix(self):
def test_getxmp_no_prefix(self) -> None:
with Image.open("Tests/images/xmp_no_prefix.jpg") as im:
if ElementTree is None:
with pytest.warns(
@ -964,7 +967,7 @@ class TestFileJpeg:
else:
assert im.getxmp() == {"xmpmeta": {"key": "value"}}
def test_getxmp_padded(self):
def test_getxmp_padded(self) -> None:
with Image.open("Tests/images/xmp_padded.jpg") as im:
if ElementTree is None:
with pytest.warns(
@ -976,16 +979,16 @@ class TestFileJpeg:
assert im.getxmp() == {"xmpmeta": None}
@pytest.mark.timeout(timeout=1)
def test_eof(self):
def test_eof(self) -> None:
# Even though this decoder never says that it is finished
# the image should still end when there is no new data
class InfiniteMockPyDecoder(ImageFile.PyDecoder):
def decode(self, buffer):
def decode(self, buffer: bytes) -> tuple[int, int]:
return 0, 0
decoder = InfiniteMockPyDecoder(None)
def closure(mode, *args):
def closure(mode: str, *args) -> InfiniteMockPyDecoder:
decoder.__init__(mode, *args)
return decoder
@ -999,7 +1002,7 @@ class TestFileJpeg:
im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_separate_tables(self):
def test_separate_tables(self) -> None:
im = hopper()
data = [] # [interchange, tables-only, image-only]
for streamtype in range(3):
@ -1021,14 +1024,14 @@ class TestFileJpeg:
with Image.open(BytesIO(data[1] + data[2])) as combined_im:
assert_image_equal(interchange_im, combined_im)
def test_repr_jpeg(self):
def test_repr_jpeg(self) -> None:
im = hopper()
with Image.open(BytesIO(im._repr_jpeg_())) as repr_jpeg:
assert repr_jpeg.format == "JPEG"
assert_image_similar(im, repr_jpeg, 17)
def test_repr_jpeg_error_returns_none(self):
def test_repr_jpeg_error_returns_none(self) -> None:
im = hopper("F")
assert im._repr_jpeg_() is None
@ -1037,7 +1040,7 @@ class TestFileJpeg:
@pytest.mark.skipif(not is_win32(), reason="Windows only")
@skip_unless_feature("jpg")
class TestFileCloseW32:
def test_fd_leak(self, tmp_path):
def test_fd_leak(self, tmp_path: Path) -> None:
tmpfile = str(tmp_path / "temp.jpg")
with Image.open("Tests/images/hopper.jpg") as im:

View File

@ -1,7 +1,10 @@
from __future__ import annotations
import os
import re
from io import BytesIO
from pathlib import Path
from typing import Any
import pytest
@ -34,7 +37,7 @@ test_card.load()
# 'Not enough memory to handle tile data'
def roundtrip(im, **options):
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
out = BytesIO()
im.save(out, "JPEG2000", **options)
test_bytes = out.tell()
@ -45,7 +48,7 @@ def roundtrip(im, **options):
return im
def test_sanity():
def test_sanity() -> None:
# Internal version number
assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("jpg_2000"))
@ -58,20 +61,20 @@ def test_sanity():
assert im.get_format_mimetype() == "image/jp2"
def test_jpf():
def test_jpf() -> None:
with Image.open("Tests/images/balloon.jpf") as im:
assert im.format == "JPEG2000"
assert im.get_format_mimetype() == "image/jpx"
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file)
def test_bytesio():
def test_bytesio() -> None:
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
data = BytesIO(f.read())
assert_image_similar_tofile(test_card, data, 1.0e-3)
@ -81,7 +84,7 @@ def test_bytesio():
# PIL (they were made using Adobe Photoshop)
def test_lossless(tmp_path):
def test_lossless(tmp_path: Path) -> None:
with Image.open("Tests/images/test-card-lossless.jp2") as im:
im.load()
outfile = str(tmp_path / "temp_test-card.png")
@ -89,54 +92,54 @@ def test_lossless(tmp_path):
assert_image_similar(im, test_card, 1.0e-3)
def test_lossy_tiled():
def test_lossy_tiled() -> None:
assert_image_similar_tofile(
test_card, "Tests/images/test-card-lossy-tiled.jp2", 2.0
)
def test_lossless_rt():
def test_lossless_rt() -> None:
im = roundtrip(test_card)
assert_image_equal(im, test_card)
def test_lossy_rt():
def test_lossy_rt() -> None:
im = roundtrip(test_card, quality_layers=[20])
assert_image_similar(im, test_card, 2.0)
def test_tiled_rt():
def test_tiled_rt() -> None:
im = roundtrip(test_card, tile_size=(128, 128))
assert_image_equal(im, test_card)
def test_tiled_offset_rt():
def test_tiled_offset_rt() -> None:
im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32))
assert_image_equal(im, test_card)
def test_tiled_offset_too_small():
def test_tiled_offset_too_small() -> None:
with pytest.raises(ValueError):
roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32))
def test_irreversible_rt():
def test_irreversible_rt() -> None:
im = roundtrip(test_card, irreversible=True, quality_layers=[20])
assert_image_similar(im, test_card, 2.0)
def test_prog_qual_rt():
def test_prog_qual_rt() -> None:
im = roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP")
assert_image_similar(im, test_card, 2.0)
def test_prog_res_rt():
def test_prog_res_rt() -> None:
im = roundtrip(test_card, num_resolutions=8, progression="RLCP")
assert_image_equal(im, test_card)
@pytest.mark.parametrize("num_resolutions", range(2, 6))
def test_default_num_resolutions(num_resolutions):
def test_default_num_resolutions(num_resolutions: int) -> None:
d = 1 << (num_resolutions - 1)
im = test_card.resize((d - 1, d - 1))
with pytest.raises(OSError):
@ -145,7 +148,7 @@ def test_default_num_resolutions(num_resolutions):
assert_image_equal(im, reloaded)
def test_reduce():
def test_reduce() -> None:
with Image.open("Tests/images/test-card-lossless.jp2") as im:
assert callable(im.reduce)
@ -159,7 +162,7 @@ def test_reduce():
assert im.size == (40, 30)
def test_load_dpi():
def test_load_dpi() -> None:
with Image.open("Tests/images/test-card-lossless.jp2") as im:
assert im.info["dpi"] == (71.9836, 71.9836)
@ -167,7 +170,7 @@ def test_load_dpi():
assert "dpi" not in im.info
def test_restricted_icc_profile():
def test_restricted_icc_profile() -> None:
ImageFile.LOAD_TRUNCATED_IMAGES = True
try:
# JPEG2000 image with a restricted ICC profile and a known colorspace
@ -177,7 +180,7 @@ def test_restricted_icc_profile():
ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_header_errors():
def test_header_errors() -> None:
for path in (
"Tests/images/invalid_header_length.jp2",
"Tests/images/not_enough_data.jp2",
@ -191,17 +194,17 @@ def test_header_errors():
pass
def test_layers_type(tmp_path):
def test_layers_type(tmp_path: Path) -> None:
outfile = str(tmp_path / "temp_layers.jp2")
for quality_layers in [[100, 50, 10], (100, 50, 10), None]:
test_card.save(outfile, quality_layers=quality_layers)
for quality_layers in ["quality_layers", ("100", "50", "10")]:
for quality_layers_str in ["quality_layers", ("100", "50", "10")]:
with pytest.raises(ValueError):
test_card.save(outfile, quality_layers=quality_layers)
test_card.save(outfile, quality_layers=quality_layers_str)
def test_layers():
def test_layers() -> None:
out = BytesIO()
test_card.save(out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP")
out.seek(0)
@ -231,7 +234,7 @@ def test_layers():
("foo.jp2", {"no_jp2": False}, 4, b"jP"),
),
)
def test_no_jp2(name, args, offset, data):
def test_no_jp2(name: str, args: dict[str, bool], offset: int, data: bytes) -> None:
out = BytesIO()
if name:
out.name = name
@ -240,7 +243,7 @@ def test_no_jp2(name, args, offset, data):
assert out.read(2) == data
def test_mct():
def test_mct() -> None:
# Three component
for val in (0, 1):
out = BytesIO()
@ -261,7 +264,7 @@ def test_mct():
assert_image_similar(im, jp2, 1.0e-3)
def test_sgnd(tmp_path):
def test_sgnd(tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.jp2")
im = Image.new("L", (1, 1))
@ -276,7 +279,7 @@ def test_sgnd(tmp_path):
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
def test_rgba(ext):
def test_rgba(ext: str) -> None:
# Arrange
with Image.open("Tests/images/rgb_trns_ycbc" + ext) as im:
# Act
@ -287,47 +290,47 @@ def test_rgba(ext):
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
def test_16bit_monochrome_has_correct_mode(ext):
def test_16bit_monochrome_has_correct_mode(ext: str) -> None:
with Image.open("Tests/images/16bit.cropped" + ext) as im:
im.load()
assert im.mode == "I;16"
def test_16bit_monochrome_jp2_like_tiff():
def test_16bit_monochrome_jp2_like_tiff() -> None:
with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit:
assert_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.jp2", 1e-3)
def test_16bit_monochrome_j2k_like_tiff():
def test_16bit_monochrome_j2k_like_tiff() -> None:
with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit:
assert_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.j2k", 1e-3)
def test_16bit_j2k_roundtrips():
def test_16bit_j2k_roundtrips() -> None:
with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
im = roundtrip(j2k)
assert_image_equal(im, j2k)
def test_16bit_jp2_roundtrips():
def test_16bit_jp2_roundtrips() -> None:
with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
im = roundtrip(jp2)
assert_image_equal(im, jp2)
def test_issue_6194():
def test_issue_6194() -> None:
with Image.open("Tests/images/issue_6194.j2k") as im:
assert im.getpixel((5, 5)) == 31
def test_unbound_local():
def test_unbound_local() -> None:
# prepatch, a malformed jp2 file could cause an UnboundLocalError exception.
with pytest.raises(OSError):
with Image.open("Tests/images/unbound_variable.jp2"):
pass
def test_parser_feed():
def test_parser_feed() -> None:
# Arrange
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
data = f.read()
@ -344,12 +347,12 @@ def test_parser_feed():
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
)
@pytest.mark.parametrize("name", ("subsampling_1", "subsampling_2", "zoo1", "zoo2"))
def test_subsampling_decode(name):
def test_subsampling_decode(name: str) -> None:
test = f"{EXTRA_DIR}/{name}.jp2"
reference = f"{EXTRA_DIR}/{name}.ppm"
with Image.open(test) as im:
epsilon = 3 # for YCbCr images
epsilon = 3.0 # for YCbCr images
with Image.open(reference) as im2:
width, height = im2.size
if name[-1] == "2":
@ -360,7 +363,7 @@ def test_subsampling_decode(name):
assert_image_similar(im, expected, epsilon)
def test_comment():
def test_comment() -> None:
with Image.open("Tests/images/comment.jp2") as im:
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
@ -371,7 +374,7 @@ def test_comment():
pass
def test_save_comment():
def test_save_comment() -> None:
for comment in ("Created by Pillow", b"Created by Pillow"):
out = BytesIO()
test_card.save(out, "JPEG2000", comment=comment)
@ -398,7 +401,7 @@ def test_save_comment():
"Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k",
],
)
def test_crashes(test_file):
def test_crashes(test_file: str) -> None:
with open(test_file, "rb") as f:
with Image.open(f) as im:
# Valgrind should not complain here
@ -409,7 +412,7 @@ def test_crashes(test_file):
@skip_unless_feature_version("jpg_2000", "2.4.0")
def test_plt_marker():
def test_plt_marker() -> None:
# Search the start of the codesteam for PLT
out = BytesIO()
test_card.save(out, "JPEG2000", no_jp2=True, plt=True)

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import base64
import io
import itertools
@ -6,6 +7,7 @@ import os
import re
import sys
from collections import namedtuple
from pathlib import Path
import pytest
@ -25,7 +27,7 @@ from .helper import (
@skip_unless_feature("libtiff")
class LibTiffTestCase:
def _assert_noerr(self, tmp_path, im):
def _assert_noerr(self, tmp_path: Path, im: Image.Image) -> None:
"""Helper tests that assert basic sanity about the g4 tiff reading"""
# 1 bit
assert im.mode == "1"
@ -49,10 +51,10 @@ class LibTiffTestCase:
class TestFileLibTiff(LibTiffTestCase):
def test_version(self):
def test_version(self) -> None:
assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff"))
def test_g4_tiff(self, tmp_path):
def test_g4_tiff(self, tmp_path: Path) -> None:
"""Test the ordinary file path load path"""
test_file = "Tests/images/hopper_g4_500.tif"
@ -60,12 +62,12 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.size == (500, 500)
self._assert_noerr(tmp_path, im)
def test_g4_large(self, tmp_path):
def test_g4_large(self, tmp_path: Path) -> None:
test_file = "Tests/images/pport_g4.tif"
with Image.open(test_file) as im:
self._assert_noerr(tmp_path, im)
def test_g4_tiff_file(self, tmp_path):
def test_g4_tiff_file(self, tmp_path: Path) -> None:
"""Testing the string load path"""
test_file = "Tests/images/hopper_g4_500.tif"
@ -74,7 +76,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.size == (500, 500)
self._assert_noerr(tmp_path, im)
def test_g4_tiff_bytesio(self, tmp_path):
def test_g4_tiff_bytesio(self, tmp_path: Path) -> None:
"""Testing the stringio loading code path"""
test_file = "Tests/images/hopper_g4_500.tif"
s = io.BytesIO()
@ -85,7 +87,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.size == (500, 500)
self._assert_noerr(tmp_path, im)
def test_g4_non_disk_file_object(self, tmp_path):
def test_g4_non_disk_file_object(self, tmp_path: Path) -> None:
"""Testing loading from non-disk non-BytesIO file object"""
test_file = "Tests/images/hopper_g4_500.tif"
s = io.BytesIO()
@ -97,18 +99,18 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.size == (500, 500)
self._assert_noerr(tmp_path, im)
def test_g4_eq_png(self):
def test_g4_eq_png(self) -> None:
"""Checking that we're actually getting the data that we expect"""
with Image.open("Tests/images/hopper_bw_500.png") as png:
assert_image_equal_tofile(png, "Tests/images/hopper_g4_500.tif")
# see https://github.com/python-pillow/Pillow/issues/279
def test_g4_fillorder_eq_png(self):
def test_g4_fillorder_eq_png(self) -> None:
"""Checking that we're actually getting the data that we expect"""
with Image.open("Tests/images/g4-fillorder-test.tif") as g4:
assert_image_equal_tofile(g4, "Tests/images/g4-fillorder-test.png")
def test_g4_write(self, tmp_path):
def test_g4_write(self, tmp_path: Path) -> None:
"""Checking to see that the saved image is the same as what we wrote"""
test_file = "Tests/images/hopper_g4_500.tif"
with Image.open(test_file) as orig:
@ -127,7 +129,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert orig.tobytes() != reread.tobytes()
def test_adobe_deflate_tiff(self):
def test_adobe_deflate_tiff(self) -> None:
test_file = "Tests/images/tiff_adobe_deflate.tif"
with Image.open(test_file) as im:
assert im.mode == "RGB"
@ -138,7 +140,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
@pytest.mark.parametrize("legacy_api", (False, True))
def test_write_metadata(self, legacy_api, tmp_path):
def test_write_metadata(self, legacy_api: bool, tmp_path: Path) -> None:
"""Test metadata writing through libtiff"""
f = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper_g4.tif") as img:
@ -183,7 +185,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert field in reloaded, f"{field} not in metadata"
@pytest.mark.valgrind_known_error(reason="Known invalid metadata")
def test_additional_metadata(self, tmp_path):
def test_additional_metadata(self, tmp_path: Path) -> None:
# these should not crash. Seriously dummy data, most of it doesn't make
# any sense, so we're running up against limits where we're asking
# libtiff to do stupid things.
@ -240,8 +242,8 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.WRITE_LIBTIFF = False
def test_custom_metadata(self, tmp_path):
tc = namedtuple("test_case", "value,type,supported_by_default")
def test_custom_metadata(self, tmp_path: Path) -> None:
tc = namedtuple("tc", "value,type,supported_by_default")
custom = {
37000 + k: v
for k, v in enumerate(
@ -282,7 +284,9 @@ class TestFileLibTiff(LibTiffTestCase):
for libtiff in libtiffs:
TiffImagePlugin.WRITE_LIBTIFF = libtiff
def check_tags(tiffinfo):
def check_tags(
tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str]
) -> None:
im = hopper()
out = str(tmp_path / "temp.tif")
@ -321,7 +325,7 @@ class TestFileLibTiff(LibTiffTestCase):
)
TiffImagePlugin.WRITE_LIBTIFF = False
def test_subifd(self, tmp_path):
def test_subifd(self, tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/g4_orientation_6.tif") as im:
im.tag_v2[SUBIFD] = 10000
@ -329,7 +333,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not segfault
im.save(outfile)
def test_xmlpacket_tag(self, tmp_path):
def test_xmlpacket_tag(self, tmp_path: Path) -> None:
TiffImagePlugin.WRITE_LIBTIFF = True
out = str(tmp_path / "temp.tif")
@ -340,7 +344,7 @@ class TestFileLibTiff(LibTiffTestCase):
if 700 in reloaded.tag_v2:
assert reloaded.tag_v2[700] == b"xmlpacket tag"
def test_int_dpi(self, tmp_path):
def test_int_dpi(self, tmp_path: Path) -> None:
# issue #1765
im = hopper("RGB")
out = str(tmp_path / "temp.tif")
@ -350,7 +354,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as reloaded:
assert reloaded.info["dpi"] == (72.0, 72.0)
def test_g3_compression(self, tmp_path):
def test_g3_compression(self, tmp_path: Path) -> None:
with Image.open("Tests/images/hopper_g4_500.tif") as i:
out = str(tmp_path / "temp.tif")
i.save(out, compression="group3")
@ -359,7 +363,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert reread.info["compression"] == "group3"
assert_image_equal(reread, i)
def test_little_endian(self, tmp_path):
def test_little_endian(self, tmp_path: Path) -> None:
with Image.open("Tests/images/16bit.deflate.tif") as im:
assert im.getpixel((0, 0)) == 480
assert im.mode == "I;16"
@ -378,7 +382,7 @@ class TestFileLibTiff(LibTiffTestCase):
# UNDONE - libtiff defaults to writing in native endian, so
# on big endian, we'll get back mode = 'I;16B' here.
def test_big_endian(self, tmp_path):
def test_big_endian(self, tmp_path: Path) -> None:
with Image.open("Tests/images/16bit.MM.deflate.tif") as im:
assert im.getpixel((0, 0)) == 480
assert im.mode == "I;16B"
@ -395,7 +399,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert reread.info["compression"] == im.info["compression"]
assert reread.getpixel((0, 0)) == 480
def test_g4_string_info(self, tmp_path):
def test_g4_string_info(self, tmp_path: Path) -> None:
"""Tests String data in info directory"""
test_file = "Tests/images/hopper_g4_500.tif"
with Image.open(test_file) as orig:
@ -408,7 +412,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert "temp.tif" == reread.tag_v2[269]
assert "temp.tif" == reread.tag[269][0]
def test_12bit_rawmode(self):
def test_12bit_rawmode(self) -> None:
"""Are we generating the same interpretation
of the image as Imagemagick is?"""
TiffImagePlugin.READ_LIBTIFF = True
@ -423,7 +427,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/12in16bit.tif")
def test_blur(self, tmp_path):
def test_blur(self, tmp_path: Path) -> None:
# test case from irc, how to do blur on b/w image
# and save to compressed tif.
out = str(tmp_path / "temp.tif")
@ -435,7 +439,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, out)
def test_compressions(self, tmp_path):
def test_compressions(self, tmp_path: Path) -> None:
# Test various tiff compressions and assert similar image content but reduced
# file sizes.
im = hopper("RGB")
@ -461,7 +465,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert size_compressed > size_jpeg
assert size_jpeg > size_jpeg_30
def test_tiff_jpeg_compression(self, tmp_path):
def test_tiff_jpeg_compression(self, tmp_path: Path) -> None:
im = hopper("RGB")
out = str(tmp_path / "temp.tif")
im.save(out, compression="tiff_jpeg")
@ -469,7 +473,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as reloaded:
assert reloaded.info["compression"] == "jpeg"
def test_tiff_deflate_compression(self, tmp_path):
def test_tiff_deflate_compression(self, tmp_path: Path) -> None:
im = hopper("RGB")
out = str(tmp_path / "temp.tif")
im.save(out, compression="tiff_deflate")
@ -477,7 +481,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as reloaded:
assert reloaded.info["compression"] == "tiff_adobe_deflate"
def test_quality(self, tmp_path):
def test_quality(self, tmp_path: Path) -> None:
im = hopper("RGB")
out = str(tmp_path / "temp.tif")
@ -492,7 +496,7 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(out, compression="jpeg", quality=0)
im.save(out, compression="jpeg", quality=100)
def test_cmyk_save(self, tmp_path):
def test_cmyk_save(self, tmp_path: Path) -> None:
im = hopper("CMYK")
out = str(tmp_path / "temp.tif")
@ -500,7 +504,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, out)
@pytest.mark.parametrize("im", (hopper("P"), Image.new("P", (1, 1), "#000")))
def test_palette_save(self, im, tmp_path):
def test_palette_save(self, im: Image.Image, tmp_path: Path) -> None:
out = str(tmp_path / "temp.tif")
TiffImagePlugin.WRITE_LIBTIFF = True
@ -512,14 +516,14 @@ class TestFileLibTiff(LibTiffTestCase):
assert len(reloaded.tag_v2[320]) == 768
@pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4"))
def test_bw_compression_w_rgb(self, compression, tmp_path):
def test_bw_compression_w_rgb(self, compression: str, tmp_path: Path) -> None:
im = hopper("RGB")
out = str(tmp_path / "temp.tif")
with pytest.raises(OSError):
im.save(out, compression=compression)
def test_fp_leak(self):
def test_fp_leak(self) -> None:
im = Image.open("Tests/images/hopper_g4_500.tif")
fn = im.fp.fileno()
@ -533,7 +537,7 @@ class TestFileLibTiff(LibTiffTestCase):
with pytest.raises(OSError):
os.close(fn)
def test_multipage(self):
def test_multipage(self) -> None:
# issue #862
TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/multipage.tiff") as im:
@ -556,7 +560,7 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False
def test_multipage_nframes(self):
def test_multipage_nframes(self) -> None:
# issue #862
TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/multipage.tiff") as im:
@ -569,7 +573,7 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False
def test_multipage_seek_backwards(self):
def test_multipage_seek_backwards(self) -> None:
TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/multipage.tiff") as im:
im.seek(1)
@ -580,14 +584,14 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False
def test__next(self):
def test__next(self) -> None:
TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/hopper.tif") as im:
assert not im.tag.next
im.load()
assert not im.tag.next
def test_4bit(self):
def test_4bit(self) -> None:
# Arrange
test_file = "Tests/images/hopper_gray_4bpp.tif"
original = hopper("L")
@ -602,7 +606,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.mode == "L"
assert_image_similar(im, original, 7.3)
def test_gray_semibyte_per_pixel(self):
def test_gray_semibyte_per_pixel(self) -> None:
test_files = (
(
24.8, # epsilon
@ -635,7 +639,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert im2.mode == "L"
assert_image_equal(im, im2)
def test_save_bytesio(self):
def test_save_bytesio(self) -> None:
# PR 1011
# Test TIFF saving to io.BytesIO() object.
@ -645,7 +649,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Generate test image
pilim = hopper()
def save_bytesio(compression=None):
def save_bytesio(compression: str | None = None) -> None:
buffer_io = io.BytesIO()
pilim.save(buffer_io, format="tiff", compression=compression)
buffer_io.seek(0)
@ -660,7 +664,7 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.WRITE_LIBTIFF = False
TiffImagePlugin.READ_LIBTIFF = False
def test_save_ycbcr(self, tmp_path):
def test_save_ycbcr(self, tmp_path: Path) -> None:
im = hopper("YCbCr")
outfile = str(tmp_path / "temp.tif")
im.save(outfile, compression="jpeg")
@ -669,7 +673,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert reloaded.tag_v2[530] == (1, 1)
assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255)
def test_exif_ifd(self, tmp_path):
def test_exif_ifd(self, tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/tiff_adobe_deflate.tif") as im:
assert im.tag_v2[34665] == 125456
@ -679,7 +683,7 @@ class TestFileLibTiff(LibTiffTestCase):
if Image.core.libtiff_support_custom_tags:
assert reloaded.tag_v2[34665] == 125456
def test_crashing_metadata(self, tmp_path):
def test_crashing_metadata(self, tmp_path: Path) -> None:
# issue 1597
with Image.open("Tests/images/rdf.tif") as im:
out = str(tmp_path / "temp.tif")
@ -689,7 +693,7 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(out, format="TIFF")
TiffImagePlugin.WRITE_LIBTIFF = False
def test_page_number_x_0(self, tmp_path):
def test_page_number_x_0(self, tmp_path: Path) -> None:
# Issue 973
# Test TIFF with tag 297 (Page Number) having value of 0 0.
# The first number is the current page number.
@ -703,7 +707,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not divide by zero
im.save(outfile)
def test_fd_duplication(self, tmp_path):
def test_fd_duplication(self, tmp_path: Path) -> None:
# https://github.com/python-pillow/Pillow/issues/1651
tmpfile = str(tmp_path / "temp.tif")
@ -717,7 +721,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not raise PermissionError.
os.remove(tmpfile)
def test_read_icc(self):
def test_read_icc(self) -> None:
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
icc = img.info.get("icc_profile")
assert icc is not None
@ -728,8 +732,8 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False
assert icc == icc_libtiff
def test_write_icc(self, tmp_path):
def check_write(libtiff):
def test_write_icc(self, tmp_path: Path) -> None:
def check_write(libtiff: bool) -> None:
TiffImagePlugin.WRITE_LIBTIFF = libtiff
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
@ -748,7 +752,7 @@ class TestFileLibTiff(LibTiffTestCase):
for libtiff in libtiffs:
check_write(libtiff)
def test_multipage_compression(self):
def test_multipage_compression(self) -> None:
with Image.open("Tests/images/compression.tif") as im:
im.seek(0)
assert im._compression == "tiff_ccitt"
@ -764,7 +768,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.size == (10, 10)
im.load()
def test_save_tiff_with_jpegtables(self, tmp_path):
def test_save_tiff_with_jpegtables(self, tmp_path: Path) -> None:
# Arrange
outfile = str(tmp_path / "temp.tif")
@ -776,7 +780,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not raise UnicodeDecodeError or anything else
im.save(outfile)
def test_16bit_RGB_tiff(self):
def test_16bit_RGB_tiff(self) -> None:
with Image.open("Tests/images/tiff_16bit_RGB.tiff") as im:
assert im.mode == "RGB"
assert im.size == (100, 40)
@ -792,7 +796,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
def test_16bit_RGBa_tiff(self):
def test_16bit_RGBa_tiff(self) -> None:
with Image.open("Tests/images/tiff_16bit_RGBa.tiff") as im:
assert im.mode == "RGBA"
assert im.size == (100, 40)
@ -804,7 +808,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
@skip_unless_feature("jpg")
def test_gimp_tiff(self):
def test_gimp_tiff(self) -> None:
# Read TIFF JPEG images from GIMP [@PIL168]
filename = "Tests/images/pil168.tif"
with Image.open(filename) as im:
@ -817,14 +821,14 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/pil168.png")
def test_sampleformat(self):
def test_sampleformat(self) -> None:
# https://github.com/python-pillow/Pillow/issues/1466
with Image.open("Tests/images/copyleft.tiff") as im:
assert im.mode == "RGB"
assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB")
def test_sampleformat_write(self, tmp_path):
def test_sampleformat_write(self, tmp_path: Path) -> None:
im = Image.new("F", (1, 1))
out = str(tmp_path / "temp.tif")
TiffImagePlugin.WRITE_LIBTIFF = True
@ -835,7 +839,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert reloaded.mode == "F"
assert reloaded.getexif()[SAMPLEFORMAT] == 3
def test_lzma(self, capfd):
def test_lzma(self, capfd: pytest.CaptureFixture[str]) -> None:
try:
with Image.open("Tests/images/hopper_lzma.tif") as im:
assert im.mode == "RGB"
@ -851,7 +855,7 @@ class TestFileLibTiff(LibTiffTestCase):
sys.stderr.write(captured.err)
raise
def test_webp(self, capfd):
def test_webp(self, capfd: pytest.CaptureFixture[str]) -> None:
try:
with Image.open("Tests/images/hopper_webp.tif") as im:
assert im.mode == "RGB"
@ -873,7 +877,7 @@ class TestFileLibTiff(LibTiffTestCase):
sys.stderr.write(captured.err)
raise
def test_lzw(self):
def test_lzw(self) -> None:
with Image.open("Tests/images/hopper_lzw.tif") as im:
assert im.mode == "RGB"
assert im.size == (128, 128)
@ -881,12 +885,12 @@ class TestFileLibTiff(LibTiffTestCase):
im2 = hopper()
assert_image_similar(im, im2, 5)
def test_strip_cmyk_jpeg(self):
def test_strip_cmyk_jpeg(self) -> None:
infile = "Tests/images/tiff_strip_cmyk_jpeg.tif"
with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
def test_strip_cmyk_16l_jpeg(self):
def test_strip_cmyk_16l_jpeg(self) -> None:
infile = "Tests/images/tiff_strip_cmyk_16l_jpeg.tif"
with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
@ -894,7 +898,7 @@ class TestFileLibTiff(LibTiffTestCase):
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_strip_ycbcr_jpeg_2x2_sampling(self):
def test_strip_ycbcr_jpeg_2x2_sampling(self) -> None:
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.2)
@ -902,12 +906,12 @@ class TestFileLibTiff(LibTiffTestCase):
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_strip_ycbcr_jpeg_1x1_sampling(self):
def test_strip_ycbcr_jpeg_1x1_sampling(self) -> None:
infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01)
def test_tiled_cmyk_jpeg(self):
def test_tiled_cmyk_jpeg(self) -> None:
infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif"
with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
@ -915,7 +919,7 @@ class TestFileLibTiff(LibTiffTestCase):
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_tiled_ycbcr_jpeg_1x1_sampling(self):
def test_tiled_ycbcr_jpeg_1x1_sampling(self) -> None:
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01)
@ -923,45 +927,45 @@ class TestFileLibTiff(LibTiffTestCase):
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_tiled_ycbcr_jpeg_2x2_sampling(self):
def test_tiled_ycbcr_jpeg_2x2_sampling(self) -> None:
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.5)
def test_strip_planar_rgb(self):
def test_strip_planar_rgb(self) -> None:
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_strip_raw.tif tiff_strip_planar_lzw.tiff
infile = "Tests/images/tiff_strip_planar_lzw.tiff"
with Image.open(infile) as im:
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
def test_tiled_planar_rgb(self):
def test_tiled_planar_rgb(self) -> None:
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff
infile = "Tests/images/tiff_tiled_planar_lzw.tiff"
with Image.open(infile) as im:
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
def test_tiled_planar_16bit_RGB(self):
def test_tiled_planar_16bit_RGB(self) -> None:
# gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im:
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
def test_strip_planar_16bit_RGB(self):
def test_strip_planar_16bit_RGB(self) -> None:
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff
with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im:
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png")
def test_tiled_planar_16bit_RGBa(self):
def test_tiled_planar_16bit_RGBa(self) -> None:
# gdal_translate -co TILED=yes \
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
# tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im:
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
def test_strip_planar_16bit_RGBa(self):
def test_strip_planar_16bit_RGBa(self) -> None:
# gdal_translate -co TILED=no \
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
# tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff
@ -969,7 +973,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
@pytest.mark.parametrize("compression", (None, "jpeg"))
def test_block_tile_tags(self, compression, tmp_path):
def test_block_tile_tags(self, compression: str | None, tmp_path: Path) -> None:
im = hopper()
out = str(tmp_path / "temp.tif")
@ -985,11 +989,11 @@ class TestFileLibTiff(LibTiffTestCase):
for tag in tags:
assert tag not in reloaded.getexif()
def test_old_style_jpeg(self):
def test_old_style_jpeg(self) -> None:
with Image.open("Tests/images/old-style-jpeg-compression.tif") as im:
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
def test_open_missing_samplesperpixel(self):
def test_open_missing_samplesperpixel(self) -> None:
with Image.open(
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif"
) as im:
@ -1018,21 +1022,23 @@ class TestFileLibTiff(LibTiffTestCase):
),
],
)
def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
def test_wrong_bits_per_sample(
self, file_name: str, mode: str, size: tuple[int, int], tile
) -> None:
with Image.open("Tests/images/" + file_name) as im:
assert im.mode == mode
assert im.size == size
assert im.tile == tile
im.load()
def test_no_rows_per_strip(self):
def test_no_rows_per_strip(self) -> None:
# This image does not have a RowsPerStrip TIFF tag
infile = "Tests/images/no_rows_per_strip.tif"
with Image.open(infile) as im:
im.load()
assert im.size == (950, 975)
def test_orientation(self):
def test_orientation(self) -> None:
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
for i in range(2, 9):
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
@ -1043,7 +1049,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_similar(base_im, im, 0.7)
def test_exif_transpose(self):
def test_exif_transpose(self) -> None:
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
for i in range(2, 9):
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
@ -1052,7 +1058,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_similar(base_im, im, 0.7)
@pytest.mark.valgrind_known_error(reason="Backtrace in Python Core")
def test_sampleformat_not_corrupted(self):
def test_sampleformat_not_corrupted(self) -> None:
# Assert that a TIFF image with SampleFormat=UINT tag is not corrupted
# when saving to a new file.
# Pillow 6.0 fails with "OSError: cannot identify image file".
@ -1073,7 +1079,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as im:
im.load()
def test_realloc_overflow(self):
def test_realloc_overflow(self) -> None:
TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im:
with pytest.raises(OSError) as e:
@ -1084,7 +1090,7 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False
@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg"))
def test_save_multistrip(self, compression, tmp_path):
def test_save_multistrip(self, compression: str, tmp_path: Path) -> None:
im = hopper("RGB").resize((256, 256))
out = str(tmp_path / "temp.tif")
im.save(out, compression=compression)
@ -1094,14 +1100,14 @@ class TestFileLibTiff(LibTiffTestCase):
assert len(im.tag_v2[STRIPOFFSETS]) > 1
@pytest.mark.parametrize("argument", (True, False))
def test_save_single_strip(self, argument, tmp_path):
def test_save_single_strip(self, argument: bool, tmp_path: Path) -> None:
im = hopper("RGB").resize((256, 256))
out = str(tmp_path / "temp.tif")
if not argument:
TiffImagePlugin.STRIP_SIZE = 2**18
try:
arguments = {"compression": "tiff_adobe_deflate"}
arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"}
if argument:
arguments["strip_size"] = 2**18
im.save(out, **arguments)
@ -1112,13 +1118,13 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.STRIP_SIZE = 65536
@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None))
def test_save_zero(self, compression, tmp_path):
def test_save_zero(self, compression: str | None, tmp_path: Path) -> None:
im = Image.new("RGB", (0, 0))
out = str(tmp_path / "temp.tif")
with pytest.raises(SystemError):
im.save(out, compression=compression)
def test_save_many_compressed(self, tmp_path):
def test_save_many_compressed(self, tmp_path: Path) -> None:
im = hopper()
out = str(tmp_path / "temp.tif")
for _ in range(10000):
@ -1132,7 +1138,7 @@ class TestFileLibTiff(LibTiffTestCase):
("Tests/images/child_ifd_jpeg.tiff", (20,)),
),
)
def test_get_child_images(self, path, sizes):
def test_get_child_images(self, path: str, sizes: tuple[int, ...]) -> None:
with Image.open(path) as im:
ims = im.get_child_images()

View File

@ -1,5 +1,7 @@
from __future__ import annotations
from io import BytesIO
from pathlib import Path
from PIL import Image
@ -7,7 +9,6 @@ from .test_file_libtiff import LibTiffTestCase
class TestFileLibTiffSmall(LibTiffTestCase):
"""The small lena image was failing on open in the libtiff
decoder because the file pointer was set to the wrong place
by a spurious seek. It wasn't failing with the byteio method.
@ -16,7 +17,7 @@ class TestFileLibTiffSmall(LibTiffTestCase):
file just before reading in libtiff. These tests remain
to ensure that it stays fixed."""
def test_g4_hopper_file(self, tmp_path):
def test_g4_hopper_file(self, tmp_path: Path) -> None:
"""Testing the open file load path"""
test_file = "Tests/images/hopper_g4.tif"
@ -25,7 +26,7 @@ class TestFileLibTiffSmall(LibTiffTestCase):
assert im.size == (128, 128)
self._assert_noerr(tmp_path, im)
def test_g4_hopper_bytesio(self, tmp_path):
def test_g4_hopper_bytesio(self, tmp_path: Path) -> None:
"""Testing the bytesio loading code path"""
test_file = "Tests/images/hopper_g4.tif"
s = BytesIO()
@ -36,7 +37,7 @@ class TestFileLibTiffSmall(LibTiffTestCase):
assert im.size == (128, 128)
self._assert_noerr(tmp_path, im)
def test_g4_hopper(self, tmp_path):
def test_g4_hopper(self, tmp_path: Path) -> None:
"""The 128x128 lena image failed for some reason."""
test_file = "Tests/images/hopper_g4.tif"

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import Image, McIdasImagePlugin
@ -6,14 +7,14 @@ from PIL import Image, McIdasImagePlugin
from .helper import assert_image_equal_tofile
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
McIdasImagePlugin.McIdasImageFile(invalid_file)
def test_valid_file():
def test_valid_file() -> None:
# Arrange
# https://ghrc.nsstc.nasa.gov/hydro/details/cmx3g8
# https://ghrc.nsstc.nasa.gov/pub/fieldCampaigns/camex3/cmx3g8/browse/

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import Image, ImagePalette
@ -12,7 +13,7 @@ pytestmark = skip_unless_feature("libtiff")
TEST_FILE = "Tests/images/hopper.mic"
def test_sanity():
def test_sanity() -> None:
with Image.open(TEST_FILE) as im:
im.load()
assert im.mode == "RGBA"
@ -27,22 +28,22 @@ def test_sanity():
assert_image_similar(im, im2, 10)
def test_n_frames():
def test_n_frames() -> None:
with Image.open(TEST_FILE) as im:
assert im.n_frames == 1
def test_is_animated():
def test_is_animated() -> None:
with Image.open(TEST_FILE) as im:
assert not im.is_animated
def test_tell():
def test_tell() -> None:
with Image.open(TEST_FILE) as im:
assert im.tell() == 0
def test_seek():
def test_seek() -> None:
with Image.open(TEST_FILE) as im:
im.seek(0)
assert im.tell() == 0
@ -52,7 +53,7 @@ def test_seek():
assert im.tell() == 0
def test_close():
def test_close() -> None:
with Image.open(TEST_FILE) as im:
pass
assert im.ole.fp.closed
@ -62,7 +63,7 @@ def test_close():
assert im.ole.fp.closed
def test_invalid_file():
def test_invalid_file() -> None:
# Test an invalid OLE file
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):

View File

@ -1,6 +1,8 @@
from __future__ import annotations
import warnings
from io import BytesIO
from typing import Any
import pytest
@ -18,7 +20,7 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"]
pytestmark = skip_unless_feature("jpg")
def roundtrip(im, **options):
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
out = BytesIO()
im.save(out, "MPO", **options)
test_bytes = out.tell()
@ -29,7 +31,7 @@ def roundtrip(im, **options):
@pytest.mark.parametrize("test_file", test_files)
def test_sanity(test_file):
def test_sanity(test_file: str) -> None:
with Image.open(test_file) as im:
im.load()
assert im.mode == "RGB"
@ -38,8 +40,8 @@ def test_sanity(test_file):
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file():
def open():
def test_unclosed_file() -> None:
def open() -> None:
im = Image.open(test_files[0])
im.load()
@ -47,14 +49,14 @@ def test_unclosed_file():
open()
def test_closed_file():
def test_closed_file() -> None:
with warnings.catch_warnings():
im = Image.open(test_files[0])
im.load()
im.close()
def test_seek_after_close():
def test_seek_after_close() -> None:
im = Image.open(test_files[0])
im.close()
@ -62,14 +64,14 @@ def test_seek_after_close():
im.seek(1)
def test_context_manager():
def test_context_manager() -> None:
with warnings.catch_warnings():
with Image.open(test_files[0]) as im:
im.load()
@pytest.mark.parametrize("test_file", test_files)
def test_app(test_file):
def test_app(test_file: str) -> None:
# Test APP/COM reader (@PIL135)
with Image.open(test_file) as im:
assert im.applist[0][0] == "APP1"
@ -81,7 +83,7 @@ def test_app(test_file):
@pytest.mark.parametrize("test_file", test_files)
def test_exif(test_file):
def test_exif(test_file: str) -> None:
with Image.open(test_file) as im_original:
im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif())
@ -92,7 +94,7 @@ def test_exif(test_file):
assert info[34665] == 188
def test_frame_size():
def test_frame_size() -> None:
# This image has been hexedited to contain a different size
# in the EXIF data of the second frame
with Image.open("Tests/images/sugarshack_frame_size.mpo") as im:
@ -105,7 +107,7 @@ def test_frame_size():
assert im.size == (640, 480)
def test_ignore_frame_size():
def test_ignore_frame_size() -> None:
# Ignore the different size of the second frame
# since this is not a "Large Thumbnail" image
with Image.open("Tests/images/ignore_frame_size.mpo") as im:
@ -119,7 +121,7 @@ def test_ignore_frame_size():
assert im.size == (64, 64)
def test_parallax():
def test_parallax() -> None:
# Nintendo
with Image.open("Tests/images/sugarshack.mpo") as im:
exif = im.getexif()
@ -132,7 +134,7 @@ def test_parallax():
assert exif.get_ifd(0x927C)[0xB211] == -3.125
def test_reload_exif_after_seek():
def test_reload_exif_after_seek() -> None:
with Image.open("Tests/images/sugarshack.mpo") as im:
exif = im.getexif()
del exif[296]
@ -142,14 +144,14 @@ def test_reload_exif_after_seek():
@pytest.mark.parametrize("test_file", test_files)
def test_mp(test_file):
def test_mp(test_file: str) -> None:
with Image.open(test_file) as im:
mpinfo = im._getmp()
assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2
def test_mp_offset():
def test_mp_offset() -> None:
# This image has been manually hexedited to have an IFD offset of 10
# in APP2 data, in contrast to normal 8
with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im:
@ -158,7 +160,7 @@ def test_mp_offset():
assert mpinfo[45057] == 2
def test_mp_no_data():
def test_mp_no_data() -> None:
# This image has been manually hexedited to have the second frame
# beyond the end of the file
with Image.open("Tests/images/sugarshack_no_data.mpo") as im:
@ -167,7 +169,7 @@ def test_mp_no_data():
@pytest.mark.parametrize("test_file", test_files)
def test_mp_attribute(test_file):
def test_mp_attribute(test_file: str) -> None:
with Image.open(test_file) as im:
mpinfo = im._getmp()
for frame_number, mpentry in enumerate(mpinfo[0xB002]):
@ -184,7 +186,7 @@ def test_mp_attribute(test_file):
@pytest.mark.parametrize("test_file", test_files)
def test_seek(test_file):
def test_seek(test_file: str) -> None:
with Image.open(test_file) as im:
assert im.tell() == 0
# prior to first image raises an error, both blatant and borderline
@ -208,13 +210,13 @@ def test_seek(test_file):
assert im.tell() == 0
def test_n_frames():
def test_n_frames() -> None:
with Image.open("Tests/images/sugarshack.mpo") as im:
assert im.n_frames == 2
assert im.is_animated
def test_eoferror():
def test_eoferror() -> None:
with Image.open("Tests/images/sugarshack.mpo") as im:
n_frames = im.n_frames
@ -228,7 +230,7 @@ def test_eoferror():
@pytest.mark.parametrize("test_file", test_files)
def test_image_grab(test_file):
def test_image_grab(test_file: str) -> None:
with Image.open(test_file) as im:
assert im.tell() == 0
im0 = im.tobytes()
@ -243,7 +245,7 @@ def test_image_grab(test_file):
@pytest.mark.parametrize("test_file", test_files)
def test_save(test_file):
def test_save(test_file: str) -> None:
with Image.open(test_file) as im:
assert im.tell() == 0
jpg0 = roundtrip(im)
@ -254,7 +256,7 @@ def test_save(test_file):
assert_image_similar(im, jpg1, 30)
def test_save_all():
def test_save_all() -> None:
for test_file in test_files:
with Image.open(test_file) as im:
im_reloaded = roundtrip(im, save_all=True)

View File

@ -1,5 +1,7 @@
from __future__ import annotations
import os
from pathlib import Path
import pytest
@ -12,7 +14,7 @@ EXTRA_DIR = "Tests/images/picins"
YA_EXTRA_DIR = "Tests/images/msp"
def test_sanity(tmp_path):
def test_sanity(tmp_path: Path) -> None:
test_file = str(tmp_path / "temp.msp")
hopper("1").save(test_file)
@ -24,14 +26,14 @@ def test_sanity(tmp_path):
assert im.format == "MSP"
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
MspImagePlugin.MspImageFile(invalid_file)
def test_bad_checksum():
def test_bad_checksum() -> None:
# Arrange
# This was created by forcing Pillow to save with checksum=0
bad_checksum = "Tests/images/hopper_bad_checksum.msp"
@ -41,7 +43,7 @@ def test_bad_checksum():
MspImagePlugin.MspImageFile(bad_checksum)
def test_open_windows_v1():
def test_open_windows_v1() -> None:
# Arrange
# Act
with Image.open(TEST_FILE) as im:
@ -50,7 +52,7 @@ def test_open_windows_v1():
assert isinstance(im, MspImagePlugin.MspImageFile)
def _assert_file_image_equal(source_path, target_path):
def _assert_file_image_equal(source_path, target_path) -> None:
with Image.open(source_path) as im:
assert_image_equal_tofile(im, target_path)
@ -58,7 +60,7 @@ def _assert_file_image_equal(source_path, target_path):
@pytest.mark.skipif(
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
)
def test_open_windows_v2():
def test_open_windows_v2() -> None:
files = (
os.path.join(EXTRA_DIR, f)
for f in os.listdir(EXTRA_DIR)
@ -71,7 +73,7 @@ def test_open_windows_v2():
@pytest.mark.skipif(
not os.path.exists(YA_EXTRA_DIR), reason="Even More Extra image files not installed"
)
def test_msp_v2():
def test_msp_v2() -> None:
for f in os.listdir(YA_EXTRA_DIR):
if ".MSP" not in f:
continue
@ -79,7 +81,7 @@ def test_msp_v2():
_assert_file_image_equal(path, path.replace(".MSP", ".png"))
def test_cannot_save_wrong_mode(tmp_path):
def test_cannot_save_wrong_mode(tmp_path: Path) -> None:
# Arrange
im = hopper()
filename = str(tmp_path / "temp.msp")

View File

@ -1,6 +1,8 @@
from __future__ import annotations
import os.path
import subprocess
from pathlib import Path
import pytest
@ -9,7 +11,7 @@ from PIL import Image
from .helper import assert_image_equal, hopper, magick_command
def helper_save_as_palm(tmp_path, mode):
def helper_save_as_palm(tmp_path: Path, mode: str) -> None:
# Arrange
im = hopper(mode)
outfile = str(tmp_path / ("temp_" + mode + ".palm"))
@ -22,7 +24,7 @@ def helper_save_as_palm(tmp_path, mode):
assert os.path.getsize(outfile) > 0
def open_with_magick(magick, tmp_path, f):
def open_with_magick(magick: list[str], tmp_path: Path, f: str) -> Image.Image:
outfile = str(tmp_path / "temp.png")
rc = subprocess.call(
magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
@ -31,7 +33,7 @@ def open_with_magick(magick, tmp_path, f):
return Image.open(outfile)
def roundtrip(tmp_path, mode):
def roundtrip(tmp_path: Path, mode: str) -> None:
magick = magick_command()
if not magick:
return
@ -44,7 +46,7 @@ def roundtrip(tmp_path, mode):
assert_image_equal(converted, im)
def test_monochrome(tmp_path):
def test_monochrome(tmp_path: Path) -> None:
# Arrange
mode = "1"
@ -54,7 +56,7 @@ def test_monochrome(tmp_path):
@pytest.mark.xfail(reason="Palm P image is wrong")
def test_p_mode(tmp_path):
def test_p_mode(tmp_path: Path) -> None:
# Arrange
mode = "P"
@ -64,6 +66,6 @@ def test_p_mode(tmp_path):
@pytest.mark.parametrize("mode", ("L", "RGB"))
def test_oserror(tmp_path, mode):
def test_oserror(tmp_path: Path, mode: str) -> None:
with pytest.raises(OSError):
helper_save_as_palm(tmp_path, mode)

View File

@ -1,8 +1,9 @@
from __future__ import annotations
from PIL import Image
def test_load_raw():
def test_load_raw() -> None:
with Image.open("Tests/images/hopper.pcd") as im:
im.load() # should not segfault.

View File

@ -1,4 +1,7 @@
from __future__ import annotations
from pathlib import Path
import pytest
from PIL import Image, ImageFile, PcxImagePlugin
@ -6,7 +9,7 @@ from PIL import Image, ImageFile, PcxImagePlugin
from .helper import assert_image_equal, hopper
def _roundtrip(tmp_path, im):
def _roundtrip(tmp_path: Path, im) -> None:
f = str(tmp_path / "temp.pcx")
im.save(f)
with Image.open(f) as im2:
@ -17,7 +20,7 @@ def _roundtrip(tmp_path, im):
assert_image_equal(im2, im)
def test_sanity(tmp_path):
def test_sanity(tmp_path: Path) -> None:
for mode in ("1", "L", "P", "RGB"):
_roundtrip(tmp_path, hopper(mode))
@ -33,7 +36,7 @@ def test_sanity(tmp_path):
im.save(f)
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
@ -41,7 +44,7 @@ def test_invalid_file():
@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB"))
def test_odd(tmp_path, mode):
def test_odd(tmp_path: Path, mode) -> None:
# See issue #523, odd sized images should have a stride that's even.
# Not that ImageMagick or GIMP write PCX that way.
# We were not handling properly.
@ -50,7 +53,7 @@ def test_odd(tmp_path, mode):
_roundtrip(tmp_path, hopper(mode).resize((511, 511)))
def test_odd_read():
def test_odd_read() -> None:
# Reading an image with an odd stride, making it malformed
with Image.open("Tests/images/odd_stride.pcx") as im:
im.load()
@ -58,7 +61,7 @@ def test_odd_read():
assert im.size == (371, 150)
def test_pil184():
def test_pil184() -> None:
# Check reading of files where xmin/xmax is not zero.
test_file = "Tests/images/pil184.pcx"
@ -70,7 +73,7 @@ def test_pil184():
assert im.histogram()[0] + im.histogram()[255] == 447 * 144
def test_1px_width(tmp_path):
def test_1px_width(tmp_path: Path) -> None:
im = Image.new("L", (1, 256))
px = im.load()
for y in range(256):
@ -78,7 +81,7 @@ def test_1px_width(tmp_path):
_roundtrip(tmp_path, im)
def test_large_count(tmp_path):
def test_large_count(tmp_path: Path) -> None:
im = Image.new("L", (256, 1))
px = im.load()
for x in range(256):
@ -86,7 +89,7 @@ def test_large_count(tmp_path):
_roundtrip(tmp_path, im)
def _test_buffer_overflow(tmp_path, im, size=1024):
def _test_buffer_overflow(tmp_path: Path, im, size: int = 1024) -> None:
_last = ImageFile.MAXBLOCK
ImageFile.MAXBLOCK = size
try:
@ -95,7 +98,7 @@ def _test_buffer_overflow(tmp_path, im, size=1024):
ImageFile.MAXBLOCK = _last
def test_break_in_count_overflow(tmp_path):
def test_break_in_count_overflow(tmp_path: Path) -> None:
im = Image.new("L", (256, 5))
px = im.load()
for y in range(4):
@ -104,7 +107,7 @@ def test_break_in_count_overflow(tmp_path):
_test_buffer_overflow(tmp_path, im)
def test_break_one_in_loop(tmp_path):
def test_break_one_in_loop(tmp_path: Path) -> None:
im = Image.new("L", (256, 5))
px = im.load()
for y in range(5):
@ -113,7 +116,7 @@ def test_break_one_in_loop(tmp_path):
_test_buffer_overflow(tmp_path, im)
def test_break_many_in_loop(tmp_path):
def test_break_many_in_loop(tmp_path: Path) -> None:
im = Image.new("L", (256, 5))
px = im.load()
for y in range(4):
@ -124,7 +127,7 @@ def test_break_many_in_loop(tmp_path):
_test_buffer_overflow(tmp_path, im)
def test_break_one_at_end(tmp_path):
def test_break_one_at_end(tmp_path: Path) -> None:
im = Image.new("L", (256, 5))
px = im.load()
for y in range(5):
@ -134,7 +137,7 @@ def test_break_one_at_end(tmp_path):
_test_buffer_overflow(tmp_path, im)
def test_break_many_at_end(tmp_path):
def test_break_many_at_end(tmp_path: Path) -> None:
im = Image.new("L", (256, 5))
px = im.load()
for y in range(5):
@ -146,7 +149,7 @@ def test_break_many_at_end(tmp_path):
_test_buffer_overflow(tmp_path, im)
def test_break_padding(tmp_path):
def test_break_padding(tmp_path: Path) -> None:
im = Image.new("L", (257, 5))
px = im.load()
for y in range(5):

View File

@ -1,9 +1,11 @@
from __future__ import annotations
import io
import os
import os.path
import tempfile
import time
from pathlib import Path
import pytest
@ -12,7 +14,7 @@ from PIL import Image, PdfParser, features
from .helper import hopper, mark_if_feature_version, skip_unless_feature
def helper_save_as_pdf(tmp_path, mode, **kwargs):
def helper_save_as_pdf(tmp_path: Path, mode, **kwargs):
# Arrange
im = hopper(mode)
outfile = str(tmp_path / ("temp_" + mode + ".pdf"))
@ -39,17 +41,17 @@ def helper_save_as_pdf(tmp_path, mode, **kwargs):
@pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK"))
def test_save(tmp_path, mode):
def test_save(tmp_path: Path, mode) -> None:
helper_save_as_pdf(tmp_path, mode)
@skip_unless_feature("jpg_2000")
@pytest.mark.parametrize("mode", ("LA", "RGBA"))
def test_save_alpha(tmp_path, mode):
def test_save_alpha(tmp_path: Path, mode) -> None:
helper_save_as_pdf(tmp_path, mode)
def test_p_alpha(tmp_path):
def test_p_alpha(tmp_path: Path) -> None:
# Arrange
outfile = str(tmp_path / "temp.pdf")
with Image.open("Tests/images/pil123p.png") as im:
@ -65,7 +67,7 @@ def test_p_alpha(tmp_path):
assert b"\n/SMask " in contents
def test_monochrome(tmp_path):
def test_monochrome(tmp_path: Path) -> None:
# Arrange
mode = "1"
@ -74,7 +76,7 @@ def test_monochrome(tmp_path):
assert os.path.getsize(outfile) < (5000 if features.check("libtiff") else 15000)
def test_unsupported_mode(tmp_path):
def test_unsupported_mode(tmp_path: Path) -> None:
im = hopper("PA")
outfile = str(tmp_path / "temp_PA.pdf")
@ -82,7 +84,7 @@ def test_unsupported_mode(tmp_path):
im.save(outfile)
def test_resolution(tmp_path):
def test_resolution(tmp_path: Path) -> None:
im = hopper()
outfile = str(tmp_path / "temp.pdf")
@ -110,7 +112,7 @@ def test_resolution(tmp_path):
{"dpi": (75, 150), "resolution": 200},
),
)
def test_dpi(params, tmp_path):
def test_dpi(params, tmp_path: Path) -> None:
im = hopper()
outfile = str(tmp_path / "temp.pdf")
@ -134,7 +136,7 @@ def test_dpi(params, tmp_path):
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_save_all(tmp_path):
def test_save_all(tmp_path: Path) -> None:
# Single frame image
helper_save_as_pdf(tmp_path, "RGB", save_all=True)
@ -170,7 +172,7 @@ def test_save_all(tmp_path):
assert os.path.getsize(outfile) > 0
def test_multiframe_normal_save(tmp_path):
def test_multiframe_normal_save(tmp_path: Path) -> None:
# Test saving a multiframe image without save_all
with Image.open("Tests/images/dispose_bgnd.gif") as im:
outfile = str(tmp_path / "temp.pdf")
@ -180,7 +182,7 @@ def test_multiframe_normal_save(tmp_path):
assert os.path.getsize(outfile) > 0
def test_pdf_open(tmp_path):
def test_pdf_open(tmp_path: Path) -> None:
# fail on a buffer full of null bytes
with pytest.raises(PdfParser.PdfFormatError):
PdfParser.PdfParser(buf=bytearray(65536))
@ -217,14 +219,14 @@ def test_pdf_open(tmp_path):
assert not hopper_pdf.should_close_file
def test_pdf_append_fails_on_nonexistent_file():
def test_pdf_append_fails_on_nonexistent_file() -> None:
im = hopper("RGB")
with tempfile.TemporaryDirectory() as temp_dir:
with pytest.raises(OSError):
im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True)
def check_pdf_pages_consistency(pdf):
def check_pdf_pages_consistency(pdf) -> None:
pages_info = pdf.read_indirect(pdf.pages_ref)
assert b"Parent" not in pages_info
assert b"Kids" in pages_info
@ -242,7 +244,7 @@ def check_pdf_pages_consistency(pdf):
assert kids_not_used == []
def test_pdf_append(tmp_path):
def test_pdf_append(tmp_path: Path) -> None:
# make a PDF file
pdf_filename = helper_save_as_pdf(tmp_path, "RGB", producer="PdfParser")
@ -293,7 +295,7 @@ def test_pdf_append(tmp_path):
check_pdf_pages_consistency(pdf)
def test_pdf_info(tmp_path):
def test_pdf_info(tmp_path: Path) -> None:
# make a PDF file
pdf_filename = helper_save_as_pdf(
tmp_path,
@ -322,7 +324,7 @@ def test_pdf_info(tmp_path):
check_pdf_pages_consistency(pdf)
def test_pdf_append_to_bytesio():
def test_pdf_append_to_bytesio() -> None:
im = hopper("RGB")
f = io.BytesIO()
im.save(f, format="PDF")
@ -337,7 +339,7 @@ def test_pdf_append_to_bytesio():
@pytest.mark.timeout(1)
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
def test_redos(newline):
def test_redos(newline) -> None:
malicious = b" trailer<<>>" + newline * 3456
# This particular exception isn't relevant here.

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import Image, PixarImagePlugin
@ -8,7 +9,7 @@ from .helper import assert_image_similar, hopper
TEST_FILE = "Tests/images/hopper.pxr"
def test_sanity():
def test_sanity() -> None:
with Image.open(TEST_FILE) as im:
im.load()
assert im.mode == "RGB"
@ -20,7 +21,7 @@ def test_sanity():
assert_image_similar(im, im2, 4.8)
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):

View File

@ -1,9 +1,12 @@
from __future__ import annotations
import re
import sys
import warnings
import zlib
from io import BytesIO
from pathlib import Path
from typing import Any
import pytest
@ -34,7 +37,7 @@ TEST_PNG_FILE = "Tests/images/hopper.png"
MAGIC = PngImagePlugin._MAGIC
def chunk(cid, *data):
def chunk(cid: bytes, *data: bytes) -> bytes:
test_file = BytesIO()
PngImagePlugin.putchunk(*(test_file, cid) + data)
return test_file.getvalue()
@ -50,11 +53,11 @@ HEAD = MAGIC + IHDR
TAIL = IDAT + IEND
def load(data):
def load(data: bytes) -> Image.Image:
return Image.open(BytesIO(data))
def roundtrip(im, **options):
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
out = BytesIO()
im.save(out, "PNG", **options)
out.seek(0)
@ -63,7 +66,7 @@ def roundtrip(im, **options):
@skip_unless_feature("zlib")
class TestFilePng:
def get_chunks(self, filename):
def get_chunks(self, filename: str) -> list[bytes]:
chunks = []
with open(filename, "rb") as fp:
fp.read(8)
@ -78,7 +81,7 @@ class TestFilePng:
png.crc(cid, s)
return chunks
def test_sanity(self, tmp_path):
def test_sanity(self, tmp_path: Path) -> None:
# internal version number
assert re.search(r"\d+(\.\d+){1,3}$", features.version_codec("zlib"))
@ -101,13 +104,13 @@ class TestFilePng:
reloaded = reloaded.convert(mode)
assert_image_equal(reloaded, im)
def test_invalid_file(self):
def test_invalid_file(self) -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
PngImagePlugin.PngImageFile(invalid_file)
def test_broken(self):
def test_broken(self) -> None:
# Check reading of totally broken files. In this case, the test
# file was checked into Subversion as a text file.
@ -116,7 +119,7 @@ class TestFilePng:
with Image.open(test_file):
pass
def test_bad_text(self):
def test_bad_text(self) -> None:
# Make sure PIL can read malformed tEXt chunks (@PIL152)
im = load(HEAD + chunk(b"tEXt") + TAIL)
@ -134,7 +137,7 @@ class TestFilePng:
im = load(HEAD + chunk(b"tEXt", b"spam\0egg\0") + TAIL)
assert im.info == {"spam": "egg\x00"}
def test_bad_ztxt(self):
def test_bad_ztxt(self) -> None:
# Test reading malformed zTXt chunks (python-pillow/Pillow#318)
im = load(HEAD + chunk(b"zTXt") + TAIL)
@ -155,7 +158,7 @@ class TestFilePng:
im = load(HEAD + chunk(b"zTXt", b"spam\0\0" + zlib.compress(b"egg")) + TAIL)
assert im.info == {"spam": "egg"}
def test_bad_itxt(self):
def test_bad_itxt(self) -> None:
im = load(HEAD + chunk(b"iTXt") + TAIL)
assert im.info == {}
@ -199,7 +202,7 @@ class TestFilePng:
assert im.info["spam"].lang == "en"
assert im.info["spam"].tkey == "Spam"
def test_interlace(self):
def test_interlace(self) -> None:
test_file = "Tests/images/pil123p.png"
with Image.open(test_file) as im:
assert_image(im, "P", (162, 150))
@ -214,7 +217,7 @@ class TestFilePng:
im.load()
def test_load_transparent_p(self):
def test_load_transparent_p(self) -> None:
test_file = "Tests/images/pil123p.png"
with Image.open(test_file) as im:
assert_image(im, "P", (162, 150))
@ -224,7 +227,7 @@ class TestFilePng:
# image has 124 unique alpha values
assert len(im.getchannel("A").getcolors()) == 124
def test_load_transparent_rgb(self):
def test_load_transparent_rgb(self) -> None:
test_file = "Tests/images/rgb_trns.png"
with Image.open(test_file) as im:
assert im.info["transparency"] == (0, 255, 52)
@ -236,7 +239,7 @@ class TestFilePng:
# image has 876 transparent pixels
assert im.getchannel("A").getcolors()[0][0] == 876
def test_save_p_transparent_palette(self, tmp_path):
def test_save_p_transparent_palette(self, tmp_path: Path) -> None:
in_file = "Tests/images/pil123p.png"
with Image.open(in_file) as im:
# 'transparency' contains a byte string with the opacity for
@ -257,7 +260,7 @@ class TestFilePng:
# image has 124 unique alpha values
assert len(im.getchannel("A").getcolors()) == 124
def test_save_p_single_transparency(self, tmp_path):
def test_save_p_single_transparency(self, tmp_path: Path) -> None:
in_file = "Tests/images/p_trns_single.png"
with Image.open(in_file) as im:
# pixel value 164 is full transparent
@ -280,7 +283,7 @@ class TestFilePng:
# image has 876 transparent pixels
assert im.getchannel("A").getcolors()[0][0] == 876
def test_save_p_transparent_black(self, tmp_path):
def test_save_p_transparent_black(self, tmp_path: Path) -> None:
# check if solid black image with full transparency
# is supported (check for #1838)
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))
@ -298,7 +301,7 @@ class TestFilePng:
assert_image(im, "RGBA", (10, 10))
assert im.getcolors() == [(100, (0, 0, 0, 0))]
def test_save_grayscale_transparency(self, tmp_path):
def test_save_grayscale_transparency(self, tmp_path: Path) -> None:
for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items():
in_file = "Tests/images/" + mode.lower() + "_trns.png"
with Image.open(in_file) as im:
@ -319,13 +322,13 @@ class TestFilePng:
test_im_rgba = test_im.convert("RGBA")
assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
def test_save_rgb_single_transparency(self, tmp_path):
def test_save_rgb_single_transparency(self, tmp_path: Path) -> None:
in_file = "Tests/images/caption_6_33_22.png"
with Image.open(in_file) as im:
test_file = str(tmp_path / "temp.png")
im.save(test_file)
def test_load_verify(self):
def test_load_verify(self) -> None:
# Check open/load/verify exception (@PIL150)
with Image.open(TEST_PNG_FILE) as im:
@ -338,7 +341,7 @@ class TestFilePng:
with pytest.raises(RuntimeError):
im.verify()
def test_verify_struct_error(self):
def test_verify_struct_error(self) -> None:
# Check open/load/verify exception (#1755)
# offsets to test, -10: breaks in i32() in read. (OSError)
@ -354,7 +357,7 @@ class TestFilePng:
with pytest.raises((OSError, SyntaxError)):
im.verify()
def test_verify_ignores_crc_error(self):
def test_verify_ignores_crc_error(self) -> None:
# check ignores crc errors in ancillary chunks
chunk_data = chunk(b"tEXt", b"spam")
@ -371,7 +374,7 @@ class TestFilePng:
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_verify_not_ignores_crc_error_in_required_chunk(self):
def test_verify_not_ignores_crc_error_in_required_chunk(self) -> None:
# check does not ignore crc errors in required chunks
image_data = MAGIC + IHDR[:-1] + b"q" + TAIL
@ -383,18 +386,18 @@ class TestFilePng:
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_roundtrip_dpi(self):
def test_roundtrip_dpi(self) -> None:
# Check dpi roundtripping
with Image.open(TEST_PNG_FILE) as im:
im = roundtrip(im, dpi=(100.33, 100.33))
assert im.info["dpi"] == (100.33, 100.33)
def test_load_float_dpi(self):
def test_load_float_dpi(self) -> None:
with Image.open(TEST_PNG_FILE) as im:
assert im.info["dpi"] == (95.9866, 95.9866)
def test_roundtrip_text(self):
def test_roundtrip_text(self) -> None:
# Check text roundtripping
with Image.open(TEST_PNG_FILE) as im:
@ -406,7 +409,7 @@ class TestFilePng:
assert im.info == {"TXT": "VALUE", "ZIP": "VALUE"}
assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
def test_roundtrip_itxt(self):
def test_roundtrip_itxt(self) -> None:
# Check iTXt roundtripping
im = Image.new("RGB", (32, 32))
@ -422,7 +425,7 @@ class TestFilePng:
assert im.text["eggs"].lang == "en"
assert im.text["eggs"].tkey == "Eggs"
def test_nonunicode_text(self):
def test_nonunicode_text(self) -> None:
# Check so that non-Unicode text is saved as a tEXt rather than iTXt
im = Image.new("RGB", (32, 32))
@ -431,10 +434,10 @@ class TestFilePng:
im = roundtrip(im, pnginfo=info)
assert isinstance(im.info["Text"], str)
def test_unicode_text(self):
def test_unicode_text(self) -> None:
# Check preservation of non-ASCII characters
def rt_text(value):
def rt_text(value: str) -> None:
im = Image.new("RGB", (32, 32))
info = PngImagePlugin.PngInfo()
info.add_text("Text", value)
@ -447,7 +450,7 @@ class TestFilePng:
rt_text(chr(0x4E00) + chr(0x66F0) + chr(0x9FBA) + chr(0x3042) + chr(0xAC00))
rt_text("A" + chr(0xC4) + chr(0x472) + chr(0x3042)) # Combined
def test_scary(self):
def test_scary(self) -> None:
# Check reading of evil PNG file. For information, see:
# http://scary.beasts.org/security/CESA-2004-001.txt
# The first byte is removed from pngtest_bad.png
@ -461,7 +464,7 @@ class TestFilePng:
with Image.open(pngfile):
pass
def test_trns_rgb(self):
def test_trns_rgb(self) -> None:
# Check writing and reading of tRNS chunks for RGB images.
# Independent file sample provided by Sebastian Spaeth.
@ -476,7 +479,7 @@ class TestFilePng:
im = roundtrip(im, transparency=(0, 1, 2))
assert im.info["transparency"] == (0, 1, 2)
def test_trns_p(self, tmp_path):
def test_trns_p(self, tmp_path: Path) -> None:
# Check writing a transparency of 0, issue #528
im = hopper("P")
im.info["transparency"] = 0
@ -489,13 +492,13 @@ class TestFilePng:
assert_image_equal(im2.convert("RGBA"), im.convert("RGBA"))
def test_trns_null(self):
def test_trns_null(self) -> None:
# Check reading images with null tRNS value, issue #1239
test_file = "Tests/images/tRNS_null_1x1.png"
with Image.open(test_file) as im:
assert im.info["transparency"] == 0
def test_save_icc_profile(self):
def test_save_icc_profile(self) -> None:
with Image.open("Tests/images/icc_profile_none.png") as im:
assert im.info["icc_profile"] is None
@ -505,40 +508,40 @@ class TestFilePng:
im = roundtrip(im, icc_profile=expected_icc)
assert im.info["icc_profile"] == expected_icc
def test_discard_icc_profile(self):
def test_discard_icc_profile(self) -> None:
with Image.open("Tests/images/icc_profile.png") as im:
assert "icc_profile" in im.info
im = roundtrip(im, icc_profile=None)
assert "icc_profile" not in im.info
def test_roundtrip_icc_profile(self):
def test_roundtrip_icc_profile(self) -> None:
with Image.open("Tests/images/icc_profile.png") as im:
expected_icc = im.info["icc_profile"]
im = roundtrip(im)
assert im.info["icc_profile"] == expected_icc
def test_roundtrip_no_icc_profile(self):
def test_roundtrip_no_icc_profile(self) -> None:
with Image.open("Tests/images/icc_profile_none.png") as im:
assert im.info["icc_profile"] is None
im = roundtrip(im)
assert "icc_profile" not in im.info
def test_repr_png(self):
def test_repr_png(self) -> None:
im = hopper()
with Image.open(BytesIO(im._repr_png_())) as repr_png:
assert repr_png.format == "PNG"
assert_image_equal(im, repr_png)
def test_repr_png_error_returns_none(self):
def test_repr_png_error_returns_none(self) -> None:
im = hopper("F")
assert im._repr_png_() is None
def test_chunk_order(self, tmp_path):
def test_chunk_order(self, tmp_path: Path) -> None:
with Image.open("Tests/images/icc_profile.png") as im:
test_file = str(tmp_path / "temp.png")
im.convert("P").save(test_file, dpi=(100, 100))
@ -559,17 +562,17 @@ class TestFilePng:
# pHYs - before IDAT
assert chunks.index(b"pHYs") < chunks.index(b"IDAT")
def test_getchunks(self):
def test_getchunks(self) -> None:
im = hopper()
chunks = PngImagePlugin.getchunks(im)
assert len(chunks) == 3
def test_read_private_chunks(self):
def test_read_private_chunks(self) -> None:
with Image.open("Tests/images/exif.png") as im:
assert im.private_chunks == [(b"orNT", b"\x01")]
def test_roundtrip_private_chunk(self):
def test_roundtrip_private_chunk(self) -> None:
# Check private chunk roundtripping
with Image.open(TEST_PNG_FILE) as im:
@ -587,7 +590,7 @@ class TestFilePng:
(b"prIV", b"VALUE3", True),
]
def test_textual_chunks_after_idat(self):
def test_textual_chunks_after_idat(self) -> None:
with Image.open("Tests/images/hopper.png") as im:
assert "comment" in im.text
for k, v in {
@ -614,7 +617,7 @@ class TestFilePng:
with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
def test_padded_idat(self):
def test_padded_idat(self) -> None:
# This image has been manually hexedited
# so that the IDAT chunk has padding at the end
# Set MAXBLOCK to the length of the actual data
@ -634,7 +637,7 @@ class TestFilePng:
@pytest.mark.parametrize(
"cid", (b"IHDR", b"sRGB", b"pHYs", b"acTL", b"fcTL", b"fdAT")
)
def test_truncated_chunks(self, cid):
def test_truncated_chunks(self, cid: bytes) -> None:
fp = BytesIO()
with PngImagePlugin.PngStream(fp) as png:
with pytest.raises(ValueError):
@ -644,7 +647,7 @@ class TestFilePng:
png.call(cid, 0, 0)
ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_specify_bits(self, tmp_path):
def test_specify_bits(self, tmp_path: Path) -> None:
im = hopper("P")
out = str(tmp_path / "temp.png")
@ -653,7 +656,7 @@ class TestFilePng:
with Image.open(out) as reloaded:
assert len(reloaded.png.im_palette[1]) == 48
def test_plte_length(self, tmp_path):
def test_plte_length(self, tmp_path: Path) -> None:
im = Image.new("P", (1, 1))
im.putpalette((1, 1, 1))
@ -663,7 +666,7 @@ class TestFilePng:
with Image.open(out) as reloaded:
assert len(reloaded.png.im_palette[1]) == 3
def test_getxmp(self):
def test_getxmp(self) -> None:
with Image.open("Tests/images/color_snakes.png") as im:
if ElementTree is None:
with pytest.warns(
@ -678,7 +681,7 @@ class TestFilePng:
assert description["PixelXDimension"] == "10"
assert description["subject"]["Seq"] is None
def test_exif(self):
def test_exif(self) -> None:
# With an EXIF chunk
with Image.open("Tests/images/exif.png") as im:
exif = im._getexif()
@ -704,7 +707,7 @@ class TestFilePng:
exif = im.getexif()
assert exif[274] == 3
def test_exif_save(self, tmp_path):
def test_exif_save(self, tmp_path: Path) -> None:
# Test exif is not saved from info
test_file = str(tmp_path / "temp.png")
with Image.open("Tests/images/exif.png") as im:
@ -724,7 +727,7 @@ class TestFilePng:
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
def test_exif_from_jpg(self, tmp_path):
def test_exif_from_jpg(self, tmp_path: Path) -> None:
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
test_file = str(tmp_path / "temp.png")
im.save(test_file, exif=im.getexif())
@ -733,7 +736,7 @@ class TestFilePng:
exif = reloaded._getexif()
assert exif[305] == "Adobe Photoshop CS Macintosh"
def test_exif_argument(self, tmp_path):
def test_exif_argument(self, tmp_path: Path) -> None:
with Image.open(TEST_PNG_FILE) as im:
test_file = str(tmp_path / "temp.png")
im.save(test_file, exif=b"exifstring")
@ -741,11 +744,11 @@ class TestFilePng:
with Image.open(test_file) as reloaded:
assert reloaded.info["exif"] == b"Exif\x00\x00exifstring"
def test_tell(self):
def test_tell(self) -> None:
with Image.open(TEST_PNG_FILE) as im:
assert im.tell() == 0
def test_seek(self):
def test_seek(self) -> None:
with Image.open(TEST_PNG_FILE) as im:
im.seek(0)
@ -753,7 +756,7 @@ class TestFilePng:
im.seek(1)
@pytest.mark.parametrize("buffer", (True, False))
def test_save_stdout(self, buffer):
def test_save_stdout(self, buffer: bool) -> None:
old_stdout = sys.stdout
if buffer:
@ -785,7 +788,7 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase):
mem_limit = 2 * 1024 # max increase in K
iterations = 100 # Leak is 56k/iteration, this will leak 5.6megs
def test_leak_load(self):
def test_leak_load(self) -> None:
with open("Tests/images/hopper.png", "rb") as f:
DATA = BytesIO(f.read(16 * 1024))
@ -793,7 +796,7 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase):
with Image.open(DATA) as im:
im.load()
def core():
def core() -> None:
with Image.open(DATA) as im:
im.load()

View File

@ -1,18 +1,25 @@
from __future__ import annotations
import sys
from io import BytesIO
from pathlib import Path
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"
def test_sanity():
def test_sanity() -> None:
with Image.open(TEST_FILE) as im:
assert im.mode == "RGB"
assert im.size == (128, 128)
@ -63,7 +70,9 @@ def test_sanity():
),
),
)
def test_arbitrary_maxval(data, mode, pixels):
def test_arbitrary_maxval(
data: bytes, mode: str, pixels: tuple[int | tuple[int, int, int], ...]
) -> None:
fp = BytesIO(data)
with Image.open(fp) as im:
assert im.size == (3, 1)
@ -73,7 +82,7 @@ def test_arbitrary_maxval(data, mode, pixels):
assert tuple(px[x, 0] for x in range(3)) == pixels
def test_16bit_pgm():
def test_16bit_pgm() -> None:
with Image.open("Tests/images/16_bit_binary.pgm") as im:
assert im.mode == "I"
assert im.size == (20, 100)
@ -82,22 +91,60 @@ def test_16bit_pgm():
assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.png")
def test_16bit_pgm_write(tmp_path):
def test_16bit_pgm_write(tmp_path: Path) -> None:
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):
def test_pnm(tmp_path: Path) -> None:
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: Path) -> None:
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: Path) -> None:
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: bytes) -> None:
with pytest.raises(ValueError):
with Image.open(BytesIO(data)):
pass
@pytest.mark.parametrize(
@ -117,12 +164,12 @@ def test_pnm(tmp_path):
),
),
)
def test_plain(plain_path, raw_path):
def test_plain(plain_path: str, raw_path: str) -> None:
with Image.open(plain_path) as im:
assert_image_equal_tofile(im, raw_path)
def test_16bit_plain_pgm():
def test_16bit_plain_pgm() -> None:
# P2 with maxval 2 ** 16 - 1
with Image.open("Tests/images/hopper_16bit_plain.pgm") as im:
assert im.mode == "I"
@ -141,7 +188,9 @@ def test_16bit_plain_pgm():
(b"P3\n2 2\n255", b"0 0 0 001 1 1 2 2 2 255 255 255", 10**6),
),
)
def test_plain_data_with_comment(tmp_path, header, data, comment_count):
def test_plain_data_with_comment(
tmp_path: Path, header: bytes, data: bytes, comment_count: int
) -> None:
path1 = str(tmp_path / "temp1.ppm")
path2 = str(tmp_path / "temp2.ppm")
comment = b"# comment" * comment_count
@ -154,7 +203,7 @@ def test_plain_data_with_comment(tmp_path, header, data, comment_count):
@pytest.mark.parametrize("data", (b"P1\n128 128\n", b"P3\n128 128\n255\n"))
def test_plain_truncated_data(tmp_path, data):
def test_plain_truncated_data(tmp_path: Path, data: bytes) -> None:
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(data)
@ -165,7 +214,7 @@ def test_plain_truncated_data(tmp_path, data):
@pytest.mark.parametrize("data", (b"P1\n128 128\n1009", b"P3\n128 128\n255\n100A"))
def test_plain_invalid_data(tmp_path, data):
def test_plain_invalid_data(tmp_path: Path, data: bytes) -> None:
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(data)
@ -182,7 +231,7 @@ def test_plain_invalid_data(tmp_path, data):
b"P3\n128 128\n255\n012345678910 0", # token too long
),
)
def test_plain_ppm_token_too_long(tmp_path, data):
def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None:
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(data)
@ -192,7 +241,7 @@ def test_plain_ppm_token_too_long(tmp_path, data):
im.load()
def test_plain_ppm_value_too_large(tmp_path):
def test_plain_ppm_value_too_large(tmp_path: Path) -> None:
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(b"P3\n128 128\n255\n256")
@ -202,12 +251,12 @@ def test_plain_ppm_value_too_large(tmp_path):
im.load()
def test_magic():
def test_magic() -> None:
with pytest.raises(SyntaxError):
PpmImagePlugin.PpmImageFile(fp=BytesIO(b"PyInvalid"))
def test_header_with_comments(tmp_path):
def test_header_with_comments(tmp_path: Path) -> None:
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(b"P6 #comment\n#comment\r12#comment\r8\n128 #comment\n255\n")
@ -216,7 +265,7 @@ def test_header_with_comments(tmp_path):
assert im.size == (128, 128)
def test_non_integer_token(tmp_path):
def test_non_integer_token(tmp_path: Path) -> None:
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(b"P6\nTEST")
@ -226,7 +275,7 @@ def test_non_integer_token(tmp_path):
pass
def test_header_token_too_long(tmp_path):
def test_header_token_too_long(tmp_path: Path) -> None:
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(b"P6\n 01234567890")
@ -238,7 +287,7 @@ def test_header_token_too_long(tmp_path):
assert str(e.value) == "Token too long in file header: 01234567890"
def test_truncated_file(tmp_path):
def test_truncated_file(tmp_path: Path) -> None:
# Test EOF in header
path = str(tmp_path / "temp.pgm")
with open(path, "wb") as f:
@ -257,7 +306,7 @@ def test_truncated_file(tmp_path):
im.load()
def test_not_enough_image_data(tmp_path):
def test_not_enough_image_data(tmp_path: Path) -> None:
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(b"P2 1 2 255 255")
@ -268,7 +317,7 @@ def test_not_enough_image_data(tmp_path):
@pytest.mark.parametrize("maxval", (b"0", b"65536"))
def test_invalid_maxval(maxval, tmp_path):
def test_invalid_maxval(maxval: bytes, tmp_path: Path) -> None:
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(b"P6\n3 1 " + maxval)
@ -280,7 +329,7 @@ def test_invalid_maxval(maxval, tmp_path):
assert str(e.value) == "maxval must be greater than 0 and less than 65536"
def test_neg_ppm():
def test_neg_ppm() -> None:
# Storage.c accepted negative values for xsize, ysize. the
# internal open_ppm function didn't check for sanity but it
# has been removed. The default opener doesn't accept negative
@ -291,7 +340,7 @@ def test_neg_ppm():
pass
def test_mimetypes(tmp_path):
def test_mimetypes(tmp_path: Path) -> None:
path = str(tmp_path / "temp.pgm")
with open(path, "wb") as f:
@ -306,7 +355,7 @@ def test_mimetypes(tmp_path):
@pytest.mark.parametrize("buffer", (True, False))
def test_save_stdout(buffer):
def test_save_stdout(buffer: bool) -> None:
old_stdout = sys.stdout
if buffer:

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import warnings
import pytest
@ -10,7 +11,7 @@ from .helper import assert_image_equal_tofile, assert_image_similar, hopper, is_
test_file = "Tests/images/hopper.psd"
def test_sanity():
def test_sanity() -> None:
with Image.open(test_file) as im:
im.load()
assert im.mode == "RGB"
@ -23,8 +24,8 @@ def test_sanity():
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file():
def open():
def test_unclosed_file() -> None:
def open() -> None:
im = Image.open(test_file)
im.load()
@ -32,27 +33,27 @@ def test_unclosed_file():
open()
def test_closed_file():
def test_closed_file() -> None:
with warnings.catch_warnings():
im = Image.open(test_file)
im.load()
im.close()
def test_context_manager():
def test_context_manager() -> None:
with warnings.catch_warnings():
with Image.open(test_file) as im:
im.load()
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
PsdImagePlugin.PsdImageFile(invalid_file)
def test_n_frames():
def test_n_frames() -> None:
with Image.open("Tests/images/hopper_merged.psd") as im:
assert im.n_frames == 1
assert not im.is_animated
@ -63,7 +64,7 @@ def test_n_frames():
assert im.is_animated
def test_eoferror():
def test_eoferror() -> None:
with Image.open(test_file) as im:
# PSD seek index starts at 1 rather than 0
n_frames = im.n_frames + 1
@ -77,7 +78,7 @@ def test_eoferror():
im.seek(n_frames - 1)
def test_seek_tell():
def test_seek_tell() -> None:
with Image.open(test_file) as im:
layer_number = im.tell()
assert layer_number == 1
@ -94,30 +95,30 @@ def test_seek_tell():
assert layer_number == 2
def test_seek_eoferror():
def test_seek_eoferror() -> None:
with Image.open(test_file) as im:
with pytest.raises(EOFError):
im.seek(-1)
def test_open_after_exclusive_load():
def test_open_after_exclusive_load() -> None:
with Image.open(test_file) as im:
im.load()
im.seek(im.tell() + 1)
im.load()
def test_rgba():
def test_rgba() -> None:
with Image.open("Tests/images/rgba.psd") as im:
assert_image_equal_tofile(im, "Tests/images/imagedraw_square.png")
def test_layer_skip():
def test_layer_skip() -> None:
with Image.open("Tests/images/five_channels.psd") as im:
assert im.n_frames == 1
def test_icc_profile():
def test_icc_profile() -> None:
with Image.open(test_file) as im:
assert "icc_profile" in im.info
@ -125,12 +126,12 @@ def test_icc_profile():
assert len(icc_profile) == 3144
def test_no_icc_profile():
def test_no_icc_profile() -> None:
with Image.open("Tests/images/hopper_merged.psd") as im:
assert "icc_profile" not in im.info
def test_combined_larger_than_size():
def test_combined_larger_than_size() -> None:
# The combined size of the individual parts is larger than the
# declared 'size' of the extra data field, resulting in a backwards seek.
@ -156,7 +157,7 @@ def test_combined_larger_than_size():
("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError),
],
)
def test_crashes(test_file, raises):
def test_crashes(test_file, raises) -> None:
with open(test_file, "rb") as f:
with pytest.raises(raises):
with Image.open(f):

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import pytest
from PIL import Image, QoiImagePlugin
@ -6,7 +7,7 @@ from PIL import Image, QoiImagePlugin
from .helper import assert_image_equal_tofile
def test_sanity():
def test_sanity() -> None:
with Image.open("Tests/images/hopper.qoi") as im:
assert im.mode == "RGB"
assert im.size == (128, 128)
@ -22,7 +23,7 @@ def test_sanity():
assert_image_equal_tofile(im, "Tests/images/pil123rgba.png")
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):

View File

@ -1,4 +1,7 @@
from __future__ import annotations
from pathlib import Path
import pytest
from PIL import Image, SgiImagePlugin
@ -11,7 +14,7 @@ from .helper import (
)
def test_rgb():
def test_rgb() -> None:
# Created with ImageMagick then renamed:
# convert hopper.ppm -compress None sgi:hopper.rgb
test_file = "Tests/images/hopper.rgb"
@ -21,11 +24,11 @@ def test_rgb():
assert im.get_format_mimetype() == "image/rgb"
def test_rgb16():
def test_rgb16() -> None:
assert_image_equal_tofile(hopper(), "Tests/images/hopper16.rgb")
def test_l():
def test_l() -> None:
# Created with ImageMagick
# convert hopper.ppm -monochrome -compress None sgi:hopper.bw
test_file = "Tests/images/hopper.bw"
@ -35,7 +38,7 @@ def test_l():
assert im.get_format_mimetype() == "image/sgi"
def test_rgba():
def test_rgba() -> None:
# Created with ImageMagick:
# convert transparent.png -compress None transparent.sgi
test_file = "Tests/images/transparent.sgi"
@ -45,7 +48,7 @@ def test_rgba():
assert im.get_format_mimetype() == "image/sgi"
def test_rle():
def test_rle() -> None:
# Created with ImageMagick:
# convert hopper.ppm hopper.sgi
test_file = "Tests/images/hopper.sgi"
@ -54,22 +57,22 @@ def test_rle():
assert_image_equal_tofile(im, "Tests/images/hopper.rgb")
def test_rle16():
def test_rle16() -> None:
test_file = "Tests/images/tv16.sgi"
with Image.open(test_file) as im:
assert_image_equal_tofile(im, "Tests/images/tv.rgb")
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(ValueError):
SgiImagePlugin.SgiImageFile(invalid_file)
def test_write(tmp_path):
def roundtrip(img):
def test_write(tmp_path: Path) -> None:
def roundtrip(img: Image.Image) -> None:
out = str(tmp_path / "temp.sgi")
img.save(out, format="sgi")
assert_image_equal_tofile(img, out)
@ -88,7 +91,7 @@ def test_write(tmp_path):
roundtrip(Image.new("L", (10, 1)))
def test_write16(tmp_path):
def test_write16(tmp_path: Path) -> None:
test_file = "Tests/images/hopper16.rgb"
with Image.open(test_file) as im:
@ -98,7 +101,7 @@ def test_write16(tmp_path):
assert_image_equal_tofile(im, out)
def test_unsupported_mode(tmp_path):
def test_unsupported_mode(tmp_path: Path) -> None:
im = hopper("LA")
out = str(tmp_path / "temp.sgi")

View File

@ -1,7 +1,9 @@
from __future__ import annotations
import tempfile
import warnings
from io import BytesIO
from pathlib import Path
import pytest
@ -12,7 +14,7 @@ from .helper import assert_image_equal_tofile, hopper, is_pypy
TEST_FILE = "Tests/images/hopper.spider"
def test_sanity():
def test_sanity() -> None:
with Image.open(TEST_FILE) as im:
im.load()
assert im.mode == "F"
@ -21,8 +23,8 @@ def test_sanity():
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file():
def open():
def test_unclosed_file() -> None:
def open() -> None:
im = Image.open(TEST_FILE)
im.load()
@ -30,20 +32,20 @@ def test_unclosed_file():
open()
def test_closed_file():
def test_closed_file() -> None:
with warnings.catch_warnings():
im = Image.open(TEST_FILE)
im.load()
im.close()
def test_context_manager():
def test_context_manager() -> None:
with warnings.catch_warnings():
with Image.open(TEST_FILE) as im:
im.load()
def test_save(tmp_path):
def test_save(tmp_path: Path) -> None:
# Arrange
temp = str(tmp_path / "temp.spider")
im = hopper()
@ -58,7 +60,7 @@ def test_save(tmp_path):
assert im2.format == "SPIDER"
def test_tempfile():
def test_tempfile() -> None:
# Arrange
im = hopper()
@ -74,11 +76,11 @@ def test_tempfile():
assert reloaded.format == "SPIDER"
def test_is_spider_image():
def test_is_spider_image() -> None:
assert SpiderImagePlugin.isSpiderImage(TEST_FILE)
def test_tell():
def test_tell() -> None:
# Arrange
with Image.open(TEST_FILE) as im:
# Act
@ -88,13 +90,13 @@ def test_tell():
assert index == 0
def test_n_frames():
def test_n_frames() -> None:
with Image.open(TEST_FILE) as im:
assert im.n_frames == 1
assert not im.is_animated
def test_load_image_series():
def test_load_image_series() -> None:
# Arrange
not_spider_file = "Tests/images/hopper.ppm"
file_list = [TEST_FILE, not_spider_file, "path/not_found.ext"]
@ -108,7 +110,7 @@ def test_load_image_series():
assert img_list[0].size == (128, 128)
def test_load_image_series_no_input():
def test_load_image_series_no_input() -> None:
# Arrange
file_list = None
@ -119,7 +121,7 @@ def test_load_image_series_no_input():
assert img_list is None
def test_is_int_not_a_number():
def test_is_int_not_a_number() -> None:
# Arrange
not_a_number = "a"
@ -130,7 +132,7 @@ def test_is_int_not_a_number():
assert ret == 0
def test_invalid_file():
def test_invalid_file() -> None:
invalid_file = "Tests/images/invalid.spider"
with pytest.raises(OSError):
@ -138,20 +140,20 @@ def test_invalid_file():
pass
def test_nonstack_file():
def test_nonstack_file() -> None:
with Image.open(TEST_FILE) as im:
with pytest.raises(EOFError):
im.seek(0)
def test_nonstack_dos():
def test_nonstack_dos() -> None:
with Image.open(TEST_FILE) as im:
for i, frame in enumerate(ImageSequence.Iterator(im)):
assert i <= 1, "Non-stack DOS file test failed"
# for issue #4093
def test_odd_size():
def test_odd_size() -> None:
data = BytesIO()
width = 100
im = Image.new("F", (width, 64))

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import os
import pytest
@ -10,7 +11,7 @@ from .helper import assert_image_equal_tofile, assert_image_similar, hopper
EXTRA_DIR = "Tests/images/sunraster"
def test_sanity():
def test_sanity() -> None:
# Arrange
# Created with ImageMagick: convert hopper.jpg hopper.ras
test_file = "Tests/images/hopper.ras"
@ -27,7 +28,7 @@ def test_sanity():
SunImagePlugin.SunImageFile(invalid_file)
def test_im1():
def test_im1() -> None:
with Image.open("Tests/images/sunraster.im1") as im:
assert_image_equal_tofile(im, "Tests/images/sunraster.im1.png")
@ -35,7 +36,7 @@ def test_im1():
@pytest.mark.skipif(
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
)
def test_others():
def test_others() -> None:
files = (
os.path.join(EXTRA_DIR, f)
for f in os.listdir(EXTRA_DIR)

View File

@ -1,4 +1,5 @@
from __future__ import annotations
import warnings
import pytest
@ -18,7 +19,7 @@ TEST_TAR_FILE = "Tests/images/hopper.tar"
("jpg", "hopper.jpg", "JPEG"),
),
)
def test_sanity(codec, test_path, format):
def test_sanity(codec: str, test_path: str, format: str) -> None:
if features.check(codec):
with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar:
with Image.open(tar) as im:
@ -29,18 +30,18 @@ def test_sanity(codec, test_path, format):
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file():
def test_unclosed_file() -> None:
with pytest.warns(ResourceWarning):
TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
def test_close():
def test_close() -> None:
with warnings.catch_warnings():
tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
tar.close()
def test_contextmanager():
def test_contextmanager() -> None:
with warnings.catch_warnings():
with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"):
pass

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