mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-20 21:41:02 +03:00
Merge branch 'main' into progress
This commit is contained in:
commit
26e2a9f29c
|
@ -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
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
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
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
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
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
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
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
|
||||
|
||||
|
|
4
.github/workflows/wheels.yml
vendored
4
.github/workflows/wheels.yml
vendored
|
@ -41,7 +41,6 @@ jobs:
|
|||
python-version:
|
||||
- pp39
|
||||
- pp310
|
||||
- cp38
|
||||
- cp39
|
||||
- cp310
|
||||
- cp311
|
||||
|
@ -136,8 +135,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 +205,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
|
||||
|
||||
|
|
32
CHANGES.rst
32
CHANGES.rst
|
@ -2,9 +2,39 @@
|
|||
Changelog (Pillow)
|
||||
==================
|
||||
|
||||
10.4.0 (unreleased)
|
||||
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]
|
||||
|
||||
|
|
|
@ -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,9 +11,10 @@ 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
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 364 B After Width: | Height: | Size: 391 B |
Binary file not shown.
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 414 B |
BIN
Tests/images/rgba16.tga
Normal file
BIN
Tests/images/rgba16.tga
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 B |
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -685,13 +685,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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -4,9 +4,10 @@ import os
|
|||
import os.path
|
||||
import tempfile
|
||||
import time
|
||||
from collections.abc import Generator
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any, Generator
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import sys
|
|||
import tempfile
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
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:
|
||||
|
@ -125,6 +132,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
|
||||
|
@ -559,6 +575,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:
|
||||
|
@ -921,6 +938,21 @@ class TestImage:
|
|||
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)
|
||||
|
@ -74,6 +47,8 @@ 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"]
|
||||
with pytest.raises(TypeError):
|
||||
|
@ -116,6 +91,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 +102,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 +122,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 +146,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 +173,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 +209,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]
|
||||
|
|
|
@ -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 converted_color is not None
|
||||
converted_color = converted_color[0]
|
||||
assert converted_color == 1
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -80,6 +80,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 +119,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 +142,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]
|
||||
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
@ -235,11 +239,14 @@ class TestCoreResampleConsistency:
|
|||
self, mode: str, fill: tuple[int, int, int] | float
|
||||
) -> tuple[Image.Image, 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:
|
||||
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:
|
||||
|
@ -271,6 +278,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,6 +288,7 @@ 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])}
|
||||
assert 256 == len(used_colors), (
|
||||
|
@ -310,6 +319,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,6 +329,7 @@ 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:
|
||||
|
@ -406,6 +417,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]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -679,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"))
|
||||
|
@ -700,3 +701,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")
|
||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import contextlib
|
||||
import os.path
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
|
@ -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("Disabled for testing"))
|
||||
|
||||
|
||||
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:
|
||||
|
@ -76,8 +82,3 @@ def test_oom() -> None:
|
|||
font = ImageFont.ImageFont()
|
||||
font._load_pilfont_data(fp, Image.new("L", (1, 1)))
|
||||
font.getmask("A" * 1_000_000)
|
||||
|
||||
|
||||
def test_freetypefont_without_freetype() -> None:
|
||||
with pytest.raises(ImportError):
|
||||
ImageFont.truetype("Tests/fonts/FreeMono.ttf")
|
||||
|
|
|
@ -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)
|
||||
|
@ -432,6 +437,16 @@ 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 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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
@ -65,6 +66,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()
|
||||
|
||||
|
|
|
@ -70,6 +70,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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -109,6 +109,7 @@ def _test_img_equals_nparray(img: Image.Image, np_img: _typing.NumpyArray) -> No
|
|||
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])
|
||||
|
@ -141,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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -68,8 +68,8 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images::
|
|||
.. tab:: Windows
|
||||
|
||||
We provide Pillow binaries for Windows compiled for the matrix of supported
|
||||
Pythons in the wheel format. These include x86, x86-64 and arm64 versions
|
||||
(with the exception of Python 3.8 on arm64). These binaries include support
|
||||
Pythons in the wheel format. These include x86, x86-64 and arm64 versions.
|
||||
These binaries include support
|
||||
for all optional libraries except libimagequant and libxcb. Raqm support
|
||||
requires FriBiDi to be installed separately::
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
Python,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5
|
||||
Pillow >= 10.1,Yes,Yes,Yes,Yes,Yes,,,
|
||||
Pillow 10.0,,Yes,Yes,Yes,Yes,,,
|
||||
Pillow 9.3 - 9.5,,Yes,Yes,Yes,Yes,Yes,,
|
||||
Pillow 9.0 - 9.2,,,Yes,Yes,Yes,Yes,,
|
||||
Pillow 8.3.2 - 8.4,,,Yes,Yes,Yes,Yes,Yes,
|
||||
Pillow 8.0 - 8.3.1,,,,Yes,Yes,Yes,Yes,
|
||||
Pillow 7.0 - 7.2,,,,,Yes,Yes,Yes,Yes
|
||||
Python,3.13,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5
|
||||
Pillow >= 11,Yes,Yes,Yes,Yes,Yes,,,,
|
||||
Pillow 10.1 - 10.4,,Yes,Yes,Yes,Yes,Yes,,,
|
||||
Pillow 10.0,,,Yes,Yes,Yes,Yes,,,
|
||||
Pillow 9.3 - 9.5,,,Yes,Yes,Yes,Yes,Yes,,
|
||||
Pillow 9.0 - 9.2,,,,Yes,Yes,Yes,Yes,,
|
||||
Pillow 8.3.2 - 8.4,,,,Yes,Yes,Yes,Yes,Yes,
|
||||
Pillow 8.0 - 8.3.1,,,,,Yes,Yes,Yes,Yes,
|
||||
Pillow 7.0 - 7.2,,,,,,Yes,Yes,Yes,Yes
|
||||
|
|
|
|
@ -27,8 +27,6 @@ These platforms are built and tested for every change.
|
|||
+----------------------------------+----------------------------+---------------------+
|
||||
| CentOS Stream 9 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Debian 11 Bullseye | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Debian 12 Bookworm | 3.11 | x86, x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 39 | 3.12 | x86-64 |
|
||||
|
@ -37,14 +35,12 @@ These platforms are built and tested for every change.
|
|||
+----------------------------------+----------------------------+---------------------+
|
||||
| Gentoo | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 13 Ventura | 3.8, 3.9 | x86-64 |
|
||||
| macOS 13 Ventura | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
|
||||
| | PyPy3 | |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.9, 3.10, 3.11, | x86-64 |
|
||||
| | 3.12, 3.13, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.10 | arm64v8 |
|
||||
|
@ -52,16 +48,16 @@ These platforms are built and tested for every change.
|
|||
| Ubuntu Linux 24.04 LTS (Noble) | 3.12 | x86-64, ppc64le, |
|
||||
| | | s390x |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2016 | 3.8 | x86-64 |
|
||||
| Windows Server 2016 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||
| Windows Server 2022 | 3.9, 3.10, 3.11, | x86-64 |
|
||||
| | 3.12, 3.13, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.12 | x86 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.9 (MinGW) | x86-64 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.8, 3.9 (Cygwin) | x86-64 |
|
||||
| | 3.9 (Cygwin) | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
|
||||
|
||||
|
|
|
@ -381,6 +381,11 @@ Constants
|
|||
Set to 89,478,485, approximately 0.25GB for a 24-bit (3 bpp) image.
|
||||
See :py:meth:`~PIL.Image.open` for more information about how this is used.
|
||||
|
||||
.. data:: WARN_POSSIBLE_FORMATS
|
||||
|
||||
Set to false. If true, when an image cannot be identified, warnings will be raised
|
||||
from formats that attempted to read the data.
|
||||
|
||||
Transpose methods
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ Functions
|
|||
.. autofunction:: PIL.ImageFont.load_path
|
||||
.. autofunction:: PIL.ImageFont.truetype
|
||||
.. autofunction:: PIL.ImageFont.load_default
|
||||
.. autofunction:: PIL.ImageFont.load_default_imagefont
|
||||
|
||||
Methods
|
||||
-------
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
.. py:module:: PIL.PyAccess
|
||||
.. py:currentmodule:: PIL.PyAccess
|
||||
|
||||
:py:mod:`~PIL.PyAccess` Module
|
||||
==============================
|
||||
|
||||
The :py:mod:`~PIL.PyAccess` module provides a CFFI/Python implementation of the :ref:`PixelAccess`. This implementation is far faster on PyPy than the PixelAccess version.
|
||||
|
||||
.. note:: Accessing individual pixels is fairly slow. If you are
|
||||
looping over all of the pixels in an image, there is likely
|
||||
a faster way using other parts of the Pillow API.
|
||||
|
||||
:mod:`~PIL.Image`, :mod:`~PIL.ImageChops` and :mod:`~PIL.ImageOps`
|
||||
have methods for many standard operations. If you wish to perform
|
||||
a custom mapping, check out :py:meth:`~PIL.Image.Image.point`.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
The following script loads an image, accesses one pixel from it, then changes it. ::
|
||||
|
||||
from PIL import Image
|
||||
|
||||
with Image.open("hopper.jpg") as im:
|
||||
px = im.load()
|
||||
print(px[4, 4])
|
||||
px[4, 4] = (0, 0, 0)
|
||||
print(px[4, 4])
|
||||
|
||||
Results in the following::
|
||||
|
||||
(23, 24, 68)
|
||||
(0, 0, 0)
|
||||
|
||||
Access using negative indexes is also possible. ::
|
||||
|
||||
px[-1, -1] = (0, 0, 0)
|
||||
print(px[-1, -1])
|
||||
|
||||
|
||||
|
||||
:py:class:`PyAccess` Class
|
||||
--------------------------
|
||||
|
||||
.. autoclass:: PIL.PyAccess.PyAccess()
|
||||
:members:
|
||||
:special-members: __getitem__, __setitem__
|
|
@ -32,7 +32,6 @@ Reference
|
|||
JpegPresets
|
||||
PSDraw
|
||||
PixelAccess
|
||||
PyAccess
|
||||
features
|
||||
../PIL
|
||||
plugins
|
||||
|
|
|
@ -158,7 +158,7 @@ PyAccess and Image.USE_CFFI_ACCESS
|
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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
|
||||
:py:mod:`!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
|
||||
|
|
|
@ -4,21 +4,16 @@
|
|||
Security
|
||||
========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
ImageShow.WindowsViewer.show_file
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
If an attacker has control over the ``path`` passed to
|
||||
``ImageShow.WindowsViewer.show_file()``, they may be able to
|
||||
execute arbitrary shell commands.
|
||||
|
||||
:cve:`YYYY-XXXXX`: TODO
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Backwards Incompatible Changes
|
||||
==============================
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
To prevent this, a :py:exc:`FileNotFoundError` will be raised if the ``path``
|
||||
does not exist as a file. To provide a consistent experience, the error has
|
||||
been added to all :py:class:`~PIL.ImageShow` viewers.
|
||||
|
||||
Deprecations
|
||||
============
|
||||
|
@ -28,6 +23,13 @@ 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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -39,14 +41,6 @@ ImageDraw.getdraw hints parameter
|
|||
|
||||
The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated.
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API Additions
|
||||
=============
|
||||
|
||||
|
@ -57,11 +51,6 @@ Added :py:meth:`~PIL.ImageDraw.ImageDraw.circle`. It provides the same functiona
|
|||
:py:meth:`~PIL.ImageDraw.ImageDraw.ellipse`, but instead of taking a bounding box, it
|
||||
takes a center point and radius.
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Other Changes
|
||||
=============
|
||||
|
||||
|
|
77
docs/releasenotes/11.0.0.rst
Normal file
77
docs/releasenotes/11.0.0.rst
Normal file
|
@ -0,0 +1,77 @@
|
|||
11.0.0
|
||||
------
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
:cve:`YYYY-XXXXX`: TODO
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Backwards Incompatible Changes
|
||||
==============================
|
||||
|
||||
Python 3.8
|
||||
^^^^^^^^^^
|
||||
|
||||
Pillow has dropped support for Python 3.8,
|
||||
which reached end-of-life in October 2024.
|
||||
|
||||
PSFile
|
||||
^^^^^^
|
||||
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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.
|
||||
|
||||
Deprecations
|
||||
============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API Additions
|
||||
=============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Other Changes
|
||||
=============
|
||||
|
||||
Python 3.13
|
||||
^^^^^^^^^^^
|
||||
|
||||
Pillow 10.4.0 had wheels built against Python 3.13 beta, available as a preview to help
|
||||
others prepare for 3.13, and to ensure Pillow could be used immediately at the release
|
||||
of 3.13.0 final (2024-10-01, :pep:`719`).
|
||||
|
||||
Pillow 11.0.0 now officially supports Python 3.13.
|
|
@ -32,7 +32,7 @@ Deprecations
|
|||
PSFile
|
||||
^^^^^^
|
||||
|
||||
The :py:class:`~PIL.EpsImagePlugin.PSFile` class has been deprecated and will
|
||||
The :py:class:`!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.
|
||||
|
|
|
@ -14,6 +14,7 @@ expected to be backported to earlier versions.
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
11.0.0
|
||||
10.4.0
|
||||
10.3.0
|
||||
10.2.0
|
||||
|
|
112
pyproject.toml
112
pyproject.toml
|
@ -14,18 +14,20 @@ readme = "README.md"
|
|||
keywords = [
|
||||
"Imaging",
|
||||
]
|
||||
license = {text = "HPND"}
|
||||
authors = [{name = "Jeffrey A. Clark", email = "aclark@aclark.net"}]
|
||||
requires-python = ">=3.8"
|
||||
license = { text = "HPND" }
|
||||
authors = [
|
||||
{ name = "Jeffrey A. Clark", email = "aclark@aclark.net" },
|
||||
]
|
||||
requires-python = ">=3.9"
|
||||
classifiers = [
|
||||
"Development Status :: 6 - Mature",
|
||||
"License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
"Topic :: Multimedia :: Graphics",
|
||||
|
@ -38,8 +40,7 @@ classifiers = [
|
|||
dynamic = [
|
||||
"version",
|
||||
]
|
||||
[project.optional-dependencies]
|
||||
docs = [
|
||||
optional-dependencies.docs = [
|
||||
"furo",
|
||||
"olefile",
|
||||
"sphinx>=7.3",
|
||||
|
@ -47,13 +48,13 @@ docs = [
|
|||
"sphinx-inline-tabs",
|
||||
"sphinxext-opengraph",
|
||||
]
|
||||
fpx = [
|
||||
optional-dependencies.fpx = [
|
||||
"olefile",
|
||||
]
|
||||
mic = [
|
||||
optional-dependencies.mic = [
|
||||
"olefile",
|
||||
]
|
||||
tests = [
|
||||
optional-dependencies.tests = [
|
||||
"check-manifest",
|
||||
"coverage",
|
||||
"defusedxml",
|
||||
|
@ -65,28 +66,29 @@ tests = [
|
|||
"pytest-cov",
|
||||
"pytest-timeout",
|
||||
]
|
||||
typing = [
|
||||
'typing-extensions; python_version < "3.10"',
|
||||
optional-dependencies.typing = [
|
||||
"typing-extensions; python_version<'3.10'",
|
||||
]
|
||||
xmp = [
|
||||
optional-dependencies.xmp = [
|
||||
"defusedxml",
|
||||
]
|
||||
[project.urls]
|
||||
Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst"
|
||||
Documentation = "https://pillow.readthedocs.io"
|
||||
Funding = "https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi"
|
||||
Homepage = "https://python-pillow.org"
|
||||
Mastodon = "https://fosstodon.org/@pillow"
|
||||
"Release notes" = "https://pillow.readthedocs.io/en/stable/releasenotes/index.html"
|
||||
Source = "https://github.com/python-pillow/Pillow"
|
||||
urls.Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst"
|
||||
urls.Documentation = "https://pillow.readthedocs.io"
|
||||
urls.Funding = "https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi"
|
||||
urls.Homepage = "https://python-pillow.org"
|
||||
urls.Mastodon = "https://fosstodon.org/@pillow"
|
||||
urls."Release notes" = "https://pillow.readthedocs.io/en/stable/releasenotes/index.html"
|
||||
urls.Source = "https://github.com/python-pillow/Pillow"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["PIL"]
|
||||
packages = [
|
||||
"PIL",
|
||||
]
|
||||
include-package-data = true
|
||||
package-dir = {"" = "src"}
|
||||
package-dir = { "" = "src" }
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = {attr = "PIL.__version__"}
|
||||
version = { attr = "PIL.__version__" }
|
||||
|
||||
[tool.cibuildwheel]
|
||||
before-all = ".github/workflows/wheels-dependencies.sh"
|
||||
|
@ -98,45 +100,53 @@ test-extras = "tests"
|
|||
[tool.ruff]
|
||||
fix = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"C4", # flake8-comprehensions
|
||||
"E", # pycodestyle errors
|
||||
"EM", # flake8-errmsg
|
||||
"F", # pyflakes errors
|
||||
"I", # isort
|
||||
"ISC", # flake8-implicit-str-concat
|
||||
"LOG", # flake8-logging
|
||||
"PGH", # pygrep-hooks
|
||||
"PYI", # flake8-pyi
|
||||
lint.select = [
|
||||
"C4", # flake8-comprehensions
|
||||
"E", # pycodestyle errors
|
||||
"EM", # flake8-errmsg
|
||||
"F", # pyflakes errors
|
||||
"I", # isort
|
||||
"ISC", # flake8-implicit-str-concat
|
||||
"LOG", # flake8-logging
|
||||
"PGH", # pygrep-hooks
|
||||
"PYI", # flake8-pyi
|
||||
"RUF100", # unused noqa (yesqa)
|
||||
"UP", # pyupgrade
|
||||
"W", # pycodestyle warnings
|
||||
"YTT", # flake8-2020
|
||||
"UP", # pyupgrade
|
||||
"W", # pycodestyle warnings
|
||||
"YTT", # flake8-2020
|
||||
]
|
||||
ignore = [
|
||||
"E203", # Whitespace before ':'
|
||||
"E221", # Multiple spaces before operator
|
||||
"E226", # Missing whitespace around arithmetic operator
|
||||
"E241", # Multiple spaces after ','
|
||||
lint.ignore = [
|
||||
"E203", # Whitespace before ':'
|
||||
"E221", # Multiple spaces before operator
|
||||
"E226", # Missing whitespace around arithmetic operator
|
||||
"E241", # Multiple spaces after ','
|
||||
"PYI026", # flake8-pyi: typing.TypeAlias added in Python 3.10
|
||||
"PYI034", # flake8-pyi: typing.Self added in Python 3.11
|
||||
]
|
||||
lint.per-file-ignores."Tests/oss-fuzz/fuzz_font.py" = [
|
||||
"I002",
|
||||
]
|
||||
lint.per-file-ignores."Tests/oss-fuzz/fuzz_pillow.py" = [
|
||||
"I002",
|
||||
]
|
||||
lint.isort.known-first-party = [
|
||||
"PIL",
|
||||
]
|
||||
lint.isort.required-imports = [
|
||||
"from __future__ import annotations",
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"Tests/oss-fuzz/fuzz_font.py" = ["I002"]
|
||||
"Tests/oss-fuzz/fuzz_pillow.py" = ["I002"]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["PIL"]
|
||||
required-imports = ["from __future__ import annotations"]
|
||||
[tool.pyproject-fmt]
|
||||
max_supported_python = "3.13"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "-ra --color=yes"
|
||||
testpaths = ["Tests"]
|
||||
testpaths = [
|
||||
"Tests",
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.8"
|
||||
python_version = "3.9"
|
||||
pretty = true
|
||||
disallow_any_generics = true
|
||||
enable_error_code = "ignore-without-code"
|
||||
|
|
2
setup.py
2
setup.py
|
@ -43,7 +43,7 @@ WEBP_ROOT = None
|
|||
ZLIB_ROOT = None
|
||||
FUZZING_BUILD = "LIB_FUZZING_ENGINE" in os.environ
|
||||
|
||||
if sys.platform == "win32" and sys.version_info >= (3, 13):
|
||||
if sys.platform == "win32" and sys.version_info >= (3, 14):
|
||||
import atexit
|
||||
|
||||
atexit.register(
|
||||
|
|
|
@ -31,7 +31,6 @@ from typing import IO
|
|||
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i32le as i32
|
||||
from ._deprecate import deprecate
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
|
@ -159,43 +158,6 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
|||
return im
|
||||
|
||||
|
||||
class PSFile:
|
||||
"""
|
||||
Wrapper for bytesio object that treats either CR or LF as end of line.
|
||||
This class is no longer used internally, but kept for backwards compatibility.
|
||||
"""
|
||||
|
||||
def __init__(self, fp):
|
||||
deprecate(
|
||||
"PSFile",
|
||||
11,
|
||||
action="If you need the functionality of this class "
|
||||
"you will need to implement it yourself.",
|
||||
)
|
||||
self.fp = fp
|
||||
self.char = None
|
||||
|
||||
def seek(self, offset, whence=io.SEEK_SET):
|
||||
self.char = None
|
||||
self.fp.seek(offset, whence)
|
||||
|
||||
def readline(self) -> str:
|
||||
s = [self.char or b""]
|
||||
self.char = None
|
||||
|
||||
c = self.fp.read(1)
|
||||
while (c not in b"\r\n") and len(c):
|
||||
s.append(c)
|
||||
c = self.fp.read(1)
|
||||
|
||||
self.char = self.fp.read(1)
|
||||
# line endings can be 1 or 2 of \r \n, in either order
|
||||
if self.char in b"\r\n":
|
||||
self.char = None
|
||||
|
||||
return b"".join(s).decode("latin-1")
|
||||
|
||||
|
||||
def _accept(prefix: bytes) -> bool:
|
||||
return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
format = "FPX"
|
||||
format_description = "FlashPix"
|
||||
|
||||
def _open(self):
|
||||
def _open(self) -> None:
|
||||
#
|
||||
# read the OLE directory and see if this is a likely
|
||||
# to be a FlashPix file
|
||||
|
@ -64,7 +64,8 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
msg = "not an FPX file; invalid OLE file"
|
||||
raise SyntaxError(msg) from e
|
||||
|
||||
if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
|
||||
root = self.ole.root
|
||||
if not root or root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
|
||||
msg = "not an FPX file; bad root CLSID"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
|
@ -99,8 +100,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
|
||||
s = prop[0x2000002 | id]
|
||||
|
||||
bands = i32(s, 4)
|
||||
if bands > 4:
|
||||
if not isinstance(s, bytes) or (bands := i32(s, 4)) > 4:
|
||||
msg = "Invalid number of bands"
|
||||
raise OSError(msg)
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import subprocess
|
|||
import sys
|
||||
from enum import IntEnum
|
||||
from functools import cached_property
|
||||
from typing import IO, TYPE_CHECKING, Any, List, Literal, NamedTuple, Union
|
||||
from typing import IO, TYPE_CHECKING, Any, Literal, NamedTuple, Union
|
||||
|
||||
from . import (
|
||||
Image,
|
||||
|
@ -330,7 +330,6 @@ class GifImageFile(ImageFile.ImageFile):
|
|||
LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
|
||||
or palette
|
||||
):
|
||||
self.pyaccess = None
|
||||
if "transparency" in self.info:
|
||||
self.im.putpalettealpha(self.info["transparency"], 0)
|
||||
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
|
||||
|
@ -504,7 +503,7 @@ def _normalize_mode(im: Image.Image) -> Image.Image:
|
|||
return im.convert("L")
|
||||
|
||||
|
||||
_Palette = Union[bytes, bytearray, List[int], ImagePalette.ImagePalette]
|
||||
_Palette = Union[bytes, bytearray, list[int], ImagePalette.ImagePalette]
|
||||
|
||||
|
||||
def _normalize_palette(
|
||||
|
|
|
@ -329,7 +329,6 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
# if tile is PNG, it won't really be loaded yet
|
||||
im.load()
|
||||
self.im = im.im
|
||||
self.pyaccess = None
|
||||
self._mode = im.mode
|
||||
if im.palette:
|
||||
self.palette = im.palette
|
||||
|
|
|
@ -38,7 +38,7 @@ import struct
|
|||
import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections.abc import Callable, MutableMapping
|
||||
from collections.abc import Callable, MutableMapping, Sequence
|
||||
from enum import IntEnum
|
||||
from types import ModuleType
|
||||
from typing import (
|
||||
|
@ -47,8 +47,6 @@ from typing import (
|
|||
Any,
|
||||
Literal,
|
||||
Protocol,
|
||||
Sequence,
|
||||
Tuple,
|
||||
cast,
|
||||
)
|
||||
|
||||
|
@ -85,6 +83,8 @@ class DecompressionBombError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
WARN_POSSIBLE_FORMATS: bool = False
|
||||
|
||||
# Limit to around a quarter gigabyte for a 24-bit (3 bpp) image
|
||||
MAX_IMAGE_PIXELS: int | None = int(1024 * 1024 * 1024 // 4 // 3)
|
||||
|
||||
|
@ -123,14 +123,6 @@ except ImportError as v:
|
|||
raise
|
||||
|
||||
|
||||
USE_CFFI_ACCESS = False
|
||||
cffi: ModuleType | None
|
||||
try:
|
||||
import cffi
|
||||
except ImportError:
|
||||
cffi = None
|
||||
|
||||
|
||||
def isImageType(t: Any) -> TypeGuard[Image]:
|
||||
"""
|
||||
Checks if an object is an image object.
|
||||
|
@ -227,7 +219,7 @@ if hasattr(core, "DEFAULT_STRATEGY"):
|
|||
# Registries
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ImageFile, PyAccess
|
||||
from . import ImageFile, ImagePalette
|
||||
ID: list[str] = []
|
||||
OPEN: dict[
|
||||
str,
|
||||
|
@ -547,7 +539,6 @@ class Image:
|
|||
self.palette = None
|
||||
self.info = {}
|
||||
self.readonly = 0
|
||||
self.pyaccess = None
|
||||
self._exif = None
|
||||
|
||||
@property
|
||||
|
@ -629,7 +620,6 @@ class Image:
|
|||
def _copy(self) -> None:
|
||||
self.load()
|
||||
self.im = self.im.copy()
|
||||
self.pyaccess = None
|
||||
self.readonly = 0
|
||||
|
||||
def _ensure_mutable(self) -> None:
|
||||
|
@ -880,7 +870,7 @@ class Image:
|
|||
msg = "cannot decode image data"
|
||||
raise ValueError(msg)
|
||||
|
||||
def load(self) -> core.PixelAccess | PyAccess.PyAccess | None:
|
||||
def load(self) -> core.PixelAccess | None:
|
||||
"""
|
||||
Allocates storage for the image and loads the pixel data. In
|
||||
normal cases, you don't need to call this method, since the
|
||||
|
@ -893,7 +883,7 @@ class Image:
|
|||
operations. See :ref:`file-handling` for more information.
|
||||
|
||||
:returns: An image access object.
|
||||
:rtype: :py:class:`.PixelAccess` or :py:class:`.PyAccess`
|
||||
:rtype: :py:class:`.PixelAccess`
|
||||
"""
|
||||
if self.im is not None and self.palette and self.palette.dirty:
|
||||
# realize palette
|
||||
|
@ -913,14 +903,6 @@ class Image:
|
|||
)
|
||||
|
||||
if self.im is not None:
|
||||
if cffi and USE_CFFI_ACCESS:
|
||||
if self.pyaccess:
|
||||
return self.pyaccess
|
||||
from . import PyAccess
|
||||
|
||||
self.pyaccess = PyAccess.new(self, self.readonly)
|
||||
if self.pyaccess:
|
||||
return self.pyaccess
|
||||
return self.im.pixel_access(self.readonly)
|
||||
return None
|
||||
|
||||
|
@ -1113,7 +1095,7 @@ class Image:
|
|||
if trns is not None:
|
||||
try:
|
||||
new_im.info["transparency"] = new_im.palette.getcolor(
|
||||
cast(Tuple[int, int, int], trns), # trns was converted to RGB
|
||||
cast(tuple[int, ...], trns), # trns was converted to RGB
|
||||
new_im,
|
||||
)
|
||||
except Exception:
|
||||
|
@ -1163,12 +1145,9 @@ class Image:
|
|||
# crash fail if we leave a bytes transparency in an rgb/l mode.
|
||||
del new_im.info["transparency"]
|
||||
if trns is not None:
|
||||
if new_im.mode == "P":
|
||||
if new_im.mode == "P" and new_im.palette:
|
||||
try:
|
||||
new_im.info["transparency"] = new_im.palette.getcolor(
|
||||
cast(Tuple[int, int, int], trns), # trns was converted to RGB
|
||||
new_im,
|
||||
)
|
||||
new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im)
|
||||
except ValueError as e:
|
||||
del new_im.info["transparency"]
|
||||
if str(e) != "cannot allocate more than 256 colors":
|
||||
|
@ -1186,7 +1165,7 @@ class Image:
|
|||
colors: int = 256,
|
||||
method: int | None = None,
|
||||
kmeans: int = 0,
|
||||
palette=None,
|
||||
palette: Image | None = None,
|
||||
dither: Dither = Dither.FLOYDSTEINBERG,
|
||||
) -> Image:
|
||||
"""
|
||||
|
@ -1258,8 +1237,8 @@ class Image:
|
|||
from . import ImagePalette
|
||||
|
||||
mode = im.im.getpalettemode()
|
||||
palette = im.im.getpalette(mode, mode)[: colors * len(mode)]
|
||||
im.palette = ImagePalette.ImagePalette(mode, palette)
|
||||
palette_data = im.im.getpalette(mode, mode)[: colors * len(mode)]
|
||||
im.palette = ImagePalette.ImagePalette(mode, palette_data)
|
||||
|
||||
return im
|
||||
|
||||
|
@ -1414,7 +1393,9 @@ class Image:
|
|||
self.load()
|
||||
return self.im.getbbox(alpha_only)
|
||||
|
||||
def getcolors(self, maxcolors: int = 256):
|
||||
def getcolors(
|
||||
self, maxcolors: int = 256
|
||||
) -> list[tuple[int, int]] | list[tuple[int, float]] | None:
|
||||
"""
|
||||
Returns a list of colors used in this image.
|
||||
|
||||
|
@ -1475,7 +1456,7 @@ class Image:
|
|||
return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
|
||||
return self.im.getextrema()
|
||||
|
||||
def getxmp(self):
|
||||
def getxmp(self) -> dict[str, Any]:
|
||||
"""
|
||||
Returns a dictionary containing the XMP tags.
|
||||
Requires defusedxml to be installed.
|
||||
|
@ -1511,7 +1492,7 @@ class Image:
|
|||
return {}
|
||||
if "xmp" not in self.info:
|
||||
return {}
|
||||
root = ElementTree.fromstring(self.info["xmp"])
|
||||
root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00"))
|
||||
return {get_name(root.tag): get_value(root)}
|
||||
|
||||
def getexif(self) -> Exif:
|
||||
|
@ -1686,8 +1667,6 @@ class Image:
|
|||
"""
|
||||
|
||||
self.load()
|
||||
if self.pyaccess:
|
||||
return self.pyaccess.getpixel(xy)
|
||||
return self.im.getpixel(tuple(xy))
|
||||
|
||||
def getprojection(self) -> tuple[list[int], list[int]]:
|
||||
|
@ -1984,7 +1963,6 @@ class Image:
|
|||
msg = "alpha channel could not be added"
|
||||
raise ValueError(msg) from e # sanity check
|
||||
self.im = im
|
||||
self.pyaccess = None
|
||||
self._mode = self.im.mode
|
||||
except KeyError as e:
|
||||
msg = "illegal image mode"
|
||||
|
@ -2039,7 +2017,11 @@ class Image:
|
|||
|
||||
self.im.putdata(data, scale, offset)
|
||||
|
||||
def putpalette(self, data, rawmode="RGB") -> None:
|
||||
def putpalette(
|
||||
self,
|
||||
data: ImagePalette.ImagePalette | bytes | Sequence[int],
|
||||
rawmode: str = "RGB",
|
||||
) -> None:
|
||||
"""
|
||||
Attaches a palette to this image. The image must be a "P", "PA", "L"
|
||||
or "LA" image.
|
||||
|
@ -2102,9 +2084,6 @@ class Image:
|
|||
self._copy()
|
||||
self.load()
|
||||
|
||||
if self.pyaccess:
|
||||
return self.pyaccess.putpixel(xy, value)
|
||||
|
||||
if (
|
||||
self.mode in ("P", "PA")
|
||||
and isinstance(value, (list, tuple))
|
||||
|
@ -2118,7 +2097,9 @@ class Image:
|
|||
value = (palette_index, alpha) if self.mode == "PA" else palette_index
|
||||
return self.im.putpixel(xy, value)
|
||||
|
||||
def remap_palette(self, dest_map, source_palette=None):
|
||||
def remap_palette(
|
||||
self, dest_map: list[int], source_palette: bytes | bytearray | None = None
|
||||
) -> Image:
|
||||
"""
|
||||
Rewrites the image to reorder the palette.
|
||||
|
||||
|
@ -2786,7 +2767,6 @@ class Image:
|
|||
self._mode = self.im.mode
|
||||
|
||||
self.readonly = 0
|
||||
self.pyaccess = None
|
||||
|
||||
# FIXME: the different transform methods need further explanation
|
||||
# instead of bloating the method docs, add a separate chapter.
|
||||
|
@ -3110,7 +3090,7 @@ def new(
|
|||
and isinstance(color, (list, tuple))
|
||||
and all(isinstance(i, int) for i in color)
|
||||
):
|
||||
color_ints: tuple[int, ...] = cast(Tuple[int, ...], tuple(color))
|
||||
color_ints: tuple[int, ...] = cast(tuple[int, ...], tuple(color))
|
||||
if len(color_ints) == 3 or len(color_ints) == 4:
|
||||
# RGB or RGBA value for a P image
|
||||
from . import ImagePalette
|
||||
|
@ -3461,7 +3441,7 @@ def open(
|
|||
|
||||
preinit()
|
||||
|
||||
accept_warnings: list[str] = []
|
||||
warning_messages: list[str] = []
|
||||
|
||||
def _open_core(
|
||||
fp: IO[bytes],
|
||||
|
@ -3477,17 +3457,15 @@ def open(
|
|||
factory, accept = OPEN[i]
|
||||
result = not accept or accept(prefix)
|
||||
if isinstance(result, str):
|
||||
accept_warnings.append(result)
|
||||
warning_messages.append(result)
|
||||
elif result:
|
||||
fp.seek(0)
|
||||
im = factory(fp, filename)
|
||||
_decompression_bomb_check(im.size)
|
||||
return im
|
||||
except (SyntaxError, IndexError, TypeError, struct.error):
|
||||
# Leave disabled by default, spams the logs with image
|
||||
# opening failures that are entirely expected.
|
||||
# logger.debug("", exc_info=True)
|
||||
continue
|
||||
except (SyntaxError, IndexError, TypeError, struct.error) as e:
|
||||
if WARN_POSSIBLE_FORMATS:
|
||||
warning_messages.append(i + " opening failed. " + str(e))
|
||||
except BaseException:
|
||||
if exclusive_fp:
|
||||
fp.close()
|
||||
|
@ -3512,7 +3490,7 @@ def open(
|
|||
|
||||
if exclusive_fp:
|
||||
fp.close()
|
||||
for message in accept_warnings:
|
||||
for message in warning_messages:
|
||||
warnings.warn(message)
|
||||
msg = "cannot identify image file %r" % (filename if filename else fp)
|
||||
raise UnidentifiedImageError(msg)
|
||||
|
@ -3577,7 +3555,7 @@ def composite(image1: Image, image2: Image, mask: Image) -> Image:
|
|||
return image
|
||||
|
||||
|
||||
def eval(image, *args):
|
||||
def eval(image: Image, *args: Callable[[int], float]) -> Image:
|
||||
"""
|
||||
Applies the function (which should take one argument) to each pixel
|
||||
in the given image. If the image has more than one band, the same
|
||||
|
|
|
@ -299,6 +299,31 @@ class ImageCmsTransform(Image.ImagePointHandler):
|
|||
proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC,
|
||||
flags: Flags = Flags.NONE,
|
||||
):
|
||||
supported_modes = (
|
||||
"RGB",
|
||||
"RGBA",
|
||||
"RGBX",
|
||||
"CMYK",
|
||||
"I;16",
|
||||
"I;16L",
|
||||
"I;16B",
|
||||
"YCbCr",
|
||||
"LAB",
|
||||
"L",
|
||||
"1",
|
||||
)
|
||||
for mode in (input_mode, output_mode):
|
||||
if mode not in supported_modes:
|
||||
deprecate(
|
||||
mode,
|
||||
12,
|
||||
{
|
||||
"L;16": "I;16 or I;16L",
|
||||
"L:16B": "I;16B",
|
||||
"YCCA": "YCbCr",
|
||||
"YCC": "YCbCr",
|
||||
}.get(mode),
|
||||
)
|
||||
if proof is None:
|
||||
self.transform = core.buildTransform(
|
||||
input.profile, output.profile, input_mode, output_mode, intent, flags
|
||||
|
|
|
@ -34,8 +34,9 @@ from __future__ import annotations
|
|||
import math
|
||||
import numbers
|
||||
import struct
|
||||
from collections.abc import Sequence
|
||||
from types import ModuleType
|
||||
from typing import TYPE_CHECKING, AnyStr, Callable, List, Sequence, Tuple, Union, cast
|
||||
from typing import TYPE_CHECKING, AnyStr, Callable, Union, cast
|
||||
|
||||
from . import Image, ImageColor
|
||||
from ._deprecate import deprecate
|
||||
|
@ -51,7 +52,7 @@ except AttributeError:
|
|||
if TYPE_CHECKING:
|
||||
from . import ImageDraw2, ImageFont
|
||||
|
||||
_Ink = Union[float, Tuple[int, ...], str]
|
||||
_Ink = Union[float, tuple[int, ...], str]
|
||||
|
||||
"""
|
||||
A simple 2D drawing interface for PIL images.
|
||||
|
@ -1124,7 +1125,7 @@ def _compute_regular_polygon_vertices(
|
|||
msg = "bounding_circle should only contain numeric data"
|
||||
raise ValueError(msg)
|
||||
|
||||
*centroid, polygon_radius = cast(List[float], list(bounding_circle))
|
||||
*centroid, polygon_radius = cast(list[float], list(bounding_circle))
|
||||
elif len(bounding_circle) == 2 and isinstance(bounding_circle[0], (list, tuple)):
|
||||
if not all(
|
||||
isinstance(i, (int, float)) for i in bounding_circle[0]
|
||||
|
@ -1136,7 +1137,7 @@ def _compute_regular_polygon_vertices(
|
|||
msg = "bounding_circle centre should contain 2D coordinates (e.g. (x, y))"
|
||||
raise ValueError(msg)
|
||||
|
||||
centroid = cast(List[float], list(bounding_circle[0]))
|
||||
centroid = cast(list[float], list(bounding_circle[0]))
|
||||
polygon_radius = cast(float, bounding_circle[1])
|
||||
else:
|
||||
msg = (
|
||||
|
|
|
@ -18,8 +18,9 @@ from __future__ import annotations
|
|||
|
||||
import abc
|
||||
import functools
|
||||
from collections.abc import Sequence
|
||||
from types import ModuleType
|
||||
from typing import TYPE_CHECKING, Any, Callable, Sequence, cast
|
||||
from typing import TYPE_CHECKING, Any, Callable, cast
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import _imaging
|
||||
|
|
|
@ -906,6 +906,142 @@ def load_path(filename: str | bytes) -> ImageFont:
|
|||
raise OSError(msg)
|
||||
|
||||
|
||||
def load_default_imagefont() -> ImageFont:
|
||||
f = ImageFont()
|
||||
f._load_pilfont_data(
|
||||
# courB08
|
||||
BytesIO(
|
||||
base64.b64decode(
|
||||
b"""
|
||||
UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA
|
||||
BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL
|
||||
AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA
|
||||
AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB
|
||||
ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A
|
||||
BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB
|
||||
//kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA
|
||||
AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH
|
||||
AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA
|
||||
ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv
|
||||
AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/
|
||||
/gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5
|
||||
AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA
|
||||
AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG
|
||||
AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA
|
||||
BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA
|
||||
AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA
|
||||
2AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF
|
||||
AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA////
|
||||
+gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA
|
||||
////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA
|
||||
BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv
|
||||
AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA
|
||||
AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA
|
||||
AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA
|
||||
BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP//
|
||||
//kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA
|
||||
AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF
|
||||
AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB
|
||||
mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn
|
||||
AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA
|
||||
AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7
|
||||
AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA
|
||||
Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB
|
||||
//sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA
|
||||
AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ
|
||||
AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC
|
||||
DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ
|
||||
AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/
|
||||
+wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5
|
||||
AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/
|
||||
///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG
|
||||
AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA
|
||||
BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA
|
||||
Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC
|
||||
eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG
|
||||
AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA////
|
||||
+gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA
|
||||
////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA
|
||||
BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT
|
||||
AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A
|
||||
AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA
|
||||
Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA
|
||||
Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP//
|
||||
//cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA
|
||||
AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ
|
||||
AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA
|
||||
LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5
|
||||
AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA
|
||||
AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5
|
||||
AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA
|
||||
AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG
|
||||
AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA
|
||||
EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK
|
||||
AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
|
||||
pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
|
||||
AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
|
||||
+QAGAAIAzgAKANUAEw==
|
||||
"""
|
||||
)
|
||||
),
|
||||
Image.open(
|
||||
BytesIO(
|
||||
base64.b64decode(
|
||||
b"""
|
||||
iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
|
||||
Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
|
||||
M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
|
||||
LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F
|
||||
IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA
|
||||
Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791
|
||||
NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx
|
||||
in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9
|
||||
SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY
|
||||
AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt
|
||||
y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG
|
||||
ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY
|
||||
lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H
|
||||
/Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3
|
||||
AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47
|
||||
c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/
|
||||
/yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw
|
||||
pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv
|
||||
oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR
|
||||
evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
|
||||
AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
|
||||
Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
|
||||
w7IkEbzhVQAAAABJRU5ErkJggg==
|
||||
"""
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
return f
|
||||
|
||||
|
||||
def load_default(size: float | None = None) -> FreeTypeFont | ImageFont:
|
||||
"""If FreeType support is available, load a version of Aileron Regular,
|
||||
https://dotcolon.net/font/aileron, with a more limited character set.
|
||||
|
@ -920,9 +1056,8 @@ def load_default(size: float | None = None) -> FreeTypeFont | ImageFont:
|
|||
|
||||
:return: A font object.
|
||||
"""
|
||||
f: FreeTypeFont | ImageFont
|
||||
if isinstance(core, ModuleType) or size is not None:
|
||||
f = truetype(
|
||||
return truetype(
|
||||
BytesIO(
|
||||
base64.b64decode(
|
||||
b"""
|
||||
|
@ -1152,137 +1287,4 @@ AAAAAAQAAAADa3tfFAAAAANAan9kAAAAA4QodoQ==
|
|||
10 if size is None else size,
|
||||
layout_engine=Layout.BASIC,
|
||||
)
|
||||
else:
|
||||
f = ImageFont()
|
||||
f._load_pilfont_data(
|
||||
# courB08
|
||||
BytesIO(
|
||||
base64.b64decode(
|
||||
b"""
|
||||
UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA
|
||||
BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL
|
||||
AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA
|
||||
AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB
|
||||
ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A
|
||||
BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB
|
||||
//kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA
|
||||
AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH
|
||||
AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA
|
||||
ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv
|
||||
AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/
|
||||
/gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5
|
||||
AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA
|
||||
AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG
|
||||
AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA
|
||||
BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA
|
||||
AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA
|
||||
2AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF
|
||||
AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA////
|
||||
+gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA
|
||||
////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA
|
||||
BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv
|
||||
AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA
|
||||
AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA
|
||||
AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA
|
||||
BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP//
|
||||
//kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA
|
||||
AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF
|
||||
AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB
|
||||
mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn
|
||||
AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA
|
||||
AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7
|
||||
AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA
|
||||
Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB
|
||||
//sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA
|
||||
AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ
|
||||
AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC
|
||||
DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ
|
||||
AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/
|
||||
+wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5
|
||||
AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/
|
||||
///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG
|
||||
AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA
|
||||
BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA
|
||||
Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC
|
||||
eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG
|
||||
AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA////
|
||||
+gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA
|
||||
////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA
|
||||
BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT
|
||||
AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A
|
||||
AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA
|
||||
Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA
|
||||
Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP//
|
||||
//cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA
|
||||
AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ
|
||||
AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA
|
||||
LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5
|
||||
AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA
|
||||
AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5
|
||||
AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA
|
||||
AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG
|
||||
AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA
|
||||
EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK
|
||||
AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
|
||||
pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
|
||||
AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
|
||||
+QAGAAIAzgAKANUAEw==
|
||||
"""
|
||||
)
|
||||
),
|
||||
Image.open(
|
||||
BytesIO(
|
||||
base64.b64decode(
|
||||
b"""
|
||||
iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
|
||||
Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
|
||||
M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
|
||||
LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F
|
||||
IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA
|
||||
Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791
|
||||
NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx
|
||||
in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9
|
||||
SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY
|
||||
AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt
|
||||
y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG
|
||||
ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY
|
||||
lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H
|
||||
/Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3
|
||||
AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47
|
||||
c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/
|
||||
/yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw
|
||||
pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv
|
||||
oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR
|
||||
evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
|
||||
AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
|
||||
Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
|
||||
w7IkEbzhVQAAAABJRU5ErkJggg==
|
||||
"""
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
return f
|
||||
return load_default_imagefont()
|
||||
|
|
|
@ -21,7 +21,8 @@ from __future__ import annotations
|
|||
import functools
|
||||
import operator
|
||||
import re
|
||||
from typing import Protocol, Sequence, cast
|
||||
from collections.abc import Sequence
|
||||
from typing import Protocol, cast
|
||||
|
||||
from . import ExifTags, Image, ImagePalette
|
||||
|
||||
|
@ -361,7 +362,9 @@ def pad(
|
|||
else:
|
||||
out = Image.new(image.mode, size, color)
|
||||
if resized.palette:
|
||||
out.putpalette(resized.getpalette())
|
||||
palette = resized.getpalette()
|
||||
if palette is not None:
|
||||
out.putpalette(palette)
|
||||
if resized.width != size[0]:
|
||||
x = round((size[0] - resized.width) * max(0, min(centering[0], 1)))
|
||||
out.paste(resized, (x, 0))
|
||||
|
@ -698,7 +701,6 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image
|
|||
transposed_image = image.transpose(method)
|
||||
if in_place:
|
||||
image.im = transposed_image.im
|
||||
image.pyaccess = None
|
||||
image._size = transposed_image._size
|
||||
exif_image = image if in_place else transposed_image
|
||||
|
||||
|
@ -709,17 +711,18 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image
|
|||
exif_image.info["exif"] = exif.tobytes()
|
||||
elif "Raw profile type exif" in exif_image.info:
|
||||
exif_image.info["Raw profile type exif"] = exif.tobytes().hex()
|
||||
elif "XML:com.adobe.xmp" in exif_image.info:
|
||||
for pattern in (
|
||||
r'tiff:Orientation="([0-9])"',
|
||||
r"<tiff:Orientation>([0-9])</tiff:Orientation>",
|
||||
):
|
||||
exif_image.info["XML:com.adobe.xmp"] = re.sub(
|
||||
pattern, "", exif_image.info["XML:com.adobe.xmp"]
|
||||
)
|
||||
exif_image.info["xmp"] = re.sub(
|
||||
pattern.encode(), b"", exif_image.info["xmp"]
|
||||
)
|
||||
for key in ("XML:com.adobe.xmp", "xmp"):
|
||||
if key in exif_image.info:
|
||||
for pattern in (
|
||||
r'tiff:Orientation="([0-9])"',
|
||||
r"<tiff:Orientation>([0-9])</tiff:Orientation>",
|
||||
):
|
||||
value = exif_image.info[key]
|
||||
exif_image.info[key] = (
|
||||
re.sub(pattern, "", value)
|
||||
if isinstance(value, str)
|
||||
else re.sub(pattern.encode(), b"", value)
|
||||
)
|
||||
if not in_place:
|
||||
return transposed_image
|
||||
elif not in_place:
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import array
|
||||
from typing import IO, TYPE_CHECKING, Sequence
|
||||
from collections.abc import Sequence
|
||||
from typing import IO, TYPE_CHECKING
|
||||
|
||||
from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
|
||||
|
||||
|
|
|
@ -118,6 +118,8 @@ class Viewer:
|
|||
"""
|
||||
Display given file.
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError
|
||||
os.system(self.get_command(path, **options)) # nosec
|
||||
return 1
|
||||
|
||||
|
@ -142,6 +144,8 @@ class WindowsViewer(Viewer):
|
|||
"""
|
||||
Display given file.
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError
|
||||
subprocess.Popen(
|
||||
self.get_command(path, **options),
|
||||
shell=True,
|
||||
|
@ -171,6 +175,8 @@ class MacViewer(Viewer):
|
|||
"""
|
||||
Display given file.
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError
|
||||
subprocess.call(["open", "-a", "Preview.app", path])
|
||||
executable = sys.executable or shutil.which("python3")
|
||||
if executable:
|
||||
|
@ -215,6 +221,8 @@ class XDGViewer(UnixViewer):
|
|||
"""
|
||||
Display given file.
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError
|
||||
subprocess.Popen(["xdg-open", path])
|
||||
return 1
|
||||
|
||||
|
@ -237,6 +245,8 @@ class DisplayViewer(UnixViewer):
|
|||
"""
|
||||
Display given file.
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError
|
||||
args = ["display"]
|
||||
title = options.get("title")
|
||||
if title:
|
||||
|
@ -259,6 +269,8 @@ class GmDisplayViewer(UnixViewer):
|
|||
"""
|
||||
Display given file.
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError
|
||||
subprocess.Popen(["gm", "display", path])
|
||||
return 1
|
||||
|
||||
|
@ -275,6 +287,8 @@ class EogViewer(UnixViewer):
|
|||
"""
|
||||
Display given file.
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError
|
||||
subprocess.Popen(["eog", "-n", path])
|
||||
return 1
|
||||
|
||||
|
@ -299,6 +313,8 @@ class XVViewer(UnixViewer):
|
|||
"""
|
||||
Display given file.
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError
|
||||
args = ["xv"]
|
||||
title = options.get("title")
|
||||
if title:
|
||||
|
|
|
@ -28,8 +28,9 @@ from __future__ import annotations
|
|||
|
||||
import tkinter
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
|
||||
from . import Image
|
||||
from . import Image, ImageFile
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Check for Tkinter interface hooks
|
||||
|
@ -49,14 +50,15 @@ def _pilbitmap_check() -> int:
|
|||
return _pilbitmap_ok
|
||||
|
||||
|
||||
def _get_image_from_kw(kw):
|
||||
def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None:
|
||||
source = None
|
||||
if "file" in kw:
|
||||
source = kw.pop("file")
|
||||
elif "data" in kw:
|
||||
source = BytesIO(kw.pop("data"))
|
||||
if source:
|
||||
return Image.open(source)
|
||||
if not source:
|
||||
return None
|
||||
return Image.open(source)
|
||||
|
||||
|
||||
def _pyimagingtkcall(command, photo, id):
|
||||
|
@ -96,12 +98,27 @@ class PhotoImage:
|
|||
image file).
|
||||
"""
|
||||
|
||||
def __init__(self, image=None, size=None, **kw):
|
||||
def __init__(
|
||||
self,
|
||||
image: Image.Image | str | None = None,
|
||||
size: tuple[int, int] | None = None,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
# Tk compatibility: file or data
|
||||
if image is None:
|
||||
image = _get_image_from_kw(kw)
|
||||
|
||||
if hasattr(image, "mode") and hasattr(image, "size"):
|
||||
if image is None:
|
||||
msg = "Image is required"
|
||||
raise ValueError(msg)
|
||||
elif isinstance(image, str):
|
||||
mode = image
|
||||
image = None
|
||||
|
||||
if size is None:
|
||||
msg = "If first argument is mode, size is required"
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
# got an image instead of a mode
|
||||
mode = image.mode
|
||||
if mode == "P":
|
||||
|
@ -114,9 +131,6 @@ class PhotoImage:
|
|||
mode = "RGB" # default
|
||||
size = image.size
|
||||
kw["width"], kw["height"] = size
|
||||
else:
|
||||
mode = image
|
||||
image = None
|
||||
|
||||
if mode not in ["1", "L", "RGB", "RGBA"]:
|
||||
mode = Image.getmodebase(mode)
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
#
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Sequence
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
from . import Image
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
#
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from io import BytesIO
|
||||
from typing import Sequence
|
||||
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i16be as i16
|
||||
|
|
|
@ -18,7 +18,7 @@ from __future__ import annotations
|
|||
import io
|
||||
import os
|
||||
import struct
|
||||
from typing import IO, Tuple, cast
|
||||
from typing import IO, cast
|
||||
|
||||
from . import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
|
@ -82,7 +82,7 @@ class BoxReader:
|
|||
self.remaining_in_box = -1
|
||||
|
||||
# Read the length and type of the next box
|
||||
lbox, tbox = cast(Tuple[int, bytes], self.read_fields(">I4s"))
|
||||
lbox, tbox = cast(tuple[int, bytes], self.read_fields(">I4s"))
|
||||
if lbox == 1:
|
||||
lbox = cast(int, self.read_fields(">Q")[0])
|
||||
hlen = 16
|
||||
|
|
|
@ -96,7 +96,7 @@ def APP(self, marker):
|
|||
self.info["exif"] = s
|
||||
self._exif_offset = self.fp.tell() - n + 6
|
||||
elif marker == 0xFFE1 and s[:29] == b"http://ns.adobe.com/xap/1.0/\x00":
|
||||
self.info["xmp"] = s.split(b"\x00")[1]
|
||||
self.info["xmp"] = s.split(b"\x00", 1)[1]
|
||||
elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
|
||||
# extract FlashPix information (incomplete)
|
||||
self.info["flashpix"] = s # FIXME: value will change
|
||||
|
@ -161,38 +161,6 @@ def APP(self, marker):
|
|||
# plus constant header size
|
||||
self.info["mpoffset"] = self.fp.tell() - n + 4
|
||||
|
||||
# If DPI isn't in JPEG header, fetch from EXIF
|
||||
if "dpi" not in self.info and "exif" in self.info:
|
||||
try:
|
||||
exif = self.getexif()
|
||||
resolution_unit = exif[0x0128]
|
||||
x_resolution = exif[0x011A]
|
||||
try:
|
||||
dpi = float(x_resolution[0]) / x_resolution[1]
|
||||
except TypeError:
|
||||
dpi = x_resolution
|
||||
if math.isnan(dpi):
|
||||
msg = "DPI is not a number"
|
||||
raise ValueError(msg)
|
||||
if resolution_unit == 3: # cm
|
||||
# 1 dpcm = 2.54 dpi
|
||||
dpi *= 2.54
|
||||
self.info["dpi"] = dpi, dpi
|
||||
except (
|
||||
struct.error,
|
||||
KeyError,
|
||||
SyntaxError,
|
||||
TypeError,
|
||||
ValueError,
|
||||
ZeroDivisionError,
|
||||
):
|
||||
# struct.error for truncated EXIF
|
||||
# KeyError for dpi not included
|
||||
# SyntaxError for invalid/unreadable EXIF
|
||||
# ValueError or TypeError for dpi being an invalid float
|
||||
# ZeroDivisionError for invalid dpi rational value
|
||||
self.info["dpi"] = 72, 72
|
||||
|
||||
|
||||
def COM(self: JpegImageFile, marker: int) -> None:
|
||||
#
|
||||
|
@ -411,6 +379,8 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
msg = "no marker found"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
self._read_dpi_from_exif()
|
||||
|
||||
def load_read(self, read_bytes: int) -> bytes:
|
||||
"""
|
||||
internal: read more image data
|
||||
|
@ -499,6 +469,35 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
def _getexif(self) -> dict[str, Any] | None:
|
||||
return _getexif(self)
|
||||
|
||||
def _read_dpi_from_exif(self) -> None:
|
||||
# If DPI isn't in JPEG header, fetch from EXIF
|
||||
if "dpi" in self.info or "exif" not in self.info:
|
||||
return
|
||||
try:
|
||||
exif = self.getexif()
|
||||
resolution_unit = exif[0x0128]
|
||||
x_resolution = exif[0x011A]
|
||||
try:
|
||||
dpi = float(x_resolution[0]) / x_resolution[1]
|
||||
except TypeError:
|
||||
dpi = x_resolution
|
||||
if math.isnan(dpi):
|
||||
msg = "DPI is not a number"
|
||||
raise ValueError(msg)
|
||||
if resolution_unit == 3: # cm
|
||||
# 1 dpcm = 2.54 dpi
|
||||
dpi *= 2.54
|
||||
self.info["dpi"] = dpi, dpi
|
||||
except (
|
||||
struct.error, # truncated EXIF
|
||||
KeyError, # dpi not included
|
||||
SyntaxError, # invalid/unreadable EXIF
|
||||
TypeError, # dpi is an invalid float
|
||||
ValueError, # dpi is an invalid float
|
||||
ZeroDivisionError, # invalid dpi rational value
|
||||
):
|
||||
self.info["dpi"] = 72, 72
|
||||
|
||||
def _getmp(self):
|
||||
return _getmp(self)
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
self.__fp = self.fp
|
||||
self.seek(0)
|
||||
|
||||
def seek(self, frame):
|
||||
def seek(self, frame: int) -> None:
|
||||
if not self._seek_check(frame):
|
||||
return
|
||||
try:
|
||||
|
|
|
@ -8,7 +8,7 @@ import os
|
|||
import re
|
||||
import time
|
||||
import zlib
|
||||
from typing import TYPE_CHECKING, Any, List, NamedTuple, Union
|
||||
from typing import TYPE_CHECKING, Any, NamedTuple, Union
|
||||
|
||||
|
||||
# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
|
||||
|
@ -240,7 +240,7 @@ class PdfName:
|
|||
return bytes(result)
|
||||
|
||||
|
||||
class PdfArray(List[Any]):
|
||||
class PdfArray(list[Any]):
|
||||
def __bytes__(self) -> bytes:
|
||||
return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"
|
||||
|
||||
|
|
|
@ -851,8 +851,6 @@ class PngImageFile(ImageFile.ImageFile):
|
|||
self.png.rewind()
|
||||
self.__prepare_idat = self.__rewind_idat
|
||||
self.im = None
|
||||
if self.pyaccess:
|
||||
self.pyaccess = None
|
||||
self.info = self.png.im_info
|
||||
self.tile = self.png.im_tile
|
||||
self.fp = self._fp
|
||||
|
@ -1039,8 +1037,6 @@ class PngImageFile(ImageFile.ImageFile):
|
|||
mask = updated.convert("RGBA")
|
||||
self._prev_im.paste(updated, self.dispose_extent, mask)
|
||||
self.im = self._prev_im
|
||||
if self.pyaccess:
|
||||
self.pyaccess = None
|
||||
|
||||
def _getexif(self) -> dict[str, Any] | None:
|
||||
if "exif" not in self.info:
|
||||
|
|
|
@ -1,381 +0,0 @@
|
|||
#
|
||||
# The Python Imaging Library
|
||||
# Pillow fork
|
||||
#
|
||||
# Python implementation of the PixelAccess Object
|
||||
#
|
||||
# Copyright (c) 1997-2009 by Secret Labs AB. All rights reserved.
|
||||
# Copyright (c) 1995-2009 by Fredrik Lundh.
|
||||
# Copyright (c) 2013 Eric Soroos
|
||||
#
|
||||
# See the README file for information on usage and redistribution
|
||||
#
|
||||
|
||||
# Notes:
|
||||
#
|
||||
# * Implements the pixel access object following Access.c
|
||||
# * Taking only the tuple form, which is used from python.
|
||||
# * Fill.c uses the integer form, but it's still going to use the old
|
||||
# Access.c implementation.
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ._deprecate import deprecate
|
||||
|
||||
FFI: type
|
||||
try:
|
||||
from cffi import FFI
|
||||
|
||||
defs = """
|
||||
struct Pixel_RGBA {
|
||||
unsigned char r,g,b,a;
|
||||
};
|
||||
struct Pixel_I16 {
|
||||
unsigned char l,r;
|
||||
};
|
||||
"""
|
||||
ffi = FFI()
|
||||
ffi.cdef(defs)
|
||||
except ImportError as ex:
|
||||
# Allow error import for doc purposes, but error out when accessing
|
||||
# anything in core.
|
||||
from ._util import DeferredError
|
||||
|
||||
FFI = ffi = DeferredError.new(ex)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import Image
|
||||
|
||||
|
||||
class PyAccess:
|
||||
def __init__(self, img: Image.Image, readonly: bool = False) -> None:
|
||||
deprecate("PyAccess", 11)
|
||||
vals = dict(img.im.unsafe_ptrs)
|
||||
self.readonly = readonly
|
||||
self.image8 = ffi.cast("unsigned char **", vals["image8"])
|
||||
self.image32 = ffi.cast("int **", vals["image32"])
|
||||
self.image = ffi.cast("unsigned char **", vals["image"])
|
||||
self.xsize, self.ysize = img.im.size
|
||||
self._img = img
|
||||
|
||||
# Keep pointer to im object to prevent dereferencing.
|
||||
self._im = img.im
|
||||
if self._im.mode in ("P", "PA"):
|
||||
self._palette = img.palette
|
||||
|
||||
# Debugging is polluting test traces, only useful here
|
||||
# when hacking on PyAccess
|
||||
# logger.debug("%s", vals)
|
||||
self._post_init()
|
||||
|
||||
def _post_init(self) -> None:
|
||||
pass
|
||||
|
||||
def __setitem__(
|
||||
self,
|
||||
xy: tuple[int, int] | list[int],
|
||||
color: float | tuple[int, ...] | list[int],
|
||||
) -> None:
|
||||
"""
|
||||
Modifies the pixel at x,y. The color is given as a single
|
||||
numerical value for single band images, and a tuple for
|
||||
multi-band images. In addition to this, RGB and RGBA tuples
|
||||
are accepted for P and PA images.
|
||||
|
||||
:param xy: The pixel coordinate, given as (x, y). See
|
||||
:ref:`coordinate-system`.
|
||||
:param color: The pixel value.
|
||||
"""
|
||||
if self.readonly:
|
||||
msg = "Attempt to putpixel a read only image"
|
||||
raise ValueError(msg)
|
||||
(x, y) = xy
|
||||
if x < 0:
|
||||
x = self.xsize + x
|
||||
if y < 0:
|
||||
y = self.ysize + y
|
||||
(x, y) = self.check_xy((x, y))
|
||||
|
||||
if (
|
||||
self._im.mode in ("P", "PA")
|
||||
and isinstance(color, (list, tuple))
|
||||
and len(color) in [3, 4]
|
||||
):
|
||||
# RGB or RGBA value for a P or PA image
|
||||
if self._im.mode == "PA":
|
||||
alpha = color[3] if len(color) == 4 else 255
|
||||
color = color[:3]
|
||||
palette_index = self._palette.getcolor(color, self._img)
|
||||
color = (palette_index, alpha) if self._im.mode == "PA" else palette_index
|
||||
|
||||
return self.set_pixel(x, y, color)
|
||||
|
||||
def __getitem__(self, xy: tuple[int, int] | list[int]) -> float | tuple[int, ...]:
|
||||
"""
|
||||
Returns the pixel at x,y. The pixel is returned as a single
|
||||
value for single band images or a tuple for multiple band
|
||||
images
|
||||
|
||||
:param xy: The pixel coordinate, given as (x, y). See
|
||||
:ref:`coordinate-system`.
|
||||
:returns: a pixel value for single band images, a tuple of
|
||||
pixel values for multiband images.
|
||||
"""
|
||||
(x, y) = xy
|
||||
if x < 0:
|
||||
x = self.xsize + x
|
||||
if y < 0:
|
||||
y = self.ysize + y
|
||||
(x, y) = self.check_xy((x, y))
|
||||
return self.get_pixel(x, y)
|
||||
|
||||
putpixel = __setitem__
|
||||
getpixel = __getitem__
|
||||
|
||||
def check_xy(self, xy: tuple[int, int]) -> tuple[int, int]:
|
||||
(x, y) = xy
|
||||
if not (0 <= x < self.xsize and 0 <= y < self.ysize):
|
||||
msg = "pixel location out of range"
|
||||
raise ValueError(msg)
|
||||
return xy
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> float | tuple[int, ...]:
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_pixel(
|
||||
self, x: int, y: int, color: float | tuple[int, ...] | list[int]
|
||||
) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class _PyAccess32_2(PyAccess):
|
||||
"""PA, LA, stored in first and last bytes of a 32 bit word"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> tuple[int, int]:
|
||||
pixel = self.pixels[y][x]
|
||||
return pixel.r, pixel.a
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
pixel = self.pixels[y][x]
|
||||
# tuple
|
||||
pixel.r = min(color[0], 255)
|
||||
pixel.a = min(color[1], 255)
|
||||
|
||||
|
||||
class _PyAccess32_3(PyAccess):
|
||||
"""RGB and friends, stored in the first three bytes of a 32 bit word"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> tuple[int, int, int]:
|
||||
pixel = self.pixels[y][x]
|
||||
return pixel.r, pixel.g, pixel.b
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
pixel = self.pixels[y][x]
|
||||
# tuple
|
||||
pixel.r = min(color[0], 255)
|
||||
pixel.g = min(color[1], 255)
|
||||
pixel.b = min(color[2], 255)
|
||||
pixel.a = 255
|
||||
|
||||
|
||||
class _PyAccess32_4(PyAccess):
|
||||
"""RGBA etc, all 4 bytes of a 32 bit word"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> tuple[int, int, int, int]:
|
||||
pixel = self.pixels[y][x]
|
||||
return pixel.r, pixel.g, pixel.b, pixel.a
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
pixel = self.pixels[y][x]
|
||||
# tuple
|
||||
pixel.r = min(color[0], 255)
|
||||
pixel.g = min(color[1], 255)
|
||||
pixel.b = min(color[2], 255)
|
||||
pixel.a = min(color[3], 255)
|
||||
|
||||
|
||||
class _PyAccess8(PyAccess):
|
||||
"""1, L, P, 8 bit images stored as uint8"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = self.image8
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
return self.pixels[y][x]
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
try:
|
||||
# integer
|
||||
self.pixels[y][x] = min(color, 255)
|
||||
except TypeError:
|
||||
# tuple
|
||||
self.pixels[y][x] = min(color[0], 255)
|
||||
|
||||
|
||||
class _PyAccessI16_N(PyAccess):
|
||||
"""I;16 access, native bitendian without conversion"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("unsigned short **", self.image)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
return self.pixels[y][x]
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
try:
|
||||
# integer
|
||||
self.pixels[y][x] = min(color, 65535)
|
||||
except TypeError:
|
||||
# tuple
|
||||
self.pixels[y][x] = min(color[0], 65535)
|
||||
|
||||
|
||||
class _PyAccessI16_L(PyAccess):
|
||||
"""I;16L access, with conversion"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
pixel = self.pixels[y][x]
|
||||
return pixel.l + pixel.r * 256
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
pixel = self.pixels[y][x]
|
||||
try:
|
||||
color = min(color, 65535)
|
||||
except TypeError:
|
||||
color = min(color[0], 65535)
|
||||
|
||||
pixel.l = color & 0xFF
|
||||
pixel.r = color >> 8
|
||||
|
||||
|
||||
class _PyAccessI16_B(PyAccess):
|
||||
"""I;16B access, with conversion"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("struct Pixel_I16 **", self.image)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
pixel = self.pixels[y][x]
|
||||
return pixel.l * 256 + pixel.r
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
pixel = self.pixels[y][x]
|
||||
try:
|
||||
color = min(color, 65535)
|
||||
except Exception:
|
||||
color = min(color[0], 65535)
|
||||
|
||||
pixel.l = color >> 8
|
||||
pixel.r = color & 0xFF
|
||||
|
||||
|
||||
class _PyAccessI32_N(PyAccess):
|
||||
"""Signed Int32 access, native endian"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = self.image32
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
return self.pixels[y][x]
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
self.pixels[y][x] = color
|
||||
|
||||
|
||||
class _PyAccessI32_Swap(PyAccess):
|
||||
"""I;32L/B access, with byteswapping conversion"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = self.image32
|
||||
|
||||
def reverse(self, i):
|
||||
orig = ffi.new("int *", i)
|
||||
chars = ffi.cast("unsigned char *", orig)
|
||||
chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], chars[1], chars[0]
|
||||
return ffi.cast("int *", chars)[0]
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> int:
|
||||
return self.reverse(self.pixels[y][x])
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
self.pixels[y][x] = self.reverse(color)
|
||||
|
||||
|
||||
class _PyAccessF(PyAccess):
|
||||
"""32 bit float access"""
|
||||
|
||||
def _post_init(self, *args, **kwargs):
|
||||
self.pixels = ffi.cast("float **", self.image32)
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> float:
|
||||
return self.pixels[y][x]
|
||||
|
||||
def set_pixel(self, x, y, color):
|
||||
try:
|
||||
# not a tuple
|
||||
self.pixels[y][x] = color
|
||||
except TypeError:
|
||||
# tuple
|
||||
self.pixels[y][x] = color[0]
|
||||
|
||||
|
||||
mode_map = {
|
||||
"1": _PyAccess8,
|
||||
"L": _PyAccess8,
|
||||
"P": _PyAccess8,
|
||||
"I;16N": _PyAccessI16_N,
|
||||
"LA": _PyAccess32_2,
|
||||
"La": _PyAccess32_2,
|
||||
"PA": _PyAccess32_2,
|
||||
"RGB": _PyAccess32_3,
|
||||
"LAB": _PyAccess32_3,
|
||||
"HSV": _PyAccess32_3,
|
||||
"YCbCr": _PyAccess32_3,
|
||||
"RGBA": _PyAccess32_4,
|
||||
"RGBa": _PyAccess32_4,
|
||||
"RGBX": _PyAccess32_4,
|
||||
"CMYK": _PyAccess32_4,
|
||||
"F": _PyAccessF,
|
||||
"I": _PyAccessI32_N,
|
||||
}
|
||||
|
||||
if sys.byteorder == "little":
|
||||
mode_map["I;16"] = _PyAccessI16_N
|
||||
mode_map["I;16L"] = _PyAccessI16_N
|
||||
mode_map["I;16B"] = _PyAccessI16_B
|
||||
|
||||
mode_map["I;32L"] = _PyAccessI32_N
|
||||
mode_map["I;32B"] = _PyAccessI32_Swap
|
||||
else:
|
||||
mode_map["I;16"] = _PyAccessI16_L
|
||||
mode_map["I;16L"] = _PyAccessI16_L
|
||||
mode_map["I;16B"] = _PyAccessI16_N
|
||||
|
||||
mode_map["I;32L"] = _PyAccessI32_Swap
|
||||
mode_map["I;32B"] = _PyAccessI32_N
|
||||
|
||||
|
||||
def new(img: Image.Image, readonly: bool = False) -> PyAccess | None:
|
||||
access_type = mode_map.get(img.mode, None)
|
||||
if not access_type:
|
||||
logger.debug("PyAccess Not Implemented: %s", img.mode)
|
||||
return None
|
||||
return access_type(img, readonly)
|
|
@ -37,7 +37,7 @@ from __future__ import annotations
|
|||
import os
|
||||
import struct
|
||||
import sys
|
||||
from typing import IO, TYPE_CHECKING, Any, Tuple, cast
|
||||
from typing import IO, TYPE_CHECKING, Any, cast
|
||||
|
||||
from . import Image, ImageFile
|
||||
|
||||
|
@ -187,7 +187,7 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
def convert2byte(self, depth: int = 255) -> Image.Image:
|
||||
extrema = self.getextrema()
|
||||
assert isinstance(extrema[0], float)
|
||||
minimum, maximum = cast(Tuple[float, float], extrema)
|
||||
minimum, maximum = cast(tuple[float, float], extrema)
|
||||
m: float = 1
|
||||
if maximum != minimum:
|
||||
m = depth / (maximum - minimum)
|
||||
|
|
|
@ -36,7 +36,7 @@ MODES = {
|
|||
(3, 1): "1",
|
||||
(3, 8): "L",
|
||||
(3, 16): "LA",
|
||||
(2, 16): "BGR;5",
|
||||
(2, 16): "BGRA;15Z",
|
||||
(2, 24): "BGR",
|
||||
(2, 32): "BGRA",
|
||||
}
|
||||
|
@ -87,9 +87,7 @@ class TgaImageFile(ImageFile.ImageFile):
|
|||
elif imagetype in (1, 9):
|
||||
self._mode = "P" if colormaptype else "L"
|
||||
elif imagetype in (2, 10):
|
||||
self._mode = "RGB"
|
||||
if depth == 32:
|
||||
self._mode = "RGBA"
|
||||
self._mode = "RGB" if depth == 24 else "RGBA"
|
||||
else:
|
||||
msg = "unknown TGA mode"
|
||||
raise SyntaxError(msg)
|
||||
|
@ -118,15 +116,16 @@ class TgaImageFile(ImageFile.ImageFile):
|
|||
start, size, mapdepth = i16(s, 3), i16(s, 5), s[7]
|
||||
if mapdepth == 16:
|
||||
self.palette = ImagePalette.raw(
|
||||
"BGR;15", b"\0" * 2 * start + self.fp.read(2 * size)
|
||||
"BGRA;15Z", bytes(2 * start) + self.fp.read(2 * size)
|
||||
)
|
||||
self.palette.mode = "RGBA"
|
||||
elif mapdepth == 24:
|
||||
self.palette = ImagePalette.raw(
|
||||
"BGR", b"\0" * 3 * start + self.fp.read(3 * size)
|
||||
"BGR", bytes(3 * start) + self.fp.read(3 * size)
|
||||
)
|
||||
elif mapdepth == 32:
|
||||
self.palette = ImagePalette.raw(
|
||||
"BGRA", b"\0" * 4 * start + self.fp.read(4 * size)
|
||||
"BGRA", bytes(4 * start) + self.fp.read(4 * size)
|
||||
)
|
||||
else:
|
||||
msg = "unknown TGA map depth"
|
||||
|
|
|
@ -1663,10 +1663,14 @@ def _save(im, fp, filename):
|
|||
legacy_ifd = im.tag.to_v2()
|
||||
|
||||
supplied_tags = {**legacy_ifd, **getattr(im, "tag_v2", {})}
|
||||
if SAMPLEFORMAT in supplied_tags:
|
||||
# SAMPLEFORMAT is determined by the image format and should not be copied
|
||||
# from legacy_ifd.
|
||||
del supplied_tags[SAMPLEFORMAT]
|
||||
for tag in (
|
||||
# IFD offset that may not be correct in the saved image
|
||||
EXIFIFD,
|
||||
# Determined by the image format and should not be copied from legacy_ifd.
|
||||
SAMPLEFORMAT,
|
||||
):
|
||||
if tag in supplied_tags:
|
||||
del supplied_tags[tag]
|
||||
|
||||
# additions written by Greg Couch, gregc@cgl.ucsf.edu
|
||||
# inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
|
||||
|
|
|
@ -45,8 +45,6 @@ def deprecate(
|
|||
elif when <= int(__version__.split(".")[0]):
|
||||
msg = f"{deprecated} {is_} deprecated and should be removed."
|
||||
raise RuntimeError(msg)
|
||||
elif when == 11:
|
||||
removed = "Pillow 11 (2024-10-15)"
|
||||
elif when == 12:
|
||||
removed = "Pillow 12 (2025-10-15)"
|
||||
else:
|
||||
|
|
|
@ -2,14 +2,16 @@ from __future__ import annotations
|
|||
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Protocol, Sequence, TypeVar, Union
|
||||
from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, Union
|
||||
|
||||
try:
|
||||
import numpy.typing as npt
|
||||
if TYPE_CHECKING:
|
||||
try:
|
||||
import numpy.typing as npt
|
||||
|
||||
NumpyArray = npt.NDArray[Any]
|
||||
except ImportError:
|
||||
pass
|
||||
NumpyArray = npt.NDArray[Any] # requires numpy>=1.21
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from typing import TypeGuard
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Master version for Pillow
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = "10.4.0.dev0"
|
||||
__version__ = "11.0.0.dev0"
|
||||
|
|
|
@ -223,20 +223,22 @@ findLCMStype(char *PILmode) {
|
|||
if (strcmp(PILmode, "CMYK") == 0) {
|
||||
return TYPE_CMYK_8;
|
||||
}
|
||||
if (strcmp(PILmode, "L;16") == 0) {
|
||||
if (strcmp(PILmode, "I;16") == 0 || strcmp(PILmode, "I;16L") == 0 ||
|
||||
strcmp(PILmode, "L;16") == 0) {
|
||||
return TYPE_GRAY_16;
|
||||
}
|
||||
if (strcmp(PILmode, "L;16B") == 0) {
|
||||
if (strcmp(PILmode, "I;16B") == 0 || strcmp(PILmode, "L;16B") == 0) {
|
||||
return TYPE_GRAY_16_SE;
|
||||
}
|
||||
if (strcmp(PILmode, "YCCA") == 0 || strcmp(PILmode, "YCC") == 0) {
|
||||
if (strcmp(PILmode, "YCbCr") == 0 || strcmp(PILmode, "YCCA") == 0 ||
|
||||
strcmp(PILmode, "YCC") == 0) {
|
||||
return TYPE_YCbCr_8;
|
||||
}
|
||||
if (strcmp(PILmode, "LAB") == 0) {
|
||||
// LabX equivalent like ALab, but not reversed -- no #define in lcms2
|
||||
return (COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1));
|
||||
}
|
||||
/* presume "L" by default */
|
||||
/* presume "1" or "L" by default */
|
||||
return TYPE_GRAY_8;
|
||||
}
|
||||
|
||||
|
|
|
@ -718,6 +718,21 @@ ImagingUnpackBGRA15(UINT8 *out, const UINT8 *in, int pixels) {
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
ImagingUnpackBGRA15Z(UINT8 *out, const UINT8 *in, int pixels) {
|
||||
int i, pixel;
|
||||
/* RGB, rearranged channels, 5/5/5/1 bits per pixel, inverted alpha */
|
||||
for (i = 0; i < pixels; i++) {
|
||||
pixel = in[0] + (in[1] << 8);
|
||||
out[B] = (pixel & 31) * 255 / 31;
|
||||
out[G] = ((pixel >> 5) & 31) * 255 / 31;
|
||||
out[R] = ((pixel >> 10) & 31) * 255 / 31;
|
||||
out[A] = ~((pixel >> 15) * 255);
|
||||
out += 4;
|
||||
in += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ImagingUnpackRGB16(UINT8 *out, const UINT8 *in, int pixels) {
|
||||
int i, pixel;
|
||||
|
@ -1538,7 +1553,7 @@ static struct {
|
|||
|
||||
/* flags: "I" inverted data; "R" reversed bit order; "B" big
|
||||
endian byte order (default is little endian); "L" line
|
||||
interleave, "S" signed, "F" floating point */
|
||||
interleave, "S" signed, "F" floating point, "Z" inverted alpha */
|
||||
|
||||
/* exception: rawmodes "I" and "F" are always native endian byte order */
|
||||
|
||||
|
@ -1646,6 +1661,7 @@ static struct {
|
|||
{"RGBA", "RGBA;L", 32, unpackRGBAL},
|
||||
{"RGBA", "RGBA;15", 16, ImagingUnpackRGBA15},
|
||||
{"RGBA", "BGRA;15", 16, ImagingUnpackBGRA15},
|
||||
{"RGBA", "BGRA;15Z", 16, ImagingUnpackBGRA15Z},
|
||||
{"RGBA", "RGBA;4B", 16, ImagingUnpackRGBA4B},
|
||||
{"RGBA", "RGBA;16L", 64, unpackRGBA16L},
|
||||
{"RGBA", "RGBA;16B", 64, unpackRGBA16B},
|
||||
|
|
4
tox.ini
4
tox.ini
|
@ -3,11 +3,10 @@ requires =
|
|||
tox>=4.2
|
||||
env_list =
|
||||
lint
|
||||
py{py3, 312, 311, 310, 39, 38}
|
||||
py{py3, 313, 312, 311, 310, 39}
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
cffi
|
||||
numpy
|
||||
extras =
|
||||
tests
|
||||
|
@ -39,7 +38,6 @@ deps =
|
|||
ipython
|
||||
numpy
|
||||
packaging
|
||||
types-cffi
|
||||
types-defusedxml
|
||||
types-olefile
|
||||
extras =
|
||||
|
|
|
@ -16,7 +16,7 @@ For more extensive info, see the [Windows build instructions](build.rst).
|
|||
|
||||
The following is a simplified version of the script used on AppVeyor:
|
||||
```
|
||||
set PYTHON=C:\Python38\bin
|
||||
set PYTHON=C:\Python39\bin
|
||||
cd /D C:\Pillow\winbuild
|
||||
%PYTHON%\python.exe build_prepare.py -v --depends=C:\pillow-depends
|
||||
build\build_dep_all.cmd
|
||||
|
|
|
@ -114,7 +114,7 @@ Example
|
|||
|
||||
The following is a simplified version of the script used on AppVeyor::
|
||||
|
||||
set PYTHON=C:\Python38\bin
|
||||
set PYTHON=C:\Python39\bin
|
||||
cd /D C:\Pillow\winbuild
|
||||
%PYTHON%\python.exe build_prepare.py -v --depends C:\pillow-depends
|
||||
build\build_dep_all.cmd
|
||||
|
|
Loading…
Reference in New Issue
Block a user