Skip to content

Commit 46b0b0e

Browse files
authored
Merge branch 'main' into types-cms2
2 parents 9473278 + a6c7a04 commit 46b0b0e

20 files changed

Lines changed: 231 additions & 97 deletions

.github/ISSUE_TEMPLATE/ISSUE_REPORT.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@ Thank you.
4848
* Python:
4949
* Pillow:
5050

51+
```text
52+
Please paste here the output of running:
53+
54+
python3 -m PIL.report
55+
or
56+
python3 -m PIL --report
57+
58+
Or the output of the following Python code:
59+
60+
from PIL import report
61+
# or
62+
from PIL import features
63+
features.pilinfo(supported_formats=False)
64+
```
65+
5166
<!--
5267
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
5368

.github/workflows/wheels-dependencies.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ ARCHIVE_SDIR=pillow-depends-main
1616

1717
# Package versions for fresh source builds
1818
FREETYPE_VERSION=2.13.2
19-
HARFBUZZ_VERSION=8.3.1
19+
HARFBUZZ_VERSION=8.4.0
2020
LIBPNG_VERSION=1.6.43
2121
JPEGTURBO_VERSION=3.0.2
2222
OPENJPEG_VERSION=2.5.2

.github/workflows/wheels.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
paths:
66
- ".ci/requirements-cibw.txt"
77
- ".github/workflows/wheel*"
8+
- "setup.py"
89
- "wheels/*"
910
- "winbuild/build_prepare.py"
1011
- "winbuild/fribidi.cmake"
@@ -14,6 +15,7 @@ on:
1415
paths:
1516
- ".ci/requirements-cibw.txt"
1617
- ".github/workflows/wheel*"
18+
- "setup.py"
1719
- "wheels/*"
1820
- "winbuild/build_prepare.py"
1921
- "winbuild/fribidi.cmake"

CHANGES.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ Changelog (Pillow)
55
10.3.0 (unreleased)
66
-------------------
77

8+
- Determine MPO size from markers, not EXIF data #7884
9+
[radarhere]
10+
11+
- Improved conversion from RGB to RGBa, LA and La #7888
12+
[radarhere]
13+
14+
- Support FITS images with GZIP_1 compression #7894
15+
[radarhere]
16+
817
- Use I;16 mode for 9-bit JPEG 2000 images #7900
918
[scaramallion, radarhere]
1019

Tests/helper.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,6 @@ def hopper(mode: str | None = None, cache: dict[str, Image.Image] = {}) -> Image
263263
if im is None:
264264
if mode == "F":
265265
im = hopper("L").convert(mode)
266-
elif mode[:4] == "I;16":
267-
im = hopper("I").convert(mode)
268266
else:
269267
im = hopper().convert(mode)
270268
cache[mode] = im

Tests/test_features.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,10 @@ def test_unsupported_module() -> None:
117117
features.version_module(module)
118118

119119

120-
def test_pilinfo() -> None:
120+
@pytest.mark.parametrize("supported_formats", (True, False))
121+
def test_pilinfo(supported_formats) -> None:
121122
buf = io.StringIO()
122-
features.pilinfo(buf)
123+
features.pilinfo(buf, supported_formats=supported_formats)
123124
out = buf.getvalue()
124125
lines = out.splitlines()
125126
assert lines[0] == "-" * 68
@@ -129,9 +130,15 @@ def test_pilinfo() -> None:
129130
while lines[0].startswith(" "):
130131
lines = lines[1:]
131132
assert lines[0] == "-" * 68
132-
assert lines[1].startswith("Python modules loaded from ")
133-
assert lines[2].startswith("Binary modules loaded from ")
134-
assert lines[3] == "-" * 68
133+
assert lines[1].startswith("Python executable is")
134+
lines = lines[2:]
135+
if lines[0].startswith("Environment Python files loaded from"):
136+
lines = lines[1:]
137+
assert lines[0].startswith("System Python files loaded from")
138+
assert lines[1] == "-" * 68
139+
assert lines[2].startswith("Python Pillow modules loaded from ")
140+
assert lines[3].startswith("Binary Pillow modules loaded from ")
141+
assert lines[4] == "-" * 68
135142
jpeg = (
136143
"\n"
137144
+ "-" * 68
@@ -142,4 +149,4 @@ def test_pilinfo() -> None:
142149
+ "-" * 68
143150
+ "\n"
144151
)
145-
assert jpeg in out
152+
assert supported_formats == (jpeg in out)

Tests/test_file_jpeg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,7 @@ def test_no_dpi_in_exif(self) -> None:
825825
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
826826
# Act / Assert
827827
# "When the image resolution is unknown, 72 [dpi] is designated."
828-
# https://exiv2.org/tags.html
828+
# https://web.archive.org/web/20240227115053/https://exiv2.org/tags.html
829829
assert im.info.get("dpi") == (72, 72)
830830

831831
def test_invalid_exif(self) -> None:

Tests/test_image.py

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,36 +33,38 @@
3333
skip_unless_feature,
3434
)
3535

36+
# name, pixel size
37+
image_modes = (
38+
("1", 1),
39+
("L", 1),
40+
("LA", 4),
41+
("La", 4),
42+
("P", 1),
43+
("PA", 4),
44+
("F", 4),
45+
("I", 4),
46+
("I;16", 2),
47+
("I;16L", 2),
48+
("I;16B", 2),
49+
("I;16N", 2),
50+
("RGB", 4),
51+
("RGBA", 4),
52+
("RGBa", 4),
53+
("RGBX", 4),
54+
("BGR;15", 2),
55+
("BGR;16", 2),
56+
("BGR;24", 3),
57+
("CMYK", 4),
58+
("YCbCr", 4),
59+
("HSV", 4),
60+
("LAB", 4),
61+
)
62+
63+
image_mode_names = [name for name, _ in image_modes]
64+
3665

3766
class TestImage:
38-
@pytest.mark.parametrize(
39-
"mode",
40-
(
41-
"1",
42-
"P",
43-
"PA",
44-
"L",
45-
"LA",
46-
"La",
47-
"F",
48-
"I",
49-
"I;16",
50-
"I;16L",
51-
"I;16B",
52-
"I;16N",
53-
"RGB",
54-
"RGBX",
55-
"RGBA",
56-
"RGBa",
57-
"BGR;15",
58-
"BGR;16",
59-
"BGR;24",
60-
"CMYK",
61-
"YCbCr",
62-
"LAB",
63-
"HSV",
64-
),
65-
)
67+
@pytest.mark.parametrize("mode", image_mode_names)
6668
def test_image_modes_success(self, mode: str) -> None:
6769
Image.new(mode, (1, 1))
6870

@@ -1042,6 +1044,35 @@ def test_close_graceful(self, caplog: pytest.LogCaptureFixture) -> None:
10421044
assert im.fp is None
10431045

10441046

1047+
class TestImageBytes:
1048+
@pytest.mark.parametrize("mode", image_mode_names)
1049+
def test_roundtrip_bytes_constructor(self, mode: str) -> None:
1050+
im = hopper(mode)
1051+
source_bytes = im.tobytes()
1052+
1053+
reloaded = Image.frombytes(mode, im.size, source_bytes)
1054+
assert reloaded.tobytes() == source_bytes
1055+
1056+
@pytest.mark.parametrize("mode", image_mode_names)
1057+
def test_roundtrip_bytes_method(self, mode: str) -> None:
1058+
im = hopper(mode)
1059+
source_bytes = im.tobytes()
1060+
1061+
reloaded = Image.new(mode, im.size)
1062+
reloaded.frombytes(source_bytes)
1063+
assert reloaded.tobytes() == source_bytes
1064+
1065+
@pytest.mark.parametrize(("mode", "pixelsize"), image_modes)
1066+
def test_getdata_putdata(self, mode: str, pixelsize: int) -> None:
1067+
im = Image.new(mode, (2, 2))
1068+
source_bytes = bytes(range(im.width * im.height * pixelsize))
1069+
im.frombytes(source_bytes)
1070+
1071+
reloaded = Image.new(mode, im.size)
1072+
reloaded.putdata(im.getdata())
1073+
assert_image_equal(im, reloaded)
1074+
1075+
10451076
class MockEncoder(ImageFile.PyEncoder):
10461077
pass
10471078

Tests/test_main.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@
44
import subprocess
55
import sys
66

7+
import pytest
78

8-
def test_main() -> None:
9-
out = subprocess.check_output([sys.executable, "-m", "PIL"]).decode("utf-8")
9+
10+
@pytest.mark.parametrize(
11+
"args, report",
12+
((["PIL"], False), (["PIL", "--report"], True), (["PIL.report"], True)),
13+
)
14+
def test_main(args, report) -> None:
15+
args = [sys.executable, "-m"] + args
16+
out = subprocess.check_output(args).decode("utf-8")
1017
lines = out.splitlines()
1118
assert lines[0] == "-" * 68
1219
assert lines[1].startswith("Pillow ")
@@ -15,9 +22,15 @@ def test_main() -> None:
1522
while lines[0].startswith(" "):
1623
lines = lines[1:]
1724
assert lines[0] == "-" * 68
18-
assert lines[1].startswith("Python modules loaded from ")
19-
assert lines[2].startswith("Binary modules loaded from ")
20-
assert lines[3] == "-" * 68
25+
assert lines[1].startswith("Python executable is")
26+
lines = lines[2:]
27+
if lines[0].startswith("Environment Python files loaded from"):
28+
lines = lines[1:]
29+
assert lines[0].startswith("System Python files loaded from")
30+
assert lines[1] == "-" * 68
31+
assert lines[2].startswith("Python Pillow modules loaded from ")
32+
assert lines[3].startswith("Binary Pillow modules loaded from ")
33+
assert lines[4] == "-" * 68
2134
jpeg = (
2235
os.linesep
2336
+ "-" * 68
@@ -31,4 +44,4 @@ def test_main() -> None:
3144
+ "-" * 68
3245
+ os.linesep
3346
)
34-
assert jpeg in out
47+
assert report == (jpeg not in out)

_custom_build/backend.py

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,41 +11,12 @@
1111
class _CustomBuildMetaBackend(backend_class):
1212
def run_setup(self, setup_script="setup.py"):
1313
if self.config_settings:
14+
for key, values in self.config_settings.items():
15+
if not isinstance(values, list):
16+
values = [values]
17+
for value in values:
18+
sys.argv.append(f"--pillow-configuration={key}={value}")
1419

15-
def config_has(key, value):
16-
settings = self.config_settings.get(key)
17-
if settings:
18-
if not isinstance(settings, list):
19-
settings = [settings]
20-
return value in settings
21-
22-
flags = []
23-
for dependency in (
24-
"zlib",
25-
"jpeg",
26-
"tiff",
27-
"freetype",
28-
"raqm",
29-
"lcms",
30-
"webp",
31-
"webpmux",
32-
"jpeg2000",
33-
"imagequant",
34-
"xcb",
35-
):
36-
if config_has(dependency, "enable"):
37-
flags.append("--enable-" + dependency)
38-
elif config_has(dependency, "disable"):
39-
flags.append("--disable-" + dependency)
40-
for dependency in ("raqm", "fribidi"):
41-
if config_has(dependency, "vendor"):
42-
flags.append("--vendor-" + dependency)
43-
if self.config_settings.get("platform-guessing") == "disable":
44-
flags.append("--disable-platform-guessing")
45-
if self.config_settings.get("debug") == "true":
46-
flags.append("--debug")
47-
if flags:
48-
sys.argv = sys.argv[:1] + ["build_ext"] + flags + sys.argv[1:]
4920
return super().run_setup(setup_script)
5021

5122
def build_wheel(
@@ -54,5 +25,15 @@ def build_wheel(
5425
self.config_settings = config_settings
5526
return super().build_wheel(wheel_directory, config_settings, metadata_directory)
5627

28+
def build_editable(
29+
self, wheel_directory, config_settings=None, metadata_directory=None
30+
):
31+
self.config_settings = config_settings
32+
return super().build_editable(
33+
wheel_directory, config_settings, metadata_directory
34+
)
35+
5736

58-
build_wheel = _CustomBuildMetaBackend().build_wheel
37+
_backend = _CustomBuildMetaBackend()
38+
build_wheel = _backend.build_wheel
39+
build_editable = _backend.build_editable

0 commit comments

Comments
 (0)