Skip to content

Commit

Permalink
Merge branch 'main' into format
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere authored Jan 8, 2025
2 parents 7015fe4 + af3b904 commit 88617e6
Show file tree
Hide file tree
Showing 25 changed files with 282 additions and 180 deletions.
6 changes: 1 addition & 5 deletions .ci/after_success.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,4 @@

# gather the coverage data
python3 -m pip install coverage
if [[ $MATRIX_DOCKER ]]; then
python3 -m coverage xml --ignore-errors
else
python3 -m coverage xml
fi
python3 -m coverage xml
2 changes: 1 addition & 1 deletion .github/workflows/test-cygwin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
persist-credentials: false

- name: Install Cygwin
uses: cygwin/cygwin-install-action@v4
uses: cygwin/cygwin-install-action@v5
with:
packages: >
gcc-g++
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ jobs:
- name: After success
run: |
PATH="$PATH:~/.local/bin"
docker start pillow_container
sudo docker cp pillow_container:/Pillow /Pillow
sudo chown -R runner /Pillow
pil_path=`docker exec pillow_container /vpy3/bin/python -c 'import os, PIL;print(os.path.realpath(os.path.dirname(PIL.__file__)))'`
docker stop pillow_container
sudo mkdir -p $pil_path
sudo cp src/PIL/*.py $pil_path
cd /Pillow
.ci/after_success.sh
env:
MATRIX_DOCKER: ${{ matrix.docker }}
- name: Upload coverage
uses: codecov/codecov-action@v5
Expand Down
17 changes: 7 additions & 10 deletions .github/workflows/wheels-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,15 @@ fi
ARCHIVE_SDIR=pillow-depends-main

# Package versions for fresh source builds
FREETYPE_VERSION=2.13.2
FREETYPE_VERSION=2.13.3
HARFBUZZ_VERSION=10.1.0
LIBPNG_VERSION=1.6.44
LIBPNG_VERSION=1.6.45
JPEGTURBO_VERSION=3.1.0
OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.6.3
TIFF_VERSION=4.6.0
LCMS2_VERSION=2.16
if [[ -n "$IS_MACOS" ]]; then
GIFLIB_VERSION=5.2.2
else
GIFLIB_VERSION=5.2.1
fi
ZLIB_NG_VERSION=2.2.2
ZLIB_NG_VERSION=2.2.3
LIBWEBP_VERSION=1.5.0
BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0
Expand Down Expand Up @@ -103,7 +98,7 @@ function build_harfbuzz {

function build {
build_xz
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
yum remove -y zlib-devel
fi
build_zlib_ng
Expand Down Expand Up @@ -140,7 +135,9 @@ function build {
if [[ -n "$IS_MACOS" ]]; then
CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names"
fi
build_libwebp
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: 2 additions & 0 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
paths:
- ".ci/requirements-cibw.txt"
- ".github/workflows/wheel*"
- "pyproject.toml"
- "setup.py"
- "wheels/*"
- "winbuild/build_prepare.py"
Expand All @@ -23,6 +24,7 @@ on:
paths:
- ".ci/requirements-cibw.txt"
- ".github/workflows/wheel*"
- "pyproject.toml"
- "setup.py"
- "wheels/*"
- "winbuild/build_prepare.py"
Expand Down
6 changes: 3 additions & 3 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.8.4
rev: v0.8.6
hooks:
- id: ruff
args: [--exit-non-zero-on-fix]
Expand All @@ -24,7 +24,7 @@ repos:
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)

- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v19.1.5
rev: v19.1.6
hooks:
- id: clang-format
types: [c]
Expand Down Expand Up @@ -57,7 +57,7 @@ repos:
- id: check-renovate

- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v0.10.0
rev: v1.0.0
hooks:
- id: zizmor

Expand Down
10 changes: 9 additions & 1 deletion Tests/test_file_blp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from PIL import Image
from PIL import BlpImagePlugin, Image

from .helper import (
assert_image_equal,
Expand All @@ -19,6 +19,7 @@ def test_load_blp1() -> None:
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")

with Image.open("Tests/images/blp/blp1_jpeg2.blp") as im:
assert im.mode == "RGBA"
im.load()


Expand All @@ -37,6 +38,13 @@ def test_load_blp2_dxt1a() -> None:
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")


def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"

with pytest.raises(BlpImagePlugin.BLPFormatError):
BlpImagePlugin.BlpImageFile(invalid_file)


def test_save(tmp_path: Path) -> None:
f = str(tmp_path / "temp.blp")

Expand Down
3 changes: 1 addition & 2 deletions Tests/test_file_ico.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ def test_truncated_mask() -> None:

try:
with Image.open(io.BytesIO(data)) as im:
with Image.open("Tests/images/hopper_mask.png") as expected:
assert im.mode == "1"
assert im.mode == "1"

# 32 bpp
output = io.BytesIO()
Expand Down
18 changes: 12 additions & 6 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def test(xdpi: int, ydpi: int | None = None) -> tuple[int, int] | None:
assert test(100, 200) == (100, 200)
assert test(0) is None # square pixels

def test_dpi_jfif_cm(self):
def test_dpi_jfif_cm(self) -> None:
with Image.open("Tests/images/jfif_unit_cm.jpg") as im:
assert im.info["dpi"] == (2.54, 5.08)

Expand Down Expand Up @@ -281,7 +281,10 @@ def test_progressive(self) -> None:
assert not im2.info.get("progressive")
assert im3.info.get("progressive")

assert_image_equal(im1, im3)
if features.check_feature("mozjpeg"):
assert_image_similar(im1, im3, 9.39)
else:
assert_image_equal(im1, im3)
assert im1_bytes >= im3_bytes

def test_progressive_large_buffer(self, tmp_path: Path) -> None:
Expand Down Expand Up @@ -353,7 +356,6 @@ def test_empty_exif_gps(self) -> None:
assert exif.get_ifd(0x8825) == {}

transposed = ImageOps.exif_transpose(im)
assert transposed is not None
exif = transposed.getexif()
assert exif.get_ifd(0x8825) == {}

Expand Down Expand Up @@ -424,8 +426,12 @@ def test_progressive_compat(self) -> None:

im2 = self.roundtrip(hopper(), progressive=1)
im3 = self.roundtrip(hopper(), progression=1) # compatibility
assert_image_equal(im1, im2)
assert_image_equal(im1, im3)
if features.check_feature("mozjpeg"):
assert_image_similar(im1, im2, 9.39)
assert_image_similar(im1, im3, 9.39)
else:
assert_image_equal(im1, im2)
assert_image_equal(im1, im3)
assert im2.info.get("progressive")
assert im2.info.get("progression")
assert im3.info.get("progressive")
Expand Down Expand Up @@ -1031,7 +1037,7 @@ def decode(

with Image.open(TEST_FILE) as im:
im.tile = [
("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)),
ImageFile._Tile("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)),
]
ImageFile.LOAD_TRUNCATED_IMAGES = True
im.load()
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,7 @@ def test_realloc_overflow(self, monkeypatch: pytest.MonkeyPatch) -> None:
im.load()

# Assert that the error code is IMAGING_CODEC_MEMORY
assert str(e.value) == "-9"
assert str(e.value) == "decoder error -9"

@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg"))
def test_save_multistrip(self, compression: str, tmp_path: Path) -> None:
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ def test_textual_chunks_after_idat(self) -> None:
with Image.open("Tests/images/truncated_image.png") as im:
# The file is truncated
with pytest.raises(OSError):
im.text()
im.text
ImageFile.LOAD_TRUNCATED_IMAGES = True
assert isinstance(im.text, dict)
ImageFile.LOAD_TRUNCATED_IMAGES = False
Expand Down
6 changes: 3 additions & 3 deletions Tests/test_file_spider.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import pytest

from PIL import Image, ImageSequence, SpiderImagePlugin
from PIL import Image, SpiderImagePlugin

from .helper import assert_image_equal, hopper, is_pypy

Expand Down Expand Up @@ -154,8 +154,8 @@ def test_nonstack_file() -> None:

def test_nonstack_dos() -> None:
with Image.open(TEST_FILE) as im:
for i, frame in enumerate(ImageSequence.Iterator(im)):
assert i <= 1, "Non-stack DOS file test failed"
with pytest.raises(EOFError):
im.seek(0)


# for issue #4093
Expand Down
46 changes: 42 additions & 4 deletions Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,18 @@ def test_bigtiff(self, tmp_path: Path) -> None:

def test_bigtiff_save(self, tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.tif")
hopper().save(outfile, big_tiff=True)
im = hopper()
im.save(outfile, big_tiff=True)

with Image.open(outfile) as im:
with Image.open(outfile) as reloaded:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert reloaded.tag_v2._bigtiff is True

im.save(outfile, save_all=True, append_images=[im], big_tiff=True)

with Image.open(outfile) as reloaded:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.tag_v2._bigtiff is True
assert reloaded.tag_v2._bigtiff is True

def test_seek_too_large(self) -> None:
with pytest.raises(ValueError, match="Unable to seek to frame"):
Expand Down Expand Up @@ -776,7 +783,7 @@ def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
assert reread.n_frames == 3

def test_fixoffsets(self) -> None:
b = BytesIO(b"II\x2a\x00\x00\x00\x00\x00")
b = BytesIO(b"II\x2A\x00\x00\x00\x00\x00")
with TiffImagePlugin.AppendingTiffWriter(b) as a:
b.seek(0)
a.fixOffsets(1, isShort=True)
Expand All @@ -789,6 +796,37 @@ def test_fixoffsets(self) -> None:
with pytest.raises(RuntimeError):
a.fixOffsets(1)

b = BytesIO(b"II\x2A\x00\x00\x00\x00\x00")
with TiffImagePlugin.AppendingTiffWriter(b) as a:
a.offsetOfNewPage = 2**16

b.seek(0)
a.fixOffsets(1, isShort=True)

b = BytesIO(b"II\x2B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
with TiffImagePlugin.AppendingTiffWriter(b) as a:
a.offsetOfNewPage = 2**32

b.seek(0)
a.fixOffsets(1, isShort=True)

b.seek(0)
a.fixOffsets(1, isLong=True)

def test_appending_tiff_writer_writelong(self) -> None:
data = b"II\x2A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b = BytesIO(data)
with TiffImagePlugin.AppendingTiffWriter(b) as a:
a.writeLong(2**32 - 1)
assert b.getvalue() == data + b"\xff\xff\xff\xff"

def test_appending_tiff_writer_rewritelastshorttolong(self) -> None:
data = b"II\x2A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b = BytesIO(data)
with TiffImagePlugin.AppendingTiffWriter(b) as a:
a.rewriteLastShortToLong(2**32 - 1)
assert b.getvalue() == data[:-2] + b"\xff\xff\xff\xff"

def test_saving_icc_profile(self, tmp_path: Path) -> None:
# Tests saving TIFF with icc_profile set.
# At the time of writing this will only work for non-compressed tiffs
Expand Down
6 changes: 0 additions & 6 deletions Tests/test_imageops.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,6 @@ def check(orientation_im: Image.Image) -> None:
else:
original_exif = im.info["exif"]
transposed_im = ImageOps.exif_transpose(im)
assert transposed_im is not None
assert_image_similar(base_im, transposed_im, 17)
if orientation_im is base_im:
assert "exif" not in im.info
Expand All @@ -417,7 +416,6 @@ def check(orientation_im: Image.Image) -> None:

# Repeat the operation to test that it does not keep transposing
transposed_im2 = ImageOps.exif_transpose(transposed_im)
assert transposed_im2 is not None
assert_image_equal(transposed_im2, transposed_im)

check(base_im)
Expand All @@ -433,7 +431,6 @@ def check(orientation_im: Image.Image) -> None:
assert im.getexif()[0x0112] == 3

transposed_im = ImageOps.exif_transpose(im)
assert transposed_im is not None
assert 0x0112 not in transposed_im.getexif()

transposed_im._reload_exif()
Expand All @@ -446,14 +443,12 @@ def check(orientation_im: Image.Image) -> None:
assert im.getexif()[0x0112] == 3

transposed_im = ImageOps.exif_transpose(im)
assert transposed_im is not None
assert 0x0112 not in transposed_im.getexif()

# Orientation set directly on Image.Exif
im = hopper()
im.getexif()[0x0112] = 3
transposed_im = ImageOps.exif_transpose(im)
assert transposed_im is not None
assert 0x0112 not in transposed_im.getexif()


Expand All @@ -464,7 +459,6 @@ def test_exif_transpose_xml_without_xmp() -> None:

del im.info["xmp"]
transposed_im = ImageOps.exif_transpose(im)
assert transposed_im is not None
assert 0x0112 not in transposed_im.getexif()


Expand Down
2 changes: 1 addition & 1 deletion docs/installation/platform-support.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ These platforms have been reported to work at the versions mentioned.
| Operating system | | Tested Python | | Latest tested | | Tested |
| | | versions | | Pillow version | | processors |
+==================================+============================+==================+==============+
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.0.0 |arm |
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.1.0 |arm |
| +----------------------------+------------------+ |
| | 3.8 | 10.4.0 | |
+----------------------------------+----------------------------+------------------+--------------+
Expand Down
Loading

0 comments on commit 88617e6

Please sign in to comment.