Skip to content

Commit

Permalink
Merge branch 'main' into libavif-plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere authored Mar 3, 2025
2 parents 10dfa63 + c7ed097 commit 9abfdbc
Show file tree
Hide file tree
Showing 102 changed files with 959 additions and 675 deletions.
4 changes: 2 additions & 2 deletions .ci/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

aptget_update()
{
if [ ! -z $1 ]; then
if [ -n "$1" ]; then
echo ""
echo "Retrying apt-get update..."
echo ""
fi
output=`sudo apt-get update 2>&1`
output=$(sudo apt-get update 2>&1)
echo "$output"
if [[ $output == *[WE]:\ * ]]; then
return 1
Expand Down
2 changes: 1 addition & 1 deletion .ci/requirements-cibw.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
cibuildwheel==2.22.0
cibuildwheel==2.23.0
2 changes: 1 addition & 1 deletion .ci/requirements-mypy.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mypy==1.14.1
mypy==1.15.0
IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6
ipython
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test-mingw.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ jobs:
mingw-w64-x86_64-ghostscript \
mingw-w64-x86_64-lcms2 \
mingw-w64-x86_64-libavif \
mingw-w64-x86_64-libimagequant \
mingw-w64-x86_64-libjpeg-turbo \
mingw-w64-x86_64-libraqm \
mingw-w64-x86_64-libtiff \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
strategy:
fail-fast: false
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"]
os: ["windows-latest"]
include:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
"ubuntu-latest",
]
python-version: [
"pypy3.11",
"pypy3.10",
"3.14",
"3.13t",
Expand Down
27 changes: 15 additions & 12 deletions .github/workflows/wheels-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ ARCHIVE_SDIR=pillow-depends-main

# Package versions for fresh source builds
FREETYPE_VERSION=2.13.3
HARFBUZZ_VERSION=10.2.0
LIBPNG_VERSION=1.6.46
HARFBUZZ_VERSION=10.4.0
LIBPNG_VERSION=1.6.47
JPEGTURBO_VERSION=3.1.0
OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.6.4
TIFF_VERSION=4.6.0
LCMS2_VERSION=2.16
LCMS2_VERSION=2.17
ZLIB_NG_VERSION=2.2.4
LIBWEBP_VERSION=1.5.0
BZIP2_VERSION=1.0.8
Expand All @@ -55,13 +55,10 @@ LIBAVIF_VERSION=1.1.1
function build_pkg_config {
if [ -e pkg-config-stamp ]; then return; fi
# 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 \
--with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \
--with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include
CFLAGS=$ORIGINAL_CFLAGS
export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
touch pkg-config-stamp
}
Expand All @@ -73,6 +70,14 @@ function build_zlib_ng {
&& ./configure --prefix=$BUILD_PREFIX --zlib-compat \
&& make -j4 \
&& 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
}

Expand Down Expand Up @@ -171,15 +176,13 @@ function build {
build_lcms2
build_openjpeg

ORIGINAL_CFLAGS=$CFLAGS
CFLAGS="$CFLAGS -O3 -DNDEBUG"
webp_cflags="-O3 -DNDEBUG"
if [[ -n "$IS_MACOS" ]]; then
CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names"
webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names"
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 \
--enable-libwebpmux --enable-libwebpdemux
CFLAGS=$ORIGINAL_CFLAGS

build_brotli

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
- name: "macOS 10.15 x86_64"
os: macos-13
cibw_arch: x86_64
build: "pp310*"
build: "pp3*"
macosx_deployment_target: "10.15"
- name: "macOS arm64"
os: macos-latest
Expand Down
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.4
rev: v0.9.9
hooks:
- id: ruff
args: [--exit-non-zero-on-fix]
Expand All @@ -11,7 +11,7 @@ repos:
- id: black

- repo: https://github.com/PyCQA/bandit
rev: 1.8.2
rev: 1.8.3
hooks:
- id: bandit
args: [--severity-level=high]
Expand Down Expand Up @@ -50,14 +50,14 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/

- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.31.1
rev: 0.31.2
hooks:
- id: check-github-workflows
- id: check-readthedocs
- id: check-renovate

- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.3.0
rev: v1.4.1
hooks:
- id: zizmor

Expand All @@ -67,7 +67,7 @@ repos:
- id: sphinx-lint

- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.5.0
rev: v2.5.1
hooks:
- id: pyproject-fmt

Expand Down
5 changes: 0 additions & 5 deletions Tests/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import shutil
import subprocess
import sys
import sysconfig
import tempfile
from collections.abc import Sequence
from functools import lru_cache
Expand Down Expand Up @@ -342,10 +341,6 @@ def is_pypy() -> bool:
return hasattr(sys, "pypy_translation_info")


def is_mingw() -> bool:
return sysconfig.get_platform() == "mingw"


class CachedProperty:
def __init__(self, func: Callable[[Any], Any]) -> None:
self.func = func
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_dcx.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ def test_sanity() -> None:

@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None:
def open() -> None:
def open_test_image() -> None:
im = Image.open(TEST_FILE)
im.load()

with pytest.warns(ResourceWarning):
open()
open_test_image()


def test_closed_file() -> None:
Expand Down
2 changes: 2 additions & 0 deletions Tests/test_file_dds.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
px = im.getpixel((0, 0))
assert isinstance(px, tuple)
assert px[0] != 0
assert px[1] != 0
assert px[2] != 0

px = im.getpixel((1, 0))
assert isinstance(px, tuple)
assert px[0] != 0
assert px[1] != 0
assert px[2] != 0
Expand Down
8 changes: 6 additions & 2 deletions Tests/test_file_eps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
def test_load() -> None:
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
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:
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_fli.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:

@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None:
def open() -> None:
def open_test_image() -> None:
im = Image.open(static_test_file)
im.load()

with pytest.warns(ResourceWarning):
open()
open_test_image()


def test_closed_file() -> None:
Expand Down
15 changes: 15 additions & 0 deletions Tests/test_file_ftex.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from __future__ import annotations

import io
import struct

import pytest

from PIL import FtexImagePlugin, Image
Expand All @@ -23,3 +26,15 @@ def test_invalid_file() -> None:

with pytest.raises(SyntaxError):
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
8 changes: 6 additions & 2 deletions Tests/test_file_gbr.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ def test_gbr_file() -> None:

def test_load() -> None:
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
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:
Expand Down
3 changes: 3 additions & 0 deletions Tests/test_file_gd.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

from PIL import GdImageFile, UnidentifiedImageError

from .helper import assert_image_similar_tofile

TEST_GD_FILE = "Tests/images/hopper.gd"


def test_sanity() -> None:
with GdImageFile.open(TEST_GD_FILE) as im:
assert im.size == (128, 128)
assert im.format == "GD"
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.jpg", 14)


def test_bad_mode() -> None:
Expand Down
27 changes: 21 additions & 6 deletions Tests/test_file_gif.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
# sample gif stream
TEST_GIF = "Tests/images/hopper.gif"

with open(TEST_GIF, "rb") as f:
data = f.read()


def test_sanity() -> None:
with Image.open(TEST_GIF) as im:
Expand All @@ -37,12 +34,12 @@ def test_sanity() -> None:

@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None:
def open() -> None:
def open_test_image() -> None:
im = Image.open(TEST_GIF)
im.load()

with pytest.warns(ResourceWarning):
open()
open_test_image()


def test_closed_file() -> None:
Expand Down Expand Up @@ -310,6 +307,7 @@ def test_roundtrip_save_all_1(tmp_path: Path) -> None:
def test_loading_multiple_palettes(path: str, mode: str) -> None:
with Image.open(path) as im:
assert im.mode == "P"
assert im.palette is not None
first_frame_colors = im.palette.colors.keys()
original_color = im.convert("RGB").getpixel((0, 0))

Expand Down Expand Up @@ -528,6 +526,7 @@ def test_dispose_background_transparency() -> None:
with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img:
img.seek(2)
px = img.load()
assert px is not None
assert px[35, 30][3] == 0


Expand Down Expand Up @@ -762,6 +761,21 @@ def test_dispose2_previous_frame(tmp_path: Path) -> None:
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:
out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/different_transparency.gif") as im:
Expand Down Expand Up @@ -1311,6 +1325,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None:
with Image.open(out) as im:
# Assert that the frames are correct, and each frame has the same palette
assert_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
assert im.palette is not None
assert im.palette.palette == im.global_palette.palette

im.seek(1)
Expand Down Expand Up @@ -1348,7 +1363,7 @@ def test_save_I(tmp_path: Path) -> None:
def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None:
# Test getheader/getdata against legacy values.
# 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.info = {"background": 0}

Expand Down
Loading

0 comments on commit 9abfdbc

Please sign in to comment.