diff --git a/.ci/install.sh b/.ci/install.sh
index cd9035f6a..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
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/.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 ec22a8184..eb27b4bf7 100644
--- a/.github/workflows/test-docker.yml
+++ b/.github/workflows/test-docker.yml
@@ -52,6 +52,7 @@ jobs:
debian-12-bookworm-x86,
debian-12-bookworm-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 fd70545ce..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,6 +59,7 @@ 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"
@@ -71,10 +72,10 @@ jobs:
- 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 a6b1c6300..8b2dc06ae 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -9,7 +9,6 @@ repos:
rev: 23.10.1
hooks:
- id: black
- args: [--target-version=py38]
- repo: https://github.com/PyCQA/bandit
rev: 1.7.5
diff --git a/.travis.yml b/.travis.yml
index 503f243e6..8f8250809 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,10 +2,8 @@ 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
@@ -15,120 +13,39 @@ 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 0f1e419aa..251917654 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,6 +5,24 @@ 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]
diff --git a/Makefile b/Makefile
index b7f07e24d..ad0a1adab 100644
--- a/Makefile
+++ b/Makefile
@@ -118,6 +118,6 @@ lint:
.PHONY: lint-fix
lint-fix:
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
- python3 -m black --target-version py38 .
+ 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 d94b1985b..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) # noqa: UP031
+ 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/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 039eb33d1..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
@@ -999,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)
@@ -1010,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 fd5531cd8..f19fb7690 100644
--- a/Tests/test_imagefont.py
+++ b/Tests/test_imagefont.py
@@ -1082,3 +1082,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_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/docs/conf.py b/docs/conf.py
index ef2cb5b88..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,10 +319,6 @@ 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/.*",
r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg",
diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst
index 3cf5ad765..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
diff --git a/docs/installation.rst b/docs/installation.rst
index ab15fe643..78900aa57 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -1,6 +1,14 @@
Installation
============
+.. raw:: html
+
+
+
Warnings
--------
@@ -151,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
@@ -176,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**,
@@ -463,6 +469,8 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| 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 |
@@ -574,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/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 59d8da44e..f9cea0612 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -47,6 +47,12 @@ docs = [
"sphinx-removed-in",
"sphinxext-opengraph",
]
+fpx = [
+ "olefile",
+]
+mic = [
+ "olefile",
+]
tests = [
"check-manifest",
"coverage",
@@ -59,6 +65,9 @@ tests = [
"pytest-cov",
"pytest-timeout",
]
+xmp = [
+ "defusedxml",
+]
[project.urls]
Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst"
Documentation = "https://pillow.readthedocs.io"
@@ -77,10 +86,17 @@ 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]
-target-version = "py38"
line-length = 88
select = [
+ "C4", # flake8-comprehensions
"E", # pycodestyle errors
"EM", # flake8-errmsg
"F", # pyflakes errors
diff --git a/setup.py b/setup.py
index f13f03713..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:
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 63369eb64..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) # noqa: UP031
+ 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 5226c986d..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) # noqa: UP031
+ 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 930ca060b..2853bd596 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -40,7 +40,7 @@ from enum import IntEnum
from pathlib import Path
try:
- import defusedxml.ElementTree as ElementTree
+ from defusedxml import ElementTree
except ImportError:
ElementTree = None
@@ -549,16 +549,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
@@ -1159,7 +1160,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)
@@ -3100,7 +3101,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 # noqa: UP031
+ 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 40eed46bc..098a317fe 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -923,7 +923,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 8432a187f..902e8ce5f 100644
--- a/src/PIL/ImageFile.py
+++ b/src/PIL/ImageFile.py
@@ -430,7 +430,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/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/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/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 07df577ae..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 # noqa: UP031
+ 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 # noqa: UP031
+ return f"{self.object_id} {self.generation} obj"
class XrefTable:
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/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 e2a7927b7..71fac0264 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 4c47db1fb..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",
@@ -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",
@@ -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,