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
- name: Install Cygwin
uses: cygwin/cygwin-install-action@v5
uses: cygwin/cygwin-install-action@v6
with:
packages: >
gcc-g++
@ -89,10 +89,6 @@ jobs:
with:
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
uses: actions/cache@v4
with:

View File

@ -35,7 +35,7 @@ jobs:
strategy:
fail-fast: false
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"]
include:
# Test the oldest Python on 32-bit

View File

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

View File

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

View File

@ -13,6 +13,7 @@ include LICENSE
include Makefile
include tox.ini
graft Tests
graft Tests/images
graft checks
graft patches
graft src
@ -28,8 +29,19 @@ exclude .editorconfig
exclude .readthedocs.yml
exclude codecov.yml
exclude renovate.json
exclude Tests/images/README.md
exclude Tests/images/crash*.tif
exclude Tests/images/string_dimension.tiff
global-exclude .git*
global-exclude *.pyc
global-exclude *.so
prune .ci
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,10 +18,6 @@ def test_check() -> None:
for codec in features.codecs:
assert features.check_codec(codec) == features.check(codec)
for feature in features.features:
if "webp" in feature:
with pytest.warns(DeprecationWarning, match="webp"):
assert features.check_feature(feature) == features.check(feature)
else:
assert features.check_feature(feature) == features.check(feature)
@ -48,10 +44,6 @@ def test_version() -> None:
for codec in features.codecs:
test(codec, features.version_codec)
for feature in features.features:
if "webp" in feature:
with pytest.warns(DeprecationWarning, match="webp"):
test(feature, features.version_feature)
else:
test(feature, features.version_feature)
@ -112,6 +104,25 @@ def test_unsupported_module() -> None:
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))
def test_pilinfo(supported_formats: bool) -> None:
buf = io.StringIO()

View File

@ -315,3 +315,10 @@ int main(int argc, char* argv[])
process = subprocess.Popen(["embed_pil.exe"], env=env)
process.communicate()
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("L") == (256, 0, 662)
assert histogram("LA") == (512, 0, 16384)
assert histogram("La") == (512, 0, 16384)
assert histogram("I") == (256, 0, 662)
assert histogram("F") == (256, 0, 662)
assert histogram("P") == (256, 0, 1551)
assert histogram("PA") == (512, 0, 16384)
assert histogram("RGB") == (768, 4, 675)
assert histogram("RGBA") == (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))
converted_im = im.convert("LAB")
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")
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:
# Arrange
data = pyroma.projectdata.map_metadata_keys(metadata("Pillow"))
data = map_metadata_keys(metadata("Pillow"))
# Act
rating = pyroma.ratings.rate(data)

View File

@ -52,6 +52,15 @@ another mode before saving::
im = Image.new("I", (1, 1))
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
----------------

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
Pillow >= 11,Yes,Yes,Yes,Yes,Yes,,,,
Pillow 10.1 - 10.4,,Yes,Yes,Yes,Yes,Yes,,,
Pillow 10.0,,,Yes,Yes,Yes,Yes,,,
Pillow 9.3 - 9.5,,,Yes,Yes,Yes,Yes,Yes,,
Pillow 9.0 - 9.2,,,,Yes,Yes,Yes,Yes,,
Pillow 8.3.2 - 8.4,,,,Yes,Yes,Yes,Yes,Yes,
Pillow 8.0 - 8.3.1,,,,,Yes,Yes,Yes,Yes,
Pillow 7.0 - 7.2,,,,,,Yes,Yes,Yes,Yes
Python,3.14,3.13,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5
Pillow 12,Yes,Yes,Yes,Yes,Yes,,,,,
Pillow 11,,Yes,Yes,Yes,Yes,Yes,,,,
Pillow 10.1 - 10.4,,,Yes,Yes,Yes,Yes,Yes,,,
Pillow 10.0,,,,Yes,Yes,Yes,Yes,,,
Pillow 9.3 - 9.5,,,,Yes,Yes,Yes,Yes,Yes,,
Pillow 9.0 - 9.2,,,,,Yes,Yes,Yes,Yes,,
Pillow 8.3.2 - 8.4,,,,,Yes,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
============
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
===========
@ -134,7 +136,11 @@ TODO
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.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Multimedia :: Graphics",
@ -70,7 +71,7 @@ optional-dependencies.tests = [
"markdown2",
"olefile",
"packaging",
"pyroma",
"pyroma>=5",
"pytest",
"pytest-cov",
"pytest-timeout",
@ -209,7 +210,7 @@ lint.isort.required-imports = [
]
[tool.pyproject-fmt]
max_supported_python = "3.13"
max_supported_python = "3.14"
[tool.pytest.ini_options]
addopts = "-ra --color=auto"

View File

@ -23,9 +23,10 @@ import operator
import sys
from enum import IntEnum, IntFlag
from functools import reduce
from typing import Literal, SupportsFloat, SupportsInt, Union
from typing import Any, Literal, SupportsFloat, SupportsInt, Union
from . import Image
from ._deprecate import deprecate
from ._typing import SupportsRead
try:
@ -233,9 +234,7 @@ class ImageCmsProfile:
low-level profile object
"""
self.filename = None
self.product_name = None # profile.product_name
self.product_info = None # profile.product_info
self.filename: str | None = None
if isinstance(profile, str):
if sys.platform == "win32":
@ -256,6 +255,13 @@ class ImageCmsProfile:
msg = "Invalid type for Profile" # type: ignore[unreachable]
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:
"""
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);
for (y = 0; y < im->ysize; y++) {
UINT8 *in = (UINT8 *)im->image[y];
for (x = 0; x < im->xsize; x++) {
h->histogram[(*in++)]++;
h->histogram[(*in++) + 256]++;
h->histogram[(*in++) + 512]++;
h->histogram[(*in++) + 768]++;
for (x = 0; x < im->xsize; x++, in += 4) {
h->histogram[*in]++;
if (im->bands == 2) {
h->histogram[*(in + 3) + 256]++;
} else {
h->histogram[*(in + 1) + 256]++;
h->histogram[*(in + 2) + 512]++;
h->histogram[*(in + 3) + 768]++;
}
}
}
ImagingSectionLeave(&cookie);

View File

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