Skip to content

Commit

Permalink
Merge branch 'main' into context_manager
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere authored Mar 3, 2025
2 parents 45034b2 + c7ed097 commit 0dc62ba
Show file tree
Hide file tree
Showing 27 changed files with 159 additions and 87 deletions.
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 @@ -60,6 +60,7 @@ jobs:
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-ghostscript \
mingw-w64-x86_64-lcms2 \
mingw-w64-x86_64-libimagequant \
mingw-w64-x86_64-libjpeg-turbo \
mingw-w64-x86_64-libraqm \
mingw-w64-x86_64-libtiff \
Expand Down
6 changes: 3 additions & 3 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 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
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
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
7 changes: 6 additions & 1 deletion Tests/test_file_mpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,17 @@ def roundtrip(im: Image.Image, **options: Any) -> ImageFile.ImageFile:

@pytest.mark.parametrize("test_file", test_files)
def test_sanity(test_file: str) -> None:
with Image.open(test_file) as im:
def check(im: ImageFile.ImageFile) -> None:
im.load()
assert im.mode == "RGB"
assert im.size == (640, 480)
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")
def test_unclosed_file() -> None:
Expand Down
57 changes: 56 additions & 1 deletion Tests/test_file_sun.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from __future__ import annotations

import io
import os

import pytest

from PIL import Image, SunImagePlugin
from PIL import Image, SunImagePlugin, _binary

from .helper import assert_image_equal_tofile, assert_image_similar, hopper

Expand Down Expand Up @@ -33,6 +34,60 @@ def test_im1() -> None:
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(
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
)
Expand Down
13 changes: 11 additions & 2 deletions Tests/test_font_bdf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import io

import pytest

from PIL import BdfFontFile, FontFile
Expand All @@ -8,13 +10,20 @@


def test_sanity() -> None:
with open(filename, "rb") as test_file:
font = BdfFontFile.BdfFontFile(test_file)
with open(filename, "rb") as fp:
font = BdfFontFile.BdfFontFile(fp)

assert isinstance(font, FontFile.FontFile)
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:
with open("Tests/images/flower.jpg", "rb") as fp:
with pytest.raises(SyntaxError):
Expand Down
15 changes: 14 additions & 1 deletion Tests/test_fontfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,20 @@

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:
Expand Down
4 changes: 0 additions & 4 deletions Tests/test_imagedraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,6 @@ def test_shape1() -> None:
x3, y3 = 95, 5

# Act
assert ImageDraw.Outline is not None
s = ImageDraw.Outline()
s.move(x0, y0)
s.curve(x1, y1, x2, y2, x3, y3)
Expand All @@ -471,7 +470,6 @@ def test_shape2() -> None:
x3, y3 = 5, 95

# Act
assert ImageDraw.Outline is not None
s = ImageDraw.Outline()
s.move(x0, y0)
s.curve(x1, y1, x2, y2, x3, y3)
Expand All @@ -490,7 +488,6 @@ def test_transform() -> None:
draw = ImageDraw.Draw(im)

# Act
assert ImageDraw.Outline is not None
s = ImageDraw.Outline()
s.line(0, 0)
s.transform((0, 0, 0, 0, 0, 0))
Expand Down Expand Up @@ -1527,7 +1524,6 @@ def test_same_color_outline(bbox: Coords) -> None:
x2, y2 = 95, 50
x3, y3 = 95, 5

assert ImageDraw.Outline is not None
s = ImageDraw.Outline()
s.move(x0, y0)
s.curve(x1, y1, x2, y2, x3, y3)
Expand Down
9 changes: 9 additions & 0 deletions Tests/test_imageops.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,15 @@ def check(orientation_im: Image.Image) -> None:
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:
with Image.open("Tests/images/xmp_tags_orientation.png") as im:
assert im.getexif()[0x0112] == 3
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
# -- General configuration ------------------------------------------------

# 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
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
Expand Down Expand Up @@ -121,7 +121,7 @@
# generating warnings in “nitpicky mode”. Note that type should include the domain name
# if present. Example entries would be ('py:func', 'int') or
# ('envvar', 'LD_LIBRARY_PATH').
nitpick_ignore = [("py:class", "_io.BytesIO"), ("py:class", "_CmsProfileCompatible")]
nitpick_ignore = [("py:class", "_CmsProfileCompatible")]


# -- Options for HTML output ----------------------------------------------
Expand Down
3 changes: 2 additions & 1 deletion docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,8 @@ The :py:meth:`~PIL.Image.open` method may set the following
Raw EXIF data from the image.

**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

Expand Down
2 changes: 1 addition & 1 deletion docs/installation/building-from-source.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management

* 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.

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ dynamic = [
optional-dependencies.docs = [
"furo",
"olefile",
"sphinx>=8.1",
"sphinx>=8.2",
"sphinx-copybutton",
"sphinx-inline-tabs",
"sphinxext-opengraph",
Expand Down
3 changes: 1 addition & 2 deletions src/PIL/FtexImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ def _open(self) -> None:
self._size = 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.
# I don't know of any multi-format file.
assert format_count == 1
Expand All @@ -96,6 +94,7 @@ def _open(self) -> None:
self._mode = "RGBA"
self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))]
elif format == Format.UNCOMPRESSED:
self._mode = "RGB"
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, "RGB")]
else:
msg = f"Invalid texture compression format: {repr(format)}"
Expand Down
Loading

0 comments on commit 0dc62ba

Please sign in to comment.