Merge branch 'main' into zlib_macos

This commit is contained in:
Andrew Murray 2025-03-04 08:32:39 +11:00 committed by GitHub
commit 9fd56a553e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 78 additions and 79 deletions

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

@ -60,6 +60,7 @@ jobs:
mingw-w64-x86_64-gcc \ mingw-w64-x86_64-gcc \
mingw-w64-x86_64-ghostscript \ mingw-w64-x86_64-ghostscript \
mingw-w64-x86_64-lcms2 \ mingw-w64-x86_64-lcms2 \
mingw-w64-x86_64-libimagequant \
mingw-w64-x86_64-libjpeg-turbo \ mingw-w64-x86_64-libjpeg-turbo \
mingw-w64-x86_64-libraqm \ mingw-w64-x86_64-libraqm \
mingw-w64-x86_64-libtiff \ mingw-w64-x86_64-libtiff \

View File

@ -38,13 +38,13 @@ 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_VERSION=1.3.1 ZLIB_VERSION=1.3.1
ZLIB_NG_VERSION=2.2.4 ZLIB_NG_VERSION=2.2.4
LIBWEBP_VERSION=1.5.0 LIBWEBP_VERSION=1.5.0

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

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

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

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

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

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

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

@ -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)}"

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

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

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

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

@ -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:

View File

@ -113,11 +113,11 @@ V = {
"BROTLI": "1.1.0", "BROTLI": "1.1.0",
"FREETYPE": "2.13.3", "FREETYPE": "2.13.3",
"FRIBIDI": "1.0.16", "FRIBIDI": "1.0.16",
"HARFBUZZ": "10.2.0", "HARFBUZZ": "10.4.0",
"JPEGTURBO": "3.1.0", "JPEGTURBO": "3.1.0",
"LCMS2": "2.16", "LCMS2": "2.17",
"LIBIMAGEQUANT": "4.3.4", "LIBIMAGEQUANT": "4.3.4",
"LIBPNG": "1.6.46", "LIBPNG": "1.6.47",
"LIBWEBP": "1.5.0", "LIBWEBP": "1.5.0",
"OPENJPEG": "2.5.3", "OPENJPEG": "2.5.3",
"TIFF": "4.6.0", "TIFF": "4.6.0",