Merge branch 'main' into init
|
@ -21,9 +21,9 @@ environment:
|
|||
- PYTHON: C:/Python312
|
||||
ARCHITECTURE: x86
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||
- PYTHON: C:/Python38-x64
|
||||
- PYTHON: C:/Python39-x64
|
||||
ARCHITECTURE: AMD64
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
|
||||
|
||||
install:
|
||||
|
@ -38,7 +38,7 @@ install:
|
|||
- path c:\nasm-2.16.03;C:\Program Files\gs\gs10.03.1\bin;%PATH%
|
||||
- cd c:\pillow\winbuild\
|
||||
- ps: |
|
||||
c:\python38\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
|
||||
c:\python39\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
|
||||
c:\pillow\winbuild\build\build_dep_all.cmd
|
||||
$host.SetShouldExit(0)
|
||||
- path C:\pillow\winbuild\build\bin;%PATH%
|
||||
|
|
|
@ -28,8 +28,6 @@ fi
|
|||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade wheel
|
||||
# TODO Update condition when cffi supports 3.13
|
||||
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
|
||||
python3 -m pip install coverage
|
||||
python3 -m pip install defusedxml
|
||||
python3 -m pip install olefile
|
||||
|
@ -39,8 +37,7 @@ python3 -m pip install -U pytest-timeout
|
|||
python3 -m pip install pyroma
|
||||
|
||||
if [[ $(uname) != CYGWIN* ]]; then
|
||||
# TODO Update condition when NumPy supports 3.13
|
||||
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi
|
||||
python3 -m pip install numpy
|
||||
|
||||
# PyQt6 doesn't support PyPy3
|
||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||
|
@ -51,7 +48,6 @@ if [[ $(uname) != CYGWIN* ]]; then
|
|||
# Pyroma uses non-isolated build and fails with old setuptools
|
||||
if [[
|
||||
$GHA_PYTHON_VERSION == pypy3.9
|
||||
|| $GHA_PYTHON_VERSION == 3.8
|
||||
|| $GHA_PYTHON_VERSION == 3.9
|
||||
]]; then
|
||||
# To match pyproject.toml
|
||||
|
|
|
@ -1 +1 @@
|
|||
cibuildwheel==2.19.1
|
||||
cibuildwheel==2.19.2
|
||||
|
|
|
@ -1 +1 @@
|
|||
mypy==1.10.0
|
||||
mypy==1.10.1
|
||||
|
|
|
@ -19,6 +19,5 @@ exclude_also =
|
|||
[run]
|
||||
omit =
|
||||
Tests/32bit_segfault_check.py
|
||||
Tests/bench_cffi_access.py
|
||||
Tests/check_*.py
|
||||
Tests/createfontdatachunk.py
|
||||
|
|
7
.github/workflows/macos-install.sh
vendored
|
@ -18,9 +18,6 @@ else
|
|||
fi
|
||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||
|
||||
# TODO Update condition when cffi supports 3.13
|
||||
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
|
||||
|
||||
python3 -m pip install coverage
|
||||
python3 -m pip install defusedxml
|
||||
python3 -m pip install olefile
|
||||
|
@ -28,9 +25,7 @@ python3 -m pip install -U pytest
|
|||
python3 -m pip install -U pytest-cov
|
||||
python3 -m pip install -U pytest-timeout
|
||||
python3 -m pip install pyroma
|
||||
|
||||
# TODO Update condition when NumPy supports 3.13
|
||||
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi
|
||||
python3 -m pip install numpy
|
||||
|
||||
# extra test images
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
|
3
.github/workflows/test-cygwin.yml
vendored
|
@ -35,7 +35,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-minor-version: [8, 9]
|
||||
python-minor-version: [9]
|
||||
|
||||
timeout-minutes: 40
|
||||
|
||||
|
@ -72,7 +72,6 @@ jobs:
|
|||
make
|
||||
netpbm
|
||||
perl
|
||||
python3${{ matrix.python-minor-version }}-cffi
|
||||
python3${{ matrix.python-minor-version }}-cython
|
||||
python3${{ matrix.python-minor-version }}-devel
|
||||
python3${{ matrix.python-minor-version }}-numpy
|
||||
|
|
2
.github/workflows/test-docker.yml
vendored
|
@ -44,13 +44,11 @@ jobs:
|
|||
amazon-2023-amd64,
|
||||
arch,
|
||||
centos-stream-9-amd64,
|
||||
debian-11-bullseye-amd64,
|
||||
debian-12-bookworm-x86,
|
||||
debian-12-bookworm-amd64,
|
||||
fedora-39-amd64,
|
||||
fedora-40-amd64,
|
||||
gentoo,
|
||||
ubuntu-20.04-focal-amd64,
|
||||
ubuntu-22.04-jammy-amd64,
|
||||
ubuntu-24.04-noble-amd64,
|
||||
]
|
||||
|
|
1
.github/workflows/test-mingw.yml
vendored
|
@ -64,7 +64,6 @@ jobs:
|
|||
mingw-w64-x86_64-libtiff \
|
||||
mingw-w64-x86_64-libwebp \
|
||||
mingw-w64-x86_64-openjpeg2 \
|
||||
mingw-w64-x86_64-python3-cffi \
|
||||
mingw-w64-x86_64-python3-numpy \
|
||||
mingw-w64-x86_64-python3-olefile \
|
||||
mingw-w64-x86_64-python3-setuptools \
|
||||
|
|
2
.github/workflows/test-windows.yml
vendored
|
@ -35,7 +35,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
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.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
|
||||
timeout-minutes: 30
|
||||
|
||||
|
|
5
.github/workflows/test.yml
vendored
|
@ -48,7 +48,6 @@ jobs:
|
|||
"3.11",
|
||||
"3.10",
|
||||
"3.9",
|
||||
"3.8",
|
||||
]
|
||||
include:
|
||||
- python-version: "3.11"
|
||||
|
@ -59,13 +58,9 @@ jobs:
|
|||
# M1 only available for 3.10+
|
||||
- os: "macos-13"
|
||||
python-version: "3.9"
|
||||
- os: "macos-13"
|
||||
python-version: "3.8"
|
||||
exclude:
|
||||
- os: "macos-14"
|
||||
python-version: "3.9"
|
||||
- os: "macos-14"
|
||||
python-version: "3.8"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
|
|
2
.github/workflows/wheels-test.sh
vendored
|
@ -12,7 +12,7 @@ elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
|
|||
else
|
||||
yum install -y fribidi
|
||||
fi
|
||||
if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ] && !([[ "$OSTYPE" == "darwin"* ]] && [[ $(python3 --version) == *"3.13."* ]]); then
|
||||
if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; then
|
||||
python3 -m pip install numpy
|
||||
fi
|
||||
|
||||
|
|
11
.github/workflows/wheels.yml
vendored
|
@ -41,12 +41,8 @@ jobs:
|
|||
python-version:
|
||||
- pp39
|
||||
- pp310
|
||||
- cp38
|
||||
- cp39
|
||||
- cp310
|
||||
- cp311
|
||||
- cp312
|
||||
- cp313
|
||||
- cp3{9,10,11}
|
||||
- cp3{12,13}
|
||||
spec:
|
||||
- manylinux2014
|
||||
- manylinux_2_28
|
||||
|
@ -136,8 +132,6 @@ jobs:
|
|||
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_PRERELEASE_PYTHONS: True
|
||||
CIBW_SKIP: pp38-*
|
||||
CIBW_TEST_SKIP: cp38-macosx_arm64
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
@ -208,7 +202,6 @@ jobs:
|
|||
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
||||
CIBW_CACHE_PATH: "C:\\cibw"
|
||||
CIBW_PRERELEASE_PYTHONS: True
|
||||
CIBW_SKIP: pp38-*
|
||||
CIBW_TEST_SKIP: "*-win_arm64"
|
||||
CIBW_TEST_COMMAND: 'docker run --rm
|
||||
-v {project}:C:\pillow
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.4.7
|
||||
rev: v0.5.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--exit-non-zero-on-fix]
|
||||
|
@ -11,7 +11,7 @@ repos:
|
|||
- id: black
|
||||
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.7.8
|
||||
rev: 1.7.9
|
||||
hooks:
|
||||
- id: bandit
|
||||
args: [--severity-level=high]
|
||||
|
@ -24,7 +24,7 @@ repos:
|
|||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v18.1.5
|
||||
rev: v18.1.8
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types: [c]
|
||||
|
@ -50,7 +50,7 @@ repos:
|
|||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.28.4
|
||||
rev: 0.28.6
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
- id: check-readthedocs
|
||||
|
@ -62,7 +62,7 @@ repos:
|
|||
- id: sphinx-lint
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: 1.8.0
|
||||
rev: 2.1.3
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
|
|
92
CHANGES.rst
|
@ -2,9 +2,99 @@
|
|||
Changelog (Pillow)
|
||||
==================
|
||||
|
||||
10.4.0 (unreleased)
|
||||
11.0.0 (unreleased)
|
||||
-------------------
|
||||
|
||||
- Drop support for Python 3.8 #8183
|
||||
[hugovk, radarhere]
|
||||
|
||||
- Add support for Python 3.13 #8181
|
||||
[hugovk, radarhere]
|
||||
|
||||
- Fix incompatibility with NumPy 1.20 #8187
|
||||
[neutrinoceros, radarhere]
|
||||
|
||||
- Remove PSFile, PyAccess and USE_CFFI_ACCESS #8182
|
||||
[hugovk, radarhere]
|
||||
|
||||
10.4.0 (2024-07-01)
|
||||
-------------------
|
||||
|
||||
- Raise FileNotFoundError if show_file() path does not exist #8178
|
||||
[radarhere]
|
||||
|
||||
- Improved reading 16-bit TGA images with colour #7965
|
||||
[Yay295, radarhere]
|
||||
|
||||
- Deprecate non-image ImageCms modes #8031
|
||||
[radarhere]
|
||||
|
||||
- Fixed processing multiple JPEG EXIF markers #8127
|
||||
[radarhere]
|
||||
|
||||
- Do not preserve EXIFIFD tag by default when saving TIFF images #8110
|
||||
[radarhere]
|
||||
|
||||
- Added ImageFont.load_default_imagefont() #8086
|
||||
[radarhere]
|
||||
|
||||
- Added Image.WARN_POSSIBLE_FORMATS #8063
|
||||
[radarhere]
|
||||
|
||||
- Remove zero-byte end padding when parsing any XMP data #8171
|
||||
[radarhere]
|
||||
|
||||
- Do not detect Ultra HDR images as MPO #8056
|
||||
[radarhere]
|
||||
|
||||
- Raise SyntaxError specific to JP2 #8146
|
||||
[Yay295, radarhere]
|
||||
|
||||
- Do not use first frame duration for other frames when saving APNG images #8104
|
||||
[radarhere]
|
||||
|
||||
- Consider I;16 pixel size when using a 1 mode mask #8112
|
||||
[radarhere]
|
||||
|
||||
- When saving multiple PNG frames, convert to mode rather than raw mode #8087
|
||||
[radarhere]
|
||||
|
||||
- Added byte support to FreeTypeFont #8141
|
||||
[radarhere]
|
||||
|
||||
- Allow float center for rotate operations #8114
|
||||
[radarhere]
|
||||
|
||||
- Do not read layers immediately when opening PSD images #8039
|
||||
[radarhere]
|
||||
|
||||
- Restore original thread state #8065
|
||||
[radarhere]
|
||||
|
||||
- Read IM and TIFF images as RGB, rather than RGBX #7997
|
||||
[radarhere]
|
||||
|
||||
- Only preserve TIFF IPTC_NAA_CHUNK tag if type is BYTE or UNDEFINED #7948
|
||||
[radarhere]
|
||||
|
||||
- Clarify ImageDraw2 error message when size is missing #8165
|
||||
[radarhere]
|
||||
|
||||
- Support unpacking more rawmodes to RGBA palettes #7966
|
||||
[radarhere]
|
||||
|
||||
- Removed support for Qt 5 #8159
|
||||
[radarhere]
|
||||
|
||||
- Improve ``ImageFont.freetype`` support for XDG directories on Linux #8135
|
||||
[mamg22, radarhere]
|
||||
|
||||
- Improved consistency of XMP handling #8069
|
||||
[radarhere]
|
||||
|
||||
- Use pkg-config to help find libwebp and raqm #8142
|
||||
[radarhere]
|
||||
|
||||
- Accept 't' suffix for libtiff version #8126, #8129
|
||||
[radarhere]
|
||||
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
from PIL import PyAccess
|
||||
|
||||
from .helper import hopper
|
||||
|
||||
# Not running this test by default. No DOS against CI.
|
||||
|
||||
|
||||
def iterate_get(size, access) -> None:
|
||||
(w, h) = size
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
access[(x, y)]
|
||||
|
||||
|
||||
def iterate_set(size, access) -> None:
|
||||
(w, h) = size
|
||||
for x in range(w):
|
||||
for y in range(h):
|
||||
access[(x, y)] = (x % 256, y % 256, 0)
|
||||
|
||||
|
||||
def timer(func, label, *args) -> None:
|
||||
iterations = 5000
|
||||
starttime = time.time()
|
||||
for x in range(iterations):
|
||||
func(*args)
|
||||
if time.time() - starttime > 10:
|
||||
break
|
||||
endtime = time.time()
|
||||
print(
|
||||
f"{label}: completed {x + 1} iterations in {endtime - starttime:.4f}s, "
|
||||
f"{(endtime - starttime) / (x + 1.0):.6f}s per iteration"
|
||||
)
|
||||
|
||||
|
||||
def test_direct() -> None:
|
||||
im = hopper()
|
||||
im.load()
|
||||
# im = Image.new("RGB", (2000, 2000), (1, 3, 2))
|
||||
caccess = im.im.pixel_access(False)
|
||||
access = PyAccess.new(im, False)
|
||||
|
||||
assert access is not None
|
||||
assert caccess[(0, 0)] == access[(0, 0)]
|
||||
|
||||
print(f"Size: {im.width}x{im.height}")
|
||||
timer(iterate_get, "PyAccess - get", im.size, access)
|
||||
timer(iterate_set, "PyAccess - set", im.size, access)
|
||||
timer(iterate_get, "C-api - get", im.size, caccess)
|
||||
timer(iterate_set, "C-api - set", im.size, caccess)
|
|
@ -11,14 +11,15 @@ import subprocess
|
|||
import sys
|
||||
import sysconfig
|
||||
import tempfile
|
||||
from collections.abc import Sequence
|
||||
from functools import lru_cache
|
||||
from io import BytesIO
|
||||
from typing import Any, Callable, Sequence
|
||||
from typing import Any, Callable
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
from PIL import Image, ImageMath, features
|
||||
from PIL import Image, ImageFile, ImageMath, features
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -59,9 +60,7 @@ def convert_to_comparable(
|
|||
return new_a, new_b
|
||||
|
||||
|
||||
def assert_deep_equal(
|
||||
a: Sequence[Any], b: Sequence[Any], msg: str | None = None
|
||||
) -> None:
|
||||
def assert_deep_equal(a: Any, b: Any, msg: str | None = None) -> None:
|
||||
try:
|
||||
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
|
||||
except Exception:
|
||||
|
@ -240,7 +239,7 @@ class PillowLeakTestCase:
|
|||
# helpers
|
||||
|
||||
|
||||
def fromstring(data: bytes) -> Image.Image:
|
||||
def fromstring(data: bytes) -> ImageFile.ImageFile:
|
||||
return Image.open(BytesIO(data))
|
||||
|
||||
|
||||
|
|
BIN
Tests/images/imagedraw_polygon_width_I.tiff
Normal file
Before Width: | Height: | Size: 364 B After Width: | Height: | Size: 391 B |
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 414 B |
BIN
Tests/images/rgba16.tga
Normal file
After Width: | Height: | Size: 48 B |
BIN
Tests/images/ultrahdr.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
Tests/images/unknown_mode.j2k
Normal file
|
@ -321,6 +321,7 @@ class TestColorLut3DCoreAPI:
|
|||
-1, 2, 2, 2, 2, 2,
|
||||
])).load()
|
||||
# fmt: on
|
||||
assert transformed is not None
|
||||
assert transformed[0, 0] == (0, 0, 255)
|
||||
assert transformed[50, 50] == (0, 0, 255)
|
||||
assert transformed[255, 0] == (0, 255, 255)
|
||||
|
@ -341,6 +342,7 @@ class TestColorLut3DCoreAPI:
|
|||
-3, 5, 5, 5, 5, 5,
|
||||
])).load()
|
||||
# fmt: on
|
||||
assert transformed is not None
|
||||
assert transformed[0, 0] == (0, 0, 255)
|
||||
assert transformed[50, 50] == (0, 0, 255)
|
||||
assert transformed[255, 0] == (0, 255, 255)
|
||||
|
@ -354,10 +356,10 @@ class TestColorLut3DCoreAPI:
|
|||
class TestColorLut3DFilter:
|
||||
def test_wrong_args(self) -> None:
|
||||
with pytest.raises(ValueError, match="should be either an integer"):
|
||||
ImageFilter.Color3DLUT("small", [1])
|
||||
ImageFilter.Color3DLUT("small", [1]) # type: ignore[arg-type]
|
||||
|
||||
with pytest.raises(ValueError, match="should be either an integer"):
|
||||
ImageFilter.Color3DLUT((11, 11), [1])
|
||||
ImageFilter.Color3DLUT((11, 11), [1]) # type: ignore[arg-type]
|
||||
|
||||
with pytest.raises(ValueError, match=r"in \[2, 65\] range"):
|
||||
ImageFilter.Color3DLUT((11, 11, 1), [1])
|
||||
|
|
|
@ -9,9 +9,9 @@ from PIL import _deprecate
|
|||
"version, expected",
|
||||
[
|
||||
(
|
||||
11,
|
||||
"Old thing is deprecated and will be removed in Pillow 11 "
|
||||
r"\(2024-10-15\)\. Use new thing instead\.",
|
||||
12,
|
||||
"Old thing is deprecated and will be removed in Pillow 12 "
|
||||
r"\(2025-10-15\)\. Use new thing instead\.",
|
||||
),
|
||||
(
|
||||
None,
|
||||
|
@ -54,18 +54,18 @@ def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
|
|||
|
||||
def test_plural() -> None:
|
||||
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 12 \(2025-10-15\)\. "
|
||||
r"Use new thing instead\."
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=expected):
|
||||
_deprecate.deprecate("Old things", 11, "new thing", plural=True)
|
||||
_deprecate.deprecate("Old things", 12, "new thing", plural=True)
|
||||
|
||||
|
||||
def test_replacement_and_action() -> None:
|
||||
expected = "Use only one of 'replacement' and 'action'"
|
||||
with pytest.raises(ValueError, match=expected):
|
||||
_deprecate.deprecate(
|
||||
"Old thing", 11, replacement="new thing", action="Upgrade to new thing"
|
||||
"Old thing", 12, replacement="new thing", action="Upgrade to new thing"
|
||||
)
|
||||
|
||||
|
||||
|
@ -78,16 +78,16 @@ def test_replacement_and_action() -> None:
|
|||
)
|
||||
def test_action(action: str) -> None:
|
||||
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 12 \(2025-10-15\)\. "
|
||||
r"Upgrade to new thing\."
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=expected):
|
||||
_deprecate.deprecate("Old thing", 11, action=action)
|
||||
_deprecate.deprecate("Old thing", 12, action=action)
|
||||
|
||||
|
||||
def test_no_replacement_or_action() -> None:
|
||||
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 12 \(2025-10-15\)"
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=expected):
|
||||
_deprecate.deprecate("Old thing", 11)
|
||||
_deprecate.deprecate("Old thing", 12)
|
||||
|
|
|
@ -706,10 +706,21 @@ def test_different_modes_in_later_frames(
|
|||
assert reloaded.mode == mode
|
||||
|
||||
|
||||
def test_apng_repeated_seeks_give_correct_info() -> None:
|
||||
def test_different_durations(tmp_path: Path) -> None:
|
||||
test_file = str(tmp_path / "temp.png")
|
||||
|
||||
with Image.open("Tests/images/apng/different_durations.png") as im:
|
||||
for i in range(3):
|
||||
for _ in range(3):
|
||||
im.seek(0)
|
||||
assert im.info["duration"] == 4000
|
||||
|
||||
im.seek(1)
|
||||
assert im.info["duration"] == 1000
|
||||
|
||||
im.save(test_file, save_all=True)
|
||||
|
||||
with Image.open(test_file) as reloaded:
|
||||
assert reloaded.info["duration"] == 4000
|
||||
|
||||
reloaded.seek(1)
|
||||
assert reloaded.info["duration"] == 1000
|
||||
|
|
|
@ -64,6 +64,9 @@ def test_handler(tmp_path: Path) -> None:
|
|||
im.fp.close()
|
||||
return Image.new("RGB", (1, 1))
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
return self.loaded
|
||||
|
||||
def save(self, im: Image.Image, fp: IO[bytes], filename: str) -> None:
|
||||
self.saved = True
|
||||
|
||||
|
@ -71,10 +74,10 @@ def test_handler(tmp_path: Path) -> None:
|
|||
BufrStubImagePlugin.register_handler(handler)
|
||||
with Image.open(TEST_FILE) as im:
|
||||
assert handler.opened
|
||||
assert not handler.loaded
|
||||
assert not handler.is_loaded()
|
||||
|
||||
im.load()
|
||||
assert handler.loaded
|
||||
assert handler.is_loaded()
|
||||
|
||||
temp_file = str(tmp_path / "temp.bufr")
|
||||
im.save(temp_file)
|
||||
|
|
|
@ -329,46 +329,6 @@ def test_read_binary_preview() -> None:
|
|||
pass
|
||||
|
||||
|
||||
def test_readline_psfile(tmp_path: Path) -> None:
|
||||
# check all the freaking line endings possible from the spec
|
||||
# test_string = u'something\r\nelse\n\rbaz\rbif\n'
|
||||
line_endings = ["\r\n", "\n", "\n\r", "\r"]
|
||||
strings = ["something", "else", "baz", "bif"]
|
||||
|
||||
def _test_readline(t: EpsImagePlugin.PSFile, ending: str) -> None:
|
||||
ending = f"Failure with line ending: {''.join(str(ord(s)) for s in ending)}"
|
||||
assert t.readline().strip("\r\n") == "something", ending
|
||||
assert t.readline().strip("\r\n") == "else", ending
|
||||
assert t.readline().strip("\r\n") == "baz", ending
|
||||
assert t.readline().strip("\r\n") == "bif", ending
|
||||
|
||||
def _test_readline_io_psfile(test_string: str, ending: str) -> None:
|
||||
f = io.BytesIO(test_string.encode("latin-1"))
|
||||
with pytest.warns(DeprecationWarning):
|
||||
t = EpsImagePlugin.PSFile(f)
|
||||
_test_readline(t, ending)
|
||||
|
||||
def _test_readline_file_psfile(test_string: str, ending: str) -> None:
|
||||
f = str(tmp_path / "temp.txt")
|
||||
with open(f, "wb") as w:
|
||||
w.write(test_string.encode("latin-1"))
|
||||
|
||||
with open(f, "rb") as r:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
t = EpsImagePlugin.PSFile(r)
|
||||
_test_readline(t, ending)
|
||||
|
||||
for ending in line_endings:
|
||||
s = ending.join(strings)
|
||||
_test_readline_io_psfile(s, ending)
|
||||
_test_readline_file_psfile(s, ending)
|
||||
|
||||
|
||||
def test_psfile_deprecation() -> None:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
EpsImagePlugin.PSFile(None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
|
||||
@pytest.mark.parametrize(
|
||||
"line_ending",
|
||||
|
@ -425,9 +385,10 @@ def test_timeout(test_file: str) -> None:
|
|||
def test_bounding_box_in_trailer() -> None:
|
||||
# Check bounding boxes are parsed in the same way
|
||||
# when specified in the header and the trailer
|
||||
with Image.open("Tests/images/zero_bb_trailer.eps") as trailer_image, Image.open(
|
||||
FILE1
|
||||
) as header_image:
|
||||
with (
|
||||
Image.open("Tests/images/zero_bb_trailer.eps") as trailer_image,
|
||||
Image.open(FILE1) as header_image,
|
||||
):
|
||||
assert trailer_image.size == header_image.size
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from collections.abc import Generator
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Generator
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -353,7 +353,7 @@ def test_palette_434(tmp_path: Path) -> None:
|
|||
|
||||
def roundtrip(im: Image.Image, **kwargs: bool) -> Image.Image:
|
||||
out = str(tmp_path / "temp.gif")
|
||||
im.copy().save(out, **kwargs)
|
||||
im.copy().save(out, "GIF", **kwargs)
|
||||
reloaded = Image.open(out)
|
||||
|
||||
return reloaded
|
||||
|
|
|
@ -64,6 +64,9 @@ def test_handler(tmp_path: Path) -> None:
|
|||
im.fp.close()
|
||||
return Image.new("RGB", (1, 1))
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
return self.loaded
|
||||
|
||||
def save(self, im: Image.Image, fp: IO[bytes], filename: str) -> None:
|
||||
self.saved = True
|
||||
|
||||
|
@ -71,10 +74,10 @@ def test_handler(tmp_path: Path) -> None:
|
|||
GribStubImagePlugin.register_handler(handler)
|
||||
with Image.open(TEST_FILE) as im:
|
||||
assert handler.opened
|
||||
assert not handler.loaded
|
||||
assert not handler.is_loaded()
|
||||
|
||||
im.load()
|
||||
assert handler.loaded
|
||||
assert handler.is_loaded()
|
||||
|
||||
temp_file = str(tmp_path / "temp.grib")
|
||||
im.save(temp_file)
|
||||
|
|
|
@ -66,6 +66,9 @@ def test_handler(tmp_path: Path) -> None:
|
|||
im.fp.close()
|
||||
return Image.new("RGB", (1, 1))
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
return self.loaded
|
||||
|
||||
def save(self, im: Image.Image, fp: IO[bytes], filename: str) -> None:
|
||||
self.saved = True
|
||||
|
||||
|
@ -73,10 +76,10 @@ def test_handler(tmp_path: Path) -> None:
|
|||
Hdf5StubImagePlugin.register_handler(handler)
|
||||
with Image.open(TEST_FILE) as im:
|
||||
assert handler.opened
|
||||
assert not handler.loaded
|
||||
assert not handler.is_loaded()
|
||||
|
||||
im.load()
|
||||
assert handler.loaded
|
||||
assert handler.is_loaded()
|
||||
|
||||
temp_file = str(tmp_path / "temp.h5")
|
||||
im.save(temp_file)
|
||||
|
|
|
@ -872,7 +872,7 @@ class TestFileJpeg:
|
|||
|
||||
def test_multiple_exif(self) -> None:
|
||||
with Image.open("Tests/images/multiple_exif.jpg") as im:
|
||||
assert im.info["exif"] == b"Exif\x00\x00firstsecond"
|
||||
assert im.getexif()[270] == "firstsecond"
|
||||
|
||||
@mark_if_feature_version(
|
||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||
|
@ -948,6 +948,7 @@ class TestFileJpeg:
|
|||
):
|
||||
assert im.getxmp() == {}
|
||||
else:
|
||||
assert "xmp" in im.info
|
||||
xmp = im.getxmp()
|
||||
|
||||
description = xmp["xmpmeta"]["RDF"]["Description"]
|
||||
|
@ -1032,8 +1033,10 @@ class TestFileJpeg:
|
|||
|
||||
def test_repr_jpeg(self) -> None:
|
||||
im = hopper()
|
||||
b = im._repr_jpeg_()
|
||||
assert b is not None
|
||||
|
||||
with Image.open(BytesIO(im._repr_jpeg_())) as repr_jpeg:
|
||||
with Image.open(BytesIO(b)) as repr_jpeg:
|
||||
assert repr_jpeg.format == "JPEG"
|
||||
assert_image_similar(im, repr_jpeg, 17)
|
||||
|
||||
|
|
|
@ -335,9 +335,15 @@ def test_issue_6194() -> None:
|
|||
assert im.getpixel((5, 5)) == 31
|
||||
|
||||
|
||||
def test_unknown_j2k_mode() -> None:
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
with Image.open("Tests/images/unknown_mode.j2k"):
|
||||
pass
|
||||
|
||||
|
||||
def test_unbound_local() -> None:
|
||||
# prepatch, a malformed jp2 file could cause an UnboundLocalError exception.
|
||||
with pytest.raises(OSError):
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
with Image.open("Tests/images/unbound_variable.jp2"):
|
||||
pass
|
||||
|
||||
|
|
|
@ -92,11 +92,22 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
def test_g4_non_disk_file_object(self, tmp_path: Path) -> None:
|
||||
"""Testing loading from non-disk non-BytesIO file object"""
|
||||
test_file = "Tests/images/hopper_g4_500.tif"
|
||||
s = io.BytesIO()
|
||||
with open(test_file, "rb") as f:
|
||||
s.write(f.read())
|
||||
s.seek(0)
|
||||
r = io.BufferedReader(s)
|
||||
data = f.read()
|
||||
|
||||
class NonBytesIO(io.RawIOBase):
|
||||
def read(self, size: int = -1) -> bytes:
|
||||
nonlocal data
|
||||
if size == -1:
|
||||
size = len(data)
|
||||
result = data[:size]
|
||||
data = data[size:]
|
||||
return result
|
||||
|
||||
def readable(self) -> bool:
|
||||
return True
|
||||
|
||||
r = io.BufferedReader(NonBytesIO())
|
||||
with Image.open(r) as im:
|
||||
assert im.size == (500, 500)
|
||||
self._assert_noerr(tmp_path, im)
|
||||
|
@ -685,13 +696,18 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
assert reloaded.tag_v2[530] == (1, 1)
|
||||
assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255)
|
||||
|
||||
def test_exif_ifd(self, tmp_path: Path) -> None:
|
||||
outfile = str(tmp_path / "temp.tif")
|
||||
def test_exif_ifd(self) -> None:
|
||||
out = io.BytesIO()
|
||||
with Image.open("Tests/images/tiff_adobe_deflate.tif") as im:
|
||||
assert im.tag_v2[34665] == 125456
|
||||
im.save(outfile)
|
||||
im.save(out, "TIFF")
|
||||
|
||||
with Image.open(outfile) as reloaded:
|
||||
with Image.open(out) as reloaded:
|
||||
assert 34665 not in reloaded.tag_v2
|
||||
|
||||
im.save(out, "TIFF", tiffinfo={34665: 125456})
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
if Image.core.libtiff_support_custom_tags:
|
||||
assert reloaded.tag_v2[34665] == 125456
|
||||
|
||||
|
@ -1043,7 +1059,11 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
],
|
||||
)
|
||||
def test_wrong_bits_per_sample(
|
||||
self, file_name: str, mode: str, size: tuple[int, int], tile
|
||||
self,
|
||||
file_name: str,
|
||||
mode: str,
|
||||
size: tuple[int, int],
|
||||
tile: list[tuple[str, tuple[int, int, int, int], int, tuple[Any, ...]]],
|
||||
) -> None:
|
||||
with Image.open("Tests/images/" + file_name) as im:
|
||||
assert im.mode == mode
|
||||
|
@ -1130,7 +1150,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
arguments: dict[str, str | int] = {"compression": "tiff_adobe_deflate"}
|
||||
if argument:
|
||||
arguments["strip_size"] = 2**18
|
||||
im.save(out, **arguments)
|
||||
im.save(out, "TIFF", **arguments)
|
||||
|
||||
with Image.open(out) as im:
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
|
|
|
@ -226,6 +226,11 @@ def test_eoferror() -> None:
|
|||
im.seek(n_frames - 1)
|
||||
|
||||
|
||||
def test_ultra_hdr() -> None:
|
||||
with Image.open("Tests/images/ultrahdr.jpg") as im:
|
||||
assert im.format == "JPEG"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_file", test_files)
|
||||
def test_image_grab(test_file: str) -> None:
|
||||
with Image.open(test_file) as im:
|
||||
|
|
|
@ -76,6 +76,7 @@ def test_pil184() -> None:
|
|||
def test_1px_width(tmp_path: Path) -> None:
|
||||
im = Image.new("L", (1, 256))
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
for y in range(256):
|
||||
px[0, y] = y
|
||||
_roundtrip(tmp_path, im)
|
||||
|
@ -84,6 +85,7 @@ def test_1px_width(tmp_path: Path) -> None:
|
|||
def test_large_count(tmp_path: Path) -> None:
|
||||
im = Image.new("L", (256, 1))
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
for x in range(256):
|
||||
px[x, 0] = x // 67 * 67
|
||||
_roundtrip(tmp_path, im)
|
||||
|
@ -101,6 +103,7 @@ def _test_buffer_overflow(tmp_path: Path, im: Image.Image, size: int = 1024) ->
|
|||
def test_break_in_count_overflow(tmp_path: Path) -> None:
|
||||
im = Image.new("L", (256, 5))
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
for y in range(4):
|
||||
for x in range(256):
|
||||
px[x, y] = x % 128
|
||||
|
@ -110,6 +113,7 @@ def test_break_in_count_overflow(tmp_path: Path) -> None:
|
|||
def test_break_one_in_loop(tmp_path: Path) -> None:
|
||||
im = Image.new("L", (256, 5))
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
for y in range(5):
|
||||
for x in range(256):
|
||||
px[x, y] = x % 128
|
||||
|
@ -119,6 +123,7 @@ def test_break_one_in_loop(tmp_path: Path) -> None:
|
|||
def test_break_many_in_loop(tmp_path: Path) -> None:
|
||||
im = Image.new("L", (256, 5))
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
for y in range(4):
|
||||
for x in range(256):
|
||||
px[x, y] = x % 128
|
||||
|
@ -130,6 +135,7 @@ def test_break_many_in_loop(tmp_path: Path) -> None:
|
|||
def test_break_one_at_end(tmp_path: Path) -> None:
|
||||
im = Image.new("L", (256, 5))
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
for y in range(5):
|
||||
for x in range(256):
|
||||
px[x, y] = x % 128
|
||||
|
@ -140,6 +146,7 @@ def test_break_one_at_end(tmp_path: Path) -> None:
|
|||
def test_break_many_at_end(tmp_path: Path) -> None:
|
||||
im = Image.new("L", (256, 5))
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
for y in range(5):
|
||||
for x in range(256):
|
||||
px[x, y] = x % 128
|
||||
|
@ -152,6 +159,7 @@ def test_break_many_at_end(tmp_path: Path) -> None:
|
|||
def test_break_padding(tmp_path: Path) -> None:
|
||||
im = Image.new("L", (257, 5))
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
for y in range(5):
|
||||
for x in range(257):
|
||||
px[x, y] = x % 128
|
||||
|
|
|
@ -5,8 +5,9 @@ import os
|
|||
import os.path
|
||||
import tempfile
|
||||
import time
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
from typing import Any, Generator
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -117,7 +118,7 @@ def test_dpi(params: dict[str, int | tuple[int, int]], tmp_path: Path) -> None:
|
|||
im = hopper()
|
||||
|
||||
outfile = str(tmp_path / "temp.pdf")
|
||||
im.save(outfile, **params)
|
||||
im.save(outfile, "PDF", **params)
|
||||
|
||||
with open(outfile, "rb") as fp:
|
||||
contents = fp.read()
|
||||
|
|
|
@ -535,8 +535,10 @@ class TestFilePng:
|
|||
|
||||
def test_repr_png(self) -> None:
|
||||
im = hopper()
|
||||
b = im._repr_png_()
|
||||
assert b is not None
|
||||
|
||||
with Image.open(BytesIO(im._repr_png_())) as repr_png:
|
||||
with Image.open(BytesIO(b)) as repr_png:
|
||||
assert repr_png.format == "PNG"
|
||||
assert_image_equal(im, repr_png)
|
||||
|
||||
|
@ -655,11 +657,12 @@ class TestFilePng:
|
|||
png.call(cid, 0, 0)
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||
|
||||
def test_specify_bits(self, tmp_path: Path) -> None:
|
||||
@pytest.mark.parametrize("save_all", (True, False))
|
||||
def test_specify_bits(self, save_all: bool, tmp_path: Path) -> None:
|
||||
im = hopper("P")
|
||||
|
||||
out = str(tmp_path / "temp.png")
|
||||
im.save(out, bits=4)
|
||||
im.save(out, bits=4, save_all=save_all)
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert len(reloaded.png.im_palette[1]) == 48
|
||||
|
@ -683,6 +686,7 @@ class TestFilePng:
|
|||
):
|
||||
assert im.getxmp() == {}
|
||||
else:
|
||||
assert "xmp" in im.info
|
||||
xmp = im.getxmp()
|
||||
|
||||
description = xmp["xmpmeta"]["RDF"]["Description"]
|
||||
|
@ -767,16 +771,12 @@ class TestFilePng:
|
|||
def test_save_stdout(self, buffer: bool) -> None:
|
||||
old_stdout = sys.stdout
|
||||
|
||||
if buffer:
|
||||
class MyStdOut:
|
||||
buffer = BytesIO()
|
||||
|
||||
class MyStdOut:
|
||||
buffer = BytesIO()
|
||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||
|
||||
mystdout = MyStdOut()
|
||||
else:
|
||||
mystdout = BytesIO()
|
||||
|
||||
sys.stdout = mystdout
|
||||
sys.stdout = mystdout # type: ignore[assignment]
|
||||
|
||||
with Image.open(TEST_PNG_FILE) as im:
|
||||
im.save(sys.stdout, "PNG")
|
||||
|
@ -784,7 +784,7 @@ class TestFilePng:
|
|||
# Reset stdout
|
||||
sys.stdout = old_stdout
|
||||
|
||||
if buffer:
|
||||
if isinstance(mystdout, MyStdOut):
|
||||
mystdout = mystdout.buffer
|
||||
with Image.open(mystdout) as reloaded:
|
||||
assert_image_equal_tofile(reloaded, TEST_PNG_FILE)
|
||||
|
|
|
@ -368,16 +368,12 @@ def test_mimetypes(tmp_path: Path) -> None:
|
|||
def test_save_stdout(buffer: bool) -> None:
|
||||
old_stdout = sys.stdout
|
||||
|
||||
if buffer:
|
||||
class MyStdOut:
|
||||
buffer = BytesIO()
|
||||
|
||||
class MyStdOut:
|
||||
buffer = BytesIO()
|
||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||
|
||||
mystdout = MyStdOut()
|
||||
else:
|
||||
mystdout = BytesIO()
|
||||
|
||||
sys.stdout = mystdout
|
||||
sys.stdout = mystdout # type: ignore[assignment]
|
||||
|
||||
with Image.open(TEST_FILE) as im:
|
||||
im.save(sys.stdout, "PPM")
|
||||
|
@ -385,7 +381,7 @@ def test_save_stdout(buffer: bool) -> None:
|
|||
# Reset stdout
|
||||
sys.stdout = old_stdout
|
||||
|
||||
if buffer:
|
||||
if isinstance(mystdout, MyStdOut):
|
||||
mystdout = mystdout.buffer
|
||||
with Image.open(mystdout) as reloaded:
|
||||
assert_image_equal_tofile(reloaded, TEST_FILE)
|
||||
|
|
|
@ -4,7 +4,7 @@ import warnings
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, PsdImagePlugin, UnidentifiedImageError
|
||||
from PIL import Image, PsdImagePlugin
|
||||
|
||||
from .helper import assert_image_equal_tofile, assert_image_similar, hopper, is_pypy
|
||||
|
||||
|
@ -150,20 +150,26 @@ def test_combined_larger_than_size() -> None:
|
|||
@pytest.mark.parametrize(
|
||||
"test_file,raises",
|
||||
[
|
||||
(
|
||||
"Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd",
|
||||
UnidentifiedImageError,
|
||||
),
|
||||
(
|
||||
"Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd",
|
||||
UnidentifiedImageError,
|
||||
),
|
||||
("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError),
|
||||
("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError),
|
||||
],
|
||||
)
|
||||
def test_crashes(test_file: str, raises) -> None:
|
||||
def test_crashes(test_file: str, raises: type[Exception]) -> None:
|
||||
with open(test_file, "rb") as f:
|
||||
with pytest.raises(raises):
|
||||
with Image.open(f):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_file",
|
||||
[
|
||||
"Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd",
|
||||
"Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd",
|
||||
],
|
||||
)
|
||||
def test_layer_crashes(test_file: str) -> None:
|
||||
with open(test_file, "rb") as f:
|
||||
with Image.open(f) as im:
|
||||
with pytest.raises(SyntaxError):
|
||||
im.layers
|
||||
|
|
|
@ -105,6 +105,7 @@ def test_load_image_series() -> None:
|
|||
img_list = SpiderImagePlugin.loadImageSeries(file_list)
|
||||
|
||||
# Assert
|
||||
assert img_list is not None
|
||||
assert len(img_list) == 1
|
||||
assert isinstance(img_list[0], Image.Image)
|
||||
assert img_list[0].size == (128, 128)
|
||||
|
|
|
@ -72,12 +72,21 @@ def test_palette_depth_8(tmp_path: Path) -> None:
|
|||
|
||||
def test_palette_depth_16(tmp_path: Path) -> None:
|
||||
with Image.open("Tests/images/p_16.tga") as im:
|
||||
assert_image_equal_tofile(im.convert("RGB"), "Tests/images/p_16.png")
|
||||
assert im.palette.mode == "RGBA"
|
||||
assert_image_equal_tofile(im.convert("RGBA"), "Tests/images/p_16.png")
|
||||
|
||||
out = str(tmp_path / "temp.png")
|
||||
im.save(out)
|
||||
with Image.open(out) as reloaded:
|
||||
assert_image_equal_tofile(reloaded.convert("RGB"), "Tests/images/p_16.png")
|
||||
assert_image_equal_tofile(reloaded.convert("RGBA"), "Tests/images/p_16.png")
|
||||
|
||||
|
||||
def test_rgba_16() -> None:
|
||||
with Image.open("Tests/images/rgba16.tga") as im:
|
||||
assert im.mode == "RGBA"
|
||||
|
||||
assert im.getpixel((0, 0)) == (172, 0, 255, 255)
|
||||
assert im.getpixel((1, 0)) == (0, 255, 82, 0)
|
||||
|
||||
|
||||
def test_id_field() -> None:
|
||||
|
|
|
@ -2,10 +2,10 @@ from __future__ import annotations
|
|||
|
||||
import os
|
||||
import warnings
|
||||
from collections.abc import Generator
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Generator
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -78,6 +78,7 @@ class TestFileTiff:
|
|||
|
||||
def test_seek_after_close(self) -> None:
|
||||
im = Image.open("Tests/images/multipage.tiff")
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
im.close()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
|
@ -120,7 +121,7 @@ class TestFileTiff:
|
|||
def test_set_legacy_api(self) -> None:
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
with pytest.raises(Exception) as e:
|
||||
ifd.legacy_api = None
|
||||
ifd.legacy_api = False
|
||||
assert str(e.value) == "Not allowing setting of legacy api"
|
||||
|
||||
def test_xyres_tiff(self) -> None:
|
||||
|
@ -424,13 +425,13 @@ class TestFileTiff:
|
|||
def test_load_float(self) -> None:
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
data = b"abcdabcd"
|
||||
ret = ifd.load_float(data, False)
|
||||
ret = getattr(ifd, "load_float")(data, False)
|
||||
assert ret == (1.6777999408082104e22, 1.6777999408082104e22)
|
||||
|
||||
def test_load_double(self) -> None:
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
data = b"abcdefghabcdefgh"
|
||||
ret = ifd.load_double(data, False)
|
||||
ret = getattr(ifd, "load_double")(data, False)
|
||||
assert ret == (8.540883223036124e194, 8.540883223036124e194)
|
||||
|
||||
def test_ifd_tag_type(self) -> None:
|
||||
|
@ -599,7 +600,7 @@ class TestFileTiff:
|
|||
def test_with_underscores(self, tmp_path: Path) -> None:
|
||||
kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36}
|
||||
filename = str(tmp_path / "temp.tif")
|
||||
hopper("RGB").save(filename, **kwargs)
|
||||
hopper("RGB").save(filename, "TIFF", **kwargs)
|
||||
with Image.open(filename) as im:
|
||||
# legacy interface
|
||||
assert im.tag[X_RESOLUTION][0][0] == 72
|
||||
|
@ -621,6 +622,22 @@ class TestFileTiff:
|
|||
|
||||
assert_image_equal_tofile(im, tmpfile)
|
||||
|
||||
def test_iptc(self, tmp_path: Path) -> None:
|
||||
# Do not preserve IPTC_NAA_CHUNK by default if type is LONG
|
||||
outfile = str(tmp_path / "temp.tif")
|
||||
with Image.open("Tests/images/hopper.tif") as im:
|
||||
im.load()
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
ifd[33723] = 1
|
||||
ifd.tagtype[33723] = 4
|
||||
im.tag_v2 = ifd
|
||||
im.save(outfile)
|
||||
|
||||
with Image.open(outfile) as im:
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
assert 33723 not in im.tag_v2
|
||||
|
||||
def test_rowsperstrip(self, tmp_path: Path) -> None:
|
||||
outfile = str(tmp_path / "temp.tif")
|
||||
im = hopper()
|
||||
|
@ -759,6 +776,7 @@ class TestFileTiff:
|
|||
):
|
||||
assert im.getxmp() == {}
|
||||
else:
|
||||
assert "xmp" in im.info
|
||||
xmp = im.getxmp()
|
||||
|
||||
description = xmp["xmpmeta"]["RDF"]["Description"]
|
||||
|
|
|
@ -11,7 +11,11 @@ from PIL.TiffImagePlugin import IFDRational
|
|||
|
||||
from .helper import assert_deep_equal, hopper
|
||||
|
||||
TAG_IDS = {info.name: info.value for info in TiffTags.TAGS_V2.values()}
|
||||
TAG_IDS: dict[str, int] = {
|
||||
info.name: info.value
|
||||
for info in TiffTags.TAGS_V2.values()
|
||||
if info.value is not None
|
||||
}
|
||||
|
||||
|
||||
def test_rt_metadata(tmp_path: Path) -> None:
|
||||
|
@ -411,8 +415,8 @@ def test_empty_values() -> None:
|
|||
info = TiffImagePlugin.ImageFileDirectory_v2(head)
|
||||
info.load(data)
|
||||
# Should not raise ValueError.
|
||||
info = dict(info)
|
||||
assert 33432 in info
|
||||
info_dict = dict(info)
|
||||
assert 33432 in info_dict
|
||||
|
||||
|
||||
def test_photoshop_info(tmp_path: Path) -> None:
|
||||
|
|
|
@ -5,6 +5,7 @@ import re
|
|||
import sys
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -70,7 +71,9 @@ class TestFileWebp:
|
|||
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
|
||||
assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0)
|
||||
|
||||
def _roundtrip(self, tmp_path: Path, mode, epsilon, args={}) -> None:
|
||||
def _roundtrip(
|
||||
self, tmp_path: Path, mode: str, epsilon: float, args: dict[str, Any] = {}
|
||||
) -> None:
|
||||
temp_file = str(tmp_path / "temp.webp")
|
||||
|
||||
hopper(mode).save(temp_file, **args)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
@ -96,7 +97,9 @@ def test_write_animation_RGB(tmp_path: Path) -> None:
|
|||
check(temp_file1)
|
||||
|
||||
# Tests appending using a generator
|
||||
def im_generator(ims):
|
||||
def im_generator(
|
||||
ims: list[Image.Image],
|
||||
) -> Generator[Image.Image, None, None]:
|
||||
yield from ims
|
||||
|
||||
temp_file2 = str(tmp_path / "temp_generator.webp")
|
||||
|
|
|
@ -129,6 +129,7 @@ def test_getxmp() -> None:
|
|||
):
|
||||
assert im.getxmp() == {}
|
||||
else:
|
||||
assert "xmp" in im.info
|
||||
assert (
|
||||
im.getxmp()["xmpmeta"]["xmptk"]
|
||||
== "Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27 "
|
||||
|
|
|
@ -34,7 +34,7 @@ class TestDefaultFontLeak(TestTTypeFontLeak):
|
|||
|
||||
def test_leak(self) -> None:
|
||||
if features.check_module("freetype2"):
|
||||
ImageFont.core = _util.DeferredError(ImportError)
|
||||
ImageFont.core = _util.DeferredError(ImportError("Disabled for testing"))
|
||||
try:
|
||||
default_font = ImageFont.load_default()
|
||||
finally:
|
||||
|
|
|
@ -8,7 +8,8 @@ import sys
|
|||
import tempfile
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import IO
|
||||
from types import ModuleType
|
||||
from typing import IO, Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -35,6 +36,12 @@ from .helper import (
|
|||
skip_unless_feature,
|
||||
)
|
||||
|
||||
ElementTree: ModuleType | None
|
||||
try:
|
||||
from defusedxml import ElementTree
|
||||
except ImportError:
|
||||
ElementTree = None
|
||||
|
||||
|
||||
# Deprecation helper
|
||||
def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image:
|
||||
|
@ -129,6 +136,15 @@ class TestImage:
|
|||
assert im.mode == "RGB"
|
||||
assert im.size == (128, 128)
|
||||
|
||||
def test_open_verbose_failure(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(Image, "WARN_POSSIBLE_FORMATS", True)
|
||||
|
||||
im = io.BytesIO(b"")
|
||||
with pytest.warns(UserWarning):
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
with Image.open(im):
|
||||
pass
|
||||
|
||||
def test_width_height(self) -> None:
|
||||
im = Image.new("RGB", (1, 2))
|
||||
assert im.width == 1
|
||||
|
@ -179,11 +195,19 @@ class TestImage:
|
|||
def test_fp_name(self, tmp_path: Path) -> None:
|
||||
temp_file = str(tmp_path / "temp.jpg")
|
||||
|
||||
class FP:
|
||||
class FP(io.BytesIO):
|
||||
name: str
|
||||
|
||||
def write(self, b: bytes) -> None:
|
||||
pass
|
||||
if sys.version_info >= (3, 12):
|
||||
from collections.abc import Buffer
|
||||
|
||||
def write(self, data: Buffer) -> int:
|
||||
return len(data)
|
||||
|
||||
else:
|
||||
|
||||
def write(self, data: Any) -> int:
|
||||
return len(data)
|
||||
|
||||
fp = FP()
|
||||
fp.name = temp_file
|
||||
|
@ -352,8 +376,9 @@ class TestImage:
|
|||
img = Image.alpha_composite(dst, src)
|
||||
|
||||
# Assert
|
||||
img_colors = sorted(img.getcolors())
|
||||
assert img_colors == expected_colors
|
||||
img_colors = img.getcolors()
|
||||
assert img_colors is not None
|
||||
assert sorted(img_colors) == expected_colors
|
||||
|
||||
def test_alpha_inplace(self) -> None:
|
||||
src = Image.new("RGBA", (128, 128), "blue")
|
||||
|
@ -397,13 +422,13 @@ class TestImage:
|
|||
|
||||
# errors
|
||||
with pytest.raises(ValueError):
|
||||
source.alpha_composite(over, "invalid source")
|
||||
source.alpha_composite(over, "invalid destination") # type: ignore[arg-type]
|
||||
with pytest.raises(ValueError):
|
||||
source.alpha_composite(over, (0, 0), "invalid destination")
|
||||
source.alpha_composite(over, (0, 0), "invalid source") # type: ignore[arg-type]
|
||||
with pytest.raises(ValueError):
|
||||
source.alpha_composite(over, 0)
|
||||
source.alpha_composite(over, 0) # type: ignore[arg-type]
|
||||
with pytest.raises(ValueError):
|
||||
source.alpha_composite(over, (0, 0), 0)
|
||||
source.alpha_composite(over, (0, 0), 0) # type: ignore[arg-type]
|
||||
with pytest.raises(ValueError):
|
||||
source.alpha_composite(over, (0, 0), (0, -1))
|
||||
|
||||
|
@ -555,6 +580,7 @@ class TestImage:
|
|||
for mode in ("I", "F", "L"):
|
||||
im = Image.new(mode, (100, 100), (5,))
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
assert px[0, 0] == 5
|
||||
|
||||
def test_linear_gradient_wrong_mode(self) -> None:
|
||||
|
@ -649,7 +675,9 @@ class TestImage:
|
|||
|
||||
im_remapped = im.remap_palette([1, 0])
|
||||
assert im_remapped.info["transparency"] == 1
|
||||
assert len(im_remapped.getpalette()) == 6
|
||||
palette = im_remapped.getpalette()
|
||||
assert palette is not None
|
||||
assert len(palette) == 6
|
||||
|
||||
# Test unused transparency
|
||||
im.info["transparency"] = 2
|
||||
|
@ -680,7 +708,7 @@ class TestImage:
|
|||
else:
|
||||
assert new_image.palette is None
|
||||
|
||||
_make_new(im, im_p, ImagePalette.ImagePalette(list(range(256)) * 3))
|
||||
_make_new(im, im_p, ImagePalette.ImagePalette("RGB"))
|
||||
_make_new(im_p, im, None)
|
||||
_make_new(im, blank_p, ImagePalette.ImagePalette())
|
||||
_make_new(im, blank_pa, ImagePalette.ImagePalette())
|
||||
|
@ -913,6 +941,25 @@ class TestImage:
|
|||
assert tag not in exif.get_ifd(0x8769)
|
||||
assert exif.get_ifd(0xA005)
|
||||
|
||||
def test_empty_xmp(self) -> None:
|
||||
with Image.open("Tests/images/hopper.gif") as im:
|
||||
assert im.getxmp() == {}
|
||||
|
||||
def test_getxmp_padded(self) -> None:
|
||||
im = Image.new("RGB", (1, 1))
|
||||
im.info["xmp"] = (
|
||||
b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
|
||||
b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00'
|
||||
)
|
||||
if ElementTree is None:
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="XMP data cannot be read without defusedxml dependency",
|
||||
):
|
||||
assert im.getxmp() == {}
|
||||
else:
|
||||
assert im.getxmp() == {"xmpmeta": None}
|
||||
|
||||
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
|
||||
def test_zero_tobytes(self, size: tuple[int, int]) -> None:
|
||||
im = Image.new("RGB", size)
|
||||
|
|
|
@ -12,19 +12,6 @@ from PIL import Image
|
|||
|
||||
from .helper import assert_image_equal, hopper, is_win32
|
||||
|
||||
# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
|
||||
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
|
||||
cffi: ModuleType | None
|
||||
if os.environ.get("PYTHONOPTIMIZE") == "2":
|
||||
cffi = None
|
||||
else:
|
||||
try:
|
||||
import cffi
|
||||
|
||||
from PIL import PyAccess
|
||||
except ImportError:
|
||||
cffi = None
|
||||
|
||||
numpy: ModuleType | None
|
||||
try:
|
||||
import numpy
|
||||
|
@ -32,21 +19,7 @@ except ImportError:
|
|||
numpy = None
|
||||
|
||||
|
||||
class AccessTest:
|
||||
# Initial value
|
||||
_init_cffi_access = Image.USE_CFFI_ACCESS
|
||||
_need_cffi_access = False
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls) -> None:
|
||||
Image.USE_CFFI_ACCESS = cls._need_cffi_access
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls) -> None:
|
||||
Image.USE_CFFI_ACCESS = cls._init_cffi_access
|
||||
|
||||
|
||||
class TestImagePutPixel(AccessTest):
|
||||
class TestImagePutPixel:
|
||||
def test_sanity(self) -> None:
|
||||
im1 = hopper()
|
||||
im2 = Image.new(im1.mode, im1.size, 0)
|
||||
|
@ -54,7 +27,9 @@ class TestImagePutPixel(AccessTest):
|
|||
for y in range(im1.size[1]):
|
||||
for x in range(im1.size[0]):
|
||||
pos = x, y
|
||||
im2.putpixel(pos, im1.getpixel(pos))
|
||||
value = im1.getpixel(pos)
|
||||
assert value is not None
|
||||
im2.putpixel(pos, value)
|
||||
|
||||
assert_image_equal(im1, im2)
|
||||
|
||||
|
@ -64,7 +39,9 @@ class TestImagePutPixel(AccessTest):
|
|||
for y in range(im1.size[1]):
|
||||
for x in range(im1.size[0]):
|
||||
pos = x, y
|
||||
im2.putpixel(pos, im1.getpixel(pos))
|
||||
value = im1.getpixel(pos)
|
||||
assert value is not None
|
||||
im2.putpixel(pos, value)
|
||||
|
||||
assert not im2.readonly
|
||||
assert_image_equal(im1, im2)
|
||||
|
@ -74,10 +51,12 @@ class TestImagePutPixel(AccessTest):
|
|||
pix1 = im1.load()
|
||||
pix2 = im2.load()
|
||||
|
||||
assert pix1 is not None
|
||||
assert pix2 is not None
|
||||
with pytest.raises(TypeError):
|
||||
pix1[0, "0"]
|
||||
pix1[0, "0"] # type: ignore[index]
|
||||
with pytest.raises(TypeError):
|
||||
pix1["0", 0]
|
||||
pix1["0", 0] # type: ignore[index]
|
||||
|
||||
for y in range(im1.size[1]):
|
||||
for x in range(im1.size[0]):
|
||||
|
@ -96,7 +75,9 @@ class TestImagePutPixel(AccessTest):
|
|||
for y in range(-1, -im1.size[1] - 1, -1):
|
||||
for x in range(-1, -im1.size[0] - 1, -1):
|
||||
pos = x, y
|
||||
im2.putpixel(pos, im1.getpixel(pos))
|
||||
value = im1.getpixel(pos)
|
||||
assert value is not None
|
||||
im2.putpixel(pos, value)
|
||||
|
||||
assert_image_equal(im1, im2)
|
||||
|
||||
|
@ -106,7 +87,9 @@ class TestImagePutPixel(AccessTest):
|
|||
for y in range(-1, -im1.size[1] - 1, -1):
|
||||
for x in range(-1, -im1.size[0] - 1, -1):
|
||||
pos = x, y
|
||||
im2.putpixel(pos, im1.getpixel(pos))
|
||||
value = im1.getpixel(pos)
|
||||
assert value is not None
|
||||
im2.putpixel(pos, value)
|
||||
|
||||
assert not im2.readonly
|
||||
assert_image_equal(im1, im2)
|
||||
|
@ -116,6 +99,8 @@ class TestImagePutPixel(AccessTest):
|
|||
pix1 = im1.load()
|
||||
pix2 = im2.load()
|
||||
|
||||
assert pix1 is not None
|
||||
assert pix2 is not None
|
||||
for y in range(-1, -im1.size[1] - 1, -1):
|
||||
for x in range(-1, -im1.size[0] - 1, -1):
|
||||
pix2[x, y] = pix1[x, y]
|
||||
|
@ -125,13 +110,14 @@ class TestImagePutPixel(AccessTest):
|
|||
@pytest.mark.skipif(numpy is None, reason="NumPy not installed")
|
||||
def test_numpy(self) -> None:
|
||||
im = hopper()
|
||||
pix = im.load()
|
||||
px = im.load()
|
||||
|
||||
assert px is not None
|
||||
assert numpy is not None
|
||||
assert pix[numpy.int32(1), numpy.int32(2)] == (18, 20, 59)
|
||||
assert px[numpy.int32(1), numpy.int32(2)] == (18, 20, 59)
|
||||
|
||||
|
||||
class TestImageGetPixel(AccessTest):
|
||||
class TestImageGetPixel:
|
||||
@staticmethod
|
||||
def color(mode: str) -> int | tuple[int, ...]:
|
||||
bands = Image.getmodebands(mode)
|
||||
|
@ -144,9 +130,6 @@ class TestImageGetPixel(AccessTest):
|
|||
return tuple(range(1, bands + 1))
|
||||
|
||||
def check(self, mode: str, expected_color_int: int | None = None) -> None:
|
||||
if self._need_cffi_access and mode.startswith("BGR;"):
|
||||
pytest.skip("Support not added to deprecated module for BGR;* modes")
|
||||
|
||||
expected_color = (
|
||||
self.color(mode) if expected_color_int is None else expected_color_int
|
||||
)
|
||||
|
@ -171,15 +154,14 @@ class TestImageGetPixel(AccessTest):
|
|||
# Check 0x0 image with None initial color
|
||||
im = Image.new(mode, (0, 0), None)
|
||||
assert im.load() is not None
|
||||
error = ValueError if self._need_cffi_access else IndexError
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.putpixel((0, 0), expected_color)
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.getpixel((0, 0))
|
||||
# Check negative index
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.putpixel((-1, -1), expected_color)
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.getpixel((-1, -1))
|
||||
|
||||
# Check initial color
|
||||
|
@ -199,10 +181,10 @@ class TestImageGetPixel(AccessTest):
|
|||
|
||||
# Check 0x0 image with initial color
|
||||
im = Image.new(mode, (0, 0), expected_color)
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.getpixel((0, 0))
|
||||
# Check negative index
|
||||
with pytest.raises(error):
|
||||
with pytest.raises(IndexError):
|
||||
im.getpixel((-1, -1))
|
||||
|
||||
@pytest.mark.parametrize("mode", Image.MODES)
|
||||
|
@ -235,126 +217,7 @@ class TestImageGetPixel(AccessTest):
|
|||
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||
class TestCffiPutPixel(TestImagePutPixel):
|
||||
_need_cffi_access = True
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||
class TestCffiGetPixel(TestImageGetPixel):
|
||||
_need_cffi_access = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||
class TestCffi(AccessTest):
|
||||
_need_cffi_access = True
|
||||
|
||||
def _test_get_access(self, im: Image.Image) -> None:
|
||||
"""Do we get the same thing as the old pixel access
|
||||
|
||||
Using private interfaces, forcing a capi access and
|
||||
a pyaccess for the same image"""
|
||||
caccess = im.im.pixel_access(False)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, False)
|
||||
assert access is not None
|
||||
|
||||
w, h = im.size
|
||||
for x in range(0, w, 10):
|
||||
for y in range(0, h, 10):
|
||||
assert access[(x, y)] == caccess[(x, y)]
|
||||
|
||||
# Access an out-of-range pixel
|
||||
with pytest.raises(ValueError):
|
||||
access[(access.xsize + 1, access.ysize + 1)]
|
||||
|
||||
def test_get_vs_c(self) -> None:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
rgb = hopper("RGB")
|
||||
rgb.load()
|
||||
self._test_get_access(rgb)
|
||||
for mode in ("RGBA", "L", "LA", "1", "P", "F"):
|
||||
self._test_get_access(hopper(mode))
|
||||
|
||||
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
|
||||
im = Image.new(mode, (10, 10), 40000)
|
||||
self._test_get_access(im)
|
||||
|
||||
def _test_set_access(self, im: Image.Image, color: tuple[int, ...] | float) -> None:
|
||||
"""Are we writing the correct bits into the image?
|
||||
|
||||
Using private interfaces, forcing a capi access and
|
||||
a pyaccess for the same image"""
|
||||
caccess = im.im.pixel_access(False)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, False)
|
||||
assert access is not None
|
||||
|
||||
w, h = im.size
|
||||
for x in range(0, w, 10):
|
||||
for y in range(0, h, 10):
|
||||
access[(x, y)] = color
|
||||
assert color == caccess[(x, y)]
|
||||
|
||||
# Attempt to set the value on a read-only image
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, True)
|
||||
assert access is not None
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
access[(0, 0)] = color
|
||||
|
||||
def test_set_vs_c(self) -> None:
|
||||
rgb = hopper("RGB")
|
||||
with pytest.warns(DeprecationWarning):
|
||||
rgb.load()
|
||||
self._test_set_access(rgb, (255, 128, 0))
|
||||
self._test_set_access(hopper("RGBA"), (255, 192, 128, 0))
|
||||
self._test_set_access(hopper("L"), 128)
|
||||
self._test_set_access(hopper("LA"), (128, 128))
|
||||
self._test_set_access(hopper("1"), 255)
|
||||
self._test_set_access(hopper("P"), 128)
|
||||
self._test_set_access(hopper("PA"), (128, 128))
|
||||
self._test_set_access(hopper("F"), 1024.0)
|
||||
|
||||
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
|
||||
im = Image.new(mode, (10, 10), 40000)
|
||||
self._test_set_access(im, 45000)
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
def test_not_implemented(self) -> None:
|
||||
assert PyAccess.new(hopper("BGR;15")) is None
|
||||
|
||||
# Ref https://github.com/python-pillow/Pillow/pull/2009
|
||||
def test_reference_counting(self) -> None:
|
||||
size = 10
|
||||
|
||||
for _ in range(10):
|
||||
# Do not save references to the image, only to the access object
|
||||
with pytest.warns(DeprecationWarning):
|
||||
px = Image.new("L", (size, 1), 0).load()
|
||||
for i in range(size):
|
||||
# Pixels can contain garbage if image is released
|
||||
assert px[i, 0] == 0
|
||||
|
||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||
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)):
|
||||
im = Image.new(mode, (1, 1))
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, False)
|
||||
assert access is not None
|
||||
|
||||
access.putpixel((0, 0), color)
|
||||
|
||||
if len(color) == 3:
|
||||
color += (255,)
|
||||
assert im.convert("RGBA").getpixel((0, 0)) == color
|
||||
|
||||
|
||||
class TestImagePutPixelError(AccessTest):
|
||||
class TestImagePutPixelError:
|
||||
IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"]
|
||||
IMAGE_MODES2 = ["L", "I", "I;16"]
|
||||
INVALID_TYPES = ["foo", 1.0, None]
|
||||
|
@ -364,7 +227,7 @@ class TestImagePutPixelError(AccessTest):
|
|||
im = hopper(mode)
|
||||
for v in self.INVALID_TYPES:
|
||||
with pytest.raises(TypeError, match="color must be int or tuple"):
|
||||
im.putpixel((0, 0), v)
|
||||
im.putpixel((0, 0), v) # type: ignore[arg-type]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mode", "band_numbers", "match"),
|
||||
|
@ -398,7 +261,7 @@ class TestImagePutPixelError(AccessTest):
|
|||
with pytest.raises(
|
||||
TypeError, match="color must be int or single-element tuple"
|
||||
):
|
||||
im.putpixel((0, 0), v)
|
||||
im.putpixel((0, 0), v) # type: ignore[arg-type]
|
||||
|
||||
@pytest.mark.parametrize("mode", IMAGE_MODES1 + IMAGE_MODES2)
|
||||
def test_putpixel_overflow_error(self, mode: str) -> None:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
@ -13,13 +13,16 @@ numpy = pytest.importorskip("numpy", reason="NumPy not installed")
|
|||
|
||||
im = hopper().resize((128, 100))
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import numpy.typing as npt
|
||||
|
||||
|
||||
def test_toarray() -> None:
|
||||
def test(mode: str) -> tuple[tuple[int, ...], str, int]:
|
||||
ai = numpy.array(im.convert(mode))
|
||||
return ai.shape, ai.dtype.str, ai.nbytes
|
||||
|
||||
def test_with_dtype(dtype) -> None:
|
||||
def test_with_dtype(dtype: npt.DTypeLike) -> None:
|
||||
ai = numpy.array(im, dtype=dtype)
|
||||
assert ai.dtype == dtype
|
||||
|
||||
|
|
|
@ -222,8 +222,10 @@ def test_l_macro_rounding(convert_mode: str) -> None:
|
|||
|
||||
converted_im = im.convert(convert_mode)
|
||||
px = converted_im.load()
|
||||
assert px is not None
|
||||
converted_color = px[0, 0]
|
||||
if convert_mode == "LA":
|
||||
assert isinstance(converted_color, tuple)
|
||||
converted_color = converted_color[0]
|
||||
assert converted_color == 1
|
||||
|
||||
|
|
|
@ -16,7 +16,9 @@ def draft_roundtrip(
|
|||
im = Image.new(in_mode, in_size)
|
||||
data = tostring(im, "JPEG")
|
||||
im = fromstring(data)
|
||||
mode, box = im.draft(req_mode, req_size)
|
||||
result = im.draft(req_mode, req_size)
|
||||
assert result is not None
|
||||
box = result[1]
|
||||
scale, _ = im.decoderconfig
|
||||
assert box[:2] == (0, 0)
|
||||
assert (im.width - scale) < box[2] <= im.width
|
||||
|
|
|
@ -137,7 +137,7 @@ def test_builtinfilter_p() -> None:
|
|||
builtin_filter = ImageFilter.BuiltinFilter()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
builtin_filter.filter(hopper("P"))
|
||||
builtin_filter.filter(hopper("P").im)
|
||||
|
||||
|
||||
def test_kernel_not_enough_coefficients() -> None:
|
||||
|
|
|
@ -54,17 +54,21 @@ def test_pack() -> None:
|
|||
assert A is None
|
||||
|
||||
A = im.getcolors(maxcolors=3)
|
||||
assert A is not None
|
||||
A.sort()
|
||||
assert A == expected
|
||||
|
||||
A = im.getcolors(maxcolors=4)
|
||||
assert A is not None
|
||||
A.sort()
|
||||
assert A == expected
|
||||
|
||||
A = im.getcolors(maxcolors=8)
|
||||
assert A is not None
|
||||
A.sort()
|
||||
assert A == expected
|
||||
|
||||
A = im.getcolors(maxcolors=16)
|
||||
assert A is not None
|
||||
A.sort()
|
||||
assert A == expected
|
||||
|
|
|
@ -12,9 +12,10 @@ from .helper import hopper
|
|||
|
||||
def test_sanity() -> None:
|
||||
im = hopper()
|
||||
pix = im.load()
|
||||
px = im.load()
|
||||
|
||||
assert pix[0, 0] == (20, 20, 70)
|
||||
assert px is not None
|
||||
assert px[0, 0] == (20, 20, 70)
|
||||
|
||||
|
||||
def test_close() -> None:
|
||||
|
|
|
@ -14,6 +14,7 @@ class TestImagingPaste:
|
|||
self, im: Image.Image, expected: list[tuple[int, int, int, int]]
|
||||
) -> None:
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
actual = [
|
||||
px[0, 0],
|
||||
px[self.size // 2, 0],
|
||||
|
@ -48,6 +49,7 @@ class TestImagingPaste:
|
|||
def mask_1(self) -> Image.Image:
|
||||
mask = Image.new("1", (self.size, self.size))
|
||||
px = mask.load()
|
||||
assert px is not None
|
||||
for y in range(mask.height):
|
||||
for x in range(mask.width):
|
||||
px[y, x] = (x + y) % 2
|
||||
|
@ -61,6 +63,7 @@ class TestImagingPaste:
|
|||
def gradient_L(self) -> Image.Image:
|
||||
gradient = Image.new("L", (self.size, self.size))
|
||||
px = gradient.load()
|
||||
assert px is not None
|
||||
for y in range(gradient.height):
|
||||
for x in range(gradient.width):
|
||||
px[y, x] = (x + y) % 255
|
||||
|
@ -338,3 +341,8 @@ class TestImagingPaste:
|
|||
|
||||
im.copy().paste(im2)
|
||||
im.copy().paste(im2, (0, 0))
|
||||
|
||||
def test_incorrect_abbreviated_form(self) -> None:
|
||||
im = Image.new("L", (1, 1))
|
||||
with pytest.raises(ValueError):
|
||||
im.paste(im, im, im)
|
||||
|
|
|
@ -61,4 +61,4 @@ def test_f_lut() -> None:
|
|||
def test_f_mode() -> None:
|
||||
im = hopper("F")
|
||||
with pytest.raises(ValueError):
|
||||
im.point(None)
|
||||
im.point([])
|
||||
|
|
|
@ -31,7 +31,7 @@ def test_sanity() -> None:
|
|||
|
||||
def test_long_integers() -> None:
|
||||
# see bug-200802-systemerror
|
||||
def put(value: int) -> tuple[int, int, int, int]:
|
||||
def put(value: int) -> float | tuple[int, ...] | None:
|
||||
im = Image.new("RGBA", (1, 1))
|
||||
im.putdata([value])
|
||||
return im.getpixel((0, 0))
|
||||
|
@ -113,13 +113,13 @@ def test_array_F() -> None:
|
|||
def test_not_flattened() -> None:
|
||||
im = Image.new("L", (1, 1))
|
||||
with pytest.raises(TypeError):
|
||||
im.putdata([[0]]) # type: ignore[list-item]
|
||||
im.putdata([[0]])
|
||||
with pytest.raises(TypeError):
|
||||
im.putdata([[0]], 2) # type: ignore[list-item]
|
||||
im.putdata([[0]], 2)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
im = Image.new("I", (1, 1))
|
||||
im.putdata([[0]]) # type: ignore[list-item]
|
||||
im.putdata([[0]])
|
||||
with pytest.raises(TypeError):
|
||||
im = Image.new("F", (1, 1))
|
||||
im.putdata([[0]]) # type: ignore[list-item]
|
||||
im.putdata([[0]])
|
||||
|
|
|
@ -79,6 +79,7 @@ def test_putpalette_with_alpha_values() -> None:
|
|||
(
|
||||
("RGBA", (1, 2, 3, 4)),
|
||||
("RGBAX", (1, 2, 3, 4, 0)),
|
||||
("ARGB", (4, 1, 2, 3)),
|
||||
),
|
||||
)
|
||||
def test_rgba_palette(mode: str, palette: tuple[int, ...]) -> None:
|
||||
|
|
|
@ -31,7 +31,9 @@ def test_libimagequant_quantize() -> None:
|
|||
converted = image.quantize(100, Image.Quantize.LIBIMAGEQUANT)
|
||||
assert converted.mode == "P"
|
||||
assert_image_similar(converted.convert("RGB"), image, 15)
|
||||
assert len(converted.getcolors()) == 100
|
||||
colors = converted.getcolors()
|
||||
assert colors is not None
|
||||
assert len(colors) == 100
|
||||
|
||||
|
||||
def test_octree_quantize() -> None:
|
||||
|
@ -39,7 +41,9 @@ def test_octree_quantize() -> None:
|
|||
converted = image.quantize(100, Image.Quantize.FASTOCTREE)
|
||||
assert converted.mode == "P"
|
||||
assert_image_similar(converted.convert("RGB"), image, 20)
|
||||
assert len(converted.getcolors()) == 100
|
||||
colors = converted.getcolors()
|
||||
assert colors is not None
|
||||
assert len(colors) == 100
|
||||
|
||||
|
||||
def test_rgba_quantize() -> None:
|
||||
|
@ -80,6 +84,7 @@ def test_quantize_no_dither2() -> None:
|
|||
assert tuple(quantized.palette.palette) == data
|
||||
|
||||
px = quantized.load()
|
||||
assert px is not None
|
||||
for x in range(9):
|
||||
assert px[x, 0] == (0 if x < 5 else 1)
|
||||
|
||||
|
@ -118,10 +123,12 @@ def test_colors() -> None:
|
|||
def test_transparent_colors_equal() -> None:
|
||||
im = Image.new("RGBA", (1, 2), (0, 0, 0, 0))
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
px[0, 1] = (255, 255, 255, 0)
|
||||
|
||||
converted = im.quantize()
|
||||
converted_px = converted.load()
|
||||
assert converted_px is not None
|
||||
assert converted_px[0, 0] == converted_px[0, 1]
|
||||
|
||||
|
||||
|
@ -139,6 +146,7 @@ def test_palette(method: Image.Quantize, color: tuple[int, ...]) -> None:
|
|||
|
||||
converted = im.quantize(method=method)
|
||||
converted_px = converted.load()
|
||||
assert converted_px is not None
|
||||
assert converted_px[0, 0] == converted.palette.colors[color]
|
||||
|
||||
|
||||
|
@ -154,4 +162,6 @@ def test_small_palette() -> None:
|
|||
im = im.quantize(palette=p)
|
||||
|
||||
# Assert
|
||||
assert len(im.getcolors()) == 2
|
||||
quantized_colors = im.getcolors()
|
||||
assert quantized_colors is not None
|
||||
assert len(quantized_colors) == 2
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from contextlib import contextmanager
|
||||
from typing import Generator
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -74,6 +74,7 @@ class TestImagingCoreResampleAccuracy:
|
|||
data = data.replace(" ", "")
|
||||
sample = Image.new("L", size)
|
||||
s_px = sample.load()
|
||||
assert s_px is not None
|
||||
w, h = size[0] // 2, size[1] // 2
|
||||
for y in range(h):
|
||||
for x in range(w):
|
||||
|
@ -87,6 +88,8 @@ class TestImagingCoreResampleAccuracy:
|
|||
def check_case(self, case: Image.Image, sample: Image.Image) -> None:
|
||||
s_px = sample.load()
|
||||
c_px = case.load()
|
||||
assert s_px is not None
|
||||
assert c_px is not None
|
||||
for y in range(case.size[1]):
|
||||
for x in range(case.size[0]):
|
||||
if c_px[x, y] != s_px[x, y]:
|
||||
|
@ -98,6 +101,7 @@ class TestImagingCoreResampleAccuracy:
|
|||
|
||||
def serialize_image(self, image: Image.Image) -> str:
|
||||
s_px = image.load()
|
||||
assert s_px is not None
|
||||
return "\n".join(
|
||||
" ".join(f"{s_px[x, y]:02x}" for x in range(image.size[0]))
|
||||
for y in range(image.size[1])
|
||||
|
@ -233,13 +237,16 @@ class TestImagingCoreResampleAccuracy:
|
|||
class TestCoreResampleConsistency:
|
||||
def make_case(
|
||||
self, mode: str, fill: tuple[int, int, int] | float
|
||||
) -> tuple[Image.Image, tuple[int, ...]]:
|
||||
) -> tuple[Image.Image, float | tuple[int, ...]]:
|
||||
im = Image.new(mode, (512, 9), fill)
|
||||
return im.resize((9, 512), Image.Resampling.LANCZOS), im.load()[0, 0]
|
||||
px = im.load()
|
||||
assert px is not None
|
||||
return im.resize((9, 512), Image.Resampling.LANCZOS), px[0, 0]
|
||||
|
||||
def run_case(self, case: tuple[Image.Image, int | tuple[int, ...]]) -> None:
|
||||
def run_case(self, case: tuple[Image.Image, float | tuple[int, ...]]) -> None:
|
||||
channel, color = case
|
||||
px = channel.load()
|
||||
assert px is not None
|
||||
for x in range(channel.size[0]):
|
||||
for y in range(channel.size[1]):
|
||||
if px[x, y] != color:
|
||||
|
@ -249,6 +256,7 @@ class TestCoreResampleConsistency:
|
|||
def test_8u(self) -> None:
|
||||
im, color = self.make_case("RGB", (0, 64, 255))
|
||||
r, g, b = im.split()
|
||||
assert isinstance(color, tuple)
|
||||
self.run_case((r, color[0]))
|
||||
self.run_case((g, color[1]))
|
||||
self.run_case((b, color[2]))
|
||||
|
@ -271,6 +279,7 @@ class TestCoreResampleAlphaCorrect:
|
|||
def make_levels_case(self, mode: str) -> Image.Image:
|
||||
i = Image.new(mode, (256, 16))
|
||||
px = i.load()
|
||||
assert px is not None
|
||||
for y in range(i.size[1]):
|
||||
for x in range(i.size[0]):
|
||||
pix = [x] * len(mode)
|
||||
|
@ -280,8 +289,13 @@ class TestCoreResampleAlphaCorrect:
|
|||
|
||||
def run_levels_case(self, i: Image.Image) -> None:
|
||||
px = i.load()
|
||||
assert px is not None
|
||||
for y in range(i.size[1]):
|
||||
used_colors = {px[x, y][0] for x in range(i.size[0])}
|
||||
used_colors = set()
|
||||
for x in range(i.size[0]):
|
||||
value = px[x, y]
|
||||
assert isinstance(value, tuple)
|
||||
used_colors.add(value[0])
|
||||
assert 256 == len(used_colors), (
|
||||
"All colors should be present in resized image. "
|
||||
f"Only {len(used_colors)} on line {y}."
|
||||
|
@ -310,6 +324,7 @@ class TestCoreResampleAlphaCorrect:
|
|||
) -> Image.Image:
|
||||
i = Image.new(mode, (64, 64), dirty_pixel)
|
||||
px = i.load()
|
||||
assert px is not None
|
||||
xdiv4 = i.size[0] // 4
|
||||
ydiv4 = i.size[1] // 4
|
||||
for y in range(ydiv4 * 2):
|
||||
|
@ -319,14 +334,16 @@ class TestCoreResampleAlphaCorrect:
|
|||
|
||||
def run_dirty_case(self, i: Image.Image, clean_pixel: tuple[int, ...]) -> None:
|
||||
px = i.load()
|
||||
assert px is not None
|
||||
for y in range(i.size[1]):
|
||||
for x in range(i.size[0]):
|
||||
if px[x, y][-1] != 0 and px[x, y][:-1] != clean_pixel:
|
||||
value = px[x, y]
|
||||
assert isinstance(value, tuple)
|
||||
if value[-1] != 0 and value[:-1] != clean_pixel:
|
||||
message = (
|
||||
f"pixel at ({x}, {y}) is different:\n"
|
||||
f"{px[x, y]}\n{clean_pixel}"
|
||||
f"pixel at ({x}, {y}) is different:\n{value}\n{clean_pixel}"
|
||||
)
|
||||
assert px[x, y][:3] == clean_pixel, message
|
||||
assert value[:3] == clean_pixel, message
|
||||
|
||||
def test_dirty_pixels_rgba(self) -> None:
|
||||
case = self.make_dirty_case("RGBA", (255, 255, 0, 128), (0, 0, 255, 0))
|
||||
|
@ -406,6 +423,7 @@ class TestCoreResampleCoefficients:
|
|||
draw.rectangle((0, 0, i.size[0] // 2 - 1, 0), test_color)
|
||||
|
||||
px = i.resize((5, i.size[1]), Image.Resampling.BICUBIC).load()
|
||||
assert px is not None
|
||||
if px[2, 0] != test_color // 2:
|
||||
assert test_color // 2 == px[2, 0]
|
||||
|
||||
|
@ -445,7 +463,7 @@ class TestCoreResampleBox:
|
|||
im.resize((32, 32), resample, (20, 20, 100, 20))
|
||||
|
||||
with pytest.raises(TypeError, match="must be sequence of length 4"):
|
||||
im.resize((32, 32), resample, (im.width, im.height))
|
||||
im.resize((32, 32), resample, (im.width, im.height)) # type: ignore[arg-type]
|
||||
|
||||
with pytest.raises(ValueError, match="can't be negative"):
|
||||
im.resize((32, 32), resample, (-20, 20, 100, 100))
|
||||
|
|
|
@ -4,9 +4,9 @@ Tests for resize functionality.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from itertools import permutations
|
||||
from pathlib import Path
|
||||
from typing import Generator
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -285,14 +285,14 @@ class TestReducingGapResize:
|
|||
|
||||
class TestImageResize:
|
||||
def test_resize(self) -> None:
|
||||
def resize(mode: str, size: tuple[int, int]) -> None:
|
||||
def resize(mode: str, size: tuple[int, int] | list[int]) -> None:
|
||||
out = hopper(mode).resize(size)
|
||||
assert out.mode == mode
|
||||
assert out.size == size
|
||||
assert out.size == tuple(size)
|
||||
|
||||
for mode in "1", "P", "L", "RGB", "I", "F":
|
||||
resize(mode, (112, 103))
|
||||
resize(mode, (188, 214))
|
||||
resize(mode, [188, 214])
|
||||
|
||||
# Test unknown resampling filter
|
||||
with hopper() as im:
|
||||
|
|
|
@ -192,8 +192,9 @@ class TestImageTransform:
|
|||
|
||||
im = op(im, (40, 10))
|
||||
|
||||
colors = sorted(im.getcolors())
|
||||
assert colors == sorted(
|
||||
colors = im.getcolors()
|
||||
assert colors is not None
|
||||
assert sorted(colors) == sorted(
|
||||
(
|
||||
(20 * 10, opaque),
|
||||
(20 * 10, transparent),
|
||||
|
|
|
@ -391,23 +391,25 @@ def test_overlay() -> None:
|
|||
def test_logical() -> None:
|
||||
def table(
|
||||
op: Callable[[Image.Image, Image.Image], Image.Image], a: int, b: int
|
||||
) -> tuple[int, int, int, int]:
|
||||
) -> list[float]:
|
||||
out = []
|
||||
for x in (a, b):
|
||||
imx = Image.new("1", (1, 1), x)
|
||||
for y in (a, b):
|
||||
imy = Image.new("1", (1, 1), y)
|
||||
out.append(op(imx, imy).getpixel((0, 0)))
|
||||
return tuple(out)
|
||||
value = op(imx, imy).getpixel((0, 0))
|
||||
assert not isinstance(value, tuple) and value is not None
|
||||
out.append(value)
|
||||
return out
|
||||
|
||||
assert table(ImageChops.logical_and, 0, 1) == (0, 0, 0, 255)
|
||||
assert table(ImageChops.logical_or, 0, 1) == (0, 255, 255, 255)
|
||||
assert table(ImageChops.logical_xor, 0, 1) == (0, 255, 255, 0)
|
||||
assert table(ImageChops.logical_and, 0, 1) == [0, 0, 0, 255]
|
||||
assert table(ImageChops.logical_or, 0, 1) == [0, 255, 255, 255]
|
||||
assert table(ImageChops.logical_xor, 0, 1) == [0, 255, 255, 0]
|
||||
|
||||
assert table(ImageChops.logical_and, 0, 128) == (0, 0, 0, 255)
|
||||
assert table(ImageChops.logical_or, 0, 128) == (0, 255, 255, 255)
|
||||
assert table(ImageChops.logical_xor, 0, 128) == (0, 255, 255, 0)
|
||||
assert table(ImageChops.logical_and, 0, 128) == [0, 0, 0, 255]
|
||||
assert table(ImageChops.logical_or, 0, 128) == [0, 255, 255, 255]
|
||||
assert table(ImageChops.logical_xor, 0, 128) == [0, 255, 255, 0]
|
||||
|
||||
assert table(ImageChops.logical_and, 0, 255) == (0, 0, 0, 255)
|
||||
assert table(ImageChops.logical_or, 0, 255) == (0, 255, 255, 255)
|
||||
assert table(ImageChops.logical_xor, 0, 255) == (0, 255, 255, 0)
|
||||
assert table(ImageChops.logical_and, 0, 255) == [0, 0, 0, 255]
|
||||
assert table(ImageChops.logical_or, 0, 255) == [0, 255, 255, 255]
|
||||
assert table(ImageChops.logical_xor, 0, 255) == [0, 255, 255, 0]
|
||||
|
|
|
@ -103,7 +103,7 @@ def test_sanity() -> None:
|
|||
|
||||
|
||||
def test_flags() -> None:
|
||||
assert ImageCms.Flags.NONE == 0
|
||||
assert ImageCms.Flags.NONE.value == 0
|
||||
assert ImageCms.Flags.GRIDPOINTS(0) == ImageCms.Flags.NONE
|
||||
assert ImageCms.Flags.GRIDPOINTS(256) == ImageCms.Flags.NONE
|
||||
|
||||
|
@ -569,9 +569,9 @@ def assert_aux_channel_preserved(
|
|||
for delta in nine_grid_deltas:
|
||||
channel_data.paste(
|
||||
channel_pattern,
|
||||
tuple(
|
||||
paste_offset[c] + delta[c] * channel_pattern.size[c]
|
||||
for c in range(2)
|
||||
(
|
||||
paste_offset[0] + delta[0] * channel_pattern.size[0],
|
||||
paste_offset[1] + delta[1] * channel_pattern.size[1],
|
||||
),
|
||||
)
|
||||
chans.append(channel_data)
|
||||
|
@ -642,7 +642,8 @@ def test_auxiliary_channels_isolated() -> None:
|
|||
# convert with and without AUX data, test colors are equal
|
||||
src_colorSpace = cast(Literal["LAB", "XYZ", "sRGB"], src_format[1])
|
||||
source_profile = ImageCms.createProfile(src_colorSpace)
|
||||
destination_profile = ImageCms.createProfile(dst_format[1])
|
||||
dst_colorSpace = cast(Literal["LAB", "XYZ", "sRGB"], dst_format[1])
|
||||
destination_profile = ImageCms.createProfile(dst_colorSpace)
|
||||
source_image = src_format[3]
|
||||
test_transform = ImageCms.buildTransform(
|
||||
source_profile,
|
||||
|
@ -678,7 +679,8 @@ def test_auxiliary_channels_isolated() -> None:
|
|||
|
||||
def test_long_modes() -> None:
|
||||
p = ImageCms.getOpenProfile("Tests/icc/sGrey-v2-nano.icc")
|
||||
ImageCms.buildTransform(p, p, "ABCDEFGHI", "ABCDEFGHI")
|
||||
with pytest.warns(DeprecationWarning):
|
||||
ImageCms.buildTransform(p, p, "ABCDEFGHI", "ABCDEFGHI")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
|
||||
|
@ -689,7 +691,9 @@ def test_rgb_lab(mode: str) -> None:
|
|||
|
||||
im = Image.new("LAB", (1, 1), (255, 0, 0))
|
||||
converted_im = im.convert(mode)
|
||||
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
|
||||
value = converted_im.getpixel((0, 0))
|
||||
assert isinstance(value, tuple)
|
||||
assert value[:3] == (0, 255, 255)
|
||||
|
||||
|
||||
def test_deprecation() -> None:
|
||||
|
@ -699,3 +703,9 @@ def test_deprecation() -> None:
|
|||
assert ImageCms.VERSION == "1.0.0 pil"
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert isinstance(ImageCms.FLAGS, dict)
|
||||
|
||||
profile = ImageCmsProfile(ImageCms.createProfile("sRGB"))
|
||||
with pytest.warns(DeprecationWarning):
|
||||
ImageCms.ImageCmsTransform(profile, profile, "RGBA;16B", "RGB")
|
||||
with pytest.warns(DeprecationWarning):
|
||||
ImageCms.ImageCmsTransform(profile, profile, "RGB", "RGBA;16B")
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os.path
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -448,6 +448,7 @@ def test_shape1() -> None:
|
|||
x3, y3 = 95, 5
|
||||
|
||||
# Act
|
||||
assert ImageDraw.Outline is not None
|
||||
s = ImageDraw.Outline()
|
||||
s.move(x0, y0)
|
||||
s.curve(x1, y1, x2, y2, x3, y3)
|
||||
|
@ -469,6 +470,7 @@ def test_shape2() -> None:
|
|||
x3, y3 = 5, 95
|
||||
|
||||
# Act
|
||||
assert ImageDraw.Outline is not None
|
||||
s = ImageDraw.Outline()
|
||||
s.move(x0, y0)
|
||||
s.curve(x1, y1, x2, y2, x3, y3)
|
||||
|
@ -487,6 +489,7 @@ def test_transform() -> None:
|
|||
draw = ImageDraw.Draw(im)
|
||||
|
||||
# Act
|
||||
assert ImageDraw.Outline is not None
|
||||
s = ImageDraw.Outline()
|
||||
s.line(0, 0)
|
||||
s.transform((0, 0, 0, 0, 0, 0))
|
||||
|
@ -631,6 +634,19 @@ def test_polygon(points: Coords) -> None:
|
|||
assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("points", POINTS)
|
||||
def test_polygon_width_I16(points: Coords) -> None:
|
||||
# Arrange
|
||||
im = Image.new("I;16", (W, H))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
# Act
|
||||
draw.polygon(points, outline=0xFFFF, width=2)
|
||||
|
||||
# Assert
|
||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon_width_I.tiff")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
||||
@pytest.mark.parametrize("kite_points", KITE_POINTS)
|
||||
def test_polygon_kite(
|
||||
|
@ -913,7 +929,12 @@ def test_rounded_rectangle_translucent(
|
|||
def test_floodfill(bbox: Coords) -> None:
|
||||
red = ImageColor.getrgb("red")
|
||||
|
||||
for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]:
|
||||
mode_values: list[tuple[str, int | tuple[int, ...]]] = [
|
||||
("L", 1),
|
||||
("RGBA", (255, 0, 0, 0)),
|
||||
("RGB", red),
|
||||
]
|
||||
for mode, value in mode_values:
|
||||
# Arrange
|
||||
im = Image.new(mode, (W, H))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
@ -1401,25 +1422,44 @@ def test_default_font_size() -> None:
|
|||
|
||||
im = Image.new("RGB", (220, 25))
|
||||
draw = ImageDraw.Draw(im)
|
||||
with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):
|
||||
|
||||
def check(func: Callable[[], None]) -> None:
|
||||
if freetype_support:
|
||||
func()
|
||||
else:
|
||||
with pytest.raises(ImportError):
|
||||
func()
|
||||
|
||||
def draw_text() -> None:
|
||||
draw.text((0, 0), text, font_size=16)
|
||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
|
||||
|
||||
with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):
|
||||
check(draw_text)
|
||||
|
||||
def draw_textlength() -> None:
|
||||
assert draw.textlength(text, font_size=16) == 216
|
||||
|
||||
with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):
|
||||
check(draw_textlength)
|
||||
|
||||
def draw_textbbox() -> None:
|
||||
assert draw.textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19)
|
||||
|
||||
check(draw_textbbox)
|
||||
|
||||
im = Image.new("RGB", (220, 25))
|
||||
draw = ImageDraw.Draw(im)
|
||||
with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):
|
||||
|
||||
def draw_multiline_text() -> None:
|
||||
draw.multiline_text((0, 0), text, font_size=16)
|
||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
|
||||
|
||||
with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):
|
||||
check(draw_multiline_text)
|
||||
|
||||
def draw_multiline_textbbox() -> None:
|
||||
assert draw.multiline_textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19)
|
||||
|
||||
check(draw_multiline_textbbox)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bbox", BBOX)
|
||||
def test_same_color_outline(bbox: Coords) -> None:
|
||||
|
@ -1429,6 +1469,7 @@ def test_same_color_outline(bbox: Coords) -> None:
|
|||
x2, y2 = 95, 50
|
||||
x3, y3 = 95, 5
|
||||
|
||||
assert ImageDraw.Outline is not None
|
||||
s = ImageDraw.Outline()
|
||||
s.move(x0, y0)
|
||||
s.curve(x1, y1, x2, y2, x3, y3)
|
||||
|
@ -1467,7 +1508,7 @@ def test_same_color_outline(bbox: Coords) -> None:
|
|||
(4, "square", {}),
|
||||
(8, "regular_octagon", {}),
|
||||
(4, "square_rotate_45", {"rotation": 45}),
|
||||
(3, "triangle_width", {"width": 5, "outline": "yellow"}),
|
||||
(3, "triangle_width", {"outline": "yellow", "width": 5}),
|
||||
],
|
||||
)
|
||||
def test_draw_regular_polygon(
|
||||
|
@ -1477,7 +1518,10 @@ def test_draw_regular_polygon(
|
|||
filename = f"Tests/images/imagedraw_{polygon_name}.png"
|
||||
draw = ImageDraw.Draw(im)
|
||||
bounding_circle = ((W // 2, H // 2), 25)
|
||||
draw.regular_polygon(bounding_circle, n_sides, fill="red", **args)
|
||||
rotation = int(args.get("rotation", 0))
|
||||
outline = args.get("outline")
|
||||
width = int(args.get("width", 1))
|
||||
draw.regular_polygon(bounding_circle, n_sides, rotation, "red", outline, width)
|
||||
assert_image_equal_tofile(im, filename)
|
||||
|
||||
|
||||
|
@ -1569,7 +1613,7 @@ def test_compute_regular_polygon_vertices_input_error_handling(
|
|||
error_message: str,
|
||||
) -> None:
|
||||
with pytest.raises(expected_error) as e:
|
||||
ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
|
||||
ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) # type: ignore[arg-type]
|
||||
assert str(e.value) == error_message
|
||||
|
||||
|
||||
|
@ -1630,6 +1674,6 @@ def test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None:
|
|||
draw.rounded_rectangle(xy)
|
||||
|
||||
|
||||
def test_getdraw():
|
||||
def test_getdraw() -> None:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
ImageDraw.getdraw(None, [])
|
||||
|
|
|
@ -51,9 +51,18 @@ def test_sanity() -> None:
|
|||
pen = ImageDraw2.Pen("blue", width=7)
|
||||
draw.line(list(range(10)), pen)
|
||||
|
||||
draw, handler = ImageDraw.getdraw(im)
|
||||
draw2, handler = ImageDraw.getdraw(im)
|
||||
assert draw2 is not None
|
||||
pen = ImageDraw2.Pen("blue", width=7)
|
||||
draw.line(list(range(10)), pen)
|
||||
draw2.line(list(range(10)), pen)
|
||||
|
||||
|
||||
def test_mode() -> None:
|
||||
draw = ImageDraw2.Draw("L", (1, 1))
|
||||
assert draw.image.mode == "L"
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ImageDraw2.Draw("L")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bbox", BBOX)
|
||||
|
|
|
@ -209,7 +209,7 @@ class MockPyDecoder(ImageFile.PyDecoder):
|
|||
|
||||
super().__init__(mode, *args)
|
||||
|
||||
def decode(self, buffer):
|
||||
def decode(self, buffer: bytes) -> tuple[int, int]:
|
||||
# eof
|
||||
return -1, 0
|
||||
|
||||
|
@ -222,7 +222,7 @@ class MockPyEncoder(ImageFile.PyEncoder):
|
|||
|
||||
super().__init__(mode, *args)
|
||||
|
||||
def encode(self, buffer):
|
||||
def encode(self, bufsize: int) -> tuple[int, int, bytes]:
|
||||
return 1, 1, b""
|
||||
|
||||
def cleanup(self) -> None:
|
||||
|
@ -305,7 +305,7 @@ class TestPyDecoder(CodecsTest):
|
|||
def test_decode(self) -> None:
|
||||
decoder = ImageFile.PyDecoder(None)
|
||||
with pytest.raises(NotImplementedError):
|
||||
decoder.decode(None)
|
||||
decoder.decode(b"")
|
||||
|
||||
|
||||
class TestPyEncoder(CodecsTest):
|
||||
|
@ -351,7 +351,9 @@ class TestPyEncoder(CodecsTest):
|
|||
ImageFile._save(
|
||||
im, fp, [("MOCK", (xoff, yoff, -10, yoff + ysize), 0, "RGB")]
|
||||
)
|
||||
assert MockPyEncoder.last.cleanup_called
|
||||
last: MockPyEncoder | None = MockPyEncoder.last
|
||||
assert last
|
||||
assert last.cleanup_called
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ImageFile._save(
|
||||
|
@ -381,7 +383,7 @@ class TestPyEncoder(CodecsTest):
|
|||
def test_encode(self) -> None:
|
||||
encoder = ImageFile.PyEncoder(None)
|
||||
with pytest.raises(NotImplementedError):
|
||||
encoder.encode(None)
|
||||
encoder.encode(0)
|
||||
|
||||
bytes_consumed, errcode = encoder.encode_to_pyfd()
|
||||
assert bytes_consumed == 0
|
||||
|
|
|
@ -209,7 +209,7 @@ def test_getlength(
|
|||
assert length == length_raqm
|
||||
|
||||
|
||||
def test_float_size() -> None:
|
||||
def test_float_size(layout_engine: ImageFont.Layout) -> None:
|
||||
lengths = []
|
||||
for size in (48, 48.5, 49):
|
||||
f = ImageFont.truetype(
|
||||
|
@ -494,8 +494,8 @@ def test_default_font() -> None:
|
|||
assert_image_equal_tofile(im, "Tests/images/default_font_freetype.png")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", (None, "1", "RGBA"))
|
||||
def test_getbbox(font: ImageFont.FreeTypeFont, mode: str | None) -> None:
|
||||
@pytest.mark.parametrize("mode", ("", "1", "RGBA"))
|
||||
def test_getbbox(font: ImageFont.FreeTypeFont, mode: str) -> None:
|
||||
assert (0, 4, 12, 16) == font.getbbox("A", mode)
|
||||
|
||||
|
||||
|
@ -548,7 +548,7 @@ def test_find_font(
|
|||
|
||||
def loadable_font(
|
||||
filepath: str, size: int, index: int, encoding: str, *args: Any
|
||||
):
|
||||
) -> ImageFont.FreeTypeFont:
|
||||
_freeTypeFont = getattr(ImageFont, "_FreeTypeFont")
|
||||
if filepath == path_to_fake:
|
||||
return _freeTypeFont(FONT_PATH, size, index, encoding, *args)
|
||||
|
@ -564,6 +564,7 @@ def test_find_font(
|
|||
# catching syntax like errors
|
||||
monkeypatch.setattr(sys, "platform", platform)
|
||||
if platform == "linux":
|
||||
monkeypatch.setenv("XDG_DATA_HOME", os.path.expanduser("~/.local/share"))
|
||||
monkeypatch.setenv("XDG_DATA_DIRS", "/usr/share/:/usr/local/share/")
|
||||
|
||||
def fake_walker(path: str) -> list[tuple[str, list[str], list[str]]]:
|
||||
|
@ -1096,6 +1097,23 @@ def test_too_many_characters(font: ImageFont.FreeTypeFont) -> None:
|
|||
imagefont.getmask("A" * 1_000_001)
|
||||
|
||||
|
||||
def test_bytes(font: ImageFont.FreeTypeFont) -> None:
|
||||
assert font.getlength(b"test") == font.getlength("test")
|
||||
|
||||
assert font.getbbox(b"test") == font.getbbox("test")
|
||||
|
||||
assert_image_equal(
|
||||
Image.Image()._new(font.getmask(b"test")),
|
||||
Image.Image()._new(font.getmask("test")),
|
||||
)
|
||||
|
||||
assert_image_equal(
|
||||
Image.Image()._new(font.getmask2(b"test")[0]),
|
||||
Image.Image()._new(font.getmask2("test")[0]),
|
||||
)
|
||||
assert font.getmask2(b"test")[1] == font.getmask2("test")[1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_file",
|
||||
[
|
||||
|
|
|
@ -9,51 +9,57 @@ from PIL import Image, ImageDraw, ImageFont, _util, features
|
|||
|
||||
from .helper import assert_image_equal_tofile
|
||||
|
||||
original_core = ImageFont.core
|
||||
fonts = [ImageFont.load_default_imagefont()]
|
||||
if not features.check_module("freetype2"):
|
||||
default_font = ImageFont.load_default()
|
||||
if isinstance(default_font, ImageFont.ImageFont):
|
||||
fonts.append(default_font)
|
||||
|
||||
|
||||
def setup_module() -> None:
|
||||
if features.check_module("freetype2"):
|
||||
ImageFont.core = _util.DeferredError(ImportError)
|
||||
|
||||
|
||||
def teardown_module() -> None:
|
||||
ImageFont.core = original_core
|
||||
|
||||
|
||||
def test_default_font() -> None:
|
||||
@pytest.mark.parametrize("font", fonts)
|
||||
def test_default_font(font: ImageFont.ImageFont) -> None:
|
||||
# Arrange
|
||||
txt = 'This is a "better than nothing" default font.'
|
||||
im = Image.new(mode="RGB", size=(300, 100))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
# Act
|
||||
default_font = ImageFont.load_default()
|
||||
draw.text((10, 10), txt, font=default_font)
|
||||
draw.text((10, 10), txt, font=font)
|
||||
|
||||
# Assert
|
||||
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
||||
|
||||
|
||||
def test_size_without_freetype() -> None:
|
||||
with pytest.raises(ImportError):
|
||||
ImageFont.load_default(size=14)
|
||||
def test_without_freetype() -> None:
|
||||
original_core = ImageFont.core
|
||||
if features.check_module("freetype2"):
|
||||
ImageFont.core = _util.DeferredError(ImportError("Disabled for testing"))
|
||||
try:
|
||||
with pytest.raises(ImportError):
|
||||
ImageFont.truetype("Tests/fonts/FreeMono.ttf")
|
||||
|
||||
assert isinstance(ImageFont.load_default(), ImageFont.ImageFont)
|
||||
|
||||
with pytest.raises(ImportError):
|
||||
ImageFont.load_default(size=14)
|
||||
finally:
|
||||
ImageFont.core = original_core
|
||||
|
||||
|
||||
def test_unicode() -> None:
|
||||
@pytest.mark.parametrize("font", fonts)
|
||||
def test_unicode(font: ImageFont.ImageFont) -> None:
|
||||
# should not segfault, should return UnicodeDecodeError
|
||||
# issue #2826
|
||||
font = ImageFont.load_default()
|
||||
with pytest.raises(UnicodeEncodeError):
|
||||
font.getbbox("’")
|
||||
|
||||
|
||||
def test_textbbox() -> None:
|
||||
@pytest.mark.parametrize("font", fonts)
|
||||
def test_textbbox(font: ImageFont.ImageFont) -> None:
|
||||
im = Image.new("RGB", (200, 200))
|
||||
d = ImageDraw.Draw(im)
|
||||
default_font = ImageFont.load_default()
|
||||
assert d.textlength("test", font=default_font) == 24
|
||||
assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, 24, 11)
|
||||
assert d.textlength("test", font=font) == 24
|
||||
assert d.textbbox((0, 0), "test", font=font) == (0, 0, 24, 11)
|
||||
|
||||
|
||||
def test_decompression_bomb() -> None:
|
||||
|
|
|
@ -60,6 +60,8 @@ class TestImageGrab:
|
|||
def test_grabclipboard(self) -> None:
|
||||
if sys.platform == "darwin":
|
||||
subprocess.call(["screencapture", "-cx"])
|
||||
|
||||
ImageGrab.grabclipboard()
|
||||
elif sys.platform == "win32":
|
||||
p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE)
|
||||
p.stdin.write(
|
||||
|
@ -69,6 +71,8 @@ $bmp = New-Object Drawing.Bitmap 200, 200
|
|||
[Windows.Forms.Clipboard]::SetImage($bmp)"""
|
||||
)
|
||||
p.communicate()
|
||||
|
||||
ImageGrab.grabclipboard()
|
||||
else:
|
||||
if not shutil.which("wl-paste") and not shutil.which("xclip"):
|
||||
with pytest.raises(
|
||||
|
@ -77,9 +81,6 @@ $bmp = New-Object Drawing.Bitmap 200, 200
|
|||
r" ImageGrab.grabclipboard\(\) on Linux",
|
||||
):
|
||||
ImageGrab.grabclipboard()
|
||||
return
|
||||
|
||||
ImageGrab.grabclipboard()
|
||||
|
||||
@pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
|
||||
def test_grabclipboard_file(self) -> None:
|
||||
|
@ -89,6 +90,7 @@ $bmp = New-Object Drawing.Bitmap 200, 200
|
|||
p.communicate()
|
||||
|
||||
im = ImageGrab.grabclipboard()
|
||||
assert isinstance(im, list)
|
||||
assert len(im) == 1
|
||||
assert os.path.samefile(im[0], "Tests/images/hopper.gif")
|
||||
|
||||
|
@ -105,6 +107,7 @@ $ms = new-object System.IO.MemoryStream(, $bytes)
|
|||
p.communicate()
|
||||
|
||||
im = ImageGrab.grabclipboard()
|
||||
assert isinstance(im, Image.Image)
|
||||
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
||||
|
||||
@pytest.mark.skipif(
|
||||
|
@ -120,6 +123,7 @@ $ms = new-object System.IO.MemoryStream(, $bytes)
|
|||
with open(image_path, "rb") as fp:
|
||||
subprocess.call(["wl-copy"], stdin=fp)
|
||||
im = ImageGrab.grabclipboard()
|
||||
assert isinstance(im, Image.Image)
|
||||
assert_image_equal_tofile(im, image_path)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
|
|
|
@ -41,11 +41,15 @@ A = string_to_img(
|
|||
def img_to_string(im: Image.Image) -> str:
|
||||
"""Turn a (small) binary image into a string representation"""
|
||||
chars = ".1"
|
||||
width, height = im.size
|
||||
return "\n".join(
|
||||
"".join(chars[im.getpixel((c, r)) > 0] for c in range(width))
|
||||
for r in range(height)
|
||||
)
|
||||
result = []
|
||||
for r in range(im.height):
|
||||
line = ""
|
||||
for c in range(im.width):
|
||||
value = im.getpixel((c, r))
|
||||
assert not isinstance(value, tuple) and value is not None
|
||||
line += chars[value > 0]
|
||||
result.append(line)
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def img_string_normalize(im: str) -> str:
|
||||
|
|
|
@ -165,10 +165,14 @@ def test_pad() -> None:
|
|||
def test_pad_round() -> None:
|
||||
im = Image.new("1", (1, 1), 1)
|
||||
new_im = ImageOps.pad(im, (4, 1))
|
||||
assert new_im.load()[2, 0] == 1
|
||||
px = new_im.load()
|
||||
assert px is not None
|
||||
assert px[2, 0] == 1
|
||||
|
||||
new_im = ImageOps.pad(im, (1, 4))
|
||||
assert new_im.load()[0, 2] == 1
|
||||
px = new_im.load()
|
||||
assert px is not None
|
||||
assert px[0, 2] == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||
|
@ -223,6 +227,7 @@ def test_expand_palette(border: int | tuple[int, int, int, int]) -> None:
|
|||
else:
|
||||
left, top, right, bottom = border
|
||||
px = im_expanded.convert("RGB").load()
|
||||
assert px is not None
|
||||
for x in range(im_expanded.width):
|
||||
for b in range(top):
|
||||
assert px[x, b] == (255, 0, 0)
|
||||
|
@ -254,20 +259,26 @@ def test_colorize_2color() -> None:
|
|||
left = (0, 1)
|
||||
middle = (127, 1)
|
||||
right = (255, 1)
|
||||
value = im_test.getpixel(left)
|
||||
assert isinstance(value, tuple)
|
||||
assert_tuple_approx_equal(
|
||||
im_test.getpixel(left),
|
||||
value,
|
||||
(255, 0, 0),
|
||||
threshold=1,
|
||||
msg="black test pixel incorrect",
|
||||
)
|
||||
value = im_test.getpixel(middle)
|
||||
assert isinstance(value, tuple)
|
||||
assert_tuple_approx_equal(
|
||||
im_test.getpixel(middle),
|
||||
value,
|
||||
(127, 63, 0),
|
||||
threshold=1,
|
||||
msg="mid test pixel incorrect",
|
||||
)
|
||||
value = im_test.getpixel(right)
|
||||
assert isinstance(value, tuple)
|
||||
assert_tuple_approx_equal(
|
||||
im_test.getpixel(right),
|
||||
value,
|
||||
(0, 127, 0),
|
||||
threshold=1,
|
||||
msg="white test pixel incorrect",
|
||||
|
@ -290,20 +301,26 @@ def test_colorize_2color_offset() -> None:
|
|||
left = (25, 1)
|
||||
middle = (75, 1)
|
||||
right = (125, 1)
|
||||
value = im_test.getpixel(left)
|
||||
assert isinstance(value, tuple)
|
||||
assert_tuple_approx_equal(
|
||||
im_test.getpixel(left),
|
||||
value,
|
||||
(255, 0, 0),
|
||||
threshold=1,
|
||||
msg="black test pixel incorrect",
|
||||
)
|
||||
value = im_test.getpixel(middle)
|
||||
assert isinstance(value, tuple)
|
||||
assert_tuple_approx_equal(
|
||||
im_test.getpixel(middle),
|
||||
value,
|
||||
(127, 63, 0),
|
||||
threshold=1,
|
||||
msg="mid test pixel incorrect",
|
||||
)
|
||||
value = im_test.getpixel(right)
|
||||
assert isinstance(value, tuple)
|
||||
assert_tuple_approx_equal(
|
||||
im_test.getpixel(right),
|
||||
value,
|
||||
(0, 127, 0),
|
||||
threshold=1,
|
||||
msg="white test pixel incorrect",
|
||||
|
@ -334,29 +351,37 @@ def test_colorize_3color_offset() -> None:
|
|||
middle = (100, 1)
|
||||
right_middle = (150, 1)
|
||||
right = (225, 1)
|
||||
value = im_test.getpixel(left)
|
||||
assert isinstance(value, tuple)
|
||||
assert_tuple_approx_equal(
|
||||
im_test.getpixel(left),
|
||||
value,
|
||||
(255, 0, 0),
|
||||
threshold=1,
|
||||
msg="black test pixel incorrect",
|
||||
)
|
||||
value = im_test.getpixel(left_middle)
|
||||
assert isinstance(value, tuple)
|
||||
assert_tuple_approx_equal(
|
||||
im_test.getpixel(left_middle),
|
||||
value,
|
||||
(127, 0, 127),
|
||||
threshold=1,
|
||||
msg="low-mid test pixel incorrect",
|
||||
)
|
||||
value = im_test.getpixel(middle)
|
||||
assert isinstance(value, tuple)
|
||||
assert_tuple_approx_equal(value, (0, 0, 255), threshold=1, msg="mid incorrect")
|
||||
value = im_test.getpixel(right_middle)
|
||||
assert isinstance(value, tuple)
|
||||
assert_tuple_approx_equal(
|
||||
im_test.getpixel(middle), (0, 0, 255), threshold=1, msg="mid incorrect"
|
||||
)
|
||||
assert_tuple_approx_equal(
|
||||
im_test.getpixel(right_middle),
|
||||
value,
|
||||
(0, 63, 127),
|
||||
threshold=1,
|
||||
msg="high-mid test pixel incorrect",
|
||||
)
|
||||
value = im_test.getpixel(right)
|
||||
assert isinstance(value, tuple)
|
||||
assert_tuple_approx_equal(
|
||||
im_test.getpixel(right),
|
||||
value,
|
||||
(0, 127, 0),
|
||||
threshold=1,
|
||||
msg="white test pixel incorrect",
|
||||
|
@ -432,6 +457,17 @@ def test_exif_transpose() -> None:
|
|||
assert 0x0112 not in transposed_im.getexif()
|
||||
|
||||
|
||||
def test_exif_transpose_xml_without_xmp() -> None:
|
||||
with Image.open("Tests/images/xmp_tags_orientation.png") as im:
|
||||
assert im.getexif()[0x0112] == 3
|
||||
assert "XML:com.adobe.xmp" in im.info
|
||||
|
||||
del im.info["xmp"]
|
||||
transposed_im = ImageOps.exif_transpose(im)
|
||||
assert transposed_im is not None
|
||||
assert 0x0112 not in transposed_im.getexif()
|
||||
|
||||
|
||||
def test_exif_transpose_in_place() -> None:
|
||||
with Image.open("Tests/images/orientation_rectangle.jpg") as im:
|
||||
assert im.size == (2, 1)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Generator
|
||||
from collections.abc import Generator
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -101,7 +101,7 @@ def test_blur_accuracy(test_images: dict[str, ImageFile.ImageFile]) -> None:
|
|||
assert i.im.getpixel((x, y))[c] >= 250
|
||||
# Fuzzy match.
|
||||
|
||||
def gp(x, y):
|
||||
def gp(x: int, y: int) -> tuple[int, ...]:
|
||||
return i.im.getpixel((x, y))
|
||||
|
||||
assert 236 <= gp(7, 4)[0] <= 239
|
||||
|
|
|
@ -45,7 +45,7 @@ def test_getcolor() -> None:
|
|||
|
||||
# Test unknown color specifier
|
||||
with pytest.raises(ValueError):
|
||||
palette.getcolor("unknown")
|
||||
palette.getcolor("unknown") # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_getcolor_rgba_color_rgb_palette() -> None:
|
||||
|
@ -88,13 +88,13 @@ def test_file(tmp_path: Path) -> None:
|
|||
|
||||
palette.save(f)
|
||||
|
||||
p = ImagePalette.load(f)
|
||||
lut = ImagePalette.load(f)
|
||||
|
||||
# load returns raw palette information
|
||||
assert len(p[0]) == 768
|
||||
assert p[1] == "RGB"
|
||||
assert len(lut[0]) == 768
|
||||
assert lut[1] == "RGB"
|
||||
|
||||
p = ImagePalette.raw(p[1], p[0])
|
||||
p = ImagePalette.raw(lut[1], lut[0])
|
||||
assert isinstance(p, ImagePalette.ImagePalette)
|
||||
assert p.palette == palette.tobytes()
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
import array
|
||||
import math
|
||||
import struct
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
|
@ -41,18 +41,13 @@ def test_rgb() -> None:
|
|||
checkrgb(0, 0, 255)
|
||||
|
||||
|
||||
def test_image() -> None:
|
||||
modes = ["1", "RGB", "RGBA", "L", "P"]
|
||||
qt_format = ImageQt.QImage.Format if ImageQt.qt_version == "6" else ImageQt.QImage
|
||||
if hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+
|
||||
modes.append("I;16")
|
||||
|
||||
for mode in modes:
|
||||
im = hopper(mode)
|
||||
roundtripped_im = ImageQt.fromqimage(ImageQt.ImageQt(im))
|
||||
if mode not in ("RGB", "RGBA"):
|
||||
im = im.convert("RGB")
|
||||
assert_image_similar(roundtripped_im, im, 1)
|
||||
@pytest.mark.parametrize("mode", ("1", "RGB", "RGBA", "L", "P", "I;16"))
|
||||
def test_image(mode: str) -> None:
|
||||
im = hopper(mode)
|
||||
roundtripped_im = ImageQt.fromqimage(ImageQt.ImageQt(im))
|
||||
if mode not in ("RGB", "RGBA"):
|
||||
im = im.convert("RGB")
|
||||
assert_image_similar(roundtripped_im, im, 1)
|
||||
|
||||
|
||||
def test_closed_file() -> None:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
@ -15,8 +16,11 @@ def test_sanity() -> None:
|
|||
|
||||
|
||||
def test_register() -> None:
|
||||
# Test registering a viewer that is not a class
|
||||
ImageShow.register("not a class")
|
||||
# Test registering a viewer that is an instance
|
||||
class TestViewer(ImageShow.Viewer):
|
||||
pass
|
||||
|
||||
ImageShow.register(TestViewer())
|
||||
|
||||
# Restore original state
|
||||
ImageShow._viewers.pop()
|
||||
|
@ -65,6 +69,27 @@ def test_show_without_viewers() -> None:
|
|||
ImageShow._viewers = viewers
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"viewer",
|
||||
(
|
||||
ImageShow.Viewer(),
|
||||
ImageShow.WindowsViewer(),
|
||||
ImageShow.MacViewer(),
|
||||
ImageShow.XDGViewer(),
|
||||
ImageShow.DisplayViewer(),
|
||||
ImageShow.GmDisplayViewer(),
|
||||
ImageShow.EogViewer(),
|
||||
ImageShow.XVViewer(),
|
||||
ImageShow.IPythonViewer(),
|
||||
),
|
||||
)
|
||||
def test_show_file(viewer: ImageShow.Viewer) -> None:
|
||||
assert not os.path.exists("missing.png")
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
viewer.show_file("missing.png")
|
||||
|
||||
|
||||
def test_viewer() -> None:
|
||||
viewer = ImageShow.Viewer()
|
||||
|
||||
|
|
|
@ -45,10 +45,12 @@ def test_kw() -> None:
|
|||
|
||||
# Test "file"
|
||||
im = ImageTk._get_image_from_kw(kw)
|
||||
assert im is not None
|
||||
assert_image_equal(im, im1)
|
||||
|
||||
# Test "data"
|
||||
im = ImageTk._get_image_from_kw(kw)
|
||||
assert im is not None
|
||||
assert_image_equal(im, im2)
|
||||
|
||||
# Test no relevant entry
|
||||
|
@ -70,6 +72,11 @@ def test_photoimage(mode: str) -> None:
|
|||
reloaded = ImageTk.getimage(im_tk)
|
||||
assert_image_equal(reloaded, im.convert("RGBA"))
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ImageTk.PhotoImage()
|
||||
with pytest.raises(ValueError):
|
||||
ImageTk.PhotoImage(mode)
|
||||
|
||||
|
||||
def test_photoimage_apply_transparency() -> None:
|
||||
with Image.open("Tests/images/pil123p.png") as im:
|
||||
|
|
|
@ -45,21 +45,22 @@ if is_win32():
|
|||
memcpy = ctypes.cdll.msvcrt.memcpy
|
||||
memcpy.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t]
|
||||
|
||||
CreateCompatibleDC = ctypes.windll.gdi32.CreateCompatibleDC
|
||||
windll = getattr(ctypes, "windll")
|
||||
CreateCompatibleDC = windll.gdi32.CreateCompatibleDC
|
||||
CreateCompatibleDC.argtypes = [ctypes.wintypes.HDC]
|
||||
CreateCompatibleDC.restype = ctypes.wintypes.HDC
|
||||
|
||||
DeleteDC = ctypes.windll.gdi32.DeleteDC
|
||||
DeleteDC = windll.gdi32.DeleteDC
|
||||
DeleteDC.argtypes = [ctypes.wintypes.HDC]
|
||||
|
||||
SelectObject = ctypes.windll.gdi32.SelectObject
|
||||
SelectObject = windll.gdi32.SelectObject
|
||||
SelectObject.argtypes = [ctypes.wintypes.HDC, ctypes.wintypes.HGDIOBJ]
|
||||
SelectObject.restype = ctypes.wintypes.HGDIOBJ
|
||||
|
||||
DeleteObject = ctypes.windll.gdi32.DeleteObject
|
||||
DeleteObject = windll.gdi32.DeleteObject
|
||||
DeleteObject.argtypes = [ctypes.wintypes.HGDIOBJ]
|
||||
|
||||
CreateDIBSection = ctypes.windll.gdi32.CreateDIBSection
|
||||
CreateDIBSection = windll.gdi32.CreateDIBSection
|
||||
CreateDIBSection.argtypes = [
|
||||
ctypes.wintypes.HDC,
|
||||
ctypes.c_void_p,
|
||||
|
|
|
@ -16,6 +16,8 @@ def verify(im1: Image.Image) -> None:
|
|||
assert im1.size == im2.size
|
||||
pix1 = im1.load()
|
||||
pix2 = im2.load()
|
||||
assert pix1 is not None
|
||||
assert pix2 is not None
|
||||
for y in range(im1.size[1]):
|
||||
for x in range(im1.size[0]):
|
||||
xy = x, y
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
from PIL import Image, _typing
|
||||
|
||||
from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import numpy
|
||||
import numpy.typing
|
||||
import numpy.typing as npt
|
||||
else:
|
||||
numpy = pytest.importorskip("numpy", reason="NumPy not installed")
|
||||
|
||||
|
@ -19,9 +19,7 @@ TEST_IMAGE_SIZE = (10, 10)
|
|||
|
||||
|
||||
def test_numpy_to_image() -> None:
|
||||
def to_image(
|
||||
dtype: numpy.typing.DTypeLike, bands: int = 1, boolean: int = 0
|
||||
) -> Image.Image:
|
||||
def to_image(dtype: npt.DTypeLike, bands: int = 1, boolean: int = 0) -> Image.Image:
|
||||
if bands == 1:
|
||||
if boolean:
|
||||
data = [0, 255] * 50
|
||||
|
@ -106,13 +104,12 @@ def test_1d_array() -> None:
|
|||
assert_image(Image.fromarray(a), "L", (1, 5))
|
||||
|
||||
|
||||
def _test_img_equals_nparray(
|
||||
img: Image.Image, np_img: numpy.typing.NDArray[Any]
|
||||
) -> None:
|
||||
def _test_img_equals_nparray(img: Image.Image, np_img: _typing.NumpyArray) -> None:
|
||||
assert len(np_img.shape) >= 2
|
||||
np_size = np_img.shape[1], np_img.shape[0]
|
||||
assert img.size == np_size
|
||||
px = img.load()
|
||||
assert px is not None
|
||||
for x in range(0, img.size[0], int(img.size[0] / 10)):
|
||||
for y in range(0, img.size[1], int(img.size[1] / 10)):
|
||||
assert_deep_equal(px[x, y], np_img[y, x])
|
||||
|
@ -145,6 +142,7 @@ def test_save_tiff_uint16() -> None:
|
|||
img = Image.fromarray(a)
|
||||
|
||||
img_px = img.load()
|
||||
assert img_px is not None
|
||||
assert img_px[0, 0] == pixel_value
|
||||
|
||||
|
||||
|
@ -166,7 +164,7 @@ def test_save_tiff_uint16() -> None:
|
|||
("HSV", numpy.uint8),
|
||||
),
|
||||
)
|
||||
def test_to_array(mode: str, dtype: numpy.typing.DTypeLike) -> None:
|
||||
def test_to_array(mode: str, dtype: npt.DTypeLike) -> None:
|
||||
img = hopper(mode)
|
||||
|
||||
# Resize to non-square
|
||||
|
@ -200,6 +198,15 @@ def test_putdata() -> None:
|
|||
assert len(im.getdata()) == len(arr)
|
||||
|
||||
|
||||
def test_resize() -> None:
|
||||
im = hopper()
|
||||
size = (64, 64)
|
||||
|
||||
im_resized = im.resize(numpy.array(size))
|
||||
|
||||
assert im_resized.size == size
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dtype",
|
||||
(
|
||||
|
@ -216,7 +223,7 @@ def test_putdata() -> None:
|
|||
numpy.float64,
|
||||
),
|
||||
)
|
||||
def test_roundtrip_eye(dtype: numpy.typing.DTypeLike) -> None:
|
||||
def test_roundtrip_eye(dtype: npt.DTypeLike) -> None:
|
||||
arr = numpy.eye(10, dtype=dtype)
|
||||
numpy.testing.assert_array_equal(arr, numpy.array(Image.fromarray(arr)))
|
||||
|
||||
|
|
|
@ -54,16 +54,12 @@ def test_stdout(buffer: bool) -> None:
|
|||
# Temporarily redirect stdout
|
||||
old_stdout = sys.stdout
|
||||
|
||||
if buffer:
|
||||
class MyStdOut:
|
||||
buffer = BytesIO()
|
||||
|
||||
class MyStdOut:
|
||||
buffer = BytesIO()
|
||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||
|
||||
mystdout = MyStdOut()
|
||||
else:
|
||||
mystdout = BytesIO()
|
||||
|
||||
sys.stdout = mystdout
|
||||
sys.stdout = mystdout # type: ignore[assignment]
|
||||
|
||||
ps = PSDraw.PSDraw()
|
||||
_create_document(ps)
|
||||
|
@ -71,6 +67,6 @@ def test_stdout(buffer: bool) -> None:
|
|||
# Reset stdout
|
||||
sys.stdout = old_stdout
|
||||
|
||||
if buffer:
|
||||
if isinstance(mystdout, MyStdOut):
|
||||
mystdout = mystdout.buffer
|
||||
assert mystdout.getvalue() != b""
|
||||
|
|
|
@ -11,7 +11,11 @@ from PIL.TiffImagePlugin import IFDRational
|
|||
from .helper import hopper, skip_unless_feature
|
||||
|
||||
|
||||
def _test_equal(num, denom, target) -> None:
|
||||
def _test_equal(
|
||||
num: float | Fraction | IFDRational,
|
||||
denom: int,
|
||||
target: float | Fraction | IFDRational,
|
||||
) -> None:
|
||||
t = IFDRational(num, denom)
|
||||
|
||||
assert target == t
|
||||
|
|
|
@ -17,6 +17,5 @@ coverage:
|
|||
# Matches 'omit:' in .coveragerc
|
||||
ignore:
|
||||
- "Tests/32bit_segfault_check.py"
|
||||
- "Tests/bench_cffi_access.py"
|
||||
- "Tests/check_*.py"
|
||||
- "Tests/createfontdatachunk.py"
|
||||
|
|
|
@ -338,7 +338,7 @@ linkcheck_allowed_redirects = {
|
|||
# https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html
|
||||
_repo = "https://github.com/python-pillow/Pillow/"
|
||||
extlinks = {
|
||||
"cve": ("https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-%s", "CVE-%s"),
|
||||
"cve": ("https://www.cve.org/CVERecord?id=CVE-%s", "CVE-%s"),
|
||||
"cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"),
|
||||
"issue": (_repo + "issues/%s", "#%s"),
|
||||
"pr": (_repo + "pull/%s", "#%s"),
|
||||
|
|
|
@ -12,28 +12,6 @@ Deprecated features
|
|||
Below are features which are considered deprecated. Where appropriate,
|
||||
a :py:exc:`DeprecationWarning` is issued.
|
||||
|
||||
PSFile
|
||||
~~~~~~
|
||||
|
||||
.. deprecated:: 9.5.0
|
||||
|
||||
The :py:class:`~PIL.EpsImagePlugin.PSFile` class has been deprecated and will
|
||||
be removed in Pillow 11 (2024-10-15). This class was only made as a helper to
|
||||
be used internally, so there is no replacement. If you need this functionality
|
||||
though, it is a very short class that can easily be recreated in your own code.
|
||||
|
||||
PyAccess and Image.USE_CFFI_ACCESS
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 10.0.0
|
||||
|
||||
Since Pillow's C API is now faster than PyAccess on PyPy,
|
||||
:py:mod:`~PIL.PyAccess` has been deprecated and will be removed in Pillow
|
||||
11.0.0 (2024-10-15). Pillow's C API will now be used by default on PyPy instead.
|
||||
|
||||
``Image.USE_CFFI_ACCESS``, for switching from the C API to PyAccess, is
|
||||
similarly deprecated.
|
||||
|
||||
ImageFile.raise_oserror
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -107,6 +85,15 @@ BGR;15, BGR 16 and BGR;24
|
|||
|
||||
The experimental BGR;15, BGR;16 and BGR;24 modes have been deprecated.
|
||||
|
||||
Non-image modes in ImageCms
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. deprecated:: 10.4.0
|
||||
|
||||
The use in :py:mod:`.ImageCms` of input modes and output modes that are not Pillow
|
||||
image modes has been deprecated. Defaulting to "L" or "1" if the mode cannot be mapped
|
||||
is also deprecated.
|
||||
|
||||
Support for LibTIFF earlier than 4
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -128,6 +115,29 @@ Removed features
|
|||
Deprecated features are only removed in major releases after an appropriate
|
||||
period of deprecation has passed.
|
||||
|
||||
PSFile
|
||||
~~~~~~
|
||||
|
||||
.. deprecated:: 9.5.0
|
||||
.. versionremoved:: 11.0.0
|
||||
|
||||
The :py:class:`!PSFile` class was removed in Pillow 11 (2024-10-15).
|
||||
This class was only made as a helper to be used internally,
|
||||
so there is no replacement. If you need this functionality though,
|
||||
it is a very short class that can easily be recreated in your own code.
|
||||
|
||||
PyAccess and Image.USE_CFFI_ACCESS
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 10.0.0
|
||||
.. versionremoved:: 11.0.0
|
||||
|
||||
Since Pillow's C API is now faster than PyAccess on PyPy, ``PyAccess`` has been
|
||||
removed. Pillow's C API will now be used on PyPy instead.
|
||||
|
||||
``Image.USE_CFFI_ACCESS``, for switching from the C API to PyAccess, was
|
||||
similarly removed.
|
||||
|
||||
Tk/Tcl 8.4
|
||||
~~~~~~~~~~
|
||||
|
||||
|
@ -286,7 +296,7 @@ Previously, the ``size`` methods returned a ``height`` that included the vertica
|
|||
offset of the text, while the new ``bbox`` methods distinguish this as a ``top``
|
||||
offset.
|
||||
|
||||
.. image:: ./example/size_vs_bbox.png
|
||||
.. image:: ./example/size_vs_bbox.webp
|
||||
:alt: In bbox methods, top measures the vertical distance above the text, while bottom measures that plus the vertical distance of the text itself. In size methods, height also measures the vertical distance above the text plus the vertical distance of the text itself.
|
||||
:align: center
|
||||
|
||||
|
|
Before Width: | Height: | Size: 9.5 KiB |
|
@ -26,5 +26,5 @@ if __name__ == "__main__":
|
|||
d.line(((x * 200, y * 100), (x * 200, (y + 1) * 100)), "black", 3)
|
||||
if y != 0:
|
||||
d.line(((x * 200, y * 100), ((x + 1) * 200, y * 100)), "black", 3)
|
||||
im.save("docs/example/anchors.png")
|
||||
im.save("docs/example/anchors.webp")
|
||||
im.show()
|
||||
|
|
BIN
docs/example/anchors.webp
Normal file
After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 19 KiB |
BIN
docs/example/image_thumbnail.webp
Normal file
After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 19 KiB |
BIN
docs/example/imageops_contain.webp
Normal file
After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 38 KiB |
BIN
docs/example/imageops_cover.webp
Normal file
After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 28 KiB |
BIN
docs/example/imageops_fit.webp
Normal file
After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 19 KiB |