Compare commits

..

No commits in common. "main" and "10.4.x" have entirely different histories.
main ... 10.4.x

665 changed files with 14013 additions and 40334 deletions

100
.appveyor.yml Normal file
View File

@ -0,0 +1,100 @@
skip_commits:
files:
- ".github/**/*"
- ".gitmodules"
- "docs/**/*"
- "wheels/**/*"
version: '{build}'
clone_folder: c:\pillow
init:
- ECHO %PYTHON%
#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
# Uncomment previous line to get RDP access during the build.
environment:
COVERAGE_CORE: sysmon
EXECUTABLE: python.exe
TEST_OPTIONS:
DEPLOY: YES
matrix:
- PYTHON: C:/Python312
ARCHITECTURE: x86
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
- PYTHON: C:/Python38-x64
ARCHITECTURE: AMD64
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
install:
- '%PYTHON%\%EXECUTABLE% --version'
- '%PYTHON%\%EXECUTABLE% -m pip install --upgrade pip'
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
- 7z x pillow-test-images.zip -oc:\
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
- curl -fsSL -o nasm-win64.zip https://raw.githubusercontent.com/python-pillow/pillow-depends/main/nasm-2.16.03-win64.zip
- 7z x nasm-win64.zip -oc:\
- choco install ghostscript --version=10.3.1
- path c:\nasm-2.16.03;C:\Program Files\gs\gs10.03.1\bin;%PATH%
- cd c:\pillow\winbuild\
- ps: |
c:\python38\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
c:\pillow\winbuild\build\build_dep_all.cmd
$host.SetShouldExit(0)
- path C:\pillow\winbuild\build\bin;%PATH%
build_script:
- cd c:\pillow
- winbuild\build\build_env.cmd
- '%PYTHON%\%EXECUTABLE% -m pip install -v -C raqm=vendor -C fribidi=vendor .'
- '%PYTHON%\%EXECUTABLE% selftest.py --installed'
test_script:
- cd c:\pillow
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout defusedxml numpy olefile pyroma'
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
#- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest?
after_test:
- curl -Os https://uploader.codecov.io/latest/windows/codecov.exe
- .\codecov.exe --file coverage.xml --name %PYTHON% --flags AppVeyor
matrix:
fast_finish: true
cache:
- '%LOCALAPPDATA%\pip\Cache'
artifacts:
- path: pillow\*.egg
name: egg
- path: pillow\*.whl
name: wheel
before_deploy:
- cd c:\pillow
- '%PYTHON%\%EXECUTABLE% -m pip wheel -v -C raqm=vendor -C fribidi=vendor .'
- ps: Get-ChildItem .\*.whl | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
deploy:
provider: S3
region: us-west-2
access_key_id: AKIAIRAXC62ZNTVQJMOQ
secret_access_key:
secure: Hwb6klTqtBeMgxAjRoDltiiqpuH8xbwD4UooDzBSiCWXjuFj1lyl4kHgHwTCCGqi
bucket: pillow-nightly
folder: win/$(APPVEYOR_BUILD_NUMBER)/
artifact: /.*egg|wheel/
on:
APPVEYOR_REPO_NAME: python-pillow/Pillow
branch: main
deploy: YES
# Uncomment the following lines to get RDP access after the build/test and block for
# up to the timeout limit (~1hr)
#
#on_finish:
#- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))

View File

@ -2,4 +2,8 @@
# gather the coverage data
python3 -m pip install coverage
python3 -m coverage xml
if [[ $MATRIX_DOCKER ]]; then
python3 -m coverage xml --ignore-errors
else
python3 -m coverage xml
fi

View File

@ -3,5 +3,8 @@
set -e
python3 -m coverage erase
if [ $(uname) == "Darwin" ]; then
export CPPFLAGS="-I/usr/local/miniconda/include";
fi
make clean
make install-coverage

View File

@ -2,58 +2,73 @@
aptget_update()
{
if [ -n "$1" ]; then
if [ ! -z $1 ]; then
echo ""
echo "Retrying apt-get update..."
echo ""
fi
output=$(sudo apt-get update 2>&1)
output=`sudo apt-get update 2>&1`
echo "$output"
if [[ $output == *[WE]:\ * ]]; then
return 1
fi
}
aptget_update || aptget_update retry || aptget_update retry
if [[ $(uname) != CYGWIN* ]]; then
aptget_update || aptget_update retry || aptget_update retry
fi
set -e
sudo apt-get -qq install libfreetype6-dev liblcms2-dev libtiff-dev python3-tk\
ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
sway wl-clipboard libopenblas-dev nasm
if [[ $(uname) != CYGWIN* ]]; then
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
sway wl-clipboard libopenblas-dev
fi
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade wheel
# TODO Update condition when cffi supports 3.13
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
python3 -m pip install coverage
python3 -m pip install defusedxml
python3 -m pip install ipython
python3 -m pip install olefile
python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
# optional test dependencies, only install if there's a binary package.
python3 -m pip install --only-binary=:all: numpy || true
python3 -m pip install --only-binary=:all: pyarrow || true
# PyQt6 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
# pyqt6 doesn't yet support free-threading; only install if a wheel is available
python3 -m pip install --only-binary=:all: pyqt6 || true
if [[ $(uname) != CYGWIN* ]]; then
# TODO Update condition when NumPy supports 3.13
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi
# PyQt6 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
python3 -m pip install pyqt6
fi
# Pyroma uses non-isolated build and fails with old setuptools
if [[
$GHA_PYTHON_VERSION == pypy3.9
|| $GHA_PYTHON_VERSION == 3.8
|| $GHA_PYTHON_VERSION == 3.9
]]; then
# To match pyproject.toml
python3 -m pip install "setuptools>=67.8"
fi
# webp
pushd depends && ./install_webp.sh && popd
# libimagequant
pushd depends && ./install_imagequant.sh && popd
# raqm
pushd depends && ./install_raqm.sh && popd
# extra test images
pushd depends && ./install_extra_test_images.sh && popd
else
cd depends && ./install_extra_test_images.sh && cd ..
fi
# webp
pushd depends && ./install_webp.sh && popd
# libimagequant
pushd depends && ./install_imagequant.sh && popd
# raqm
pushd depends && sudo ./install_raqm.sh && popd
# libavif
pushd depends && ./install_libavif.sh && popd
# extra test images
pushd depends && ./install_extra_test_images.sh && popd

View File

@ -1 +1 @@
cibuildwheel==3.4.1
cibuildwheel==2.19.1

View File

@ -1,15 +1 @@
mypy==1.20.2
arro3-compute
arro3-core
IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6
ipython
numpy
packaging
pyarrow-stubs
pybind11
pytest
types-atheris
types-defusedxml
types-olefile
types-setuptools
mypy==1.10.1

View File

@ -1 +0,0 @@
check-jsonschema==0.37.1

View File

@ -1,3 +0,0 @@
python.exe -c "from PIL import Image"
IF ERRORLEVEL 1 EXIT /B
python.exe -bb -m pytest -vv -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests

View File

@ -4,4 +4,4 @@ set -e
python3 -c "from PIL import Image"
python3 -bb -m pytest -vv -x -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests $REVERSE
python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests $REVERSE

View File

@ -1,10 +1,9 @@
# A clang-format style that approximates Python's PEP 7
# Useful for IDE integration
Language: C
BasedOnStyle: Google
AlwaysBreakAfterReturnType: All
AllowShortIfStatementsOnASingleLine: false
AlignAfterOpenBracket: BlockIndent
AlignAfterOpenBracket: AlwaysBreak
BinPackArguments: false
BinPackParameters: false
BreakBeforeBraces: Attach
@ -12,26 +11,7 @@ ColumnLimit: 88
DerivePointerAlignment: false
IndentGotoLabels: false
IndentWidth: 4
PointerAlignment: Right
ReflowComments: true
SortIncludes: false
SpaceBeforeParens: ControlStatements
SpacesInParentheses: false
TabWidth: 4
UseTab: Never
---
Language: Cpp
BasedOnStyle: Google
AlwaysBreakAfterReturnType: All
AllowShortIfStatementsOnASingleLine: false
AlignAfterOpenBracket: BlockIndent
BinPackArguments: false
BinPackParameters: false
BreakBeforeBraces: Attach
ColumnLimit: 88
DerivePointerAlignment: false
IndentGotoLabels: false
IndentWidth: 4
PointerAlignment: Right
ReflowComments: true
SortIncludes: false

View File

@ -18,5 +18,7 @@ exclude_also =
[run]
omit =
checks/*.py
Tests/32bit_segfault_check.py
Tests/bench_cffi_access.py
Tests/check_*.py
Tests/createfontdatachunk.py

View File

@ -9,7 +9,7 @@ Please send a pull request to the `main` branch. Please include [documentation](
- Fork the Pillow repository.
- Create a branch from `main`.
- Develop bug fixes, features, tests, etc.
- Run the test suite. You can enable GitHub Actions (https://github.com/MY-USERNAME/Pillow/actions) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
- Run the test suite. You can enable GitHub Actions (https://github.com/MY-USERNAME/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
- Create a pull request to pull the changes from your branch to the Pillow `main`.
### Guidelines
@ -17,8 +17,9 @@ Please send a pull request to the `main` branch. Please include [documentation](
- Separate code commits from reformatting commits.
- Provide tests for any newly added code.
- Follow PEP 8.
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running extra tests.
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor.
- Include [release notes](https://github.com/python-pillow/Pillow/tree/main/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests.
- Do not add to the [changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) for proposed changes, as that is updated after changes are merged.
## Reporting Issues

3
.github/FUNDING.yml vendored
View File

@ -1,2 +1 @@
github: python-pillow
tidelift: pypi/pillow
tidelift: "pypi/pillow"

View File

@ -1,424 +0,0 @@
# Incident Response Plan — Pillow
This document describes how the Pillow maintainers detect, triage, fix, communicate, and
learn from security incidents. It supplements the existing [Security Policy](SECURITY.md)
and [Release Checklist](../RELEASING.md).
---
## 1. Preparation
Maintaining readiness before an incident occurs reduces response time and errors under pressure.
### 1.1 Version Support Matrix
Security fixes are applied to the **latest stable release only**. Users on older versions
are expected to upgrade. Reporters should assume only the latest release will receive a patch.
| Branch | Status |
|---|---|
| `main` / latest stable | ✅ Security fixes applied |
| All older releases | ❌ No security support — please upgrade |
### 1.2 Team Readiness
The four members of the Pillow core team are in regular contact and share collective
responsibility for incident response. Any core team member may act as Incident Lead.
Contact details are known to all team members.
### 1.3 Readiness Review
At each quarterly release, maintainers should re-read this document and update any stale content.
---
## 2. Scope
This plan covers:
| Incident type | Examples |
|---|---|
| Vulnerability in Pillow's own Python or C code | Buffer overflow in an image decoder, integer overflow in `ImagingNew` |
| Vulnerability in a bundled or wheel-shipped C library | libjpeg, libwebp, libtiff, libpng, openjpeg, libavif |
| Supply-chain compromise | Malicious commit, stolen maintainer credentials, tampered PyPI wheel |
| CI/CD or infrastructure compromise | GitHub Actions secret leak, Codecov breach, PyPI token exposure |
| Critical non-security regression | Data-loss bug shipped in a release, crash on all supported platforms |
---
## 3. Definitions
| Term | Meaning |
|---|---|
| **Incident** | Any event that compromises or threatens the confidentiality, integrity, or availability of Pillow's code, release artifacts, or infrastructure. |
| **Vulnerability** | A security flaw in Pillow or a bundled library that can be exploited by a crafted image or API call. |
| **Incident Lead** | The maintainer who owns coordination of the response from triage to closure. |
| **Embargo** | A period during which fix details are kept private to allow coordinated patching before public disclosure. |
| **Yank** | A PyPI action that keeps a release downloadable by pinned users but removes it from default `pip install` resolution. |
| **CVE** | Common Vulnerabilities and Exposures — a public identifier assigned to a specific vulnerability. |
| **CNA** | CVE Numbering Authority — GitHub is a CNA and can assign CVEs directly through the advisory workflow. |
---
## 4. Roles
| Role | Responsibility |
|---|---|
| **Incident Lead** | First maintainer to triage the report. Owns the incident until resolution. |
| **Patch Owner** | Writes and tests the fix (may be the same person as Incident Lead). |
| **Release Manager** | Cuts the point release following [RELEASING.md](../RELEASING.md). |
| **Communications Owner** | Drafts the GitHub Security Advisory, announces on Mastodon, notifies distros. |
| **Tidelift Contact** | For reports that arrive via Tidelift, coordinate through the Tidelift security portal. |
One person may fill multiple roles.
---
## 5. Severity Classification
Use the [CVSS 4.0](https://www.first.org/cvss/v4.0/specification-document) base score as
a guide, mapped to the following levels:
| Severity | CVSS | Definition | Target Response SLA |
|---|---|---|---|
| **Critical** | 9.0 10.0 | Remote code execution, arbitrary write, or complete integrity/confidentiality loss achievable by opening a crafted image | Best effort; embargoed release where possible |
| **High** | 7.0 8.9 | Heap/stack buffer overflow, use-after-free, or significant information disclosure | Best effort |
| **Medium** | 4.0 6.9 | Denial of service via crafted image, out-of-bounds read, limited info disclosure | Next scheduled quarterly release, or earlier point release if needed |
| **Low** | 0.1 3.9 | Minor information disclosure, unlikely to be exploitable in practice | Next quarterly release |
Supply-chain and CI/CD incidents are always treated as **Critical** regardless of CVSS.
> **Note:** These are good-faith targets for a small volunteer maintainer team, not contractual SLAs. Public safety and transparency will always be prioritised, even when timing varies.
---
## 6. Detection Sources
Vulnerabilities and incidents may be reported or discovered through:
1. **GitHub private security advisory** — preferred channel; see [SECURITY.md](SECURITY.md)
2. **Tidelift security contact**<https://tidelift.com/docs/security>
3. **External researcher / coordinated disclosure** — e.g. Google Project Zero, vendor PSIRT
4. **Automated scanning** — Dependabot, GitHub code-scanning (CodeQL), CI fuzzing
5. **Distro security teams** — Debian, Red Hat, Ubuntu, Alpine may report upstream
6. **User bug report** — public issue (reassess if it has security implications and convert to a private advisory if needed)
---
## 7. Response Process
### 7.1 Triage (all severities)
1. **Acknowledge receipt** to the reporter within **72 hours** using the template in
[Appendix A](#appendix-a-communication-templates). Ask the reporter:
- How they would like to be credited (name, handle, or anonymous)
- Whether they intend to publish their own advisory, and if so, their preferred timeline
- Thank them explicitly — reporters do the project a favour by disclosing privately.
2. Reproduce the issue. If the report is invalid, close it and notify the reporter.
3. Assign a severity level ([Section 5: Severity Classification](#5-severity-classification)).
4. If the GitHub Security Advisory was not created by the reporter, create one now and keep
it **private** until the fix is released. Add the reporter as a collaborator if they wish
to be involved.
5. **Request a CVE** through the GitHub Security Advisory workflow (GitHub is a CVE
Numbering Authority — no separate MITRE form required). The CVE is reserved privately
and published automatically when the advisory goes public.
6. **Escalation** — Escalate beyond the core maintainer team if any of the following apply:
- The fix requires changes to CPython or a dependency outside Pillow's control → contact the relevant upstream immediately
- A legal concern arises (e.g. GDPR-reportable data exposure) → contact the project's legal/fiscal sponsor
- The Incident Lead is unreachable for > 24 hours on a Critical issue → any other maintainer may assume the role
### 7.2 Fix Development
1. Develop the fix in a **private fork** or directly in the private security advisory
workspace on GitHub. Do **not** push to a public branch before the embargo lifts.
2. Write a regression test that fails before the fix and passes after.
3. Review the patch with at least one other maintainer.
### 7.3 Standard (Non-Embargoed) Release
For Medium and Low severity, or when no distro pre-notification is needed:
1. Merge the fix to `main`, then cherry-pick to all affected release branches
(see [RELEASING.md — Point release](../RELEASING.md)).
2. Amend commit messages to include the CVE identifier.
3. Follow the [Point release](../RELEASING.md#point-release) process in RELEASING.md to
tag, push, and confirm wheels are live on PyPI.
4. Publish the GitHub Security Advisory (this simultaneously publishes the CVE).
### 7.4 Embargoed Release
For Critical and High severity where distro pre-notification improves user safety:
1. Prepare patches against all affected release branches and test locally.
2. Agree on an **embargo date** with the reporter (typically 714 days out, up to 90 days for
complex issues).
3. Privately send the patch to distros via the
[linux-distros](https://oss-security.openwall.org/wiki/mailing-lists/distros) mailing list
or directly to individual distro security teams.
4. On the embargo date:
- Amend commit messages with the CVE identifier.
- Follow the [Embargoed release](../RELEASING.md#embargoed-release) process in
RELEASING.md to tag, push, and confirm wheels are live on PyPI.
- Publish the GitHub Security Advisory.
### 7.5 Supply-Chain / Infrastructure Compromise
1. **Immediately** revoke any potentially compromised credentials:
- PyPI API tokens
- GitHub personal access tokens and OAuth apps
- Codecov or other CI service tokens
2. Audit recent commits and releases for tampering:
- Verify release tags against known-good SHAs
- Re-inspect any wheel published since the potential compromise window
3. If a PyPI release is suspected to be tampered: yank it immediately via the
[PyPI release management page](https://pypi.org/manage/project/Pillow/releases/)
(login required); see [https://pypi.org/security/](https://pypi.org/security/) for
reporting to the PyPI security team.
4. Issue a public advisory describing the scope and any user action required.
### 7.6 Recovery
After the fix is released and the advisory is public:
1. Verify that the patched wheels are live on PyPI and passing CI across all supported platforms.
2. Confirm any yanked releases are handled correctly .
3. Resume normal development operations on `main`.
4. Monitor the GitHub issue tracker and Mastodon for user reports of residual problems for at least **72 hours** post-release.
5. Close the private GitHub Security Advisory once recovery is confirmed.
---
## 8. Communication
### Internal (during embargo)
- Use the **private GitHub Security Advisory** thread for coordination with the reporter.
- Use private communication channels for all other coordination.
- Do not discuss details in public issues, PRs, or Gitter/IRC channels.
### External (at or after disclosure)
| Audience | Channel | Timing |
|---|---|---|
| General users | [GitHub Security Advisory](https://github.com/python-pillow/Pillow/security/advisories) | At release |
| PyPI ecosystem | CVE published via advisory | At release |
| Downstream distros | Direct email or linux-distros list | Before embargo date (embargoed) |
| Tidelift subscribers | Tidelift security portal | At release (or coordinated) |
| Community | [Mastodon @pillow](https://fosstodon.org/@pillow) | At release |
**Advisory content should include:**
- CVE identifier and CVSS score
- Affected Pillow versions
- Fixed version(s)
- Nature of the vulnerability (without full exploit details if still fresh)
- Credit to the reporter (with their consent)
- Upgrade instructions (`python3 -m pip install --upgrade Pillow`)
---
## 9. Dependency Map
Understanding what Pillow depends on (upstream) and what depends on Pillow (downstream)
is essential for scoping impact and coordinating notifications during an incident.
### 9.1 Upstream Dependencies
#### Bundled C libraries (shipped in official wheels)
These libraries are compiled into Pillow's binary wheels. A CVE in any of them may
require a Pillow point release even if Pillow's own code is unchanged.
| Library | Purpose | Security advisory tracker |
|---|---|---|
| [libjpeg-turbo](https://libjpeg-turbo.org/) | JPEG encode/decode | [GitHub](https://github.com/libjpeg-turbo/libjpeg-turbo/security) |
| [libpng](http://www.libpng.org/pub/png/libpng.html) | PNG encode/decode within FreeType 2, OpenJPEG and WebP | [SourceForge](https://sourceforge.net/p/libpng/bugs/) |
| [libtiff](https://libtiff.gitlab.io/libtiff/) | TIFF encode/decode | [GitLab](https://gitlab.com/libtiff/libtiff/-/work_items) |
| [libwebp](https://chromium.googlesource.com/webm/libwebp) | WebP encode/decode | [Chromium tracker](https://issues.webmproject.org/issues) |
| [libavif](https://github.com/AOMediaCodec/libavif) | AVIF encode/decode | [GitHub](https://github.com/AOMediaCodec/libavif/security) |
| [aom](https://aomedia.googlesource.com/aom/) | AV1 codec (AVIF) | [Chromium tracker](https://aomedia.issues.chromium.org/issues) |
| [dav1d](https://code.videolan.org/videolan/dav1d) | AV1 decode (AVIF) | [VideoLAN Security](https://www.videolan.org/security/) |
| [openjpeg](https://www.openjpeg.org/) | JPEG 2000 encode/decode | [GitHub](https://github.com/uclouvain/openjpeg/security) |
| [freetype2](https://freetype.org/) | Font rendering | [GitLab](https://gitlab.freedesktop.org/freetype/freetype/-/work_items) |
| [lcms2](https://www.littlecms.com/) | ICC color management | [GitHub](https://github.com/mm2/Little-CMS/security) |
| [harfbuzz](https://harfbuzz.github.io/) | Text shaping (via raqm) | [GitHub](https://github.com/harfbuzz/harfbuzz/security) |
| [raqm](https://github.com/HOST-Oman/libraqm) | Complex text layout | [GitHub](https://github.com/HOST-Oman/libraqm) |
| [fribidi](https://github.com/fribidi/fribidi) | Unicode bidi (via raqm) | [GitHub](https://github.com/fribidi/fribidi) |
| [zlib](https://zlib.net/) | Deflate compression | [zlib.net](https://zlib.net/) |
| [liblzma / xz-utils](https://tukaani.org/xz/) | XZ/LZMA compression | [GitHub](https://github.com/tukaani-project/xz/security) |
| [bzip2](https://gitlab.com/bzip2/bzip2) | BZ2 compression | [GitLab](https://gitlab.com/bzip2/bzip2/-/work_items) |
| [zstd](https://github.com/facebook/zstd) | Zstandard compression | [GitHub](https://github.com/facebook/zstd/security) |
| [brotli](https://github.com/google/brotli) | Brotli compression | [GitHub](https://github.com/google/brotli/security) |
| [libyuv](https://chromium.googlesource.com/libyuv/libyuv/) | YUV conversion | [Chromium tracker](https://libyuv.issues.chromium.org/issues) |
#### Python-level dependencies
| Package | Required? | Purpose |
|---|---|---|
| `setuptools` | Build-time only | Package build backend |
| `pybind11` | Build-time only | Compile C files in parallel |
| `olefile` | Optional (`fpx`, `mic` extras) | OLE2 container parsing (FPX, MIC formats) |
| `defusedxml` | Optional (`xmp` extra) | Safe XML parsing for XMP metadata |
See [`pyproject.toml`](../pyproject.toml) for the complete and authoritative list of
optional dependencies.
### 9.2 Responding to an Upstream Vulnerability
When a CVE is published for a bundled C library:
1. Assess whether the vulnerable code path is reachable through Pillow's API.
2. If reachable, treat as a Pillow vulnerability and follow [Section 5: Severity Classification](#5-severity-classification).
3. Update the bundled library version in the wheel build scripts and rebuild wheels.
4. Reference the upstream CVE in Pillow's release notes and GitHub Security Advisory.
5. If not reachable, document the rationale in a public issue so downstream distributors
can make informed decisions about patching their system packages.
### 9.3 Downstream Dependencies
A vulnerability in Pillow can have wide impact. Notify or consider the blast radius of
these downstream consumers when assessing severity and planning communications.
#### Linux distribution packages
| Distribution | Package name | Security contact |
|---|---|---|
| Debian / Ubuntu | `python3-pil` | [Debian Security](https://www.debian.org/security/) / [Ubuntu Security](https://ubuntu.com/security) |
| Fedora / RHEL / CentOS | `python3-pillow` | [Red Hat Security](https://access.redhat.com/security/) |
| Alpine Linux | `py3-pillow` | [Alpine security](https://security.alpinelinux.org/) |
| Arch Linux | `python-pillow` | [Arch security tracker](https://security.archlinux.org/) |
| Homebrew | `pillow` | [Homebrew maintainers](https://github.com/Homebrew/homebrew-core/security) |
| conda-forge | `pillow` | [conda-forge](https://github.com/conda-forge/pillow-feedstock) |
#### Major Python ecosystem consumers
These are high-profile projects known to depend on Pillow; a critical vulnerability may
warrant proactive notification.
| Project | Usage |
|---|---|
| [matplotlib](https://matplotlib.org/) | Image I/O for plots |
| [scikit-image](https://scikit-image.org/) | Image processing |
| [torchvision](https://github.com/pytorch/vision) (PyTorch) | Dataset loading, transforms |
| [Keras / TensorFlow](https://keras.io/) | Image preprocessing utilities |
| [Django](https://www.djangoproject.com/) | `ImageField` validation and thumbnail generation |
| [Wagtail](https://wagtail.org/) | CMS image renditions |
| [Plone](https://plone.org/) | CMS image handling |
| [Jupyter / IPython](https://jupyter.org/) | Inline image display |
| [ReportLab](https://www.reportlab.com/) | PDF image embedding |
| [Tidelift subscribers](https://tidelift.com/) | Enterprise consumers (coordinated via Tidelift) |
#### Pillow ecosystem plugins
Third-party plugins extend Pillow and are distributed separately on PyPI. Their
maintainers should be notified for Critical/High issues that affect the plugin API
or the formats they decode. See the
[full plugin list](https://pillow.readthedocs.io/en/stable/handbook/third-party-plugins.html#plugin-list).
---
## 11. Plan Maintenance
This document is a living record. It should be kept current so it is useful when an incident actually occurs. Revisit it during the [Section 1.3 readiness review](#13-readiness-review) at each quarterly release.
---
## 12. References
- [Security Policy](SECURITY.md)
- [Release Checklist](../RELEASING.md)
- [Contributing Guide](CONTRIBUTING.md)
- [Tidelift Security Contact](https://tidelift.com/docs/security)
- [GitHub: Privately reporting a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability)
- [GitHub as a CVE Numbering Authority (CNA)](https://docs.github.com/en/code-security/security-advisories/working-with-repository-security-advisories/about-repository-security-advisories)
- [FIRST CVSS 4.0 Calculator](https://www.first.org/cvss/calculator/4.0)
- [linux-distros mailing list](https://oss-security.openwall.org/wiki/mailing-lists/distros)
- [OpenSSF CVD Guide](https://github.com/ossf/oss-vulnerability-guide) *(basis for this plan)*
---
## Appendix A: Communication Templates
### A.1 Reporter Acknowledgment
> Subject: Re: [Security] \<brief issue description\>
>
> Hi \<name\>,
>
> Thank you for taking the time to report this issue. We appreciate it.
>
> We have received your report and will review it as soon as possible. We will
> keep you updated on our progress.
>
> Questions:
>
> - How would you like to be credited in the advisory? (name, handle,
> organisation, or anonymous)
> - Do you plan to publish your own write-up or advisory? If so, do you have a
> disclosure date in mind?
>
> We apply coordinated disclosure principles to all vulnerability reports. If
> you have any questions or concerns at any point, please reply to this thread.
>
> Thank you again,
> The Pillow team
### A.2 Embargoed Distro Notification
> Subject: [EMBARGOED] Pillow security issue — \<CVE-XXXX-XXXXX\> — disclosure \<DATE\>
>
> This is an embargoed notification of a vulnerability in Pillow. Please keep this
> information confidential until the disclosure date listed below.
>
> **CVE:** \<CVE-XXXX-XXXXX\>
>
> **Affected versions:** \<e.g. Pillow < 11.x.x\>
>
> **Fixed version:** \<version\>
>
> **Severity:** \<Critical / High / Medium / Low\> (CVSS \<score\>: \<vector\>)
>
> **Reporter:** \<name / affiliation, or "reported privately"\>
>
> **Public disclosure date:** \<DATE TIME UTC\>
>
> **Summary:**
> \<One paragraph describing the vulnerability class and impact without a full exploit.\>
>
> **Proof of concept:**
> \<Minimal reproducer or attached patch.\>
>
> **Remediation:**
> Upgrade to Pillow \<fixed version\>. No known workaround.
>
> Please do not share this information, issue public patches, or make user communications
> before the disclosure date. We will notify this list immediately if the date changes.
>
> — The Pillow maintainers
### A.3 Public Disclosure Advisory
*(Published as a GitHub Security Advisory; the CVE and date are included automatically.)*
> **Summary:** \<One-paragraph technical summary.\>
>
> **CVE:** \<CVE-XXXX-XXXXX\>
>
> **Affected versions:** Pillow \< \<fixed version\>
>
> **Fixed version:** \<version\>
>
> **Severity:** \<rating\> (CVSS \<score\>)
>
> **Reporter:** \<credited name / "reported privately"\>
>
> **Details:**
> \<Fuller technical description. Include attack scenario where helpful.\>
>
> **Remediation:**
> ```
> python3 -m pip install --upgrade Pillow
> ```
>
> **Timeline:**
> - Reported: \<date\>
> - Fixed: \<date\>
> - Disclosed: \<date\>

View File

@ -1,46 +0,0 @@
---
name: "Maintainers only: Release"
about: For maintainers to schedule a quarterly release
labels: Release
---
## Main release
Released quarterly on January 2nd, April 1st, July 1st and October 15th.
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
* [ ] Develop and prepare release in `main` branch.
* [ ] Add release notes e.g. https://github.com/python-pillow/Pillow/pull/8885
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in `main` branch.
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
* [ ] Create branch and tag for release e.g.:
```bash
git branch [[MAJOR.MINOR]].x
git tag [[MAJOR.MINOR]].0
git push --tags
```
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) has passed, including the "Upload release to PyPI" job. This will have been triggered by the new tag.
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases).
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
```bash
git push --all
```
## Publicize release
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
## Documentation
* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
## Docker images
* [ ] Update Pillow in the Docker Images repository
```bash
git clone https://github.com/python-pillow/docker-images
cd docker-images
./update-pillow-tag.sh [[release tag]]
```

20
.github/SECURITY.md vendored
View File

@ -1,21 +1,5 @@
# Security policy
## Reporting a vulnerability
To report sensitive vulnerability information, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
To report sensitive vulnerability information, report it [privately on GitHub](https://github.com/python-pillow/Pillow/security/advisories/new).
If you cannot use GitHub, use the [Tidelift security contact](https://tidelift.com/docs/security). Tidelift will coordinate the fix and disclosure.
**DO NOT report sensitive vulnerability information in public.**
## Threat model
Pillow's primary attack surface is parsing untrusted image data. A full STRIDE threat model covering spoofing, tampering, repudiation, information disclosure, denial of service, and elevation of privilege is maintained in the [Security handbook page](https://pillow.readthedocs.io/en/latest/handbook/security.html).
Key risks to be aware of when using Pillow to process untrusted images:
- **Decompression bombs** — do not set `Image.MAX_IMAGE_PIXELS = None` in production.
- **EPS files invoke Ghostscript** — block EPS input at the application layer unless strictly required.
- **`ImageMath.unsafe_eval()`** — never pass user-controlled strings to this function; use `lambda_eval` instead.
- **C extension memory safety** — keep Pillow and its bundled C libraries (libjpeg, libpng, libtiff, libwebp, etc.) up to date.
- **Sandboxing** — for high-risk deployments, run image processing in a sandboxed subprocess.
If your organisation/employer is a distributor of Pillow and would like advance notification of security-related bugs, please let us know your preferred contact method.

View File

@ -1,271 +0,0 @@
"""Compare sizes of newly-built dists against the latest release on PyPI.
Fetches file sizes for the latest Pillow release from the PyPI JSON API
(no download required) and compares them to a directory of freshly-built
wheels and sdist. Outputs a table to stdout (and to
`$GITHUB_STEP_SUMMARY` if set).
Usage:
`uv run .github/compare-dist-sizes.py <dist-dir>`
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "humanize",
# "prettytable",
# "termcolor",
# ]
# ///
from __future__ import annotations
import argparse
import json
import os
import re
import sys
import urllib.request
from pathlib import Path
import humanize
from prettytable import PrettyTable, TableStyle
from termcolor import colored
PYPI_JSON_URL = "https://pypi.org/pypi/pillow/json"
# Wheel filename: {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl
# sdist filename: {distribution}-{version}.tar.gz
WHEEL_RE = re.compile(
r"^[^-]+-[^-]+(?:-(?P<build>\d[^-]*))?"
r"-(?P<python>[^-]+)-(?P<abi>[^-]+)-(?P<platform>[^-]+)\.whl$",
re.IGNORECASE,
)
SDIST_RE = re.compile(
r"^(?P<dist>[^-]+)-(?P<version>.+)\.tar\.gz$",
re.IGNORECASE,
)
def key_for(filename: str) -> str:
"""Return a version-independent identifier for a dist file."""
if m := WHEEL_RE.match(filename):
build = f"{m['build']}-" if m["build"] else ""
return f"wheel:{build}{m['python']}-{m['abi']}-{m['platform']}"
if SDIST_RE.match(filename):
return "sdist"
msg = f"Unexpected dist name: {filename}"
raise ValueError(msg)
def display_for(filename: str) -> str:
"""Strip the `pillow-{version}-` prefix for compact table display."""
if m := WHEEL_RE.match(filename):
build = f"{m['build']}-" if m["build"] else ""
return f"{build}{m['python']}-{m['abi']}-{m['platform']}.whl"
if SDIST_RE.match(filename):
return "sdist (.tar.gz)"
return filename
def fetch_pypi_sizes() -> tuple[str, dict[str, tuple[str, int]]]:
"""Return (version, {key: (filename, size)}) for the latest PyPI release."""
with urllib.request.urlopen(PYPI_JSON_URL) as response:
data = json.load(response)
version = data["info"]["version"]
sizes: dict[str, tuple[str, int]] = {}
for entry in data.get("urls", []):
filename = entry["filename"]
key = key_for(filename)
sizes[key] = (filename, entry["size"])
return version, sizes
def collect_local_sizes(dist_dir: Path) -> dict[str, tuple[str, int]]:
sizes: dict[str, tuple[str, int]] = {}
for path in sorted(dist_dir.iterdir()):
if not path.is_file():
continue
key = key_for(path.name)
sizes[key] = (path.name, path.stat().st_size)
return sizes
def human(n: int | None) -> str:
if n is None:
return "n/a"
return humanize.naturalsize(n)
def pct_change(before: int | None, after: int | None) -> str:
if before is None or after is None:
return "n/a"
delta = 0 if before == 0 else (after - before) / before * 100
return f"{delta:+.2f}%"
def pct_severity(text: str) -> dict[str, str] | None:
"""Return status indicators based on the change percent."""
if text == "n/a":
return None
pct = float(text.rstrip("%"))
if pct >= 5:
return {"color": "red", "emoji": "🔴"}
if pct > 0:
return {"color": "yellow", "emoji": "🟡"}
else:
return {"color": "green", "emoji": "🟢"}
def render_table(
baseline_label: str,
baseline_sizes: dict[str, tuple[str, int]],
local_sizes: dict[str, tuple[str, int]],
*,
markdown: bool,
) -> str:
table = PrettyTable()
table.set_style(TableStyle.MARKDOWN if markdown else TableStyle.SINGLE_BORDER)
table.field_names = ["File", "Size before", "Size now", "Change"]
table.align = "r"
table.align["File"] = "l"
def style(cells: list[str], role: str) -> list[str]:
severity = pct_severity(cells[3])
if markdown:
if severity:
cells[3] = f"{severity['emoji']} {cells[3]}"
if role == "orphan":
return [f"*{c}*" for c in cells]
if role == "summary":
return [f"**{c}**" for c in cells]
return cells
if role == "orphan":
return [colored(c, "dark_grey") for c in cells]
bold_attrs = ["bold"] if role == "summary" else []
if bold_attrs:
cells[:3] = [colored(c, attrs=bold_attrs) for c in cells[:3]]
if severity:
cells[3] = colored(cells[3], severity["color"], attrs=bold_attrs)
elif bold_attrs:
cells[3] = colored(cells[3], attrs=bold_attrs)
return cells
keys = list(set(baseline_sizes) | set(local_sizes))
# Put sdist first for readability
keys.sort(key=lambda k: (k != "sdist", k))
wheel_before = []
wheel_after = []
total_before = []
total_after = []
for key in keys:
baseline_entry = baseline_sizes.get(key)
local_entry = local_sizes.get(key)
display_name = display_for((local_entry or baseline_entry)[0])
before = baseline_entry[1] if baseline_entry else None
after = local_entry[1] if local_entry else None
if after is None:
# Removed since baseline: ignore in totals
role = "orphan"
else:
# Present locally (in both, or newly added): count in totals
total_after.append(after)
if before is not None:
total_before.append(before)
if key != "sdist":
wheel_after.append(after)
if before is not None:
wheel_before.append(before)
role = "data"
cells = [
display_name,
human(before),
human(after),
pct_change(before, after),
]
table.add_row(style(cells, role))
if not markdown:
table.add_divider()
if wheel_after:
avg_before = sum(wheel_before) // len(wheel_before) if wheel_before else None
table.add_row(
style(
[
f"wheel average ({len(wheel_after)} wheels)",
human(avg_before),
human(sum(wheel_after) // len(wheel_after)),
pct_change(avg_before, sum(wheel_after) // len(wheel_after)),
],
"summary",
)
)
table.add_row(
style(
[
f"wheel total ({len(wheel_after)} wheels)",
human(sum(wheel_before)),
human(sum(wheel_after)),
pct_change(sum(wheel_before), sum(wheel_after)),
],
"summary",
),
divider=not markdown,
)
if total_after:
table.add_row(
style(
[
f"artifacts total ({len(total_after)} artifacts)",
human(sum(total_before)),
human(sum(total_after)),
pct_change(sum(total_before), sum(total_after)),
],
"summary",
)
)
title = f"## Dist size comparison vs {baseline_label}"
if not markdown:
title = colored(title, attrs=["bold"])
return f"{title}\n\n{table.get_string()}\n"
def main() -> int:
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument(
"dist_dir",
type=Path,
help="Directory containing newly-built wheels and sdist",
)
args = parser.parse_args()
if not args.dist_dir.is_dir():
print(f"error: {args.dist_dir} is not a directory", file=sys.stderr)
return 1
baseline_version, baseline_sizes = fetch_pypi_sizes()
baseline_label = f"Pillow {baseline_version} on PyPI"
local_sizes = collect_local_sizes(args.dist_dir)
print(render_table(baseline_label, baseline_sizes, local_sizes, markdown=False))
if summary_path := os.environ.get("GITHUB_STEP_SUMMARY"):
with open(summary_path, "a", encoding="utf-8") as f:
f.write(
render_table(baseline_label, baseline_sizes, local_sizes, markdown=True)
)
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,19 +0,0 @@
{
"brotli": "1.2.0",
"bzip2": "1.0.8",
"freetype": "2.14.3",
"fribidi": "1.0.16",
"harfbuzz": "14.2.0",
"jpegturbo": "3.1.4.1",
"lcms2": "2.19",
"libavif": "1.4.1",
"libimagequant": "4.4.1",
"libpng": "1.6.58",
"libwebp": "1.6.0",
"libxcb": "1.17.0",
"openjpeg": "2.5.4",
"tiff": "4.7.1",
"xz": "5.8.3",
"zlib-ng": "2.3.3",
"zstd": "1.5.7"
}

View File

@ -1,560 +0,0 @@
#!/usr/bin/env python3
"""Generate a CycloneDX 1.7 SBOM for Pillow's C extensions and their
vendored/optional native library dependencies.
Usage:
python3 .github/generate-sbom.py [output-file]
Output defaults to pillow-{version}.cdx.json in the current directory.
"""
from __future__ import annotations
import argparse
import base64
import datetime as dt
import difflib
import hashlib
import json
import urllib.request
import uuid
from pathlib import Path
def get_version() -> str:
version_file = Path(__file__).parent.parent / "src" / "PIL" / "_version.py"
return version_file.read_text(encoding="utf-8").split('"')[1]
def load_dep_versions() -> dict[str, str]:
deps_file = Path(__file__).parent / "dependencies.json"
return json.loads(deps_file.read_text(encoding="utf-8"))
def sha256_file(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def upstream_diff_b64(
upstream_url: str,
upstream_display: bytes,
local_path: Path,
local_display: bytes,
) -> str:
"""
Fetch an upstream file and return a base64-encoded unified diff vs the local copy.
"""
with urllib.request.urlopen(upstream_url) as resp:
upstream_text = resp.read()
local_text = local_path.read_bytes()
diff_lines = difflib.diff_bytes(
difflib.unified_diff,
upstream_text.splitlines(keepends=True),
local_text.splitlines(keepends=True),
fromfile=b"a/" + upstream_display,
tofile=b"b/" + local_display,
)
return base64.b64encode(b"".join(diff_lines)).decode()
def generate(version: str) -> dict:
serial = str(uuid.uuid4())
now = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
purl = f"pkg:pypi/pillow@{version}"
root = Path(__file__).parent.parent
thirdparty = root / "src" / "thirdparty"
versions = load_dep_versions()
metadata_component = {
"bom-ref": purl,
"type": "library",
"name": "Pillow",
"version": version,
"description": "Python Imaging Library (fork)",
"licenses": [{"license": {"id": "MIT-CMU"}}],
"purl": purl,
"externalReferences": [
{"type": "website", "url": "https://python-pillow.github.io"},
{"type": "vcs", "url": "https://github.com/python-pillow/Pillow"},
{"type": "documentation", "url": "https://pillow.readthedocs.io"},
{
"type": "security-contact",
"url": "https://github.com/python-pillow/Pillow/security/policy",
},
],
}
c_extensions = [
("PIL._avif", "AVIF image format extension"),
(
"PIL._imaging",
"Core image processing extension "
"(decode, encode, map, display, outline, path, libImaging)",
),
("PIL._imagingcms", "LittleCMS2 colour management extension"),
("PIL._imagingft", "FreeType font rendering extension"),
("PIL._imagingmath", "Image math operations extension"),
("PIL._imagingmorph", "Image morphology extension"),
("PIL._imagingtk", "Tk/Tcl display extension"),
("PIL._webp", "WebP image format extension"),
]
ext_components = [
{
"bom-ref": f"{purl}#c-ext/{name}",
"type": "library",
"name": name,
"version": version,
"description": desc,
"licenses": [{"license": {"id": "MIT-CMU"}}],
"purl": f"{purl}#c-ext/{name}",
}
for name, desc in c_extensions
]
vendored_components = [
{
"bom-ref": f"{purl}#thirdparty/fribidi-shim",
"type": "library",
"name": "fribidi-shim",
"version": "1.x",
"description": "FriBiDi runtime-loading shim "
"(vendored in src/thirdparty/fribidi-shim/); "
"loads libfribidi dynamically",
"licenses": [{"license": {"id": "LGPL-2.1-or-later"}}],
"hashes": [
{
"alg": "SHA-256",
"content": sha256_file(thirdparty / "fribidi-shim" / "fribidi.c"),
}
],
"pedigree": {
"notes": "Pillow-authored shim; not taken from an upstream project."
},
"externalReferences": [
{"type": "website", "url": "https://github.com/fribidi/fribidi"},
],
},
{
"bom-ref": "pkg:github/python/pythoncapi-compat",
"type": "library",
"name": "pythoncapi_compat",
"description": "Backport header for new CPython C-API functions "
"(vendored in src/thirdparty/pythoncapi_compat.h)",
"licenses": [{"license": {"id": "0BSD"}}],
"hashes": [
{
"alg": "SHA-256",
"content": sha256_file(thirdparty / "pythoncapi_compat.h"),
}
],
"pedigree": {
"notes": "Vendored unmodified from upstream python/pythoncapi-compat."
},
"externalReferences": [
{
"type": "vcs",
"url": "https://github.com/python/pythoncapi-compat",
},
],
},
{
"bom-ref": f"{purl}#thirdparty/raqm",
"type": "library",
"name": "raqm",
"version": "0.10.5",
"description": "Complex text layout library "
"(vendored in src/thirdparty/raqm/)",
"licenses": [{"license": {"id": "MIT"}}],
"hashes": [
{
"alg": "SHA-256",
"content": sha256_file(thirdparty / "raqm" / "raqm.c"),
}
],
"pedigree": {
"ancestors": [
{
"bom-ref": "pkg:github/HOST-Oman/libraqm@0.10.5#upstream",
"type": "library",
"name": "raqm",
"version": "0.10.5",
"purl": "pkg:github/HOST-Oman/libraqm@0.10.5",
"externalReferences": [
{
"type": "distribution",
"url": "https://github.com/HOST-Oman/libraqm/releases/tag/v0.10.5",
}
],
}
],
"patches": [
{
"type": "unofficial",
"diff": {
"text": {
# raqm-version.h.in → raqm-version.h:
# template @RAQM_VERSION_*@ placeholders replaced
# with literal 0.10.5 values; filename changed to
# drop the .in suffix; minor indentation fix.
"content": upstream_diff_b64(
"https://raw.githubusercontent.com/HOST-Oman/libraqm/v0.10.5/src/raqm-version.h.in",
b"src/raqm-version.h.in",
thirdparty / "raqm" / "raqm-version.h",
b"src/raqm-version.h",
),
"encoding": "base64",
}
},
},
{
"type": "unofficial",
"diff": {
"text": {
# raqm.c: wrap the <fribidi.h> include in an
# #ifdef HAVE_FRIBIDI_SYSTEM guard so that when
# building without a system FriBiDi Pillow's own
# fribidi-shim is used instead.
"content": upstream_diff_b64(
"https://raw.githubusercontent.com/HOST-Oman/libraqm/v0.10.5/src/raqm.c",
b"src/raqm.c",
thirdparty / "raqm" / "raqm.c",
b"src/raqm.c",
),
"encoding": "base64",
}
},
},
],
"notes": (
"Vendored from upstream HOST-Oman/libraqm v0.10.5 with two "
"Pillow-specific modifications: (1) raqm-version.h.in was "
"pre-processed into raqm-version.h with version placeholders "
"replaced by literal values; (2) raqm.c wraps the <fribidi.h> "
"include in an #ifdef HAVE_FRIBIDI_SYSTEM guard so Pillow's "
"bundled fribidi-shim is used when a system FriBiDi is absent."
),
},
"externalReferences": [
{
"type": "vcs",
"url": "https://github.com/python-pillow/Pillow/tree/main/src/thirdparty/raqm",
},
],
},
]
native_deps = [
{
"bom-ref": "pkg:generic/freetype2",
"type": "library",
"name": "FreeType",
"version": versions["freetype"],
"scope": "optional",
"description": "Font rendering (optional, used by PIL._imagingft). "
"Required for text/font support.",
"licenses": [{"license": {"id": "FTL"}}],
"externalReferences": [
{"type": "website", "url": "https://freetype.org"},
{
"type": "distribution",
"url": "https://download.savannah.gnu.org/releases/freetype/",
},
],
},
{
"bom-ref": "pkg:generic/fribidi",
"type": "library",
"name": "FriBiDi",
"version": versions["fribidi"],
"scope": "optional",
"description": "Unicode bidi algorithm library (optional, "
"loaded at runtime by fribidi-shim).",
"licenses": [{"license": {"id": "LGPL-2.1-or-later"}}],
"externalReferences": [
{"type": "website", "url": "https://github.com/fribidi/fribidi"},
{
"type": "distribution",
"url": "https://github.com/fribidi/fribidi/releases",
},
],
},
{
"bom-ref": "pkg:generic/harfbuzz",
"type": "library",
"name": "HarfBuzz",
"version": versions["harfbuzz"],
"scope": "optional",
"description": "Text shaping (optional, required by libraqm "
"for complex text layout).",
"licenses": [{"license": {"id": "MIT"}}],
"externalReferences": [
{"type": "website", "url": "https://harfbuzz.github.io"},
{
"type": "distribution",
"url": "https://github.com/harfbuzz/harfbuzz/releases",
},
],
},
{
"bom-ref": "pkg:generic/libavif",
"type": "library",
"name": "libavif",
"version": versions["libavif"],
"scope": "optional",
"description": "AVIF codec (optional, used by PIL._avif).",
"licenses": [{"license": {"id": "BSD-2-Clause"}}],
"externalReferences": [
{"type": "website", "url": "https://github.com/AOMediaCodec/libavif"},
{
"type": "distribution",
"url": "https://github.com/AOMediaCodec/libavif/releases",
},
],
},
{
"bom-ref": "pkg:generic/libimagequant",
"type": "library",
"name": "libimagequant",
"version": versions["libimagequant"],
"scope": "optional",
"description": "Improved colour quantization (optional).",
"licenses": [{"license": {"id": "GPL-3.0-or-later"}}],
"externalReferences": [
{"type": "website", "url": "https://pngquant.org/lib/"},
{
"type": "distribution",
"url": "https://github.com/ImageOptim/libimagequant/tags",
},
],
},
{
"bom-ref": "pkg:generic/libjpeg",
"type": "library",
"name": "libjpeg / libjpeg-turbo",
"version": versions["jpegturbo"],
"description": "JPEG codec (required by default; disable with "
"-C jpeg=disable).",
"licenses": [
{"license": {"id": "IJG"}},
{"license": {"id": "BSD-3-Clause"}},
],
"externalReferences": [
{"type": "website", "url": "https://ijg.org"},
{"type": "website", "url": "https://libjpeg-turbo.org"},
{
"type": "distribution",
"url": "https://github.com/libjpeg-turbo/libjpeg-turbo/releases",
},
],
},
{
"bom-ref": "pkg:generic/libtiff",
"type": "library",
"name": "libtiff",
"version": versions["tiff"],
"scope": "optional",
"description": "TIFF codec (optional).",
"licenses": [{"license": {"id": "libtiff"}}],
"externalReferences": [
{"type": "website", "url": "https://libtiff.gitlab.io/libtiff/"},
{
"type": "distribution",
"url": "https://download.osgeo.org/libtiff/",
},
],
},
{
"bom-ref": "pkg:generic/libwebp",
"type": "library",
"name": "libwebp",
"version": versions["libwebp"],
"scope": "optional",
"description": "WebP codec (optional, used by PIL._webp).",
"licenses": [{"license": {"id": "BSD-3-Clause"}}],
"externalReferences": [
{
"type": "website",
"url": "https://chromium.googlesource.com/webm/libwebp",
},
{
"type": "distribution",
"url": "https://chromium.googlesource.com/webm/libwebp",
},
],
},
{
"bom-ref": "pkg:generic/libxcb",
"type": "library",
"name": "libxcb",
"version": versions["libxcb"],
"scope": "optional",
"description": "X11 screen-grab support (optional, "
"used by PIL._imaging on macOS and Linux).",
"licenses": [{"license": {"id": "X11"}}],
"externalReferences": [
{"type": "website", "url": "https://xcb.freedesktop.org"},
{
"type": "distribution",
"url": "https://xcb.freedesktop.org/dist/",
},
],
},
{
"bom-ref": "pkg:generic/littlecms2",
"type": "library",
"name": "Little CMS 2",
"version": versions["lcms2"],
"scope": "optional",
"description": "Colour management (optional, used by PIL._imagingcms).",
"licenses": [{"license": {"id": "MIT"}}],
"externalReferences": [
{"type": "website", "url": "https://www.littlecms.com"},
{
"type": "distribution",
"url": "https://github.com/mm2/Little-CMS/releases",
},
],
},
{
"bom-ref": "pkg:generic/openjpeg",
"type": "library",
"name": "OpenJPEG",
"version": versions["openjpeg"],
"scope": "optional",
"description": "JPEG 2000 codec (optional).",
"licenses": [{"license": {"id": "BSD-2-Clause"}}],
"externalReferences": [
{"type": "website", "url": "https://www.openjpeg.org"},
{
"type": "distribution",
"url": "https://github.com/uclouvain/openjpeg/releases",
},
],
},
{
"bom-ref": "pkg:pypi/pybind11",
"type": "library",
"name": "pybind11",
"scope": "excluded",
"description": "Parallel C compilation library (build-time dependency).",
"licenses": [{"license": {"id": "BSD-3-Clause"}}],
"externalReferences": [
{"type": "website", "url": "https://pybind11.readthedocs.io"},
{
"type": "distribution",
"url": "https://github.com/pybind/pybind11/releases",
},
],
},
{
"bom-ref": "pkg:generic/zlib",
"type": "library",
"name": "zlib",
"version": versions["zlib-ng"],
"description": "Deflate/PNG compression (required by default; "
"disable with -C zlib=disable).",
"licenses": [{"license": {"id": "Zlib"}}],
"externalReferences": [
{"type": "website", "url": "https://zlib.net"},
{"type": "distribution", "url": "https://zlib.net"},
],
},
]
dependencies = [
{
"ref": purl,
"dependsOn": [e["bom-ref"] for e in ext_components],
},
{
"ref": f"{purl}#c-ext/PIL._avif",
"dependsOn": ["pkg:generic/libavif"],
},
{
"ref": f"{purl}#c-ext/PIL._imaging",
"dependsOn": [
"pkg:generic/libimagequant",
"pkg:generic/libjpeg",
"pkg:generic/libtiff",
"pkg:generic/libxcb",
"pkg:generic/openjpeg",
"pkg:generic/zlib",
],
},
{
"ref": f"{purl}#c-ext/PIL._imagingcms",
"dependsOn": ["pkg:generic/littlecms2"],
},
{
"ref": f"{purl}#c-ext/PIL._imagingft",
"dependsOn": [
"pkg:generic/freetype2",
"pkg:generic/fribidi",
"pkg:generic/harfbuzz",
f"{purl}#thirdparty/fribidi-shim",
f"{purl}#thirdparty/raqm",
],
},
{
"ref": f"{purl}#c-ext/PIL._webp",
"dependsOn": ["pkg:generic/libwebp"],
},
{
"ref": f"{purl}#thirdparty/raqm",
"dependsOn": [
"pkg:generic/harfbuzz",
f"{purl}#thirdparty/fribidi-shim",
],
},
]
return {
"$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": f"urn:uuid:{serial}",
"version": 1,
"metadata": {
"timestamp": now,
"lifecycles": [{"phase": "build"}],
"tools": {
"components": [
{
"type": "application",
"name": "generate-sbom.py",
"group": "pillow",
}
]
},
"component": metadata_component,
},
"components": ext_components + vendored_components + native_deps,
"dependencies": dependencies,
}
def main() -> None:
version = get_version()
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument(
"output",
nargs="?",
type=Path,
default=Path(f"pillow-{version}.cdx.json"),
help="output file",
)
args = parser.parse_args()
sbom = generate(version)
args.output.write_text(json.dumps(sbom, indent=2) + "\n", encoding="utf-8")
print(
f"Wrote {args.output} (Pillow {version}, {len(sbom['components'])} components)"
)
if __name__ == "__main__":
main()

2
.github/mergify.yml vendored
View File

@ -8,6 +8,8 @@ pull_request_rules:
- status-success=Docker Test Successful
- status-success=Windows Test Successful
- status-success=MinGW
- status-success=Cygwin Test Successful
- status-success=continuous-integration/appveyor/pr
actions:
merge:
method: merge

View File

@ -3,19 +3,18 @@ tag-template: "$NEXT_MINOR_VERSION"
change-template: '- $TITLE #$NUMBER [@$AUTHOR]'
categories:
- title: "Removals"
label: "Removal"
- title: "Dependencies"
label: "Dependency"
- title: "Deprecations"
label: "Deprecation"
- title: "Documentation"
label: "Documentation"
- title: "Dependencies"
label: "Dependency"
- title: "Removals"
label: "Removal"
- title: "Testing"
label: "Testing"
- title: "Type hints"
label: "Type hints"
- title: "Other changes"
exclude-labels:
- "changelog: skip"
@ -24,4 +23,6 @@ template: |
https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html
## Changes
$CHANGES

166
.github/renovate.json vendored
View File

@ -1,175 +1,17 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
"config:base"
],
"labels": [
"Dependency"
],
"minimumReleaseAge": "7 days",
"prCreation": "not-pending",
"schedule": [
"* * 3 * *"
],
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"brotli\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "brotli",
"packageNameTemplate": "google/brotli",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"bzip2\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "bzip2",
"packageNameTemplate": "bzip2/bzip2",
"datasourceTemplate": "gitlab-tags",
"extractVersionTemplate": "^bzip2-(?<version>.+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"freetype\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "freetype",
"packageNameTemplate": "freetype/freetype",
"datasourceTemplate": "gitlab-tags",
"registryUrlTemplate": "https://gitlab.freedesktop.org",
"extractVersionTemplate": "^VER-(?<version>[\\d-]+)$",
"versioningTemplate": "regex:^(?<major>\\d+)[.-](?<minor>\\d+)[.-](?<patch>\\d+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"fribidi\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "fribidi",
"packageNameTemplate": "fribidi/fribidi",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"harfbuzz\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "harfbuzz",
"packageNameTemplate": "harfbuzz/harfbuzz",
"datasourceTemplate": "github-releases"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"jpegturbo\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "jpegturbo",
"packageNameTemplate": "libjpeg-turbo/libjpeg-turbo",
"datasourceTemplate": "github-releases"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"lcms2\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "lcms2",
"packageNameTemplate": "mm2/Little-CMS",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^lcms(?<version>.+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"libavif\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "libavif",
"packageNameTemplate": "AOMediaCodec/libavif",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"libimagequant\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "libimagequant",
"packageNameTemplate": "ImageOptim/libimagequant",
"datasourceTemplate": "github-tags"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"libpng\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "libpng",
"packageNameTemplate": "pnggroup/libpng",
"datasourceTemplate": "github-tags",
"extractVersionTemplate": "^v(?<version>.+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"libwebp\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "libwebp",
"packageNameTemplate": "webmproject/libwebp",
"datasourceTemplate": "github-tags",
"extractVersionTemplate": "^v(?<version>.+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"libxcb\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "libxcb",
"packageNameTemplate": "xorg/lib/libxcb",
"datasourceTemplate": "gitlab-tags",
"registryUrlTemplate": "https://gitlab.freedesktop.org",
"extractVersionTemplate": "^libxcb-(?<version>.+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"openjpeg\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "openjpeg",
"packageNameTemplate": "uclouvain/openjpeg",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"tiff\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "tiff",
"packageNameTemplate": "libtiff/libtiff",
"datasourceTemplate": "gitlab-tags",
"extractVersionTemplate": "^v(?<version>.+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"xz\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "xz",
"packageNameTemplate": "tukaani-project/xz",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.+)$"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"zlib-ng\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "zlib-ng",
"packageNameTemplate": "zlib-ng/zlib-ng",
"datasourceTemplate": "github-releases"
},
{
"customType": "regex",
"managerFilePatterns": ["/^\\.github/dependencies\\.json$/"],
"matchStrings": ["\"zstd\":\\s*\"(?<currentValue>\\d+[^\"]*)\""],
"depNameTemplate": "zstd",
"packageNameTemplate": "facebook/zstd",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.+)$"
}
],
"packageRules": [
{
"groupName": "github-actions",
"matchManagers": ["github-actions"],
"separateMajorMinor": false
"separateMajorMinor": "false"
}
]
],
"schedule": ["on the 3rd day of the month"]
}

View File

@ -1,13 +0,0 @@
brew "aom"
brew "dav1d"
brew "freetype"
brew "ghostscript"
brew "jpeg-turbo"
brew "libimagequant"
brew "libraqm"
brew "libtiff"
brew "little-cms2"
brew "openjpeg"
brew "rav1e"
brew "svt-av1"
brew "webp"

View File

@ -4,14 +4,15 @@ on:
push:
branches:
- "**"
paths: &paths
- ".github/dependencies.json"
paths:
- ".github/workflows/cifuzz.yml"
- ".github/workflows/wheels-dependencies.sh"
- "**.c"
- "**.h"
pull_request:
paths: *paths
paths:
- ".github/workflows/cifuzz.yml"
- "**.c"
- "**.h"
workflow_dispatch:
permissions:
@ -21,36 +22,33 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@d87225267726cf7ce1a3e17cf103c5ac943c4f05 # master
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'pillow'
language: python
dry-run: false
- name: Run Fuzzers
id: run
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@d87225267726cf7ce1a3e17cf103c5ac943c4f05 # master
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'pillow'
fuzz-seconds: 600
language: python
dry-run: false
- name: Upload New Crash
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@v4
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
- name: Upload Legacy Crash
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@v4
if: steps.run.outcome == 'success'
with:
name: crash

View File

@ -4,12 +4,15 @@ on:
push:
branches:
- "**"
paths: &paths
paths:
- ".github/workflows/docs.yml"
- "docs/**"
- "src/PIL/**"
pull_request:
paths: *paths
paths:
- ".github/workflows/docs.yml"
- "docs/**"
- "src/PIL/**"
workflow_dispatch:
permissions:
@ -29,12 +32,10 @@ jobs:
name: Docs
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@v5
with:
python-version: "3.x"
cache: pip
@ -45,35 +46,19 @@ jobs:
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Cache libavif
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: cache-libavif
with:
path: ~/cache-libavif
key: ${{ runner.os }}-libavif-${{ hashFiles('depends/install_libavif.sh', 'depends/libavif-svt4.patch') }}
- name: Cache libimagequant
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
uses: actions/cache@v4
id: cache-libimagequant
with:
path: ~/cache-libimagequant
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
- name: Cache libwebp
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: cache-libwebp
with:
path: ~/cache-libwebp
key: ${{ runner.os }}-libwebp-${{ hashFiles('depends/install_webp.sh') }}
- name: Install Linux dependencies
run: |
.ci/install.sh
env:
GHA_PYTHON_VERSION: "3.x"
GHA_LIBAVIF_CACHE_HIT: ${{ steps.cache-libavif.outputs.cache-hit }}
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
GHA_LIBWEBP_CACHE_HIT: ${{ steps.cache-libwebp.outputs.cache-hit }}
- name: Build
run: |

View File

@ -2,31 +2,53 @@ name: Lint
on: [push, pull_request, workflow_dispatch]
permissions: {}
env:
FORCE_COLOR: 1
PREK_COLOR: always
RUFF_OUTPUT_FORMAT: github
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
build:
runs-on: ubuntu-latest
name: Lint
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- name: Lint
run: uvx --with tox-uv tox -e lint
- name: Mypy
run: uvx --with tox-uv tox -e mypy
- uses: actions/checkout@v4
- name: pre-commit cache
uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
restore-keys: |
lint-pre-commit-
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
cache: pip
cache-dependency-path: "setup.py"
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Install dependencies
run: |
python3 -m pip install -U pip
python3 -m pip install -U tox
- name: Lint
run: tox -e lint
env:
PRE_COMMIT_COLOR: always
- name: Mypy
run: tox -e mypy

View File

@ -2,23 +2,35 @@
set -e
brew bundle --file=.github/workflows/Brewfile
brew install \
freetype \
ghostscript \
libimagequant \
libjpeg \
libtiff \
little-cms2 \
openjpeg \
webp
if [[ "$ImageOS" == "macos13" ]]; then
brew install --ignore-dependencies libraqm
else
brew install libraqm
fi
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
# TODO Update condition when cffi supports 3.13
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
python3 -m pip install coverage
python3 -m pip install defusedxml
python3 -m pip install ipython
python3 -m pip install olefile
python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
# optional test dependencies, only install if there's a binary package.
python3 -m pip install --only-binary=:all: numpy || true
python3 -m pip install --only-binary=:all: pyarrow || true
# libavif
pushd depends && ./install_libavif.sh && popd
# TODO Update condition when NumPy supports 3.13
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi
# extra test images
pushd depends && ./install_extra_test_images.sh && popd

View File

@ -14,9 +14,6 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
update_release_draft:
permissions:
@ -26,6 +23,6 @@ jobs:
runs-on: ubuntu-latest
steps:
# Drafts your next release notes as pull requests are merged into "main"
- uses: release-drafter/release-drafter@5de93583980a40bd78603b6dfdcda5b4df377b32 # v7.2.0
- uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -6,26 +6,21 @@ on:
workflow_dispatch:
permissions:
contents: read
issues: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
stale:
if: github.event.repository.fork == false
permissions:
issues: write
if: github.repository_owner == 'python-pillow'
runs-on: ubuntu-latest
steps:
- name: "Check issues"
uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
only-labels: "Awaiting OP Action"

151
.github/workflows/test-cygwin.yml vendored Normal file
View File

@ -0,0 +1,151 @@
name: Test Cygwin
on:
push:
branches:
- "**"
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- "docs/**"
- "wheels/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- "docs/**"
- "wheels/**"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
COVERAGE_CORE: sysmon
jobs:
build:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
python-minor-version: [8, 9]
timeout-minutes: 40
name: Python 3.${{ matrix.python-minor-version }}
steps:
- name: Fix line endings
run: |
git config --global core.autocrlf input
- name: Checkout Pillow
uses: actions/checkout@v4
- name: Install Cygwin
uses: cygwin/cygwin-install-action@v4
with:
packages: >
gcc-g++
ghostscript
git
ImageMagick
jpeg
libfreetype-devel
libimagequant-devel
libjpeg-devel
liblapack-devel
liblcms2-devel
libopenjp2-devel
libraqm-devel
libtiff-devel
libwebp-devel
libxcb-devel
libxcb-xinerama0
make
netpbm
perl
python3${{ matrix.python-minor-version }}-cffi
python3${{ matrix.python-minor-version }}-cython
python3${{ matrix.python-minor-version }}-devel
python3${{ matrix.python-minor-version }}-numpy
python3${{ matrix.python-minor-version }}-sip
python3${{ matrix.python-minor-version }}-tkinter
wget
xorg-server-extra
zlib-devel
- name: Add Lapack to PATH
uses: egor-tensin/cleanup-path@v4
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:
path: 'C:\cygwin\home\runneradmin\.cache\pip'
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }}
restore-keys: |
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
- name: Build system information
run: |
dash.exe -c "python3 .github/workflows/system-info.py"
- name: Install dependencies
run: |
bash.exe .ci/install.sh
- name: Build
shell: bash.exe -eo pipefail -o igncr "{0}"
run: |
.ci/build.sh
- name: Test
run: |
bash.exe xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh
- name: Prepare to upload errors
if: failure()
run: |
dash.exe -c "mkdir -p Tests/errors"
- name: Upload errors
uses: actions/upload-artifact@v4
if: failure()
with:
name: errors
path: Tests/errors
- name: After success
run: |
bash.exe .ci/after_success.sh
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
flags: GHA_Cygwin
name: Cygwin Python 3.${{ matrix.python-minor-version }}
token: ${{ secrets.CODECOV_ORG_TOKEN }}
success:
permissions:
contents: none
needs: build
runs-on: ubuntu-latest
name: Cygwin Test Successful
steps:
- name: Success
run: echo Cygwin Test Successful

View File

@ -4,14 +4,19 @@ on:
push:
branches:
- "**"
paths-ignore: &paths-ignore
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- "docs/**"
- "wheels/**"
pull_request:
paths-ignore: *paths-ignore
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- "docs/**"
- "wheels/**"
workflow_dispatch:
permissions:
@ -21,65 +26,59 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
docker: [
# Run slower jobs first to give them a headstart and reduce waiting time
ubuntu-26.04-resolute-ppc64le,
ubuntu-26.04-resolute-s390x,
ubuntu-22.04-jammy-arm64v8,
ubuntu-24.04-noble-ppc64le,
ubuntu-24.04-noble-s390x,
# Then run the remainder
alpine,
amazon-2-amd64,
amazon-2023-amd64,
arch,
centos-stream-9-amd64,
centos-stream-10-amd64,
debian-13-trixie-x86,
debian-13-trixie-amd64,
fedora-43-amd64,
fedora-44-amd64,
debian-11-bullseye-amd64,
debian-12-bookworm-x86,
debian-12-bookworm-amd64,
fedora-39-amd64,
fedora-40-amd64,
gentoo,
ubuntu-20.04-focal-amd64,
ubuntu-22.04-jammy-amd64,
ubuntu-24.04-noble-amd64,
ubuntu-26.04-resolute-amd64,
]
dockerTag: [main]
include:
- docker: "ubuntu-26.04-resolute-ppc64le"
- docker: "ubuntu-22.04-jammy-arm64v8"
qemu-arch: "aarch64"
- docker: "ubuntu-24.04-noble-ppc64le"
qemu-arch: "ppc64le"
- docker: "ubuntu-26.04-resolute-s390x"
- docker: "ubuntu-24.04-noble-s390x"
qemu-arch: "s390x"
- docker: "ubuntu-26.04-resolute-arm64v8"
os: "ubuntu-24.04-arm"
dockerTag: main
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/checkout@v4
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Set up QEMU
if: "matrix.qemu-arch"
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
with:
platforms: ${{ matrix.qemu-arch }}
run: |
docker run --rm --privileged aptman/qus -s -- -p ${{ matrix.qemu-arch }}
- name: Docker pull
run: |
docker pull ${{ matrix.qemu-arch && format('--platform=linux/{0}', matrix.qemu-arch)}} pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
- name: Docker build
run: |
@ -90,21 +89,23 @@ jobs:
- name: After success
run: |
PATH="$PATH:~/.local/bin"
docker start pillow_container
sudo docker cp pillow_container:/Pillow /Pillow
sudo chown -R runner /Pillow
pil_path=`docker exec pillow_container /vpy3/bin/python -c 'import os, PIL;print(os.path.realpath(os.path.dirname(PIL.__file__)))'`
docker stop pillow_container
sudo mkdir -p $pil_path
sudo cp src/PIL/*.py $pil_path
cd /Pillow
.ci/after_success.sh
env:
MATRIX_DOCKER: ${{ matrix.docker }}
- name: Upload coverage
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
uses: codecov/codecov-action@v4
with:
flags: GHA_Docker
name: ${{ matrix.docker }}
gcov: true
token: ${{ secrets.CODECOV_ORG_TOKEN }}
success:
permissions:

View File

@ -4,14 +4,19 @@ on:
push:
branches:
- "**"
paths-ignore: &paths-ignore
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- "docs/**"
- "wheels/**"
pull_request:
paths-ignore: *paths-ignore
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- "docs/**"
- "wheels/**"
workflow_dispatch:
permissions:
@ -23,7 +28,6 @@ concurrency:
env:
COVERAGE_CORE: sysmon
FORCE_COLOR: 1
jobs:
build:
@ -41,9 +45,7 @@ jobs:
steps:
- name: Checkout Pillow
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
uses: actions/checkout@v4
- name: Set up shell
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
@ -56,34 +58,36 @@ jobs:
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-ghostscript \
mingw-w64-x86_64-lcms2 \
mingw-w64-x86_64-libavif \
mingw-w64-x86_64-libimagequant \
mingw-w64-x86_64-libjpeg-turbo \
mingw-w64-x86_64-libraqm \
mingw-w64-x86_64-libtiff \
mingw-w64-x86_64-libwebp \
mingw-w64-x86_64-openjpeg2 \
mingw-w64-x86_64-python-numpy \
mingw-w64-x86_64-python-olefile \
mingw-w64-x86_64-python-pip \
mingw-w64-x86_64-python-pytest \
mingw-w64-x86_64-python-pytest-cov \
mingw-w64-x86_64-python-pytest-timeout \
mingw-w64-x86_64-python3-cffi \
mingw-w64-x86_64-python3-numpy \
mingw-w64-x86_64-python3-olefile \
mingw-w64-x86_64-python3-setuptools \
mingw-w64-x86_64-python-pyqt6
python3 -m ensurepip
python3 -m pip install pyroma pytest pytest-cov pytest-timeout
pushd depends && ./install_extra_test_images.sh && popd
- name: Build Pillow
run: CFLAGS="-coverage" python3 -m pip install .
run: SETUPTOOLS_USE_DISTUTILS="stdlib" CFLAGS="-coverage" python3 -m pip install .
- name: Test Pillow
run: |
python3 selftest.py --installed
.ci/test.sh
python3 -c "from PIL import Image"
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
- name: Upload coverage
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
file: ./coverage.xml
flags: GHA_Windows
name: "MSYS2 MinGW"
token: ${{ secrets.CODECOV_ORG_TOKEN }}

View File

@ -1,64 +0,0 @@
name: Test Valgrind Memory Leaks
# like the Docker tests, but running valgrind only on *.c/*.h changes.
# this is very expensive. Only run on the pull request.
on:
# push:
# branches:
# - "**"
# paths:
# - ".github/workflows/test-valgrind-memory.yml"
# - "**.c"
# - "**.h"
# - "depends/docker-test-valgrind-memory.sh"
pull_request:
paths:
- ".github/workflows/test-valgrind-memory.yml"
- "**.c"
- "**.h"
- "depends/docker-test-valgrind-memory.sh"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
docker: [
ubuntu-22.04-jammy-amd64-valgrind,
]
dockerTag: [main]
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Docker pull
run: |
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
- name: Build and Run Valgrind
run: |
# The Pillow user in the docker container is UID 1001
sudo chown -R 1001 $GITHUB_WORKSPACE
docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} /Pillow/depends/docker-test-valgrind-memory.sh
sudo chown -R runner $GITHUB_WORKSPACE

View File

@ -6,12 +6,15 @@ on:
push:
branches:
- "**"
paths: &paths
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"
- "**.h"
pull_request:
paths: *paths
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"
- "**.h"
workflow_dispatch:
permissions:
@ -21,9 +24,6 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
build:
@ -39,9 +39,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/checkout@v4
- name: Build system information
run: python3 .github/workflows/system-info.py

View File

@ -4,14 +4,19 @@ on:
push:
branches:
- "**"
paths-ignore: &paths-ignore
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- "docs/**"
- "wheels/**"
pull_request:
paths-ignore: *paths-ignore
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- "docs/**"
- "wheels/**"
workflow_dispatch:
permissions:
@ -23,70 +28,57 @@ concurrency:
env:
COVERAGE_CORE: sysmon
FORCE_COLOR: 1
jobs:
build:
runs-on: ${{ matrix.os }}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
python-version: ["pypy3.11", "3.11", "3.12", "3.13", "3.14", "3.15"]
architecture: ["x64"]
os: ["windows-latest"]
include:
# Test the oldest Python on 32-bit
- { python-version: "3.10", architecture: "x86", os: "windows-2022" }
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
timeout-minutes: 45
timeout-minutes: 30
name: Python ${{ matrix.python-version }} (${{ matrix.architecture }})
name: Python ${{ matrix.python-version }}
steps:
- name: Checkout Pillow
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
uses: actions/checkout@v4
- name: Checkout cached dependencies
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
persist-credentials: false
repository: python-pillow/pillow-depends
path: winbuild\depends
- name: Checkout extra test images
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
persist-credentials: false
repository: python-pillow/test-images
path: Tests\test-images
# sets env: pythonLocation
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
architecture: ${{ matrix.architecture }}
cache: pip
cache-dependency-path: ".github/workflows/test-windows.yml"
- name: Print build system information
run: python3 .github/workflows/system-info.py
- name: Upgrade pip
run: |
python3 -m pip install --upgrade pip
- name: Install CPython dependencies
if: "!contains(matrix.python-version, 'pypy') && matrix.architecture != 'x86'"
run: |
python3 -m pip install PyQt6
- name: Install PyArrow dependency
run: |
python3 -m pip install --only-binary=:all: pyarrow || true
- name: Install Python dependencies
run: >
python3 -m pip install
coverage>=7.4.2
defusedxml
olefile
pyroma
pytest
pytest-cov
pytest-timeout
- name: Install dependencies
id: install
@ -94,8 +86,8 @@ jobs:
choco install nasm --no-progress
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
choco install ghostscript --version=10.7.0 --no-progress
echo "C:\Program Files\gs\gs10.07.0\bin" >> $env:GITHUB_PATH
choco install ghostscript --version=10.3.1 --no-progress
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
# Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images
@ -108,7 +100,7 @@ jobs:
- name: Cache build
id: build-cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
uses: actions/cache@v4
with:
path: winbuild\build
key:
@ -145,10 +137,6 @@ jobs:
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libpng.cmd"
- name: Build dependencies / libavif
if: steps.build-cache.outputs.cache-hit != 'true' && matrix.architecture == 'x64'
run: "& winbuild\\build\\build_dep_libavif.cmd"
# for FreeType WOFF2 font support
- name: Build dependencies / brotli
if: steps.build-cache.outputs.cache-hit != 'true'
@ -184,14 +172,13 @@ jobs:
# trim ~150MB for each job
- name: Optimize build cache
if: steps.build-cache.outputs.cache-hit != 'true'
run: |
rm -rf winbuild\build\src
shell: bash
run: rmdir /S /Q winbuild\build\src
shell: cmd
- name: Build Pillow
run: |
$FLAGS="-C raqm=vendor -C fribidi=vendor"
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS .[tests]"
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
& $env:pythonLocation\python.exe selftest.py --installed
shell: pwsh
@ -203,7 +190,9 @@ jobs:
- name: Test Pillow
run: |
.ci\test.cmd
path %GITHUB_WORKSPACE%\\winbuild\\build\\bin;%PATH%
python.exe -m pytest -vx -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests
shell: cmd
- name: Prepare to upload errors
if: failure()
@ -212,7 +201,7 @@ jobs:
shell: bash
- name: Upload errors
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@v4
if: failure()
with:
name: errors
@ -224,11 +213,12 @@ jobs:
shell: pwsh
- name: Upload coverage
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
file: ./coverage.xml
flags: GHA_Windows
name: ${{ runner.os }} Python ${{ matrix.python-version }}
token: ${{ secrets.CODECOV_ORG_TOKEN }}
success:
permissions:

View File

@ -4,14 +4,19 @@ on:
push:
branches:
- "**"
paths-ignore: &paths-ignore
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- "docs/**"
- "wheels/**"
pull_request:
paths-ignore: *paths-ignore
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- "docs/**"
- "wheels/**"
workflow_dispatch:
permissions:
@ -24,7 +29,6 @@ concurrency:
env:
COVERAGE_CORE: sysmon
FORCE_COLOR: 1
PIP_DISABLE_PIP_VERSION_CHECK: 1
jobs:
build:
@ -33,38 +37,44 @@ jobs:
fail-fast: false
matrix:
os: [
"macos-latest",
"macos-14",
"ubuntu-latest",
]
python-version: [
"pypy3.11",
"3.15t",
"3.15",
"3.14t",
"3.14",
"pypy3.10",
"pypy3.9",
"3.13",
"3.12",
"3.11",
"3.10",
"3.9",
"3.8",
]
include:
- { python-version: "3.12", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
- { python-version: "3.11", PYTHONOPTIMIZE: 2 }
# Intel
- { os: "macos-26-intel", python-version: "3.10" }
- python-version: "3.11"
PYTHONOPTIMIZE: 1
REVERSE: "--reverse"
- python-version: "3.10"
PYTHONOPTIMIZE: 2
# M1 only available for 3.10+
- os: "macos-13"
python-version: "3.9"
- os: "macos-13"
python-version: "3.8"
exclude:
- { os: "macos-latest", python-version: "3.10" }
- os: "macos-14"
python-version: "3.9"
- os: "macos-14"
python-version: "3.8"
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
@ -76,39 +86,21 @@ jobs:
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Cache libavif
if: startsWith(matrix.os, 'ubuntu')
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: cache-libavif
with:
path: ~/cache-libavif
key: ${{ runner.os }}-libavif-${{ hashFiles('depends/install_libavif.sh', 'depends/libavif-svt4.patch') }}
- name: Cache libimagequant
if: startsWith(matrix.os, 'ubuntu')
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
uses: actions/cache@v4
id: cache-libimagequant
with:
path: ~/cache-libimagequant
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
- name: Cache libwebp
if: startsWith(matrix.os, 'ubuntu')
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: cache-libwebp
with:
path: ~/cache-libwebp
key: ${{ runner.os }}-libwebp-${{ hashFiles('depends/install_webp.sh') }}
- name: Install Linux dependencies
if: startsWith(matrix.os, 'ubuntu')
run: |
.ci/install.sh
env:
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
GHA_LIBAVIF_CACHE_HIT: ${{ steps.cache-libavif.outputs.cache-hit }}
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
GHA_LIBWEBP_CACHE_HIT: ${{ steps.cache-libwebp.outputs.cache-hit }}
- name: Install macOS dependencies
if: startsWith(matrix.os, 'macOS')
@ -118,7 +110,7 @@ jobs:
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
- name: Register gcc problem matcher
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.14'"
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'"
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
- name: Build
@ -147,7 +139,7 @@ jobs:
mkdir -p Tests/errors
- name: Upload errors
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@v4
if: failure()
with:
name: errors
@ -158,10 +150,12 @@ jobs:
.ci/after_success.sh
- name: Upload coverage
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
uses: codecov/codecov-action@v4
with:
flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
gcov: true
token: ${{ secrets.CODECOV_ORG_TOKEN }}
success:
permissions:

View File

@ -1,412 +1,150 @@
#!/bin/bash
# Safety check - Pillow builds require that CIBW_ARCHS is set, and that it only
# contains a single value (even though cibuildwheel allows multiple values in
# CIBW_ARCHS). This check doesn't work on Linux because of how the CIBW_ARCHS
# variable is exposed.
function check_cibw_archs {
if [[ -z "$CIBW_ARCHS" ]]; then
echo "ERROR: Pillow builds require CIBW_ARCHS be defined."
exit 1
fi
if [[ "$CIBW_ARCHS" == *" "* ]]; then
echo "ERROR: Pillow builds only support a single architecture in CIBW_ARCHS."
exit 1
fi
}
# Setup that needs to be done before multibuild utils are invoked. Process
# potential cross-build platforms before native platforms to ensure that we pick
# up the cross environment.
PROJECTDIR=$(pwd)
if [[ "$CIBW_PLATFORM" == "ios" ]]; then
check_cibw_archs
# On iOS, CIBW_ARCHS is actually a multi-arch - arm64_iphoneos,
# arm64_iphonesimulator or x86_64_iphonesimulator. Split into the CPU
# platform, and the iOS SDK.
PLAT=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\1/")
IOS_SDK=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\2/")
# Build iOS builds in `build/iphoneos` or `build/iphonesimulator`
# (depending on the build target). Install them into `build/deps/iphoneos`
# or `build/deps/iphonesimulator`
WORKDIR=$(pwd)/build/$IOS_SDK
BUILD_PREFIX=$(pwd)/build/deps/$IOS_SDK
# GNU tooling insists on using aarch64 rather than arm64
if [[ $PLAT == "arm64" ]]; then
GNU_ARCH=aarch64
else
GNU_ARCH=x86_64
fi
IOS_SDK_PATH=$(xcrun --sdk $IOS_SDK --show-sdk-path)
CMAKE_SYSTEM_NAME=iOS
IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET
if [[ "$IOS_SDK" == "iphonesimulator" ]]; then
IOS_HOST_TRIPLE=$IOS_HOST_TRIPLE-simulator
fi
# GNU Autotools doesn't recognize the existence of arm64-apple-ios-simulator
# as a valid host. However, the only difference between arm64-apple-ios and
# arm64-apple-ios-simulator is the choice of sysroot, and that is
# coordinated by CC, CFLAGS etc. From the perspective of configure, the two
# platforms are identical, so we can use arm64-apple-ios consistently.
# This (mostly) avoids us needing to patch config.sub in dependency sources.
HOST_CONFIGURE_FLAGS="--disable-shared --enable-static --host=$GNU_ARCH-apple-ios --build=$GNU_ARCH-apple-darwin"
# CMake has native support for iOS. However, most of that support is based
# on using the Xcode builder, which isn't very helpful for most of Pillow's
# dependencies. Therefore, we lean on the OSX configurations, plus CC, CFLAGS
# etc. to ensure the right sysroot is selected.
HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO -DENABLE_SHARED=NO"
# Meson needs to be pointed at a cross-platform configuration file
# This will be generated once CC etc. have been evaluated.
HOST_MESON_FLAGS="--cross-file $WORKDIR/meson-cross.txt -Dprefer_static=true -Ddefault_library=static"
elif [[ "$(uname -s)" == "Darwin" ]]; then
check_cibw_archs
# Build macOS dependencies in `build/darwin`
# Install them into `build/deps/darwin`
PLAT=$CIBW_ARCHS
WORKDIR=$(pwd)/build/darwin
BUILD_PREFIX=$(pwd)/build/deps/darwin
else
# Build prefix will default to /usr/local
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
WORKDIR=$(pwd)/build
MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
MB_ML_VER=${AUDITWHEEL_POLICY:9}
fi
# Define custom utilities
# Test for macOS with [ -n "$IS_MACOS" ]
if [ -z "$IS_MACOS" ]; then
export MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
export MB_ML_VER=${AUDITWHEEL_POLICY:9}
fi
export PLAT=$CIBW_ARCHS
source wheels/multibuild/common_utils.sh
source wheels/multibuild/library_builders.sh
if [[ -z "$IS_MACOS" ]]; then
if [ -z "$IS_MACOS" ]; then
source wheels/multibuild/manylinux_utils.sh
fi
ARCHIVE_SDIR=pillow-depends-main
VERSIONS_FILE="$PROJECTDIR/.github/dependencies.json"
_get_ver() { python3 -c "import json; print(json.load(open('$VERSIONS_FILE'))['$1'])"; }
FREETYPE_VERSION=$(_get_ver freetype)
HARFBUZZ_VERSION=$(_get_ver harfbuzz)
LIBPNG_VERSION=$(_get_ver libpng)
JPEGTURBO_VERSION=$(_get_ver jpegturbo)
OPENJPEG_VERSION=$(_get_ver openjpeg)
XZ_VERSION=$(_get_ver xz)
ZSTD_VERSION=$(_get_ver zstd)
TIFF_VERSION=$(_get_ver tiff)
LCMS2_VERSION=$(_get_ver lcms2)
ZLIB_NG_VERSION=$(_get_ver zlib-ng)
LIBWEBP_VERSION=$(_get_ver libwebp)
BZIP2_VERSION=$(_get_ver bzip2)
LIBXCB_VERSION=$(_get_ver libxcb)
BROTLI_VERSION=$(_get_ver brotli)
LIBAVIF_VERSION=$(_get_ver libavif)
# Package versions for fresh source builds
FREETYPE_VERSION=2.13.2
HARFBUZZ_VERSION=8.5.0
LIBPNG_VERSION=1.6.43
JPEGTURBO_VERSION=3.0.3
OPENJPEG_VERSION=2.5.2
XZ_VERSION=5.4.5
TIFF_VERSION=4.6.0
LCMS2_VERSION=2.16
if [[ -n "$IS_MACOS" ]]; then
GIFLIB_VERSION=5.2.2
else
GIFLIB_VERSION=5.2.1
fi
if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then
ZLIB_VERSION=1.3.1
else
ZLIB_VERSION=1.2.8
fi
LIBWEBP_VERSION=1.4.0
BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0
function build_pkg_config {
if [ -e pkg-config-stamp ]; then return; fi
# This essentially duplicates the Homebrew recipe.
# On iOS, we need a binary that can be executed on the build machine; but we
# can create a host-specific pc-path to store iOS .pc files. To ensure a
# macOS-compatible build, we temporarily clear environment flags that set
# iOS-specific values.
if [[ -n "$IOS_SDK" ]]; then
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET
unset HOST_CONFIGURE_FLAGS
unset IPHONEOS_DEPLOYMENT_TARGET
fi
CFLAGS="$CFLAGS -Wno-int-conversion" CPPFLAGS="" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
--disable-debug --disable-host-tool --with-internal-glib \
--with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \
--with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include
if [[ -n "$IOS_SDK" ]]; then
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
IPHONEOS_DEPLOYMENT_TARGET=$ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET
fi;
export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
touch pkg-config-stamp
}
function build_zlib_ng {
if [ -e zlib-stamp ]; then return; fi
# zlib-ng uses a "configure" script, but it's not a GNU autotools script, so
# it doesn't honor the usual flags. Temporarily disable any
# cross-compilation flags.
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
unset HOST_CONFIGURE_FLAGS
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --installnamedir=$BUILD_PREFIX/lib --zlib-compat
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
touch zlib-stamp
}
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "x86_64" ]]; then
function build_openjpeg {
local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz openjpeg-${OPENJPEG_VERSION}.tar.gz)
(cd $out_dir \
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
&& make install)
touch openjpeg-stamp
}
fi
function build_brotli {
if [ -e brotli-stamp ]; then return; fi
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
local cmake=$(get_modern_cmake)
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-1.1.0.tar.gz)
(cd $out_dir \
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib -DCMAKE_MACOSX_BUNDLE=OFF $HOST_CMAKE_FLAGS . \
&& make -j4 install)
touch brotli-stamp
}
function build_harfbuzz {
if [ -e harfbuzz-stamp ]; then return; fi
python3 -m pip install meson ninja
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
(cd $out_dir \
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled $HOST_MESON_FLAGS)
(cd $out_dir/build \
&& meson install)
touch harfbuzz-stamp
}
function build_libavif {
if [ -e libavif-stamp ]; then return; fi
python3 -m pip install meson ninja
if ([[ "$PLAT" == "x86_64" ]] && [[ -z "$IOS_SDK" ]]) || [ -n "$SANITIZER" ]; then
build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
&& $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
&& make install)
if [[ "$MB_ML_LIBC" == "manylinux" ]]; then
cp /usr/local/lib64/libbrotli* /usr/local/lib
cp /usr/local/lib64/pkgconfig/libbrotli* /usr/local/lib/pkgconfig
fi
local build_shared=ON
local lto=ON
local libavif_cmake_flags
if [[ -n "$IS_MACOS" ]]; then
lto=OFF
libavif_cmake_flags=(
-DCMAKE_C_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_CXX_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,-S,-x,-dead_strip_dylibs" \
)
if [[ -n "$IOS_SDK" ]]; then
build_shared=OFF
fi
else
libavif_cmake_flags=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now")
fi
if [[ -n "$IOS_SDK" ]] && [[ "$PLAT" == "x86_64" ]]; then
libavif_cmake_flags+=(-DAOM_TARGET_CPU=generic)
else
libavif_cmake_flags+=(
-DAVIF_CODEC_AOM_DECODE=OFF \
-DAVIF_CODEC_DAV1D=LOCAL
)
fi
local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz)
# CONFIG_AV1_HIGHBITDEPTH=0 is a flag for libaom (included as a subproject
# of libavif) that disables support for encoding high bit depth images.
(cd $out_dir \
&& cmake \
-DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \
-DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib \
-DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \
-DBUILD_SHARED_LIBS=$build_shared \
-DAVIF_LIBSHARPYUV=LOCAL \
-DAVIF_LIBYUV=LOCAL \
-DAVIF_CODEC_AOM=LOCAL \
-DCONFIG_AV1_HIGHBITDEPTH=0 \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=$lto \
-DCMAKE_C_VISIBILITY_PRESET=hidden \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden \
-DCMAKE_BUILD_TYPE=MinSizeRel \
"${libavif_cmake_flags[@]}" \
$HOST_CMAKE_FLAGS . )
if [[ -n "$IOS_SDK" ]]; then
# libavif's CMake configuration generates a meson cross file... but it
# doesn't work for iOS cross-compilation. Copy in Pillow-generated
# meson-cross config to replace the cmake-generated version.
cp $WORKDIR/meson-cross.txt $out_dir/crossfile-apple.meson
fi
(cd $out_dir && make -j4 install)
touch libavif-stamp
}
function build_zstd {
if [ -e zstd-stamp ]; then return; fi
local out_dir=$(fetch_unpack https://github.com/facebook/zstd/releases/download/v$ZSTD_VERSION/zstd-$ZSTD_VERSION.tar.gz)
(cd $out_dir \
&& make -j4 install)
touch zstd-stamp
}
function build {
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
sudo chown -R runner /usr/local
fi
build_xz
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
yum remove -y zlib-devel
fi
if [[ -n "$IS_MACOS" ]]; then
CFLAGS="$CFLAGS -headerpad_max_install_names" build_zlib_ng
else
build_zlib_ng
fi
build_new_zlib
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
if [[ -n "$IS_MACOS" ]]; then
build_simple xorgproto 2025.1 https://www.x.org/pub/individual/proto
build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib
if [ -n "$IS_MACOS" ]; then
build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
cp /usr/local/share/pkgconfig/xcb-proto.pc /usr/local/lib/pkgconfig
fi
else
sed "s/\${pc_sysrootdir\}//" $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc
fi
build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
build_libjpeg_turbo
if [[ -n "$IS_MACOS" ]]; then
# Custom tiff build to include jpeg; by default, configure won't include
# headers/libs in the custom macOS/iOS prefix. Explicitly disable webp,
# libdeflate and zstd, because on x86_64 macs, it will pick up the
# Homebrew versions of those libraries from /usr/local.
build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \
--with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \
--disable-webp --disable-libdeflate --disable-zstd
else
build_zstd
build_tiff
fi
build_libavif
build_tiff
build_libpng
build_lcms2
build_openjpeg
if [ -f /usr/local/lib64/libopenjp2.so ]; then
cp /usr/local/lib64/libopenjp2.so /usr/local/lib
fi
webp_cflags="-O3 -DNDEBUG"
ORIGINAL_CFLAGS=$CFLAGS
CFLAGS="$CFLAGS -O3 -DNDEBUG"
if [[ -n "$IS_MACOS" ]]; then
webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names"
CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names"
fi
webp_ldflags=""
if [[ -n "$IOS_SDK" ]]; then
webp_ldflags="$webp_ldflags -llzma -lz"
fi
CFLAGS="$CFLAGS $webp_cflags" LDFLAGS="$LDFLAGS $webp_ldflags" build_simple libwebp $LIBWEBP_VERSION \
https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \
--enable-libwebpmux --enable-libwebpdemux
build_libwebp
CFLAGS=$ORIGINAL_CFLAGS
build_brotli
if [[ -n "$IS_MACOS" ]]; then
if [ -n "$IS_MACOS" ]; then
# Custom freetype build
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
else
build_freetype
fi
if [[ -z "$IOS_SDK" ]]; then
# On iOS, there's no vendor-provided raqm, and we can't ship it due to
# licensing, so there's no point building harfbuzz.
build_harfbuzz
if [ -z "$IS_MACOS" ]; then
export FREETYPE_LIBS=-lfreetype
export FREETYPE_CFLAGS=-I/usr/local/include/freetype2/
fi
build_simple harfbuzz $HARFBUZZ_VERSION https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION tar.xz --with-freetype=yes --with-glib=no
if [ -z "$IS_MACOS" ]; then
export FREETYPE_LIBS=""
export FREETYPE_CFLAGS=""
fi
}
function create_meson_cross_config {
cat << EOF > $WORKDIR/meson-cross.txt
[binaries]
pkg-config = '$BUILD_PREFIX/bin/pkg-config'
cmake = '$(which cmake)'
c = '$CC'
cpp = '$CXX'
strip = '$STRIP'
[built-in options]
c_args = '$CFLAGS -I$BUILD_PREFIX/include'
cpp_args = '$CXXFLAGS -I$BUILD_PREFIX/include'
c_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
cpp_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
[host_machine]
system = 'darwin'
subsystem = 'ios'
kernel = 'xnu'
cpu_family = '$(uname -m)'
cpu = '$(uname -m)'
endian = 'little'
EOF
}
# Perform all dependency builds in the build subfolder.
mkdir -p $WORKDIR
pushd $WORKDIR > /dev/null
# Any stuff that you need to do before you start building the wheels
# Runs in the root directory of this repository.
if [[ ! -d $WORKDIR/pillow-depends-main ]]; then
if [[ ! -f $PROJECTDIR/pillow-depends-main.zip ]]; then
echo "Download pillow dependency sources..."
curl -fSL -o $PROJECTDIR/pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
fi
echo "Unpacking pillow dependency sources..."
untar $PROJECTDIR/pillow-depends-main.zip
fi
curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
untar pillow-depends-main.zip
if [[ -n "$IS_MACOS" ]]; then
# Ensure the basic structure of the build prefix directory exists.
mkdir -p "$BUILD_PREFIX/bin"
mkdir -p "$BUILD_PREFIX/lib"
# libtiff and libxcb cause a conflict with building libtiff and libxcb
# libxau and libxdmcp cause an issue on macOS < 11
# remove cairo to fix building harfbuzz on arm64
# remove lcms2 and libpng to fix building openjpeg on arm64
# remove jpeg-turbo to avoid inclusion on arm64
# remove webp and zstd to avoid inclusion on x86_64
# curl from brew requires zstd, use system curl
brew remove --ignore-dependencies libpng libtiff libxcb libxau libxdmcp curl cairo lcms2 zstd
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
brew remove --ignore-dependencies jpeg-turbo
else
brew remove --ignore-dependencies webp
fi
# Ensure pkg-config is available. This is done *before* setting CC, CFLAGS
# etc. to ensure that the build is *always* a macOS build, even when building
# for iOS.
build_pkg_config
# Ensure cmake is available, and that the default prefix used by CMake is
# the build prefix
python3 -m pip install cmake
export CMAKE_PREFIX_PATH=$BUILD_PREFIX
if [[ -n "$IOS_SDK" ]]; then
export AR="$(xcrun --find --sdk $IOS_SDK ar)"
export CPP="$(xcrun --find --sdk $IOS_SDK clang) -E"
export CC=$(xcrun --find --sdk $IOS_SDK clang)
export CXX=$(xcrun --find --sdk $IOS_SDK clang++)
export LD=$(xcrun --find --sdk $IOS_SDK ld)
export STRIP=$(xcrun --find --sdk $IOS_SDK strip)
CPPFLAGS="$CPPFLAGS --sysroot=$IOS_SDK_PATH"
CFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
CXXFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
# Having IPHONEOS_DEPLOYMENT_TARGET in the environment causes problems
# with some cross-building toolchains, because it introduces implicit
# behavior into clang.
unset IPHONEOS_DEPLOYMENT_TARGET
# Now that we know CC etc., we can create a meson cross-configuration file
create_meson_cross_config
fi
brew install pkg-config
fi
wrap_wheel_builder build
# A safety catch for iOS. iOS can't use dynamic libraries, but clang will prefer
# to link dynamic libraries to static libraries. The only way to reliably
# prevent this is to not have dynamic libraries available in the first place.
# The build process *shouldn't* generate any dylibs... but just in case, purge
# any dylibs that *have* been installed into the build prefix directory.
if [[ -n "$IOS_SDK" ]]; then
find "$BUILD_PREFIX" -name "*.dylib" -exec rm -rf {} \;
fi
# Return to the project root to finish the build
popd > /dev/null
# Append licenses
for filename in wheels/dependency_licenses/*; do
echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE

View File

@ -9,18 +9,14 @@ if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
}
$env:path += ";$pillow\winbuild\build\bin\"
if (Test-Path $venv\Scripts\pypy.exe) {
$python = "pypy.exe"
} else {
$python = "python.exe"
}
& "$venv\Scripts\activate.ps1"
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
cd $pillow
& $venv\Scripts\$python -VV
& python -VV
if (!$?) { exit $LASTEXITCODE }
& $venv\Scripts\$python selftest.py
& python selftest.py
if (!$?) { exit $LASTEXITCODE }
& $venv\Scripts\$python -m pytest -vv -x checks\check_wheel.py
& python -m pytest -vx Tests\check_wheel.py
if (!$?) { exit $LASTEXITCODE }
& $venv\Scripts\$python -m pytest -vv -x Tests
& python -m pytest -vx Tests
if (!$?) { exit $LASTEXITCODE }

View File

@ -1,29 +1,20 @@
#!/bin/bash
set -e
# Ensure fribidi is installed by the system.
if [[ "$OSTYPE" == "darwin"* ]]; then
# If Homebrew is on the path during the build, it may leak into the wheels.
# However, we *do* need Homebrew to provide a copy of fribidi for
# testing purposes so that we can verify the fribidi shim works as expected.
if [[ "$(uname -m)" == "x86_64" ]]; then
HOMEBREW_PREFIX=/usr/local
else
HOMEBREW_PREFIX=/opt/homebrew
brew install fribidi
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
if [ -f /opt/homebrew/lib/libfribidi.dylib ]; then
sudo cp /opt/homebrew/lib/libfribidi.dylib /usr/local/lib
fi
$HOMEBREW_PREFIX/bin/brew install fribidi
# Add the lib folder for fribidi so that the vendored library can be found.
# Don't use $HOMEWBREW_PREFIX/lib directly - use the lib folder where the
# installed copy of fribidi is cellared. This ensures we don't pick up the
# Homebrew version of any other library that we're dependent on (most notably,
# freetype).
export DYLD_LIBRARY_PATH=$(dirname $(realpath $HOMEBREW_PREFIX/lib/libfribidi.dylib))
elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
apk add curl fribidi
else
yum install -y fribidi
fi
if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ] && !([[ "$OSTYPE" == "darwin"* ]] && [[ $(python3 --version) == *"3.13."* ]]); then
python3 -m pip install numpy
fi
if [ ! -d "test-images-main" ]; then
curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
@ -33,5 +24,5 @@ fi
# Runs tests
python3 selftest.py
python3 -m pytest -vv -x checks/check_wheel.py
python3 -m pytest -vv -x
python3 -m pytest Tests/check_wheel.py
python3 -m pytest

View File

@ -1,23 +1,10 @@
name: Wheels
on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# │ │ │ │ │
- cron: "42 1 * * 0,3"
push:
paths: &paths
paths:
- ".ci/requirements-cibw.txt"
- ".ci/requirements-sbom.txt"
- ".github/compare-dist-sizes.py"
- ".github/dependencies.json"
- ".github/generate-sbom.py"
- ".github/workflows/wheels*"
- "pyproject.toml"
- ".github/workflows/wheel*"
- "setup.py"
- "wheels/*"
- "winbuild/build_prepare.py"
@ -25,7 +12,13 @@ on:
tags:
- "*"
pull_request:
paths: *paths
paths:
- ".ci/requirements-cibw.txt"
- ".github/workflows/wheel*"
- "setup.py"
- "wheels/*"
- "winbuild/build_prepare.py"
- "winbuild/fribidi.cmake"
workflow_dispatch:
permissions:
@ -36,80 +29,97 @@ concurrency:
cancel-in-progress: true
env:
EXPECTED_DISTS: 66
FORCE_COLOR: 1
jobs:
build-native-wheels:
if: github.event_name != 'schedule' || github.event.repository.fork == false
build-1-QEMU-emulated-wheels:
name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version:
- pp39
- pp310
- cp38
- cp39
- cp310
- cp311
- cp312
- cp313
spec:
- manylinux2014
- manylinux_2_28
- musllinux
exclude:
- { python-version: pp39, spec: musllinux }
- { python-version: pp310, spec: musllinux }
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-python@v5
with:
python-version: "3.x"
# https://github.com/docker/setup-qemu-action
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Install cibuildwheel
run: |
python3 -m pip install -r .ci/requirements-cibw.txt
- name: Build wheels
run: |
python3 -m cibuildwheel --output-dir wheelhouse
env:
# Build only the currently selected Linux architecture (so we can
# parallelise for speed).
CIBW_ARCHS: "aarch64"
# Likewise, select only one Python version per job to speed this up.
CIBW_BUILD: "${{ matrix.python-version }}-${{ matrix.spec == 'musllinux' && 'musllinux' || 'manylinux' }}*"
CIBW_PRERELEASE_PYTHONS: True
# Extra options for manylinux.
CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.spec }}
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.spec }}
- uses: actions/upload-artifact@v4
with:
name: dist-qemu-${{ matrix.python-version }}-${{ matrix.spec }}
path: ./wheelhouse/*.whl
build-2-native-wheels:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: "macOS 10.10 x86_64"
platform: macos
os: macos-26-intel
- name: "macOS x86_64"
os: macos-13
cibw_arch: x86_64
build: "cp3{10,11}*"
macosx_deployment_target: "10.10"
- name: "macOS 10.13 x86_64"
platform: macos
os: macos-26-intel
cibw_arch: x86_64
build: "cp3{12,13}*"
macosx_deployment_target: "10.13"
- name: "macOS 10.15 x86_64"
platform: macos
os: macos-26-intel
cibw_arch: x86_64
build: "{cp314,pp3}*"
macosx_deployment_target: "10.15"
- name: "macOS arm64"
platform: macos
os: macos-latest
os: macos-14
cibw_arch: arm64
macosx_deployment_target: "11.0"
- name: "manylinux2014 and musllinux x86_64"
os: ubuntu-latest
cibw_arch: x86_64
- name: "manylinux_2_28 x86_64"
platform: linux
os: ubuntu-latest
cibw_arch: x86_64
build: "*manylinux*"
- name: "musllinux x86_64"
platform: linux
os: ubuntu-latest
cibw_arch: x86_64
build: "*musllinux*"
- name: "manylinux_2_28 aarch64"
platform: linux
os: ubuntu-24.04-arm
cibw_arch: aarch64
build: "*manylinux*"
- name: "musllinux aarch64"
platform: linux
os: ubuntu-24.04-arm
cibw_arch: aarch64
build: "*musllinux*"
- name: "iOS arm64 device"
platform: ios
os: macos-latest
cibw_arch: arm64_iphoneos
- name: "iOS arm64 simulator"
platform: ios
os: macos-latest
cibw_arch: arm64_iphonesimulator
- name: "iOS x86_64 simulator"
platform: ios
os: macos-26-intel
cibw_arch: x86_64_iphonesimulator
manylinux: "manylinux_2_28"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@v4
with:
persist-credentials: false
submodules: true
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@v5
with:
python-version: "3.x"
@ -121,44 +131,40 @@ jobs:
run: |
python3 -m cibuildwheel --output-dir wheelhouse
env:
CIBW_PLATFORM: ${{ matrix.platform }}
CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_BUILD: ${{ matrix.build }}
CIBW_ENABLE: cpython-prerelease pypy
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_PRERELEASE_PYTHONS: True
CIBW_SKIP: pp38-*
CIBW_TEST_SKIP: cp38-macosx_arm64
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.name }}
name: dist-${{ matrix.os }}-${{ matrix.cibw_arch }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }}
path: ./wheelhouse/*.whl
windows:
if: github.event_name != 'schedule' || github.event.repository.fork == false
name: Windows ${{ matrix.cibw_arch }}
runs-on: ${{ matrix.os }}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- cibw_arch: x86
os: windows-latest
- cibw_arch: AMD64
os: windows-latest
- cibw_arch: ARM64
os: windows-11-arm
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/checkout@v4
- name: Checkout extra test images
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
persist-credentials: false
repository: python-pillow/test-images
path: Tests\test-images
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@v5
with:
python-version: "3.x"
@ -179,23 +185,30 @@ jobs:
- name: Build wheels
run: |
for f in winbuild/build/license/*; do
name=$(basename "${f%.*}")
# Skip FriBiDi license, it is not included in the wheel.
[[ $name == fribidi* ]] && continue
# Skip imagequant license, it is not included in the wheel.
[[ $name == libimagequant* ]] && continue
echo "" >> LICENSE
echo "===== $name =====" >> LICENSE
echo "" >> LICENSE
cat "$f" >> LICENSE
done
cmd //c "winbuild\\build\\build_env.cmd && $pythonLocation\\python.exe -m cibuildwheel . --output-dir wheelhouse"
setlocal EnableDelayedExpansion
for %%f in (winbuild\build\license\*) do (
set x=%%~nf
rem Skip FriBiDi license, it is not included in the wheel.
set fribidi=!x:~0,7!
if NOT !fribidi!==fribidi (
rem Skip imagequant license, it is not included in the wheel.
set libimagequant=!x:~0,13!
if NOT !libimagequant!==libimagequant (
echo. >> LICENSE
echo ===== %%~nf ===== >> LICENSE
echo. >> LICENSE
type %%f >> LICENSE
)
)
)
call winbuild\\build\\build_env.cmd
%pythonLocation%\python.exe -m cibuildwheel . --output-dir wheelhouse
env:
CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
CIBW_CACHE_PATH: "C:\\cibw"
CIBW_ENABLE: cpython-prerelease pypy
CIBW_PRERELEASE_PYTHONS: True
CIBW_SKIP: pp38-*
CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_COMMAND: 'docker run --rm
-v {project}:C:\pillow
@ -204,151 +217,42 @@ jobs:
-e CI -e GITHUB_ACTIONS
mcr.microsoft.com/windows/servercore:ltsc2022
powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test'
shell: bash
shell: cmd
- name: Upload wheels
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@v4
with:
name: dist-windows-${{ matrix.cibw_arch }}
path: ./wheelhouse/*.whl
- name: Upload fribidi.dll
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@v4
with:
name: fribidi-windows-${{ matrix.cibw_arch }}
path: winbuild\build\bin\fribidi*
sdist:
if: github.event_name != 'schedule' || github.event.repository.fork == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@v5
with:
python-version: "3.x"
cache: pip
cache-dependency-path: "Makefile"
- run: make sdist
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- uses: actions/upload-artifact@v4
with:
name: dist-sdist
path: dist/*.tar.gz
count-dists:
needs: [build-native-wheels, windows, sdist]
runs-on: ubuntu-latest
name: Count dists
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: dist-*
path: dist
merge-multiple: true
- name: "What did we get?"
run: |
ls -alR
echo "Number of dists, should be $EXPECTED_DISTS:"
files=$(ls dist 2>/dev/null | wc -l)
echo $files
[ "$files" -eq $EXPECTED_DISTS ] || exit 1
compare-dist-sizes:
needs: [build-native-wheels, windows, sdist]
runs-on: ubuntu-latest
name: Compare dist sizes vs PyPI
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: dist-*
path: dist
merge-multiple: true
- name: Compare dist sizes vs latest PyPI release
run: uv run .github/compare-dist-sizes.py dist
scientific-python-nightly-wheels-publish:
if: github.event.repository.fork == false && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
needs: count-dists
runs-on: ubuntu-latest
name: Upload wheels to scientific-python-nightly-wheels
environment:
name: release-anaconda
url: https://anaconda.org/channels/scientific-python-nightly-wheels/packages/pillow/overview
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: dist-!(sdist)*
path: dist
merge-multiple: true
- name: Upload wheels to scientific-python-nightly-wheels
uses: scientific-python/upload-nightly-action@e76cfec8a4611fd02808a801b0ff5a7d7c1b2d99 # 0.6.4
with:
artifacts_path: dist
anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}
sbom:
if: github.event_name != 'schedule' || github.event.repository.fork == false
runs-on: ubuntu-latest
name: Generate SBOM
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
- name: Generate CycloneDX SBOM
run: python3 .github/generate-sbom.py
- name: Upload SBOM as workflow artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sbom
path: "pillow-*.cdx.json"
- name: Validate SBOM
run: |
python3 -m pip install -r .ci/requirements-sbom.txt
check-jsonschema --schemafile "https://raw.githubusercontent.com/CycloneDX/specification/1.7/schema/bom-1.7.schema.json" pillow-*.cdx.json
sbom-publish:
if: |
github.event.repository.fork == false
&& github.event_name == 'push'
&& startsWith(github.ref, 'refs/tags')
needs: [count-dists, sbom]
runs-on: ubuntu-latest
name: Publish SBOM to GitHub release
permissions:
contents: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sbom
path: .
- name: Attach SBOM to GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload "$GITHUB_REF_NAME" pillow-*.cdx.json
pypi-publish:
if: github.event.repository.fork == false && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
needs: count-dists
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist]
runs-on: ubuntu-latest
name: Upload release to PyPI
environment:
@ -357,12 +261,10 @@ jobs:
permissions:
id-token: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- uses: actions/download-artifact@v4
with:
pattern: dist-*
path: dist
merge-multiple: true
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
attestations: true
uses: pypa/gh-action-pypi-publish@release/v1

8
.gitignore vendored
View File

@ -19,7 +19,6 @@ lib64/
parts/
sdist/
var/
wheelhouse/
*.egg-info/
.installed.cfg
*.egg
@ -91,12 +90,5 @@ Tests/images/msp
Tests/images/picins
Tests/images/sunraster
# Test and dependency downloads
pillow-depends-main.zip
pillow-test-images.zip
# pyinstaller
*.spec
# Generated SBOM
pillow-*.cdx.json

View File

@ -1,30 +1,30 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.12
rev: v0.4.7
hooks:
- id: ruff-check
- id: ruff
args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 26.3.1
rev: 24.4.2
hooks:
- id: black
- repo: https://github.com/PyCQA/bandit
rev: 1.9.4
rev: 1.7.8
hooks:
- id: bandit
args: [--severity-level=high]
files: ^src/
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.6
rev: v1.5.5
hooks:
- id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v22.1.4
rev: v18.1.5
hooks:
- id: clang-format
types: [c]
@ -36,53 +36,43 @@ repos:
- id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
rev: v4.6.0
hooks:
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: check-merge-conflict
- id: check-json
- id: check-toml
- id: check-yaml
args: [--allow-multiple-documents]
- id: end-of-file-fixer
exclude: ^Tests/images/
- id: file-contents-sorter
files: .github/workflows/Brewfile
- id: trailing-whitespace
exclude: ^\.github/.*TEMPLATE|^Tests/(fonts|images)/
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.37.2
rev: 0.28.4
hooks:
- id: check-github-workflows
- id: check-readthedocs
- id: check-renovate
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.24.1
hooks:
- id: zizmor
- repo: https://github.com/sphinx-contrib/sphinx-lint
rev: v1.0.2
rev: v0.9.1
hooks:
- id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.21.1
rev: 1.8.0
hooks:
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.25
rev: v0.18
hooks:
- id: validate-pyproject
additional_dependencies: [trove-classifiers>=2024.10.12]
- repo: https://github.com/tox-dev/tox-ini-fmt
rev: 1.7.1
rev: 1.3.1
hooks:
- id: tox-ini-fmt

View File

@ -1,8 +1,5 @@
version: 2
sphinx:
configuration: docs/conf.py
formats: [pdf]
build:

View File

@ -2,181 +2,6 @@
Changelog (Pillow)
==================
11.1.0 and newer
----------------
See GitHub Releases:
- https://github.com/python-pillow/Pillow/releases
11.0.0 (2024-10-15)
-------------------
- Update licence to MIT-CMU #8460
[hugovk]
- Conditionally define ImageCms type hint to avoid requiring core #8197
[radarhere]
- Support writing LONG8 offsets in AppendingTiffWriter #8417
[radarhere]
- Use ImageFile.MAXBLOCK when saving TIFF images #8461
[radarhere]
- Do not close provided file handles with libtiff when saving #8458
[radarhere]
- Support ImageFilter.BuiltinFilter for I;16* images #8438
[radarhere]
- Use ImagingCore.ptr instead of ImagingCore.id #8341
[homm, radarhere, hugovk]
- Updated EPS mode when opening images without transparency #8281
[Yay295, radarhere]
- Use transparency when combining P frames from APNGs #8443
[radarhere]
- Support all resampling filters when resizing I;16* images #8422
[radarhere]
- Free memory on early return #8413
[radarhere]
- Cast int before potentially exceeding INT_MAX #8402
[radarhere]
- Check image value before use #8400
[radarhere]
- Improved copying imagequant libraries #8420
[radarhere]
- Use Capsule for WebP saving #8386
[homm, radarhere]
- Fixed writing multiple StripOffsets to TIFF #8317
[Yay295, radarhere]
- Fix dereference before checking for NULL in ImagingTransformAffine #8398
[PavlNekrasov]
- Use transposed size after opening for TIFF images #8390
[radarhere, homm]
- Improve ImageFont error messages #8338
[yngvem, radarhere, hugovk]
- Mention MAX_TEXT_CHUNK limit in PNG error message #8391
[radarhere]
- Cast Dib handle to int #8385
[radarhere]
- Accept float stroke widths #8369
[radarhere]
- Deprecate ICNS (width, height, scale) sizes in favour of load(scale) #8352
[radarhere]
- Improved handling of RGBA palettes when saving GIF images #8366
[radarhere]
- Deprecate isImageType #8364
[radarhere]
- Support converting more modes to LAB by converting to RGBA first #8358
[radarhere]
- Deprecate support for FreeType 2.9.0 #8356
[hugovk, radarhere]
- Removed unused TiffImagePlugin IFD_LEGACY_API #8355
[radarhere]
- Handle duplicate EXIF header #8350
[zakajd, radarhere]
- Return early from BoxBlur if either width or height is zero #8347
[radarhere]
- Check text is either string or bytes #8308
[radarhere]
- Added writing XMP bytes to JPEG #8286
[radarhere]
- Support JPEG2000 RGBA palettes #8256
[radarhere]
- Expand C image to match GIF frame image size #8237
[radarhere]
- Allow saving I;16 images as PPM #8231
[radarhere]
- When IFD is missing, connect get_ifd() dictionary to Exif #8230
[radarhere]
- Skip truncated ICO mask if LOAD_TRUNCATED_IMAGES is enabled #8180
[radarhere]
- Treat unknown JPEG2000 colorspace as unspecified #8343
[radarhere]
- Updated error message when saving WebP with invalid width or height #8322
[radarhere, hugovk]
- Remove warning if NumPy failed to raise an error during conversion #8326
[radarhere]
- If left and right sides meet in ImageDraw.rounded_rectangle(), do not draw rectangle to fill gap #8304
[radarhere]
- Remove WebP support without anim, mux/demux, and with buggy alpha #8213
[homm, radarhere]
- Add missing TIFF CMYK;16B reader #8298
[homm]
- Remove all WITH_* flags from _imaging.c and other flags #8211
[homm]
- Improve ImageDraw2 shape methods #8265
[radarhere]
- Lock around usages of imaging memory arenas #8238
[lysnikolaou]
- Deprecate JpegImageFile huffman_ac and huffman_dc #8274
[radarhere]
- Deprecate ImageMath lambda_eval and unsafe_eval options argument #8242
[radarhere]
- Changed ContainerIO to subclass IO #8240
[radarhere]
- Move away from APIs that use borrowed references under the free-threaded build #8216
[hugovk, lysnikolaou]
- Allow size argument to resize() to be a NumPy array #8201
[radarhere]
- Drop support for Python 3.8 #8183
[hugovk, radarhere]
- Add support for Python 3.13 #8181
[hugovk, radarhere]
- Fix incompatibility with NumPy 1.20 #8187
[neutrinoceros, radarhere]
- Remove PSFile, PyAccess and USE_CFFI_ACCESS #8182
[hugovk, radarhere]
10.4.0 (2024-07-01)
-------------------

View File

@ -5,9 +5,9 @@ The Python Imaging Library (PIL) is
Pillow is the friendly PIL fork. It is
Copyright © 2010 by Jeffrey 'Alex' Clark and contributors
Copyright © 2010-2024 by Jeffrey A. Clark and contributors
Like PIL, Pillow is licensed under the open source MIT-CMU License:
Like PIL, Pillow is licensed under the open source HPND License:
By obtaining, using, and/or copying this software and/or its associated
documentation, you agree that you have read, understood, and will comply

View File

@ -13,8 +13,6 @@ include LICENSE
include Makefile
include tox.ini
graft Tests
graft Tests/images
graft checks
graft src
graft depends
graft winbuild
@ -22,25 +20,15 @@ graft docs
graft _custom_build
# build/src control detritus
exclude .appveyor.yml
exclude .clang-format
exclude .coveragerc
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

@ -17,16 +17,14 @@ coverage:
.PHONY: doc
.PHONY: html
doc html:
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
$(MAKE) -C docs html
.PHONY: htmlview
htmlview:
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
$(MAKE) -C docs htmlview
.PHONY: htmllive
htmllive:
$(MAKE) -C docs htmllive
.PHONY: doccheck
doccheck:
$(MAKE) doc
@ -47,7 +45,6 @@ help:
@echo " docserve run an HTTP server on the docs directory"
@echo " html make HTML docs"
@echo " htmlview open the index page built by the html target in your browser"
@echo " htmllive rebuild and reload HTML files in your browser"
@echo " install make and install"
@echo " install-coverage make and install with C coverage"
@echo " lint run the lint checks"
@ -75,7 +72,7 @@ debug:
.PHONY: release-test
release-test:
python3 checks/check_release_notes.py
python3 Tests/check_release_notes.py
python3 -m pip install -e .[tests]
python3 selftest.py
python3 -m pytest Tests
@ -97,27 +94,13 @@ test:
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
python3 -m pytest -qq
.PHONY: test-p
test-p:
python3 -c "import xdist" > /dev/null 2>&1 || python3 -m pip install pytest-xdist
python3 -m pytest -qq -n auto
.PHONY: valgrind
valgrind:
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
--log-file=/tmp/valgrind-output \
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
.PHONY: valgrind-leak
valgrind-leak:
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp \
--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite \
--log-file=/tmp/valgrind-output \
python3 -m pytest -vv --valgrind --valgrind-log=/tmp/valgrind-output
.PHONY: readme
readme:
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2
@ -134,7 +117,7 @@ lint-fix:
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
python3 -m black .
python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff
python3 -m ruff check --fix .
python3 -m ruff --fix .
.PHONY: mypy
mypy:

View File

@ -6,13 +6,11 @@
## Python Imaging Library (Fork)
Pillow is the friendly PIL fork by [Jeffrey 'Alex' Clark and
Pillow is the friendly PIL fork by [Jeffrey A. Clark and
contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
PIL is the Python Imaging Library by Fredrik Lundh and contributors.
Development is supported by:
- [Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise) (since 2018)
- [Thanks.dev](https://thanks.dev) (since 2023)
- [GitHub Sponsors](https://github.com/sponsors/python-pillow) (since 2026)
As of 2019, Pillow development is
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
<table>
<tr>
@ -38,16 +36,22 @@ Development is supported by:
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml"><img
alt="GitHub Actions build status (Test MinGW)"
src="https://github.com/python-pillow/Pillow/workflows/Test%20MinGW/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-cygwin.yml"><img
alt="GitHub Actions build status (Test Cygwin)"
src="https://github.com/python-pillow/Pillow/workflows/Test%20Cygwin/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml"><img
alt="GitHub Actions build status (Test Docker)"
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
alt="AppVeyor CI build status (Windows)"
src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
alt="GitHub Actions build status (Wheels)"
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
alt="Code coverage"
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
<a href="https://issues.oss-fuzz.com/issues?q=title:pillow"><img
<a href="https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:pillow"><img
alt="Fuzzing Status"
src="https://oss-fuzz-build-logs.storage.googleapis.com/badges/pillow.svg"></a>
</td>
@ -94,7 +98,7 @@ This library provides extensive file format support, an efficient internal repre
The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool.
## More information
## More Information
- [Documentation](https://pillow.readthedocs.io/)
- [Installation](https://pillow.readthedocs.io/en/latest/installation/basic-installation.html)
@ -103,13 +107,9 @@ The core image library is designed for fast access to data stored in a few basic
- [Issues](https://github.com/python-pillow/Pillow/issues)
- [Pull requests](https://github.com/python-pillow/Pillow/pulls)
- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
- [Changelog](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
## Report a vulnerability
## Report a Vulnerability
To report sensitive vulnerability information, report it [privately on GitHub](https://github.com/python-pillow/Pillow/security/advisories/new).
If you cannot use GitHub, use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
DO NOT report sensitive vulnerability information in public.
To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security).

View File

@ -1,26 +1,46 @@
# Release checklist
# Release Checklist
See https://pillow.readthedocs.io/en/stable/releasenotes/versioning.html for
information about how the version numbers line up with releases.
## Main release
## Main Release
Released quarterly on January 2nd, April 1st, July 1st and October 15th.
* [ ] Create a new issue and select the "Maintainers only: Release" template.
## Point release
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
* [ ] Develop and prepare release in `main` branch.
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
* [ ] Update `CHANGES.rst`.
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
* [ ] Create branch and tag for release e.g.:
```bash
git branch 5.2.x
git tag 5.2.0
git push --tags
```
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
has passed, including the "Upload release to PyPI" job. This will have been triggered
by the new tag.
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases).
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
```bash
git push --all
```
## Point Release
Released as needed for security, installation or critical bug fixes.
* [ ] Make necessary changes in `main` branch.
* [ ] Update `CHANGES.rst`.
* [ ] Check out release branch e.g.:
```bash
git checkout -t remotes/origin/5.2.x
```
* [ ] Cherry pick individual commits from `main` branch to release branch e.g. `5.2.x`, then `git push`.
* [ ] If this is a security fix: amend commits to include the CVE identifier in the commit message.
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in release branch e.g. `5.2.x`.
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in release branch e.g. `5.2.x`.
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
* [ ] Run pre-release check via `make release-test`.
* [ ] Create tag for release e.g.:
@ -39,9 +59,8 @@ Released as needed for security, installation or critical bug fixes.
```bash
git push
```
* [ ] If this is a security fix: publish the [GitHub Security Advisory or Advisories](https://github.com/python-pillow/Pillow/security/advisories).
## Embargoed release
## Embargoed Release
Released as needed privately to individual vendors for critical security-related bug fixes.
@ -65,7 +84,7 @@ Released as needed privately to individual vendors for critical security-related
git push origin 2.5.x
```
## Publicize release
## Publicize Release
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
@ -73,7 +92,7 @@ Released as needed privately to individual vendors for critical security-related
* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
## Docker images
## Docker Images
* [ ] Update Pillow in the Docker Images repository
```bash

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python3
from __future__ import annotations
import sys

View File

@ -1,4 +1,4 @@
Pillow tests
Pillow Tests
============
Test scripts are named ``test_xxx.py``. Helper classes and functions can be found in ``helper.py``.

View File

@ -0,0 +1,54 @@
from __future__ import annotations
import time
from PIL import PyAccess
from .helper import hopper
# Not running this test by default. No DOS against CI.
def iterate_get(size, access) -> None:
(w, h) = size
for x in range(w):
for y in range(h):
access[(x, y)]
def iterate_set(size, access) -> None:
(w, h) = size
for x in range(w):
for y in range(h):
access[(x, y)] = (x % 256, y % 256, 0)
def timer(func, label, *args) -> None:
iterations = 5000
starttime = time.time()
for x in range(iterations):
func(*args)
if time.time() - starttime > 10:
break
endtime = time.time()
print(
f"{label}: completed {x + 1} iterations in {endtime - starttime:.4f}s, "
f"{(endtime - starttime) / (x + 1.0):.6f}s per iteration"
)
def test_direct() -> None:
im = hopper()
im.load()
# im = Image.new("RGB", (2000, 2000), (1, 3, 2))
caccess = im.im.pixel_access(False)
access = PyAccess.new(im, False)
assert access is not None
assert caccess[(0, 0)] == access[(0, 0)]
print(f"Size: {im.width}x{im.height}")
timer(iterate_get, "PyAccess - get", im.size, access)
timer(iterate_set, "PyAccess - set", im.size, access)
timer(iterate_get, "C-api - get", im.size, caccess)
timer(iterate_set, "C-api - set", im.size, caccess)

View File

@ -1,19 +1,18 @@
#!/usr/bin/env python3
from __future__ import annotations
import sys
from collections.abc import Callable
from typing import Any
from typing import Any, Callable
import pytest
from PIL import Image
from .helper import is_win32
min_iterations = 100
max_iterations = 10000
pytestmark = pytest.mark.skipif(
sys.platform.startswith("win32"), reason="requires Unix or macOS"
)
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
def _get_mem_usage() -> float:

View File

@ -1,11 +1,12 @@
from __future__ import annotations
import sys
from io import BytesIO
import pytest
from PIL import Image, features
from PIL import Image
from .helper import is_win32, skip_unless_feature
# Limits for testing the leak
mem_limit = 1024 * 1048576
@ -14,10 +15,8 @@ iterations = int((mem_limit / stack_size) * 2)
test_file = "Tests/images/rgb_trns_ycbc.jp2"
pytestmark = [
pytest.mark.skipif(
sys.platform.startswith("win32"), reason="requires Unix or macOS"
),
pytest.mark.skipif(not features.check("jpg_2000"), reason="jpg_2000 not available"),
pytest.mark.skipif(is_win32(), reason="requires Unix or macOS"),
skip_unless_feature("jpg_2000"),
]

View File

@ -9,6 +9,6 @@ from PIL import Image
def test_j2k_overflow(tmp_path: Path) -> None:
im = Image.new("RGBA", (1024, 131584))
target = tmp_path / "temp.jpc"
target = str(tmp_path / "temp.jpc")
with pytest.raises(OSError):
im.save(target)

View File

@ -1,11 +1,10 @@
from __future__ import annotations
import sys
from io import BytesIO
import pytest
from PIL import Image
from .helper import hopper, is_win32
iterations = 5000
@ -14,14 +13,12 @@ iterations = 5000
When run on a system without the jpeg leak fixes,
the valgrind runs look like this.
valgrind --tool=massif python test-installed.py -s -v checks/check_jpeg_leaks.py
valgrind --tool=massif python test-installed.py -s -v Tests/check_jpeg_leaks.py
"""
pytestmark = pytest.mark.skipif(
sys.platform.startswith("win32"), reason="requires Unix or macOS"
)
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
"""
pre patch:
@ -115,10 +112,10 @@ standard_chrominance_qtable = (
),
)
def test_qtables_leak(qtables: tuple[tuple[int, ...]] | list[tuple[int, ...]]) -> None:
with Image.open("Tests/images/hopper.ppm") as im:
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables)
im = hopper("RGB")
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables)
def test_exif_leak() -> None:
@ -176,12 +173,12 @@ def test_exif_leak() -> None:
0 +----------------------------------------------------------------------->Gi
0 11.33
"""
im = hopper("RGB")
exif = b"12345678" * 4096
with Image.open("Tests/images/hopper.ppm") as im:
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG", exif=exif)
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG", exif=exif)
def test_base_save() -> None:
@ -210,7 +207,8 @@ def test_base_save() -> None:
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
0 +----------------------------------------------------------------------->Gi
0 7.882"""
with Image.open("Tests/images/hopper.ppm") as im:
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG")
im = hopper("RGB")
for _ in range(iterations):
test_output = BytesIO()
im.save(test_output, "JPEG")

View File

@ -32,7 +32,7 @@ pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit sy
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
f = tmp_path / "temp.png"
f = str(tmp_path / "temp.png")
im = Image.new("L", (xdim, ydim), 0)
im.save(f)

View File

@ -28,7 +28,7 @@ pytestmark = pytest.mark.skipif(sys.maxsize <= 2**32, reason="requires 64-bit sy
def _write_png(tmp_path: Path, xdim: int, ydim: int) -> None:
dtype = np.uint8
a = np.zeros((xdim, ydim), dtype=dtype)
f = tmp_path / "temp.png"
f = str(tmp_path / "temp.png")
im = Image.fromarray(a, "L")
im.save(f)

View File

@ -3,25 +3,26 @@ from __future__ import annotations
import zlib
from io import BytesIO
import pytest
from PIL import Image, ImageFile, PngImagePlugin
TEST_FILE = "Tests/images/png_decompression_dos.png"
def test_ignore_dos_text(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
def test_ignore_dos_text() -> None:
ImageFile.LOAD_TRUNCATED_IMAGES = True
with Image.open(TEST_FILE) as im:
try:
im = Image.open(TEST_FILE)
im.load()
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
assert isinstance(im, PngImagePlugin.PngImageFile)
for s in im.text.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
assert isinstance(im, PngImagePlugin.PngImageFile)
for s in im.text.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
for s in im.info.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
for s in im.info.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
def test_dos_text() -> None:

43
Tests/check_wheel.py Normal file
View File

@ -0,0 +1,43 @@
from __future__ import annotations
import sys
from PIL import features
def test_wheel_modules() -> None:
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
# tkinter is not available in cibuildwheel installed CPython on Windows
try:
import tkinter
assert tkinter
except ImportError:
expected_modules.remove("tkinter")
assert set(features.get_supported_modules()) == expected_modules
def test_wheel_codecs() -> None:
expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
assert set(features.get_supported_codecs()) == expected_codecs
def test_wheel_features() -> None:
expected_features = {
"webp_anim",
"webp_mux",
"transp_webp",
"raqm",
"fribidi",
"harfbuzz",
"libjpeg_turbo",
"xcb",
}
if sys.platform == "win32":
expected_features.remove("xcb")
assert set(features.get_supported_features()) == expected_features

View File

@ -1,17 +1,9 @@
from __future__ import annotations
import io
import sys
import sysconfig
import pytest
FREE_THREADED_BUILD = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
gil_enabled_at_start = True
if FREE_THREADED_BUILD:
gil_enabled_at_start = sys._is_gil_enabled() # type: ignore[attr-defined]
def pytest_report_header(config: pytest.Config) -> str:
try:
@ -24,25 +16,6 @@ def pytest_report_header(config: pytest.Config) -> str:
return f"pytest_report_header failed: {e}"
def pytest_terminal_summary(terminalreporter: pytest.TerminalReporter) -> None:
if (
FREE_THREADED_BUILD
and not gil_enabled_at_start
and sys._is_gil_enabled() # type: ignore[attr-defined]
):
tr = terminalreporter
tr.ensure_newline()
tr.section("GIL re-enabled", red=True, bold=True)
tr.line("The GIL was re-enabled at runtime during the tests.")
tr.line("This can happen with no test failures if the RuntimeWarning")
tr.line("raised by Python when this happens is filtered by a test.")
tr.line("")
tr.line("Please ensure all new C modules declare support for running")
tr.line("without the GIL. Any new tests that intentionally imports")
tr.line("code that re-enables the GIL should do so in a subprocess.")
pytest.exit("GIL re-enabled during tests", returncode=1)
def pytest_configure(config: pytest.Config) -> None:
config.addinivalue_line(
"markers",

1
Tests/createfontdatachunk.py Normal file → Executable file
View File

@ -1,3 +1,4 @@
#!/usr/bin/env python3
from __future__ import annotations
import base64

View File

@ -2,7 +2,7 @@
NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts
NotoSans-Regular.ttf, from https://www.google.com/get/noto/
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype. AdobeVFPrototypeDuplicates.ttf is a modified version of this
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype
TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
ter-x20b.pcf, from http://terminus-font.sourceforge.net/

View File

@ -1,10 +1,10 @@
STARTFONT
FONT ÿ
SIZE 10
FONTBOUNDINGBOX 1 1 0 0
CHARS 1
FONTBOUNDINGBOX
CHARS
STARTCHAR
ENCODING 65
ENCODING
BBX 2 5
ENDCHAR
ENDFONT

View File

@ -9,21 +9,17 @@ import os
import shutil
import subprocess
import sys
import sysconfig
import tempfile
from functools import lru_cache
from io import BytesIO
from typing import Any, Callable, Sequence
import pytest
from packaging.version import parse as parse_version
from PIL import Image, ImageFile, ImageMath, features
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Callable, Sequence
from pathlib import Path
from typing import Any
logger = logging.getLogger(__name__)
uploader = None
@ -55,15 +51,17 @@ def convert_to_comparable(
if a.mode == "P":
new_a = Image.new("L", a.size)
new_b = Image.new("L", b.size)
new_a.putdata(a.get_flattened_data())
new_b.putdata(b.get_flattened_data())
new_a.putdata(a.getdata())
new_b.putdata(b.getdata())
elif a.mode == "I;16":
new_a = a.convert("I")
new_b = b.convert("I")
return new_a, new_b
def assert_deep_equal(a: Any, b: Any, msg: str | None = None) -> None:
def assert_deep_equal(
a: Sequence[Any], b: Sequence[Any], msg: str | None = None
) -> None:
try:
assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
except Exception:
@ -99,14 +97,12 @@ def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) -
def assert_image_equal_tofile(
a: Image.Image,
filename: str | Path,
msg: str | None = None,
mode: str | None = None,
a: Image.Image, filename: str, msg: str | None = None, mode: str | None = None
) -> None:
with Image.open(filename) as im:
converted_im = im.convert(mode) if mode else im
assert_image_equal(a, converted_im, msg)
with Image.open(filename) as img:
if mode:
img = img.convert(mode)
assert_image_equal(a, img, msg)
def assert_image_similar(
@ -142,14 +138,21 @@ def assert_image_similar(
def assert_image_similar_tofile(
a: Image.Image,
filename: str | Path,
filename: str,
epsilon: float,
msg: str | None = None,
mode: str | None = None,
) -> None:
with Image.open(filename) as img:
if mode:
img = img.convert(mode)
assert_image_similar(a, img, epsilon, msg)
def assert_all_same(items: Sequence[Any], msg: str | None = None) -> None:
assert items.count(items[0]) == len(items), msg
def assert_not_all_same(items: Sequence[Any], msg: str | None = None) -> None:
assert items.count(items[0]) != len(items), msg
@ -163,25 +166,11 @@ def assert_tuple_approx_equal(
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
def timeout_unless_slower_valgrind(timeout: float) -> pytest.MarkDecorator:
if "PILLOW_VALGRIND_TEST" in os.environ:
return pytest.mark.pil_noop_mark()
return pytest.mark.timeout(timeout)
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
reason = f"{feature} not available"
return pytest.mark.skipif(not features.check(feature), reason=reason)
def has_feature_version(feature: str, required: str) -> bool:
version = features.version(feature)
assert version is not None
version_required = parse_version(required)
version_available = parse_version(version)
return version_available >= version_required
def skip_unless_feature_version(
feature: str, required: str, reason: str | None = None
) -> pytest.MarkDecorator:
@ -281,13 +270,17 @@ def _cached_hopper(mode: str) -> Image.Image:
im = hopper("L")
else:
im = hopper()
try:
im = im.convert(mode)
except ImportError:
if mode == "LAB":
im = Image.open("Tests/images/hopper.Lab.tif")
else:
raise
if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning):
im = im.convert(mode)
else:
try:
im = im.convert(mode)
except ImportError:
if mode == "LAB":
im = Image.open("Tests/images/hopper.Lab.tif")
else:
raise
return im
@ -301,6 +294,16 @@ def djpeg_available() -> bool:
return False
def cjpeg_available() -> bool:
if shutil.which("cjpeg"):
try:
subprocess.check_call(["cjpeg", "-version"])
return True
except subprocess.CalledProcessError: # pragma: no cover
return False
return False
def netpbm_available() -> bool:
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
@ -325,7 +328,16 @@ def magick_command() -> list[str] | None:
return None
def on_appveyor() -> bool:
return "APPVEYOR" in os.environ
def on_github_actions() -> bool:
return "GITHUB_ACTIONS" in os.environ
def on_ci() -> bool:
# GitHub Actions and AppVeyor have "CI"
return "CI" in os.environ
@ -347,6 +359,10 @@ def is_pypy() -> bool:
return hasattr(sys, "pypy_translation_info")
def is_mingw() -> bool:
return sysconfig.get_platform() == "mingw"
class CachedProperty:
def __init__(self, func: Callable[[Any], Any]) -> None:
self.func = func

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

View File

@ -0,0 +1,578 @@
<!DOCTYPE html>
<html>
<head>
<title>BMP Suite Image List</title>
<style>
.b { background:url(bkgd.png); }
.q { background-color:#fff0e0; }
.bad { background-color:#ffa0a0; }
</style>
</head>
<body>
<h1>BMP Suite Image List</h1>
<p><i>For <a href="http://entropymine.com/jason/bmpsuite/">BMP Suite</a>
version 2.3</i></p>
<p>This document describes the images in <i>BMP Suite</i>, and shows what
I allege to be the correct way to interpret them. PNG and JPEG images are
used for reference.
</p>
<p>It also shows how your web browser displays the BMP images,
but that&rsquo;s not its main purpose.
BMP is poor image format to use on web pages, so a web browser&rsquo;s
level of support for it is arguably not important.</p>
<table border=1 cellpadding=8>
<tr>
<th>File</th>
<th>Ver.</th>
<th>Correct display</th>
<th>In your browser</th>
<th>Notes</th>
</tr>
<tr>
<td>g/pal1.bmp</td>
<td>3</td>
<td class=b><img src="pal1.png"></td>
<td class=b><img src="../g/pal1.bmp"></td>
<td>1 bit/pixel paletted image, in which black is the first color in
the palette.</td>
</tr>
<tr>
<td>g/pal1wb.bmp</td>
<td>3</td>
<td class=b><img src="pal1.png"></td>
<td class=b><img src="../g/pal1wb.bmp"></td>
<td>1 bit/pixel paletted image, in which white is the first color in
the palette.</td>
</tr>
<tr>
<td>g/pal1bg.bmp</td>
<td>3</td>
<td class=b><img src="pal1bg.png"></td>
<td class=b><img src="../g/pal1bg.bmp"></td>
<td>1 bit/pixel paletted image, with colors other than black and white.</td>
</tr>
<tr>
<td class=q>q/pal1p1.bmp</td>
<td>3</td>
<td class=b><img src="pal1p1.png"></td>
<td class=b><img src="../q/pal1p1.bmp"></td>
<td>1 bit/pixel paletted image, with only one color in the palette.
The documentation says that 1-bpp images have a palette size of 2
(not &ldquo;up to 2&rdquo;), but it would be silly for a viewer not to
support a size of 1.</td>
</tr>
<tr>
<td class=q>q/pal2.bmp</td>
<td>3</td>
<td class=b><img src="pal2.png"></td>
<td class=b><img src="../q/pal2.bmp"></td>
<td>A paletted image with 2 bits/pixel. Usually only 1, 4,
and 8 are allowed, but 2 is legal on Windows CE.</td>
</tr>
<tr>
<td>g/pal4.bmp</td>
<td>3</td>
<td class=b><img src="pal4.png"></td>
<td class=b><img src="../g/pal4.bmp"></td>
<td>Paletted image with 12 palette colors, and 4 bits/pixel.</td>
</tr>
<tr>
<td>g/pal4rle.bmp</td>
<td>3</td>
<td class=b><img src="pal4.png"></td>
<td class=b><img src="../g/pal4rle.bmp"></td>
<td>4-bit image that uses RLE compression.</td>
</tr>
<tr>
<td class=q>q/pal4rletrns.bmp</td>
<td>3</td>
<td class=b><img src="pal4rletrns.png"><br>
or<br><img src="pal4rletrns-0.png"><br>
or<br><img src="pal4rletrns-b.png"></td>
<td class=b><img src="../q/pal4rletrns.bmp"></td>
<td>An RLE-compressed image that used &ldquo;delta&rdquo;
codes to skip over some pixels, leaving them undefined. Some viewers
make undefined pixels transparent, others make them black, and
others assign them palette color 0 (purple, in this case).</td>
</tr>
<tr>
<td>g/pal8.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8.bmp"></td>
<td>Our standard paletted image, with 252 palette colors, and 8
bits/pixel.</td>
</tr>
<tr>
<td>g/pal8-0.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8-0.bmp"></td>
<td>Every field that can be set to 0 is set to 0: pixels/meter=0;
colors used=0 (meaning the default 256); size-of-image=0.</td>
</tr>
<tr>
<td>g/pal8rle.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8rle.bmp"></td>
<td>8-bit image that uses RLE compression.</td>
</tr>
<tr>
<td class=q>q/pal8rletrns.bmp</td>
<td>3</td>
<td class=b><img src="pal8rletrns.png"><br>
or<br><img src="pal8rletrns-0.png"><br>
or<br><img src="pal8rletrns-b.png"></td>
<td class=b><img src="../q/pal8rletrns.bmp"></td>
<td>8-bit version of q/pal4rletrns.bmp.</td>
</tr>
<tr>
<td>g/pal8w126.bmp</td>
<td>3</td>
<td class=b><img src="pal8w126.png"></td>
<td class=b><img src="../g/pal8w126.bmp"></td>
<td rowspan=3>Images with different widths and heights.
In BMP format, rows are padded to a multiple of four bytes, so we
test all four possibilities.</td>
</tr>
<tr>
<td>g/pal8w125.bmp</td>
<td>3</td>
<td class=b><img src="pal8w125.png"></td>
<td class=b><img src="../g/pal8w125.bmp"></td>
</tr>
<tr>
<td>g/pal8w124.bmp</td>
<td>3</td>
<td class=b><img src="pal8w124.png"></td>
<td class=b><img src="../g/pal8w124.bmp"></td>
</tr>
<tr>
<td>g/pal8topdown.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8topdown.bmp"></td>
<td>BMP images are normally stored from the bottom up, but
there is a way to store them from the top down.</td>
</tr>
<tr>
<td class=q>q/pal8offs.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8offs.bmp"></td>
<td>A file with some unused bytes between the palette and the
image. This is probably valid, but I&rsquo;m not 100% sure.</td>
</tr>
<tr>
<td class=q>q/pal8oversizepal.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8oversizepal.bmp"></td>
<td>An 8-bit image with 300 palette colors. This may be invalid,
because the documentation could
be interpreted to imply that 8-bit images aren&rsquo;t allowed
to have more than 256 colors.</td>
</tr>
<tr>
<td>g/pal8nonsquare.bmp</td>
<td>3</td>
<td class=b>
<img src="pal8nonsquare-v.png"><br>
or<br>
<img src="pal8nonsquare-e.png">
</td>
<td class=b><img src="../g/pal8nonsquare.bmp"></td>
<td>An image with non-square pixels: the X pixels/meter is twice
the Y pixels/meter. Image <i>editors</i> can be expected to
leave the image &ldquo;squashed&rdquo;; image <i>viewers</i> should
consider stretching it to its correct proportions.</td>
</tr>
<tr>
<td>g/pal8os2.bmp</td>
<td>OS/2v1</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8os2.bmp"></td>
<td>An OS/2-style bitmap.</td>
</tr>
<tr>
<td class=q>q/pal8os2sp.bmp</td>
<td>OS/2v1</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8os2sp.bmp"></td>
<td>An OS/2v1 with a less-than-full-sized palette.
Probably not valid, but such files have been seen in the wild.</td>
</tr>
<tr>
<td class=q>q/pal8os2v2.bmp</td>
<td>OS/2v2</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8os2v2.bmp"></td>
<td>My attempt to make an OS/2v2 bitmap.</td>
</tr>
<tr>
<td class=q>q/pal8os2v2-16.bmp</td>
<td>OS/2v2</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8os2v2-16.bmp"></td>
<td>An OS/2v2 bitmap whose header has only 16 bytes, instead of the full 64.</td>
</tr>
<tr>
<td>g/pal8v4.bmp</td>
<td>4</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8v4.bmp"></td>
<td>A v4 bitmap. I&rsquo;m not sure that the gamma and chromaticity values in
this file are sensible, because I can&rsquo;t find any detailed documentation
of them.</td>
</tr>
<tr>
<td>g/pal8v5.bmp</td>
<td>5</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8v5.bmp"></td>
<td>A v5 bitmap. Version 5 has additional colorspace options over v4, so it
is easier to create, and ought to be more portable.</td>
</tr>
<tr>
<td>g/rgb16.bmp</td>
<td>3</td>
<td class=b><img src="rgb16.png"></td>
<td class=b><img src="../g/rgb16.bmp"></td>
<td>A 16-bit image with the default color format: 5 bits each for red,
green, and blue, and 1 unused bit.
The whitest colors should (I assume) be displayed as pure white:
<span style="background-color:rgb(255,255,255)">(255,255,255)</span>, not
<span style="background-color:rgb(248,248,248)">(248,248,248)</span>.</td>
</tr>
<tr>
<td>g/rgb16-565.bmp</td>
<td>3</td>
<td class=b><img src="rgb16-565.png"></td>
<td class=b><img src="../g/rgb16-565.bmp"></td>
<td>A 16-bit image with a BITFIELDS segment indicating 5 red, 6 green,
and 5 blue bits. This is a standard 16-bit format, even supported by
old versions of Windows that don&rsquo;t support any other non-default 16-bit
formats.
The whitest colors should be displayed as pure white:
<span style="background-color:rgb(255,255,255)">(255,255,255)</span>, not
<span style="background-color:rgb(248,252,248)">(248,252,248)</span>.</td>
</tr>
<tr>
<td>g/rgb16-565pal.bmp</td>
<td>3</td>
<td class=b><img src="rgb16-565.png"></td>
<td class=b><img src="../g/rgb16-565pal.bmp"></td>
<td>A 16-bit image with both a BITFIELDS segment and a palette.</td>
</tr>
<tr>
<td class=q>q/rgb16-231.bmp</td>
<td>3</td>
<td class=b><img src="rgb16-231.png"></td>
<td class=b><img src="../q/rgb16-231.bmp"></td>
<td>An unusual and silly 16-bit image, with 2 red bits, 3 green bits, and 1
blue bit. Most viewers do support this image, but the colors may be darkened
with a yellow-green shadow. That&rsquo;s because they&rsquo;re doing simple
bit-shifting (possibly including one round of bit replication), instead of
proper scaling.</td>
</tr>
<tr>
<td class=q>q/rgba16-4444.bmp</td>
<td>5</td>
<td class=b><img src="rgba16-4444.png"></td>
<td class=b><img src="../q/rgba16-4444.bmp"></td>
<td>A 16-bit image with an alpha channel. There are 4 bits for each color
channel, and 4 bits for the alpha channel.
It&rsquo;s not clear if this is valid, but I can&rsquo;t find anything that
suggests it isn&rsquo;t.
</td>
</tr>
<tr>
<td>g/rgb24.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb24.bmp"></td>
<td>A perfectly ordinary 24-bit (truecolor) image.</td>
</tr>
<tr>
<td>g/rgb24pal.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb24pal.bmp"></td>
<td>A 24-bit image, with a palette containing 256 colors. There is little if
any reason for a truecolor image to contain a palette, but it is legal.</td>
</tr>
<tr>
<td class=q>q/rgb24largepal.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24largepal.bmp"></td>
<td>A 24-bit image, with a palette containing 300 colors.
The fact that the palette has more than 256 colors may cause some viewers
to complain, but the documentation does not mention a size limit.</td>
</tr>
<tr>
<td class=q>q/rgb24prof.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24prof.bmp"></td>
<td>My attempt to make a BMP file with an embedded color profile.</td>
</tr>
<tr>
<td class=q>q/rgb24lprof.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24lprof.bmp"></td>
<td>My attempt to make a BMP file with a linked color profile.</td>
</tr>
<tr>
<td class=q>q/rgb24jpeg.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.jpg"></td>
<td class=b><img src="../q/rgb24jpeg.bmp"></td>
<td rowspan=2>My attempt to make BMP files with embedded JPEG and PNG images.
These are not likely to be supported by much of anything (they&rsquo;re
intended for printers).</td>
</tr>
<tr>
<td class=q>q/rgb24png.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24png.bmp"></td>
</tr>
<tr>
<td>g/rgb32.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb32.bmp"></td>
<td>A 32-bit image using the default color format for 32-bit images (no
BITFIELDS segment). There are 8 bits per color channel, and 8 unused
bits. The unused bits are set to 0.</td>
</tr>
<tr>
<td>g/rgb32bf.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb32bf.bmp"></td>
<td>A 32-bit image with a BITFIELDS segment. As usual, there are 8 bits per
color channel, and 8 unused bits. But the color channels are in an unusual
order, so the viewer must read the BITFIELDS, and not just guess.</td>
</tr>
<tr>
<td class=q>q/rgb32fakealpha.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"><br>
or<br>
<img class=b src="fakealpha.png">
</td>
<td class=b><img src="../q/rgb32fakealpha.bmp"></td>
<td>Same as g/rgb32.bmp, except that the unused bits are set to something
other than 0.
If the image becomes transparent toward the bottom, it probably means
the viewer uses heuristics to guess whether the undefined
data represents transparency.</td>
</tr>
<tr>
<td class=q>q/rgb32-111110.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb32-111110.bmp"></td>
<td>A 32 bits/pixel image, with all 32 bits used: 11 each for red and
green, and 10 for blue. As far as I know, this is perfectly valid, but it
is unusual.</td>
</tr>
<tr>
<td class=q>q/rgba32.bmp</td>
<td>5</td>
<td class=b><img src="rgba32.png"></td>
<td class=b><img src="../q/rgba32.bmp"></td>
<td>A BMP with an alpha channel. Transparency is barely documented,
so it&rsquo;s <i>possible</i> that this file is not correctly formed.
The color channels are in an unusual order, to prevent viewers from
passing this test by making a lucky guess.</td>
</tr>
<tr>
<td class=q>q/rgba32abf.bmp</td>
<td>3</td>
<td class=b><img src="rgba32.png"></td>
<td class=b><img src="../q/rgba32abf.bmp"></td>
<td>An image of type BI_ALHPABITFIELDS. Supposedly, this was used on
Windows CE. I don&rsquo;t know whether it is constructed correctly.</td>
</tr>
<tr>
<td class=bad>b/badbitcount.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badbitcount.bmp"></td>
<td>Header indicates an absurdly large number of bits/pixel.</td>
</tr>
<tr>
<td class=bad>b/badbitssize.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badbitssize.bmp"></td>
<td>Header incorrectly indicates that the bitmap is several GB in size.</td>
</tr>
<tr>
<td class=bad>b/baddens1.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/baddens1.bmp"></td>
<td rowspan=2>Density (pixels per meter) suggests the image is <i>much</i>
larger in one dimension than the other.</td>
</tr>
<tr>
<td class=bad>b/baddens2.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/baddens2.bmp"></td>
</tr>
<tr>
<td class=bad>b/badfilesize.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badfilesize.bmp"></td>
<td>Header incorrectly indicates that the file is several GB in size.</td>
</tr>
<tr>
<td class=bad>b/badheadersize.bmp</td>
<td>?</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badheadersize.bmp"></td>
<td>Header size is 66 bytes, which is not a valid size for any known BMP
version.</td>
</tr>
<tr>
<td class=bad>b/badpalettesize.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badpalettesize.bmp"></td>
<td>Header incorrectly indicates that the palette contains an absurdly large
number of colors.</td>
</tr>
<tr>
<td class=bad>b/badplanes.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badplanes.bmp"></td>
<td>The &ldquo;planes&rdquo; setting, which is required to be 1, is not 1.</td>
</tr>
<tr>
<td class=bad>b/badrle.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badrle.bmp"></td>
<td>An invalid RLE-compressed image that tries to cause buffer overruns.</td>
</tr>
<tr>
<td class=bad>b/badwidth.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badwidth.bmp"></td>
<td>The image claims to be a negative number of pixels in width.</td>
</tr>
<tr>
<td class=bad>b/pal8badindex.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/pal8badindex.bmp"></td>
<td>Many of the palette indices used in the image are not present in the
palette.</td>
</tr>
<tr>
<td class=bad>b/reallybig.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/reallybig.bmp"></td>
<td>An image with a very large reported width and height.</td>
</tr>
<tr>
<td class=bad>b/rletopdown.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/rletopdown.bmp"></td>
<td>An RLE-compressed image that tries to use top-down orientation,
which isn&rsquo;t allowed.</td>
</tr>
<tr>
<td class=bad>b/shortfile.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/shortfile.bmp"></td>
<td>A file that has been truncated in the middle of the bitmap.</td>
</tr>
</table>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Some files were not shown because too many files have changed in this diff Show More