Merge branch 'main' into msys

This commit is contained in:
Andrew Murray 2025-03-04 08:53:25 +11:00
commit 799d908783
101 changed files with 985 additions and 713 deletions

View File

@ -2,12 +2,12 @@
aptget_update() aptget_update()
{ {
if [ ! -z $1 ]; then if [ -n "$1" ]; then
echo "" echo ""
echo "Retrying apt-get update..." echo "Retrying apt-get update..."
echo "" echo ""
fi fi
output=`sudo apt-get update 2>&1` output=$(sudo apt-get update 2>&1)
echo "$output" echo "$output"
if [[ $output == *[WE]:\ * ]]; then if [[ $output == *[WE]:\ * ]]; then
return 1 return 1

View File

@ -1 +1 @@
cibuildwheel==2.22.0 cibuildwheel==2.23.0

View File

@ -1,4 +1,4 @@
mypy==1.14.1 mypy==1.15.0
IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6 IceSpringPySideStubs-PySide6
ipython ipython

View File

@ -35,7 +35,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"] python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"]
architecture: ["x64"] architecture: ["x64"]
os: ["windows-latest"] os: ["windows-latest"]
include: include:

View File

@ -41,6 +41,7 @@ jobs:
"ubuntu-latest", "ubuntu-latest",
] ]
python-version: [ python-version: [
"pypy3.11",
"pypy3.10", "pypy3.10",
"3.14", "3.14",
"3.13t", "3.13t",

View File

@ -38,14 +38,14 @@ ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds # Package versions for fresh source builds
FREETYPE_VERSION=2.13.3 FREETYPE_VERSION=2.13.3
HARFBUZZ_VERSION=10.2.0 HARFBUZZ_VERSION=10.4.0
LIBPNG_VERSION=1.6.46 LIBPNG_VERSION=1.6.47
JPEGTURBO_VERSION=3.1.0 JPEGTURBO_VERSION=3.1.0
OPENJPEG_VERSION=2.5.3 OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.6.4 XZ_VERSION=5.6.4
TIFF_VERSION=4.6.0 TIFF_VERSION=4.6.0
LCMS2_VERSION=2.16 LCMS2_VERSION=2.17
ZLIB_NG_VERSION=2.2.3 ZLIB_NG_VERSION=2.2.4
LIBWEBP_VERSION=1.5.0 LIBWEBP_VERSION=1.5.0
BZIP2_VERSION=1.0.8 BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0 LIBXCB_VERSION=1.17.0
@ -54,13 +54,10 @@ BROTLI_VERSION=1.1.0
function build_pkg_config { function build_pkg_config {
if [ -e pkg-config-stamp ]; then return; fi if [ -e pkg-config-stamp ]; then return; fi
# This essentially duplicates the Homebrew recipe # This essentially duplicates the Homebrew recipe
ORIGINAL_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -Wno-int-conversion" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
CFLAGS="$CFLAGS -Wno-int-conversion"
build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
--disable-debug --disable-host-tool --with-internal-glib \ --disable-debug --disable-host-tool --with-internal-glib \
--with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \ --with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \
--with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include --with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include
CFLAGS=$ORIGINAL_CFLAGS
export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
touch pkg-config-stamp touch pkg-config-stamp
} }
@ -72,6 +69,14 @@ function build_zlib_ng {
&& ./configure --prefix=$BUILD_PREFIX --zlib-compat \ && ./configure --prefix=$BUILD_PREFIX --zlib-compat \
&& make -j4 \ && make -j4 \
&& make install) && make install)
if [ -n "$IS_MACOS" ]; then
# Ensure that on macOS, the library name is an absolute path, not an
# @rpath, so that delocate picks up the right library (and doesn't need
# DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an
# option to control the install_name.
install_name_tool -id $BUILD_PREFIX/lib/libz.1.dylib $BUILD_PREFIX/lib/libz.1.dylib
fi
touch zlib-stamp touch zlib-stamp
} }
@ -130,15 +135,13 @@ function build {
build_lcms2 build_lcms2
build_openjpeg build_openjpeg
ORIGINAL_CFLAGS=$CFLAGS webp_cflags="-O3 -DNDEBUG"
CFLAGS="$CFLAGS -O3 -DNDEBUG"
if [[ -n "$IS_MACOS" ]]; then if [[ -n "$IS_MACOS" ]]; then
CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names" webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names"
fi fi
build_simple libwebp $LIBWEBP_VERSION \ CFLAGS="$CFLAGS $webp_cflags" build_simple libwebp $LIBWEBP_VERSION \
https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \ https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \
--enable-libwebpmux --enable-libwebpdemux --enable-libwebpmux --enable-libwebpdemux
CFLAGS=$ORIGINAL_CFLAGS
build_brotli build_brotli

View File

@ -63,7 +63,7 @@ jobs:
- name: "macOS 10.15 x86_64" - name: "macOS 10.15 x86_64"
os: macos-13 os: macos-13
cibw_arch: x86_64 cibw_arch: x86_64
build: "pp310*" build: "pp3*"
macosx_deployment_target: "10.15" macosx_deployment_target: "10.15"
- name: "macOS arm64" - name: "macOS arm64"
os: macos-latest os: macos-latest

View File

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.4 rev: v0.9.9
hooks: hooks:
- id: ruff - id: ruff
args: [--exit-non-zero-on-fix] args: [--exit-non-zero-on-fix]
@ -11,7 +11,7 @@ repos:
- id: black - id: black
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit
rev: 1.8.2 rev: 1.8.3
hooks: hooks:
- id: bandit - id: bandit
args: [--severity-level=high] args: [--severity-level=high]
@ -50,14 +50,14 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/python-jsonschema/check-jsonschema - repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.31.1 rev: 0.31.2
hooks: hooks:
- id: check-github-workflows - id: check-github-workflows
- id: check-readthedocs - id: check-readthedocs
- id: check-renovate - id: check-renovate
- repo: https://github.com/woodruffw/zizmor-pre-commit - repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.3.0 rev: v1.4.1
hooks: hooks:
- id: zizmor - id: zizmor
@ -67,7 +67,7 @@ repos:
- id: sphinx-lint - id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt - repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.5.0 rev: v2.5.1
hooks: hooks:
- id: pyproject-fmt - id: pyproject-fmt

View File

@ -9,7 +9,6 @@ import os
import shutil import shutil
import subprocess import subprocess
import sys import sys
import sysconfig
import tempfile import tempfile
from collections.abc import Sequence from collections.abc import Sequence
from functools import lru_cache from functools import lru_cache
@ -342,10 +341,6 @@ def is_pypy() -> bool:
return hasattr(sys, "pypy_translation_info") return hasattr(sys, "pypy_translation_info")
def is_mingw() -> bool:
return sysconfig.get_platform() == "mingw"
class CachedProperty: class CachedProperty:
def __init__(self, func: Callable[[Any], Any]) -> None: def __init__(self, func: Callable[[Any], Any]) -> None:
self.func = func self.func = func

View File

@ -26,12 +26,12 @@ def test_sanity() -> None:
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None: def test_unclosed_file() -> None:
def open() -> None: def open_test_image() -> None:
im = Image.open(TEST_FILE) im = Image.open(TEST_FILE)
im.load() im.load()
with pytest.warns(ResourceWarning): with pytest.warns(ResourceWarning):
open() open_test_image()
def test_closed_file() -> None: def test_closed_file() -> None:

View File

@ -331,11 +331,13 @@ def test_dxt5_colorblock_alpha_issue_4142() -> None:
with Image.open("Tests/images/dxt5-colorblock-alpha-issue-4142.dds") as im: with Image.open("Tests/images/dxt5-colorblock-alpha-issue-4142.dds") as im:
px = im.getpixel((0, 0)) px = im.getpixel((0, 0))
assert isinstance(px, tuple)
assert px[0] != 0 assert px[0] != 0
assert px[1] != 0 assert px[1] != 0
assert px[2] != 0 assert px[2] != 0
px = im.getpixel((1, 0)) px = im.getpixel((1, 0))
assert isinstance(px, tuple)
assert px[0] != 0 assert px[0] != 0
assert px[1] != 0 assert px[1] != 0
assert px[2] != 0 assert px[2] != 0

View File

@ -95,10 +95,14 @@ def test_sanity(filename: str, size: tuple[int, int], scale: int) -> None:
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_load() -> None: def test_load() -> None:
with Image.open(FILE1) as im: with Image.open(FILE1) as im:
assert im.load()[0, 0] == (255, 255, 255) px = im.load()
assert px is not None
assert px[0, 0] == (255, 255, 255)
# Test again now that it has already been loaded once # Test again now that it has already been loaded once
assert im.load()[0, 0] == (255, 255, 255) px = im.load()
assert px is not None
assert px[0, 0] == (255, 255, 255)
def test_binary() -> None: def test_binary() -> None:

View File

@ -52,12 +52,12 @@ def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None: def test_unclosed_file() -> None:
def open() -> None: def open_test_image() -> None:
im = Image.open(static_test_file) im = Image.open(static_test_file)
im.load() im.load()
with pytest.warns(ResourceWarning): with pytest.warns(ResourceWarning):
open() open_test_image()
def test_closed_file() -> None: def test_closed_file() -> None:

View File

@ -1,5 +1,8 @@
from __future__ import annotations from __future__ import annotations
import io
import struct
import pytest import pytest
from PIL import FtexImagePlugin, Image from PIL import FtexImagePlugin, Image
@ -23,3 +26,15 @@ def test_invalid_file() -> None:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
FtexImagePlugin.FtexImageFile(invalid_file) FtexImagePlugin.FtexImageFile(invalid_file)
def test_invalid_texture() -> None:
with open("Tests/images/ftex_dxt1.ftc", "rb") as fp:
data = fp.read()
# Change texture compression format
data = data[:24] + struct.pack("<i", 2) + data[28:]
with pytest.raises(ValueError, match="Invalid texture compression format: 2"):
with Image.open(io.BytesIO(data)):
pass

View File

@ -14,10 +14,14 @@ def test_gbr_file() -> None:
def test_load() -> None: def test_load() -> None:
with Image.open("Tests/images/gbr.gbr") as im: with Image.open("Tests/images/gbr.gbr") as im:
assert im.load()[0, 0] == (0, 0, 0, 0) px = im.load()
assert px is not None
assert px[0, 0] == (0, 0, 0, 0)
# Test again now that it has already been loaded once # Test again now that it has already been loaded once
assert im.load()[0, 0] == (0, 0, 0, 0) px = im.load()
assert px is not None
assert px[0, 0] == (0, 0, 0, 0)
def test_multiple_load_operations() -> None: def test_multiple_load_operations() -> None:

View File

@ -4,6 +4,8 @@ import pytest
from PIL import GdImageFile, UnidentifiedImageError from PIL import GdImageFile, UnidentifiedImageError
from .helper import assert_image_similar_tofile
TEST_GD_FILE = "Tests/images/hopper.gd" TEST_GD_FILE = "Tests/images/hopper.gd"
@ -11,6 +13,7 @@ def test_sanity() -> None:
with GdImageFile.open(TEST_GD_FILE) as im: with GdImageFile.open(TEST_GD_FILE) as im:
assert im.size == (128, 128) assert im.size == (128, 128)
assert im.format == "GD" assert im.format == "GD"
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.jpg", 14)
def test_bad_mode() -> None: def test_bad_mode() -> None:

View File

@ -22,9 +22,6 @@ from .helper import (
# sample gif stream # sample gif stream
TEST_GIF = "Tests/images/hopper.gif" TEST_GIF = "Tests/images/hopper.gif"
with open(TEST_GIF, "rb") as f:
data = f.read()
def test_sanity() -> None: def test_sanity() -> None:
with Image.open(TEST_GIF) as im: with Image.open(TEST_GIF) as im:
@ -37,12 +34,12 @@ def test_sanity() -> None:
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None: def test_unclosed_file() -> None:
def open() -> None: def open_test_image() -> None:
im = Image.open(TEST_GIF) im = Image.open(TEST_GIF)
im.load() im.load()
with pytest.warns(ResourceWarning): with pytest.warns(ResourceWarning):
open() open_test_image()
def test_closed_file() -> None: def test_closed_file() -> None:
@ -310,6 +307,7 @@ def test_roundtrip_save_all_1(tmp_path: Path) -> None:
def test_loading_multiple_palettes(path: str, mode: str) -> None: def test_loading_multiple_palettes(path: str, mode: str) -> None:
with Image.open(path) as im: with Image.open(path) as im:
assert im.mode == "P" assert im.mode == "P"
assert im.palette is not None
first_frame_colors = im.palette.colors.keys() first_frame_colors = im.palette.colors.keys()
original_color = im.convert("RGB").getpixel((0, 0)) original_color = im.convert("RGB").getpixel((0, 0))
@ -528,6 +526,7 @@ def test_dispose_background_transparency() -> None:
with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img: with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img:
img.seek(2) img.seek(2)
px = img.load() px = img.load()
assert px is not None
assert px[35, 30][3] == 0 assert px[35, 30][3] == 0
@ -762,6 +761,21 @@ def test_dispose2_previous_frame(tmp_path: Path) -> None:
assert im.getpixel((0, 0)) == (0, 0, 0, 255) assert im.getpixel((0, 0)) == (0, 0, 0, 255)
def test_dispose2_without_transparency(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im = Image.new("P", (100, 100))
im2 = Image.new("P", (100, 100), (0, 0, 0))
im2.putpixel((50, 50), (255, 0, 0))
im.save(out, save_all=True, append_images=[im2], disposal=2)
with Image.open(out) as reloaded:
reloaded.seek(1)
assert reloaded.tile[0].extents == (0, 0, 100, 100)
def test_transparency_in_second_frame(tmp_path: Path) -> None: def test_transparency_in_second_frame(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/different_transparency.gif") as im: with Image.open("Tests/images/different_transparency.gif") as im:
@ -1311,6 +1325,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None:
with Image.open(out) as im: with Image.open(out) as im:
# Assert that the frames are correct, and each frame has the same palette # Assert that the frames are correct, and each frame has the same palette
assert_image_equal(im.convert("RGB"), frames[0].convert("RGB")) assert_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
assert im.palette is not None
assert im.palette.palette == im.global_palette.palette assert im.palette.palette == im.global_palette.palette
im.seek(1) im.seek(1)
@ -1345,32 +1360,30 @@ def test_save_I(tmp_path: Path) -> None:
assert_image_equal(reloaded.convert("L"), im.convert("L")) assert_image_equal(reloaded.convert("L"), im.convert("L"))
def test_getdata() -> None: def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None:
# Test getheader/getdata against legacy values. # Test getheader/getdata against legacy values.
# Create a 'P' image with holes in the palette. # Create a 'P' image with holes in the palette.
im = Image._wedge().resize((16, 16), Image.Resampling.NEAREST) im = Image.linear_gradient(mode="L").resize((16, 16), Image.Resampling.NEAREST)
im.putpalette(ImagePalette.ImagePalette("RGB")) im.putpalette(ImagePalette.ImagePalette("RGB"))
im.info = {"background": 0} im.info = {"background": 0}
passed_palette = bytes(255 - i // 3 for i in range(768)) passed_palette = bytes(255 - i // 3 for i in range(768))
GifImagePlugin._FORCE_OPTIMIZE = True monkeypatch.setattr(GifImagePlugin, "_FORCE_OPTIMIZE", True)
try:
h = GifImagePlugin.getheader(im, passed_palette)
d = GifImagePlugin.getdata(im)
import pickle h = GifImagePlugin.getheader(im, passed_palette)
d = GifImagePlugin.getdata(im)
# Enable to get target values on pre-refactor version import pickle
# with open('Tests/images/gif_header_data.pkl', 'wb') as f:
# pickle.dump((h, d), f, 1)
with open("Tests/images/gif_header_data.pkl", "rb") as f:
(h_target, d_target) = pickle.load(f)
assert h == h_target # Enable to get target values on pre-refactor version
assert d == d_target # with open('Tests/images/gif_header_data.pkl', 'wb') as f:
finally: # pickle.dump((h, d), f, 1)
GifImagePlugin._FORCE_OPTIMIZE = False with open("Tests/images/gif_header_data.pkl", "rb") as f:
(h_target, d_target) = pickle.load(f)
assert h == h_target
assert d == d_target
def test_lzw_bits() -> None: def test_lzw_bits() -> None:

View File

@ -32,10 +32,14 @@ def test_sanity() -> None:
def test_load() -> None: def test_load() -> None:
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
assert im.load()[0, 0] == (0, 0, 0, 0) px = im.load()
assert px is not None
assert px[0, 0] == (0, 0, 0, 0)
# Test again now that it has already been loaded once # Test again now that it has already been loaded once
assert im.load()[0, 0] == (0, 0, 0, 0) px = im.load()
assert px is not None
assert px[0, 0] == (0, 0, 0, 0)
def test_save(tmp_path: Path) -> None: def test_save(tmp_path: Path) -> None:

View File

@ -24,7 +24,9 @@ def test_sanity() -> None:
def test_load() -> None: def test_load() -> None:
with Image.open(TEST_ICO_FILE) as im: with Image.open(TEST_ICO_FILE) as im:
assert im.load()[0, 0] == (1, 1, 9, 255) px = im.load()
assert px is not None
assert px[0, 0] == (1, 1, 9, 255)
def test_mask() -> None: def test_mask() -> None:

View File

@ -31,12 +31,12 @@ def test_name_limit(tmp_path: Path) -> None:
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None: def test_unclosed_file() -> None:
def open() -> None: def open_test_image() -> None:
im = Image.open(TEST_IM) im = Image.open(TEST_IM)
im.load() im.load()
with pytest.warns(ResourceWarning): with pytest.warns(ResourceWarning):
open() open_test_image()
def test_closed_file() -> None: def test_closed_file() -> None:

View File

@ -63,6 +63,7 @@ def test_sanity() -> None:
with Image.open("Tests/images/test-card-lossless.jp2") as im: with Image.open("Tests/images/test-card-lossless.jp2") as im:
px = im.load() px = im.load()
assert px is not None
assert px[0, 0] == (0, 0, 0) assert px[0, 0] == (0, 0, 0)
assert im.mode == "RGB" assert im.mode == "RGB"
assert im.size == (640, 480) assert im.size == (640, 480)
@ -421,6 +422,7 @@ def test_subsampling_decode(name: str) -> None:
def test_pclr() -> None: def test_pclr() -> None:
with Image.open(f"{EXTRA_DIR}/issue104_jpxstream.jp2") as im: with Image.open(f"{EXTRA_DIR}/issue104_jpxstream.jp2") as im:
assert im.mode == "P" assert im.mode == "P"
assert im.palette is not None
assert len(im.palette.colors) == 256 assert len(im.palette.colors) == 256
assert im.palette.colors[(255, 255, 255)] == 0 assert im.palette.colors[(255, 255, 255)] == 0
@ -428,6 +430,7 @@ def test_pclr() -> None:
f"{EXTRA_DIR}/147af3f1083de4393666b7d99b01b58b_signal_sigsegv_130c531_6155_5136.jp2" f"{EXTRA_DIR}/147af3f1083de4393666b7d99b01b58b_signal_sigsegv_130c531_6155_5136.jp2"
) as im: ) as im:
assert im.mode == "P" assert im.mode == "P"
assert im.palette is not None
assert len(im.palette.colors) == 139 assert len(im.palette.colors) == 139
assert im.palette.colors[(0, 0, 0, 0)] == 0 assert im.palette.colors[(0, 0, 0, 0)] == 0

View File

@ -29,21 +29,26 @@ def roundtrip(im: Image.Image, **options: Any) -> ImageFile.ImageFile:
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
def test_sanity(test_file: str) -> None: def test_sanity(test_file: str) -> None:
with Image.open(test_file) as im: def check(im: ImageFile.ImageFile) -> None:
im.load() im.load()
assert im.mode == "RGB" assert im.mode == "RGB"
assert im.size == (640, 480) assert im.size == (640, 480)
assert im.format == "MPO" assert im.format == "MPO"
with Image.open(test_file) as im:
check(im)
with MpoImagePlugin.MpoImageFile(test_file) as im:
check(im)
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None: def test_unclosed_file() -> None:
def open() -> None: def open_test_image() -> None:
im = Image.open(test_files[0]) im = Image.open(test_files[0])
im.load() im.load()
with pytest.warns(ResourceWarning): with pytest.warns(ResourceWarning):
open() open_test_image()
def test_closed_file() -> None: def test_closed_file() -> None:
@ -77,8 +82,8 @@ def test_app(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.applist[0][0] == "APP1" assert im.applist[0][0] == "APP1"
assert im.applist[1][0] == "APP2" assert im.applist[1][0] == "APP2"
assert ( assert im.applist[1][1].startswith(
im.applist[1][1][:16] == b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00" b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00"
) )
assert len(im.applist) == 2 assert len(im.applist) == 2

View File

@ -79,6 +79,7 @@ def test_arbitrary_maxval(
assert im.mode == mode assert im.mode == mode
px = im.load() px = im.load()
assert px is not None
assert tuple(px[x, 0] for x in range(3)) == pixels assert tuple(px[x, 0] for x in range(3)) == pixels

View File

@ -25,12 +25,12 @@ def test_sanity() -> None:
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None: def test_unclosed_file() -> None:
def open() -> None: def open_test_image() -> None:
im = Image.open(test_file) im = Image.open(test_file)
im.load() im.load()
with pytest.warns(ResourceWarning): with pytest.warns(ResourceWarning):
open() open_test_image()
def test_closed_file() -> None: def test_closed_file() -> None:

View File

@ -24,12 +24,12 @@ def test_sanity() -> None:
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None: def test_unclosed_file() -> None:
def open() -> None: def open_test_image() -> None:
im = Image.open(TEST_FILE) im = Image.open(TEST_FILE)
im.load() im.load()
with pytest.warns(ResourceWarning): with pytest.warns(ResourceWarning):
open() open_test_image()
def test_closed_file() -> None: def test_closed_file() -> None:

View File

@ -1,10 +1,11 @@
from __future__ import annotations from __future__ import annotations
import io
import os import os
import pytest import pytest
from PIL import Image, SunImagePlugin from PIL import Image, SunImagePlugin, _binary
from .helper import assert_image_equal_tofile, assert_image_similar, hopper from .helper import assert_image_equal_tofile, assert_image_similar, hopper
@ -33,6 +34,60 @@ def test_im1() -> None:
assert_image_equal_tofile(im, "Tests/images/sunraster.im1.png") assert_image_equal_tofile(im, "Tests/images/sunraster.im1.png")
def _sun_header(
depth: int = 0, file_type: int = 0, palette_length: int = 0
) -> io.BytesIO:
return io.BytesIO(
_binary.o32be(0x59A66A95)
+ b"\x00" * 8
+ _binary.o32be(depth)
+ b"\x00" * 4
+ _binary.o32be(file_type)
+ b"\x00" * 4
+ _binary.o32be(palette_length)
)
def test_unsupported_mode_bit_depth() -> None:
with pytest.raises(SyntaxError, match="Unsupported Mode/Bit Depth"):
with SunImagePlugin.SunImageFile(_sun_header()):
pass
def test_unsupported_color_palette_length() -> None:
with pytest.raises(SyntaxError, match="Unsupported Color Palette Length"):
with SunImagePlugin.SunImageFile(_sun_header(depth=1, palette_length=1025)):
pass
def test_unsupported_palette_type() -> None:
with pytest.raises(SyntaxError, match="Unsupported Palette Type"):
with SunImagePlugin.SunImageFile(_sun_header(depth=1, palette_length=1)):
pass
def test_unsupported_file_type() -> None:
with pytest.raises(SyntaxError, match="Unsupported Sun Raster file type"):
with SunImagePlugin.SunImageFile(_sun_header(depth=1, file_type=6)):
pass
@pytest.mark.skipif(
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
)
def test_rgbx() -> None:
with open(os.path.join(EXTRA_DIR, "32bpp.ras"), "rb") as fp:
data = fp.read()
# Set file type to 3
data = data[:20] + _binary.o32be(3) + data[24:]
with Image.open(io.BytesIO(data)) as im:
r, g, b = im.split()
im = Image.merge("RGB", (b, g, r))
assert_image_equal_tofile(im, os.path.join(EXTRA_DIR, "32bpp.png"))
@pytest.mark.skipif( @pytest.mark.skipif(
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
) )

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import warnings import warnings
from pathlib import Path
import pytest import pytest
@ -29,6 +30,22 @@ def test_sanity(codec: str, test_path: str, format: str) -> None:
assert im.format == format assert im.format == format
def test_unexpected_end(tmp_path: Path) -> None:
tmpfile = str(tmp_path / "temp.tar")
with open(tmpfile, "w"):
pass
with pytest.raises(OSError, match="unexpected end of tar file"):
with TarIO.TarIO(tmpfile, "test"):
pass
def test_cannot_find_subfile() -> None:
with pytest.raises(OSError, match="cannot find subfile"):
with TarIO.TarIO(TEST_TAR_FILE, "test"):
pass
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None: def test_unclosed_file() -> None:
with pytest.warns(ResourceWarning): with pytest.warns(ResourceWarning):

View File

@ -72,6 +72,7 @@ def test_palette_depth_8(tmp_path: Path) -> None:
def test_palette_depth_16(tmp_path: Path) -> None: def test_palette_depth_16(tmp_path: Path) -> None:
with Image.open("Tests/images/p_16.tga") as im: with Image.open("Tests/images/p_16.tga") as im:
assert im.palette is not None
assert im.palette.mode == "RGBA" assert im.palette.mode == "RGBA"
assert_image_equal_tofile(im.convert("RGBA"), "Tests/images/p_16.png") assert_image_equal_tofile(im.convert("RGBA"), "Tests/images/p_16.png")
@ -213,10 +214,14 @@ def test_save_orientation(tmp_path: Path) -> None:
def test_horizontal_orientations() -> None: def test_horizontal_orientations() -> None:
# These images have been manually hexedited to have the relevant orientations # These images have been manually hexedited to have the relevant orientations
with Image.open("Tests/images/rgb32rle_top_right.tga") as im: with Image.open("Tests/images/rgb32rle_top_right.tga") as im:
assert im.load()[90, 90][:3] == (0, 0, 0) px = im.load()
assert px is not None
assert px[90, 90][:3] == (0, 0, 0)
with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im: with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im:
assert im.load()[90, 90][:3] == (0, 255, 0) px = im.load()
assert px is not None
assert px[90, 90][:3] == (0, 255, 0)
def test_save_rle(tmp_path: Path) -> None: def test_save_rle(tmp_path: Path) -> None:

View File

@ -63,12 +63,12 @@ class TestFileTiff:
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file(self) -> None: def test_unclosed_file(self) -> None:
def open() -> None: def open_test_image() -> None:
im = Image.open("Tests/images/multipage.tiff") im = Image.open("Tests/images/multipage.tiff")
im.load() im.load()
with pytest.warns(ResourceWarning): with pytest.warns(ResourceWarning):
open() open_test_image()
def test_closed_file(self) -> None: def test_closed_file(self) -> None:
with warnings.catch_warnings(): with warnings.catch_warnings():

View File

@ -21,7 +21,11 @@ def test_open() -> None:
def test_load() -> None: def test_load() -> None:
with WalImageFile.open(TEST_FILE) as im: with WalImageFile.open(TEST_FILE) as im:
assert im.load()[0, 0] == 122 px = im.load()
assert px is not None
assert px[0, 0] == 122
# Test again now that it has already been loaded once # Test again now that it has already been loaded once
assert im.load()[0, 0] == 122 px = im.load()
assert px is not None
assert px[0, 0] == 122

View File

@ -40,7 +40,7 @@ def test_read_exif_metadata() -> None:
def test_read_exif_metadata_without_prefix() -> None: def test_read_exif_metadata_without_prefix() -> None:
with Image.open("Tests/images/flower2.webp") as im: with Image.open("Tests/images/flower2.webp") as im:
# Assert prefix is not present # Assert prefix is not present
assert im.info["exif"][:6] != b"Exif\x00\x00" assert not im.info["exif"].startswith(b"Exif\x00\x00")
exif = im.getexif() exif = im.getexif()
assert exif[305] == "Adobe Photoshop CS6 (Macintosh)" assert exif[305] == "Adobe Photoshop CS6 (Macintosh)"

View File

@ -32,7 +32,9 @@ def test_load_raw() -> None:
def test_load() -> None: def test_load() -> None:
with Image.open("Tests/images/drawing.emf") as im: with Image.open("Tests/images/drawing.emf") as im:
if hasattr(Image.core, "drawwmf"): if hasattr(Image.core, "drawwmf"):
assert im.load()[0, 0] == (255, 255, 255) px = im.load()
assert px is not None
assert px[0, 0] == (255, 255, 255)
def test_load_zero_inch() -> None: def test_load_zero_inch() -> None:

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
import io
import pytest import pytest
from PIL import BdfFontFile, FontFile from PIL import BdfFontFile, FontFile
@ -8,13 +10,20 @@ filename = "Tests/images/courB08.bdf"
def test_sanity() -> None: def test_sanity() -> None:
with open(filename, "rb") as test_file: with open(filename, "rb") as fp:
font = BdfFontFile.BdfFontFile(test_file) font = BdfFontFile.BdfFontFile(fp)
assert isinstance(font, FontFile.FontFile) assert isinstance(font, FontFile.FontFile)
assert len([_f for _f in font.glyph if _f]) == 190 assert len([_f for _f in font.glyph if _f]) == 190
def test_zero_width_chars() -> None:
with open(filename, "rb") as fp:
data = fp.read()
data = data[:2650] + b"\x00\x00" + data[2652:]
BdfFontFile.BdfFontFile(io.BytesIO(data))
def test_invalid_file() -> None: def test_invalid_file() -> None:
with open("Tests/images/flower.jpg", "rb") as fp: with open("Tests/images/flower.jpg", "rb") as fp:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):

View File

@ -4,7 +4,20 @@ from pathlib import Path
import pytest import pytest
from PIL import FontFile from PIL import FontFile, Image
def test_compile() -> None:
font = FontFile.FontFile()
font.glyph[0] = ((0, 0), (0, 0, 0, 0), (0, 0, 0, 1), Image.new("L", (0, 0)))
font.compile()
assert font.ysize == 1
font.ysize = 2
font.compile()
# Assert that compiling again did not change anything
assert font.ysize == 2
def test_save(tmp_path: Path) -> None: def test_save(tmp_path: Path) -> None:

View File

@ -22,28 +22,26 @@ def test_sanity() -> None:
Image.new("HSV", (100, 100)) Image.new("HSV", (100, 100))
def wedge() -> Image.Image: def linear_gradient() -> Image.Image:
w = Image._wedge() im = Image.linear_gradient(mode="L")
w90 = w.rotate(90) im90 = im.rotate(90)
(px, h) = w.size (px, h) = im.size
r = Image.new("L", (px * 3, h)) r = Image.new("L", (px * 3, h))
g = r.copy() g = r.copy()
b = r.copy() b = r.copy()
r.paste(w, (0, 0)) r.paste(im, (0, 0))
r.paste(w90, (px, 0)) r.paste(im90, (px, 0))
g.paste(w90, (0, 0)) g.paste(im90, (0, 0))
g.paste(w, (2 * px, 0)) g.paste(im, (2 * px, 0))
b.paste(w, (px, 0)) b.paste(im, (px, 0))
b.paste(w90, (2 * px, 0)) b.paste(im90, (2 * px, 0))
img = Image.merge("RGB", (r, g, b)) return Image.merge("RGB", (r, g, b))
return img
def to_xxx_colorsys( def to_xxx_colorsys(
@ -79,8 +77,8 @@ def to_rgb_colorsys(im: Image.Image) -> Image.Image:
return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB") return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB")
def test_wedge() -> None: def test_linear_gradient() -> None:
src = wedge().resize((3 * 32, 32), Image.Resampling.BILINEAR) src = linear_gradient().resize((3 * 32, 32), Image.Resampling.BILINEAR)
im = src.convert("HSV") im = src.convert("HSV")
comparable = to_hsv_colorsys(src) comparable = to_hsv_colorsys(src)

View File

@ -74,12 +74,12 @@ class TestImage:
def test_sanity(self) -> None: def test_sanity(self) -> None:
im = Image.new("L", (100, 100)) im = Image.new("L", (100, 100))
assert repr(im)[:45] == "<PIL.Image.Image image mode=L size=100x100 at" assert repr(im).startswith("<PIL.Image.Image image mode=L size=100x100 at")
assert im.mode == "L" assert im.mode == "L"
assert im.size == (100, 100) assert im.size == (100, 100)
im = Image.new("RGB", (100, 100)) im = Image.new("RGB", (100, 100))
assert repr(im)[:45] == "<PIL.Image.Image image mode=RGB size=100x100 " assert repr(im).startswith("<PIL.Image.Image image mode=RGB size=100x100 ")
assert im.mode == "RGB" assert im.mode == "RGB"
assert im.size == (100, 100) assert im.size == (100, 100)
@ -658,6 +658,7 @@ class TestImage:
im.putpalette(list(range(256)) * 4, "RGBA") im.putpalette(list(range(256)) * 4, "RGBA")
im_remapped = im.remap_palette(list(range(256))) im_remapped = im.remap_palette(list(range(256)))
assert_image_equal(im, im_remapped) assert_image_equal(im, im_remapped)
assert im.palette is not None
assert im.palette.palette == im_remapped.palette.palette assert im.palette.palette == im_remapped.palette.palette
# Test illegal image mode # Test illegal image mode

View File

@ -234,6 +234,7 @@ def test_gif_with_rgba_palette_to_p() -> None:
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
im.info["transparency"] = 255 im.info["transparency"] = 255
im.load() im.load()
assert im.palette is not None
assert im.palette.mode == "RGB" assert im.palette.mode == "RGB"
im_p = im.convert("P") im_p = im.convert("P")

View File

@ -47,6 +47,7 @@ class TestImageTransform:
transformed = im.transform( transformed = im.transform(
im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0] im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0]
) )
assert im.palette is not None
assert im.palette.palette == transformed.palette.palette assert im.palette.palette == transformed.palette.palette
def test_extent(self) -> None: def test_extent(self) -> None:

View File

@ -448,7 +448,6 @@ def test_shape1() -> None:
x3, y3 = 95, 5 x3, y3 = 95, 5
# Act # Act
assert ImageDraw.Outline is not None
s = ImageDraw.Outline() s = ImageDraw.Outline()
s.move(x0, y0) s.move(x0, y0)
s.curve(x1, y1, x2, y2, x3, y3) s.curve(x1, y1, x2, y2, x3, y3)
@ -470,7 +469,6 @@ def test_shape2() -> None:
x3, y3 = 5, 95 x3, y3 = 5, 95
# Act # Act
assert ImageDraw.Outline is not None
s = ImageDraw.Outline() s = ImageDraw.Outline()
s.move(x0, y0) s.move(x0, y0)
s.curve(x1, y1, x2, y2, x3, y3) s.curve(x1, y1, x2, y2, x3, y3)
@ -489,7 +487,6 @@ def test_transform() -> None:
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
# Act # Act
assert ImageDraw.Outline is not None
s = ImageDraw.Outline() s = ImageDraw.Outline()
s.line(0, 0) s.line(0, 0)
s.transform((0, 0, 0, 0, 0, 0)) s.transform((0, 0, 0, 0, 0, 0))
@ -1526,7 +1523,6 @@ def test_same_color_outline(bbox: Coords) -> None:
x2, y2 = 95, 50 x2, y2 = 95, 50
x3, y3 = 95, 5 x3, y3 = 95, 5
assert ImageDraw.Outline is not None
s = ImageDraw.Outline() s = ImageDraw.Outline()
s.move(x0, y0) s.move(x0, y0)
s.curve(x1, y1, x2, y2, x3, y3) s.curve(x1, y1, x2, y2, x3, y3)

View File

@ -448,6 +448,15 @@ def test_exif_transpose() -> None:
assert 0x0112 not in transposed_im.getexif() assert 0x0112 not in transposed_im.getexif()
def test_exif_transpose_with_xmp_tuple() -> None:
with Image.open("Tests/images/xmp_tags_orientation.png") as im:
assert im.getexif()[0x0112] == 3
im.info["xmp"] = (b"test",)
transposed_im = ImageOps.exif_transpose(im)
assert 0x0112 not in transposed_im.getexif()
def test_exif_transpose_xml_without_xmp() -> None: def test_exif_transpose_xml_without_xmp() -> None:
with Image.open("Tests/images/xmp_tags_orientation.png") as im: with Image.open("Tests/images/xmp_tags_orientation.png") as im:
assert im.getexif()[0x0112] == 3 assert im.getexif()[0x0112] == 3

View File

@ -17,6 +17,7 @@ def test_sanity() -> None:
def test_reload() -> None: def test_reload() -> None:
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
original = im.copy() original = im.copy()
assert im.palette is not None
im.palette.dirty = 1 im.palette.dirty = 1
assert_image_equal(im.convert("RGB"), original.convert("RGB")) assert_image_equal(im.convert("RGB"), original.convert("RGB"))

View File

@ -22,7 +22,7 @@ import PIL
# -- General configuration ------------------------------------------------ # -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = "8.1" needs_sphinx = "8.2"
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@ -121,7 +121,7 @@ nitpicky = True
# generating warnings in “nitpicky mode”. Note that type should include the domain name # generating warnings in “nitpicky mode”. Note that type should include the domain name
# if present. Example entries would be ('py:func', 'int') or # if present. Example entries would be ('py:func', 'int') or
# ('envvar', 'LD_LIBRARY_PATH'). # ('envvar', 'LD_LIBRARY_PATH').
nitpick_ignore = [("py:class", "_io.BytesIO"), ("py:class", "_CmsProfileCompatible")] nitpick_ignore = [("py:class", "_CmsProfileCompatible")]
# -- Options for HTML output ---------------------------------------------- # -- Options for HTML output ----------------------------------------------

View File

@ -285,7 +285,7 @@ Image.register_decoder("DXT5", DXT5Decoder)
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"DDS " return prefix.startswith(b"DDS ")
Image.register_open(DdsImageFile.format, DdsImageFile, _accept) Image.register_open(DdsImageFile.format, DdsImageFile, _accept)

View File

@ -454,7 +454,8 @@ The :py:meth:`~PIL.Image.open` method may set the following
Raw EXIF data from the image. Raw EXIF data from the image.
**comment** **comment**
A comment about the image. A comment about the image, from the COM marker. This is separate from the
UserComment tag that may be stored in the EXIF data.
.. versionadded:: 7.1.0 .. versionadded:: 7.1.0

View File

@ -54,7 +54,7 @@ true color.
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"SPAM" return prefix.startswith(b"SPAM")
class SpamImageFile(ImageFile.ImageFile): class SpamImageFile(ImageFile.ImageFile):

View File

@ -51,7 +51,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management * **littlecms** provides color management
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
above uses liblcms2. Tested with **1.19** and **2.7-2.16**. above uses liblcms2. Tested with **1.19** and **2.7-2.17**.
* **libwebp** provides the WebP format. * **libwebp** provides the WebP format.

View File

@ -43,7 +43,7 @@ dynamic = [
optional-dependencies.docs = [ optional-dependencies.docs = [
"furo", "furo",
"olefile", "olefile",
"sphinx>=8.1", "sphinx>=8.2",
"sphinx-copybutton", "sphinx-copybutton",
"sphinx-inline-tabs", "sphinx-inline-tabs",
"sphinxext-opengraph", "sphinxext-opengraph",
@ -104,7 +104,6 @@ test-extras = "tests"
[tool.cibuildwheel.macos.environment] [tool.cibuildwheel.macos.environment]
PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
DYLD_LIBRARY_PATH = "$(pwd)/build/deps/darwin/lib"
[tool.black] [tool.black]
exclude = "wheels/multibuild" exclude = "wheels/multibuild"

View File

@ -26,17 +26,6 @@ from typing import BinaryIO
from . import FontFile, Image from . import FontFile, Image
bdf_slant = {
"R": "Roman",
"I": "Italic",
"O": "Oblique",
"RI": "Reverse Italic",
"RO": "Reverse Oblique",
"OT": "Other",
}
bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"}
def bdf_char( def bdf_char(
f: BinaryIO, f: BinaryIO,
@ -54,7 +43,7 @@ def bdf_char(
s = f.readline() s = f.readline()
if not s: if not s:
return None return None
if s[:9] == b"STARTCHAR": if s.startswith(b"STARTCHAR"):
break break
id = s[9:].strip().decode("ascii") id = s[9:].strip().decode("ascii")
@ -62,7 +51,7 @@ def bdf_char(
props = {} props = {}
while True: while True:
s = f.readline() s = f.readline()
if not s or s[:6] == b"BITMAP": if not s or s.startswith(b"BITMAP"):
break break
i = s.find(b" ") i = s.find(b" ")
props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii") props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")
@ -71,7 +60,7 @@ def bdf_char(
bitmap = bytearray() bitmap = bytearray()
while True: while True:
s = f.readline() s = f.readline()
if not s or s[:7] == b"ENDCHAR": if not s or s.startswith(b"ENDCHAR"):
break break
bitmap += s[:-1] bitmap += s[:-1]
@ -107,7 +96,7 @@ class BdfFontFile(FontFile.FontFile):
super().__init__() super().__init__()
s = fp.readline() s = fp.readline()
if s[:13] != b"STARTFONT 2.1": if not s.startswith(b"STARTFONT 2.1"):
msg = "not a valid BDF file" msg = "not a valid BDF file"
raise SyntaxError(msg) raise SyntaxError(msg)
@ -116,7 +105,7 @@ class BdfFontFile(FontFile.FontFile):
while True: while True:
s = fp.readline() s = fp.readline()
if not s or s[:13] == b"ENDPROPERTIES": if not s or s.startswith(b"ENDPROPERTIES"):
break break
i = s.find(b" ") i = s.find(b" ")
props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii") props[s[:i].decode("ascii")] = s[i + 1 : -1].decode("ascii")

View File

@ -246,7 +246,7 @@ class BLPFormatError(NotImplementedError):
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] in (b"BLP1", b"BLP2") return prefix.startswith((b"BLP1", b"BLP2"))
class BlpImageFile(ImageFile.ImageFile): class BlpImageFile(ImageFile.ImageFile):

View File

@ -50,7 +50,7 @@ BIT2MODE = {
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:2] == b"BM" return prefix.startswith(b"BM")
def _dib_accept(prefix: bytes) -> bool: def _dib_accept(prefix: bytes) -> bool:

View File

@ -33,7 +33,7 @@ def register_handler(handler: ImageFile.StubHandler | None) -> None:
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"BUFR" or prefix[:4] == b"ZCZC" return prefix.startswith((b"BUFR", b"ZCZC"))
class BufrStubImageFile(ImageFile.StubImageFile): class BufrStubImageFile(ImageFile.StubImageFile):

View File

@ -26,7 +26,7 @@ from ._binary import i32le as i32
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"\0\0\2\0" return prefix.startswith(b"\0\0\2\0")
## ##

View File

@ -564,7 +564,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"DDS " return prefix.startswith(b"DDS ")
Image.register_open(DdsImageFile.format, DdsImageFile, _accept) Image.register_open(DdsImageFile.format, DdsImageFile, _accept)

View File

@ -170,7 +170,9 @@ def Ghostscript(
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5) return prefix.startswith(b"%!PS") or (
len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5
)
## ##
@ -295,7 +297,7 @@ class EpsImageFile(ImageFile.ImageFile):
m = field.match(s) m = field.match(s)
if m: if m:
k = m.group(1) k = m.group(1)
if k[:8] == "PS-Adobe": if k.startswith("PS-Adobe"):
self.info["PS-Adobe"] = k[9:] self.info["PS-Adobe"] = k[9:]
else: else:
self.info[k] = "" self.info[k] = ""

View File

@ -17,7 +17,7 @@ from . import Image, ImageFile
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:6] == b"SIMPLE" return prefix.startswith(b"SIMPLE")
class FitsImageFile(ImageFile.ImageFile): class FitsImageFile(ImageFile.ImageFile):

View File

@ -42,7 +42,7 @@ MODES = {
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:8] == olefile.MAGIC return prefix.startswith(olefile.MAGIC)
## ##

View File

@ -79,8 +79,6 @@ class FtexImageFile(ImageFile.ImageFile):
self._size = struct.unpack("<2i", self.fp.read(8)) self._size = struct.unpack("<2i", self.fp.read(8))
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8)) mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
self._mode = "RGB"
# Only support single-format files. # Only support single-format files.
# I don't know of any multi-format file. # I don't know of any multi-format file.
assert format_count == 1 assert format_count == 1
@ -95,6 +93,7 @@ class FtexImageFile(ImageFile.ImageFile):
self._mode = "RGBA" self._mode = "RGBA"
self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))] self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))]
elif format == Format.UNCOMPRESSED: elif format == Format.UNCOMPRESSED:
self._mode = "RGB"
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, "RGB")] self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, "RGB")]
else: else:
msg = f"Invalid texture compression format: {repr(format)}" msg = f"Invalid texture compression format: {repr(format)}"
@ -108,7 +107,7 @@ class FtexImageFile(ImageFile.ImageFile):
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == MAGIC return prefix.startswith(MAGIC)
Image.register_open(FtexImageFile.format, FtexImageFile, _accept) Image.register_open(FtexImageFile.format, FtexImageFile, _accept)

View File

@ -56,7 +56,7 @@ class GdImageFile(ImageFile.ImageFile):
msg = "Not a valid GD 2.x .gd file" msg = "Not a valid GD 2.x .gd file"
raise SyntaxError(msg) raise SyntaxError(msg)
self._mode = "L" # FIXME: "P" self._mode = "P"
self._size = i16(s, 2), i16(s, 4) self._size = i16(s, 2), i16(s, 4)
true_color = s[6] true_color = s[6]
@ -68,14 +68,14 @@ class GdImageFile(ImageFile.ImageFile):
self.info["transparency"] = tindex self.info["transparency"] = tindex
self.palette = ImagePalette.raw( self.palette = ImagePalette.raw(
"XBGR", s[7 + true_color_offset + 4 : 7 + true_color_offset + 4 + 256 * 4] "RGBX", s[7 + true_color_offset + 6 : 7 + true_color_offset + 6 + 256 * 4]
) )
self.tile = [ self.tile = [
ImageFile._Tile( ImageFile._Tile(
"raw", "raw",
(0, 0) + self.size, (0, 0) + self.size,
7 + true_color_offset + 4 + 256 * 4, 7 + true_color_offset + 6 + 256 * 4,
"L", "L",
) )
] ]

View File

@ -67,7 +67,7 @@ LOADING_STRATEGY = LoadingStrategy.RGB_AFTER_FIRST
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:6] in [b"GIF87a", b"GIF89a"] return prefix.startswith((b"GIF87a", b"GIF89a"))
## ##
@ -257,7 +257,7 @@ class GifImageFile(ImageFile.ImageFile):
# application extension # application extension
# #
info["extension"] = block, self.fp.tell() info["extension"] = block, self.fp.tell()
if block[:11] == b"NETSCAPE2.0": if block.startswith(b"NETSCAPE2.0"):
block = self.data() block = self.data()
if block and len(block) >= 3 and block[0] == 1: if block and len(block) >= 3 and block[0] == 1:
self.info["loop"] = i16(block, 1) self.info["loop"] = i16(block, 1)
@ -689,16 +689,21 @@ def _write_multiple_frames(
im_frames[-1].encoderinfo["duration"] += encoderinfo["duration"] im_frames[-1].encoderinfo["duration"] += encoderinfo["duration"]
continue continue
if im_frames[-1].encoderinfo.get("disposal") == 2: if im_frames[-1].encoderinfo.get("disposal") == 2:
if background_im is None: # To appear correctly in viewers using a convention,
color = im.encoderinfo.get( # only consider transparency, and not background color
"transparency", im.info.get("transparency", (0, 0, 0)) color = im.encoderinfo.get(
) "transparency", im.info.get("transparency")
background = _get_background(im_frame, color) )
background_im = Image.new("P", im_frame.size, background) if color is not None:
first_palette = im_frames[0].im.palette if background_im is None:
assert first_palette is not None background = _get_background(im_frame, color)
background_im.putpalette(first_palette, first_palette.mode) background_im = Image.new("P", im_frame.size, background)
bbox = _getbbox(background_im, im_frame)[1] first_palette = im_frames[0].im.palette
assert first_palette is not None
background_im.putpalette(first_palette, first_palette.mode)
bbox = _getbbox(background_im, im_frame)[1]
else:
bbox = (0, 0) + im_frame.size
elif encoderinfo.get("optimize") and im_frame.mode != "1": elif encoderinfo.get("optimize") and im_frame.mode != "1":
if "transparency" not in encoderinfo: if "transparency" not in encoderinfo:
assert im_frame.palette is not None assert im_frame.palette is not None
@ -764,7 +769,8 @@ def _write_multiple_frames(
if not palette: if not palette:
frame_data.encoderinfo["include_color_table"] = True frame_data.encoderinfo["include_color_table"] = True
im_frame = im_frame.crop(frame_data.bbox) if frame_data.bbox != (0, 0) + im_frame.size:
im_frame = im_frame.crop(frame_data.bbox)
offset = frame_data.bbox[:2] offset = frame_data.bbox[:2]
_write_frame_data(fp, im_frame, offset, frame_data.encoderinfo) _write_frame_data(fp, im_frame, offset, frame_data.encoderinfo)
return True return True

View File

@ -116,7 +116,7 @@ class GimpGradientFile(GradientFile):
"""File handler for GIMP's gradient format.""" """File handler for GIMP's gradient format."""
def __init__(self, fp: IO[bytes]) -> None: def __init__(self, fp: IO[bytes]) -> None:
if fp.readline()[:13] != b"GIMP Gradient": if not fp.readline().startswith(b"GIMP Gradient"):
msg = "not a GIMP gradient file" msg = "not a GIMP gradient file"
raise SyntaxError(msg) raise SyntaxError(msg)

View File

@ -29,7 +29,7 @@ class GimpPaletteFile:
def __init__(self, fp: IO[bytes]) -> None: def __init__(self, fp: IO[bytes]) -> None:
palette = [o8(i) * 3 for i in range(256)] palette = [o8(i) * 3 for i in range(256)]
if fp.readline()[:12] != b"GIMP Palette": if not fp.readline().startswith(b"GIMP Palette"):
msg = "not a GIMP palette file" msg = "not a GIMP palette file"
raise SyntaxError(msg) raise SyntaxError(msg)

View File

@ -33,7 +33,7 @@ def register_handler(handler: ImageFile.StubHandler | None) -> None:
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"GRIB" and prefix[7] == 1 return prefix.startswith(b"GRIB") and prefix[7] == 1
class GribStubImageFile(ImageFile.StubImageFile): class GribStubImageFile(ImageFile.StubImageFile):

View File

@ -33,7 +33,7 @@ def register_handler(handler: ImageFile.StubHandler | None) -> None:
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:8] == b"\x89HDF\r\n\x1a\n" return prefix.startswith(b"\x89HDF\r\n\x1a\n")
class HDF5StubImageFile(ImageFile.StubImageFile): class HDF5StubImageFile(ImageFile.StubImageFile):

View File

@ -117,14 +117,14 @@ def read_png_or_jpeg2000(
sig = fobj.read(12) sig = fobj.read(12)
im: Image.Image im: Image.Image
if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a": if sig.startswith(b"\x89PNG\x0d\x0a\x1a\x0a"):
fobj.seek(start) fobj.seek(start)
im = PngImagePlugin.PngImageFile(fobj) im = PngImagePlugin.PngImageFile(fobj)
Image._decompression_bomb_check(im.size) Image._decompression_bomb_check(im.size)
return {"RGBA": im} return {"RGBA": im}
elif ( elif (
sig[:4] == b"\xff\x4f\xff\x51" sig.startswith(b"\xff\x4f\xff\x51")
or sig[:4] == b"\x0d\x0a\x87\x0a" or sig.startswith(b"\x0d\x0a\x87\x0a")
or sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a" or sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"
): ):
if not enable_jpeg2k: if not enable_jpeg2k:
@ -387,7 +387,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == MAGIC return prefix.startswith(MAGIC)
Image.register_open(IcnsImageFile.format, IcnsImageFile, _accept) Image.register_open(IcnsImageFile.format, IcnsImageFile, _accept)

View File

@ -118,7 +118,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == _MAGIC return prefix.startswith(_MAGIC)
class IconHeader(NamedTuple): class IconHeader(NamedTuple):

View File

@ -155,9 +155,9 @@ class ImImageFile(ImageFile.ImageFile):
msg = "not an IM file" msg = "not an IM file"
raise SyntaxError(msg) raise SyntaxError(msg)
if s[-2:] == b"\r\n": if s.endswith(b"\r\n"):
s = s[:-2] s = s[:-2]
elif s[-1:] == b"\n": elif s.endswith(b"\n"):
s = s[:-1] s = s[:-1]
try: try:
@ -209,7 +209,7 @@ class ImImageFile(ImageFile.ImageFile):
self._mode = self.info[MODE] self._mode = self.info[MODE]
# Skip forward to start of image data # Skip forward to start of image data
while s and s[:1] != b"\x1a": while s and not s.startswith(b"\x1a"):
s = self.fp.read(1) s = self.fp.read(1)
if not s: if not s:
msg = "File truncated" msg = "File truncated"
@ -247,7 +247,7 @@ class ImImageFile(ImageFile.ImageFile):
self._fp = self.fp # FIXME: hack self._fp = self.fp # FIXME: hack
if self.rawmode[:2] == "F;": if self.rawmode.startswith("F;"):
# ifunc95 formats # ifunc95 formats
try: try:
# use bit decoder (if necessary) # use bit decoder (if necessary)

View File

@ -2996,15 +2996,6 @@ class ImageTransformHandler:
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Factories # Factories
#
# Debugging
def _wedge() -> Image:
"""Create grayscale wedge (for debugging only)"""
return Image()._new(core.wedge("L"))
def _check_size(size: Any) -> None: def _check_size(size: Any) -> None:
""" """
@ -4007,7 +3998,7 @@ class Exif(_ExifBase):
if tag == ExifTags.IFD.MakerNote: if tag == ExifTags.IFD.MakerNote:
from .TiffImagePlugin import ImageFileDirectory_v2 from .TiffImagePlugin import ImageFileDirectory_v2
if tag_data[:8] == b"FUJIFILM": if tag_data.startswith(b"FUJIFILM"):
ifd_offset = i32le(tag_data, 8) ifd_offset = i32le(tag_data, 8)
ifd_data = tag_data[ifd_offset:] ifd_data = tag_data[ifd_offset:]

View File

@ -42,11 +42,7 @@ from ._deprecate import deprecate
from ._typing import Coords from ._typing import Coords
# experimental access to the outline API # experimental access to the outline API
Outline: Callable[[], Image.core._Outline] | None Outline: Callable[[], Image.core._Outline] = Image.core.outline
try:
Outline = Image.core.outline
except AttributeError:
Outline = None
if TYPE_CHECKING: if TYPE_CHECKING:
from . import ImageDraw2, ImageFont from . import ImageDraw2, ImageFont

View File

@ -729,11 +729,15 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image
r"<tiff:Orientation>([0-9])</tiff:Orientation>", r"<tiff:Orientation>([0-9])</tiff:Orientation>",
): ):
value = exif_image.info[key] value = exif_image.info[key]
exif_image.info[key] = ( if isinstance(value, str):
re.sub(pattern, "", value) value = re.sub(pattern, "", value)
if isinstance(value, str) elif isinstance(value, tuple):
else re.sub(pattern.encode(), b"", value) value = tuple(
) re.sub(pattern.encode(), b"", v) for v in value
)
else:
value = re.sub(pattern.encode(), b"", value)
exif_image.info[key] = value
if not in_place: if not in_place:
return transposed_image return transposed_image
elif not in_place: elif not in_place:

View File

@ -28,7 +28,7 @@ from __future__ import annotations
import tkinter import tkinter
from io import BytesIO from io import BytesIO
from typing import TYPE_CHECKING, Any, cast from typing import TYPE_CHECKING, Any
from . import Image, ImageFile from . import Image, ImageFile
@ -263,28 +263,3 @@ def getimage(photo: PhotoImage) -> Image.Image:
_pyimagingtkcall("PyImagingPhotoGet", photo, im.getim()) _pyimagingtkcall("PyImagingPhotoGet", photo, im.getim())
return im return im
def _show(image: Image.Image, title: str | None) -> None:
"""Helper for the Image.show method."""
class UI(tkinter.Label):
def __init__(self, master: tkinter.Toplevel, im: Image.Image) -> None:
self.image: BitmapImage | PhotoImage
if im.mode == "1":
self.image = BitmapImage(im, foreground="white", master=master)
else:
self.image = PhotoImage(im, master=master)
if TYPE_CHECKING:
image = cast(tkinter._Image, self.image)
else:
image = self.image
super().__init__(master, image=image, bg="black", bd=0)
if not getattr(tkinter, "_default_root"):
msg = "tkinter not initialized"
raise OSError(msg)
top = tkinter.Toplevel()
if title:
top.title(title)
UI(top, image).pack()

View File

@ -352,9 +352,8 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return ( return prefix.startswith(
prefix[:4] == b"\xff\x4f\xff\x51" (b"\xff\x4f\xff\x51", b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a")
or prefix[:12] == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"
) )

View File

@ -77,7 +77,7 @@ def APP(self: JpegImageFile, marker: int) -> None:
self.app[app] = s # compatibility self.app[app] = s # compatibility
self.applist.append((app, s)) self.applist.append((app, s))
if marker == 0xFFE0 and s[:4] == b"JFIF": if marker == 0xFFE0 and s.startswith(b"JFIF"):
# extract JFIF information # extract JFIF information
self.info["jfif"] = version = i16(s, 5) # version self.info["jfif"] = version = i16(s, 5) # version
self.info["jfif_version"] = divmod(version, 256) self.info["jfif_version"] = divmod(version, 256)
@ -95,19 +95,19 @@ def APP(self: JpegImageFile, marker: int) -> None:
self.info["dpi"] = tuple(d * 2.54 for d in jfif_density) self.info["dpi"] = tuple(d * 2.54 for d in jfif_density)
self.info["jfif_unit"] = jfif_unit self.info["jfif_unit"] = jfif_unit
self.info["jfif_density"] = jfif_density self.info["jfif_density"] = jfif_density
elif marker == 0xFFE1 and s[:6] == b"Exif\0\0": elif marker == 0xFFE1 and s.startswith(b"Exif\0\0"):
# extract EXIF information # extract EXIF information
if "exif" in self.info: if "exif" in self.info:
self.info["exif"] += s[6:] self.info["exif"] += s[6:]
else: else:
self.info["exif"] = s self.info["exif"] = s
self._exif_offset = self.fp.tell() - n + 6 self._exif_offset = self.fp.tell() - n + 6
elif marker == 0xFFE1 and s[:29] == b"http://ns.adobe.com/xap/1.0/\x00": elif marker == 0xFFE1 and s.startswith(b"http://ns.adobe.com/xap/1.0/\x00"):
self.info["xmp"] = s.split(b"\x00", 1)[1] self.info["xmp"] = s.split(b"\x00", 1)[1]
elif marker == 0xFFE2 and s[:5] == b"FPXR\0": elif marker == 0xFFE2 and s.startswith(b"FPXR\0"):
# extract FlashPix information (incomplete) # extract FlashPix information (incomplete)
self.info["flashpix"] = s # FIXME: value will change self.info["flashpix"] = s # FIXME: value will change
elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0": elif marker == 0xFFE2 and s.startswith(b"ICC_PROFILE\0"):
# Since an ICC profile can be larger than the maximum size of # Since an ICC profile can be larger than the maximum size of
# a JPEG marker (64K), we need provisions to split it into # a JPEG marker (64K), we need provisions to split it into
# multiple markers. The format defined by the ICC specifies # multiple markers. The format defined by the ICC specifies
@ -120,7 +120,7 @@ def APP(self: JpegImageFile, marker: int) -> None:
# reassemble the profile, rather than assuming that the APP2 # reassemble the profile, rather than assuming that the APP2
# markers appear in the correct sequence. # markers appear in the correct sequence.
self.icclist.append(s) self.icclist.append(s)
elif marker == 0xFFED and s[:14] == b"Photoshop 3.0\x00": elif marker == 0xFFED and s.startswith(b"Photoshop 3.0\x00"):
# parse the image resource block # parse the image resource block
offset = 14 offset = 14
photoshop = self.info.setdefault("photoshop", {}) photoshop = self.info.setdefault("photoshop", {})
@ -153,7 +153,7 @@ def APP(self: JpegImageFile, marker: int) -> None:
except struct.error: except struct.error:
break # insufficient data break # insufficient data
elif marker == 0xFFEE and s[:5] == b"Adobe": elif marker == 0xFFEE and s.startswith(b"Adobe"):
self.info["adobe"] = i16(s, 5) self.info["adobe"] = i16(s, 5)
# extract Adobe custom properties # extract Adobe custom properties
try: try:
@ -162,7 +162,7 @@ def APP(self: JpegImageFile, marker: int) -> None:
pass pass
else: else:
self.info["adobe_transform"] = adobe_transform self.info["adobe_transform"] = adobe_transform
elif marker == 0xFFE2 and s[:4] == b"MPF\0": elif marker == 0xFFE2 and s.startswith(b"MPF\0"):
# extract MPO information # extract MPO information
self.info["mp"] = s[4:] self.info["mp"] = s[4:]
# offset is current location minus buffer size # offset is current location minus buffer size
@ -325,7 +325,7 @@ MARKER = {
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
# Magic number was taken from https://en.wikipedia.org/wiki/JPEG # Magic number was taken from https://en.wikipedia.org/wiki/JPEG
return prefix[:3] == b"\xff\xd8\xff" return prefix.startswith(b"\xff\xd8\xff")
## ##
@ -547,7 +547,7 @@ def _getmp(self: JpegImageFile) -> dict[int, Any] | None:
return None return None
file_contents = io.BytesIO(data) file_contents = io.BytesIO(data)
head = file_contents.read(8) head = file_contents.read(8)
endianness = ">" if head[:4] == b"\x4d\x4d\x00\x2a" else "<" endianness = ">" if head.startswith(b"\x4d\x4d\x00\x2a") else "<"
# process dictionary # process dictionary
from . import TiffImagePlugin from . import TiffImagePlugin

View File

@ -23,7 +23,7 @@ from . import Image, ImageFile
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:8] == b"\x00\x00\x00\x00\x00\x00\x00\x04" return prefix.startswith(b"\x00\x00\x00\x00\x00\x00\x00\x04")
## ##

View File

@ -26,7 +26,7 @@ from . import Image, TiffImagePlugin
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:8] == olefile.MAGIC return prefix.startswith(olefile.MAGIC)
## ##
@ -54,7 +54,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
self.images = [ self.images = [
path path
for path in self.ole.listdir() for path in self.ole.listdir()
if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image" if path[1:] and path[0].endswith(".ACI") and path[1] == "Image"
] ]
# if we didn't find any images, this is probably not # if we didn't find any images, this is probably not
@ -73,12 +73,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
def seek(self, frame: int) -> None: def seek(self, frame: int) -> None:
if not self._seek_check(frame): if not self._seek_check(frame):
return return
try: filename = self.images[frame]
filename = self.images[frame]
except IndexError as e:
msg = "no such frame"
raise EOFError(msg) from e
self.fp = self.ole.openstream(filename) self.fp = self.ole.openstream(filename)
TiffImagePlugin.TiffImageFile._open(self) TiffImagePlugin.TiffImageFile._open(self)

View File

@ -54,7 +54,7 @@ class BitStream:
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"\x00\x00\x01\xb3" return prefix.startswith(b"\x00\x00\x01\xb3")
## ##

View File

@ -37,7 +37,7 @@ from ._binary import o16le as o16
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] in [b"DanM", b"LinS"] return prefix.startswith((b"DanM", b"LinS"))
## ##
@ -69,7 +69,7 @@ class MspImageFile(ImageFile.ImageFile):
self._mode = "1" self._mode = "1"
self._size = i16(s, 4), i16(s, 6) self._size = i16(s, 4), i16(s, 6)
if s[:4] == b"DanM": if s.startswith(b"DanM"):
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, "1")] self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, "1")]
else: else:
self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32)] self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32)]

View File

@ -32,7 +32,7 @@ class PaletteFile:
if not s: if not s:
break break
if s[:1] == b"#": if s.startswith(b"#"):
continue continue
if len(s) > 100: if len(s) > 100:
msg = "bad palette file" msg = "bad palette file"

View File

@ -34,7 +34,7 @@ class PcdImageFile(ImageFile.ImageFile):
self.fp.seek(2048) self.fp.seek(2048)
s = self.fp.read(2048) s = self.fp.read(2048)
if s[:4] != b"PCD_": if not s.startswith(b"PCD_"):
msg = "not a PCD file" msg = "not a PCD file"
raise SyntaxError(msg) raise SyntaxError(msg)

View File

@ -28,7 +28,7 @@ from ._binary import i16le as i16
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"\200\350\000\000" return prefix.startswith(b"\200\350\000\000")
## ##

View File

@ -740,7 +740,7 @@ class PngStream(ChunkStream):
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:8] == _MAGIC return prefix.startswith(_MAGIC)
## ##

View File

@ -47,7 +47,7 @@ MODES = {
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[0:1] == b"P" and prefix[1] in b"0123456fy" return prefix.startswith(b"P") and prefix[1] in b"0123456fy"
## ##

View File

@ -47,7 +47,7 @@ MODES = {
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"8BPS" return prefix.startswith(b"8BPS")
## ##
@ -169,15 +169,11 @@ class PsdImageFile(ImageFile.ImageFile):
return return
# seek to given layer (1..max) # seek to given layer (1..max)
try: _, mode, _, tile = self.layers[layer - 1]
_, mode, _, tile = self.layers[layer - 1] self._mode = mode
self._mode = mode self.tile = tile
self.tile = tile self.frame = layer
self.frame = layer self.fp = self._fp
self.fp = self._fp
except IndexError as e:
msg = "no such layer"
raise EOFError(msg) from e
def tell(self) -> int: def tell(self) -> int:
# return layer number (0=image, 1..max=layers) # return layer number (0=image, 1..max=layers)

View File

@ -14,7 +14,7 @@ from ._binary import i32be as i32
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"qoif" return prefix.startswith(b"qoif")
class QoiImageFile(ImageFile.ImageFile): class QoiImageFile(ImageFile.ImageFile):

View File

@ -288,7 +288,7 @@ if not getattr(Image.core, "libtiff_support_custom_tags", True):
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:4] in PREFIXES return prefix.startswith(tuple(PREFIXES))
def _limit_rational( def _limit_rational(
@ -404,7 +404,7 @@ class IFDRational(Rational):
def __repr__(self) -> str: def __repr__(self) -> str:
return str(float(self._val)) return str(float(self._val))
def __hash__(self) -> int: def __hash__(self) -> int: # type: ignore[override]
return self._val.__hash__() return self._val.__hash__()
def __eq__(self, other: object) -> bool: def __eq__(self, other: object) -> bool:
@ -1280,7 +1280,7 @@ class TiffImageFile(ImageFile.ImageFile):
blocks = {} blocks = {}
val = self.tag_v2.get(ExifTags.Base.ImageResources) val = self.tag_v2.get(ExifTags.Base.ImageResources)
if val: if val:
while val[:4] == b"8BIM": while val.startswith(b"8BIM"):
id = i16(val[4:6]) id = i16(val[4:6])
n = math.ceil((val[6] + 1) / 2) * 2 n = math.ceil((val[6] + 1) / 2) * 2
size = i32(val[6 + n : 10 + n]) size = i32(val[6 + n : 10 + n])

View File

@ -21,7 +21,7 @@ _VP8_MODES_BY_IDENTIFIER = {
def _accept(prefix: bytes) -> bool | str: def _accept(prefix: bytes) -> bool | str:
is_riff_file_format = prefix[:4] == b"RIFF" is_riff_file_format = prefix.startswith(b"RIFF")
is_webp_file = prefix[8:12] == b"WEBP" is_webp_file = prefix[8:12] == b"WEBP"
is_valid_vp8_mode = prefix[12:16] in _VP8_MODES_BY_IDENTIFIER is_valid_vp8_mode = prefix[12:16] in _VP8_MODES_BY_IDENTIFIER
@ -46,8 +46,7 @@ class WebPImageFile(ImageFile.ImageFile):
self._decoder = _webp.WebPAnimDecoder(self.fp.read()) self._decoder = _webp.WebPAnimDecoder(self.fp.read())
# Get info from decoder # Get info from decoder
width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info() self._size, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()
self._size = width, height
self.info["loop"] = loop_count self.info["loop"] = loop_count
bg_a, bg_r, bg_g, bg_b = ( bg_a, bg_r, bg_g, bg_b = (
(bgcolor >> 24) & 0xFF, (bgcolor >> 24) & 0xFF,

View File

@ -68,9 +68,7 @@ if hasattr(Image.core, "drawwmf"):
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return ( return prefix.startswith((b"\xd7\xcd\xc6\x9a\x00\x00", b"\x01\x00\x00\x00"))
prefix[:6] == b"\xd7\xcd\xc6\x9a\x00\x00" or prefix[:4] == b"\x01\x00\x00\x00"
)
## ##
@ -87,7 +85,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
# check placable header # check placable header
s = self.fp.read(80) s = self.fp.read(80)
if s[:6] == b"\xd7\xcd\xc6\x9a\x00\x00": if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"):
# placeable windows metafile # placeable windows metafile
# get units per inch # get units per inch
@ -116,7 +114,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
msg = "Unsupported WMF file format" msg = "Unsupported WMF file format"
raise SyntaxError(msg) raise SyntaxError(msg)
elif s[:4] == b"\x01\x00\x00\x00" and s[40:44] == b" EMF": elif s.startswith(b"\x01\x00\x00\x00") and s[40:44] == b" EMF":
# enhanced metafile # enhanced metafile
# get bounding box # get bounding box

View File

@ -34,7 +34,7 @@ for r in range(8):
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:6] == _MAGIC return prefix.startswith(_MAGIC)
## ##

View File

@ -38,7 +38,7 @@ xbm_head = re.compile(
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix.lstrip()[:7] == b"#define" return prefix.lstrip().startswith(b"#define")
## ##

View File

@ -25,7 +25,7 @@ xpm_head = re.compile(b'"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)')
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
return prefix[:9] == b"/* XPM */" return prefix.startswith(b"/* XPM */")
## ##
@ -67,9 +67,9 @@ class XpmImageFile(ImageFile.ImageFile):
for _ in range(pal): for _ in range(pal):
s = self.fp.readline() s = self.fp.readline()
if s[-2:] == b"\r\n": if s.endswith(b"\r\n"):
s = s[:-2] s = s[:-2]
elif s[-1:] in b"\r\n": elif s.endswith((b"\r", b"\n")):
s = s[:-1] s = s[:-1]
c = s[1] c = s[1]
@ -81,7 +81,7 @@ class XpmImageFile(ImageFile.ImageFile):
rgb = s[i + 1] rgb = s[i + 1]
if rgb == b"None": if rgb == b"None":
self.info["transparency"] = c self.info["transparency"] = c
elif rgb[:1] == b"#": elif rgb.startswith(b"#"):
# FIXME: handle colour names (see ImagePalette.py) # FIXME: handle colour names (see ImagePalette.py)
rgb = int(rgb[1:], 16) rgb = int(rgb[1:], 16)
palette[c] = ( palette[c] = (

View File

@ -3769,102 +3769,26 @@ static PySequenceMethods image_as_sequence = {
/* type description */ /* type description */
static PyTypeObject Imaging_Type = { static PyTypeObject Imaging_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "ImagingCore", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingCore",
sizeof(ImagingObject), /*tp_basicsize*/ .tp_basicsize = sizeof(ImagingObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)_dealloc,
/* methods */ .tp_as_sequence = &image_as_sequence,
(destructor)_dealloc, /*tp_dealloc*/ .tp_methods = methods,
0, /*tp_vectorcall_offset*/ .tp_getset = getsetters,
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
&image_as_sequence, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
methods, /*tp_methods*/
0, /*tp_members*/
getsetters, /*tp_getset*/
}; };
static PyTypeObject ImagingFont_Type = { static PyTypeObject ImagingFont_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "ImagingFont", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingFont",
sizeof(ImagingFontObject), /*tp_basicsize*/ .tp_basicsize = sizeof(ImagingFontObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)_font_dealloc,
/* methods */ .tp_methods = _font_methods,
(destructor)_font_dealloc, /*tp_dealloc*/
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
_font_methods, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
}; };
static PyTypeObject ImagingDraw_Type = { static PyTypeObject ImagingDraw_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "ImagingDraw", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingDraw",
sizeof(ImagingDrawObject), /*tp_basicsize*/ .tp_basicsize = sizeof(ImagingDrawObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)_draw_dealloc,
/* methods */ .tp_methods = _draw_methods,
(destructor)_draw_dealloc, /*tp_dealloc*/
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
_draw_methods, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
}; };
static PyMappingMethods pixel_access_as_mapping = { static PyMappingMethods pixel_access_as_mapping = {
@ -3876,20 +3800,10 @@ static PyMappingMethods pixel_access_as_mapping = {
/* type description */ /* type description */
static PyTypeObject PixelAccess_Type = { static PyTypeObject PixelAccess_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "PixelAccess", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "PixelAccess",
sizeof(PixelAccessObject), /*tp_basicsize*/ .tp_basicsize = sizeof(PixelAccessObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)pixel_access_dealloc,
/* methods */ .tp_as_mapping = &pixel_access_as_mapping,
(destructor)pixel_access_dealloc, /*tp_dealloc*/
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
&pixel_access_as_mapping, /*tp_as_mapping*/
0 /*tp_hash*/
}; };
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
@ -4256,7 +4170,6 @@ static PyMethodDef functions[] = {
{"effect_noise", (PyCFunction)_effect_noise, METH_VARARGS}, {"effect_noise", (PyCFunction)_effect_noise, METH_VARARGS},
{"linear_gradient", (PyCFunction)_linear_gradient, METH_VARARGS}, {"linear_gradient", (PyCFunction)_linear_gradient, METH_VARARGS},
{"radial_gradient", (PyCFunction)_radial_gradient, METH_VARARGS}, {"radial_gradient", (PyCFunction)_radial_gradient, METH_VARARGS},
{"wedge", (PyCFunction)_linear_gradient, METH_VARARGS}, /* Compatibility */
/* Drawing support stuff */ /* Drawing support stuff */
{"font", (PyCFunction)_font_new, METH_VARARGS}, {"font", (PyCFunction)_font_new, METH_VARARGS},

View File

@ -1410,36 +1410,11 @@ static struct PyGetSetDef cms_profile_getsetters[] = {
}; };
static PyTypeObject CmsProfile_Type = { static PyTypeObject CmsProfile_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsProfile", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "PIL.ImageCms.core.CmsProfile",
sizeof(CmsProfileObject), /*tp_basicsize*/ .tp_basicsize = sizeof(CmsProfileObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)cms_profile_dealloc,
/* methods */ .tp_methods = cms_profile_methods,
(destructor)cms_profile_dealloc, /*tp_dealloc*/ .tp_getset = cms_profile_getsetters,
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
cms_profile_methods, /*tp_methods*/
0, /*tp_members*/
cms_profile_getsetters, /*tp_getset*/
}; };
static struct PyMethodDef cms_transform_methods[] = { static struct PyMethodDef cms_transform_methods[] = {
@ -1447,36 +1422,10 @@ static struct PyMethodDef cms_transform_methods[] = {
}; };
static PyTypeObject CmsTransform_Type = { static PyTypeObject CmsTransform_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsTransform", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "PIL.ImageCms.core.CmsTransform",
sizeof(CmsTransformObject), /*tp_basicsize*/ .tp_basicsize = sizeof(CmsTransformObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)cms_transform_dealloc,
/* methods */ .tp_methods = cms_transform_methods,
(destructor)cms_transform_dealloc, /*tp_dealloc*/
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
cms_transform_methods, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
}; };
static int static int

View File

@ -1518,36 +1518,11 @@ static struct PyGetSetDef font_getsetters[] = {
}; };
static PyTypeObject Font_Type = { static PyTypeObject Font_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "Font", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "Font",
sizeof(FontObject), /*tp_basicsize*/ .tp_basicsize = sizeof(FontObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)font_dealloc,
/* methods */ .tp_methods = font_methods,
(destructor)font_dealloc, /*tp_dealloc*/ .tp_getset = font_getsetters,
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
font_methods, /*tp_methods*/
0, /*tp_members*/
font_getsetters, /*tp_getset*/
}; };
static PyMethodDef _functions[] = { static PyMethodDef _functions[] = {

View File

@ -449,7 +449,7 @@ _anim_decoder_get_info(PyObject *self) {
WebPAnimInfo *info = &(decp->info); WebPAnimInfo *info = &(decp->info);
return Py_BuildValue( return Py_BuildValue(
"IIIIIs", "(II)IIIs",
info->canvas_width, info->canvas_width,
info->canvas_height, info->canvas_height,
info->loop_count, info->loop_count,
@ -530,36 +530,10 @@ static struct PyMethodDef _anim_encoder_methods[] = {
// WebPAnimEncoder type definition // WebPAnimEncoder type definition
static PyTypeObject WebPAnimEncoder_Type = { static PyTypeObject WebPAnimEncoder_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimEncoder", /*tp_name */ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "WebPAnimEncoder",
sizeof(WebPAnimEncoderObject), /*tp_basicsize */ .tp_basicsize = sizeof(WebPAnimEncoderObject),
0, /*tp_itemsize */ .tp_dealloc = (destructor)_anim_encoder_dealloc,
/* methods */ .tp_methods = _anim_encoder_methods,
(destructor)_anim_encoder_dealloc, /*tp_dealloc*/
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
_anim_encoder_methods, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
}; };
// WebPAnimDecoder methods // WebPAnimDecoder methods
@ -573,36 +547,10 @@ static struct PyMethodDef _anim_decoder_methods[] = {
// WebPAnimDecoder type definition // WebPAnimDecoder type definition
static PyTypeObject WebPAnimDecoder_Type = { static PyTypeObject WebPAnimDecoder_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimDecoder", /*tp_name */ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "WebPAnimDecoder",
sizeof(WebPAnimDecoderObject), /*tp_basicsize */ .tp_basicsize = sizeof(WebPAnimDecoderObject),
0, /*tp_itemsize */ .tp_dealloc = (destructor)_anim_decoder_dealloc,
/* methods */ .tp_methods = _anim_decoder_methods,
(destructor)_anim_decoder_dealloc, /*tp_dealloc*/
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
_anim_decoder_methods, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
}; };
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */

View File

@ -256,36 +256,11 @@ static struct PyGetSetDef getseters[] = {
}; };
static PyTypeObject ImagingDecoderType = { static PyTypeObject ImagingDecoderType = {
PyVarObject_HEAD_INIT(NULL, 0) "ImagingDecoder", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingDecoder",
sizeof(ImagingDecoderObject), /*tp_basicsize*/ .tp_basicsize = sizeof(ImagingDecoderObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)_dealloc,
/* methods */ .tp_methods = methods,
(destructor)_dealloc, /*tp_dealloc*/ .tp_getset = getseters,
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
methods, /*tp_methods*/
0, /*tp_members*/
getseters, /*tp_getset*/
}; };
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */

View File

@ -248,36 +248,11 @@ static struct PyGetSetDef getsetters[] = {
}; };
static PyTypeObject ImagingDisplayType = { static PyTypeObject ImagingDisplayType = {
PyVarObject_HEAD_INIT(NULL, 0) "ImagingDisplay", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingDisplay",
sizeof(ImagingDisplayObject), /*tp_basicsize*/ .tp_basicsize = sizeof(ImagingDisplayObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)_delete,
/* methods */ .tp_methods = methods,
(destructor)_delete, /*tp_dealloc*/ .tp_getset = getsetters,
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
methods, /*tp_methods*/
0, /*tp_members*/
getsetters, /*tp_getset*/
}; };
PyObject * PyObject *

View File

@ -323,36 +323,11 @@ static struct PyGetSetDef getseters[] = {
}; };
static PyTypeObject ImagingEncoderType = { static PyTypeObject ImagingEncoderType = {
PyVarObject_HEAD_INIT(NULL, 0) "ImagingEncoder", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingEncoder",
sizeof(ImagingEncoderObject), /*tp_basicsize*/ .tp_basicsize = sizeof(ImagingEncoderObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)_dealloc,
/* methods */ .tp_methods = methods,
(destructor)_dealloc, /*tp_dealloc*/ .tp_getset = getseters,
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
methods, /*tp_methods*/
0, /*tp_members*/
getseters, /*tp_getset*/
}; };
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
@ -1253,7 +1228,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
PyObject *quality_layers = NULL; PyObject *quality_layers = NULL;
Py_ssize_t num_resolutions = 0; Py_ssize_t num_resolutions = 0;
PyObject *cblk_size = NULL, *precinct_size = NULL; PyObject *cblk_size = NULL, *precinct_size = NULL;
PyObject *irreversible = NULL; int irreversible = 0;
char *progression = "LRCP"; char *progression = "LRCP";
OPJ_PROG_ORDER prog_order; OPJ_PROG_ORDER prog_order;
char *cinema_mode = "no"; char *cinema_mode = "no";
@ -1267,7 +1242,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, args,
"ss|OOOsOnOOOssbbnz#p", "ss|OOOsOnOOpssbbnz#p",
&mode, &mode,
&format, &format,
&offset, &offset,
@ -1402,7 +1377,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
precinct_size, &context->precinct_width, &context->precinct_height precinct_size, &context->precinct_width, &context->precinct_height
); );
context->irreversible = PyObject_IsTrue(irreversible); context->irreversible = irreversible;
context->progression = prog_order; context->progression = prog_order;
context->cinema_mode = cine_mode; context->cinema_mode = cine_mode;
context->mct = mct; context->mct = mct;

View File

@ -149,34 +149,8 @@ static struct PyMethodDef _outline_methods[] = {
}; };
static PyTypeObject OutlineType = { static PyTypeObject OutlineType = {
PyVarObject_HEAD_INIT(NULL, 0) "Outline", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "Outline",
sizeof(OutlineObject), /*tp_basicsize*/ .tp_basicsize = sizeof(OutlineObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)_outline_dealloc,
/* methods */ .tp_methods = _outline_methods,
(destructor)_outline_dealloc, /*tp_dealloc*/
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
_outline_methods, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
}; };

View File

@ -598,34 +598,11 @@ static PyMappingMethods path_as_mapping = {
}; };
static PyTypeObject PyPathType = { static PyTypeObject PyPathType = {
PyVarObject_HEAD_INIT(NULL, 0) "Path", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0).tp_name = "Path",
sizeof(PyPathObject), /*tp_basicsize*/ .tp_basicsize = sizeof(PyPathObject),
0, /*tp_itemsize*/ .tp_dealloc = (destructor)path_dealloc,
/* methods */ .tp_as_sequence = &path_as_sequence,
(destructor)path_dealloc, /*tp_dealloc*/ .tp_as_mapping = &path_as_mapping,
0, /*tp_vectorcall_offset*/ .tp_methods = methods,
0, /*tp_getattr*/ .tp_getset = getsetters,
0, /*tp_setattr*/
0, /*tp_as_async*/
0, /*tp_repr*/
0, /*tp_as_number*/
&path_as_sequence, /*tp_as_sequence*/
&path_as_mapping, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
methods, /*tp_methods*/
0, /*tp_members*/
getsetters, /*tp_getset*/
}; };

View File

@ -10,7 +10,7 @@
// https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h // https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h
// //
// This file was vendored from the following commit: // This file was vendored from the following commit:
// https://github.com/python/pythoncapi-compat/commit/0041177c4f348c8952b4c8980b2c90856e61c7c7 // https://github.com/python/pythoncapi-compat/commit/c84545f0e1e21757d4901f75c47333d25a3fcff0
// //
// SPDX-License-Identifier: 0BSD // SPDX-License-Identifier: 0BSD
@ -22,11 +22,15 @@ extern "C" {
#endif #endif
#include <Python.h> #include <Python.h>
#include <stddef.h> // offsetof()
// Python 3.11.0b4 added PyFrame_Back() to Python.h // Python 3.11.0b4 added PyFrame_Back() to Python.h
#if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) #if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION)
# include "frameobject.h" // PyFrameObject, PyFrame_GetBack() # include "frameobject.h" // PyFrameObject, PyFrame_GetBack()
#endif #endif
#if PY_VERSION_HEX < 0x030C00A3
# include <structmember.h> // T_SHORT, READONLY
#endif
#ifndef _Py_CAST #ifndef _Py_CAST
@ -290,7 +294,7 @@ PyFrame_GetVarString(PyFrameObject *frame, const char *name)
// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 // bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5
#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) #if PY_VERSION_HEX < 0x030900A5 || (defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000)
static inline PyInterpreterState * static inline PyInterpreterState *
PyThreadState_GetInterpreter(PyThreadState *tstate) PyThreadState_GetInterpreter(PyThreadState *tstate)
{ {
@ -583,7 +587,7 @@ static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
return 0; return 0;
} }
*pobj = Py_NewRef(obj); *pobj = Py_NewRef(obj);
return (*pobj != NULL); return 1;
} }
#endif #endif
@ -921,7 +925,7 @@ static inline int
PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
{ {
PyObject **dict = _PyObject_GetDictPtr(obj); PyObject **dict = _PyObject_GetDictPtr(obj);
if (*dict == NULL) { if (dict == NULL || *dict == NULL) {
return -1; return -1;
} }
Py_VISIT(*dict); Py_VISIT(*dict);
@ -932,7 +936,7 @@ static inline void
PyObject_ClearManagedDict(PyObject *obj) PyObject_ClearManagedDict(PyObject *obj)
{ {
PyObject **dict = _PyObject_GetDictPtr(obj); PyObject **dict = _PyObject_GetDictPtr(obj);
if (*dict == NULL) { if (dict == NULL || *dict == NULL) {
return; return;
} }
Py_CLEAR(*dict); Py_CLEAR(*dict);
@ -1207,11 +1211,11 @@ static inline int PyTime_PerfCounter(PyTime_t *result)
#endif #endif
// gh-111389 added hash constants to Python 3.13.0a5. These constants were // gh-111389 added hash constants to Python 3.13.0a5. These constants were
// added first as private macros to Python 3.4.0b1 and PyPy 7.3.9. // added first as private macros to Python 3.4.0b1 and PyPy 7.3.8.
#if (!defined(PyHASH_BITS) \ #if (!defined(PyHASH_BITS) \
&& ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \
|| (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \
&& PYPY_VERSION_NUM >= 0x07090000))) && PYPY_VERSION_NUM >= 0x07030800)))
# define PyHASH_BITS _PyHASH_BITS # define PyHASH_BITS _PyHASH_BITS
# define PyHASH_MODULUS _PyHASH_MODULUS # define PyHASH_MODULUS _PyHASH_MODULUS
# define PyHASH_INF _PyHASH_INF # define PyHASH_INF _PyHASH_INF
@ -1523,6 +1527,36 @@ static inline int PyLong_GetSign(PyObject *obj, int *sign)
} }
#endif #endif
// gh-126061 added PyLong_IsPositive/Negative/Zero() to Python in 3.14.0a2
#if PY_VERSION_HEX < 0x030E00A2
static inline int PyLong_IsPositive(PyObject *obj)
{
if (!PyLong_Check(obj)) {
PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name);
return -1;
}
return _PyLong_Sign(obj) == 1;
}
static inline int PyLong_IsNegative(PyObject *obj)
{
if (!PyLong_Check(obj)) {
PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name);
return -1;
}
return _PyLong_Sign(obj) == -1;
}
static inline int PyLong_IsZero(PyObject *obj)
{
if (!PyLong_Check(obj)) {
PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name);
return -1;
}
return _PyLong_Sign(obj) == 0;
}
#endif
// gh-124502 added PyUnicode_Equal() to Python 3.14.0a0 // gh-124502 added PyUnicode_Equal() to Python 3.14.0a0
#if PY_VERSION_HEX < 0x030E00A0 #if PY_VERSION_HEX < 0x030E00A0
@ -1693,6 +1727,479 @@ static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue)
#endif #endif
// gh-102471 added import and export API for integers to 3.14.0a2.
#if PY_VERSION_HEX < 0x030E00A2 && PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION)
// Helpers to access PyLongObject internals.
static inline void
_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size)
{
#if PY_VERSION_HEX >= 0x030C0000
op->long_value.lv_tag = (uintptr_t)(1 - sign) | ((uintptr_t)(size) << 3);
#elif PY_VERSION_HEX >= 0x030900A4
Py_SET_SIZE(op, sign * size);
#else
Py_SIZE(op) = sign * size;
#endif
}
static inline Py_ssize_t
_PyLong_DigitCount(const PyLongObject *op)
{
#if PY_VERSION_HEX >= 0x030C0000
return (Py_ssize_t)(op->long_value.lv_tag >> 3);
#else
return _PyLong_Sign((PyObject*)op) < 0 ? -Py_SIZE(op) : Py_SIZE(op);
#endif
}
static inline digit*
_PyLong_GetDigits(const PyLongObject *op)
{
#if PY_VERSION_HEX >= 0x030C0000
return (digit*)(op->long_value.ob_digit);
#else
return (digit*)(op->ob_digit);
#endif
}
typedef struct PyLongLayout {
uint8_t bits_per_digit;
uint8_t digit_size;
int8_t digits_order;
int8_t digit_endianness;
} PyLongLayout;
typedef struct PyLongExport {
int64_t value;
uint8_t negative;
Py_ssize_t ndigits;
const void *digits;
Py_uintptr_t _reserved;
} PyLongExport;
typedef struct PyLongWriter PyLongWriter;
static inline const PyLongLayout*
PyLong_GetNativeLayout(void)
{
static const PyLongLayout PyLong_LAYOUT = {
PyLong_SHIFT,
sizeof(digit),
-1, // least significant first
PY_LITTLE_ENDIAN ? -1 : 1,
};
return &PyLong_LAYOUT;
}
static inline int
PyLong_Export(PyObject *obj, PyLongExport *export_long)
{
if (!PyLong_Check(obj)) {
memset(export_long, 0, sizeof(*export_long));
PyErr_Format(PyExc_TypeError, "expected int, got %s",
Py_TYPE(obj)->tp_name);
return -1;
}
// Fast-path: try to convert to a int64_t
PyLongObject *self = (PyLongObject*)obj;
int overflow;
#if SIZEOF_LONG == 8
long value = PyLong_AsLongAndOverflow(obj, &overflow);
#else
// Windows has 32-bit long, so use 64-bit long long instead
long long value = PyLong_AsLongLongAndOverflow(obj, &overflow);
#endif
Py_BUILD_ASSERT(sizeof(value) == sizeof(int64_t));
// the function cannot fail since obj is a PyLongObject
assert(!(value == -1 && PyErr_Occurred()));
if (!overflow) {
export_long->value = value;
export_long->negative = 0;
export_long->ndigits = 0;
export_long->digits = 0;
export_long->_reserved = 0;
}
else {
export_long->value = 0;
export_long->negative = _PyLong_Sign(obj) < 0;
export_long->ndigits = _PyLong_DigitCount(self);
if (export_long->ndigits == 0) {
export_long->ndigits = 1;
}
export_long->digits = _PyLong_GetDigits(self);
export_long->_reserved = (Py_uintptr_t)Py_NewRef(obj);
}
return 0;
}
static inline void
PyLong_FreeExport(PyLongExport *export_long)
{
PyObject *obj = (PyObject*)export_long->_reserved;
if (obj) {
export_long->_reserved = 0;
Py_DECREF(obj);
}
}
static inline PyLongWriter*
PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits)
{
if (ndigits <= 0) {
PyErr_SetString(PyExc_ValueError, "ndigits must be positive");
return NULL;
}
assert(digits != NULL);
PyLongObject *obj = _PyLong_New(ndigits);
if (obj == NULL) {
return NULL;
}
_PyLong_SetSignAndDigitCount(obj, negative?-1:1, ndigits);
*digits = _PyLong_GetDigits(obj);
return (PyLongWriter*)obj;
}
static inline void
PyLongWriter_Discard(PyLongWriter *writer)
{
PyLongObject *obj = (PyLongObject *)writer;
assert(Py_REFCNT(obj) == 1);
Py_DECREF(obj);
}
static inline PyObject*
PyLongWriter_Finish(PyLongWriter *writer)
{
PyObject *obj = (PyObject *)writer;
PyLongObject *self = (PyLongObject*)obj;
Py_ssize_t j = _PyLong_DigitCount(self);
Py_ssize_t i = j;
int sign = _PyLong_Sign(obj);
assert(Py_REFCNT(obj) == 1);
// Normalize and get singleton if possible
while (i > 0 && _PyLong_GetDigits(self)[i-1] == 0) {
--i;
}
if (i != j) {
if (i == 0) {
sign = 0;
}
_PyLong_SetSignAndDigitCount(self, sign, i);
}
if (i <= 1) {
long val = sign * (long)(_PyLong_GetDigits(self)[0]);
Py_DECREF(obj);
return PyLong_FromLong(val);
}
return obj;
}
#endif
#if PY_VERSION_HEX < 0x030C00A3
# define Py_T_SHORT T_SHORT
# define Py_T_INT T_INT
# define Py_T_LONG T_LONG
# define Py_T_FLOAT T_FLOAT
# define Py_T_DOUBLE T_DOUBLE
# define Py_T_STRING T_STRING
# define _Py_T_OBJECT T_OBJECT
# define Py_T_CHAR T_CHAR
# define Py_T_BYTE T_BYTE
# define Py_T_UBYTE T_UBYTE
# define Py_T_USHORT T_USHORT
# define Py_T_UINT T_UINT
# define Py_T_ULONG T_ULONG
# define Py_T_STRING_INPLACE T_STRING_INPLACE
# define Py_T_BOOL T_BOOL
# define Py_T_OBJECT_EX T_OBJECT_EX
# define Py_T_LONGLONG T_LONGLONG
# define Py_T_ULONGLONG T_ULONGLONG
# define Py_T_PYSSIZET T_PYSSIZET
# if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION)
# define _Py_T_NONE T_NONE
# endif
# define Py_READONLY READONLY
# define Py_AUDIT_READ READ_RESTRICTED
# define _Py_WRITE_RESTRICTED PY_WRITE_RESTRICTED
#endif
// gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4
#if PY_VERSION_HEX < 0x030E00A4
static inline FILE* Py_fopen(PyObject *path, const char *mode)
{
#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
PyAPI_FUNC(FILE*) _Py_fopen_obj(PyObject *path, const char *mode);
return _Py_fopen_obj(path, mode);
#else
FILE *f;
PyObject *bytes;
#if PY_VERSION_HEX >= 0x03000000
if (!PyUnicode_FSConverter(path, &bytes)) {
return NULL;
}
#else
if (!PyString_Check(path)) {
PyErr_SetString(PyExc_TypeError, "except str");
return NULL;
}
bytes = Py_NewRef(path);
#endif
const char *path_bytes = PyBytes_AS_STRING(bytes);
f = fopen(path_bytes, mode);
Py_DECREF(bytes);
if (f == NULL) {
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path);
return NULL;
}
return f;
#endif
}
static inline int Py_fclose(FILE *file)
{
return fclose(file);
}
#endif
#if 0x03090000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION)
static inline PyObject*
PyConfig_Get(const char *name)
{
typedef enum {
_PyConfig_MEMBER_INT,
_PyConfig_MEMBER_UINT,
_PyConfig_MEMBER_ULONG,
_PyConfig_MEMBER_BOOL,
_PyConfig_MEMBER_WSTR,
_PyConfig_MEMBER_WSTR_OPT,
_PyConfig_MEMBER_WSTR_LIST,
} PyConfigMemberType;
typedef struct {
const char *name;
size_t offset;
PyConfigMemberType type;
const char *sys_attr;
} PyConfigSpec;
#define PYTHONCAPI_COMPAT_SPEC(MEMBER, TYPE, sys_attr) \
{#MEMBER, offsetof(PyConfig, MEMBER), \
_PyConfig_MEMBER_##TYPE, sys_attr}
static const PyConfigSpec config_spec[] = {
PYTHONCAPI_COMPAT_SPEC(argv, WSTR_LIST, "argv"),
PYTHONCAPI_COMPAT_SPEC(base_exec_prefix, WSTR_OPT, "base_exec_prefix"),
PYTHONCAPI_COMPAT_SPEC(base_executable, WSTR_OPT, "_base_executable"),
PYTHONCAPI_COMPAT_SPEC(base_prefix, WSTR_OPT, "base_prefix"),
PYTHONCAPI_COMPAT_SPEC(bytes_warning, UINT, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(exec_prefix, WSTR_OPT, "exec_prefix"),
PYTHONCAPI_COMPAT_SPEC(executable, WSTR_OPT, "executable"),
PYTHONCAPI_COMPAT_SPEC(inspect, BOOL, _Py_NULL),
#if 0x030C0000 <= PY_VERSION_HEX
PYTHONCAPI_COMPAT_SPEC(int_max_str_digits, UINT, _Py_NULL),
#endif
PYTHONCAPI_COMPAT_SPEC(interactive, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(module_search_paths, WSTR_LIST, "path"),
PYTHONCAPI_COMPAT_SPEC(optimization_level, UINT, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(parser_debug, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(platlibdir, WSTR, "platlibdir"),
PYTHONCAPI_COMPAT_SPEC(prefix, WSTR_OPT, "prefix"),
PYTHONCAPI_COMPAT_SPEC(pycache_prefix, WSTR_OPT, "pycache_prefix"),
PYTHONCAPI_COMPAT_SPEC(quiet, BOOL, _Py_NULL),
#if 0x030B0000 <= PY_VERSION_HEX
PYTHONCAPI_COMPAT_SPEC(stdlib_dir, WSTR_OPT, "_stdlib_dir"),
#endif
PYTHONCAPI_COMPAT_SPEC(use_environment, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(verbose, UINT, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(warnoptions, WSTR_LIST, "warnoptions"),
PYTHONCAPI_COMPAT_SPEC(write_bytecode, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(xoptions, WSTR_LIST, "_xoptions"),
PYTHONCAPI_COMPAT_SPEC(buffered_stdio, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(check_hash_pycs_mode, WSTR, _Py_NULL),
#if 0x030B0000 <= PY_VERSION_HEX
PYTHONCAPI_COMPAT_SPEC(code_debug_ranges, BOOL, _Py_NULL),
#endif
PYTHONCAPI_COMPAT_SPEC(configure_c_stdio, BOOL, _Py_NULL),
#if 0x030D0000 <= PY_VERSION_HEX
PYTHONCAPI_COMPAT_SPEC(cpu_count, INT, _Py_NULL),
#endif
PYTHONCAPI_COMPAT_SPEC(dev_mode, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(dump_refs, BOOL, _Py_NULL),
#if 0x030B0000 <= PY_VERSION_HEX
PYTHONCAPI_COMPAT_SPEC(dump_refs_file, WSTR_OPT, _Py_NULL),
#endif
#ifdef Py_GIL_DISABLED
PYTHONCAPI_COMPAT_SPEC(enable_gil, INT, _Py_NULL),
#endif
PYTHONCAPI_COMPAT_SPEC(faulthandler, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(filesystem_encoding, WSTR, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(filesystem_errors, WSTR, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(hash_seed, ULONG, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(home, WSTR_OPT, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(import_time, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(install_signal_handlers, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(isolated, BOOL, _Py_NULL),
#ifdef MS_WINDOWS
PYTHONCAPI_COMPAT_SPEC(legacy_windows_stdio, BOOL, _Py_NULL),
#endif
PYTHONCAPI_COMPAT_SPEC(malloc_stats, BOOL, _Py_NULL),
#if 0x030A0000 <= PY_VERSION_HEX
PYTHONCAPI_COMPAT_SPEC(orig_argv, WSTR_LIST, "orig_argv"),
#endif
PYTHONCAPI_COMPAT_SPEC(parse_argv, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(pathconfig_warnings, BOOL, _Py_NULL),
#if 0x030C0000 <= PY_VERSION_HEX
PYTHONCAPI_COMPAT_SPEC(perf_profiling, UINT, _Py_NULL),
#endif
PYTHONCAPI_COMPAT_SPEC(program_name, WSTR, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(run_command, WSTR_OPT, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(run_filename, WSTR_OPT, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(run_module, WSTR_OPT, _Py_NULL),
#if 0x030B0000 <= PY_VERSION_HEX
PYTHONCAPI_COMPAT_SPEC(safe_path, BOOL, _Py_NULL),
#endif
PYTHONCAPI_COMPAT_SPEC(show_ref_count, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(site_import, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(skip_source_first_line, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(stdio_encoding, WSTR, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(stdio_errors, WSTR, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(tracemalloc, UINT, _Py_NULL),
#if 0x030B0000 <= PY_VERSION_HEX
PYTHONCAPI_COMPAT_SPEC(use_frozen_modules, BOOL, _Py_NULL),
#endif
PYTHONCAPI_COMPAT_SPEC(use_hash_seed, BOOL, _Py_NULL),
PYTHONCAPI_COMPAT_SPEC(user_site_directory, BOOL, _Py_NULL),
#if 0x030A0000 <= PY_VERSION_HEX
PYTHONCAPI_COMPAT_SPEC(warn_default_encoding, BOOL, _Py_NULL),
#endif
};
#undef PYTHONCAPI_COMPAT_SPEC
const PyConfigSpec *spec;
int found = 0;
for (size_t i=0; i < sizeof(config_spec) / sizeof(config_spec[0]); i++) {
spec = &config_spec[i];
if (strcmp(spec->name, name) == 0) {
found = 1;
break;
}
}
if (found) {
if (spec->sys_attr != NULL) {
PyObject *value = PySys_GetObject(spec->sys_attr);
if (value == NULL) {
PyErr_Format(PyExc_RuntimeError, "lost sys.%s", spec->sys_attr);
return NULL;
}
return Py_NewRef(value);
}
PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void);
const PyConfig *config = _Py_GetConfig();
void *member = (char *)config + spec->offset;
switch (spec->type) {
case _PyConfig_MEMBER_INT:
case _PyConfig_MEMBER_UINT:
{
int value = *(int *)member;
return PyLong_FromLong(value);
}
case _PyConfig_MEMBER_BOOL:
{
int value = *(int *)member;
return PyBool_FromLong(value != 0);
}
case _PyConfig_MEMBER_ULONG:
{
unsigned long value = *(unsigned long *)member;
return PyLong_FromUnsignedLong(value);
}
case _PyConfig_MEMBER_WSTR:
case _PyConfig_MEMBER_WSTR_OPT:
{
wchar_t *wstr = *(wchar_t **)member;
if (wstr != NULL) {
return PyUnicode_FromWideChar(wstr, -1);
}
else {
return Py_NewRef(Py_None);
}
}
case _PyConfig_MEMBER_WSTR_LIST:
{
const PyWideStringList *list = (const PyWideStringList *)member;
PyObject *tuple = PyTuple_New(list->length);
if (tuple == NULL) {
return NULL;
}
for (Py_ssize_t i = 0; i < list->length; i++) {
PyObject *item = PyUnicode_FromWideChar(list->items[i], -1);
if (item == NULL) {
Py_DECREF(tuple);
return NULL;
}
PyTuple_SET_ITEM(tuple, i, item);
}
return tuple;
}
default:
Py_UNREACHABLE();
}
}
PyErr_Format(PyExc_ValueError, "unknown config option name: %s", name);
return NULL;
}
static inline int
PyConfig_GetInt(const char *name, int *value)
{
PyObject *obj = PyConfig_Get(name);
if (obj == NULL) {
return -1;
}
if (!PyLong_Check(obj)) {
Py_DECREF(obj);
PyErr_Format(PyExc_TypeError, "config option %s is not an int", name);
return -1;
}
int as_int = PyLong_AsInt(obj);
Py_DECREF(obj);
if (as_int == -1 && PyErr_Occurred()) {
PyErr_Format(PyExc_OverflowError,
"config option %s value does not fit into a C int", name);
return -1;
}
*value = as_int;
return 0;
}
#endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION)
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -11,12 +11,8 @@ deps =
extras = extras =
tests tests
commands = commands =
make clean
{envpython} -m pip install .
{envpython} selftest.py {envpython} selftest.py
{envpython} -m pytest -W always {posargs} {envpython} -m pytest -W always {posargs}
allowlist_externals =
make
[testenv:lint] [testenv:lint]
skip_install = true skip_install = true

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