Skip to content

Commit 8bbe4aa

Browse files
authored
Merge branch 'main' into feature/webp-is-lossless
2 parents a24c417 + 1cd2d0f commit 8bbe4aa

16 files changed

Lines changed: 123 additions & 47 deletions

.ci/requirements-cibw.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
cibuildwheel==3.4.0
1+
cibuildwheel==3.4.1

.ci/requirements-mypy.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
mypy==1.19.1
1+
mypy==1.20.2
22
arro3-compute
33
arro3-core
44
IceSpringPySideStubs-PyQt6

.github/dependencies.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
"bzip2": "1.0.8",
44
"freetype": "2.14.3",
55
"fribidi": "1.0.16",
6-
"harfbuzz": "13.2.1",
6+
"harfbuzz": "14.2.0",
77
"jpegturbo": "3.1.4.1",
8-
"lcms2": "2.18",
8+
"lcms2": "2.19",
99
"libavif": "1.4.1",
1010
"libimagequant": "4.4.1",
11-
"libpng": "1.6.56",
11+
"libpng": "1.6.58",
1212
"libwebp": "1.6.0",
1313
"libxcb": "1.17.0",
1414
"openjpeg": "2.5.4",

.github/workflows/cifuzz.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ jobs:
3030
steps:
3131
- name: Build Fuzzers
3232
id: build
33-
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@e41e2f295eb18d630932fdd33d072527ba74c87b # master
33+
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@c11174f47deee98f260dede5d661614bda78ae39 # master
3434
with:
3535
oss-fuzz-project-name: 'pillow'
3636
language: python
3737
dry-run: false
3838
- name: Run Fuzzers
3939
id: run
40-
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@e41e2f295eb18d630932fdd33d072527ba74c87b # master
40+
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@c11174f47deee98f260dede5d661614bda78ae39 # master
4141
with:
4242
oss-fuzz-project-name: 'pillow'
4343
fuzz-seconds: 600

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
with:
2626
python-version: "3.x"
2727
- name: Install uv
28-
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
28+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
2929
- name: Lint
3030
run: uvx --with tox-uv tox -e lint
3131
- name: Mypy

.github/workflows/wheels.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ jobs:
270270
path: dist
271271
merge-multiple: true
272272
- name: Upload wheels to scientific-python-nightly-wheels
273-
uses: scientific-python/upload-nightly-action@5748273c71e2d8d3a61f3a11a16421c8954f9ecf # 0.6.3
273+
uses: scientific-python/upload-nightly-action@e76cfec8a4611fd02808a801b0ff5a7d7c1b2d99 # 0.6.4
274274
with:
275275
artifacts_path: dist
276276
anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}

Tests/test_file_png.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,8 +502,9 @@ def test_trns_rgb(self) -> None:
502502
im = roundtrip(im)
503503
assert im.info["transparency"] == (248, 248, 248)
504504

505-
im = roundtrip(im, transparency=(0, 1, 2))
506-
assert im.info["transparency"] == (0, 1, 2)
505+
for transparency in ((0, 1, 2), [0, 1, 2]):
506+
im = roundtrip(im, transparency=transparency)
507+
assert im.info["transparency"] == (0, 1, 2)
507508

508509
def test_trns_p(self, tmp_path: Path) -> None:
509510
# Check writing a transparency of 0, issue #528
@@ -518,6 +519,36 @@ def test_trns_p(self, tmp_path: Path) -> None:
518519

519520
assert_image_equal(im2.convert("RGBA"), im.convert("RGBA"))
520521

522+
def test_trns_invalid(self, tmp_path: Path) -> None:
523+
out = tmp_path / "temp.png"
524+
525+
for mode in ("1", "L", "I;16"):
526+
im = Image.new(mode, (1, 1))
527+
with pytest.raises(
528+
ValueError, match=f"transparency for {mode} must be an integer"
529+
):
530+
im.save(out, transparency="invalid")
531+
532+
im = Image.new("I", (1, 1))
533+
with pytest.warns(DeprecationWarning, match="Saving I mode images as PNG"):
534+
with pytest.raises(ValueError):
535+
im.save(out, transparency="invalid")
536+
537+
im = Image.new("P", (1, 1))
538+
with pytest.raises(
539+
ValueError, match="transparency for P must be an integer or bytes"
540+
):
541+
im.save(out, transparency="invalid")
542+
543+
im = Image.new("RGB", (1, 1))
544+
with pytest.raises(
545+
ValueError, match="transparency for RGB must be list or tuple"
546+
):
547+
im.save(out, transparency="invalid")
548+
549+
with pytest.raises(ValueError, match="transparency for RGB must have length 3"):
550+
im.save(out, transparency=(1, 2))
551+
521552
def test_trns_null(self) -> None:
522553
# Check reading images with null tRNS value, issue #1239
523554
test_file = "Tests/images/tRNS_null_1x1.png"

Tests/test_image.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,7 @@ def test_exif_jpeg(self, tmp_path: Path) -> None:
862862
def test_exif_webp(self, tmp_path: Path) -> None:
863863
with Image.open("Tests/images/hopper.webp") as im:
864864
exif = im.getexif()
865-
assert exif == {}
865+
assert dict(exif) == {}
866866

867867
out = tmp_path / "temp.webp"
868868
exif[258] = 8
@@ -884,7 +884,7 @@ def check_exif() -> None:
884884
def test_exif_png(self, tmp_path: Path) -> None:
885885
with Image.open("Tests/images/exif.png") as im:
886886
exif = im.getexif()
887-
assert exif == {274: 1}
887+
assert dict(exif) == {274: 1}
888888

889889
out = tmp_path / "temp.png"
890890
exif[258] = 8

Tests/test_image_resample.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,3 +627,37 @@ def test_skip_vertical(self, flt: Image.Resampling) -> None:
627627
0.4,
628628
f">>> {size} {box} {flt}",
629629
)
630+
631+
632+
class TestCoreResample16bpc:
633+
# Lanczos weighting during downsampling can push accumulated float sums
634+
@pytest.mark.parametrize(
635+
"offset",
636+
(
637+
# below 0. These must be clamped to 0, not corrupted byte-by-byte.
638+
0, # Left half = 65535, right half = 0
639+
# above 65535. These must be clamped to 65535, not corrupted byte-by-byte.
640+
50, # # Left half = 0, right half = 65535
641+
),
642+
)
643+
def test_resampling_clamp_overflow(self, offset: int) -> None:
644+
ims = {}
645+
width, height = 100, 10
646+
for mode in ("I;16", "F"):
647+
im = Image.new(mode, (width, height))
648+
im.paste(65535, (offset, 0, offset + width // 2, height))
649+
650+
# 5x downsampling with Lanczos
651+
# creates ~8.7% overshoot or undershoot at the step edge
652+
ims[mode] = im.resize((20, height), Image.Resampling.LANCZOS)
653+
654+
for y in range(height):
655+
for x in range(20):
656+
v = ims["F"].getpixel((x, y))
657+
assert isinstance(v, float)
658+
expected = max(0, min(65535, round(v)))
659+
660+
value = ims["I;16"].getpixel((x, y))
661+
assert (
662+
value == expected
663+
), f"Pixel ({x}, {y}): expected {expected}, got {value}"

docs/installation/building-from-source.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Many of Pillow's features require external libraries:
5151
* **littlecms** provides color management
5252

5353
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
54-
above uses liblcms2. Tested with **1.19** and **2.7-2.18**.
54+
above uses liblcms2. Tested with **1.19** and **2.7-2.19**.
5555

5656
* **libwebp** provides the WebP format.
5757

0 commit comments

Comments
 (0)