Merge branch 'main' into libjpeg-turbo

This commit is contained in:
Andrew Murray 2024-02-29 23:07:55 +11:00 committed by GitHub
commit 92aafeb6f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
236 changed files with 3210 additions and 2548 deletions

View File

@ -6,6 +6,7 @@ init:
# Uncomment previous line to get RDP access during the build. # Uncomment previous line to get RDP access during the build.
environment: environment:
COVERAGE_CORE: sysmon
EXECUTABLE: python.exe EXECUTABLE: python.exe
TEST_OPTIONS: TEST_OPTIONS:
DEPLOY: YES DEPLOY: YES

View File

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

View File

@ -0,0 +1 @@
mypy==1.7.1

View File

@ -10,6 +10,11 @@ exclude_also =
if DEBUG: if DEBUG:
# Don't complain about compatibility code for missing optional dependencies # Don't complain about compatibility code for missing optional dependencies
except ImportError 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] [run]
omit = omit =

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
tidelift: "pypi/Pillow" tidelift: "pypi/pillow"

View File

@ -7,10 +7,12 @@ on:
paths: paths:
- ".github/workflows/docs.yml" - ".github/workflows/docs.yml"
- "docs/**" - "docs/**"
- "src/PIL/**"
pull_request: pull_request:
paths: paths:
- ".github/workflows/docs.yml" - ".github/workflows/docs.yml"
- "docs/**" - "docs/**"
- "src/PIL/**"
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@ -37,16 +39,26 @@ jobs:
with: with:
python-version: "3.x" python-version: "3.x"
cache: pip cache: pip
cache-dependency-path: ".ci/*.sh" cache-dependency-path: |
".ci/*.sh"
"pyproject.toml"
- name: Build system information - name: Build system information
run: python3 .github/workflows/system-info.py 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 - name: Install Linux dependencies
run: | run: |
.ci/install.sh .ci/install.sh
env: env:
GHA_PYTHON_VERSION: "3.x" GHA_PYTHON_VERSION: "3.x"
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
- name: Build - name: Build
run: | run: |

View File

@ -2,7 +2,16 @@
set -e 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" export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
# TODO Update condition when cffi supports 3.13 # TODO Update condition when cffi supports 3.13

View File

@ -23,6 +23,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# Drafts your next release notes as pull requests are merged into "main" # 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: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

@ -26,6 +26,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
env:
COVERAGE_CORE: sysmon
jobs: jobs:
build: build:
runs-on: windows-latest runs-on: windows-latest
@ -47,9 +50,8 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Cygwin - name: Install Cygwin
uses: cygwin/cygwin-install-action@v4 uses: egor-tensin/setup-cygwin@v4
with: with:
platform: x86_64
packages: > packages: >
gcc-g++ gcc-g++
ghostscript ghostscript
@ -69,6 +71,7 @@ jobs:
make make
netpbm netpbm
perl perl
python39=3.9.16-1
python3${{ matrix.python-minor-version }}-cffi python3${{ matrix.python-minor-version }}-cffi
python3${{ matrix.python-minor-version }}-cython python3${{ matrix.python-minor-version }}-cython
python3${{ matrix.python-minor-version }}-devel python3${{ matrix.python-minor-version }}-devel
@ -80,13 +83,13 @@ jobs:
zlib-devel zlib-devel
- name: Add Lapack to PATH - name: Add Lapack to PATH
uses: egor-tensin/cleanup-path@v3 uses: egor-tensin/cleanup-path@v4
with: with:
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack' dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
- name: Select Python version - name: Select Python version
run: | 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 - name: Get latest NumPy version
id: latest-numpy id: latest-numpy
@ -141,7 +144,7 @@ jobs:
bash.exe .ci/after_success.sh bash.exe .ci/after_success.sh
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3.1.5
with: with:
file: ./coverage.xml file: ./coverage.xml
flags: GHA_Cygwin flags: GHA_Cygwin

View File

@ -101,7 +101,7 @@ jobs:
MATRIX_DOCKER: ${{ matrix.docker }} MATRIX_DOCKER: ${{ matrix.docker }}
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3.1.5
with: with:
flags: GHA_Docker flags: GHA_Docker
name: ${{ matrix.docker }} name: ${{ matrix.docker }}

View File

@ -26,6 +26,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
env:
COVERAGE_CORE: sysmon
jobs: jobs:
build: build:
runs-on: windows-latest runs-on: windows-latest
@ -82,7 +85,7 @@ jobs:
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3.1.5
with: with:
file: ./coverage.xml file: ./coverage.xml
flags: GHA_Windows flags: GHA_Windows

View File

@ -26,13 +26,16 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
env:
COVERAGE_CORE: sysmon
jobs: jobs:
build: build:
runs-on: windows-latest runs-on: windows-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13.0-alpha.3"]
timeout-minutes: 30 timeout-minutes: 30
@ -66,8 +69,16 @@ jobs:
- name: Print build system information - name: Print build system information
run: python3 .github/workflows/system-info.py run: python3 .github/workflows/system-info.py
- name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma - name: Install Python dependencies
run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma run: >
python3 -m pip install
coverage>=7.4.2
defusedxml
olefile
pyroma
pytest
pytest-cov
pytest-timeout
- name: Install dependencies - name: Install dependencies
id: install id: install
@ -202,7 +213,7 @@ jobs:
shell: pwsh shell: pwsh
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3.1.5
with: with:
file: ./coverage.xml file: ./coverage.xml
flags: GHA_Windows flags: GHA_Windows

View File

@ -26,6 +26,10 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
env:
COVERAGE_CORE: sysmon
FORCE_COLOR: 1
jobs: jobs:
build: build:
@ -33,7 +37,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ os: [
"macos-latest", "macos-14",
"ubuntu-latest", "ubuntu-latest",
] ]
python-version: [ python-version: [
@ -47,11 +51,21 @@ jobs:
"3.8", "3.8",
] ]
include: include:
- python-version: "3.9" - python-version: "3.11"
PYTHONOPTIMIZE: 1 PYTHONOPTIMIZE: 1
REVERSE: "--reverse" REVERSE: "--reverse"
- python-version: "3.8" - python-version: "3.10"
PYTHONOPTIMIZE: 2 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 }} runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }} name: ${{ matrix.os }} Python ${{ matrix.python-version }}
@ -65,17 +79,28 @@ jobs:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
allow-prereleases: true allow-prereleases: true
cache: pip cache: pip
cache-dependency-path: ".ci/*.sh" cache-dependency-path: |
".ci/*.sh"
"pyproject.toml"
- name: Build system information - name: Build system information
run: python3 .github/workflows/system-info.py 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 - name: Install Linux dependencies
if: startsWith(matrix.os, 'ubuntu') if: startsWith(matrix.os, 'ubuntu')
run: | run: |
.ci/install.sh .ci/install.sh
env: env:
GHA_PYTHON_VERSION: ${{ matrix.python-version }} GHA_PYTHON_VERSION: ${{ matrix.python-version }}
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
- name: Install macOS dependencies - name: Install macOS dependencies
if: startsWith(matrix.os, 'macOS') if: startsWith(matrix.os, 'macOS')
@ -125,9 +150,9 @@ jobs:
.ci/after_success.sh .ci/after_success.sh
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3.1.5
with: 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 }} name: ${{ matrix.os }} Python ${{ matrix.python-version }}
gcov: true gcov: true

View File

@ -19,7 +19,7 @@ FREETYPE_VERSION=2.13.2
HARFBUZZ_VERSION=8.3.0 HARFBUZZ_VERSION=8.3.0
LIBPNG_VERSION=1.6.40 LIBPNG_VERSION=1.6.40
JPEGTURBO_VERSION=3.0.2 JPEGTURBO_VERSION=3.0.2
OPENJPEG_VERSION=2.5.0 OPENJPEG_VERSION=2.5.2
XZ_VERSION=5.4.5 XZ_VERSION=5.4.5
TIFF_VERSION=4.6.0 TIFF_VERSION=4.6.0
LCMS2_VERSION=2.16 LCMS2_VERSION=2.16
@ -40,7 +40,7 @@ BROTLI_VERSION=1.1.0
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "x86_64" ]]; then if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "x86_64" ]]; then
function build_openjpeg { function build_openjpeg {
local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz openjpeg-2.5.0.tar.gz) local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz openjpeg-${OPENJPEG_VERSION}.tar.gz)
(cd $out_dir \ (cd $out_dir \
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
&& make install) && make install)
@ -72,14 +72,12 @@ function build {
build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto
if [ -n "$IS_MACOS" ]; then if [ -n "$IS_MACOS" ]; then
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
build_simple xorgproto 2023.2 https://www.x.org/pub/individual/proto 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 libXau 1.0.11 https://www.x.org/pub/individual/lib
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist 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 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 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
fi
else else
sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc
fi fi
@ -95,6 +93,9 @@ function build {
done done
fi fi
build_openjpeg build_openjpeg
if [ -f /usr/local/lib64/libopenjp2.so ]; then
cp /usr/local/lib64/libopenjp2.so /usr/local/lib
fi
ORIGINAL_CFLAGS=$CFLAGS ORIGINAL_CFLAGS=$CFLAGS
CFLAGS="$CFLAGS -O3 -DNDEBUG" CFLAGS="$CFLAGS -O3 -DNDEBUG"
@ -131,13 +132,13 @@ untar pillow-depends-main.zip
if [[ -n "$IS_MACOS" ]]; then if [[ -n "$IS_MACOS" ]]; then
# webp, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb # 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 # if php is installed, brew tries to reinstall these after installing openblas
# remove cairo to fix building harfbuzz on arm64 # remove cairo to fix building harfbuzz on arm64
# remove lcms2 and libpng to fix building openjpeg on arm64 # remove lcms2 and libpng to fix building openjpeg on arm64
# remove zstd to avoid inclusion on x86_64 # remove zstd to avoid inclusion on x86_64
# curl from brew requires zstd, use system curl # 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 brew install pkg-config
fi fi

View File

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

View File

@ -5,6 +5,18 @@ Changelog (Pillow)
10.3.0 (unreleased) 10.3.0 (unreleased)
------------------- -------------------
- Release GIL while calling ``WebPAnimDecoderGetNext`` #7782
[evanmiller, radarhere]
- Fixed reading FLI/FLC images with a prefix chunk #7804
[twolife]
- 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 - Do not support using test-image-results to upload images after test failures #7739
[radarhere] [radarhere]

View File

@ -64,7 +64,7 @@ As of 2019, Pillow development is
src="https://zenodo.org/badge/17549/python-pillow/Pillow.svg"></a> src="https://zenodo.org/badge/17549/python-pillow/Pillow.svg"></a>
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img <a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
alt="Tidelift" alt="Tidelift"
src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a> src="https://tidelift.com/badges/package/pypi/pillow?style=flat"></a>
<a href="https://pypi.org/project/pillow/"><img <a href="https://pypi.org/project/pillow/"><img
alt="Newest PyPI version" alt="Newest PyPI version"
src="https://img.shields.io/pypi/v/pillow.svg"></a> src="https://img.shields.io/pypi/v/pillow.svg"></a>
@ -82,9 +82,6 @@ As of 2019, Pillow development is
<a href="https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img <a href="https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img
alt="Join the chat at https://gitter.im/python-pillow/Pillow" alt="Join the chat at https://gitter.im/python-pillow/Pillow"
src="https://badges.gitter.im/python-pillow/Pillow.svg"></a> src="https://badges.gitter.im/python-pillow/Pillow.svg"></a>
<a href="https://twitter.com/PythonPillow"><img
alt="Follow on https://twitter.com/PythonPillow"
src="https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg"></a>
<a href="https://fosstodon.org/@pillow"><img <a href="https://fosstodon.org/@pillow"><img
alt="Follow on https://fosstodon.org/@pillow" alt="Follow on https://fosstodon.org/@pillow"
src="https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg" src="https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg"

View File

@ -86,7 +86,7 @@ Released as needed privately to individual vendors for critical security-related
## Publicize Release ## Publicize Release
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010 * [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
## Documentation ## Documentation

View File

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

View File

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

2
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 # Reproductions/tests for OOB read errors in FliDecode.c
# When run in python, all of these images should fail for # When run in python, all of these images should fail for

View File

@ -1,6 +1,7 @@
""" """
Helper functions. Helper functions.
""" """
from __future__ import annotations from __future__ import annotations
import logging import logging
@ -243,7 +244,7 @@ def fromstring(data: bytes) -> Image.Image:
return Image.open(BytesIO(data)) return Image.open(BytesIO(data))
def tostring(im: Image.Image, string_format: str, **options: dict[str, Any]) -> bytes: def tostring(im: Image.Image, string_format: str, **options: Any) -> bytes:
out = BytesIO() out = BytesIO()
im.save(out, string_format, **options) im.save(out, string_format, **options)
return out.getvalue() return out.getvalue()

BIN
Tests/images/2422.flc Normal file

Binary file not shown.

View File

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

View File

@ -1,5 +1,3 @@
#!/usr/bin/python3
# Copyright 2020 Google LLC # Copyright 2020 Google LLC
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");

View File

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

View File

@ -3,12 +3,12 @@ from __future__ import annotations
from PIL import _binary from PIL import _binary
def test_standard(): def test_standard() -> None:
assert _binary.i8(b"*") == 42 assert _binary.i8(b"*") == 42
assert _binary.o8(42) == b"*" 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.i16le(b"\xff\xff\x00\x00") == 65535
assert _binary.i32le(b"\xff\xff\x00\x00") == 65535 assert _binary.i32le(b"\xff\xff\x00\x00") == 65535
@ -16,7 +16,7 @@ def test_little_endian():
assert _binary.o32le(65535) == b"\xff\xff\x00\x00" 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.i16be(b"\x00\x00\xff\xff") == 0
assert _binary.i32be(b"\x00\x00\xff\xff") == 65535 assert _binary.i32be(b"\x00\x00\xff\xff") == 65535

View File

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

View File

@ -16,18 +16,18 @@ sample.putdata(sum([
# fmt: on # fmt: on
def test_imageops_box_blur(): def test_imageops_box_blur() -> None:
i = sample.filter(ImageFilter.BoxBlur(1)) i = sample.filter(ImageFilter.BoxBlur(1))
assert i.mode == sample.mode assert i.mode == sample.mode
assert i.size == sample.size assert i.size == sample.size
assert isinstance(i, Image.Image) 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)) 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()) it = iter(im.getdata())
for data_row in data: for data_row in data:
im_row = [next(it) for _ in range(im.size[0])] im_row = [next(it) for _ in range(im.size[0])]
@ -37,7 +37,13 @@ def assert_image(im, data, delta=0):
next(it) 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 # check grayscale image
assert_image(box_blur(im, radius, passes), data, delta) assert_image(box_blur(im, radius, passes), data, delta)
rgba = Image.merge("RGBA", (im, im, im, im)) rgba = Image.merge("RGBA", (im, im, im, im))
@ -45,7 +51,7 @@ def assert_blur(im, radius, data, passes=1, delta=0):
assert_image(band, data, delta) assert_image(band, data, delta)
def test_color_modes(): def test_color_modes() -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
box_blur(sample.convert("1")) box_blur(sample.convert("1"))
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -65,7 +71,7 @@ def test_color_modes():
box_blur(sample.convert("YCbCr")) box_blur(sample.convert("YCbCr"))
def test_radius_0(): def test_radius_0() -> None:
assert_blur( assert_blur(
sample, sample,
0, 0,
@ -81,7 +87,7 @@ def test_radius_0():
) )
def test_radius_0_02(): def test_radius_0_02() -> None:
assert_blur( assert_blur(
sample, sample,
0.02, 0.02,
@ -98,7 +104,7 @@ def test_radius_0_02():
) )
def test_radius_0_05(): def test_radius_0_05() -> None:
assert_blur( assert_blur(
sample, sample,
0.05, 0.05,
@ -115,7 +121,7 @@ def test_radius_0_05():
) )
def test_radius_0_1(): def test_radius_0_1() -> None:
assert_blur( assert_blur(
sample, sample,
0.1, 0.1,
@ -132,7 +138,7 @@ def test_radius_0_1():
) )
def test_radius_0_5(): def test_radius_0_5() -> None:
assert_blur( assert_blur(
sample, sample,
0.5, 0.5,
@ -149,7 +155,7 @@ def test_radius_0_5():
) )
def test_radius_1(): def test_radius_1() -> None:
assert_blur( assert_blur(
sample, sample,
1, 1,
@ -166,7 +172,7 @@ def test_radius_1():
) )
def test_radius_1_5(): def test_radius_1_5() -> None:
assert_blur( assert_blur(
sample, sample,
1.5, 1.5,
@ -183,7 +189,7 @@ def test_radius_1_5():
) )
def test_radius_bigger_then_half(): def test_radius_bigger_then_half() -> None:
assert_blur( assert_blur(
sample, sample,
3, 3,
@ -200,7 +206,7 @@ def test_radius_bigger_then_half():
) )
def test_radius_bigger_then_width(): def test_radius_bigger_then_width() -> None:
assert_blur( assert_blur(
sample, sample,
10, 10,
@ -215,7 +221,7 @@ def test_radius_bigger_then_width():
) )
def test_extreme_large_radius(): def test_extreme_large_radius() -> None:
assert_blur( assert_blur(
sample, sample,
600, 600,
@ -230,7 +236,7 @@ def test_extreme_large_radius():
) )
def test_two_passes(): def test_two_passes() -> None:
assert_blur( assert_blur(
sample, sample,
1, 1,
@ -248,7 +254,7 @@ def test_two_passes():
) )
def test_three_passes(): def test_three_passes() -> None:
assert_blur( assert_blur(
sample, sample,
1, 1,

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
from array import array from array import array
from types import ModuleType
import pytest import pytest
@ -8,6 +9,7 @@ from PIL import Image, ImageFilter
from .helper import assert_image_equal from .helper import assert_image_equal
numpy: ModuleType | None
try: try:
import numpy import numpy
except ImportError: except ImportError:
@ -15,7 +17,9 @@ except ImportError:
class TestColorLut3DCoreAPI: 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): if isinstance(size, tuple):
size_1d, size_2d, size_3d = size size_1d, size_2d, size_3d = size
else: else:
@ -41,7 +45,7 @@ class TestColorLut3DCoreAPI:
[item for sublist in table for item in sublist], [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) im = Image.new("RGB", (10, 10), 0)
with pytest.raises(ValueError, match="filter"): with pytest.raises(ValueError, match="filter"):
@ -101,7 +105,7 @@ class TestColorLut3DCoreAPI:
with pytest.raises(TypeError): with pytest.raises(TypeError):
im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, 16) im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, 16)
def test_correct_args(self): def test_correct_args(self) -> None:
im = Image.new("RGB", (10, 10), 0) im = Image.new("RGB", (10, 10), 0)
im.im.color_lut_3d( im.im.color_lut_3d(
@ -136,7 +140,7 @@ class TestColorLut3DCoreAPI:
*self.generate_identity_table(3, (3, 3, 65)), *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"): with pytest.raises(ValueError, match="wrong mode"):
im = Image.new("L", (10, 10), 0) im = Image.new("L", (10, 10), 0)
im.im.color_lut_3d( im.im.color_lut_3d(
@ -167,7 +171,7 @@ class TestColorLut3DCoreAPI:
"RGB", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3) "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 = Image.new("RGBA", (10, 10), 0)
im.im.color_lut_3d( im.im.color_lut_3d(
"RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3) "RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(3, 3)
@ -188,7 +192,7 @@ class TestColorLut3DCoreAPI:
"RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3) "RGBA", Image.Resampling.BILINEAR, *self.generate_identity_table(4, 3)
) )
def test_identities(self): def test_identities(self) -> None:
g = Image.linear_gradient("L") g = Image.linear_gradient("L")
im = Image.merge( im = Image.merge(
"RGB", "RGB",
@ -224,7 +228,7 @@ class TestColorLut3DCoreAPI:
), ),
) )
def test_identities_4_channels(self): def test_identities_4_channels(self) -> None:
g = Image.linear_gradient("L") g = Image.linear_gradient("L")
im = Image.merge( im = Image.merge(
"RGB", "RGB",
@ -247,7 +251,7 @@ class TestColorLut3DCoreAPI:
), ),
) )
def test_copy_alpha_channel(self): def test_copy_alpha_channel(self) -> None:
g = Image.linear_gradient("L") g = Image.linear_gradient("L")
im = Image.merge( im = Image.merge(
"RGBA", "RGBA",
@ -270,7 +274,7 @@ class TestColorLut3DCoreAPI:
), ),
) )
def test_channels_order(self): def test_channels_order(self) -> None:
g = Image.linear_gradient("L") g = Image.linear_gradient("L")
im = Image.merge( im = Image.merge(
"RGB", "RGB",
@ -295,7 +299,7 @@ class TestColorLut3DCoreAPI:
]))) ])))
# fmt: on # fmt: on
def test_overflow(self): def test_overflow(self) -> None:
g = Image.linear_gradient("L") g = Image.linear_gradient("L")
im = Image.merge( im = Image.merge(
"RGB", "RGB",
@ -348,7 +352,7 @@ class TestColorLut3DCoreAPI:
class TestColorLut3DFilter: class TestColorLut3DFilter:
def test_wrong_args(self): def test_wrong_args(self) -> None:
with pytest.raises(ValueError, match="should be either an integer"): with pytest.raises(ValueError, match="should be either an integer"):
ImageFilter.Color3DLUT("small", [1]) ImageFilter.Color3DLUT("small", [1])
@ -376,7 +380,7 @@ class TestColorLut3DFilter:
with pytest.raises(ValueError, match="Only 3 or 4 output"): with pytest.raises(ValueError, match="Only 3 or 4 output"):
ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8, channels=2) 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) lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8)
assert tuple(lut.size) == (2, 2, 2) assert tuple(lut.size) == (2, 2, 2)
assert lut.name == "Color 3D LUT" assert lut.name == "Color 3D LUT"
@ -394,7 +398,8 @@ class TestColorLut3DFilter:
assert lut.table == list(range(4)) * 8 assert lut.table == list(range(4)) * 8
@pytest.mark.skipif(numpy is None, reason="NumPy not installed") @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) table = numpy.ones((5, 6, 7, 3), dtype=numpy.float16)
with pytest.raises(ValueError, match="should have either channels"): with pytest.raises(ValueError, match="should have either channels"):
lut = ImageFilter.Color3DLUT((5, 6, 7), table) lut = ImageFilter.Color3DLUT((5, 6, 7), table)
@ -427,7 +432,8 @@ class TestColorLut3DFilter:
assert lut.table[0] == 33 assert lut.table[0] == 33
@pytest.mark.skipif(numpy is None, reason="NumPy not installed") @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") g = Image.linear_gradient("L")
im = Image.merge( im = Image.merge(
"RGB", "RGB",
@ -466,7 +472,7 @@ class TestColorLut3DFilter:
lut.table = numpy.array(lut.table, dtype=numpy.int8) lut.table = numpy.array(lut.table, dtype=numpy.int8)
im.filter(lut) im.filter(lut)
def test_repr(self): def test_repr(self) -> None:
lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8)
assert repr(lut) == "<Color3DLUT from list size=2x2x2 channels=3>" assert repr(lut) == "<Color3DLUT from list size=2x2x2 channels=3>"
@ -484,7 +490,7 @@ class TestColorLut3DFilter:
class TestGenerateColorLut3D: 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"): with pytest.raises(ValueError, match="3 or 4 output channels"):
ImageFilter.Color3DLUT.generate( ImageFilter.Color3DLUT.generate(
5, channels=2, callback=lambda r, g, b: (r, g, b) 5, channels=2, callback=lambda r, g, b: (r, g, b)
@ -498,7 +504,7 @@ class TestGenerateColorLut3D:
5, channels=4, callback=lambda r, g, b: (r, g, b) 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)) lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
assert tuple(lut.size) == (5, 5, 5) assert tuple(lut.size) == (5, 5, 5)
assert lut.name == "Color 3D LUT" assert lut.name == "Color 3D LUT"
@ -508,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] 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 # fmt: on
def test_4_channels(self): def test_4_channels(self) -> None:
lut = ImageFilter.Color3DLUT.generate( lut = ImageFilter.Color3DLUT.generate(
5, channels=4, callback=lambda r, g, b: (b, r, g, (r + g + b) / 2) 5, channels=4, callback=lambda r, g, b: (b, r, g, (r + g + b) / 2)
) )
@ -521,7 +527,7 @@ class TestGenerateColorLut3D:
] ]
# fmt: on # fmt: on
def test_apply(self): def test_apply(self) -> None:
lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
g = Image.linear_gradient("L") g = Image.linear_gradient("L")
@ -537,7 +543,7 @@ class TestGenerateColorLut3D:
class TestTransformColorLut3D: 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)) source = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b))
with pytest.raises(ValueError, match="Only 3 or 4 output"): with pytest.raises(ValueError, match="Only 3 or 4 output"):
@ -552,7 +558,7 @@ class TestTransformColorLut3D:
with pytest.raises(TypeError): with pytest.raises(TypeError):
source.transform(lambda r, g, b, a: (r, g, b)) 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( source = ImageFilter.Color3DLUT.generate(
2, lambda r, g, b: (r, g, b), target_mode="HSV" 2, lambda r, g, b: (r, g, b), target_mode="HSV"
) )
@ -563,7 +569,7 @@ class TestTransformColorLut3D:
lut = source.transform(lambda r, g, b: (r, g, b), target_mode="RGB") lut = source.transform(lambda r, g, b: (r, g, b), target_mode="RGB")
assert lut.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)) 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)) lut = source.transform(lambda r, g, b: (r * r, g * g, b * b))
assert tuple(lut.size) == tuple(source.size) assert tuple(lut.size) == tuple(source.size)
@ -571,7 +577,7 @@ class TestTransformColorLut3D:
assert lut.table != source.table 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] 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)) 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) lut = source.transform(lambda r, g, b: (r * r, g * g, b * b, 1), channels=4)
assert tuple(lut.size) == tuple(source.size) assert tuple(lut.size) == tuple(source.size)
@ -583,7 +589,7 @@ class TestTransformColorLut3D:
0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1] 0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1]
# fmt: on # fmt: on
def test_4_to_3_channels(self): def test_4_to_3_channels(self) -> None:
source = ImageFilter.Color3DLUT.generate( source = ImageFilter.Color3DLUT.generate(
(3, 6, 5), lambda r, g, b: (r, g, b, 1), channels=4 (3, 6, 5), lambda r, g, b: (r, g, b, 1), channels=4
) )
@ -599,7 +605,7 @@ class TestTransformColorLut3D:
1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0] 1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0]
# fmt: on # fmt: on
def test_4_to_4_channels(self): def test_4_to_4_channels(self) -> None:
source = ImageFilter.Color3DLUT.generate( source = ImageFilter.Color3DLUT.generate(
(6, 5, 4), lambda r, g, b: (r, g, b, 1), channels=4 (6, 5, 4), lambda r, g, b: (r, g, b, 1), channels=4
) )
@ -613,7 +619,7 @@ class TestTransformColorLut3D:
0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5] 0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5]
# fmt: on # fmt: on
def test_with_normals_3_channels(self): def test_with_normals_3_channels(self) -> None:
source = ImageFilter.Color3DLUT.generate( source = ImageFilter.Color3DLUT.generate(
(6, 5, 4), lambda r, g, b: (r * r, g * g, b * b) (6, 5, 4), lambda r, g, b: (r * r, g * g, b * b)
) )
@ -629,7 +635,7 @@ class TestTransformColorLut3D:
0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0] 0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0]
# fmt: on # fmt: on
def test_with_normals_4_channels(self): def test_with_normals_4_channels(self) -> None:
source = ImageFilter.Color3DLUT.generate( source = ImageFilter.Color3DLUT.generate(
(3, 6, 5), lambda r, g, b: (r * r, g * g, b * b, 1), channels=4 (3, 6, 5), lambda r, g, b: (r * r, g * g, b * b, 1), channels=4
) )

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from PIL import Image from PIL import Image
@ -12,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: with Image.open("Tests/images/blp/blp1_jpeg.blp") as im:
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png") assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
@ -20,22 +22,22 @@ def test_load_blp1():
im.load() im.load()
def test_load_blp2_raw(): def test_load_blp2_raw() -> None:
with Image.open("Tests/images/blp/blp2_raw.blp") as im: with Image.open("Tests/images/blp/blp2_raw.blp") as im:
assert_image_equal_tofile(im, "Tests/images/blp/blp2_raw.png") 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: with Image.open("Tests/images/blp/blp2_dxt1.blp") as im:
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1.png") 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: with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im:
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png") assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
def test_save(tmp_path): def test_save(tmp_path: Path) -> None:
f = str(tmp_path / "temp.blp") f = str(tmp_path / "temp.blp")
for version in ("BLP1", "BLP2"): for version in ("BLP1", "BLP2"):
@ -69,7 +71,7 @@ def test_save(tmp_path):
"Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp", "Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp",
], ],
) )
def test_crashes(test_file): def test_crashes(test_file: str) -> None:
with open(test_file, "rb") as f: with open(test_file, "rb") as f:
with Image.open(f) as im: with Image.open(f) as im:
with pytest.raises(OSError): with pytest.raises(OSError):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import warnings
import pytest import pytest
from PIL import FliImagePlugin, Image from PIL import FliImagePlugin, Image, ImageFile
from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy
@ -12,11 +12,14 @@ from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy
# save as...-> hopper.fli, default options. # save as...-> hopper.fli, default options.
static_test_file = "Tests/images/hopper.fli" static_test_file = "Tests/images/hopper.fli"
# From https://samples.libav.org/fli-flc/ # From https://samples.ffmpeg.org/fli-flc/
animated_test_file = "Tests/images/a.fli" animated_test_file = "Tests/images/a.fli"
# From https://samples.ffmpeg.org/fli-flc/
animated_test_file_with_prefix_chunk = "Tests/images/2422.flc"
def test_sanity():
def test_sanity() -> None:
with Image.open(static_test_file) as im: with Image.open(static_test_file) as im:
im.load() im.load()
assert im.mode == "P" assert im.mode == "P"
@ -32,9 +35,27 @@ def test_sanity():
assert im.is_animated assert im.is_animated
def test_prefix_chunk() -> None:
ImageFile.LOAD_TRUNCATED_IMAGES = True
try:
with Image.open(animated_test_file_with_prefix_chunk) as im:
assert im.mode == "P"
assert im.size == (320, 200)
assert im.format == "FLI"
assert im.info["duration"] == 171
assert im.is_animated
palette = im.getpalette()
assert palette[3:6] == [255, 255, 255]
assert palette[381:384] == [204, 204, 12]
assert palette[765:] == [252, 0, 0]
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file(): def test_unclosed_file() -> None:
def open(): def open() -> None:
im = Image.open(static_test_file) im = Image.open(static_test_file)
im.load() im.load()
@ -42,14 +63,14 @@ def test_unclosed_file():
open() open()
def test_closed_file(): def test_closed_file() -> None:
with warnings.catch_warnings(): with warnings.catch_warnings():
im = Image.open(static_test_file) im = Image.open(static_test_file)
im.load() im.load()
im.close() im.close()
def test_seek_after_close(): def test_seek_after_close() -> None:
im = Image.open(animated_test_file) im = Image.open(animated_test_file)
im.seek(1) im.seek(1)
im.close() im.close()
@ -58,13 +79,13 @@ def test_seek_after_close():
im.seek(0) im.seek(0)
def test_context_manager(): def test_context_manager() -> None:
with warnings.catch_warnings(): with warnings.catch_warnings():
with Image.open(static_test_file) as im: with Image.open(static_test_file) as im:
im.load() im.load()
def test_tell(): def test_tell() -> None:
# Arrange # Arrange
with Image.open(static_test_file) as im: with Image.open(static_test_file) as im:
# Act # Act
@ -74,20 +95,20 @@ def test_tell():
assert frame == 0 assert frame == 0
def test_invalid_file(): def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
FliImagePlugin.FliImageFile(invalid_file) 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("Tests/images/hopper_palette_chunk_second.fli") as im:
with Image.open(static_test_file) as expected: with Image.open(static_test_file) as expected:
assert_image_equal(im.convert("RGB"), expected.convert("RGB")) 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: with Image.open(static_test_file) as im:
assert im.n_frames == 1 assert im.n_frames == 1
assert not im.is_animated assert not im.is_animated
@ -97,7 +118,7 @@ def test_n_frames():
assert im.is_animated assert im.is_animated
def test_eoferror(): def test_eoferror() -> None:
with Image.open(animated_test_file) as im: with Image.open(animated_test_file) as im:
n_frames = im.n_frames n_frames = im.n_frames
@ -110,7 +131,7 @@ def test_eoferror():
im.seek(n_frames - 1) im.seek(n_frames - 1)
def test_seek_tell(): def test_seek_tell() -> None:
with Image.open(animated_test_file) as im: with Image.open(animated_test_file) as im:
layer_number = im.tell() layer_number = im.tell()
assert layer_number == 0 assert layer_number == 0
@ -132,7 +153,7 @@ def test_seek_tell():
assert layer_number == 1 assert layer_number == 1
def test_seek(): def test_seek() -> None:
with Image.open(animated_test_file) as im: with Image.open(animated_test_file) as im:
im.seek(50) im.seek(50)
@ -147,7 +168,7 @@ def test_seek():
], ],
) )
@pytest.mark.timeout(timeout=3) @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 open(test_file, "rb") as f:
with Image.open(f) as im: with Image.open(f) as im:
with pytest.raises(OSError): with pytest.raises(OSError):
@ -160,7 +181,7 @@ def test_timeouts(test_file):
"Tests/images/crash-5762152299364352.fli", "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 open(test_file, "rb") as f:
with Image.open(f) as im: with Image.open(f) as im:
with pytest.raises(OSError): with pytest.raises(OSError):

View File

@ -11,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: with Image.open("Tests/images/input_bw_one_band.fpx") as im:
assert im.mode == "L" assert im.mode == "L"
assert im.size == (70, 46) assert im.size == (70, 46)
@ -20,7 +20,7 @@ def test_sanity():
assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png") 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: with Image.open("Tests/images/input_bw_one_band.fpx") as im:
pass pass
assert im.ole.fp.closed assert im.ole.fp.closed
@ -30,7 +30,7 @@ def test_close():
assert im.ole.fp.closed assert im.ole.fp.closed
def test_invalid_file(): def test_invalid_file() -> None:
# Test an invalid OLE file # Test an invalid OLE file
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
@ -42,7 +42,7 @@ def test_invalid_file():
FpxImagePlugin.FpxImageFile(ole_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 pytest.raises(OSError, match="Invalid number of bands"):
with Image.open("Tests/images/input_bw_five_bands.fpx"): with Image.open("Tests/images/input_bw_five_bands.fpx"):
pass pass

View File

@ -7,18 +7,18 @@ from PIL import FtexImagePlugin, Image
from .helper import assert_image_equal_tofile, assert_image_similar from .helper import assert_image_equal_tofile, assert_image_similar
def test_load_raw(): def test_load_raw() -> None:
with Image.open("Tests/images/ftex_uncompressed.ftu") as im: with Image.open("Tests/images/ftex_uncompressed.ftu") as im:
assert_image_equal_tofile(im, "Tests/images/ftex_uncompressed.png") 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.ftc") as im:
with Image.open("Tests/images/ftex_dxt1.png") as target: with Image.open("Tests/images/ftex_dxt1.png") as target:
assert_image_similar(im, target.convert("RGBA"), 15) assert_image_similar(im, target.convert("RGBA"), 15)
def test_invalid_file(): def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):

View File

@ -7,12 +7,12 @@ from PIL import GbrImagePlugin, Image
from .helper import assert_image_equal_tofile 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: with Image.open("Tests/images/gbr.gbr") as im:
assert_image_equal_tofile(im, "Tests/images/gbr.png") assert_image_equal_tofile(im, "Tests/images/gbr.png")
def test_load(): def test_load() -> None:
with Image.open("Tests/images/gbr.gbr") as im: with Image.open("Tests/images/gbr.gbr") as im:
assert im.load()[0, 0] == (0, 0, 0, 0) assert im.load()[0, 0] == (0, 0, 0, 0)
@ -20,14 +20,14 @@ def test_load():
assert im.load()[0, 0] == (0, 0, 0, 0) 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: with Image.open("Tests/images/gbr.gbr") as im:
im.load() im.load()
im.load() im.load()
assert_image_equal_tofile(im, "Tests/images/gbr.png") assert_image_equal_tofile(im, "Tests/images/gbr.png")
def test_invalid_file(): def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):

View File

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

View File

@ -2,6 +2,8 @@ from __future__ import annotations
import warnings import warnings
from io import BytesIO from io import BytesIO
from pathlib import Path
from typing import Generator
import pytest import pytest
@ -23,7 +25,7 @@ with open(TEST_GIF, "rb") as f:
data = f.read() data = f.read()
def test_sanity(): def test_sanity() -> None:
with Image.open(TEST_GIF) as im: with Image.open(TEST_GIF) as im:
im.load() im.load()
assert im.mode == "P" assert im.mode == "P"
@ -33,8 +35,8 @@ def test_sanity():
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file(): def test_unclosed_file() -> None:
def open(): def open() -> None:
im = Image.open(TEST_GIF) im = Image.open(TEST_GIF)
im.load() im.load()
@ -42,14 +44,14 @@ def test_unclosed_file():
open() open()
def test_closed_file(): def test_closed_file() -> None:
with warnings.catch_warnings(): with warnings.catch_warnings():
im = Image.open(TEST_GIF) im = Image.open(TEST_GIF)
im.load() im.load()
im.close() im.close()
def test_seek_after_close(): def test_seek_after_close() -> None:
im = Image.open("Tests/images/iss634.gif") im = Image.open("Tests/images/iss634.gif")
im.load() im.load()
im.close() im.close()
@ -62,20 +64,20 @@ def test_seek_after_close():
im.seek(1) im.seek(1)
def test_context_manager(): def test_context_manager() -> None:
with warnings.catch_warnings(): with warnings.catch_warnings():
with Image.open(TEST_GIF) as im: with Image.open(TEST_GIF) as im:
im.load() im.load()
def test_invalid_file(): def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
GifImagePlugin.GifImageFile(invalid_file) 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: with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
assert im.mode == "L" assert im.mode == "L"
assert im.load()[0, 0] == 128 assert im.load()[0, 0] == 128
@ -86,7 +88,7 @@ def test_l_mode_transparency():
assert im.load()[0, 0] == 128 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: with Image.open("Tests/images/no_palette_after_rgb.gif") as im:
im.seek(1) im.seek(1)
assert im.mode == "RGB" assert im.mode == "RGB"
@ -95,13 +97,13 @@ def test_l_mode_after_rgb():
assert im.mode == "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: with Image.open("Tests/images/palette_not_needed_for_second_frame.gif") as im:
im.seek(1) im.seek(1)
assert_image_similar(im, hopper("L").convert("RGB"), 8) 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: with Image.open("Tests/images/iss634.gif") as im:
expected_rgb_always = im.convert("RGB") expected_rgb_always = im.convert("RGB")
@ -142,14 +144,14 @@ def test_strategy():
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST
def test_optimize(): def test_optimize() -> None:
def test_grayscale(optimize): def test_grayscale(optimize: int) -> int:
im = Image.new("L", (1, 1), 0) im = Image.new("L", (1, 1), 0)
filename = BytesIO() filename = BytesIO()
im.save(filename, "GIF", optimize=optimize) im.save(filename, "GIF", optimize=optimize)
return len(filename.getvalue()) return len(filename.getvalue())
def test_bilevel(optimize): def test_bilevel(optimize: int) -> int:
im = Image.new("1", (1, 1), 0) im = Image.new("1", (1, 1), 0)
test_file = BytesIO() test_file = BytesIO()
im.save(test_file, "GIF", optimize=optimize) im.save(test_file, "GIF", optimize=optimize)
@ -177,7 +179,9 @@ def test_optimize():
(4, 513, 256), (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. # 256 color Palette image, posterize to > 128 and < 128 levels.
# Size bigger and smaller than 512x512. # Size bigger and smaller than 512x512.
# Check the palette for number of colors allocated. # Check the palette for number of colors allocated.
@ -199,14 +203,14 @@ def test_optimize_correctness(colors, size, expected_palette_length):
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) 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))) im = Image.frombytes("L", (16, 16), bytes(range(256)))
test_file = BytesIO() test_file = BytesIO()
im.save(test_file, "GIF", optimize=True) im.save(test_file, "GIF", optimize=True)
assert im.mode == "L" 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 = Image.new("P", (8, 1))
im.palette = ImagePalette.raw("RGB", bytes((0, 0, 0) * 150)) im.palette = ImagePalette.raw("RGB", bytes((0, 0, 0) * 150))
for i in range(8): for i in range(8):
@ -219,7 +223,7 @@ def test_optimize_if_palette_can_be_reduced_by_half():
assert len(reloaded.palette.palette) // 3 == colors 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") out = str(tmp_path / "temp.gif")
im = Image.new("P", (1, 256)) im = Image.new("P", (1, 256))
@ -240,7 +244,7 @@ def test_full_palette_second_frame(tmp_path):
reloaded.getpixel((0, i)) == i reloaded.getpixel((0, i)) == i
def test_roundtrip(tmp_path): def test_roundtrip(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
im = hopper() im = hopper()
im.save(out) im.save(out)
@ -248,7 +252,7 @@ def test_roundtrip(tmp_path):
assert_image_similar(reread.convert("RGB"), im, 50) 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 # see https://github.com/python-pillow/Pillow/issues/403
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
with Image.open(TEST_GIF) as im: with Image.open(TEST_GIF) as im:
@ -258,7 +262,7 @@ def test_roundtrip2(tmp_path):
assert_image_similar(reread.convert("RGB"), hopper(), 50) 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 # Single frame image
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
im = hopper() im = hopper()
@ -275,7 +279,7 @@ def test_roundtrip_save_all(tmp_path):
assert reread.n_frames == 5 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") out = str(tmp_path / "temp.gif")
im = Image.new("1", (1, 1)) im = Image.new("1", (1, 1))
im2 = Image.new("1", (1, 1), 1) im2 = Image.new("1", (1, 1), 1)
@ -296,7 +300,7 @@ def test_roundtrip_save_all_1(tmp_path):
("Tests/images/dispose_bgnd_rgba.gif", "RGBA"), ("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: with Image.open(path) as im:
assert im.mode == "P" assert im.mode == "P"
first_frame_colors = im.palette.colors.keys() first_frame_colors = im.palette.colors.keys()
@ -314,7 +318,7 @@ def test_loading_multiple_palettes(path, mode):
assert im.load()[24, 24] not in first_frame_colors 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"] important_headers = ["background", "version", "duration", "loop"]
# Multiframe image # Multiframe image
with Image.open("Tests/images/dispose_bgnd.gif") as im: with Image.open("Tests/images/dispose_bgnd.gif") as im:
@ -327,7 +331,7 @@ def test_headers_saving_for_animated_gifs(tmp_path):
assert info[header] == reread.info[header] 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 # see https://github.com/python-pillow/Pillow/issues/513
with Image.open(TEST_GIF) as im: with Image.open(TEST_GIF) as im:
@ -343,12 +347,12 @@ def test_palette_handling(tmp_path):
assert_image_similar(im, reloaded.convert("RGB"), 10) 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 # 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") out = str(tmp_path / "temp.gif")
im.copy().save(out, *args, **kwargs) im.copy().save(out, **kwargs)
reloaded = Image.open(out) reloaded = Image.open(out)
return reloaded return reloaded
@ -368,7 +372,7 @@ def test_palette_434(tmp_path):
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") @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: with Image.open(TEST_GIF) as img:
img = img.convert("RGB") img = img.convert("RGB")
@ -379,7 +383,7 @@ def test_save_netpbm_bmp_mode(tmp_path):
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") @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: with Image.open(TEST_GIF) as img:
img = img.convert("L") img = img.convert("L")
@ -389,7 +393,7 @@ def test_save_netpbm_l_mode(tmp_path):
assert_image_similar(img, reloaded.convert("L"), 0) 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: with Image.open("Tests/images/dispose_none.gif") as img:
frame_count = 0 frame_count = 0
try: try:
@ -400,7 +404,7 @@ def test_seek():
assert frame_count == 5 assert frame_count == 5
def test_seek_info(): def test_seek_info() -> None:
with Image.open("Tests/images/iss634.gif") as im: with Image.open("Tests/images/iss634.gif") as im:
info = im.info.copy() info = im.info.copy()
@ -410,7 +414,7 @@ def test_seek_info():
assert im.info == info assert im.info == info
def test_seek_rewind(): def test_seek_rewind() -> None:
with Image.open("Tests/images/iss634.gif") as im: with Image.open("Tests/images/iss634.gif") as im:
im.seek(2) im.seek(2)
im.seek(1) im.seek(1)
@ -428,7 +432,7 @@ def test_seek_rewind():
("Tests/images/iss634.gif", 42), ("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 # Test is_animated before n_frames
with Image.open(path) as im: with Image.open(path) as im:
assert im.is_animated == (n_frames != 1) assert im.is_animated == (n_frames != 1)
@ -439,7 +443,7 @@ def test_n_frames(path, n_frames):
assert im.is_animated == (n_frames != 1) assert im.is_animated == (n_frames != 1)
def test_no_change(): def test_no_change() -> None:
# Test n_frames does not change the image # Test n_frames does not change the image
with Image.open("Tests/images/dispose_bgnd.gif") as im: with Image.open("Tests/images/dispose_bgnd.gif") as im:
im.seek(1) im.seek(1)
@ -460,7 +464,7 @@ def test_no_change():
assert_image_equal(im, expected) assert_image_equal(im, expected)
def test_eoferror(): def test_eoferror() -> None:
with Image.open(TEST_GIF) as im: with Image.open(TEST_GIF) as im:
n_frames = im.n_frames n_frames = im.n_frames
@ -473,13 +477,13 @@ def test_eoferror():
im.seek(n_frames - 1) 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: with Image.open("Tests/images/first_frame_transparency.gif") as im:
px = im.load() px = im.load()
assert px[0, 0] == im.info["transparency"] 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: with Image.open("Tests/images/dispose_none.gif") as img:
try: try:
while True: while True:
@ -489,7 +493,7 @@ def test_dispose_none():
pass pass
def test_dispose_none_load_end(): def test_dispose_none_load_end() -> None:
# Test image created with: # Test image created with:
# #
# im = Image.open("transparent.gif") # im = Image.open("transparent.gif")
@ -502,7 +506,7 @@ def test_dispose_none_load_end():
assert_image_equal_tofile(img, "Tests/images/dispose_none_load_end_second.png") 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: with Image.open("Tests/images/dispose_bgnd.gif") as img:
try: try:
while True: while True:
@ -512,7 +516,7 @@ def test_dispose_background():
pass pass
def test_dispose_background_transparency(): def test_dispose_background_transparency() -> None:
with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img: with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img:
img.seek(2) img.seek(2)
px = img.load() px = img.load()
@ -540,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 GifImagePlugin.LOADING_STRATEGY = loading_strategy
try: try:
with Image.open("Tests/images/transparent_dispose.gif") as img: with Image.open("Tests/images/transparent_dispose.gif") as img:
@ -553,7 +560,7 @@ def test_transparent_dispose(loading_strategy, expected_colors):
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST 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: with Image.open("Tests/images/dispose_prev.gif") as img:
try: try:
while True: while True:
@ -563,7 +570,7 @@ def test_dispose_previous():
pass 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: with Image.open("Tests/images/dispose_prev_first_frame.gif") as im:
im.seek(1) im.seek(1)
assert_image_equal_tofile( assert_image_equal_tofile(
@ -571,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: with Image.open("Tests/images/dispose_none.gif") as img:
img.load() img.load()
img.seek(1) img.seek(1)
@ -582,7 +589,7 @@ def test_previous_frame_loaded():
assert_image_equal(img_skipped, img) 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") out = str(tmp_path / "temp.gif")
im_list = [ im_list = [
Image.new("L", (100, 100), "#000"), Image.new("L", (100, 100), "#000"),
@ -610,7 +617,7 @@ def test_save_dispose(tmp_path):
assert img.disposal_method == i + 1 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") out = str(tmp_path / "temp.gif")
# Four colors: white, gray, black, red # Four colors: white, gray, black, red
@ -641,7 +648,7 @@ def test_dispose2_palette(tmp_path):
assert rgb_img.getpixel((50, 50)) == circle 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") out = str(tmp_path / "temp.gif")
# 4 frames: red/blue, red/red, blue/blue, red/blue # 4 frames: red/blue, red/red, blue/blue, red/blue
@ -683,7 +690,7 @@ def test_dispose2_diff(tmp_path):
assert rgb_img.getpixel((1, 1)) == (255, 255, 255, 0) 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") out = str(tmp_path / "temp.gif")
im_list = [] im_list = []
@ -709,7 +716,7 @@ def test_dispose2_background(tmp_path):
assert im.getpixel((0, 0)) == (255, 0, 0) 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") out = str(tmp_path / "temp.gif")
im_list = [Image.new("RGBA", (1, 20))] im_list = [Image.new("RGBA", (1, 20))]
@ -727,7 +734,7 @@ def test_dispose2_background_frame(tmp_path):
assert im.n_frames == 3 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") out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/different_transparency.gif") as im: with Image.open("Tests/images/different_transparency.gif") as im:
assert im.info["transparency"] == 0 assert im.info["transparency"] == 0
@ -747,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: with Image.open("Tests/images/iss634.gif") as img:
# Seek to the second frame # Seek to the second frame
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
@ -757,7 +764,7 @@ def test_no_transparency_in_second_frame():
assert img.histogram()[255] == 0 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") out = str(tmp_path / "temp.gif")
im = Image.new("P", (1, 2)) im = Image.new("P", (1, 2))
@ -773,7 +780,7 @@ def test_remapped_transparency(tmp_path):
assert reloaded.info["transparency"] == reloaded.getpixel((0, 1)) assert reloaded.info["transparency"] == reloaded.getpixel((0, 1))
def test_duration(tmp_path): def test_duration(tmp_path: Path) -> None:
duration = 1000 duration = 1000
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
@ -787,7 +794,7 @@ def test_duration(tmp_path):
assert reread.info["duration"] == duration assert reread.info["duration"] == duration
def test_multiple_duration(tmp_path): def test_multiple_duration(tmp_path: Path) -> None:
duration_list = [1000, 2000, 3000] duration_list = [1000, 2000, 3000]
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
@ -822,7 +829,7 @@ def test_multiple_duration(tmp_path):
pass pass
def test_roundtrip_info_duration(tmp_path): def test_roundtrip_info_duration(tmp_path: Path) -> None:
duration_list = [100, 500, 500] duration_list = [100, 500, 500]
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
@ -839,7 +846,7 @@ def test_roundtrip_info_duration(tmp_path):
] == duration_list ] == 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") out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/duplicate_frame.gif") as im: with Image.open("Tests/images/duplicate_frame.gif") as im:
assert [frame.info["duration"] for frame in ImageSequence.Iterator(im)] == [ assert [frame.info["duration"] for frame in ImageSequence.Iterator(im)] == [
@ -855,7 +862,7 @@ def test_roundtrip_info_duration_combined(tmp_path):
] == [1000, 2000] ] == [1000, 2000]
def test_identical_frames(tmp_path): def test_identical_frames(tmp_path: Path) -> None:
duration_list = [1000, 1500, 2000, 4000] duration_list = [1000, 1500, 2000, 4000]
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
@ -888,7 +895,9 @@ def test_identical_frames(tmp_path):
1500, 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") out = str(tmp_path / "temp.gif")
im_list = [ im_list = [
Image.new("L", (100, 100), "#000"), Image.new("L", (100, 100), "#000"),
@ -905,7 +914,7 @@ def test_identical_frames_to_single_frame(duration, tmp_path):
assert reread.info["duration"] == 4500 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") out = str(tmp_path / "temp.gif")
im = Image.new("L", (100, 100), "#000") im = Image.new("L", (100, 100), "#000")
im.save(out, loop=None) im.save(out, loop=None)
@ -913,7 +922,7 @@ def test_loop_none(tmp_path):
assert "loop" not in reread.info 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 number_of_loops = 2
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
@ -931,7 +940,7 @@ def test_number_of_loops(tmp_path):
assert im.info["loop"] == 2 assert im.info["loop"] == 2
def test_background(tmp_path): def test_background(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
im = Image.new("L", (100, 100), "#000") im = Image.new("L", (100, 100), "#000")
im.info["background"] = 1 im.info["background"] = 1
@ -940,7 +949,7 @@ def test_background(tmp_path):
assert reread.info["background"] == im.info["background"] 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") out = str(tmp_path / "temp.gif")
# Test opaque WebP background # Test opaque WebP background
@ -955,7 +964,7 @@ def test_webp_background(tmp_path):
im.save(out) im.save(out)
def test_comment(tmp_path): def test_comment(tmp_path: Path) -> None:
with Image.open(TEST_GIF) as im: with Image.open(TEST_GIF) as im:
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0" assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0"
@ -975,7 +984,7 @@ def test_comment(tmp_path):
assert reread.info["version"] == b"GIF89a" 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") out = str(tmp_path / "temp.gif")
im = Image.new("L", (100, 100), "#000") im = Image.new("L", (100, 100), "#000")
comment = b"Test comment text" comment = b"Test comment text"
@ -990,18 +999,18 @@ def test_comment_over_255(tmp_path):
assert reread.info["version"] == b"GIF89a" 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: with Image.open("Tests/images/hopper_zero_comment_subblocks.gif") as im:
assert_image_equal_tofile(im, TEST_GIF) 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: with Image.open("Tests/images/multiple_comments.gif") as im:
# Multiple comment blocks in a frame are separated not concatenated # Multiple comment blocks in a frame are separated not concatenated
assert im.info["comment"] == b"Test comment 1\nTest comment 2" 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") out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
assert "comment" in im.info assert "comment" in im.info
@ -1014,7 +1023,7 @@ def test_empty_string_comment(tmp_path):
assert "comment" not in frame.info 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 # Test that a comment block at the beginning is kept
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
for frame in ImageSequence.Iterator(im): for frame in ImageSequence.Iterator(im):
@ -1045,10 +1054,10 @@ def test_retain_comment_in_subsequent_frames(tmp_path):
assert frame.info["comment"] == b"Test" assert frame.info["comment"] == b"Test"
def test_version(tmp_path): def test_version(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif") 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) im.save(out)
with Image.open(out) as reread: with Image.open(out) as reread:
assert reread.info["version"] == version assert reread.info["version"] == version
@ -1075,7 +1084,7 @@ def test_version(tmp_path):
assert_version_after_save(im, b"GIF87a") 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") out = str(tmp_path / "temp.gif")
# Test appending single frame images # Test appending single frame images
@ -1087,7 +1096,7 @@ def test_append_images(tmp_path):
assert reread.n_frames == 3 assert reread.n_frames == 3
# Tests appending using a generator # Tests appending using a generator
def im_generator(ims): def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
yield from ims yield from ims
im.save(out, save_all=True, append_images=im_generator(ims)) im.save(out, save_all=True, append_images=im_generator(ims))
@ -1104,7 +1113,7 @@ def test_append_images(tmp_path):
assert reread.n_frames == 10 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 # From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
# transparency. # transparency.
# Need a palette that isn't using the 0 color, # Need a palette that isn't using the 0 color,
@ -1124,7 +1133,7 @@ def test_transparent_optimize(tmp_path):
assert reloaded.info["transparency"] == reloaded.getpixel((252, 0)) 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") out = str(tmp_path / "temp.gif")
im = Image.new("RGB", (256, 1)) im = Image.new("RGB", (256, 1))
@ -1139,7 +1148,7 @@ def test_removed_transparency(tmp_path):
assert "transparency" not in reloaded.info 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") out = str(tmp_path / "temp.gif")
# Single frame # Single frame
@ -1161,7 +1170,7 @@ def test_rgb_transparency(tmp_path):
assert "transparency" not in reloaded.info 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") out = str(tmp_path / "temp.gif")
im = hopper("P") im = hopper("P")
@ -1172,13 +1181,13 @@ def test_rgba_transparency(tmp_path):
assert_image_equal(hopper("P").convert("RGB"), reloaded) 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: with Image.open("Tests/images/background_outside_palette.gif") as im:
im.seek(1) im.seek(1)
assert im.info["background"] == 255 assert im.info["background"] == 255
def test_bbox(tmp_path): def test_bbox(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
im = Image.new("RGB", (100, 100), "#fff") im = Image.new("RGB", (100, 100), "#fff")
@ -1189,7 +1198,7 @@ def test_bbox(tmp_path):
assert reread.n_frames == 2 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") out = str(tmp_path / "temp.gif")
im = Image.new("RGBA", (1, 2), (255, 0, 0, 255)) im = Image.new("RGBA", (1, 2), (255, 0, 0, 255))
@ -1201,7 +1210,7 @@ def test_bbox_alpha(tmp_path):
assert reread.n_frames == 2 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 # Generate an L mode image with a separate palette
im = hopper("P") im = hopper("P")
@ -1215,7 +1224,7 @@ def test_palette_save_L(tmp_path):
assert_image_equal(reloaded.convert("RGB"), im.convert("RGB")) 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 = Image.new("P", (1, 2))
im.putpixel((0, 1), 1) im.putpixel((0, 1), 1)
@ -1229,7 +1238,7 @@ def test_palette_save_P(tmp_path):
assert reloaded_rgb.getpixel((0, 1)) == (4, 5, 6) 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 = Image.new("P", (1, 2))
im.putpixel((0, 1), 1) im.putpixel((0, 1), 1)
@ -1242,7 +1251,7 @@ def test_palette_save_duplicate_entries(tmp_path):
assert reloaded.convert("RGB").getpixel((0, 1)) == (0, 0, 0) 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 = [] frames = []
colors = ((255, 0, 0), (0, 255, 0)) colors = ((255, 0, 0), (0, 255, 0))
for color in colors: for color in colors:
@ -1265,7 +1274,7 @@ def test_palette_save_all_P(tmp_path):
assert im.palette.palette == im.global_palette.palette 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 # Pass in a different palette, as an ImagePalette.ImagePalette
# effectively the same as test_palette_save_P # effectively the same as test_palette_save_P
@ -1280,7 +1289,7 @@ def test_palette_save_ImagePalette(tmp_path):
assert_image_equal(reloaded.convert("RGB"), im.convert("RGB")) 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' # Test saving something that would trigger the auto-convert to 'L'
im = hopper("I") im = hopper("I")
@ -1292,7 +1301,7 @@ def test_save_I(tmp_path):
assert_image_equal(reloaded.convert("L"), im.convert("L")) assert_image_equal(reloaded.convert("L"), im.convert("L"))
def test_getdata(): def test_getdata() -> None:
# Test getheader/getdata against legacy values. # Test getheader/getdata against legacy values.
# Create a 'P' image with holes in the palette. # Create a 'P' image with holes in the palette.
im = Image._wedge().resize((16, 16), Image.Resampling.NEAREST) im = Image._wedge().resize((16, 16), Image.Resampling.NEAREST)
@ -1320,7 +1329,7 @@ def test_getdata():
GifImagePlugin._FORCE_OPTIMIZE = False GifImagePlugin._FORCE_OPTIMIZE = False
def test_lzw_bits(): def test_lzw_bits() -> None:
# see https://github.com/python-pillow/Pillow/issues/2811 # see https://github.com/python-pillow/Pillow/issues/2811
with Image.open("Tests/images/issue_2811.gif") as im: with Image.open("Tests/images/issue_2811.gif") as im:
assert im.tile[0][3][0] == 11 # LZW bits assert im.tile[0][3][0] == 11 # LZW bits
@ -1328,7 +1337,7 @@ def test_lzw_bits():
im.load() im.load()
def test_extents(): def test_extents() -> None:
with Image.open("Tests/images/test_extents.gif") as im: with Image.open("Tests/images/test_extents.gif") as im:
assert im.size == (100, 100) assert im.size == (100, 100)
@ -1340,7 +1349,7 @@ def test_extents():
assert im.size == (150, 150) 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, # The Global Color Table Flag isn't set, so there is no background color index,
# but the disposal method is "Restore to background color" # but the disposal method is "Restore to background color"
with Image.open("Tests/images/missing_background.gif") as im: with Image.open("Tests/images/missing_background.gif") as im:
@ -1348,7 +1357,7 @@ def test_missing_background():
assert_image_equal_tofile(im, "Tests/images/missing_background_first_frame.png") 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") out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/transparent.png") as im: with Image.open("Tests/images/transparent.png") as im:
im.save(out) im.save(out)

View File

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

View File

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

View File

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

View File

@ -1,5 +1,8 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
from typing import IO
import pytest import pytest
from PIL import Hdf5StubImagePlugin, Image from PIL import Hdf5StubImagePlugin, Image
@ -7,7 +10,7 @@ from PIL import Hdf5StubImagePlugin, Image
TEST_FILE = "Tests/images/hdf5.h5" TEST_FILE = "Tests/images/hdf5.h5"
def test_open(): def test_open() -> None:
# Act # Act
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
# Assert # Assert
@ -18,7 +21,7 @@ def test_open():
assert im.size == (1, 1) assert im.size == (1, 1)
def test_invalid_file(): def test_invalid_file() -> None:
# Arrange # Arrange
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
@ -27,7 +30,7 @@ def test_invalid_file():
Hdf5StubImagePlugin.HDF5StubImageFile(invalid_file) Hdf5StubImagePlugin.HDF5StubImageFile(invalid_file)
def test_load(): def test_load() -> None:
# Arrange # Arrange
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler # Act / Assert: stub cannot load without an implemented handler
@ -35,7 +38,7 @@ def test_load():
im.load() im.load()
def test_save(): def test_save() -> None:
# Arrange # Arrange
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
dummy_fp = None dummy_fp = None
@ -48,21 +51,21 @@ def test_save():
Hdf5StubImagePlugin._save(im, dummy_fp, dummy_filename) Hdf5StubImagePlugin._save(im, dummy_fp, dummy_filename)
def test_handler(tmp_path): def test_handler(tmp_path: Path) -> None:
class TestHandler: class TestHandler:
opened = False opened = False
loaded = False loaded = False
saved = False saved = False
def open(self, im): def open(self, im: Image.Image) -> None:
self.opened = True self.opened = True
def load(self, im): def load(self, im: Image.Image) -> Image.Image:
self.loaded = True self.loaded = True
im.fp.close() im.fp.close()
return Image.new("RGB", (1, 1)) 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 self.saved = True
handler = TestHandler() handler = TestHandler()

View File

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

View File

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

View File

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

View File

@ -9,13 +9,13 @@ from PIL import Image, ImtImagePlugin
from .helper import assert_image_equal_tofile from .helper import assert_image_equal_tofile
def test_sanity(): def test_sanity() -> None:
with Image.open("Tests/images/bw_gradient.imt") as im: with Image.open("Tests/images/bw_gradient.imt") as im:
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png") assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
@pytest.mark.parametrize("data", (b"\n", b"\n-", b"width 1\n")) @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 io.BytesIO(data) as fp:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
ImtImagePlugin.ImtImageFile(fp) ImtImagePlugin.ImtImageFile(fp)

View File

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

View File

@ -4,6 +4,9 @@ import os
import re import re
import warnings import warnings
from io import BytesIO from io import BytesIO
from pathlib import Path
from types import ModuleType
from typing import Any
import pytest import pytest
@ -31,6 +34,7 @@ from .helper import (
skip_unless_feature, skip_unless_feature,
) )
ElementTree: ModuleType | None
try: try:
from defusedxml import ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:
@ -41,7 +45,7 @@ TEST_FILE = "Tests/images/hopper.jpg"
@skip_unless_feature("jpg") @skip_unless_feature("jpg")
class TestFileJpeg: class TestFileJpeg:
def roundtrip(self, im, **options): def roundtrip(self, im: Image.Image, **options: Any) -> Image.Image:
out = BytesIO() out = BytesIO()
im.save(out, "JPEG", **options) im.save(out, "JPEG", **options)
test_bytes = out.tell() test_bytes = out.tell()
@ -50,7 +54,7 @@ class TestFileJpeg:
im.bytes = test_bytes # for testing only im.bytes = test_bytes # for testing only
return im 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 """Generates a very hard to compress file
:param size: tuple :param size: tuple
:param mode: optional image mode :param mode: optional image mode
@ -58,7 +62,7 @@ class TestFileJpeg:
""" """
return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode))) 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 # internal version number
assert re.search(r"\d+\.\d+$", features.version_codec("jpg")) assert re.search(r"\d+\.\d+$", features.version_codec("jpg"))
@ -70,13 +74,13 @@ class TestFileJpeg:
assert im.get_format_mimetype() == "image/jpeg" assert im.get_format_mimetype() == "image/jpeg"
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) @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") f = str(tmp_path / "temp.jpg")
im = Image.new("RGB", size) im = Image.new("RGB", size)
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.save(f) im.save(f)
def test_app(self): def test_app(self) -> None:
# Test APP/COM reader (@PIL135) # Test APP/COM reader (@PIL135)
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert im.applist[0] == ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00") assert im.applist[0] == ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00")
@ -89,7 +93,7 @@ class TestFileJpeg:
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00" assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
assert im.app["COM"] == im.info["comment"] 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: with Image.open(TEST_FILE) as im:
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00" assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
@ -107,15 +111,13 @@ class TestFileJpeg:
assert "comment" not in reloaded.info assert "comment" not in reloaded.info
# Test that a comment argument overrides the default comment # 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() out = BytesIO()
im.save(out, format="JPEG", comment=comment) im.save(out, format="JPEG", comment=comment)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
if not isinstance(comment, bytes): assert reloaded.info["comment"] == b"Test comment text"
comment = comment.encode()
assert reloaded.info["comment"] == comment
def test_cmyk(self): def test_cmyk(self) -> None:
# Test CMYK handling. Thanks to Tim and Charlie for test data, # Test CMYK handling. Thanks to Tim and Charlie for test data,
# Michael for getting me to look one more time. # Michael for getting me to look one more time.
f = "Tests/images/pil_sample_cmyk.jpg" f = "Tests/images/pil_sample_cmyk.jpg"
@ -143,8 +145,8 @@ class TestFileJpeg:
) )
assert k > 0.9 assert k > 0.9
def test_rgb(self): def test_rgb(self) -> None:
def getchannels(im): def getchannels(im: Image.Image) -> tuple[int, int, int]:
return tuple(v[0] for v in im.layer) return tuple(v[0] for v in im.layer)
im = hopper() im = hopper()
@ -160,8 +162,8 @@ class TestFileJpeg:
"test_image_path", "test_image_path",
[TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"], [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"],
) )
def test_dpi(self, test_image_path): def test_dpi(self, test_image_path: str) -> None:
def test(xdpi, ydpi=None): def test(xdpi: int, ydpi: int | None = None):
with Image.open(test_image_path) as im: with Image.open(test_image_path) as im:
im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))
return im.info.get("dpi") return im.info.get("dpi")
@ -174,7 +176,7 @@ class TestFileJpeg:
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
def test_icc(self, tmp_path): def test_icc(self, tmp_path: Path) -> None:
# Test ICC support # Test ICC support
with Image.open("Tests/images/rgb.jpg") as im1: with Image.open("Tests/images/rgb.jpg") as im1:
icc_profile = im1.info["icc_profile"] icc_profile = im1.info["icc_profile"]
@ -206,7 +208,7 @@ class TestFileJpeg:
ImageFile.MAXBLOCK * 4 + 3, # large block 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 # Make sure that the "extra" support handles large blocks
# The ICC APP marker can store 65519 bytes per marker, so # The ICC APP marker can store 65519 bytes per marker, so
# using a 4-byte test code should allow us to detect out of # using a 4-byte test code should allow us to detect out of
@ -219,7 +221,7 @@ class TestFileJpeg:
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
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 # https://github.com/python-pillow/Pillow/issues/148
# Sometimes the meta data on the icc_profile block is bigger than # Sometimes the meta data on the icc_profile block is bigger than
# Image.MAXBLOCK or the image size. # Image.MAXBLOCK or the image size.
@ -243,7 +245,7 @@ class TestFileJpeg:
f = str(tmp_path / "temp3.jpg") f = str(tmp_path / "temp3.jpg")
im.save(f, progressive=True, quality=94, exif=b" " * 43668) im.save(f, progressive=True, quality=94, exif=b" " * 43668)
def test_optimize(self): def test_optimize(self) -> None:
im1 = self.roundtrip(hopper()) im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), optimize=0) im2 = self.roundtrip(hopper(), optimize=0)
im3 = self.roundtrip(hopper(), optimize=1) im3 = self.roundtrip(hopper(), optimize=1)
@ -252,14 +254,14 @@ class TestFileJpeg:
assert im1.bytes >= im2.bytes assert im1.bytes >= im2.bytes
assert im1.bytes >= im3.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 # https://github.com/python-pillow/Pillow/issues/148
f = str(tmp_path / "temp.jpg") f = str(tmp_path / "temp.jpg")
# this requires ~ 1.5x Image.MAXBLOCK # this requires ~ 1.5x Image.MAXBLOCK
im = Image.new("RGB", (4096, 4096), 0xFF3333) im = Image.new("RGB", (4096, 4096), 0xFF3333)
im.save(f, format="JPEG", optimize=True) im.save(f, format="JPEG", optimize=True)
def test_progressive(self): def test_progressive(self) -> None:
im1 = self.roundtrip(hopper()) im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), progressive=False) im2 = self.roundtrip(hopper(), progressive=False)
im3 = self.roundtrip(hopper(), progressive=True) im3 = self.roundtrip(hopper(), progressive=True)
@ -270,25 +272,25 @@ class TestFileJpeg:
assert_image_equal(im1, im3) assert_image_equal(im1, im3)
assert im1.bytes >= im3.bytes 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") f = str(tmp_path / "temp.jpg")
# this requires ~ 1.5x Image.MAXBLOCK # this requires ~ 1.5x Image.MAXBLOCK
im = Image.new("RGB", (4096, 4096), 0xFF3333) im = Image.new("RGB", (4096, 4096), 0xFF3333)
im.save(f, format="JPEG", progressive=True) 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") f = str(tmp_path / "temp.jpg")
im = self.gen_random_image((255, 255)) im = self.gen_random_image((255, 255))
# this requires more bytes than pixels in the image # this requires more bytes than pixels in the image
im.save(f, format="JPEG", progressive=True, quality=100) 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. # Issue 2272, quality 90 cmyk image is tripping the large buffer bug.
f = BytesIO() f = BytesIO()
im = self.gen_random_image((256, 256), "CMYK") im = self.gen_random_image((256, 256), "CMYK")
im.save(f, format="JPEG", progressive=True, quality=94) 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 # https://github.com/python-pillow/Pillow/issues/148
f = str(tmp_path / "temp.jpg") f = str(tmp_path / "temp.jpg")
im = hopper() im = hopper()
@ -297,12 +299,12 @@ class TestFileJpeg:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.save(f, "JPEG", quality=90, exif=b"1" * 65534) 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: with Image.open("Tests/images/exif_typeerror.jpg") as im:
# Should not raise a TypeError # Should not raise a TypeError
im._getexif() im._getexif()
def test_exif_gps(self, tmp_path): def test_exif_gps(self, tmp_path: Path) -> None:
expected_exif_gps = { expected_exif_gps = {
0: b"\x00\x00\x00\x01", 0: b"\x00\x00\x00\x01",
2: 4294967295, 2: 4294967295,
@ -327,7 +329,7 @@ class TestFileJpeg:
exif = reloaded._getexif() exif = reloaded._getexif()
assert exif[gps_index] == expected_exif_gps 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: with Image.open("Tests/images/empty_gps_ifd.jpg") as im:
exif = im.getexif() exif = im.getexif()
del exif[0x8769] del exif[0x8769]
@ -345,7 +347,7 @@ class TestFileJpeg:
# Assert that it was transposed # Assert that it was transposed
assert 0x0112 not in exif 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 # In 7.2.0, Exif rationals were changed to be read as
# TiffImagePlugin.IFDRational. This class had a bug in __eq__, # TiffImagePlugin.IFDRational. This class had a bug in __eq__,
# breaking the self-equality of Exif data # breaking the self-equality of Exif data
@ -355,7 +357,7 @@ class TestFileJpeg:
exifs.append(im._getexif()) exifs.append(im._getexif())
assert exifs[0] == exifs[1] 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. # 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 # 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 # this test passes on 2.9 and 3.1, but not 3.0
@ -390,12 +392,12 @@ class TestFileJpeg:
for tag, value in expected_exif.items(): for tag, value in expected_exif.items():
assert value == exif[tag] 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: with Image.open("Tests/images/exif_gps_typeerror.jpg") as im:
# Should not raise a TypeError # Should not raise a TypeError
im._getexif() im._getexif()
def test_progressive_compat(self): def test_progressive_compat(self) -> None:
im1 = self.roundtrip(hopper()) im1 = self.roundtrip(hopper())
assert not im1.info.get("progressive") assert not im1.info.get("progressive")
assert not im1.info.get("progression") assert not im1.info.get("progression")
@ -416,7 +418,7 @@ class TestFileJpeg:
assert im3.info.get("progressive") assert im3.info.get("progressive")
assert im3.info.get("progression") assert im3.info.get("progression")
def test_quality(self): def test_quality(self) -> None:
im1 = self.roundtrip(hopper()) im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), quality=50) im2 = self.roundtrip(hopper(), quality=50)
assert_image(im1, im2.mode, im2.size) assert_image(im1, im2.mode, im2.size)
@ -426,13 +428,13 @@ class TestFileJpeg:
assert_image(im1, im3.mode, im3.size) assert_image(im1, im3.mode, im3.size)
assert im2.bytes > im3.bytes assert im2.bytes > im3.bytes
def test_smooth(self): def test_smooth(self) -> None:
im1 = self.roundtrip(hopper()) im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), smooth=100) im2 = self.roundtrip(hopper(), smooth=100)
assert_image(im1, im2.mode, im2.size) assert_image(im1, im2.mode, im2.size)
def test_subsampling(self): def test_subsampling(self) -> None:
def getsampling(im): def getsampling(im: Image.Image):
layer = im.layer layer = im.layer
return layer[0][1:3] + layer[1][1:3] + layer[2][1:3] return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]
@ -440,46 +442,46 @@ class TestFileJpeg:
for subsampling in (-1, 3): # (default, invalid) for subsampling in (-1, 3): # (default, invalid)
im = self.roundtrip(hopper(), subsampling=subsampling) im = self.roundtrip(hopper(), subsampling=subsampling)
assert getsampling(im) == (2, 2, 1, 1, 1, 1) assert getsampling(im) == (2, 2, 1, 1, 1, 1)
for subsampling in (0, "4:4:4"): for subsampling1 in (0, "4:4:4"):
im = self.roundtrip(hopper(), subsampling=subsampling) im = self.roundtrip(hopper(), subsampling=subsampling1)
assert getsampling(im) == (1, 1, 1, 1, 1, 1) assert getsampling(im) == (1, 1, 1, 1, 1, 1)
for subsampling in (1, "4:2:2"): for subsampling1 in (1, "4:2:2"):
im = self.roundtrip(hopper(), subsampling=subsampling) im = self.roundtrip(hopper(), subsampling=subsampling1)
assert getsampling(im) == (2, 1, 1, 1, 1, 1) assert getsampling(im) == (2, 1, 1, 1, 1, 1)
for subsampling in (2, "4:2:0", "4:1:1"): for subsampling1 in (2, "4:2:0", "4:1:1"):
im = self.roundtrip(hopper(), subsampling=subsampling) im = self.roundtrip(hopper(), subsampling=subsampling1)
assert getsampling(im) == (2, 2, 1, 1, 1, 1) assert getsampling(im) == (2, 2, 1, 1, 1, 1)
# RGB colorspace # 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 # "4:4:4" doesn't really make sense for RGB, but the conversion
# to an integer happens at a higher level # 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) 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): with pytest.raises(OSError):
self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling) self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling1)
with pytest.raises(TypeError): with pytest.raises(TypeError):
self.roundtrip(hopper(), subsampling="1:1:1") 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: with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
info = im._getexif() info = im._getexif()
assert info[305] == "Adobe Photoshop CS Macintosh" 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: with Image.open("Tests/images/flower.jpg") as im:
ims = im.get_child_images() ims = im.get_child_images()
assert len(ims) == 1 assert len(ims) == 1
assert_image_similar_tofile(ims[0], "Tests/images/flower_thumbnail.png", 2.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: with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
assert im._getmp() is None assert im._getmp() is None
def test_quality_keep(self, tmp_path): def test_quality_keep(self, tmp_path: Path) -> None:
# RGB # RGB
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
f = str(tmp_path / "temp.jpg") f = str(tmp_path / "temp.jpg")
@ -493,13 +495,13 @@ class TestFileJpeg:
f = str(tmp_path / "temp.jpg") f = str(tmp_path / "temp.jpg")
im.save(f, quality="keep") 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 # https://github.com/python-pillow/Pillow/issues/630
filename = "Tests/images/junk_jpeg_header.jpg" filename = "Tests/images/junk_jpeg_header.jpg"
with Image.open(filename): with Image.open(filename):
pass pass
def test_ff00_jpeg_header(self): def test_ff00_jpeg_header(self) -> None:
filename = "Tests/images/jpeg_ff00_header.jpg" filename = "Tests/images/jpeg_ff00_header.jpg"
with Image.open(filename): with Image.open(filename):
pass pass
@ -507,7 +509,7 @@ class TestFileJpeg:
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
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" filename = "Tests/images/truncated_jpeg.jpg"
ImageFile.LOAD_TRUNCATED_IMAGES = True ImageFile.LOAD_TRUNCATED_IMAGES = True
with Image.open(filename) as im: with Image.open(filename) as im:
@ -515,7 +517,7 @@ class TestFileJpeg:
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False
assert im.getbbox() is not None 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" filename = "Tests/images/truncated_jpeg.jpg"
with Image.open(filename) as im: with Image.open(filename) as im:
with pytest.raises(OSError): with pytest.raises(OSError):
@ -528,8 +530,8 @@ class TestFileJpeg:
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
def test_qtables(self, tmp_path): def test_qtables(self, tmp_path: Path) -> None:
def _n_qtables_helper(n, test_file): def _n_qtables_helper(n: int, test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
f = str(tmp_path / "temp.jpg") f = str(tmp_path / "temp.jpg")
im.save(f, qtables=[[n] * 64] * n) im.save(f, qtables=[[n] * 64] * n)
@ -637,24 +639,24 @@ class TestFileJpeg:
with pytest.raises(ValueError): with pytest.raises(ValueError):
self.roundtrip(im, qtables=[[1, 2, 3, 4]]) 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: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
assert len(im.quantization) == 2 assert len(im.quantization) == 2
assert len(im.quantization[0]) == 64 assert len(im.quantization[0]) == 64
assert max(im.quantization[0]) > 255 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: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
im2 = self.roundtrip(im, qtables="keep") im2 = self.roundtrip(im, qtables="keep")
assert im.quantization == im2.quantization 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: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
im2 = self.roundtrip(im, qtables={0: im.quantization[0]}) im2 = self.roundtrip(im, qtables={0: im.quantization[0]})
assert len(im2.quantization) == 1 assert len(im2.quantization) == 1
assert im2.quantization[0] == im.quantization[0] 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: with Image.open(TEST_FILE) as im:
im2 = self.roundtrip(im, quality=10) im2 = self.roundtrip(im, quality=10)
assert len(im2.quantization) == 2 assert len(im2.quantization) == 2
@ -665,7 +667,7 @@ class TestFileJpeg:
"blocks, rows, markers", "blocks, rows, markers",
((0, 0, 0), (1, 0, 15), (3, 0, 5), (8, 0, 1), (0, 1, 3), (0, 2, 1)), ((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 im = Image.new("RGB", (32, 32)) # 16 MCUs
out = BytesIO() out = BytesIO()
im.save( im.save(
@ -679,20 +681,20 @@ class TestFileJpeg:
assert len(re.findall(b"\xff[\xd0-\xd7]", out.getvalue())) == markers assert len(re.findall(b"\xff[\xd0-\xd7]", out.getvalue())) == markers
@pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") @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: with Image.open(TEST_FILE) as img:
img.load_djpeg() img.load_djpeg()
assert_image_similar_tofile(img, TEST_FILE, 5) assert_image_similar_tofile(img, TEST_FILE, 5)
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") @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: with Image.open(TEST_FILE) as img:
tempfile = str(tmp_path / "temp.jpg") tempfile = str(tmp_path / "temp.jpg")
JpegImagePlugin._save_cjpeg(img, 0, tempfile) JpegImagePlugin._save_cjpeg(img, 0, tempfile)
# Default save quality is 75%, so a tiny bit of difference is alright # Default save quality is 75%, so a tiny bit of difference is alright
assert_image_similar_tofile(img, tempfile, 17) assert_image_similar_tofile(img, tempfile, 17)
def test_no_duplicate_0x1001_tag(self): def test_no_duplicate_0x1001_tag(self) -> None:
# Arrange # Arrange
tag_ids = {v: k for k, v in ExifTags.TAGS.items()} tag_ids = {v: k for k, v in ExifTags.TAGS.items()}
@ -700,7 +702,7 @@ class TestFileJpeg:
assert tag_ids["RelatedImageWidth"] == 0x1001 assert tag_ids["RelatedImageWidth"] == 0x1001
assert tag_ids["RelatedImageLength"] == 0x1002 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)) im = self.gen_random_image((512, 512))
f = str(tmp_path / "temp.jpeg") f = str(tmp_path / "temp.jpeg")
im.save(f, quality=100, optimize=True) im.save(f, quality=100, optimize=True)
@ -711,7 +713,7 @@ class TestFileJpeg:
reloaded.save(f, quality="keep", progressive=True) reloaded.save(f, quality="keep", progressive=True)
reloaded.save(f, quality="keep", optimize=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""" """Treat unknown MPO as JPEG"""
# Arrange # Arrange
@ -723,20 +725,20 @@ class TestFileJpeg:
assert im.format == "JPEG" assert im.format == "JPEG"
@pytest.mark.parametrize("mode", ("1", "L", "RGB", "RGBX", "CMYK", "YCbCr")) @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() out = BytesIO()
img = Image.new(mode, (20, 20)) img = Image.new(mode, (20, 20))
img.save(out, "JPEG") img.save(out, "JPEG")
@pytest.mark.parametrize("mode", ("LA", "La", "RGBA", "RGBa", "P")) @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 # ref https://github.com/python-pillow/Pillow/issues/2005
out = BytesIO() out = BytesIO()
img = Image.new(mode, (20, 20)) img = Image.new(mode, (20, 20))
with pytest.raises(OSError): with pytest.raises(OSError):
img.save(out, "JPEG") 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 # Arrange
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/hopper.tif") as im: with Image.open("Tests/images/hopper.tif") as im:
@ -748,7 +750,7 @@ class TestFileJpeg:
reloaded.load() reloaded.load()
assert im.info["dpi"] == reloaded.info["dpi"] 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") outfile = str(tmp_path / "temp.jpg")
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
im.save(outfile, dpi=(72.2, 72.2)) im.save(outfile, dpi=(72.2, 72.2))
@ -761,7 +763,7 @@ class TestFileJpeg:
with Image.open(outfile) as reloaded: with Image.open(outfile) as reloaded:
assert reloaded.info["dpi"] == (73, 73) assert reloaded.info["dpi"] == (73, 73)
def test_dpi_tuple_from_exif(self): def test_dpi_tuple_from_exif(self) -> None:
# Arrange # Arrange
# This Photoshop CC 2017 image has DPI in EXIF not metadata # This Photoshop CC 2017 image has DPI in EXIF not metadata
# EXIF XResolution is (2000000, 10000) # EXIF XResolution is (2000000, 10000)
@ -769,7 +771,7 @@ class TestFileJpeg:
# Act / Assert # Act / Assert
assert im.info.get("dpi") == (200, 200) assert im.info.get("dpi") == (200, 200)
def test_dpi_int_from_exif(self): def test_dpi_int_from_exif(self) -> None:
# Arrange # Arrange
# This image has DPI in EXIF not metadata # This image has DPI in EXIF not metadata
# EXIF XResolution is 72 # EXIF XResolution is 72
@ -777,7 +779,7 @@ class TestFileJpeg:
# Act / Assert # Act / Assert
assert im.info.get("dpi") == (72, 72) assert im.info.get("dpi") == (72, 72)
def test_dpi_from_dpcm_exif(self): def test_dpi_from_dpcm_exif(self) -> None:
# Arrange # Arrange
# This is photoshop-200dpi.jpg with EXIF resolution unit set to cm: # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:
# exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg
@ -785,7 +787,7 @@ class TestFileJpeg:
# Act / Assert # Act / Assert
assert im.info.get("dpi") == (508, 508) assert im.info.get("dpi") == (508, 508)
def test_dpi_exif_zero_division(self): def test_dpi_exif_zero_division(self) -> None:
# Arrange # Arrange
# This is photoshop-200dpi.jpg with EXIF resolution set to 0/0: # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:
# exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg
@ -794,7 +796,7 @@ class TestFileJpeg:
# This should return the default, and not raise a ZeroDivisionError # This should return the default, and not raise a ZeroDivisionError
assert im.info.get("dpi") == (72, 72) assert im.info.get("dpi") == (72, 72)
def test_dpi_exif_string(self): def test_dpi_exif_string(self) -> None:
# Arrange # Arrange
# 0x011A tag in this exif contains string '300300\x02' # 0x011A tag in this exif contains string '300300\x02'
with Image.open("Tests/images/broken_exif_dpi.jpg") as im: with Image.open("Tests/images/broken_exif_dpi.jpg") as im:
@ -802,14 +804,14 @@ class TestFileJpeg:
# This should return the default # This should return the default
assert im.info.get("dpi") == (72, 72) assert im.info.get("dpi") == (72, 72)
def test_dpi_exif_truncated(self): def test_dpi_exif_truncated(self) -> None:
# Arrange # Arrange
with Image.open("Tests/images/truncated_exif_dpi.jpg") as im: with Image.open("Tests/images/truncated_exif_dpi.jpg") as im:
# Act / Assert # Act / Assert
# This should return the default # This should return the default
assert im.info.get("dpi") == (72, 72) assert im.info.get("dpi") == (72, 72)
def test_no_dpi_in_exif(self): def test_no_dpi_in_exif(self) -> None:
# Arrange # Arrange
# This is photoshop-200dpi.jpg with resolution removed from EXIF: # This is photoshop-200dpi.jpg with resolution removed from EXIF:
# exiftool "-*resolution*"= photoshop-200dpi.jpg # exiftool "-*resolution*"= photoshop-200dpi.jpg
@ -819,7 +821,7 @@ class TestFileJpeg:
# https://exiv2.org/tags.html # https://exiv2.org/tags.html
assert im.info.get("dpi") == (72, 72) 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 # This is no-dpi-in-exif with the tiff header of the exif block
# hexedited from MM * to FF FF FF FF # hexedited from MM * to FF FF FF FF
with Image.open("Tests/images/invalid-exif.jpg") as im: with Image.open("Tests/images/invalid-exif.jpg") as im:
@ -830,7 +832,7 @@ class TestFileJpeg:
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
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: with Image.open("Tests/images/flower.jpg") as im:
exif = im.getexif() exif = im.getexif()
assert exif[282] == 180 assert exif[282] == 180
@ -842,14 +844,14 @@ class TestFileJpeg:
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert reloaded.getexif()[282] == 180 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 # When no x or y resolution is defined in EXIF
with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im: with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im:
# This should return the default, and not a ValueError or # This should return the default, and not a ValueError or
# OSError for an unidentified image. # OSError for an unidentified image.
assert im.info.get("dpi") == (72, 72) assert im.info.get("dpi") == (72, 72)
def test_ifd_offset_exif(self): def test_ifd_offset_exif(self) -> None:
# Arrange # Arrange
# This image has been manually hexedited to have an IFD offset of 10, # This image has been manually hexedited to have an IFD offset of 10,
# in contrast to normal 8 # in contrast to normal 8
@ -857,14 +859,14 @@ class TestFileJpeg:
# Act / Assert # Act / Assert
assert im._getexif()[306] == "2017:03:13 23:03:09" assert im._getexif()[306] == "2017:03:13 23:03:09"
def test_multiple_exif(self): def test_multiple_exif(self) -> None:
with Image.open("Tests/images/multiple_exif.jpg") as im: with Image.open("Tests/images/multiple_exif.jpg") as im:
assert im.info["exif"] == b"Exif\x00\x00firstsecond" assert im.info["exif"] == b"Exif\x00\x00firstsecond"
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
def test_photoshop(self): def test_photoshop(self) -> None:
with Image.open("Tests/images/photoshop-200dpi.jpg") as im: with Image.open("Tests/images/photoshop-200dpi.jpg") as im:
assert im.info["photoshop"][0x03ED] == { assert im.info["photoshop"][0x03ED] == {
"XResolution": 200.0, "XResolution": 200.0,
@ -881,14 +883,14 @@ class TestFileJpeg:
with Image.open("Tests/images/app13.jpg") as im: with Image.open("Tests/images/app13.jpg") as im:
assert "photoshop" not in im.info 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: with Image.open("Tests/images/app13-multiple.jpg") as im:
assert "photoshop" in im.info assert "photoshop" in im.info
assert 24 == len(im.info["photoshop"]) assert 24 == len(im.info["photoshop"])
apps_13_lengths = [len(v) for k, v in im.applist if k == "APP13"] apps_13_lengths = [len(v) for k, v in im.applist if k == "APP13"]
assert [65504, 24] == apps_13_lengths 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: with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
assert im.info["adobe_transform"] == 1 assert im.info["adobe_transform"] == 1
@ -902,11 +904,11 @@ class TestFileJpeg:
assert "adobe" in im.info assert "adobe" in im.info
assert "adobe_transform" not 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: with Image.open("Tests/images/icc-after-SOF.jpg") as im:
assert im.info["icc_profile"] == b"profile" assert im.info["icc_profile"] == b"profile"
def test_jpeg_magic_number(self): def test_jpeg_magic_number(self) -> None:
size = 4097 size = 4097
buffer = BytesIO(b"\xFF" * size) # Many xFF bytes buffer = BytesIO(b"\xFF" * size) # Many xFF bytes
buffer.max_pos = 0 buffer.max_pos = 0
@ -925,7 +927,7 @@ class TestFileJpeg:
# Assert the entire file has not been read # Assert the entire file has not been read
assert 0 < buffer.max_pos < size 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: with Image.open("Tests/images/xmp_test.jpg") as im:
if ElementTree is None: if ElementTree is None:
with pytest.warns( with pytest.warns(
@ -954,7 +956,7 @@ class TestFileJpeg:
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
assert im.getxmp() == {} 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: with Image.open("Tests/images/xmp_no_prefix.jpg") as im:
if ElementTree is None: if ElementTree is None:
with pytest.warns( with pytest.warns(
@ -965,7 +967,7 @@ class TestFileJpeg:
else: else:
assert im.getxmp() == {"xmpmeta": {"key": "value"}} 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: with Image.open("Tests/images/xmp_padded.jpg") as im:
if ElementTree is None: if ElementTree is None:
with pytest.warns( with pytest.warns(
@ -977,16 +979,16 @@ class TestFileJpeg:
assert im.getxmp() == {"xmpmeta": None} assert im.getxmp() == {"xmpmeta": None}
@pytest.mark.timeout(timeout=1) @pytest.mark.timeout(timeout=1)
def test_eof(self): def test_eof(self) -> None:
# Even though this decoder never says that it is finished # Even though this decoder never says that it is finished
# the image should still end when there is no new data # the image should still end when there is no new data
class InfiniteMockPyDecoder(ImageFile.PyDecoder): class InfiniteMockPyDecoder(ImageFile.PyDecoder):
def decode(self, buffer): def decode(self, buffer: bytes) -> tuple[int, int]:
return 0, 0 return 0, 0
decoder = InfiniteMockPyDecoder(None) decoder = InfiniteMockPyDecoder(None)
def closure(mode, *args): def closure(mode: str, *args) -> InfiniteMockPyDecoder:
decoder.__init__(mode, *args) decoder.__init__(mode, *args)
return decoder return decoder
@ -1000,7 +1002,7 @@ class TestFileJpeg:
im.load() im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_separate_tables(self): def test_separate_tables(self) -> None:
im = hopper() im = hopper()
data = [] # [interchange, tables-only, image-only] data = [] # [interchange, tables-only, image-only]
for streamtype in range(3): for streamtype in range(3):
@ -1022,14 +1024,14 @@ class TestFileJpeg:
with Image.open(BytesIO(data[1] + data[2])) as combined_im: with Image.open(BytesIO(data[1] + data[2])) as combined_im:
assert_image_equal(interchange_im, combined_im) assert_image_equal(interchange_im, combined_im)
def test_repr_jpeg(self): def test_repr_jpeg(self) -> None:
im = hopper() im = hopper()
with Image.open(BytesIO(im._repr_jpeg_())) as repr_jpeg: with Image.open(BytesIO(im._repr_jpeg_())) as repr_jpeg:
assert repr_jpeg.format == "JPEG" assert repr_jpeg.format == "JPEG"
assert_image_similar(im, repr_jpeg, 17) 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") im = hopper("F")
assert im._repr_jpeg_() is None assert im._repr_jpeg_() is None
@ -1038,7 +1040,7 @@ class TestFileJpeg:
@pytest.mark.skipif(not is_win32(), reason="Windows only") @pytest.mark.skipif(not is_win32(), reason="Windows only")
@skip_unless_feature("jpg") @skip_unless_feature("jpg")
class TestFileCloseW32: class TestFileCloseW32:
def test_fd_leak(self, tmp_path): def test_fd_leak(self, tmp_path: Path) -> None:
tmpfile = str(tmp_path / "temp.jpg") tmpfile = str(tmp_path / "temp.jpg")
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:

View File

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

View File

@ -7,6 +7,7 @@ import os
import re import re
import sys import sys
from collections import namedtuple from collections import namedtuple
from pathlib import Path
import pytest import pytest
@ -26,7 +27,7 @@ from .helper import (
@skip_unless_feature("libtiff") @skip_unless_feature("libtiff")
class LibTiffTestCase: 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""" """Helper tests that assert basic sanity about the g4 tiff reading"""
# 1 bit # 1 bit
assert im.mode == "1" assert im.mode == "1"
@ -50,10 +51,10 @@ class LibTiffTestCase:
class TestFileLibTiff(LibTiffTestCase): class TestFileLibTiff(LibTiffTestCase):
def test_version(self): def test_version(self) -> None:
assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff")) 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 the ordinary file path load path"""
test_file = "Tests/images/hopper_g4_500.tif" test_file = "Tests/images/hopper_g4_500.tif"
@ -61,12 +62,12 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.size == (500, 500) assert im.size == (500, 500)
self._assert_noerr(tmp_path, im) 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" test_file = "Tests/images/pport_g4.tif"
with Image.open(test_file) as im: with Image.open(test_file) as im:
self._assert_noerr(tmp_path, 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""" """Testing the string load path"""
test_file = "Tests/images/hopper_g4_500.tif" test_file = "Tests/images/hopper_g4_500.tif"
@ -75,7 +76,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.size == (500, 500) assert im.size == (500, 500)
self._assert_noerr(tmp_path, im) 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""" """Testing the stringio loading code path"""
test_file = "Tests/images/hopper_g4_500.tif" test_file = "Tests/images/hopper_g4_500.tif"
s = io.BytesIO() s = io.BytesIO()
@ -86,7 +87,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.size == (500, 500) assert im.size == (500, 500)
self._assert_noerr(tmp_path, im) 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""" """Testing loading from non-disk non-BytesIO file object"""
test_file = "Tests/images/hopper_g4_500.tif" test_file = "Tests/images/hopper_g4_500.tif"
s = io.BytesIO() s = io.BytesIO()
@ -98,18 +99,18 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.size == (500, 500) assert im.size == (500, 500)
self._assert_noerr(tmp_path, im) 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""" """Checking that we're actually getting the data that we expect"""
with Image.open("Tests/images/hopper_bw_500.png") as png: with Image.open("Tests/images/hopper_bw_500.png") as png:
assert_image_equal_tofile(png, "Tests/images/hopper_g4_500.tif") assert_image_equal_tofile(png, "Tests/images/hopper_g4_500.tif")
# see https://github.com/python-pillow/Pillow/issues/279 # 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""" """Checking that we're actually getting the data that we expect"""
with Image.open("Tests/images/g4-fillorder-test.tif") as g4: with Image.open("Tests/images/g4-fillorder-test.tif") as g4:
assert_image_equal_tofile(g4, "Tests/images/g4-fillorder-test.png") 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""" """Checking to see that the saved image is the same as what we wrote"""
test_file = "Tests/images/hopper_g4_500.tif" test_file = "Tests/images/hopper_g4_500.tif"
with Image.open(test_file) as orig: with Image.open(test_file) as orig:
@ -128,7 +129,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert orig.tobytes() != reread.tobytes() 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" test_file = "Tests/images/tiff_adobe_deflate.tif"
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.mode == "RGB" assert im.mode == "RGB"
@ -139,7 +140,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
@pytest.mark.parametrize("legacy_api", (False, True)) @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""" """Test metadata writing through libtiff"""
f = str(tmp_path / "temp.tiff") f = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper_g4.tif") as img: with Image.open("Tests/images/hopper_g4.tif") as img:
@ -184,7 +185,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert field in reloaded, f"{field} not in metadata" assert field in reloaded, f"{field} not in metadata"
@pytest.mark.valgrind_known_error(reason="Known invalid 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 # 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 # any sense, so we're running up against limits where we're asking
# libtiff to do stupid things. # libtiff to do stupid things.
@ -241,8 +242,8 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.WRITE_LIBTIFF = False TiffImagePlugin.WRITE_LIBTIFF = False
def test_custom_metadata(self, tmp_path): def test_custom_metadata(self, tmp_path: Path) -> None:
tc = namedtuple("test_case", "value,type,supported_by_default") tc = namedtuple("tc", "value,type,supported_by_default")
custom = { custom = {
37000 + k: v 37000 + k: v
for k, v in enumerate( for k, v in enumerate(
@ -283,7 +284,9 @@ class TestFileLibTiff(LibTiffTestCase):
for libtiff in libtiffs: for libtiff in libtiffs:
TiffImagePlugin.WRITE_LIBTIFF = libtiff TiffImagePlugin.WRITE_LIBTIFF = libtiff
def check_tags(tiffinfo): def check_tags(
tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str]
) -> None:
im = hopper() im = hopper()
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
@ -322,7 +325,7 @@ class TestFileLibTiff(LibTiffTestCase):
) )
TiffImagePlugin.WRITE_LIBTIFF = False TiffImagePlugin.WRITE_LIBTIFF = False
def test_subifd(self, tmp_path): def test_subifd(self, tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/g4_orientation_6.tif") as im: with Image.open("Tests/images/g4_orientation_6.tif") as im:
im.tag_v2[SUBIFD] = 10000 im.tag_v2[SUBIFD] = 10000
@ -330,7 +333,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not segfault # Should not segfault
im.save(outfile) im.save(outfile)
def test_xmlpacket_tag(self, tmp_path): def test_xmlpacket_tag(self, tmp_path: Path) -> None:
TiffImagePlugin.WRITE_LIBTIFF = True TiffImagePlugin.WRITE_LIBTIFF = True
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
@ -341,7 +344,7 @@ class TestFileLibTiff(LibTiffTestCase):
if 700 in reloaded.tag_v2: if 700 in reloaded.tag_v2:
assert reloaded.tag_v2[700] == b"xmlpacket tag" 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 # issue #1765
im = hopper("RGB") im = hopper("RGB")
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
@ -351,7 +354,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert reloaded.info["dpi"] == (72.0, 72.0) 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: with Image.open("Tests/images/hopper_g4_500.tif") as i:
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
i.save(out, compression="group3") i.save(out, compression="group3")
@ -360,7 +363,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert reread.info["compression"] == "group3" assert reread.info["compression"] == "group3"
assert_image_equal(reread, i) 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: with Image.open("Tests/images/16bit.deflate.tif") as im:
assert im.getpixel((0, 0)) == 480 assert im.getpixel((0, 0)) == 480
assert im.mode == "I;16" assert im.mode == "I;16"
@ -379,7 +382,7 @@ class TestFileLibTiff(LibTiffTestCase):
# UNDONE - libtiff defaults to writing in native endian, so # UNDONE - libtiff defaults to writing in native endian, so
# on big endian, we'll get back mode = 'I;16B' here. # 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: with Image.open("Tests/images/16bit.MM.deflate.tif") as im:
assert im.getpixel((0, 0)) == 480 assert im.getpixel((0, 0)) == 480
assert im.mode == "I;16B" assert im.mode == "I;16B"
@ -396,7 +399,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert reread.info["compression"] == im.info["compression"] assert reread.info["compression"] == im.info["compression"]
assert reread.getpixel((0, 0)) == 480 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""" """Tests String data in info directory"""
test_file = "Tests/images/hopper_g4_500.tif" test_file = "Tests/images/hopper_g4_500.tif"
with Image.open(test_file) as orig: with Image.open(test_file) as orig:
@ -409,7 +412,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert "temp.tif" == reread.tag_v2[269] assert "temp.tif" == reread.tag_v2[269]
assert "temp.tif" == reread.tag[269][0] assert "temp.tif" == reread.tag[269][0]
def test_12bit_rawmode(self): def test_12bit_rawmode(self) -> None:
"""Are we generating the same interpretation """Are we generating the same interpretation
of the image as Imagemagick is?""" of the image as Imagemagick is?"""
TiffImagePlugin.READ_LIBTIFF = True TiffImagePlugin.READ_LIBTIFF = True
@ -424,7 +427,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") 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 # test case from irc, how to do blur on b/w image
# and save to compressed tif. # and save to compressed tif.
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
@ -436,7 +439,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, out) 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 # Test various tiff compressions and assert similar image content but reduced
# file sizes. # file sizes.
im = hopper("RGB") im = hopper("RGB")
@ -462,7 +465,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert size_compressed > size_jpeg assert size_compressed > size_jpeg
assert size_jpeg > size_jpeg_30 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") im = hopper("RGB")
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
im.save(out, compression="tiff_jpeg") im.save(out, compression="tiff_jpeg")
@ -470,7 +473,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert reloaded.info["compression"] == "jpeg" 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") im = hopper("RGB")
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
im.save(out, compression="tiff_deflate") im.save(out, compression="tiff_deflate")
@ -478,7 +481,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert reloaded.info["compression"] == "tiff_adobe_deflate" assert reloaded.info["compression"] == "tiff_adobe_deflate"
def test_quality(self, tmp_path): def test_quality(self, tmp_path: Path) -> None:
im = hopper("RGB") im = hopper("RGB")
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
@ -493,7 +496,7 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(out, compression="jpeg", quality=0) im.save(out, compression="jpeg", quality=0)
im.save(out, compression="jpeg", quality=100) 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") im = hopper("CMYK")
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
@ -501,7 +504,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, out) assert_image_equal_tofile(im, out)
@pytest.mark.parametrize("im", (hopper("P"), Image.new("P", (1, 1), "#000"))) @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") out = str(tmp_path / "temp.tif")
TiffImagePlugin.WRITE_LIBTIFF = True TiffImagePlugin.WRITE_LIBTIFF = True
@ -513,14 +516,14 @@ class TestFileLibTiff(LibTiffTestCase):
assert len(reloaded.tag_v2[320]) == 768 assert len(reloaded.tag_v2[320]) == 768
@pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4")) @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") im = hopper("RGB")
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
with pytest.raises(OSError): with pytest.raises(OSError):
im.save(out, compression=compression) 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") im = Image.open("Tests/images/hopper_g4_500.tif")
fn = im.fp.fileno() fn = im.fp.fileno()
@ -534,7 +537,7 @@ class TestFileLibTiff(LibTiffTestCase):
with pytest.raises(OSError): with pytest.raises(OSError):
os.close(fn) os.close(fn)
def test_multipage(self): def test_multipage(self) -> None:
# issue #862 # issue #862
TiffImagePlugin.READ_LIBTIFF = True TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
@ -557,7 +560,7 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False
def test_multipage_nframes(self): def test_multipage_nframes(self) -> None:
# issue #862 # issue #862
TiffImagePlugin.READ_LIBTIFF = True TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
@ -570,7 +573,7 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False
def test_multipage_seek_backwards(self): def test_multipage_seek_backwards(self) -> None:
TiffImagePlugin.READ_LIBTIFF = True TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
im.seek(1) im.seek(1)
@ -581,14 +584,14 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False
def test__next(self): def test__next(self) -> None:
TiffImagePlugin.READ_LIBTIFF = True TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/hopper.tif") as im: with Image.open("Tests/images/hopper.tif") as im:
assert not im.tag.next assert not im.tag.next
im.load() im.load()
assert not im.tag.next assert not im.tag.next
def test_4bit(self): def test_4bit(self) -> None:
# Arrange # Arrange
test_file = "Tests/images/hopper_gray_4bpp.tif" test_file = "Tests/images/hopper_gray_4bpp.tif"
original = hopper("L") original = hopper("L")
@ -603,7 +606,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.mode == "L" assert im.mode == "L"
assert_image_similar(im, original, 7.3) assert_image_similar(im, original, 7.3)
def test_gray_semibyte_per_pixel(self): def test_gray_semibyte_per_pixel(self) -> None:
test_files = ( test_files = (
( (
24.8, # epsilon 24.8, # epsilon
@ -636,7 +639,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert im2.mode == "L" assert im2.mode == "L"
assert_image_equal(im, im2) assert_image_equal(im, im2)
def test_save_bytesio(self): def test_save_bytesio(self) -> None:
# PR 1011 # PR 1011
# Test TIFF saving to io.BytesIO() object. # Test TIFF saving to io.BytesIO() object.
@ -646,7 +649,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Generate test image # Generate test image
pilim = hopper() pilim = hopper()
def save_bytesio(compression=None): def save_bytesio(compression: str | None = None) -> None:
buffer_io = io.BytesIO() buffer_io = io.BytesIO()
pilim.save(buffer_io, format="tiff", compression=compression) pilim.save(buffer_io, format="tiff", compression=compression)
buffer_io.seek(0) buffer_io.seek(0)
@ -661,7 +664,7 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.WRITE_LIBTIFF = False TiffImagePlugin.WRITE_LIBTIFF = False
TiffImagePlugin.READ_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") im = hopper("YCbCr")
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
im.save(outfile, compression="jpeg") im.save(outfile, compression="jpeg")
@ -670,7 +673,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert reloaded.tag_v2[530] == (1, 1) assert reloaded.tag_v2[530] == (1, 1)
assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255) 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") outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/tiff_adobe_deflate.tif") as im: with Image.open("Tests/images/tiff_adobe_deflate.tif") as im:
assert im.tag_v2[34665] == 125456 assert im.tag_v2[34665] == 125456
@ -680,7 +683,7 @@ class TestFileLibTiff(LibTiffTestCase):
if Image.core.libtiff_support_custom_tags: if Image.core.libtiff_support_custom_tags:
assert reloaded.tag_v2[34665] == 125456 assert reloaded.tag_v2[34665] == 125456
def test_crashing_metadata(self, tmp_path): def test_crashing_metadata(self, tmp_path: Path) -> None:
# issue 1597 # issue 1597
with Image.open("Tests/images/rdf.tif") as im: with Image.open("Tests/images/rdf.tif") as im:
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
@ -690,7 +693,7 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(out, format="TIFF") im.save(out, format="TIFF")
TiffImagePlugin.WRITE_LIBTIFF = False 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 # Issue 973
# Test TIFF with tag 297 (Page Number) having value of 0 0. # Test TIFF with tag 297 (Page Number) having value of 0 0.
# The first number is the current page number. # The first number is the current page number.
@ -704,7 +707,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not divide by zero # Should not divide by zero
im.save(outfile) 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 # https://github.com/python-pillow/Pillow/issues/1651
tmpfile = str(tmp_path / "temp.tif") tmpfile = str(tmp_path / "temp.tif")
@ -718,7 +721,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not raise PermissionError. # Should not raise PermissionError.
os.remove(tmpfile) os.remove(tmpfile)
def test_read_icc(self): def test_read_icc(self) -> None:
with Image.open("Tests/images/hopper.iccprofile.tif") as img: with Image.open("Tests/images/hopper.iccprofile.tif") as img:
icc = img.info.get("icc_profile") icc = img.info.get("icc_profile")
assert icc is not None assert icc is not None
@ -729,8 +732,8 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False
assert icc == icc_libtiff assert icc == icc_libtiff
def test_write_icc(self, tmp_path): def test_write_icc(self, tmp_path: Path) -> None:
def check_write(libtiff): def check_write(libtiff: bool) -> None:
TiffImagePlugin.WRITE_LIBTIFF = libtiff TiffImagePlugin.WRITE_LIBTIFF = libtiff
with Image.open("Tests/images/hopper.iccprofile.tif") as img: with Image.open("Tests/images/hopper.iccprofile.tif") as img:
@ -749,7 +752,7 @@ class TestFileLibTiff(LibTiffTestCase):
for libtiff in libtiffs: for libtiff in libtiffs:
check_write(libtiff) check_write(libtiff)
def test_multipage_compression(self): def test_multipage_compression(self) -> None:
with Image.open("Tests/images/compression.tif") as im: with Image.open("Tests/images/compression.tif") as im:
im.seek(0) im.seek(0)
assert im._compression == "tiff_ccitt" assert im._compression == "tiff_ccitt"
@ -765,7 +768,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.size == (10, 10) assert im.size == (10, 10)
im.load() im.load()
def test_save_tiff_with_jpegtables(self, tmp_path): def test_save_tiff_with_jpegtables(self, tmp_path: Path) -> None:
# Arrange # Arrange
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
@ -777,7 +780,7 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not raise UnicodeDecodeError or anything else # Should not raise UnicodeDecodeError or anything else
im.save(outfile) 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: with Image.open("Tests/images/tiff_16bit_RGB.tiff") as im:
assert im.mode == "RGB" assert im.mode == "RGB"
assert im.size == (100, 40) assert im.size == (100, 40)
@ -793,7 +796,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") 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: with Image.open("Tests/images/tiff_16bit_RGBa.tiff") as im:
assert im.mode == "RGBA" assert im.mode == "RGBA"
assert im.size == (100, 40) assert im.size == (100, 40)
@ -805,7 +808,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
@skip_unless_feature("jpg") @skip_unless_feature("jpg")
def test_gimp_tiff(self): def test_gimp_tiff(self) -> None:
# Read TIFF JPEG images from GIMP [@PIL168] # Read TIFF JPEG images from GIMP [@PIL168]
filename = "Tests/images/pil168.tif" filename = "Tests/images/pil168.tif"
with Image.open(filename) as im: with Image.open(filename) as im:
@ -818,14 +821,14 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/pil168.png") 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 # https://github.com/python-pillow/Pillow/issues/1466
with Image.open("Tests/images/copyleft.tiff") as im: with Image.open("Tests/images/copyleft.tiff") as im:
assert im.mode == "RGB" assert im.mode == "RGB"
assert_image_equal_tofile(im, "Tests/images/copyleft.png", 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)) im = Image.new("F", (1, 1))
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
TiffImagePlugin.WRITE_LIBTIFF = True TiffImagePlugin.WRITE_LIBTIFF = True
@ -836,7 +839,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert reloaded.mode == "F" assert reloaded.mode == "F"
assert reloaded.getexif()[SAMPLEFORMAT] == 3 assert reloaded.getexif()[SAMPLEFORMAT] == 3
def test_lzma(self, capfd): def test_lzma(self, capfd: pytest.CaptureFixture[str]) -> None:
try: try:
with Image.open("Tests/images/hopper_lzma.tif") as im: with Image.open("Tests/images/hopper_lzma.tif") as im:
assert im.mode == "RGB" assert im.mode == "RGB"
@ -852,7 +855,7 @@ class TestFileLibTiff(LibTiffTestCase):
sys.stderr.write(captured.err) sys.stderr.write(captured.err)
raise raise
def test_webp(self, capfd): def test_webp(self, capfd: pytest.CaptureFixture[str]) -> None:
try: try:
with Image.open("Tests/images/hopper_webp.tif") as im: with Image.open("Tests/images/hopper_webp.tif") as im:
assert im.mode == "RGB" assert im.mode == "RGB"
@ -874,7 +877,7 @@ class TestFileLibTiff(LibTiffTestCase):
sys.stderr.write(captured.err) sys.stderr.write(captured.err)
raise raise
def test_lzw(self): def test_lzw(self) -> None:
with Image.open("Tests/images/hopper_lzw.tif") as im: with Image.open("Tests/images/hopper_lzw.tif") as im:
assert im.mode == "RGB" assert im.mode == "RGB"
assert im.size == (128, 128) assert im.size == (128, 128)
@ -882,12 +885,12 @@ class TestFileLibTiff(LibTiffTestCase):
im2 = hopper() im2 = hopper()
assert_image_similar(im, im2, 5) 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" infile = "Tests/images/tiff_strip_cmyk_jpeg.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) 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" infile = "Tests/images/tiff_strip_cmyk_16l_jpeg.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
@ -895,7 +898,7 @@ class TestFileLibTiff(LibTiffTestCase):
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
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" infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.2) assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.2)
@ -903,12 +906,12 @@ class TestFileLibTiff(LibTiffTestCase):
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
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" infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01) 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" infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
@ -916,7 +919,7 @@ class TestFileLibTiff(LibTiffTestCase):
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
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" infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01) assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01)
@ -924,45 +927,45 @@ class TestFileLibTiff(LibTiffTestCase):
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
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" infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.5) 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 \ # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_strip_raw.tif tiff_strip_planar_lzw.tiff # tiff_strip_raw.tif tiff_strip_planar_lzw.tiff
infile = "Tests/images/tiff_strip_planar_lzw.tiff" infile = "Tests/images/tiff_strip_planar_lzw.tiff"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") 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 \ # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff # tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff
infile = "Tests/images/tiff_tiled_planar_lzw.tiff" infile = "Tests/images/tiff_tiled_planar_lzw.tiff"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") 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 \ # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff # tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im: 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") 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 \ # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
# tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff # tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff
with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im: 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") 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 \ # gdal_translate -co TILED=yes \
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
# tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff # tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff
with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im: 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") 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 \ # gdal_translate -co TILED=no \
# -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \
# tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff # tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff
@ -970,7 +973,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png")
@pytest.mark.parametrize("compression", (None, "jpeg")) @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() im = hopper()
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
@ -986,11 +989,11 @@ class TestFileLibTiff(LibTiffTestCase):
for tag in tags: for tag in tags:
assert tag not in reloaded.getexif() 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: with Image.open("Tests/images/old-style-jpeg-compression.tif") as im:
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png") 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( with Image.open(
"Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif" "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif"
) as im: ) as im:
@ -1019,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: with Image.open("Tests/images/" + file_name) as im:
assert im.mode == mode assert im.mode == mode
assert im.size == size assert im.size == size
assert im.tile == tile assert im.tile == tile
im.load() 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 # This image does not have a RowsPerStrip TIFF tag
infile = "Tests/images/no_rows_per_strip.tif" infile = "Tests/images/no_rows_per_strip.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
im.load() im.load()
assert im.size == (950, 975) 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: with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
for i in range(2, 9): for i in range(2, 9):
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im: with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
@ -1044,7 +1049,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_similar(base_im, im, 0.7) 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: with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
for i in range(2, 9): for i in range(2, 9):
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im: with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
@ -1053,7 +1058,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_similar(base_im, im, 0.7) assert_image_similar(base_im, im, 0.7)
@pytest.mark.valgrind_known_error(reason="Backtrace in Python Core") @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 # Assert that a TIFF image with SampleFormat=UINT tag is not corrupted
# when saving to a new file. # when saving to a new file.
# Pillow 6.0 fails with "OSError: cannot identify image file". # Pillow 6.0 fails with "OSError: cannot identify image file".
@ -1074,7 +1079,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as im: with Image.open(out) as im:
im.load() im.load()
def test_realloc_overflow(self): def test_realloc_overflow(self) -> None:
TiffImagePlugin.READ_LIBTIFF = True TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im: with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im:
with pytest.raises(OSError) as e: with pytest.raises(OSError) as e:
@ -1085,7 +1090,7 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False
@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg")) @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)) im = hopper("RGB").resize((256, 256))
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
im.save(out, compression=compression) im.save(out, compression=compression)
@ -1095,14 +1100,14 @@ class TestFileLibTiff(LibTiffTestCase):
assert len(im.tag_v2[STRIPOFFSETS]) > 1 assert len(im.tag_v2[STRIPOFFSETS]) > 1
@pytest.mark.parametrize("argument", (True, False)) @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)) im = hopper("RGB").resize((256, 256))
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
if not argument: if not argument:
TiffImagePlugin.STRIP_SIZE = 2**18 TiffImagePlugin.STRIP_SIZE = 2**18
try: try:
arguments = {"compression": "tiff_adobe_deflate"} arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"}
if argument: if argument:
arguments["strip_size"] = 2**18 arguments["strip_size"] = 2**18
im.save(out, **arguments) im.save(out, **arguments)
@ -1113,13 +1118,13 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.STRIP_SIZE = 65536 TiffImagePlugin.STRIP_SIZE = 65536
@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None)) @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)) im = Image.new("RGB", (0, 0))
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
with pytest.raises(SystemError): with pytest.raises(SystemError):
im.save(out, compression=compression) 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() im = hopper()
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
for _ in range(10000): for _ in range(10000):
@ -1133,7 +1138,7 @@ class TestFileLibTiff(LibTiffTestCase):
("Tests/images/child_ifd_jpeg.tiff", (20,)), ("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: with Image.open(path) as im:
ims = im.get_child_images() ims = im.get_child_images()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,8 @@ import os
import os.path import os.path
import tempfile import tempfile
import time import time
from pathlib import Path
from typing import Any, Generator
import pytest import pytest
@ -13,7 +15,7 @@ from PIL import Image, PdfParser, features
from .helper import hopper, mark_if_feature_version, skip_unless_feature 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: str, **kwargs: Any) -> str:
# Arrange # Arrange
im = hopper(mode) im = hopper(mode)
outfile = str(tmp_path / ("temp_" + mode + ".pdf")) outfile = str(tmp_path / ("temp_" + mode + ".pdf"))
@ -40,17 +42,17 @@ def helper_save_as_pdf(tmp_path, mode, **kwargs):
@pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK")) @pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK"))
def test_save(tmp_path, mode): def test_save(tmp_path: Path, mode: str) -> None:
helper_save_as_pdf(tmp_path, mode) helper_save_as_pdf(tmp_path, mode)
@skip_unless_feature("jpg_2000") @skip_unless_feature("jpg_2000")
@pytest.mark.parametrize("mode", ("LA", "RGBA")) @pytest.mark.parametrize("mode", ("LA", "RGBA"))
def test_save_alpha(tmp_path, mode): def test_save_alpha(tmp_path: Path, mode: str) -> None:
helper_save_as_pdf(tmp_path, mode) helper_save_as_pdf(tmp_path, mode)
def test_p_alpha(tmp_path): def test_p_alpha(tmp_path: Path) -> None:
# Arrange # Arrange
outfile = str(tmp_path / "temp.pdf") outfile = str(tmp_path / "temp.pdf")
with Image.open("Tests/images/pil123p.png") as im: with Image.open("Tests/images/pil123p.png") as im:
@ -66,7 +68,7 @@ def test_p_alpha(tmp_path):
assert b"\n/SMask " in contents assert b"\n/SMask " in contents
def test_monochrome(tmp_path): def test_monochrome(tmp_path: Path) -> None:
# Arrange # Arrange
mode = "1" mode = "1"
@ -75,7 +77,7 @@ def test_monochrome(tmp_path):
assert os.path.getsize(outfile) < (5000 if features.check("libtiff") else 15000) 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") im = hopper("PA")
outfile = str(tmp_path / "temp_PA.pdf") outfile = str(tmp_path / "temp_PA.pdf")
@ -83,7 +85,7 @@ def test_unsupported_mode(tmp_path):
im.save(outfile) im.save(outfile)
def test_resolution(tmp_path): def test_resolution(tmp_path: Path) -> None:
im = hopper() im = hopper()
outfile = str(tmp_path / "temp.pdf") outfile = str(tmp_path / "temp.pdf")
@ -111,7 +113,7 @@ def test_resolution(tmp_path):
{"dpi": (75, 150), "resolution": 200}, {"dpi": (75, 150), "resolution": 200},
), ),
) )
def test_dpi(params, tmp_path): def test_dpi(params: dict[str, int | tuple[int, int]], tmp_path: Path) -> None:
im = hopper() im = hopper()
outfile = str(tmp_path / "temp.pdf") outfile = str(tmp_path / "temp.pdf")
@ -135,7 +137,7 @@ def test_dpi(params, tmp_path):
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
def test_save_all(tmp_path): def test_save_all(tmp_path: Path) -> None:
# Single frame image # Single frame image
helper_save_as_pdf(tmp_path, "RGB", save_all=True) helper_save_as_pdf(tmp_path, "RGB", save_all=True)
@ -155,7 +157,7 @@ def test_save_all(tmp_path):
assert os.path.getsize(outfile) > 0 assert os.path.getsize(outfile) > 0
# Test appending using a generator # Test appending using a generator
def im_generator(ims): def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
yield from ims yield from ims
im.save(outfile, save_all=True, append_images=im_generator(ims)) im.save(outfile, save_all=True, append_images=im_generator(ims))
@ -171,7 +173,7 @@ def test_save_all(tmp_path):
assert os.path.getsize(outfile) > 0 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 # Test saving a multiframe image without save_all
with Image.open("Tests/images/dispose_bgnd.gif") as im: with Image.open("Tests/images/dispose_bgnd.gif") as im:
outfile = str(tmp_path / "temp.pdf") outfile = str(tmp_path / "temp.pdf")
@ -181,7 +183,7 @@ def test_multiframe_normal_save(tmp_path):
assert os.path.getsize(outfile) > 0 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 # fail on a buffer full of null bytes
with pytest.raises(PdfParser.PdfFormatError): with pytest.raises(PdfParser.PdfFormatError):
PdfParser.PdfParser(buf=bytearray(65536)) PdfParser.PdfParser(buf=bytearray(65536))
@ -218,14 +220,14 @@ def test_pdf_open(tmp_path):
assert not hopper_pdf.should_close_file 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") im = hopper("RGB")
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
with pytest.raises(OSError): with pytest.raises(OSError):
im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True) im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True)
def check_pdf_pages_consistency(pdf): def check_pdf_pages_consistency(pdf: PdfParser.PdfParser) -> None:
pages_info = pdf.read_indirect(pdf.pages_ref) pages_info = pdf.read_indirect(pdf.pages_ref)
assert b"Parent" not in pages_info assert b"Parent" not in pages_info
assert b"Kids" in pages_info assert b"Kids" in pages_info
@ -243,7 +245,7 @@ def check_pdf_pages_consistency(pdf):
assert kids_not_used == [] assert kids_not_used == []
def test_pdf_append(tmp_path): def test_pdf_append(tmp_path: Path) -> None:
# make a PDF file # make a PDF file
pdf_filename = helper_save_as_pdf(tmp_path, "RGB", producer="PdfParser") pdf_filename = helper_save_as_pdf(tmp_path, "RGB", producer="PdfParser")
@ -294,7 +296,7 @@ def test_pdf_append(tmp_path):
check_pdf_pages_consistency(pdf) check_pdf_pages_consistency(pdf)
def test_pdf_info(tmp_path): def test_pdf_info(tmp_path: Path) -> None:
# make a PDF file # make a PDF file
pdf_filename = helper_save_as_pdf( pdf_filename = helper_save_as_pdf(
tmp_path, tmp_path,
@ -323,7 +325,7 @@ def test_pdf_info(tmp_path):
check_pdf_pages_consistency(pdf) check_pdf_pages_consistency(pdf)
def test_pdf_append_to_bytesio(): def test_pdf_append_to_bytesio() -> None:
im = hopper("RGB") im = hopper("RGB")
f = io.BytesIO() f = io.BytesIO()
im.save(f, format="PDF") im.save(f, format="PDF")
@ -338,7 +340,7 @@ def test_pdf_append_to_bytesio():
@pytest.mark.timeout(1) @pytest.mark.timeout(1)
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower") @pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
@pytest.mark.parametrize("newline", (b"\r", b"\n")) @pytest.mark.parametrize("newline", (b"\r", b"\n"))
def test_redos(newline): def test_redos(newline: bytes) -> None:
malicious = b" trailer<<>>" + newline * 3456 malicious = b" trailer<<>>" + newline * 3456
# This particular exception isn't relevant here. # This particular exception isn't relevant here.

View File

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

View File

@ -5,6 +5,9 @@ import sys
import warnings import warnings
import zlib import zlib
from io import BytesIO from io import BytesIO
from pathlib import Path
from types import ModuleType
from typing import Any
import pytest import pytest
@ -21,6 +24,7 @@ from .helper import (
skip_unless_feature, skip_unless_feature,
) )
ElementTree: ModuleType | None
try: try:
from defusedxml import ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:
@ -35,7 +39,7 @@ TEST_PNG_FILE = "Tests/images/hopper.png"
MAGIC = PngImagePlugin._MAGIC MAGIC = PngImagePlugin._MAGIC
def chunk(cid, *data): def chunk(cid: bytes, *data: bytes) -> bytes:
test_file = BytesIO() test_file = BytesIO()
PngImagePlugin.putchunk(*(test_file, cid) + data) PngImagePlugin.putchunk(*(test_file, cid) + data)
return test_file.getvalue() return test_file.getvalue()
@ -51,11 +55,11 @@ HEAD = MAGIC + IHDR
TAIL = IDAT + IEND TAIL = IDAT + IEND
def load(data): def load(data: bytes) -> Image.Image:
return Image.open(BytesIO(data)) return Image.open(BytesIO(data))
def roundtrip(im, **options): def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
out = BytesIO() out = BytesIO()
im.save(out, "PNG", **options) im.save(out, "PNG", **options)
out.seek(0) out.seek(0)
@ -64,7 +68,7 @@ def roundtrip(im, **options):
@skip_unless_feature("zlib") @skip_unless_feature("zlib")
class TestFilePng: class TestFilePng:
def get_chunks(self, filename): def get_chunks(self, filename: str) -> list[bytes]:
chunks = [] chunks = []
with open(filename, "rb") as fp: with open(filename, "rb") as fp:
fp.read(8) fp.read(8)
@ -79,7 +83,7 @@ class TestFilePng:
png.crc(cid, s) png.crc(cid, s)
return chunks return chunks
def test_sanity(self, tmp_path): def test_sanity(self, tmp_path: Path) -> None:
# internal version number # internal version number
assert re.search(r"\d+(\.\d+){1,3}$", features.version_codec("zlib")) assert re.search(r"\d+(\.\d+){1,3}$", features.version_codec("zlib"))
@ -102,13 +106,13 @@ class TestFilePng:
reloaded = reloaded.convert(mode) reloaded = reloaded.convert(mode)
assert_image_equal(reloaded, im) assert_image_equal(reloaded, im)
def test_invalid_file(self): def test_invalid_file(self) -> None:
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
PngImagePlugin.PngImageFile(invalid_file) PngImagePlugin.PngImageFile(invalid_file)
def test_broken(self): def test_broken(self) -> None:
# Check reading of totally broken files. In this case, the test # Check reading of totally broken files. In this case, the test
# file was checked into Subversion as a text file. # file was checked into Subversion as a text file.
@ -117,7 +121,7 @@ class TestFilePng:
with Image.open(test_file): with Image.open(test_file):
pass pass
def test_bad_text(self): def test_bad_text(self) -> None:
# Make sure PIL can read malformed tEXt chunks (@PIL152) # Make sure PIL can read malformed tEXt chunks (@PIL152)
im = load(HEAD + chunk(b"tEXt") + TAIL) im = load(HEAD + chunk(b"tEXt") + TAIL)
@ -135,7 +139,7 @@ class TestFilePng:
im = load(HEAD + chunk(b"tEXt", b"spam\0egg\0") + TAIL) im = load(HEAD + chunk(b"tEXt", b"spam\0egg\0") + TAIL)
assert im.info == {"spam": "egg\x00"} 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) # Test reading malformed zTXt chunks (python-pillow/Pillow#318)
im = load(HEAD + chunk(b"zTXt") + TAIL) im = load(HEAD + chunk(b"zTXt") + TAIL)
@ -156,7 +160,7 @@ class TestFilePng:
im = load(HEAD + chunk(b"zTXt", b"spam\0\0" + zlib.compress(b"egg")) + TAIL) im = load(HEAD + chunk(b"zTXt", b"spam\0\0" + zlib.compress(b"egg")) + TAIL)
assert im.info == {"spam": "egg"} assert im.info == {"spam": "egg"}
def test_bad_itxt(self): def test_bad_itxt(self) -> None:
im = load(HEAD + chunk(b"iTXt") + TAIL) im = load(HEAD + chunk(b"iTXt") + TAIL)
assert im.info == {} assert im.info == {}
@ -200,7 +204,7 @@ class TestFilePng:
assert im.info["spam"].lang == "en" assert im.info["spam"].lang == "en"
assert im.info["spam"].tkey == "Spam" assert im.info["spam"].tkey == "Spam"
def test_interlace(self): def test_interlace(self) -> None:
test_file = "Tests/images/pil123p.png" test_file = "Tests/images/pil123p.png"
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert_image(im, "P", (162, 150)) assert_image(im, "P", (162, 150))
@ -215,7 +219,7 @@ class TestFilePng:
im.load() im.load()
def test_load_transparent_p(self): def test_load_transparent_p(self) -> None:
test_file = "Tests/images/pil123p.png" test_file = "Tests/images/pil123p.png"
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert_image(im, "P", (162, 150)) assert_image(im, "P", (162, 150))
@ -225,7 +229,7 @@ class TestFilePng:
# image has 124 unique alpha values # image has 124 unique alpha values
assert len(im.getchannel("A").getcolors()) == 124 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" test_file = "Tests/images/rgb_trns.png"
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.info["transparency"] == (0, 255, 52) assert im.info["transparency"] == (0, 255, 52)
@ -237,7 +241,7 @@ class TestFilePng:
# image has 876 transparent pixels # image has 876 transparent pixels
assert im.getchannel("A").getcolors()[0][0] == 876 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" in_file = "Tests/images/pil123p.png"
with Image.open(in_file) as im: with Image.open(in_file) as im:
# 'transparency' contains a byte string with the opacity for # 'transparency' contains a byte string with the opacity for
@ -258,7 +262,7 @@ class TestFilePng:
# image has 124 unique alpha values # image has 124 unique alpha values
assert len(im.getchannel("A").getcolors()) == 124 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" in_file = "Tests/images/p_trns_single.png"
with Image.open(in_file) as im: with Image.open(in_file) as im:
# pixel value 164 is full transparent # pixel value 164 is full transparent
@ -281,7 +285,7 @@ class TestFilePng:
# image has 876 transparent pixels # image has 876 transparent pixels
assert im.getchannel("A").getcolors()[0][0] == 876 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 # check if solid black image with full transparency
# is supported (check for #1838) # is supported (check for #1838)
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0)) im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))
@ -299,7 +303,7 @@ class TestFilePng:
assert_image(im, "RGBA", (10, 10)) assert_image(im, "RGBA", (10, 10))
assert im.getcolors() == [(100, (0, 0, 0, 0))] 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(): for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items():
in_file = "Tests/images/" + mode.lower() + "_trns.png" in_file = "Tests/images/" + mode.lower() + "_trns.png"
with Image.open(in_file) as im: with Image.open(in_file) as im:
@ -320,13 +324,13 @@ class TestFilePng:
test_im_rgba = test_im.convert("RGBA") test_im_rgba = test_im.convert("RGBA")
assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent 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" in_file = "Tests/images/caption_6_33_22.png"
with Image.open(in_file) as im: with Image.open(in_file) as im:
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")
im.save(test_file) im.save(test_file)
def test_load_verify(self): def test_load_verify(self) -> None:
# Check open/load/verify exception (@PIL150) # Check open/load/verify exception (@PIL150)
with Image.open(TEST_PNG_FILE) as im: with Image.open(TEST_PNG_FILE) as im:
@ -339,7 +343,7 @@ class TestFilePng:
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
im.verify() im.verify()
def test_verify_struct_error(self): def test_verify_struct_error(self) -> None:
# Check open/load/verify exception (#1755) # Check open/load/verify exception (#1755)
# offsets to test, -10: breaks in i32() in read. (OSError) # offsets to test, -10: breaks in i32() in read. (OSError)
@ -355,7 +359,7 @@ class TestFilePng:
with pytest.raises((OSError, SyntaxError)): with pytest.raises((OSError, SyntaxError)):
im.verify() im.verify()
def test_verify_ignores_crc_error(self): def test_verify_ignores_crc_error(self) -> None:
# check ignores crc errors in ancillary chunks # check ignores crc errors in ancillary chunks
chunk_data = chunk(b"tEXt", b"spam") chunk_data = chunk(b"tEXt", b"spam")
@ -372,7 +376,7 @@ class TestFilePng:
finally: finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False 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 # check does not ignore crc errors in required chunks
image_data = MAGIC + IHDR[:-1] + b"q" + TAIL image_data = MAGIC + IHDR[:-1] + b"q" + TAIL
@ -384,18 +388,18 @@ class TestFilePng:
finally: finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_roundtrip_dpi(self): def test_roundtrip_dpi(self) -> None:
# Check dpi roundtripping # Check dpi roundtripping
with Image.open(TEST_PNG_FILE) as im: with Image.open(TEST_PNG_FILE) as im:
im = roundtrip(im, dpi=(100.33, 100.33)) im = roundtrip(im, dpi=(100.33, 100.33))
assert im.info["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: with Image.open(TEST_PNG_FILE) as im:
assert im.info["dpi"] == (95.9866, 95.9866) assert im.info["dpi"] == (95.9866, 95.9866)
def test_roundtrip_text(self): def test_roundtrip_text(self) -> None:
# Check text roundtripping # Check text roundtripping
with Image.open(TEST_PNG_FILE) as im: with Image.open(TEST_PNG_FILE) as im:
@ -407,7 +411,7 @@ class TestFilePng:
assert im.info == {"TXT": "VALUE", "ZIP": "VALUE"} assert im.info == {"TXT": "VALUE", "ZIP": "VALUE"}
assert im.text == {"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 # Check iTXt roundtripping
im = Image.new("RGB", (32, 32)) im = Image.new("RGB", (32, 32))
@ -423,7 +427,7 @@ class TestFilePng:
assert im.text["eggs"].lang == "en" assert im.text["eggs"].lang == "en"
assert im.text["eggs"].tkey == "Eggs" 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 # Check so that non-Unicode text is saved as a tEXt rather than iTXt
im = Image.new("RGB", (32, 32)) im = Image.new("RGB", (32, 32))
@ -432,10 +436,10 @@ class TestFilePng:
im = roundtrip(im, pnginfo=info) im = roundtrip(im, pnginfo=info)
assert isinstance(im.info["Text"], str) assert isinstance(im.info["Text"], str)
def test_unicode_text(self): def test_unicode_text(self) -> None:
# Check preservation of non-ASCII characters # Check preservation of non-ASCII characters
def rt_text(value): def rt_text(value: str) -> None:
im = Image.new("RGB", (32, 32)) im = Image.new("RGB", (32, 32))
info = PngImagePlugin.PngInfo() info = PngImagePlugin.PngInfo()
info.add_text("Text", value) info.add_text("Text", value)
@ -448,7 +452,7 @@ class TestFilePng:
rt_text(chr(0x4E00) + chr(0x66F0) + chr(0x9FBA) + chr(0x3042) + chr(0xAC00)) rt_text(chr(0x4E00) + chr(0x66F0) + chr(0x9FBA) + chr(0x3042) + chr(0xAC00))
rt_text("A" + chr(0xC4) + chr(0x472) + chr(0x3042)) # Combined 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: # Check reading of evil PNG file. For information, see:
# http://scary.beasts.org/security/CESA-2004-001.txt # http://scary.beasts.org/security/CESA-2004-001.txt
# The first byte is removed from pngtest_bad.png # The first byte is removed from pngtest_bad.png
@ -462,7 +466,7 @@ class TestFilePng:
with Image.open(pngfile): with Image.open(pngfile):
pass pass
def test_trns_rgb(self): def test_trns_rgb(self) -> None:
# Check writing and reading of tRNS chunks for RGB images. # Check writing and reading of tRNS chunks for RGB images.
# Independent file sample provided by Sebastian Spaeth. # Independent file sample provided by Sebastian Spaeth.
@ -477,7 +481,7 @@ class TestFilePng:
im = roundtrip(im, transparency=(0, 1, 2)) im = roundtrip(im, transparency=(0, 1, 2))
assert im.info["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 # Check writing a transparency of 0, issue #528
im = hopper("P") im = hopper("P")
im.info["transparency"] = 0 im.info["transparency"] = 0
@ -490,13 +494,13 @@ class TestFilePng:
assert_image_equal(im2.convert("RGBA"), im.convert("RGBA")) 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 # Check reading images with null tRNS value, issue #1239
test_file = "Tests/images/tRNS_null_1x1.png" test_file = "Tests/images/tRNS_null_1x1.png"
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.info["transparency"] == 0 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: with Image.open("Tests/images/icc_profile_none.png") as im:
assert im.info["icc_profile"] is None assert im.info["icc_profile"] is None
@ -506,40 +510,40 @@ class TestFilePng:
im = roundtrip(im, icc_profile=expected_icc) im = roundtrip(im, icc_profile=expected_icc)
assert im.info["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: with Image.open("Tests/images/icc_profile.png") as im:
assert "icc_profile" in im.info assert "icc_profile" in im.info
im = roundtrip(im, icc_profile=None) im = roundtrip(im, icc_profile=None)
assert "icc_profile" not in im.info 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: with Image.open("Tests/images/icc_profile.png") as im:
expected_icc = im.info["icc_profile"] expected_icc = im.info["icc_profile"]
im = roundtrip(im) im = roundtrip(im)
assert im.info["icc_profile"] == expected_icc 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: with Image.open("Tests/images/icc_profile_none.png") as im:
assert im.info["icc_profile"] is None assert im.info["icc_profile"] is None
im = roundtrip(im) im = roundtrip(im)
assert "icc_profile" not in im.info assert "icc_profile" not in im.info
def test_repr_png(self): def test_repr_png(self) -> None:
im = hopper() im = hopper()
with Image.open(BytesIO(im._repr_png_())) as repr_png: with Image.open(BytesIO(im._repr_png_())) as repr_png:
assert repr_png.format == "PNG" assert repr_png.format == "PNG"
assert_image_equal(im, repr_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") im = hopper("F")
assert im._repr_png_() is None 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: with Image.open("Tests/images/icc_profile.png") as im:
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")
im.convert("P").save(test_file, dpi=(100, 100)) im.convert("P").save(test_file, dpi=(100, 100))
@ -560,17 +564,17 @@ class TestFilePng:
# pHYs - before IDAT # pHYs - before IDAT
assert chunks.index(b"pHYs") < chunks.index(b"IDAT") assert chunks.index(b"pHYs") < chunks.index(b"IDAT")
def test_getchunks(self): def test_getchunks(self) -> None:
im = hopper() im = hopper()
chunks = PngImagePlugin.getchunks(im) chunks = PngImagePlugin.getchunks(im)
assert len(chunks) == 3 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: with Image.open("Tests/images/exif.png") as im:
assert im.private_chunks == [(b"orNT", b"\x01")] 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 # Check private chunk roundtripping
with Image.open(TEST_PNG_FILE) as im: with Image.open(TEST_PNG_FILE) as im:
@ -588,7 +592,7 @@ class TestFilePng:
(b"prIV", b"VALUE3", True), (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: with Image.open("Tests/images/hopper.png") as im:
assert "comment" in im.text assert "comment" in im.text
for k, v in { for k, v in {
@ -615,7 +619,7 @@ class TestFilePng:
with Image.open("Tests/images/hopper_idat_after_image_end.png") as im: with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
def test_padded_idat(self): def test_padded_idat(self) -> None:
# This image has been manually hexedited # This image has been manually hexedited
# so that the IDAT chunk has padding at the end # so that the IDAT chunk has padding at the end
# Set MAXBLOCK to the length of the actual data # Set MAXBLOCK to the length of the actual data
@ -635,7 +639,7 @@ class TestFilePng:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"cid", (b"IHDR", b"sRGB", b"pHYs", b"acTL", b"fcTL", b"fdAT") "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() fp = BytesIO()
with PngImagePlugin.PngStream(fp) as png: with PngImagePlugin.PngStream(fp) as png:
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -645,7 +649,7 @@ class TestFilePng:
png.call(cid, 0, 0) png.call(cid, 0, 0)
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_specify_bits(self, tmp_path): def test_specify_bits(self, tmp_path: Path) -> None:
im = hopper("P") im = hopper("P")
out = str(tmp_path / "temp.png") out = str(tmp_path / "temp.png")
@ -654,7 +658,7 @@ class TestFilePng:
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert len(reloaded.png.im_palette[1]) == 48 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 = Image.new("P", (1, 1))
im.putpalette((1, 1, 1)) im.putpalette((1, 1, 1))
@ -664,7 +668,7 @@ class TestFilePng:
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert len(reloaded.png.im_palette[1]) == 3 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: with Image.open("Tests/images/color_snakes.png") as im:
if ElementTree is None: if ElementTree is None:
with pytest.warns( with pytest.warns(
@ -679,7 +683,7 @@ class TestFilePng:
assert description["PixelXDimension"] == "10" assert description["PixelXDimension"] == "10"
assert description["subject"]["Seq"] is None assert description["subject"]["Seq"] is None
def test_exif(self): def test_exif(self) -> None:
# With an EXIF chunk # With an EXIF chunk
with Image.open("Tests/images/exif.png") as im: with Image.open("Tests/images/exif.png") as im:
exif = im._getexif() exif = im._getexif()
@ -705,7 +709,7 @@ class TestFilePng:
exif = im.getexif() exif = im.getexif()
assert exif[274] == 3 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 exif is not saved from info
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")
with Image.open("Tests/images/exif.png") as im: with Image.open("Tests/images/exif.png") as im:
@ -725,7 +729,7 @@ class TestFilePng:
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
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: with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")
im.save(test_file, exif=im.getexif()) im.save(test_file, exif=im.getexif())
@ -734,7 +738,7 @@ class TestFilePng:
exif = reloaded._getexif() exif = reloaded._getexif()
assert exif[305] == "Adobe Photoshop CS Macintosh" 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: with Image.open(TEST_PNG_FILE) as im:
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")
im.save(test_file, exif=b"exifstring") im.save(test_file, exif=b"exifstring")
@ -742,11 +746,11 @@ class TestFilePng:
with Image.open(test_file) as reloaded: with Image.open(test_file) as reloaded:
assert reloaded.info["exif"] == b"Exif\x00\x00exifstring" 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: with Image.open(TEST_PNG_FILE) as im:
assert im.tell() == 0 assert im.tell() == 0
def test_seek(self): def test_seek(self) -> None:
with Image.open(TEST_PNG_FILE) as im: with Image.open(TEST_PNG_FILE) as im:
im.seek(0) im.seek(0)
@ -754,7 +758,7 @@ class TestFilePng:
im.seek(1) im.seek(1)
@pytest.mark.parametrize("buffer", (True, False)) @pytest.mark.parametrize("buffer", (True, False))
def test_save_stdout(self, buffer): def test_save_stdout(self, buffer: bool) -> None:
old_stdout = sys.stdout old_stdout = sys.stdout
if buffer: if buffer:
@ -786,7 +790,7 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase):
mem_limit = 2 * 1024 # max increase in K mem_limit = 2 * 1024 # max increase in K
iterations = 100 # Leak is 56k/iteration, this will leak 5.6megs 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: with open("Tests/images/hopper.png", "rb") as f:
DATA = BytesIO(f.read(16 * 1024)) DATA = BytesIO(f.read(16 * 1024))
@ -794,7 +798,7 @@ class TestTruncatedPngPLeaks(PillowLeakTestCase):
with Image.open(DATA) as im: with Image.open(DATA) as im:
im.load() im.load()
def core(): def core() -> None:
with Image.open(DATA) as im: with Image.open(DATA) as im:
im.load() im.load()

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import sys import sys
from io import BytesIO from io import BytesIO
from pathlib import Path
import pytest import pytest
@ -18,7 +19,7 @@ from .helper import (
TEST_FILE = "Tests/images/hopper.ppm" TEST_FILE = "Tests/images/hopper.ppm"
def test_sanity(): def test_sanity() -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert im.mode == "RGB" assert im.mode == "RGB"
assert im.size == (128, 128) assert im.size == (128, 128)
@ -69,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) fp = BytesIO(data)
with Image.open(fp) as im: with Image.open(fp) as im:
assert im.size == (3, 1) assert im.size == (3, 1)
@ -79,7 +82,7 @@ def test_arbitrary_maxval(data, mode, pixels):
assert tuple(px[x, 0] for x in range(3)) == 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: with Image.open("Tests/images/16_bit_binary.pgm") as im:
assert im.mode == "I" assert im.mode == "I"
assert im.size == (20, 100) assert im.size == (20, 100)
@ -88,7 +91,7 @@ def test_16bit_pgm():
assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.png") 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: with Image.open("Tests/images/16_bit_binary.pgm") as im:
filename = str(tmp_path / "temp.pgm") filename = str(tmp_path / "temp.pgm")
im.save(filename, "PPM") im.save(filename, "PPM")
@ -96,7 +99,7 @@ def test_16bit_pgm_write(tmp_path):
assert_image_equal_tofile(im, filename) 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: with Image.open("Tests/images/hopper.pnm") as im:
assert_image_similar(im, hopper(), 0.0001) assert_image_similar(im, hopper(), 0.0001)
@ -106,7 +109,7 @@ def test_pnm(tmp_path):
assert_image_equal_tofile(im, filename) assert_image_equal_tofile(im, filename)
def test_pfm(tmp_path): def test_pfm(tmp_path: Path) -> None:
with Image.open("Tests/images/hopper.pfm") as im: with Image.open("Tests/images/hopper.pfm") as im:
assert im.info["scale"] == 1.0 assert im.info["scale"] == 1.0
assert_image_equal(im, hopper("F")) assert_image_equal(im, hopper("F"))
@ -117,7 +120,7 @@ def test_pfm(tmp_path):
assert_image_equal_tofile(im, filename) assert_image_equal_tofile(im, filename)
def test_pfm_big_endian(tmp_path): def test_pfm_big_endian(tmp_path: Path) -> None:
with Image.open("Tests/images/hopper_be.pfm") as im: with Image.open("Tests/images/hopper_be.pfm") as im:
assert im.info["scale"] == 2.5 assert im.info["scale"] == 2.5
assert_image_equal(im, hopper("F")) assert_image_equal(im, hopper("F"))
@ -138,7 +141,7 @@ def test_pfm_big_endian(tmp_path):
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): def test_pfm_invalid(data: bytes) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
with Image.open(BytesIO(data)): with Image.open(BytesIO(data)):
pass pass
@ -161,12 +164,12 @@ def test_pfm_invalid(data):
), ),
), ),
) )
def test_plain(plain_path, raw_path): def test_plain(plain_path: str, raw_path: str) -> None:
with Image.open(plain_path) as im: with Image.open(plain_path) as im:
assert_image_equal_tofile(im, raw_path) assert_image_equal_tofile(im, raw_path)
def test_16bit_plain_pgm(): def test_16bit_plain_pgm() -> None:
# P2 with maxval 2 ** 16 - 1 # P2 with maxval 2 ** 16 - 1
with Image.open("Tests/images/hopper_16bit_plain.pgm") as im: with Image.open("Tests/images/hopper_16bit_plain.pgm") as im:
assert im.mode == "I" assert im.mode == "I"
@ -185,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), (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") path1 = str(tmp_path / "temp1.ppm")
path2 = str(tmp_path / "temp2.ppm") path2 = str(tmp_path / "temp2.ppm")
comment = b"# comment" * comment_count comment = b"# comment" * comment_count
@ -198,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")) @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") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(data) f.write(data)
@ -209,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")) @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") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(data) f.write(data)
@ -226,7 +231,7 @@ def test_plain_invalid_data(tmp_path, data):
b"P3\n128 128\n255\n012345678910 0", # token too long 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") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(data) f.write(data)
@ -236,7 +241,7 @@ def test_plain_ppm_token_too_long(tmp_path, data):
im.load() 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") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(b"P3\n128 128\n255\n256") f.write(b"P3\n128 128\n255\n256")
@ -246,12 +251,12 @@ def test_plain_ppm_value_too_large(tmp_path):
im.load() im.load()
def test_magic(): def test_magic() -> None:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
PpmImagePlugin.PpmImageFile(fp=BytesIO(b"PyInvalid")) 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") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(b"P6 #comment\n#comment\r12#comment\r8\n128 #comment\n255\n") f.write(b"P6 #comment\n#comment\r12#comment\r8\n128 #comment\n255\n")
@ -260,7 +265,7 @@ def test_header_with_comments(tmp_path):
assert im.size == (128, 128) 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") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(b"P6\nTEST") f.write(b"P6\nTEST")
@ -270,7 +275,7 @@ def test_non_integer_token(tmp_path):
pass 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") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(b"P6\n 01234567890") f.write(b"P6\n 01234567890")
@ -282,7 +287,7 @@ def test_header_token_too_long(tmp_path):
assert str(e.value) == "Token too long in file header: 01234567890" 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 # Test EOF in header
path = str(tmp_path / "temp.pgm") path = str(tmp_path / "temp.pgm")
with open(path, "wb") as f: with open(path, "wb") as f:
@ -301,7 +306,7 @@ def test_truncated_file(tmp_path):
im.load() 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") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(b"P2 1 2 255 255") f.write(b"P2 1 2 255 255")
@ -312,7 +317,7 @@ def test_not_enough_image_data(tmp_path):
@pytest.mark.parametrize("maxval", (b"0", b"65536")) @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") path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(b"P6\n3 1 " + maxval) f.write(b"P6\n3 1 " + maxval)
@ -324,7 +329,7 @@ def test_invalid_maxval(maxval, tmp_path):
assert str(e.value) == "maxval must be greater than 0 and less than 65536" 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 # Storage.c accepted negative values for xsize, ysize. the
# internal open_ppm function didn't check for sanity but it # internal open_ppm function didn't check for sanity but it
# has been removed. The default opener doesn't accept negative # has been removed. The default opener doesn't accept negative
@ -335,7 +340,7 @@ def test_neg_ppm():
pass pass
def test_mimetypes(tmp_path): def test_mimetypes(tmp_path: Path) -> None:
path = str(tmp_path / "temp.pgm") path = str(tmp_path / "temp.pgm")
with open(path, "wb") as f: with open(path, "wb") as f:
@ -350,7 +355,7 @@ def test_mimetypes(tmp_path):
@pytest.mark.parametrize("buffer", (True, False)) @pytest.mark.parametrize("buffer", (True, False))
def test_save_stdout(buffer): def test_save_stdout(buffer: bool) -> None:
old_stdout = sys.stdout old_stdout = sys.stdout
if buffer: if buffer:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ TEST_TAR_FILE = "Tests/images/hopper.tar"
("jpg", "hopper.jpg", "JPEG"), ("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): if features.check(codec):
with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar: with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar:
with Image.open(tar) as im: with Image.open(tar) as im:
@ -30,18 +30,18 @@ def test_sanity(codec, test_path, format):
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file(): def test_unclosed_file() -> None:
with pytest.warns(ResourceWarning): with pytest.warns(ResourceWarning):
TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
def test_close(): def test_close() -> None:
with warnings.catch_warnings(): with warnings.catch_warnings():
tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
tar.close() tar.close()
def test_contextmanager(): def test_contextmanager() -> None:
with warnings.catch_warnings(): with warnings.catch_warnings():
with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"): with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"):
pass pass

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import os import os
from glob import glob from glob import glob
from itertools import product from itertools import product
from pathlib import Path
import pytest import pytest
@ -21,8 +22,8 @@ _ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
@pytest.mark.parametrize("mode", _MODES) @pytest.mark.parametrize("mode", _MODES)
def test_sanity(mode, tmp_path): def test_sanity(mode: str, tmp_path: Path) -> None:
def roundtrip(original_im): def roundtrip(original_im: Image.Image) -> None:
out = str(tmp_path / "temp.tga") out = str(tmp_path / "temp.tga")
original_im.save(out, rle=rle) original_im.save(out, rle=rle)
@ -64,7 +65,7 @@ def test_sanity(mode, tmp_path):
roundtrip(original_im) roundtrip(original_im)
def test_palette_depth_16(tmp_path): def test_palette_depth_16(tmp_path: Path) -> None:
with Image.open("Tests/images/p_16.tga") as im: with Image.open("Tests/images/p_16.tga") as im:
assert_image_equal_tofile(im.convert("RGB"), "Tests/images/p_16.png") assert_image_equal_tofile(im.convert("RGB"), "Tests/images/p_16.png")
@ -74,7 +75,7 @@ def test_palette_depth_16(tmp_path):
assert_image_equal_tofile(reloaded.convert("RGB"), "Tests/images/p_16.png") assert_image_equal_tofile(reloaded.convert("RGB"), "Tests/images/p_16.png")
def test_id_field(): def test_id_field() -> None:
# tga file with id field # tga file with id field
test_file = "Tests/images/tga_id_field.tga" test_file = "Tests/images/tga_id_field.tga"
@ -84,7 +85,7 @@ def test_id_field():
assert im.size == (100, 100) assert im.size == (100, 100)
def test_id_field_rle(): def test_id_field_rle() -> None:
# tga file with id field # tga file with id field
test_file = "Tests/images/rgb32rle.tga" test_file = "Tests/images/rgb32rle.tga"
@ -94,7 +95,7 @@ def test_id_field_rle():
assert im.size == (199, 199) assert im.size == (199, 199)
def test_cross_scan_line(): def test_cross_scan_line() -> None:
with Image.open("Tests/images/cross_scan_line.tga") as im: with Image.open("Tests/images/cross_scan_line.tga") as im:
assert_image_equal_tofile(im, "Tests/images/cross_scan_line.png") assert_image_equal_tofile(im, "Tests/images/cross_scan_line.png")
@ -103,7 +104,7 @@ def test_cross_scan_line():
im.load() im.load()
def test_save(tmp_path): def test_save(tmp_path: Path) -> None:
test_file = "Tests/images/tga_id_field.tga" test_file = "Tests/images/tga_id_field.tga"
with Image.open(test_file) as im: with Image.open(test_file) as im:
out = str(tmp_path / "temp.tga") out = str(tmp_path / "temp.tga")
@ -120,7 +121,7 @@ def test_save(tmp_path):
assert test_im.size == (100, 100) assert test_im.size == (100, 100)
def test_small_palette(tmp_path): def test_small_palette(tmp_path: Path) -> None:
im = Image.new("P", (1, 1)) im = Image.new("P", (1, 1))
colors = [0, 0, 0] colors = [0, 0, 0]
im.putpalette(colors) im.putpalette(colors)
@ -132,7 +133,7 @@ def test_small_palette(tmp_path):
assert reloaded.getpalette() == colors assert reloaded.getpalette() == colors
def test_save_wrong_mode(tmp_path): def test_save_wrong_mode(tmp_path: Path) -> None:
im = hopper("PA") im = hopper("PA")
out = str(tmp_path / "temp.tga") out = str(tmp_path / "temp.tga")
@ -140,7 +141,7 @@ def test_save_wrong_mode(tmp_path):
im.save(out) im.save(out)
def test_save_mapdepth(): def test_save_mapdepth() -> None:
# This image has been manually hexedited from 200x32_p_bl_raw.tga # This image has been manually hexedited from 200x32_p_bl_raw.tga
# to include an origin # to include an origin
test_file = "Tests/images/200x32_p_bl_raw_origin.tga" test_file = "Tests/images/200x32_p_bl_raw_origin.tga"
@ -148,7 +149,7 @@ def test_save_mapdepth():
assert_image_equal_tofile(im, "Tests/images/tga/common/200x32_p.png") assert_image_equal_tofile(im, "Tests/images/tga/common/200x32_p.png")
def test_save_id_section(tmp_path): def test_save_id_section(tmp_path: Path) -> None:
test_file = "Tests/images/rgb32rle.tga" test_file = "Tests/images/rgb32rle.tga"
with Image.open(test_file) as im: with Image.open(test_file) as im:
out = str(tmp_path / "temp.tga") out = str(tmp_path / "temp.tga")
@ -179,7 +180,7 @@ def test_save_id_section(tmp_path):
assert "id_section" not in test_im.info assert "id_section" not in test_im.info
def test_save_orientation(tmp_path): def test_save_orientation(tmp_path: Path) -> None:
test_file = "Tests/images/rgb32rle.tga" test_file = "Tests/images/rgb32rle.tga"
out = str(tmp_path / "temp.tga") out = str(tmp_path / "temp.tga")
with Image.open(test_file) as im: with Image.open(test_file) as im:
@ -190,7 +191,7 @@ def test_save_orientation(tmp_path):
assert test_im.info["orientation"] == 1 assert test_im.info["orientation"] == 1
def test_horizontal_orientations(): def test_horizontal_orientations() -> None:
# These images have been manually hexedited to have the relevant orientations # These images have been manually hexedited to have the relevant orientations
with Image.open("Tests/images/rgb32rle_top_right.tga") as im: with Image.open("Tests/images/rgb32rle_top_right.tga") as im:
assert im.load()[90, 90][:3] == (0, 0, 0) assert im.load()[90, 90][:3] == (0, 0, 0)
@ -199,7 +200,7 @@ def test_horizontal_orientations():
assert im.load()[90, 90][:3] == (0, 255, 0) assert im.load()[90, 90][:3] == (0, 255, 0)
def test_save_rle(tmp_path): def test_save_rle(tmp_path: Path) -> None:
test_file = "Tests/images/rgb32rle.tga" test_file = "Tests/images/rgb32rle.tga"
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.info["compression"] == "tga_rle" assert im.info["compression"] == "tga_rle"
@ -232,7 +233,7 @@ def test_save_rle(tmp_path):
assert test_im.info["compression"] == "tga_rle" assert test_im.info["compression"] == "tga_rle"
def test_save_l_transparency(tmp_path): def test_save_l_transparency(tmp_path: Path) -> None:
# There are 559 transparent pixels in la.tga. # There are 559 transparent pixels in la.tga.
num_transparent = 559 num_transparent = 559

View File

@ -3,6 +3,9 @@ from __future__ import annotations
import os import os
import warnings import warnings
from io import BytesIO from io import BytesIO
from pathlib import Path
from types import ModuleType
from typing import Generator
import pytest import pytest
@ -19,6 +22,7 @@ from .helper import (
is_win32, is_win32,
) )
ElementTree: ModuleType | None
try: try:
from defusedxml import ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:
@ -26,7 +30,7 @@ except ImportError:
class TestFileTiff: class TestFileTiff:
def test_sanity(self, tmp_path): def test_sanity(self, tmp_path: Path) -> None:
filename = str(tmp_path / "temp.tif") filename = str(tmp_path / "temp.tif")
hopper("RGB").save(filename) hopper("RGB").save(filename)
@ -58,21 +62,21 @@ class TestFileTiff:
pass pass
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file(self): def test_unclosed_file(self) -> None:
def open(): def open() -> None:
im = Image.open("Tests/images/multipage.tiff") im = Image.open("Tests/images/multipage.tiff")
im.load() im.load()
with pytest.warns(ResourceWarning): with pytest.warns(ResourceWarning):
open() open()
def test_closed_file(self): def test_closed_file(self) -> None:
with warnings.catch_warnings(): with warnings.catch_warnings():
im = Image.open("Tests/images/multipage.tiff") im = Image.open("Tests/images/multipage.tiff")
im.load() im.load()
im.close() im.close()
def test_seek_after_close(self): def test_seek_after_close(self) -> None:
im = Image.open("Tests/images/multipage.tiff") im = Image.open("Tests/images/multipage.tiff")
im.close() im.close()
@ -81,12 +85,12 @@ class TestFileTiff:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.seek(1) im.seek(1)
def test_context_manager(self): def test_context_manager(self) -> None:
with warnings.catch_warnings(): with warnings.catch_warnings():
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
im.load() im.load()
def test_mac_tiff(self): def test_mac_tiff(self) -> None:
# Read RGBa images from macOS [@PIL136] # Read RGBa images from macOS [@PIL136]
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
@ -98,7 +102,7 @@ class TestFileTiff:
assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) assert_image_similar_tofile(im, "Tests/images/pil136.png", 1)
def test_bigtiff(self, tmp_path): def test_bigtiff(self, tmp_path: Path) -> None:
with Image.open("Tests/images/hopper_bigtiff.tif") as im: with Image.open("Tests/images/hopper_bigtiff.tif") as im:
assert_image_equal_tofile(im, "Tests/images/hopper.tif") assert_image_equal_tofile(im, "Tests/images/hopper.tif")
@ -109,13 +113,13 @@ class TestFileTiff:
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2) im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
def test_set_legacy_api(self): def test_set_legacy_api(self) -> None:
ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd = TiffImagePlugin.ImageFileDirectory_v2()
with pytest.raises(Exception) as e: with pytest.raises(Exception) as e:
ifd.legacy_api = None ifd.legacy_api = None
assert str(e.value) == "Not allowing setting of legacy api" assert str(e.value) == "Not allowing setting of legacy api"
def test_xyres_tiff(self): def test_xyres_tiff(self) -> None:
filename = "Tests/images/pil168.tif" filename = "Tests/images/pil168.tif"
with Image.open(filename) as im: with Image.open(filename) as im:
# legacy api # legacy api
@ -128,7 +132,7 @@ class TestFileTiff:
assert im.info["dpi"] == (72.0, 72.0) assert im.info["dpi"] == (72.0, 72.0)
def test_xyres_fallback_tiff(self): def test_xyres_fallback_tiff(self) -> None:
filename = "Tests/images/compression.tif" filename = "Tests/images/compression.tif"
with Image.open(filename) as im: with Image.open(filename) as im:
# v2 api # v2 api
@ -142,7 +146,7 @@ class TestFileTiff:
# Fallback "inch". # Fallback "inch".
assert im.info["dpi"] == (100.0, 100.0) assert im.info["dpi"] == (100.0, 100.0)
def test_int_resolution(self): def test_int_resolution(self) -> None:
filename = "Tests/images/pil168.tif" filename = "Tests/images/pil168.tif"
with Image.open(filename) as im: with Image.open(filename) as im:
# Try to read a file where X,Y_RESOLUTION are ints # Try to read a file where X,Y_RESOLUTION are ints
@ -155,14 +159,14 @@ class TestFileTiff:
"resolution_unit, dpi", "resolution_unit, dpi",
[(None, 72.8), (2, 72.8), (3, 184.912)], [(None, 72.8), (2, 72.8), (3, 184.912)],
) )
def test_load_float_dpi(self, resolution_unit, dpi): def test_load_float_dpi(self, resolution_unit: int | None, dpi: float) -> None:
with Image.open( with Image.open(
"Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif" "Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif"
) as im: ) as im:
assert im.tag_v2.get(RESOLUTION_UNIT) == resolution_unit assert im.tag_v2.get(RESOLUTION_UNIT) == resolution_unit
assert im.info["dpi"] == (dpi, dpi) assert im.info["dpi"] == (dpi, dpi)
def test_save_float_dpi(self, tmp_path): def test_save_float_dpi(self, tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/hopper.tif") as im: with Image.open("Tests/images/hopper.tif") as im:
dpi = (72.2, 72.2) dpi = (72.2, 72.2)
@ -171,7 +175,7 @@ class TestFileTiff:
with Image.open(outfile) as reloaded: with Image.open(outfile) as reloaded:
assert reloaded.info["dpi"] == dpi assert reloaded.info["dpi"] == dpi
def test_save_setting_missing_resolution(self): def test_save_setting_missing_resolution(self) -> None:
b = BytesIO() b = BytesIO()
with Image.open("Tests/images/10ct_32bit_128.tiff") as im: with Image.open("Tests/images/10ct_32bit_128.tiff") as im:
im.save(b, format="tiff", resolution=123.45) im.save(b, format="tiff", resolution=123.45)
@ -179,7 +183,7 @@ class TestFileTiff:
assert im.tag_v2[X_RESOLUTION] == 123.45 assert im.tag_v2[X_RESOLUTION] == 123.45
assert im.tag_v2[Y_RESOLUTION] == 123.45 assert im.tag_v2[Y_RESOLUTION] == 123.45
def test_invalid_file(self): def test_invalid_file(self) -> None:
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
@ -190,30 +194,30 @@ class TestFileTiff:
TiffImagePlugin.TiffImageFile(invalid_file) TiffImagePlugin.TiffImageFile(invalid_file)
TiffImagePlugin.PREFIXES.pop() TiffImagePlugin.PREFIXES.pop()
def test_bad_exif(self): def test_bad_exif(self) -> None:
with Image.open("Tests/images/hopper_bad_exif.jpg") as i: with Image.open("Tests/images/hopper_bad_exif.jpg") as i:
# Should not raise struct.error. # Should not raise struct.error.
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
i._getexif() i._getexif()
def test_save_rgba(self, tmp_path): def test_save_rgba(self, tmp_path: Path) -> None:
im = hopper("RGBA") im = hopper("RGBA")
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
im.save(outfile) im.save(outfile)
def test_save_unsupported_mode(self, tmp_path): def test_save_unsupported_mode(self, tmp_path: Path) -> None:
im = hopper("HSV") im = hopper("HSV")
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
with pytest.raises(OSError): with pytest.raises(OSError):
im.save(outfile) im.save(outfile)
def test_8bit_s(self): def test_8bit_s(self) -> None:
with Image.open("Tests/images/8bit.s.tif") as im: with Image.open("Tests/images/8bit.s.tif") as im:
im.load() im.load()
assert im.mode == "L" assert im.mode == "L"
assert im.getpixel((50, 50)) == 184 assert im.getpixel((50, 50)) == 184
def test_little_endian(self): def test_little_endian(self) -> None:
with Image.open("Tests/images/16bit.cropped.tif") as im: with Image.open("Tests/images/16bit.cropped.tif") as im:
assert im.getpixel((0, 0)) == 480 assert im.getpixel((0, 0)) == 480
assert im.mode == "I;16" assert im.mode == "I;16"
@ -223,7 +227,7 @@ class TestFileTiff:
assert b[0] == ord(b"\xe0") assert b[0] == ord(b"\xe0")
assert b[1] == ord(b"\x01") assert b[1] == ord(b"\x01")
def test_big_endian(self): def test_big_endian(self) -> None:
with Image.open("Tests/images/16bit.MM.cropped.tif") as im: with Image.open("Tests/images/16bit.MM.cropped.tif") as im:
assert im.getpixel((0, 0)) == 480 assert im.getpixel((0, 0)) == 480
assert im.mode == "I;16B" assert im.mode == "I;16B"
@ -233,7 +237,7 @@ class TestFileTiff:
assert b[0] == ord(b"\x01") assert b[0] == ord(b"\x01")
assert b[1] == ord(b"\xe0") assert b[1] == ord(b"\xe0")
def test_16bit_r(self): def test_16bit_r(self) -> None:
with Image.open("Tests/images/16bit.r.tif") as im: with Image.open("Tests/images/16bit.r.tif") as im:
assert im.getpixel((0, 0)) == 480 assert im.getpixel((0, 0)) == 480
assert im.mode == "I;16" assert im.mode == "I;16"
@ -242,14 +246,14 @@ class TestFileTiff:
assert b[0] == ord(b"\xe0") assert b[0] == ord(b"\xe0")
assert b[1] == ord(b"\x01") assert b[1] == ord(b"\x01")
def test_16bit_s(self): def test_16bit_s(self) -> None:
with Image.open("Tests/images/16bit.s.tif") as im: with Image.open("Tests/images/16bit.s.tif") as im:
im.load() im.load()
assert im.mode == "I" assert im.mode == "I"
assert im.getpixel((0, 0)) == 32767 assert im.getpixel((0, 0)) == 32767
assert im.getpixel((0, 1)) == 0 assert im.getpixel((0, 1)) == 0
def test_12bit_rawmode(self): def test_12bit_rawmode(self) -> None:
"""Are we generating the same interpretation """Are we generating the same interpretation
of the image as Imagemagick is?""" of the image as Imagemagick is?"""
@ -262,7 +266,7 @@ class TestFileTiff:
assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") assert_image_equal_tofile(im, "Tests/images/12in16bit.tif")
def test_32bit_float(self): def test_32bit_float(self) -> None:
# Issue 614, specific 32-bit float format # Issue 614, specific 32-bit float format
path = "Tests/images/10ct_32bit_128.tiff" path = "Tests/images/10ct_32bit_128.tiff"
with Image.open(path) as im: with Image.open(path) as im:
@ -271,7 +275,7 @@ class TestFileTiff:
assert im.getpixel((0, 0)) == -0.4526388943195343 assert im.getpixel((0, 0)) == -0.4526388943195343
assert im.getextrema() == (-3.140936851501465, 3.140684127807617) assert im.getextrema() == (-3.140936851501465, 3.140684127807617)
def test_unknown_pixel_mode(self): def test_unknown_pixel_mode(self) -> None:
with pytest.raises(OSError): with pytest.raises(OSError):
with Image.open("Tests/images/hopper_unknown_pixel_mode.tif"): with Image.open("Tests/images/hopper_unknown_pixel_mode.tif"):
pass pass
@ -283,12 +287,12 @@ class TestFileTiff:
("Tests/images/multipage.tiff", 3), ("Tests/images/multipage.tiff", 3),
), ),
) )
def test_n_frames(self, path, n_frames): def test_n_frames(self, path: str, n_frames: int) -> None:
with Image.open(path) as im: with Image.open(path) as im:
assert im.n_frames == n_frames assert im.n_frames == n_frames
assert im.is_animated == (n_frames != 1) assert im.is_animated == (n_frames != 1)
def test_eoferror(self): def test_eoferror(self) -> None:
with Image.open("Tests/images/multipage-lastframe.tif") as im: with Image.open("Tests/images/multipage-lastframe.tif") as im:
n_frames = im.n_frames n_frames = im.n_frames
@ -300,7 +304,7 @@ class TestFileTiff:
# Test that seeking to the last frame does not raise an error # Test that seeking to the last frame does not raise an error
im.seek(n_frames - 1) im.seek(n_frames - 1)
def test_multipage(self): def test_multipage(self) -> None:
# issue #862 # issue #862
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
# file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue
@ -324,13 +328,13 @@ class TestFileTiff:
assert im.size == (20, 20) assert im.size == (20, 20)
assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255) assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255)
def test_multipage_last_frame(self): def test_multipage_last_frame(self) -> None:
with Image.open("Tests/images/multipage-lastframe.tif") as im: with Image.open("Tests/images/multipage-lastframe.tif") as im:
im.load() im.load()
assert im.size == (20, 20) assert im.size == (20, 20)
assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255) assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255)
def test_frame_order(self): def test_frame_order(self) -> None:
# A frame can't progress to itself after reading # A frame can't progress to itself after reading
with Image.open("Tests/images/multipage_single_frame_loop.tiff") as im: with Image.open("Tests/images/multipage_single_frame_loop.tiff") as im:
assert im.n_frames == 1 assert im.n_frames == 1
@ -343,7 +347,7 @@ class TestFileTiff:
with Image.open("Tests/images/multipage_out_of_order.tiff") as im: with Image.open("Tests/images/multipage_out_of_order.tiff") as im:
assert im.n_frames == 3 assert im.n_frames == 3
def test___str__(self): def test___str__(self) -> None:
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
with Image.open(filename) as im: with Image.open(filename) as im:
# Act # Act
@ -352,7 +356,7 @@ class TestFileTiff:
# Assert # Assert
assert isinstance(ret, str) assert isinstance(ret, str)
def test_dict(self): def test_dict(self) -> None:
# Arrange # Arrange
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
with Image.open(filename) as im: with Image.open(filename) as im:
@ -392,7 +396,7 @@ class TestFileTiff:
} }
assert dict(im.tag) == legacy_tags assert dict(im.tag) == legacy_tags
def test__delitem__(self): def test__delitem__(self) -> None:
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
with Image.open(filename) as im: with Image.open(filename) as im:
len_before = len(dict(im.ifd)) len_before = len(dict(im.ifd))
@ -401,36 +405,36 @@ class TestFileTiff:
assert len_before == len_after + 1 assert len_before == len_after + 1
@pytest.mark.parametrize("legacy_api", (False, True)) @pytest.mark.parametrize("legacy_api", (False, True))
def test_load_byte(self, legacy_api): def test_load_byte(self, legacy_api: bool) -> None:
ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd = TiffImagePlugin.ImageFileDirectory_v2()
data = b"abc" data = b"abc"
ret = ifd.load_byte(data, legacy_api) ret = ifd.load_byte(data, legacy_api)
assert ret == b"abc" assert ret == b"abc"
def test_load_string(self): def test_load_string(self) -> None:
ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd = TiffImagePlugin.ImageFileDirectory_v2()
data = b"abc\0" data = b"abc\0"
ret = ifd.load_string(data, False) ret = ifd.load_string(data, False)
assert ret == "abc" assert ret == "abc"
def test_load_float(self): def test_load_float(self) -> None:
ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd = TiffImagePlugin.ImageFileDirectory_v2()
data = b"abcdabcd" data = b"abcdabcd"
ret = ifd.load_float(data, False) ret = ifd.load_float(data, False)
assert ret == (1.6777999408082104e22, 1.6777999408082104e22) assert ret == (1.6777999408082104e22, 1.6777999408082104e22)
def test_load_double(self): def test_load_double(self) -> None:
ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd = TiffImagePlugin.ImageFileDirectory_v2()
data = b"abcdefghabcdefgh" data = b"abcdefghabcdefgh"
ret = ifd.load_double(data, False) ret = ifd.load_double(data, False)
assert ret == (8.540883223036124e194, 8.540883223036124e194) assert ret == (8.540883223036124e194, 8.540883223036124e194)
def test_ifd_tag_type(self): def test_ifd_tag_type(self) -> None:
with Image.open("Tests/images/ifd_tag_type.tiff") as im: with Image.open("Tests/images/ifd_tag_type.tiff") as im:
assert 0x8825 in im.tag_v2 assert 0x8825 in im.tag_v2
def test_exif(self, tmp_path): def test_exif(self, tmp_path: Path) -> None:
def check_exif(exif): def check_exif(exif: Image.Exif) -> None:
assert sorted(exif.keys()) == [ assert sorted(exif.keys()) == [
256, 256,
257, 257,
@ -481,7 +485,7 @@ class TestFileTiff:
exif = im.getexif() exif = im.getexif()
check_exif(exif) check_exif(exif)
def test_modify_exif(self, tmp_path): def test_modify_exif(self, tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/ifd_tag_type.tiff") as im: with Image.open("Tests/images/ifd_tag_type.tiff") as im:
exif = im.getexif() exif = im.getexif()
@ -493,7 +497,7 @@ class TestFileTiff:
exif = im.getexif() exif = im.getexif()
assert exif[264] == 100 assert exif[264] == 100
def test_reload_exif_after_seek(self): def test_reload_exif_after_seek(self) -> None:
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
exif = im.getexif() exif = im.getexif()
del exif[256] del exif[256]
@ -501,7 +505,7 @@ class TestFileTiff:
assert 256 in exif assert 256 in exif
def test_exif_frames(self): def test_exif_frames(self) -> None:
# Test that EXIF data can change across frames # Test that EXIF data can change across frames
with Image.open("Tests/images/g4-multi.tiff") as im: with Image.open("Tests/images/g4-multi.tiff") as im:
assert im.getexif()[273] == (328, 815) assert im.getexif()[273] == (328, 815)
@ -510,7 +514,7 @@ class TestFileTiff:
assert im.getexif()[273] == (1408, 1907) assert im.getexif()[273] == (1408, 1907)
@pytest.mark.parametrize("mode", ("1", "L")) @pytest.mark.parametrize("mode", ("1", "L"))
def test_photometric(self, mode, tmp_path): def test_photometric(self, mode: str, tmp_path: Path) -> None:
filename = str(tmp_path / "temp.tif") filename = str(tmp_path / "temp.tif")
im = hopper(mode) im = hopper(mode)
im.save(filename, tiffinfo={262: 0}) im.save(filename, tiffinfo={262: 0})
@ -518,13 +522,13 @@ class TestFileTiff:
assert reloaded.tag_v2[262] == 0 assert reloaded.tag_v2[262] == 0
assert_image_equal(im, reloaded) assert_image_equal(im, reloaded)
def test_seek(self): def test_seek(self) -> None:
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
with Image.open(filename) as im: with Image.open(filename) as im:
im.seek(0) im.seek(0)
assert im.tell() == 0 assert im.tell() == 0
def test_seek_eof(self): def test_seek_eof(self) -> None:
filename = "Tests/images/pil136.tiff" filename = "Tests/images/pil136.tiff"
with Image.open(filename) as im: with Image.open(filename) as im:
assert im.tell() == 0 assert im.tell() == 0
@ -533,21 +537,21 @@ class TestFileTiff:
with pytest.raises(EOFError): with pytest.raises(EOFError):
im.seek(1) im.seek(1)
def test__limit_rational_int(self): def test__limit_rational_int(self) -> None:
from PIL.TiffImagePlugin import _limit_rational from PIL.TiffImagePlugin import _limit_rational
value = 34 value = 34
ret = _limit_rational(value, 65536) ret = _limit_rational(value, 65536)
assert ret == (34, 1) assert ret == (34, 1)
def test__limit_rational_float(self): def test__limit_rational_float(self) -> None:
from PIL.TiffImagePlugin import _limit_rational from PIL.TiffImagePlugin import _limit_rational
value = 22.3 value = 22.3
ret = _limit_rational(value, 65536) ret = _limit_rational(value, 65536)
assert ret == (223, 10) assert ret == (223, 10)
def test_4bit(self): def test_4bit(self) -> None:
test_file = "Tests/images/hopper_gray_4bpp.tif" test_file = "Tests/images/hopper_gray_4bpp.tif"
original = hopper("L") original = hopper("L")
with Image.open(test_file) as im: with Image.open(test_file) as im:
@ -555,7 +559,7 @@ class TestFileTiff:
assert im.mode == "L" assert im.mode == "L"
assert_image_similar(im, original, 7.3) assert_image_similar(im, original, 7.3)
def test_gray_semibyte_per_pixel(self): def test_gray_semibyte_per_pixel(self) -> None:
test_files = ( test_files = (
( (
24.8, # epsilon 24.8, # epsilon
@ -588,7 +592,7 @@ class TestFileTiff:
assert im2.mode == "L" assert im2.mode == "L"
assert_image_equal(im, im2) assert_image_equal(im, im2)
def test_with_underscores(self, tmp_path): def test_with_underscores(self, tmp_path: Path) -> None:
kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36} kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36}
filename = str(tmp_path / "temp.tif") filename = str(tmp_path / "temp.tif")
hopper("RGB").save(filename, **kwargs) hopper("RGB").save(filename, **kwargs)
@ -601,7 +605,7 @@ class TestFileTiff:
assert im.tag_v2[X_RESOLUTION] == 72 assert im.tag_v2[X_RESOLUTION] == 72
assert im.tag_v2[Y_RESOLUTION] == 36 assert im.tag_v2[Y_RESOLUTION] == 36
def test_roundtrip_tiff_uint16(self, tmp_path): def test_roundtrip_tiff_uint16(self, tmp_path: Path) -> None:
# Test an image of all '0' values # Test an image of all '0' values
pixel_value = 0x1234 pixel_value = 0x1234
infile = "Tests/images/uint16_1_4660.tif" infile = "Tests/images/uint16_1_4660.tif"
@ -613,7 +617,7 @@ class TestFileTiff:
assert_image_equal_tofile(im, tmpfile) assert_image_equal_tofile(im, tmpfile)
def test_rowsperstrip(self, tmp_path): def test_rowsperstrip(self, tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
im = hopper() im = hopper()
im.save(outfile, tiffinfo={278: 256}) im.save(outfile, tiffinfo={278: 256})
@ -621,25 +625,25 @@ class TestFileTiff:
with Image.open(outfile) as im: with Image.open(outfile) as im:
assert im.tag_v2[278] == 256 assert im.tag_v2[278] == 256
def test_strip_raw(self): def test_strip_raw(self) -> None:
infile = "Tests/images/tiff_strip_raw.tif" infile = "Tests/images/tiff_strip_raw.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
def test_strip_planar_raw(self): def test_strip_planar_raw(self) -> None:
# gdal_translate -of GTiff -co INTERLEAVE=BAND \ # gdal_translate -of GTiff -co INTERLEAVE=BAND \
# tiff_strip_raw.tif tiff_strip_planar_raw.tiff # tiff_strip_raw.tif tiff_strip_planar_raw.tiff
infile = "Tests/images/tiff_strip_planar_raw.tif" infile = "Tests/images/tiff_strip_planar_raw.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
def test_strip_planar_raw_with_overviews(self): def test_strip_planar_raw_with_overviews(self) -> None:
# gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16 # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16
infile = "Tests/images/tiff_strip_planar_raw_with_overviews.tif" infile = "Tests/images/tiff_strip_planar_raw_with_overviews.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
def test_tiled_planar_raw(self): def test_tiled_planar_raw(self) -> None:
# gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \ # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \
# -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \ # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \
# tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff
@ -647,7 +651,7 @@ class TestFileTiff:
with Image.open(infile) as im: with Image.open(infile) as im:
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
def test_planar_configuration_save(self, tmp_path): def test_planar_configuration_save(self, tmp_path: Path) -> None:
infile = "Tests/images/tiff_tiled_planar_raw.tif" infile = "Tests/images/tiff_tiled_planar_raw.tif"
with Image.open(infile) as im: with Image.open(infile) as im:
assert im._planar_configuration == 2 assert im._planar_configuration == 2
@ -659,7 +663,7 @@ class TestFileTiff:
assert_image_equal_tofile(reloaded, infile) assert_image_equal_tofile(reloaded, infile)
@pytest.mark.parametrize("mode", ("P", "PA")) @pytest.mark.parametrize("mode", ("P", "PA"))
def test_palette(self, mode, tmp_path): def test_palette(self, mode: str, tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
im = hopper(mode) im = hopper(mode)
@ -668,7 +672,7 @@ class TestFileTiff:
with Image.open(outfile) as reloaded: with Image.open(outfile) as reloaded:
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
def test_tiff_save_all(self): def test_tiff_save_all(self) -> None:
mp = BytesIO() mp = BytesIO()
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
im.save(mp, format="tiff", save_all=True) im.save(mp, format="tiff", save_all=True)
@ -688,7 +692,7 @@ class TestFileTiff:
assert reread.n_frames == 3 assert reread.n_frames == 3
# Test appending using a generator # Test appending using a generator
def im_generator(ims): def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
yield from ims yield from ims
mp = BytesIO() mp = BytesIO()
@ -698,7 +702,7 @@ class TestFileTiff:
with Image.open(mp) as reread: with Image.open(mp) as reread:
assert reread.n_frames == 3 assert reread.n_frames == 3
def test_saving_icc_profile(self, tmp_path): def test_saving_icc_profile(self, tmp_path: Path) -> None:
# Tests saving TIFF with icc_profile set. # Tests saving TIFF with icc_profile set.
# At the time of writing this will only work for non-compressed tiffs # At the time of writing this will only work for non-compressed tiffs
# as libtiff does not support embedded ICC profiles, # as libtiff does not support embedded ICC profiles,
@ -712,7 +716,7 @@ class TestFileTiff:
with Image.open(tmpfile) as reloaded: with Image.open(tmpfile) as reloaded:
assert b"Dummy value" == reloaded.info["icc_profile"] assert b"Dummy value" == reloaded.info["icc_profile"]
def test_save_icc_profile(self, tmp_path): def test_save_icc_profile(self, tmp_path: Path) -> None:
im = hopper() im = hopper()
assert "icc_profile" not in im.info assert "icc_profile" not in im.info
@ -723,14 +727,14 @@ class TestFileTiff:
with Image.open(outfile) as reloaded: with Image.open(outfile) as reloaded:
assert reloaded.info["icc_profile"] == icc_profile assert reloaded.info["icc_profile"] == icc_profile
def test_save_bmp_compression(self, tmp_path): def test_save_bmp_compression(self, tmp_path: Path) -> None:
with Image.open("Tests/images/hopper.bmp") as im: with Image.open("Tests/images/hopper.bmp") as im:
assert im.info["compression"] == 0 assert im.info["compression"] == 0
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
im.save(outfile) im.save(outfile)
def test_discard_icc_profile(self, tmp_path): def test_discard_icc_profile(self, tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.tif") outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/icc_profile.png") as im: with Image.open("Tests/images/icc_profile.png") as im:
@ -741,7 +745,7 @@ class TestFileTiff:
with Image.open(outfile) as reloaded: with Image.open(outfile) as reloaded:
assert "icc_profile" not in reloaded.info assert "icc_profile" not in reloaded.info
def test_getxmp(self): def test_getxmp(self) -> None:
with Image.open("Tests/images/lab.tif") as im: with Image.open("Tests/images/lab.tif") as im:
if ElementTree is None: if ElementTree is None:
with pytest.warns( with pytest.warns(
@ -756,7 +760,7 @@ class TestFileTiff:
assert description[0]["format"] == "image/tiff" assert description[0]["format"] == "image/tiff"
assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"] assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
def test_get_photoshop_blocks(self): def test_get_photoshop_blocks(self) -> None:
with Image.open("Tests/images/lab.tif") as im: with Image.open("Tests/images/lab.tif") as im:
assert list(im.get_photoshop_blocks().keys()) == [ assert list(im.get_photoshop_blocks().keys()) == [
1061, 1061,
@ -782,7 +786,7 @@ class TestFileTiff:
4001, 4001,
] ]
def test_tiff_chunks(self, tmp_path): def test_tiff_chunks(self, tmp_path: Path) -> None:
tmpfile = str(tmp_path / "temp.tif") tmpfile = str(tmp_path / "temp.tif")
im = hopper() im = hopper()
@ -803,7 +807,7 @@ class TestFileTiff:
assert_image_equal_tofile(im, tmpfile) assert_image_equal_tofile(im, tmpfile)
def test_close_on_load_exclusive(self, tmp_path): def test_close_on_load_exclusive(self, tmp_path: Path) -> None:
# similar to test_fd_leak, but runs on unixlike os # similar to test_fd_leak, but runs on unixlike os
tmpfile = str(tmp_path / "temp.tif") tmpfile = str(tmp_path / "temp.tif")
@ -816,7 +820,7 @@ class TestFileTiff:
im.load() im.load()
assert fp.closed assert fp.closed
def test_close_on_load_nonexclusive(self, tmp_path): def test_close_on_load_nonexclusive(self, tmp_path: Path) -> None:
tmpfile = str(tmp_path / "temp.tif") tmpfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/uint16_1_4660.tif") as im: with Image.open("Tests/images/uint16_1_4660.tif") as im:
@ -838,7 +842,7 @@ class TestFileTiff:
not os.path.exists("Tests/images/string_dimension.tiff"), not os.path.exists("Tests/images/string_dimension.tiff"),
reason="Extra image files not installed", reason="Extra image files not installed",
) )
def test_string_dimension(self): def test_string_dimension(self) -> None:
# Assert that an error is raised if one of the dimensions is a string # Assert that an error is raised if one of the dimensions is a string
with Image.open("Tests/images/string_dimension.tiff") as im: with Image.open("Tests/images/string_dimension.tiff") as im:
with pytest.raises(OSError): with pytest.raises(OSError):
@ -846,7 +850,7 @@ class TestFileTiff:
@pytest.mark.timeout(6) @pytest.mark.timeout(6)
@pytest.mark.filterwarnings("ignore:Truncated File Read") @pytest.mark.filterwarnings("ignore:Truncated File Read")
def test_timeout(self): def test_timeout(self) -> None:
with Image.open("Tests/images/timeout-6646305047838720") as im: with Image.open("Tests/images/timeout-6646305047838720") as im:
ImageFile.LOAD_TRUNCATED_IMAGES = True ImageFile.LOAD_TRUNCATED_IMAGES = True
im.load() im.load()
@ -859,7 +863,7 @@ class TestFileTiff:
], ],
) )
@pytest.mark.timeout(2) @pytest.mark.timeout(2)
def test_oom(self, test_file): def test_oom(self, test_file: str) -> None:
with pytest.raises(UnidentifiedImageError): with pytest.raises(UnidentifiedImageError):
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
with Image.open(test_file): with Image.open(test_file):
@ -868,7 +872,7 @@ class TestFileTiff:
@pytest.mark.skipif(not is_win32(), reason="Windows only") @pytest.mark.skipif(not is_win32(), reason="Windows only")
class TestFileTiffW32: class TestFileTiffW32:
def test_fd_leak(self, tmp_path): def test_fd_leak(self, tmp_path: Path) -> None:
tmpfile = str(tmp_path / "temp.tif") tmpfile = str(tmp_path / "temp.tif")
# this is an mmaped file. # this is an mmaped file.

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import io import io
import struct import struct
from pathlib import Path
import pytest import pytest
@ -13,7 +14,7 @@ from .helper import assert_deep_equal, hopper
TAG_IDS = {info.name: info.value for info in TiffTags.TAGS_V2.values()} TAG_IDS = {info.name: info.value for info in TiffTags.TAGS_V2.values()}
def test_rt_metadata(tmp_path): def test_rt_metadata(tmp_path: Path) -> None:
"""Test writing arbitrary metadata into the tiff image directory """Test writing arbitrary metadata into the tiff image directory
Use case is ImageJ private tags, one numeric, one arbitrary Use case is ImageJ private tags, one numeric, one arbitrary
data. https://github.com/python-pillow/Pillow/issues/291 data. https://github.com/python-pillow/Pillow/issues/291
@ -79,7 +80,7 @@ def test_rt_metadata(tmp_path):
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8) assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
def test_read_metadata(): def test_read_metadata() -> None:
with Image.open("Tests/images/hopper_g4.tif") as img: with Image.open("Tests/images/hopper_g4.tif") as img:
assert { assert {
"YResolution": IFDRational(4294967295, 113653537), "YResolution": IFDRational(4294967295, 113653537),
@ -120,7 +121,7 @@ def test_read_metadata():
} == img.tag.named() } == img.tag.named()
def test_write_metadata(tmp_path): def test_write_metadata(tmp_path: Path) -> None:
"""Test metadata writing through the python code""" """Test metadata writing through the python code"""
with Image.open("Tests/images/hopper.tif") as img: with Image.open("Tests/images/hopper.tif") as img:
f = str(tmp_path / "temp.tiff") f = str(tmp_path / "temp.tiff")
@ -157,7 +158,7 @@ def test_write_metadata(tmp_path):
assert value == reloaded[tag], f"{tag} didn't roundtrip" assert value == reloaded[tag], f"{tag} didn't roundtrip"
def test_change_stripbytecounts_tag_type(tmp_path): def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None:
out = str(tmp_path / "temp.tiff") out = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper.tif") as im: with Image.open("Tests/images/hopper.tif") as im:
info = im.tag_v2 info = im.tag_v2
@ -176,19 +177,21 @@ def test_change_stripbytecounts_tag_type(tmp_path):
assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG
def test_no_duplicate_50741_tag(): def test_no_duplicate_50741_tag() -> None:
assert TAG_IDS["MakerNoteSafety"] == 50741 assert TAG_IDS["MakerNoteSafety"] == 50741
assert TAG_IDS["BestQualityScale"] == 50780 assert TAG_IDS["BestQualityScale"] == 50780
def test_iptc(tmp_path): def test_iptc(tmp_path: Path) -> None:
out = str(tmp_path / "temp.tiff") out = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper.Lab.tif") as im: with Image.open("Tests/images/hopper.Lab.tif") as im:
im.save(out) im.save(out)
@pytest.mark.parametrize("value, expected", ((b"test", "test"), (1, "1"))) @pytest.mark.parametrize("value, expected", ((b"test", "test"), (1, "1")))
def test_writing_other_types_to_ascii(value, expected, tmp_path): def test_writing_other_types_to_ascii(
value: bytes | int, expected: str, tmp_path: Path
) -> None:
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()
tag = TiffTags.TAGS_V2[271] tag = TiffTags.TAGS_V2[271]
@ -205,7 +208,7 @@ def test_writing_other_types_to_ascii(value, expected, tmp_path):
@pytest.mark.parametrize("value", (1, IFDRational(1))) @pytest.mark.parametrize("value", (1, IFDRational(1)))
def test_writing_other_types_to_bytes(value, tmp_path): def test_writing_other_types_to_bytes(value: int | IFDRational, tmp_path: Path) -> None:
im = hopper() im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()
@ -221,7 +224,7 @@ def test_writing_other_types_to_bytes(value, tmp_path):
assert reloaded.tag_v2[700] == b"\x01" assert reloaded.tag_v2[700] == b"\x01"
def test_writing_other_types_to_undefined(tmp_path): def test_writing_other_types_to_undefined(tmp_path: Path) -> None:
im = hopper() im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()
@ -237,7 +240,7 @@ def test_writing_other_types_to_undefined(tmp_path):
assert reloaded.tag_v2[33723] == b"1" assert reloaded.tag_v2[33723] == b"1"
def test_undefined_zero(tmp_path): def test_undefined_zero(tmp_path: Path) -> None:
# Check that the tag has not been changed since this test was created # Check that the tag has not been changed since this test was created
tag = TiffTags.TAGS_V2[45059] tag = TiffTags.TAGS_V2[45059]
assert tag.type == TiffTags.UNDEFINED assert tag.type == TiffTags.UNDEFINED
@ -252,7 +255,7 @@ def test_undefined_zero(tmp_path):
assert info[45059] == original assert info[45059] == original
def test_empty_metadata(): def test_empty_metadata() -> None:
f = io.BytesIO(b"II*\x00\x08\x00\x00\x00") f = io.BytesIO(b"II*\x00\x08\x00\x00\x00")
head = f.read(8) head = f.read(8)
info = TiffImagePlugin.ImageFileDirectory(head) info = TiffImagePlugin.ImageFileDirectory(head)
@ -261,7 +264,7 @@ def test_empty_metadata():
info.load(f) info.load(f)
def test_iccprofile(tmp_path): def test_iccprofile(tmp_path: Path) -> None:
# https://github.com/python-pillow/Pillow/issues/1462 # https://github.com/python-pillow/Pillow/issues/1462
out = str(tmp_path / "temp.tiff") out = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper.iccprofile.tif") as im: with Image.open("Tests/images/hopper.iccprofile.tif") as im:
@ -272,7 +275,7 @@ def test_iccprofile(tmp_path):
assert im.info["icc_profile"] == reloaded.info["icc_profile"] assert im.info["icc_profile"] == reloaded.info["icc_profile"]
def test_iccprofile_binary(): def test_iccprofile_binary() -> None:
# https://github.com/python-pillow/Pillow/issues/1526 # https://github.com/python-pillow/Pillow/issues/1526
# We should be able to load this, # We should be able to load this,
# but probably won't be able to save it. # but probably won't be able to save it.
@ -282,19 +285,19 @@ def test_iccprofile_binary():
assert im.info["icc_profile"] assert im.info["icc_profile"]
def test_iccprofile_save_png(tmp_path): def test_iccprofile_save_png(tmp_path: Path) -> None:
with Image.open("Tests/images/hopper.iccprofile.tif") as im: with Image.open("Tests/images/hopper.iccprofile.tif") as im:
outfile = str(tmp_path / "temp.png") outfile = str(tmp_path / "temp.png")
im.save(outfile) im.save(outfile)
def test_iccprofile_binary_save_png(tmp_path): def test_iccprofile_binary_save_png(tmp_path: Path) -> None:
with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im: with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im:
outfile = str(tmp_path / "temp.png") outfile = str(tmp_path / "temp.png")
im.save(outfile) im.save(outfile)
def test_exif_div_zero(tmp_path): def test_exif_div_zero(tmp_path: Path) -> None:
im = hopper() im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()
info[41988] = TiffImagePlugin.IFDRational(0, 0) info[41988] = TiffImagePlugin.IFDRational(0, 0)
@ -307,7 +310,7 @@ def test_exif_div_zero(tmp_path):
assert 0 == reloaded.tag_v2[41988].denominator assert 0 == reloaded.tag_v2[41988].denominator
def test_ifd_unsigned_rational(tmp_path): def test_ifd_unsigned_rational(tmp_path: Path) -> None:
im = hopper() im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()
@ -338,7 +341,7 @@ def test_ifd_unsigned_rational(tmp_path):
assert 1 == reloaded.tag_v2[41493].denominator assert 1 == reloaded.tag_v2[41493].denominator
def test_ifd_signed_rational(tmp_path): def test_ifd_signed_rational(tmp_path: Path) -> None:
im = hopper() im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()
@ -381,7 +384,7 @@ def test_ifd_signed_rational(tmp_path):
assert -1 == reloaded.tag_v2[37380].denominator assert -1 == reloaded.tag_v2[37380].denominator
def test_ifd_signed_long(tmp_path): def test_ifd_signed_long(tmp_path: Path) -> None:
im = hopper() im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2() info = TiffImagePlugin.ImageFileDirectory_v2()
@ -394,7 +397,7 @@ def test_ifd_signed_long(tmp_path):
assert reloaded.tag_v2[37000] == -60000 assert reloaded.tag_v2[37000] == -60000
def test_empty_values(): def test_empty_values() -> None:
data = io.BytesIO( data = io.BytesIO(
b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00" b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00"
b"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00" b"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00"
@ -409,7 +412,7 @@ def test_empty_values():
assert 33432 in info assert 33432 in info
def test_photoshop_info(tmp_path): def test_photoshop_info(tmp_path: Path) -> None:
with Image.open("Tests/images/issue_2278.tif") as im: with Image.open("Tests/images/issue_2278.tif") as im:
assert len(im.tag_v2[34377]) == 70 assert len(im.tag_v2[34377]) == 70
assert isinstance(im.tag_v2[34377], bytes) assert isinstance(im.tag_v2[34377], bytes)
@ -420,7 +423,7 @@ def test_photoshop_info(tmp_path):
assert isinstance(reloaded.tag_v2[34377], bytes) assert isinstance(reloaded.tag_v2[34377], bytes)
def test_too_many_entries(): def test_too_many_entries() -> None:
ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd = TiffImagePlugin.ImageFileDirectory_v2()
# 277: ("SamplesPerPixel", SHORT, 1), # 277: ("SamplesPerPixel", SHORT, 1),
@ -432,7 +435,7 @@ def test_too_many_entries():
assert ifd[277] == 4 assert ifd[277] == 4
def test_tag_group_data(): def test_tag_group_data() -> None:
base_ifd = TiffImagePlugin.ImageFileDirectory_v2() base_ifd = TiffImagePlugin.ImageFileDirectory_v2()
interop_ifd = TiffImagePlugin.ImageFileDirectory_v2(group=40965) interop_ifd = TiffImagePlugin.ImageFileDirectory_v2(group=40965)
for ifd in (base_ifd, interop_ifd): for ifd in (base_ifd, interop_ifd):
@ -446,7 +449,7 @@ def test_tag_group_data():
assert base_ifd.tagtype[2] != interop_ifd.tagtype[256] assert base_ifd.tagtype[2] != interop_ifd.tagtype[256]
def test_empty_subifd(tmp_path): def test_empty_subifd(tmp_path: Path) -> None:
out = str(tmp_path / "temp.jpg") out = str(tmp_path / "temp.jpg")
im = hopper() im = hopper()

View File

@ -7,7 +7,7 @@ from .helper import assert_image_equal_tofile
TEST_FILE = "Tests/images/hopper.wal" TEST_FILE = "Tests/images/hopper.wal"
def test_open(): def test_open() -> None:
with WalImageFile.open(TEST_FILE) as im: with WalImageFile.open(TEST_FILE) as im:
assert im.format == "WAL" assert im.format == "WAL"
assert im.format_description == "Quake2 Texture" assert im.format_description == "Quake2 Texture"
@ -19,7 +19,7 @@ def test_open():
assert_image_equal_tofile(im, "Tests/images/hopper_wal.png") assert_image_equal_tofile(im, "Tests/images/hopper_wal.png")
def test_load(): def test_load() -> None:
with WalImageFile.open(TEST_FILE) as im: with WalImageFile.open(TEST_FILE) as im:
assert im.load()[0, 0] == 122 assert im.load()[0, 0] == 122

View File

@ -4,6 +4,7 @@ import io
import re import re
import sys import sys
import warnings import warnings
from pathlib import Path
import pytest import pytest
@ -26,7 +27,7 @@ except ImportError:
class TestUnsupportedWebp: class TestUnsupportedWebp:
def test_unsupported(self): def test_unsupported(self) -> None:
if HAVE_WEBP: if HAVE_WEBP:
WebPImagePlugin.SUPPORTED = False WebPImagePlugin.SUPPORTED = False
@ -42,15 +43,15 @@ class TestUnsupportedWebp:
@skip_unless_feature("webp") @skip_unless_feature("webp")
class TestFileWebp: class TestFileWebp:
def setup_method(self): def setup_method(self) -> None:
self.rgb_mode = "RGB" self.rgb_mode = "RGB"
def test_version(self): def test_version(self) -> None:
_webp.WebPDecoderVersion() _webp.WebPDecoderVersion()
_webp.WebPDecoderBuggyAlpha() _webp.WebPDecoderBuggyAlpha()
assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp")) assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp"))
def test_read_rgb(self): def test_read_rgb(self) -> None:
""" """
Can we read a RGB mode WebP file without error? Can we read a RGB mode WebP file without error?
Does it have the bits we expect? Does it have the bits we expect?
@ -67,7 +68,7 @@ class TestFileWebp:
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0) assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0)
def _roundtrip(self, tmp_path, mode, epsilon, args={}): def _roundtrip(self, tmp_path: Path, mode, epsilon, args={}) -> None:
temp_file = str(tmp_path / "temp.webp") temp_file = str(tmp_path / "temp.webp")
hopper(mode).save(temp_file, **args) hopper(mode).save(temp_file, **args)
@ -93,7 +94,7 @@ class TestFileWebp:
target = target.convert(self.rgb_mode) target = target.convert(self.rgb_mode)
assert_image_similar(image, target, epsilon) assert_image_similar(image, target, epsilon)
def test_write_rgb(self, tmp_path): def test_write_rgb(self, tmp_path: Path) -> None:
""" """
Can we write a RGB mode file to webp without error? Can we write a RGB mode file to webp without error?
Does it have the bits we expect? Does it have the bits we expect?
@ -101,7 +102,7 @@ class TestFileWebp:
self._roundtrip(tmp_path, self.rgb_mode, 12.5) self._roundtrip(tmp_path, self.rgb_mode, 12.5)
def test_write_method(self, tmp_path): def test_write_method(self, tmp_path: Path) -> None:
self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6}) self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6})
buffer_no_args = io.BytesIO() buffer_no_args = io.BytesIO()
@ -112,7 +113,7 @@ class TestFileWebp:
assert buffer_no_args.getbuffer() != buffer_method.getbuffer() assert buffer_no_args.getbuffer() != buffer_method.getbuffer()
@skip_unless_feature("webp_anim") @skip_unless_feature("webp_anim")
def test_save_all(self, tmp_path): def test_save_all(self, tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.webp") temp_file = str(tmp_path / "temp.webp")
im = Image.new("RGB", (1, 1)) im = Image.new("RGB", (1, 1))
im2 = Image.new("RGB", (1, 1), "#f00") im2 = Image.new("RGB", (1, 1), "#f00")
@ -124,14 +125,14 @@ class TestFileWebp:
reloaded.seek(1) reloaded.seek(1)
assert_image_similar(im2, reloaded, 1) assert_image_similar(im2, reloaded, 1)
def test_icc_profile(self, tmp_path): def test_icc_profile(self, tmp_path: Path) -> None:
self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None}) self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None})
if _webp.HAVE_WEBPANIM: if _webp.HAVE_WEBPANIM:
self._roundtrip( self._roundtrip(
tmp_path, self.rgb_mode, 12.5, {"icc_profile": None, "save_all": True} tmp_path, self.rgb_mode, 12.5, {"icc_profile": None, "save_all": True}
) )
def test_write_unsupported_mode_L(self, tmp_path): def test_write_unsupported_mode_L(self, tmp_path: Path) -> None:
""" """
Saving a black-and-white file to WebP format should work, and be Saving a black-and-white file to WebP format should work, and be
similar to the original file. similar to the original file.
@ -139,7 +140,7 @@ class TestFileWebp:
self._roundtrip(tmp_path, "L", 10.0) self._roundtrip(tmp_path, "L", 10.0)
def test_write_unsupported_mode_P(self, tmp_path): def test_write_unsupported_mode_P(self, tmp_path: Path) -> None:
""" """
Saving a palette-based file to WebP format should work, and be Saving a palette-based file to WebP format should work, and be
similar to the original file. similar to the original file.
@ -148,14 +149,14 @@ class TestFileWebp:
self._roundtrip(tmp_path, "P", 50.0) self._roundtrip(tmp_path, "P", 50.0)
@pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system")
def test_write_encoding_error_message(self, tmp_path): def test_write_encoding_error_message(self, tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.webp") temp_file = str(tmp_path / "temp.webp")
im = Image.new("RGB", (15000, 15000)) im = Image.new("RGB", (15000, 15000))
with pytest.raises(ValueError) as e: with pytest.raises(ValueError) as e:
im.save(temp_file, method=0) im.save(temp_file, method=0)
assert str(e.value) == "encoding error 6" assert str(e.value) == "encoding error 6"
def test_WebPEncode_with_invalid_args(self): def test_WebPEncode_with_invalid_args(self) -> None:
""" """
Calling encoder functions with no arguments should result in an error. Calling encoder functions with no arguments should result in an error.
""" """
@ -166,7 +167,7 @@ class TestFileWebp:
with pytest.raises(TypeError): with pytest.raises(TypeError):
_webp.WebPEncode() _webp.WebPEncode()
def test_WebPDecode_with_invalid_args(self): def test_WebPDecode_with_invalid_args(self) -> None:
""" """
Calling decoder functions with no arguments should result in an error. Calling decoder functions with no arguments should result in an error.
""" """
@ -177,14 +178,14 @@ class TestFileWebp:
with pytest.raises(TypeError): with pytest.raises(TypeError):
_webp.WebPDecode() _webp.WebPDecode()
def test_no_resource_warning(self, tmp_path): def test_no_resource_warning(self, tmp_path: Path) -> None:
file_path = "Tests/images/hopper.webp" file_path = "Tests/images/hopper.webp"
with Image.open(file_path) as image: with Image.open(file_path) as image:
temp_file = str(tmp_path / "temp.webp") temp_file = str(tmp_path / "temp.webp")
with warnings.catch_warnings(): with warnings.catch_warnings():
image.save(temp_file) image.save(temp_file)
def test_file_pointer_could_be_reused(self): def test_file_pointer_could_be_reused(self) -> None:
file_path = "Tests/images/hopper.webp" file_path = "Tests/images/hopper.webp"
with open(file_path, "rb") as blob: with open(file_path, "rb") as blob:
Image.open(blob).load() Image.open(blob).load()
@ -195,14 +196,14 @@ class TestFileWebp:
(0, (0,), (-1, 0, 1, 2), (253, 254, 255, 256)), (0, (0,), (-1, 0, 1, 2), (253, 254, 255, 256)),
) )
@skip_unless_feature("webp_anim") @skip_unless_feature("webp_anim")
def test_invalid_background(self, background, tmp_path): def test_invalid_background(self, background, tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.webp") temp_file = str(tmp_path / "temp.webp")
im = hopper() im = hopper()
with pytest.raises(OSError): with pytest.raises(OSError):
im.save(temp_file, save_all=True, append_images=[im], background=background) im.save(temp_file, save_all=True, append_images=[im], background=background)
@skip_unless_feature("webp_anim") @skip_unless_feature("webp_anim")
def test_background_from_gif(self, tmp_path): def test_background_from_gif(self, tmp_path: Path) -> None:
# Save L mode GIF with background # Save L mode GIF with background
with Image.open("Tests/images/no_palette_with_background.gif") as im: with Image.open("Tests/images/no_palette_with_background.gif") as im:
out_webp = str(tmp_path / "temp.webp") out_webp = str(tmp_path / "temp.webp")
@ -227,7 +228,7 @@ class TestFileWebp:
assert difference < 5 assert difference < 5
@skip_unless_feature("webp_anim") @skip_unless_feature("webp_anim")
def test_duration(self, tmp_path): def test_duration(self, tmp_path: Path) -> None:
with Image.open("Tests/images/dispose_bgnd.gif") as im: with Image.open("Tests/images/dispose_bgnd.gif") as im:
assert im.info["duration"] == 1000 assert im.info["duration"] == 1000
@ -238,7 +239,7 @@ class TestFileWebp:
reloaded.load() reloaded.load()
assert reloaded.info["duration"] == 1000 assert reloaded.info["duration"] == 1000
def test_roundtrip_rgba_palette(self, tmp_path): def test_roundtrip_rgba_palette(self, tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.webp") temp_file = str(tmp_path / "temp.webp")
im = Image.new("RGBA", (1, 1)).convert("P") im = Image.new("RGBA", (1, 1)).convert("P")
assert im.mode == "P" assert im.mode == "P"

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from PIL import Image from PIL import Image
@ -14,12 +16,12 @@ from .helper import (
_webp = pytest.importorskip("PIL._webp", reason="WebP support not installed") _webp = pytest.importorskip("PIL._webp", reason="WebP support not installed")
def setup_module(): def setup_module() -> None:
if _webp.WebPDecoderBuggyAlpha(): if _webp.WebPDecoderBuggyAlpha():
pytest.skip("Buggy early version of WebP installed, not testing transparency") pytest.skip("Buggy early version of WebP installed, not testing transparency")
def test_read_rgba(): def test_read_rgba() -> None:
""" """
Can we read an RGBA mode file without error? Can we read an RGBA mode file without error?
Does it have the bits we expect? Does it have the bits we expect?
@ -39,7 +41,7 @@ def test_read_rgba():
assert_image_similar_tofile(image, "Tests/images/transparent.png", 20.0) assert_image_similar_tofile(image, "Tests/images/transparent.png", 20.0)
def test_write_lossless_rgb(tmp_path): def test_write_lossless_rgb(tmp_path: Path) -> None:
""" """
Can we write an RGBA mode file with lossless compression without error? Can we write an RGBA mode file with lossless compression without error?
Does it have the bits we expect? Does it have the bits we expect?
@ -68,7 +70,7 @@ def test_write_lossless_rgb(tmp_path):
assert_image_equal(image, pil_image) assert_image_equal(image, pil_image)
def test_write_rgba(tmp_path): def test_write_rgba(tmp_path: Path) -> None:
""" """
Can we write a RGBA mode file to WebP without error. Can we write a RGBA mode file to WebP without error.
Does it have the bits we expect? Does it have the bits we expect?
@ -99,7 +101,7 @@ def test_write_rgba(tmp_path):
assert_image_similar(image, pil_image, 1.0) assert_image_similar(image, pil_image, 1.0)
def test_keep_rgb_values_when_transparent(tmp_path): def test_keep_rgb_values_when_transparent(tmp_path: Path) -> None:
""" """
Saving transparent pixels should retain their original RGB values Saving transparent pixels should retain their original RGB values
when using the "exact" parameter. when using the "exact" parameter.
@ -128,7 +130,7 @@ def test_keep_rgb_values_when_transparent(tmp_path):
assert_image_equal(reloaded.convert("RGB"), image) assert_image_equal(reloaded.convert("RGB"), image)
def test_write_unsupported_mode_PA(tmp_path): def test_write_unsupported_mode_PA(tmp_path: Path) -> None:
""" """
Saving a palette-based file with transparency to WebP format Saving a palette-based file with transparency to WebP format
should work, and be similar to the original file. should work, and be similar to the original file.

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from packaging.version import parse as parse_version from packaging.version import parse as parse_version
@ -18,7 +20,7 @@ pytestmark = [
] ]
def test_n_frames(): def test_n_frames() -> None:
"""Ensure that WebP format sets n_frames and is_animated attributes correctly.""" """Ensure that WebP format sets n_frames and is_animated attributes correctly."""
with Image.open("Tests/images/hopper.webp") as im: with Image.open("Tests/images/hopper.webp") as im:
@ -30,7 +32,7 @@ def test_n_frames():
assert im.is_animated assert im.is_animated
def test_write_animation_L(tmp_path): def test_write_animation_L(tmp_path: Path) -> None:
""" """
Convert an animated GIF to animated WebP, then compare the frame count, and first Convert an animated GIF to animated WebP, then compare the frame count, and first
and last frames to ensure they're visually similar. and last frames to ensure they're visually similar.
@ -60,13 +62,13 @@ def test_write_animation_L(tmp_path):
assert_image_similar(im, orig.convert("RGBA"), 32.9) assert_image_similar(im, orig.convert("RGBA"), 32.9)
def test_write_animation_RGB(tmp_path): def test_write_animation_RGB(tmp_path: Path) -> None:
""" """
Write an animated WebP from RGB frames, and ensure the frames Write an animated WebP from RGB frames, and ensure the frames
are visually similar to the originals. are visually similar to the originals.
""" """
def check(temp_file): def check(temp_file) -> None:
with Image.open(temp_file) as im: with Image.open(temp_file) as im:
assert im.n_frames == 2 assert im.n_frames == 2
@ -105,7 +107,7 @@ def test_write_animation_RGB(tmp_path):
check(temp_file2) check(temp_file2)
def test_timestamp_and_duration(tmp_path): def test_timestamp_and_duration(tmp_path: Path) -> None:
""" """
Try passing a list of durations, and make sure the encoded Try passing a list of durations, and make sure the encoded
timestamps and durations are correct. timestamps and durations are correct.
@ -136,7 +138,7 @@ def test_timestamp_and_duration(tmp_path):
ts += durations[frame] ts += durations[frame]
def test_float_duration(tmp_path): def test_float_duration(tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.webp") temp_file = str(tmp_path / "temp.webp")
with Image.open("Tests/images/iss634.apng") as im: with Image.open("Tests/images/iss634.apng") as im:
assert im.info["duration"] == 70.0 assert im.info["duration"] == 70.0
@ -148,7 +150,7 @@ def test_float_duration(tmp_path):
assert reloaded.info["duration"] == 70 assert reloaded.info["duration"] == 70
def test_seeking(tmp_path): def test_seeking(tmp_path: Path) -> None:
""" """
Create an animated WebP file, and then try seeking through frames in reverse-order, Create an animated WebP file, and then try seeking through frames in reverse-order,
verifying the timestamps and durations are correct. verifying the timestamps and durations are correct.
@ -179,7 +181,7 @@ def test_seeking(tmp_path):
ts -= dur ts -= dur
def test_seek_errors(): def test_seek_errors() -> None:
with Image.open("Tests/images/iss634.webp") as im: with Image.open("Tests/images/iss634.webp") as im:
with pytest.raises(EOFError): with pytest.raises(EOFError):
im.seek(-1) im.seek(-1)

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from PIL import Image from PIL import Image
@ -10,7 +12,7 @@ _webp = pytest.importorskip("PIL._webp", reason="WebP support not installed")
RGB_MODE = "RGB" RGB_MODE = "RGB"
def test_write_lossless_rgb(tmp_path): def test_write_lossless_rgb(tmp_path: Path) -> None:
if _webp.WebPDecoderVersion() < 0x0200: if _webp.WebPDecoderVersion() < 0x0200:
pytest.skip("lossless not included") pytest.skip("lossless not included")

View File

@ -1,6 +1,8 @@
from __future__ import annotations from __future__ import annotations
from io import BytesIO from io import BytesIO
from pathlib import Path
from types import ModuleType
import pytest import pytest
@ -13,13 +15,14 @@ pytestmark = [
skip_unless_feature("webp_mux"), skip_unless_feature("webp_mux"),
] ]
ElementTree: ModuleType | None
try: try:
from defusedxml import ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:
ElementTree = None ElementTree = None
def test_read_exif_metadata(): def test_read_exif_metadata() -> None:
file_path = "Tests/images/flower.webp" file_path = "Tests/images/flower.webp"
with Image.open(file_path) as image: with Image.open(file_path) as image:
assert image.format == "WEBP" assert image.format == "WEBP"
@ -37,7 +40,7 @@ def test_read_exif_metadata():
assert exif_data == expected_exif assert exif_data == expected_exif
def test_read_exif_metadata_without_prefix(): def test_read_exif_metadata_without_prefix() -> None:
with Image.open("Tests/images/flower2.webp") as im: with Image.open("Tests/images/flower2.webp") as im:
# Assert prefix is not present # Assert prefix is not present
assert im.info["exif"][:6] != b"Exif\x00\x00" assert im.info["exif"][:6] != b"Exif\x00\x00"
@ -49,7 +52,7 @@ def test_read_exif_metadata_without_prefix():
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
def test_write_exif_metadata(): def test_write_exif_metadata() -> None:
file_path = "Tests/images/flower.jpg" file_path = "Tests/images/flower.jpg"
test_buffer = BytesIO() test_buffer = BytesIO()
with Image.open(file_path) as image: with Image.open(file_path) as image:
@ -63,7 +66,7 @@ def test_write_exif_metadata():
assert webp_exif == expected_exif[6:], "WebP EXIF didn't match" assert webp_exif == expected_exif[6:], "WebP EXIF didn't match"
def test_read_icc_profile(): def test_read_icc_profile() -> None:
file_path = "Tests/images/flower2.webp" file_path = "Tests/images/flower2.webp"
with Image.open(file_path) as image: with Image.open(file_path) as image:
assert image.format == "WEBP" assert image.format == "WEBP"
@ -80,7 +83,7 @@ def test_read_icc_profile():
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
def test_write_icc_metadata(): def test_write_icc_metadata() -> None:
file_path = "Tests/images/flower2.jpg" file_path = "Tests/images/flower2.jpg"
test_buffer = BytesIO() test_buffer = BytesIO()
with Image.open(file_path) as image: with Image.open(file_path) as image:
@ -100,7 +103,7 @@ def test_write_icc_metadata():
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
def test_read_no_exif(): def test_read_no_exif() -> None:
file_path = "Tests/images/flower.jpg" file_path = "Tests/images/flower.jpg"
test_buffer = BytesIO() test_buffer = BytesIO()
with Image.open(file_path) as image: with Image.open(file_path) as image:
@ -113,7 +116,7 @@ def test_read_no_exif():
assert not webp_image._getexif() assert not webp_image._getexif()
def test_getxmp(): def test_getxmp() -> None:
with Image.open("Tests/images/flower.webp") as im: with Image.open("Tests/images/flower.webp") as im:
assert "xmp" not in im.info assert "xmp" not in im.info
assert im.getxmp() == {} assert im.getxmp() == {}
@ -133,7 +136,7 @@ def test_getxmp():
@skip_unless_feature("webp_anim") @skip_unless_feature("webp_anim")
def test_write_animated_metadata(tmp_path): def test_write_animated_metadata(tmp_path: Path) -> None:
iccp_data = b"<iccp_data>" iccp_data = b"<iccp_data>"
exif_data = b"<exif_data>" exif_data = b"<exif_data>"
xmp_data = b"<xmp_data>" xmp_data = b"<xmp_data>"

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from PIL import Image, WmfImagePlugin from PIL import Image, WmfImagePlugin
@ -7,7 +9,7 @@ from PIL import Image, WmfImagePlugin
from .helper import assert_image_similar_tofile, hopper from .helper import assert_image_similar_tofile, hopper
def test_load_raw(): def test_load_raw() -> None:
# Test basic EMF open and rendering # Test basic EMF open and rendering
with Image.open("Tests/images/drawing.emf") as im: with Image.open("Tests/images/drawing.emf") as im:
if hasattr(Image.core, "drawwmf"): if hasattr(Image.core, "drawwmf"):
@ -25,17 +27,17 @@ def test_load_raw():
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref.png", 2.0) assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref.png", 2.0)
def test_load(): def test_load() -> None:
with Image.open("Tests/images/drawing.emf") as im: with Image.open("Tests/images/drawing.emf") as im:
if hasattr(Image.core, "drawwmf"): if hasattr(Image.core, "drawwmf"):
assert im.load()[0, 0] == (255, 255, 255) assert im.load()[0, 0] == (255, 255, 255)
def test_register_handler(tmp_path): def test_register_handler(tmp_path: Path) -> None:
class TestHandler: class TestHandler:
methodCalled = False methodCalled = False
def save(self, im, fp, filename): def save(self, im, fp, filename) -> None:
self.methodCalled = True self.methodCalled = True
handler = TestHandler() handler = TestHandler()
@ -51,12 +53,12 @@ def test_register_handler(tmp_path):
WmfImagePlugin.register_handler(original_handler) WmfImagePlugin.register_handler(original_handler)
def test_load_float_dpi(): def test_load_float_dpi() -> None:
with Image.open("Tests/images/drawing.emf") as im: with Image.open("Tests/images/drawing.emf") as im:
assert im.info["dpi"] == 1423.7668161434979 assert im.info["dpi"] == 1423.7668161434979
def test_load_set_dpi(): def test_load_set_dpi() -> None:
with Image.open("Tests/images/drawing.wmf") as im: with Image.open("Tests/images/drawing.wmf") as im:
assert im.size == (82, 82) assert im.size == (82, 82)
@ -68,7 +70,7 @@ def test_load_set_dpi():
@pytest.mark.parametrize("ext", (".wmf", ".emf")) @pytest.mark.parametrize("ext", (".wmf", ".emf"))
def test_save(ext, tmp_path): def test_save(ext, tmp_path: Path) -> None:
im = hopper() im = hopper()
tmpfile = str(tmp_path / ("temp" + ext)) tmpfile = str(tmp_path / ("temp" + ext))

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
from io import BytesIO from io import BytesIO
from pathlib import Path
import pytest import pytest
@ -32,14 +33,14 @@ static char basic_bits[] = {
""" """
def test_pil151(): def test_pil151() -> None:
with Image.open(BytesIO(PIL151)) as im: with Image.open(BytesIO(PIL151)) as im:
im.load() im.load()
assert im.mode == "1" assert im.mode == "1"
assert im.size == (32, 32) assert im.size == (32, 32)
def test_open(): def test_open() -> None:
# Arrange # Arrange
# Created with `convert hopper.png hopper.xbm` # Created with `convert hopper.png hopper.xbm`
filename = "Tests/images/hopper.xbm" filename = "Tests/images/hopper.xbm"
@ -51,7 +52,7 @@ def test_open():
assert im.size == (128, 128) assert im.size == (128, 128)
def test_open_filename_with_underscore(): def test_open_filename_with_underscore() -> None:
# Arrange # Arrange
# Created with `convert hopper.png hopper_underscore.xbm` # Created with `convert hopper.png hopper_underscore.xbm`
filename = "Tests/images/hopper_underscore.xbm" filename = "Tests/images/hopper_underscore.xbm"
@ -63,14 +64,14 @@ def test_open_filename_with_underscore():
assert im.size == (128, 128) assert im.size == (128, 128)
def test_invalid_file(): def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
XbmImagePlugin.XbmImageFile(invalid_file) XbmImagePlugin.XbmImageFile(invalid_file)
def test_save_wrong_mode(tmp_path): def test_save_wrong_mode(tmp_path: Path) -> None:
im = hopper() im = hopper()
out = str(tmp_path / "temp.xbm") out = str(tmp_path / "temp.xbm")
@ -78,7 +79,7 @@ def test_save_wrong_mode(tmp_path):
im.save(out) im.save(out)
def test_hotspot(tmp_path): def test_hotspot(tmp_path: Path) -> None:
im = hopper("1") im = hopper("1")
out = str(tmp_path / "temp.xbm") out = str(tmp_path / "temp.xbm")

View File

@ -9,7 +9,7 @@ from .helper import assert_image_similar, hopper
TEST_FILE = "Tests/images/hopper.xpm" TEST_FILE = "Tests/images/hopper.xpm"
def test_sanity(): def test_sanity() -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
im.load() im.load()
assert im.mode == "P" assert im.mode == "P"
@ -20,14 +20,14 @@ def test_sanity():
assert_image_similar(im.convert("RGB"), hopper("RGB"), 60) assert_image_similar(im.convert("RGB"), hopper("RGB"), 60)
def test_invalid_file(): def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
XpmImagePlugin.XpmImageFile(invalid_file) XpmImagePlugin.XpmImageFile(invalid_file)
def test_load_read(): def test_load_read() -> None:
# Arrange # Arrange
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
dummy_bytes = 1 dummy_bytes = 1

View File

@ -9,7 +9,7 @@ from .helper import assert_image_similar, hopper
TEST_FILE = "Tests/images/hopper.p7" TEST_FILE = "Tests/images/hopper.p7"
def test_open(): def test_open() -> None:
# Act # Act
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
# Assert # Assert
@ -20,7 +20,7 @@ def test_open():
assert_image_similar(im, im_hopper, 9) assert_image_similar(im, im_hopper, 9)
def test_unexpected_eof(): def test_unexpected_eof() -> None:
# Test unexpected EOF reading XV thumbnail file # Test unexpected EOF reading XV thumbnail file
# Arrange # Arrange
bad_file = "Tests/images/hopper_bad.p7" bad_file = "Tests/images/hopper_bad.p7"
@ -30,7 +30,7 @@ def test_unexpected_eof():
XVThumbImagePlugin.XVThumbImageFile(bad_file) XVThumbImagePlugin.XVThumbImageFile(bad_file)
def test_invalid_file(): def test_invalid_file() -> None:
# Arrange # Arrange
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"

View File

@ -1,11 +1,13 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from PIL import FontFile from PIL import FontFile
def test_save(tmp_path): def test_save(tmp_path: Path) -> None:
tempname = str(tmp_path / "temp.pil") tempname = str(tmp_path / "temp.pil")
font = FontFile.FontFile() font = FontFile.FontFile()

View File

@ -2,30 +2,27 @@ from __future__ import annotations
import colorsys import colorsys
import itertools import itertools
from typing import Callable
from PIL import Image from PIL import Image
from .helper import assert_image_similar, hopper from .helper import assert_image_similar, hopper
def int_to_float(i): def int_to_float(i: int) -> float:
return i / 255 return i / 255
def str_to_float(i): def tuple_to_ints(tp: tuple[float, float, float]) -> tuple[int, int, int]:
return ord(i) / 255
def tuple_to_ints(tp):
x, y, z = tp x, y, z = tp
return int(x * 255.0), int(y * 255.0), int(z * 255.0) return int(x * 255.0), int(y * 255.0), int(z * 255.0)
def test_sanity(): def test_sanity() -> None:
Image.new("HSV", (100, 100)) Image.new("HSV", (100, 100))
def wedge(): def wedge() -> Image.Image:
w = Image._wedge() w = Image._wedge()
w90 = w.rotate(90) w90 = w.rotate(90)
@ -49,7 +46,11 @@ def wedge():
return img return img
def to_xxx_colorsys(im, func, mode): def to_xxx_colorsys(
im: Image.Image,
func: Callable[[float, float, float], tuple[float, float, float]],
mode: str,
) -> Image.Image:
# convert the hard way using the library colorsys routines. # convert the hard way using the library colorsys routines.
(r, g, b) = im.split() (r, g, b) = im.split()
@ -70,15 +71,15 @@ def to_xxx_colorsys(im, func, mode):
return hsv return hsv
def to_hsv_colorsys(im): def to_hsv_colorsys(im: Image.Image) -> Image.Image:
return to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV") return to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV")
def to_rgb_colorsys(im): def to_rgb_colorsys(im: Image.Image) -> Image.Image:
return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB") return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB")
def test_wedge(): def test_wedge() -> None:
src = wedge().resize((3 * 32, 32), Image.Resampling.BILINEAR) src = wedge().resize((3 * 32, 32), Image.Resampling.BILINEAR)
im = src.convert("HSV") im = src.convert("HSV")
comparable = to_hsv_colorsys(src) comparable = to_hsv_colorsys(src)
@ -110,7 +111,7 @@ def test_wedge():
) )
def test_convert(): def test_convert() -> None:
im = hopper("RGB").convert("HSV") im = hopper("RGB").convert("HSV")
comparable = to_hsv_colorsys(hopper("RGB")) comparable = to_hsv_colorsys(hopper("RGB"))
@ -128,7 +129,7 @@ def test_convert():
) )
def test_hsv_to_rgb(): def test_hsv_to_rgb() -> None:
comparable = to_hsv_colorsys(hopper("RGB")) comparable = to_hsv_colorsys(hopper("RGB"))
converted = comparable.convert("RGB") converted = comparable.convert("RGB")
comparable = to_rgb_colorsys(comparable) comparable = to_rgb_colorsys(comparable)

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from PIL import Image from PIL import Image
def test_white(): def test_white() -> None:
with Image.open("Tests/images/lab.tif") as i: with Image.open("Tests/images/lab.tif") as i:
i.load() i.load()
@ -24,7 +24,7 @@ def test_white():
assert list(b) == [128] * 100 assert list(b) == [128] * 100
def test_green(): def test_green() -> None:
# l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS # l= 50 (/100), a = -100 (-128 .. 128) b=0 in PS
# == RGB: 0, 152, 117 # == RGB: 0, 152, 117
with Image.open("Tests/images/lab-green.tif") as i: with Image.open("Tests/images/lab-green.tif") as i:
@ -32,7 +32,7 @@ def test_green():
assert k == (128, 28, 128) assert k == (128, 28, 128)
def test_red(): def test_red() -> None:
# l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS # l= 50 (/100), a = 100 (-128 .. 128) b=0 in PS
# == RGB: 255, 0, 124 # == RGB: 255, 0, 124
with Image.open("Tests/images/lab-red.tif") as i: with Image.open("Tests/images/lab-red.tif") as i:

View File

@ -7,6 +7,8 @@ import shutil
import sys import sys
import tempfile import tempfile
import warnings import warnings
from pathlib import Path
from typing import IO
import pytest import pytest
@ -60,19 +62,19 @@ class TestImage:
"HSV", "HSV",
), ),
) )
def test_image_modes_success(self, mode): def test_image_modes_success(self, mode: str) -> None:
Image.new(mode, (1, 1)) Image.new(mode, (1, 1))
@pytest.mark.parametrize("mode", ("", "bad", "very very long")) @pytest.mark.parametrize("mode", ("", "bad", "very very long"))
def test_image_modes_fail(self, mode): def test_image_modes_fail(self, mode: str) -> None:
with pytest.raises(ValueError) as e: with pytest.raises(ValueError) as e:
Image.new(mode, (1, 1)) Image.new(mode, (1, 1))
assert str(e.value) == "unrecognized image mode" assert str(e.value) == "unrecognized image mode"
def test_exception_inheritance(self): def test_exception_inheritance(self) -> None:
assert issubclass(UnidentifiedImageError, OSError) assert issubclass(UnidentifiedImageError, OSError)
def test_sanity(self): def test_sanity(self) -> None:
im = Image.new("L", (100, 100)) im = Image.new("L", (100, 100))
assert repr(im)[:45] == "<PIL.Image.Image image mode=L size=100x100 at" assert repr(im)[:45] == "<PIL.Image.Image image mode=L size=100x100 at"
assert im.mode == "L" assert im.mode == "L"
@ -97,9 +99,9 @@ class TestImage:
# with pytest.raises(MemoryError): # with pytest.raises(MemoryError):
# Image.new("L", (1000000, 1000000)) # Image.new("L", (1000000, 1000000))
def test_repr_pretty(self): def test_repr_pretty(self) -> None:
class Pretty: class Pretty:
def text(self, text): def text(self, text: str) -> None:
self.pretty_output = text self.pretty_output = text
im = Image.new("L", (100, 100)) im = Image.new("L", (100, 100))
@ -108,7 +110,7 @@ class TestImage:
im._repr_pretty_(p, None) im._repr_pretty_(p, None)
assert p.pretty_output == "<PIL.Image.Image image mode=L size=100x100>" assert p.pretty_output == "<PIL.Image.Image image mode=L size=100x100>"
def test_open_formats(self): def test_open_formats(self) -> None:
PNGFILE = "Tests/images/hopper.png" PNGFILE = "Tests/images/hopper.png"
JPGFILE = "Tests/images/hopper.jpg" JPGFILE = "Tests/images/hopper.jpg"
@ -130,7 +132,7 @@ class TestImage:
assert im.mode == "RGB" assert im.mode == "RGB"
assert im.size == (128, 128) assert im.size == (128, 128)
def test_width_height(self): def test_width_height(self) -> None:
im = Image.new("RGB", (1, 2)) im = Image.new("RGB", (1, 2))
assert im.width == 1 assert im.width == 1
assert im.height == 2 assert im.height == 2
@ -138,31 +140,29 @@ class TestImage:
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
im.size = (3, 4) im.size = (3, 4)
def test_set_mode(self): def test_set_mode(self) -> None:
im = Image.new("RGB", (1, 1)) im = Image.new("RGB", (1, 1))
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
im.mode = "P" im.mode = "P"
def test_invalid_image(self): def test_invalid_image(self) -> None:
im = io.BytesIO(b"") im = io.BytesIO(b"")
with pytest.raises(UnidentifiedImageError): with pytest.raises(UnidentifiedImageError):
with Image.open(im): with Image.open(im):
pass pass
def test_bad_mode(self): def test_bad_mode(self) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
with Image.open("filename", "bad mode"): with Image.open("filename", "bad mode"):
pass pass
def test_stringio(self): def test_stringio(self) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
with Image.open(io.StringIO()): with Image.open(io.StringIO()):
pass pass
def test_pathlib(self, tmp_path): def test_pathlib(self, tmp_path: Path) -> None:
from PIL.Image import Path
with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im: with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im:
assert im.mode == "P" assert im.mode == "P"
assert im.size == (10, 10) assert im.size == (10, 10)
@ -179,11 +179,13 @@ class TestImage:
os.remove(temp_file) os.remove(temp_file)
im.save(Path(temp_file)) im.save(Path(temp_file))
def test_fp_name(self, tmp_path): def test_fp_name(self, tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.jpg") temp_file = str(tmp_path / "temp.jpg")
class FP: class FP:
def write(self, b): name: str
def write(self, b: bytes) -> None:
pass pass
fp = FP() fp = FP()
@ -192,7 +194,7 @@ class TestImage:
im = hopper() im = hopper()
im.save(fp) im.save(fp)
def test_tempfile(self): def test_tempfile(self) -> None:
# see #1460, pathlib support breaks tempfile.TemporaryFile on py27 # see #1460, pathlib support breaks tempfile.TemporaryFile on py27
# Will error out on save on 3.0.0 # Will error out on save on 3.0.0
im = hopper() im = hopper()
@ -201,13 +203,13 @@ class TestImage:
fp.seek(0) fp.seek(0)
assert_image_similar_tofile(im, fp, 20) assert_image_similar_tofile(im, fp, 20)
def test_unknown_extension(self, tmp_path): def test_unknown_extension(self, tmp_path: Path) -> None:
im = hopper() im = hopper()
temp_file = str(tmp_path / "temp.unknown") temp_file = str(tmp_path / "temp.unknown")
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.save(temp_file) im.save(temp_file)
def test_internals(self): def test_internals(self) -> None:
im = Image.new("L", (100, 100)) im = Image.new("L", (100, 100))
im.readonly = 1 im.readonly = 1
im._copy() im._copy()
@ -222,7 +224,7 @@ class TestImage:
sys.platform == "cygwin", sys.platform == "cygwin",
reason="Test requires opening an mmaped file for writing", reason="Test requires opening an mmaped file for writing",
) )
def test_readonly_save(self, tmp_path): def test_readonly_save(self, tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.bmp") temp_file = str(tmp_path / "temp.bmp")
shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file) shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file)
@ -230,7 +232,7 @@ class TestImage:
assert im.readonly assert im.readonly
im.save(temp_file) im.save(temp_file)
def test_dump(self, tmp_path): def test_dump(self, tmp_path: Path) -> None:
im = Image.new("L", (10, 10)) im = Image.new("L", (10, 10))
im._dump(str(tmp_path / "temp_L.ppm")) im._dump(str(tmp_path / "temp_L.ppm"))
@ -241,7 +243,7 @@ class TestImage:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im._dump(str(tmp_path / "temp_HSV.ppm")) im._dump(str(tmp_path / "temp_HSV.ppm"))
def test_comparison_with_other_type(self): def test_comparison_with_other_type(self) -> None:
# Arrange # Arrange
item = Image.new("RGB", (25, 25), "#000") item = Image.new("RGB", (25, 25), "#000")
num = 12 num = 12
@ -251,7 +253,7 @@ class TestImage:
assert item is not None assert item is not None
assert item != num assert item != num
def test_expand_x(self): def test_expand_x(self) -> None:
# Arrange # Arrange
im = hopper() im = hopper()
orig_size = im.size orig_size = im.size
@ -264,7 +266,7 @@ class TestImage:
assert im.size[0] == orig_size[0] + 2 * xmargin assert im.size[0] == orig_size[0] + 2 * xmargin
assert im.size[1] == orig_size[1] + 2 * xmargin assert im.size[1] == orig_size[1] + 2 * xmargin
def test_expand_xy(self): def test_expand_xy(self) -> None:
# Arrange # Arrange
im = hopper() im = hopper()
orig_size = im.size orig_size = im.size
@ -278,12 +280,12 @@ class TestImage:
assert im.size[0] == orig_size[0] + 2 * xmargin assert im.size[0] == orig_size[0] + 2 * xmargin
assert im.size[1] == orig_size[1] + 2 * ymargin assert im.size[1] == orig_size[1] + 2 * ymargin
def test_getbands(self): def test_getbands(self) -> None:
# Assert # Assert
assert hopper("RGB").getbands() == ("R", "G", "B") assert hopper("RGB").getbands() == ("R", "G", "B")
assert hopper("YCbCr").getbands() == ("Y", "Cb", "Cr") assert hopper("YCbCr").getbands() == ("Y", "Cb", "Cr")
def test_getchannel_wrong_params(self): def test_getchannel_wrong_params(self) -> None:
im = hopper() im = hopper()
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -295,7 +297,7 @@ class TestImage:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.getchannel("1") im.getchannel("1")
def test_getchannel(self): def test_getchannel(self) -> None:
im = hopper("YCbCr") im = hopper("YCbCr")
Y, Cb, Cr = im.split() Y, Cb, Cr = im.split()
@ -306,7 +308,7 @@ class TestImage:
assert_image_equal(Cr, im.getchannel(2)) assert_image_equal(Cr, im.getchannel(2))
assert_image_equal(Cr, im.getchannel("Cr")) assert_image_equal(Cr, im.getchannel("Cr"))
def test_getbbox(self): def test_getbbox(self) -> None:
# Arrange # Arrange
im = hopper() im = hopper()
@ -316,7 +318,7 @@ class TestImage:
# Assert # Assert
assert bbox == (0, 0, 128, 128) assert bbox == (0, 0, 128, 128)
def test_ne(self): def test_ne(self) -> None:
# Arrange # Arrange
im1 = Image.new("RGB", (25, 25), "black") im1 = Image.new("RGB", (25, 25), "black")
im2 = Image.new("RGB", (25, 25), "white") im2 = Image.new("RGB", (25, 25), "white")
@ -324,7 +326,7 @@ class TestImage:
# Act / Assert # Act / Assert
assert im1 != im2 assert im1 != im2
def test_alpha_composite(self): def test_alpha_composite(self) -> None:
# https://stackoverflow.com/questions/3374878 # https://stackoverflow.com/questions/3374878
# Arrange # Arrange
expected_colors = sorted( expected_colors = sorted(
@ -355,7 +357,7 @@ class TestImage:
img_colors = sorted(img.getcolors()) img_colors = sorted(img.getcolors())
assert img_colors == expected_colors assert img_colors == expected_colors
def test_alpha_inplace(self): def test_alpha_inplace(self) -> None:
src = Image.new("RGBA", (128, 128), "blue") src = Image.new("RGBA", (128, 128), "blue")
over = Image.new("RGBA", (128, 128), "red") over = Image.new("RGBA", (128, 128), "red")
@ -407,7 +409,7 @@ class TestImage:
with pytest.raises(ValueError): with pytest.raises(ValueError):
source.alpha_composite(over, (0, 0), (0, -1)) source.alpha_composite(over, (0, 0), (0, -1))
def test_register_open_duplicates(self): def test_register_open_duplicates(self) -> None:
# Arrange # Arrange
factory, accept = Image.OPEN["JPEG"] factory, accept = Image.OPEN["JPEG"]
id_length = len(Image.ID) id_length = len(Image.ID)
@ -418,7 +420,7 @@ class TestImage:
# Assert # Assert
assert len(Image.ID) == id_length assert len(Image.ID) == id_length
def test_registered_extensions_uninitialized(self): def test_registered_extensions_uninitialized(self) -> None:
# Arrange # Arrange
Image._initialized = 0 Image._initialized = 0
@ -428,7 +430,7 @@ class TestImage:
# Assert # Assert
assert Image._initialized == 2 assert Image._initialized == 2
def test_registered_extensions(self): def test_registered_extensions(self) -> None:
# Arrange # Arrange
# Open an image to trigger plugin registration # Open an image to trigger plugin registration
with Image.open("Tests/images/rgb.jpg"): with Image.open("Tests/images/rgb.jpg"):
@ -442,7 +444,7 @@ class TestImage:
for ext in [".cur", ".icns", ".tif", ".tiff"]: for ext in [".cur", ".icns", ".tif", ".tiff"]:
assert ext in extensions assert ext in extensions
def test_effect_mandelbrot(self): def test_effect_mandelbrot(self) -> None:
# Arrange # Arrange
size = (512, 512) size = (512, 512)
extent = (-3, -2.5, 2, 2.5) extent = (-3, -2.5, 2, 2.5)
@ -455,7 +457,7 @@ class TestImage:
assert im.size == (512, 512) assert im.size == (512, 512)
assert_image_equal_tofile(im, "Tests/images/effect_mandelbrot.png") assert_image_equal_tofile(im, "Tests/images/effect_mandelbrot.png")
def test_effect_mandelbrot_bad_arguments(self): def test_effect_mandelbrot_bad_arguments(self) -> None:
# Arrange # Arrange
size = (512, 512) size = (512, 512)
# Get coordinates the wrong way round: # Get coordinates the wrong way round:
@ -467,7 +469,7 @@ class TestImage:
with pytest.raises(ValueError): with pytest.raises(ValueError):
Image.effect_mandelbrot(size, extent, quality) Image.effect_mandelbrot(size, extent, quality)
def test_effect_noise(self): def test_effect_noise(self) -> None:
# Arrange # Arrange
size = (100, 100) size = (100, 100)
sigma = 128 sigma = 128
@ -485,7 +487,7 @@ class TestImage:
p4 = im.getpixel((0, 4)) p4 = im.getpixel((0, 4))
assert_not_all_same([p0, p1, p2, p3, p4]) assert_not_all_same([p0, p1, p2, p3, p4])
def test_effect_spread(self): def test_effect_spread(self) -> None:
# Arrange # Arrange
im = hopper() im = hopper()
distance = 10 distance = 10
@ -497,7 +499,7 @@ class TestImage:
assert im.size == (128, 128) assert im.size == (128, 128)
assert_image_similar_tofile(im2, "Tests/images/effect_spread.png", 110) assert_image_similar_tofile(im2, "Tests/images/effect_spread.png", 110)
def test_effect_spread_zero(self): def test_effect_spread_zero(self) -> None:
# Arrange # Arrange
im = hopper() im = hopper()
distance = 0 distance = 0
@ -508,7 +510,7 @@ class TestImage:
# Assert # Assert
assert_image_equal(im, im2) assert_image_equal(im, im2)
def test_check_size(self): def test_check_size(self) -> None:
# Checking that the _check_size function throws value errors when we want it to # Checking that the _check_size function throws value errors when we want it to
with pytest.raises(ValueError): with pytest.raises(ValueError):
Image.new("RGB", 0) # not a tuple Image.new("RGB", 0) # not a tuple
@ -537,10 +539,10 @@ class TestImage:
"PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower" "PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower"
) )
@pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0))) @pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
def test_empty_image(self, size): def test_empty_image(self, size: tuple[int, int]) -> None:
Image.new("RGB", size) Image.new("RGB", size)
def test_storage_neg(self): def test_storage_neg(self) -> None:
# Storage.c accepted negative values for xsize, ysize. Was # Storage.c accepted negative values for xsize, ysize. Was
# test_neg_ppm, but the core function for that has been # test_neg_ppm, but the core function for that has been
# removed Calling directly into core to test the error in # removed Calling directly into core to test the error in
@ -549,13 +551,13 @@ class TestImage:
with pytest.raises(ValueError): with pytest.raises(ValueError):
Image.core.fill("RGB", (2, -2), (0, 0, 0)) Image.core.fill("RGB", (2, -2), (0, 0, 0))
def test_one_item_tuple(self): def test_one_item_tuple(self) -> None:
for mode in ("I", "F", "L"): for mode in ("I", "F", "L"):
im = Image.new(mode, (100, 100), (5,)) im = Image.new(mode, (100, 100), (5,))
px = im.load() px = im.load()
assert px[0, 0] == 5 assert px[0, 0] == 5
def test_linear_gradient_wrong_mode(self): def test_linear_gradient_wrong_mode(self) -> None:
# Arrange # Arrange
wrong_mode = "RGB" wrong_mode = "RGB"
@ -564,7 +566,7 @@ class TestImage:
Image.linear_gradient(wrong_mode) Image.linear_gradient(wrong_mode)
@pytest.mark.parametrize("mode", ("L", "P", "I", "F")) @pytest.mark.parametrize("mode", ("L", "P", "I", "F"))
def test_linear_gradient(self, mode): def test_linear_gradient(self, mode: str) -> None:
# Arrange # Arrange
target_file = "Tests/images/linear_gradient.png" target_file = "Tests/images/linear_gradient.png"
@ -580,7 +582,7 @@ class TestImage:
target = target.convert(mode) target = target.convert(mode)
assert_image_equal(im, target) assert_image_equal(im, target)
def test_radial_gradient_wrong_mode(self): def test_radial_gradient_wrong_mode(self) -> None:
# Arrange # Arrange
wrong_mode = "RGB" wrong_mode = "RGB"
@ -589,7 +591,7 @@ class TestImage:
Image.radial_gradient(wrong_mode) Image.radial_gradient(wrong_mode)
@pytest.mark.parametrize("mode", ("L", "P", "I", "F")) @pytest.mark.parametrize("mode", ("L", "P", "I", "F"))
def test_radial_gradient(self, mode): def test_radial_gradient(self, mode: str) -> None:
# Arrange # Arrange
target_file = "Tests/images/radial_gradient.png" target_file = "Tests/images/radial_gradient.png"
@ -605,7 +607,7 @@ class TestImage:
target = target.convert(mode) target = target.convert(mode)
assert_image_equal(im, target) assert_image_equal(im, target)
def test_register_extensions(self): def test_register_extensions(self) -> None:
test_format = "a" test_format = "a"
exts = ["b", "c"] exts = ["b", "c"]
for ext in exts: for ext in exts:
@ -621,7 +623,7 @@ class TestImage:
assert ext_individual == ext_multiple assert ext_individual == ext_multiple
def test_remap_palette(self): def test_remap_palette(self) -> None:
# Test identity transform # Test identity transform
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
assert_image_equal(im, im.remap_palette(list(range(256)))) assert_image_equal(im, im.remap_palette(list(range(256))))
@ -640,7 +642,7 @@ class TestImage:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.remap_palette(None) im.remap_palette(None)
def test_remap_palette_transparency(self): def test_remap_palette_transparency(self) -> None:
im = Image.new("P", (1, 2), (0, 0, 0)) im = Image.new("P", (1, 2), (0, 0, 0))
im.putpixel((0, 1), (255, 0, 0)) im.putpixel((0, 1), (255, 0, 0))
im.info["transparency"] = 0 im.info["transparency"] = 0
@ -655,7 +657,7 @@ class TestImage:
im_remapped = im.remap_palette([1, 0]) im_remapped = im.remap_palette([1, 0])
assert "transparency" not in im_remapped.info assert "transparency" not in im_remapped.info
def test__new(self): def test__new(self) -> None:
im = hopper("RGB") im = hopper("RGB")
im_p = hopper("P") im_p = hopper("P")
@ -664,7 +666,11 @@ class TestImage:
blank_p.palette = None blank_p.palette = None
blank_pa.palette = None blank_pa.palette = None
def _make_new(base_image, image, palette_result=None): def _make_new(
base_image: Image.Image,
image: Image.Image,
palette_result: ImagePalette.ImagePalette | None = None,
) -> None:
new_image = base_image._new(image.im) new_image = base_image._new(image.im)
assert new_image.mode == image.mode assert new_image.mode == image.mode
assert new_image.size == image.size assert new_image.size == image.size
@ -679,17 +685,20 @@ class TestImage:
_make_new(im, blank_p, ImagePalette.ImagePalette()) _make_new(im, blank_p, ImagePalette.ImagePalette())
_make_new(im, blank_pa, ImagePalette.ImagePalette()) _make_new(im, blank_pa, ImagePalette.ImagePalette())
def test_p_from_rgb_rgba(self): @pytest.mark.parametrize(
for mode, color in [ "mode, color",
(
("RGB", "#DDEEFF"), ("RGB", "#DDEEFF"),
("RGB", (221, 238, 255)), ("RGB", (221, 238, 255)),
("RGBA", (221, 238, 255, 255)), ("RGBA", (221, 238, 255, 255)),
]: ),
)
def test_p_from_rgb_rgba(self, mode: str, color: str | tuple[int, ...]) -> None:
im = Image.new("P", (100, 100), color) im = Image.new("P", (100, 100), color)
expected = Image.new(mode, (100, 100), color) expected = Image.new(mode, (100, 100), color)
assert_image_equal(im.convert(mode), expected) assert_image_equal(im.convert(mode), expected)
def test_no_resource_warning_on_save(self, tmp_path): def test_no_resource_warning_on_save(self, tmp_path: Path) -> None:
# https://github.com/python-pillow/Pillow/issues/835 # https://github.com/python-pillow/Pillow/issues/835
# Arrange # Arrange
test_file = "Tests/images/hopper.png" test_file = "Tests/images/hopper.png"
@ -700,7 +709,7 @@ class TestImage:
with warnings.catch_warnings(): with warnings.catch_warnings():
im.save(temp_file) im.save(temp_file)
def test_no_new_file_on_error(self, tmp_path): def test_no_new_file_on_error(self, tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.jpg") temp_file = str(tmp_path / "temp.jpg")
im = Image.new("RGB", (0, 0)) im = Image.new("RGB", (0, 0))
@ -709,10 +718,10 @@ class TestImage:
assert not os.path.exists(temp_file) assert not os.path.exists(temp_file)
def test_load_on_nonexclusive_multiframe(self): def test_load_on_nonexclusive_multiframe(self) -> None:
with open("Tests/images/frozenpond.mpo", "rb") as fp: with open("Tests/images/frozenpond.mpo", "rb") as fp:
def act(fp): def act(fp: IO[bytes]) -> None:
im = Image.open(fp) im = Image.open(fp)
im.load() im.load()
@ -723,7 +732,7 @@ class TestImage:
assert not fp.closed assert not fp.closed
def test_empty_exif(self): def test_empty_exif(self) -> None:
with Image.open("Tests/images/exif.png") as im: with Image.open("Tests/images/exif.png") as im:
exif = im.getexif() exif = im.getexif()
assert dict(exif) assert dict(exif)
@ -739,7 +748,7 @@ class TestImage:
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
def test_exif_jpeg(self, tmp_path): def test_exif_jpeg(self, tmp_path: Path) -> None:
with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian
exif = im.getexif() exif = im.getexif()
assert 258 not in exif assert 258 not in exif
@ -785,7 +794,7 @@ class TestImage:
@skip_unless_feature("webp") @skip_unless_feature("webp")
@skip_unless_feature("webp_anim") @skip_unless_feature("webp_anim")
def test_exif_webp(self, tmp_path): def test_exif_webp(self, tmp_path: Path) -> None:
with Image.open("Tests/images/hopper.webp") as im: with Image.open("Tests/images/hopper.webp") as im:
exif = im.getexif() exif = im.getexif()
assert exif == {} assert exif == {}
@ -795,7 +804,7 @@ class TestImage:
exif[40963] = 455 exif[40963] = 455
exif[305] = "Pillow test" exif[305] = "Pillow test"
def check_exif(): def check_exif() -> None:
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
reloaded_exif = reloaded.getexif() reloaded_exif = reloaded.getexif()
assert reloaded_exif[258] == 8 assert reloaded_exif[258] == 8
@ -807,7 +816,7 @@ class TestImage:
im.save(out, exif=exif, save_all=True) im.save(out, exif=exif, save_all=True)
check_exif() check_exif()
def test_exif_png(self, tmp_path): def test_exif_png(self, tmp_path: Path) -> None:
with Image.open("Tests/images/exif.png") as im: with Image.open("Tests/images/exif.png") as im:
exif = im.getexif() exif = im.getexif()
assert exif == {274: 1} assert exif == {274: 1}
@ -823,7 +832,7 @@ class TestImage:
reloaded_exif = reloaded.getexif() reloaded_exif = reloaded.getexif()
assert reloaded_exif == {258: 8, 40963: 455, 305: "Pillow test"} assert reloaded_exif == {258: 8, 40963: 455, 305: "Pillow test"}
def test_exif_interop(self): def test_exif_interop(self) -> None:
with Image.open("Tests/images/flower.jpg") as im: with Image.open("Tests/images/flower.jpg") as im:
exif = im.getexif() exif = im.getexif()
assert exif.get_ifd(0xA005) == { assert exif.get_ifd(0xA005) == {
@ -837,7 +846,7 @@ class TestImage:
reloaded_exif.load(exif.tobytes()) reloaded_exif.load(exif.tobytes())
assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005) assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005)
def test_exif_ifd1(self): def test_exif_ifd1(self) -> None:
with Image.open("Tests/images/flower.jpg") as im: with Image.open("Tests/images/flower.jpg") as im:
exif = im.getexif() exif = im.getexif()
assert exif.get_ifd(ExifTags.IFD.IFD1) == { assert exif.get_ifd(ExifTags.IFD.IFD1) == {
@ -849,7 +858,7 @@ class TestImage:
283: 180.0, 283: 180.0,
} }
def test_exif_ifd(self): def test_exif_ifd(self) -> None:
with Image.open("Tests/images/flower.jpg") as im: with Image.open("Tests/images/flower.jpg") as im:
exif = im.getexif() exif = im.getexif()
del exif.get_ifd(0x8769)[0xA005] del exif.get_ifd(0x8769)[0xA005]
@ -858,7 +867,7 @@ class TestImage:
reloaded_exif.load(exif.tobytes()) reloaded_exif.load(exif.tobytes())
assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769) assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769)
def test_exif_load_from_fp(self): def test_exif_load_from_fp(self) -> None:
with Image.open("Tests/images/flower.jpg") as im: with Image.open("Tests/images/flower.jpg") as im:
data = im.info["exif"] data = im.info["exif"]
if data.startswith(b"Exif\x00\x00"): if data.startswith(b"Exif\x00\x00"):
@ -879,7 +888,7 @@ class TestImage:
34665: 196, 34665: 196,
} }
def test_exif_hide_offsets(self): def test_exif_hide_offsets(self) -> None:
with Image.open("Tests/images/flower.jpg") as im: with Image.open("Tests/images/flower.jpg") as im:
exif = im.getexif() exif = im.getexif()
@ -905,18 +914,18 @@ class TestImage:
assert exif.get_ifd(0xA005) assert exif.get_ifd(0xA005)
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
def test_zero_tobytes(self, size): def test_zero_tobytes(self, size: tuple[int, int]) -> None:
im = Image.new("RGB", size) im = Image.new("RGB", size)
assert im.tobytes() == b"" assert im.tobytes() == b""
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
def test_zero_frombytes(self, size): def test_zero_frombytes(self, size: tuple[int, int]) -> None:
Image.frombytes("RGB", size, b"") Image.frombytes("RGB", size, b"")
im = Image.new("RGB", size) im = Image.new("RGB", size)
im.frombytes(b"") im.frombytes(b"")
def test_has_transparency_data(self): def test_has_transparency_data(self) -> None:
for mode in ("1", "L", "P", "RGB"): for mode in ("1", "L", "P", "RGB"):
im = Image.new(mode, (1, 1)) im = Image.new(mode, (1, 1))
assert not im.has_transparency_data assert not im.has_transparency_data
@ -941,7 +950,7 @@ class TestImage:
assert im.palette.mode == "RGBA" assert im.palette.mode == "RGBA"
assert im.has_transparency_data assert im.has_transparency_data
def test_apply_transparency(self): def test_apply_transparency(self) -> None:
im = Image.new("P", (1, 1)) im = Image.new("P", (1, 1))
im.putpalette((0, 0, 0, 1, 1, 1)) im.putpalette((0, 0, 0, 1, 1, 1))
assert im.palette.colors == {(0, 0, 0): 0, (1, 1, 1): 1} assert im.palette.colors == {(0, 0, 0): 0, (1, 1, 1): 1}
@ -970,7 +979,7 @@ class TestImage:
im.apply_transparency() im.apply_transparency()
assert im.palette.colors[(27, 35, 6, 214)] == 24 assert im.palette.colors[(27, 35, 6, 214)] == 24
def test_constants(self): def test_constants(self) -> None:
for enum in ( for enum in (
Image.Transpose, Image.Transpose,
Image.Transform, Image.Transform,
@ -995,7 +1004,7 @@ class TestImage:
"01r_00.pcx", "01r_00.pcx",
], ],
) )
def test_overrun(self, path): def test_overrun(self, path: str) -> None:
"""For overrun completeness, test as: """For overrun completeness, test as:
valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c
""" """
@ -1009,7 +1018,7 @@ class TestImage:
assert buffer_overrun or truncated assert buffer_overrun or truncated
def test_fli_overrun2(self): def test_fli_overrun2(self) -> None:
with Image.open("Tests/images/fli_overrun2.bin") as im: with Image.open("Tests/images/fli_overrun2.bin") as im:
try: try:
im.seek(1) im.seek(1)
@ -1017,12 +1026,12 @@ class TestImage:
except OSError as e: except OSError as e:
assert str(e) == "buffer overrun when reading image file" assert str(e) == "buffer overrun when reading image file"
def test_exit_fp(self): def test_exit_fp(self) -> None:
with Image.new("L", (1, 1)) as im: with Image.new("L", (1, 1)) as im:
pass pass
assert not hasattr(im, "fp") assert not hasattr(im, "fp")
def test_close_graceful(self, caplog): def test_close_graceful(self, caplog: pytest.LogCaptureFixture) -> None:
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
copy = im.copy() copy = im.copy()
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@ -1033,17 +1042,17 @@ class TestImage:
class MockEncoder: class MockEncoder:
pass args: tuple[str, ...]
def mock_encode(*args): def mock_encode(*args: str) -> MockEncoder:
encoder = MockEncoder() encoder = MockEncoder()
encoder.args = args encoder.args = args
return encoder return encoder
class TestRegistry: class TestRegistry:
def test_encode_registry(self): def test_encode_registry(self) -> None:
Image.register_encoder("MOCK", mock_encode) Image.register_encoder("MOCK", mock_encode)
assert "MOCK" in Image.ENCODERS assert "MOCK" in Image.ENCODERS
@ -1052,6 +1061,6 @@ class TestRegistry:
assert isinstance(enc, MockEncoder) assert isinstance(enc, MockEncoder)
assert enc.args == ("RGB", "args", "extra") assert enc.args == ("RGB", "args", "extra")
def test_encode_registry_fail(self): def test_encode_registry_fail(self) -> None:
with pytest.raises(OSError): with pytest.raises(OSError):
Image._getencoder("RGB", "DoesNotExist", ("args",), extra=("extra",)) Image._getencoder("RGB", "DoesNotExist", ("args",), extra=("extra",))

View File

@ -4,6 +4,7 @@ import os
import subprocess import subprocess
import sys import sys
import sysconfig import sysconfig
from types import ModuleType
import pytest import pytest
@ -23,6 +24,7 @@ else:
except ImportError: except ImportError:
cffi = None cffi = None
numpy: ModuleType | None
try: try:
import numpy import numpy
except ImportError: except ImportError:
@ -35,16 +37,16 @@ class AccessTest:
_need_cffi_access = False _need_cffi_access = False
@classmethod @classmethod
def setup_class(cls): def setup_class(cls) -> None:
Image.USE_CFFI_ACCESS = cls._need_cffi_access Image.USE_CFFI_ACCESS = cls._need_cffi_access
@classmethod @classmethod
def teardown_class(cls): def teardown_class(cls) -> None:
Image.USE_CFFI_ACCESS = cls._init_cffi_access Image.USE_CFFI_ACCESS = cls._init_cffi_access
class TestImagePutPixel(AccessTest): class TestImagePutPixel(AccessTest):
def test_sanity(self): def test_sanity(self) -> None:
im1 = hopper() im1 = hopper()
im2 = Image.new(im1.mode, im1.size, 0) im2 = Image.new(im1.mode, im1.size, 0)
@ -71,9 +73,10 @@ class TestImagePutPixel(AccessTest):
pix1 = im1.load() pix1 = im1.load()
pix2 = im2.load() pix2 = im2.load()
for x, y in ((0, "0"), ("0", 0)):
with pytest.raises(TypeError): with pytest.raises(TypeError):
pix1[x, y] pix1[0, "0"]
with pytest.raises(TypeError):
pix1["0", 0]
for y in range(im1.size[1]): for y in range(im1.size[1]):
for x in range(im1.size[0]): for x in range(im1.size[0]):
@ -81,7 +84,7 @@ class TestImagePutPixel(AccessTest):
assert_image_equal(im1, im2) assert_image_equal(im1, im2)
def test_sanity_negative_index(self): def test_sanity_negative_index(self) -> None:
im1 = hopper() im1 = hopper()
im2 = Image.new(im1.mode, im1.size, 0) im2 = Image.new(im1.mode, im1.size, 0)
@ -119,16 +122,17 @@ class TestImagePutPixel(AccessTest):
assert_image_equal(im1, im2) assert_image_equal(im1, im2)
@pytest.mark.skipif(numpy is None, reason="NumPy not installed") @pytest.mark.skipif(numpy is None, reason="NumPy not installed")
def test_numpy(self): def test_numpy(self) -> None:
im = hopper() im = hopper()
pix = im.load() pix = im.load()
assert numpy is not None
assert pix[numpy.int32(1), numpy.int32(2)] == (18, 20, 59) assert pix[numpy.int32(1), numpy.int32(2)] == (18, 20, 59)
class TestImageGetPixel(AccessTest): class TestImageGetPixel(AccessTest):
@staticmethod @staticmethod
def color(mode): def color(mode: str) -> int | tuple[int, ...]:
bands = Image.getmodebands(mode) bands = Image.getmodebands(mode)
if bands == 1: if bands == 1:
return 1 return 1
@ -138,12 +142,13 @@ class TestImageGetPixel(AccessTest):
return (16, 32, 49) return (16, 32, 49)
return tuple(range(1, bands + 1)) return tuple(range(1, bands + 1))
def check(self, mode, expected_color=None): def check(self, mode: str, expected_color_int: int | None = None) -> None:
if self._need_cffi_access and mode.startswith("BGR;"): if self._need_cffi_access and mode.startswith("BGR;"):
pytest.skip("Support not added to deprecated module for BGR;* modes") pytest.skip("Support not added to deprecated module for BGR;* modes")
if not expected_color: expected_color = (
expected_color = self.color(mode) self.color(mode) if expected_color_int is None else expected_color_int
)
# check putpixel # check putpixel
im = Image.new(mode, (1, 1), None) im = Image.new(mode, (1, 1), None)
@ -222,25 +227,23 @@ class TestImageGetPixel(AccessTest):
"YCbCr", "YCbCr",
), ),
) )
def test_basic(self, mode): def test_basic(self, mode: str) -> None:
self.check(mode) self.check(mode)
def test_list(self): def test_list(self) -> None:
im = hopper() im = hopper()
assert im.getpixel([0, 0]) == (20, 20, 70) assert im.getpixel([0, 0]) == (20, 20, 70)
@pytest.mark.parametrize("mode", ("I;16", "I;16B")) @pytest.mark.parametrize("mode", ("I;16", "I;16B"))
@pytest.mark.parametrize( @pytest.mark.parametrize("expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1))
"expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1) def test_signedness(self, mode: str, expected_color: int) -> None:
)
def test_signedness(self, mode, expected_color):
# see https://github.com/python-pillow/Pillow/issues/452 # see https://github.com/python-pillow/Pillow/issues/452
# pixelaccess is using signed int* instead of uint* # pixelaccess is using signed int* instead of uint*
self.check(mode, expected_color) self.check(mode, expected_color)
@pytest.mark.parametrize("mode", ("P", "PA")) @pytest.mark.parametrize("mode", ("P", "PA"))
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255))) @pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
def test_p_putpixel_rgb_rgba(self, mode, color): def test_p_putpixel_rgb_rgba(self, mode: str, color: tuple[int, ...]) -> None:
im = Image.new(mode, (1, 1)) im = Image.new(mode, (1, 1))
im.putpixel((0, 0), color) im.putpixel((0, 0), color)
@ -264,7 +267,7 @@ class TestCffiGetPixel(TestImageGetPixel):
class TestCffi(AccessTest): class TestCffi(AccessTest):
_need_cffi_access = True _need_cffi_access = True
def _test_get_access(self, im): def _test_get_access(self, im: Image.Image) -> None:
"""Do we get the same thing as the old pixel access """Do we get the same thing as the old pixel access
Using private interfaces, forcing a capi access and Using private interfaces, forcing a capi access and
@ -282,7 +285,7 @@ class TestCffi(AccessTest):
with pytest.raises(ValueError): with pytest.raises(ValueError):
access[(access.xsize + 1, access.ysize + 1)] access[(access.xsize + 1, access.ysize + 1)]
def test_get_vs_c(self): def test_get_vs_c(self) -> None:
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
rgb = hopper("RGB") rgb = hopper("RGB")
rgb.load() rgb.load()
@ -301,7 +304,7 @@ class TestCffi(AccessTest):
# im = Image.new('I;32B', (10, 10), 2**10) # im = Image.new('I;32B', (10, 10), 2**10)
# self._test_get_access(im) # self._test_get_access(im)
def _test_set_access(self, im, color): def _test_set_access(self, im: Image.Image, color: tuple[int, ...] | float) -> None:
"""Are we writing the correct bits into the image? """Are we writing the correct bits into the image?
Using private interfaces, forcing a capi access and Using private interfaces, forcing a capi access and
@ -322,7 +325,7 @@ class TestCffi(AccessTest):
with pytest.raises(ValueError): with pytest.raises(ValueError):
access[(0, 0)] = color access[(0, 0)] = color
def test_set_vs_c(self): def test_set_vs_c(self) -> None:
rgb = hopper("RGB") rgb = hopper("RGB")
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
rgb.load() rgb.load()
@ -345,11 +348,11 @@ class TestCffi(AccessTest):
# self._test_set_access(im, 2**13-1) # self._test_set_access(im, 2**13-1)
@pytest.mark.filterwarnings("ignore::DeprecationWarning") @pytest.mark.filterwarnings("ignore::DeprecationWarning")
def test_not_implemented(self): def test_not_implemented(self) -> None:
assert PyAccess.new(hopper("BGR;15")) is None assert PyAccess.new(hopper("BGR;15")) is None
# ref https://github.com/python-pillow/Pillow/pull/2009 # ref https://github.com/python-pillow/Pillow/pull/2009
def test_reference_counting(self): def test_reference_counting(self) -> None:
size = 10 size = 10
for _ in range(10): for _ in range(10):
@ -361,7 +364,7 @@ class TestCffi(AccessTest):
assert px[i, 0] == 0 assert px[i, 0] == 0
@pytest.mark.parametrize("mode", ("P", "PA")) @pytest.mark.parametrize("mode", ("P", "PA"))
def test_p_putpixel_rgb_rgba(self, mode): def test_p_putpixel_rgb_rgba(self, mode: str) -> None:
for color in ((255, 0, 0), (255, 0, 0, 127 if mode == "PA" else 255)): for color in ((255, 0, 0), (255, 0, 0, 127 if mode == "PA" else 255)):
im = Image.new(mode, (1, 1)) im = Image.new(mode, (1, 1))
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
@ -379,7 +382,7 @@ class TestImagePutPixelError(AccessTest):
INVALID_TYPES = ["foo", 1.0, None] INVALID_TYPES = ["foo", 1.0, None]
@pytest.mark.parametrize("mode", IMAGE_MODES1) @pytest.mark.parametrize("mode", IMAGE_MODES1)
def test_putpixel_type_error1(self, mode): def test_putpixel_type_error1(self, mode: str) -> None:
im = hopper(mode) im = hopper(mode)
for v in self.INVALID_TYPES: for v in self.INVALID_TYPES:
with pytest.raises(TypeError, match="color must be int or tuple"): with pytest.raises(TypeError, match="color must be int or tuple"):
@ -402,14 +405,16 @@ class TestImagePutPixelError(AccessTest):
), ),
), ),
) )
def test_putpixel_invalid_number_of_bands(self, mode, band_numbers, match): def test_putpixel_invalid_number_of_bands(
self, mode: str, band_numbers: tuple[int, ...], match: str
) -> None:
im = hopper(mode) im = hopper(mode)
for band_number in band_numbers: for band_number in band_numbers:
with pytest.raises(TypeError, match=match): with pytest.raises(TypeError, match=match):
im.putpixel((0, 0), (0,) * band_number) im.putpixel((0, 0), (0,) * band_number)
@pytest.mark.parametrize("mode", IMAGE_MODES2) @pytest.mark.parametrize("mode", IMAGE_MODES2)
def test_putpixel_type_error2(self, mode): def test_putpixel_type_error2(self, mode: str) -> None:
im = hopper(mode) im = hopper(mode)
for v in self.INVALID_TYPES: for v in self.INVALID_TYPES:
with pytest.raises( with pytest.raises(
@ -418,7 +423,7 @@ class TestImagePutPixelError(AccessTest):
im.putpixel((0, 0), v) im.putpixel((0, 0), v)
@pytest.mark.parametrize("mode", IMAGE_MODES1 + IMAGE_MODES2) @pytest.mark.parametrize("mode", IMAGE_MODES1 + IMAGE_MODES2)
def test_putpixel_overflow_error(self, mode): def test_putpixel_overflow_error(self, mode: str) -> None:
im = hopper(mode) im = hopper(mode)
with pytest.raises(OverflowError): with pytest.raises(OverflowError):
im.putpixel((0, 0), 2**80) im.putpixel((0, 0), 2**80)
@ -427,10 +432,10 @@ class TestImagePutPixelError(AccessTest):
class TestEmbeddable: class TestEmbeddable:
@pytest.mark.xfail(reason="failing test") @pytest.mark.xfail(reason="failing test")
@pytest.mark.skipif(not is_win32(), reason="requires Windows") @pytest.mark.skipif(not is_win32(), reason="requires Windows")
def test_embeddable(self): def test_embeddable(self) -> None:
import ctypes import ctypes
from setuptools.command.build_ext import new_compiler from setuptools.command import build_ext
with open("embed_pil.c", "w", encoding="utf-8") as fh: with open("embed_pil.c", "w", encoding="utf-8") as fh:
fh.write( fh.write(
@ -459,7 +464,7 @@ int main(int argc, char* argv[])
% sys.prefix.replace("\\", "\\\\") % sys.prefix.replace("\\", "\\\\")
) )
compiler = new_compiler() compiler = getattr(build_ext, "new_compiler")()
compiler.add_include_dir(sysconfig.get_config_var("INCLUDEPY")) compiler.add_include_dir(sysconfig.get_config_var("INCLUDEPY"))
libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var( libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var(
@ -473,7 +478,7 @@ int main(int argc, char* argv[])
env["PATH"] = sys.prefix + ";" + env["PATH"] env["PATH"] = sys.prefix + ";" + env["PATH"]
# do not display the Windows Error Reporting dialog # do not display the Windows Error Reporting dialog
ctypes.windll.kernel32.SetErrorMode(0x0002) getattr(ctypes, "windll").kernel32.SetErrorMode(0x0002)
process = subprocess.Popen(["embed_pil.exe"], env=env) process = subprocess.Popen(["embed_pil.exe"], env=env)
process.communicate() process.communicate()

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from typing import Any
import pytest import pytest
from packaging.version import parse as parse_version from packaging.version import parse as parse_version
@ -12,12 +14,12 @@ numpy = pytest.importorskip("numpy", reason="NumPy not installed")
im = hopper().resize((128, 100)) im = hopper().resize((128, 100))
def test_toarray(): def test_toarray() -> None:
def test(mode): def test(mode: str) -> tuple[tuple[int, ...], str, int]:
ai = numpy.array(im.convert(mode)) ai = numpy.array(im.convert(mode))
return ai.shape, ai.dtype.str, ai.nbytes return ai.shape, ai.dtype.str, ai.nbytes
def test_with_dtype(dtype): def test_with_dtype(dtype) -> None:
ai = numpy.array(im, dtype=dtype) ai = numpy.array(im, dtype=dtype)
assert ai.dtype == dtype assert ai.dtype == dtype
@ -46,18 +48,18 @@ def test_toarray():
numpy.array(im_truncated) numpy.array(im_truncated)
def test_fromarray(): def test_fromarray() -> None:
class Wrapper: class Wrapper:
"""Class with API matching Image.fromarray""" """Class with API matching Image.fromarray"""
def __init__(self, img, arr_params): def __init__(self, img: Image.Image, arr_params: dict[str, Any]) -> None:
self.img = img self.img = img
self.__array_interface__ = arr_params self.__array_interface__ = arr_params
def tobytes(self): def tobytes(self) -> bytes:
return self.img.tobytes() return self.img.tobytes()
def test(mode): def test(mode: str) -> tuple[str, tuple[int, int], bool]:
i = im.convert(mode) i = im.convert(mode)
a = numpy.array(i) a = numpy.array(i)
# Make wrapper instance for image, new array interface # Make wrapper instance for image, new array interface
@ -89,7 +91,7 @@ def test_fromarray():
Image.fromarray(wrapped) Image.fromarray(wrapped)
def test_fromarray_palette(): def test_fromarray_palette() -> None:
# Arrange # Arrange
i = im.convert("L") i = im.convert("L")
a = numpy.array(i) a = numpy.array(i)

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