diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt
index c4511439ccb..833aca23d4a 100644
--- a/.ci/requirements-cibw.txt
+++ b/.ci/requirements-cibw.txt
@@ -1 +1 @@
-cibuildwheel==2.21.3
+cibuildwheel==2.22.0
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index d03fcf0d9da..ba2b7d8ed26 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -19,7 +19,6 @@ Please send a pull request to the `main` branch. Please include [documentation](
- Follow PEP 8.
- 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
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
index 3711d91f0d5..de0ab480519 100644
--- a/.github/release-drafter.yml
+++ b/.github/release-drafter.yml
@@ -3,18 +3,19 @@ tag-template: "$NEXT_MINOR_VERSION"
change-template: '- $TITLE #$NUMBER [@$AUTHOR]'
categories:
- - title: "Dependencies"
- label: "Dependency"
+ - title: "Removals"
+ label: "Removal"
- title: "Deprecations"
label: "Deprecation"
- title: "Documentation"
label: "Documentation"
- - title: "Removals"
- label: "Removal"
+ - title: "Dependencies"
+ label: "Dependency"
- title: "Testing"
label: "Testing"
- title: "Type hints"
label: "Type hints"
+ - title: "Other changes"
exclude-labels:
- "changelog: skip"
@@ -23,6 +24,4 @@ template: |
https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html
- ## Changes
-
$CHANGES
diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh
index ddb4212301d..2301a3a7ef3 100755
--- a/.github/workflows/macos-install.sh
+++ b/.github/workflows/macos-install.sh
@@ -8,8 +8,8 @@ fi
brew install \
freetype \
ghostscript \
+ jpeg-turbo \
libimagequant \
- libjpeg \
libtiff \
little-cms2 \
openjpeg \
diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml
index 656054e8924..5b0a0394688 100644
--- a/.github/workflows/test-cygwin.yml
+++ b/.github/workflows/test-cygwin.yml
@@ -133,11 +133,12 @@ jobs:
- name: After success
run: |
bash.exe .ci/after_success.sh
+ rm C:\cygwin\bin\bash.EXE
- name: Upload coverage
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
with:
- file: ./coverage.xml
+ files: ./coverage.xml
flags: GHA_Cygwin
name: Cygwin Python 3.${{ matrix.python-minor-version }}
token: ${{ secrets.CODECOV_ORG_TOKEN }}
diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml
index 03608319a60..cc5f9d4a5a9 100644
--- a/.github/workflows/test-docker.yml
+++ b/.github/workflows/test-docker.yml
@@ -100,7 +100,7 @@ jobs:
MATRIX_DOCKER: ${{ matrix.docker }}
- name: Upload coverage
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
with:
flags: GHA_Docker
name: ${{ matrix.docker }}
diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml
index 93fe8a06465..a1d6ba61c9b 100644
--- a/.github/workflows/test-mingw.yml
+++ b/.github/workflows/test-mingw.yml
@@ -85,9 +85,9 @@ jobs:
.ci/test.sh
- name: Upload coverage
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
with:
- file: ./coverage.xml
+ files: ./coverage.xml
flags: GHA_Windows
name: "MSYS2 MinGW"
token: ${{ secrets.CODECOV_ORG_TOKEN }}
diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml
index 9b5da137d8f..d905a392585 100644
--- a/.github/workflows/test-windows.yml
+++ b/.github/workflows/test-windows.yml
@@ -215,9 +215,9 @@ jobs:
shell: pwsh
- name: Upload coverage
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
with:
- file: ./coverage.xml
+ files: ./coverage.xml
flags: GHA_Windows
name: ${{ runner.os }} Python ${{ matrix.python-version }}
token: ${{ secrets.CODECOV_ORG_TOKEN }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index c33309bbf03..83a696f5f8b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -42,6 +42,7 @@ jobs:
]
python-version: [
"pypy3.10",
+ "3.13t",
"3.13",
"3.12",
"3.11",
@@ -52,14 +53,14 @@ jobs:
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
# Free-threaded
- - { os: "ubuntu-latest", python-version: "3.13-dev", disable-gil: true }
+ - { python-version: "3.13t", disable-gil: true }
# M1 only available for 3.10+
- { os: "macos-13", python-version: "3.9" }
exclude:
- { os: "macos-latest", python-version: "3.9" }
runs-on: ${{ matrix.os }}
- name: ${{ matrix.os }} Python ${{ matrix.python-version }} ${{ matrix.disable-gil && 'free-threaded' || '' }}
+ name: ${{ matrix.os }} Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
@@ -67,8 +68,7 @@ jobs:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
- if: "${{ !matrix.disable-gil }}"
+ uses: Quansight-Labs/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
@@ -77,13 +77,6 @@ jobs:
".ci/*.sh"
"pyproject.toml"
- - name: Set up Python ${{ matrix.python-version }} (free-threaded)
- uses: deadsnakes/action@v3.2.0
- if: "${{ matrix.disable-gil }}"
- with:
- python-version: ${{ matrix.python-version }}
- nogil: ${{ matrix.disable-gil }}
-
- name: Set PYTHON_GIL
if: "${{ matrix.disable-gil }}"
run: |
@@ -156,7 +149,7 @@ jobs:
.ci/after_success.sh
- name: Upload coverage
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
with:
flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh
index a2aec07c32d..0d8ba3b9783 100755
--- a/.github/workflows/wheels-dependencies.sh
+++ b/.github/workflows/wheels-dependencies.sh
@@ -38,10 +38,10 @@ ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds
FREETYPE_VERSION=2.13.2
-HARFBUZZ_VERSION=10.0.1
+HARFBUZZ_VERSION=10.1.0
LIBPNG_VERSION=1.6.44
-JPEGTURBO_VERSION=3.0.4
-OPENJPEG_VERSION=2.5.2
+JPEGTURBO_VERSION=3.1.0
+OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.6.3
TIFF_VERSION=4.6.0
LCMS2_VERSION=2.16
@@ -82,10 +82,9 @@ function build_zlib_ng {
function build_brotli {
if [ -e brotli-stamp ]; then return; fi
- local cmake=$(get_modern_cmake)
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
(cd $out_dir \
- && $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
+ && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
&& make install)
touch brotli-stamp
}
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index 45f18634100..c5e55aa621d 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -85,7 +85,7 @@ jobs:
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
+ CIBW_ENABLE: cpython-prerelease
# Extra options for manylinux.
CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.spec }}
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.spec }}
@@ -150,10 +150,9 @@ jobs:
env:
CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_BUILD: ${{ matrix.build }}
- CIBW_FREE_THREADED_SUPPORT: True
+ CIBW_ENABLE: cpython-prerelease cpython-freethreading
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
- CIBW_PRERELEASE_PYTHONS: True
CIBW_SKIP: pp39-*
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
@@ -228,8 +227,7 @@ jobs:
CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
CIBW_CACHE_PATH: "C:\\cibw"
- CIBW_FREE_THREADED_SUPPORT: True
- CIBW_PRERELEASE_PYTHONS: True
+ CIBW_ENABLE: cpython-prerelease cpython-freethreading
CIBW_SKIP: pp39-*
CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_COMMAND: 'docker run --rm
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ddc98fdc356..f91260c724f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.7.2
+ rev: v0.8.1
hooks:
- id: ruff
args: [--exit-non-zero-on-fix]
@@ -11,7 +11,7 @@ repos:
- id: black
- repo: https://github.com/PyCQA/bandit
- rev: 1.7.10
+ rev: 1.8.0
hooks:
- id: bandit
args: [--severity-level=high]
@@ -24,7 +24,7 @@ repos:
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
- repo: https://github.com/pre-commit/mirrors-clang-format
- rev: v19.1.3
+ rev: v19.1.4
hooks:
- id: clang-format
types: [c]
@@ -50,7 +50,7 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/python-jsonschema/check-jsonschema
- rev: 0.29.4
+ rev: 0.30.0
hooks:
- id: check-github-workflows
- id: check-readthedocs
@@ -67,7 +67,7 @@ repos:
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
- rev: v0.22
+ rev: v0.23
hooks:
- id: validate-pyproject
additional_dependencies: [trove-classifiers>=2024.10.12]
diff --git a/CHANGES.rst b/CHANGES.rst
index f6ed5eb82fb..dfbbd24b331 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,26 +2,12 @@
Changelog (Pillow)
==================
-11.1.0 (unreleased)
--------------------
-
-- Removed use of os.path.realpath #8545
- [radarhere]
-
-- Allow linking to zlib import library on Windows #8519
- [cubanpit, nulano]
-
-- Detach PyQt6 QPixmap instance before returning #8509
- [radarhere]
+11.1.0 and newer
+----------------
-- Corrected EMF DPI #8485
- [radarhere]
+See GitHub Releases:
-- Fix IFDRational with a zero denominator #8474
- [radarhere]
-
-- Fixed disabling a feature during install #8469
- [radarhere]
+- https://github.com/python-pillow/Pillow/releases
11.0.0 (2024-10-15)
-------------------
diff --git a/LICENSE b/LICENSE
index 8837c290c30..10dd42d9eda 100644
--- a/LICENSE
+++ b/LICENSE
@@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
Pillow is the friendly PIL fork. It is
- Copyright © 2010-2024 by Jeffrey A. Clark and contributors
+ Copyright © 2010 by Jeffrey A. Clark and contributors
Like PIL, Pillow is licensed under the open source MIT-CMU License:
diff --git a/README.md b/README.md
index 5bbebaccb4a..057d0acf0ce 100644
--- a/README.md
+++ b/README.md
@@ -107,7 +107,7 @@ 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/blob/main/CHANGES.rst)
+- [Changelog](https://github.com/python-pillow/Pillow/releases)
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
## Report a Vulnerability
diff --git a/RELEASING.md b/RELEASING.md
index 9e6ec5dd4c1..ebdbb6406e8 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -12,7 +12,6 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
* [ ] 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
@@ -34,7 +33,6 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
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
diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
index 62f8719af53..9c49b1534ed 100644
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -1098,6 +1098,25 @@ def test_exif_transpose(self) -> None:
assert_image_similar(base_im, im, 0.7)
+ @pytest.mark.parametrize(
+ "test_file",
+ [
+ "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif",
+ "Tests/images/old-style-jpeg-compression.tif",
+ ],
+ )
+ def test_buffering(self, test_file: str) -> None:
+ # load exif first
+ with Image.open(open(test_file, "rb", buffering=1048576)) as im:
+ exif = dict(im.getexif())
+
+ # load image before exif
+ with Image.open(open(test_file, "rb", buffering=1048576)) as im2:
+ im2.load()
+ exif_after_load = dict(im2.getexif())
+
+ assert exif == exif_after_load
+
@pytest.mark.valgrind_known_error(reason="Backtrace in Python Core")
def test_sampleformat_not_corrupted(self) -> None:
# Assert that a TIFF image with SampleFormat=UINT tag is not corrupted
diff --git a/codecov.yml b/codecov.yml
index 8646576bb44..84920238ffc 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -1,7 +1,7 @@
# Documentation: https://docs.codecov.com/docs/codecov-yaml
codecov:
- # Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]"
+ # Avoid "Missing base report" due to committing with "[CI skip]"
# https://github.com/codecov/support/issues/363
# https://docs.codecov.com/docs/comparing-commits
allow_coverage_offsets: true
diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh
index 8c2967bc21b..1f8d781931b 100755
--- a/depends/install_openjpeg.sh
+++ b/depends/install_openjpeg.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# install openjpeg
-archive=openjpeg-2.5.2
+archive=openjpeg-2.5.3
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
diff --git a/docs/COPYING b/docs/COPYING
index d5ee19f81a6..17fba5b87ff 100644
--- a/docs/COPYING
+++ b/docs/COPYING
@@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
Pillow is the friendly PIL fork. It is
- Copyright © 2010-2024 by Jeffrey A. Clark and contributors
+ Copyright © 2010 by Jeffrey A. Clark and contributors
Like PIL, Pillow is licensed under the open source PIL
Software License:
diff --git a/docs/conf.py b/docs/conf.py
index b81d86c6ca2..e1e3f1b8f8a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -55,7 +55,7 @@
project = "Pillow (PIL Fork)"
copyright = (
"1995-2011 Fredrik Lundh and contributors, "
- "2010-2024 Jeffrey A. Clark and contributors."
+ "2010 Jeffrey A. Clark and contributors."
)
author = "Fredrik Lundh (PIL), Jeffrey A. Clark (Pillow)"
diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst
index 3df8e0d20be..f771ae7aea5 100644
--- a/docs/handbook/tutorial.rst
+++ b/docs/handbook/tutorial.rst
@@ -678,7 +678,7 @@ Reading from URL
from PIL import Image
from urllib.request import urlopen
- url = "https://python-pillow.org/assets/images/pillow-logo.png"
+ url = "https://python-pillow.github.io/assets/images/pillow-logo.png"
img = Image.open(urlopen(url))
diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst
index 19294f72c1c..03359de31cd 100644
--- a/docs/installation/building-from-source.rst
+++ b/docs/installation/building-from-source.rst
@@ -58,7 +58,7 @@ Many of Pillow's features require external libraries:
* **openjpeg** provides JPEG 2000 functionality.
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
- **2.4.0**, **2.5.0** and **2.5.2**.
+ **2.4.0**, **2.5.0**, **2.5.2** and **2.5.3**.
* Pillow does **not** support the earlier **1.5** series which ships
with Debian Jessie.
@@ -148,13 +148,7 @@ Many of Pillow's features require external libraries:
The easiest way to install external libraries is via `Homebrew
`_. After you install Homebrew, run::
- brew install libjpeg libtiff little-cms2 openjpeg webp
-
- To install libraqm on macOS use Homebrew to install its dependencies::
-
- brew install freetype harfbuzz fribidi
-
- Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
+ brew install libjpeg libraqm libtiff little-cms2 openjpeg webp
.. tab:: Windows
diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst
index b9d633142e7..35f863374d1 100644
--- a/docs/installation/platform-support.rst
+++ b/docs/installation/platform-support.rst
@@ -55,7 +55,7 @@ These platforms are built and tested for every change.
| +----------------------------+---------------------+
| | 3.13 | x86 |
| +----------------------------+---------------------+
-| | 3.9 (MinGW) | x86-64 |
+| | 3.12 (MinGW) | x86-64 |
| +----------------------------+---------------------+
| | 3.9 (Cygwin) | x86-64 |
+----------------------------------+----------------------------+---------------------+
diff --git a/pyproject.toml b/pyproject.toml
index af09ccbc1f2..2c6c7bcd077 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -73,10 +73,10 @@ optional-dependencies.typing = [
optional-dependencies.xmp = [
"defusedxml",
]
-urls.Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst"
+urls.Changelog = "https://github.com/python-pillow/Pillow/releases"
urls.Documentation = "https://pillow.readthedocs.io"
urls.Funding = "https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi"
-urls.Homepage = "https://python-pillow.org"
+urls.Homepage = "https://python-pillow.github.io"
urls.Mastodon = "https://fosstodon.org/@pillow"
urls."Release notes" = "https://pillow.readthedocs.io/en/stable/releasenotes/index.html"
urls.Source = "https://github.com/python-pillow/Pillow"
diff --git a/setup.py b/setup.py
index fbd23a5685a..eea21fa3407 100644
--- a/setup.py
+++ b/setup.py
@@ -393,13 +393,14 @@ def finalize_options(self) -> None:
self.feature.required.discard(x)
_dbg("Disabling %s", x)
if getattr(self, f"enable_{x}"):
- msg = f"Conflicting options: --enable-{x} and --disable-{x}"
+ msg = f"Conflicting options: '-C {x}=enable' and '-C {x}=disable'"
raise ValueError(msg)
if x == "freetype":
- _dbg("--disable-freetype implies --disable-raqm")
+ _dbg("'-C freetype=disable' implies '-C raqm=disable'")
if getattr(self, "enable_raqm"):
msg = (
- "Conflicting options: --enable-raqm and --disable-freetype"
+ "Conflicting options: "
+ "'-C raqm=enable' and '-C freetype=disable'"
)
raise ValueError(msg)
setattr(self, "disable_raqm", True)
@@ -407,15 +408,17 @@ def finalize_options(self) -> None:
_dbg("Requiring %s", x)
self.feature.required.add(x)
if x == "raqm":
- _dbg("--enable-raqm implies --enable-freetype")
+ _dbg("'-C raqm=enable' implies '-C freetype=enable'")
self.feature.required.add("freetype")
for x in ("raqm", "fribidi"):
if getattr(self, f"vendor_{x}"):
if getattr(self, "disable_raqm"):
- msg = f"Conflicting options: --vendor-{x} and --disable-raqm"
+ msg = f"Conflicting options: '-C {x}=vendor' and '-C raqm=disable'"
raise ValueError(msg)
if x == "fribidi" and not getattr(self, "vendor_raqm"):
- msg = f"Conflicting options: --vendor-{x} and not --vendor-raqm"
+ msg = (
+ f"Conflicting options: '-C {x}=vendor' and not '-C raqm=vendor'"
+ )
raise ValueError(msg)
_dbg("Using vendored version of %s", x)
self.feature.vendor.add(x)
@@ -1047,7 +1050,7 @@ def debug_build() -> bool:
msg = f"""
The headers or library files could not be found for {str(err)},
-which was requested by the option flag --enable-{str(err)}
+which was requested by the option flag '-C {str(err)}=enable'
"""
sys.stderr.write(msg)
diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py
index f9f47348c66..b4215a0b1e5 100644
--- a/src/PIL/ImImagePlugin.py
+++ b/src/PIL/ImImagePlugin.py
@@ -357,7 +357,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
name = "".join([name[: 92 - len(ext)], ext])
fp.write(f"Name: {name}\r\n".encode("ascii"))
- fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode("ascii"))
+ fp.write(f"Image size (x*y): {im.size[0]}*{im.size[1]}\r\n".encode("ascii"))
fp.write(f"File size (no of images): {frames}\r\n".encode("ascii"))
if im.mode in ["P", "PA"]:
fp.write(b"Lut: 1\r\n")
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index 9b76ce8bd86..1e289b6c38b 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -692,13 +692,10 @@ def __eq__(self, other: object) -> bool:
)
def __repr__(self) -> str:
- return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % (
- self.__class__.__module__,
- self.__class__.__name__,
- self.mode,
- self.size[0],
- self.size[1],
- id(self),
+ return (
+ f"<{self.__class__.__module__}.{self.__class__.__name__} "
+ f"image mode={self.mode} size={self.size[0]}x{self.size[1]} "
+ f"at 0x{id(self):X}>"
)
def _repr_pretty_(self, p: PrettyPrinter, cycle: bool) -> None:
@@ -707,14 +704,8 @@ def _repr_pretty_(self, p: PrettyPrinter, cycle: bool) -> None:
# Same as __repr__ but without unpredictable id(self),
# to keep Jupyter notebook `text/plain` output stable.
p.text(
- "<%s.%s image mode=%s size=%dx%d>"
- % (
- self.__class__.__module__,
- self.__class__.__name__,
- self.mode,
- self.size[0],
- self.size[1],
- )
+ f"<{self.__class__.__module__}.{self.__class__.__name__} "
+ f"image mode={self.mode} size={self.size[0]}x{self.size[1]}>"
)
def _repr_image(self, image_format: str, **kwargs: Any) -> bytes | None:
diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py
index 7f27d54dcc3..9836cd68884 100644
--- a/src/PIL/ImageFile.py
+++ b/src/PIL/ImageFile.py
@@ -120,7 +120,7 @@ def __init__(
self.custom_mimetype: str | None = None
self.tile: list[_Tile] = []
- """ A list of tile descriptors, or ``None`` """
+ """ A list of tile descriptors """
self.readonly = 1 # until we know better
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
index 6510e072e5e..ab6a2f497fb 100644
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -72,7 +72,7 @@ def APP(self: JpegImageFile, marker: int) -> None:
n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n)
- app = "APP%d" % (marker & 15)
+ app = f"APP{marker & 15}"
self.app[app] = s # compatibility
self.applist.append((app, s))
diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py
index 8445d5cc740..32436cea3af 100644
--- a/src/PIL/PcxImagePlugin.py
+++ b/src/PIL/PcxImagePlugin.py
@@ -86,7 +86,7 @@ def _open(self) -> None:
elif bits == 1 and planes in (2, 4):
mode = "P"
- rawmode = "P;%dL" % planes
+ rawmode = f"P;{planes}L"
self.palette = ImagePalette.raw("RGB", s[16:64])
elif version == 5 and bits == 8 and planes == 1:
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
index 4e12272041d..4b97992a31f 100644
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -523,7 +523,7 @@ def chunk_cHRM(self, pos: int, length: int) -> bytes:
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length)
- raw_vals = struct.unpack(">%dI" % (len(s) // 4), s)
+ raw_vals = struct.unpack(f">{len(s) // 4}I", s)
self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
return s
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index 6bf39b75a5f..9ee55347aaf 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -935,9 +935,9 @@ def load(self, fp: IO[bytes]) -> None:
self._tagdata[tag] = data
self.tagtype[tag] = typ
- msg += " - value: " + (
- "" % size if size > 32 else repr(data)
- )
+ bytes_value = size if size > 32 else repr(data)
+ msg += f" - value: "
+
logger.debug(msg)
(self.next,) = (
@@ -981,9 +981,10 @@ def tobytes(self, offset: int = 0) -> bytes:
tagname = TiffTags.lookup(tag, self.group).name
typname = "ifd" if is_ifd else TYPES.get(typ, "unknown")
- msg = f"save: {tagname} ({tag}) - type: {typname} ({typ})"
- msg += " - value: " + (
- "" % len(data) if len(data) >= 16 else str(values)
+ bytes_value = len(data) if len(data) >= 16 else str(values)
+ msg = (
+ f"save: {tagname} ({tag}) - type: {typname} ({typ})"
+ f" - value: "
)
logger.debug(msg)
@@ -1216,10 +1217,6 @@ def seek(self, frame: int) -> None:
def _seek(self, frame: int) -> None:
self.fp = self._fp
- # reset buffered io handle in case fp
- # was passed to libtiff, invalidating the buffer
- self.fp.tell()
-
while len(self._frame_pos) <= frame:
if not self.__next:
msg = "no more images in TIFF file"
@@ -1303,10 +1300,6 @@ def load_end(self) -> None:
if not self.is_animated:
self._close_exclusive_fp_after_loading = True
- # reset buffered io handle in case fp
- # was passed to libtiff, invalidating the buffer
- self.fp.tell()
-
# load IFD data from fp before it is closed
exif = self.getexif()
for key in TiffTags.TAGS_V2_GROUPS:
@@ -1381,8 +1374,17 @@ def _load_libtiff(self) -> Image.core.PixelAccess | None:
logger.debug("have fileno, calling fileno version of the decoder.")
if not close_self_fp:
self.fp.seek(0)
+ # Save and restore the file position, because libtiff will move it
+ # outside of the Python runtime, and that will confuse
+ # io.BufferedReader and possible others.
+ # NOTE: This must use os.lseek(), and not fp.tell()/fp.seek(),
+ # because the buffer read head already may not equal the actual
+ # file position, and fp.seek() may just adjust it's internal
+ # pointer and not actually seek the OS file handle.
+ pos = os.lseek(fp, 0, os.SEEK_CUR)
# 4 bytes, otherwise the trace might error out
n, err = decoder.decode(b"fpfp")
+ os.lseek(fp, pos, os.SEEK_SET)
else:
# we have something else.
logger.debug("don't have fileno or getvalue. just reading")
diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py
index 335804b792f..34a9a81e11f 100644
--- a/src/PIL/_typing.py
+++ b/src/PIL/_typing.py
@@ -44,7 +44,7 @@ def __class_getitem__(cls, item: Any) -> type[bool]:
class SupportsRead(Protocol[_T_co]):
- def read(self, __length: int = ...) -> _T_co: ...
+ def read(self, length: int = ..., /) -> _T_co: ...
StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]
diff --git a/wheels/multibuild b/wheels/multibuild
index 9a9d1275f02..74a9795bc64 160000
--- a/wheels/multibuild
+++ b/wheels/multibuild
@@ -1 +1 @@
-Subproject commit 9a9d1275f025f737cdaa3c451ba07129dd95f361
+Subproject commit 74a9795bc64ff786b7e7d33bdec2843cf17e512e
diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py
index 9ab04f7e5e4..5bdb5fafedb 100644
--- a/winbuild/build_prepare.py
+++ b/winbuild/build_prepare.py
@@ -112,12 +112,12 @@ def cmd_msbuild(
"BROTLI": "1.1.0",
"FREETYPE": "2.13.3",
"FRIBIDI": "1.0.16",
- "HARFBUZZ": "10.0.1",
- "JPEGTURBO": "3.0.4",
+ "HARFBUZZ": "10.1.0",
+ "JPEGTURBO": "3.1.0",
"LCMS2": "2.16",
"LIBPNG": "1.6.44",
"LIBWEBP": "1.4.0",
- "OPENJPEG": "2.5.2",
+ "OPENJPEG": "2.5.3",
"TIFF": "4.6.0",
"XZ": "5.6.3",
"ZLIBNG": "2.2.2",
@@ -154,7 +154,7 @@ def cmd_msbuild(
cmd_copy("cjpeg-static.exe", "cjpeg.exe"),
cmd_copy("djpeg-static.exe", "djpeg.exe"),
],
- "headers": ["j*.h"],
+ "headers": ["jconfig.h", r"src\j*.h"],
"libs": ["libjpeg.lib"],
"bins": ["cjpeg.exe", "djpeg.exe"],
},