mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
Merge branch 'main' into libjpeg-turbo
This commit is contained in:
commit
92aafeb6f3
|
@ -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
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
cibuildwheel==2.16.2
|
cibuildwheel==2.16.5
|
||||||
|
|
1
.ci/requirements-mypy.txt
Normal file
1
.ci/requirements-mypy.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
mypy==1.7.1
|
|
@ -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
2
.github/FUNDING.yml
vendored
|
@ -1 +1 @@
|
||||||
tidelift: "pypi/Pillow"
|
tidelift: "pypi/pillow"
|
||||||
|
|
14
.github/workflows/docs.yml
vendored
14
.github/workflows/docs.yml
vendored
|
@ -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: |
|
||||||
|
|
11
.github/workflows/macos-install.sh
vendored
11
.github/workflows/macos-install.sh
vendored
|
@ -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
|
||||||
|
|
2
.github/workflows/release-drafter.yml
vendored
2
.github/workflows/release-drafter.yml
vendored
|
@ -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 }}
|
||||||
|
|
1
.github/workflows/system-info.py
vendored
1
.github/workflows/system-info.py
vendored
|
@ -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
|
||||||
|
|
13
.github/workflows/test-cygwin.yml
vendored
13
.github/workflows/test-cygwin.yml
vendored
|
@ -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
|
||||||
|
|
2
.github/workflows/test-docker.yml
vendored
2
.github/workflows/test-docker.yml
vendored
|
@ -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 }}
|
||||||
|
|
5
.github/workflows/test-mingw.yml
vendored
5
.github/workflows/test-mingw.yml
vendored
|
@ -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
|
||||||
|
|
19
.github/workflows/test-windows.yml
vendored
19
.github/workflows/test-windows.yml
vendored
|
@ -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
|
||||||
|
|
37
.github/workflows/test.yml
vendored
37
.github/workflows/test.yml
vendored
|
@ -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
|
||||||
|
|
||||||
|
|
13
.github/workflows/wheels-dependencies.sh
vendored
13
.github/workflows/wheels-dependencies.sh
vendored
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
12
CHANGES.rst
12
CHANGES.rst
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
2
Tests/check_jp2_overflow.py
Executable file → Normal 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
|
||||||
|
|
|
@ -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
BIN
Tests/images/2422.flc
Normal file
Binary file not shown.
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)):
|
||||||
|
|
|
@ -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\)"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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/
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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>"
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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",))
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user