Skip to content

Add benchmark tests#9654

Merged
hugovk merged 5 commits into
python-pillow:mainfrom
akx:benchmarks
Jun 29, 2026
Merged

Add benchmark tests#9654
hugovk merged 5 commits into
python-pillow:mainfrom
akx:benchmarks

Conversation

@akx

@akx akx commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Related to #9649 and python-pillow/pillow-perf#161.

This PR ports most Pillow-related tests from pillow-perf, modernized (no more compatibility with ancient versions of Pillow), converted to pytest-benchmark style, and also jams in the blend benchmark I used to measure #9649's impact.

The file lives under Tests/, as it is a pytest file (and some benchmarks use test fixture images), but is not named test_*, so it will not be automatically collected as a test (and nothing untoward should happen if you don't have pytest-benchmark installed).

The second commit in this PR wires benchmarks into CI after successful tests.

On my Macbook, running the whole benchmark suite takes about 200 seconds right now -- in CI, this could be adjusted with e.g. --benchmark-max-time at the possible expense of measurement fidelity. Also, some of the benchmarks are currently somewhat exhaustive (combinatorial parametrizes). EDIT: Ugh, about 5 minutes (so longer than the test suite) in CI 😅

@akx akx force-pushed the benchmarks branch 4 times, most recently from 6eb80ce to 096c161 Compare June 3, 2026 15:52
Comment thread .ci/benchmark.sh Outdated
@radarhere

Copy link
Copy Markdown
Member

You are only running this in our Test jobs, not others. I'm guessing you did that because you didn't want to slow down all of our CI jobs?

Comment thread Tests/benchmarks.py Outdated
Comment thread Tests/benchmarks.py Outdated
@akx

akx commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

You are only running this in our Test jobs, not others. I'm guessing you did that because you didn't want to slow down all of our CI jobs?

Yep, exactly. Since building Pillow from source doesn't actually seem to take that long, maybe it'd be worth it to split it to a separate build + benchmark only job that's run on a single "known" (given these are GHA cloud runners, we may really never know, and neighbors may be noisy) platform?

@hugovk hugovk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this!

The benchmark runs are pretty slow, especially for PyPy, which means the end-to-end time for the workflow goes from ~12 mins to ~40 mins.

Job main PR Delta Factor
ubuntu 3.10 2m57s 9m35s +6m38s 3.2x
ubuntu 3.11 3m36s 9m32s +5m56s 2.6x
ubuntu 3.12 3m14s 9m16s +6m02s 2.9x
ubuntu 3.13 3m20s 9m21s +6m01s 2.8x
ubuntu 3.14 3m02s 9m04s +6m02s 3.0x
ubuntu 3.14t 3m02s 8m54s +5m52s 2.9x
ubuntu 3.15 2m59s 9m28s +6m29s 3.2x
ubuntu 3.15t 3m15s 8m51s +5m36s 2.7x
ubuntu pypy3.11 8m46s 27m13s +18m27s 3.1x
macos 3.11 3m58s 9m30s +5m32s 2.4x
macos 3.12 3m07s 8m46s +5m39s 2.8x
macos 3.13 3m07s 10m01s +6m54s 3.2x
macos 3.14 3m03s 9m11s +6m08s 3.0x
macos 3.14t 4m15s 8m58s +4m43s 2.1x
macos 3.15 3m55s 9m57s +6m02s 2.5x
macos 3.15t 3m56s 11m57s +8m01s 3.0x
macos pypy3.11 7m58s 31m00s +23m02s 3.9x
macos-26-intel 3.10 7m18s 17m15s +9m57s 2.4x

Do we need to benchmark every version?

Also, this is benchmarking on noisy GHA runners, which might not be as reliable.

Should we use https://codspeed.io/ instead? My understanding is they have two approaches: one is to benchmark the real wall-time on their quiet servers, but there's a quota of 600 minutes per month for open source.

The other is to benchmark on GHA using pytest-codspeed, which uses a Valgrind/Cachegrind CPU simulator, where server noise is less an issue. It's slower, but does a single pass rather than many rounds, so might be quicker.

With both, we can see and compare results on their website, a bit like Codecov.

Again, we might not want to benchmark every version?

Comment thread Tests/benchmarks.py Outdated
Comment thread .github/workflows/test.yml Outdated
Comment on lines +170 to +174
- name: Upload benchmarks
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmarks-${{ matrix.os }}-${{ matrix.python-version }}
path: .benchmarks

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't working:

Warning: No files were found with the provided path: .benchmarks. No artifacts will be uploaded.

https://github.com/python-pillow/Pillow/actions/runs/27641704795/job/81743138114

@akx akx force-pushed the benchmarks branch 2 times, most recently from 1f4567e to f03d742 Compare June 17, 2026 16:16
@akx

akx commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

The benchmark runs are pretty slow, especially for PyPy, which means the end-to-end time for the workflow goes from ~12 mins to ~40 mins.
Also, this is benchmarking on noisy GHA runners, which might not be as reliable.

Yep, noted above. :)

Should we use https://codspeed.io/ instead? My understanding is they have two approaches: one is to benchmark the real wall-time on their quiet servers, but there's a quota of 600 minutes per month for open source.

That's a possibility - happily, it looks like the pytest-benchmark benchmark fixture and pytest-codspeed benchmark fixture have the same signature. (I haven't used pytest-codspeed myself though.)

Again, we might not want to benchmark every version?

I rebased things and disabled the run-in-CI commit's guts for now; it's there but commented out pending a decision.

@hugovk

hugovk commented Jun 17, 2026

Copy link
Copy Markdown
Member

Let's try it, I've enabled this repo on Codspeed:

https://app.codspeed.io/python-pillow/Pillow

Setup info:

  1. Create benchmarks
    Create or update your existing benchmarks and install the CodSpeed integration in your language. Check out the docs on Creating benchmarks

  2. Setup CI workflow
    Create or update a GitHub Actions workflow to run CodSpeed, and open a pull request to trigger a first run.

  3. Merge to default branch
    Merge the installation pull request on your default branch to receive detailed performance reports in each pull request.

@mergify

This comment was marked as off-topic.

@hugovk hugovk mentioned this pull request Jun 28, 2026
@akx

akx commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

@hugovk Wired up CodSpeed: 3e91b4b

Comment thread .github/workflows/test.yml Outdated
@akx

akx commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

I'll let at least one of the codspeeds run to completion to see that results get uploaded correctly, then refactor these into a separate workflow file that runs independent of the test cycle on a more limited set of interpreters.

@codspeed-hq

codspeed-hq Bot commented Jun 29, 2026

Copy link
Copy Markdown

Congrats! CodSpeed is installed 🎉

🆕 331 new benchmarks were detected.

You will start to see performance impacts in the reports once the benchmarks are run from your default branch.

Detected benchmarks


ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.


Open in CodSpeed

@akx

akx commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Per Codspeed's comment

At least one benchmark was run multiple times in your benchmarking workflow. This could be because you used a matrix that runs your benchmarks multiple times.
This behavior is not yet supported, so CodSpeed was unable to generate a performance report.

I changed the new benchmark CI workflow to just run on ubuntu-latest+py3.14.

@akx

akx commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

@hugovk All green now 👍

@hugovk hugovk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!


- name: Run CodSpeed benchmarks
uses: CodSpeedHQ/action@a4a36bb07c0638b0b4ca52bf1f3dad1b4289e52f # v4.18.1
if: ${{ matrix.os == 'ubuntu-latest' }}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant:

Suggested change
if: ${{ matrix.os == 'ubuntu-latest' }}

Comment thread .github/workflows/benchmark.yml Outdated
Comment thread Tests/benchmarks.py Outdated
"pyproject.toml"

- name: Cache libavif
if: startsWith(matrix.os, 'ubuntu')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if: startsWith(matrix.os, 'ubuntu')

key: ${{ runner.os }}-libavif-${{ hashFiles('depends/install_libavif.sh', 'depends/libavif-svt4.patch') }}

- name: Cache libimagequant
if: startsWith(matrix.os, 'ubuntu')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if: startsWith(matrix.os, 'ubuntu')

key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}

- name: Cache libwebp
if: startsWith(matrix.os, 'ubuntu')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if: startsWith(matrix.os, 'ubuntu')

key: ${{ runner.os }}-libwebp-${{ hashFiles('depends/install_webp.sh') }}

- name: Install Linux dependencies
if: startsWith(matrix.os, 'ubuntu')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if: startsWith(matrix.os, 'ubuntu')

@akx

akx commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

I'd keep the (redundant, yes) if: startsWith(matrix.os, 'ubuntu') stanzas in the YAML, because it makes it that much easier to copy-paste from the test workflow if they change.

Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
@hugovk hugovk merged commit 80ac5e3 into python-pillow:main Jun 29, 2026
52 of 54 checks passed
@hugovk

hugovk commented Jul 1, 2026

Copy link
Copy Markdown
Member

CodSpeed settings:

image

I've updated it to only comment when there's a change (improvement, regression or drop).

luketainton pushed a commit to luketainton/repos_webexmemebot that referenced this pull request Jul 1, 2026
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [pillow](https://github.com/python-pillow/Pillow) ([changelog](https://github.com/python-pillow/Pillow/releases)) | `<12.2.1,>=12.2.0` → `<12.3.1,>=12.3.0` | ![age](https://developer.mend.io/api/mc/badges/age/pypi/pillow/12.3.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pillow/12.2.0/12.3.0?slim=true) |

---

### Release Notes

<details>
<summary>python-pillow/Pillow (pillow)</summary>

### [`v12.3.0`](https://github.com/python-pillow/Pillow/releases/tag/12.3.0)

[Compare Source](python-pillow/Pillow@12.2.0...12.3.0)

<https://pillow.readthedocs.io/en/stable/releasenotes/12.3.0.html>

#### Removals

- Remove non-image ImageCms modes [#&#8203;9697](python-pillow/Pillow#9697) \[[@&#8203;radarhere](https://github.com/radarhere)]

#### Documentation

- Add release notes for SBOM and performance improvements [#&#8203;9747](python-pillow/Pillow#9747) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add security release notes [#&#8203;9741](python-pillow/Pillow#9741) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add release notes for Python 3.15 beta wheels [#&#8203;9696](python-pillow/Pillow#9696) \[[@&#8203;radarhere](https://github.com/radarhere)]
- ImageFont can also be used with ImageText [#&#8203;9597](python-pillow/Pillow#9597) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Additional guidelines for security reports [#&#8203;9659](python-pillow/Pillow#9659) \[[@&#8203;wiredfool](https://github.com/wiredfool)]
- Fixed typo [#&#8203;9636](python-pillow/Pillow#9636) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Added CVEs to 12.2.0 release notes [#&#8203;9591](python-pillow/Pillow#9591) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Revise development support information in README [#&#8203;9583](python-pillow/Pillow#9583) \[[@&#8203;aclark4life](https://github.com/aclark4life)]
- Add INCIDENT\_RESPONSE.md [#&#8203;9555](python-pillow/Pillow#9555) \[[@&#8203;aclark4life](https://github.com/aclark4life)]
- Add STRIDE threat model to security docs [#&#8203;9562](python-pillow/Pillow#9562) \[[@&#8203;aclark4life](https://github.com/aclark4life)]
- Add CVEs to 12.2.0 release notes [#&#8203;9556](python-pillow/Pillow#9556) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Update README with revised security policy [#&#8203;9553](python-pillow/Pillow#9553) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Update security policy [#&#8203;9552](python-pillow/Pillow#9552) \[[@&#8203;aclark4life](https://github.com/aclark4life)]
- Update macOS tested Python versions [#&#8203;9534](python-pillow/Pillow#9534) \[[@&#8203;radarhere](https://github.com/radarhere)]

#### Dependencies

- Update dependency harfbuzz to v14.2.1 [#&#8203;9720](python-pillow/Pillow#9720) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update dependency mypy to v2 [#&#8203;9653](python-pillow/Pillow#9653) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update dependency cibuildwheel to v4 [#&#8203;9665](python-pillow/Pillow#9665) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update github-actions [#&#8203;9655](python-pillow/Pillow#9655) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update dependency libavif to v1.4.2 [#&#8203;9652](python-pillow/Pillow#9652) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update dependency lcms2 to v2.19.1 [#&#8203;9651](python-pillow/Pillow#9651) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update dependency check-jsonschema to v0.37.2 [#&#8203;9650](python-pillow/Pillow#9650) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update google/oss-fuzz digest to [`d872252`](python-pillow/Pillow@d872252) [#&#8203;9614](python-pillow/Pillow#9614) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update dependency lcms2 to v2.19 [#&#8203;9609](python-pillow/Pillow#9609) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update dependency libpng to v1.6.58 - autoclosed [#&#8203;9608](python-pillow/Pillow#9608) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update dependency harfbuzz to v14 [#&#8203;9610](python-pillow/Pillow#9610) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update dependency mypy to v1.20.2 [#&#8203;9599](python-pillow/Pillow#9599) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update github-actions [#&#8203;9611](python-pillow/Pillow#9611) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update dependency cibuildwheel to v3.4.1 [#&#8203;9607](python-pillow/Pillow#9607) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Move dependency versions to single JSON and enable Renovate [#&#8203;9559](python-pillow/Pillow#9559) \[[@&#8203;hugovk](https://github.com/hugovk)]
- Updated raqm to 0.10.5 [#&#8203;9557](python-pillow/Pillow#9557) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Update dependency cibuildwheel to v3.4.0 [#&#8203;9532](python-pillow/Pillow#9532) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]

#### Testing

- Remove matrix.os from benchmark [#&#8203;9735](python-pillow/Pillow#9735) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Remove references to libavif patch [#&#8203;9734](python-pillow/Pillow#9734) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add benchmark tests [#&#8203;9654](python-pillow/Pillow#9654) \[[@&#8203;akx](https://github.com/akx)]
- Use reshape() instead of setting NumPy array shape directly [#&#8203;9728](python-pillow/Pillow#9728) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add dependencies.json to Windows cache key [#&#8203;9721](python-pillow/Pillow#9721) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Increase AVIF test epsilon for loong64 [#&#8203;9714](python-pillow/Pillow#9714) \[[@&#8203;wszqkzqk](https://github.com/wszqkzqk)]
- Add colour to Linux and Windows wheel build logs [#&#8203;9677](python-pillow/Pillow#9677) \[[@&#8203;hugovk](https://github.com/hugovk)]
- Do not test NumPy against Python 3.15 Windows AMD64 wheels [#&#8203;9674](python-pillow/Pillow#9674) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Run pyroma in `tox -e lint` instead of pytest [#&#8203;9670](python-pillow/Pillow#9670) \[[@&#8203;hugovk](https://github.com/hugovk)]
- Update Ghostscript to 10.7.1 [#&#8203;9634](python-pillow/Pillow#9634) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Update free-threading CI [#&#8203;9625](python-pillow/Pillow#9625) \[[@&#8203;hugovk](https://github.com/hugovk)]
- Increase AVIF test epsilon for riscv64 [#&#8203;9606](python-pillow/Pillow#9606) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add Fedora 44 [#&#8203;9594](python-pillow/Pillow#9594) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Test Ubuntu 26.04 LTS (Resolute Raccoon) [#&#8203;9587](python-pillow/Pillow#9587) \[[@&#8203;hugovk](https://github.com/hugovk)]
- Skip EPS test\_1 for Ghostscript 10.06.0 [#&#8203;9588](python-pillow/Pillow#9588) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Catch subprocess.CalledProcessError in test\_grab\_x11 [#&#8203;9578](python-pillow/Pillow#9578) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Correct feature name [#&#8203;9542](python-pillow/Pillow#9542) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Skip test if FreeType is not available [#&#8203;9540](python-pillow/Pillow#9540) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Remove type hint ignore [#&#8203;9538](python-pillow/Pillow#9538) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Update macOS tested Python versions [#&#8203;9534](python-pillow/Pillow#9534) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Remove Debian 12 and Fedora 42 from CI [#&#8203;9530](python-pillow/Pillow#9530) \[[@&#8203;hugovk](https://github.com/hugovk)]
- Remove manylinux2014 and Amazon Linux 2 [#&#8203;9528](python-pillow/Pillow#9528) \[[@&#8203;radarhere](https://github.com/radarhere)]

#### Type hints

- Use NumPy 2.4.6 for mypy [#&#8203;9705](python-pillow/Pillow#9705) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Update dependency mypy to v2 [#&#8203;9653](python-pillow/Pillow#9653) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]
- Update putpixel type hint to allow lists in xy [#&#8203;9585](python-pillow/Pillow#9585) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Remove type hint ignore [#&#8203;9538](python-pillow/Pillow#9538) \[[@&#8203;radarhere](https://github.com/radarhere)]

#### Other changes

- Speed up ImageChops operations [#&#8203;9738](python-pillow/Pillow#9738) \[[@&#8203;akx](https://github.com/akx)]
- Speed up `Image.filter()` [#&#8203;9736](python-pillow/Pillow#9736) \[[@&#8203;akx](https://github.com/akx)]
- Speed up `Image.getchannel()`, `Image.merge()`, `Image.putalpha()` and `Image.split()` [#&#8203;9675](python-pillow/Pillow#9675) \[[@&#8203;akx](https://github.com/akx)]
- Speed up `Image.fill()`, `Image.linear_gradient()` and `Image.radial_gradient()`. [#&#8203;9737](python-pillow/Pillow#9737) \[[@&#8203;akx](https://github.com/akx)]
- Speed up `Image.resample()` [#&#8203;9739](python-pillow/Pillow#9739) \[[@&#8203;akx](https://github.com/akx)]
- Speed up `alpha_composite`, `matrix`, `negative`, `quantize` [#&#8203;9740](python-pillow/Pillow#9740) \[[@&#8203;akx](https://github.com/akx)]
- Remove PyErr\_Clear() of "weird" exceptions [#&#8203;9730](python-pillow/Pillow#9730) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Check realloc return value [#&#8203;9722](python-pillow/Pillow#9722) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add max\_length to PdfStream decode() [#&#8203;9718](python-pillow/Pillow#9718) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Return early when there is no fill region [#&#8203;9732](python-pillow/Pillow#9732) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Allow error to be raised if PyDict\_SetItemString fails [#&#8203;9731](python-pillow/Pillow#9731) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Speed up `Image.blend()` [#&#8203;9649](python-pillow/Pillow#9649) \[[@&#8203;akx](https://github.com/akx)]
- Raise OverflowError if number of vertices is too large for Path [#&#8203;9729](python-pillow/Pillow#9729) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Remove unused HSV and LAB matrix conversion from C [#&#8203;9724](python-pillow/Pillow#9724) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Prevent reusing ImagingDecoderObject.setimage [#&#8203;9656](python-pillow/Pillow#9656) \[[@&#8203;Serotav](https://github.com/Serotav)]
- Raise ValueError if FPX tile size is not 64px by 64px [#&#8203;9660](python-pillow/Pillow#9660) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Only clear error if it is BufferError [#&#8203;9727](python-pillow/Pillow#9727) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Apply XOR mask to 1 and L mode CUR images [#&#8203;9641](python-pillow/Pillow#9641) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Do not raise error from unknown channel ID when parsing PSD layers [#&#8203;9644](python-pillow/Pillow#9644) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Raise ValueError if P;2L or P;4L data is truncated in frombytes() [#&#8203;9725](python-pillow/Pillow#9725) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Embed SBOM into wheels [#&#8203;9679](python-pillow/Pillow#9679) \[[@&#8203;hugovk](https://github.com/hugovk)]
- Do not set eval() globals in ImageMath.unsafe\_eval() [#&#8203;9576](python-pillow/Pillow#9576) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add Tcl/Tk license to wheels [#&#8203;9663](python-pillow/Pillow#9663) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Ensure map stride is at least one full row of pixels [#&#8203;9719](python-pillow/Pillow#9719) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Raise OverflowError if text width exceeds INT\_MAX [#&#8203;9717](python-pillow/Pillow#9717) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Raise error if image modes do not match ImageCms transform modes [#&#8203;9715](python-pillow/Pillow#9715) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Use int64\_t for text height [#&#8203;9716](python-pillow/Pillow#9716) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Return if error occurs in Py\_mod\_exec slot [#&#8203;9712](python-pillow/Pillow#9712) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add decompression bomb checks to FontFile classes [#&#8203;9711](python-pillow/Pillow#9711) \[[@&#8203;radarhere](https://github.com/radarhere)]
- If C error is raised, return NULL [#&#8203;9706](python-pillow/Pillow#9706) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Prevent saving 1 mode images as TGA with run-length encoding [#&#8203;9709](python-pillow/Pillow#9709) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Raise ValueError if EPS BeginBinary bytecount is negative [#&#8203;9708](python-pillow/Pillow#9708) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Do not DECREF tuple until tuple items are no longer used [#&#8203;9707](python-pillow/Pillow#9707) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Do not update NumPy automatically [#&#8203;9713](python-pillow/Pillow#9713) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Simplified code [#&#8203;9642](python-pillow/Pillow#9642) \[[@&#8203;radarhere](https://github.com/radarhere)]
- If realloc fails, do not reduce block size [#&#8203;9702](python-pillow/Pillow#9702) \[[@&#8203;radarhere](https://github.com/radarhere)]
- DECREF PyDict\_GetItemRef result [#&#8203;9701](python-pillow/Pillow#9701) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Use int64\_t to calculate paste box dimensions [#&#8203;9703](python-pillow/Pillow#9703) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Calculate JPEG2000 total\_component\_width for each tile in isolation [#&#8203;9704](python-pillow/Pillow#9704) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Raise ValueError if value is not bytes for TIFF\_BYTE or TIFF\_ASCII tag [#&#8203;9699](python-pillow/Pillow#9699) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Release Py\_Buffer on error [#&#8203;9698](python-pillow/Pillow#9698) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Use os.startfile() in WindowsViewer show\_file() [#&#8203;9692](python-pillow/Pillow#9692) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Validate large filter sizes when initializing RankFilter [#&#8203;9695](python-pillow/Pillow#9695) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add decompression bomb check to GdImageFile [#&#8203;9693](python-pillow/Pillow#9693) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Free image bands when an error occurs while splitting an image [#&#8203;9694](python-pillow/Pillow#9694) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Check PyList\_Append return value [#&#8203;9690](python-pillow/Pillow#9690) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Check WebPMuxNew return value [#&#8203;9689](python-pillow/Pillow#9689) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Check PyCapsule\_New return value [#&#8203;9691](python-pillow/Pillow#9691) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Do not return negative width for text length [#&#8203;9623](python-pillow/Pillow#9623) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add args argument to METH\_NOARGS methods [#&#8203;9687](python-pillow/Pillow#9687) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Check ImagingNewDirty return value [#&#8203;9688](python-pillow/Pillow#9688) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Use int64\_t for text width [#&#8203;9686](python-pillow/Pillow#9686) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Move PyDateTime\_IMPORT inside Py\_mod\_exec slot [#&#8203;9580](python-pillow/Pillow#9580) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Validate size and rank when initializing RankFilter [#&#8203;9661](python-pillow/Pillow#9661) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Raise ValueError if insufficient data is read from DDS RGB file [#&#8203;9405](python-pillow/Pillow#9405) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Correct `IFDRational.__float__()` return value [#&#8203;9676](python-pillow/Pillow#9676) \[[@&#8203;nyxst4ck](https://github.com/nyxst4ck)]
- Correct length when accessing ImagePath.Path subscript [#&#8203;9685](python-pillow/Pillow#9685) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Release reference on non-flattened sequence error [#&#8203;9684](python-pillow/Pillow#9684) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Do not release Py\_buffer until buf is no longer in use [#&#8203;9683](python-pillow/Pillow#9683) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Do not resize macOS retina screenshots by default [#&#8203;9266](python-pillow/Pillow#9266) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add abstract BaseImageFont class [#&#8203;9595](python-pillow/Pillow#9595) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Cast before multiplying [#&#8203;9678](python-pillow/Pillow#9678) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Limit radius to half width or height of rounded rectangle [#&#8203;9561](python-pillow/Pillow#9561) \[[@&#8203;radarhere](https://github.com/radarhere)]
- linesize is always xsize multiplied by pixelsize [#&#8203;9647](python-pillow/Pillow#9647) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Check annotate\_hash\_table return value [#&#8203;9572](python-pillow/Pillow#9572) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Catch KeyError when checking mode from PNG IHDR chunk [#&#8203;9604](python-pillow/Pillow#9604) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Only pass one argument to C expand [#&#8203;9664](python-pillow/Pillow#9664) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Raise error if declared JPEG2000 marker length is too small [#&#8203;9666](python-pillow/Pillow#9666) \[[@&#8203;radarhere](https://github.com/radarhere)]
- In \_dump(), use Python PPM save, instead of C [#&#8203;9566](python-pillow/Pillow#9566) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Raise error consistently from inside ImagingNewArrow [#&#8203;9571](python-pillow/Pillow#9571) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Simplify `RankFilter.c` check [#&#8203;9662](python-pillow/Pillow#9662) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Support opening and saving L mode AVIF images with libavif >= 1.3.0 [#&#8203;9471](python-pillow/Pillow#9471) \[[@&#8203;radarhere](https://github.com/radarhere)]
- \[pre-commit.ci] pre-commit autoupdate [#&#8203;9648](python-pillow/Pillow#9648) \[@&#8203;[pre-commit-ci\[bot\]](https://github.com/apps/pre-commit-ci)]
- Apply libtiff patch to fix CVE-2026-4775 [#&#8203;9646](python-pillow/Pillow#9646) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Remove duplicate code [#&#8203;9640](python-pillow/Pillow#9640) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Switch iOS back to macos-26-intel [#&#8203;9631](python-pillow/Pillow#9631) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Don't use list as default in PdfParser read\_prev\_trailer [#&#8203;9629](python-pillow/Pillow#9629) \[[@&#8203;danigm](https://github.com/danigm)]
- Add support for Python 3.15 [#&#8203;9624](python-pillow/Pillow#9624) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Do not draw line or arc if width is zero [#&#8203;9589](python-pillow/Pillow#9589) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Use \_accept check in WebP \_open [#&#8203;9605](python-pillow/Pillow#9605) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Compare dist sizes vs latest PyPI release [#&#8203;9621](python-pillow/Pillow#9621) \[[@&#8203;hugovk](https://github.com/hugovk)]
- Do not generate SBOM in scheduled run on fork [#&#8203;9620](python-pillow/Pillow#9620) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Use plugin method directly when saving PDFs [#&#8203;9547](python-pillow/Pillow#9547) \[[@&#8203;radarhere](https://github.com/radarhere)]
- \[pre-commit.ci] pre-commit autoupdate [#&#8203;9617](python-pillow/Pillow#9617) \[@&#8203;[pre-commit-ci\[bot\]](https://github.com/apps/pre-commit-ci)]
- Set Renovate prCreation to not-pending [#&#8203;9616](python-pillow/Pillow#9616) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Raise error if PNG transparency has incorrect type or length when saving [#&#8203;9536](python-pillow/Pillow#9536) \[[@&#8203;radarhere](https://github.com/radarhere)]
- If PdfParser buffer is memoryview, release it when closing [#&#8203;9596](python-pillow/Pillow#9596) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Correct integer overflow in 16-bit resampling [#&#8203;9480](python-pillow/Pillow#9480) \[[@&#8203;hayatoikoma](https://github.com/hayatoikoma)]
- SBOM: Use real versions from dependencies.json [#&#8203;9593](python-pillow/Pillow#9593) \[[@&#8203;hugovk](https://github.com/hugovk)]
- Restrict SBOM upload to only Pillow JSON [#&#8203;9598](python-pillow/Pillow#9598) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Generate CycloneDX SBOM at release time via CI [#&#8203;9550](python-pillow/Pillow#9550) \[[@&#8203;aclark4life](https://github.com/aclark4life)]
- Raise ValueError if ImageOps border has unsupported format [#&#8203;9426](python-pillow/Pillow#9426) \[[@&#8203;veeceey](https://github.com/veeceey)]
- Unsafe pointer dereference from unchecked Python integer in Tk initialization [#&#8203;9548](python-pillow/Pillow#9548) \[[@&#8203;barttran2k](https://github.com/barttran2k)]
- Reorder renovate.json [#&#8203;9565](python-pillow/Pillow#9565) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Add python-pillow GitHub Sponsors to FUNDING.yml [#&#8203;9563](python-pillow/Pillow#9563) \[[@&#8203;aclark4life](https://github.com/aclark4life)]
- Correct environment URL [#&#8203;9558](python-pillow/Pillow#9558) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Remove or protect secrets in Actions [#&#8203;9544](python-pillow/Pillow#9544) \[@&#8203;[pre-commit-ci\[bot\]](https://github.com/apps/pre-commit-ci)]
- Move Homebrew dependencies into Brewfile [#&#8203;9546](python-pillow/Pillow#9546) \[[@&#8203;hugovk](https://github.com/hugovk)]
- Do not precompute horizontal coefficients if not horizontal resizing [#&#8203;9543](python-pillow/Pillow#9543) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Fix comparison warnings [#&#8203;9541](python-pillow/Pillow#9541) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Close PdfParser if error occurs during init [#&#8203;9539](python-pillow/Pillow#9539) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Drop experimental Python 3.13 free-threaded wheels [#&#8203;9535](python-pillow/Pillow#9535) \[[@&#8203;radarhere](https://github.com/radarhere)]
- Update github-actions [#&#8203;9533](python-pillow/Pillow#9533) \[@&#8203;[renovate\[bot\]](https://github.com/apps/renovate)]

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNCIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL2RlcGVuZGVuY2llcyJdfQ==-->

Reviewed-on: https://git.tainton.uk/repos/webexmemebot/pulls/596
Co-authored-by: renovate[bot] <renovate-bot@git.tainton.uk>
Co-committed-by: renovate[bot] <renovate-bot@git.tainton.uk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants