Merge branch 'main' into pyarrow_band_names

This commit is contained in:
Andrew Murray 2025-08-01 10:25:14 +10:00 committed by GitHub
commit 5fc0cf19c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 137 additions and 47 deletions

View File

@ -1 +1 @@
cibuildwheel==3.0.1 cibuildwheel==3.1.2

View File

@ -52,7 +52,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Install Cygwin - name: Install Cygwin
uses: cygwin/cygwin-install-action@v5 uses: cygwin/cygwin-install-action@v6
with: with:
packages: > packages: >
gcc-g++ gcc-g++
@ -89,10 +89,6 @@ jobs:
with: with:
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack' dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
- name: Select Python version
run: |
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
- name: pip cache - name: pip cache
uses: actions/cache@v4 uses: actions/cache@v4
with: with:

View File

@ -35,7 +35,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"] python-version: ["pypy3.11", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"]
architecture: ["x64"] architecture: ["x64"]
include: include:
# Test the oldest Python on 32-bit # Test the oldest Python on 32-bit

View File

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

View File

@ -95,7 +95,7 @@ ARCHIVE_SDIR=pillow-depends-main
# you change those versions, ensure the patch is also updated. # you change those versions, ensure the patch is also updated.
FREETYPE_VERSION=2.13.3 FREETYPE_VERSION=2.13.3
HARFBUZZ_VERSION=11.2.1 HARFBUZZ_VERSION=11.2.1
LIBPNG_VERSION=1.6.49 LIBPNG_VERSION=1.6.50
JPEGTURBO_VERSION=3.1.1 JPEGTURBO_VERSION=3.1.1
OPENJPEG_VERSION=2.5.3 OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.8.1 XZ_VERSION=5.8.1

View File

@ -13,6 +13,7 @@ include LICENSE
include Makefile include Makefile
include tox.ini include tox.ini
graft Tests graft Tests
graft Tests/images
graft checks graft checks
graft patches graft patches
graft src graft src
@ -28,8 +29,19 @@ exclude .editorconfig
exclude .readthedocs.yml exclude .readthedocs.yml
exclude codecov.yml exclude codecov.yml
exclude renovate.json exclude renovate.json
exclude Tests/images/README.md
exclude Tests/images/crash*.tif
exclude Tests/images/string_dimension.tiff
global-exclude .git* global-exclude .git*
global-exclude *.pyc global-exclude *.pyc
global-exclude *.so global-exclude *.so
prune .ci prune .ci
prune wheels prune wheels
prune winbuild/build
prune winbuild/depends
prune Tests/errors
prune Tests/images/jpeg2000
prune Tests/images/msp
prune Tests/images/picins
prune Tests/images/sunraster
prune Tests/test-images

View File

@ -18,11 +18,7 @@ def test_check() -> None:
for codec in features.codecs: for codec in features.codecs:
assert features.check_codec(codec) == features.check(codec) assert features.check_codec(codec) == features.check(codec)
for feature in features.features: for feature in features.features:
if "webp" in feature: assert features.check_feature(feature) == features.check(feature)
with pytest.warns(DeprecationWarning, match="webp"):
assert features.check_feature(feature) == features.check(feature)
else:
assert features.check_feature(feature) == features.check(feature)
def test_version() -> None: def test_version() -> None:
@ -48,11 +44,7 @@ def test_version() -> None:
for codec in features.codecs: for codec in features.codecs:
test(codec, features.version_codec) test(codec, features.version_codec)
for feature in features.features: for feature in features.features:
if "webp" in feature: test(feature, features.version_feature)
with pytest.warns(DeprecationWarning, match="webp"):
test(feature, features.version_feature)
else:
test(feature, features.version_feature)
@skip_unless_feature("libjpeg_turbo") @skip_unless_feature("libjpeg_turbo")
@ -112,6 +104,25 @@ def test_unsupported_module() -> None:
features.version_module(module) features.version_module(module)
def test_unsupported_feature() -> None:
# Arrange
feature = "unsupported_feature"
# Act / Assert
with pytest.raises(ValueError):
features.check_feature(feature)
with pytest.raises(ValueError):
features.version_feature(feature)
def test_unsupported_version() -> None:
assert features.version("unsupported_version") is None
def test_modulenotfound(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(features, "features", {"test": ("PIL._test", "", "")})
assert features.check_feature("test") is None
@pytest.mark.parametrize("supported_formats", (True, False)) @pytest.mark.parametrize("supported_formats", (True, False))
def test_pilinfo(supported_formats: bool) -> None: def test_pilinfo(supported_formats: bool) -> None:
buf = io.StringIO() buf = io.StringIO()

View File

@ -315,3 +315,10 @@ int main(int argc, char* argv[])
process = subprocess.Popen(["embed_pil.exe"], env=env) process = subprocess.Popen(["embed_pil.exe"], env=env)
process.communicate() process.communicate()
assert process.returncode == 0 assert process.returncode == 0
def teardown_method(self) -> None:
try:
os.remove("embed_pil.c")
except FileNotFoundError:
# If the test was skipped or failed, the file won't exist
pass

View File

@ -10,9 +10,12 @@ def test_histogram() -> None:
assert histogram("1") == (256, 0, 10994) assert histogram("1") == (256, 0, 10994)
assert histogram("L") == (256, 0, 662) assert histogram("L") == (256, 0, 662)
assert histogram("LA") == (512, 0, 16384)
assert histogram("La") == (512, 0, 16384)
assert histogram("I") == (256, 0, 662) assert histogram("I") == (256, 0, 662)
assert histogram("F") == (256, 0, 662) assert histogram("F") == (256, 0, 662)
assert histogram("P") == (256, 0, 1551) assert histogram("P") == (256, 0, 1551)
assert histogram("PA") == (512, 0, 16384)
assert histogram("RGB") == (768, 4, 675) assert histogram("RGB") == (768, 4, 675)
assert histogram("RGBA") == (1024, 0, 16384) assert histogram("RGBA") == (1024, 0, 16384)
assert histogram("CMYK") == (1024, 0, 16384) assert histogram("CMYK") == (1024, 0, 16384)

View File

@ -690,3 +690,17 @@ def test_cmyk_lab() -> None:
im = Image.new("CMYK", (1, 1)) im = Image.new("CMYK", (1, 1))
converted_im = im.convert("LAB") converted_im = im.convert("LAB")
assert converted_im.getpixel((0, 0)) == (255, 128, 128) assert converted_im.getpixel((0, 0)) == (255, 128, 128)
def test_deprecation() -> None:
profile = ImageCmsProfile(ImageCms.createProfile("sRGB"))
with pytest.warns(
DeprecationWarning, match="ImageCms.ImageCmsProfile.product_name"
):
profile.product_name
with pytest.warns(
DeprecationWarning, match="ImageCms.ImageCmsProfile.product_info"
):
profile.product_info
with pytest.raises(AttributeError):
profile.this_attribute_does_not_exist

View File

@ -9,9 +9,30 @@ from PIL import __version__
pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed") pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed")
def map_metadata_keys(metadata):
# Convert installed wheel metadata into canonical Core Metadata 2.4 format.
# This was a utility method in pyroma 4.3.3; it was removed in 5.0.
# This implementation is constructed from the relevant logic from
# Pyroma 5.0's `build_metadata()` implementation. This has been submitted
# upstream to Pyroma as https://github.com/regebro/pyroma/pull/116,
# so it may be possible to simplify this test in future.
data = {}
for key in set(metadata.keys()):
value = metadata.get_all(key)
key = pyroma.projectdata.normalize(key)
if len(value) == 1:
value = value[0]
if value.strip() == "UNKNOWN":
continue
data[key] = value
return data
def test_pyroma() -> None: def test_pyroma() -> None:
# Arrange # Arrange
data = pyroma.projectdata.map_metadata_keys(metadata("Pillow")) data = map_metadata_keys(metadata("Pillow"))
# Act # Act
rating = pyroma.ratings.rate(data) rating = pyroma.ratings.rate(data)

View File

@ -52,6 +52,15 @@ another mode before saving::
im = Image.new("I", (1, 1)) im = Image.new("I", (1, 1))
im.convert("I;16").save("out.png") im.convert("I;16").save("out.png")
ImageCms.ImageCmsProfile.product_name and .product_info
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 12.0.0
``ImageCms.ImageCmsProfile.product_name`` and the corresponding
``.product_info`` attributes have been deprecated, and will be removed in
Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
Removed features Removed features
---------------- ----------------

View File

@ -1,9 +1,10 @@
Python,3.13,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5 Python,3.14,3.13,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5
Pillow >= 11,Yes,Yes,Yes,Yes,Yes,,,, Pillow 12,Yes,Yes,Yes,Yes,Yes,,,,,
Pillow 10.1 - 10.4,,Yes,Yes,Yes,Yes,Yes,,, Pillow 11,,Yes,Yes,Yes,Yes,Yes,,,,
Pillow 10.0,,,Yes,Yes,Yes,Yes,,, Pillow 10.1 - 10.4,,,Yes,Yes,Yes,Yes,Yes,,,
Pillow 9.3 - 9.5,,,Yes,Yes,Yes,Yes,Yes,, Pillow 10.0,,,,Yes,Yes,Yes,Yes,,,
Pillow 9.0 - 9.2,,,,Yes,Yes,Yes,Yes,, Pillow 9.3 - 9.5,,,,Yes,Yes,Yes,Yes,Yes,,
Pillow 8.3.2 - 8.4,,,,Yes,Yes,Yes,Yes,Yes, Pillow 9.0 - 9.2,,,,,Yes,Yes,Yes,Yes,,
Pillow 8.0 - 8.3.1,,,,,Yes,Yes,Yes,Yes, Pillow 8.3.2 - 8.4,,,,,Yes,Yes,Yes,Yes,Yes,
Pillow 7.0 - 7.2,,,,,,Yes,Yes,Yes,Yes Pillow 8.0 - 8.3.1,,,,,,Yes,Yes,Yes,Yes,
Pillow 7.0 - 7.2,,,,,,,Yes,Yes,Yes,Yes

1 Python 3.14 3.13 3.12 3.11 3.10 3.9 3.8 3.7 3.6 3.5
2 Pillow >= 11 Pillow 12 Yes Yes Yes Yes Yes Yes
3 Pillow 10.1 - 10.4 Pillow 11 Yes Yes Yes Yes Yes Yes
4 Pillow 10.0 Pillow 10.1 - 10.4 Yes Yes Yes Yes Yes
5 Pillow 9.3 - 9.5 Pillow 10.0 Yes Yes Yes Yes Yes
6 Pillow 9.0 - 9.2 Pillow 9.3 - 9.5 Yes Yes Yes Yes Yes
7 Pillow 8.3.2 - 8.4 Pillow 9.0 - 9.2 Yes Yes Yes Yes Yes
8 Pillow 8.0 - 8.3.1 Pillow 8.3.2 - 8.4 Yes Yes Yes Yes Yes
9 Pillow 7.0 - 7.2 Pillow 8.0 - 8.3.1 Yes Yes Yes Yes Yes
10 Pillow 7.0 - 7.2 Yes Yes Yes Yes

View File

@ -110,10 +110,12 @@ vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
Deprecations Deprecations
============ ============
TODO ImageCms.ImageCmsProfile.product_name and .product_info
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO ``ImageCms.ImageCmsProfile.product_name`` and the corresponding
``.product_info`` attributes have been deprecated, and will be removed in
Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
API changes API changes
=========== ===========
@ -134,7 +136,11 @@ TODO
Other changes Other changes
============= =============
TODO Python 3.14
^^^^ ^^^^^^^^^^^
TODO Pillow 11.3.0 had wheels built against Python 3.14 beta, available as a preview to help
others prepare for 3.14, and to ensure Pillow could be used immediately at the release
of 3.14.0 final (2025-10-07, :pep:`745`).
Pillow 12.0.0 now officially supports Python 3.14.

View File

@ -29,6 +29,7 @@ classifiers = [
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Multimedia :: Graphics", "Topic :: Multimedia :: Graphics",
@ -70,7 +71,7 @@ optional-dependencies.tests = [
"markdown2", "markdown2",
"olefile", "olefile",
"packaging", "packaging",
"pyroma", "pyroma>=5",
"pytest", "pytest",
"pytest-cov", "pytest-cov",
"pytest-timeout", "pytest-timeout",
@ -209,7 +210,7 @@ lint.isort.required-imports = [
] ]
[tool.pyproject-fmt] [tool.pyproject-fmt]
max_supported_python = "3.13" max_supported_python = "3.14"
[tool.pytest.ini_options] [tool.pytest.ini_options]
addopts = "-ra --color=auto" addopts = "-ra --color=auto"

View File

@ -23,9 +23,10 @@ import operator
import sys import sys
from enum import IntEnum, IntFlag from enum import IntEnum, IntFlag
from functools import reduce from functools import reduce
from typing import Literal, SupportsFloat, SupportsInt, Union from typing import Any, Literal, SupportsFloat, SupportsInt, Union
from . import Image from . import Image
from ._deprecate import deprecate
from ._typing import SupportsRead from ._typing import SupportsRead
try: try:
@ -233,9 +234,7 @@ class ImageCmsProfile:
low-level profile object low-level profile object
""" """
self.filename = None self.filename: str | None = None
self.product_name = None # profile.product_name
self.product_info = None # profile.product_info
if isinstance(profile, str): if isinstance(profile, str):
if sys.platform == "win32": if sys.platform == "win32":
@ -256,6 +255,13 @@ class ImageCmsProfile:
msg = "Invalid type for Profile" # type: ignore[unreachable] msg = "Invalid type for Profile" # type: ignore[unreachable]
raise TypeError(msg) raise TypeError(msg)
def __getattr__(self, name: str) -> Any:
if name in ("product_name", "product_info"):
deprecate(f"ImageCms.ImageCmsProfile.{name}", 13)
return None
msg = f"'{self.__class__.__name__}' object has no attribute '{name}'"
raise AttributeError(msg)
def tobytes(self) -> bytes: def tobytes(self) -> bytes:
""" """
Returns the profile in a format suitable for embedding in Returns the profile in a format suitable for embedding in

View File

@ -132,11 +132,15 @@ ImagingGetHistogram(Imaging im, Imaging imMask, void *minmax) {
ImagingSectionEnter(&cookie); ImagingSectionEnter(&cookie);
for (y = 0; y < im->ysize; y++) { for (y = 0; y < im->ysize; y++) {
UINT8 *in = (UINT8 *)im->image[y]; UINT8 *in = (UINT8 *)im->image[y];
for (x = 0; x < im->xsize; x++) { for (x = 0; x < im->xsize; x++, in += 4) {
h->histogram[(*in++)]++; h->histogram[*in]++;
h->histogram[(*in++) + 256]++; if (im->bands == 2) {
h->histogram[(*in++) + 512]++; h->histogram[*(in + 3) + 256]++;
h->histogram[(*in++) + 768]++; } else {
h->histogram[*(in + 1) + 256]++;
h->histogram[*(in + 2) + 512]++;
h->histogram[*(in + 3) + 768]++;
}
} }
} }
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);

View File

@ -121,7 +121,7 @@ V = {
"LCMS2": "2.17", "LCMS2": "2.17",
"LIBAVIF": "1.3.0", "LIBAVIF": "1.3.0",
"LIBIMAGEQUANT": "4.3.4", "LIBIMAGEQUANT": "4.3.4",
"LIBPNG": "1.6.49", "LIBPNG": "1.6.50",
"LIBWEBP": "1.6.0", "LIBWEBP": "1.6.0",
"OPENJPEG": "2.5.3", "OPENJPEG": "2.5.3",
"TIFF": "4.7.0", "TIFF": "4.7.0",