diff --git a/.appveyor.yml b/.appveyor.yml
index cc4d56d0b..0f5dea9c5 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -10,7 +10,7 @@ environment:
TEST_OPTIONS:
DEPLOY: YES
matrix:
- - PYTHON: C:/Python311
+ - PYTHON: C:/Python312
ARCHITECTURE: x86
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
- PYTHON: C:/Python38-x64
@@ -43,7 +43,7 @@ build_script:
test_script:
- cd c:\pillow
-- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout'
+- '%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'
diff --git a/.ci/install.sh b/.ci/install.sh
index 4748feb3d..30b64349d 100755
--- a/.ci/install.sh
+++ b/.ci/install.sh
@@ -28,7 +28,8 @@ fi
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade wheel
-PYTHONOPTIMIZE=0 python3 -m pip install cffi
+# 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 olefile
@@ -38,7 +39,8 @@ python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
if [[ $(uname) != CYGWIN* ]]; then
- python3 -m pip install numpy
+ # 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
@@ -46,6 +48,16 @@ if [[ $(uname) != CYGWIN* ]]; then
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
diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt
new file mode 100644
index 000000000..dd61634cd
--- /dev/null
+++ b/.ci/requirements-cibw.txt
@@ -0,0 +1 @@
+cibuildwheel==2.16.2
diff --git a/.editorconfig b/.editorconfig
index d74549fe2..c3627ae4f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -13,7 +13,7 @@ indent_style = space
trim_trailing_whitespace = true
-[*.yml]
+[*.{toml,yml}]
# Two-space indentation
indent_size = 2
diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh
index a20838a15..f41324c4b 100755
--- a/.github/workflows/macos-install.sh
+++ b/.github/workflows/macos-install.sh
@@ -5,7 +5,9 @@ set -e
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
-PYTHONOPTIMIZE=0 python3 -m pip install cffi
+# 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 olefile
@@ -14,7 +16,8 @@ python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
-python3 -m pip install numpy
+# 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
diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml
index c8fd69ba0..eb27b4bf7 100644
--- a/.github/workflows/test-docker.yml
+++ b/.github/workflows/test-docker.yml
@@ -51,8 +51,8 @@ jobs:
debian-11-bullseye-amd64,
debian-12-bookworm-x86,
debian-12-bookworm-amd64,
- fedora-37-amd64,
fedora-38-amd64,
+ fedora-39-amd64,
gentoo,
ubuntu-20.04-focal-amd64,
ubuntu-22.04-jammy-amd64,
diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml
index 3d7ec8e67..8d8cb0b15 100644
--- a/.github/workflows/test-windows.yml
+++ b/.github/workflows/test-windows.yml
@@ -32,7 +32,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12"]
+ python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
timeout-minutes: 30
@@ -59,22 +59,23 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
cache: pip
cache-dependency-path: ".github/workflows/test-windows.yml"
- name: Print build system information
run: python3 .github/workflows/system-info.py
- - name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml
- run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml
+ - name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma
+ run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma
- name: Install dependencies
id: install
run: |
- 7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\"
- echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH
+ choco install nasm --no-progress
+ echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
- choco install ghostscript --version=10.0.0.20230317
+ choco install ghostscript --version=10.0.0.20230317 --no-progress
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
# Install extra test images
@@ -166,7 +167,6 @@ jobs:
- name: Build Pillow
run: |
$FLAGS="-C raqm=vendor -C fribidi=vendor"
- if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS+=" -C imagequant=disable" }
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
@@ -208,47 +208,6 @@ jobs:
flags: GHA_Windows
name: ${{ runner.os }} Python ${{ matrix.python-version }}
- - name: Build wheel
- id: wheel
- if: "github.event_name != 'pull_request'"
- run: |
- mkdir fribidi
- copy winbuild\build\bin\fribidi* fribidi
- 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
- )
- )
- )
- for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo dist=dist-%%a >> %GITHUB_OUTPUT%
- call winbuild\\build\\build_env.cmd
- %pythonLocation%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor -C imagequant=disable .
- shell: cmd
-
- - name: Upload wheel
- uses: actions/upload-artifact@v3
- if: "github.event_name != 'pull_request'"
- with:
- name: ${{ steps.wheel.outputs.dist }}
- path: "*.whl"
-
- - name: Upload fribidi.dll
- if: "github.event_name != 'pull_request' && matrix.python-version == 3.11"
- uses: actions/upload-artifact@v3
- with:
- name: fribidi
- path: fribidi\*
-
success:
permissions:
contents: none
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 201f9ef77..33dc561e5 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -41,6 +41,7 @@ jobs:
python-version: [
"pypy3.10",
"pypy3.9",
+ "3.13",
"3.12",
"3.11",
"3.10",
@@ -64,6 +65,7 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
cache: pip
cache-dependency-path: ".ci/*.sh"
diff --git a/.github/workflows/wheels-build.sh b/.github/workflows/wheels-build.sh
deleted file mode 100755
index 0aeec6b96..000000000
--- a/.github/workflows/wheels-build.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/bin/bash
-
-if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
- # webp, zstd, xz, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb
- # libxdmcp causes an issue on macOS < 11
- # curl from brew requires zstd, use system curl
- # if php is installed, brew tries to reinstall these after installing openblas
- # remove lcms2 and libpng to fix building openjpeg on arm64
- brew remove --ignore-dependencies webp zstd xz libpng libtiff libxcb libxdmcp curl php lcms2 ghostscript
-
- brew install pkg-config
-
- if [[ "$PLAT" == "arm64" ]]; then
- export MACOSX_DEPLOYMENT_TARGET="11.0"
- else
- export MACOSX_DEPLOYMENT_TARGET="10.10"
- fi
-fi
-
-if [[ "$MB_PYTHON_VERSION" == pypy3* ]]; then
- MB_PYTHON_OSX_VER="10.9"
-fi
-
-echo "::group::Install a virtualenv"
- source wheels/multibuild/common_utils.sh
- source wheels/multibuild/travis_steps.sh
- python3 -m pip install virtualenv
- before_install
-echo "::endgroup::"
-
-echo "::group::Build wheel"
- build_wheel
- ls -l "${GITHUB_WORKSPACE}/${WHEEL_SDIR}/"
-echo "::endgroup::"
-
-if [[ $MACOSX_DEPLOYMENT_TARGET != "11.0" ]]; then
- echo "::group::Test wheel"
- install_run
- echo "::endgroup::"
-fi
diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh
new file mode 100755
index 000000000..2605664eb
--- /dev/null
+++ b/.github/workflows/wheels-dependencies.sh
@@ -0,0 +1,151 @@
+#!/bin/bash
+# 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
+ source wheels/multibuild/manylinux_utils.sh
+fi
+
+ARCHIVE_SDIR=pillow-depends-main
+
+# Package versions for fresh source builds
+FREETYPE_VERSION=2.13.2
+HARFBUZZ_VERSION=8.3.0
+LIBPNG_VERSION=1.6.40
+JPEGTURBO_VERSION=3.0.1
+OPENJPEG_VERSION=2.5.0
+XZ_VERSION=5.4.5
+TIFF_VERSION=4.6.0
+LCMS2_VERSION=2.15
+if [[ -n "$IS_MACOS" ]]; then
+ GIFLIB_VERSION=5.1.4
+else
+ GIFLIB_VERSION=5.2.1
+fi
+if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then
+ ZLIB_VERSION=1.3
+else
+ ZLIB_VERSION=1.2.8
+fi
+LIBWEBP_VERSION=1.3.2
+BZIP2_VERSION=1.0.8
+LIBXCB_VERSION=1.16
+BROTLI_VERSION=1.1.0
+
+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-2.5.0.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 {
+ 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_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
+}
+
+function build {
+ if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
+ export BUILD_PREFIX="/usr/local"
+ fi
+ build_xz
+ if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
+ yum remove -y zlib-devel
+ fi
+ build_new_zlib
+
+ build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto
+ if [ -n "$IS_MACOS" ]; then
+ if [[ "$CIBW_ARCHS" == "arm64" ]]; then
+ build_simple xorgproto 2023.2 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 [ -f /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc ]; then
+ cp /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc /Library/Frameworks/Python.framework/Versions/Current/lib/pkgconfig/xcb-proto.pc
+ fi
+ fi
+ else
+ 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
+ build_tiff
+ build_libpng
+ build_lcms2
+ if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
+ for dylib in libjpeg.dylib libtiff.dylib liblcms2.dylib; do
+ cp $BUILD_PREFIX/lib/$dylib /opt/arm64-builds/lib
+ done
+ fi
+ build_openjpeg
+
+ ORIGINAL_CFLAGS=$CFLAGS
+ CFLAGS="$CFLAGS -O3 -DNDEBUG"
+ if [[ -n "$IS_MACOS" ]]; then
+ CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names"
+ fi
+ build_libwebp
+ CFLAGS=$ORIGINAL_CFLAGS
+
+ build_brotli
+
+ 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 "$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
+}
+
+# Any stuff that you need to do before you start building the wheels
+# Runs in the root directory of this repository.
+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
+ # webp, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb
+ # libxdmcp causes an issue on macOS < 11
+ # if php is installed, brew tries to reinstall these after installing openblas
+ # remove cairo to fix building harfbuzz on arm64
+ # remove lcms2 and libpng to fix building openjpeg on arm64
+ # remove zstd to avoid inclusion on x86_64
+ # curl from brew requires zstd, use system curl
+ brew remove --ignore-dependencies webp libpng libtiff libxcb libxdmcp curl php cairo lcms2 ghostscript zstd
+
+ brew install pkg-config
+fi
+
+wrap_wheel_builder build
+
+# Append licenses
+for filename in wheels/dependency_licenses/*; do
+ echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE
+ cat $filename >> LICENSE
+done
diff --git a/.github/workflows/wheels-linux.yml b/.github/workflows/wheels-linux.yml
deleted file mode 100644
index 8b2d9d451..000000000
--- a/.github/workflows/wheels-linux.yml
+++ /dev/null
@@ -1,69 +0,0 @@
-name: Build Linux wheels
-
-on:
- workflow_call:
- inputs:
- artifacts-name:
- required: true
- type: string
-
-env:
- CONFIG_PATH: "wheels/config.sh"
- REPO_DIR: "."
- TEST_DEPENDS: "pytest pytest-timeout"
-
-jobs:
- build:
- name: ${{ matrix.python }} ${{ matrix.mb-ml-libc }}${{ matrix.mb-ml-ver }}
- runs-on: "ubuntu-latest"
- strategy:
- fail-fast: false
- matrix:
- python: [
- "pypy3.9-7.3.13",
- "pypy3.10-7.3.13",
- "3.8",
- "3.9",
- "3.10",
- "3.11",
- "3.12",
- ]
- mb-ml-libc: [ "manylinux" ]
- mb-ml-ver: [ 2014, "_2_28" ]
- include:
- - python: "3.8"
- mb-ml-libc: "musllinux"
- mb-ml-ver: "_1_1"
- - python: "3.9"
- mb-ml-libc: "musllinux"
- mb-ml-ver: "_1_1"
- - python: "3.10"
- mb-ml-libc: "musllinux"
- mb-ml-ver: "_1_1"
- - python: "3.11"
- mb-ml-libc: "musllinux"
- mb-ml-ver: "_1_1"
- - python: "3.12"
- mb-ml-libc: "musllinux"
- mb-ml-ver: "_1_1"
- env:
- MB_PYTHON_VERSION: ${{ matrix.python }}
- MB_ML_LIBC: ${{ matrix.mb-ml-libc }}
- MB_ML_VER: ${{ matrix.mb-ml-ver }}
- steps:
- - uses: actions/checkout@v4
- with:
- submodules: true
- - uses: actions/setup-python@v4
- with:
- python-version: "3.x"
- - name: Build Wheel
- run: .github/workflows/wheels-build.sh
- - uses: actions/upload-artifact@v3
- with:
- name: ${{ inputs.artifacts-name }}
- path: wheelhouse/*.whl
- # Uncomment to get SSH access for testing
- # - name: Setup tmate session
- # if: failure()
- # uses: mxschmitt/action-tmate@v3
diff --git a/.github/workflows/wheels-macos.yml b/.github/workflows/wheels-macos.yml
deleted file mode 100644
index c51abf39a..000000000
--- a/.github/workflows/wheels-macos.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-name: Build macOS wheels
-
-on:
- workflow_call:
- inputs:
- artifacts-name:
- required: true
- type: string
-
-env:
- CONFIG_PATH: "wheels/config.sh"
- REPO_DIR: "."
- TEST_DEPENDS: "pytest pytest-timeout"
-
-jobs:
- build:
- name: ${{ matrix.python }} ${{ matrix.platform }}
- runs-on: "macos-latest"
- strategy:
- fail-fast: false
- matrix:
- python: [
- "pypy3.9-7.3.13",
- "pypy3.10-7.3.13",
- "3.8",
- "3.9",
- "3.10",
- "3.11",
- "3.12",
- ]
- platform: [ "x86_64", "arm64" ]
- exclude:
- - python: "pypy3.9-7.3.13"
- platform: "arm64"
- - python: "pypy3.10-7.3.13"
- platform: "arm64"
- env:
- PLAT: ${{ matrix.platform }}
- MB_PYTHON_VERSION: ${{ matrix.python }}
- TRAVIS_OS_NAME: "osx"
- steps:
- - uses: actions/checkout@v4
- with:
- submodules: true
- - uses: actions/setup-python@v4
- with:
- python-version: "3.x"
- - name: Build Wheel
- run: .github/workflows/wheels-build.sh
- - uses: actions/upload-artifact@v3
- with:
- name: ${{ inputs.artifacts-name }}
- path: wheelhouse/*.whl
- # Uncomment to get SSH access for testing
- # - name: Setup tmate session
- # if: failure()
- # uses: mxschmitt/action-tmate@v3
diff --git a/.github/workflows/wheels-test.ps1 b/.github/workflows/wheels-test.ps1
new file mode 100644
index 000000000..f593c7228
--- /dev/null
+++ b/.github/workflows/wheels-test.ps1
@@ -0,0 +1,22 @@
+param ([string]$venv, [string]$pillow="C:\pillow")
+$ErrorActionPreference = 'Stop'
+$ProgressPreference = 'SilentlyContinue'
+Set-PSDebug -Trace 1
+if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
+ # unlike CPython, PyPy requires Visual C++ Redistributable to be installed
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+ Invoke-WebRequest -Uri 'https://aka.ms/vs/15/release/vc_redist.x64.exe' -OutFile 'vc_redist.x64.exe'
+ C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
+}
+$env:path += ";$pillow\winbuild\build\bin\"
+& "$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
+& python -VV
+if (!$?) { exit $LASTEXITCODE }
+& python selftest.py
+if (!$?) { exit $LASTEXITCODE }
+& python -m pytest -vx Tests\check_wheel.py
+if (!$?) { exit $LASTEXITCODE }
+& python -m pytest -vx Tests
+if (!$?) { exit $LASTEXITCODE }
diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh
new file mode 100755
index 000000000..207ec1567
--- /dev/null
+++ b/.github/workflows/wheels-test.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -e
+
+if [[ "$OSTYPE" == "darwin"* ]]; then
+ brew install fribidi
+ export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
+elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
+ apk add curl fribidi
+else
+ yum install -y fribidi
+fi
+if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; 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
+ unzip pillow-test-images.zip
+ mv test-images-main/* Tests/images
+fi
+
+# Runs tests
+python3 selftest.py
+python3 -m pytest Tests/check_wheel.py
+python3 -m pytest
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index 4381a9856..c4737bfc7 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -3,14 +3,20 @@ name: Wheels
on:
push:
paths:
- - ".github/workflows/wheels*.yml"
+ - ".ci/requirements-cibw.txt"
+ - ".github/workflows/wheel*"
- "wheels/*"
+ - "winbuild/build_prepare.py"
+ - "winbuild/fribidi.cmake"
tags:
- "*"
pull_request:
paths:
- - ".github/workflows/wheels*.yml"
+ - ".ci/requirements-cibw.txt"
+ - ".github/workflows/wheel*"
- "wheels/*"
+ - "winbuild/build_prepare.py"
+ - "winbuild/fribidi.cmake"
workflow_dispatch:
permissions:
@@ -20,21 +26,179 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
-jobs:
- macos:
- uses: ./.github/workflows/wheels-macos.yml
- with:
- artifacts-name: "wheels"
+env:
+ FORCE_COLOR: 1
- linux:
- uses: ./.github/workflows/wheels-linux.yml
- with:
- artifacts-name: "wheels"
+jobs:
+ build:
+ name: ${{ matrix.name }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: "macOS x86_64"
+ os: macos-latest
+ archs: x86_64
+ macosx_deployment_target: "10.10"
+ - name: "macOS arm64"
+ os: macos-latest
+ archs: arm64
+ macosx_deployment_target: "11.0"
+ - name: "manylinux2014 and musllinux x86_64"
+ os: ubuntu-latest
+ archs: x86_64
+ - name: "manylinux_2_28 x86_64"
+ os: ubuntu-latest
+ archs: x86_64
+ build: "*manylinux*"
+ manylinux: "manylinux_2_28"
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: true
+
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.x"
+
+ - name: Build wheels
+ run: |
+ python3 -m pip install -r .ci/requirements-cibw.txt
+ python3 -m cibuildwheel --output-dir wheelhouse
+ env:
+ CIBW_ARCHS: ${{ matrix.archs }}
+ CIBW_BUILD: ${{ matrix.build }}
+ CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
+ CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
+ CIBW_SKIP: pp38-*
+ CIBW_TEST_SKIP: "*-macosx_arm64"
+ MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
+
+ - uses: actions/upload-artifact@v3
+ with:
+ name: dist
+ path: ./wheelhouse/*.whl
+
+ windows:
+ name: Windows ${{ matrix.arch }}
+ runs-on: windows-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - arch: x86
+ cibw_arch: x86
+ - arch: x64
+ cibw_arch: AMD64
+ - arch: ARM64
+ cibw_arch: ARM64
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Checkout extra test images
+ uses: actions/checkout@v4
+ with:
+ repository: python-pillow/test-images
+ path: Tests\test-images
+
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.x"
+
+ - name: Prepare for build
+ run: |
+ choco install nasm --no-progress
+ echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
+
+ # Install extra test images
+ xcopy /S /Y Tests\test-images\* Tests\images
+
+ & python.exe -m pip install -r .ci/requirements-cibw.txt
+
+ # Cannot cross-compile FriBiDi (only used for tests)
+ $FLAGS = ("--no-imagequant", "--architecture=${{ matrix.arch }}")
+ if ('${{ matrix.arch }}' -eq 'ARM64') { $FLAGS += "--no-fribidi" }
+ & python.exe winbuild\build_prepare.py -v @FLAGS
+ shell: pwsh
+
+ - name: Build wheels
+ run: |
+ 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_TEST_SKIP: "*-win_arm64"
+ CIBW_TEST_COMMAND: 'docker run --rm
+ -v {project}:C:\pillow
+ -v C:\cibw:C:\cibw
+ -v %CD%\..\venv-test:%CD%\..\venv-test
+ -e CI -e GITHUB_ACTIONS
+ mcr.microsoft.com/windows/servercore:ltsc2022
+ powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test'
+ shell: cmd
+
+ - name: Upload wheels
+ uses: actions/upload-artifact@v3
+ with:
+ name: dist
+ path: ./wheelhouse/*.whl
+
+ - name: Prepare to upload FriBiDi
+ if: "matrix.arch != 'ARM64'"
+ run: |
+ mkdir fribidi\${{ matrix.arch }}
+ copy winbuild\build\bin\fribidi* fribidi\${{ matrix.arch }}
+ shell: cmd
+
+ - name: Upload fribidi.dll
+ if: "matrix.arch != 'ARM64'"
+ uses: actions/upload-artifact@v3
+ with:
+ name: fribidi
+ path: fribidi\*
+
+ sdist:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.x"
+ cache: pip
+ cache-dependency-path: "Makefile"
+
+ - run: make sdist
+
+ - uses: actions/upload-artifact@v3
+ with:
+ name: dist
+ path: dist/*.tar.gz
success:
permissions:
contents: none
- needs: [macos, linux]
+ needs: [build, windows, sdist]
runs-on: ubuntu-latest
name: Wheels Successful
steps:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a8c7696df..8b2dc06ae 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,20 +1,14 @@
repos:
- - repo: https://github.com/asottile/pyupgrade
- rev: v3.13.0
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.1.4
hooks:
- - id: pyupgrade
- args: [--py38-plus]
+ - id: ruff
+ args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
- rev: 23.9.1
+ rev: 23.10.1
hooks:
- id: black
- args: [--target-version=py38]
-
- - repo: https://github.com/PyCQA/isort
- rev: 5.12.0
- hooks:
- - id: isort
- repo: https://github.com/PyCQA/bandit
rev: 1.7.5
@@ -23,32 +17,19 @@ repos:
args: [--severity-level=high]
files: ^src/
- - repo: https://github.com/asottile/yesqa
- rev: v1.5.0
- hooks:
- - id: yesqa
-
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.4
hooks:
- id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
- - repo: https://github.com/PyCQA/flake8
- rev: 6.1.0
- hooks:
- - id: flake8
- additional_dependencies:
- [flake8-2020, flake8-errmsg, flake8-implicit-str-concat, flake8-logging]
-
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- - id: python-check-blanket-noqa
- id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.4.0
+ rev: v4.5.0
hooks:
- id: check-executables-have-shebangs
- id: check-merge-conflict
@@ -61,17 +42,17 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/sphinx-contrib/sphinx-lint
- rev: v0.6.8
+ rev: v0.8.1
hooks:
- id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt
- rev: 1.2.0
+ rev: 1.4.1
hooks:
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
- rev: v0.14
+ rev: v0.15
hooks:
- id: validate-pyproject
diff --git a/.travis.yml b/.travis.yml
index e4584ec88..8f8250809 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,133 +2,50 @@ if: tag IS present OR type = api
env:
global:
- - CONFIG_PATH=wheels/config.sh
- - REPO_DIR=.
- - PLAT=aarch64
- - TEST_DEPENDS=pytest-timeout
+ - CIBW_ARCHS=aarch64
+ - CIBW_SKIP=pp38-*
language: python
# Default Python version is usually 3.6
-python: "3.11"
-dist: focal
+python: "3.12"
+dist: jammy
services: docker
jobs:
include:
- - name: "3.8 Focal manylinux2014 aarch64"
+ - name: "manylinux2014 aarch64"
os: linux
arch: arm64
env:
- - MB_ML_VER=2014
- - MB_PYTHON_VERSION=3.8
- - name: "3.8 Focal manylinux_2_28 aarch64"
+ - CIBW_BUILD="*manylinux*"
+ - CIBW_MANYLINUX_AARCH64_IMAGE=manylinux2014
+ - CIBW_MANYLINUX_PYPY_AARCH64_IMAGE=manylinux2014
+ - name: "manylinux_2_28 aarch64"
os: linux
arch: arm64
env:
- - MB_ML_VER="_2_28"
- - MB_PYTHON_VERSION=3.8
- - name: "3.8 musllinux_1_1 aarch64"
+ - CIBW_BUILD="*manylinux*"
+ - CIBW_MANYLINUX_AARCH64_IMAGE=manylinux_2_28
+ - CIBW_MANYLINUX_PYPY_AARCH64_IMAGE=manylinux_2_28
+ - name: "musllinux aarch64"
os: linux
arch: arm64
env:
- - MB_ML_VER="_1_1"
- - MB_ML_LIBC="musllinux"
- - MB_PYTHON_VERSION=3.8
- - name: "3.9 Focal manylinux2014 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER=2014
- - MB_PYTHON_VERSION=3.9
- - name: "3.9 Focal manylinux_2_28 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER="_2_28"
- - MB_PYTHON_VERSION=3.9
- - name: "3.9 musllinux_1_1 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER="_1_1"
- - MB_ML_LIBC="musllinux"
- - MB_PYTHON_VERSION=3.9
- - name: "3.10 Focal manylinux2014 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER=2014
- - MB_PYTHON_VERSION=3.10
- - name: "3.10 Focal manylinux_2_28 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER="_2_28"
- - MB_PYTHON_VERSION=3.10
- - name: "3.10 musllinux_1_1 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER="_1_1"
- - MB_ML_LIBC="musllinux"
- - MB_PYTHON_VERSION=3.10
- - name: "3.11 Focal manylinux_2_28 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER=2014
- - MB_PYTHON_VERSION=3.11
- - name: "3.11 Focal manylinux_2_28 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER="_2_28"
- - MB_PYTHON_VERSION=3.11
- - name: "3.11 musllinux_1_1 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER="_1_1"
- - MB_ML_LIBC="musllinux"
- - MB_PYTHON_VERSION=3.11
- - name: "3.12 Focal manylinux_2_28 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER=2014
- - MB_PYTHON_VERSION=3.12
- - name: "3.12 Focal manylinux_2_28 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER="_2_28"
- - MB_PYTHON_VERSION=3.12
- - name: "3.12 musllinux_1_1 aarch64"
- os: linux
- arch: arm64
- env:
- - MB_ML_VER="_1_1"
- - MB_ML_LIBC="musllinux"
- - MB_PYTHON_VERSION=3.12
-
-before_install:
- - source wheels/multibuild/common_utils.sh
- - source wheels/multibuild/travis_steps.sh
- - before_install
+ - CIBW_BUILD="*musllinux*"
install:
- - build_multilinux aarch64 build_wheel
- - ls -l "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/"
+ - python3 -m pip install -r .ci/requirements-cibw.txt
script:
- - install_run
+ - python3 -m cibuildwheel --output-dir wheelhouse
+ - ls -l "${TRAVIS_BUILD_DIR}/wheelhouse/"
# Upload wheels to GitHub Releases
deploy:
provider: releases
api_key: $GITHUB_RELEASE_TOKEN
file_glob: true
- file: "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/*.whl"
+ file: "${TRAVIS_BUILD_DIR}/wheelhouse/*.whl"
on:
repo: python-pillow/Pillow
tags: true
diff --git a/CHANGES.rst b/CHANGES.rst
index f4d11ba48..251917654 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,30 @@
Changelog (Pillow)
==================
+10.2.0 (unreleased)
+-------------------
+
+- Raise ValueError when TrueType font size is not greater than zero #7584
+ [akx, radarhere]
+
+- If absent, do not try to close fp when closing image #7557
+ [RaphaelVRossi, radarhere]
+
+- Allow configuring JPEG restart marker interval on save #7488
+ [bgilbert, radarhere]
+
+- Decrement reference count for PyObject #7549
+ [radarhere]
+
+- Implement ``streamtype=1`` option for tables-only JPEG encoding #7491
+ [bgilbert, radarhere]
+
+- If save_all PNG only has one frame, do not create animated image #7522
+ [radarhere]
+
+- Fixed frombytes() for images with a zero dimension #7493
+ [radarhere]
+
10.1.0 (2023-10-15)
-------------------
diff --git a/MANIFEST.in b/MANIFEST.in
index 2bbddefa3..af25dfd2d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -5,8 +5,10 @@ include *.md
include *.py
include *.rst
include *.sh
+include *.toml
include *.txt
include *.yaml
+include .flake8
include LICENSE
include Makefile
include tox.ini
diff --git a/Makefile b/Makefile
index 57d756b47..ad0a1adab 100644
--- a/Makefile
+++ b/Makefile
@@ -49,7 +49,7 @@ help:
@echo " install make and install"
@echo " install-coverage make and install with C coverage"
@echo " lint run the lint checks"
- @echo " lint-fix run Black and isort to (mostly) fix lint issues"
+ @echo " lint-fix run Ruff to (mostly) fix lint issues"
@echo " release-test run code and package tests before release"
@echo " test run tests on installed Pillow"
@@ -118,6 +118,6 @@ lint:
.PHONY: lint-fix
lint-fix:
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
- python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort
- python3 -m black --target-version py38 .
- python3 -m isort .
+ python3 -m black .
+ python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff
+ python3 -m ruff --fix .
diff --git a/RELEASING.md b/RELEASING.md
index 02551a3a9..8b0673203 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -20,12 +20,8 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
git tag 5.2.0
git push --tags
```
-* [ ] Create and check source distribution:
- ```bash
- make sdist
- ```
-* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
-* [ ] Check and upload all binaries and source distributions e.g.:
+* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
+* [ ] Check and upload all source and binary distributions e.g.:
```bash
python3 -m twine check --strict dist/*
python3 -m twine upload dist/Pillow-5.2.0*
@@ -59,8 +55,8 @@ Released as needed for security, installation or critical bug fixes.
```bash
make sdist
```
-* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
-* [ ] Check and upload all binaries and source distributions e.g.:
+* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
+* [ ] Check and upload all source and binary distributions e.g.:
```bash
python3 -m twine check --strict dist/*
python3 -m twine upload dist/Pillow-5.2.1*
@@ -90,20 +86,20 @@ Released as needed privately to individual vendors for critical security-related
```bash
make sdist
```
-* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
+* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
```bash
git push origin 2.5.x
```
-## Binary Distributions
+## Source and Binary Distributions
### macOS and Linux
-* [ ] Download wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
+* [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
```bash
gh run download --dir dist
- # select wheels
+ # select dist
```
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
and copy into `dist`.
diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py
index 69ebef9b4..36ce63296 100644
--- a/Tests/bench_cffi_access.py
+++ b/Tests/bench_cffi_access.py
@@ -45,7 +45,7 @@ def test_direct():
assert caccess[(0, 0)] == access[(0, 0)]
- print("Size: %sx%s" % im.size)
+ 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)
diff --git a/Tests/check_wheel.py b/Tests/check_wheel.py
new file mode 100644
index 000000000..cc52cb75e
--- /dev/null
+++ b/Tests/check_wheel.py
@@ -0,0 +1,41 @@
+import sys
+
+from PIL import features
+
+
+def test_wheel_modules():
+ 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():
+ expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
+
+ assert set(features.get_supported_codecs()) == expected_codecs
+
+
+def test_wheel_features():
+ 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
diff --git a/Tests/helper.py b/Tests/helper.py
index de5468d84..cce7eca3a 100644
--- a/Tests/helper.py
+++ b/Tests/helper.py
@@ -5,6 +5,7 @@ Helper functions.
import logging
import os
import shutil
+import subprocess
import sys
import sysconfig
import tempfile
@@ -95,7 +96,7 @@ def assert_image_equal(a, b, msg=None):
except Exception:
pass
- assert False, msg or "got different content"
+ pytest.fail(msg or "got different content")
def assert_image_equal_tofile(a, filename, msg=None, mode=None):
@@ -258,11 +259,21 @@ def hopper(mode=None, cache={}):
def djpeg_available():
- return bool(shutil.which("djpeg"))
+ if shutil.which("djpeg"):
+ try:
+ subprocess.check_call(["djpeg", "-version"])
+ return True
+ except subprocess.CalledProcessError: # pragma: no cover
+ return False
def cjpeg_available():
- return bool(shutil.which("cjpeg"))
+ if shutil.which("cjpeg"):
+ try:
+ subprocess.check_call(["cjpeg", "-version"])
+ return True
+ except subprocess.CalledProcessError: # pragma: no cover
+ return False
def netpbm_available():
diff --git a/Tests/oss-fuzz/build.sh b/Tests/oss-fuzz/build.sh
index 37fad7bc8..3aa6c7f6a 100755
--- a/Tests/oss-fuzz/build.sh
+++ b/Tests/oss-fuzz/build.sh
@@ -15,7 +15,7 @@
#
################################################################################
-python3 setup.py build --build-base=/tmp/build install
+python3 -m pip install .
# Build fuzzers in $OUT.
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py
index fffbc54ca..d0c81b5e9 100644
--- a/Tests/test_file_apng.py
+++ b/Tests/test_file_apng.py
@@ -350,7 +350,7 @@ def test_apng_save(tmp_path):
im.load()
assert not im.is_animated
assert im.n_frames == 1
- assert im.get_format_mimetype() == "image/apng"
+ assert im.get_format_mimetype() == "image/png"
assert im.info.get("default_image") is None
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
@@ -450,26 +450,29 @@ def test_apng_save_duration_loop(tmp_path):
test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150]
)
with Image.open(test_file) as im:
- im.load()
assert im.n_frames == 1
- assert im.info.get("duration") == 750
+ assert "duration" not in im.info
+
+ different_frame = Image.new("RGBA", (128, 64))
+ frame.save(
+ test_file,
+ save_all=True,
+ append_images=[frame, different_frame],
+ duration=[500, 100, 150],
+ )
+ with Image.open(test_file) as im:
+ assert im.n_frames == 2
+ assert im.info["duration"] == 600
+
+ im.seek(1)
+ assert im.info["duration"] == 150
# test info duration
- frame.info["duration"] = 750
- frame.save(test_file, save_all=True)
+ frame.info["duration"] = 300
+ frame.save(test_file, save_all=True, append_images=[frame, different_frame])
with Image.open(test_file) as im:
- assert im.info.get("duration") == 750
-
-
-def test_apng_save_duplicate_duration(tmp_path):
- test_file = str(tmp_path / "temp.png")
- frame = Image.new("RGB", (1, 1))
-
- # Test a single duration is correctly combined across duplicate frames
- frame.save(test_file, save_all=True, append_images=[frame, frame], duration=500)
- with Image.open(test_file) as im:
- assert im.n_frames == 1
- assert im.info.get("duration") == 1500
+ assert im.n_frames == 2
+ assert im.info["duration"] == 600
def test_apng_save_disposal(tmp_path):
@@ -674,7 +677,8 @@ def test_seek_after_close():
@pytest.mark.parametrize("mode", ("RGBA", "RGB", "P"))
@pytest.mark.parametrize("default_image", (True, False))
-def test_different_modes_in_later_frames(mode, default_image, tmp_path):
+@pytest.mark.parametrize("duplicate", (True, False))
+def test_different_modes_in_later_frames(mode, default_image, duplicate, tmp_path):
test_file = str(tmp_path / "temp.png")
im = Image.new("L", (1, 1))
@@ -682,7 +686,7 @@ def test_different_modes_in_later_frames(mode, default_image, tmp_path):
test_file,
save_all=True,
default_image=default_image,
- append_images=[Image.new(mode, (1, 1))],
+ append_images=[im.convert(mode) if duplicate else Image.new(mode, (1, 1), 1)],
)
with Image.open(test_file) as reloaded:
assert reloaded.mode == mode
diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py
index d2edcfc27..dac35a8d0 100644
--- a/Tests/test_file_iptc.py
+++ b/Tests/test_file_iptc.py
@@ -1,6 +1,8 @@
import sys
from io import BytesIO, StringIO
+import pytest
+
from PIL import Image, IptcImagePlugin
from .helper import hopper
@@ -44,7 +46,7 @@ def test_getiptcinfo_fotostation():
for tag in iptc.keys():
if tag[0] == 240:
return
- assert False, "FotoStation tag not found"
+ pytest.fail("FotoStation tag not found")
def test_getiptcinfo_zero_padding():
diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py
index a0822d000..ef070b6c5 100644
--- a/Tests/test_file_jpeg.py
+++ b/Tests/test_file_jpeg.py
@@ -643,6 +643,23 @@ class TestFileJpeg:
assert max(im2.quantization[0]) <= 255
assert max(im2.quantization[1]) <= 255
+ @pytest.mark.parametrize(
+ "blocks, rows, markers",
+ ((0, 0, 0), (1, 0, 15), (3, 0, 5), (8, 0, 1), (0, 1, 3), (0, 2, 1)),
+ )
+ def test_restart_markers(self, blocks, rows, markers):
+ im = Image.new("RGB", (32, 32)) # 16 MCUs
+ out = BytesIO()
+ im.save(
+ out,
+ format="JPEG",
+ restart_marker_blocks=blocks,
+ restart_marker_rows=rows,
+ # force 8x8 pixel MCUs
+ subsampling=0,
+ )
+ assert len(re.findall(b"\xff[\xd0-\xd7]", out.getvalue())) == markers
+
@pytest.mark.skipif(not djpeg_available(), reason="djpeg not available")
def test_load_djpeg(self):
with Image.open(TEST_FILE) as img:
@@ -961,6 +978,28 @@ class TestFileJpeg:
im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = False
+ def test_separate_tables(self):
+ im = hopper()
+ data = [] # [interchange, tables-only, image-only]
+ for streamtype in range(3):
+ out = BytesIO()
+ im.save(out, format="JPEG", streamtype=streamtype)
+ data.append(out.getvalue())
+
+ # SOI, EOI
+ for marker in b"\xff\xd8", b"\xff\xd9":
+ assert marker in data[1] and marker in data[2]
+ # DHT, DQT
+ for marker in b"\xff\xc4", b"\xff\xdb":
+ assert marker in data[1] and marker not in data[2]
+ # SOF0, SOS, APP0 (JFIF header)
+ for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
+ assert marker not in data[1] and marker in data[2]
+
+ with Image.open(BytesIO(data[0])) as interchange_im:
+ with Image.open(BytesIO(data[1] + data[2])) as combined_im:
+ assert_image_equal(interchange_im, combined_im)
+
def test_repr_jpeg(self):
im = hopper()
diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py
index 99df26fc9..2016b3ccb 100644
--- a/Tests/test_file_jpeg2k.py
+++ b/Tests/test_file_jpeg2k.py
@@ -416,7 +416,7 @@ def test_plt_marker():
while True:
marker = out.read(2)
if not marker:
- assert False, "End of stream without PLT"
+ pytest.fail("End of stream without PLT")
jp2_boxid = _binary.i16be(marker)
if jp2_boxid == 0xFF4F:
@@ -426,7 +426,7 @@ def test_plt_marker():
# PLT
return
elif jp2_boxid == 0xFF93:
- assert False, "SOD without finding PLT first"
+ pytest.fail("SOD without finding PLT first")
hdr = out.read(2)
length = _binary.i16be(hdr)
diff --git a/Tests/test_image.py b/Tests/test_image.py
index 83dac7080..f0861bb4f 100644
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -1,4 +1,5 @@
import io
+import logging
import os
import shutil
import sys
@@ -906,6 +907,13 @@ class TestImage:
im = Image.new("RGB", size)
assert im.tobytes() == b""
+ @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
+ def test_zero_frombytes(self, size):
+ Image.frombytes("RGB", size, b"")
+
+ im = Image.new("RGB", size)
+ im.frombytes(b"")
+
def test_has_transparency_data(self):
for mode in ("1", "L", "P", "RGB"):
im = Image.new(mode, (1, 1))
@@ -992,7 +1000,7 @@ class TestImage:
with Image.open(os.path.join("Tests/images", path)) as im:
try:
im.load()
- assert False
+ pytest.fail()
except OSError as e:
buffer_overrun = str(e) == "buffer overrun when reading image file"
truncated = "image file is truncated" in str(e)
@@ -1003,10 +1011,19 @@ class TestImage:
with Image.open("Tests/images/fli_overrun2.bin") as im:
try:
im.seek(1)
- assert False
+ pytest.fail()
except OSError as e:
assert str(e) == "buffer overrun when reading image file"
+ def test_close_graceful(self, caplog):
+ with Image.open("Tests/images/hopper.jpg") as im:
+ copy = im.copy()
+ with caplog.at_level(logging.DEBUG):
+ im.close()
+ copy.close()
+ assert len(caplog.records) == 0
+ assert im.fp is None
+
class MockEncoder:
pass
diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py
index 981753eb9..3bafc4c9c 100644
--- a/Tests/test_image_quantize.py
+++ b/Tests/test_image_quantize.py
@@ -67,7 +67,7 @@ def test_quantize_no_dither():
def test_quantize_no_dither2():
im = Image.new("RGB", (9, 1))
- im.putdata(list((p,) * 3 for p in range(0, 36, 4)))
+ im.putdata([(p,) * 3 for p in range(0, 36, 4)])
palette = Image.new("P", (1, 1))
data = (0, 0, 0, 32, 32, 32)
diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py
index 83c54cf62..b5bfa903f 100644
--- a/Tests/test_image_resize.py
+++ b/Tests/test_image_resize.py
@@ -195,7 +195,7 @@ class TestReducingGapResize:
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
)
- with pytest.raises(AssertionError):
+ with pytest.raises(pytest.fail.Exception):
assert_image_equal(ref, im)
assert_image_similar(ref, im, epsilon)
@@ -210,7 +210,7 @@ class TestReducingGapResize:
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0
)
- with pytest.raises(AssertionError):
+ with pytest.raises(pytest.fail.Exception):
assert_image_equal(ref, im)
assert_image_similar(ref, im, epsilon)
@@ -225,7 +225,7 @@ class TestReducingGapResize:
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0
)
- with pytest.raises(AssertionError):
+ with pytest.raises(pytest.fail.Exception):
assert_image_equal(ref, im)
assert_image_similar(ref, im, epsilon)
diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py
index 4fd07a2b4..96a2c2662 100644
--- a/Tests/test_image_thumbnail.py
+++ b/Tests/test_image_thumbnail.py
@@ -147,7 +147,7 @@ def test_reducing_gap_values():
ref = hopper()
ref.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=None)
- with pytest.raises(AssertionError):
+ with pytest.raises(pytest.fail.Exception):
assert_image_equal(ref, im)
assert_image_similar(ref, im, 3.5)
diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py
index db0df047f..e21bf8cbf 100644
--- a/Tests/test_imagefont.py
+++ b/Tests/test_imagefont.py
@@ -1071,3 +1071,9 @@ def test_raqm_missing_warning(monkeypatch):
"Raqm layout was requested, but Raqm is not available. "
"Falling back to basic layout."
)
+
+
+@pytest.mark.parametrize("size", [-1, 0])
+def test_invalid_truetype_sizes_raise_valueerror(layout_engine, size):
+ with pytest.raises(ValueError):
+ ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine)
diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py
index f8059eca4..a75cbadc4 100644
--- a/Tests/test_imagegrab.py
+++ b/Tests/test_imagegrab.py
@@ -11,6 +11,10 @@ from .helper import assert_image_equal_tofile, skip_unless_feature
class TestImageGrab:
+ @pytest.mark.skipif(
+ os.environ.get("USERNAME") == "ContainerAdministrator",
+ reason="can't grab screen when running in Docker",
+ )
@pytest.mark.skipif(
sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS"
)
diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py
index 6d153ccea..a3bb536ce 100644
--- a/Tests/test_imageops.py
+++ b/Tests/test_imageops.py
@@ -433,6 +433,12 @@ def test_exif_transpose_in_place():
assert_image_equal(im, expected)
+def test_autocontrast_unsupported_mode():
+ im = Image.new("RGBA", (1, 1))
+ with pytest.raises(OSError):
+ ImageOps.autocontrast(im)
+
+
def test_autocontrast_cutoff():
# Test the cutoff argument of autocontrast
with Image.open("Tests/images/bw_gradient.png") as img:
diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py
index e54372b60..3e73339ed 100644
--- a/Tests/test_imageshow.py
+++ b/Tests/test_imageshow.py
@@ -85,7 +85,7 @@ def test_ipythonviewer():
test_viewer = viewer
break
else:
- assert False
+ pytest.fail()
im = hopper()
assert test_viewer.show(im) == 1
diff --git a/_custom_build/backend.py b/_custom_build/backend.py
index 9b3265a94..23225d6b8 100644
--- a/_custom_build/backend.py
+++ b/_custom_build/backend.py
@@ -1,6 +1,6 @@
import sys
-from setuptools.build_meta import * # noqa: F401, F403
+from setuptools.build_meta import * # noqa: F403
from setuptools.build_meta import build_wheel
backend_class = build_wheel.__self__.__class__
diff --git a/docs/conf.py b/docs/conf.py
index 7dffcfae2..833dfa215 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -166,6 +166,12 @@ html_static_path = ["resources"]
# directly to the root of the documentation.
# html_extra_path = []
+html_css_files = ["css/dark.css"]
+
+html_js_files = [
+ "js/activate_tab.js",
+]
+
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
@@ -313,19 +319,15 @@ texinfo_documents = [
# texinfo_no_detailmenu = False
-def setup(app):
- app.add_css_file("css/dark.css")
-
-
linkcheck_allowed_redirects = {
- r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*", # noqa: E501
- r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", # noqa: E501
- r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", # noqa: E501
- r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", # noqa: E501
+ r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*",
+ r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg",
+ r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*",
+ r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest",
r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/",
- r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", # noqa: E501
- r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", # noqa: E501
- r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", # noqa: E501
+ r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*",
+ r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg",
+ r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+",
}
# sphinx.ext.extlinks
@@ -338,6 +340,7 @@ extlinks = {
"cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"),
"issue": (_repo + "issues/%s", "#%s"),
"pr": (_repo + "pull/%s", "#%s"),
+ "pypi": ("https://pypi.org/project/%s/", "%s"),
}
# sphinxext.opengraph
diff --git a/docs/deprecations.rst b/docs/deprecations.rst
index ce956cade..b4fbb8d50 100644
--- a/docs/deprecations.rst
+++ b/docs/deprecations.rst
@@ -10,7 +10,7 @@ Deprecated features
-------------------
Below are features which are considered deprecated. Where appropriate,
-a ``DeprecationWarning`` is issued.
+a :py:exc:`DeprecationWarning` is issued.
PSFile
~~~~~~
@@ -267,7 +267,7 @@ ImageFile.raise_ioerror
.. deprecated:: 7.2.0
.. versionremoved:: 9.0.0
-``IOError`` was merged into ``OSError`` in Python 3.3.
+:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3.
So, ``ImageFile.raise_ioerror`` has been removed.
Use ``ImageFile.raise_oserror`` instead.
@@ -293,9 +293,9 @@ im.offset
``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead.
It was documented as deprecated in PIL 1.1.2,
-raised a ``DeprecationWarning`` since 1.1.5,
-an ``Exception`` since Pillow 3.0.0
-and ``NotImplementedError`` since 3.3.0.
+raised a :py:exc:`DeprecationWarning` since 1.1.5,
+an :py:exc:`Exception` since Pillow 3.0.0
+and :py:exc:`NotImplementedError` since 3.3.0.
Image.fromstring, im.fromstring and im.tostring
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -307,9 +307,9 @@ Image.fromstring, im.fromstring and im.tostring
* ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead.
* ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead.
-They issued a ``DeprecationWarning`` since 2.0.0,
-an ``Exception`` since 3.0.0
-and ``NotImplementedError`` since 3.3.0.
+They issued a :py:exc:`DeprecationWarning` since 2.0.0,
+an :py:exc:`Exception` since 3.0.0
+and :py:exc:`NotImplementedError` since 3.3.0.
ImageCms.CmsProfile attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -318,7 +318,7 @@ ImageCms.CmsProfile attributes
.. versionremoved:: 8.0.0
Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed. From 6.0.0,
-they issued a ``DeprecationWarning``:
+they issued a :py:exc:`DeprecationWarning`:
======================== ===================================================
Removed Use instead
@@ -442,7 +442,7 @@ PIL.OleFileIO
.. deprecated:: 4.0.0
.. versionremoved:: 6.0.0
-PIL.OleFileIO was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of
-the upstream olefile Python package, and replaced with an ``ImportError`` in 5.0.0
+``PIL.OleFileIO`` was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of
+the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError` in 5.0.0
(2018-01). The deprecated file has now been removed from Pillow. If needed, install from
PyPI (eg. ``python3 -m pip install olefile``).
diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst
index d5d95d3ce..38c00f870 100644
--- a/docs/handbook/image-file-formats.rst
+++ b/docs/handbook/image-file-formats.rst
@@ -266,9 +266,12 @@ following options are available::
:py:class:`PIL.ImagePalette.ImagePalette` object.
**optimize**
- If present and true, attempt to compress the palette by
- eliminating unused colors. This is only useful if the palette can
- be compressed to the next smaller power of 2 elements.
+ Whether to attempt to compress the palette by eliminating unused colors.
+ This is attempted by default, unless a palette is specified as an option or
+ as part of the first image's :py:attr:`~PIL.Image.Image.info` dictionary.
+
+ This is only useful if the palette can be compressed to the next smaller
+ power of 2 elements.
Note that if the image you are saving comes from an existing GIF, it may have
the following properties in its :py:attr:`~PIL.Image.Image.info` dictionary.
@@ -494,6 +497,18 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
If absent, the setting will be determined by libjpeg or libjpeg-turbo.
+**restart_marker_blocks**
+ If present, emit a restart marker whenever the specified number of MCU
+ blocks has been produced.
+
+ .. versionadded:: 10.2.0
+
+**restart_marker_rows**
+ If present, emit a restart marker whenever the specified number of MCU
+ rows has been produced.
+
+ .. versionadded:: 10.2.0
+
**qtables**
If present, sets the qtables for the encoder. This is listed as an
advanced option for wizards in the JPEG documentation. Use with
@@ -1296,6 +1311,8 @@ Pillow reads Kodak FlashPix files. In the current version, only the highest
resolution image is read from the file, and the viewing transform is not taken
into account.
+To enable FPX support, you must install :pypi:`olefile`.
+
.. note::
To enable full FlashPix support, you need to build and install the IJG JPEG
@@ -1372,6 +1389,8 @@ the first sprite in the file is loaded. You can use :py:meth:`~PIL.Image.Image.s
Note that there may be an embedded gamma of 2.2 in MIC files.
+To enable MIC support, you must install :pypi:`olefile`.
+
MPO
^^^
diff --git a/docs/installation.rst b/docs/installation.rst
index 162880182..78900aa57 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -1,6 +1,14 @@
Installation
============
+.. raw:: html
+
+
+
Warnings
--------
@@ -42,6 +50,11 @@ Install Pillow with :command:`pip`::
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow
+Optionally, install :pypi:`defusedxml` for Pillow to read XMP data,
+and :pypi:`olefile` for Pillow to read FPX and MIC images::
+
+ python3 -m pip install --upgrade defusedxml olefile
+
.. tab:: Linux
@@ -146,13 +159,13 @@ Many of Pillow's features require external libraries:
* Pillow has been tested with libjpeg versions **6b**, **8**, **9-9d** and
libjpeg-turbo version **8**.
- * Starting with Pillow 3.0.0, libjpeg is required by default, but
- may be disabled with the ``--disable-jpeg`` flag.
+ * Starting with Pillow 3.0.0, libjpeg is required by default. It can be
+ disabled with the ``-C jpeg=disable`` flag.
* **zlib** provides access to compressed PNGs
- * Starting with Pillow 3.0.0, zlib is required by default, but may
- be disabled with the ``--disable-zlib`` flag.
+ * Starting with Pillow 3.0.0, zlib is required by default. It can be
+ disabled with the ``-C zlib=disable`` flag.
* **libtiff** provides compressed TIFF functionality
@@ -171,8 +184,6 @@ Many of Pillow's features require external libraries:
transparent WebP files. Versions **0.3.0** and above support
transparency.
-* **tcl/tk** provides support for tkinter bitmap and photo images.
-
* **openjpeg** provides JPEG 2000 functionality.
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
@@ -356,7 +367,7 @@ for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no
additional configuration should be required. If they are installed in
a non-standard location, you may need to configure setuptools to use
those locations by editing :file:`setup.py` or
-:file:`setup.cfg`, or by adding environment variables on the command
+:file:`pyproject.toml`, or by adding environment variables on the command
line::
CFLAGS="-I/usr/pkg/include" python3 -m pip install --upgrade Pillow --no-binary :all:
@@ -456,10 +467,10 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| Debian 12 Bookworm | 3.11 | x86, x86-64 |
+----------------------------------+----------------------------+---------------------+
-| Fedora 37 | 3.11 | x86-64 |
-+----------------------------------+----------------------------+---------------------+
| Fedora 38 | 3.11 | x86-64 |
+----------------------------------+----------------------------+---------------------+
+| Fedora 39 | 3.12 | x86-64 |
++----------------------------------+----------------------------+---------------------+
| Gentoo | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 |
@@ -478,7 +489,7 @@ These platforms are built and tested for every change.
| Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | 3.12, PyPy3 | |
| +----------------------------+---------------------+
-| | 3.11 | x86 |
+| | 3.12 | x86 |
| +----------------------------+---------------------+
| | 3.9 (MinGW) | x86-64 |
| +----------------------------+---------------------+
@@ -571,6 +582,10 @@ These platforms have been reported to work at the versions mentioned.
+----------------------------------+----------------------------+------------------+--------------+
| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
+| Windows 11 | 3.9, 3.10, 3.11, 3.12 | 10.1.0 |arm64 |
++----------------------------------+----------------------------+------------------+--------------+
+| Windows 11 Pro | 3.11, 3.12 | 10.1.0 |x86-64 |
++----------------------------------+----------------------------+------------------+--------------+
| Windows 10 | 3.7 | 7.1.0 |x86-64 |
+----------------------------------+----------------------------+------------------+--------------+
| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 |
diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst
index 2abfa0cc9..a944a13fa 100644
--- a/docs/reference/ImageFont.rst
+++ b/docs/reference/ImageFont.rst
@@ -10,7 +10,7 @@ this class store bitmap fonts, and are used with the
PIL uses its own font file format to store bitmap fonts, limited to 256 characters. You can use
`pilfont.py `_
-from `pillow-scripts `_ to convert BDF and
+from :pypi:`pillow-scripts` to convert BDF and
PCF font descriptors (X window font formats) to this format.
Starting with version 1.1.4, PIL can be configured to support TrueType and
@@ -20,7 +20,7 @@ the imToolkit package.
.. warning::
To protect against potential DOS attacks when using arbitrary strings as
- text input, Pillow will raise a ``ValueError`` if the number of characters
+ text input, Pillow will raise a :py:exc:`ValueError` if the number of characters
is over a certain limit, :py:data:`MAX_STRING_LENGTH`.
This threshold can be changed by setting
@@ -89,5 +89,5 @@ Constants
.. data:: MAX_STRING_LENGTH
Set to 1,000,000, to protect against potential DOS attacks. Pillow will
- raise a ``ValueError`` if the number of characters is over this limit. The
+ raise a :py:exc:`ValueError` if the number of characters is over this limit. The
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
diff --git a/docs/releasenotes/10.0.0.rst b/docs/releasenotes/10.0.0.rst
index 06acfc7af..a3f238119 100644
--- a/docs/releasenotes/10.0.0.rst
+++ b/docs/releasenotes/10.0.0.rst
@@ -173,8 +173,8 @@ been processed before Pillow started checking for decompression bombs.
Added ImageFont.MAX_STRING_LENGTH
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-To protect against potential DOS attacks when using arbitrary strings as text
-input, Pillow will now raise a ``ValueError`` if the number of characters
+:cve:`2023-44271`: To protect against potential DOS attacks when using arbitrary strings as text
+input, Pillow will now raise a :py:exc:`ValueError` if the number of characters
passed into ImageFont methods is over a certain limit,
:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`.
diff --git a/docs/releasenotes/10.1.0.rst b/docs/releasenotes/10.1.0.rst
index 8c3413c8c..fd556bdf1 100644
--- a/docs/releasenotes/10.1.0.rst
+++ b/docs/releasenotes/10.1.0.rst
@@ -8,7 +8,7 @@ Setting image mode
^^^^^^^^^^^^^^^^^^
If you attempt to set the mode of an image directly, e.g.
-``im.mode = "RGBA"``, you will now receive an ``AttributeError``. This is
+``im.mode = "RGBA"``, you will now receive an :py:exc:`AttributeError`. This is
not about removing existing functionality, but instead about raising an
explicit error to prevent later consequences. The ``convert`` method is the
correct way to change an image's mode.
diff --git a/docs/releasenotes/2.8.0.rst b/docs/releasenotes/2.8.0.rst
index c522fe8b0..4dbbc0bdd 100644
--- a/docs/releasenotes/2.8.0.rst
+++ b/docs/releasenotes/2.8.0.rst
@@ -10,7 +10,7 @@ operations. As a result PIL was unable to open them as images, requiring a wrap
``cStringIO`` or ``BytesIO``.
Now new functionality has been added to ``Image.open()`` by way of an ``.seek(0)`` check and
-catch on exception ``AttributeError`` or ``io.UnsupportedOperation``. If this is caught we
+catch on exception :py:exc:`AttributeError` or :py:exc:`io.UnsupportedOperation`. If this is caught we
attempt to wrap the object using ``io.BytesIO`` (which will only work on buffer-file-like
objects).
diff --git a/docs/releasenotes/3.4.0.rst b/docs/releasenotes/3.4.0.rst
index dc5e2e295..2bbafe741 100644
--- a/docs/releasenotes/3.4.0.rst
+++ b/docs/releasenotes/3.4.0.rst
@@ -19,7 +19,7 @@ Deprecation Warning when Saving JPEGs
JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0
silently drops the alpha channel. With this release Pillow will now
-issue a ``DeprecationWarning`` when attempting to save a ``RGBA`` mode
+issue a :py:exc:`DeprecationWarning` when attempting to save a ``RGBA`` mode
image as a JPEG. This will become an error in Pillow 4.2.
New DDS Decoders
diff --git a/docs/releasenotes/4.0.0.rst b/docs/releasenotes/4.0.0.rst
index cbf131c93..5778de26a 100644
--- a/docs/releasenotes/4.0.0.rst
+++ b/docs/releasenotes/4.0.0.rst
@@ -17,8 +17,8 @@ Pillow 4.0 supports Python 3.6.
OleFileIO.py
============
-OleFileIO.py has been removed as a vendored file and is now installed
-from the upstream olefile pypi package. All internal dependencies are
+``OleFileIO.py`` has been removed as a vendored file and is now installed
+from the upstream :pypi:`olefile` PyPI package. All internal dependencies are
redirected to the olefile package. Direct accesses to
``PIL.OlefileIO`` raises a deprecation warning, then patches the
upstream olefile into ``sys.modules`` in its place.
diff --git a/docs/releasenotes/5.0.0.rst b/docs/releasenotes/5.0.0.rst
index 509edbe6d..be00a45cd 100644
--- a/docs/releasenotes/5.0.0.rst
+++ b/docs/releasenotes/5.0.0.rst
@@ -28,7 +28,7 @@ Scripts
The scripts formerly installed by Pillow have been split into a
separate package, pillow-scripts, living at
-https://github.com/python-pillow/pillow-scripts .
+https://github.com/python-pillow/pillow-scripts.
API Changes
@@ -37,7 +37,7 @@ API Changes
OleFileIO.py
^^^^^^^^^^^^
-The olefile module is no longer a required dependency when installing Pillow.
+The :pypi:`olefile` module is no longer a required dependency when installing Pillow.
Support for plugins requiring olefile will not be loaded if it is not
installed. This allows library consumers to avoid installing this dependency
if they choose. Some library consumers have little interest in the format
diff --git a/docs/releasenotes/5.3.0.rst b/docs/releasenotes/5.3.0.rst
index bff56566b..8f276da24 100644
--- a/docs/releasenotes/5.3.0.rst
+++ b/docs/releasenotes/5.3.0.rst
@@ -8,7 +8,7 @@ Image size
^^^^^^^^^^
If you attempt to set the size of an image directly, e.g.
-``im.size = (100, 100)``, you will now receive an ``AttributeError``. This is
+``im.size = (100, 100)``, you will now receive an :py:exc:`AttributeError`. This is
not about removing existing functionality, but instead about raising an
explicit error to prevent later consequences. The ``resize`` method is the
correct way to change an image's size.
@@ -16,7 +16,8 @@ correct way to change an image's size.
The exceptions to this are:
* The ICO and ICNS image formats, which use ``im.size = (100, 100)`` to select a subimage.
-* The TIFF image format, which now has a ``DeprecationWarning`` for this action, as direct image size setting was previously necessary to work around an issue with tile extents.
+* The TIFF image format, which now has a :py:exc:`DeprecationWarning` for this action,
+ as direct image size setting was previously necessary to work around an issue with tile extents.
API Additions
diff --git a/docs/releasenotes/5.4.1.rst b/docs/releasenotes/5.4.1.rst
index 78f483db6..bbabd6520 100644
--- a/docs/releasenotes/5.4.1.rst
+++ b/docs/releasenotes/5.4.1.rst
@@ -15,7 +15,7 @@ PNG: Handle IDAT chunks after image end
Some PNG images have multiple IDAT chunks. In some cases, Pillow will stop
reading image data before the IDAT chunks finish. A regression caused an
-``EOFError`` exception when previously there was none. This is now fixed, and
+:py:exc:`EOFError` exception when previously there was none. This is now fixed, and
file reading continues in case there are subsequent text chunks.
PNG: MIME type
@@ -30,7 +30,7 @@ File closing
^^^^^^^^^^^^
A regression caused an unsupported image file to report a
-``ValueError: seek of closed file`` exception instead of an ``OSError``. This
+``ValueError: seek of closed file`` exception instead of an :py:exc:`OSError`. This
has been fixed by ensuring that image plugins only close their internal ``__fp``
if they are not the same as ``ImageFile``'s ``fp``, allowing each to manage their own
file pointers.
diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst
index 3e3b945a0..5e69f0b6b 100644
--- a/docs/releasenotes/6.0.0.rst
+++ b/docs/releasenotes/6.0.0.rst
@@ -14,8 +14,8 @@ Pillow for Python 3.4 is 5.4.1.
Removed deprecated PIL.OleFileIO
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-PIL.OleFileIO was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of
-the upstream olefile Python package, and replaced with an ``ImportError``. The
+``PIL.OleFileIO`` was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of
+the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError`. The
deprecated file has now been removed from Pillow. If needed, install from PyPI (eg.
``python3 -m pip install olefile``).
@@ -103,7 +103,7 @@ ImageCms.CmsProfile attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Some attributes in ``ImageCms.CmsProfile`` have been deprecated since Pillow 3.2.0. From
-6.0.0, they issue a ``DeprecationWarning``:
+6.0.0, they issue a :py:exc:`DeprecationWarning`:
======================== ===============================
Deprecated Use instead
diff --git a/docs/releasenotes/6.1.0.rst b/docs/releasenotes/6.1.0.rst
index e7c593e6e..ce3edc5fa 100644
--- a/docs/releasenotes/6.1.0.rst
+++ b/docs/releasenotes/6.1.0.rst
@@ -58,7 +58,7 @@ file. ``ImageFont.FreeTypeFont`` has four new methods,
:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_name` for using named styles, and
:py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_axes` and
:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_axes` for using font axes
-instead. An ``IOError`` will be raised if the font is not a variation font. FreeType
+instead. An :py:exc:`IOError` will be raised if the font is not a variation font. FreeType
2.9.1 or greater is required.
Other Changes
diff --git a/docs/releasenotes/7.0.0.rst b/docs/releasenotes/7.0.0.rst
index f2e235289..ed6026593 100644
--- a/docs/releasenotes/7.0.0.rst
+++ b/docs/releasenotes/7.0.0.rst
@@ -85,7 +85,7 @@ Custom unidentified image error
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Pillow will now throw a custom ``UnidentifiedImageError`` when an image cannot be
-identified. For backwards compatibility, this will inherit from ``OSError``.
+identified. For backwards compatibility, this will inherit from :py:exc:`OSError`.
New argument ``reducing_gap`` for Image.resize() and Image.thumbnail() methods
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/releasenotes/7.1.2.rst b/docs/releasenotes/7.1.2.rst
index b12d84e33..ec0063e79 100644
--- a/docs/releasenotes/7.1.2.rst
+++ b/docs/releasenotes/7.1.2.rst
@@ -7,7 +7,7 @@ Fix another regression seeking PNG files
This fixes a regression introduced in 7.1.0 when adding support for APNG files.
When calling ``seek(n)`` on a regular PNG where ``n > 0``, it failed to raise an
-``EOFError`` as it should have done, resulting in:
+:py:exc:`EOFError` as it should have done, resulting in:
.. code-block:: pycon
diff --git a/docs/releasenotes/7.2.0.rst b/docs/releasenotes/7.2.0.rst
index ff1b7c9e7..91e54da19 100644
--- a/docs/releasenotes/7.2.0.rst
+++ b/docs/releasenotes/7.2.0.rst
@@ -53,6 +53,6 @@ a custom :py:class:`~PIL.ImageShow.Viewer` class.
ImageFile.raise_ioerror
~~~~~~~~~~~~~~~~~~~~~~~
-``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror``
+:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror``
is now deprecated and will be removed in a future release. Use
``ImageFile.raise_oserror`` instead.
diff --git a/docs/releasenotes/8.0.0.rst b/docs/releasenotes/8.0.0.rst
index 00c691a74..2bf299dd3 100644
--- a/docs/releasenotes/8.0.0.rst
+++ b/docs/releasenotes/8.0.0.rst
@@ -168,7 +168,7 @@ offset.
Error for large BMP files
^^^^^^^^^^^^^^^^^^^^^^^^^
-Previously, if a BMP file was too large, an ``OSError`` would be raised. Now,
+Previously, if a BMP file was too large, an :py:exc:`OSError` would be raised. Now,
``DecompressionBombError`` is used instead, as Pillow already uses for other formats.
Dark theme for docs
diff --git a/docs/releasenotes/8.3.1.rst b/docs/releasenotes/8.3.1.rst
index e97070c11..6af2b37bf 100644
--- a/docs/releasenotes/8.3.1.rst
+++ b/docs/releasenotes/8.3.1.rst
@@ -22,9 +22,10 @@ Catch OSError when checking if destination is sys.stdout
========================================================
In 8.3.0, a check to see if the destination was ``sys.stdout`` when saving an image was
-updated. This lead to an OSError being raised if the environment restricted access.
+updated. This lead to an :py:exc:`OSError` being raised if the environment restricted
+access.
-The OSError is now silently caught.
+The :py:exc:`OSError` is now silently caught.
Fixed removing orientation in ImageOps.exif_transpose
=====================================================
@@ -34,7 +35,7 @@ original image EXIF data was not modified, and the orientation was only removed
the modified copy.
However, for certain images the orientation was already missing from the modified
-image, leading to a KeyError.
+image, leading to a :py:exc:`KeyError`.
This error has been resolved, and the copying of metadata to the modified image
improved.
diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst
index 73e77ad3e..090ec8024 100644
--- a/docs/releasenotes/9.0.0.rst
+++ b/docs/releasenotes/9.0.0.rst
@@ -63,7 +63,7 @@ a custom :py:class:`~PIL.ImageShow.Viewer` class.
ImageFile.raise_ioerror
^^^^^^^^^^^^^^^^^^^^^^^
-``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror``
+:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror``
has been removed. Use ``ImageFile.raise_oserror`` instead.
diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst
index 19690ca59..02da702a7 100644
--- a/docs/releasenotes/9.1.0.rst
+++ b/docs/releasenotes/9.1.0.rst
@@ -8,14 +8,14 @@ Raise an error when performing a negative crop
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now
-it will raise a ``ValueError``, to help reduce confusion if a user has unintentionally
+it will raise a :py:exc:`ValueError`, to help reduce confusion if a user has unintentionally
provided the wrong arguments.
Added specific error if path coordinate type is incorrect
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Rather than returning a ``SystemError``, passing the incorrect types of coordinates into
-a path will now raise a more specific ``ValueError``, with the message "incorrect
+Rather than returning a :py:exc:`SystemError`, passing the incorrect types of coordinates into
+a path will now raise a more specific :py:exc:`ValueError`, with the message "incorrect
coordinate type".
Replace requirements.txt with extras
diff --git a/docs/resources/js/activate_tab.js b/docs/resources/js/activate_tab.js
new file mode 100644
index 000000000..92522b5ce
--- /dev/null
+++ b/docs/resources/js/activate_tab.js
@@ -0,0 +1,36 @@
+// Based on https://stackoverflow.com/a/38241481/724176
+function getOS() {
+ const userAgent = window.navigator.userAgent,
+ platform = window.navigator.userAgentData?.platform || window.navigator.platform,
+ macosPlatforms = ["macOS", "Macintosh", "MacIntel", "MacPPC", "Mac68K"],
+ windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"];
+
+ if (macosPlatforms.includes(platform)) {
+ return "macOS";
+ } else if (windowsPlatforms.includes(platform)) {
+ return "Windows";
+ } else if (/Android/.test(userAgent)) {
+ return "Android";
+ } else if (/Linux/.test(platform)) {
+ return "Linux";
+ }
+}
+
+function activateTab(tabName) {
+ // Find all label elements with the specified tab name
+ const labels = document.querySelectorAll(".tab-label");
+
+ labels.forEach((label) => {
+ if (label.textContent == tabName) {
+ // Find the associated input element using the "for" attribute
+ const tabInputId = label.getAttribute("for");
+ const tabInput = document.getElementById(tabInputId);
+
+ // Check if the input element exists before attempting to set the "checked" attribute
+ if (tabInput) {
+ // Activate the tab by setting its "checked" attribute to true
+ tabInput.checked = true;
+ }
+ }
+ });
+}
diff --git a/pyproject.toml b/pyproject.toml
index fd9c05f92..f9cea0612 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,3 +6,122 @@ requires = [
backend-path = [
"_custom_build",
]
+
+[project]
+name = "pillow"
+description = "Python Imaging Library (Fork)"
+readme = "README.md"
+keywords = [
+ "Imaging",
+]
+license = {text = "HPND"}
+authors = [{name = "Jeffrey A. Clark (Alex)", email = "aclark@aclark.net"}]
+requires-python = ">=3.8"
+classifiers = [
+ "Development Status :: 6 - Mature",
+ "License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+ "Topic :: Multimedia :: Graphics",
+ "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera",
+ "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture",
+ "Topic :: Multimedia :: Graphics :: Graphics Conversion",
+ "Topic :: Multimedia :: Graphics :: Viewers",
+]
+dynamic = [
+ "version",
+]
+[project.optional-dependencies]
+docs = [
+ "furo",
+ "olefile",
+ "sphinx>=2.4",
+ "sphinx-copybutton",
+ "sphinx-inline-tabs",
+ "sphinx-removed-in",
+ "sphinxext-opengraph",
+]
+fpx = [
+ "olefile",
+]
+mic = [
+ "olefile",
+]
+tests = [
+ "check-manifest",
+ "coverage",
+ "defusedxml",
+ "markdown2",
+ "olefile",
+ "packaging",
+ "pyroma",
+ "pytest",
+ "pytest-cov",
+ "pytest-timeout",
+]
+xmp = [
+ "defusedxml",
+]
+[project.urls]
+Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst"
+Documentation = "https://pillow.readthedocs.io"
+Funding = "https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi"
+Homepage = "https://python-pillow.org"
+Mastodon = "https://fosstodon.org/@pillow"
+"Release notes" = "https://pillow.readthedocs.io/en/stable/releasenotes/index.html"
+Source = "https://github.com/python-pillow/Pillow"
+Twitter = "https://twitter.com/PythonPillow"
+
+[tool.setuptools]
+packages = ["PIL"]
+include-package-data = true
+package-dir = {"" = "src"}
+
+[tool.setuptools.dynamic]
+version = {attr = "PIL.__version__"}
+
+[tool.cibuildwheel]
+before-all = ".github/workflows/wheels-dependencies.sh"
+build-verbosity = 1
+config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable"
+test-command = "cd {project} && .github/workflows/wheels-test.sh"
+test-extras = "tests"
+
+[tool.ruff]
+line-length = 88
+select = [
+ "C4", # flake8-comprehensions
+ "E", # pycodestyle errors
+ "EM", # flake8-errmsg
+ "F", # pyflakes errors
+ "I", # isort
+ "ISC", # flake8-implicit-str-concat
+ "PGH", # pygrep-hooks
+ "RUF100", # unused noqa (yesqa)
+ "UP", # pyupgrade
+ "W", # pycodestyle warnings
+ "YTT", # flake8-2020
+ # "LOG", # TODO: enable flake8-logging when it's not in preview anymore
+]
+extend-ignore = [
+ "E203", # Whitespace before ':'
+ "E221", # Multiple spaces before operator
+ "E226", # Missing whitespace around arithmetic operator
+ "E241", # Multiple spaces after ','
+]
+
+[tool.ruff.per-file-ignores]
+"Tests/*.py" = ["I001"]
+
+[tool.ruff.isort]
+known-first-party = ["PIL"]
+
+[tool.pytest.ini_options]
+addopts = "-ra --color=yes"
+testpaths = ["Tests"]
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index e560f9516..000000000
--- a/setup.cfg
+++ /dev/null
@@ -1,74 +0,0 @@
-[metadata]
-name = Pillow
-description = Python Imaging Library (Fork)
-long_description = file: README.md
-long_description_content_type = text/markdown
-url = https://python-pillow.org
-author = Jeffrey A. Clark (Alex)
-author_email = aclark@aclark.net
-license = HPND
-classifiers =
- Development Status :: 6 - Mature
- License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)
- Programming Language :: Python :: 3
- Programming Language :: Python :: 3 :: Only
- Programming Language :: Python :: 3.8
- Programming Language :: Python :: 3.9
- Programming Language :: Python :: 3.10
- Programming Language :: Python :: 3.11
- Programming Language :: Python :: 3.12
- Programming Language :: Python :: Implementation :: CPython
- Programming Language :: Python :: Implementation :: PyPy
- Topic :: Multimedia :: Graphics
- Topic :: Multimedia :: Graphics :: Capture :: Digital Camera
- Topic :: Multimedia :: Graphics :: Capture :: Screen Capture
- Topic :: Multimedia :: Graphics :: Graphics Conversion
- Topic :: Multimedia :: Graphics :: Viewers
-keywords = Imaging
-project_urls =
- Documentation=https://pillow.readthedocs.io
- Source=https://github.com/python-pillow/Pillow
- Funding=https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi
- Release notes=https://pillow.readthedocs.io/en/stable/releasenotes/index.html
- Changelog=https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst
- Twitter=https://twitter.com/PythonPillow
- Mastodon=https://fosstodon.org/@pillow
-
-[options]
-packages = PIL
-python_requires = >=3.8
-include_package_data = True
-package_dir =
- = src
-
-[options.extras_require]
-docs =
- furo
- olefile
- sphinx>=2.4
- sphinx-copybutton
- sphinx-inline-tabs
- sphinx-removed-in
- sphinxext-opengraph
-tests =
- check-manifest
- coverage
- defusedxml
- markdown2
- olefile
- packaging
- pyroma
- pytest
- pytest-cov
- pytest-timeout
-
-[flake8]
-extend-ignore = E203
-max-line-length = 88
-
-[isort]
-profile = black
-
-[tool:pytest]
-addopts = -ra --color=yes
-testpaths = Tests
diff --git a/setup.py b/setup.py
index 935166716..2a364ba97 100755
--- a/setup.py
+++ b/setup.py
@@ -440,17 +440,17 @@ class pil_build_ext(build_ext):
#
# add configured kits
- for root_name, lib_name in dict(
- JPEG_ROOT="libjpeg",
- JPEG2K_ROOT="libopenjp2",
- TIFF_ROOT=("libtiff-5", "libtiff-4"),
- ZLIB_ROOT="zlib",
- FREETYPE_ROOT="freetype2",
- HARFBUZZ_ROOT="harfbuzz",
- FRIBIDI_ROOT="fribidi",
- LCMS_ROOT="lcms2",
- IMAGEQUANT_ROOT="libimagequant",
- ).items():
+ for root_name, lib_name in {
+ "JPEG_ROOT": "libjpeg",
+ "JPEG2K_ROOT": "libopenjp2",
+ "TIFF_ROOT": ("libtiff-5", "libtiff-4"),
+ "ZLIB_ROOT": "zlib",
+ "FREETYPE_ROOT": "freetype2",
+ "HARFBUZZ_ROOT": "harfbuzz",
+ "FRIBIDI_ROOT": "fribidi",
+ "LCMS_ROOT": "lcms2",
+ "IMAGEQUANT_ROOT": "libimagequant",
+ }.items():
root = globals()[root_name]
if root is None and root_name in os.environ:
@@ -986,7 +986,6 @@ ext_modules = [
try:
setup(
- version=PILLOW_VERSION,
cmdclass={"build_ext": pil_build_ext},
ext_modules=ext_modules,
zip_safe=not (debug_build() or PLATFORM_MINGW),
diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py
index ef719e3ec..b51019c66 100644
--- a/src/PIL/BmpImagePlugin.py
+++ b/src/PIL/BmpImagePlugin.py
@@ -396,7 +396,7 @@ def _save(im, fp, filename, bitmap_header=True):
dpi = info.get("dpi", (96, 96))
# 1 meter == 39.3701 inches
- ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi))
+ ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi)
stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
header = 40 # or 64 for OS/2 version 2
diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py
index 94efff341..fc0dae44b 100644
--- a/src/PIL/CurImagePlugin.py
+++ b/src/PIL/CurImagePlugin.py
@@ -64,8 +64,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
d, e, o, a = self.tile[0]
self.tile[0] = d, (0, 0) + self.size, o, a
- return
-
#
# --------------------------------------------------------------------
diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py
index 9b2fce0ac..c05208c80 100644
--- a/src/PIL/EpsImagePlugin.py
+++ b/src/PIL/EpsImagePlugin.py
@@ -77,14 +77,11 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
# Hack to support hi-res rendering
scale = int(scale) or 1
- # orig_size = size
- # orig_bbox = bbox
- size = (size[0] * scale, size[1] * scale)
+ width = size[0] * scale
+ height = size[1] * scale
# resolution is dependent on bbox and size
- res = (
- 72.0 * size[0] / (bbox[2] - bbox[0]),
- 72.0 * size[1] / (bbox[3] - bbox[1]),
- )
+ res_x = 72.0 * width / (bbox[2] - bbox[0])
+ res_y = 72.0 * height / (bbox[3] - bbox[1])
out_fd, outfile = tempfile.mkstemp()
os.close(out_fd)
@@ -121,8 +118,8 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
command = [
gs_binary,
"-q", # quiet mode
- "-g%dx%d" % size, # set output geometry (pixels)
- "-r%fx%f" % res, # set input DPI (dots per inch)
+ f"-g{width:d}x{height:d}", # set output geometry (pixels)
+ f"-r{res_x:f}x{res_y:f}", # set input DPI (dots per inch)
"-dBATCH", # exit after processing
"-dNOPAUSE", # don't pause between pages
"-dSAFER", # safe mode
diff --git a/src/PIL/FitsImagePlugin.py b/src/PIL/FitsImagePlugin.py
index e0e51aaac..1ff8a7e91 100644
--- a/src/PIL/FitsImagePlugin.py
+++ b/src/PIL/FitsImagePlugin.py
@@ -54,12 +54,10 @@ class FitsImageFile(ImageFile.ImageFile):
self._mode = "L"
elif number_of_bits == 16:
self._mode = "I"
- # rawmode = "I;16S"
elif number_of_bits == 32:
self._mode = "I"
elif number_of_bits in (-32, -64):
self._mode = "F"
- # rawmode = "F" if number_of_bits == -32 else "F;64F"
offset = math.ceil(self.fp.tell() / 2880) * 2880
self.tile = [("raw", (0, 0) + self.size, offset, (self.mode, 0, -1))]
diff --git a/src/PIL/FontFile.py b/src/PIL/FontFile.py
index 5ec0a6632..085917ac3 100644
--- a/src/PIL/FontFile.py
+++ b/src/PIL/FontFile.py
@@ -78,7 +78,6 @@ class FontFile:
if glyph:
d, dst, src, im = glyph
xx = src[2] - src[0]
- # yy = src[3] - src[1]
x0, y0 = x, y
x = x + xx
if x > WIDTH:
diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py
index a878cbfd2..3027ef45b 100644
--- a/src/PIL/FpxImagePlugin.py
+++ b/src/PIL/FpxImagePlugin.py
@@ -227,6 +227,7 @@ class FpxImageFile(ImageFile.ImageFile):
break # isn't really required
self.stream = stream
+ self._fp = self.fp
self.fp = None
def load(self):
diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py
index 4ce295f7f..0793d4b42 100644
--- a/src/PIL/GifImagePlugin.py
+++ b/src/PIL/GifImagePlugin.py
@@ -281,14 +281,9 @@ class GifImageFile(ImageFile.ImageFile):
bits = self.fp.read(1)[0]
self.__offset = self.fp.tell()
break
-
- else:
- pass
- # raise OSError, "illegal GIF tag `%x`" % s[0]
s = None
if interlace is None:
- # self._fp = None
msg = "image not found in GIF frame"
raise EOFError(msg)
@@ -661,7 +656,7 @@ def _save(im, fp, filename, save_all=False):
palette = im.encoderinfo.get("palette", im.info.get("palette"))
else:
palette = None
- im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
+ im.encoderinfo.setdefault("optimize", True)
if not save_all or not _write_multiple_frames(im, fp, palette):
_write_single_frame(im, fp, palette)
diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py
index 0aa4f7a84..b415a3219 100644
--- a/src/PIL/IcnsImagePlugin.py
+++ b/src/PIL/IcnsImagePlugin.py
@@ -391,8 +391,8 @@ if __name__ == "__main__":
with open(sys.argv[1], "rb") as fp:
imf = IcnsImageFile(fp)
for size in imf.info["sizes"]:
- imf.size = size
- imf.save("out-%s-%s-%s.png" % size)
+ width, height, scale = imf.size = size
+ imf.save(f"out-{width}-{height}-{scale}.png")
with Image.open(sys.argv[1]) as im:
im.save("out.png")
if sys.platform == "windows":
diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py
index 0445a2ab2..7f0f0047c 100644
--- a/src/PIL/IcoImagePlugin.py
+++ b/src/PIL/IcoImagePlugin.py
@@ -174,9 +174,7 @@ class IcoFile:
self.entry = sorted(self.entry, key=lambda x: x["color_depth"])
# ICO images are usually squares
- # self.entry = sorted(self.entry, key=lambda x: x['width'])
- self.entry = sorted(self.entry, key=lambda x: x["square"])
- self.entry.reverse()
+ self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True)
def sizes(self):
"""
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index 621ac5a4e..3f0863b18 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -42,7 +42,7 @@ from pathlib import Path
from typing import NamedTuple
try:
- import defusedxml.ElementTree as ElementTree
+ from defusedxml import ElementTree
except ImportError:
ElementTree = None
@@ -558,16 +558,17 @@ class Image:
:py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for
more information.
"""
- try:
- if getattr(self, "_fp", False):
- if self._fp != self.fp:
- self._fp.close()
- self._fp = DeferredError(ValueError("Operation on closed image"))
- if self.fp:
- self.fp.close()
- self.fp = None
- except Exception as msg:
- logger.debug("Error closing: %s", msg)
+ if hasattr(self, "fp"):
+ try:
+ if getattr(self, "_fp", False):
+ if self._fp != self.fp:
+ self._fp.close()
+ self._fp = DeferredError(ValueError("Operation on closed image"))
+ if self.fp:
+ self.fp.close()
+ self.fp = None
+ except Exception as msg:
+ logger.debug("Error closing: %s", msg)
if getattr(self, "map", None):
self.map = None
@@ -801,6 +802,9 @@ class Image:
but loads data into this image instead of creating a new image object.
"""
+ if self.width == 0 or self.height == 0:
+ return
+
# may pass tuple instead of argument list
if len(args) == 1 and isinstance(args[0], tuple):
args = args[0]
@@ -1166,7 +1170,7 @@ class Image:
if palette.mode != "P":
msg = "bad mode for palette image"
raise ValueError(msg)
- if self.mode != "RGB" and self.mode != "L":
+ if self.mode not in {"RGB", "L"}:
msg = "only RGB or L mode images can be quantized to a palette"
raise ValueError(msg)
im = self.im.convert("P", dither, palette.im)
@@ -2977,15 +2981,16 @@ def frombytes(mode, size, data, decoder_name="raw", *args):
_check_size(size)
- # may pass tuple instead of argument list
- if len(args) == 1 and isinstance(args[0], tuple):
- args = args[0]
-
- if decoder_name == "raw" and args == ():
- args = mode
-
im = new(mode, size)
- im.frombytes(data, decoder_name, args)
+ if im.width != 0 and im.height != 0:
+ # may pass tuple instead of argument list
+ if len(args) == 1 and isinstance(args[0], tuple):
+ args = args[0]
+
+ if decoder_name == "raw" and args == ():
+ args = mode
+
+ im.frombytes(data, decoder_name, args)
return im
@@ -3106,7 +3111,8 @@ def fromarray(obj, mode=None):
try:
mode, rawmode = _fromarray_typemap[typekey]
except KeyError as e:
- msg = "Cannot handle this data type: %s, %s" % typekey
+ typekey_shape, typestr = typekey
+ msg = f"Cannot handle this data type: {typekey_shape}, {typestr}"
raise TypeError(msg) from e
else:
rawmode = mode
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
index fbf320d72..6509d4c8e 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -921,7 +921,7 @@ def floodfill(image, xy, value, border=None, thresh=0):
if border is None:
fill = _color_diff(p, background) <= thresh
else:
- fill = p != value and p != border
+ fill = p not in (value, border)
if fill:
pixel[s, t] = value
new_edge.add((s, t))
diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py
index 98d3ad990..e8455d064 100644
--- a/src/PIL/ImageFile.py
+++ b/src/PIL/ImageFile.py
@@ -431,7 +431,6 @@ class Parser:
with io.BytesIO(self.data) as fp:
im = Image.open(fp)
except OSError:
- # traceback.print_exc()
pass # not enough data
else:
flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py
index 57268b8f5..c24f86ef3 100644
--- a/src/PIL/ImageFilter.py
+++ b/src/PIL/ImageFilter.py
@@ -222,7 +222,7 @@ class UnsharpMask(MultibandFilter):
.. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking
- """ # noqa: E501
+ """
name = "UnsharpMask"
diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py
index c29562135..0331a5c45 100644
--- a/src/PIL/ImageFont.py
+++ b/src/PIL/ImageFont.py
@@ -788,8 +788,13 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
.. versionadded:: 4.2.0
:return: A font object.
:exception OSError: If the file could not be read.
+ :exception ValueError: If the font size is not greater than zero.
"""
+ if size <= 0:
+ msg = "font size must be greater than 0"
+ raise ValueError(msg)
+
def freetype(font):
return FreeTypeFont(font, size, index, encoding, layout_engine)
diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py
index eb6bbe6c6..2c73acb97 100644
--- a/src/PIL/ImageMath.py
+++ b/src/PIL/ImageMath.py
@@ -239,7 +239,7 @@ def eval(expression, _dict={}, **kw):
args = ops.copy()
args.update(_dict)
args.update(kw)
- for k, v in list(args.items()):
+ for k, v in args.items():
if hasattr(v, "im"):
args[k] = _Operand(v)
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
index 6d70f0248..4f83a4edb 100644
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -56,7 +56,7 @@ def _lut(image, lut):
lut = lut + lut + lut
return image.point(lut)
else:
- msg = "not supported for this image mode"
+ msg = f"not supported for mode {image.mode}"
raise OSError(msg)
diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py
index 1ba50b5ec..cb4f1dba1 100644
--- a/src/PIL/ImagePalette.py
+++ b/src/PIL/ImagePalette.py
@@ -257,8 +257,6 @@ def load(filename):
if lut:
break
except (SyntaxError, ValueError):
- # import traceback
- # traceback.print_exc()
pass
else:
msg = "cannot load palette"
diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py
index 9b7245454..d017565a9 100644
--- a/src/PIL/ImageQt.py
+++ b/src/PIL/ImageQt.py
@@ -83,16 +83,6 @@ def fromqimage(im):
def fromqpixmap(im):
return fromqimage(im)
- # buffer = QBuffer()
- # buffer.open(QIODevice.ReadWrite)
- # # im.save(buffer)
- # # What if png doesn't support some image features like animation?
- # im.save(buffer, 'ppm')
- # bytes_io = BytesIO()
- # bytes_io.write(buffer.data())
- # buffer.close()
- # bytes_io.seek(0)
- # return Image.open(bytes_io)
def align8to32(bytes, width, mode):
@@ -208,9 +198,5 @@ def toqimage(im):
def toqpixmap(im):
- # # This doesn't work. For now using a dumb approach.
- # im_data = _toqclass_helper(im)
- # result = QPixmap(im_data["size"][0], im_data["size"][1])
- # result.loadFromData(im_data["data"])
qimage = toqimage(im)
return QPixmap.fromImage(qimage)
diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py
index 316cd17c7..3a40cf987 100644
--- a/src/PIL/IptcImagePlugin.py
+++ b/src/PIL/IptcImagePlugin.py
@@ -18,10 +18,9 @@ import os
import tempfile
from . import Image, ImageFile
-from ._binary import i8
+from ._binary import i8, o8
from ._binary import i16be as i16
from ._binary import i32be as i32
-from ._binary import o8
COMPRESSION = {1: "raw", 5: "jpeg"}
diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py
index 963d6c1a3..bb0cb676a 100644
--- a/src/PIL/Jpeg2KImagePlugin.py
+++ b/src/PIL/Jpeg2KImagePlugin.py
@@ -334,10 +334,7 @@ def _save(im, fp, filename):
if quality_layers is not None and not (
isinstance(quality_layers, (list, tuple))
and all(
- [
- isinstance(quality_layer, (int, float))
- for quality_layer in quality_layers
- ]
+ isinstance(quality_layer, (int, float)) for quality_layer in quality_layers
)
):
msg = "quality_layers must be a sequence of numbers"
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
index c091697f5..b8a5e7a59 100644
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -397,7 +397,7 @@ class JpegImageFile(ImageFile.ImageFile):
# self.__offset = self.fp.tell()
break
s = self.fp.read(1)
- elif i == 0 or i == 0xFFFF:
+ elif i in {0, 0xFFFF}:
# padded marker or junk; move on
s = b"\xff"
elif i == 0xFF00: # Skip extraneous data (escaped 0xFF)
@@ -787,6 +787,8 @@ def _save(im, fp, filename):
dpi[0],
dpi[1],
subsampling,
+ info.get("restart_marker_blocks", 0),
+ info.get("restart_marker_rows", 0),
qtables,
comment,
extra,
diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py
index 801318930..e4154902f 100644
--- a/src/PIL/MicImagePlugin.py
+++ b/src/PIL/MicImagePlugin.py
@@ -66,6 +66,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
self._n_frames = len(self.images)
self.is_animated = self._n_frames > 1
+ self.__fp = self.fp
self.seek(0)
def seek(self, frame):
@@ -87,10 +88,12 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
return self.frame
def close(self):
+ self.__fp.close()
self.ole.close()
super().close()
def __exit__(self, *args):
+ self.__fp.close()
self.ole.close()
super().__exit__()
diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py
index f9261c77d..89083b4ff 100644
--- a/src/PIL/MpoImagePlugin.py
+++ b/src/PIL/MpoImagePlugin.py
@@ -33,9 +33,6 @@ from . import (
from ._binary import i16be as i16
from ._binary import o32le
-# def _accept(prefix):
-# return JpegImagePlugin._accept(prefix)
-
def _save(im, fp, filename):
JpegImagePlugin._save(im, fp, filename)
diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py
index dc1012f54..8bdb65cce 100644
--- a/src/PIL/PdfParser.py
+++ b/src/PIL/PdfParser.py
@@ -82,7 +82,7 @@ class IndirectReference(
collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"])
):
def __str__(self):
- return "%s %s R" % self
+ return f"{self.object_id} {self.generation} R"
def __bytes__(self):
return self.__str__().encode("us-ascii")
@@ -103,7 +103,7 @@ class IndirectReference(
class IndirectObjectDef(IndirectReference):
def __str__(self):
- return "%s %s obj" % self
+ return f"{self.object_id} {self.generation} obj"
class XrefTable:
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
index 1bd0f442f..dbcdee1c2 100644
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -1156,6 +1156,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
encoderinfo["duration"] = duration
im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
+ if len(im_frames) == 1 and not default_image:
+ return im_frames[0]["im"]
+
# animation control
chunk(
fp,
@@ -1391,8 +1394,10 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
chunk(fp, b"eXIf", exif)
if save_all:
- _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
- else:
+ im = _write_multiple_frames(
+ im, fp, chunk, rawmode, default_image, append_images
+ )
+ if im:
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
if info:
diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py
index e480ab055..93f1528c5 100644
--- a/src/PIL/PpmImagePlugin.py
+++ b/src/PIL/PpmImagePlugin.py
@@ -328,9 +328,6 @@ def _save(im, fp, filename):
fp.write(b"65535\n")
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
- # ALTERNATIVE: save via builtin debug function
- # im._dump(filename)
-
#
# --------------------------------------------------------------------
diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py
index 99b46a4a6..24d30d2a6 100644
--- a/src/PIL/PyAccess.py
+++ b/src/PIL/PyAccess.py
@@ -244,7 +244,7 @@ class _PyAccessI16_L(PyAccess):
except TypeError:
color = min(color[0], 65535)
- pixel.l = color & 0xFF # noqa: E741
+ pixel.l = color & 0xFF
pixel.r = color >> 8
@@ -265,7 +265,7 @@ class _PyAccessI16_B(PyAccess):
except Exception:
color = min(color[0], 65535)
- pixel.l = color >> 8 # noqa: E741
+ pixel.l = color >> 8
pixel.r = color & 0xFF
diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py
index acb9ce5a3..a2a259c89 100644
--- a/src/PIL/SgiImagePlugin.py
+++ b/src/PIL/SgiImagePlugin.py
@@ -123,7 +123,7 @@ class SgiImageFile(ImageFile.ImageFile):
def _save(im, fp, filename):
- if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "L":
+ if im.mode not in {"RGB", "RGBA", "L"}:
msg = "Unsupported SGI image mode"
raise ValueError(msg)
@@ -155,7 +155,7 @@ def _save(im, fp, filename):
# Z Dimension: Number of channels
z = len(im.mode)
- if dim == 1 or dim == 2:
+ if dim in {1, 2}:
z = 1
# assert we've got the right number of bands.
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index dabf8dbfb..a78a5aef1 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -1885,13 +1885,14 @@ class AppendingTiffWriter:
8, # long8
]
- # StripOffsets = 273
- # FreeOffsets = 288
- # TileOffsets = 324
- # JPEGQTables = 519
- # JPEGDCTables = 520
- # JPEGACTables = 521
- Tags = {273, 288, 324, 519, 520, 521}
+ Tags = {
+ 273, # StripOffsets
+ 288, # FreeOffsets
+ 324, # TileOffsets
+ 519, # JPEGQTables
+ 520, # JPEGDCTables
+ 521, # JPEGACTables
+ }
def __init__(self, fn, new=False):
if hasattr(fn, "read"):
@@ -1941,8 +1942,6 @@ class AppendingTiffWriter:
iimm = self.f.read(4)
if not iimm:
- # msg = "nothing written into new page"
- # raise RuntimeError(msg)
# Make it easy to finish a frame without committing to a new one.
return
diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py
index 30b05e4e1..b02c637b6 100644
--- a/src/PIL/TiffTags.py
+++ b/src/PIL/TiffTags.py
@@ -56,7 +56,7 @@ def lookup(tag, group=None):
##
# Map tag numbers to tag info.
#
-# id: (Name, Type, Length, enum_values)
+# id: (Name, Type, Length[, enum_values])
#
# The length here differs from the length in the tiff spec. For
# numbers, the tiff spec is for the number of fields returned. We
@@ -427,7 +427,7 @@ def _populate():
TAGS_V2[k] = TagInfo(k, *v)
- for group, tags in TAGS_V2_GROUPS.items():
+ for tags in TAGS_V2_GROUPS.values():
for k, v in tags.items():
tags[k] = TagInfo(k, *v)
@@ -438,22 +438,6 @@ _populate()
TYPES = {}
-# was:
-# TYPES = {
-# 1: "byte",
-# 2: "ascii",
-# 3: "short",
-# 4: "long",
-# 5: "rational",
-# 6: "signed byte",
-# 7: "undefined",
-# 8: "signed short",
-# 9: "signed long",
-# 10: "signed rational",
-# 11: "float",
-# 12: "double",
-# }
-
#
# These tags are handled by default in libtiff, without
# adding to the custom dictionary. From tif_dir.c, searching for
diff --git a/src/_imagingft.c b/src/_imagingft.c
index 64175de8b..7849c821d 100644
--- a/src/_imagingft.c
+++ b/src/_imagingft.c
@@ -885,7 +885,9 @@ font_render(FontObject *self, PyObject *args) {
PyMem_Del(glyph_info);
return NULL;
}
- id = PyLong_AsSsize_t(PyObject_GetAttrString(image, "id"));
+ PyObject *imageId = PyObject_GetAttrString(image, "id");
+ id = PyLong_AsSsize_t(imageId);
+ Py_XDECREF(imageId);
im = (Imaging)id;
x_offset -= stroke_width;
diff --git a/src/encode.c b/src/encode.c
index 08544aede..4664ad0f3 100644
--- a/src/encode.c
+++ b/src/encode.c
@@ -1045,6 +1045,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
Py_ssize_t streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */
Py_ssize_t xdpi = 0, ydpi = 0;
Py_ssize_t subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */
+ Py_ssize_t restart_marker_blocks = 0;
+ Py_ssize_t restart_marker_rows = 0;
PyObject *qtables = NULL;
unsigned int *qarrays = NULL;
int qtablesLen = 0;
@@ -1057,7 +1059,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
- "ss|nnnnnnnnOz#y#y#",
+ "ss|nnnnnnnnnnOz#y#y#",
&mode,
&rawmode,
&quality,
@@ -1068,6 +1070,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
&xdpi,
&ydpi,
&subsampling,
+ &restart_marker_blocks,
+ &restart_marker_rows,
&qtables,
&comment,
&comment_size,
@@ -1156,6 +1160,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype;
((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi;
((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi;
+ ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_blocks = restart_marker_blocks;
+ ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_rows = restart_marker_rows;
((JPEGENCODERSTATE *)encoder->state.context)->comment = comment;
((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size;
((JPEGENCODERSTATE *)encoder->state.context)->extra = extra;
diff --git a/src/libImaging/Jpeg.h b/src/libImaging/Jpeg.h
index 1d7550818..5cc74e69b 100644
--- a/src/libImaging/Jpeg.h
+++ b/src/libImaging/Jpeg.h
@@ -83,6 +83,10 @@ typedef struct {
/* Chroma Subsampling (-1=default, 0=none, 1=medium, 2=high) */
int subsampling;
+ /* Restart marker interval, in MCU blocks or MCU rows, or 0 for none */
+ unsigned int restart_marker_blocks;
+ unsigned int restart_marker_rows;
+
/* Converter input mode (input to the shuffler) */
char rawmode[8 + 1];
diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c
index 2a24eff39..9da830b18 100644
--- a/src/libImaging/JpegEncode.c
+++ b/src/libImaging/JpegEncode.c
@@ -210,6 +210,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
}
context->cinfo.smoothing_factor = context->smooth;
context->cinfo.optimize_coding = (boolean)context->optimize;
+ context->cinfo.restart_interval = context->restart_marker_blocks;
+ context->cinfo.restart_in_rows = context->restart_marker_rows;
if (context->xdpi > 0 && context->ydpi > 0) {
context->cinfo.write_JFIF_header = TRUE;
context->cinfo.density_unit = 1; /* dots per inch */
@@ -218,9 +220,9 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
}
switch (context->streamtype) {
case 1:
- /* tables only -- not yet implemented */
- state->errcode = IMAGING_CODEC_CONFIG;
- return -1;
+ /* tables only */
+ jpeg_write_tables(&context->cinfo);
+ goto cleanup;
case 2:
/* image only */
jpeg_suppress_tables(&context->cinfo, TRUE);
@@ -316,6 +318,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
}
jpeg_finish_compress(&context->cinfo);
+cleanup:
/* Clean up */
if (context->comment) {
free(context->comment);
diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c
index 35122f182..e3b81590e 100644
--- a/src/libImaging/TiffDecode.c
+++ b/src/libImaging/TiffDecode.c
@@ -14,6 +14,10 @@
#ifdef HAVE_LIBTIFF
+#ifdef HAVE_UNISTD_H
+#include /* lseek */
+#endif
+
#ifndef uint
#define uint uint32
#endif
diff --git a/src/libImaging/TiffDecode.h b/src/libImaging/TiffDecode.h
index c7c7d48ed..02454ba03 100644
--- a/src/libImaging/TiffDecode.h
+++ b/src/libImaging/TiffDecode.h
@@ -13,12 +13,6 @@
#include
#endif
-/* UNDONE -- what are we using from this? */
-/*#ifndef _UNISTD_H
- # include
- # endif
-*/
-
#ifndef min
#define min(x, y) ((x > y) ? y : x)
#define max(x, y) ((x < y) ? y : x)
diff --git a/wheels/README.md b/wheels/README.md
index c15c034b6..8b412b7fe 100644
--- a/wheels/README.md
+++ b/wheels/README.md
@@ -1,7 +1,11 @@
README
------
-This directory creates wheels for tagged versions of Pillow.
+[cibuildwheel](https://github.com/pypa/cibuildwheel) is used to build macOS and Linux
+wheels for tagged versions of Pillow.
+
+This directory contains [multibuild](https://github.com/multi-build/multibuild) to
+build dependencies for the wheels, and dependency licenses to be included.
Archives
--------
@@ -16,8 +20,8 @@ But, the build will look in that repository before downloading from the
URL, so if there is a library that often fails to download, or you think might
fail to download, then download it and add it to the Git repository.
-See the `pre_build` in `config.sh` and the `fetch_unpack` routine in
-`multibuild/common_utils.sh` for the logic, and the build recipes in
+See `build` in `.github/workflows/wheels-dependencies.sh` and the `fetch_unpack`
+routine in `multibuild/common_utils.sh` for the logic, and the build recipes in
`multibuild/library_builders.sh` for the filename to give to the downloaded
archive.
@@ -27,5 +31,5 @@ Wheels
Wheels are
[GitHub Actions artifacts created for tags, relevant changes or manual builds](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml).
-Windows wheels are not created here. Instead, they are
+Windows wheels are created separately. They are
[GitHub Actions artifacts created on each run of the Windows workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml?query=branch%3Amain).
diff --git a/wheels/config.sh b/wheels/config.sh
deleted file mode 100644
index f36b98f25..000000000
--- a/wheels/config.sh
+++ /dev/null
@@ -1,187 +0,0 @@
-# Define custom utilities
-# Test for macOS with [ -n "$IS_MACOS" ]
-
-ARCHIVE_SDIR=pillow-depends-main
-
-# Package versions for fresh source builds
-FREETYPE_VERSION=2.13.2
-HARFBUZZ_VERSION=8.2.1
-LIBPNG_VERSION=1.6.40
-JPEGTURBO_VERSION=3.0.1
-OPENJPEG_VERSION=2.5.0
-XZ_VERSION=5.4.4
-TIFF_VERSION=4.6.0
-LCMS2_VERSION=2.15
-if [[ -n "$IS_MACOS" ]]; then
- GIFLIB_VERSION=5.1.4
-else
- GIFLIB_VERSION=5.2.1
-fi
-if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then
- ZLIB_VERSION=1.3
-else
- ZLIB_VERSION=1.2.8
-fi
-LIBWEBP_VERSION=1.3.2
-BZIP2_VERSION=1.0.8
-LIBXCB_VERSION=1.16
-BROTLI_VERSION=1.1.0
-
-if [[ -n "$IS_MACOS" ]] && [[ "$PLAT" == "x86_64" ]]; then
- function build_openjpeg {
- local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${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 {
- local cmake=$(get_modern_cmake)
- local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz)
- (cd $out_dir \
- && $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
-}
-
-function pre_build {
- # Any stuff that you need to do before you start building the wheels
- # Runs in the root directory of this repository.
- curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
- untar pillow-depends-main.zip
-
- build_xz
- if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
- yum remove -y zlib-devel
- fi
- build_new_zlib
-
- if [ -n "$IS_MACOS" ]; then
- ORIGINAL_BUILD_PREFIX=$BUILD_PREFIX
- ORIGINAL_PKG_CONFIG_PATH=$PKG_CONFIG_PATH
- BUILD_PREFIX=`dirname $(dirname $(which python))`
- PKG_CONFIG_PATH="$BUILD_PREFIX/lib/pkgconfig"
- fi
- build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto
- if [ -n "$IS_MACOS" ]; then
- build_simple xorgproto 2023.2 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
- cp venv/share/pkgconfig/xcb-proto.pc venv/lib/pkgconfig/xcb-proto.pc
- else
- 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
- if [ -n "$IS_MACOS" ]; then
- BUILD_PREFIX=$ORIGINAL_BUILD_PREFIX
- PKG_CONFIG_PATH=$ORIGINAL_PKG_CONFIG_PATH
- fi
-
- build_libjpeg_turbo
- if [[ -n "$IS_MACOS" ]]; then
- rm /usr/local/lib/libjpeg.dylib
- fi
- build_tiff
- build_libpng
- build_lcms2
- build_openjpeg
-
- ORIGINAL_CFLAGS=$CFLAGS
- CFLAGS="$CFLAGS -O3 -DNDEBUG"
- if [[ -n "$IS_MACOS" ]]; then
- CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names"
- fi
- build_libwebp
- CFLAGS=$ORIGINAL_CFLAGS
-
- build_brotli
-
- 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 "$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
-
- # Append licenses
- for filename in wheels/dependency_licenses/*; do
- echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE
- cat $filename >> LICENSE
- done
-}
-
-function pip_wheel_cmd {
- local abs_wheelhouse=$1
- if [ -z "$IS_MACOS" ]; then
- CFLAGS="$CFLAGS --std=c99" # for Raqm
- fi
- python3 -m pip wheel $(pip_opts) \
- -C raqm=enable -C raqm=vendor -C fribidi=vendor \
- -w $abs_wheelhouse --no-deps .
-}
-
-function run_tests_in_repo {
- # Run Pillow tests from within source repo
- python3 selftest.py
- python3 -m pytest
-}
-
-EXP_CODECS="jpg jpg_2000 libtiff zlib"
-EXP_MODULES="freetype2 littlecms2 pil tkinter webp"
-EXP_FEATURES="fribidi harfbuzz libjpeg_turbo raqm transp_webp webp_anim webp_mux xcb"
-
-function run_tests {
- if [ -n "$IS_MACOS" ]; then
- brew install fribidi
- export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
- elif [ -n "$IS_ALPINE" ]; then
- apk add curl fribidi
- else
- apt-get update
- apt-get install -y curl libfribidi0 libopenblas-dev pkg-config unzip
- fi
- if [ -z "$IS_ALPINE" ]; then
- python3 -m pip install numpy
- fi
- python3 -m pip install defusedxml olefile pyroma
-
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
- untar pillow-test-images.zip
- mv test-images-main/* ../Tests/images
-
- # Runs tests on installed distribution from an empty directory
- (cd .. && run_tests_in_repo)
- # Test against expected codecs, modules and features
- local ret=0
- local codecs=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_codecs())))')
- if [ "$codecs" != "$EXP_CODECS" ]; then
- echo "Codecs should be: '$EXP_CODECS'; but are '$codecs'"
- ret=1
- fi
- local modules=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_modules())))')
- if [ "$modules" != "$EXP_MODULES" ]; then
- echo "Modules should be: '$EXP_MODULES'; but are '$modules'"
- ret=1
- fi
- local features=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_features())))')
- if [ "$features" != "$EXP_FEATURES" ]; then
- echo "Features should be: '$EXP_FEATURES'; but are '$features'"
- ret=1
- fi
- return $ret
-}
diff --git a/wheels/dependency_licenses/FREETYPE2.txt b/wheels/dependency_licenses/FREETYPE2.txt
index cca8d8ce1..93efc6126 100644
--- a/wheels/dependency_licenses/FREETYPE2.txt
+++ b/wheels/dependency_licenses/FREETYPE2.txt
@@ -38,3 +38,615 @@ the 'Old MIT' license, compatible to the above two licenses.
The MD5 checksum support (only used for debugging in development
builds) is in the public domain.
+
+--------------------------------------------------------------------------
+
+ The FreeType Project LICENSE
+ ----------------------------
+
+ 2006-Jan-27
+
+ Copyright 1996-2002, 2006 by
+ David Turner, Robert Wilhelm, and Werner Lemberg
+
+
+
+Introduction
+============
+
+ The FreeType Project is distributed in several archive packages;
+ some of them may contain, in addition to the FreeType font engine,
+ various tools and contributions which rely on, or relate to, the
+ FreeType Project.
+
+ This license applies to all files found in such packages, and
+ which do not fall under their own explicit license. The license
+ affects thus the FreeType font engine, the test programs,
+ documentation and makefiles, at the very least.
+
+ This license was inspired by the BSD, Artistic, and IJG
+ (Independent JPEG Group) licenses, which all encourage inclusion
+ and use of free software in commercial and freeware products
+ alike. As a consequence, its main points are that:
+
+ o We don't promise that this software works. However, we will be
+ interested in any kind of bug reports. (`as is' distribution)
+
+ o You can use this software for whatever you want, in parts or
+ full form, without having to pay us. (`royalty-free' usage)
+
+ o You may not pretend that you wrote this software. If you use
+ it, or only parts of it, in a program, you must acknowledge
+ somewhere in your documentation that you have used the
+ FreeType code. (`credits')
+
+ We specifically permit and encourage the inclusion of this
+ software, with or without modifications, in commercial products.
+ We disclaim all warranties covering The FreeType Project and
+ assume no liability related to The FreeType Project.
+
+
+ Finally, many people asked us for a preferred form for a
+ credit/disclaimer to use in compliance with this license. We thus
+ encourage you to use the following text:
+
+ """
+ Portions of this software are copyright © The FreeType
+ Project (www.freetype.org). All rights reserved.
+ """
+
+ Please replace with the value from the FreeType version you
+ actually use.
+
+
+Legal Terms
+===========
+
+0. Definitions
+--------------
+
+ Throughout this license, the terms `package', `FreeType Project',
+ and `FreeType archive' refer to the set of files originally
+ distributed by the authors (David Turner, Robert Wilhelm, and
+ Werner Lemberg) as the `FreeType Project', be they named as alpha,
+ beta or final release.
+
+ `You' refers to the licensee, or person using the project, where
+ `using' is a generic term including compiling the project's source
+ code as well as linking it to form a `program' or `executable'.
+ This program is referred to as `a program using the FreeType
+ engine'.
+
+ This license applies to all files distributed in the original
+ FreeType Project, including all source code, binaries and
+ documentation, unless otherwise stated in the file in its
+ original, unmodified form as distributed in the original archive.
+ If you are unsure whether or not a particular file is covered by
+ this license, you must contact us to verify this.
+
+ The FreeType Project is copyright (C) 1996-2000 by David Turner,
+ Robert Wilhelm, and Werner Lemberg. All rights reserved except as
+ specified below.
+
+1. No Warranty
+--------------
+
+ THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO
+ USE, OF THE FREETYPE PROJECT.
+
+2. Redistribution
+-----------------
+
+ This license grants a worldwide, royalty-free, perpetual and
+ irrevocable right and license to use, execute, perform, compile,
+ display, copy, create derivative works of, distribute and
+ sublicense the FreeType Project (in both source and object code
+ forms) and derivative works thereof for any purpose; and to
+ authorize others to exercise some or all of the rights granted
+ herein, subject to the following conditions:
+
+ o Redistribution of source code must retain this license file
+ (`FTL.TXT') unaltered; any additions, deletions or changes to
+ the original files must be clearly indicated in accompanying
+ documentation. The copyright notices of the unaltered,
+ original files must be preserved in all copies of source
+ files.
+
+ o Redistribution in binary form must provide a disclaimer that
+ states that the software is based in part of the work of the
+ FreeType Team, in the distribution documentation. We also
+ encourage you to put an URL to the FreeType web page in your
+ documentation, though this isn't mandatory.
+
+ These conditions apply to any software derived from or based on
+ the FreeType Project, not just the unmodified files. If you use
+ our work, you must acknowledge us. However, no fee need be paid
+ to us.
+
+3. Advertising
+--------------
+
+ Neither the FreeType authors and contributors nor you shall use
+ the name of the other for commercial, advertising, or promotional
+ purposes without specific prior written permission.
+
+ We suggest, but do not require, that you use one or more of the
+ following phrases to refer to this software in your documentation
+ or advertising materials: `FreeType Project', `FreeType Engine',
+ `FreeType library', or `FreeType Distribution'.
+
+ As you have not signed this license, you are not required to
+ accept it. However, as the FreeType Project is copyrighted
+ material, only this license, or another one contracted with the
+ authors, grants you the right to use, distribute, and modify it.
+ Therefore, by using, distributing, or modifying the FreeType
+ Project, you indicate that you understand and accept all the terms
+ of this license.
+
+4. Contacts
+-----------
+
+ There are two mailing lists related to FreeType:
+
+ o freetype@nongnu.org
+
+ Discusses general use and applications of FreeType, as well as
+ future and wanted additions to the library and distribution.
+ If you are looking for support, start in this list if you
+ haven't found anything to help you in the documentation.
+
+ o freetype-devel@nongnu.org
+
+ Discusses bugs, as well as engine internals, design issues,
+ specific licenses, porting, etc.
+
+ Our home page can be found at
+
+ https://www.freetype.org
+
+
+--- end of FTL.TXT ---
+
+--------------------------------------------------------------------------
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
+
+--------------------------------------------------------------------------
+
+The following license details are part of `src/bdf/README`:
+
+```
+License
+*******
+
+Copyright (C) 2001-2002 by Francesco Zappa Nardelli
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*** Portions of the driver (that is, bdflib.c and bdf.h):
+
+Copyright 2000 Computing Research Labs, New Mexico State University
+Copyright 2001-2002, 2011 Francesco Zappa Nardelli
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE COMPUTING RESEARCH LAB OR NEW MEXICO STATE UNIVERSITY BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
+OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
+THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+Credits
+*******
+
+This driver is based on excellent Mark Leisher's bdf library. If you
+find something good in this driver you should probably thank him, not
+me.
+```
+
+The following license details are part of `src/pcf/README`:
+
+```
+License
+*******
+
+Copyright (C) 2000 by Francesco Zappa Nardelli
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+Credits
+*******
+
+Keith Packard wrote the pcf driver found in XFree86. His work is at
+the same time the specification and the sample implementation of the
+PCF format. Undoubtedly, this driver is inspired from his work.
+```
diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py
index a1f1755be..c5c9441d4 100644
--- a/winbuild/build_prepare.py
+++ b/winbuild/build_prepare.py
@@ -118,6 +118,12 @@ DEPS = {
"(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n=========="
".+(libjpeg-turbo Licenses\n======================\n\n.+)$"
),
+ "patch": {
+ r"CMakeLists.txt": {
+ # libjpeg-turbo does not detect MSVC x86_arm64 cross-compiler correctly
+ 'if(MSVC_IDE AND CMAKE_GENERATOR_PLATFORM MATCHES "arm64")': "if({architecture} STREQUAL ARM64)", # noqa: E501
+ },
+ },
"build": [
*cmds_cmake(
("jpeg-static", "cjpeg-static", "djpeg-static"),
@@ -148,9 +154,9 @@ DEPS = {
"libs": [r"*.lib"],
},
"xz": {
- "url": SF_PROJECTS + "/lzmautils/files/xz-5.4.4.tar.gz/download",
- "filename": "xz-5.4.4.tar.gz",
- "dir": "xz-5.4.4",
+ "url": SF_PROJECTS + "/lzmautils/files/xz-5.4.5.tar.gz/download",
+ "filename": "xz-5.4.5.tar.gz",
+ "dir": "xz-5.4.5",
"license": "COPYING",
"build": [
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
@@ -213,7 +219,6 @@ DEPS = {
],
"headers": [r"libtiff\tiff*.h"],
"libs": [r"libtiff\*.lib"],
- # "bins": [r"libtiff\*.dll"],
},
"libpng": {
"url": SF_PROJECTS + "/libpng/files/libpng16/1.6.39/lpng1639.zip/download",
@@ -239,7 +244,7 @@ DEPS = {
"libs": ["*.lib"],
},
"freetype": {
- "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz", # noqa: E501
+ "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz",
"filename": "freetype-2.13.2.tar.gz",
"dir": "freetype-2.13.2",
"license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"],
@@ -272,7 +277,6 @@ DEPS = {
cmd_xcopy("include", "{inc_dir}"),
],
"libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],
- # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"],
},
"lcms2": {
"url": SF_PROJECTS + "/lcms/files/lcms/2.15/lcms2-2.15.tar.gz/download",
@@ -321,7 +325,7 @@ DEPS = {
},
"libimagequant": {
# commit: Merge branch 'master' into msvc (matches 2.17.0 tag)
- "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", # noqa: E501
+ "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
"filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
"dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab",
"license": "COPYRIGHT",
@@ -329,6 +333,8 @@ DEPS = {
"CMakeLists.txt": {
"if(OPENMP_FOUND)": "if(false)",
"install": "#install",
+ # libimagequant does not detect MSVC x86_arm64 cross-compiler correctly
+ "if(${{CMAKE_SYSTEM_PROCESSOR}} STREQUAL ARM64)": "if({architecture} STREQUAL ARM64)", # noqa: E501
}
},
"build": [
@@ -339,9 +345,9 @@ DEPS = {
"libs": [r"imagequant.lib"],
},
"harfbuzz": {
- "url": "https://github.com/harfbuzz/harfbuzz/archive/8.2.1.zip",
- "filename": "harfbuzz-8.2.1.zip",
- "dir": "harfbuzz-8.2.1",
+ "url": "https://github.com/harfbuzz/harfbuzz/archive/8.3.0.zip",
+ "filename": "harfbuzz-8.3.0.zip",
+ "dir": "harfbuzz-8.3.0",
"license": "COPYING",
"build": [
*cmds_cmake(
@@ -369,12 +375,17 @@ DEPS = {
# based on distutils._msvccompiler from CPython 3.7.4
-def find_msvs() -> dict[str, str] | None:
+def find_msvs(architecture: str) -> dict[str, str] | None:
root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
if not root:
print("Program Files not found")
return None
+ if architecture == "ARM64":
+ tools = "Microsoft.VisualStudio.Component.VC.Tools.ARM64"
+ else:
+ tools = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"
+
try:
vspath = (
subprocess.check_output(
@@ -385,7 +396,7 @@ def find_msvs() -> dict[str, str] | None:
"-latest",
"-prerelease",
"-requires",
- "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ tools,
"-property",
"installationPath",
"-products",
@@ -471,7 +482,7 @@ def extract_dep(url: str, filename: str) -> None:
msg = "Attempted Path Traversal in Zip File"
raise RuntimeError(msg)
zf.extractall(sources_dir)
- elif filename.endswith(".tar.gz") or filename.endswith(".tgz"):
+ elif filename.endswith((".tar.gz", ".tgz")):
with tarfile.open(file, "r:gz") as tgz:
for member in tgz.getnames():
member_abspath = os.path.abspath(os.path.join(sources_dir, member))
@@ -575,14 +586,19 @@ def build_dep(name: str) -> str:
def build_dep_all() -> None:
lines = [r'call "{build_dir}\build_env.cmd"']
+ gha_groups = "GITHUB_ACTIONS" in os.environ
for dep_name in DEPS:
print()
if dep_name in disabled:
print(f"Skipping disabled dependency {dep_name}")
continue
script = build_dep(dep_name)
+ if gha_groups:
+ lines.append(f"@echo ::group::Running {script}")
lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"')
lines.append("if errorlevel 1 echo Build failed! && exit /B 1")
+ if gha_groups:
+ lines.append("@echo ::endgroup::")
print()
lines.append("@echo All Pillow dependencies built successfully!")
write_script("build_dep_all.cmd", lines)
@@ -656,7 +672,7 @@ if __name__ == "__main__":
arch_prefs = ARCHITECTURES[args.architecture]
print("Target architecture:", args.architecture)
- msvs = find_msvs()
+ msvs = find_msvs(args.architecture)
if msvs is None:
msg = "Visual Studio not found. Please install Visual Studio 2017 or newer."
raise RuntimeError(msg)
@@ -691,6 +707,11 @@ if __name__ == "__main__":
disabled += ["libimagequant"]
if args.no_fribidi:
disabled += ["fribidi"]
+ elif args.architecture == "ARM64" and platform.machine() != "ARM64":
+ import warnings
+
+ warnings.warn("Cross-compiling FriBiDi is currently not supported, disabling")
+ disabled += ["fribidi"]
prefs = {
"architecture": args.architecture,