diff --git a/.ci/install.sh b/.ci/install.sh
index acb84f046..52b821417 100755
--- a/.ci/install.sh
+++ b/.ci/install.sh
@@ -13,24 +13,21 @@ aptget_update()
return 1
fi
}
-if [[ $(uname) != CYGWIN* ]]; then
- aptget_update || aptget_update retry || aptget_update retry
-fi
+aptget_update || aptget_update retry || aptget_update retry
set -e
-if [[ $(uname) != CYGWIN* ]]; then
- sudo apt-get -qq install libfreetype6-dev liblcms2-dev libtiff-dev python3-tk\
- ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
- cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
- sway wl-clipboard libopenblas-dev nasm
-fi
+sudo apt-get -qq install libfreetype6-dev liblcms2-dev libtiff-dev python3-tk\
+ ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
+ cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
+ sway wl-clipboard libopenblas-dev nasm
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade wheel
python3 -m pip install coverage
python3 -m pip install defusedxml
python3 -m pip install ipython
+python3 -m pip install numpy
python3 -m pip install olefile
python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
@@ -40,36 +37,24 @@ python3 -m pip install pyroma
# fails on beta 3.14 and PyPy
python3 -m pip install --only-binary=:all: pyarrow || true
-if [[ $(uname) != CYGWIN* ]]; then
- python3 -m pip install numpy
-
- # PyQt6 doesn't support PyPy3
- if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
- sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
- # TODO Update condition when pyqt6 supports free-threading
- if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi
- fi
-
- # Pyroma uses non-isolated build and fails with old setuptools
- if [[ $GHA_PYTHON_VERSION == 3.9 ]]; then
- # To match pyproject.toml
- python3 -m pip install "setuptools>=77"
- fi
-
- # webp
- pushd depends && ./install_webp.sh && popd
-
- # libimagequant
- pushd depends && ./install_imagequant.sh && popd
-
- # raqm
- pushd depends && ./install_raqm.sh && popd
-
- # libavif
- pushd depends && ./install_libavif.sh && popd
-
- # extra test images
- pushd depends && ./install_extra_test_images.sh && popd
-else
- cd depends && ./install_extra_test_images.sh && cd ..
+# PyQt6 doesn't support PyPy3
+if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
+ sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
+ # TODO Update condition when pyqt6 supports free-threading
+ if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi
fi
+
+# webp
+pushd depends && ./install_webp.sh && popd
+
+# libimagequant
+pushd depends && ./install_imagequant.sh && popd
+
+# raqm
+pushd depends && sudo ./install_raqm.sh && popd
+
+# libavif
+pushd depends && sudo ./install_libavif.sh && popd
+
+# extra test images
+pushd depends && ./install_extra_test_images.sh && popd
diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt
index 520b6e320..485866de6 100644
--- a/.ci/requirements-cibw.txt
+++ b/.ci/requirements-cibw.txt
@@ -1 +1 @@
-cibuildwheel==3.0.0
+cibuildwheel==3.3.0
diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt
index 44b5badab..5b0e2eaf8 100644
--- a/.ci/requirements-mypy.txt
+++ b/.ci/requirements-mypy.txt
@@ -1,10 +1,13 @@
-mypy==1.16.1
+mypy==1.19.0
+arro3-compute
+arro3-core
IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6
ipython
numpy
packaging
pyarrow-stubs
+pybind11
pytest
sphinx
types-atheris
diff --git a/.github/mergify.yml b/.github/mergify.yml
index 9bb089615..14222db10 100644
--- a/.github/mergify.yml
+++ b/.github/mergify.yml
@@ -8,7 +8,6 @@ pull_request_rules:
- status-success=Docker Test Successful
- status-success=Windows Test Successful
- status-success=MinGW
- - status-success=Cygwin Test Successful
actions:
merge:
method: merge
diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml
index 0456bbaba..6a86b8aeb 100644
--- a/.github/workflows/cifuzz.yml
+++ b/.github/workflows/cifuzz.yml
@@ -44,13 +44,13 @@ jobs:
language: python
dry-run: false
- name: Upload New Crash
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
- name: Upload Legacy Crash
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
if: steps.run.outcome == 'success'
with:
name: crash
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 626824f38..e88abf16f 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -32,12 +32,12 @@ jobs:
name: Docs
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: "3.x"
cache: pip
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 8e789a734..77d1d1caa 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -20,7 +20,7 @@ jobs:
name: Lint
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
persist-credentials: false
@@ -33,7 +33,7 @@ jobs:
lint-pre-commit-
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: "3.x"
cache: pip
diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh
index 94e3d5d08..b114d4a23 100755
--- a/.github/workflows/macos-install.sh
+++ b/.github/workflows/macos-install.sh
@@ -2,9 +2,6 @@
set -e
-if [[ "$ImageOS" == "macos13" ]]; then
- brew uninstall gradle maven
-fi
brew install \
aom \
dav1d \
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 61ccf58e2..1b0c3c654 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -22,7 +22,7 @@ jobs:
steps:
- name: "Check issues"
- uses: actions/stale@v9
+ uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
only-labels: "Awaiting OP Action"
diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml
deleted file mode 100644
index abfeaa77f..000000000
--- a/.github/workflows/test-cygwin.yml
+++ /dev/null
@@ -1,154 +0,0 @@
-name: Test Cygwin
-
-on:
- push:
- branches:
- - "**"
- paths-ignore:
- - ".github/workflows/docs.yml"
- - ".github/workflows/wheels*"
- - ".gitmodules"
- - "docs/**"
- - "wheels/**"
- pull_request:
- paths-ignore:
- - ".github/workflows/docs.yml"
- - ".github/workflows/wheels*"
- - ".gitmodules"
- - "docs/**"
- - "wheels/**"
- workflow_dispatch:
-
-permissions:
- contents: read
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-env:
- COVERAGE_CORE: sysmon
-
-jobs:
- build:
- runs-on: windows-latest
- strategy:
- fail-fast: false
- matrix:
- python-minor-version: [9]
-
- timeout-minutes: 40
-
- name: Python 3.${{ matrix.python-minor-version }}
-
- steps:
- - name: Fix line endings
- run: |
- git config --global core.autocrlf input
-
- - name: Checkout Pillow
- uses: actions/checkout@v4
- with:
- persist-credentials: false
-
- - name: Install Cygwin
- uses: cygwin/cygwin-install-action@v5
- with:
- packages: >
- gcc-g++
- ghostscript
- git
- ImageMagick
- jpeg
- libfreetype-devel
- libimagequant-devel
- libjpeg-devel
- liblapack-devel
- liblcms2-devel
- libopenjp2-devel
- libraqm-devel
- libtiff-devel
- libwebp-devel
- libxcb-devel
- libxcb-xinerama0
- make
- netpbm
- perl
- python3${{ matrix.python-minor-version }}-cython
- python3${{ matrix.python-minor-version }}-devel
- python3${{ matrix.python-minor-version }}-ipython
- python3${{ matrix.python-minor-version }}-numpy
- python3${{ matrix.python-minor-version }}-sip
- python3${{ matrix.python-minor-version }}-tkinter
- wget
- xorg-server-extra
- zlib-devel
-
- - name: Add Lapack to PATH
- uses: egor-tensin/cleanup-path@v4
- with:
- dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
-
- - name: Select Python version
- run: |
- ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
-
- - name: pip cache
- uses: actions/cache@v4
- with:
- path: 'C:\cygwin\home\runneradmin\.cache\pip'
- key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }}
- restore-keys: |
- ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
-
- - name: Build system information
- run: |
- dash.exe -c "python3 .github/workflows/system-info.py"
-
- - name: Install dependencies
- run: |
- bash.exe .ci/install.sh
-
- - name: Build
- shell: bash.exe -eo pipefail -o igncr "{0}"
- run: |
- .ci/build.sh
-
- - name: Test
- run: |
- bash.exe xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh
-
- - name: Prepare to upload errors
- if: failure()
- run: |
- dash.exe -c "mkdir -p Tests/errors"
-
- - name: Upload errors
- uses: actions/upload-artifact@v4
- if: failure()
- with:
- name: errors
- path: Tests/errors
-
- - name: After success
- run: |
- bash.exe .ci/after_success.sh
- rm C:\cygwin\bin\bash.EXE
-
- - name: Upload coverage
- uses: codecov/codecov-action@v5
- with:
- files: ./coverage.xml
- flags: GHA_Cygwin
- name: Cygwin Python 3.${{ matrix.python-minor-version }}
- token: ${{ secrets.CODECOV_ORG_TOKEN }}
-
- success:
- permissions:
- contents: none
- needs: build
- runs-on: ubuntu-latest
- name: Cygwin Test Successful
- steps:
- - name: Success
- run: echo Cygwin Test Successful
diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml
index 0b90732eb..091edb222 100644
--- a/.github/workflows/test-docker.yml
+++ b/.github/workflows/test-docker.yml
@@ -47,8 +47,10 @@ jobs:
centos-stream-10-amd64,
debian-12-bookworm-x86,
debian-12-bookworm-amd64,
- fedora-41-amd64,
+ debian-13-trixie-x86,
+ debian-13-trixie-amd64,
fedora-42-amd64,
+ fedora-43-amd64,
gentoo,
ubuntu-22.04-jammy-amd64,
ubuntu-24.04-noble-amd64,
@@ -66,7 +68,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
persist-credentials: false
diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml
index 5a83c16c3..e247414c8 100644
--- a/.github/workflows/test-mingw.yml
+++ b/.github/workflows/test-mingw.yml
@@ -45,7 +45,7 @@ jobs:
steps:
- name: Checkout Pillow
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
persist-credentials: false
diff --git a/.github/workflows/test-valgrind-memory.yml b/.github/workflows/test-valgrind-memory.yml
index e6a5f6e77..bd244aa5a 100644
--- a/.github/workflows/test-valgrind-memory.yml
+++ b/.github/workflows/test-valgrind-memory.yml
@@ -41,7 +41,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
persist-credentials: false
diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml
index 8818b3b23..81cfb8456 100644
--- a/.github/workflows/test-valgrind.yml
+++ b/.github/workflows/test-valgrind.yml
@@ -39,7 +39,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
persist-credentials: false
diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml
index 6d8acc44f..c4d0fa046 100644
--- a/.github/workflows/test-windows.yml
+++ b/.github/workflows/test-windows.yml
@@ -35,11 +35,11 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"]
+ python-version: ["pypy3.11", "3.11", "3.12", "3.13", "3.14"]
architecture: ["x64"]
include:
# Test the oldest Python on 32-bit
- - { python-version: "3.9", architecture: "x86" }
+ - { python-version: "3.10", architecture: "x86" }
timeout-minutes: 45
@@ -47,19 +47,19 @@ jobs:
steps:
- name: Checkout Pillow
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
persist-credentials: false
- name: Checkout cached dependencies
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
persist-credentials: false
repository: python-pillow/pillow-depends
path: winbuild\depends
- name: Checkout extra test images
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
persist-credentials: false
repository: python-pillow/test-images
@@ -67,7 +67,7 @@ jobs:
# sets env: pythonLocation
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
@@ -97,8 +97,8 @@ jobs:
choco install nasm --no-progress
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
- choco install ghostscript --version=10.5.1 --no-progress
- echo "C:\Program Files\gs\gs10.05.1\bin" >> $env:GITHUB_PATH
+ choco install ghostscript --version=10.6.0 --no-progress
+ echo "C:\Program Files\gs\gs10.06.0\bin" >> $env:GITHUB_PATH
# Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images
@@ -216,7 +216,7 @@ jobs:
shell: bash
- name: Upload errors
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
if: failure()
with:
name: errors
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b4b516228..167faa239 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -42,7 +42,6 @@ jobs:
]
python-version: [
"pypy3.11",
- "pypy3.10",
"3.14t",
"3.14",
"3.13t",
@@ -50,29 +49,28 @@ jobs:
"3.12",
"3.11",
"3.10",
- "3.9",
]
include:
- - { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
- - { python-version: "3.10", PYTHONOPTIMIZE: 2 }
+ - { python-version: "3.12", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
+ - { python-version: "3.11", PYTHONOPTIMIZE: 2 }
# Free-threaded
- { python-version: "3.14t", disable-gil: true }
- { python-version: "3.13t", disable-gil: true }
- # M1 only available for 3.10+
- - { os: "macos-13", python-version: "3.9" }
+ # Intel
+ - { os: "macos-15-intel", python-version: "3.10" }
exclude:
- - { os: "macos-latest", python-version: "3.9" }
+ - { os: "macos-latest", python-version: "3.10" }
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
@@ -113,7 +111,7 @@ jobs:
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
- name: Register gcc problem matcher
- if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'"
+ if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.14'"
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
- name: Build
@@ -142,7 +140,7 @@ jobs:
mkdir -p Tests/errors
- name: Upload errors
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
if: failure()
with:
name: errors
diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh
index d761d93b6..07ea75a75 100755
--- a/.github/workflows/wheels-dependencies.sh
+++ b/.github/workflows/wheels-dependencies.sh
@@ -32,7 +32,6 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then
# or `build/deps/iphonesimulator`
WORKDIR=$(pwd)/build/$IOS_SDK
BUILD_PREFIX=$(pwd)/build/deps/$IOS_SDK
- PATCH_DIR=$(pwd)/patches/iOS
# GNU tooling insists on using aarch64 rather than arm64
if [[ $PLAT == "arm64" ]]; then
@@ -60,7 +59,7 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then
# on using the Xcode builder, which isn't very helpful for most of Pillow's
# dependencies. Therefore, we lean on the OSX configurations, plus CC, CFLAGS
# etc. to ensure the right sysroot is selected.
- HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO"
+ HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO -DENABLE_SHARED=NO"
# Meson needs to be pointed at a cross-platform configuration file
# This will be generated once CC etc. have been evaluated.
@@ -90,23 +89,29 @@ fi
ARCHIVE_SDIR=pillow-depends-main
-# Package versions for fresh source builds. Version numbers with "Patched"
-# annotations have a source code patch that is required for some platforms. If
-# you change those versions, ensure the patch is also updated.
-FREETYPE_VERSION=2.13.3
-HARFBUZZ_VERSION=11.2.1
-LIBPNG_VERSION=1.6.49
-JPEGTURBO_VERSION=3.1.1
-OPENJPEG_VERSION=2.5.3
+# Package versions for fresh source builds.
+if [[ -n "$IOS_SDK" ]]; then
+ FREETYPE_VERSION=2.13.3
+else
+ FREETYPE_VERSION=2.14.1
+fi
+HARFBUZZ_VERSION=12.2.0
+LIBPNG_VERSION=1.6.51
+JPEGTURBO_VERSION=3.1.2
+OPENJPEG_VERSION=2.5.4
XZ_VERSION=5.8.1
-TIFF_VERSION=4.7.0
+ZSTD_VERSION=1.5.7
+TIFF_VERSION=4.7.1
LCMS2_VERSION=2.17
-ZLIB_VERSION=1.3.1
-ZLIB_NG_VERSION=2.2.4
-LIBWEBP_VERSION=1.5.0 # Patched; next release won't need patching. See patch file.
+if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "aarch64" ]]; then
+ ZLIB_NG_VERSION=2.2.5
+else
+ ZLIB_NG_VERSION=2.3.1
+fi
+LIBWEBP_VERSION=1.6.0
BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0
-BROTLI_VERSION=1.1.0 # Patched; next release won't need patching. See patch file.
+BROTLI_VERSION=1.2.0
LIBAVIF_VERSION=1.3.0
function build_pkg_config {
@@ -145,18 +150,13 @@ function build_zlib_ng {
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
unset HOST_CONFIGURE_FLAGS
- build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat
+ if [[ "$ZLIB_NG_VERSION" == 2.2.5 ]]; then
+ build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat
+ else
+ build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --installnamedir=$BUILD_PREFIX/lib --zlib-compat
+ fi
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
-
- if [[ -n "$IS_MACOS" ]] && [[ -z "$IOS_SDK" ]]; then
- # Ensure that on macOS, the library name is an absolute path, not an
- # @rpath, so that delocate picks up the right library (and doesn't need
- # DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an
- # option to control the install_name. This isn't needed on iOS, as iOS
- # only builds the static library.
- install_name_tool -id $BUILD_PREFIX/lib/libz.1.dylib $BUILD_PREFIX/lib/libz.1.dylib
- fi
touch zlib-stamp
}
@@ -164,8 +164,8 @@ function build_brotli {
if [ -e brotli-stamp ]; then return; fi
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
(cd $out_dir \
- && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \
- && make install)
+ && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib -DCMAKE_MACOSX_BUNDLE=OFF $HOST_CMAKE_FLAGS . \
+ && make -j4 install)
touch brotli-stamp
}
@@ -186,30 +186,43 @@ function build_libavif {
python3 -m pip install meson ninja
- if [[ "$PLAT" == "x86_64" ]] || [ -n "$SANITIZER" ]; then
+ if ([[ "$PLAT" == "x86_64" ]] && [[ -z "$IOS_SDK" ]]) || [ -n "$SANITIZER" ]; then
build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
fi
local build_type=MinSizeRel
+ local build_shared=ON
local lto=ON
local libavif_cmake_flags
- if [ -n "$IS_MACOS" ]; then
+ if [[ -n "$IS_MACOS" ]]; then
lto=OFF
libavif_cmake_flags=(
-DCMAKE_C_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_CXX_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,-S,-x,-dead_strip_dylibs" \
)
+ if [[ -n "$IOS_SDK" ]]; then
+ build_shared=OFF
+ fi
else
if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "x86_64" ]]; then
build_type=Release
fi
libavif_cmake_flags=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now")
fi
+ if [[ -n "$IOS_SDK" ]] && [[ "$PLAT" == "x86_64" ]]; then
+ libavif_cmake_flags+=(-DAOM_TARGET_CPU=generic)
+ else
+ libavif_cmake_flags+=(
+ -DAVIF_CODEC_AOM_DECODE=OFF \
+ -DAVIF_CODEC_DAV1D=LOCAL
+ )
+ fi
local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz)
+
# CONFIG_AV1_HIGHBITDEPTH=0 is a flag for libaom (included as a subproject
# of libavif) that disables support for encoding high bit depth images.
(cd $out_dir \
@@ -217,30 +230,45 @@ function build_libavif {
-DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \
-DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib \
-DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \
- -DBUILD_SHARED_LIBS=ON \
+ -DBUILD_SHARED_LIBS=$build_shared \
-DAVIF_LIBSHARPYUV=LOCAL \
-DAVIF_LIBYUV=LOCAL \
-DAVIF_CODEC_AOM=LOCAL \
-DCONFIG_AV1_HIGHBITDEPTH=0 \
- -DAVIF_CODEC_AOM_DECODE=OFF \
- -DAVIF_CODEC_DAV1D=LOCAL \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=$lto \
-DCMAKE_C_VISIBILITY_PRESET=hidden \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden \
-DCMAKE_BUILD_TYPE=$build_type \
"${libavif_cmake_flags[@]}" \
- . \
- && make install)
+ $HOST_CMAKE_FLAGS . )
+
+ if [[ -n "$IOS_SDK" ]]; then
+ # libavif's CMake configuration generates a meson cross file... but it
+ # doesn't work for iOS cross-compilation. Copy in Pillow-generated
+ # meson-cross config to replace the cmake-generated version.
+ cp $WORKDIR/meson-cross.txt $out_dir/crossfile-apple.meson
+ fi
+
+ (cd $out_dir && make -j4 install)
+
touch libavif-stamp
}
+function build_zstd {
+ if [ -e zstd-stamp ]; then return; fi
+ local out_dir=$(fetch_unpack https://github.com/facebook/zstd/releases/download/v$ZSTD_VERSION/zstd-$ZSTD_VERSION.tar.gz)
+ (cd $out_dir \
+ && make -j4 install)
+ touch zstd-stamp
+}
+
function build {
build_xz
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
yum remove -y zlib-devel
fi
- if [[ -n "$IS_MACOS" ]] && [[ "$MACOSX_DEPLOYMENT_TARGET" == "10.10" || "$MACOSX_DEPLOYMENT_TARGET" == "10.13" ]]; then
- build_new_zlib
+ if [[ -n "$IS_MACOS" ]]; then
+ CFLAGS="$CFLAGS -headerpad_max_install_names" build_zlib_ng
else
build_zlib_ng
fi
@@ -265,13 +293,11 @@ function build {
--with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \
--disable-webp --disable-libdeflate --disable-zstd
else
+ build_zstd
build_tiff
fi
- if [[ -z "$IOS_SDK" ]]; then
- # Short term workaround; don't build libavif on iOS
- build_libavif
- fi
+ build_libavif
build_libpng
build_lcms2
build_openjpeg
@@ -280,7 +306,11 @@ function build {
if [[ -n "$IS_MACOS" ]]; then
webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names"
fi
- CFLAGS="$CFLAGS $webp_cflags" build_simple libwebp $LIBWEBP_VERSION \
+ webp_ldflags=""
+ if [[ -n "$IOS_SDK" ]]; then
+ webp_ldflags="$webp_ldflags -llzma -lz"
+ fi
+ CFLAGS="$CFLAGS $webp_cflags" LDFLAGS="$LDFLAGS $webp_ldflags" build_simple libwebp $LIBWEBP_VERSION \
https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \
--enable-libwebpmux --enable-libwebpdemux
@@ -288,6 +318,10 @@ function build {
if [[ -n "$IS_MACOS" ]]; then
# Custom freetype build
+ if [[ -z "$IOS_SDK" ]]; then
+ build_simple sed 4.9 https://mirrors.middlendian.com/gnu/sed
+ fi
+
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
else
build_freetype
@@ -380,6 +414,15 @@ fi
wrap_wheel_builder build
+# A safety catch for iOS. iOS can't use dynamic libraries, but clang will prefer
+# to link dynamic libraries to static libraries. The only way to reliably
+# prevent this is to not have dynamic libraries available in the first place.
+# The build process *shouldn't* generate any dylibs... but just in case, purge
+# any dylibs that *have* been installed into the build prefix directory.
+if [[ -n "$IOS_SDK" ]]; then
+ find "$BUILD_PREFIX" -name "*.dylib" -exec rm -rf {} \;
+fi
+
# Return to the project root to finish the build
popd > /dev/null
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index 52a3f2cdb..fb71ead37 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -39,6 +39,7 @@ concurrency:
cancel-in-progress: true
env:
+ EXPECTED_DISTS: 91
FORCE_COLOR: 1
jobs:
@@ -52,21 +53,21 @@ jobs:
include:
- name: "macOS 10.10 x86_64"
platform: macos
- os: macos-13
+ os: macos-15-intel
cibw_arch: x86_64
- build: "cp3{9,10,11}*"
+ build: "cp3{10,11}*"
macosx_deployment_target: "10.10"
- name: "macOS 10.13 x86_64"
platform: macos
- os: macos-13
+ os: macos-15-intel
cibw_arch: x86_64
- build: "cp3{12,13,14}*"
+ build: "cp3{12,13}*"
macosx_deployment_target: "10.13"
- name: "macOS 10.15 x86_64"
platform: macos
- os: macos-13
+ os: macos-15-intel
cibw_arch: x86_64
- build: "pp3*"
+ build: "{cp314,pp3}*"
macosx_deployment_target: "10.15"
- name: "macOS arm64"
platform: macos
@@ -77,22 +78,22 @@ jobs:
platform: linux
os: ubuntu-latest
cibw_arch: x86_64
+ manylinux: "manylinux2014"
- name: "manylinux_2_28 x86_64"
platform: linux
os: ubuntu-latest
cibw_arch: x86_64
build: "*manylinux*"
- manylinux: "manylinux_2_28"
- name: "manylinux2014 and musllinux aarch64"
platform: linux
os: ubuntu-24.04-arm
cibw_arch: aarch64
+ manylinux: "manylinux2014"
- name: "manylinux_2_28 aarch64"
platform: linux
os: ubuntu-24.04-arm
cibw_arch: aarch64
build: "*manylinux*"
- manylinux: "manylinux_2_28"
- name: "iOS arm64 device"
platform: ios
os: macos-latest
@@ -103,15 +104,15 @@ jobs:
cibw_arch: arm64_iphonesimulator
- name: "iOS x86_64 simulator"
platform: ios
- os: macos-13
+ os: macos-15-intel
cibw_arch: x86_64_iphonesimulator
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
persist-credentials: false
submodules: true
- - uses: actions/setup-python@v5
+ - uses: actions/setup-python@v6
with:
python-version: "3.x"
@@ -133,7 +134,7 @@ jobs:
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
- - uses: actions/upload-artifact@v4
+ - uses: actions/upload-artifact@v5
with:
name: dist-${{ matrix.name }}
path: ./wheelhouse/*.whl
@@ -153,18 +154,18 @@ jobs:
- cibw_arch: ARM64
os: windows-11-arm
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- name: Checkout extra test images
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
persist-credentials: false
repository: python-pillow/test-images
path: Tests\test-images
- - uses: actions/setup-python@v5
+ - uses: actions/setup-python@v6
with:
python-version: "3.x"
@@ -219,46 +220,64 @@ jobs:
shell: cmd
- name: Upload wheels
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: dist-windows-${{ matrix.cibw_arch }}
path: ./wheelhouse/*.whl
- name: Upload fribidi.dll
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v5
with:
name: fribidi-windows-${{ matrix.cibw_arch }}
path: winbuild\build\bin\fribidi*
sdist:
- if: github.event_name != 'schedule'
+ if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: "3.x"
- run: make sdist
- - uses: actions/upload-artifact@v4
+ - uses: actions/upload-artifact@v5
with:
name: dist-sdist
path: dist/*.tar.gz
+ count-dists:
+ needs: [build-native-wheels, windows, sdist]
+ runs-on: ubuntu-latest
+ name: Count dists
+ steps:
+ - uses: actions/download-artifact@v6
+ with:
+ pattern: dist-*
+ path: dist
+ merge-multiple: true
+ - name: "What did we get?"
+ run: |
+ ls -alR
+ echo "Number of dists, should be $EXPECTED_DISTS:"
+ files=$(ls dist 2>/dev/null | wc -l)
+ echo $files
+ [ "$files" -eq $EXPECTED_DISTS ] || exit 1
+
scientific-python-nightly-wheels-publish:
if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
- needs: [build-native-wheels, windows]
+ needs: count-dists
runs-on: ubuntu-latest
name: Upload wheels to scientific-python-nightly-wheels
steps:
- - uses: actions/download-artifact@v4
+ - uses: actions/download-artifact@v6
with:
- pattern: dist-*
+ pattern: dist-!(sdist)*
path: dist
merge-multiple: true
- name: Upload wheels to scientific-python-nightly-wheels
@@ -269,7 +288,7 @@ jobs:
pypi-publish:
if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
- needs: [build-native-wheels, windows, sdist]
+ needs: count-dists
runs-on: ubuntu-latest
name: Upload release to PyPI
environment:
@@ -278,7 +297,7 @@ jobs:
permissions:
id-token: write
steps:
- - uses: actions/download-artifact@v4
+ - uses: actions/download-artifact@v6
with:
pattern: dist-*
path: dist
diff --git a/.github/zizmor.yml b/.github/zizmor.yml
index 5bdc48c30..e60c79441 100644
--- a/.github/zizmor.yml
+++ b/.github/zizmor.yml
@@ -1,6 +1,8 @@
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
-# https://woodruffw.github.io/zizmor/configuration/
+# https://docs.zizmor.sh/configuration/
rules:
+ obfuscation:
+ disable: true
unpinned-uses:
config:
policies:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 75c7d3632..8477729e6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,17 +1,17 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.12.2
+ rev: v0.14.7
hooks:
- id: ruff-check
args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
- rev: 25.1.0
+ rev: 25.11.0
hooks:
- id: black
- repo: https://github.com/PyCQA/bandit
- rev: 1.8.6
+ rev: 1.9.2
hooks:
- id: bandit
args: [--severity-level=high]
@@ -21,10 +21,10 @@ repos:
rev: v1.5.5
hooks:
- id: remove-tabs
- exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
+ exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
- repo: https://github.com/pre-commit/mirrors-clang-format
- rev: v20.1.7
+ rev: v21.1.6
hooks:
- id: clang-format
types: [c]
@@ -36,7 +36,7 @@ repos:
- id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v5.0.0
+ rev: v6.0.0
hooks:
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
@@ -46,29 +46,29 @@ repos:
- id: check-yaml
args: [--allow-multiple-documents]
- id: end-of-file-fixer
- exclude: ^Tests/images/|\.patch$
+ exclude: ^Tests/images/
- id: trailing-whitespace
- exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
+ exclude: ^\.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/python-jsonschema/check-jsonschema
- rev: 0.33.2
+ rev: 0.35.0
hooks:
- id: check-github-workflows
- id: check-readthedocs
- id: check-renovate
- - repo: https://github.com/woodruffw/zizmor-pre-commit
- rev: v1.11.0
+ - repo: https://github.com/zizmorcore/zizmor-pre-commit
+ rev: v1.18.0
hooks:
- id: zizmor
- repo: https://github.com/sphinx-contrib/sphinx-lint
- rev: v1.0.0
+ rev: v1.0.2
hooks:
- id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt
- rev: v2.6.0
+ rev: v2.11.1
hooks:
- id: pyproject-fmt
@@ -79,7 +79,7 @@ repos:
additional_dependencies: [trove-classifiers>=2024.10.12]
- repo: https://github.com/tox-dev/tox-ini-fmt
- rev: 1.5.0
+ rev: 1.7.0
hooks:
- id: tox-ini-fmt
diff --git a/MANIFEST.in b/MANIFEST.in
index 95a6b1b92..d4623a4a8 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -13,8 +13,8 @@ include LICENSE
include Makefile
include tox.ini
graft Tests
+graft Tests/images
graft checks
-graft patches
graft src
graft depends
graft winbuild
@@ -28,8 +28,19 @@ exclude .editorconfig
exclude .readthedocs.yml
exclude codecov.yml
exclude renovate.json
+exclude Tests/images/README.md
+exclude Tests/images/crash*.tif
+exclude Tests/images/string_dimension.tiff
global-exclude .git*
global-exclude *.pyc
global-exclude *.so
prune .ci
prune wheels
+prune winbuild/build
+prune winbuild/depends
+prune Tests/errors
+prune Tests/images/jpeg2000
+prune Tests/images/msp
+prune Tests/images/picins
+prune Tests/images/sunraster
+prune Tests/test-images
diff --git a/README.md b/README.md
index 365d356a0..8585ef6cb 100644
--- a/README.md
+++ b/README.md
@@ -36,9 +36,6 @@ As of 2019, Pillow development is
-
diff --git a/Tests/createfontdatachunk.py b/Tests/createfontdatachunk.py
old mode 100755
new mode 100644
index 41c76f87e..0a3fdb809
--- a/Tests/createfontdatachunk.py
+++ b/Tests/createfontdatachunk.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python3
from __future__ import annotations
import base64
diff --git a/Tests/helper.py b/Tests/helper.py
index 34e4d6e75..dbdd30b42 100644
--- a/Tests/helper.py
+++ b/Tests/helper.py
@@ -10,17 +10,20 @@ import shutil
import subprocess
import sys
import tempfile
-from collections.abc import Sequence
from functools import lru_cache
from io import BytesIO
-from pathlib import Path
-from typing import Any, Callable
import pytest
from packaging.version import parse as parse_version
from PIL import Image, ImageFile, ImageMath, features
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable, Sequence
+ from pathlib import Path
+ from typing import Any
+
logger = logging.getLogger(__name__)
uploader = None
@@ -172,6 +175,14 @@ def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
return pytest.mark.skipif(not features.check(feature), reason=reason)
+def has_feature_version(feature: str, required: str) -> bool:
+ version = features.version(feature)
+ assert version is not None
+ version_required = parse_version(required)
+ version_available = parse_version(version)
+ return version_available >= version_required
+
+
def skip_unless_feature_version(
feature: str, required: str, reason: str | None = None
) -> pytest.MarkDecorator:
@@ -291,16 +302,6 @@ def djpeg_available() -> bool:
return False
-def cjpeg_available() -> bool:
- if shutil.which("cjpeg"):
- try:
- subprocess.check_call(["cjpeg", "-version"])
- return True
- except subprocess.CalledProcessError: # pragma: no cover
- return False
- return False
-
-
def netpbm_available() -> bool:
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
diff --git a/Tests/images/colr_bungee.png b/Tests/images/colr_bungee.png
index b10a60be0..9ec6b1182 100644
Binary files a/Tests/images/colr_bungee.png and b/Tests/images/colr_bungee.png differ
diff --git a/Tests/images/colr_bungee_mask.png b/Tests/images/colr_bungee_mask.png
index f13e17677..79106b639 100644
Binary files a/Tests/images/colr_bungee_mask.png and b/Tests/images/colr_bungee_mask.png differ
diff --git a/Tests/images/colr_bungee_older.png b/Tests/images/colr_bungee_older.png
new file mode 100644
index 000000000..b10a60be0
Binary files /dev/null and b/Tests/images/colr_bungee_older.png differ
diff --git a/Tests/images/crash-5762152299364352.fli b/Tests/images/crash-5762152299364352.fli
index 944fe0b56..d7588eea8 100644
Binary files a/Tests/images/crash-5762152299364352.fli and b/Tests/images/crash-5762152299364352.fli differ
diff --git a/Tests/images/frame_size.mpo b/Tests/images/frame_size.mpo
new file mode 100644
index 000000000..ee5c6cdf7
Binary files /dev/null and b/Tests/images/frame_size.mpo differ
diff --git a/Tests/images/sugarshack_frame_size.mpo b/Tests/images/sugarshack_frame_size.mpo
deleted file mode 100644
index abff98ea5..000000000
Binary files a/Tests/images/sugarshack_frame_size.mpo and /dev/null differ
diff --git a/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli b/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli
index ce4607d2d..73da81dcb 100644
Binary files a/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli and b/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli differ
diff --git a/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli b/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli
index 77a94b87a..abe642e6a 100644
Binary files a/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli and b/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli differ
diff --git a/Tests/images/unimplemented_pixel_format.dds b/Tests/images/unimplemented_pixel_format.dds
new file mode 100644
index 000000000..9092df8b1
Binary files /dev/null and b/Tests/images/unimplemented_pixel_format.dds differ
diff --git a/Tests/images/zero_mask_totals.dds b/Tests/images/zero_mask_totals.dds
new file mode 100644
index 000000000..31e329e4f
Binary files /dev/null and b/Tests/images/zero_mask_totals.dds differ
diff --git a/Tests/test_arro3.py b/Tests/test_arro3.py
new file mode 100644
index 000000000..672eedc9b
--- /dev/null
+++ b/Tests/test_arro3.py
@@ -0,0 +1,275 @@
+from __future__ import annotations
+
+import json
+from typing import Any, NamedTuple
+
+import pytest
+
+from PIL import Image
+
+from .helper import (
+ assert_deep_equal,
+ assert_image_equal,
+ hopper,
+ is_big_endian,
+)
+
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from arro3 import compute
+ from arro3.core import (
+ Array,
+ DataType,
+ Field,
+ fixed_size_list_array,
+ )
+else:
+ arro3 = pytest.importorskip("arro3", reason="Arro3 not installed")
+ from arro3 import compute
+ from arro3.core import Array, DataType, Field, fixed_size_list_array
+
+TEST_IMAGE_SIZE = (10, 10)
+
+
+def _test_img_equals_pyarray(
+ img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
+) -> None:
+ assert img.height * img.width * elts_per_pixel == len(arr)
+ px = img.load()
+ assert px is not None
+ if elts_per_pixel > 1 and mask is None:
+ # have to do element-wise comparison when we're comparing
+ # flattened r,g,b,a to a pixel.
+ mask = list(range(elts_per_pixel))
+ for x in range(0, img.size[0], int(img.size[0] / 10)):
+ for y in range(0, img.size[1], int(img.size[1] / 10)):
+ if mask:
+ pixel = px[x, y]
+ assert isinstance(pixel, tuple)
+ for ix, elt in enumerate(mask):
+ if elts_per_pixel == 1:
+ assert pixel[ix] == arr[y * img.width + x].as_py()[elt]
+ else:
+ assert (
+ pixel[ix]
+ == arr[(y * img.width + x) * elts_per_pixel + elt].as_py()
+ )
+ else:
+ assert_deep_equal(px[x, y], arr[y * img.width + x].as_py())
+
+
+def _test_img_equals_int32_pyarray(
+ img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
+) -> None:
+ assert img.height * img.width * elts_per_pixel == len(arr)
+ px = img.load()
+ assert px is not None
+ if mask is None:
+ # have to do element-wise comparison when we're comparing
+ # flattened rgba in an uint32 to a pixel.
+ mask = list(range(elts_per_pixel))
+ for x in range(0, img.size[0], int(img.size[0] / 10)):
+ for y in range(0, img.size[1], int(img.size[1] / 10)):
+ pixel = px[x, y]
+ assert isinstance(pixel, tuple)
+ arr_pixel_int = arr[y * img.width + x].as_py()
+ arr_pixel_tuple = (
+ arr_pixel_int % 256,
+ (arr_pixel_int // 256) % 256,
+ (arr_pixel_int // 256**2) % 256,
+ (arr_pixel_int // 256**3),
+ )
+ if is_big_endian():
+ arr_pixel_tuple = arr_pixel_tuple[::-1]
+
+ for ix, elt in enumerate(mask):
+ assert pixel[ix] == arr_pixel_tuple[elt]
+
+
+fl_uint8_4_type = DataType.list(Field("_", DataType.uint8()).with_nullable(False), 4)
+
+
+@pytest.mark.parametrize(
+ "mode, dtype, mask",
+ (
+ ("L", DataType.uint8(), None),
+ ("I", DataType.int32(), None),
+ ("F", DataType.float32(), None),
+ ("LA", fl_uint8_4_type, [0, 3]),
+ ("RGB", fl_uint8_4_type, [0, 1, 2]),
+ ("RGBA", fl_uint8_4_type, None),
+ ("RGBX", fl_uint8_4_type, None),
+ ("CMYK", fl_uint8_4_type, None),
+ ("YCbCr", fl_uint8_4_type, [0, 1, 2]),
+ ("HSV", fl_uint8_4_type, [0, 1, 2]),
+ ),
+)
+def test_to_array(mode: str, dtype: DataType, mask: list[int] | None) -> None:
+ img = hopper(mode)
+
+ # Resize to non-square
+ img = img.crop((3, 0, 124, 127))
+ assert img.size == (121, 127)
+
+ arr = Array(img)
+ _test_img_equals_pyarray(img, arr, mask)
+ assert arr.type == dtype
+
+ reloaded = Image.fromarrow(arr, mode, img.size)
+ assert_image_equal(img, reloaded)
+
+
+def test_lifetime() -> None:
+ # valgrind shouldn't error out here.
+ # arrays should be accessible after the image is deleted.
+
+ img = hopper("L")
+
+ arr_1 = Array(img)
+ arr_2 = Array(img)
+
+ del img
+
+ assert compute.sum(arr_1).as_py() > 0
+ del arr_1
+
+ assert compute.sum(arr_2).as_py() > 0
+ del arr_2
+
+
+def test_lifetime2() -> None:
+ # valgrind shouldn't error out here.
+ # img should remain after the arrays are collected.
+
+ img = hopper("L")
+
+ arr_1 = Array(img)
+ arr_2 = Array(img)
+
+ assert compute.sum(arr_1).as_py() > 0
+ del arr_1
+
+ assert compute.sum(arr_2).as_py() > 0
+ del arr_2
+
+ img2 = img.copy()
+ px = img2.load()
+ assert px # make mypy happy
+ assert isinstance(px[0, 0], int)
+
+
+class DataShape(NamedTuple):
+ dtype: DataType
+ # Strictly speaking, elt should be a pixel or pixel component, so
+ # list[uint8][4], float, int, uint32, uint8, etc. But more
+ # correctly, it should be exactly the dtype from the line above.
+ elt: Any
+ elts_per_pixel: int
+
+
+UINT_ARR = DataShape(
+ dtype=fl_uint8_4_type,
+ elt=[1, 2, 3, 4], # array of 4 uint8 per pixel
+ elts_per_pixel=1, # only one array per pixel
+)
+
+UINT = DataShape(
+ dtype=DataType.uint8(),
+ elt=3, # one uint8,
+ elts_per_pixel=4, # but repeated 4x per pixel
+)
+
+UINT32 = DataShape(
+ dtype=DataType.uint32(),
+ elt=0xABCDEF45, # one packed int, doesn't fit in a int32 > 0x80000000
+ elts_per_pixel=1, # one per pixel
+)
+
+INT32 = DataShape(
+ dtype=DataType.uint32(),
+ elt=0x12CDEF45, # one packed int
+ elts_per_pixel=1, # one per pixel
+)
+
+
+@pytest.mark.parametrize(
+ "mode, data_tp, mask",
+ (
+ ("L", DataShape(DataType.uint8(), 3, 1), None),
+ ("I", DataShape(DataType.int32(), 1 << 24, 1), None),
+ ("F", DataShape(DataType.float32(), 3.14159, 1), None),
+ ("LA", UINT_ARR, [0, 3]),
+ ("LA", UINT, [0, 3]),
+ ("RGB", UINT_ARR, [0, 1, 2]),
+ ("RGBA", UINT_ARR, None),
+ ("CMYK", UINT_ARR, None),
+ ("YCbCr", UINT_ARR, [0, 1, 2]),
+ ("HSV", UINT_ARR, [0, 1, 2]),
+ ("RGB", UINT, [0, 1, 2]),
+ ("RGBA", UINT, None),
+ ("CMYK", UINT, None),
+ ("YCbCr", UINT, [0, 1, 2]),
+ ("HSV", UINT, [0, 1, 2]),
+ ),
+)
+def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
+ (dtype, elt, elts_per_pixel) = data_tp
+
+ ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
+ if dtype == fl_uint8_4_type:
+ tmp_arr = Array(elt * (ct_pixels * elts_per_pixel), type=DataType.uint8())
+ arr = fixed_size_list_array(tmp_arr, 4)
+ else:
+ arr = Array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
+ img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
+
+ _test_img_equals_pyarray(img, arr, mask, elts_per_pixel)
+
+
+@pytest.mark.parametrize(
+ "mode, mask",
+ (
+ ("LA", [0, 3]),
+ ("RGB", [0, 1, 2]),
+ ("RGBA", None),
+ ("CMYK", None),
+ ("YCbCr", [0, 1, 2]),
+ ("HSV", [0, 1, 2]),
+ ),
+)
+@pytest.mark.parametrize("data_tp", (UINT32, INT32))
+def test_from_int32array(mode: str, mask: list[int] | None, data_tp: DataShape) -> None:
+ (dtype, elt, elts_per_pixel) = data_tp
+
+ ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
+ arr = Array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
+ img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
+
+ _test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel)
+
+
+@pytest.mark.parametrize(
+ "mode, metadata",
+ (
+ ("LA", ["L", "X", "X", "A"]),
+ ("RGB", ["R", "G", "B", "X"]),
+ ("RGBX", ["R", "G", "B", "X"]),
+ ("RGBA", ["R", "G", "B", "A"]),
+ ("CMYK", ["C", "M", "Y", "K"]),
+ ("YCbCr", ["Y", "Cb", "Cr", "X"]),
+ ("HSV", ["H", "S", "V", "X"]),
+ ),
+)
+def test_image_metadata(mode: str, metadata: list[str]) -> None:
+ img = hopper(mode)
+
+ arr = Array(img)
+
+ assert arr.type.value_field
+ assert arr.type.value_field.metadata
+ assert arr.type.value_field.metadata[b"image"]
+
+ parsed_metadata = json.loads(arr.type.value_field.metadata[b"image"].decode("utf8"))
+
+ assert "bands" in parsed_metadata
+ assert parsed_metadata["bands"] == metadata
diff --git a/Tests/test_features.py b/Tests/test_features.py
index 520c25b46..93d803fc1 100644
--- a/Tests/test_features.py
+++ b/Tests/test_features.py
@@ -2,7 +2,6 @@ from __future__ import annotations
import io
import re
-from typing import Callable
import pytest
@@ -10,6 +9,10 @@ from PIL import features
from .helper import skip_unless_feature
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
def test_check() -> None:
# Check the correctness of the convenience function
@@ -18,11 +21,7 @@ def test_check() -> None:
for codec in features.codecs:
assert features.check_codec(codec) == features.check(codec)
for feature in features.features:
- if "webp" in feature:
- with pytest.warns(DeprecationWarning, match="webp"):
- assert features.check_feature(feature) == features.check(feature)
- else:
- assert features.check_feature(feature) == features.check(feature)
+ assert features.check_feature(feature) == features.check(feature)
def test_version() -> None:
@@ -48,11 +47,7 @@ def test_version() -> None:
for codec in features.codecs:
test(codec, features.version_codec)
for feature in features.features:
- if "webp" in feature:
- with pytest.warns(DeprecationWarning, match="webp"):
- test(feature, features.version_feature)
- else:
- test(feature, features.version_feature)
+ test(feature, features.version_feature)
@skip_unless_feature("libjpeg_turbo")
@@ -112,6 +107,25 @@ def test_unsupported_module() -> None:
features.version_module(module)
+def test_unsupported_feature() -> None:
+ # Arrange
+ feature = "unsupported_feature"
+ # Act / Assert
+ with pytest.raises(ValueError):
+ features.check_feature(feature)
+ with pytest.raises(ValueError):
+ features.version_feature(feature)
+
+
+def test_unsupported_version() -> None:
+ assert features.version("unsupported_version") is None
+
+
+def test_modulenotfound(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setattr(features, "features", {"test": ("PIL._test", "", "")})
+ assert features.check_feature("test") is None
+
+
@pytest.mark.parametrize("supported_formats", (True, False))
def test_pilinfo(supported_formats: bool) -> None:
buf = io.StringIO()
diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py
index 287bbd462..ea6c9a368 100644
--- a/Tests/test_file_apng.py
+++ b/Tests/test_file_apng.py
@@ -770,6 +770,25 @@ def test_apng_save_size(tmp_path: Path) -> None:
assert reloaded.size == (200, 200)
+def test_compress_level() -> None:
+ compress_level_sizes = {}
+ for compress_level in (0, 9):
+ out = BytesIO()
+
+ im = Image.new("L", (100, 100))
+ im.save(
+ out,
+ "PNG",
+ save_all=True,
+ append_images=[Image.new("L", (200, 200))],
+ compress_level=compress_level,
+ )
+
+ compress_level_sizes[compress_level] = len(out.getvalue())
+
+ assert compress_level_sizes[0] > compress_level_sizes[9]
+
+
def test_seek_after_close() -> None:
im = Image.open("Tests/images/apng/delay.png")
im.seek(1)
diff --git a/Tests/test_file_avif.py b/Tests/test_file_avif.py
index a966cbe88..fc1209a04 100644
--- a/Tests/test_file_avif.py
+++ b/Tests/test_file_avif.py
@@ -14,6 +14,7 @@ import pytest
from PIL import (
AvifImagePlugin,
+ GifImagePlugin,
Image,
ImageDraw,
ImageFile,
@@ -266,6 +267,7 @@ class TestFileAvif:
def test_background_from_gif(self, tmp_path: Path) -> None:
with Image.open("Tests/images/chi.gif") as im:
original_value = im.convert("RGB").getpixel((1, 1))
+ assert isinstance(original_value, tuple)
# Save as AVIF
out_avif = tmp_path / "temp.avif"
@@ -278,6 +280,7 @@ class TestFileAvif:
with Image.open(out_gif) as reread:
reread_value = reread.convert("RGB").getpixel((1, 1))
+ assert isinstance(reread_value, tuple)
difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)])
assert difference <= 6
@@ -286,6 +289,7 @@ class TestFileAvif:
with Image.open("Tests/images/chi.gif") as im:
im.save(temp_file)
with Image.open(temp_file) as im:
+ assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 1
def test_invalid_file(self) -> None:
@@ -644,10 +648,12 @@ class TestAvifAnimation:
"""
with Image.open(TEST_AVIF_FILE) as im:
+ assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 1
assert not im.is_animated
with Image.open("Tests/images/avif/star.avifs") as im:
+ assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 5
assert im.is_animated
@@ -658,11 +664,13 @@ class TestAvifAnimation:
"""
with Image.open("Tests/images/avif/star.gif") as original:
+ assert isinstance(original, GifImagePlugin.GifImageFile)
assert original.n_frames > 1
temp_file = tmp_path / "temp.avif"
original.save(temp_file, save_all=True)
with Image.open(temp_file) as im:
+ assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == original.n_frames
# Compare first frame in P mode to frame from original GIF
@@ -682,6 +690,7 @@ class TestAvifAnimation:
def check(temp_file: Path) -> None:
with Image.open(temp_file) as im:
+ assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 4
# Compare first frame to original
@@ -754,6 +763,7 @@ class TestAvifAnimation:
)
with Image.open(temp_file) as im:
+ assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 5
assert im.is_animated
@@ -783,6 +793,7 @@ class TestAvifAnimation:
)
with Image.open(temp_file) as im:
+ assert isinstance(im, AvifImagePlugin.AvifImageFile)
assert im.n_frames == 5
assert im.is_animated
diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py
index 746b2e180..c1c430aa5 100644
--- a/Tests/test_file_bmp.py
+++ b/Tests/test_file_bmp.py
@@ -6,6 +6,8 @@ from pathlib import Path
import pytest
from PIL import BmpImagePlugin, Image, _binary
+from PIL._binary import o16le as o16
+from PIL._binary import o32le as o32
from .helper import (
assert_image_equal,
@@ -114,7 +116,7 @@ def test_save_float_dpi(tmp_path: Path) -> None:
def test_load_dib() -> None:
- # test for #1293, Imagegrab returning Unsupported Bitfields Format
+ # test for #1293, ImageGrab returning Unsupported Bitfields Format
with Image.open("Tests/images/clipboard.dib") as im:
assert im.format == "DIB"
assert im.get_format_mimetype() == "image/bmp"
@@ -219,6 +221,18 @@ def test_rle8_eof(file_name: str, length: int) -> None:
im.load()
+def test_unsupported_bmp_bitfields_layout() -> None:
+ fp = io.BytesIO(
+ o32(40) # header size
+ + b"\x00" * 10
+ + o16(1) # bits
+ + o32(3) # BITFIELDS compression
+ + b"\x00" * 32
+ )
+ with pytest.raises(OSError, match="Unsupported BMP bitfields layout"):
+ Image.open(fp)
+
+
def test_offset() -> None:
# This image has been hexedited
# to exclude the palette size from the pixel data offset
diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py
index dbf1b866d..4b3e3afcb 100644
--- a/Tests/test_file_cur.py
+++ b/Tests/test_file_cur.py
@@ -1,8 +1,13 @@
from __future__ import annotations
+from io import BytesIO
+
import pytest
from PIL import CurImagePlugin, Image
+from PIL._binary import o8
+from PIL._binary import o16le as o16
+from PIL._binary import o32le as o32
TEST_FILE = "Tests/images/deerstalker.cur"
@@ -17,6 +22,24 @@ def test_sanity() -> None:
assert im.getpixel((16, 16)) == (84, 87, 86, 255)
+def test_largest_cursor() -> None:
+ magic = b"\x00\x00\x02\x00"
+ sizes = ((1, 1), (8, 8), (4, 4))
+ data = magic + o16(len(sizes))
+ for w, h in sizes:
+ image_offset = 6 + len(sizes) * 16 if (w, h) == max(sizes) else 0
+ data += o8(w) + o8(h) + o8(0) * 10 + o32(image_offset)
+ data += (
+ o32(12) # header size
+ + o16(8) # width
+ + o16(16) # height
+ + o16(0) # planes
+ + o16(1) # bits
+ )
+ with Image.open(BytesIO(data)) as im:
+ assert im.size == (8, 8)
+
+
def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"
@@ -26,6 +49,7 @@ def test_invalid_file() -> None:
no_cursors_file = "Tests/images/no_cursors.cur"
cur = CurImagePlugin.CurImageFile(TEST_FILE)
+ assert cur.fp is not None
cur.fp.close()
with open(no_cursors_file, "rb") as cur.fp:
with pytest.raises(TypeError):
diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py
index 5c7a943b1..60d0c09bc 100644
--- a/Tests/test_file_dds.py
+++ b/Tests/test_file_dds.py
@@ -380,21 +380,33 @@ def test_palette() -> None:
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
+def test_zero_mask_totals() -> None:
+ with Image.open("Tests/images/zero_mask_totals.dds") as im:
+ im.load()
+
+
+def test_unsupported_header_size() -> None:
+ with pytest.raises(OSError, match="Unsupported header size 0"):
+ with Image.open(BytesIO(b"DDS " + b"\x00" * 4)):
+ pass
+
+
def test_unsupported_bitcount() -> None:
- with pytest.raises(OSError):
+ with pytest.raises(OSError, match="Unsupported bitcount 24 for 131072"):
with Image.open("Tests/images/unsupported_bitcount.dds"):
pass
@pytest.mark.parametrize(
- "test_file",
+ "test_file, message",
(
- "Tests/images/unimplemented_dxgi_format.dds",
- "Tests/images/unimplemented_pfflags.dds",
+ ("Tests/images/unimplemented_dxgi_format.dds", "Unimplemented DXGI format 93"),
+ ("Tests/images/unimplemented_pixel_format.dds", "Unimplemented pixel format 0"),
+ ("Tests/images/unimplemented_pfflags.dds", "Unknown pixel format flags 8"),
),
)
-def test_not_implemented(test_file: str) -> None:
- with pytest.raises(NotImplementedError):
+def test_not_implemented(test_file: str, message: str) -> None:
+ with pytest.raises(NotImplementedError, match=message):
with Image.open(test_file):
pass
diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py
index d94de7287..b50915f28 100644
--- a/Tests/test_file_eps.py
+++ b/Tests/test_file_eps.py
@@ -197,6 +197,14 @@ def test_load_long_binary_data(prefix: bytes) -> None:
assert img.format == "EPS"
+def test_begin_binary() -> None:
+ with open("Tests/images/eps/binary_preview_map.eps", "rb") as fp:
+ data = bytearray(fp.read())
+ data[76875 : 76875 + 11] = b"%" * 11
+ with Image.open(io.BytesIO(data)) as img:
+ assert img.size == (399, 480)
+
+
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py
index 0fadd01d0..13c6a4323 100644
--- a/Tests/test_file_fli.py
+++ b/Tests/test_file_fli.py
@@ -48,6 +48,7 @@ def test_sanity() -> None:
def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
with Image.open(animated_test_file_with_prefix_chunk) as im:
+ assert isinstance(im, FliImagePlugin.FliImageFile)
assert im.mode == "P"
assert im.size == (320, 200)
assert im.format == "FLI"
@@ -55,6 +56,7 @@ def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None:
assert im.is_animated
palette = im.getpalette()
+ assert palette is not None
assert palette[3:6] == [255, 255, 255]
assert palette[381:384] == [204, 204, 12]
assert palette[765:] == [252, 0, 0]
diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py
index 1b834cd3c..d89ef0583 100644
--- a/Tests/test_file_gbr.py
+++ b/Tests/test_file_gbr.py
@@ -1,8 +1,10 @@
from __future__ import annotations
+from io import BytesIO
+
import pytest
-from PIL import GbrImagePlugin, Image
+from PIL import GbrImagePlugin, Image, _binary
from .helper import assert_image_equal_tofile
@@ -31,8 +33,49 @@ def test_multiple_load_operations() -> None:
assert_image_equal_tofile(im, "Tests/images/gbr.png")
-def test_invalid_file() -> None:
- invalid_file = "Tests/images/flower.jpg"
+def create_gbr_image(info: dict[str, int] = {}, magic_number: bytes = b"") -> BytesIO:
+ return BytesIO(
+ b"".join(
+ _binary.o32be(i)
+ for i in [
+ info.get("header_size", 20),
+ info.get("version", 1),
+ info.get("width", 1),
+ info.get("height", 1),
+ info.get("color_depth", 1),
+ ]
+ )
+ + magic_number
+ )
- with pytest.raises(SyntaxError):
+
+def test_invalid_file() -> None:
+ for f in [
+ create_gbr_image({"header_size": 0}),
+ create_gbr_image({"width": 0}),
+ create_gbr_image({"height": 0}),
+ ]:
+ with pytest.raises(SyntaxError, match="not a GIMP brush"):
+ GbrImagePlugin.GbrImageFile(f)
+
+ invalid_file = "Tests/images/flower.jpg"
+ with pytest.raises(SyntaxError, match="Unsupported GIMP brush version"):
GbrImagePlugin.GbrImageFile(invalid_file)
+
+
+def test_unsupported_gimp_brush() -> None:
+ f = create_gbr_image({"color_depth": 2})
+ with pytest.raises(SyntaxError, match="Unsupported GIMP brush color depth: 2"):
+ GbrImagePlugin.GbrImageFile(f)
+
+
+def test_bad_magic_number() -> None:
+ f = create_gbr_image({"version": 2}, magic_number=b"badm")
+ with pytest.raises(SyntaxError, match="not a GIMP brush, bad magic number"):
+ GbrImagePlugin.GbrImageFile(f)
+
+
+def test_L() -> None:
+ f = create_gbr_image()
+ with Image.open(f) as im:
+ assert im.mode == "L"
diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py
index 806532c17..8a49fd4fa 100644
--- a/Tests/test_file_gd.py
+++ b/Tests/test_file_gd.py
@@ -1,5 +1,7 @@
from __future__ import annotations
+from io import BytesIO
+
import pytest
from PIL import GdImageFile, UnidentifiedImageError
@@ -16,6 +18,14 @@ def test_sanity() -> None:
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.jpg", 14)
+def test_transparency() -> None:
+ with open(TEST_GD_FILE, "rb") as fp:
+ data = bytearray(fp.read())
+ data[7:11] = b"\x00\x00\x00\x05"
+ with GdImageFile.open(BytesIO(data)) as im:
+ assert im.info["transparency"] == 5
+
+
def test_bad_mode() -> None:
with pytest.raises(ValueError):
GdImageFile.open(TEST_GD_FILE, "bad mode")
diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py
index 8b606291c..e90a5e7b6 100644
--- a/Tests/test_file_gif.py
+++ b/Tests/test_file_gif.py
@@ -294,6 +294,7 @@ def test_roundtrip_save_all(tmp_path: Path) -> None:
im.save(out, save_all=True)
with Image.open(out) as reread:
+ assert isinstance(reread, GifImagePlugin.GifImageFile)
assert reread.n_frames == 5
@@ -1421,6 +1422,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None:
with Image.open(out) as im:
# Assert that the frames are correct, and each frame has the same palette
+ assert isinstance(im, GifImagePlugin.GifImageFile)
assert_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
assert im.palette is not None
assert im.global_palette is not None
diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py
index 3c4c892c8..0376b9997 100644
--- a/Tests/test_file_iptc.py
+++ b/Tests/test_file_iptc.py
@@ -2,6 +2,8 @@ from __future__ import annotations
from io import BytesIO
+import pytest
+
from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags
from .helper import assert_image_equal, hopper
@@ -9,21 +11,78 @@ from .helper import assert_image_equal, hopper
TEST_FILE = "Tests/images/iptc.jpg"
+def create_iptc_image(info: dict[str, int] = {}) -> BytesIO:
+ def field(tag: tuple[int, int], value: bytes) -> bytes:
+ return bytes((0x1C,) + tag + (0, len(value))) + value
+
+ data = field((3, 60), bytes((info.get("layers", 1), info.get("component", 0))))
+ data += field((3, 120), bytes((info.get("compression", 1),)))
+ if "band" in info:
+ data += field((3, 65), bytes((info["band"] + 1,)))
+ data += field((3, 20), b"\x01") # width
+ data += field((3, 30), b"\x01") # height
+ data += field(
+ (8, 10),
+ bytes((info.get("data", 0),)),
+ )
+
+ return BytesIO(data)
+
+
def test_open() -> None:
expected = Image.new("L", (1, 1))
- f = BytesIO(
- b"\x1c\x03<\x00\x02\x01\x00\x1c\x03x\x00\x01\x01\x1c\x03\x14\x00\x01\x01"
- b"\x1c\x03\x1e\x00\x01\x01\x1c\x08\n\x00\x01\x00"
- )
+ f = create_iptc_image()
with Image.open(f) as im:
- assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")]
+ assert im.tile == [("iptc", (0, 0, 1, 1), 25, ("raw", None))]
assert_image_equal(im, expected)
with Image.open(f) as im:
assert im.load() is not None
+def test_field_length() -> None:
+ f = create_iptc_image()
+ f.seek(28)
+ f.write(b"\xff")
+ with pytest.raises(OSError, match="illegal field length in IPTC/NAA file"):
+ with Image.open(f):
+ pass
+
+
+@pytest.mark.parametrize("layers, mode", ((3, "RGB"), (4, "CMYK")))
+def test_layers(layers: int, mode: str) -> None:
+ for band in range(-1, layers):
+ info = {"layers": layers, "component": 1, "data": 5}
+ if band != -1:
+ info["band"] = band
+ f = create_iptc_image(info)
+ with Image.open(f) as im:
+ assert im.mode == mode
+
+ data = [0] * layers
+ data[max(band, 0)] = 5
+ assert im.getpixel((0, 0)) == tuple(data)
+
+
+def test_unknown_compression() -> None:
+ f = create_iptc_image({"compression": 2})
+ with pytest.raises(OSError, match="Unknown IPTC image compression"):
+ with Image.open(f):
+ pass
+
+
+def test_getiptcinfo() -> None:
+ f = create_iptc_image()
+ with Image.open(f) as im:
+ assert IptcImagePlugin.getiptcinfo(im) == {
+ (3, 60): b"\x01\x00",
+ (3, 120): b"\x01",
+ (3, 20): b"\x01",
+ (3, 30): b"\x01",
+ }
+
+
def test_getiptcinfo_jpg_none() -> None:
# Arrange
with hopper() as im:
diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py
index 08e879807..96e7f4239 100644
--- a/Tests/test_file_jpeg.py
+++ b/Tests/test_file_jpeg.py
@@ -26,7 +26,6 @@ from .helper import (
assert_image_equal_tofile,
assert_image_similar,
assert_image_similar_tofile,
- cjpeg_available,
djpeg_available,
hopper,
is_win32,
@@ -331,8 +330,10 @@ class TestFileJpeg:
# Reading
with Image.open("Tests/images/exif_gps.jpg") as im:
- exif = im._getexif()
- assert exif[gps_index] == expected_exif_gps
+ assert isinstance(im, JpegImagePlugin.JpegImageFile)
+ exif_data = im._getexif()
+ assert exif_data is not None
+ assert exif_data[gps_index] == expected_exif_gps
# Writing
f = tmp_path / "temp.jpg"
@@ -341,8 +342,10 @@ class TestFileJpeg:
hopper().save(f, exif=exif)
with Image.open(f) as reloaded:
- exif = reloaded._getexif()
- assert exif[gps_index] == expected_exif_gps
+ assert isinstance(reloaded, JpegImagePlugin.JpegImageFile)
+ exif_data = reloaded._getexif()
+ assert exif_data is not None
+ assert exif_data[gps_index] == expected_exif_gps
def test_empty_exif_gps(self) -> None:
with Image.open("Tests/images/empty_gps_ifd.jpg") as im:
@@ -369,6 +372,7 @@ class TestFileJpeg:
exifs = []
for i in range(2):
with Image.open("Tests/images/exif-200dpcm.jpg") as im:
+ assert isinstance(im, JpegImagePlugin.JpegImageFile)
exifs.append(im._getexif())
assert exifs[0] == exifs[1]
@@ -402,13 +406,17 @@ class TestFileJpeg:
}
with Image.open("Tests/images/exif_gps.jpg") as im:
+ assert isinstance(im, JpegImagePlugin.JpegImageFile)
exif = im._getexif()
+ assert exif is not None
for tag, value in expected_exif.items():
assert value == exif[tag]
def test_exif_gps_typeerror(self) -> None:
with Image.open("Tests/images/exif_gps_typeerror.jpg") as im:
+ assert isinstance(im, JpegImagePlugin.JpegImageFile)
+
# Should not raise a TypeError
im._getexif()
@@ -488,7 +496,9 @@ class TestFileJpeg:
def test_exif(self) -> None:
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
+ assert isinstance(im, JpegImagePlugin.JpegImageFile)
info = im._getexif()
+ assert info is not None
assert info[305] == "Adobe Photoshop CS Macintosh"
def test_get_child_images(self) -> None:
@@ -691,11 +701,13 @@ class TestFileJpeg:
def test_save_multiple_16bit_qtables(self) -> None:
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
+ assert isinstance(im, JpegImagePlugin.JpegImageFile)
im2 = self.roundtrip(im, qtables="keep")
assert im.quantization == im2.quantization
def test_save_single_16bit_qtable(self) -> None:
with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im:
+ assert isinstance(im, JpegImagePlugin.JpegImageFile)
im2 = self.roundtrip(im, qtables={0: im.quantization[0]})
assert len(im2.quantization) == 1
assert im2.quantization[0] == im.quantization[0]
@@ -731,14 +743,6 @@ class TestFileJpeg:
img.load_djpeg()
assert_image_similar_tofile(img, TEST_FILE, 5)
- @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
- def test_save_cjpeg(self, tmp_path: Path) -> None:
- with Image.open(TEST_FILE) as img:
- tempfile = str(tmp_path / "temp.jpg")
- JpegImagePlugin._save_cjpeg(img, BytesIO(), tempfile)
- # Default save quality is 75%, so a tiny bit of difference is alright
- assert_image_similar_tofile(img, tempfile, 17)
-
def test_no_duplicate_0x1001_tag(self) -> None:
# Arrange
tag_ids = {v: k for k, v in ExifTags.TAGS.items()}
@@ -907,7 +911,10 @@ class TestFileJpeg:
# in contrast to normal 8
with Image.open("Tests/images/exif-ifd-offset.jpg") as im:
# Act / Assert
- assert im._getexif()[306] == "2017:03:13 23:03:09"
+ assert isinstance(im, JpegImagePlugin.JpegImageFile)
+ exif = im._getexif()
+ assert exif is not None
+ assert exif[306] == "2017:03:13 23:03:09"
def test_multiple_exif(self) -> None:
with Image.open("Tests/images/multiple_exif.jpg") as im:
diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
index c245a5a9b..7cb3ea8e4 100644
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -355,6 +355,56 @@ class TestFileLibTiff(LibTiffTestCase):
# Should not segfault
im.save(outfile)
+ @pytest.mark.parametrize("tagtype", (TiffTags.SIGNED_RATIONAL, TiffTags.IFD))
+ def test_tag_type(
+ self, tagtype: int, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+ ) -> None:
+ monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
+
+ ifd = TiffImagePlugin.ImageFileDirectory_v2()
+ ifd[37000] = 100
+ ifd.tagtype[37000] = tagtype
+
+ out = tmp_path / "temp.tif"
+ im = Image.new("L", (1, 1))
+ im.save(out, tiffinfo=ifd)
+
+ with Image.open(out) as reloaded:
+ assert reloaded.tag_v2[37000] == 100
+
+ def test_inknames_tag(
+ self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+ ) -> None:
+ monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
+
+ out = tmp_path / "temp.tif"
+ hopper("L").save(out, tiffinfo={333: "name\x00"})
+
+ with Image.open(out) as reloaded:
+ assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
+ assert reloaded.tag_v2[333] in ("name", "name\x00")
+
+ def test_whitepoint_tag(
+ self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+ ) -> None:
+ monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
+
+ out = tmp_path / "temp.tif"
+ hopper().save(out, tiffinfo={318: (0.3127, 0.3289)})
+
+ with Image.open(out) as reloaded:
+ assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
+ assert reloaded.tag_v2[318] == pytest.approx((0.3127, 0.3289))
+
+ # Save tag by default
+ out = tmp_path / "temp2.tif"
+ with Image.open("Tests/images/rdf.tif") as im:
+ im.save(out)
+
+ with Image.open(out) as reloaded:
+ assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
+ assert reloaded.tag_v2[318] == pytest.approx((0.3127, 0.3289999))
+
def test_xmlpacket_tag(
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
@@ -365,8 +415,7 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
- if 700 in reloaded.tag_v2:
- assert reloaded.tag_v2[700] == b"xmlpacket tag"
+ assert reloaded.tag_v2[700] == b"xmlpacket tag"
def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
# issue #1765
@@ -873,8 +922,8 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.mode == "RGB"
assert im.size == (128, 128)
assert im.format == "TIFF"
- im2 = hopper()
- assert_image_similar(im, im2, 5)
+ with hopper() as im2:
+ assert_image_similar(im, im2, 5)
except OSError:
captured = capfd.readouterr()
if "LZMA compression support is not configured" in captured.err:
diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py
index 9aeb306e4..0706af4c0 100644
--- a/Tests/test_file_mic.py
+++ b/Tests/test_file_mic.py
@@ -22,10 +22,10 @@ def test_sanity() -> None:
# Adjust for the gamma of 2.2 encoded into the file
lut = ImagePalette.make_gamma_lut(1 / 2.2)
- im = Image.merge("RGBA", [chan.point(lut) for chan in im.split()])
+ im1 = Image.merge("RGBA", [chan.point(lut) for chan in im.split()])
im2 = hopper("RGBA")
- assert_image_similar(im, im2, 10)
+ assert_image_similar(im1, im2, 10)
def test_n_frames() -> None:
diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py
index 85dd5fc7f..2619dac6f 100644
--- a/Tests/test_file_mpo.py
+++ b/Tests/test_file_mpo.py
@@ -104,25 +104,27 @@ def test_exif(test_file: str) -> None:
def test_frame_size() -> None:
- # This image has been hexedited to contain a different size
- # in the SOF marker of the second frame
- with Image.open("Tests/images/sugarshack_frame_size.mpo") as im:
- assert im.size == (640, 480)
+ with Image.open("Tests/images/frame_size.mpo") as im:
+ assert im.size == (56, 70)
+ im.load()
im.seek(1)
- assert im.size == (680, 480)
+ assert im.size == (349, 434)
+ im.load()
im.seek(0)
- assert im.size == (640, 480)
+ assert im.size == (56, 70)
def test_ignore_frame_size() -> None:
# Ignore the different size of the second frame
# since this is not a "Large Thumbnail" image
with Image.open("Tests/images/ignore_frame_size.mpo") as im:
+ assert isinstance(im, MpoImagePlugin.MpoImageFile)
assert im.size == (64, 64)
im.seek(1)
+ assert im.mpinfo is not None
assert (
im.mpinfo[0xB002][1]["Attribute"]["MPType"]
== "Multi-Frame Image: (Disparity)"
@@ -155,6 +157,7 @@ def test_reload_exif_after_seek() -> None:
@pytest.mark.parametrize("test_file", test_files)
def test_mp(test_file: str) -> None:
with Image.open(test_file) as im:
+ assert isinstance(im, MpoImagePlugin.MpoImageFile)
mpinfo = im._getmp()
assert mpinfo is not None
assert mpinfo[45056] == b"0100"
@@ -165,6 +168,7 @@ def test_mp_offset() -> None:
# This image has been manually hexedited to have an IFD offset of 10
# in APP2 data, in contrast to normal 8
with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im:
+ assert isinstance(im, MpoImagePlugin.MpoImageFile)
mpinfo = im._getmp()
assert mpinfo is not None
assert mpinfo[45056] == b"0100"
@@ -182,6 +186,7 @@ def test_mp_no_data() -> None:
@pytest.mark.parametrize("test_file", test_files)
def test_mp_attribute(test_file: str) -> None:
with Image.open(test_file) as im:
+ assert isinstance(im, MpoImagePlugin.MpoImageFile)
mpinfo = im._getmp()
assert mpinfo is not None
for frame_number, mpentry in enumerate(mpinfo[0xB002]):
@@ -295,12 +300,12 @@ def test_save_all() -> None:
im_reloaded.seek(1)
assert_image_similar(im, im_reloaded, 30)
- im = Image.new("RGB", (1, 1))
+ im_rgb = Image.new("RGB", (1, 1))
for colors in (("#f00",), ("#f00", "#0f0")):
append_images = [Image.new("RGB", (1, 1), color) for color in colors]
- im_reloaded = roundtrip(im, save_all=True, append_images=append_images)
+ im_reloaded = roundtrip(im_rgb, save_all=True, append_images=append_images)
- assert_image_equal(im, im_reloaded)
+ assert_image_equal(im_rgb, im_reloaded)
assert isinstance(im_reloaded, MpoImagePlugin.MpoImageFile)
assert im_reloaded.mpinfo is not None
assert im_reloaded.mpinfo[45056] == b"0100"
@@ -310,7 +315,7 @@ def test_save_all() -> None:
assert_image_similar(im_reloaded, im_expected, 1)
# Test that a single frame image will not be saved as an MPO
- jpg = roundtrip(im, save_all=True)
+ jpg = roundtrip(im_rgb, save_all=True)
assert "mp" not in jpg.info
diff --git a/Tests/test_file_pcd.py b/Tests/test_file_pcd.py
index 81a316fc1..15dd7f116 100644
--- a/Tests/test_file_pcd.py
+++ b/Tests/test_file_pcd.py
@@ -1,10 +1,17 @@
from __future__ import annotations
+from io import BytesIO
+
+import pytest
+
from PIL import Image
+from .helper import assert_image_equal
+
def test_load_raw() -> None:
with Image.open("Tests/images/hopper.pcd") as im:
+ assert im.size == (768, 512)
im.load() # should not segfault.
# Note that this image was created with a resized hopper
@@ -15,3 +22,18 @@ def test_load_raw() -> None:
# target = hopper().resize((768,512))
# assert_image_similar(im, target, 10)
+
+
+@pytest.mark.parametrize("orientation", (1, 3))
+def test_rotated(orientation: int) -> None:
+ with open("Tests/images/hopper.pcd", "rb") as fp:
+ data = bytearray(fp.read())
+ data[2048 + 1538] = orientation
+ f = BytesIO(data)
+ with Image.open(f) as im:
+ assert im.size == (512, 768)
+
+ with Image.open("Tests/images/hopper.pcd") as expected:
+ assert_image_equal(
+ im, expected.rotate(90 if orientation == 1 else 270, expand=True)
+ )
diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py
index 0a51fd493..9875fe096 100644
--- a/Tests/test_file_png.py
+++ b/Tests/test_file_png.py
@@ -229,7 +229,9 @@ class TestFilePng:
assert_image(im, "RGBA", (162, 150))
# image has 124 unique alpha values
- assert len(im.getchannel("A").getcolors()) == 124
+ colors = im.getchannel("A").getcolors()
+ assert colors is not None
+ assert len(colors) == 124
def test_load_transparent_rgb(self) -> None:
test_file = "Tests/images/rgb_trns.png"
@@ -241,7 +243,9 @@ class TestFilePng:
assert_image(im, "RGBA", (64, 64))
# image has 876 transparent pixels
- assert im.getchannel("A").getcolors()[0][0] == 876
+ colors = im.getchannel("A").getcolors()
+ assert colors is not None
+ assert colors[0][0] == 876
def test_save_p_transparent_palette(self, tmp_path: Path) -> None:
in_file = "Tests/images/pil123p.png"
@@ -262,7 +266,9 @@ class TestFilePng:
assert_image(im, "RGBA", (162, 150))
# image has 124 unique alpha values
- assert len(im.getchannel("A").getcolors()) == 124
+ colors = im.getchannel("A").getcolors()
+ assert colors is not None
+ assert len(colors) == 124
def test_save_p_single_transparency(self, tmp_path: Path) -> None:
in_file = "Tests/images/p_trns_single.png"
@@ -285,7 +291,9 @@ class TestFilePng:
assert im.getpixel((31, 31)) == (0, 255, 52, 0)
# image has 876 transparent pixels
- assert im.getchannel("A").getcolors()[0][0] == 876
+ colors = im.getchannel("A").getcolors()
+ assert colors is not None
+ assert colors[0][0] == 876
def test_save_p_transparent_black(self, tmp_path: Path) -> None:
# check if solid black image with full transparency
@@ -313,7 +321,9 @@ class TestFilePng:
assert im.info["transparency"] == 255
im_rgba = im.convert("RGBA")
- assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
+ colors = im_rgba.getchannel("A").getcolors()
+ assert colors is not None
+ assert colors[0][0] == num_transparent
test_file = tmp_path / "temp.png"
im.save(test_file)
@@ -324,7 +334,18 @@ class TestFilePng:
assert_image_equal(im, test_im)
test_im_rgba = test_im.convert("RGBA")
- assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent
+ colors = test_im_rgba.getchannel("A").getcolors()
+ assert colors is not None
+ assert colors[0][0] == num_transparent
+
+ def test_save_1_transparency(self, tmp_path: Path) -> None:
+ out = tmp_path / "temp.png"
+
+ im = Image.new("1", (1, 1), 1)
+ im.save(out, transparency=1)
+
+ with Image.open(out) as reloaded:
+ assert reloaded.info["transparency"] == 255
def test_save_rgb_single_transparency(self, tmp_path: Path) -> None:
in_file = "Tests/images/caption_6_33_22.png"
@@ -671,6 +692,9 @@ class TestFilePng:
im.save(out, bits=4, save_all=save_all)
with Image.open(out) as reloaded:
+ assert isinstance(reloaded, PngImagePlugin.PngImageFile)
+ assert reloaded.png is not None
+ assert reloaded.png.im_palette is not None
assert len(reloaded.png.im_palette[1]) == 48
def test_plte_length(self, tmp_path: Path) -> None:
@@ -681,6 +705,9 @@ class TestFilePng:
im.save(out)
with Image.open(out) as reloaded:
+ assert isinstance(reloaded, PngImagePlugin.PngImageFile)
+ assert reloaded.png is not None
+ assert reloaded.png.im_palette is not None
assert len(reloaded.png.im_palette[1]) == 3
def test_getxmp(self) -> None:
@@ -702,13 +729,17 @@ class TestFilePng:
def test_exif(self) -> None:
# With an EXIF chunk
with Image.open("Tests/images/exif.png") as im:
- exif = im._getexif()
- assert exif[274] == 1
+ assert isinstance(im, PngImagePlugin.PngImageFile)
+ exif_data = im._getexif()
+ assert exif_data is not None
+ assert exif_data[274] == 1
# With an ImageMagick zTXt chunk
with Image.open("Tests/images/exif_imagemagick.png") as im:
- exif = im._getexif()
- assert exif[274] == 1
+ assert isinstance(im, PngImagePlugin.PngImageFile)
+ exif_data = im._getexif()
+ assert exif_data is not None
+ assert exif_data[274] == 1
# Assert that info still can be extracted
# when the image is no longer a PngImageFile instance
@@ -717,8 +748,10 @@ class TestFilePng:
# With a tEXt chunk
with Image.open("Tests/images/exif_text.png") as im:
- exif = im._getexif()
- assert exif[274] == 1
+ assert isinstance(im, PngImagePlugin.PngImageFile)
+ exif_data = im._getexif()
+ assert exif_data is not None
+ assert exif_data[274] == 1
# With XMP tags
with Image.open("Tests/images/xmp_tags_orientation.png") as im:
@@ -740,8 +773,10 @@ class TestFilePng:
im.save(test_file, exif=im.getexif())
with Image.open(test_file) as reloaded:
- exif = reloaded._getexif()
- assert exif[274] == 1
+ assert isinstance(reloaded, PngImagePlugin.PngImageFile)
+ exif_data = reloaded._getexif()
+ assert exif_data is not None
+ assert exif_data[274] == 1
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py
index 68f2f9468..598e9a445 100644
--- a/Tests/test_file_ppm.py
+++ b/Tests/test_file_ppm.py
@@ -92,6 +92,13 @@ def test_16bit_pgm() -> None:
assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.tiff")
+def test_p4_save(tmp_path: Path) -> None:
+ with Image.open("Tests/images/hopper_1bit.pbm") as im:
+ filename = tmp_path / "temp.pbm"
+ im.save(filename)
+ assert_image_equal_tofile(im, filename)
+
+
def test_16bit_pgm_write(tmp_path: Path) -> None:
with Image.open("Tests/images/16_bit_binary.pgm") as im:
filename = tmp_path / "temp.pgm"
@@ -134,6 +141,12 @@ def test_pfm_big_endian(tmp_path: Path) -> None:
assert_image_equal_tofile(im, filename)
+def test_save_unsupported_mode(tmp_path: Path) -> None:
+ im = hopper("P")
+ with pytest.raises(OSError, match="cannot write mode P as PPM"):
+ im.save(tmp_path / "out.ppm")
+
+
@pytest.mark.parametrize(
"data",
[
diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py
index c2f162cf9..78534e154 100644
--- a/Tests/test_file_sun.py
+++ b/Tests/test_file_sun.py
@@ -84,8 +84,8 @@ def test_rgbx() -> None:
with Image.open(io.BytesIO(data)) as im:
r, g, b = im.split()
- im = Image.merge("RGB", (b, g, r))
- assert_image_equal_tofile(im, os.path.join(EXTRA_DIR, "32bpp.png"))
+ im_rgb = Image.merge("RGB", (b, g, r))
+ assert_image_equal_tofile(im_rgb, os.path.join(EXTRA_DIR, "32bpp.png"))
@pytest.mark.skipif(
diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py
index bd39de2e1..bb8d3eefc 100644
--- a/Tests/test_file_tga.py
+++ b/Tests/test_file_tga.py
@@ -274,13 +274,17 @@ def test_save_l_transparency(tmp_path: Path) -> None:
in_file = "Tests/images/la.tga"
with Image.open(in_file) as im:
assert im.mode == "LA"
- assert im.getchannel("A").getcolors()[0][0] == num_transparent
+ colors = im.getchannel("A").getcolors()
+ assert colors is not None
+ assert colors[0][0] == num_transparent
out = tmp_path / "temp.tga"
im.save(out)
with Image.open(out) as test_im:
assert test_im.mode == "LA"
- assert test_im.getchannel("A").getcolors()[0][0] == num_transparent
+ colors = test_im.getchannel("A").getcolors()
+ assert colors is not None
+ assert colors[0][0] == num_transparent
assert_image_equal(im, test_im)
diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py
index 0cf07ab33..a184bb0c0 100644
--- a/Tests/test_file_tiff.py
+++ b/Tests/test_file_tiff.py
@@ -764,9 +764,9 @@ class TestFileTiff:
# Test appending images
mp = BytesIO()
- im = Image.new("RGB", (100, 100), "#f00")
+ im_rgb = Image.new("RGB", (100, 100), "#f00")
ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]]
- im.copy().save(mp, format="TIFF", save_all=True, append_images=ims)
+ im_rgb.copy().save(mp, format="TIFF", save_all=True, append_images=ims)
mp.seek(0, os.SEEK_SET)
with Image.open(mp) as reread:
@@ -778,7 +778,7 @@ class TestFileTiff:
yield from ims
mp = BytesIO()
- im.save(mp, format="TIFF", save_all=True, append_images=im_generator(ims))
+ im_rgb.save(mp, format="TIFF", save_all=True, append_images=im_generator(ims))
mp.seek(0, os.SEEK_SET)
with Image.open(mp) as reread:
diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py
index 36ad8cee9..322ef5abc 100644
--- a/Tests/test_file_tiff_metadata.py
+++ b/Tests/test_file_tiff_metadata.py
@@ -175,13 +175,13 @@ def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None:
del info[278]
# Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT
- im = im.resize((500, 500))
- info[TiffImagePlugin.IMAGEWIDTH] = im.width
+ im_resized = im.resize((500, 500))
+ info[TiffImagePlugin.IMAGEWIDTH] = im_resized.width
# STRIPBYTECOUNTS can be a SHORT or a LONG
info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT
- im.save(out, tiffinfo=info)
+ im_resized.save(out, tiffinfo=info)
with Image.open(out) as reloaded:
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
diff --git a/Tests/test_file_wal.py b/Tests/test_file_wal.py
index b15d79d61..549d47054 100644
--- a/Tests/test_file_wal.py
+++ b/Tests/test_file_wal.py
@@ -1,5 +1,7 @@
from __future__ import annotations
+from io import BytesIO
+
from PIL import WalImageFile
from .helper import assert_image_equal_tofile
@@ -13,12 +15,22 @@ def test_open() -> None:
assert im.format_description == "Quake2 Texture"
assert im.mode == "P"
assert im.size == (128, 128)
+ assert "next_name" not in im.info
assert isinstance(im, WalImageFile.WalImageFile)
assert_image_equal_tofile(im, "Tests/images/hopper_wal.png")
+def test_next_name() -> None:
+ with open(TEST_FILE, "rb") as fp:
+ data = bytearray(fp.read())
+ data[56:60] = b"Test"
+ f = BytesIO(data)
+ with WalImageFile.open(f) as im:
+ assert im.info["next_name"] == b"Test"
+
+
def test_load() -> None:
with WalImageFile.open(TEST_FILE) as im:
px = im.load()
diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py
index 503761374..600448fb9 100644
--- a/Tests/test_file_webp_animated.py
+++ b/Tests/test_file_webp_animated.py
@@ -4,13 +4,13 @@ from collections.abc import Generator
from pathlib import Path
import pytest
-from packaging.version import parse as parse_version
-from PIL import GifImagePlugin, Image, WebPImagePlugin, features
+from PIL import GifImagePlugin, Image, WebPImagePlugin
from .helper import (
assert_image_equal,
assert_image_similar,
+ has_feature_version,
is_big_endian,
skip_unless_feature,
)
@@ -53,11 +53,8 @@ def test_write_animation_L(tmp_path: Path) -> None:
im.load()
assert_image_similar(im, orig.convert("RGBA"), 32.9)
- if is_big_endian():
- version = features.version_module("webp")
- assert version is not None
- if parse_version(version) < parse_version("1.2.2"):
- pytest.skip("Fails with libwebp earlier than 1.2.2")
+ if is_big_endian() and not has_feature_version("webp", "1.2.2"):
+ pytest.skip("Fails with libwebp earlier than 1.2.2")
orig.seek(orig.n_frames - 1)
im.seek(im.n_frames - 1)
orig.load()
@@ -81,11 +78,8 @@ def test_write_animation_RGB(tmp_path: Path) -> None:
assert_image_equal(im, frame1.convert("RGBA"))
# Compare second frame to original
- if is_big_endian():
- version = features.version_module("webp")
- assert version is not None
- if parse_version(version) < parse_version("1.2.2"):
- pytest.skip("Fails with libwebp earlier than 1.2.2")
+ if is_big_endian() and not has_feature_version("webp", "1.2.2"):
+ pytest.skip("Fails with libwebp earlier than 1.2.2")
im.seek(1)
im.load()
assert_image_equal(im, frame2.convert("RGBA"))
diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py
index 7543d22da..3de412b83 100644
--- a/Tests/test_file_webp_metadata.py
+++ b/Tests/test_file_webp_metadata.py
@@ -22,11 +22,13 @@ except ImportError:
def test_read_exif_metadata() -> None:
file_path = "Tests/images/flower.webp"
with Image.open(file_path) as image:
+ assert isinstance(image, WebPImagePlugin.WebPImageFile)
assert image.format == "WEBP"
exif_data = image.info.get("exif", None)
assert exif_data
exif = image._getexif()
+ assert exif is not None
# Camera make
assert exif[271] == "Canon"
diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py
index dcf5f000f..906080d15 100644
--- a/Tests/test_file_wmf.py
+++ b/Tests/test_file_wmf.py
@@ -44,6 +44,18 @@ def test_load_zero_inch() -> None:
pass
+def test_load_unsupported_wmf() -> None:
+ b = BytesIO(b"\xd7\xcd\xc6\x9a\x00\x00" + b"\x01" * 10)
+ with pytest.raises(SyntaxError, match="Unsupported WMF file format"):
+ WmfImagePlugin.WmfStubImageFile(b)
+
+
+def test_load_unsupported() -> None:
+ b = BytesIO(b"\x01\x00\x00\x00")
+ with pytest.raises(SyntaxError, match="Unsupported file format"):
+ WmfImagePlugin.WmfStubImageFile(b)
+
+
def test_render() -> None:
with open("Tests/images/drawing.emf", "rb") as fp:
data = fp.read()
diff --git a/Tests/test_font_crash.py b/Tests/test_font_crash.py
index b82340ef7..54bd2d183 100644
--- a/Tests/test_font_crash.py
+++ b/Tests/test_font_crash.py
@@ -9,7 +9,8 @@ from .helper import skip_unless_feature
class TestFontCrash:
def _fuzz_font(self, font: ImageFont.FreeTypeFont) -> None:
- # from fuzzers.fuzz_font
+ # Copy of the code from fuzz_font() in Tests/oss-fuzz/fuzzers.py
+ # that triggered a problem when fuzzing
font.getbbox("ABC")
font.getmask("test text")
with Image.new(mode="RGBA", size=(200, 200)) as im:
diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py
index 9cbf18566..861eccc11 100644
--- a/Tests/test_format_hsv.py
+++ b/Tests/test_format_hsv.py
@@ -2,12 +2,15 @@ from __future__ import annotations
import colorsys
import itertools
-from typing import Callable
from PIL import Image
from .helper import assert_image_similar, hopper
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
def int_to_float(i: int) -> float:
return i / 255
diff --git a/Tests/test_image.py b/Tests/test_image.py
index 83b027aa2..88f55638e 100644
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -19,6 +19,7 @@ from PIL import (
ImageDraw,
ImageFile,
ImagePalette,
+ ImageShow,
UnidentifiedImageError,
features,
)
@@ -283,33 +284,6 @@ class TestImage:
assert item is not None
assert item != num
- def test_expand_x(self) -> None:
- # Arrange
- im = hopper()
- orig_size = im.size
- xmargin = 5
-
- # Act
- im = im._expand(xmargin)
-
- # Assert
- assert im.size[0] == orig_size[0] + 2 * xmargin
- assert im.size[1] == orig_size[1] + 2 * xmargin
-
- def test_expand_xy(self) -> None:
- # Arrange
- im = hopper()
- orig_size = im.size
- xmargin = 5
- ymargin = 3
-
- # Act
- im = im._expand(xmargin, ymargin)
-
- # Assert
- assert im.size[0] == orig_size[0] + 2 * xmargin
- assert im.size[1] == orig_size[1] + 2 * ymargin
-
def test_getbands(self) -> None:
# Assert
assert hopper("RGB").getbands() == ("R", "G", "B")
@@ -388,6 +362,37 @@ class TestImage:
assert img_colors is not None
assert sorted(img_colors) == expected_colors
+ def test_alpha_composite_la(self) -> None:
+ # Arrange
+ expected_colors = sorted(
+ [
+ (3300, (255, 255)),
+ (1156, (170, 192)),
+ (1122, (128, 255)),
+ (1089, (0, 0)),
+ (1122, (255, 128)),
+ (1122, (0, 128)),
+ (1089, (0, 255)),
+ ]
+ )
+
+ dst = Image.new("LA", size=(100, 100), color=(0, 255))
+ draw = ImageDraw.Draw(dst)
+ draw.rectangle((0, 33, 100, 66), fill=(0, 128))
+ draw.rectangle((0, 67, 100, 100), fill=(0, 0))
+ src = Image.new("LA", size=(100, 100), color=(255, 255))
+ draw = ImageDraw.Draw(src)
+ draw.rectangle((33, 0, 66, 100), fill=(255, 128))
+ draw.rectangle((67, 0, 100, 100), fill=(255, 0))
+
+ # Act
+ img = Image.alpha_composite(dst, src)
+
+ # Assert
+ img_colors = img.getcolors()
+ assert img_colors is not None
+ assert sorted(img_colors) == expected_colors
+
def test_alpha_inplace(self) -> None:
src = Image.new("RGBA", (128, 128), "blue")
@@ -608,8 +613,8 @@ class TestImage:
assert im.getpixel((0, 0)) == 0
assert im.getpixel((255, 255)) == 255
with Image.open(target_file) as target:
- target = target.convert(mode)
- assert_image_equal(im, target)
+ im_target = target.convert(mode)
+ assert_image_equal(im, im_target)
def test_radial_gradient_wrong_mode(self) -> None:
# Arrange
@@ -633,8 +638,8 @@ class TestImage:
assert im.getpixel((0, 0)) == 255
assert im.getpixel((128, 128)) == 0
with Image.open(target_file) as target:
- target = target.convert(mode)
- assert_image_equal(im, target)
+ im_target = target.convert(mode)
+ assert_image_equal(im, im_target)
def test_register_extensions(self) -> None:
test_format = "a"
@@ -658,20 +663,20 @@ class TestImage:
assert_image_equal(im, im.remap_palette(list(range(256))))
# Test identity transform with an RGBA palette
- im = Image.new("P", (256, 1))
+ im_p = Image.new("P", (256, 1))
for x in range(256):
- im.putpixel((x, 0), x)
- im.putpalette(list(range(256)) * 4, "RGBA")
- im_remapped = im.remap_palette(list(range(256)))
- assert_image_equal(im, im_remapped)
- assert im.palette is not None
+ im_p.putpixel((x, 0), x)
+ im_p.putpalette(list(range(256)) * 4, "RGBA")
+ im_remapped = im_p.remap_palette(list(range(256)))
+ assert_image_equal(im_p, im_remapped)
+ assert im_p.palette is not None
assert im_remapped.palette is not None
- assert im.palette.palette == im_remapped.palette.palette
+ assert im_p.palette.palette == im_remapped.palette.palette
# Test illegal image mode
- with hopper() as im:
+ with hopper() as im_hopper:
with pytest.raises(ValueError):
- im.remap_palette([])
+ im_hopper.remap_palette([])
def test_remap_palette_transparency(self) -> None:
im = Image.new("P", (1, 2), (0, 0, 0))
@@ -922,6 +927,17 @@ class TestImage:
reloaded_exif.load(exif.tobytes())
assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769)
+ def test_delete_ifd_tag(self) -> None:
+ with Image.open("Tests/images/flower.jpg") as im:
+ exif = im.getexif()
+ exif.get_ifd(0x8769)
+ assert 0x8769 in exif
+ del exif[0x8769]
+
+ reloaded_exif = Image.Exif()
+ reloaded_exif.load(exif.tobytes())
+ assert 0x8769 not in reloaded_exif
+
def test_exif_load_from_fp(self) -> None:
with Image.open("Tests/images/flower.jpg") as im:
data = im.info["exif"]
@@ -1005,6 +1021,13 @@ class TestImage:
with pytest.warns(DeprecationWarning, match="Image.Image.get_child_images"):
assert im.get_child_images() == []
+ def test_show(self, monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setattr(ImageShow, "_viewers", [])
+
+ im = Image.new("RGB", (1, 1))
+ with pytest.warns(DeprecationWarning, match="Image._show"):
+ Image._show(im)
+
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
def test_zero_tobytes(self, size: tuple[int, int]) -> None:
im = Image.new("RGB", size)
@@ -1076,6 +1099,12 @@ class TestImage:
assert im.palette is not None
assert im.palette.colors[(27, 35, 6, 214)] == 24
+ def test_merge_pa(self) -> None:
+ p = hopper("P")
+ a = Image.new("L", p.size)
+ pa = Image.merge("PA", (p, a))
+ assert p.getpalette() == pa.getpalette()
+
def test_constants(self) -> None:
for enum in (
Image.Transpose,
diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py
index b3de5c13d..07c12594a 100644
--- a/Tests/test_image_access.py
+++ b/Tests/test_image_access.py
@@ -315,3 +315,10 @@ int main(int argc, char* argv[])
process = subprocess.Popen(["embed_pil.exe"], env=env)
process.communicate()
assert process.returncode == 0
+
+ def teardown_method(self) -> None:
+ try:
+ os.remove("embed_pil.c")
+ except FileNotFoundError:
+ # If the test was skipped or failed, the file won't exist
+ pass
diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py
index ecbce3d6f..abb22f949 100644
--- a/Tests/test_image_array.py
+++ b/Tests/test_image_array.py
@@ -101,9 +101,8 @@ def test_fromarray_strides_without_tobytes() -> None:
self.__array_interface__ = arr_params
with pytest.raises(ValueError):
- wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)})
- with pytest.warns(DeprecationWarning, match="'mode' parameter"):
- Image.fromarray(wrapped, "L")
+ wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1), "typestr": "|u1"})
+ Image.fromarray(wrapped, "L")
def test_fromarray_palette() -> None:
@@ -112,9 +111,16 @@ def test_fromarray_palette() -> None:
a = numpy.array(i)
# Act
- with pytest.warns(DeprecationWarning, match="'mode' parameter"):
- out = Image.fromarray(a, "P")
+ out = Image.fromarray(a, "P")
# Assert that the Python and C palettes match
assert out.palette is not None
assert len(out.palette.colors) == len(out.im.getpalette()) / 3
+
+
+def test_deprecation() -> None:
+ a = numpy.array(im.convert("L"))
+ with pytest.warns(
+ DeprecationWarning, match="'mode' parameter for changing data types"
+ ):
+ Image.fromarray(a, "1")
diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py
index 33f844437..547a6c2c6 100644
--- a/Tests/test_image_convert.py
+++ b/Tests/test_image_convert.py
@@ -80,8 +80,8 @@ def test_16bit() -> None:
_test_float_conversion(im)
for color in (65535, 65536):
- im = Image.new("I", (1, 1), color)
- im_i16 = im.convert("I;16")
+ im_i = Image.new("I", (1, 1), color)
+ im_i16 = im_i.convert("I;16")
assert im_i16.getpixel((0, 0)) == 65535
@@ -97,6 +97,13 @@ def test_opaque() -> None:
assert_image_equal(alpha, solid)
+def test_rgba() -> None:
+ with Image.open("Tests/images/transparent.png") as im:
+ assert im.mode == "RGBA"
+
+ assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
+
+
def test_rgba_p() -> None:
im = hopper("RGBA")
im.putalpha(hopper("L"))
@@ -107,11 +114,19 @@ def test_rgba_p() -> None:
assert_image_similar(im, comparable, 20)
-def test_rgba() -> None:
- with Image.open("Tests/images/transparent.png") as im:
- assert im.mode == "RGBA"
+def test_rgba_pa() -> None:
+ im = hopper("RGBA").convert("PA").convert("RGB")
+ expected = hopper("RGB")
- assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
+ assert_image_similar(im, expected, 9.3)
+
+
+def test_pa() -> None:
+ im = hopper().convert("PA")
+
+ palette = im.palette
+ assert palette is not None
+ assert palette.colors != {}
def test_trns_p(tmp_path: Path) -> None:
diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py
index 07fec2e64..b90ce84bc 100644
--- a/Tests/test_image_crop.py
+++ b/Tests/test_image_crop.py
@@ -78,13 +78,13 @@ def test_crop_crash() -> None:
extents = (1, 1, 10, 10)
# works prepatch
with Image.open(test_img) as img:
- img2 = img.crop(extents)
- img2.load()
+ img1 = img.crop(extents)
+ img1.load()
# fail prepatch
with Image.open(test_img) as img:
- img = img.crop(extents)
- img.load()
+ img2 = img.crop(extents)
+ img2.load()
def test_crop_zero() -> None:
diff --git a/Tests/test_image_getdata.py b/Tests/test_image_getdata.py
index dd3d70b34..c8b213d84 100644
--- a/Tests/test_image_getdata.py
+++ b/Tests/test_image_getdata.py
@@ -15,7 +15,7 @@ def test_sanity() -> None:
def test_mode() -> None:
- def getdata(mode: str) -> tuple[float | tuple[int, ...], int, int]:
+ def getdata(mode: str) -> tuple[float | tuple[int, ...] | None, int, int]:
im = hopper(mode).resize((32, 30), Image.Resampling.NEAREST)
data = im.getdata()
return data[0], len(data), len(list(data))
diff --git a/Tests/test_image_histogram.py b/Tests/test_image_histogram.py
index dbd55d4c2..436eb78a2 100644
--- a/Tests/test_image_histogram.py
+++ b/Tests/test_image_histogram.py
@@ -10,9 +10,12 @@ def test_histogram() -> None:
assert histogram("1") == (256, 0, 10994)
assert histogram("L") == (256, 0, 662)
+ assert histogram("LA") == (512, 0, 16384)
+ assert histogram("La") == (512, 0, 16384)
assert histogram("I") == (256, 0, 662)
assert histogram("F") == (256, 0, 662)
assert histogram("P") == (256, 0, 1551)
+ assert histogram("PA") == (512, 0, 16384)
assert histogram("RGB") == (768, 4, 675)
assert histogram("RGBA") == (1024, 0, 16384)
assert histogram("CMYK") == (1024, 0, 16384)
diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py
index 2cff6c893..37e4df103 100644
--- a/Tests/test_image_paste.py
+++ b/Tests/test_image_paste.py
@@ -124,6 +124,21 @@ class TestImagingPaste:
im = im.crop((12, 23, im2.width + 12, im2.height + 23))
assert_image_equal(im, im2)
+ @pytest.mark.parametrize("y", [10, -10])
+ @pytest.mark.parametrize("mode", ["L", "RGB"])
+ @pytest.mark.parametrize("mask_mode", ["", "1", "L", "LA", "RGBa"])
+ def test_image_self(self, y: int, mode: str, mask_mode: str) -> None:
+ im = getattr(self, "gradient_" + mode)
+ mask = Image.new(mask_mode, im.size, 0xFFFFFFFF) if mask_mode else None
+
+ im_self = im.copy()
+ im_self.paste(im_self, (0, y), mask)
+
+ im_copy = im.copy()
+ im_copy.paste(im_copy.copy(), (0, y), mask)
+
+ assert_image_equal(im_self, im_copy)
+
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_image_mask_1(self, mode: str) -> None:
im = Image.new(mode, (200, 200), "white")
diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py
index f2c447f71..661764b60 100644
--- a/Tests/test_image_putpalette.py
+++ b/Tests/test_image_putpalette.py
@@ -62,6 +62,7 @@ def test_putpalette_with_alpha_values() -> None:
expected = im.convert("RGBA")
palette = im.getpalette()
+ assert palette is not None
transparency = im.info.pop("transparency")
palette_with_alpha_values = []
diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py
index 6d313cb8c..887628560 100644
--- a/Tests/test_image_quantize.py
+++ b/Tests/test_image_quantize.py
@@ -1,11 +1,16 @@
from __future__ import annotations
import pytest
-from packaging.version import parse as parse_version
-from PIL import Image, features
+from PIL import Image
-from .helper import assert_image_similar, hopper, is_ppc64le, skip_unless_feature
+from .helper import (
+ assert_image_similar,
+ has_feature_version,
+ hopper,
+ is_ppc64le,
+ skip_unless_feature,
+)
def test_sanity() -> None:
@@ -23,11 +28,8 @@ def test_sanity() -> None:
@skip_unless_feature("libimagequant")
def test_libimagequant_quantize() -> None:
image = hopper()
- if is_ppc64le():
- version = features.version_feature("libimagequant")
- assert version is not None
- if parse_version(version) < parse_version("4"):
- pytest.skip("Fails with libimagequant earlier than 4.0.0 on ppc64le")
+ if is_ppc64le() and not has_feature_version("libimagequant", "4"):
+ pytest.skip("Fails with libimagequant earlier than 4.0.0 on ppc64le")
converted = image.quantize(100, Image.Quantize.LIBIMAGEQUANT)
assert converted.mode == "P"
assert_image_similar(converted.convert("RGB"), image, 15)
@@ -56,8 +58,8 @@ def test_rgba_quantize() -> None:
def test_quantize() -> None:
with Image.open("Tests/images/caption_6_33_22.png") as image:
- image = image.convert("RGB")
- converted = image.quantize()
+ converted = image.convert("RGB")
+ converted = converted.quantize()
assert converted.mode == "P"
assert_image_similar(converted.convert("RGB"), image, 1)
@@ -65,13 +67,13 @@ def test_quantize() -> None:
def test_quantize_no_dither() -> None:
image = hopper()
with Image.open("Tests/images/caption_6_33_22.png") as palette:
- palette = palette.convert("P")
+ palette_p = palette.convert("P")
- converted = image.quantize(dither=Image.Dither.NONE, palette=palette)
+ converted = image.quantize(dither=Image.Dither.NONE, palette=palette_p)
assert converted.mode == "P"
assert converted.palette is not None
- assert palette.palette is not None
- assert converted.palette.palette == palette.palette.palette
+ assert palette_p.palette is not None
+ assert converted.palette.palette == palette_p.palette.palette
def test_quantize_no_dither2() -> None:
@@ -95,10 +97,10 @@ def test_quantize_no_dither2() -> None:
def test_quantize_dither_diff() -> None:
image = hopper()
with Image.open("Tests/images/caption_6_33_22.png") as palette:
- palette = palette.convert("P")
+ palette_p = palette.convert("P")
- dither = image.quantize(dither=Image.Dither.FLOYDSTEINBERG, palette=palette)
- nodither = image.quantize(dither=Image.Dither.NONE, palette=palette)
+ dither = image.quantize(dither=Image.Dither.FLOYDSTEINBERG, palette=palette_p)
+ nodither = image.quantize(dither=Image.Dither.NONE, palette=palette_p)
assert dither.tobytes() != nodither.tobytes()
@@ -116,6 +118,15 @@ def test_quantize_kmeans(method: Image.Quantize) -> None:
im.quantize(kmeans=-1, method=method)
+@skip_unless_feature("libimagequant")
+def test_resize() -> None:
+ im = hopper().resize((100, 100))
+ converted = im.quantize(100, Image.Quantize.LIBIMAGEQUANT)
+ colors = converted.getcolors()
+ assert colors is not None
+ assert len(colors) == 100
+
+
def test_colors() -> None:
im = hopper()
colors = 2
diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py
index 270500a44..323d31f51 100644
--- a/Tests/test_image_resize.py
+++ b/Tests/test_image_resize.py
@@ -314,8 +314,8 @@ class TestImageResize:
@skip_unless_feature("libtiff")
def test_transposed(self) -> None:
with Image.open("Tests/images/g4_orientation_5.tif") as im:
- im = im.resize((64, 64))
- assert im.size == (64, 64)
+ im_resized = im.resize((64, 64))
+ assert im_resized.size == (64, 64)
@pytest.mark.parametrize(
"mode", ("L", "RGB", "I", "I;16", "I;16L", "I;16B", "I;16N", "F")
diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py
index 252a15db7..c3ff52f57 100644
--- a/Tests/test_image_rotate.py
+++ b/Tests/test_image_rotate.py
@@ -43,8 +43,8 @@ def test_angle(angle: int) -> None:
with Image.open("Tests/images/test-card.png") as im:
rotate(im, im.mode, angle)
- im = hopper()
- assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1))
+ im_hopper = hopper()
+ assert_image_equal(im_hopper.rotate(angle), im_hopper.rotate(angle, expand=1))
@pytest.mark.parametrize("angle", (0, 45, 90, 180, 270))
@@ -76,9 +76,9 @@ def test_center_0() -> None:
with Image.open("Tests/images/hopper_45.png") as target:
target_origin = target.size[1] / 2
- target = target.crop((0, target_origin, 128, target_origin + 128))
+ im_target = target.crop((0, target_origin, 128, target_origin + 128))
- assert_image_similar(im, target, 15)
+ assert_image_similar(im, im_target, 15)
def test_center_14() -> None:
@@ -87,22 +87,22 @@ def test_center_14() -> None:
with Image.open("Tests/images/hopper_45.png") as target:
target_origin = target.size[1] / 2 - 14
- target = target.crop((6, target_origin, 128 + 6, target_origin + 128))
+ im_target = target.crop((6, target_origin, 128 + 6, target_origin + 128))
- assert_image_similar(im, target, 10)
+ assert_image_similar(im, im_target, 10)
def test_translate() -> None:
im = hopper()
with Image.open("Tests/images/hopper_45.png") as target:
target_origin = (target.size[1] / 2 - 64) - 5
- target = target.crop(
+ im_target = target.crop(
(target_origin, target_origin, target_origin + 128, target_origin + 128)
)
im = im.rotate(45, translate=(5, 5), resample=Image.Resampling.BICUBIC)
- assert_image_similar(im, target, 1)
+ assert_image_similar(im, im_target, 1)
def test_fastpath_center() -> None:
diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py
index 1181f6fca..2ae230f3d 100644
--- a/Tests/test_image_thumbnail.py
+++ b/Tests/test_image_thumbnail.py
@@ -159,9 +159,9 @@ def test_reducing_gap_for_DCT_scaling() -> None:
with Image.open("Tests/images/hopper.jpg") as ref:
# thumbnail should call draft with reducing_gap scale
ref.draft(None, (18 * 3, 18 * 3))
- ref = ref.resize((18, 18), Image.Resampling.BICUBIC)
+ im_ref = ref.resize((18, 18), Image.Resampling.BICUBIC)
with Image.open("Tests/images/hopper.jpg") as im:
im.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=3.0)
- assert_image_similar(ref, im, 1.4)
+ assert_image_similar(im_ref, im, 1.4)
diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py
index 0429eb99d..7cf52ddba 100644
--- a/Tests/test_image_transform.py
+++ b/Tests/test_image_transform.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import math
-from typing import Callable
import pytest
@@ -9,6 +8,10 @@ from PIL import Image, ImageTransform
from .helper import assert_image_equal, assert_image_similar, hopper
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
class TestImageTransform:
def test_sanity(self) -> None:
diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py
index 4309214f5..61812ca7d 100644
--- a/Tests/test_imagechops.py
+++ b/Tests/test_imagechops.py
@@ -1,11 +1,13 @@
from __future__ import annotations
-from typing import Callable
-
from PIL import Image, ImageChops
from .helper import assert_image_equal, hopper
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
BLACK = (0, 0, 0)
BROWN = (127, 64, 0)
CYAN = (0, 255, 255)
diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py
index 55a4a87fb..5fd7caa7c 100644
--- a/Tests/test_imagecms.py
+++ b/Tests/test_imagecms.py
@@ -7,7 +7,7 @@ import shutil
import sys
from io import BytesIO
from pathlib import Path
-from typing import Any, Literal, cast
+from typing import Literal, cast
import pytest
@@ -31,6 +31,9 @@ except ImportError:
# Skipped via setup_module()
pass
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from typing import Any
SRGB = "Tests/icc/sRGB_IEC61966-2-1_black_scaled.icc"
HAVE_PROFILE = os.path.exists(SRGB)
@@ -208,9 +211,10 @@ def test_exceptions() -> None:
ImageCms.getProfileName(None) # type: ignore[arg-type]
skip_missing()
- # Python <= 3.9: "an integer is required (got type NoneType)"
- # Python > 3.9: "'NoneType' object cannot be interpreted as an integer"
- with pytest.raises(ImageCms.PyCMSError, match="integer"):
+ with pytest.raises(
+ ImageCms.PyCMSError,
+ match="'NoneType' object cannot be interpreted as an integer",
+ ):
ImageCms.isIntentSupported(SRGB, None, None) # type: ignore[arg-type]
@@ -690,3 +694,17 @@ def test_cmyk_lab() -> None:
im = Image.new("CMYK", (1, 1))
converted_im = im.convert("LAB")
assert converted_im.getpixel((0, 0)) == (255, 128, 128)
+
+
+def test_deprecation() -> None:
+ profile = ImageCmsProfile(ImageCms.createProfile("sRGB"))
+ with pytest.warns(
+ DeprecationWarning, match="ImageCms.ImageCmsProfile.product_name"
+ ):
+ profile.product_name
+ with pytest.warns(
+ DeprecationWarning, match="ImageCms.ImageCmsProfile.product_info"
+ ):
+ profile.product_info
+ with pytest.raises(AttributeError):
+ profile.this_attribute_does_not_exist
diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py
index e1dcbc52c..49765cd68 100644
--- a/Tests/test_imagedraw.py
+++ b/Tests/test_imagedraw.py
@@ -1,13 +1,10 @@
from __future__ import annotations
import os.path
-from collections.abc import Sequence
-from typing import Callable
import pytest
from PIL import Image, ImageColor, ImageDraw, ImageFont, features
-from PIL._typing import Coords
from .helper import (
assert_image_equal,
@@ -17,6 +14,12 @@ from .helper import (
skip_unless_feature,
)
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable, Sequence
+
+ from PIL._typing import Coords
+
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (190, 190, 190)
@@ -195,10 +198,10 @@ def test_bitmap() -> None:
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
with Image.open("Tests/images/pil123rgba.png") as small:
- small = small.resize((50, 50), Image.Resampling.NEAREST)
+ small_resized = small.resize((50, 50), Image.Resampling.NEAREST)
# Act
- draw.bitmap((10, 10), small)
+ draw.bitmap((10, 10), small_resized)
# Assert
assert_image_equal_tofile(im, "Tests/images/imagedraw_bitmap.png")
@@ -1491,7 +1494,9 @@ def test_default_font_size() -> None:
def draw_text() -> None:
draw.text((0, 0), text, font_size=16)
- assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
+ assert_image_similar_tofile(
+ im, "Tests/images/imagedraw_default_font_size.png", 1
+ )
check(draw_text)
@@ -1510,7 +1515,9 @@ def test_default_font_size() -> None:
def draw_multiline_text() -> None:
draw.multiline_text((0, 0), text, font_size=16)
- assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
+ assert_image_similar_tofile(
+ im, "Tests/images/imagedraw_default_font_size.png", 1
+ )
check(draw_multiline_text)
diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py
index d4dfb1b6d..7dfb3abf9 100644
--- a/Tests/test_imagefile.py
+++ b/Tests/test_imagefile.py
@@ -164,6 +164,11 @@ class TestImageFile:
with pytest.raises(OSError):
p.close()
+ def test_negative_offset(self) -> None:
+ with Image.open("Tests/images/raw_negative_stride.bin") as im:
+ with pytest.raises(ValueError, match="Tile offset cannot be negative"):
+ im.load()
+
def test_no_format(self) -> None:
buf = BytesIO(b"\x00" * 255)
diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py
index 4565d35ba..39ee9b9c9 100644
--- a/Tests/test_imagefont.py
+++ b/Tests/test_imagefont.py
@@ -19,6 +19,7 @@ from .helper import (
assert_image_equal,
assert_image_equal_tofile,
assert_image_similar_tofile,
+ has_feature_version,
is_win32,
skip_unless_feature,
skip_unless_feature_version,
@@ -492,6 +493,11 @@ def test_stroke_mask() -> None:
assert mask.getpixel((42, 5)) == 255
+def test_load_invalid_file() -> None:
+ with pytest.raises(SyntaxError, match="Not a PILfont file"):
+ ImageFont.load("Tests/images/1_trns.png")
+
+
def test_load_when_image_not_found() -> None:
with tempfile.NamedTemporaryFile(delete=False) as tmp:
pass
@@ -549,7 +555,7 @@ def test_default_font() -> None:
draw.text((10, 60), txt, font=larger_default_font)
# Assert
- assert_image_equal_tofile(im, "Tests/images/default_font_freetype.png")
+ assert_image_similar_tofile(im, "Tests/images/default_font_freetype.png", 0.13)
@pytest.mark.parametrize("mode", ("", "1", "RGBA"))
@@ -1055,7 +1061,10 @@ def test_colr(layout_engine: ImageFont.Layout) -> None:
d.text((15, 5), "Bungee", font=font, embedded_color=True)
- assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 21)
+ if has_feature_version("freetype2", "2.14.0"):
+ assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 6.1)
+ else:
+ assert_image_similar_tofile(im, "Tests/images/colr_bungee_older.png", 21)
@skip_unless_feature_version("freetype2", "2.10.0")
@@ -1071,7 +1080,7 @@ def test_colr_mask(layout_engine: ImageFont.Layout) -> None:
d.text((15, 5), "Bungee", "black", font=font)
- assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22)
+ assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 14.1)
def test_woff2(layout_engine: ImageFont.Layout) -> None:
diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py
index 5954de874..633f6756b 100644
--- a/Tests/test_imagefontctl.py
+++ b/Tests/test_imagefontctl.py
@@ -7,6 +7,7 @@ from PIL import Image, ImageDraw, ImageFont
from .helper import (
assert_image_equal_tofile,
assert_image_similar_tofile,
+ has_feature_version,
skip_unless_feature,
)
@@ -104,11 +105,9 @@ def test_text_direction_ttb() -> None:
im = Image.new(mode="RGB", size=(100, 300))
draw = ImageDraw.Draw(im)
- try:
- draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb")
- except ValueError as ex:
- if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
- pytest.skip("libraqm 0.7 or greater not available")
+ if not has_feature_version("raqm", "0.7"):
+ pytest.skip("libraqm 0.7 or greater not available")
+ draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb")
target = "Tests/images/test_direction_ttb.png"
assert_image_similar_tofile(im, target, 2.8)
@@ -119,19 +118,17 @@ def test_text_direction_ttb_stroke() -> None:
im = Image.new(mode="RGB", size=(100, 300))
draw = ImageDraw.Draw(im)
- try:
- draw.text(
- (27, 27),
- "あい",
- font=ttf,
- fill=500,
- direction="ttb",
- stroke_width=2,
- stroke_fill="#0f0",
- )
- except ValueError as ex:
- if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
- pytest.skip("libraqm 0.7 or greater not available")
+ if not has_feature_version("raqm", "0.7"):
+ pytest.skip("libraqm 0.7 or greater not available")
+ draw.text(
+ (27, 27),
+ "あい",
+ font=ttf,
+ fill=500,
+ direction="ttb",
+ stroke_width=2,
+ stroke_fill="#0f0",
+ )
target = "Tests/images/test_direction_ttb_stroke.png"
assert_image_similar_tofile(im, target, 19.4)
@@ -186,7 +183,7 @@ def test_x_max_and_y_offset() -> None:
draw.text((0, 0), "لح", font=ttf, fill=500)
target = "Tests/images/test_x_max_and_y_offset.png"
- assert_image_similar_tofile(im, target, 0.5)
+ assert_image_similar_tofile(im, target, 3.8)
def test_language() -> None:
@@ -219,14 +216,9 @@ def test_getlength(
im = Image.new(mode, (1, 1), 0)
d = ImageDraw.Draw(im)
- try:
- assert d.textlength(text, ttf, direction) == expected
- except ValueError as ex:
- if (
- direction == "ttb"
- and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction"
- ):
- pytest.skip("libraqm 0.7 or greater not available")
+ if direction == "ttb" and not has_feature_version("raqm", "0.7"):
+ pytest.skip("libraqm 0.7 or greater not available")
+ assert d.textlength(text, ttf, direction) == expected
@pytest.mark.parametrize("mode", ("L", "1"))
@@ -242,17 +234,12 @@ def test_getlength_combine(mode: str, direction: str, text: str) -> None:
ttf = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
- try:
- target = ttf.getlength("ii", mode, direction)
- actual = ttf.getlength(text, mode, direction)
+ if direction == "ttb" and not has_feature_version("raqm", "0.7"):
+ pytest.skip("libraqm 0.7 or greater not available")
+ target = ttf.getlength("ii", mode, direction)
+ actual = ttf.getlength(text, mode, direction)
- assert actual == target
- except ValueError as ex:
- if (
- direction == "ttb"
- and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction"
- ):
- pytest.skip("libraqm 0.7 or greater not available")
+ assert actual == target
@pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm"))
@@ -265,11 +252,9 @@ def test_anchor_ttb(anchor: str) -> None:
d = ImageDraw.Draw(im)
d.line(((0, 200), (200, 200)), "gray")
d.line(((100, 0), (100, 400)), "gray")
- try:
- d.text((100, 200), text, fill="black", anchor=anchor, direction="ttb", font=f)
- except ValueError as ex:
- if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
- pytest.skip("libraqm 0.7 or greater not available")
+ if not has_feature_version("raqm", "0.7"):
+ pytest.skip("libraqm 0.7 or greater not available")
+ d.text((100, 200), text, fill="black", anchor=anchor, direction="ttb", font=f)
assert_image_similar_tofile(im, path, 1) # fails at 5
@@ -310,10 +295,12 @@ combine_tests = (
# this tests various combining characters for anchor alignment and clipping
@pytest.mark.parametrize(
- "name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests]
+ "name, text, anchor, direction, epsilon",
+ combine_tests,
+ ids=[r[0] for r in combine_tests],
)
def test_combine(
- name: str, text: str, dir: str | None, anchor: str | None, epsilon: float
+ name: str, text: str, direction: str | None, anchor: str | None, epsilon: float
) -> None:
path = f"Tests/images/test_combine_{name}.png"
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
@@ -322,11 +309,9 @@ def test_combine(
d = ImageDraw.Draw(im)
d.line(((0, 200), (400, 200)), "gray")
d.line(((200, 0), (200, 400)), "gray")
- try:
- d.text((200, 200), text, fill="black", anchor=anchor, direction=dir, font=f)
- except ValueError as ex:
- if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
- pytest.skip("libraqm 0.7 or greater not available")
+ if direction == "ttb" and not has_feature_version("raqm", "0.7"):
+ pytest.skip("libraqm 0.7 or greater not available")
+ d.text((200, 200), text, fill="black", anchor=anchor, direction=direction, font=f)
assert_image_similar_tofile(im, path, epsilon)
diff --git a/Tests/test_imagefontpil.py b/Tests/test_imagefontpil.py
index 3eb98d379..8c1cb3f58 100644
--- a/Tests/test_imagefontpil.py
+++ b/Tests/test_imagefontpil.py
@@ -30,6 +30,14 @@ def test_default_font(font: ImageFont.ImageFont) -> None:
assert_image_equal_tofile(im, "Tests/images/default_font.png")
+def test_invalid_mode() -> None:
+ font = ImageFont.ImageFont()
+ fp = BytesIO()
+ with Image.open("Tests/images/hopper.png") as im:
+ with pytest.raises(TypeError, match="invalid font image mode"):
+ font._load_pilfont_data(fp, im)
+
+
def test_without_freetype() -> None:
original_core = ImageFont.core
if features.check_module("freetype2"):
diff --git a/Tests/test_imagemath_lambda_eval.py b/Tests/test_imagemath_lambda_eval.py
index 26c04b9a0..ce2a32ae8 100644
--- a/Tests/test_imagemath_lambda_eval.py
+++ b/Tests/test_imagemath_lambda_eval.py
@@ -2,7 +2,9 @@ from __future__ import annotations
from typing import Any
-from PIL import Image, ImageMath
+import pytest
+
+from PIL import Image, ImageMath, _imagingmath
def pixel(im: Image.Image | int) -> str | int:
@@ -498,3 +500,31 @@ def test_logical_not_equal() -> None:
)
== "I 1"
)
+
+
+def test_reflected_operands() -> None:
+ assert pixel(ImageMath.lambda_eval(lambda args: 1 + args["A"], **images)) == "I 2"
+ assert pixel(ImageMath.lambda_eval(lambda args: 1 - args["A"], **images)) == "I 0"
+ assert pixel(ImageMath.lambda_eval(lambda args: 1 * args["A"], **images)) == "I 1"
+ assert pixel(ImageMath.lambda_eval(lambda args: 1 / args["A"], **images)) == "I 1"
+ assert pixel(ImageMath.lambda_eval(lambda args: 1 % args["A"], **images)) == "I 0"
+ assert pixel(ImageMath.lambda_eval(lambda args: 1 ** args["A"], **images)) == "I 1"
+ assert pixel(ImageMath.lambda_eval(lambda args: 1 & args["A"], **images)) == "I 1"
+ assert pixel(ImageMath.lambda_eval(lambda args: 1 | args["A"], **images)) == "I 1"
+ assert pixel(ImageMath.lambda_eval(lambda args: 1 ^ args["A"], **images)) == "I 0"
+
+
+def test_unsupported_mode() -> None:
+ im = Image.new("RGB", (1, 1))
+ with pytest.raises(ValueError, match="unsupported mode: RGB"):
+ ImageMath.lambda_eval(lambda args: args["im"] + 1, im=im)
+
+
+def test_bad_operand_type(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.delattr(_imagingmath, "abs_I")
+ with pytest.raises(TypeError, match="bad operand type for 'abs'"):
+ ImageMath.lambda_eval(lambda args: abs(args["I"]), I=I)
+
+ monkeypatch.delattr(_imagingmath, "max_F")
+ with pytest.raises(TypeError, match="bad operand type for 'max'"):
+ ImageMath.lambda_eval(lambda args: args["max"](args["I"], args["F"]), I=I, F=F)
diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py
index 515e29cea..ca192a809 100644
--- a/Tests/test_imagemorph.py
+++ b/Tests/test_imagemorph.py
@@ -7,7 +7,7 @@ import pytest
from PIL import Image, ImageMorph, _imagingmorph
-from .helper import assert_image_equal_tofile, hopper
+from .helper import assert_image_equal_tofile, hopper, timeout_unless_slower_valgrind
def string_to_img(image_string: str) -> Image.Image:
@@ -266,16 +266,18 @@ def test_unknown_pattern() -> None:
ImageMorph.LutBuilder(op_name="unknown")
-def test_pattern_syntax_error() -> None:
+@pytest.mark.parametrize(
+ "pattern", ("a pattern with a syntax error", "4:(" + "X" * 30000)
+)
+@timeout_unless_slower_valgrind(1)
+def test_pattern_syntax_error(pattern: str) -> None:
# Arrange
lb = ImageMorph.LutBuilder(op_name="corner")
- new_patterns = ["a pattern with a syntax error"]
+ new_patterns = [pattern]
lb.add_patterns(new_patterns)
# Act / Assert
- with pytest.raises(
- Exception, match='Syntax error in pattern "a pattern with a syntax error"'
- ):
+ with pytest.raises(Exception, match='Syntax error in pattern "'):
lb.build_lut()
diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py
index 9f2fd5ba2..63cd0e4d4 100644
--- a/Tests/test_imageops.py
+++ b/Tests/test_imageops.py
@@ -186,6 +186,21 @@ def test_palette(mode: str) -> None:
)
+def test_rgba_palette() -> None:
+ im = Image.new("P", (1, 1))
+
+ red = (255, 0, 0, 255)
+ translucent_black = (0, 0, 0, 127)
+ im.putpalette(red + translucent_black, "RGBA")
+
+ expanded_im = ImageOps.expand(im, 1, 1)
+
+ palette = expanded_im.palette
+ assert palette is not None
+ assert palette.mode == "RGBA"
+ assert expanded_im.convert("RGBA").getpixel((0, 0)) == translucent_black
+
+
def test_pil163() -> None:
# Division by zero in equalize if < 255 pixels in image (@PIL163)
@@ -246,10 +261,10 @@ def test_colorize_2color() -> None:
# Open test image (256px by 10px, black to white)
with Image.open("Tests/images/bw_gradient.png") as im:
- im = im.convert("L")
+ im_l = im.convert("L")
# Create image with original 2-color functionality
- im_test = ImageOps.colorize(im, "red", "green")
+ im_test = ImageOps.colorize(im_l, "red", "green")
# Test output image (2-color)
left = (0, 1)
@@ -286,11 +301,11 @@ def test_colorize_2color_offset() -> None:
# Open test image (256px by 10px, black to white)
with Image.open("Tests/images/bw_gradient.png") as im:
- im = im.convert("L")
+ im_l = im.convert("L")
# Create image with original 2-color functionality with offsets
im_test = ImageOps.colorize(
- im, black="red", white="green", blackpoint=50, whitepoint=100
+ im_l, black="red", white="green", blackpoint=50, whitepoint=100
)
# Test output image (2-color) with offsets
@@ -328,11 +343,11 @@ def test_colorize_3color_offset() -> None:
# Open test image (256px by 10px, black to white)
with Image.open("Tests/images/bw_gradient.png") as im:
- im = im.convert("L")
+ im_l = im.convert("L")
# Create image with new three color functionality with offsets
im_test = ImageOps.colorize(
- im,
+ im_l,
black="red",
white="green",
mid="blue",
diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py
index 782022f51..6ad21502f 100644
--- a/Tests/test_imagepalette.py
+++ b/Tests/test_imagepalette.py
@@ -49,6 +49,12 @@ def test_getcolor() -> None:
palette.getcolor("unknown") # type: ignore[arg-type]
+def test_getcolor_rgba() -> None:
+ palette = ImagePalette.ImagePalette("RGBA", (1, 2, 3, 4))
+ palette.getcolor((5, 6, 7, 8))
+ assert palette.palette == b"\x01\x02\x03\x04\x05\x06\x07\x08"
+
+
def test_getcolor_rgba_color_rgb_palette() -> None:
palette = ImagePalette.ImagePalette("RGB")
diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py
index 7b9ac80bc..32da22e04 100644
--- a/Tests/test_imagesequence.py
+++ b/Tests/test_imagesequence.py
@@ -76,9 +76,14 @@ def test_consecutive() -> None:
def test_palette_mmap() -> None:
# Using mmap in ImageFile can require to reload the palette.
with Image.open("Tests/images/multipage-mmap.tiff") as im:
- color1 = im.getpalette()[:3]
+ palette = im.getpalette()
+ assert palette is not None
+ color1 = palette[:3]
im.seek(0)
- color2 = im.getpalette()[:3]
+
+ palette = im.getpalette()
+ assert palette is not None
+ color2 = palette[:3]
assert color1 == color2
diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py
index 7a2f58767..8d6731acc 100644
--- a/Tests/test_imageshow.py
+++ b/Tests/test_imageshow.py
@@ -59,15 +59,12 @@ def test_show(mode: str) -> None:
assert ImageShow.show(im)
-def test_show_without_viewers() -> None:
- viewers = ImageShow._viewers
- ImageShow._viewers = []
+def test_show_without_viewers(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setattr(ImageShow, "_viewers", [])
with hopper() as im:
assert not ImageShow.show(im)
- ImageShow._viewers = viewers
-
@pytest.mark.parametrize(
"viewer",
diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py
index 0dfbc5a2a..0baab7ce2 100644
--- a/Tests/test_imagestat.py
+++ b/Tests/test_imagestat.py
@@ -57,3 +57,13 @@ def test_constant() -> None:
assert st.rms[0] == 128
assert st.var[0] == 0
assert st.stddev[0] == 0
+
+
+def test_zero_count() -> None:
+ im = Image.new("L", (0, 0))
+
+ st = ImageStat.Stat(im)
+
+ assert st.mean == [0]
+ assert st.rms == [0]
+ assert st.var == [0]
diff --git a/Tests/test_imagetext.py b/Tests/test_imagetext.py
new file mode 100644
index 000000000..2b424629d
--- /dev/null
+++ b/Tests/test_imagetext.py
@@ -0,0 +1,110 @@
+from __future__ import annotations
+
+import pytest
+
+from PIL import Image, ImageDraw, ImageFont, ImageText, features
+
+from .helper import assert_image_similar_tofile, skip_unless_feature
+
+FONT_PATH = "Tests/fonts/FreeMono.ttf"
+
+
+@pytest.fixture(
+ scope="module",
+ params=[
+ pytest.param(ImageFont.Layout.BASIC),
+ pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")),
+ ],
+)
+def layout_engine(request: pytest.FixtureRequest) -> ImageFont.Layout:
+ return request.param
+
+
+@pytest.fixture(
+ scope="module",
+ params=[
+ None,
+ pytest.param(ImageFont.Layout.BASIC, marks=skip_unless_feature("freetype2")),
+ pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")),
+ ],
+)
+def font(
+ request: pytest.FixtureRequest,
+) -> ImageFont.ImageFont | ImageFont.FreeTypeFont:
+ layout_engine = request.param
+ if layout_engine is None:
+ return ImageFont.load_default_imagefont()
+ else:
+ return ImageFont.truetype(FONT_PATH, 20, layout_engine=layout_engine)
+
+
+def test_get_length(font: ImageFont.ImageFont | ImageFont.FreeTypeFont) -> None:
+ factor = 1 if isinstance(font, ImageFont.ImageFont) else 2
+ assert ImageText.Text("A", font).get_length() == 6 * factor
+ assert ImageText.Text("AB", font).get_length() == 12 * factor
+ assert ImageText.Text("M", font).get_length() == 6 * factor
+ assert ImageText.Text("y", font).get_length() == 6 * factor
+ assert ImageText.Text("a", font).get_length() == 6 * factor
+
+ text = ImageText.Text("\n", font)
+ with pytest.raises(ValueError, match="can't measure length of multiline text"):
+ text.get_length()
+
+
+@pytest.mark.parametrize(
+ "text, expected",
+ (
+ ("A", (0, 4, 12, 16)),
+ ("AB", (0, 4, 24, 16)),
+ ("M", (0, 4, 12, 16)),
+ ("y", (0, 7, 12, 20)),
+ ("a", (0, 7, 12, 16)),
+ ),
+)
+def test_get_bbox(
+ font: ImageFont.ImageFont | ImageFont.FreeTypeFont,
+ text: str,
+ expected: tuple[int, int, int, int],
+) -> None:
+ if isinstance(font, ImageFont.ImageFont):
+ expected = (0, 0, expected[2] // 2, 11)
+ assert ImageText.Text(text, font).get_bbox() == expected
+
+
+def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None:
+ if features.check_module("freetype2"):
+ font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
+ text = ImageText.Text("Hello World!", font)
+ text.embed_color()
+ assert text.get_length() == 288
+
+ im = Image.new("RGB", (300, 64), "white")
+ draw = ImageDraw.Draw(im)
+ draw.text((10, 10), text, "#fa6")
+
+ assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1)
+
+ text = ImageText.Text("", mode="1")
+ with pytest.raises(
+ ValueError, match="Embedded color supported only in RGB and RGBA modes"
+ ):
+ text.embed_color()
+
+
+@skip_unless_feature("freetype2")
+def test_stroke() -> None:
+ for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items():
+ # Arrange
+ im = Image.new("RGB", (120, 130))
+ draw = ImageDraw.Draw(im)
+ font = ImageFont.truetype(FONT_PATH, 120)
+ text = ImageText.Text("A", font)
+ text.stroke(2, stroke_fill)
+
+ # Act
+ draw.text((12, 12), text, "#f00")
+
+ # Assert
+ assert_image_similar_tofile(
+ im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1
+ )
diff --git a/Tests/test_nanoarrow.py b/Tests/test_nanoarrow.py
new file mode 100644
index 000000000..69980e719
--- /dev/null
+++ b/Tests/test_nanoarrow.py
@@ -0,0 +1,302 @@
+from __future__ import annotations
+
+import json
+from typing import Any, NamedTuple
+
+import pytest
+
+from PIL import Image
+
+from .helper import (
+ assert_deep_equal,
+ assert_image_equal,
+ hopper,
+ is_big_endian,
+)
+
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ import nanoarrow # type: ignore [import-not-found]
+else:
+ nanoarrow = pytest.importorskip("nanoarrow", reason="Nanoarrow not installed")
+
+TEST_IMAGE_SIZE = (10, 10)
+
+
+def _test_img_equals_pyarray(
+ img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
+) -> None:
+ assert img.height * img.width * elts_per_pixel == len(arr)
+ px = img.load()
+ assert px is not None
+ if elts_per_pixel > 1 and mask is None:
+ # have to do element-wise comparison when we're comparing
+ # flattened r,g,b,a to a pixel.
+ mask = list(range(elts_per_pixel))
+ for x in range(0, img.size[0], int(img.size[0] / 10)):
+ for y in range(0, img.size[1], int(img.size[1] / 10)):
+ if mask:
+ pixel = px[x, y]
+ assert isinstance(pixel, tuple)
+ for ix, elt in enumerate(mask):
+ if elts_per_pixel == 1:
+ assert pixel[ix] == arr[y * img.width + x].as_py()[elt]
+ else:
+ assert (
+ pixel[ix]
+ == arr[(y * img.width + x) * elts_per_pixel + elt].as_py()
+ )
+ else:
+ assert_deep_equal(px[x, y], arr[y * img.width + x].as_py())
+
+
+def _test_img_equals_int32_pyarray(
+ img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
+) -> None:
+ assert img.height * img.width * elts_per_pixel == len(arr)
+ px = img.load()
+ assert px is not None
+ if mask is None:
+ # have to do element-wise comparison when we're comparing
+ # flattened rgba in an uint32 to a pixel.
+ mask = list(range(elts_per_pixel))
+ for x in range(0, img.size[0], int(img.size[0] / 10)):
+ for y in range(0, img.size[1], int(img.size[1] / 10)):
+ pixel = px[x, y]
+ assert isinstance(pixel, tuple)
+ arr_pixel_int = arr[y * img.width + x].as_py()
+ arr_pixel_tuple = (
+ arr_pixel_int % 256,
+ (arr_pixel_int // 256) % 256,
+ (arr_pixel_int // 256**2) % 256,
+ (arr_pixel_int // 256**3),
+ )
+ if is_big_endian():
+ arr_pixel_tuple = arr_pixel_tuple[::-1]
+
+ for ix, elt in enumerate(mask):
+ assert pixel[ix] == arr_pixel_tuple[elt]
+
+
+fl_uint8_4_type = nanoarrow.fixed_size_list(
+ value_type=nanoarrow.uint8(nullable=False), list_size=4, nullable=False
+)
+
+
+@pytest.mark.parametrize(
+ "mode, dtype, mask",
+ (
+ ("L", nanoarrow.uint8(nullable=False), None),
+ ("I", nanoarrow.int32(nullable=False), None),
+ ("F", nanoarrow.float32(nullable=False), None),
+ ("LA", fl_uint8_4_type, [0, 3]),
+ ("RGB", fl_uint8_4_type, [0, 1, 2]),
+ ("RGBA", fl_uint8_4_type, None),
+ ("RGBX", fl_uint8_4_type, None),
+ ("CMYK", fl_uint8_4_type, None),
+ ("YCbCr", fl_uint8_4_type, [0, 1, 2]),
+ ("HSV", fl_uint8_4_type, [0, 1, 2]),
+ ),
+)
+def test_to_array(mode: str, dtype: nanoarrow, mask: list[int] | None) -> None:
+ img = hopper(mode)
+
+ # Resize to non-square
+ img = img.crop((3, 0, 124, 127))
+ assert img.size == (121, 127)
+
+ arr = nanoarrow.Array(img)
+ _test_img_equals_pyarray(img, arr, mask)
+ assert arr.schema.type == dtype.type
+ assert arr.schema.nullable == dtype.nullable
+
+ reloaded = Image.fromarrow(arr, mode, img.size)
+ assert_image_equal(img, reloaded)
+
+
+def test_lifetime() -> None:
+ # valgrind shouldn't error out here.
+ # arrays should be accessible after the image is deleted.
+
+ img = hopper("L")
+
+ arr_1 = nanoarrow.Array(img)
+ arr_2 = nanoarrow.Array(img)
+
+ del img
+
+ assert sum(arr_1.iter_py()) > 0
+ del arr_1
+
+ assert sum(arr_2.iter_py()) > 0
+ del arr_2
+
+
+def test_lifetime2() -> None:
+ # valgrind shouldn't error out here.
+ # img should remain after the arrays are collected.
+
+ img = hopper("L")
+
+ arr_1 = nanoarrow.Array(img)
+ arr_2 = nanoarrow.Array(img)
+
+ assert sum(arr_1.iter_py()) > 0
+ del arr_1
+
+ assert sum(arr_2.iter_py()) > 0
+ del arr_2
+
+ img2 = img.copy()
+ px = img2.load()
+ assert px # make mypy happy
+ assert isinstance(px[0, 0], int)
+
+
+class DataShape(NamedTuple):
+ dtype: nanoarrow
+ # Strictly speaking, elt should be a pixel or pixel component, so
+ # list[uint8][4], float, int, uint32, uint8, etc. But more
+ # correctly, it should be exactly the dtype from the line above.
+ elt: Any
+ elts_per_pixel: int
+
+
+UINT_ARR = DataShape(
+ dtype=fl_uint8_4_type,
+ elt=[1, 2, 3, 4], # array of 4 uint8 per pixel
+ elts_per_pixel=1, # only one array per pixel
+)
+
+UINT = DataShape(
+ dtype=nanoarrow.uint8(),
+ elt=3, # one uint8,
+ elts_per_pixel=4, # but repeated 4x per pixel
+)
+
+UINT32 = DataShape(
+ dtype=nanoarrow.uint32(),
+ elt=0xABCDEF45, # one packed int, doesn't fit in a int32 > 0x80000000
+ elts_per_pixel=1, # one per pixel
+)
+
+INT32 = DataShape(
+ dtype=nanoarrow.uint32(),
+ elt=0x12CDEF45, # one packed int
+ elts_per_pixel=1, # one per pixel
+)
+
+
+@pytest.mark.parametrize(
+ "mode, data_tp, mask",
+ (
+ ("L", DataShape(nanoarrow.uint8(), 3, 1), None),
+ ("I", DataShape(nanoarrow.int32(), 1 << 24, 1), None),
+ ("F", DataShape(nanoarrow.float32(), 3.14159, 1), None),
+ ("LA", UINT_ARR, [0, 3]),
+ ("LA", UINT, [0, 3]),
+ ("RGB", UINT_ARR, [0, 1, 2]),
+ ("RGBA", UINT_ARR, None),
+ ("CMYK", UINT_ARR, None),
+ ("YCbCr", UINT_ARR, [0, 1, 2]),
+ ("HSV", UINT_ARR, [0, 1, 2]),
+ ("RGB", UINT, [0, 1, 2]),
+ ("RGBA", UINT, None),
+ ("CMYK", UINT, None),
+ ("YCbCr", UINT, [0, 1, 2]),
+ ("HSV", UINT, [0, 1, 2]),
+ ),
+)
+def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
+ (dtype, elt, elts_per_pixel) = data_tp
+
+ ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
+ if dtype == fl_uint8_4_type:
+ tmp_arr = nanoarrow.Array(
+ elt * (ct_pixels * elts_per_pixel), schema=nanoarrow.uint8()
+ )
+ c_array = nanoarrow.c_array_from_buffers(
+ dtype, ct_pixels, buffers=[], children=[tmp_arr]
+ )
+ arr = nanoarrow.Array(c_array)
+ else:
+ arr = nanoarrow.Array(
+ nanoarrow.c_array([elt] * (ct_pixels * elts_per_pixel), schema=dtype)
+ )
+ img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
+
+ _test_img_equals_pyarray(img, arr, mask, elts_per_pixel)
+
+
+@pytest.mark.parametrize(
+ "mode, mask",
+ (
+ ("LA", [0, 3]),
+ ("RGB", [0, 1, 2]),
+ ("RGBA", None),
+ ("CMYK", None),
+ ("YCbCr", [0, 1, 2]),
+ ("HSV", [0, 1, 2]),
+ ),
+)
+@pytest.mark.parametrize("data_tp", (UINT32, INT32))
+def test_from_int32array(mode: str, mask: list[int] | None, data_tp: DataShape) -> None:
+ (dtype, elt, elts_per_pixel) = data_tp
+
+ ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
+ arr = nanoarrow.Array(
+ nanoarrow.c_array([elt] * (ct_pixels * elts_per_pixel), schema=dtype)
+ )
+ img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
+
+ _test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel)
+
+
+@pytest.mark.parametrize(
+ "mode, metadata",
+ (
+ ("LA", ["L", "X", "X", "A"]),
+ ("RGB", ["R", "G", "B", "X"]),
+ ("RGBX", ["R", "G", "B", "X"]),
+ ("RGBA", ["R", "G", "B", "A"]),
+ ("CMYK", ["C", "M", "Y", "K"]),
+ ("YCbCr", ["Y", "Cb", "Cr", "X"]),
+ ("HSV", ["H", "S", "V", "X"]),
+ ),
+)
+def test_image_nested_metadata(mode: str, metadata: list[str]) -> None:
+ img = hopper(mode)
+
+ arr = nanoarrow.Array(img)
+
+ assert arr.schema.value_type.metadata
+ assert arr.schema.value_type.metadata[b"image"]
+
+ parsed_metadata = json.loads(
+ arr.schema.value_type.metadata[b"image"].decode("utf8")
+ )
+
+ assert "bands" in parsed_metadata
+ assert parsed_metadata["bands"] == metadata
+
+
+@pytest.mark.parametrize(
+ "mode, metadata",
+ (
+ ("L", ["L"]),
+ ("I", ["I"]),
+ ("F", ["F"]),
+ ),
+)
+def test_image_flat_metadata(mode: str, metadata: list[str]) -> None:
+ img = hopper(mode)
+
+ arr = nanoarrow.Array(img)
+
+ assert arr.schema.metadata
+ assert arr.schema.metadata[b"image"]
+
+ parsed_metadata = json.loads(arr.schema.metadata[b"image"].decode("utf8"))
+
+ assert "bands" in parsed_metadata
+ assert parsed_metadata["bands"] == metadata
diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py
index ef54deeeb..f6acb3aff 100644
--- a/Tests/test_numpy.py
+++ b/Tests/test_numpy.py
@@ -28,15 +28,13 @@ def test_numpy_to_image() -> None:
a = numpy.array(data, dtype=dtype)
a.shape = TEST_IMAGE_SIZE
i = Image.fromarray(a)
- if list(i.getdata()) != data:
- print("data mismatch for", dtype)
+ assert list(i.getdata()) == data
else:
data = list(range(100))
a = numpy.array([[x] * bands for x in data], dtype=dtype)
a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands
i = Image.fromarray(a)
- if list(i.getchannel(0).getdata()) != list(range(100)):
- print("data mismatch for", dtype)
+ assert list(i.getchannel(0).getdata()) == list(range(100))
return i
# Check supported 1-bit integer formats
diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py
index 54cef00ad..fc76f81e9 100644
--- a/Tests/test_pickle.py
+++ b/Tests/test_pickle.py
@@ -90,18 +90,18 @@ def test_pickle_la_mode_with_palette(tmp_path: Path) -> None:
# Arrange
filename = tmp_path / "temp.pkl"
with Image.open("Tests/images/hopper.jpg") as im:
- im = im.convert("PA")
+ im_pa = im.convert("PA")
# Act / Assert
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
- im._mode = "LA"
+ im_pa._mode = "LA"
with open(filename, "wb") as f:
- pickle.dump(im, f, protocol)
+ pickle.dump(im_pa, f, protocol)
with open(filename, "rb") as f:
loaded_im = pickle.load(f)
- im._mode = "PA"
- assert im == loaded_im
+ im_pa._mode = "PA"
+ assert im_pa == loaded_im
@skip_unless_feature("webp")
diff --git a/Tests/test_pyarrow.py b/Tests/test_pyarrow.py
index 8dad94fe0..a69504e78 100644
--- a/Tests/test_pyarrow.py
+++ b/Tests/test_pyarrow.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import json
from typing import Any, NamedTuple
import pytest
@@ -244,3 +245,29 @@ def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None)
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
_test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel)
+
+
+@pytest.mark.parametrize(
+ "mode, metadata",
+ (
+ ("LA", ["L", "X", "X", "A"]),
+ ("RGB", ["R", "G", "B", "X"]),
+ ("RGBX", ["R", "G", "B", "X"]),
+ ("RGBA", ["R", "G", "B", "A"]),
+ ("CMYK", ["C", "M", "Y", "K"]),
+ ("YCbCr", ["Y", "Cb", "Cr", "X"]),
+ ("HSV", ["H", "S", "V", "X"]),
+ ),
+)
+def test_image_metadata(mode: str, metadata: list[str]) -> None:
+ img = hopper(mode)
+
+ arr = pyarrow.array(img) # type: ignore[call-overload]
+
+ assert arr.type.field(0).metadata
+ assert arr.type.field(0).metadata[b"image"]
+
+ parsed_metadata = json.loads(arr.type.field(0).metadata[b"image"].decode("utf8"))
+
+ assert "bands" in parsed_metadata
+ assert parsed_metadata["bands"] == metadata
diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py
index a161d3f05..5871a7213 100644
--- a/Tests/test_pyroma.py
+++ b/Tests/test_pyroma.py
@@ -9,9 +9,30 @@ from PIL import __version__
pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed")
+def map_metadata_keys(md):
+ # Convert installed wheel metadata into canonical Core Metadata 2.4 format.
+ # This was a utility method in pyroma 4.3.3; it was removed in 5.0.
+ # This implementation is constructed from the relevant logic from
+ # Pyroma 5.0's `build_metadata()` implementation. This has been submitted
+ # upstream to Pyroma as https://github.com/regebro/pyroma/pull/116,
+ # so it may be possible to simplify this test in future.
+ data = {}
+ for key in set(md.keys()):
+ value = md.get_all(key)
+ key = pyroma.projectdata.normalize(key)
+
+ if len(value) == 1:
+ value = value[0]
+ if value.strip() == "UNKNOWN":
+ continue
+
+ data[key] = value
+ return data
+
+
def test_pyroma() -> None:
# Arrange
- data = pyroma.projectdata.map_metadata_keys(metadata("Pillow"))
+ data = map_metadata_keys(metadata("Pillow"))
# Act
rating = pyroma.ratings.rate(data)
diff --git a/Tests/test_qt_image_qapplication.py b/Tests/test_qt_image_qapplication.py
index 82a3e0741..b31e2a4ef 100644
--- a/Tests/test_qt_image_qapplication.py
+++ b/Tests/test_qt_image_qapplication.py
@@ -1,8 +1,5 @@
from __future__ import annotations
-from pathlib import Path
-from typing import Union
-
import pytest
from PIL import Image, ImageQt
@@ -11,18 +8,8 @@ from .helper import assert_image_equal_tofile, assert_image_similar, hopper
TYPE_CHECKING = False
if TYPE_CHECKING:
- import PyQt6
- import PySide6
+ from pathlib import Path
- QApplication = Union[PyQt6.QtWidgets.QApplication, PySide6.QtWidgets.QApplication]
- QHBoxLayout = Union[PyQt6.QtWidgets.QHBoxLayout, PySide6.QtWidgets.QHBoxLayout]
- QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage]
- QLabel = Union[PyQt6.QtWidgets.QLabel, PySide6.QtWidgets.QLabel]
- QPainter = Union[PyQt6.QtGui.QPainter, PySide6.QtGui.QPainter]
- QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap]
- QPoint = Union[PyQt6.QtCore.QPoint, PySide6.QtCore.QPoint]
- QRegion = Union[PyQt6.QtGui.QRegion, PySide6.QtGui.QRegion]
- QWidget = Union[PyQt6.QtWidgets.QWidget, PySide6.QtWidgets.QWidget]
if ImageQt.qt_is_installed:
from PIL.ImageQt import QPixmap
@@ -32,11 +19,16 @@ if ImageQt.qt_is_installed:
from PyQt6.QtGui import QImage, QPainter, QRegion
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
elif ImageQt.qt_version == "side6":
- from PySide6.QtCore import QPoint
- from PySide6.QtGui import QImage, QPainter, QRegion
- from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
+ from PySide6.QtCore import QPoint # type: ignore[assignment]
+ from PySide6.QtGui import QImage, QPainter, QRegion # type: ignore[assignment]
+ from PySide6.QtWidgets import ( # type: ignore[assignment]
+ QApplication,
+ QHBoxLayout,
+ QLabel,
+ QWidget,
+ )
- class Example(QWidget): # type: ignore[misc]
+ class Example(QWidget):
def __init__(self) -> None:
super().__init__()
@@ -47,9 +39,9 @@ if ImageQt.qt_is_installed:
pixmap1 = getattr(ImageQt.QPixmap, "fromImage")(qimage)
# hbox
- QHBoxLayout(self) # type: ignore[operator]
+ QHBoxLayout(self)
- lbl = QLabel(self) # type: ignore[operator]
+ lbl = QLabel(self)
# Segfault in the problem
lbl.setPixmap(pixmap1.copy())
@@ -63,7 +55,7 @@ def roundtrip(expected: Image.Image) -> None:
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
def test_sanity(tmp_path: Path) -> None:
# Segfault test
- app: QApplication | None = QApplication([]) # type: ignore[operator]
+ app: QApplication | None = QApplication([])
ex = Example()
assert app # Silence warning
assert ex # Silence warning
@@ -84,11 +76,11 @@ def test_sanity(tmp_path: Path) -> None:
imageqt = ImageQt.ImageQt(im)
data = getattr(QPixmap, "fromImage")(imageqt)
qt_format = getattr(QImage, "Format") if ImageQt.qt_version == "6" else QImage
- qimage = QImage(128, 128, getattr(qt_format, "Format_ARGB32")) # type: ignore[operator]
- painter = QPainter(qimage) # type: ignore[operator]
- image_label = QLabel() # type: ignore[operator]
+ qimage = QImage(128, 128, getattr(qt_format, "Format_ARGB32"))
+ painter = QPainter(qimage)
+ image_label = QLabel()
image_label.setPixmap(data)
- image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128)) # type: ignore[operator]
+ image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128))
painter.end()
rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png")
qimage.save(rendered_tempfile)
diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py
index 8cb7ffb9b..0004b5521 100644
--- a/Tests/test_qt_image_toqimage.py
+++ b/Tests/test_qt_image_toqimage.py
@@ -1,13 +1,15 @@
from __future__ import annotations
-from pathlib import Path
-
import pytest
from PIL import ImageQt
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from pathlib import Path
+
pytestmark = pytest.mark.skipif(
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
)
@@ -21,7 +23,7 @@ def test_sanity(mode: str, tmp_path: Path) -> None:
src = hopper(mode)
data = ImageQt.toqimage(src)
- assert isinstance(data, QImage) # type: ignore[arg-type, misc]
+ assert isinstance(data, QImage)
assert not data.isNull()
# reload directly from the qimage
diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py
index 03e92b5b9..a7e95ed83 100644
--- a/Tests/test_shell_injection.py
+++ b/Tests/test_shell_injection.py
@@ -2,14 +2,18 @@ from __future__ import annotations
import shutil
from io import BytesIO
-from pathlib import Path
-from typing import IO, Callable
import pytest
from PIL import GifImagePlugin, Image, JpegImagePlugin
-from .helper import cjpeg_available, djpeg_available, is_win32, netpbm_available
+from .helper import djpeg_available, is_win32, netpbm_available
+
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable
+ from pathlib import Path
+ from typing import IO
TEST_JPG = "Tests/images/hopper.jpg"
TEST_GIF = "Tests/images/hopper.gif"
@@ -42,19 +46,16 @@ class TestShellInjection:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
im.load_djpeg()
- @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
- def test_save_cjpeg_filename(self, tmp_path: Path) -> None:
- with Image.open(TEST_JPG) as im:
- self.assert_save_filename_check(tmp_path, im, JpegImagePlugin._save_cjpeg)
-
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None:
with Image.open(TEST_GIF) as im:
- im = im.convert("RGB")
- self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm)
+ im_rgb = im.convert("RGB")
+ self.assert_save_filename_check(
+ tmp_path, im_rgb, GifImagePlugin._save_netpbm
+ )
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_filename_l_mode(self, tmp_path: Path) -> None:
with Image.open(TEST_GIF) as im:
- im = im.convert("L")
- self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm)
+ im_l = im.convert("L")
+ self.assert_save_filename_check(tmp_path, im_l, GifImagePlugin._save_netpbm)
diff --git a/checks/32bit_segfault_check.py b/checks/32bit_segfault_check.py
old mode 100755
new mode 100644
index 06ed2ed2f..e277bc10a
--- a/checks/32bit_segfault_check.py
+++ b/checks/32bit_segfault_check.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python3
from __future__ import annotations
import sys
diff --git a/checks/check_imaging_leaks.py b/checks/check_imaging_leaks.py
old mode 100755
new mode 100644
index 231789ca0..65090b6b6
--- a/checks/check_imaging_leaks.py
+++ b/checks/check_imaging_leaks.py
@@ -1,18 +1,19 @@
-#!/usr/bin/env python3
from __future__ import annotations
-from typing import Any, Callable
+import sys
+from collections.abc import Callable
+from typing import Any
import pytest
from PIL import Image
-from .helper import is_win32
-
min_iterations = 100
max_iterations = 10000
-pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
+pytestmark = pytest.mark.skipif(
+ sys.platform.startswith("win32"), reason="requires Unix or macOS"
+)
def _get_mem_usage() -> float:
diff --git a/checks/check_j2k_leaks.py b/checks/check_j2k_leaks.py
index bbe35b591..7103d502e 100644
--- a/checks/check_j2k_leaks.py
+++ b/checks/check_j2k_leaks.py
@@ -1,12 +1,11 @@
from __future__ import annotations
+import sys
from io import BytesIO
import pytest
-from PIL import Image
-
-from .helper import is_win32, skip_unless_feature
+from PIL import Image, features
# Limits for testing the leak
mem_limit = 1024 * 1048576
@@ -15,8 +14,10 @@ iterations = int((mem_limit / stack_size) * 2)
test_file = "Tests/images/rgb_trns_ycbc.jp2"
pytestmark = [
- pytest.mark.skipif(is_win32(), reason="requires Unix or macOS"),
- skip_unless_feature("jpg_2000"),
+ pytest.mark.skipif(
+ sys.platform.startswith("win32"), reason="requires Unix or macOS"
+ ),
+ pytest.mark.skipif(not features.check("jpg_2000"), reason="jpg_2000 not available"),
]
diff --git a/checks/check_jpeg_leaks.py b/checks/check_jpeg_leaks.py
index 2f42ad734..2c27ce1d5 100644
--- a/checks/check_jpeg_leaks.py
+++ b/checks/check_jpeg_leaks.py
@@ -1,10 +1,11 @@
from __future__ import annotations
+import sys
from io import BytesIO
import pytest
-from .helper import hopper, is_win32
+from PIL import Image
iterations = 5000
@@ -18,7 +19,9 @@ valgrind --tool=massif python test-installed.py -s -v checks/check_jpeg_leaks.py
"""
-pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
+pytestmark = pytest.mark.skipif(
+ sys.platform.startswith("win32"), reason="requires Unix or macOS"
+)
"""
pre patch:
@@ -112,10 +115,10 @@ standard_chrominance_qtable = (
),
)
def test_qtables_leak(qtables: tuple[tuple[int, ...]] | list[tuple[int, ...]]) -> None:
- im = hopper("RGB")
- for _ in range(iterations):
- test_output = BytesIO()
- im.save(test_output, "JPEG", qtables=qtables)
+ with Image.open("Tests/images/hopper.ppm") as im:
+ for _ in range(iterations):
+ test_output = BytesIO()
+ im.save(test_output, "JPEG", qtables=qtables)
def test_exif_leak() -> None:
@@ -173,12 +176,12 @@ def test_exif_leak() -> None:
0 +----------------------------------------------------------------------->Gi
0 11.33
"""
- im = hopper("RGB")
exif = b"12345678" * 4096
- for _ in range(iterations):
- test_output = BytesIO()
- im.save(test_output, "JPEG", exif=exif)
+ with Image.open("Tests/images/hopper.ppm") as im:
+ for _ in range(iterations):
+ test_output = BytesIO()
+ im.save(test_output, "JPEG", exif=exif)
def test_base_save() -> None:
@@ -207,8 +210,7 @@ def test_base_save() -> None:
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
0 +----------------------------------------------------------------------->Gi
0 7.882"""
- im = hopper("RGB")
-
- for _ in range(iterations):
- test_output = BytesIO()
- im.save(test_output, "JPEG")
+ with Image.open("Tests/images/hopper.ppm") as im:
+ for _ in range(iterations):
+ test_output = BytesIO()
+ im.save(test_output, "JPEG")
diff --git a/checks/check_wheel.py b/checks/check_wheel.py
index 3d806eb71..f716c8498 100644
--- a/checks/check_wheel.py
+++ b/checks/check_wheel.py
@@ -4,7 +4,6 @@ import platform
import sys
from PIL import features
-from Tests.helper import is_pypy
def test_wheel_modules() -> None:
@@ -25,8 +24,7 @@ def test_wheel_modules() -> None:
elif sys.platform == "ios":
# tkinter is not available on iOS
- # libavif is not available on iOS (for now)
- expected_modules -= {"tkinter", "avif"}
+ expected_modules.remove("tkinter")
assert set(features.get_supported_modules()) == expected_modules
@@ -49,8 +47,6 @@ def test_wheel_features() -> None:
if sys.platform == "win32":
expected_features.remove("xcb")
- elif sys.platform == "darwin" and not is_pypy() and platform.processor() != "arm":
- expected_features.remove("zlib_ng")
elif sys.platform == "ios":
# Can't distribute raqm due to licensing, and there's no system version;
# fribidi and harfbuzz won't be available if raqm isn't available.
diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh
index 88756f8f9..de63abdec 100755
--- a/depends/install_imagequant.sh
+++ b/depends/install_imagequant.sh
@@ -2,7 +2,7 @@
# install libimagequant
archive_name=libimagequant
-archive_version=4.3.4
+archive_version=4.4.1
archive=$archive_name-$archive_version
diff --git a/depends/install_libavif.sh b/depends/install_libavif.sh
index 26af8a36c..50ba01755 100755
--- a/depends/install_libavif.sh
+++ b/depends/install_libavif.sh
@@ -59,6 +59,6 @@ cmake \
"${LIBAVIF_CMAKE_FLAGS[@]}" \
.
-sudo make install
+make install
popd
diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh
index 1f8d78193..bc7c7c634 100755
--- a/depends/install_openjpeg.sh
+++ b/depends/install_openjpeg.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# install openjpeg
-archive=openjpeg-2.5.3
+archive=openjpeg-2.5.4
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh
index 5d862403e..33bb2d0a7 100755
--- a/depends/install_raqm.sh
+++ b/depends/install_raqm.sh
@@ -2,12 +2,12 @@
# install raqm
-archive=libraqm-0.10.2
+archive=libraqm-0.10.3
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
pushd $archive
-meson build --prefix=/usr && sudo ninja -C build install
+meson build --prefix=/usr && ninja -C build install
popd
diff --git a/depends/install_webp.sh b/depends/install_webp.sh
index 9d2977715..d7f3cd2f5 100755
--- a/depends/install_webp.sh
+++ b/depends/install_webp.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# install webp
-archive=libwebp-1.5.0
+archive=libwebp-1.6.0
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
diff --git a/docs/deprecations.rst b/docs/deprecations.rst
index 236554565..cc5ac283f 100644
--- a/docs/deprecations.rst
+++ b/docs/deprecations.rst
@@ -12,13 +12,6 @@ Deprecated features
Below are features which are considered deprecated. Where appropriate,
a :py:exc:`DeprecationWarning` is issued.
-ImageDraw.getdraw hints parameter
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. deprecated:: 10.4.0
-
-The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated.
-
ExifTags.IFD.Makernote
^^^^^^^^^^^^^^^^^^^^^^
@@ -42,8 +35,12 @@ Image.fromarray mode parameter
.. deprecated:: 11.3.0
-The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
-mode can be automatically determined from the object's shape and type instead.
+Using the ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` was deprecated in
+Pillow 11.3.0. In Pillow 12.0.0, this was partially reverted, and it is now only
+deprecated when changing data types. Since pixel values do not contain information
+about palettes or color spaces, the parameter can still be used to place grayscale L
+mode data within a P mode image, or read RGB data as YCbCr for example. If omitted, the
+mode will be automatically determined from the object's shape and type.
Saving I mode images as PNG
^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -59,6 +56,23 @@ another mode before saving::
im = Image.new("I", (1, 1))
im.convert("I;16").save("out.png")
+ImageCms.ImageCmsProfile.product_name and .product_info
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. deprecated:: 12.0.0
+
+``ImageCms.ImageCmsProfile.product_name`` and the corresponding
+``.product_info`` attributes have been deprecated, and will be removed in
+Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
+
+Image._show
+~~~~~~~~~~~
+
+.. deprecated:: 12.0.0
+
+``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15).
+Use :py:meth:`~PIL.ImageShow.show` instead.
+
Removed features
----------------
@@ -186,6 +200,7 @@ ICNS (width, height, scale) sizes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 11.0.0
+.. versionremoved:: 12.0.0
Setting an ICNS image size to ``(width, height, scale)`` before loading has been
removed. Instead, ``load(scale)`` can be used.
diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst
index c9d3f5e91..46f612be3 100644
--- a/docs/handbook/concepts.rst
+++ b/docs/handbook/concepts.rst
@@ -101,6 +101,28 @@ Palette
The palette mode (``P``) uses a color palette to define the actual color for
each pixel.
+.. _colors:
+
+Colors
+------
+
+To specify colors, you can use tuples with a value for each channel in the image, e.g.
+``Image.new("RGB", (1, 1), (255, 0, 0))``.
+
+If an image has a single channel, you can use a single number instead, e.g.
+``Image.new("L", (1, 1), 255)``. For "F" mode images, floating point values are also
+accepted. In the case of "P" mode images, these will be indexes for the color palette.
+
+If a single value is used for an image with more than one channel, it will still be
+parsed::
+
+ >>> from PIL import Image
+ >>> im = Image.new("RGBA", (1, 1), 0x04030201)
+ >>> im.getpixel((0, 0))
+ (1, 2, 3, 4)
+
+Some methods accept other forms, such as color names. See :ref:`color-names`.
+
Info
----
diff --git a/docs/handbook/third-party-plugins.rst b/docs/handbook/third-party-plugins.rst
index a189a5773..1c7dfb5e9 100644
--- a/docs/handbook/third-party-plugins.rst
+++ b/docs/handbook/third-party-plugins.rst
@@ -11,7 +11,7 @@ Here is a list of PyPI projects that offer additional plugins:
* :pypi:`heif-image-plugin`: Simple HEIF/HEIC images plugin, based on the pyheif library.
* :pypi:`jxlpy`: Introduces reading and writing support for JPEG XL.
* :pypi:`pillow-heif`: Python bindings to libheif for working with HEIF images.
-* :pypi:`pillow-jpls`: Plugin for the JPEG-LS codec, based on the Charls JPEG-LS implemetation. Python bindings implemented using pybind11.
+* :pypi:`pillow-jpls`: Plugin for the JPEG-LS codec, based on the Charls JPEG-LS implementation. Python bindings implemented using pybind11.
* :pypi:`pillow-jxl-plugin`: Plugin for JPEG-XL, using Rust for bindings.
* :pypi:`pillow-mbm`: Adds support for KSP's proprietary MBM texture format.
* :pypi:`pillow-svg`: Implements basic SVG read support. Supports basic paths, shapes, and text.
diff --git a/docs/index.rst b/docs/index.rst
index 689088d48..ee51621ac 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -29,10 +29,6 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more = 11,Yes,Yes,Yes,Yes,Yes,,,,
-Pillow 10.1 - 10.4,,Yes,Yes,Yes,Yes,Yes,,,
-Pillow 10.0,,,Yes,Yes,Yes,Yes,,,
-Pillow 9.3 - 9.5,,,Yes,Yes,Yes,Yes,Yes,,
-Pillow 9.0 - 9.2,,,,Yes,Yes,Yes,Yes,,
-Pillow 8.3.2 - 8.4,,,,Yes,Yes,Yes,Yes,Yes,
-Pillow 8.0 - 8.3.1,,,,,Yes,Yes,Yes,Yes,
-Pillow 7.0 - 7.2,,,,,,Yes,Yes,Yes,Yes
+Python,3.14,3.13,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5
+Pillow 12,Yes,Yes,Yes,Yes,Yes,,,,,
+Pillow 11,,Yes,Yes,Yes,Yes,Yes,,,,
+Pillow 10.1 - 10.4,,,Yes,Yes,Yes,Yes,Yes,,,
+Pillow 10.0,,,,Yes,Yes,Yes,Yes,,,
+Pillow 9.3 - 9.5,,,,Yes,Yes,Yes,Yes,Yes,,
+Pillow 9.0 - 9.2,,,,,Yes,Yes,Yes,Yes,,
+Pillow 8.3.2 - 8.4,,,,,Yes,Yes,Yes,Yes,Yes,
+Pillow 8.0 - 8.3.1,,,,,,Yes,Yes,Yes,Yes,
+Pillow 7.0 - 7.2,,,,,,,Yes,Yes,Yes,Yes
diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst
index c2227f1d2..17e38719a 100644
--- a/docs/installation/platform-support.rst
+++ b/docs/installation/platform-support.rst
@@ -19,45 +19,45 @@ These platforms are built and tested for every change.
+==================================+============================+=====================+
| Alpine | 3.12 | x86-64 |
+----------------------------------+----------------------------+---------------------+
-| Amazon Linux 2 | 3.9 | x86-64 |
+| Amazon Linux 2 | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+
-| Amazon Linux 2023 | 3.9 | x86-64 |
+| Amazon Linux 2023 | 3.11 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Arch | 3.13 | x86-64 |
+----------------------------------+----------------------------+---------------------+
-| CentOS Stream 9 | 3.9 | x86-64 |
+| CentOS Stream 9 | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| CentOS Stream 10 | 3.12 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Debian 12 Bookworm | 3.11 | x86, x86-64 |
+----------------------------------+----------------------------+---------------------+
-| Fedora 41 | 3.13 | x86-64 |
+| Debian 13 Trixie | 3.13 | x86, x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 42 | 3.13 | x86-64 |
+----------------------------------+----------------------------+---------------------+
+| Fedora 43 | 3.14 | x86-64 |
++----------------------------------+----------------------------+---------------------+
| Gentoo | 3.12 | x86-64 |
+----------------------------------+----------------------------+---------------------+
-| macOS 13 Ventura | 3.9 | x86-64 |
-+----------------------------------+----------------------------+---------------------+
-| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
-| | 3.14, PyPy3 | |
+| macOS 15 Sequoia | 3.10 | x86-64 |
+| +----------------------------+---------------------+
+| | 3.11, 3.12, 3.13, 3.14, | arm64 |
+| | PyPy3 | |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+
-| Ubuntu Linux 24.04 LTS (Noble) | 3.9, 3.10, 3.11, | x86-64 |
-| | 3.12, 3.13, 3.14, PyPy3 | |
+| Ubuntu Linux 24.04 LTS (Noble) | 3.10, 3.11, 3.12, 3.13, | x86-64 |
+| | 3.14, PyPy3 | |
| +----------------------------+---------------------+
| | 3.12 | arm64v8, ppc64le, |
| | | s390x |
+----------------------------------+----------------------------+---------------------+
-| Windows Server 2022 | 3.9 | x86 |
+| Windows Server 2022 | 3.10 | x86 |
| +----------------------------+---------------------+
-| | 3.10, 3.11, 3.12, 3.13, | x86-64 |
-| | 3.14, PyPy3 | |
+| | 3.11, 3.12, 3.13, 3.14, | x86-64 |
+| | PyPy3 | |
| +----------------------------+---------------------+
| | 3.12 (MinGW) | x86-64 |
-| +----------------------------+---------------------+
-| | 3.9 (Cygwin) | x86-64 |
+----------------------------------+----------------------------+---------------------+
@@ -71,98 +71,102 @@ These platforms have been reported to work at the versions mentioned.
Contributors please test Pillow on your platform then update this
document and send a pull request.
-+----------------------------------+----------------------------+------------------+--------------+
-| Operating system | | Tested Python | | Latest tested | | Tested |
-| | | versions | | Pillow version | | processors |
-+==================================+============================+==================+==============+
-| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
-| +----------------------------+------------------+ |
-| | 3.8 | 10.4.0 | |
-+----------------------------------+----------------------------+------------------+--------------+
-| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.4.0 |arm |
-+----------------------------------+----------------------------+------------------+--------------+
-| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
-| +----------------------------+------------------+ |
-| | 3.7 | 9.5.0 | |
-+----------------------------------+----------------------------+------------------+--------------+
-| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
-+----------------------------------+----------------------------+------------------+--------------+
-| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
-| +----------------------------+------------------+--------------+
-| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 |
-| +----------------------------+------------------+ |
-| | 3.6 | 8.4.0 | |
-+----------------------------------+----------------------------+------------------+--------------+
-| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 |
-| +----------------------------+------------------+ |
-| | 3.5 | 7.2.0 | |
-+----------------------------------+----------------------------+------------------+--------------+
-| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
-| +----------------------------+------------------+ |
-| | 2.7 | 6.0.0 | |
-| +----------------------------+------------------+ |
-| | 3.4 | 5.4.1 | |
-+----------------------------------+----------------------------+------------------+--------------+
-| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 |
-| +----------------------------+------------------+ |
-| | 3.3 | 4.1.0 | |
-+----------------------------------+----------------------------+------------------+--------------+
-| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Redhat Linux 6 | 2.6 | |x86 |
-+----------------------------------+----------------------------+------------------+--------------+
-| CentOS 6.3 | 2.7, 3.3 | |x86 |
-+----------------------------------+----------------------------+------------------+--------------+
-| CentOS 8 | 3.9 | 9.0.0 |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 |
-| | | PyPy5.3.1, PyPy3 v2.4.0 | | |
-| +----------------------------+------------------+--------------+
-| | 2.7 | 4.3.0 |x86-64 |
-| +----------------------------+------------------+--------------+
-| | 2.7, 3.2 | 3.4.1 |ppc |
-+----------------------------------+----------------------------+------------------+--------------+
-| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm |
-+----------------------------------+----------------------------+------------------+--------------+
-| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm |
-+----------------------------------+----------------------------+------------------+--------------+
-| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm |
-| +----------------------------+------------------+ |
-| | 2.7 | 6.2.2 | |
-+----------------------------------+----------------------------+------------------+--------------+
-| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Windows 11 23H2 | 3.9, 3.10, 3.11, 3.12, 3.13| 11.0.0 |arm64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Windows 11 Pro | 3.11, 3.12 | 10.2.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 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
-| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 |
-+----------------------------------+----------------------------+------------------+--------------+
++----------------------------------+-----------------------------+------------------+--------------+
+| Operating system | | Tested Python | | Latest tested | | Tested |
+| | | versions | | Pillow version | | processors |
++==================================+=============================+==================+==============+
+| macOS 26 Tahoe | 3.10, 3.11, 3.12, 3.13, 3.14| 12.0.0 |arm |
+| +-----------------------------+------------------+ |
+| | 3.9 | 11.3.0 | |
++----------------------------------+-----------------------------+------------------+--------------+
+| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13 | 11.3.0 |arm |
+| +-----------------------------+------------------+ |
+| | 3.8 | 10.4.0 | |
++----------------------------------+-----------------------------+------------------+--------------+
+| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.4.0 |arm |
++----------------------------------+-----------------------------+------------------+--------------+
+| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
+| +-----------------------------+------------------+ |
+| | 3.7 | 9.5.0 | |
++----------------------------------+-----------------------------+------------------+--------------+
+| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
++----------------------------------+-----------------------------+------------------+--------------+
+| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
+| +-----------------------------+------------------+--------------+
+| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 |
+| +-----------------------------+------------------+ |
+| | 3.6 | 8.4.0 | |
++----------------------------------+-----------------------------+------------------+--------------+
+| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 |
+| +-----------------------------+------------------+ |
+| | 3.5 | 7.2.0 | |
++----------------------------------+-----------------------------+------------------+--------------+
+| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
+| +-----------------------------+------------------+ |
+| | 2.7 | 6.0.0 | |
+| +-----------------------------+------------------+ |
+| | 3.4 | 5.4.1 | |
++----------------------------------+-----------------------------+------------------+--------------+
+| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 |
+| +-----------------------------+------------------+ |
+| | 3.3 | 4.1.0 | |
++----------------------------------+-----------------------------+------------------+--------------+
+| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Redhat Linux 6 | 2.6 | |x86 |
++----------------------------------+-----------------------------+------------------+--------------+
+| CentOS 6.3 | 2.7, 3.3 | |x86 |
++----------------------------------+-----------------------------+------------------+--------------+
+| CentOS 8 | 3.9 | 9.0.0 |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 |
+| | | PyPy5.3.1, PyPy3 v2.4.0 | | |
+| +-----------------------------+------------------+--------------+
+| | 2.7 | 4.3.0 |x86-64 |
+| +-----------------------------+------------------+--------------+
+| | 2.7, 3.2 | 3.4.1 |ppc |
++----------------------------------+-----------------------------+------------------+--------------+
+| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm |
++----------------------------------+-----------------------------+------------------+--------------+
+| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm |
++----------------------------------+-----------------------------+------------------+--------------+
+| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm |
+| +-----------------------------+------------------+ |
+| | 2.7 | 6.2.2 | |
++----------------------------------+-----------------------------+------------------+--------------+
+| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Windows 11 23H2 | 3.9, 3.10, 3.11, 3.12, 3.13 | 11.0.0 |arm64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Windows 11 Pro | 3.11, 3.12 | 10.2.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 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
+| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 |
++----------------------------------+-----------------------------+------------------+--------------+
diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst
index 6e73233a1..4c9567593 100644
--- a/docs/reference/ImageDraw.rst
+++ b/docs/reference/ImageDraw.rst
@@ -45,9 +45,7 @@ Colors
^^^^^^
To specify colors, you can use numbers or tuples just as you would use with
-:py:meth:`PIL.Image.new` or :py:meth:`PIL.Image.Image.putpixel`. For “1”,
-“L”, and “I” images, use integers. For “RGB” images, use a 3-tuple containing
-integer values. For “F” images, use integer or floating point values.
+:py:meth:`PIL.Image.new`. See :ref:`colors` for more information.
For palette images (mode “P”), use integers as color indexes. In 1.1.4 and
later, you can also use RGB 3-tuples or color names (see below). The drawing
@@ -59,6 +57,43 @@ Color names
See :ref:`color-names` for the color names supported by Pillow.
+Alpha channel
+^^^^^^^^^^^^^
+
+By default, when drawing onto an existing image, the image's pixel values are simply
+replaced by the new color::
+
+ im = Image.new("RGBA", (1, 1), (255, 0, 0))
+ d = ImageDraw.Draw(im)
+ d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
+ assert im.getpixel((0, 0)) == (0, 255, 0, 127)
+
+ # Alpha channel values have no effect when drawing with RGB mode
+ im = Image.new("RGB", (1, 1), (255, 0, 0))
+ d = ImageDraw.Draw(im)
+ d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
+ assert im.getpixel((0, 0)) == (0, 255, 0)
+
+If you would like to combine translucent color with an RGB image, then initialize the
+ImageDraw instance with the RGBA mode::
+
+ from PIL import Image, ImageDraw
+ im = Image.new("RGB", (1, 1), (255, 0, 0))
+ d = ImageDraw.Draw(im, "RGBA")
+ d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
+ assert im.getpixel((0, 0)) == (128, 127, 0)
+
+If you would like to combine translucent color with an RGBA image underneath, you will
+need to combine multiple images::
+
+ from PIL import Image, ImageDraw
+ im = Image.new("RGBA", (1, 1), (255, 0, 0, 255))
+ im2 = Image.new("RGBA", (1, 1))
+ d = ImageDraw.Draw(im2)
+ d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
+ im.paste(im2.convert("RGB"), mask=im2)
+ assert im.getpixel((0, 0)) == (128, 127, 0, 255)
+
Fonts
^^^^^
@@ -547,6 +582,8 @@ Methods
hello_world = hello + world # kerning is disabled, no need to adjust
assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) # True
+ .. seealso:: :py:meth:`PIL.ImageText.Text.get_length`
+
.. versionadded:: 8.0.0
:param text: Text to be measured. May not contain any newline characters.
@@ -648,6 +685,8 @@ Methods
1/64 pixel precision. The bounding box includes extra margins for
some fonts, e.g. italics or accents.
+ .. seealso:: :py:meth:`PIL.ImageText.Text.get_bbox`
+
.. versionadded:: 8.0.0
:param xy: The anchor coordinates of the text.
diff --git a/docs/reference/ImageFile.rst b/docs/reference/ImageFile.rst
index 043559352..4c34ff812 100644
--- a/docs/reference/ImageFile.rst
+++ b/docs/reference/ImageFile.rst
@@ -74,5 +74,6 @@ Constants
---------
.. autodata:: PIL.ImageFile.LOAD_TRUNCATED_IMAGES
+.. autodata:: PIL.ImageFile.MAXBLOCK
.. autodata:: PIL.ImageFile.ERRORS
:annotation:
diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst
index f6a2ec5bc..413866785 100644
--- a/docs/reference/ImageGrab.rst
+++ b/docs/reference/ImageGrab.rst
@@ -20,7 +20,9 @@ or the clipboard to a PIL image memory.
used as a fallback if they are installed. To disable this behaviour, pass
``xdisplay=""`` instead.
- .. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux)
+ .. versionadded:: 1.1.3 Windows support
+ .. versionadded:: 3.0.0 macOS support
+ .. versionadded:: 7.1.0 Linux support
:param bbox: What region to copy. Default is the entire screen.
On macOS, this is not increased to 2x for Retina screens, so the full
@@ -42,9 +44,11 @@ or the clipboard to a PIL image memory.
.. versionadded:: 7.1.0
:param window:
- HWND, to capture a single window. Windows only.
+ Capture a single window. On Windows, this is a HWND. On macOS, this is a
+ CGWindowID.
- .. versionadded:: 11.2.1
+ .. versionadded:: 11.2.1 Windows support
+ .. versionadded:: 12.1.0 macOS support
:return: An image
.. py:function:: grabclipboard()
@@ -53,7 +57,9 @@ or the clipboard to a PIL image memory.
On Linux, ``wl-paste`` or ``xclip`` is required.
- .. versionadded:: 1.1.4 (Windows), 3.3.0 (macOS), 9.4.0 (Linux)
+ .. versionadded:: 1.1.4 Windows support
+ .. versionadded:: 3.3.0 macOS support
+ .. versionadded:: 9.4.0 Linux support
:return: On Windows, an image, a list of filenames,
or None if the clipboard does not contain image data or filenames.
diff --git a/docs/reference/ImageText.rst b/docs/reference/ImageText.rst
new file mode 100644
index 000000000..8744ad368
--- /dev/null
+++ b/docs/reference/ImageText.rst
@@ -0,0 +1,61 @@
+.. py:module:: PIL.ImageText
+.. py:currentmodule:: PIL.ImageText
+
+:py:mod:`~PIL.ImageText` module
+===============================
+
+The :py:mod:`~PIL.ImageText` module defines a :py:class:`~PIL.ImageText.Text` class.
+Instances of this class provide a way to use fonts with text strings or bytes. The
+result is a simple API to apply styling to pieces of text and measure or draw them.
+
+Example
+-------
+
+::
+
+ from PIL import Image, ImageDraw, ImageFont, ImageText
+ font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 24)
+
+ text = ImageText.Text("Hello world", font)
+ text.embed_color()
+ text.stroke(2, "#0f0")
+
+ print(text.get_length()) # 154.0
+ print(text.get_bbox()) # (-2, 3, 156, 22)
+
+ im = Image.new("RGB", text.get_bbox()[2:])
+ d = ImageDraw.Draw(im)
+ d.text((0, 0), text, "#f00")
+
+Comparison
+----------
+
+Without ``ImageText.Text``::
+
+ from PIL import Image, ImageDraw
+ im = Image.new(mode, size)
+ d = ImageDraw.Draw(im)
+
+ d.textlength(text, font, direction, features, language, embedded_color)
+ d.multiline_textbbox(xy, text, font, anchor, spacing, align, direction, features, language, stroke_width, embedded_color)
+ d.text(xy, text, fill, font, anchor, spacing, align, direction, features, language, stroke_width, stroke_fill, embedded_color)
+
+With ``ImageText.Text``::
+
+ from PIL import ImageText
+ text = ImageText.Text(text, font, mode, spacing, direction, features, language)
+ text.embed_color()
+ text.stroke(stroke_width, stroke_fill)
+
+ text.get_length()
+ text.get_bbox(xy, anchor, align)
+
+ im = Image.new(mode, size)
+ d = ImageDraw.Draw(im)
+ d.text(xy, text, fill, anchor=anchor, align=align)
+
+Methods
+-------
+
+.. autoclass:: PIL.ImageText.Text
+ :members:
diff --git a/docs/reference/PixelAccess.rst b/docs/reference/PixelAccess.rst
index 9d7cf83b6..e4af94b9f 100644
--- a/docs/reference/PixelAccess.rst
+++ b/docs/reference/PixelAccess.rst
@@ -59,7 +59,7 @@ Access using negative indexes is also possible. ::
Modifies the pixel at x,y. The color is given as a single
numerical value for single band images, and a tuple for
- multi-band images.
+ multi-band images. See :ref:`colors` for more information.
:param xy: The pixel coordinate, given as (x, y).
:param color: The pixel value according to its mode,
diff --git a/docs/reference/index.rst b/docs/reference/index.rst
index effcd3c46..1ce26c909 100644
--- a/docs/reference/index.rst
+++ b/docs/reference/index.rst
@@ -24,6 +24,7 @@ Reference
ImageSequence
ImageShow
ImageStat
+ ImageText
ImageTk
ImageTransform
ImageWin
diff --git a/docs/reference/internal_modules.rst b/docs/reference/internal_modules.rst
index 19f78864d..41a8837b3 100644
--- a/docs/reference/internal_modules.rst
+++ b/docs/reference/internal_modules.rst
@@ -53,11 +53,6 @@ on some Python versions.
An object that supports the read method.
-.. py:data:: TypeGuard
- :value: typing.TypeGuard
-
- See :py:obj:`typing.TypeGuard`.
-
:mod:`~PIL._util` module
------------------------
diff --git a/docs/releasenotes/11.3.0.rst b/docs/releasenotes/11.3.0.rst
index 409d50295..5c04a0373 100644
--- a/docs/releasenotes/11.3.0.rst
+++ b/docs/releasenotes/11.3.0.rst
@@ -29,6 +29,13 @@ Image.fromarray mode parameter
The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
mode can be automatically determined from the object's shape and type instead.
+.. note::
+
+ Since pixel values do not contain information about palettes or color spaces, part
+ of this functionality was restored in Pillow 12.0.0. The parameter can be used to
+ place grayscale L mode data within a P mode image, or read RGB data as YCbCr for
+ example.
+
Saving I mode images as PNG
^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/releasenotes/12.0.0.rst b/docs/releasenotes/12.0.0.rst
index 68b664443..4c00d8c4c 100644
--- a/docs/releasenotes/12.0.0.rst
+++ b/docs/releasenotes/12.0.0.rst
@@ -1,22 +1,15 @@
12.0.0
------
-Security
-========
-
-TODO
-^^^^
-
-TODO
-
-:cve:`YYYY-XXXXX`: TODO
-^^^^^^^^^^^^^^^^^^^^^^^
-
-TODO
-
Backwards incompatible changes
==============================
+Python 3.9
+^^^^^^^^^^
+
+Pillow has dropped support for Python 3.9,
+which reached end-of-life in October 2025.
+
ImageFile.raise_oserror
^^^^^^^^^^^^^^^^^^^^^^^
@@ -110,31 +103,84 @@ vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
Deprecations
============
-TODO
-^^^^
+Image._show
+^^^^^^^^^^^
-TODO
+``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15).
+Use :py:meth:`~PIL.ImageShow.show` instead.
+
+ImageCms.ImageCmsProfile.product_name and .product_info
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+``ImageCms.ImageCmsProfile.product_name`` and the corresponding
+``.product_info`` attributes have been deprecated, and will be removed in
+Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
API changes
===========
-TODO
-^^^^
+Image.alpha_composite: LA images
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-TODO
+:py:meth:`~PIL.Image.alpha_composite` can now use LA images as well as RGBA.
API additions
=============
-TODO
-^^^^
+Added ImageText.Text
+^^^^^^^^^^^^^^^^^^^^
-TODO
+:py:class:`PIL.ImageText.Text` has been added, as a simpler way to use fonts with text
+strings or bytes.
+
+Without ``ImageText.Text``::
+
+ from PIL import Image, ImageDraw
+ im = Image.new(mode, size)
+ d = ImageDraw.Draw(im)
+
+ d.textlength(text, font, direction, features, language, embedded_color)
+ d.multiline_textbbox(xy, text, font, anchor, spacing, align, direction, features, language, stroke_width, embedded_color)
+ d.text(xy, text, fill, font, anchor, spacing, align, direction, features, language, stroke_width, stroke_fill, embedded_color)
+
+With ``ImageText.Text``::
+
+ from PIL import ImageText
+ text = ImageText.Text(text, font, mode, spacing, direction, features, language)
+ text.embed_color()
+ text.stroke(stroke_width, stroke_fill)
+
+ text.get_length()
+ text.get_bbox(xy, anchor, align)
+
+ im = Image.new(mode, size)
+ d = ImageDraw.Draw(im)
+ d.text(xy, text, fill, anchor=anchor, align=align)
Other changes
=============
-TODO
-^^^^
+Python 3.14
+^^^^^^^^^^^
-TODO
+Pillow 11.3.0 had wheels built against Python 3.14 beta, available as a preview to help
+others prepare for 3.14, and to ensure Pillow could be used immediately at the release
+of 3.14.0 final (2025-10-07, :pep:`745`).
+
+Pillow 12.0.0 now officially supports Python 3.14.
+
+Image.fromarray mode parameter
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In Pillow 11.3.0, the ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` was
+deprecated. Part of this functionality has been restored in Pillow 12.0.0. Since pixel
+values do not contain information about palettes or color spaces, the parameter can be
+used to place grayscale L mode data within a P mode image, or read RGB data as YCbCr
+for example.
+
+ImageMorph operations must have length 1
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Valid ImageMorph operations are 4, N, 1 and M. By limiting the length to 1 character
+within Pillow, long execution times can be avoided if a user provided long pattern
+strings. Reported by `Jang Choi `__.
diff --git a/patches/README.md b/patches/README.md
deleted file mode 100644
index ff4a8f099..000000000
--- a/patches/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-Although we try to use official sources for dependencies, sometimes the official
-sources don't support a platform (especially mobile platforms), or there's a bug
-fix/feature that is required to support Pillow's usage.
-
-This folder contains patches that must be applied to official sources, organized
-by the platforms that need those patches.
-
-Each patch is against the root of the unpacked official tarball, and is named by
-appending `.patch` to the end of the tarball that is to be patched. This
-includes the full version number; so if the version is bumped, the patch will
-at a minimum require a filename change.
-
-Wherever possible, these patches should be contributed upstream, in the hope that
-future Pillow versions won't need to maintain these patches.
diff --git a/patches/iOS/brotli-1.1.0.tar.gz.patch b/patches/iOS/brotli-1.1.0.tar.gz.patch
deleted file mode 100644
index f165a9ac1..000000000
--- a/patches/iOS/brotli-1.1.0.tar.gz.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-# Brotli 1.1.0 doesn't have explicit support for iOS as a CMAKE_SYSTEM_NAME.
-# That release was from 2023; there have been subsequent changes that allow
-# Brotli to build on iOS without any patches, as long as -DBROTLI_BUILD_TOOLS=NO
-# is specified on the command line.
-#
-diff -ru brotli-1.1.0-orig/CMakeLists.txt brotli-1.1.0/CMakeLists.txt
---- brotli-1.1.0-orig/CMakeLists.txt 2023-08-29 19:00:29
-+++ brotli-1.1.0/CMakeLists.txt 2024-11-07 10:46:26
-@@ -114,6 +114,8 @@
- add_definitions(-DOS_MACOSX)
- set(CMAKE_MACOS_RPATH TRUE)
- set(CMAKE_INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib")
-+elseif(${CMAKE_SYSTEM_NAME} MATCHES "iOS")
-+ add_definitions(-DOS_IOS)
- endif()
-
- if(BROTLI_EMSCRIPTEN)
-@@ -174,10 +176,12 @@
-
- # Installation
- if(NOT BROTLI_BUNDLED_MODE)
-- install(
-- TARGETS brotli
-- RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
-- )
-+ if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "iOS")
-+ install(
-+ TARGETS brotli
-+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
-+ )
-+ endif()
-
- install(
- TARGETS ${BROTLI_LIBRARIES_CORE}
-diff -ru brotli-1.1.0-orig/c/common/platform.h brotli-1.1.0/c/common/platform.h
---- brotli-1.1.0-orig/c/common/platform.h 2023-08-29 19:00:29
-+++ brotli-1.1.0/c/common/platform.h 2024-11-07 10:47:28
-@@ -33,7 +33,7 @@
- #include
- #elif defined(OS_FREEBSD)
- #include
--#elif defined(OS_MACOSX)
-+#elif defined(OS_MACOSX) || defined(OS_IOS)
- #include
- /* Let's try and follow the Linux convention */
- #define BROTLI_X_BYTE_ORDER BYTE_ORDER
diff --git a/patches/iOS/libwebp-1.5.0.tar.gz.patch b/patches/iOS/libwebp-1.5.0.tar.gz.patch
deleted file mode 100644
index fefb72b68..000000000
--- a/patches/iOS/libwebp-1.5.0.tar.gz.patch
+++ /dev/null
@@ -1,42 +0,0 @@
-# libwebp example binaries require dependencies that aren't available for iOS builds.
-# There's also no easy way to invoke the build to *exclude* the example builds.
-# Since we don't need the examples anyway, remove them from the Makefile.
-#
-# As a point of reference, libwebp provides an XCFramework build script that involves
-# 7 separate invocations of make to avoid building the examples. Patching the Makefile
-# to remove the examples is a simpler approach, and one that is more compatible with
-# the existing multibuild infrastructure.
-#
-# In the next release, it should be possible to pass --disable-libwebpexamples
-# instead of applying this patch.
-#
-diff -ur libwebp-1.5.0-orig/Makefile.am libwebp-1.5.0/Makefile.am
---- libwebp-1.5.0-orig/Makefile.am 2024-12-20 09:17:50
-+++ libwebp-1.5.0/Makefile.am 2025-01-09 11:24:17
-@@ -5,5 +5,3 @@
- if BUILD_EXTRAS
- SUBDIRS += extras
- endif
--
--SUBDIRS += examples
-diff -ur libwebp-1.5.0-orig/Makefile.in libwebp-1.5.0/Makefile.in
---- libwebp-1.5.0-orig/Makefile.in 2024-12-20 09:52:53
-+++ libwebp-1.5.0/Makefile.in 2025-01-09 11:24:17
-@@ -156,7 +156,7 @@
- unique=`for i in $$list; do \
- if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
- done | $(am__uniquify_input)`
--DIST_SUBDIRS = sharpyuv src imageio man extras examples
-+DIST_SUBDIRS = sharpyuv src imageio man extras
- am__DIST_COMMON = $(srcdir)/Makefile.in \
- $(top_srcdir)/src/webp/config.h.in AUTHORS COPYING ChangeLog \
- NEWS README.md ar-lib compile config.guess config.sub \
-@@ -351,7 +351,7 @@
- top_srcdir = @top_srcdir@
- webp_libname_prefix = @webp_libname_prefix@
- ACLOCAL_AMFLAGS = -I m4
--SUBDIRS = sharpyuv src imageio man $(am__append_1) examples
-+SUBDIRS = sharpyuv src imageio man $(am__append_1)
- EXTRA_DIST = COPYING autogen.sh
- all: all-recursive
-
diff --git a/pyproject.toml b/pyproject.toml
index abab61e6b..f4514925d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,7 @@
[build-system]
build-backend = "backend"
requires = [
+ "pybind11",
"setuptools>=77",
]
backend-path = [
@@ -19,15 +20,15 @@ license-files = [ "LICENSE" ]
authors = [
{ name = "Jeffrey A. Clark", email = "aclark@aclark.net" },
]
-requires-python = ">=3.9"
+requires-python = ">=3.10"
classifiers = [
"Development Status :: 6 - Mature",
"Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Multimedia :: Graphics",
@@ -56,6 +57,9 @@ optional-dependencies.mic = [
"olefile",
]
optional-dependencies.test-arrow = [
+ "arro3-compute",
+ "arro3-core",
+ "nanoarrow",
"pyarrow",
]
@@ -66,7 +70,7 @@ optional-dependencies.tests = [
"markdown2",
"olefile",
"packaging",
- "pyroma",
+ "pyroma>=5",
"pytest",
"pytest-cov",
"pytest-timeout",
@@ -74,9 +78,6 @@ optional-dependencies.tests = [
"trove-classifiers>=2024.10.12",
]
-optional-dependencies.typing = [
- "typing-extensions; python_version<'3.10'",
-]
optional-dependencies.xmp = [
"defusedxml",
]
@@ -187,7 +188,6 @@ lint.ignore = [
"PT011", # pytest-raises-too-broad
"PT012", # pytest-raises-with-multiple-statements
"PT017", # pytest-assert-in-except
- "PYI026", # flake8-pyi: typing.TypeAlias added in Python 3.10
"PYI034", # flake8-pyi: typing.Self added in Python 3.11
]
lint.per-file-ignores."Tests/oss-fuzz/fuzz_font.py" = [
@@ -205,7 +205,7 @@ lint.isort.required-imports = [
]
[tool.pyproject-fmt]
-max_supported_python = "3.13"
+max_supported_python = "3.14"
[tool.pytest.ini_options]
addopts = "-ra --color=auto"
@@ -214,7 +214,7 @@ testpaths = [
]
[tool.mypy]
-python_version = "3.9"
+python_version = "3.10"
pretty = true
disallow_any_generics = true
enable_error_code = "ignore-without-code"
diff --git a/setup.py b/setup.py
index 477d187a2..032c1c6d2 100644
--- a/setup.py
+++ b/setup.py
@@ -17,9 +17,24 @@ import sys
import warnings
from collections.abc import Iterator
+from pybind11.setup_helpers import ParallelCompile
from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from setuptools import _BuildInfo
+
+configuration: dict[str, list[str]] = {}
+
+# parse configuration from _custom_build/backend.py
+while sys.argv[-1].startswith("--pillow-configuration="):
+ _, key, value = sys.argv.pop().split("=", 2)
+ configuration.setdefault(key, []).append(value)
+
+default = int(configuration.get("parallel", ["0"])[-1])
+ParallelCompile("MAX_CONCURRENCY", default).install()
+
def get_version() -> str:
version_file = "src/PIL/_version.py"
@@ -27,9 +42,6 @@ def get_version() -> str:
return f.read().split('"')[1]
-configuration: dict[str, list[str]] = {}
-
-
PILLOW_VERSION = get_version()
AVIF_ROOT = None
FREETYPE_ROOT = None
@@ -386,9 +398,7 @@ class pil_build_ext(build_ext):
cpu_count = os.cpu_count()
if cpu_count is not None:
try:
- self.parallel = int(
- os.environ.get("MAX_CONCURRENCY", min(4, cpu_count))
- )
+ self.parallel = int(os.environ.get("MAX_CONCURRENCY", cpu_count))
except TypeError:
pass
for x in self.feature:
@@ -1066,6 +1076,10 @@ def debug_build() -> bool:
return hasattr(sys, "gettotalrefcount") or FUZZING_BUILD
+libraries: list[tuple[str, _BuildInfo]] = [
+ ("pil_imaging_mode", {"sources": ["src/libImaging/Mode.c"]}),
+]
+
files: list[str | os.PathLike[str]] = ["src/_imaging.c"]
for src_file in _IMAGING:
files.append("src/" + src_file + ".c")
@@ -1083,15 +1097,11 @@ ext_modules = [
]
-# parse configuration from _custom_build/backend.py
-while sys.argv[-1].startswith("--pillow-configuration="):
- _, key, value = sys.argv.pop().split("=", 2)
- configuration.setdefault(key, []).append(value)
-
try:
setup(
cmdclass={"build_ext": pil_build_ext},
ext_modules=ext_modules,
+ libraries=libraries,
zip_safe=not (debug_build() or PLATFORM_MINGW),
)
except RequiredDependencyException as err:
diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py
index b817dbc87..9c188e084 100644
--- a/src/PIL/CurImagePlugin.py
+++ b/src/PIL/CurImagePlugin.py
@@ -17,7 +17,7 @@
#
from __future__ import annotations
-from . import BmpImagePlugin, Image, ImageFile
+from . import BmpImagePlugin, Image
from ._binary import i16le as i16
from ._binary import i32le as i32
@@ -38,6 +38,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
format_description = "Windows Cursor"
def _open(self) -> None:
+ assert self.fp is not None
offset = self.fp.tell()
# check magic
@@ -63,8 +64,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
# patch up the bitmap height
self._size = self.size[0], self.size[1] // 2
- d, e, o, a = self.tile[0]
- self.tile[0] = ImageFile._Tile(d, (0, 0) + self.size, o, a)
+ self.tile = [self.tile[0]._replace(extents=(0, 0) + self.size)]
#
diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py
index f9ade18f9..312f602a6 100644
--- a/src/PIL/DdsImagePlugin.py
+++ b/src/PIL/DdsImagePlugin.py
@@ -12,7 +12,6 @@ https://creativecommons.org/publicdomain/zero/1.0/
from __future__ import annotations
-import io
import struct
import sys
from enum import IntEnum, IntFlag
@@ -333,6 +332,7 @@ class DdsImageFile(ImageFile.ImageFile):
format_description = "DirectDraw Surface"
def _open(self) -> None:
+ assert self.fp is not None
if not _accept(self.fp.read(4)):
msg = "not a DDS file"
raise SyntaxError(msg)
@@ -340,21 +340,20 @@ class DdsImageFile(ImageFile.ImageFile):
if header_size != 124:
msg = f"Unsupported header size {repr(header_size)}"
raise OSError(msg)
- header_bytes = self.fp.read(header_size - 4)
- if len(header_bytes) != 120:
- msg = f"Incomplete header: {len(header_bytes)} bytes"
+ header = self.fp.read(header_size - 4)
+ if len(header) != 120:
+ msg = f"Incomplete header: {len(header)} bytes"
raise OSError(msg)
- header = io.BytesIO(header_bytes)
- flags, height, width = struct.unpack("<3I", header.read(12))
+ flags, height, width = struct.unpack("<3I", header[:12])
self._size = (width, height)
extents = (0, 0) + self.size
- pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
- struct.unpack("<11I", header.read(44)) # reserved
+ pitch, depth, mipmaps = struct.unpack("<3I", header[12:24])
+ struct.unpack("<11I", header[24:68]) # reserved
# pixel format
- pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16))
+ pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header[68:84])
n = 0
rawmode = None
if pfflags & DDPF.RGB:
@@ -366,7 +365,7 @@ class DdsImageFile(ImageFile.ImageFile):
self._mode = "RGB"
mask_count = 3
- masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
+ masks = struct.unpack(f"<{mask_count}I", header[84 : 84 + mask_count * 4])
self.tile = [ImageFile._Tile("dds_rgb", extents, 0, (bitcount, masks))]
return
elif pfflags & DDPF.LUMINANCE:
@@ -516,6 +515,8 @@ class DdsRgbDecoder(ImageFile.PyDecoder):
# Remove the zero padding, and scale it to 8 bits
data += o8(
int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255)
+ if mask_totals[i]
+ else 0
)
self.set_as_raw(data)
return -1, 0
diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py
index 5e2ddad99..69f3062b4 100644
--- a/src/PIL/EpsImagePlugin.py
+++ b/src/PIL/EpsImagePlugin.py
@@ -354,6 +354,9 @@ class EpsImageFile(ImageFile.ImageFile):
read_comment(s)
elif bytes_mv[:9] == b"%%Trailer":
trailer_reached = True
+ elif bytes_mv[:14] == b"%%BeginBinary:":
+ bytecount = int(byte_arr[14:bytes_read])
+ self.fp.seek(bytecount, os.SEEK_CUR)
bytes_read = 0
# A "BoundingBox" is always required,
diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py
index 7c5bfeefa..da1e8e95c 100644
--- a/src/PIL/FliImagePlugin.py
+++ b/src/PIL/FliImagePlugin.py
@@ -30,7 +30,7 @@ from ._util import DeferredError
def _accept(prefix: bytes) -> bool:
return (
- len(prefix) >= 6
+ len(prefix) >= 16
and i16(prefix, 4) in [0xAF11, 0xAF12]
and i16(prefix, 14) in [0, 3] # flags
)
@@ -48,8 +48,14 @@ class FliImageFile(ImageFile.ImageFile):
def _open(self) -> None:
# HEAD
+ assert self.fp is not None
s = self.fp.read(128)
- if not (_accept(s) and s[20:22] == b"\x00\x00"):
+ if not (
+ _accept(s)
+ and s[20:22] == b"\x00" * 2
+ and s[42:80] == b"\x00" * 38
+ and s[88:] == b"\x00" * 40
+ ):
msg = "not an FLI/FLC file"
raise SyntaxError(msg)
@@ -77,8 +83,7 @@ class FliImageFile(ImageFile.ImageFile):
if i16(s, 4) == 0xF100:
# prefix chunk; ignore it
- self.__offset = self.__offset + i32(s)
- self.fp.seek(self.__offset)
+ self.fp.seek(self.__offset + i32(s))
s = self.fp.read(16)
if i16(s, 4) == 0xF1FA:
@@ -111,6 +116,7 @@ class FliImageFile(ImageFile.ImageFile):
# load palette
i = 0
+ assert self.fp is not None
for e in range(i16(self.fp.read(2))):
s = self.fp.read(2)
i = i + s[0]
diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py
index f319d7e84..d69295363 100644
--- a/src/PIL/GbrImagePlugin.py
+++ b/src/PIL/GbrImagePlugin.py
@@ -54,7 +54,7 @@ class GbrImageFile(ImageFile.ImageFile):
width = i32(self.fp.read(4))
height = i32(self.fp.read(4))
color_depth = i32(self.fp.read(4))
- if width <= 0 or height <= 0:
+ if width == 0 or height == 0:
msg = "not a GIMP brush"
raise SyntaxError(msg)
if color_depth not in (1, 4):
@@ -71,7 +71,7 @@ class GbrImageFile(ImageFile.ImageFile):
raise SyntaxError(msg)
self.info["spacing"] = i32(self.fp.read(4))
- comment = self.fp.read(comment_length)[:-1]
+ self.info["comment"] = self.fp.read(comment_length)[:-1]
if color_depth == 1:
self._mode = "L"
@@ -80,8 +80,6 @@ class GbrImageFile(ImageFile.ImageFile):
self._size = width, height
- self.info["comment"] = comment
-
# Image might not be small
Image._decompression_bomb_check(self.size)
diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py
index ed726b07d..b4c0411d2 100644
--- a/src/PIL/GifImagePlugin.py
+++ b/src/PIL/GifImagePlugin.py
@@ -30,7 +30,7 @@ import os
import subprocess
from enum import IntEnum
from functools import cached_property
-from typing import IO, Any, Literal, NamedTuple, Union, cast
+from typing import Any, NamedTuple, cast
from . import (
Image,
@@ -48,6 +48,8 @@ from ._util import DeferredError
TYPE_CHECKING = False
if TYPE_CHECKING:
+ from typing import IO, Literal
+
from . import _imaging
from ._typing import Buffer
@@ -534,7 +536,7 @@ def _normalize_mode(im: Image.Image) -> Image.Image:
return im.convert("L")
-_Palette = Union[bytes, bytearray, list[int], ImagePalette.ImagePalette]
+_Palette = bytes | bytearray | list[int] | ImagePalette.ImagePalette
def _normalize_palette(
diff --git a/src/PIL/GimpGradientFile.py b/src/PIL/GimpGradientFile.py
index ec62f8e4e..5f2691882 100644
--- a/src/PIL/GimpGradientFile.py
+++ b/src/PIL/GimpGradientFile.py
@@ -21,10 +21,14 @@ See the GIMP distribution for more information.)
from __future__ import annotations
from math import log, pi, sin, sqrt
-from typing import IO, Callable
from ._binary import o8
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable
+ from typing import IO
+
EPSILON = 1e-10
"""""" # Enable auto-doc for data member
diff --git a/src/PIL/GimpPaletteFile.py b/src/PIL/GimpPaletteFile.py
index 379ffd739..016257d3d 100644
--- a/src/PIL/GimpPaletteFile.py
+++ b/src/PIL/GimpPaletteFile.py
@@ -17,7 +17,10 @@ from __future__ import annotations
import re
from io import BytesIO
-from typing import IO
+
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from typing import IO
class GimpPaletteFile:
diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py
index 439fc5a3e..dfa798893 100644
--- a/src/PIL/GribStubImagePlugin.py
+++ b/src/PIL/GribStubImagePlugin.py
@@ -33,7 +33,7 @@ def register_handler(handler: ImageFile.StubHandler | None) -> None:
def _accept(prefix: bytes) -> bool:
- return prefix.startswith(b"GRIB") and prefix[7] == 1
+ return len(prefix) >= 8 and prefix.startswith(b"GRIB") and prefix[7] == 1
class GribStubImageFile(ImageFile.StubImageFile):
diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py
index bd35ac890..d5da07d47 100644
--- a/src/PIL/IcoImagePlugin.py
+++ b/src/PIL/IcoImagePlugin.py
@@ -17,6 +17,20 @@
# .
# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
#
+# Copyright 2008 Bryan Davis
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
# Icon format references:
# * https://en.wikipedia.org/wiki/ICO_(file_format)
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index 5ff6a30f5..d95146208 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -38,10 +38,9 @@ import struct
import sys
import tempfile
import warnings
-from collections.abc import Callable, Iterator, MutableMapping, Sequence
+from collections.abc import MutableMapping
from enum import IntEnum
-from types import ModuleType
-from typing import IO, Any, Literal, Protocol, cast
+from typing import IO, Protocol, cast
# VERSION was removed in Pillow 6.0.0.
# PILLOW_VERSION was removed in Pillow 9.0.0.
@@ -64,6 +63,12 @@ try:
except ImportError:
ElementTree = None
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable, Iterator, Sequence
+ from types import ModuleType
+ from typing import Any, Literal
+
logger = logging.getLogger(__name__)
@@ -98,7 +103,6 @@ try:
raise ImportError(msg)
except ImportError as v:
- core = DeferredError.new(ImportError("The _imaging C module is not installed."))
# Explanations for ways that we know we might have an import error
if str(v).startswith("Module use of python"):
# The _imaging C module is present, but not compiled for
@@ -1005,8 +1009,14 @@ class Image:
new_im.info["transparency"] = transparency
return new_im
- if mode == "P" and self.mode == "RGBA":
- return self.quantize(colors)
+ if self.mode == "RGBA":
+ if mode == "P":
+ return self.quantize(colors)
+ elif mode == "PA":
+ r, g, b, a = self.split()
+ rgb = merge("RGB", (r, g, b))
+ p = rgb.quantize(colors)
+ return merge("PA", (p, a))
trns = None
delete_trns = False
@@ -1138,7 +1148,7 @@ class Image:
raise ValueError(msg) from e
new_im = self._new(im)
- if mode == "P" and palette != Palette.ADAPTIVE:
+ if mode in ("P", "PA") and palette != Palette.ADAPTIVE:
from . import ImagePalette
new_im.palette = ImagePalette.ImagePalette("RGB", im.getpalette("RGB"))
@@ -1332,12 +1342,6 @@ class Image:
"""
pass
- def _expand(self, xmargin: int, ymargin: int | None = None) -> Image:
- if ymargin is None:
- ymargin = xmargin
- self.load()
- return self._new(self.im.expand(xmargin, ymargin))
-
def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image:
"""
Filters this image using the given filter. For a list of
@@ -1730,9 +1734,10 @@ class Image:
details).
Instead of an image, the source can be a integer or tuple
- containing pixel values. The method then fills the region
- with the given color. When creating RGB images, you can
- also use color strings as supported by the ImageColor module.
+ containing pixel values. The method then fills the region
+ with the given color. When creating RGB images, you can
+ also use color strings as supported by the ImageColor module. See
+ :ref:`colors` for more information.
If a mask is given, this method updates only the regions
indicated by the mask. You can use either "1", "L", "LA", "RGBA"
@@ -1988,7 +1993,8 @@ class Image:
sequence ends. The scale and offset values are used to adjust the
sequence values: **pixel = value*scale + offset**.
- :param data: A flattened sequence object.
+ :param data: A flattened sequence object. See :ref:`colors` for more
+ information about values.
:param scale: An optional scale value. The default is 1.0.
:param offset: An optional offset value. The default is 0.0.
"""
@@ -2047,7 +2053,7 @@ class Image:
Modifies the pixel at the given position. The color is given as
a single numerical value for single-band images, and a tuple for
multi-band images. In addition to this, RGB and RGBA tuples are
- accepted for P and PA images.
+ accepted for P and PA images. See :ref:`colors` for more information.
Note that this method is relatively slow. For more extensive changes,
use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw`
@@ -2064,9 +2070,7 @@ class Image:
:param value: The pixel value.
"""
- if self.readonly:
- self._copy()
- self.load()
+ self._ensure_mutable()
if (
self.mode in ("P", "PA")
@@ -2646,7 +2650,9 @@ class Image:
:param title: Optional title to use for the image window, where possible.
"""
- _show(self, title=title)
+ from . import ImageShow
+
+ ImageShow.show(self, title)
def split(self) -> tuple[Image, ...]:
"""
@@ -3075,12 +3081,12 @@ def new(
:param mode: The mode to use for the new image. See:
:ref:`concept-modes`.
:param size: A 2-tuple, containing (width, height) in pixels.
- :param color: What color to use for the image. Default is black.
- If given, this should be a single integer or floating point value
- for single-band modes, and a tuple for multi-band modes (one value
- per band). When creating RGB or HSV images, you can also use color
- strings as supported by the ImageColor module. If the color is
- None, the image is not initialised.
+ :param color: What color to use for the image. Default is black. If given,
+ this should be a single integer or floating point value for single-band
+ modes, and a tuple for multi-band modes (one value per band). When
+ creating RGB or HSV images, you can also use color strings as supported
+ by the ImageColor module. See :ref:`colors` for more information. If the
+ color is None, the image is not initialised.
:returns: An :py:class:`~PIL.Image.Image` object.
"""
@@ -3271,19 +3277,10 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
transferred. This means that P and PA mode images will lose their palette.
:param obj: Object with array interface
- :param mode: Optional mode to use when reading ``obj``. Will be determined from
- type if ``None``. Deprecated.
-
- This will not be used to convert the data after reading, but will be used to
- change how the data is read::
-
- from PIL import Image
- import numpy as np
- a = np.full((1, 1), 300)
- im = Image.fromarray(a, mode="L")
- im.getpixel((0, 0)) # 44
- im = Image.fromarray(a, mode="RGB")
- im.getpixel((0, 0)) # (44, 1, 0)
+ :param mode: Optional mode to use when reading ``obj``. Since pixel values do not
+ contain information about palettes or color spaces, this can be used to place
+ grayscale L mode data within a P mode image, or read RGB data as YCbCr for
+ example.
See: :ref:`concept-modes` for general information about modes.
:returns: An image object.
@@ -3294,21 +3291,28 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
shape = arr["shape"]
ndim = len(shape)
strides = arr.get("strides", None)
- if mode is None:
- try:
- typekey = (1, 1) + shape[2:], arr["typestr"]
- except KeyError as e:
+ try:
+ typekey = (1, 1) + shape[2:], arr["typestr"]
+ except KeyError as e:
+ if mode is not None:
+ typekey = None
+ color_modes: list[str] = []
+ else:
msg = "Cannot handle this data type"
raise TypeError(msg) from e
+ if typekey is not None:
try:
- mode, rawmode = _fromarray_typemap[typekey]
+ typemode, rawmode, color_modes = _fromarray_typemap[typekey]
except KeyError as e:
typekey_shape, typestr = typekey
msg = f"Cannot handle this data type: {typekey_shape}, {typestr}"
raise TypeError(msg) from e
- else:
- deprecate("'mode' parameter", 13)
+ if mode is not None:
+ if mode != typemode and mode not in color_modes:
+ deprecate("'mode' parameter for changing data types", 13)
rawmode = mode
+ else:
+ mode = typemode
if mode in ["1", "L", "I", "P", "F"]:
ndmax = 2
elif mode == "RGB":
@@ -3405,29 +3409,29 @@ def fromqpixmap(im: ImageQt.QPixmap) -> ImageFile.ImageFile:
_fromarray_typemap = {
- # (shape, typestr) => mode, rawmode
+ # (shape, typestr) => mode, rawmode, color modes
# first two members of shape are set to one
- ((1, 1), "|b1"): ("1", "1;8"),
- ((1, 1), "|u1"): ("L", "L"),
- ((1, 1), "|i1"): ("I", "I;8"),
- ((1, 1), "u2"): ("I", "I;16B"),
- ((1, 1), "i2"): ("I", "I;16BS"),
- ((1, 1), "u4"): ("I", "I;32B"),
- ((1, 1), "i4"): ("I", "I;32BS"),
- ((1, 1), "f4"): ("F", "F;32BF"),
- ((1, 1), "f8"): ("F", "F;64BF"),
- ((1, 1, 2), "|u1"): ("LA", "LA"),
- ((1, 1, 3), "|u1"): ("RGB", "RGB"),
- ((1, 1, 4), "|u1"): ("RGBA", "RGBA"),
+ ((1, 1), "|b1"): ("1", "1;8", []),
+ ((1, 1), "|u1"): ("L", "L", ["P"]),
+ ((1, 1), "|i1"): ("I", "I;8", []),
+ ((1, 1), "u2"): ("I", "I;16B", []),
+ ((1, 1), "i2"): ("I", "I;16BS", []),
+ ((1, 1), "u4"): ("I", "I;32B", []),
+ ((1, 1), "i4"): ("I", "I;32BS", []),
+ ((1, 1), "f4"): ("F", "F;32BF", []),
+ ((1, 1), "f8"): ("F", "F;64BF", []),
+ ((1, 1, 2), "|u1"): ("LA", "LA", ["La", "PA"]),
+ ((1, 1, 3), "|u1"): ("RGB", "RGB", ["YCbCr", "LAB", "HSV"]),
+ ((1, 1, 4), "|u1"): ("RGBA", "RGBA", ["RGBa", "RGBX", "CMYK"]),
# shortcuts:
- ((1, 1), f"{_ENDIAN}i4"): ("I", "I"),
- ((1, 1), f"{_ENDIAN}f4"): ("F", "F"),
+ ((1, 1), f"{_ENDIAN}i4"): ("I", "I", []),
+ ((1, 1), f"{_ENDIAN}f4"): ("F", "F", []),
}
@@ -3584,9 +3588,8 @@ def alpha_composite(im1: Image, im2: Image) -> Image:
"""
Alpha composite im2 over im1.
- :param im1: The first image. Must have mode RGBA.
- :param im2: The second image. Must have mode RGBA, and the same size as
- the first image.
+ :param im1: The first image. Must have mode RGBA or LA.
+ :param im2: The second image. Must have the same mode and size as the first image.
:returns: An :py:class:`~PIL.Image.Image` object.
"""
@@ -3812,6 +3815,7 @@ def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None:
def _show(image: Image, **options: Any) -> None:
from . import ImageShow
+ deprecate("Image._show", 13, "ImageShow.show")
ImageShow.show(image, **options)
@@ -4233,6 +4237,8 @@ class Exif(_ExifBase):
del self._info[tag]
else:
del self._data[tag]
+ if tag in self._ifds:
+ del self._ifds[tag]
def __iter__(self) -> Iterator[int]:
keys = set(self._data)
diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py
index d3555694a..513e28acf 100644
--- a/src/PIL/ImageCms.py
+++ b/src/PIL/ImageCms.py
@@ -23,9 +23,10 @@ import operator
import sys
from enum import IntEnum, IntFlag
from functools import reduce
-from typing import Literal, SupportsFloat, SupportsInt, Union
+from typing import Any, Literal, SupportsFloat, SupportsInt, Union
from . import Image
+from ._deprecate import deprecate
from ._typing import SupportsRead
try:
@@ -233,9 +234,7 @@ class ImageCmsProfile:
low-level profile object
"""
- self.filename = None
- self.product_name = None # profile.product_name
- self.product_info = None # profile.product_info
+ self.filename: str | None = None
if isinstance(profile, str):
if sys.platform == "win32":
@@ -256,6 +255,13 @@ class ImageCmsProfile:
msg = "Invalid type for Profile" # type: ignore[unreachable]
raise TypeError(msg)
+ def __getattr__(self, name: str) -> Any:
+ if name in ("product_name", "product_info"):
+ deprecate(f"ImageCms.ImageCmsProfile.{name}", 13)
+ return None
+ msg = f"'{self.__class__.__name__}' object has no attribute '{name}'"
+ raise AttributeError(msg)
+
def tobytes(self) -> bytes:
"""
Returns the profile in a format suitable for embedding in
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
index e95fa91f8..8bcf2d8ee 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -34,20 +34,21 @@ from __future__ import annotations
import math
import struct
from collections.abc import Sequence
-from types import ModuleType
-from typing import Any, AnyStr, Callable, Union, cast
+from typing import cast
-from . import Image, ImageColor
-from ._typing import Coords
-
-# experimental access to the outline API
-Outline: Callable[[], Image.core._Outline] = Image.core.outline
+from . import Image, ImageColor, ImageText
TYPE_CHECKING = False
if TYPE_CHECKING:
- from . import ImageDraw2, ImageFont
+ from collections.abc import Callable
+ from types import ModuleType
+ from typing import Any, AnyStr
-_Ink = Union[float, tuple[int, ...], str]
+ from . import ImageDraw2, ImageFont
+ from ._typing import Coords, _Ink
+
+# experimental access to the outline API
+Outline: Callable[[], Image.core._Outline] = Image.core.outline
"""
A simple 2D drawing interface for PIL images.
@@ -73,9 +74,7 @@ class ImageDraw:
must be the same as the image mode. If omitted, the mode
defaults to the mode of the image.
"""
- im.load()
- if im.readonly:
- im._copy() # make it writeable
+ im._ensure_mutable()
blend = 0
if mode is None:
mode = im.mode
@@ -536,15 +535,10 @@ class ImageDraw:
right[3] -= r + 1
self.draw.draw_rectangle(right, ink, 1)
- def _multiline_check(self, text: AnyStr) -> bool:
- split_character = "\n" if isinstance(text, str) else b"\n"
-
- return split_character in text
-
def text(
self,
xy: tuple[float, float],
- text: AnyStr,
+ text: AnyStr | ImageText.Text,
fill: _Ink | None = None,
font: (
ImageFont.ImageFont
@@ -565,29 +559,18 @@ class ImageDraw:
**kwargs: Any,
) -> None:
"""Draw text."""
- if embedded_color and self.mode not in ("RGB", "RGBA"):
- msg = "Embedded color supported only in RGB and RGBA modes"
- raise ValueError(msg)
-
- if font is None:
- font = self._getfont(kwargs.get("font_size"))
-
- if self._multiline_check(text):
- return self.multiline_text(
- xy,
- text,
- fill,
- font,
- anchor,
- spacing,
- align,
- direction,
- features,
- language,
- stroke_width,
- stroke_fill,
- embedded_color,
+ if isinstance(text, ImageText.Text):
+ image_text = text
+ else:
+ if font is None:
+ font = self._getfont(kwargs.get("font_size"))
+ image_text = ImageText.Text(
+ text, font, self.mode, spacing, direction, features, language
)
+ if embedded_color:
+ image_text.embed_color()
+ if stroke_width:
+ image_text.stroke(stroke_width, stroke_fill)
def getink(fill: _Ink | None) -> int:
ink, fill_ink = self._getink(fill)
@@ -596,70 +579,79 @@ class ImageDraw:
return fill_ink
return ink
- def draw_text(ink: int, stroke_width: float = 0) -> None:
- mode = self.fontmode
- if stroke_width == 0 and embedded_color:
- mode = "RGBA"
- coord = []
- for i in range(2):
- coord.append(int(xy[i]))
- start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
- try:
- mask, offset = font.getmask2( # type: ignore[union-attr,misc]
- text,
- mode,
- direction=direction,
- features=features,
- language=language,
- stroke_width=stroke_width,
- stroke_filled=True,
- anchor=anchor,
- ink=ink,
- start=start,
- *args,
- **kwargs,
- )
- coord = [coord[0] + offset[0], coord[1] + offset[1]]
- except AttributeError:
+ ink = getink(fill)
+ if ink is None:
+ return
+
+ stroke_ink = None
+ if image_text.stroke_width:
+ stroke_ink = (
+ getink(image_text.stroke_fill)
+ if image_text.stroke_fill is not None
+ else ink
+ )
+
+ for xy, anchor, line in image_text._split(xy, anchor, align):
+
+ def draw_text(ink: int, stroke_width: float = 0) -> None:
+ mode = self.fontmode
+ if stroke_width == 0 and embedded_color:
+ mode = "RGBA"
+ coord = []
+ for i in range(2):
+ coord.append(int(xy[i]))
+ start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
try:
- mask = font.getmask( # type: ignore[misc]
- text,
+ mask, offset = image_text.font.getmask2( # type: ignore[union-attr,misc]
+ line,
mode,
- direction,
- features,
- language,
- stroke_width,
- anchor,
- ink,
+ direction=direction,
+ features=features,
+ language=language,
+ stroke_width=stroke_width,
+ stroke_filled=True,
+ anchor=anchor,
+ ink=ink,
start=start,
*args,
**kwargs,
)
- except TypeError:
- mask = font.getmask(text)
- if mode == "RGBA":
- # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
- # extract mask and set text alpha
- color, mask = mask, mask.getband(3)
- ink_alpha = struct.pack("i", ink)[3]
- color.fillband(3, ink_alpha)
- x, y = coord
- if self.im is not None:
- self.im.paste(
- color, (x, y, x + mask.size[0], y + mask.size[1]), mask
- )
- else:
- self.draw.draw_bitmap(coord, mask, ink)
-
- ink = getink(fill)
- if ink is not None:
- stroke_ink = None
- if stroke_width:
- stroke_ink = getink(stroke_fill) if stroke_fill is not None else ink
+ coord = [coord[0] + offset[0], coord[1] + offset[1]]
+ except AttributeError:
+ try:
+ mask = image_text.font.getmask( # type: ignore[misc]
+ line,
+ mode,
+ direction,
+ features,
+ language,
+ stroke_width,
+ anchor,
+ ink,
+ start=start,
+ *args,
+ **kwargs,
+ )
+ except TypeError:
+ mask = image_text.font.getmask(line)
+ if mode == "RGBA":
+ # image_text.font.getmask2(mode="RGBA")
+ # returns color in RGB bands and mask in A
+ # extract mask and set text alpha
+ color, mask = mask, mask.getband(3)
+ ink_alpha = struct.pack("i", ink)[3]
+ color.fillband(3, ink_alpha)
+ x, y = coord
+ if self.im is not None:
+ self.im.paste(
+ color, (x, y, x + mask.size[0], y + mask.size[1]), mask
+ )
+ else:
+ self.draw.draw_bitmap(coord, mask, ink)
if stroke_ink is not None:
# Draw stroked text
- draw_text(stroke_ink, stroke_width)
+ draw_text(stroke_ink, image_text.stroke_width)
# Draw normal text
if ink != stroke_ink:
@@ -668,132 +660,6 @@ class ImageDraw:
# Only draw normal text
draw_text(ink)
- def _prepare_multiline_text(
- self,
- xy: tuple[float, float],
- text: AnyStr,
- font: (
- ImageFont.ImageFont
- | ImageFont.FreeTypeFont
- | ImageFont.TransposedFont
- | None
- ),
- anchor: str | None,
- spacing: float,
- align: str,
- direction: str | None,
- features: list[str] | None,
- language: str | None,
- stroke_width: float,
- embedded_color: bool,
- font_size: float | None,
- ) -> tuple[
- ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
- list[tuple[tuple[float, float], str, AnyStr]],
- ]:
- if anchor is None:
- anchor = "lt" if direction == "ttb" else "la"
- elif len(anchor) != 2:
- msg = "anchor must be a 2 character string"
- raise ValueError(msg)
- elif anchor[1] in "tb" and direction != "ttb":
- msg = "anchor not supported for multiline text"
- raise ValueError(msg)
-
- if font is None:
- font = self._getfont(font_size)
-
- lines = text.split("\n" if isinstance(text, str) else b"\n")
- line_spacing = (
- self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
- + stroke_width
- + spacing
- )
-
- top = xy[1]
- parts = []
- if direction == "ttb":
- left = xy[0]
- for line in lines:
- parts.append(((left, top), anchor, line))
- left += line_spacing
- else:
- widths = []
- max_width: float = 0
- for line in lines:
- line_width = self.textlength(
- line,
- font,
- direction=direction,
- features=features,
- language=language,
- embedded_color=embedded_color,
- )
- widths.append(line_width)
- max_width = max(max_width, line_width)
-
- if anchor[1] == "m":
- top -= (len(lines) - 1) * line_spacing / 2.0
- elif anchor[1] == "d":
- top -= (len(lines) - 1) * line_spacing
-
- for idx, line in enumerate(lines):
- left = xy[0]
- width_difference = max_width - widths[idx]
-
- # align by align parameter
- if align in ("left", "justify"):
- pass
- elif align == "center":
- left += width_difference / 2.0
- elif align == "right":
- left += width_difference
- else:
- msg = 'align must be "left", "center", "right" or "justify"'
- raise ValueError(msg)
-
- if (
- align == "justify"
- and width_difference != 0
- and idx != len(lines) - 1
- ):
- words = line.split(" " if isinstance(text, str) else b" ")
- if len(words) > 1:
- # align left by anchor
- if anchor[0] == "m":
- left -= max_width / 2.0
- elif anchor[0] == "r":
- left -= max_width
-
- word_widths = [
- self.textlength(
- word,
- font,
- direction=direction,
- features=features,
- language=language,
- embedded_color=embedded_color,
- )
- for word in words
- ]
- word_anchor = "l" + anchor[1]
- width_difference = max_width - sum(word_widths)
- for i, word in enumerate(words):
- parts.append(((left, top), word_anchor, word))
- left += word_widths[i] + width_difference / (len(words) - 1)
- top += line_spacing
- continue
-
- # align left by anchor
- if anchor[0] == "m":
- left -= width_difference / 2.0
- elif anchor[0] == "r":
- left -= width_difference
- parts.append(((left, top), anchor, line))
- top += line_spacing
-
- return font, parts
-
def multiline_text(
self,
xy: tuple[float, float],
@@ -817,9 +683,10 @@ class ImageDraw:
*,
font_size: float | None = None,
) -> None:
- font, lines = self._prepare_multiline_text(
+ return self.text(
xy,
text,
+ fill,
font,
anchor,
spacing,
@@ -828,25 +695,11 @@ class ImageDraw:
features,
language,
stroke_width,
+ stroke_fill,
embedded_color,
- font_size,
+ font_size=font_size,
)
- for xy, anchor, line in lines:
- self.text(
- xy,
- line,
- fill,
- font,
- anchor,
- direction=direction,
- features=features,
- language=language,
- stroke_width=stroke_width,
- stroke_fill=stroke_fill,
- embedded_color=embedded_color,
- )
-
def textlength(
self,
text: AnyStr,
@@ -864,17 +717,19 @@ class ImageDraw:
font_size: float | None = None,
) -> float:
"""Get the length of a given string, in pixels with 1/64 precision."""
- if self._multiline_check(text):
- msg = "can't measure length of multiline text"
- raise ValueError(msg)
- if embedded_color and self.mode not in ("RGB", "RGBA"):
- msg = "Embedded color supported only in RGB and RGBA modes"
- raise ValueError(msg)
-
if font is None:
font = self._getfont(font_size)
- mode = "RGBA" if embedded_color else self.fontmode
- return font.getlength(text, mode, direction, features, language)
+ image_text = ImageText.Text(
+ text,
+ font,
+ self.mode,
+ direction=direction,
+ features=features,
+ language=language,
+ )
+ if embedded_color:
+ image_text.embed_color()
+ return image_text.get_length()
def textbbox(
self,
@@ -898,33 +753,16 @@ class ImageDraw:
font_size: float | None = None,
) -> tuple[float, float, float, float]:
"""Get the bounding box of a given string, in pixels."""
- if embedded_color and self.mode not in ("RGB", "RGBA"):
- msg = "Embedded color supported only in RGB and RGBA modes"
- raise ValueError(msg)
-
if font is None:
font = self._getfont(font_size)
-
- if self._multiline_check(text):
- return self.multiline_textbbox(
- xy,
- text,
- font,
- anchor,
- spacing,
- align,
- direction,
- features,
- language,
- stroke_width,
- embedded_color,
- )
-
- mode = "RGBA" if embedded_color else self.fontmode
- bbox = font.getbbox(
- text, mode, direction, features, language, stroke_width, anchor
+ image_text = ImageText.Text(
+ text, font, self.mode, spacing, direction, features, language
)
- return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1]
+ if embedded_color:
+ image_text.embed_color()
+ if stroke_width:
+ image_text.stroke(stroke_width)
+ return image_text.get_bbox(xy, anchor, align)
def multiline_textbbox(
self,
@@ -947,7 +785,7 @@ class ImageDraw:
*,
font_size: float | None = None,
) -> tuple[float, float, float, float]:
- font, lines = self._prepare_multiline_text(
+ return self.textbbox(
xy,
text,
font,
@@ -959,37 +797,9 @@ class ImageDraw:
language,
stroke_width,
embedded_color,
- font_size,
+ font_size=font_size,
)
- bbox: tuple[float, float, float, float] | None = None
-
- for xy, anchor, line in lines:
- bbox_line = self.textbbox(
- xy,
- line,
- font,
- anchor,
- direction=direction,
- features=features,
- language=language,
- stroke_width=stroke_width,
- embedded_color=embedded_color,
- )
- if bbox is None:
- bbox = bbox_line
- else:
- bbox = (
- min(bbox[0], bbox_line[0]),
- min(bbox[1], bbox_line[1]),
- max(bbox[2], bbox_line[2]),
- max(bbox[3], bbox_line[3]),
- )
-
- if bbox is None:
- return xy[0], xy[1], xy[0], xy[1]
- return bbox
-
def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
"""
diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py
index 27b27127e..a1d98bd51 100644
--- a/src/PIL/ImageFile.py
+++ b/src/PIL/ImageFile.py
@@ -46,6 +46,18 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
MAXBLOCK = 65536
+"""
+By default, Pillow processes image data in blocks. This helps to prevent excessive use
+of resources. Codecs may disable this behaviour with ``_pulls_fd`` or ``_pushes_fd``.
+
+When reading an image, this is the number of bytes to read at once.
+
+When writing an image, this is the number of bytes to write at once.
+If the image width times 4 is greater, then that will be used instead.
+Plugins may also set a greater number.
+
+User code may set this to another number.
+"""
SAFEBLOCK = 1024 * 1024
@@ -301,6 +313,9 @@ class ImageFile(Image.Image):
and args[0] == self.mode
and args[0] in Image._MAPMODES
):
+ if offset < 0:
+ msg = "Tile offset cannot be negative"
+ raise ValueError(msg)
try:
# use mmap, if possible
import mmap
diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py
index b9ed54ab2..9326eeeda 100644
--- a/src/PIL/ImageFilter.py
+++ b/src/PIL/ImageFilter.py
@@ -19,11 +19,14 @@ from __future__ import annotations
import abc
import functools
from collections.abc import Sequence
-from types import ModuleType
-from typing import Any, Callable, cast
+from typing import cast
TYPE_CHECKING = False
if TYPE_CHECKING:
+ from collections.abc import Callable
+ from types import ModuleType
+ from typing import Any
+
from . import _imaging
from ._typing import NumpyArray
diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py
index bf3f471f5..2e8ace98d 100644
--- a/src/PIL/ImageFont.py
+++ b/src/PIL/ImageFont.py
@@ -125,11 +125,20 @@ class ImageFont:
image.close()
def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None:
+ # check image
+ if image.mode not in ("1", "L"):
+ image.close()
+
+ msg = "invalid font image mode"
+ raise TypeError(msg)
+
# read PILfont header
- if file.readline() != b"PILfont\n":
+ if file.read(8) != b"PILfont\n":
+ image.close()
+
msg = "Not a PILfont file"
raise SyntaxError(msg)
- file.readline().split(b";")
+ file.readline()
self.info = [] # FIXME: should be a dictionary
while True:
s = file.readline()
@@ -140,11 +149,6 @@ class ImageFont:
# read PILfont metrics
data = file.read(256 * 20)
- # check image
- if image.mode not in ("1", "L"):
- msg = "invalid font image mode"
- raise TypeError(msg)
-
image.load()
self.font = Image.core.font(image.im, data)
@@ -671,11 +675,7 @@ class FreeTypeFont:
:returns: A list of the named styles in a variation font.
:exception OSError: If the font is not a variation font.
"""
- try:
- names = self.font.getvarnames()
- except AttributeError as e:
- msg = "FreeType 2.9.1 or greater is required"
- raise NotImplementedError(msg) from e
+ names = self.font.getvarnames()
return [name.replace(b"\x00", b"") for name in names]
def set_variation_by_name(self, name: str | bytes) -> None:
@@ -702,11 +702,7 @@ class FreeTypeFont:
:returns: A list of the axes in a variation font.
:exception OSError: If the font is not a variation font.
"""
- try:
- axes = self.font.getvaraxes()
- except AttributeError as e:
- msg = "FreeType 2.9.1 or greater is required"
- raise NotImplementedError(msg) from e
+ axes = self.font.getvaraxes()
for axis in axes:
if axis["name"]:
axis["name"] = axis["name"].replace(b"\x00", b"")
@@ -717,11 +713,7 @@ class FreeTypeFont:
:param axes: A list of values for each axis.
:exception OSError: If the font is not a variation font.
"""
- try:
- self.font.setvaraxes(axes)
- except AttributeError as e:
- msg = "FreeType 2.9.1 or greater is required"
- raise NotImplementedError(msg) from e
+ self.font.setvaraxes(axes)
class TransposedFont:
diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py
index 1eb450734..4228078b1 100644
--- a/src/PIL/ImageGrab.py
+++ b/src/PIL/ImageGrab.py
@@ -43,7 +43,9 @@ def grab(
fh, filepath = tempfile.mkstemp(".png")
os.close(fh)
args = ["screencapture"]
- if bbox:
+ if window:
+ args += ["-l", str(window)]
+ elif bbox:
left, top, right, bottom = bbox
args += ["-R", f"{left},{top},{right-left},{bottom-top}"]
subprocess.call(args + ["-x", filepath])
@@ -51,9 +53,35 @@ def grab(
im.load()
os.unlink(filepath)
if bbox:
- im_resized = im.resize((right - left, bottom - top))
- im.close()
- return im_resized
+ if window:
+ # Determine if the window was in Retina mode or not
+ # by capturing it without the shadow,
+ # and checking how different the width is
+ fh, filepath = tempfile.mkstemp(".png")
+ os.close(fh)
+ subprocess.call(
+ ["screencapture", "-l", str(window), "-o", "-x", filepath]
+ )
+ with Image.open(filepath) as im_no_shadow:
+ retina = im.width - im_no_shadow.width > 100
+ os.unlink(filepath)
+
+ # Since screencapture's -R does not work with -l,
+ # crop the image manually
+ if retina:
+ left, top, right, bottom = bbox
+ im_cropped = im.resize(
+ (right - left, bottom - top),
+ box=tuple(coord * 2 for coord in bbox),
+ )
+ else:
+ im_cropped = im.crop(bbox)
+ im.close()
+ return im_cropped
+ else:
+ im_resized = im.resize((right - left, bottom - top))
+ im.close()
+ return im_resized
return im
elif sys.platform == "win32":
if window is not None:
diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py
index d2504b1ae..dfdc50c05 100644
--- a/src/PIL/ImageMath.py
+++ b/src/PIL/ImageMath.py
@@ -17,11 +17,15 @@
from __future__ import annotations
import builtins
-from types import CodeType
-from typing import Any, Callable
from . import Image, _imagingmath
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable
+ from types import CodeType
+ from typing import Any
+
class _Operand:
"""Wraps an image operand, providing standard operators"""
diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py
index f0a066b5b..bd70aff7b 100644
--- a/src/PIL/ImageMorph.py
+++ b/src/PIL/ImageMorph.py
@@ -150,7 +150,7 @@ class LutBuilder:
# Parse and create symmetries of the patterns strings
for p in self.patterns:
- m = re.search(r"(\w*):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", ""))
+ m = re.search(r"(\w):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", ""))
if not m:
msg = 'Syntax error in pattern "' + p + '"'
raise Exception(msg)
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
index da28854b5..42b10bd7b 100644
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -499,14 +499,15 @@ def expand(
height = top + image.size[1] + bottom
color = _color(fill, image.mode)
if image.palette:
- palette = ImagePalette.ImagePalette(palette=image.getpalette())
+ mode = image.palette.mode
+ palette = ImagePalette.ImagePalette(mode, image.getpalette(mode))
if isinstance(color, tuple) and (len(color) == 3 or len(color) == 4):
color = palette.getcolor(color)
else:
palette = None
out = Image.new(image.mode, (width, height), color)
if palette:
- out.putpalette(palette.palette)
+ out.putpalette(palette.palette, mode)
out.paste(image, (left, top))
return out
diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py
index 103697117..eae7aea8f 100644
--- a/src/PIL/ImagePalette.py
+++ b/src/PIL/ImagePalette.py
@@ -118,7 +118,7 @@ class ImagePalette:
) -> int:
if not isinstance(self.palette, bytearray):
self._palette = bytearray(self.palette)
- index = len(self.palette) // 3
+ index = len(self.palette) // len(self.mode)
special_colors: tuple[int | tuple[int, ...] | None, ...] = ()
if image:
special_colors = (
@@ -168,11 +168,12 @@ class ImagePalette:
index = self._new_color_index(image, e)
assert isinstance(self._palette, bytearray)
self.colors[color] = index
- if index * 3 < len(self.palette):
+ mode_len = len(self.mode)
+ if index * mode_len < len(self.palette):
self._palette = (
- self._palette[: index * 3]
+ self._palette[: index * mode_len]
+ bytes(color)
- + self._palette[index * 3 + 3 :]
+ + self._palette[index * mode_len + mode_len :]
)
else:
self._palette += bytes(color)
diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py
index df7a57b65..af4d0742d 100644
--- a/src/PIL/ImageQt.py
+++ b/src/PIL/ImageQt.py
@@ -19,23 +19,18 @@ from __future__ import annotations
import sys
from io import BytesIO
-from typing import Any, Callable, Union
from . import Image
from ._util import is_path
TYPE_CHECKING = False
if TYPE_CHECKING:
- import PyQt6
- import PySide6
+ from collections.abc import Callable
+ from typing import Any
from . import ImageFile
QBuffer: type
- QByteArray = Union[PyQt6.QtCore.QByteArray, PySide6.QtCore.QByteArray]
- QIODevice = Union[PyQt6.QtCore.QIODevice, PySide6.QtCore.QIODevice]
- QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage]
- QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap]
qt_version: str | None
qt_versions = [
@@ -49,11 +44,15 @@ for version, qt_module in qt_versions:
try:
qRgba: Callable[[int, int, int, int], int]
if qt_module == "PyQt6":
- from PyQt6.QtCore import QBuffer, QIODevice
+ from PyQt6.QtCore import QBuffer, QByteArray, QIODevice
from PyQt6.QtGui import QImage, QPixmap, qRgba
elif qt_module == "PySide6":
- from PySide6.QtCore import QBuffer, QIODevice
- from PySide6.QtGui import QImage, QPixmap, qRgba
+ from PySide6.QtCore import ( # type: ignore[assignment]
+ QBuffer,
+ QByteArray,
+ QIODevice,
+ )
+ from PySide6.QtGui import QImage, QPixmap, qRgba # type: ignore[assignment]
except (ImportError, RuntimeError):
continue
qt_is_installed = True
@@ -183,7 +182,7 @@ def _toqclass_helper(im: Image.Image | str | QByteArray) -> dict[str, Any]:
if qt_is_installed:
- class ImageQt(QImage): # type: ignore[misc]
+ class ImageQt(QImage):
def __init__(self, im: Image.Image | str | QByteArray) -> None:
"""
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py
index a6fc340d5..361be4897 100644
--- a/src/PIL/ImageSequence.py
+++ b/src/PIL/ImageSequence.py
@@ -16,10 +16,12 @@
##
from __future__ import annotations
-from typing import Callable
-
from . import Image
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable
+
class Iterator:
"""
diff --git a/src/PIL/ImageStat.py b/src/PIL/ImageStat.py
index 8bc504526..3a1044ba4 100644
--- a/src/PIL/ImageStat.py
+++ b/src/PIL/ImageStat.py
@@ -120,7 +120,7 @@ class Stat:
@cached_property
def mean(self) -> list[float]:
"""Average (arithmetic mean) pixel level for each band in the image."""
- return [self.sum[i] / self.count[i] for i in self.bands]
+ return [self.sum[i] / self.count[i] if self.count[i] else 0 for i in self.bands]
@cached_property
def median(self) -> list[int]:
@@ -141,13 +141,20 @@ class Stat:
@cached_property
def rms(self) -> list[float]:
"""RMS (root-mean-square) for each band in the image."""
- return [math.sqrt(self.sum2[i] / self.count[i]) for i in self.bands]
+ return [
+ math.sqrt(self.sum2[i] / self.count[i]) if self.count[i] else 0
+ for i in self.bands
+ ]
@cached_property
def var(self) -> list[float]:
"""Variance for each band in the image."""
return [
- (self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i]
+ (
+ (self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i]
+ if self.count[i]
+ else 0
+ )
for i in self.bands
]
diff --git a/src/PIL/ImageText.py b/src/PIL/ImageText.py
new file mode 100644
index 000000000..e6ccd8243
--- /dev/null
+++ b/src/PIL/ImageText.py
@@ -0,0 +1,320 @@
+from __future__ import annotations
+
+from . import ImageFont
+from ._typing import _Ink
+
+
+class Text:
+ def __init__(
+ self,
+ text: str | bytes,
+ font: (
+ ImageFont.ImageFont
+ | ImageFont.FreeTypeFont
+ | ImageFont.TransposedFont
+ | None
+ ) = None,
+ mode: str = "RGB",
+ spacing: float = 4,
+ direction: str | None = None,
+ features: list[str] | None = None,
+ language: str | None = None,
+ ) -> None:
+ """
+ :param text: String to be drawn.
+ :param font: Either an :py:class:`~PIL.ImageFont.ImageFont` instance,
+ :py:class:`~PIL.ImageFont.FreeTypeFont` instance,
+ :py:class:`~PIL.ImageFont.TransposedFont` instance or ``None``. If
+ ``None``, the default font from :py:meth:`.ImageFont.load_default`
+ will be used.
+ :param mode: The image mode this will be used with.
+ :param spacing: The number of pixels between lines.
+ :param direction: Direction of the text. It can be ``"rtl"`` (right to left),
+ ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
+ Requires libraqm.
+ :param features: A list of OpenType font features to be used during text
+ layout. This is usually used to turn on optional font features
+ that are not enabled by default, for example ``"dlig"`` or
+ ``"ss01"``, but can be also used to turn off default font
+ features, for example ``"-liga"`` to disable ligatures or
+ ``"-kern"`` to disable kerning. To get all supported
+ features, see `OpenType docs`_.
+ Requires libraqm.
+ :param language: Language of the text. Different languages may use
+ different glyph shapes or ligatures. This parameter tells
+ the font which language the text is in, and to apply the
+ correct substitutions as appropriate, if available.
+ It should be a `BCP 47 language code`_.
+ Requires libraqm.
+ """
+ self.text = text
+ self.font = font or ImageFont.load_default()
+
+ self.mode = mode
+ self.spacing = spacing
+ self.direction = direction
+ self.features = features
+ self.language = language
+
+ self.embedded_color = False
+
+ self.stroke_width: float = 0
+ self.stroke_fill: _Ink | None = None
+
+ def embed_color(self) -> None:
+ """
+ Use embedded color glyphs (COLR, CBDT, SBIX).
+ """
+ if self.mode not in ("RGB", "RGBA"):
+ msg = "Embedded color supported only in RGB and RGBA modes"
+ raise ValueError(msg)
+ self.embedded_color = True
+
+ def stroke(self, width: float = 0, fill: _Ink | None = None) -> None:
+ """
+ :param width: The width of the text stroke.
+ :param fill: Color to use for the text stroke when drawing. If not given, will
+ default to the ``fill`` parameter from
+ :py:meth:`.ImageDraw.ImageDraw.text`.
+ """
+ self.stroke_width = width
+ self.stroke_fill = fill
+
+ def _get_fontmode(self) -> str:
+ if self.mode in ("1", "P", "I", "F"):
+ return "1"
+ elif self.embedded_color:
+ return "RGBA"
+ else:
+ return "L"
+
+ def get_length(self) -> float:
+ """
+ Returns length (in pixels with 1/64 precision) of text.
+
+ This is the amount by which following text should be offset.
+ Text bounding box may extend past the length in some fonts,
+ e.g. when using italics or accents.
+
+ The result is returned as a float; it is a whole number if using basic layout.
+
+ Note that the sum of two lengths may not equal the length of a concatenated
+ string due to kerning. If you need to adjust for kerning, include the following
+ character and subtract its length.
+
+ For example, instead of::
+
+ hello = ImageText.Text("Hello", font).get_length()
+ world = ImageText.Text("World", font).get_length()
+ helloworld = ImageText.Text("HelloWorld", font).get_length()
+ assert hello + world == helloworld
+
+ use::
+
+ hello = (
+ ImageText.Text("HelloW", font).get_length() -
+ ImageText.Text("W", font).get_length()
+ ) # adjusted for kerning
+ world = ImageText.Text("World", font).get_length()
+ helloworld = ImageText.Text("HelloWorld", font).get_length()
+ assert hello + world == helloworld
+
+ or disable kerning with (requires libraqm)::
+
+ hello = ImageText.Text("Hello", font, features=["-kern"]).get_length()
+ world = ImageText.Text("World", font, features=["-kern"]).get_length()
+ helloworld = ImageText.Text(
+ "HelloWorld", font, features=["-kern"]
+ ).get_length()
+ assert hello + world == helloworld
+
+ :return: Either width for horizontal text, or height for vertical text.
+ """
+ if isinstance(self.text, str):
+ multiline = "\n" in self.text
+ else:
+ multiline = b"\n" in self.text
+ if multiline:
+ msg = "can't measure length of multiline text"
+ raise ValueError(msg)
+ return self.font.getlength(
+ self.text,
+ self._get_fontmode(),
+ self.direction,
+ self.features,
+ self.language,
+ )
+
+ def _split(
+ self, xy: tuple[float, float], anchor: str | None, align: str
+ ) -> list[tuple[tuple[float, float], str, str | bytes]]:
+ if anchor is None:
+ anchor = "lt" if self.direction == "ttb" else "la"
+ elif len(anchor) != 2:
+ msg = "anchor must be a 2 character string"
+ raise ValueError(msg)
+
+ lines = (
+ self.text.split("\n")
+ if isinstance(self.text, str)
+ else self.text.split(b"\n")
+ )
+ if len(lines) == 1:
+ return [(xy, anchor, self.text)]
+
+ if anchor[1] in "tb" and self.direction != "ttb":
+ msg = "anchor not supported for multiline text"
+ raise ValueError(msg)
+
+ fontmode = self._get_fontmode()
+ line_spacing = (
+ self.font.getbbox(
+ "A",
+ fontmode,
+ None,
+ self.features,
+ self.language,
+ self.stroke_width,
+ )[3]
+ + self.stroke_width
+ + self.spacing
+ )
+
+ top = xy[1]
+ parts = []
+ if self.direction == "ttb":
+ left = xy[0]
+ for line in lines:
+ parts.append(((left, top), anchor, line))
+ left += line_spacing
+ else:
+ widths = []
+ max_width: float = 0
+ for line in lines:
+ line_width = self.font.getlength(
+ line, fontmode, self.direction, self.features, self.language
+ )
+ widths.append(line_width)
+ max_width = max(max_width, line_width)
+
+ if anchor[1] == "m":
+ top -= (len(lines) - 1) * line_spacing / 2.0
+ elif anchor[1] == "d":
+ top -= (len(lines) - 1) * line_spacing
+
+ idx = -1
+ for line in lines:
+ left = xy[0]
+ idx += 1
+ width_difference = max_width - widths[idx]
+
+ # align by align parameter
+ if align in ("left", "justify"):
+ pass
+ elif align == "center":
+ left += width_difference / 2.0
+ elif align == "right":
+ left += width_difference
+ else:
+ msg = 'align must be "left", "center", "right" or "justify"'
+ raise ValueError(msg)
+
+ if (
+ align == "justify"
+ and width_difference != 0
+ and idx != len(lines) - 1
+ ):
+ words = (
+ line.split(" ") if isinstance(line, str) else line.split(b" ")
+ )
+ if len(words) > 1:
+ # align left by anchor
+ if anchor[0] == "m":
+ left -= max_width / 2.0
+ elif anchor[0] == "r":
+ left -= max_width
+
+ word_widths = [
+ self.font.getlength(
+ word,
+ fontmode,
+ self.direction,
+ self.features,
+ self.language,
+ )
+ for word in words
+ ]
+ word_anchor = "l" + anchor[1]
+ width_difference = max_width - sum(word_widths)
+ i = 0
+ for word in words:
+ parts.append(((left, top), word_anchor, word))
+ left += word_widths[i] + width_difference / (len(words) - 1)
+ i += 1
+ top += line_spacing
+ continue
+
+ # align left by anchor
+ if anchor[0] == "m":
+ left -= width_difference / 2.0
+ elif anchor[0] == "r":
+ left -= width_difference
+ parts.append(((left, top), anchor, line))
+ top += line_spacing
+
+ return parts
+
+ def get_bbox(
+ self,
+ xy: tuple[float, float] = (0, 0),
+ anchor: str | None = None,
+ align: str = "left",
+ ) -> tuple[float, float, float, float]:
+ """
+ Returns bounding box (in pixels) of text.
+
+ Use :py:meth:`get_length` to get the offset of following text with 1/64 pixel
+ precision. The bounding box includes extra margins for some fonts, e.g. italics
+ or accents.
+
+ :param xy: The anchor coordinates of the text.
+ :param anchor: The text anchor alignment. Determines the relative location of
+ the anchor to the text. The default alignment is top left,
+ specifically ``la`` for horizontal text and ``lt`` for
+ vertical text. See :ref:`text-anchors` for details.
+ :param align: For multiline text, ``"left"``, ``"center"``, ``"right"`` or
+ ``"justify"`` determines the relative alignment of lines. Use the
+ ``anchor`` parameter to specify the alignment to ``xy``.
+
+ :return: ``(left, top, right, bottom)`` bounding box
+ """
+ bbox: tuple[float, float, float, float] | None = None
+ fontmode = self._get_fontmode()
+ for xy, anchor, line in self._split(xy, anchor, align):
+ bbox_line = self.font.getbbox(
+ line,
+ fontmode,
+ self.direction,
+ self.features,
+ self.language,
+ self.stroke_width,
+ anchor,
+ )
+ bbox_line = (
+ bbox_line[0] + xy[0],
+ bbox_line[1] + xy[1],
+ bbox_line[2] + xy[0],
+ bbox_line[3] + xy[1],
+ )
+ if bbox is None:
+ bbox = bbox_line
+ else:
+ bbox = (
+ min(bbox[0], bbox_line[0]),
+ min(bbox[1], bbox_line[1]),
+ max(bbox[2], bbox_line[2]),
+ max(bbox[3], bbox_line[3]),
+ )
+
+ assert bbox is not None
+ return bbox
diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py
index b1fbb1bf1..c28f4dcc7 100644
--- a/src/PIL/IptcImagePlugin.py
+++ b/src/PIL/IptcImagePlugin.py
@@ -34,10 +34,6 @@ def _i(c: bytes) -> int:
return i32((b"\0\0\0\0" + c)[-4:])
-def _i8(c: int | bytes) -> int:
- return c if isinstance(c, int) else c[0]
-
-
##
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
# from TIFF and JPEG files, use the getiptcinfo function.
@@ -100,16 +96,18 @@ class IptcImageFile(ImageFile.ImageFile):
# mode
layers = self.info[(3, 60)][0]
component = self.info[(3, 60)][1]
- if (3, 65) in self.info:
- id = self.info[(3, 65)][0] - 1
- else:
- id = 0
if layers == 1 and not component:
self._mode = "L"
- elif layers == 3 and component:
- self._mode = "RGB"[id]
- elif layers == 4 and component:
- self._mode = "CMYK"[id]
+ band = None
+ else:
+ if layers == 3 and component:
+ self._mode = "RGB"
+ elif layers == 4 and component:
+ self._mode = "CMYK"
+ if (3, 65) in self.info:
+ band = self.info[(3, 65)][0] - 1
+ else:
+ band = 0
# size
self._size = self.getint((3, 20)), self.getint((3, 30))
@@ -124,39 +122,44 @@ class IptcImageFile(ImageFile.ImageFile):
# tile
if tag == (8, 10):
self.tile = [
- ImageFile._Tile("iptc", (0, 0) + self.size, offset, compression)
+ ImageFile._Tile("iptc", (0, 0) + self.size, offset, (compression, band))
]
def load(self) -> Image.core.PixelAccess | None:
- if len(self.tile) != 1 or self.tile[0][0] != "iptc":
- return ImageFile.ImageFile.load(self)
+ if self.tile:
+ args = self.tile[0].args
+ assert isinstance(args, tuple)
+ compression, band = args
- offset, compression = self.tile[0][2:]
+ self.fp.seek(self.tile[0].offset)
- self.fp.seek(offset)
-
- # Copy image data to temporary file
- o = BytesIO()
- if compression == "raw":
- # To simplify access to the extracted file,
- # prepend a PPM header
- o.write(b"P5\n%d %d\n255\n" % self.size)
- while True:
- type, size = self.field()
- if type != (8, 10):
- break
- while size > 0:
- s = self.fp.read(min(size, 8192))
- if not s:
+ # Copy image data to temporary file
+ o = BytesIO()
+ if compression == "raw":
+ # To simplify access to the extracted file,
+ # prepend a PPM header
+ o.write(b"P5\n%d %d\n255\n" % self.size)
+ while True:
+ type, size = self.field()
+ if type != (8, 10):
break
- o.write(s)
- size -= len(s)
+ while size > 0:
+ s = self.fp.read(min(size, 8192))
+ if not s:
+ break
+ o.write(s)
+ size -= len(s)
- with Image.open(o) as _im:
- _im.load()
- self.im = _im.im
- self.tile = []
- return Image.Image.load(self)
+ with Image.open(o) as _im:
+ if band is not None:
+ bands = [Image.new("L", _im.size)] * Image.getmodebands(self.mode)
+ bands[band] = _im
+ _im = Image.merge(self.mode, bands)
+ else:
+ _im.load()
+ self.im = _im.im
+ self.tile = []
+ return ImageFile.ImageFile.load(self)
Image.register_open(IptcImageFile.format, IptcImageFile)
diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py
index e0f4ecae5..4c85dd4e2 100644
--- a/src/PIL/Jpeg2KImagePlugin.py
+++ b/src/PIL/Jpeg2KImagePlugin.py
@@ -18,11 +18,15 @@ from __future__ import annotations
import io
import os
import struct
-from collections.abc import Callable
-from typing import IO, cast
+from typing import cast
from . import Image, ImageFile, ImagePalette, _binary
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable
+ from typing import IO
+
class BoxReader:
"""
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
index 082f3551a..755ca648e 100644
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -42,7 +42,6 @@ import subprocess
import sys
import tempfile
import warnings
-from typing import IO, Any
from . import Image, ImageFile
from ._binary import i16be as i16
@@ -53,6 +52,8 @@ from .JpegPresets import presets
TYPE_CHECKING = False
if TYPE_CHECKING:
+ from typing import IO, Any
+
from .MpoImagePlugin import MpoImageFile
#
@@ -192,6 +193,8 @@ def SOF(self: JpegImageFile, marker: int) -> None:
n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n)
self._size = i16(s, 3), i16(s, 1)
+ if self._im is not None and self.size != self.im.size:
+ self._im = None
self.bits = s[0]
if self.bits != 8:
@@ -845,16 +848,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
)
-def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
- # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
- tempfile = im._dump()
- subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
- try:
- os.unlink(tempfile)
- except OSError:
- pass
-
-
##
# Factory for making JPEG and MPO instances
def jpeg_factory(
diff --git a/src/PIL/PcdImagePlugin.py b/src/PIL/PcdImagePlugin.py
index 3aa249988..296f3775b 100644
--- a/src/PIL/PcdImagePlugin.py
+++ b/src/PIL/PcdImagePlugin.py
@@ -32,7 +32,7 @@ class PcdImageFile(ImageFile.ImageFile):
assert self.fp is not None
self.fp.seek(2048)
- s = self.fp.read(2048)
+ s = self.fp.read(1539)
if not s.startswith(b"PCD_"):
msg = "not a PCD file"
@@ -43,17 +43,21 @@ class PcdImageFile(ImageFile.ImageFile):
if orientation == 1:
self.tile_post_rotate = 90
elif orientation == 3:
- self.tile_post_rotate = -90
+ self.tile_post_rotate = 270
self._mode = "RGB"
- self._size = 768, 512 # FIXME: not correct for rotated images!
- self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
+ self._size = (512, 768) if orientation in (1, 3) else (768, 512)
+ self.tile = [ImageFile._Tile("pcd", (0, 0, 768, 512), 96 * 2048)]
+
+ def load_prepare(self) -> None:
+ if self._im is None and self.tile_post_rotate:
+ self.im = Image.core.new(self.mode, (768, 512))
+ ImageFile.ImageFile.load_prepare(self)
def load_end(self) -> None:
if self.tile_post_rotate:
# Handle rotated PCDs
- self.im = self.im.rotate(self.tile_post_rotate)
- self._size = self.im.size
+ self.im = self.rotate(self.tile_post_rotate, expand=True).im
#
diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py
index 0d1968b14..a00e9b919 100644
--- a/src/PIL/PcfFontFile.py
+++ b/src/PIL/PcfFontFile.py
@@ -18,7 +18,6 @@
from __future__ import annotations
import io
-from typing import BinaryIO, Callable
from . import FontFile, Image
from ._binary import i8
@@ -27,6 +26,11 @@ from ._binary import i16le as l16
from ._binary import i32be as b32
from ._binary import i32le as l32
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from collections.abc import Callable
+ from typing import BinaryIO
+
# --------------------------------------------------------------------
# declarations
diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py
index 458d586c4..6b16d5385 100644
--- a/src/PIL/PcxImagePlugin.py
+++ b/src/PIL/PcxImagePlugin.py
@@ -39,7 +39,7 @@ logger = logging.getLogger(__name__)
def _accept(prefix: bytes) -> bool:
- return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]
+ return len(prefix) >= 2 and prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]
##
diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py
index 7755ecc0d..167e13e76 100644
--- a/src/PIL/PdfImagePlugin.py
+++ b/src/PIL/PdfImagePlugin.py
@@ -27,7 +27,7 @@ import os
import time
from typing import IO, Any
-from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features
+from . import Image, ImageFile, ImageSequence, PdfParser, features
#
# --------------------------------------------------------------------
@@ -221,7 +221,7 @@ def _save(
existing_pdf.start_writing()
existing_pdf.write_header()
- existing_pdf.write_comment(f"created by Pillow {__version__} PDF driver")
+ existing_pdf.write_comment("created by Pillow PDF driver")
#
# pages
diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py
index 73d8c21c0..2c9031469 100644
--- a/src/PIL/PdfParser.py
+++ b/src/PIL/PdfParser.py
@@ -8,7 +8,15 @@ import os
import re
import time
import zlib
-from typing import IO, Any, NamedTuple, Union
+from typing import Any, NamedTuple
+
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from typing import IO
+
+ _DictBase = collections.UserDict[str | bytes, Any]
+else:
+ _DictBase = collections.UserDict
# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
@@ -251,13 +259,6 @@ class PdfArray(list[Any]):
return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"
-TYPE_CHECKING = False
-if TYPE_CHECKING:
- _DictBase = collections.UserDict[Union[str, bytes], Any]
-else:
- _DictBase = collections.UserDict
-
-
class PdfDict(_DictBase):
def __setattr__(self, key: str, value: Any) -> None:
if key == "data":
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
index 2a2b63d14..a6ef9d767 100644
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -38,9 +38,8 @@ import re
import struct
import warnings
import zlib
-from collections.abc import Callable
from enum import IntEnum
-from typing import IO, Any, NamedTuple, NoReturn, cast
+from typing import IO, NamedTuple, cast
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
from ._binary import i16be as i16
@@ -53,6 +52,9 @@ from ._util import DeferredError
TYPE_CHECKING = False
if TYPE_CHECKING:
+ from collections.abc import Callable
+ from typing import Any, NoReturn
+
from . import _imaging
logger = logging.getLogger(__name__)
@@ -507,7 +509,9 @@ class PngStream(ChunkStream):
# otherwise, we have a byte string with one alpha value
# for each palette entry
self.im_info["transparency"] = s
- elif self.im_mode in ("1", "L", "I;16"):
+ elif self.im_mode == "1":
+ self.im_info["transparency"] = 255 if i16(s) else 0
+ elif self.im_mode in ("L", "I;16"):
self.im_info["transparency"] = i16(s)
elif self.im_mode == "RGB":
self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4)
@@ -1150,6 +1154,15 @@ class _fdat:
self.seq_num += 1
+def _apply_encoderinfo(im: Image.Image, encoderinfo: dict[str, Any]) -> None:
+ im.encoderconfig = (
+ encoderinfo.get("optimize", False),
+ encoderinfo.get("compress_level", -1),
+ encoderinfo.get("compress_type", -1),
+ encoderinfo.get("dictionary", b""),
+ )
+
+
class _Frame(NamedTuple):
im: Image.Image
bbox: tuple[int, int, int, int] | None
@@ -1250,10 +1263,10 @@ def _write_multiple_frames(
# default image IDAT (if it exists)
if default_image:
- if im.mode != mode:
- im = im.convert(mode)
+ default_im = im if im.mode == mode else im.convert(mode)
+ _apply_encoderinfo(default_im, im.encoderinfo)
ImageFile._save(
- im,
+ default_im,
cast(IO[bytes], _idat(fp, chunk)),
[ImageFile._Tile("zip", (0, 0) + im.size, 0, rawmode)],
)
@@ -1287,6 +1300,7 @@ def _write_multiple_frames(
)
seq_num += 1
# frame data
+ _apply_encoderinfo(im_frame, im.encoderinfo)
if frame == 0 and not default_image:
# first frame must be in IDAT chunks for backwards compatibility
ImageFile._save(
@@ -1362,14 +1376,6 @@ def _save(
bits = 4
outmode += f";{bits}"
- # encoder options
- im.encoderconfig = (
- im.encoderinfo.get("optimize", False),
- im.encoderinfo.get("compress_level", -1),
- im.encoderinfo.get("compress_type", -1),
- im.encoderinfo.get("dictionary", b""),
- )
-
# get the corresponding PNG mode
try:
rawmode, bit_depth, color_type = _OUTMODES[outmode]
@@ -1499,6 +1505,7 @@ def _save(
im, fp, chunk, mode, rawmode, default_image, append_images
)
if single_im:
+ _apply_encoderinfo(single_im, im.encoderinfo)
ImageFile._save(
single_im,
cast(IO[bytes], _idat(fp, chunk)),
diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py
index db34d107a..307bc97ff 100644
--- a/src/PIL/PpmImagePlugin.py
+++ b/src/PIL/PpmImagePlugin.py
@@ -47,7 +47,7 @@ MODES = {
def _accept(prefix: bytes) -> bool:
- return prefix.startswith(b"P") and prefix[1] in b"0123456fy"
+ return len(prefix) >= 2 and prefix.startswith(b"P") and prefix[1] in b"0123456fy"
##
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index 07ddece6b..f543f4001 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -47,22 +47,24 @@ import math
import os
import struct
import warnings
-from collections.abc import Iterator, MutableMapping
+from collections.abc import Callable, MutableMapping
from fractions import Fraction
from numbers import Number, Rational
-from typing import IO, Any, Callable, NoReturn, cast
+from typing import IO, Any, cast
from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags
from ._binary import i16be as i16
from ._binary import i32be as i32
from ._binary import o8
-from ._typing import StrOrBytesPath
from ._util import DeferredError, is_path
from .TiffTags import TYPES
TYPE_CHECKING = False
if TYPE_CHECKING:
- from ._typing import Buffer, IntegralLike
+ from collections.abc import Iterator
+ from typing import NoReturn
+
+ from ._typing import Buffer, IntegralLike, StrOrBytesPath
logger = logging.getLogger(__name__)
@@ -250,6 +252,7 @@ OPEN_INFO = {
(II, 3, (1,), 1, (8,), ()): ("P", "P"),
(MM, 3, (1,), 1, (8,), ()): ("P", "P"),
(II, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"),
+ (MM, 3, (1,), 1, (8, 8), (0,)): ("P", "PX"),
(II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
(MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
(II, 3, (1,), 2, (8,), ()): ("P", "P;R"),
@@ -1175,6 +1178,7 @@ class TiffImageFile(ImageFile.ImageFile):
"""Open the first image in a TIFF file"""
# Header
+ assert self.fp is not None
ifh = self.fp.read(8)
if ifh[2] == 43:
ifh += self.fp.read(8)
@@ -1341,6 +1345,7 @@ class TiffImageFile(ImageFile.ImageFile):
# To be nice on memory footprint, if there's a
# file descriptor, use that instead of reading
# into a string in python.
+ assert self.fp is not None
try:
fp = hasattr(self.fp, "fileno") and self.fp.fileno()
# flush the file descriptor, prevents error on pypy 2.4+
@@ -1934,9 +1939,10 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
types[tag] = TiffTags.LONG8
elif tag in ifd.tagtype:
types[tag] = ifd.tagtype[tag]
- elif not (isinstance(value, (int, float, str, bytes))):
- continue
- else:
+ elif isinstance(value, (int, float, str, bytes)) or (
+ isinstance(value, tuple)
+ and all(isinstance(v, (int, float, IFDRational)) for v in value)
+ ):
type = TiffTags.lookup(tag).type
if type:
types[tag] = type
diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py
index 86adaa458..613a3b7de 100644
--- a/src/PIL/TiffTags.py
+++ b/src/PIL/TiffTags.py
@@ -203,6 +203,11 @@ _tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]]
531: ("YCbCrPositioning", SHORT, 1),
532: ("ReferenceBlackWhite", RATIONAL, 6),
700: ("XMP", BYTE, 0),
+ # Four private SGI tags
+ 32995: ("Matteing", SHORT, 1),
+ 32996: ("DataType", SHORT, 0),
+ 32997: ("ImageDepth", LONG, 1),
+ 32998: ("TileDepth", LONG, 1),
33432: ("Copyright", ASCII, 1),
33723: ("IptcNaaInfo", UNDEFINED, 1),
34377: ("PhotoshopInfo", BYTE, 0),
@@ -553,7 +558,6 @@ LIBTIFF_CORE = {
LIBTIFF_CORE.remove(255) # We don't have support for subfiletypes
LIBTIFF_CORE.remove(322) # We don't have support for writing tiled images with libtiff
LIBTIFF_CORE.remove(323) # Tiled images
-LIBTIFF_CORE.remove(333) # Ink Names either
# Note to advanced users: There may be combinations of these
# parameters and values that when added properly, will work and
diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py
index 87e32878b..5494f62e8 100644
--- a/src/PIL/WalImageFile.py
+++ b/src/PIL/WalImageFile.py
@@ -49,8 +49,7 @@ class WalImageFile(ImageFile.ImageFile):
# strings are null-terminated
self.info["name"] = header[:32].split(b"\0", 1)[0]
- next_name = header[56 : 56 + 32].split(b"\0", 1)[0]
- if next_name:
+ if next_name := header[56 : 56 + 32].split(b"\0", 1)[0]:
self.info["next_name"] = next_name
def load(self) -> Image.core.PixelAccess | None:
diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py
index 889842431..d9c0fbc73 100644
--- a/src/PIL/WebPImagePlugin.py
+++ b/src/PIL/WebPImagePlugin.py
@@ -1,7 +1,6 @@
from __future__ import annotations
from io import BytesIO
-from typing import IO, Any
from . import Image, ImageFile
@@ -12,6 +11,9 @@ try:
except ImportError:
SUPPORTED = False
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from typing import IO, Any
_VP8_MODES_BY_IDENTIFIER = {
b"VP8 ": "RGB",
diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py
index d569cb4b8..de714d337 100644
--- a/src/PIL/WmfImagePlugin.py
+++ b/src/PIL/WmfImagePlugin.py
@@ -80,7 +80,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
format_description = "Windows Metafile"
def _open(self) -> None:
- # check placable header
+ # check placeable header
s = self.fp.read(44)
if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"):
diff --git a/src/PIL/_imaging.pyi b/src/PIL/_imaging.pyi
index 998bc52eb..81028a596 100644
--- a/src/PIL/_imaging.pyi
+++ b/src/PIL/_imaging.pyi
@@ -1,7 +1,7 @@
from typing import Any
class ImagingCore:
- def __getitem__(self, index: int) -> float: ...
+ def __getitem__(self, index: int) -> float | tuple[int, ...] | None: ...
def __getattr__(self, name: str) -> Any: ...
class ImagingFont:
diff --git a/src/PIL/_imagingcms.pyi b/src/PIL/_imagingcms.pyi
index ddcf93ab1..4fc0d60ab 100644
--- a/src/PIL/_imagingcms.pyi
+++ b/src/PIL/_imagingcms.pyi
@@ -1,14 +1,14 @@
import datetime
import sys
-from typing import Literal, SupportsFloat, TypedDict
+from typing import Literal, SupportsFloat, TypeAlias, TypedDict
from ._typing import CapsuleType
littlecms_version: str | None
-_Tuple3f = tuple[float, float, float]
-_Tuple2x3f = tuple[_Tuple3f, _Tuple3f]
-_Tuple3x3f = tuple[_Tuple3f, _Tuple3f, _Tuple3f]
+_Tuple3f: TypeAlias = tuple[float, float, float]
+_Tuple2x3f: TypeAlias = tuple[_Tuple3f, _Tuple3f]
+_Tuple3x3f: TypeAlias = tuple[_Tuple3f, _Tuple3f, _Tuple3f]
class _IccMeasurementCondition(TypedDict):
observer: int
diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi
index 1cb1429d6..2136810ba 100644
--- a/src/PIL/_imagingft.pyi
+++ b/src/PIL/_imagingft.pyi
@@ -1,4 +1,5 @@
-from typing import Any, Callable
+from collections.abc import Callable
+from typing import Any
from . import ImageFont, _imaging
diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py
index 373938e71..a941f8980 100644
--- a/src/PIL/_typing.py
+++ b/src/PIL/_typing.py
@@ -3,7 +3,7 @@ from __future__ import annotations
import os
import sys
from collections.abc import Sequence
-from typing import Any, Protocol, TypeVar, Union
+from typing import Any, Protocol, TypeVar
TYPE_CHECKING = False
if TYPE_CHECKING:
@@ -12,8 +12,8 @@ if TYPE_CHECKING:
try:
import numpy.typing as npt
- NumpyArray = npt.NDArray[Any] # requires numpy>=1.21
- except (ImportError, AttributeError):
+ NumpyArray = npt.NDArray[Any]
+ except ImportError:
pass
if sys.version_info >= (3, 13):
@@ -26,19 +26,10 @@ if sys.version_info >= (3, 12):
else:
Buffer = Any
-if sys.version_info >= (3, 10):
- from typing import TypeGuard
-else:
- try:
- from typing_extensions import TypeGuard
- except ImportError:
- class TypeGuard: # type: ignore[no-redef]
- def __class_getitem__(cls, item: Any) -> type[bool]:
- return bool
+_Ink = float | tuple[int, ...] | str
-
-Coords = Union[Sequence[float], Sequence[Sequence[float]]]
+Coords = Sequence[float] | Sequence[Sequence[float]]
_T_co = TypeVar("_T_co", covariant=True)
@@ -48,7 +39,7 @@ class SupportsRead(Protocol[_T_co]):
def read(self, length: int = ..., /) -> _T_co: ...
-StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]
+StrOrBytesPath = str | bytes | os.PathLike[str] | os.PathLike[bytes]
-__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"]
+__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead"]
diff --git a/src/PIL/_util.py b/src/PIL/_util.py
index 8ef0d36f7..b1fa6a0f3 100644
--- a/src/PIL/_util.py
+++ b/src/PIL/_util.py
@@ -1,9 +1,12 @@
from __future__ import annotations
import os
-from typing import Any, NoReturn
-from ._typing import StrOrBytesPath, TypeGuard
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ from typing import Any, NoReturn, TypeGuard
+
+ from ._typing import StrOrBytesPath
def is_path(f: Any) -> TypeGuard[StrOrBytesPath]:
diff --git a/src/PIL/_version.py b/src/PIL/_version.py
index 6a3c01f26..41cb17a36 100644
--- a/src/PIL/_version.py
+++ b/src/PIL/_version.py
@@ -1,4 +1,4 @@
# Master version for Pillow
from __future__ import annotations
-__version__ = "12.0.0.dev0"
+__version__ = "12.1.0.dev0"
diff --git a/src/PIL/features.py b/src/PIL/features.py
index 984f7532c..ff32c2510 100644
--- a/src/PIL/features.py
+++ b/src/PIL/features.py
@@ -9,7 +9,6 @@ from typing import IO
import PIL
from . import Image
-from ._deprecate import deprecate
modules = {
"pil": ("PIL._imaging", "PILLOW_VERSION"),
@@ -120,7 +119,7 @@ def get_supported_codecs() -> list[str]:
return [f for f in codecs if check_codec(f)]
-features: dict[str, tuple[str, str | bool, str | None]] = {
+features: dict[str, tuple[str, str, str | None]] = {
"raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
@@ -146,12 +145,8 @@ def check_feature(feature: str) -> bool | None:
module, flag, ver = features[feature]
- if isinstance(flag, bool):
- deprecate(f'check_feature("{feature}")', 12)
try:
imported_module = __import__(module, fromlist=["PIL"])
- if isinstance(flag, bool):
- return flag
return getattr(imported_module, flag)
except ModuleNotFoundError:
return None
@@ -181,17 +176,7 @@ def get_supported_features() -> list[str]:
"""
:returns: A list of all supported features.
"""
- supported_features = []
- for f, (module, flag, _) in features.items():
- if flag is True:
- for feature, (feature_module, _) in modules.items():
- if feature_module == module:
- if check_module(feature):
- supported_features.append(f)
- break
- elif check_feature(f):
- supported_features.append(f)
- return supported_features
+ return [f for f in features if check_feature(f)]
def check(feature: str) -> bool | None:
diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c
index a36c3e0bd..834634bd7 100644
--- a/src/Tk/tkImaging.c
+++ b/src/Tk/tkImaging.c
@@ -121,15 +121,16 @@ PyImagingPhotoPut(
/* Mode */
- if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
+ if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) {
block.pixelSize = 1;
block.offset[0] = block.offset[1] = block.offset[2] = block.offset[3] = 0;
- } else if (strncmp(im->mode, "RGB", 3) == 0) {
+ } else if (im->mode == IMAGING_MODE_RGB || im->mode == IMAGING_MODE_RGBA ||
+ im->mode == IMAGING_MODE_RGBX || im->mode == IMAGING_MODE_RGBa) {
block.pixelSize = 4;
block.offset[0] = 0;
block.offset[1] = 1;
block.offset[2] = 2;
- if (strcmp(im->mode, "RGBA") == 0) {
+ if (im->mode == IMAGING_MODE_RGBA) {
block.offset[3] = 3; /* alpha (or reserved, under Tk 8.2) */
} else {
block.offset[3] = 0; /* no alpha */
diff --git a/src/_imaging.c b/src/_imaging.c
index fbfc0e41a..f6be4a901 100644
--- a/src/_imaging.c
+++ b/src/_imaging.c
@@ -297,6 +297,7 @@ ExportArrowArrayPyCapsule(ImagingObject *self) {
static PyObject *
_new_arrow(PyObject *self, PyObject *args) {
char *mode;
+ ModeID mode_id;
int xsize, ysize;
PyObject *schema_capsule, *array_capsule;
PyObject *ret;
@@ -307,9 +308,11 @@ _new_arrow(PyObject *self, PyObject *args) {
return NULL;
}
+ mode_id = findModeID(mode);
+
// ImagingBorrowArrow is responsible for retaining the array_capsule
ret = PyImagingNew(
- ImagingNewArrow(mode, xsize, ysize, schema_capsule, array_capsule)
+ ImagingNewArrow(mode_id, xsize, ysize, schema_capsule, array_capsule)
);
if (!ret) {
return ImagingError_ValueError("Invalid Arrow array mode or size mismatch");
@@ -368,7 +371,7 @@ ImagingError_ValueError(const char *message) {
/* -------------------------------------------------------------------- */
static int
-getbands(const char *mode) {
+getbands(const ModeID mode) {
Imaging im;
int bands;
@@ -540,12 +543,7 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) {
case IMAGING_TYPE_FLOAT32:
return PyFloat_FromDouble(pixel.f);
case IMAGING_TYPE_SPECIAL:
- if (im->bands == 1) {
- return PyLong_FromLong(pixel.h);
- } else {
- return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]);
- }
- break;
+ return PyLong_FromLong(pixel.h);
}
/* unknown type */
@@ -662,26 +660,10 @@ getink(PyObject *color, Imaging im, char *ink) {
memcpy(ink, &ftmp, sizeof(ftmp));
return ink;
case IMAGING_TYPE_SPECIAL:
- if (strncmp(im->mode, "I;16", 4) == 0) {
- ink[0] = (UINT8)r;
- ink[1] = (UINT8)(r >> 8);
- ink[2] = ink[3] = 0;
- return ink;
- } else {
- if (rIsInt) {
- b = (UINT8)(r >> 16);
- g = (UINT8)(r >> 8);
- r = (UINT8)r;
- } else if (tupleSize != 3) {
- PyErr_SetString(
- PyExc_TypeError,
- "color must be int, or tuple of one or three elements"
- );
- return NULL;
- } else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
- return NULL;
- }
- }
+ ink[0] = (UINT8)r;
+ ink[1] = (UINT8)(r >> 8);
+ ink[2] = ink[3] = 0;
+ return ink;
}
PyErr_SetString(PyExc_ValueError, wrong_mode);
@@ -694,7 +676,7 @@ getink(PyObject *color, Imaging im, char *ink) {
static PyObject *
_fill(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int xsize, ysize;
PyObject *color;
char buffer[4];
@@ -703,10 +685,12 @@ _fill(PyObject *self, PyObject *args) {
xsize = ysize = 256;
color = NULL;
- if (!PyArg_ParseTuple(args, "s|(ii)O", &mode, &xsize, &ysize, &color)) {
+ if (!PyArg_ParseTuple(args, "s|(ii)O", &mode_name, &xsize, &ysize, &color)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
im = ImagingNewDirty(mode, xsize, ysize);
if (!im) {
return NULL;
@@ -727,47 +711,55 @@ _fill(PyObject *self, PyObject *args) {
static PyObject *
_new(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int xsize, ysize;
- if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
+ if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingNew(mode, xsize, ysize));
}
static PyObject *
_new_block(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int xsize, ysize;
- if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
+ if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingNewBlock(mode, xsize, ysize));
}
static PyObject *
_linear_gradient(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
- if (!PyArg_ParseTuple(args, "s", &mode)) {
+ if (!PyArg_ParseTuple(args, "s", &mode_name)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingFillLinearGradient(mode));
}
static PyObject *
_radial_gradient(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
- if (!PyArg_ParseTuple(args, "s", &mode)) {
+ if (!PyArg_ParseTuple(args, "s", &mode_name)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingFillRadialGradient(mode));
}
@@ -907,7 +899,7 @@ _prepare_lut_table(PyObject *table, Py_ssize_t table_size) {
static PyObject *
_color_lut_3d(ImagingObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int filter;
int table_channels;
int size1D, size2D, size3D;
@@ -919,7 +911,7 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
"sii(iii)O:color_lut_3d",
- &mode,
+ &mode_name,
&filter,
&table_channels,
&size1D,
@@ -930,6 +922,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
/* actually, it is trilinear */
if (filter != IMAGING_TRANSFORM_BILINEAR) {
PyErr_SetString(PyExc_ValueError, "Only LINEAR filter is supported.");
@@ -976,11 +970,11 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
static PyObject *
_convert(ImagingObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int dither = 0;
ImagingObject *paletteimage = NULL;
- if (!PyArg_ParseTuple(args, "s|iO", &mode, &dither, &paletteimage)) {
+ if (!PyArg_ParseTuple(args, "s|iO", &mode_name, &dither, &paletteimage)) {
return NULL;
}
if (paletteimage != NULL) {
@@ -997,6 +991,8 @@ _convert(ImagingObject *self, PyObject *args) {
}
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingConvert(
self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither
));
@@ -1021,14 +1017,14 @@ _convert2(ImagingObject *self, PyObject *args) {
static PyObject *
_convert_matrix(ImagingObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
float m[12];
- if (!PyArg_ParseTuple(args, "s(ffff)", &mode, m + 0, m + 1, m + 2, m + 3)) {
+ if (!PyArg_ParseTuple(args, "s(ffff)", &mode_name, m + 0, m + 1, m + 2, m + 3)) {
PyErr_Clear();
if (!PyArg_ParseTuple(
args,
"s(ffffffffffff)",
- &mode,
+ &mode_name,
m + 0,
m + 1,
m + 2,
@@ -1046,18 +1042,22 @@ _convert_matrix(ImagingObject *self, PyObject *args) {
}
}
+ const ModeID mode = findModeID(mode_name);
+
return PyImagingNew(ImagingConvertMatrix(self->image, mode, m));
}
static PyObject *
_convert_transparent(ImagingObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
int r, g, b;
- if (PyArg_ParseTuple(args, "s(iii)", &mode, &r, &g, &b)) {
+ if (PyArg_ParseTuple(args, "s(iii)", &mode_name, &r, &g, &b)) {
+ const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, g, b));
}
PyErr_Clear();
- if (PyArg_ParseTuple(args, "si", &mode, &r)) {
+ if (PyArg_ParseTuple(args, "si", &mode_name, &r)) {
+ const ModeID mode = findModeID(mode_name);
return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, 0, 0));
}
return NULL;
@@ -1156,9 +1156,9 @@ _getpalette(ImagingObject *self, PyObject *args) {
int bits;
ImagingShuffler pack;
- char *mode = "RGB";
- char *rawmode = "RGB";
- if (!PyArg_ParseTuple(args, "|ss", &mode, &rawmode)) {
+ char *mode_name = "RGB";
+ char *rawmode_name = "RGB";
+ if (!PyArg_ParseTuple(args, "|ss", &mode_name, &rawmode_name)) {
return NULL;
}
@@ -1167,6 +1167,9 @@ _getpalette(ImagingObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
pack = ImagingFindPacker(mode, rawmode, &bits);
if (!pack) {
PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
@@ -1193,7 +1196,7 @@ _getpalettemode(ImagingObject *self) {
return NULL;
}
- return PyUnicode_FromString(self->image->palette->mode);
+ return PyUnicode_FromString(getModeData(self->image->palette->mode)->name);
}
static inline int
@@ -1474,12 +1477,14 @@ _point(ImagingObject *self, PyObject *args) {
Imaging im;
PyObject *list;
- char *mode;
- if (!PyArg_ParseTuple(args, "Oz", &list, &mode)) {
+ char *mode_name;
+ if (!PyArg_ParseTuple(args, "Oz", &list, &mode_name)) {
return NULL;
}
- if (mode && !strcmp(mode, "F")) {
+ const ModeID mode = findModeID(mode_name);
+
+ if (mode == IMAGING_MODE_F) {
FLOAT32 *data;
/* map from 8-bit data to floating point */
@@ -1490,8 +1495,7 @@ _point(ImagingObject *self, PyObject *args) {
}
im = ImagingPoint(self->image, mode, (void *)data);
free(data);
-
- } else if (!strcmp(self->image->mode, "I") && mode && !strcmp(mode, "L")) {
+ } else if (self->image->mode == IMAGING_MODE_I && mode == IMAGING_MODE_L) {
UINT8 *data;
/* map from 16-bit subset of 32-bit data to 8-bit */
@@ -1503,7 +1507,6 @@ _point(ImagingObject *self, PyObject *args) {
}
im = ImagingPoint(self->image, mode, (void *)data);
free(data);
-
} else {
INT32 *data;
UINT8 lut[1024];
@@ -1524,7 +1527,7 @@ _point(ImagingObject *self, PyObject *args) {
return NULL;
}
- if (mode && !strcmp(mode, "I")) {
+ if (mode == IMAGING_MODE_I) {
im = ImagingPoint(self->image, mode, (void *)data);
} else if (mode && bands > 1) {
for (i = 0; i < 256; i++) {
@@ -1630,9 +1633,9 @@ _putdata(ImagingObject *self, PyObject *args) {
if (image->type == IMAGING_TYPE_SPECIAL) {
// I;16*
if (
- strcmp(image->mode, "I;16B") == 0
+ image->mode == IMAGING_MODE_I_16B
#ifdef WORDS_BIGENDIAN
- || strcmp(image->mode, "I;16N") == 0
+ || image->mode == IMAGING_MODE_I_16N
#endif
) {
bigendian = 1;
@@ -1729,7 +1732,9 @@ _quantize(ImagingObject *self, PyObject *args) {
if (!self->image->xsize || !self->image->ysize) {
/* no content; return an empty image */
- return PyImagingNew(ImagingNew("P", self->image->xsize, self->image->ysize));
+ return PyImagingNew(
+ ImagingNew(IMAGING_MODE_P, self->image->xsize, self->image->ysize)
+ );
}
return PyImagingNew(ImagingQuantize(self->image, colours, method, kmeans));
@@ -1740,21 +1745,33 @@ _putpalette(ImagingObject *self, PyObject *args) {
ImagingShuffler unpack;
int bits;
- char *palette_mode, *rawmode;
+ char *palette_mode_name, *rawmode_name;
UINT8 *palette;
Py_ssize_t palettesize;
if (!PyArg_ParseTuple(
- args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize
+ args, "ssy#", &palette_mode_name, &rawmode_name, &palette, &palettesize
)) {
return NULL;
}
- if (strcmp(self->image->mode, "L") && strcmp(self->image->mode, "LA") &&
- strcmp(self->image->mode, "P") && strcmp(self->image->mode, "PA")) {
+ if (self->image->mode != IMAGING_MODE_L && self->image->mode != IMAGING_MODE_LA &&
+ self->image->mode != IMAGING_MODE_P && self->image->mode != IMAGING_MODE_PA) {
PyErr_SetString(PyExc_ValueError, wrong_mode);
return NULL;
}
+ const ModeID palette_mode = findModeID(palette_mode_name);
+ if (palette_mode == IMAGING_MODE_UNKNOWN) {
+ PyErr_SetString(PyExc_ValueError, wrong_mode);
+ return NULL;
+ }
+
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+ if (rawmode == IMAGING_RAWMODE_UNKNOWN) {
+ PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
+ return NULL;
+ }
+
unpack = ImagingFindUnpacker(palette_mode, rawmode, &bits);
if (!unpack) {
PyErr_SetString(PyExc_ValueError, wrong_raw_mode);
@@ -1768,7 +1785,13 @@ _putpalette(ImagingObject *self, PyObject *args) {
ImagingPaletteDelete(self->image->palette);
- strcpy(self->image->mode, strlen(self->image->mode) == 2 ? "PA" : "P");
+ if (self->image->mode == IMAGING_MODE_LA) {
+ self->image->mode = IMAGING_MODE_PA;
+ } else if (self->image->mode == IMAGING_MODE_L) {
+ self->image->mode = IMAGING_MODE_P;
+ } else {
+ // The image already has a palette mode so we don't need to change it.
+ }
self->image->palette = ImagingPaletteNew(palette_mode);
@@ -1796,7 +1819,7 @@ _putpalettealpha(ImagingObject *self, PyObject *args) {
return NULL;
}
- strcpy(self->image->palette->mode, "RGBA");
+ self->image->palette->mode = IMAGING_MODE_RGBA;
self->image->palette->palette[index * 4 + 3] = (UINT8)alpha;
Py_RETURN_NONE;
@@ -1821,7 +1844,7 @@ _putpalettealphas(ImagingObject *self, PyObject *args) {
return NULL;
}
- strcpy(self->image->palette->mode, "RGBA");
+ self->image->palette->mode = IMAGING_MODE_RGBA;
for (i = 0; i < length; i++) {
self->image->palette->palette[i * 4 + 3] = (UINT8)values[i];
}
@@ -1989,8 +2012,11 @@ _reduce(ImagingObject *self, PyObject *args) {
return PyImagingNew(imOut);
}
-#define IS_RGB(mode) \
- (!strcmp(mode, "RGB") || !strcmp(mode, "RGBA") || !strcmp(mode, "RGBX"))
+static int
+isRGB(const ModeID mode) {
+ return mode == IMAGING_MODE_RGB || mode == IMAGING_MODE_RGBA ||
+ mode == IMAGING_MODE_RGBX;
+}
static PyObject *
im_setmode(ImagingObject *self, PyObject *args) {
@@ -1998,23 +2024,25 @@ im_setmode(ImagingObject *self, PyObject *args) {
Imaging im;
- char *mode;
+ char *mode_name;
Py_ssize_t modelen;
- if (!PyArg_ParseTuple(args, "s#:setmode", &mode, &modelen)) {
+ if (!PyArg_ParseTuple(args, "s#:setmode", &mode_name, &modelen)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
im = self->image;
/* move all logic in here to the libImaging primitive */
- if (!strcmp(im->mode, mode)) {
+ if (im->mode == mode) {
; /* same mode; always succeeds */
- } else if (IS_RGB(im->mode) && IS_RGB(mode)) {
+ } else if (isRGB(im->mode) && isRGB(mode)) {
/* color to color */
- strcpy(im->mode, mode);
+ im->mode = mode;
im->bands = modelen;
- if (!strcmp(mode, "RGBA")) {
+ if (mode == IMAGING_MODE_RGBA) {
(void)ImagingFillBand(im, 3, 255);
}
} else {
@@ -2294,7 +2322,7 @@ _getextrema(ImagingObject *self) {
case IMAGING_TYPE_FLOAT32:
return Py_BuildValue("dd", extrema.f[0], extrema.f[1]);
case IMAGING_TYPE_SPECIAL:
- if (strcmp(self->image->mode, "I;16") == 0) {
+ if (self->image->mode == IMAGING_MODE_I_16) {
return Py_BuildValue("HH", extrema.s[0], extrema.s[1]);
}
}
@@ -2383,7 +2411,7 @@ _putband(ImagingObject *self, PyObject *args) {
static PyObject *
_merge(PyObject *self, PyObject *args) {
- char *mode;
+ char *mode_name;
ImagingObject *band0 = NULL;
ImagingObject *band1 = NULL;
ImagingObject *band2 = NULL;
@@ -2393,7 +2421,7 @@ _merge(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
"sO!|O!O!O!",
- &mode,
+ &mode_name,
&Imaging_Type,
&band0,
&Imaging_Type,
@@ -2406,6 +2434,8 @@ _merge(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
if (band0) {
bands[0] = band0->image;
}
@@ -2419,7 +2449,12 @@ _merge(PyObject *self, PyObject *args) {
bands[3] = band3->image;
}
- return PyImagingNew(ImagingMerge(mode, bands));
+ Imaging imOut = ImagingMerge(mode, bands);
+ if (!imOut) {
+ return NULL;
+ }
+ ImagingCopyPalette(imOut, bands[0]);
+ return PyImagingNew(imOut);
}
static PyObject *
@@ -3711,7 +3746,7 @@ static struct PyMethodDef methods[] = {
static PyObject *
_getattr_mode(ImagingObject *self, void *closure) {
- return PyUnicode_FromString(self->image->mode);
+ return PyUnicode_FromString(getModeData(self->image->mode)->name);
}
static PyObject *
@@ -4255,8 +4290,6 @@ setup_module(PyObject *m) {
return -1;
}
- ImagingAccessInit();
-
#ifdef HAVE_LIBJPEG
{
extern const char *ImagingJpegVersion(void);
diff --git a/src/_imagingcms.c b/src/_imagingcms.c
index e2f29d1b7..ad3b27896 100644
--- a/src/_imagingcms.c
+++ b/src/_imagingcms.c
@@ -212,32 +212,44 @@ cms_transform_dealloc(CmsTransformObject *self) {
/* internal functions */
static cmsUInt32Number
-findLCMStype(char *PILmode) {
- if (strcmp(PILmode, "RGB") == 0 || strcmp(PILmode, "RGBA") == 0 ||
- strcmp(PILmode, "RGBX") == 0) {
- return TYPE_RGBA_8;
+findLCMStype(const char *const mode_name) {
+ const ModeID mode = findModeID(mode_name);
+ switch (mode) {
+ case IMAGING_MODE_RGB:
+ case IMAGING_MODE_RGBA:
+ case IMAGING_MODE_RGBX:
+ return TYPE_RGBA_8;
+ case IMAGING_MODE_CMYK:
+ return TYPE_CMYK_8;
+ case IMAGING_MODE_I_16:
+ case IMAGING_MODE_I_16L:
+ return TYPE_GRAY_16;
+ case IMAGING_MODE_I_16B:
+ return TYPE_GRAY_16_SE;
+ case IMAGING_MODE_YCbCr:
+ return TYPE_YCbCr_8;
+ case IMAGING_MODE_LAB:
+ // LabX equivalent like ALab, but not reversed -- no #define in lcms2
+ return (
+ COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1)
+ );
+ default:
+ // This function only accepts a subset of the imaging modes Pillow has.
+ break;
}
- if (strcmp(PILmode, "RGBA;16B") == 0) {
+ // The following modes are not valid PIL Image modes.
+ if (strcmp(mode_name, "RGBA;16B") == 0) {
return TYPE_RGBA_16;
}
- if (strcmp(PILmode, "CMYK") == 0) {
- return TYPE_CMYK_8;
- }
- if (strcmp(PILmode, "I;16") == 0 || strcmp(PILmode, "I;16L") == 0 ||
- strcmp(PILmode, "L;16") == 0) {
+ if (strcmp(mode_name, "L;16") == 0) {
return TYPE_GRAY_16;
}
- if (strcmp(PILmode, "I;16B") == 0 || strcmp(PILmode, "L;16B") == 0) {
+ if (strcmp(mode_name, "L;16B") == 0) {
return TYPE_GRAY_16_SE;
}
- if (strcmp(PILmode, "YCbCr") == 0 || strcmp(PILmode, "YCCA") == 0 ||
- strcmp(PILmode, "YCC") == 0) {
+ if (strcmp(mode_name, "YCCA") == 0 || strcmp(mode_name, "YCC") == 0) {
return TYPE_YCbCr_8;
}
- if (strcmp(PILmode, "LAB") == 0) {
- // LabX equivalent like ALab, but not reversed -- no #define in lcms2
- return (COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1));
- }
/* presume "1" or "L" by default */
return TYPE_GRAY_8;
}
diff --git a/src/_imagingft.c b/src/_imagingft.c
index 29d8e9e71..d0af25b30 100644
--- a/src/_imagingft.c
+++ b/src/_imagingft.c
@@ -525,7 +525,7 @@ font_getlength(FontObject *self, PyObject *args) {
int horizontal_dir; /* is primary axis horizontal? */
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
int color = 0; /* is FT_LOAD_COLOR enabled? */
- const char *mode = NULL;
+ const char *mode_name = NULL;
const char *dir = NULL;
const char *lang = NULL;
PyObject *features = Py_None;
@@ -534,15 +534,16 @@ font_getlength(FontObject *self, PyObject *args) {
/* calculate size and bearing for a given string */
if (!PyArg_ParseTuple(
- args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang
+ args, "O|zzOz:getlength", &string, &mode_name, &dir, &features, &lang
)) {
return NULL;
}
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
- mask = mode && strcmp(mode, "1") == 0;
- color = mode && strcmp(mode, "RGBA") == 0;
+ const ModeID mode = findModeID(mode_name);
+ mask = mode == IMAGING_MODE_1;
+ color = mode == IMAGING_MODE_RGBA;
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
if (PyErr_Occurred()) {
@@ -754,7 +755,7 @@ font_getsize(FontObject *self, PyObject *args) {
int horizontal_dir; /* is primary axis horizontal? */
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
int color = 0; /* is FT_LOAD_COLOR enabled? */
- const char *mode = NULL;
+ const char *mode_name = NULL;
const char *dir = NULL;
const char *lang = NULL;
const char *anchor = NULL;
@@ -764,15 +765,23 @@ font_getsize(FontObject *self, PyObject *args) {
/* calculate size and bearing for a given string */
if (!PyArg_ParseTuple(
- args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor
+ args,
+ "O|zzOzz:getsize",
+ &string,
+ &mode_name,
+ &dir,
+ &features,
+ &lang,
+ &anchor
)) {
return NULL;
}
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
- mask = mode && strcmp(mode, "1") == 0;
- color = mode && strcmp(mode, "RGBA") == 0;
+ const ModeID mode = findModeID(mode_name);
+ mask = mode == IMAGING_MODE_1;
+ color = mode == IMAGING_MODE_RGBA;
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
if (PyErr_Occurred()) {
@@ -839,7 +848,7 @@ font_render(FontObject *self, PyObject *args) {
int stroke_filled = 0;
PY_LONG_LONG foreground_ink_long = 0;
unsigned int foreground_ink;
- const char *mode = NULL;
+ const char *mode_name = NULL;
const char *dir = NULL;
const char *lang = NULL;
const char *anchor = NULL;
@@ -859,7 +868,7 @@ font_render(FontObject *self, PyObject *args) {
"OO|zzOzfpzL(ff):render",
&string,
&fill,
- &mode,
+ &mode_name,
&dir,
&features,
&lang,
@@ -873,8 +882,9 @@ font_render(FontObject *self, PyObject *args) {
return NULL;
}
- mask = mode && strcmp(mode, "1") == 0;
- color = mode && strcmp(mode, "RGBA") == 0;
+ const ModeID mode = findModeID(mode_name);
+ mask = mode == IMAGING_MODE_1;
+ color = mode == IMAGING_MODE_RGBA;
foreground_ink = foreground_ink_long;
@@ -1221,8 +1231,6 @@ glyph_error:
return NULL;
}
-#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
- (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
static PyObject *
font_getvarnames(FontObject *self) {
int error;
@@ -1432,7 +1440,6 @@ font_setvaraxes(FontObject *self, PyObject *args) {
Py_RETURN_NONE;
}
-#endif
static void
font_dealloc(FontObject *self) {
@@ -1451,13 +1458,10 @@ static PyMethodDef font_methods[] = {
{"render", (PyCFunction)font_render, METH_VARARGS},
{"getsize", (PyCFunction)font_getsize, METH_VARARGS},
{"getlength", (PyCFunction)font_getlength, METH_VARARGS},
-#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
- (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
{"getvarnames", (PyCFunction)font_getvarnames, METH_NOARGS},
{"getvaraxes", (PyCFunction)font_getvaraxes, METH_NOARGS},
{"setvarname", (PyCFunction)font_setvarname, METH_VARARGS},
{"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS},
-#endif
{NULL, NULL}
};
diff --git a/src/_webp.c b/src/_webp.c
index e84e786ed..d065e329c 100644
--- a/src/_webp.c
+++ b/src/_webp.c
@@ -89,8 +89,8 @@ HandleMuxError(WebPMuxError err, char *chunk) {
static int
import_frame_libwebp(WebPPicture *frame, Imaging im) {
- if (strcmp(im->mode, "RGBA") && strcmp(im->mode, "RGB") &&
- strcmp(im->mode, "RGBX")) {
+ if (im->mode != IMAGING_MODE_RGBA && im->mode != IMAGING_MODE_RGB &&
+ im->mode != IMAGING_MODE_RGBX) {
PyErr_SetString(PyExc_ValueError, "unsupported image mode");
return -1;
}
@@ -104,7 +104,7 @@ import_frame_libwebp(WebPPicture *frame, Imaging im) {
return -2;
}
- int ignore_fourth_channel = strcmp(im->mode, "RGBA");
+ int ignore_fourth_channel = im->mode != IMAGING_MODE_RGBA;
for (int y = 0; y < im->ysize; ++y) {
UINT8 *src = (UINT8 *)im->image32[y];
UINT32 *dst = frame->argb + frame->argb_stride * y;
@@ -143,7 +143,7 @@ typedef struct {
PyObject_HEAD WebPAnimDecoder *dec;
WebPAnimInfo info;
WebPData data;
- char *mode;
+ ModeID mode;
} WebPAnimDecoderObject;
static PyTypeObject WebPAnimDecoder_Type;
@@ -396,7 +396,7 @@ _anim_decoder_new(PyObject *self, PyObject *args) {
const uint8_t *webp;
Py_ssize_t size;
WebPData webp_src;
- char *mode;
+ ModeID mode;
WebPDecoderConfig config;
WebPAnimDecoderObject *decp = NULL;
WebPAnimDecoder *dec = NULL;
@@ -409,10 +409,10 @@ _anim_decoder_new(PyObject *self, PyObject *args) {
webp_src.size = size;
// Sniff the mode, since the decoder API doesn't tell us
- mode = "RGBA";
+ mode = IMAGING_MODE_RGBA;
if (WebPGetFeatures(webp, size, &config.input) == VP8_STATUS_OK) {
if (!config.input.has_alpha) {
- mode = "RGBX";
+ mode = IMAGING_MODE_RGBX;
}
}
@@ -455,7 +455,7 @@ _anim_decoder_get_info(PyObject *self) {
info->loop_count,
info->bgcolor,
info->frame_count,
- decp->mode
+ getModeData(decp->mode)->name
);
}
diff --git a/src/decode.c b/src/decode.c
index 03db1ce35..051623ed4 100644
--- a/src/decode.c
+++ b/src/decode.c
@@ -266,7 +266,9 @@ static PyTypeObject ImagingDecoderType = {
/* -------------------------------------------------------------------- */
int
-get_unpacker(ImagingDecoderObject *decoder, const char *mode, const char *rawmode) {
+get_unpacker(
+ ImagingDecoderObject *decoder, const ModeID mode, const RawModeID rawmode
+) {
int bits;
ImagingShuffler unpack;
@@ -291,17 +293,20 @@ PyObject *
PyImaging_BitDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
+ const char *mode_name;
int bits = 8;
int pad = 8;
int fill = 0;
int sign = 0;
int ystep = 1;
- if (!PyArg_ParseTuple(args, "s|iiiii", &mode, &bits, &pad, &fill, &sign, &ystep)) {
+ if (!PyArg_ParseTuple(
+ args, "s|iiiii", &mode_name, &bits, &pad, &fill, &sign, &ystep
+ )) {
return NULL;
}
- if (strcmp(mode, "F") != 0) {
+ const ModeID mode = findModeID(mode_name);
+ if (mode != IMAGING_MODE_F) {
PyErr_SetString(PyExc_ValueError, "bad image mode");
return NULL;
}
@@ -331,34 +336,36 @@ PyObject *
PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *actual;
+ char *mode_name;
int n = 0;
char *pixel_format = "";
- if (!PyArg_ParseTuple(args, "si|s", &mode, &n, &pixel_format)) {
+ if (!PyArg_ParseTuple(args, "si|s", &mode_name, &n, &pixel_format)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ ModeID actual;
+
switch (n) {
case 1: /* BC1: 565 color, 1-bit alpha */
case 2: /* BC2: 565 color, 4-bit alpha */
case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated alpha */
case 7: /* BC7: 4-channel 8-bit via everything */
- actual = "RGBA";
+ actual = IMAGING_MODE_RGBA;
break;
case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */
- actual = "L";
+ actual = IMAGING_MODE_L;
break;
case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */
case 6: /* BC6: 3-channel 16-bit float */
- actual = "RGB";
+ actual = IMAGING_MODE_RGB;
break;
default:
PyErr_SetString(PyExc_ValueError, "block compression type unknown");
return NULL;
}
- if (strcmp(mode, actual) != 0) {
+ if (mode != actual) {
PyErr_SetString(PyExc_ValueError, "bad image mode");
return NULL;
}
@@ -401,15 +408,18 @@ PyObject *
PyImaging_GifDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
+ const char *mode_name;
int bits = 8;
int interlace = 0;
int transparency = -1;
- if (!PyArg_ParseTuple(args, "s|iii", &mode, &bits, &interlace, &transparency)) {
+ if (!PyArg_ParseTuple(
+ args, "s|iii", &mode_name, &bits, &interlace, &transparency
+ )) {
return NULL;
}
- if (strcmp(mode, "L") != 0 && strcmp(mode, "P") != 0) {
+ const ModeID mode = findModeID(mode_name);
+ if (mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
PyErr_SetString(PyExc_ValueError, "bad image mode");
return NULL;
}
@@ -436,12 +446,14 @@ PyObject *
PyImaging_HexDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
- if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
+ char *mode_name, *rawmode_name;
+ if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) {
return NULL;
@@ -469,16 +481,21 @@ PyImaging_HexDecoderNew(PyObject *self, PyObject *args) {
PyObject *
PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
char *compname;
int fp;
uint32_t ifdoffset;
- if (!PyArg_ParseTuple(args, "sssiI", &mode, &rawmode, &compname, &fp, &ifdoffset)) {
+ if (!PyArg_ParseTuple(
+ args, "sssiI", &mode_name, &rawmode_name, &compname, &fp, &ifdoffset
+ )) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
TRACE(("new tiff decoder %s\n", compname));
decoder = PyImaging_DecoderNew(sizeof(TIFFSTATE));
@@ -511,12 +528,15 @@ PyObject *
PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
- if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
+ char *mode_name;
+ char *rawmode_name;
+ if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) {
return NULL;
@@ -545,7 +565,7 @@ PyImaging_PcdDecoderNew(PyObject *self, PyObject *args) {
}
/* Unpack from PhotoYCC to RGB */
- if (get_unpacker(decoder, "RGB", "YCC;P") < 0) {
+ if (get_unpacker(decoder, IMAGING_MODE_RGB, IMAGING_RAWMODE_YCC_P) < 0) {
return NULL;
}
@@ -562,13 +582,15 @@ PyObject *
PyImaging_PcxDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name, *rawmode_name;
int stride;
- if (!PyArg_ParseTuple(args, "ssi", &mode, &rawmode, &stride)) {
+ if (!PyArg_ParseTuple(args, "ssi", &mode_name, &rawmode_name, &stride)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) {
return NULL;
@@ -593,14 +615,16 @@ PyObject *
PyImaging_RawDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name, *rawmode_name;
int stride = 0;
int ystep = 1;
- if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep)) {
+ if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &stride, &ystep)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(sizeof(RAWSTATE));
if (decoder == NULL) {
return NULL;
@@ -627,14 +651,16 @@ PyObject *
PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name, *rawmode_name;
int ystep = 1;
int bpc = 1;
- if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &bpc)) {
+ if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &ystep, &bpc)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(sizeof(SGISTATE));
if (decoder == NULL) {
return NULL;
@@ -661,12 +687,14 @@ PyObject *
PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
- if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) {
+ char *mode_name, *rawmode_name;
+ if (!PyArg_ParseTuple(args, "ss", &mode_name, &rawmode_name)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) {
return NULL;
@@ -689,14 +717,16 @@ PyObject *
PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name, *rawmode_name;
int ystep = 1;
int depth = 8;
- if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &depth)) {
+ if (!PyArg_ParseTuple(args, "ss|ii", &mode_name, &rawmode_name, &ystep, &depth)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(0);
if (decoder == NULL) {
return NULL;
@@ -727,7 +757,7 @@ PyImaging_XbmDecoderNew(PyObject *self, PyObject *args) {
return NULL;
}
- if (get_unpacker(decoder, "1", "1;R") < 0) {
+ if (get_unpacker(decoder, IMAGING_MODE_1, IMAGING_RAWMODE_1_R) < 0) {
return NULL;
}
@@ -748,13 +778,15 @@ PyObject *
PyImaging_ZipDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode;
+ char *mode_name, *rawmode_name;
int interlaced = 0;
- if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &interlaced)) {
+ if (!PyArg_ParseTuple(args, "ss|i", &mode_name, &rawmode_name, &interlaced)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
decoder = PyImaging_DecoderNew(sizeof(ZIPSTATE));
if (decoder == NULL) {
return NULL;
@@ -798,19 +830,21 @@ PyObject *
PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
ImagingDecoderObject *decoder;
- char *mode;
- char *rawmode; /* what we want from the decoder */
- char *jpegmode; /* what's in the file */
+ char *mode_name;
+ char *rawmode_name; /* what we want from the decoder */
+ char *jpegmode_name; /* what's in the file */
int scale = 1;
int draft = 0;
- if (!PyArg_ParseTuple(args, "ssz|ii", &mode, &rawmode, &jpegmode, &scale, &draft)) {
+ if (!PyArg_ParseTuple(
+ args, "ssz|ii", &mode_name, &rawmode_name, &jpegmode_name, &scale, &draft
+ )) {
return NULL;
}
- if (!jpegmode) {
- jpegmode = "";
- }
+ const ModeID mode = findModeID(mode_name);
+ RawModeID rawmode = findRawModeID(rawmode_name);
+ const RawModeID jpegmode = findRawModeID(jpegmode_name);
decoder = PyImaging_DecoderNew(sizeof(JPEGSTATE));
if (decoder == NULL) {
@@ -820,8 +854,8 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
// libjpeg-turbo supports different output formats.
// We are choosing Pillow's native format (3 color bytes + 1 padding)
// to avoid extra conversion in Unpack.c.
- if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) {
- rawmode = "RGBX";
+ if (ImagingJpegUseJCSExtensions() && rawmode == IMAGING_RAWMODE_RGB) {
+ rawmode = IMAGING_RAWMODE_RGBX;
}
if (get_unpacker(decoder, mode, rawmode) < 0) {
@@ -831,11 +865,13 @@ PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) {
decoder->decode = ImagingJpegDecode;
decoder->cleanup = ImagingJpegDecodeCleanup;
- strncpy(((JPEGSTATE *)decoder->state.context)->rawmode, rawmode, 8);
- strncpy(((JPEGSTATE *)decoder->state.context)->jpegmode, jpegmode, 8);
+ JPEGSTATE *jpeg_decoder_state_context = (JPEGSTATE *)decoder->state.context;
- ((JPEGSTATE *)decoder->state.context)->scale = scale;
- ((JPEGSTATE *)decoder->state.context)->draft = draft;
+ jpeg_decoder_state_context->rawmode = rawmode;
+ jpeg_decoder_state_context->jpegmode = jpegmode;
+
+ jpeg_decoder_state_context->scale = scale;
+ jpeg_decoder_state_context->draft = draft;
return (PyObject *)decoder;
}
@@ -870,8 +906,6 @@ PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) {
if (strcmp(format, "j2k") == 0) {
codec_format = OPJ_CODEC_J2K;
- } else if (strcmp(format, "jpt") == 0) {
- codec_format = OPJ_CODEC_JPT;
} else if (strcmp(format, "jp2") == 0) {
codec_format = OPJ_CODEC_JP2;
} else {
diff --git a/src/display.c b/src/display.c
index 3215f6691..5b5853a3c 100644
--- a/src/display.c
+++ b/src/display.c
@@ -47,7 +47,7 @@ typedef struct {
static PyTypeObject ImagingDisplayType;
static ImagingDisplayObject *
-_new(const char *mode, int xsize, int ysize) {
+_new(const ModeID mode, int xsize, int ysize) {
ImagingDisplayObject *display;
if (PyType_Ready(&ImagingDisplayType) < 0) {
@@ -235,7 +235,7 @@ static struct PyMethodDef methods[] = {
static PyObject *
_getattr_mode(ImagingDisplayObject *self, void *closure) {
- return Py_BuildValue("s", self->dib->mode);
+ return Py_BuildValue("s", getModeData(self->dib->mode)->name);
}
static PyObject *
@@ -258,13 +258,14 @@ static PyTypeObject ImagingDisplayType = {
PyObject *
PyImaging_DisplayWin32(PyObject *self, PyObject *args) {
ImagingDisplayObject *display;
- char *mode;
+ char *mode_name;
int xsize, ysize;
- if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) {
+ if (!PyArg_ParseTuple(args, "s(ii)", &mode_name, &xsize, &ysize)) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
display = _new(mode, xsize, ysize);
if (display == NULL) {
return NULL;
@@ -275,12 +276,9 @@ PyImaging_DisplayWin32(PyObject *self, PyObject *args) {
PyObject *
PyImaging_DisplayModeWin32(PyObject *self, PyObject *args) {
- char *mode;
int size[2];
-
- mode = ImagingGetModeDIB(size);
-
- return Py_BuildValue("s(ii)", mode, size[0], size[1]);
+ const ModeID mode = ImagingGetModeDIB(size);
+ return Py_BuildValue("s(ii)", getModeData(mode)->name, size[0], size[1]);
}
/* -------------------------------------------------------------------- */
diff --git a/src/encode.c b/src/encode.c
index e56494036..513309c8d 100644
--- a/src/encode.c
+++ b/src/encode.c
@@ -334,14 +334,19 @@ static PyTypeObject ImagingEncoderType = {
/* -------------------------------------------------------------------- */
int
-get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode) {
+get_packer(ImagingEncoderObject *encoder, const ModeID mode, const RawModeID rawmode) {
int bits;
ImagingShuffler pack;
pack = ImagingFindPacker(mode, rawmode, &bits);
if (!pack) {
Py_DECREF(encoder);
- PyErr_Format(PyExc_ValueError, "No packer found from %s to %s", mode, rawmode);
+ PyErr_Format(
+ PyExc_ValueError,
+ "No packer found from %s to %s",
+ getModeData(mode)->name,
+ getRawModeData(rawmode)->name
+ );
return -1;
}
@@ -402,11 +407,13 @@ PyObject *
PyImaging_GifEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t bits = 8;
Py_ssize_t interlace = 0;
- if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &bits, &interlace)) {
+ if (!PyArg_ParseTuple(
+ args, "ss|nn", &mode_name, &rawmode_name, &bits, &interlace
+ )) {
return NULL;
}
@@ -415,6 +422,9 @@ PyImaging_GifEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
return NULL;
}
@@ -435,11 +445,11 @@ PyObject *
PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t bits = 8;
- if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &bits)) {
+ if (!PyArg_ParseTuple(args, "ss|n", &mode_name, &rawmode_name, &bits)) {
return NULL;
}
@@ -448,6 +458,9 @@ PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
return NULL;
}
@@ -465,12 +478,12 @@ PyObject *
PyImaging_RawEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t stride = 0;
Py_ssize_t ystep = 1;
- if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &stride, &ystep)) {
+ if (!PyArg_ParseTuple(args, "ss|nn", &mode_name, &rawmode_name, &stride, &ystep)) {
return NULL;
}
@@ -479,6 +492,9 @@ PyImaging_RawEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
return NULL;
}
@@ -499,11 +515,11 @@ PyObject *
PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t ystep = 1;
- if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &ystep)) {
+ if (!PyArg_ParseTuple(args, "ss|n", &mode_name, &rawmode_name, &ystep)) {
return NULL;
}
@@ -512,6 +528,9 @@ PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
return NULL;
}
@@ -536,7 +555,7 @@ PyImaging_XbmEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
- if (get_packer(encoder, "1", "1;R") < 0) {
+ if (get_packer(encoder, IMAGING_MODE_1, IMAGING_RAWMODE_1_R) < 0) {
return NULL;
}
@@ -557,8 +576,8 @@ PyObject *
PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t optimize = 0;
Py_ssize_t compress_level = -1;
Py_ssize_t compress_type = -1;
@@ -567,8 +586,8 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
"ss|nnny#",
- &mode,
- &rawmode,
+ &mode_name,
+ &rawmode_name,
&optimize,
&compress_level,
&compress_type,
@@ -597,6 +616,9 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
free(dictionary);
return NULL;
@@ -605,7 +627,7 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
encoder->encode = ImagingZipEncode;
encoder->cleanup = ImagingZipEncodeCleanup;
- if (rawmode[0] == 'P') {
+ if (rawmode == IMAGING_RAWMODE_P || rawmode == IMAGING_RAWMODE_PA) {
/* disable filtering */
((ZIPSTATE *)encoder->state.context)->mode = ZIP_PNG_PALETTE;
}
@@ -634,8 +656,8 @@ PyObject *
PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
char *compname;
char *filename;
Py_ssize_t fp;
@@ -646,16 +668,24 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
int key_int, status, is_core_tag, is_var_length, num_core_tags, i;
TIFFDataType type = TIFF_NOTYPE;
// This list also exists in TiffTags.py
- const int core_tags[] = {256, 257, 258, 259, 262, 263, 266, 269, 274,
- 277, 278, 280, 281, 340, 341, 282, 283, 284,
- 286, 287, 296, 297, 320, 321, 338, 32995, 32998,
- 32996, 339, 32997, 330, 531, 530, 65537, 301, 532};
+ const int core_tags[] = {256, 257, 258, 259, 262, 263, 266, 269, 274, 277,
+ 278, 280, 281, 282, 283, 284, 286, 287, 296, 297,
+ 301, 320, 321, 330, 333, 338, 339, 340, 341, 530,
+ 531, 532, 32995, 32996, 32997, 32998, 65537};
Py_ssize_t tags_size;
PyObject *item;
if (!PyArg_ParseTuple(
- args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types
+ args,
+ "sssnsOO",
+ &mode_name,
+ &rawmode_name,
+ &compname,
+ &fp,
+ &filename,
+ &tags,
+ &types
)) {
return NULL;
}
@@ -693,6 +723,9 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ const RawModeID rawmode = findRawModeID(rawmode_name);
+
if (get_packer(encoder, mode, rawmode) < 0) {
return NULL;
}
@@ -788,7 +821,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
}
}
- if (type == TIFF_BYTE || type == TIFF_UNDEFINED) {
+ if (type == TIFF_BYTE || type == TIFF_UNDEFINED ||
+ key_int == TIFFTAG_INKNAMES) {
status = ImagingLibTiffSetField(
&encoder->state,
(ttag_t)key_int,
@@ -922,13 +956,25 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
);
free(av);
}
+ } else if (type == TIFF_RATIONAL) {
+ FLOAT32 *av;
+ /* malloc check ok, calloc checks for overflow */
+ av = calloc(len, sizeof(FLOAT32));
+ if (av) {
+ for (i = 0; i < len; i++) {
+ av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i));
+ }
+ status =
+ ImagingLibTiffSetField(&encoder->state, (ttag_t)key_int, av);
+ free(av);
+ }
}
} else {
if (type == TIFF_SHORT) {
status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value)
);
- } else if (type == TIFF_LONG) {
+ } else if (type == TIFF_LONG || type == TIFF_IFD) {
status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (UINT32)PyLong_AsLong(value)
);
@@ -944,10 +990,6 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value)
);
- } else if (type == TIFF_DOUBLE) {
- status = ImagingLibTiffSetField(
- &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
- );
} else if (type == TIFF_SBYTE) {
status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value)
@@ -956,7 +998,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, PyBytes_AsString(value)
);
- } else if (type == TIFF_RATIONAL) {
+ } else if (type == TIFF_DOUBLE || type == TIFF_SRATIONAL ||
+ type == TIFF_RATIONAL) {
status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
);
@@ -1076,8 +1119,8 @@ PyObject *
PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
ImagingEncoderObject *encoder;
- char *mode;
- char *rawmode;
+ char *mode_name;
+ char *rawmode_name;
Py_ssize_t quality = 0;
Py_ssize_t progressive = 0;
Py_ssize_t smooth = 0;
@@ -1101,8 +1144,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(
args,
"ss|nnnnpn(nn)nnnOz#y#y#",
- &mode,
- &rawmode,
+ &mode_name,
+ &rawmode_name,
&quality,
&progressive,
&smooth,
@@ -1130,11 +1173,14 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+ RawModeID rawmode = findRawModeID(rawmode_name);
+
// libjpeg-turbo supports different output formats.
// We are choosing Pillow's native format (3 color bytes + 1 padding)
// to avoid extra conversion in Pack.c.
- if (ImagingJpegUseJCSExtensions() && strcmp(rawmode, "RGB") == 0) {
- rawmode = "RGBX";
+ if (ImagingJpegUseJCSExtensions() && rawmode == IMAGING_RAWMODE_RGB) {
+ rawmode = IMAGING_RAWMODE_RGBX;
}
if (get_packer(encoder, mode, rawmode) < 0) {
@@ -1192,7 +1238,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
encoder->encode = ImagingJpegEncode;
JPEGENCODERSTATE *jpeg_encoder_state = (JPEGENCODERSTATE *)encoder->state.context;
- strncpy(jpeg_encoder_state->rawmode, rawmode, 8);
+ jpeg_encoder_state->rawmode = rawmode;
jpeg_encoder_state->keep_rgb = keep_rgb;
jpeg_encoder_state->quality = quality;
jpeg_encoder_state->qtables = qarrays;
diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c
index 3db52377e..c77a9c21c 100644
--- a/src/libImaging/Access.c
+++ b/src/libImaging/Access.c
@@ -11,39 +11,6 @@
#include "Imaging.h"
-/* use make_hash.py from the pillow-scripts repository to calculate these values */
-#define ACCESS_TABLE_SIZE 35
-#define ACCESS_TABLE_HASH 8940
-
-static struct ImagingAccessInstance access_table[ACCESS_TABLE_SIZE];
-
-static inline UINT32
-hash(const char *mode) {
- UINT32 i = ACCESS_TABLE_HASH;
- while (*mode) {
- i = ((i << 5) + i) ^ (UINT8)*mode++;
- }
- return i % ACCESS_TABLE_SIZE;
-}
-
-static ImagingAccess
-add_item(const char *mode) {
- UINT32 i = hash(mode);
- /* printf("hash %s => %d\n", mode, i); */
- if (access_table[i].mode && strcmp(access_table[i].mode, mode) != 0) {
- fprintf(
- stderr,
- "AccessInit: hash collision: %d for both %s and %s\n",
- i,
- mode,
- access_table[i].mode
- );
- exit(1);
- }
- access_table[i].mode = mode;
- return &access_table[i];
-}
-
/* fetch individual pixel */
static void
@@ -64,7 +31,7 @@ static void
get_pixel_16L(Imaging im, int x, int y, void *color) {
UINT8 *in = (UINT8 *)&im->image[y][x + x];
#ifdef WORDS_BIGENDIAN
- UINT16 out = in[0] + (in[1] << 8);
+ UINT16 out = in[0] + ((UINT16)in[1] << 8);
memcpy(color, &out, sizeof(out));
#else
memcpy(color, in, sizeof(UINT16));
@@ -77,7 +44,7 @@ get_pixel_16B(Imaging im, int x, int y, void *color) {
#ifdef WORDS_BIGENDIAN
memcpy(color, in, sizeof(UINT16));
#else
- UINT16 out = in[1] + (in[0] << 8);
+ UINT16 out = in[1] + ((UINT16)in[0] << 8);
memcpy(color, &out, sizeof(out));
#endif
}
@@ -87,28 +54,6 @@ get_pixel_32(Imaging im, int x, int y, void *color) {
memcpy(color, &im->image32[y][x], sizeof(INT32));
}
-static void
-get_pixel_32L(Imaging im, int x, int y, void *color) {
- UINT8 *in = (UINT8 *)&im->image[y][x * 4];
-#ifdef WORDS_BIGENDIAN
- INT32 out = in[0] + (in[1] << 8) + (in[2] << 16) + (in[3] << 24);
- memcpy(color, &out, sizeof(out));
-#else
- memcpy(color, in, sizeof(INT32));
-#endif
-}
-
-static void
-get_pixel_32B(Imaging im, int x, int y, void *color) {
- UINT8 *in = (UINT8 *)&im->image[y][x * 4];
-#ifdef WORDS_BIGENDIAN
- memcpy(color, in, sizeof(INT32));
-#else
- INT32 out = in[3] + (in[2] << 8) + (in[1] << 16) + (in[0] << 24);
- memcpy(color, &out, sizeof(out));
-#endif
-}
-
/* store individual pixel */
static void
@@ -129,71 +74,46 @@ put_pixel_16B(Imaging im, int x, int y, const void *color) {
out[1] = in[0];
}
-static void
-put_pixel_32L(Imaging im, int x, int y, const void *color) {
- memcpy(&im->image8[y][x * 4], color, 4);
-}
-
-static void
-put_pixel_32B(Imaging im, int x, int y, const void *color) {
- const char *in = color;
- UINT8 *out = (UINT8 *)&im->image8[y][x * 4];
- out[0] = in[3];
- out[1] = in[2];
- out[2] = in[1];
- out[3] = in[0];
-}
-
static void
put_pixel_32(Imaging im, int x, int y, const void *color) {
memcpy(&im->image32[y][x], color, sizeof(INT32));
}
-void
-ImagingAccessInit(void) {
-#define ADD(mode_, get_pixel_, put_pixel_) \
- { \
- ImagingAccess access = add_item(mode_); \
- access->get_pixel = get_pixel_; \
- access->put_pixel = put_pixel_; \
- }
-
- /* populate access table */
- ADD("1", get_pixel_8, put_pixel_8);
- ADD("L", get_pixel_8, put_pixel_8);
- ADD("LA", get_pixel_32_2bands, put_pixel_32);
- ADD("La", get_pixel_32_2bands, put_pixel_32);
- ADD("I", get_pixel_32, put_pixel_32);
- ADD("I;16", get_pixel_16L, put_pixel_16L);
- ADD("I;16L", get_pixel_16L, put_pixel_16L);
- ADD("I;16B", get_pixel_16B, put_pixel_16B);
+static struct ImagingAccessInstance accessors[] = {
+ {IMAGING_MODE_1, get_pixel_8, put_pixel_8},
+ {IMAGING_MODE_L, get_pixel_8, put_pixel_8},
+ {IMAGING_MODE_LA, get_pixel_32_2bands, put_pixel_32},
+ {IMAGING_MODE_La, get_pixel_32_2bands, put_pixel_32},
+ {IMAGING_MODE_I, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_I_16, get_pixel_16L, put_pixel_16L},
+ {IMAGING_MODE_I_16L, get_pixel_16L, put_pixel_16L},
+ {IMAGING_MODE_I_16B, get_pixel_16B, put_pixel_16B},
#ifdef WORDS_BIGENDIAN
- ADD("I;16N", get_pixel_16B, put_pixel_16B);
+ {IMAGING_MODE_I_16N, get_pixel_16B, put_pixel_16B},
#else
- ADD("I;16N", get_pixel_16L, put_pixel_16L);
+ {IMAGING_MODE_I_16N, get_pixel_16L, put_pixel_16L},
#endif
- ADD("I;32L", get_pixel_32L, put_pixel_32L);
- ADD("I;32B", get_pixel_32B, put_pixel_32B);
- ADD("F", get_pixel_32, put_pixel_32);
- ADD("P", get_pixel_8, put_pixel_8);
- ADD("PA", get_pixel_32_2bands, put_pixel_32);
- ADD("RGB", get_pixel_32, put_pixel_32);
- ADD("RGBA", get_pixel_32, put_pixel_32);
- ADD("RGBa", get_pixel_32, put_pixel_32);
- ADD("RGBX", get_pixel_32, put_pixel_32);
- ADD("CMYK", get_pixel_32, put_pixel_32);
- ADD("YCbCr", get_pixel_32, put_pixel_32);
- ADD("LAB", get_pixel_32, put_pixel_32);
- ADD("HSV", get_pixel_32, put_pixel_32);
-}
+ {IMAGING_MODE_F, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_P, get_pixel_8, put_pixel_8},
+ {IMAGING_MODE_PA, get_pixel_32_2bands, put_pixel_32},
+ {IMAGING_MODE_RGB, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_RGBA, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_RGBa, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_RGBX, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_CMYK, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_YCbCr, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_LAB, get_pixel_32, put_pixel_32},
+ {IMAGING_MODE_HSV, get_pixel_32, put_pixel_32},
+};
ImagingAccess
-ImagingAccessNew(Imaging im) {
- ImagingAccess access = &access_table[hash(im->mode)];
- if (im->mode[0] != access->mode[0] || strcmp(im->mode, access->mode) != 0) {
- return NULL;
+ImagingAccessNew(const Imaging im) {
+ for (size_t i = 0; i < sizeof(accessors) / sizeof(*accessors); i++) {
+ if (im->mode == accessors[i].mode) {
+ return &accessors[i];
+ }
}
- return access;
+ return NULL;
}
void
diff --git a/src/libImaging/AlphaComposite.c b/src/libImaging/AlphaComposite.c
index 6d728f908..280277e83 100644
--- a/src/libImaging/AlphaComposite.c
+++ b/src/libImaging/AlphaComposite.c
@@ -25,13 +25,12 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) {
int x, y;
/* Check arguments */
- if (!imDst || !imSrc || strcmp(imDst->mode, "RGBA") ||
- imDst->type != IMAGING_TYPE_UINT8 || imDst->bands != 4) {
+ if (!imDst || !imSrc ||
+ (imDst->mode != IMAGING_MODE_RGBA && imDst->mode != IMAGING_MODE_LA)) {
return ImagingError_ModeError();
}
- if (strcmp(imDst->mode, imSrc->mode) || imDst->type != imSrc->type ||
- imDst->bands != imSrc->bands || imDst->xsize != imSrc->xsize ||
+ if (imDst->mode != imSrc->mode || imDst->xsize != imSrc->xsize ||
imDst->ysize != imSrc->ysize) {
return ImagingError_Mismatch();
}
diff --git a/src/libImaging/Arrow.c b/src/libImaging/Arrow.c
index ccafe33b9..d2ed10f0a 100644
--- a/src/libImaging/Arrow.c
+++ b/src/libImaging/Arrow.c
@@ -55,9 +55,101 @@ ReleaseExportedSchema(struct ArrowSchema *array) {
// Mark array released
array->release = NULL;
}
+char *
+image_band_json(Imaging im) {
+ char *format = "{\"bands\": [\"%s\", \"%s\", \"%s\", \"%s\"]}";
+ char *json;
+ // Bands can be 4 bands * 2 characters each
+ int len = strlen(format) + 8 + 1;
+ int err;
+
+ json = calloc(1, len);
+
+ if (!json) {
+ return NULL;
+ }
+
+ err = PyOS_snprintf(
+ json,
+ len,
+ format,
+ im->band_names[0],
+ im->band_names[1],
+ im->band_names[2],
+ im->band_names[3]
+ );
+ if (err < 0) {
+ return NULL;
+ }
+ return json;
+}
+
+char *
+single_band_json(Imaging im) {
+ char *format = "{\"bands\": [\"%s\"]}";
+ char *json;
+ // Bands can be 1 band * (maybe but probably not) 2 characters each
+ int len = strlen(format) + 2 + 1;
+ int err;
+
+ json = calloc(1, len);
+
+ if (!json) {
+ return NULL;
+ }
+
+ err = PyOS_snprintf(json, len, format, im->band_names[0]);
+ if (err < 0) {
+ return NULL;
+ }
+ return json;
+}
+
+char *
+assemble_metadata(const char *band_json) {
+ /* format is
+ int32: number of key/value pairs (noted N below)
+ int32: byte length of key 0
+ key 0 (not null-terminated)
+ int32: byte length of value 0
+ value 0 (not null-terminated)
+ ...
+ int32: byte length of key N - 1
+ key N - 1 (not null-terminated)
+ int32: byte length of value N - 1
+ value N - 1 (not null-terminated)
+ */
+ const char *key = "image";
+ INT32 key_len = strlen(key);
+ INT32 band_json_len = strlen(band_json);
+
+ char *buf;
+ INT32 *dest_int;
+ char *dest;
+
+ buf = calloc(1, key_len + band_json_len + 4 + 1 * 8);
+ if (!buf) {
+ return NULL;
+ }
+
+ dest_int = (void *)buf;
+
+ dest_int[0] = 1;
+ dest_int[1] = key_len;
+ dest_int += 2;
+ dest = (void *)dest_int;
+ memcpy(dest, key, key_len);
+ dest += key_len;
+ dest_int = (void *)dest;
+ dest_int[0] = band_json_len;
+ dest_int += 1;
+ memcpy(dest_int, band_json, band_json_len);
+
+ return buf;
+}
int
-export_named_type(struct ArrowSchema *schema, char *format, char *name) {
+export_named_type(struct ArrowSchema *schema, char *format, const char *name) {
char *formatp;
char *namep;
size_t format_len = strlen(format) + 1;
@@ -95,6 +187,7 @@ export_named_type(struct ArrowSchema *schema, char *format, char *name) {
int
export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
int retval = 0;
+ char *band_json;
if (strcmp(im->arrow_band_format, "") == 0) {
return IMAGING_ARROW_INCOMPATIBLE_MODE;
@@ -106,7 +199,17 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
}
if (im->bands == 1) {
- return export_named_type(schema, im->arrow_band_format, im->band_names[0]);
+ retval = export_named_type(schema, im->arrow_band_format, im->band_names[0]);
+ if (retval != 0) {
+ return retval;
+ }
+ // band related metadata
+ band_json = single_band_json(im);
+ if (band_json) {
+ schema->metadata = assemble_metadata(band_json);
+ free(band_json);
+ }
+ return retval;
}
retval = export_named_type(schema, "+w:4", "");
@@ -117,13 +220,26 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
schema->n_children = 1;
schema->children = calloc(1, sizeof(struct ArrowSchema *));
schema->children[0] = (struct ArrowSchema *)calloc(1, sizeof(struct ArrowSchema));
- retval = export_named_type(schema->children[0], im->arrow_band_format, "pixel");
+ retval = export_named_type(
+ schema->children[0], im->arrow_band_format, getModeData(im->mode)->name
+ );
if (retval != 0) {
free(schema->children[0]);
free(schema->children);
schema->release(schema);
return retval;
}
+
+ // band related metadata
+ band_json = image_band_json(im);
+ if (band_json) {
+ // adding the metadata to the child array.
+ // Accessible in pyarrow via pa.array(img).type.field(0).metadata
+ // adding it to the top level is not accessible.
+ schema->children[0]->metadata = assemble_metadata(band_json);
+ free(band_json);
+ }
+
return 0;
}
diff --git a/src/libImaging/Bands.c b/src/libImaging/Bands.c
index e1b16b34a..d1b0ebc4e 100644
--- a/src/libImaging/Bands.c
+++ b/src/libImaging/Bands.c
@@ -41,7 +41,7 @@ ImagingGetBand(Imaging imIn, int band) {
band = 3;
}
- imOut = ImagingNewDirty("L", imIn->xsize, imIn->ysize);
+ imOut = ImagingNewDirty(IMAGING_MODE_L, imIn->xsize, imIn->ysize);
if (!imOut) {
return NULL;
}
@@ -82,7 +82,7 @@ ImagingSplit(Imaging imIn, Imaging bands[4]) {
}
for (i = 0; i < imIn->bands; i++) {
- bands[i] = ImagingNewDirty("L", imIn->xsize, imIn->ysize);
+ bands[i] = ImagingNewDirty(IMAGING_MODE_L, imIn->xsize, imIn->ysize);
if (!bands[i]) {
for (j = 0; j < i; ++j) {
ImagingDelete(bands[j]);
@@ -240,7 +240,7 @@ ImagingFillBand(Imaging imOut, int band, int color) {
}
Imaging
-ImagingMerge(const char *mode, Imaging bands[4]) {
+ImagingMerge(const ModeID mode, Imaging bands[4]) {
int i, x, y;
int bandsCount = 0;
Imaging imOut;
diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c
index 7b3d8f908..ac81ed6df 100644
--- a/src/libImaging/BcnDecode.c
+++ b/src/libImaging/BcnDecode.c
@@ -603,7 +603,7 @@ static void
bc6_sign_extend(UINT16 *v, int prec) {
int x = *v;
if (x & (1 << (prec - 1))) {
- x |= -1 << prec;
+ x |= -(1 << prec);
}
*v = (UINT16)x;
}
diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c
index 7a5072dde..973a7a2fa 100644
--- a/src/libImaging/BcnEncode.c
+++ b/src/libImaging/BcnEncode.c
@@ -36,10 +36,9 @@ decode_565(UINT16 x) {
static UINT16
encode_565(rgba item) {
- UINT8 r, g, b;
- r = item.color[0] >> (8 - 5);
- g = item.color[1] >> (8 - 6);
- b = item.color[2] >> (8 - 5);
+ UINT16 r = item.color[0] >> (8 - 5);
+ UINT8 g = item.color[1] >> (8 - 6);
+ UINT8 b = item.color[2] >> (8 - 5);
return (r << (5 + 6)) | (g << 5) | b;
}
@@ -157,7 +156,8 @@ encode_bc1_color(Imaging im, ImagingCodecState state, UINT8 *dst, int separate_a
static void
encode_bc2_block(Imaging im, ImagingCodecState state, UINT8 *dst) {
int i, j;
- UINT8 block[16], current_alpha;
+ UINT8 block[16];
+ UINT32 current_alpha;
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
int x = state->x + i * im->pixelsize;
@@ -253,7 +253,7 @@ int
ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
int n = state->state;
int has_alpha_channel =
- strcmp(im->mode, "RGBA") == 0 || strcmp(im->mode, "LA") == 0;
+ im->mode == IMAGING_MODE_RGBA || im->mode == IMAGING_MODE_LA;
UINT8 *dst = buf;
diff --git a/src/libImaging/Blend.c b/src/libImaging/Blend.c
index a53ae0fad..df94920f6 100644
--- a/src/libImaging/Blend.c
+++ b/src/libImaging/Blend.c
@@ -24,8 +24,8 @@ ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha) {
/* Check arguments */
if (!imIn1 || !imIn2 || imIn1->type != IMAGING_TYPE_UINT8 || imIn1->palette ||
- strcmp(imIn1->mode, "1") == 0 || imIn2->palette ||
- strcmp(imIn2->mode, "1") == 0) {
+ imIn1->mode == IMAGING_MODE_1 || imIn2->palette ||
+ imIn2->mode == IMAGING_MODE_1) {
return ImagingError_ModeError();
}
diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c
index ed91541fe..4fea4fe44 100644
--- a/src/libImaging/BoxBlur.c
+++ b/src/libImaging/BoxBlur.c
@@ -248,7 +248,7 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n)
return ImagingError_ValueError("radius must be >= 0");
}
- if (strcmp(imIn->mode, imOut->mode) || imIn->type != imOut->type ||
+ if (imIn->mode != imOut->mode || imIn->type != imOut->type ||
imIn->bands != imOut->bands || imIn->xsize != imOut->xsize ||
imIn->ysize != imOut->ysize) {
return ImagingError_Mismatch();
@@ -258,10 +258,10 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n)
return ImagingError_ModeError();
}
- if (!(strcmp(imIn->mode, "RGB") == 0 || strcmp(imIn->mode, "RGBA") == 0 ||
- strcmp(imIn->mode, "RGBa") == 0 || strcmp(imIn->mode, "RGBX") == 0 ||
- strcmp(imIn->mode, "CMYK") == 0 || strcmp(imIn->mode, "L") == 0 ||
- strcmp(imIn->mode, "LA") == 0 || strcmp(imIn->mode, "La") == 0)) {
+ if (imIn->mode != IMAGING_MODE_RGB && imIn->mode != IMAGING_MODE_RGBA &&
+ imIn->mode != IMAGING_MODE_RGBa && imIn->mode != IMAGING_MODE_RGBX &&
+ imIn->mode != IMAGING_MODE_CMYK && imIn->mode != IMAGING_MODE_L &&
+ imIn->mode != IMAGING_MODE_LA && imIn->mode != IMAGING_MODE_La) {
return ImagingError_ModeError();
}
diff --git a/src/libImaging/Chops.c b/src/libImaging/Chops.c
index f326d402f..3ce8a0903 100644
--- a/src/libImaging/Chops.c
+++ b/src/libImaging/Chops.c
@@ -18,28 +18,28 @@
#include "Imaging.h"
-#define CHOP(operation) \
- int x, y; \
- Imaging imOut; \
- imOut = create(imIn1, imIn2, NULL); \
- if (!imOut) { \
- return NULL; \
- } \
- for (y = 0; y < imOut->ysize; y++) { \
- UINT8 *out = (UINT8 *)imOut->image[y]; \
- UINT8 *in1 = (UINT8 *)imIn1->image[y]; \
- UINT8 *in2 = (UINT8 *)imIn2->image[y]; \
- for (x = 0; x < imOut->linesize; x++) { \
- int temp = operation; \
- if (temp <= 0) { \
- out[x] = 0; \
- } else if (temp >= 255) { \
- out[x] = 255; \
- } else { \
- out[x] = temp; \
- } \
- } \
- } \
+#define CHOP(operation) \
+ int x, y; \
+ Imaging imOut; \
+ imOut = create(imIn1, imIn2, IMAGING_MODE_UNKNOWN); \
+ if (!imOut) { \
+ return NULL; \
+ } \
+ for (y = 0; y < imOut->ysize; y++) { \
+ UINT8 *out = (UINT8 *)imOut->image[y]; \
+ UINT8 *in1 = (UINT8 *)imIn1->image[y]; \
+ UINT8 *in2 = (UINT8 *)imIn2->image[y]; \
+ for (x = 0; x < imOut->linesize; x++) { \
+ int temp = operation; \
+ if (temp <= 0) { \
+ out[x] = 0; \
+ } else if (temp >= 255) { \
+ out[x] = 255; \
+ } else { \
+ out[x] = temp; \
+ } \
+ } \
+ } \
return imOut;
#define CHOP2(operation, mode) \
@@ -60,11 +60,12 @@
return imOut;
static Imaging
-create(Imaging im1, Imaging im2, char *mode) {
+create(Imaging im1, Imaging im2, const ModeID mode) {
int xsize, ysize;
if (!im1 || !im2 || im1->type != IMAGING_TYPE_UINT8 ||
- (mode != NULL && (strcmp(im1->mode, "1") || strcmp(im2->mode, "1")))) {
+ (mode != IMAGING_MODE_UNKNOWN &&
+ (im1->mode != IMAGING_MODE_1 || im2->mode != IMAGING_MODE_1))) {
return (Imaging)ImagingError_ModeError();
}
if (im1->type != im2->type || im1->bands != im2->bands) {
@@ -114,27 +115,27 @@ ImagingChopSubtract(Imaging imIn1, Imaging imIn2, float scale, int offset) {
Imaging
ImagingChopAnd(Imaging imIn1, Imaging imIn2) {
- CHOP2((in1[x] && in2[x]) ? 255 : 0, "1");
+ CHOP2((in1[x] && in2[x]) ? 255 : 0, IMAGING_MODE_1);
}
Imaging
ImagingChopOr(Imaging imIn1, Imaging imIn2) {
- CHOP2((in1[x] || in2[x]) ? 255 : 0, "1");
+ CHOP2((in1[x] || in2[x]) ? 255 : 0, IMAGING_MODE_1);
}
Imaging
ImagingChopXor(Imaging imIn1, Imaging imIn2) {
- CHOP2(((in1[x] != 0) ^ (in2[x] != 0)) ? 255 : 0, "1");
+ CHOP2(((in1[x] != 0) ^ (in2[x] != 0)) ? 255 : 0, IMAGING_MODE_1);
}
Imaging
ImagingChopAddModulo(Imaging imIn1, Imaging imIn2) {
- CHOP2(in1[x] + in2[x], NULL);
+ CHOP2(in1[x] + in2[x], IMAGING_MODE_UNKNOWN);
}
Imaging
ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2) {
- CHOP2(in1[x] - in2[x], NULL);
+ CHOP2(in1[x] - in2[x], IMAGING_MODE_UNKNOWN);
}
Imaging
@@ -142,7 +143,7 @@ ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) {
CHOP2(
(((255 - in1[x]) * (in1[x] * in2[x])) / 65536) +
(in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255,
- NULL
+ IMAGING_MODE_UNKNOWN
);
}
@@ -151,7 +152,7 @@ ImagingChopHardLight(Imaging imIn1, Imaging imIn2) {
CHOP2(
(in2[x] < 128) ? ((in1[x] * in2[x]) / 127)
: 255 - (((255 - in2[x]) * (255 - in1[x])) / 127),
- NULL
+ IMAGING_MODE_UNKNOWN
);
}
@@ -160,6 +161,6 @@ ImagingOverlay(Imaging imIn1, Imaging imIn2) {
CHOP2(
(in1[x] < 128) ? ((in1[x] * in2[x]) / 127)
: 255 - (((255 - in1[x]) * (255 - in2[x])) / 127),
- NULL
+ IMAGING_MODE_UNKNOWN
);
}
diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c
index 9a2c9ff16..330e5325c 100644
--- a/src/libImaging/Convert.c
+++ b/src/libImaging/Convert.c
@@ -877,147 +877,12 @@ I16_RGB(UINT8 *out, const UINT8 *in, int xsize) {
}
}
-static struct {
- const char *from;
- const char *to;
- ImagingShuffler convert;
-} converters[] = {
-
- {"1", "L", bit2l},
- {"1", "I", bit2i},
- {"1", "F", bit2f},
- {"1", "RGB", bit2rgb},
- {"1", "RGBA", bit2rgb},
- {"1", "RGBX", bit2rgb},
- {"1", "CMYK", bit2cmyk},
- {"1", "YCbCr", bit2ycbcr},
- {"1", "HSV", bit2hsv},
-
- {"L", "1", l2bit},
- {"L", "LA", l2la},
- {"L", "I", l2i},
- {"L", "F", l2f},
- {"L", "RGB", l2rgb},
- {"L", "RGBA", l2rgb},
- {"L", "RGBX", l2rgb},
- {"L", "CMYK", l2cmyk},
- {"L", "YCbCr", l2ycbcr},
- {"L", "HSV", l2hsv},
-
- {"LA", "L", la2l},
- {"LA", "La", lA2la},
- {"LA", "RGB", la2rgb},
- {"LA", "RGBA", la2rgb},
- {"LA", "RGBX", la2rgb},
- {"LA", "CMYK", la2cmyk},
- {"LA", "YCbCr", la2ycbcr},
- {"LA", "HSV", la2hsv},
-
- {"La", "LA", la2lA},
-
- {"I", "L", i2l},
- {"I", "F", i2f},
- {"I", "RGB", i2rgb},
- {"I", "RGBA", i2rgb},
- {"I", "RGBX", i2rgb},
- {"I", "HSV", i2hsv},
-
- {"F", "L", f2l},
- {"F", "I", f2i},
-
- {"RGB", "1", rgb2bit},
- {"RGB", "L", rgb2l},
- {"RGB", "LA", rgb2la},
- {"RGB", "La", rgb2la},
- {"RGB", "I", rgb2i},
- {"RGB", "I;16", rgb2i16l},
- {"RGB", "I;16L", rgb2i16l},
- {"RGB", "I;16B", rgb2i16b},
-#ifdef WORDS_BIGENDIAN
- {"RGB", "I;16N", rgb2i16b},
-#else
- {"RGB", "I;16N", rgb2i16l},
-#endif
- {"RGB", "F", rgb2f},
- {"RGB", "RGBA", rgb2rgba},
- {"RGB", "RGBa", rgb2rgba},
- {"RGB", "RGBX", rgb2rgba},
- {"RGB", "CMYK", rgb2cmyk},
- {"RGB", "YCbCr", ImagingConvertRGB2YCbCr},
- {"RGB", "HSV", rgb2hsv},
-
- {"RGBA", "1", rgb2bit},
- {"RGBA", "L", rgb2l},
- {"RGBA", "LA", rgba2la},
- {"RGBA", "I", rgb2i},
- {"RGBA", "F", rgb2f},
- {"RGBA", "RGB", rgba2rgb},
- {"RGBA", "RGBa", rgbA2rgba},
- {"RGBA", "RGBX", rgb2rgba},
- {"RGBA", "CMYK", rgb2cmyk},
- {"RGBA", "YCbCr", ImagingConvertRGB2YCbCr},
- {"RGBA", "HSV", rgb2hsv},
-
- {"RGBa", "RGBA", rgba2rgbA},
- {"RGBa", "RGB", rgba2rgb_},
-
- {"RGBX", "1", rgb2bit},
- {"RGBX", "L", rgb2l},
- {"RGBX", "LA", rgb2la},
- {"RGBX", "I", rgb2i},
- {"RGBX", "F", rgb2f},
- {"RGBX", "RGB", rgba2rgb},
- {"RGBX", "CMYK", rgb2cmyk},
- {"RGBX", "YCbCr", ImagingConvertRGB2YCbCr},
- {"RGBX", "HSV", rgb2hsv},
-
- {"CMYK", "RGB", cmyk2rgb},
- {"CMYK", "RGBA", cmyk2rgb},
- {"CMYK", "RGBX", cmyk2rgb},
- {"CMYK", "HSV", cmyk2hsv},
-
- {"YCbCr", "L", ycbcr2l},
- {"YCbCr", "LA", ycbcr2la},
- {"YCbCr", "RGB", ImagingConvertYCbCr2RGB},
-
- {"HSV", "RGB", hsv2rgb},
-
- {"I", "I;16", I_I16L},
- {"I;16", "I", I16L_I},
- {"I;16", "RGB", I16_RGB},
- {"L", "I;16", L_I16L},
- {"I;16", "L", I16L_L},
-
- {"I", "I;16L", I_I16L},
- {"I;16L", "I", I16L_I},
- {"I", "I;16B", I_I16B},
- {"I;16B", "I", I16B_I},
-
- {"L", "I;16L", L_I16L},
- {"I;16L", "L", I16L_L},
- {"L", "I;16B", L_I16B},
- {"I;16B", "L", I16B_L},
-#ifdef WORDS_BIGENDIAN
- {"L", "I;16N", L_I16B},
- {"I;16N", "L", I16B_L},
-#else
- {"L", "I;16N", L_I16L},
- {"I;16N", "L", I16L_L},
-#endif
-
- {"I;16", "F", I16L_F},
- {"I;16L", "F", I16L_F},
- {"I;16B", "F", I16B_F},
-
- {NULL}
-};
-
-/* FIXME: translate indexed versions to pointer versions below this line */
-
/* ------------------- */
/* Palette conversions */
/* ------------------- */
+/* FIXME: translate indexed versions to pointer versions below this line */
+
static void
p2bit(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
int x;
@@ -1065,13 +930,13 @@ pa2p(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
static void
p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
int x;
- int rgb = strcmp(palette->mode, "RGB");
+ const int rgb = palette->mode == IMAGING_MODE_RGB;
for (x = 0; x < xsize; x++, in++) {
const UINT8 *rgba = &palette->palette[in[0] * 4];
*out++ = in[0];
*out++ = in[0];
*out++ = in[0];
- *out++ = rgb == 0 ? 255 : rgba[3];
+ *out++ = rgb ? 255 : rgba[3];
}
}
@@ -1225,7 +1090,7 @@ pa2ycbcr(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
}
static Imaging
-frompalette(Imaging imOut, Imaging imIn, const char *mode) {
+frompalette(Imaging imOut, Imaging imIn, const ModeID mode) {
ImagingSectionCookie cookie;
int alpha;
int y;
@@ -1237,31 +1102,31 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
return (Imaging)ImagingError_ValueError("no palette");
}
- alpha = !strcmp(imIn->mode, "PA");
+ alpha = imIn->mode == IMAGING_MODE_PA;
- if (strcmp(mode, "1") == 0) {
+ if (mode == IMAGING_MODE_1) {
convert = alpha ? pa2bit : p2bit;
- } else if (strcmp(mode, "L") == 0) {
+ } else if (mode == IMAGING_MODE_L) {
convert = alpha ? pa2l : p2l;
- } else if (strcmp(mode, "LA") == 0) {
+ } else if (mode == IMAGING_MODE_LA) {
convert = alpha ? pa2la : p2la;
- } else if (strcmp(mode, "P") == 0) {
+ } else if (mode == IMAGING_MODE_P) {
convert = pa2p;
- } else if (strcmp(mode, "PA") == 0) {
+ } else if (mode == IMAGING_MODE_PA) {
convert = p2pa;
- } else if (strcmp(mode, "I") == 0) {
+ } else if (mode == IMAGING_MODE_I) {
convert = alpha ? pa2i : p2i;
- } else if (strcmp(mode, "F") == 0) {
+ } else if (mode == IMAGING_MODE_F) {
convert = alpha ? pa2f : p2f;
- } else if (strcmp(mode, "RGB") == 0) {
+ } else if (mode == IMAGING_MODE_RGB) {
convert = alpha ? pa2rgb : p2rgb;
- } else if (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBX") == 0) {
+ } else if (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBX) {
convert = alpha ? pa2rgba : p2rgba;
- } else if (strcmp(mode, "CMYK") == 0) {
+ } else if (mode == IMAGING_MODE_CMYK) {
convert = alpha ? pa2cmyk : p2cmyk;
- } else if (strcmp(mode, "YCbCr") == 0) {
+ } else if (mode == IMAGING_MODE_YCbCr) {
convert = alpha ? pa2ycbcr : p2ycbcr;
- } else if (strcmp(mode, "HSV") == 0) {
+ } else if (mode == IMAGING_MODE_HSV) {
convert = alpha ? pa2hsv : p2hsv;
} else {
return (Imaging)ImagingError_ValueError("conversion not supported");
@@ -1271,7 +1136,7 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
if (!imOut) {
return NULL;
}
- if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) {
+ if (mode == IMAGING_MODE_P || mode == IMAGING_MODE_PA) {
ImagingPaletteDelete(imOut->palette);
imOut->palette = ImagingPaletteDuplicate(imIn->palette);
}
@@ -1295,24 +1160,26 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
#endif
static Imaging
topalette(
- Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalette, int dither
+ Imaging imOut, Imaging imIn, const ModeID mode, ImagingPalette inpalette, int dither
) {
ImagingSectionCookie cookie;
int alpha;
int x, y;
ImagingPalette palette = inpalette;
- /* Map L or RGB/RGBX/RGBA to palette image */
- if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0) {
+ /* Map L or RGB/RGBX/RGBA/RGBa to palette image */
+ if (imIn->mode != IMAGING_MODE_L && imIn->mode != IMAGING_MODE_RGB &&
+ imIn->mode != IMAGING_MODE_RGBX && imIn->mode != IMAGING_MODE_RGBA &&
+ imIn->mode != IMAGING_MODE_RGBa) {
return (Imaging)ImagingError_ValueError("conversion not supported");
}
- alpha = !strcmp(mode, "PA");
+ alpha = mode == IMAGING_MODE_PA;
if (palette == NULL) {
/* FIXME: make user configurable */
if (imIn->bands == 1) {
- palette = ImagingPaletteNew("RGB");
+ palette = ImagingPaletteNew(IMAGING_MODE_RGB);
palette->size = 256;
int i;
@@ -1499,11 +1366,11 @@ tobilevel(Imaging imOut, Imaging imIn) {
int *errors;
/* Map L or RGB to dithered 1 image */
- if (strcmp(imIn->mode, "L") != 0 && strcmp(imIn->mode, "RGB") != 0) {
+ if (imIn->mode != IMAGING_MODE_L && imIn->mode != IMAGING_MODE_RGB) {
return (Imaging)ImagingError_ValueError("conversion not supported");
}
- imOut = ImagingNew2Dirty("1", imOut, imIn);
+ imOut = ImagingNew2Dirty(IMAGING_MODE_1, imOut, imIn);
if (!imOut) {
return NULL;
}
@@ -1585,19 +1452,152 @@ tobilevel(Imaging imOut, Imaging imIn) {
#pragma optimize("", on)
#endif
+/* ------------------- */
+/* Conversion handlers */
+/* ------------------- */
+
+static struct {
+ const ModeID from;
+ const ModeID to;
+ ImagingShuffler convert;
+} converters[] = {
+ {IMAGING_MODE_1, IMAGING_MODE_L, bit2l},
+ {IMAGING_MODE_1, IMAGING_MODE_I, bit2i},
+ {IMAGING_MODE_1, IMAGING_MODE_F, bit2f},
+ {IMAGING_MODE_1, IMAGING_MODE_RGB, bit2rgb},
+ {IMAGING_MODE_1, IMAGING_MODE_RGBA, bit2rgb},
+ {IMAGING_MODE_1, IMAGING_MODE_RGBX, bit2rgb},
+ {IMAGING_MODE_1, IMAGING_MODE_CMYK, bit2cmyk},
+ {IMAGING_MODE_1, IMAGING_MODE_YCbCr, bit2ycbcr},
+ {IMAGING_MODE_1, IMAGING_MODE_HSV, bit2hsv},
+
+ {IMAGING_MODE_L, IMAGING_MODE_1, l2bit},
+ {IMAGING_MODE_L, IMAGING_MODE_LA, l2la},
+ {IMAGING_MODE_L, IMAGING_MODE_I, l2i},
+ {IMAGING_MODE_L, IMAGING_MODE_F, l2f},
+ {IMAGING_MODE_L, IMAGING_MODE_RGB, l2rgb},
+ {IMAGING_MODE_L, IMAGING_MODE_RGBA, l2rgb},
+ {IMAGING_MODE_L, IMAGING_MODE_RGBX, l2rgb},
+ {IMAGING_MODE_L, IMAGING_MODE_CMYK, l2cmyk},
+ {IMAGING_MODE_L, IMAGING_MODE_YCbCr, l2ycbcr},
+ {IMAGING_MODE_L, IMAGING_MODE_HSV, l2hsv},
+
+ {IMAGING_MODE_LA, IMAGING_MODE_L, la2l},
+ {IMAGING_MODE_LA, IMAGING_MODE_La, lA2la},
+ {IMAGING_MODE_LA, IMAGING_MODE_RGB, la2rgb},
+ {IMAGING_MODE_LA, IMAGING_MODE_RGBA, la2rgb},
+ {IMAGING_MODE_LA, IMAGING_MODE_RGBX, la2rgb},
+ {IMAGING_MODE_LA, IMAGING_MODE_CMYK, la2cmyk},
+ {IMAGING_MODE_LA, IMAGING_MODE_YCbCr, la2ycbcr},
+ {IMAGING_MODE_LA, IMAGING_MODE_HSV, la2hsv},
+
+ {IMAGING_MODE_La, IMAGING_MODE_LA, la2lA},
+
+ {IMAGING_MODE_I, IMAGING_MODE_L, i2l},
+ {IMAGING_MODE_I, IMAGING_MODE_F, i2f},
+ {IMAGING_MODE_I, IMAGING_MODE_RGB, i2rgb},
+ {IMAGING_MODE_I, IMAGING_MODE_RGBA, i2rgb},
+ {IMAGING_MODE_I, IMAGING_MODE_RGBX, i2rgb},
+ {IMAGING_MODE_I, IMAGING_MODE_HSV, i2hsv},
+
+ {IMAGING_MODE_F, IMAGING_MODE_L, f2l},
+ {IMAGING_MODE_F, IMAGING_MODE_I, f2i},
+
+ {IMAGING_MODE_RGB, IMAGING_MODE_1, rgb2bit},
+ {IMAGING_MODE_RGB, IMAGING_MODE_L, rgb2l},
+ {IMAGING_MODE_RGB, IMAGING_MODE_LA, rgb2la},
+ {IMAGING_MODE_RGB, IMAGING_MODE_La, rgb2la},
+ {IMAGING_MODE_RGB, IMAGING_MODE_I, rgb2i},
+ {IMAGING_MODE_RGB, IMAGING_MODE_I_16, rgb2i16l},
+ {IMAGING_MODE_RGB, IMAGING_MODE_I_16L, rgb2i16l},
+ {IMAGING_MODE_RGB, IMAGING_MODE_I_16B, rgb2i16b},
+#ifdef WORDS_BIGENDIAN
+ {IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16b},
+#else
+ {IMAGING_MODE_RGB, IMAGING_MODE_I_16N, rgb2i16l},
+#endif
+ {IMAGING_MODE_RGB, IMAGING_MODE_F, rgb2f},
+ {IMAGING_MODE_RGB, IMAGING_MODE_RGBA, rgb2rgba},
+ {IMAGING_MODE_RGB, IMAGING_MODE_RGBa, rgb2rgba},
+ {IMAGING_MODE_RGB, IMAGING_MODE_RGBX, rgb2rgba},
+ {IMAGING_MODE_RGB, IMAGING_MODE_CMYK, rgb2cmyk},
+ {IMAGING_MODE_RGB, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
+ {IMAGING_MODE_RGB, IMAGING_MODE_HSV, rgb2hsv},
+
+ {IMAGING_MODE_RGBA, IMAGING_MODE_1, rgb2bit},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_L, rgb2l},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_LA, rgba2la},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_I, rgb2i},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_F, rgb2f},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_RGB, rgba2rgb},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_RGBa, rgbA2rgba},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_RGBX, rgb2rgba},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_CMYK, rgb2cmyk},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
+ {IMAGING_MODE_RGBA, IMAGING_MODE_HSV, rgb2hsv},
+
+ {IMAGING_MODE_RGBa, IMAGING_MODE_RGBA, rgba2rgbA},
+ {IMAGING_MODE_RGBa, IMAGING_MODE_RGB, rgba2rgb_},
+
+ {IMAGING_MODE_RGBX, IMAGING_MODE_1, rgb2bit},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_L, rgb2l},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_LA, rgb2la},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_I, rgb2i},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_F, rgb2f},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_RGB, rgba2rgb},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_CMYK, rgb2cmyk},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_YCbCr, ImagingConvertRGB2YCbCr},
+ {IMAGING_MODE_RGBX, IMAGING_MODE_HSV, rgb2hsv},
+
+ {IMAGING_MODE_CMYK, IMAGING_MODE_RGB, cmyk2rgb},
+ {IMAGING_MODE_CMYK, IMAGING_MODE_RGBA, cmyk2rgb},
+ {IMAGING_MODE_CMYK, IMAGING_MODE_RGBX, cmyk2rgb},
+ {IMAGING_MODE_CMYK, IMAGING_MODE_HSV, cmyk2hsv},
+
+ {IMAGING_MODE_YCbCr, IMAGING_MODE_L, ycbcr2l},
+ {IMAGING_MODE_YCbCr, IMAGING_MODE_LA, ycbcr2la},
+ {IMAGING_MODE_YCbCr, IMAGING_MODE_RGB, ImagingConvertYCbCr2RGB},
+
+ {IMAGING_MODE_HSV, IMAGING_MODE_RGB, hsv2rgb},
+
+ {IMAGING_MODE_I, IMAGING_MODE_I_16, I_I16L},
+ {IMAGING_MODE_I_16, IMAGING_MODE_I, I16L_I},
+ {IMAGING_MODE_I_16, IMAGING_MODE_RGB, I16_RGB},
+ {IMAGING_MODE_L, IMAGING_MODE_I_16, L_I16L},
+ {IMAGING_MODE_I_16, IMAGING_MODE_L, I16L_L},
+
+ {IMAGING_MODE_I, IMAGING_MODE_I_16L, I_I16L},
+ {IMAGING_MODE_I_16L, IMAGING_MODE_I, I16L_I},
+ {IMAGING_MODE_I, IMAGING_MODE_I_16B, I_I16B},
+ {IMAGING_MODE_I_16B, IMAGING_MODE_I, I16B_I},
+
+ {IMAGING_MODE_L, IMAGING_MODE_I_16L, L_I16L},
+ {IMAGING_MODE_I_16L, IMAGING_MODE_L, I16L_L},
+ {IMAGING_MODE_L, IMAGING_MODE_I_16B, L_I16B},
+ {IMAGING_MODE_I_16B, IMAGING_MODE_L, I16B_L},
+#ifdef WORDS_BIGENDIAN
+ {IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16B},
+ {IMAGING_MODE_I_16N, IMAGING_MODE_L, I16B_L},
+#else
+ {IMAGING_MODE_L, IMAGING_MODE_I_16N, L_I16L},
+ {IMAGING_MODE_I_16N, IMAGING_MODE_L, I16L_L},
+#endif
+
+ {IMAGING_MODE_I_16, IMAGING_MODE_F, I16L_F},
+ {IMAGING_MODE_I_16L, IMAGING_MODE_F, I16L_F},
+ {IMAGING_MODE_I_16B, IMAGING_MODE_F, I16B_F}
+};
+
static Imaging
-convert(
- Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither
-) {
+convert(Imaging imOut, Imaging imIn, ModeID mode, ImagingPalette palette, int dither) {
ImagingSectionCookie cookie;
ImagingShuffler convert;
- int y;
if (!imIn) {
return (Imaging)ImagingError_ModeError();
}
- if (!mode) {
+ if (mode == IMAGING_MODE_UNKNOWN) {
/* Map palette image to full depth */
if (!imIn->palette) {
return (Imaging)ImagingError_ModeError();
@@ -1605,33 +1605,31 @@ convert(
mode = imIn->palette->mode;
} else {
/* Same mode? */
- if (!strcmp(imIn->mode, mode)) {
+ if (imIn->mode == mode) {
return ImagingCopy2(imOut, imIn);
}
}
/* test for special conversions */
- if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0) {
+ if (imIn->mode == IMAGING_MODE_P || imIn->mode == IMAGING_MODE_PA) {
return frompalette(imOut, imIn, mode);
}
- if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) {
+ if (mode == IMAGING_MODE_P || mode == IMAGING_MODE_PA) {
return topalette(imOut, imIn, mode, palette, dither);
}
- if (dither && strcmp(mode, "1") == 0) {
+ if (dither && mode == IMAGING_MODE_1) {
return tobilevel(imOut, imIn);
}
/* standard conversion machinery */
convert = NULL;
-
- for (y = 0; converters[y].from; y++) {
- if (!strcmp(imIn->mode, converters[y].from) &&
- !strcmp(mode, converters[y].to)) {
- convert = converters[y].convert;
+ for (size_t i = 0; i < sizeof(converters) / sizeof(*converters); i++) {
+ if (imIn->mode == converters[i].from && mode == converters[i].to) {
+ convert = converters[i].convert;
break;
}
}
@@ -1642,7 +1640,11 @@ convert(
#else
static char buf[100];
snprintf(
- buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode
+ buf,
+ 100,
+ "conversion from %.10s to %.10s not supported",
+ getModeData(imIn->mode)->name,
+ getModeData(mode)->name
);
return (Imaging)ImagingError_ValueError(buf);
#endif
@@ -1654,7 +1656,7 @@ convert(
}
ImagingSectionEnter(&cookie);
- for (y = 0; y < imIn->ysize; y++) {
+ for (int y = 0; y < imIn->ysize; y++) {
(*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize);
}
ImagingSectionLeave(&cookie);
@@ -1663,7 +1665,7 @@ convert(
}
Imaging
-ImagingConvert(Imaging imIn, const char *mode, ImagingPalette palette, int dither) {
+ImagingConvert(Imaging imIn, const ModeID mode, ImagingPalette palette, int dither) {
return convert(NULL, imIn, mode, palette, dither);
}
@@ -1673,7 +1675,7 @@ ImagingConvert2(Imaging imOut, Imaging imIn) {
}
Imaging
-ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
+ImagingConvertTransparent(Imaging imIn, const ModeID mode, int r, int g, int b) {
ImagingSectionCookie cookie;
ImagingShuffler convert;
Imaging imOut = NULL;
@@ -1687,27 +1689,27 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
return (Imaging)ImagingError_ModeError();
}
- if (strcmp(imIn->mode, "RGB") == 0 &&
- (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) {
+ if (imIn->mode == IMAGING_MODE_RGB &&
+ (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_RGBa)) {
convert = rgb2rgba;
- if (strcmp(mode, "RGBa") == 0) {
+ if (mode == IMAGING_MODE_RGBa) {
premultiplied = 1;
}
- } else if (strcmp(imIn->mode, "RGB") == 0 &&
- (strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) {
+ } else if (imIn->mode == IMAGING_MODE_RGB &&
+ (mode == IMAGING_MODE_LA || mode == IMAGING_MODE_La)) {
convert = rgb2la;
source_transparency = 1;
- if (strcmp(mode, "La") == 0) {
+ if (mode == IMAGING_MODE_La) {
premultiplied = 1;
}
- } else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 ||
- strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0) &&
- (strcmp(mode, "RGBA") == 0 || strcmp(mode, "LA") == 0)) {
- if (strcmp(imIn->mode, "1") == 0) {
+ } else if ((imIn->mode == IMAGING_MODE_1 || imIn->mode == IMAGING_MODE_I ||
+ imIn->mode == IMAGING_MODE_I_16 || imIn->mode == IMAGING_MODE_L) &&
+ (mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_LA)) {
+ if (imIn->mode == IMAGING_MODE_1) {
convert = bit2rgb;
- } else if (strcmp(imIn->mode, "I") == 0) {
+ } else if (imIn->mode == IMAGING_MODE_I) {
convert = i2rgb;
- } else if (strcmp(imIn->mode, "I;16") == 0) {
+ } else if (imIn->mode == IMAGING_MODE_I_16) {
convert = I16_RGB;
} else {
convert = l2rgb;
@@ -1719,8 +1721,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
buf,
100,
"conversion from %.10s to %.10s not supported in convert_transparent",
- imIn->mode,
- mode
+ getModeData(imIn->mode)->name,
+ getModeData(mode)->name
);
return (Imaging)ImagingError_ValueError(buf);
}
@@ -1743,15 +1745,15 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
}
Imaging
-ImagingConvertInPlace(Imaging imIn, const char *mode) {
+ImagingConvertInPlace(Imaging imIn, const ModeID mode) {
ImagingSectionCookie cookie;
ImagingShuffler convert;
int y;
/* limited support for inplace conversion */
- if (strcmp(imIn->mode, "L") == 0 && strcmp(mode, "1") == 0) {
+ if (imIn->mode == IMAGING_MODE_L && mode == IMAGING_MODE_1) {
convert = l2bit;
- } else if (strcmp(imIn->mode, "1") == 0 && strcmp(mode, "L") == 0) {
+ } else if (imIn->mode == IMAGING_MODE_1 && mode == IMAGING_MODE_L) {
convert = bit2l;
} else {
return ImagingError_ModeError();
diff --git a/src/libImaging/Dib.c b/src/libImaging/Dib.c
index c69e9e552..2afe71d4a 100644
--- a/src/libImaging/Dib.c
+++ b/src/libImaging/Dib.c
@@ -25,20 +25,17 @@
#include "ImDib.h"
-char *
+ModeID
ImagingGetModeDIB(int size_out[2]) {
/* Get device characteristics */
- HDC dc;
- char *mode;
+ const HDC dc = CreateCompatibleDC(NULL);
- dc = CreateCompatibleDC(NULL);
-
- mode = "P";
+ ModeID mode = IMAGING_MODE_P;
if (!(GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE)) {
- mode = "RGB";
+ mode = IMAGING_MODE_RGB;
if (GetDeviceCaps(dc, BITSPIXEL) == 1) {
- mode = "1";
+ mode = IMAGING_MODE_1;
}
}
@@ -53,7 +50,7 @@ ImagingGetModeDIB(int size_out[2]) {
}
ImagingDIB
-ImagingNewDIB(const char *mode, int xsize, int ysize) {
+ImagingNewDIB(const ModeID mode, int xsize, int ysize) {
/* Create a Windows bitmap */
ImagingDIB dib;
@@ -61,10 +58,12 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
int i;
/* Check mode */
- if (strcmp(mode, "1") != 0 && strcmp(mode, "L") != 0 && strcmp(mode, "RGB") != 0) {
+ if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_L && mode != IMAGING_MODE_RGB) {
return (ImagingDIB)ImagingError_ModeError();
}
+ const int pixelsize = mode == IMAGING_MODE_RGB ? 3 : 1;
+
/* Create DIB context and info header */
/* malloc check ok, small constant allocation */
dib = (ImagingDIB)malloc(sizeof(*dib));
@@ -83,7 +82,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
dib->info->bmiHeader.biWidth = xsize;
dib->info->bmiHeader.biHeight = ysize;
dib->info->bmiHeader.biPlanes = 1;
- dib->info->bmiHeader.biBitCount = strlen(mode) * 8;
+ dib->info->bmiHeader.biBitCount = pixelsize * 8;
dib->info->bmiHeader.biCompression = BI_RGB;
/* Create DIB */
@@ -103,12 +102,12 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
return (ImagingDIB)ImagingError_MemoryError();
}
- strcpy(dib->mode, mode);
+ dib->mode = mode;
dib->xsize = xsize;
dib->ysize = ysize;
- dib->pixelsize = strlen(mode);
- dib->linesize = (xsize * dib->pixelsize + 3) & -4;
+ dib->pixelsize = pixelsize;
+ dib->linesize = (xsize * pixelsize + 3) & -4;
if (dib->pixelsize == 1) {
dib->pack = dib->unpack = (ImagingShuffler)memcpy;
@@ -132,7 +131,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
}
/* Create an associated palette (for 8-bit displays only) */
- if (strcmp(ImagingGetModeDIB(NULL), "P") == 0) {
+ if (ImagingGetModeDIB(NULL) == IMAGING_MODE_P) {
char palbuf[sizeof(LOGPALETTE) + 256 * sizeof(PALETTEENTRY)];
LPLOGPALETTE pal = (LPLOGPALETTE)palbuf;
int i, r, g, b;
@@ -142,7 +141,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
pal->palNumEntries = 256;
GetSystemPaletteEntries(dib->dc, 0, 256, pal->palPalEntry);
- if (strcmp(mode, "L") == 0) {
+ if (mode == IMAGING_MODE_L) {
/* Grayscale DIB. Fill all 236 slots with a grayscale ramp
* (this is usually overkill on Windows since VGA only offers
* 6 bits grayscale resolution). Ignore the slots already
@@ -156,8 +155,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) {
}
dib->palette = CreatePalette(pal);
-
- } else if (strcmp(mode, "RGB") == 0) {
+ } else if (mode == IMAGING_MODE_RGB) {
#ifdef CUBE216
/* Colour DIB. Create a 6x6x6 colour cube (216 entries) and
diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c
index 27cac687e..d28980432 100644
--- a/src/libImaging/Draw.c
+++ b/src/libImaging/Draw.c
@@ -68,7 +68,7 @@ typedef void (*hline_handler)(Imaging, int, int, int, int, Imaging);
static inline void
point8(Imaging im, int x, int y, int ink) {
if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) {
- if (strncmp(im->mode, "I;16", 4) == 0) {
+ if (isModeI16(im->mode)) {
#ifdef WORDS_BIGENDIAN
im->image8[y][x * 2] = (UINT8)(ink >> 8);
im->image8[y][x * 2 + 1] = (UINT8)ink;
@@ -117,13 +117,13 @@ hline8(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
}
if (x0 <= x1) {
int bigendian = -1;
- if (strncmp(im->mode, "I;16", 4) == 0) {
+ if (isModeI16(im->mode)) {
bigendian =
(
#ifdef WORDS_BIGENDIAN
- strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16L") == 0
+ im->mode == IMAGING_MODE_I_16 || im->mode == IMAGING_MODE_I_16L
#else
- strcmp(im->mode, "I;16B") == 0
+ im->mode == IMAGING_MODE_I_16B
#endif
)
? 1
@@ -672,17 +672,17 @@ DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba};
/* Interface */
/* -------------------------------------------------------------------- */
-#define DRAWINIT() \
- if (im->image8) { \
- draw = &draw8; \
- if (strncmp(im->mode, "I;16", 4) == 0) { \
- ink = INK16(ink_); \
- } else { \
- ink = INK8(ink_); \
- } \
- } else { \
- draw = (op) ? &draw32rgba : &draw32; \
- memcpy(&ink, ink_, sizeof(ink)); \
+#define DRAWINIT() \
+ if (im->image8) { \
+ draw = &draw8; \
+ if (isModeI16(im->mode)) { \
+ ink = INK16(ink_); \
+ } else { \
+ ink = INK8(ink_); \
+ } \
+ } else { \
+ draw = (op) ? &draw32rgba : &draw32; \
+ memcpy(&ink, ink_, sizeof(ink)); \
}
int
diff --git a/src/libImaging/Effects.c b/src/libImaging/Effects.c
index 93e7af0bc..c05c5764e 100644
--- a/src/libImaging/Effects.c
+++ b/src/libImaging/Effects.c
@@ -36,7 +36,7 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) {
return (Imaging)ImagingError_ValueError(NULL);
}
- im = ImagingNewDirty("L", xsize, ysize);
+ im = ImagingNewDirty(IMAGING_MODE_L, xsize, ysize);
if (!im) {
return NULL;
}
@@ -80,7 +80,7 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) {
int nextok;
double this, next;
- imOut = ImagingNewDirty("L", xsize, ysize);
+ imOut = ImagingNewDirty(IMAGING_MODE_L, xsize, ysize);
if (!imOut) {
return NULL;
}
diff --git a/src/libImaging/File.c b/src/libImaging/File.c
index 901fe83ad..435dbeca0 100644
--- a/src/libImaging/File.c
+++ b/src/libImaging/File.c
@@ -23,14 +23,13 @@ int
ImagingSaveRaw(Imaging im, FILE *fp) {
int x, y, i;
- if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
+ if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) {
/* @PIL227: FIXME: for mode "1", map != 0 to 255 */
/* PGM "L" */
for (y = 0; y < im->ysize; y++) {
fwrite(im->image[y], 1, im->xsize, fp);
}
-
} else {
/* PPM "RGB" or other internal format */
for (y = 0; y < im->ysize; y++) {
@@ -58,10 +57,10 @@ ImagingSavePPM(Imaging im, const char *outfile) {
return 0;
}
- if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) {
+ if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) {
/* Write "PGM" */
fprintf(fp, "P5\n%d %d\n255\n", im->xsize, im->ysize);
- } else if (strcmp(im->mode, "RGB") == 0) {
+ } else if (im->mode == IMAGING_MODE_RGB) {
/* Write "PPM" */
fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize);
} else {
diff --git a/src/libImaging/Fill.c b/src/libImaging/Fill.c
index 28f427370..cbd303204 100644
--- a/src/libImaging/Fill.c
+++ b/src/libImaging/Fill.c
@@ -68,11 +68,12 @@ ImagingFill(Imaging im, const void *colour) {
}
Imaging
-ImagingFillLinearGradient(const char *mode) {
+ImagingFillLinearGradient(const ModeID mode) {
Imaging im;
int y;
- if (strlen(mode) != 1) {
+ if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I &&
+ mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
return (Imaging)ImagingError_ModeError();
}
@@ -102,12 +103,13 @@ ImagingFillLinearGradient(const char *mode) {
}
Imaging
-ImagingFillRadialGradient(const char *mode) {
+ImagingFillRadialGradient(const ModeID mode) {
Imaging im;
int x, y;
int d;
- if (strlen(mode) != 1) {
+ if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I &&
+ mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
return (Imaging)ImagingError_ModeError();
}
diff --git a/src/libImaging/Filter.c b/src/libImaging/Filter.c
index c46dd3cd1..cefb8fcdc 100644
--- a/src/libImaging/Filter.c
+++ b/src/libImaging/Filter.c
@@ -156,9 +156,9 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
int bigendian = 0;
if (im->type == IMAGING_TYPE_SPECIAL) {
if (
- strcmp(im->mode, "I;16B") == 0
+ im->mode == IMAGING_MODE_I_16B
#ifdef WORDS_BIGENDIAN
- || strcmp(im->mode, "I;16N") == 0
+ || im->mode == IMAGING_MODE_I_16N
#endif
) {
bigendian = 1;
@@ -310,9 +310,9 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
int bigendian = 0;
if (im->type == IMAGING_TYPE_SPECIAL) {
if (
- strcmp(im->mode, "I;16B") == 0
+ im->mode == IMAGING_MODE_I_16B
#ifdef WORDS_BIGENDIAN
- || strcmp(im->mode, "I;16N") == 0
+ || im->mode == IMAGING_MODE_I_16N
#endif
) {
bigendian = 1;
diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c
index 130ecb7f7..44994823e 100644
--- a/src/libImaging/FliDecode.c
+++ b/src/libImaging/FliDecode.c
@@ -16,9 +16,11 @@
#include "Imaging.h"
-#define I16(ptr) ((ptr)[0] + ((ptr)[1] << 8))
+#define I16(ptr) ((ptr)[0] + ((int)(ptr)[1] << 8))
-#define I32(ptr) ((ptr)[0] + ((ptr)[1] << 8) + ((ptr)[2] << 16) + ((ptr)[3] << 24))
+#define I32(ptr) \
+ ((ptr)[0] + ((INT32)(ptr)[1] << 8) + ((INT32)(ptr)[2] << 16) + \
+ ((INT32)(ptr)[3] << 24))
#define ERR_IF_DATA_OOB(offset) \
if ((data + (offset)) > ptr + bytes) { \
diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c
index 1e2abd7e7..2186f95f8 100644
--- a/src/libImaging/Geometry.c
+++ b/src/libImaging/Geometry.c
@@ -19,7 +19,7 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn) {
ImagingSectionCookie cookie;
int x, y, xr;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) {
@@ -41,7 +41,7 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn) {
ImagingSectionEnter(&cookie);
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
FLIP_LEFT_RIGHT(UINT16, image8)
} else {
FLIP_LEFT_RIGHT(UINT8, image8)
@@ -62,7 +62,7 @@ ImagingFlipTopBottom(Imaging imOut, Imaging imIn) {
ImagingSectionCookie cookie;
int y, yr;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) {
@@ -89,7 +89,7 @@ ImagingRotate90(Imaging imOut, Imaging imIn) {
int x, y, xx, yy, xr, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
@@ -127,7 +127,7 @@ ImagingRotate90(Imaging imOut, Imaging imIn) {
ImagingSectionEnter(&cookie);
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
ROTATE_90(UINT16, image8);
} else {
ROTATE_90(UINT8, image8);
@@ -149,7 +149,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn) {
int x, y, xx, yy, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
@@ -186,7 +186,7 @@ ImagingTranspose(Imaging imOut, Imaging imIn) {
ImagingSectionEnter(&cookie);
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
TRANSPOSE(UINT16, image8);
} else {
TRANSPOSE(UINT8, image8);
@@ -208,7 +208,7 @@ ImagingTransverse(Imaging imOut, Imaging imIn) {
int x, y, xr, yr, xx, yy, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
@@ -247,7 +247,7 @@ ImagingTransverse(Imaging imOut, Imaging imIn) {
ImagingSectionEnter(&cookie);
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
TRANSVERSE(UINT16, image8);
} else {
TRANSVERSE(UINT8, image8);
@@ -268,7 +268,7 @@ ImagingRotate180(Imaging imOut, Imaging imIn) {
ImagingSectionCookie cookie;
int x, y, xr, yr;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) {
@@ -291,7 +291,7 @@ ImagingRotate180(Imaging imOut, Imaging imIn) {
yr = imIn->ysize - 1;
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
ROTATE_180(UINT16, image8)
} else {
ROTATE_180(UINT8, image8)
@@ -313,7 +313,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn) {
int x, y, xx, yy, yr, xxsize, yysize;
int xxx, yyy, xxxsize, yyysize;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) {
@@ -351,7 +351,7 @@ ImagingRotate270(Imaging imOut, Imaging imIn) {
ImagingSectionEnter(&cookie);
if (imIn->image8) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
ROTATE_270(UINT16, image8);
} else {
ROTATE_270(UINT8, image8);
@@ -714,14 +714,7 @@ getfilter(Imaging im, int filterid) {
case IMAGING_TYPE_UINT8:
return nearest_filter8;
case IMAGING_TYPE_SPECIAL:
- switch (im->pixelsize) {
- case 1:
- return nearest_filter8;
- case 2:
- return nearest_filter16;
- case 4:
- return nearest_filter32;
- }
+ return nearest_filter16;
}
} else {
return nearest_filter32;
@@ -791,7 +784,7 @@ ImagingGenericTransform(
char *out;
double xx, yy;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
@@ -848,7 +841,7 @@ ImagingScaleAffine(
int xmin, xmax;
int *xintab;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
@@ -1035,7 +1028,7 @@ ImagingTransformAffine(
double xx, yy;
double xo, yo;
- if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) {
+ if (!imOut || !imIn || imIn->mode != imOut->mode) {
return (Imaging)ImagingError_ModeError();
}
diff --git a/src/libImaging/GetBBox.c b/src/libImaging/GetBBox.c
index d430893dd..d336121d5 100644
--- a/src/libImaging/GetBBox.c
+++ b/src/libImaging/GetBBox.c
@@ -90,9 +90,9 @@ ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) {
if (im->bands == 3) {
((UINT8 *)&mask)[3] = 0;
} else if (alpha_only &&
- (strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 ||
- strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 ||
- strcmp(im->mode, "PA") == 0)) {
+ (im->mode == IMAGING_MODE_RGBa || im->mode == IMAGING_MODE_RGBA ||
+ im->mode == IMAGING_MODE_La || im->mode == IMAGING_MODE_LA ||
+ im->mode == IMAGING_MODE_PA)) {
#ifdef WORDS_BIGENDIAN
mask = 0x000000ff;
#else
@@ -208,11 +208,11 @@ ImagingGetExtrema(Imaging im, void *extrema) {
memcpy(((char *)extrema) + sizeof(fmin), &fmax, sizeof(fmax));
break;
case IMAGING_TYPE_SPECIAL:
- if (strcmp(im->mode, "I;16") == 0) {
+ if (im->mode == IMAGING_MODE_I_16) {
UINT16 v;
UINT8 *pixel = *im->image8;
#ifdef WORDS_BIGENDIAN
- v = pixel[0] + (pixel[1] << 8);
+ v = pixel[0] + ((UINT16)pixel[1] << 8);
#else
memcpy(&v, pixel, sizeof(v));
#endif
@@ -221,7 +221,7 @@ ImagingGetExtrema(Imaging im, void *extrema) {
for (x = 0; x < im->xsize; x++) {
pixel = (UINT8 *)im->image[y] + x * sizeof(v);
#ifdef WORDS_BIGENDIAN
- v = pixel[0] + (pixel[1] << 8);
+ v = pixel[0] + ((UINT16)pixel[1] << 8);
#else
memcpy(&v, pixel, sizeof(v));
#endif
diff --git a/src/libImaging/Histo.c b/src/libImaging/Histo.c
index c5a547a64..7af600035 100644
--- a/src/libImaging/Histo.c
+++ b/src/libImaging/Histo.c
@@ -43,10 +43,10 @@ ImagingHistogramNew(Imaging im) {
if (!h) {
return (ImagingHistogram)ImagingError_MemoryError();
}
- strncpy(h->mode, im->mode, IMAGING_MODE_LENGTH - 1);
- h->mode[IMAGING_MODE_LENGTH - 1] = 0;
+ h->mode = im->mode;
h->bands = im->bands;
+
h->histogram = calloc(im->pixelsize, 256 * sizeof(long));
if (!h->histogram) {
free(h);
@@ -73,7 +73,7 @@ ImagingGetHistogram(Imaging im, Imaging imMask, void *minmax) {
if (im->xsize != imMask->xsize || im->ysize != imMask->ysize) {
return ImagingError_Mismatch();
}
- if (strcmp(imMask->mode, "1") != 0 && strcmp(imMask->mode, "L") != 0) {
+ if (imMask->mode != IMAGING_MODE_1 && imMask->mode != IMAGING_MODE_L) {
return ImagingError_ValueError("bad transparency mask");
}
}
@@ -132,11 +132,15 @@ ImagingGetHistogram(Imaging im, Imaging imMask, void *minmax) {
ImagingSectionEnter(&cookie);
for (y = 0; y < im->ysize; y++) {
UINT8 *in = (UINT8 *)im->image[y];
- for (x = 0; x < im->xsize; x++) {
- h->histogram[(*in++)]++;
- h->histogram[(*in++) + 256]++;
- h->histogram[(*in++) + 512]++;
- h->histogram[(*in++) + 768]++;
+ for (x = 0; x < im->xsize; x++, in += 4) {
+ h->histogram[*in]++;
+ if (im->bands == 2) {
+ h->histogram[*(in + 3) + 256]++;
+ } else {
+ h->histogram[*(in + 1) + 256]++;
+ h->histogram[*(in + 2) + 512]++;
+ h->histogram[*(in + 3) + 768]++;
+ }
}
}
ImagingSectionLeave(&cookie);
diff --git a/src/libImaging/ImDib.h b/src/libImaging/ImDib.h
index 91ff3f322..65f090f92 100644
--- a/src/libImaging/ImDib.h
+++ b/src/libImaging/ImDib.h
@@ -27,7 +27,7 @@ struct ImagingDIBInstance {
UINT8 *bits;
HPALETTE palette;
/* Used by cut and paste */
- char mode[4];
+ ModeID mode;
int xsize, ysize;
int pixelsize;
int linesize;
@@ -37,11 +37,11 @@ struct ImagingDIBInstance {
typedef struct ImagingDIBInstance *ImagingDIB;
-extern char *
+extern ModeID
ImagingGetModeDIB(int size_out[2]);
extern ImagingDIB
-ImagingNewDIB(const char *mode, int xsize, int ysize);
+ImagingNewDIB(ModeID mode, int xsize, int ysize);
extern void
ImagingDeleteDIB(ImagingDIB im);
diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h
index bfe67d462..f7049c892 100644
--- a/src/libImaging/Imaging.h
+++ b/src/libImaging/Imaging.h
@@ -11,6 +11,7 @@
*/
#include "ImPlatform.h"
+#include "Mode.h"
#if defined(__cplusplus)
extern "C" {
@@ -71,9 +72,6 @@ typedef struct ImagingPaletteInstance *ImagingPalette;
#define IMAGING_TYPE_FLOAT32 2
#define IMAGING_TYPE_SPECIAL 3 /* check mode for details */
-#define IMAGING_MODE_LENGTH \
- 6 + 1 /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "BGR;xy") */
-
typedef struct {
char *ptr;
int size;
@@ -81,12 +79,11 @@ typedef struct {
struct ImagingMemoryInstance {
/* Format */
- char mode[IMAGING_MODE_LENGTH]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK",
- "YCbCr", "BGR;xy") */
- int type; /* Data type (IMAGING_TYPE_*) */
- int depth; /* Depth (ignored in this version) */
- int bands; /* Number of bands (1, 2, 3, or 4) */
- int xsize; /* Image dimension. */
+ ModeID mode; /* Image mode (IMAGING_MODE_*) */
+ int type; /* Data type (IMAGING_TYPE_*) */
+ int depth; /* Depth (ignored in this version) */
+ int bands; /* Number of bands (1, 2, 3, or 4) */
+ int xsize; /* Image dimension. */
int ysize;
/* Colour palette (for "P" images only) */
@@ -140,15 +137,15 @@ struct ImagingMemoryInstance {
#define IMAGING_PIXEL_FLOAT32(im, x, y) (((FLOAT32 *)(im)->image32[y])[x])
struct ImagingAccessInstance {
- const char *mode;
+ ModeID mode;
void (*get_pixel)(Imaging im, int x, int y, void *pixel);
void (*put_pixel)(Imaging im, int x, int y, const void *pixel);
};
struct ImagingHistogramInstance {
/* Format */
- char mode[IMAGING_MODE_LENGTH]; /* Band names (of corresponding source image) */
- int bands; /* Number of bands (1, 3, or 4) */
+ ModeID mode; /* Mode ID of corresponding source image */
+ int bands; /* Number of bands (1, 2, 3, or 4) */
/* Data */
long *histogram; /* Histogram (bands*256 longs) */
@@ -156,7 +153,7 @@ struct ImagingHistogramInstance {
struct ImagingPaletteInstance {
/* Format */
- char mode[IMAGING_MODE_LENGTH]; /* Band names */
+ ModeID mode;
/* Data */
int size;
@@ -196,20 +193,20 @@ extern void
ImagingMemorySetBlockAllocator(ImagingMemoryArena arena, int use_block_allocator);
extern Imaging
-ImagingNew(const char *mode, int xsize, int ysize);
+ImagingNew(ModeID mode, int xsize, int ysize);
extern Imaging
-ImagingNewDirty(const char *mode, int xsize, int ysize);
+ImagingNewDirty(ModeID mode, int xsize, int ysize);
extern Imaging
-ImagingNew2Dirty(const char *mode, Imaging imOut, Imaging imIn);
+ImagingNew2Dirty(ModeID mode, Imaging imOut, Imaging imIn);
extern void
ImagingDelete(Imaging im);
extern Imaging
-ImagingNewBlock(const char *mode, int xsize, int ysize);
+ImagingNewBlock(ModeID mode, int xsize, int ysize);
extern Imaging
ImagingNewArrow(
- const char *mode,
+ const ModeID mode,
int xsize,
int ysize,
PyObject *schema_capsule,
@@ -217,9 +214,9 @@ ImagingNewArrow(
);
extern Imaging
-ImagingNewPrologue(const char *mode, int xsize, int ysize);
+ImagingNewPrologue(ModeID mode, int xsize, int ysize);
extern Imaging
-ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int structure_size);
+ImagingNewPrologueSubtype(ModeID mode, int xsize, int ysize, int structure_size);
extern void
ImagingCopyPalette(Imaging destination, Imaging source);
@@ -227,8 +224,6 @@ ImagingCopyPalette(Imaging destination, Imaging source);
extern void
ImagingHistogramDelete(ImagingHistogram histogram);
-extern void
-ImagingAccessInit(void);
extern ImagingAccess
ImagingAccessNew(Imaging im);
extern void
@@ -236,7 +231,7 @@ _ImagingAccessDelete(Imaging im, ImagingAccess access);
#define ImagingAccessDelete(im, access) /* nop, for now */
extern ImagingPalette
-ImagingPaletteNew(const char *mode);
+ImagingPaletteNew(ModeID mode);
extern ImagingPalette
ImagingPaletteNewBrowser(void);
extern ImagingPalette
@@ -308,13 +303,13 @@ ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha);
extern Imaging
ImagingCopy(Imaging im);
extern Imaging
-ImagingConvert(Imaging im, const char *mode, ImagingPalette palette, int dither);
+ImagingConvert(Imaging im, ModeID mode, ImagingPalette palette, int dither);
extern Imaging
-ImagingConvertInPlace(Imaging im, const char *mode);
+ImagingConvertInPlace(Imaging im, ModeID mode);
extern Imaging
-ImagingConvertMatrix(Imaging im, const char *mode, float m[]);
+ImagingConvertMatrix(Imaging im, ModeID mode, float m[]);
extern Imaging
-ImagingConvertTransparent(Imaging im, const char *mode, int r, int g, int b);
+ImagingConvertTransparent(Imaging im, ModeID mode, int r, int g, int b);
extern Imaging
ImagingCrop(Imaging im, int x0, int y0, int x1, int y1);
extern Imaging
@@ -328,9 +323,9 @@ ImagingFill2(
extern Imaging
ImagingFillBand(Imaging im, int band, int color);
extern Imaging
-ImagingFillLinearGradient(const char *mode);
+ImagingFillLinearGradient(ModeID mode);
extern Imaging
-ImagingFillRadialGradient(const char *mode);
+ImagingFillRadialGradient(ModeID mode);
extern Imaging
ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 offset);
extern Imaging
@@ -344,7 +339,7 @@ ImagingGaussianBlur(
extern Imaging
ImagingGetBand(Imaging im, int band);
extern Imaging
-ImagingMerge(const char *mode, Imaging bands[4]);
+ImagingMerge(ModeID mode, Imaging bands[4]);
extern int
ImagingSplit(Imaging im, Imaging bands[4]);
extern int
@@ -371,7 +366,7 @@ ImagingOffset(Imaging im, int xoffset, int yoffset);
extern int
ImagingPaste(Imaging into, Imaging im, Imaging mask, int x0, int y0, int x1, int y1);
extern Imaging
-ImagingPoint(Imaging im, const char *tablemode, const void *table);
+ImagingPoint(Imaging im, ModeID tablemode, const void *table);
extern Imaging
ImagingPointTransform(Imaging imIn, double scale, double offset);
extern Imaging
@@ -712,9 +707,9 @@ extern void
ImagingConvertYCbCr2RGB(UINT8 *out, const UINT8 *in, int pixels);
extern ImagingShuffler
-ImagingFindUnpacker(const char *mode, const char *rawmode, int *bits_out);
+ImagingFindUnpacker(ModeID mode, RawModeID rawmode, int *bits_out);
extern ImagingShuffler
-ImagingFindPacker(const char *mode, const char *rawmode, int *bits_out);
+ImagingFindPacker(ModeID mode, RawModeID rawmode, int *bits_out);
struct ImagingCodecStateInstance {
int count;
diff --git a/src/libImaging/Jpeg.h b/src/libImaging/Jpeg.h
index 7cdba9022..e07904fc7 100644
--- a/src/libImaging/Jpeg.h
+++ b/src/libImaging/Jpeg.h
@@ -28,12 +28,12 @@ typedef struct {
typedef struct {
/* CONFIGURATION */
- /* Jpeg file mode (empty if not known) */
- char jpegmode[8 + 1];
+ /* Jpeg file mode */
+ RawModeID jpegmode;
- /* Converter output mode (input to the shuffler). If empty,
- convert conversions are disabled */
- char rawmode[8 + 1];
+ /* Converter output mode (input to the shuffler) */
+ /* If not a valid mode, convert conversions are disabled */
+ RawModeID rawmode;
/* If set, trade quality for speed */
int draft;
@@ -91,7 +91,7 @@ typedef struct {
unsigned int restart_marker_rows;
/* Converter input mode (input to the shuffler) */
- char rawmode[8 + 1];
+ RawModeID rawmode;
/* Custom quantization tables () */
unsigned int *qtables;
diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c
index cc6955ca5..1b496f45e 100644
--- a/src/libImaging/Jpeg2KDecode.c
+++ b/src/libImaging/Jpeg2KDecode.c
@@ -71,7 +71,7 @@ typedef void (*j2k_unpacker_t)(
);
struct j2k_decode_unpacker {
- const char *mode;
+ const ModeID mode;
OPJ_COLOR_SPACE color_space;
unsigned components;
/* bool indicating if unpacker supports subsampling */
@@ -599,26 +599,26 @@ j2ku_sycca_rgba(
}
static const struct j2k_decode_unpacker j2k_unpackers[] = {
- {"L", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l},
- {"P", OPJ_CLRSPC_SRGB, 1, 0, j2ku_gray_l},
- {"PA", OPJ_CLRSPC_SRGB, 2, 0, j2ku_graya_la},
- {"I;16", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
- {"I;16B", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
- {"LA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
- {"RGB", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
- {"RGB", OPJ_CLRSPC_GRAY, 2, 0, j2ku_gray_rgb},
- {"RGB", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
- {"RGB", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
- {"RGB", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgb_rgb},
- {"RGB", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycc_rgb},
- {"RGBA", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
- {"RGBA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
- {"RGBA", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
- {"RGBA", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
- {"RGBA", OPJ_CLRSPC_GRAY, 4, 1, j2ku_srgba_rgba},
- {"RGBA", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgba_rgba},
- {"RGBA", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycca_rgba},
- {"CMYK", OPJ_CLRSPC_CMYK, 4, 1, j2ku_srgba_rgba},
+ {IMAGING_MODE_L, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l},
+ {IMAGING_MODE_P, OPJ_CLRSPC_SRGB, 1, 0, j2ku_gray_l},
+ {IMAGING_MODE_PA, OPJ_CLRSPC_SRGB, 2, 0, j2ku_graya_la},
+ {IMAGING_MODE_I_16, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
+ {IMAGING_MODE_I_16B, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
+ {IMAGING_MODE_LA, OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_GRAY, 2, 0, j2ku_gray_rgb},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgb_rgb},
+ {IMAGING_MODE_RGB, OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycc_rgb},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_GRAY, 4, 1, j2ku_srgba_rgba},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgba_rgba},
+ {IMAGING_MODE_RGBA, OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycca_rgba},
+ {IMAGING_MODE_CMYK, OPJ_CLRSPC_CMYK, 4, 1, j2ku_srgba_rgba},
};
/* -------------------------------------------------------------------- */
@@ -771,7 +771,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
if (color_space == j2k_unpackers[n].color_space &&
image->numcomps == j2k_unpackers[n].components &&
(j2k_unpackers[n].subsampling || (subsampling == -1)) &&
- strcmp(im->mode, j2k_unpackers[n].mode) == 0) {
+ im->mode == j2k_unpackers[n].mode) {
unpack = j2k_unpackers[n].unpacker;
break;
}
diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c
index 61e095ad6..fdfbde2d7 100644
--- a/src/libImaging/Jpeg2KEncode.c
+++ b/src/libImaging/Jpeg2KEncode.c
@@ -305,34 +305,34 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
#endif
/* Setup an opj_image */
- if (strcmp(im->mode, "L") == 0) {
+ if (im->mode == IMAGING_MODE_L) {
components = 1;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_l;
- } else if (strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16B") == 0) {
+ } else if (im->mode == IMAGING_MODE_I_16 || im->mode == IMAGING_MODE_I_16B) {
components = 1;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_i16;
prec = 16;
- } else if (strcmp(im->mode, "LA") == 0) {
+ } else if (im->mode == IMAGING_MODE_LA) {
components = 2;
color_space = OPJ_CLRSPC_GRAY;
pack = j2k_pack_la;
- } else if (strcmp(im->mode, "RGB") == 0) {
+ } else if (im->mode == IMAGING_MODE_RGB) {
components = 3;
color_space = OPJ_CLRSPC_SRGB;
pack = j2k_pack_rgb;
- } else if (strcmp(im->mode, "YCbCr") == 0) {
+ } else if (im->mode == IMAGING_MODE_YCbCr) {
components = 3;
color_space = OPJ_CLRSPC_SYCC;
pack = j2k_pack_rgb;
- } else if (strcmp(im->mode, "RGBA") == 0) {
+ } else if (im->mode == IMAGING_MODE_RGBA) {
components = 4;
color_space = OPJ_CLRSPC_SRGB;
pack = j2k_pack_rgba;
#if ((OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR == 5 && OPJ_VERSION_BUILD >= 3) || \
(OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR > 5) || OPJ_VERSION_MAJOR > 2)
- } else if (strcmp(im->mode, "CMYK") == 0) {
+ } else if (im->mode == IMAGING_MODE_CMYK) {
components = 4;
color_space = OPJ_CLRSPC_CMYK;
pack = j2k_pack_rgba;
@@ -497,9 +497,9 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
goto quick_exit;
}
- if (strcmp(im->mode, "RGBA") == 0) {
+ if (im->mode == IMAGING_MODE_RGBA) {
image->comps[3].alpha = 1;
- } else if (strcmp(im->mode, "LA") == 0) {
+ } else if (im->mode == IMAGING_MODE_LA) {
image->comps[1].alpha = 1;
}
diff --git a/src/libImaging/JpegDecode.c b/src/libImaging/JpegDecode.c
index 2970f56d1..ae3274456 100644
--- a/src/libImaging/JpegDecode.c
+++ b/src/libImaging/JpegDecode.c
@@ -180,41 +180,41 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by
/* Decoder settings */
- /* jpegmode indicates what's in the file; if not set, we'll
- trust the decoder */
- if (strcmp(context->jpegmode, "L") == 0) {
+ /* jpegmode indicates what's in the file. */
+ /* If not valid, we'll trust the decoder. */
+ if (context->jpegmode == IMAGING_RAWMODE_L) {
context->cinfo.jpeg_color_space = JCS_GRAYSCALE;
- } else if (strcmp(context->jpegmode, "RGB") == 0) {
+ } else if (context->jpegmode == IMAGING_RAWMODE_RGB) {
context->cinfo.jpeg_color_space = JCS_RGB;
- } else if (strcmp(context->jpegmode, "CMYK") == 0) {
+ } else if (context->jpegmode == IMAGING_RAWMODE_CMYK) {
context->cinfo.jpeg_color_space = JCS_CMYK;
- } else if (strcmp(context->jpegmode, "YCbCr") == 0) {
+ } else if (context->jpegmode == IMAGING_RAWMODE_YCbCr) {
context->cinfo.jpeg_color_space = JCS_YCbCr;
- } else if (strcmp(context->jpegmode, "YCbCrK") == 0) {
+ } else if (context->jpegmode == IMAGING_RAWMODE_YCbCrK) {
context->cinfo.jpeg_color_space = JCS_YCCK;
}
- /* rawmode indicates what we want from the decoder. if not
- set, conversions are disabled */
- if (strcmp(context->rawmode, "L") == 0) {
+ /* rawmode indicates what we want from the decoder. */
+ /* If not valid, conversions are disabled. */
+ if (context->rawmode == IMAGING_RAWMODE_L) {
context->cinfo.out_color_space = JCS_GRAYSCALE;
- } else if (strcmp(context->rawmode, "RGB") == 0) {
+ } else if (context->rawmode == IMAGING_RAWMODE_RGB) {
context->cinfo.out_color_space = JCS_RGB;
}
#ifdef JCS_EXTENSIONS
- else if (strcmp(context->rawmode, "RGBX") == 0) {
+ else if (context->rawmode == IMAGING_RAWMODE_RGBX) {
context->cinfo.out_color_space = JCS_EXT_RGBX;
}
#endif
- else if (strcmp(context->rawmode, "CMYK") == 0 ||
- strcmp(context->rawmode, "CMYK;I") == 0) {
+ else if (context->rawmode == IMAGING_RAWMODE_CMYK ||
+ context->rawmode == IMAGING_RAWMODE_CMYK_I) {
context->cinfo.out_color_space = JCS_CMYK;
- } else if (strcmp(context->rawmode, "YCbCr") == 0) {
+ } else if (context->rawmode == IMAGING_RAWMODE_YCbCr) {
context->cinfo.out_color_space = JCS_YCbCr;
- } else if (strcmp(context->rawmode, "YCbCrK") == 0) {
+ } else if (context->rawmode == IMAGING_RAWMODE_YCbCrK) {
context->cinfo.out_color_space = JCS_YCCK;
} else {
- /* Disable decoder conversions */
+ /* Disable decoder conversions. */
context->cinfo.jpeg_color_space = JCS_UNKNOWN;
context->cinfo.out_color_space = JCS_UNKNOWN;
}
diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c
index 972435ee1..098e431fc 100644
--- a/src/libImaging/JpegEncode.c
+++ b/src/libImaging/JpegEncode.c
@@ -114,7 +114,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
break;
case 24:
context->cinfo.input_components = 3;
- if (strcmp(im->mode, "YCbCr") == 0) {
+ if (im->mode == IMAGING_MODE_YCbCr) {
context->cinfo.in_color_space = JCS_YCbCr;
} else {
context->cinfo.in_color_space = JCS_RGB;
@@ -124,7 +124,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
context->cinfo.input_components = 4;
context->cinfo.in_color_space = JCS_CMYK;
#ifdef JCS_EXTENSIONS
- if (strcmp(context->rawmode, "RGBX") == 0) {
+ if (context->rawmode == IMAGING_RAWMODE_RGBX) {
context->cinfo.in_color_space = JCS_EXT_RGBX;
}
#endif
diff --git a/src/libImaging/Matrix.c b/src/libImaging/Matrix.c
index ec7f4d93e..d28e04edf 100644
--- a/src/libImaging/Matrix.c
+++ b/src/libImaging/Matrix.c
@@ -18,7 +18,7 @@
#define CLIPF(v) ((v <= 0.0) ? 0 : (v >= 255.0F) ? 255 : (UINT8)v)
Imaging
-ImagingConvertMatrix(Imaging im, const char *mode, float m[]) {
+ImagingConvertMatrix(Imaging im, const ModeID mode, float m[]) {
Imaging imOut;
int x, y;
ImagingSectionCookie cookie;
@@ -28,8 +28,8 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) {
return (Imaging)ImagingError_ModeError();
}
- if (strcmp(mode, "L") == 0) {
- imOut = ImagingNewDirty("L", im->xsize, im->ysize);
+ if (mode == IMAGING_MODE_L) {
+ imOut = ImagingNewDirty(IMAGING_MODE_L, im->xsize, im->ysize);
if (!imOut) {
return NULL;
}
@@ -46,8 +46,8 @@ ImagingConvertMatrix(Imaging im, const char *mode, float m[]) {
}
}
ImagingSectionLeave(&cookie);
-
- } else if (strlen(mode) == 3) {
+ } else if (mode == IMAGING_MODE_HSV || mode == IMAGING_MODE_LAB ||
+ mode == IMAGING_MODE_RGB) {
imOut = ImagingNewDirty(mode, im->xsize, im->ysize);
if (!imOut) {
return NULL;
diff --git a/src/libImaging/Mode.c b/src/libImaging/Mode.c
new file mode 100644
index 000000000..2e459c48f
--- /dev/null
+++ b/src/libImaging/Mode.c
@@ -0,0 +1,257 @@
+#include "Mode.h"
+#include
+
+#ifdef NDEBUG
+#include
+#include
+#endif
+
+const ModeData MODES[] = {
+ [IMAGING_MODE_UNKNOWN] = {""},
+
+ [IMAGING_MODE_1] = {"1"}, [IMAGING_MODE_CMYK] = {"CMYK"},
+ [IMAGING_MODE_F] = {"F"}, [IMAGING_MODE_HSV] = {"HSV"},
+ [IMAGING_MODE_I] = {"I"}, [IMAGING_MODE_L] = {"L"},
+ [IMAGING_MODE_LA] = {"LA"}, [IMAGING_MODE_LAB] = {"LAB"},
+ [IMAGING_MODE_La] = {"La"}, [IMAGING_MODE_P] = {"P"},
+ [IMAGING_MODE_PA] = {"PA"}, [IMAGING_MODE_RGB] = {"RGB"},
+ [IMAGING_MODE_RGBA] = {"RGBA"}, [IMAGING_MODE_RGBX] = {"RGBX"},
+ [IMAGING_MODE_RGBa] = {"RGBa"}, [IMAGING_MODE_YCbCr] = {"YCbCr"},
+
+ [IMAGING_MODE_I_16] = {"I;16"}, [IMAGING_MODE_I_16L] = {"I;16L"},
+ [IMAGING_MODE_I_16B] = {"I;16B"}, [IMAGING_MODE_I_16N] = {"I;16N"},
+};
+
+const ModeID
+findModeID(const char *const name) {
+ if (name == NULL) {
+ return IMAGING_MODE_UNKNOWN;
+ }
+ for (size_t i = 0; i < sizeof(MODES) / sizeof(*MODES); i++) {
+#ifdef NDEBUG
+ if (MODES[i].name == NULL) {
+ fprintf(stderr, "Mode ID %zu is not defined.\n", (size_t)i);
+ } else
+#endif
+ if (strcmp(MODES[i].name, name) == 0) {
+ return (ModeID)i;
+ }
+ }
+ return IMAGING_MODE_UNKNOWN;
+}
+
+const ModeData *const
+getModeData(const ModeID id) {
+ if (id < 0 || id > sizeof(MODES) / sizeof(*MODES)) {
+ return &MODES[IMAGING_MODE_UNKNOWN];
+ }
+ return &MODES[id];
+}
+
+const RawModeData RAWMODES[] = {
+ [IMAGING_RAWMODE_UNKNOWN] = {""},
+
+ [IMAGING_RAWMODE_1] = {"1"},
+ [IMAGING_RAWMODE_CMYK] = {"CMYK"},
+ [IMAGING_RAWMODE_F] = {"F"},
+ [IMAGING_RAWMODE_HSV] = {"HSV"},
+ [IMAGING_RAWMODE_I] = {"I"},
+ [IMAGING_RAWMODE_L] = {"L"},
+ [IMAGING_RAWMODE_LA] = {"LA"},
+ [IMAGING_RAWMODE_LAB] = {"LAB"},
+ [IMAGING_RAWMODE_La] = {"La"},
+ [IMAGING_RAWMODE_P] = {"P"},
+ [IMAGING_RAWMODE_PA] = {"PA"},
+ [IMAGING_RAWMODE_RGB] = {"RGB"},
+ [IMAGING_RAWMODE_RGBA] = {"RGBA"},
+ [IMAGING_RAWMODE_RGBX] = {"RGBX"},
+ [IMAGING_RAWMODE_RGBa] = {"RGBa"},
+ [IMAGING_RAWMODE_YCbCr] = {"YCbCr"},
+
+ [IMAGING_RAWMODE_BGR_15] = {"BGR;15"},
+ [IMAGING_RAWMODE_BGR_16] = {"BGR;16"},
+
+ [IMAGING_RAWMODE_I_16] = {"I;16"},
+ [IMAGING_RAWMODE_I_16L] = {"I;16L"},
+ [IMAGING_RAWMODE_I_16B] = {"I;16B"},
+ [IMAGING_RAWMODE_I_16N] = {"I;16N"},
+ [IMAGING_RAWMODE_I_32B] = {"I;32B"},
+
+ [IMAGING_RAWMODE_1_8] = {"1;8"},
+ [IMAGING_RAWMODE_1_I] = {"1;I"},
+ [IMAGING_RAWMODE_1_IR] = {"1;IR"},
+ [IMAGING_RAWMODE_1_R] = {"1;R"},
+ [IMAGING_RAWMODE_A] = {"A"},
+ [IMAGING_RAWMODE_ABGR] = {"ABGR"},
+ [IMAGING_RAWMODE_ARGB] = {"ARGB"},
+ [IMAGING_RAWMODE_A_16B] = {"A;16B"},
+ [IMAGING_RAWMODE_A_16L] = {"A;16L"},
+ [IMAGING_RAWMODE_A_16N] = {"A;16N"},
+ [IMAGING_RAWMODE_B] = {"B"},
+ [IMAGING_RAWMODE_BGAR] = {"BGAR"},
+ [IMAGING_RAWMODE_BGR] = {"BGR"},
+ [IMAGING_RAWMODE_BGRA] = {"BGRA"},
+ [IMAGING_RAWMODE_BGRA_15] = {"BGRA;15"},
+ [IMAGING_RAWMODE_BGRA_15Z] = {"BGRA;15Z"},
+ [IMAGING_RAWMODE_BGRA_16B] = {"BGRA;16B"},
+ [IMAGING_RAWMODE_BGRA_16L] = {"BGRA;16L"},
+ [IMAGING_RAWMODE_BGRX] = {"BGRX"},
+ [IMAGING_RAWMODE_BGR_5] = {"BGR;5"},
+ [IMAGING_RAWMODE_BGRa] = {"BGRa"},
+ [IMAGING_RAWMODE_BGXR] = {"BGXR"},
+ [IMAGING_RAWMODE_B_16B] = {"B;16B"},
+ [IMAGING_RAWMODE_B_16L] = {"B;16L"},
+ [IMAGING_RAWMODE_B_16N] = {"B;16N"},
+ [IMAGING_RAWMODE_C] = {"C"},
+ [IMAGING_RAWMODE_CMYKX] = {"CMYKX"},
+ [IMAGING_RAWMODE_CMYKXX] = {"CMYKXX"},
+ [IMAGING_RAWMODE_CMYK_16B] = {"CMYK;16B"},
+ [IMAGING_RAWMODE_CMYK_16L] = {"CMYK;16L"},
+ [IMAGING_RAWMODE_CMYK_16N] = {"CMYK;16N"},
+ [IMAGING_RAWMODE_CMYK_I] = {"CMYK;I"},
+ [IMAGING_RAWMODE_CMYK_L] = {"CMYK;L"},
+ [IMAGING_RAWMODE_C_I] = {"C;I"},
+ [IMAGING_RAWMODE_Cb] = {"Cb"},
+ [IMAGING_RAWMODE_Cr] = {"Cr"},
+ [IMAGING_RAWMODE_F_16] = {"F;16"},
+ [IMAGING_RAWMODE_F_16B] = {"F;16B"},
+ [IMAGING_RAWMODE_F_16BS] = {"F;16BS"},
+ [IMAGING_RAWMODE_F_16N] = {"F;16N"},
+ [IMAGING_RAWMODE_F_16NS] = {"F;16NS"},
+ [IMAGING_RAWMODE_F_16S] = {"F;16S"},
+ [IMAGING_RAWMODE_F_32] = {"F;32"},
+ [IMAGING_RAWMODE_F_32B] = {"F;32B"},
+ [IMAGING_RAWMODE_F_32BF] = {"F;32BF"},
+ [IMAGING_RAWMODE_F_32BS] = {"F;32BS"},
+ [IMAGING_RAWMODE_F_32F] = {"F;32F"},
+ [IMAGING_RAWMODE_F_32N] = {"F;32N"},
+ [IMAGING_RAWMODE_F_32NF] = {"F;32NF"},
+ [IMAGING_RAWMODE_F_32NS] = {"F;32NS"},
+ [IMAGING_RAWMODE_F_32S] = {"F;32S"},
+ [IMAGING_RAWMODE_F_64BF] = {"F;64BF"},
+ [IMAGING_RAWMODE_F_64F] = {"F;64F"},
+ [IMAGING_RAWMODE_F_64NF] = {"F;64NF"},
+ [IMAGING_RAWMODE_F_8] = {"F;8"},
+ [IMAGING_RAWMODE_F_8S] = {"F;8S"},
+ [IMAGING_RAWMODE_G] = {"G"},
+ [IMAGING_RAWMODE_G_16B] = {"G;16B"},
+ [IMAGING_RAWMODE_G_16L] = {"G;16L"},
+ [IMAGING_RAWMODE_G_16N] = {"G;16N"},
+ [IMAGING_RAWMODE_H] = {"H"},
+ [IMAGING_RAWMODE_I_12] = {"I;12"},
+ [IMAGING_RAWMODE_I_16BS] = {"I;16BS"},
+ [IMAGING_RAWMODE_I_16NS] = {"I;16NS"},
+ [IMAGING_RAWMODE_I_16R] = {"I;16R"},
+ [IMAGING_RAWMODE_I_16S] = {"I;16S"},
+ [IMAGING_RAWMODE_I_32] = {"I;32"},
+ [IMAGING_RAWMODE_I_32BS] = {"I;32BS"},
+ [IMAGING_RAWMODE_I_32N] = {"I;32N"},
+ [IMAGING_RAWMODE_I_32NS] = {"I;32NS"},
+ [IMAGING_RAWMODE_I_32S] = {"I;32S"},
+ [IMAGING_RAWMODE_I_8] = {"I;8"},
+ [IMAGING_RAWMODE_I_8S] = {"I;8S"},
+ [IMAGING_RAWMODE_K] = {"K"},
+ [IMAGING_RAWMODE_K_I] = {"K;I"},
+ [IMAGING_RAWMODE_LA_16B] = {"LA;16B"},
+ [IMAGING_RAWMODE_LA_L] = {"LA;L"},
+ [IMAGING_RAWMODE_L_16] = {"L;16"},
+ [IMAGING_RAWMODE_L_16B] = {"L;16B"},
+ [IMAGING_RAWMODE_L_2] = {"L;2"},
+ [IMAGING_RAWMODE_L_2I] = {"L;2I"},
+ [IMAGING_RAWMODE_L_2IR] = {"L;2IR"},
+ [IMAGING_RAWMODE_L_2R] = {"L;2R"},
+ [IMAGING_RAWMODE_L_4] = {"L;4"},
+ [IMAGING_RAWMODE_L_4I] = {"L;4I"},
+ [IMAGING_RAWMODE_L_4IR] = {"L;4IR"},
+ [IMAGING_RAWMODE_L_4R] = {"L;4R"},
+ [IMAGING_RAWMODE_L_I] = {"L;I"},
+ [IMAGING_RAWMODE_L_R] = {"L;R"},
+ [IMAGING_RAWMODE_M] = {"M"},
+ [IMAGING_RAWMODE_M_I] = {"M;I"},
+ [IMAGING_RAWMODE_PA_L] = {"PA;L"},
+ [IMAGING_RAWMODE_PX] = {"PX"},
+ [IMAGING_RAWMODE_P_1] = {"P;1"},
+ [IMAGING_RAWMODE_P_2] = {"P;2"},
+ [IMAGING_RAWMODE_P_2L] = {"P;2L"},
+ [IMAGING_RAWMODE_P_4] = {"P;4"},
+ [IMAGING_RAWMODE_P_4L] = {"P;4L"},
+ [IMAGING_RAWMODE_P_R] = {"P;R"},
+ [IMAGING_RAWMODE_R] = {"R"},
+ [IMAGING_RAWMODE_RGBAX] = {"RGBAX"},
+ [IMAGING_RAWMODE_RGBAXX] = {"RGBAXX"},
+ [IMAGING_RAWMODE_RGBA_15] = {"RGBA;15"},
+ [IMAGING_RAWMODE_RGBA_16B] = {"RGBA;16B"},
+ [IMAGING_RAWMODE_RGBA_16L] = {"RGBA;16L"},
+ [IMAGING_RAWMODE_RGBA_16N] = {"RGBA;16N"},
+ [IMAGING_RAWMODE_RGBA_4B] = {"RGBA;4B"},
+ [IMAGING_RAWMODE_RGBA_I] = {"RGBA;I"},
+ [IMAGING_RAWMODE_RGBA_L] = {"RGBA;L"},
+ [IMAGING_RAWMODE_RGBXX] = {"RGBXX"},
+ [IMAGING_RAWMODE_RGBXXX] = {"RGBXXX"},
+ [IMAGING_RAWMODE_RGBX_16B] = {"RGBX;16B"},
+ [IMAGING_RAWMODE_RGBX_16L] = {"RGBX;16L"},
+ [IMAGING_RAWMODE_RGBX_16N] = {"RGBX;16N"},
+ [IMAGING_RAWMODE_RGBX_L] = {"RGBX;L"},
+ [IMAGING_RAWMODE_RGB_15] = {"RGB;15"},
+ [IMAGING_RAWMODE_RGB_16] = {"RGB;16"},
+ [IMAGING_RAWMODE_RGB_16B] = {"RGB;16B"},
+ [IMAGING_RAWMODE_RGB_16L] = {"RGB;16L"},
+ [IMAGING_RAWMODE_RGB_16N] = {"RGB;16N"},
+ [IMAGING_RAWMODE_RGB_4B] = {"RGB;4B"},
+ [IMAGING_RAWMODE_RGB_L] = {"RGB;L"},
+ [IMAGING_RAWMODE_RGB_R] = {"RGB;R"},
+ [IMAGING_RAWMODE_RGBaX] = {"RGBaX"},
+ [IMAGING_RAWMODE_RGBaXX] = {"RGBaXX"},
+ [IMAGING_RAWMODE_RGBa_16B] = {"RGBa;16B"},
+ [IMAGING_RAWMODE_RGBa_16L] = {"RGBa;16L"},
+ [IMAGING_RAWMODE_RGBa_16N] = {"RGBa;16N"},
+ [IMAGING_RAWMODE_R_16B] = {"R;16B"},
+ [IMAGING_RAWMODE_R_16L] = {"R;16L"},
+ [IMAGING_RAWMODE_R_16N] = {"R;16N"},
+ [IMAGING_RAWMODE_S] = {"S"},
+ [IMAGING_RAWMODE_V] = {"V"},
+ [IMAGING_RAWMODE_X] = {"X"},
+ [IMAGING_RAWMODE_XBGR] = {"XBGR"},
+ [IMAGING_RAWMODE_XRGB] = {"XRGB"},
+ [IMAGING_RAWMODE_Y] = {"Y"},
+ [IMAGING_RAWMODE_YCCA_P] = {"YCCA;P"},
+ [IMAGING_RAWMODE_YCC_P] = {"YCC;P"},
+ [IMAGING_RAWMODE_YCbCrK] = {"YCbCrK"},
+ [IMAGING_RAWMODE_YCbCrX] = {"YCbCrX"},
+ [IMAGING_RAWMODE_YCbCr_L] = {"YCbCr;L"},
+ [IMAGING_RAWMODE_Y_I] = {"Y;I"},
+ [IMAGING_RAWMODE_aBGR] = {"aBGR"},
+ [IMAGING_RAWMODE_aRGB] = {"aRGB"},
+};
+
+const RawModeID
+findRawModeID(const char *const name) {
+ if (name == NULL) {
+ return IMAGING_RAWMODE_UNKNOWN;
+ }
+ for (size_t i = 0; i < sizeof(RAWMODES) / sizeof(*RAWMODES); i++) {
+#ifdef NDEBUG
+ if (RAWMODES[i].name == NULL) {
+ fprintf(stderr, "Rawmode ID %zu is not defined.\n", (size_t)i);
+ } else
+#endif
+ if (strcmp(RAWMODES[i].name, name) == 0) {
+ return (RawModeID)i;
+ }
+ }
+ return IMAGING_RAWMODE_UNKNOWN;
+}
+
+const RawModeData *const
+getRawModeData(const RawModeID id) {
+ if (id < 0 || id > sizeof(RAWMODES) / sizeof(*RAWMODES)) {
+ return &RAWMODES[IMAGING_RAWMODE_UNKNOWN];
+ }
+ return &RAWMODES[id];
+}
+
+int
+isModeI16(const ModeID mode) {
+ return mode == IMAGING_MODE_I_16 || mode == IMAGING_MODE_I_16L ||
+ mode == IMAGING_MODE_I_16B || mode == IMAGING_MODE_I_16N;
+}
diff --git a/src/libImaging/Mode.h b/src/libImaging/Mode.h
new file mode 100644
index 000000000..39c0eb919
--- /dev/null
+++ b/src/libImaging/Mode.h
@@ -0,0 +1,229 @@
+#ifndef __MODE_H__
+#define __MODE_H__
+
+typedef enum {
+ IMAGING_MODE_UNKNOWN,
+
+ IMAGING_MODE_1,
+ IMAGING_MODE_CMYK,
+ IMAGING_MODE_F,
+ IMAGING_MODE_HSV,
+ IMAGING_MODE_I,
+ IMAGING_MODE_L,
+ IMAGING_MODE_LA,
+ IMAGING_MODE_LAB,
+ IMAGING_MODE_La,
+ IMAGING_MODE_P,
+ IMAGING_MODE_PA,
+ IMAGING_MODE_RGB,
+ IMAGING_MODE_RGBA,
+ IMAGING_MODE_RGBX,
+ IMAGING_MODE_RGBa,
+ IMAGING_MODE_YCbCr,
+
+ IMAGING_MODE_I_16,
+ IMAGING_MODE_I_16L,
+ IMAGING_MODE_I_16B,
+ IMAGING_MODE_I_16N,
+} ModeID;
+
+typedef struct {
+ const char *const name;
+} ModeData;
+
+const ModeID
+findModeID(const char *const name);
+const ModeData *const
+getModeData(const ModeID id);
+
+typedef enum {
+ IMAGING_RAWMODE_UNKNOWN,
+
+ // Non-rawmode aliases.
+ IMAGING_RAWMODE_1,
+ IMAGING_RAWMODE_CMYK,
+ IMAGING_RAWMODE_F,
+ IMAGING_RAWMODE_HSV,
+ IMAGING_RAWMODE_I,
+ IMAGING_RAWMODE_L,
+ IMAGING_RAWMODE_LA,
+ IMAGING_RAWMODE_LAB,
+ IMAGING_RAWMODE_La,
+ IMAGING_RAWMODE_P,
+ IMAGING_RAWMODE_PA,
+ IMAGING_RAWMODE_RGB,
+ IMAGING_RAWMODE_RGBA,
+ IMAGING_RAWMODE_RGBX,
+ IMAGING_RAWMODE_RGBa,
+ IMAGING_RAWMODE_YCbCr,
+
+ // I;* modes.
+ IMAGING_RAWMODE_I_16,
+ IMAGING_RAWMODE_I_16L,
+ IMAGING_RAWMODE_I_16B,
+ IMAGING_RAWMODE_I_16N,
+
+ // Rawmodes
+ IMAGING_RAWMODE_1_8,
+ IMAGING_RAWMODE_1_I,
+ IMAGING_RAWMODE_1_IR,
+ IMAGING_RAWMODE_1_R,
+ IMAGING_RAWMODE_A,
+ IMAGING_RAWMODE_ABGR,
+ IMAGING_RAWMODE_ARGB,
+ IMAGING_RAWMODE_A_16B,
+ IMAGING_RAWMODE_A_16L,
+ IMAGING_RAWMODE_A_16N,
+ IMAGING_RAWMODE_B,
+ IMAGING_RAWMODE_BGAR,
+ IMAGING_RAWMODE_BGR,
+ IMAGING_RAWMODE_BGRA,
+ IMAGING_RAWMODE_BGRA_15,
+ IMAGING_RAWMODE_BGRA_15Z,
+ IMAGING_RAWMODE_BGRA_16B,
+ IMAGING_RAWMODE_BGRA_16L,
+ IMAGING_RAWMODE_BGRX,
+ IMAGING_RAWMODE_BGR_5,
+ IMAGING_RAWMODE_BGR_15,
+ IMAGING_RAWMODE_BGR_16,
+ IMAGING_RAWMODE_BGRa,
+ IMAGING_RAWMODE_BGXR,
+ IMAGING_RAWMODE_B_16B,
+ IMAGING_RAWMODE_B_16L,
+ IMAGING_RAWMODE_B_16N,
+ IMAGING_RAWMODE_C,
+ IMAGING_RAWMODE_CMYKX,
+ IMAGING_RAWMODE_CMYKXX,
+ IMAGING_RAWMODE_CMYK_16B,
+ IMAGING_RAWMODE_CMYK_16L,
+ IMAGING_RAWMODE_CMYK_16N,
+ IMAGING_RAWMODE_CMYK_I,
+ IMAGING_RAWMODE_CMYK_L,
+ IMAGING_RAWMODE_C_I,
+ IMAGING_RAWMODE_Cb,
+ IMAGING_RAWMODE_Cr,
+ IMAGING_RAWMODE_I_32B,
+ IMAGING_RAWMODE_F_16,
+ IMAGING_RAWMODE_F_16B,
+ IMAGING_RAWMODE_F_16BS,
+ IMAGING_RAWMODE_F_16N,
+ IMAGING_RAWMODE_F_16NS,
+ IMAGING_RAWMODE_F_16S,
+ IMAGING_RAWMODE_F_32,
+ IMAGING_RAWMODE_F_32B,
+ IMAGING_RAWMODE_F_32BF,
+ IMAGING_RAWMODE_F_32BS,
+ IMAGING_RAWMODE_F_32F,
+ IMAGING_RAWMODE_F_32N,
+ IMAGING_RAWMODE_F_32NF,
+ IMAGING_RAWMODE_F_32NS,
+ IMAGING_RAWMODE_F_32S,
+ IMAGING_RAWMODE_F_64BF,
+ IMAGING_RAWMODE_F_64F,
+ IMAGING_RAWMODE_F_64NF,
+ IMAGING_RAWMODE_F_8,
+ IMAGING_RAWMODE_F_8S,
+ IMAGING_RAWMODE_G,
+ IMAGING_RAWMODE_G_16B,
+ IMAGING_RAWMODE_G_16L,
+ IMAGING_RAWMODE_G_16N,
+ IMAGING_RAWMODE_H,
+ IMAGING_RAWMODE_I_12,
+ IMAGING_RAWMODE_I_16BS,
+ IMAGING_RAWMODE_I_16NS,
+ IMAGING_RAWMODE_I_16R,
+ IMAGING_RAWMODE_I_16S,
+ IMAGING_RAWMODE_I_32,
+ IMAGING_RAWMODE_I_32BS,
+ IMAGING_RAWMODE_I_32N,
+ IMAGING_RAWMODE_I_32NS,
+ IMAGING_RAWMODE_I_32S,
+ IMAGING_RAWMODE_I_8,
+ IMAGING_RAWMODE_I_8S,
+ IMAGING_RAWMODE_K,
+ IMAGING_RAWMODE_K_I,
+ IMAGING_RAWMODE_LA_16B,
+ IMAGING_RAWMODE_LA_L,
+ IMAGING_RAWMODE_L_16,
+ IMAGING_RAWMODE_L_16B,
+ IMAGING_RAWMODE_L_2,
+ IMAGING_RAWMODE_L_2I,
+ IMAGING_RAWMODE_L_2IR,
+ IMAGING_RAWMODE_L_2R,
+ IMAGING_RAWMODE_L_4,
+ IMAGING_RAWMODE_L_4I,
+ IMAGING_RAWMODE_L_4IR,
+ IMAGING_RAWMODE_L_4R,
+ IMAGING_RAWMODE_L_I,
+ IMAGING_RAWMODE_L_R,
+ IMAGING_RAWMODE_M,
+ IMAGING_RAWMODE_M_I,
+ IMAGING_RAWMODE_PA_L,
+ IMAGING_RAWMODE_PX,
+ IMAGING_RAWMODE_P_1,
+ IMAGING_RAWMODE_P_2,
+ IMAGING_RAWMODE_P_2L,
+ IMAGING_RAWMODE_P_4,
+ IMAGING_RAWMODE_P_4L,
+ IMAGING_RAWMODE_P_R,
+ IMAGING_RAWMODE_R,
+ IMAGING_RAWMODE_RGBAX,
+ IMAGING_RAWMODE_RGBAXX,
+ IMAGING_RAWMODE_RGBA_15,
+ IMAGING_RAWMODE_RGBA_16B,
+ IMAGING_RAWMODE_RGBA_16L,
+ IMAGING_RAWMODE_RGBA_16N,
+ IMAGING_RAWMODE_RGBA_4B,
+ IMAGING_RAWMODE_RGBA_I,
+ IMAGING_RAWMODE_RGBA_L,
+ IMAGING_RAWMODE_RGBXX,
+ IMAGING_RAWMODE_RGBXXX,
+ IMAGING_RAWMODE_RGBX_16B,
+ IMAGING_RAWMODE_RGBX_16L,
+ IMAGING_RAWMODE_RGBX_16N,
+ IMAGING_RAWMODE_RGBX_L,
+ IMAGING_RAWMODE_RGB_15,
+ IMAGING_RAWMODE_RGB_16,
+ IMAGING_RAWMODE_RGB_16B,
+ IMAGING_RAWMODE_RGB_16L,
+ IMAGING_RAWMODE_RGB_16N,
+ IMAGING_RAWMODE_RGB_4B,
+ IMAGING_RAWMODE_RGB_L,
+ IMAGING_RAWMODE_RGB_R,
+ IMAGING_RAWMODE_RGBaX,
+ IMAGING_RAWMODE_RGBaXX,
+ IMAGING_RAWMODE_RGBa_16B,
+ IMAGING_RAWMODE_RGBa_16L,
+ IMAGING_RAWMODE_RGBa_16N,
+ IMAGING_RAWMODE_R_16B,
+ IMAGING_RAWMODE_R_16L,
+ IMAGING_RAWMODE_R_16N,
+ IMAGING_RAWMODE_S,
+ IMAGING_RAWMODE_V,
+ IMAGING_RAWMODE_X,
+ IMAGING_RAWMODE_XBGR,
+ IMAGING_RAWMODE_XRGB,
+ IMAGING_RAWMODE_Y,
+ IMAGING_RAWMODE_YCCA_P,
+ IMAGING_RAWMODE_YCC_P,
+ IMAGING_RAWMODE_YCbCrK,
+ IMAGING_RAWMODE_YCbCrX,
+ IMAGING_RAWMODE_YCbCr_L,
+ IMAGING_RAWMODE_Y_I,
+ IMAGING_RAWMODE_aBGR,
+ IMAGING_RAWMODE_aRGB,
+} RawModeID;
+
+typedef struct {
+ const char *const name;
+} RawModeData;
+
+const RawModeID
+findRawModeID(const char *const name);
+const RawModeData *const
+getRawModeData(const RawModeID id);
+
+int
+isModeI16(const ModeID mode);
+
+#endif // __MODE_H__
diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c
index 7f8a50d19..fdf5a72aa 100644
--- a/src/libImaging/Pack.c
+++ b/src/libImaging/Pack.c
@@ -519,150 +519,145 @@ band3(UINT8 *out, const UINT8 *in, int pixels) {
}
static struct {
- const char *mode;
- const char *rawmode;
+ const ModeID mode;
+ const RawModeID rawmode;
int bits;
ImagingShuffler pack;
} packers[] = {
/* bilevel */
- {"1", "1", 1, pack1},
- {"1", "1;I", 1, pack1I},
- {"1", "1;R", 1, pack1R},
- {"1", "1;IR", 1, pack1IR},
- {"1", "L", 8, pack1L},
+ {IMAGING_MODE_1, IMAGING_RAWMODE_1, 1, pack1},
+ {IMAGING_MODE_1, IMAGING_RAWMODE_1_I, 1, pack1I},
+ {IMAGING_MODE_1, IMAGING_RAWMODE_1_R, 1, pack1R},
+ {IMAGING_MODE_1, IMAGING_RAWMODE_1_IR, 1, pack1IR},
+ {IMAGING_MODE_1, IMAGING_RAWMODE_L, 8, pack1L},
/* grayscale */
- {"L", "L", 8, copy1},
- {"L", "L;16", 16, packL16},
- {"L", "L;16B", 16, packL16B},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L, 8, copy1},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_16, 16, packL16},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_16B, 16, packL16B},
/* grayscale w. alpha */
- {"LA", "LA", 16, packLA},
- {"LA", "LA;L", 16, packLAL},
+ {IMAGING_MODE_LA, IMAGING_RAWMODE_LA, 16, packLA},
+ {IMAGING_MODE_LA, IMAGING_RAWMODE_LA_L, 16, packLAL},
/* grayscale w. alpha premultiplied */
- {"La", "La", 16, packLA},
+ {IMAGING_MODE_La, IMAGING_RAWMODE_La, 16, packLA},
/* palette */
- {"P", "P;1", 1, pack1},
- {"P", "P;2", 2, packP2},
- {"P", "P;4", 4, packP4},
- {"P", "P", 8, copy1},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_P_1, 1, pack1},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_P_2, 2, packP2},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_P_4, 4, packP4},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_P, 8, copy1},
/* palette w. alpha */
- {"PA", "PA", 16, packLA},
- {"PA", "PA;L", 16, packLAL},
+ {IMAGING_MODE_PA, IMAGING_RAWMODE_PA, 16, packLA},
+ {IMAGING_MODE_PA, IMAGING_RAWMODE_PA_L, 16, packLAL},
/* true colour */
- {"RGB", "RGB", 24, ImagingPackRGB},
- {"RGB", "RGBX", 32, copy4},
- {"RGB", "RGBA", 32, copy4},
- {"RGB", "XRGB", 32, ImagingPackXRGB},
- {"RGB", "BGR", 24, ImagingPackBGR},
- {"RGB", "BGRX", 32, ImagingPackBGRX},
- {"RGB", "XBGR", 32, ImagingPackXBGR},
- {"RGB", "RGB;L", 24, packRGBL},
- {"RGB", "R", 8, band0},
- {"RGB", "G", 8, band1},
- {"RGB", "B", 8, band2},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX, 32, copy4},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA, 32, copy4},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_XRGB, 32, ImagingPackXRGB},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGRX, 32, ImagingPackBGRX},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_XBGR, 32, ImagingPackXBGR},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_L, 24, packRGBL},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_R, 8, band0},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_G, 8, band1},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_B, 8, band2},
/* true colour w. alpha */
- {"RGBA", "RGBA", 32, copy4},
- {"RGBA", "RGBA;L", 32, packRGBXL},
- {"RGBA", "RGB", 24, ImagingPackRGB},
- {"RGBA", "BGR", 24, ImagingPackBGR},
- {"RGBA", "BGRA", 32, ImagingPackBGRA},
- {"RGBA", "ABGR", 32, ImagingPackABGR},
- {"RGBA", "BGRa", 32, ImagingPackBGRa},
- {"RGBA", "R", 8, band0},
- {"RGBA", "G", 8, band1},
- {"RGBA", "B", 8, band2},
- {"RGBA", "A", 8, band3},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA, 32, copy4},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_L, 32, packRGBXL},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA, 32, ImagingPackBGRA},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ABGR, 32, ImagingPackABGR},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRa, 32, ImagingPackBGRa},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R, 8, band0},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G, 8, band1},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B, 8, band2},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A, 8, band3},
/* true colour w. alpha premultiplied */
- {"RGBa", "RGBa", 32, copy4},
- {"RGBa", "BGRa", 32, ImagingPackBGRA},
- {"RGBa", "aBGR", 32, ImagingPackABGR},
+ {IMAGING_MODE_RGBa, IMAGING_RAWMODE_RGBa, 32, copy4},
+ {IMAGING_MODE_RGBa, IMAGING_RAWMODE_BGRa, 32, ImagingPackBGRA},
+ {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aBGR, 32, ImagingPackABGR},
/* true colour w. padding */
- {"RGBX", "RGBX", 32, copy4},
- {"RGBX", "RGBX;L", 32, packRGBXL},
- {"RGBX", "RGB", 24, ImagingPackRGB},
- {"RGBX", "BGR", 24, ImagingPackBGR},
- {"RGBX", "BGRX", 32, ImagingPackBGRX},
- {"RGBX", "XBGR", 32, ImagingPackXBGR},
- {"RGBX", "R", 8, band0},
- {"RGBX", "G", 8, band1},
- {"RGBX", "B", 8, band2},
- {"RGBX", "X", 8, band3},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX, 32, copy4},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_L, 32, packRGBXL},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB, 24, ImagingPackRGB},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR, 24, ImagingPackBGR},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGRX, 32, ImagingPackBGRX},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XBGR, 32, ImagingPackXBGR},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_R, 8, band0},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_G, 8, band1},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_B, 8, band2},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_X, 8, band3},
/* colour separation */
- {"CMYK", "CMYK", 32, copy4},
- {"CMYK", "CMYK;I", 32, copy4I},
- {"CMYK", "CMYK;L", 32, packRGBXL},
- {"CMYK", "C", 8, band0},
- {"CMYK", "M", 8, band1},
- {"CMYK", "Y", 8, band2},
- {"CMYK", "K", 8, band3},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK, 32, copy4},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_I, 32, copy4I},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_L, 32, packRGBXL},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C, 8, band0},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M, 8, band1},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y, 8, band2},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K, 8, band3},
/* video (YCbCr) */
- {"YCbCr", "YCbCr", 24, ImagingPackRGB},
- {"YCbCr", "YCbCr;L", 24, packRGBL},
- {"YCbCr", "YCbCrX", 32, copy4},
- {"YCbCr", "YCbCrK", 32, copy4},
- {"YCbCr", "Y", 8, band0},
- {"YCbCr", "Cb", 8, band1},
- {"YCbCr", "Cr", 8, band2},
+ {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr, 24, ImagingPackRGB},
+ {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr_L, 24, packRGBL},
+ {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrX, 32, copy4},
+ {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrK, 32, copy4},
+ {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Y, 8, band0},
+ {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Cb, 8, band1},
+ {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_Cr, 8, band2},
/* LAB Color */
- {"LAB", "LAB", 24, ImagingPackLAB},
- {"LAB", "L", 8, band0},
- {"LAB", "A", 8, band1},
- {"LAB", "B", 8, band2},
+ {IMAGING_MODE_LAB, IMAGING_RAWMODE_LAB, 24, ImagingPackLAB},
+ {IMAGING_MODE_LAB, IMAGING_RAWMODE_L, 8, band0},
+ {IMAGING_MODE_LAB, IMAGING_RAWMODE_A, 8, band1},
+ {IMAGING_MODE_LAB, IMAGING_RAWMODE_B, 8, band2},
/* HSV */
- {"HSV", "HSV", 24, ImagingPackRGB},
- {"HSV", "H", 8, band0},
- {"HSV", "S", 8, band1},
- {"HSV", "V", 8, band2},
+ {IMAGING_MODE_HSV, IMAGING_RAWMODE_HSV, 24, ImagingPackRGB},
+ {IMAGING_MODE_HSV, IMAGING_RAWMODE_H, 8, band0},
+ {IMAGING_MODE_HSV, IMAGING_RAWMODE_S, 8, band1},
+ {IMAGING_MODE_HSV, IMAGING_RAWMODE_V, 8, band2},
/* integer */
- {"I", "I", 32, copy4},
- {"I", "I;16B", 16, packI16B},
- {"I", "I;32S", 32, packI32S},
- {"I", "I;32NS", 32, copy4},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I, 32, copy4},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_16B, 16, packI16B},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_32S, 32, packI32S},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_32NS, 32, copy4},
/* floating point */
- {"F", "F", 32, copy4},
- {"F", "F;32F", 32, packI32S},
- {"F", "F;32NF", 32, copy4},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F, 32, copy4},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_32F, 32, packI32S},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NF, 32, copy4},
/* storage modes */
- {"I;16", "I;16", 16, copy2},
+ {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16, 16, copy2},
#ifdef WORDS_BIGENDIAN
- {"I;16", "I;16B", 16, packI16N_I16},
+ {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, packI16N_I16},
#else
- {"I;16", "I;16B", 16, packI16N_I16B},
+ {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, packI16N_I16B},
#endif
- {"I;16B", "I;16B", 16, copy2},
- {"I;16L", "I;16L", 16, copy2},
- {"I;16N", "I;16N", 16, copy2},
- {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian.
- {"I;16L", "I;16N", 16, packI16N_I16},
- {"I;16B", "I;16N", 16, packI16N_I16B},
-
- {NULL} /* sentinel */
+ {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16B, 16, copy2},
+ {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16L, 16, copy2},
+ {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2},
+ // LibTiff native->image endian.
+ {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, packI16N_I16},
+ {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, packI16N_I16},
+ {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, packI16N_I16B}
};
ImagingShuffler
-ImagingFindPacker(const char *mode, const char *rawmode, int *bits_out) {
- int i;
-
- /* find a suitable pixel packer */
- for (i = 0; packers[i].rawmode; i++) {
- if (strcmp(packers[i].mode, mode) == 0 &&
- strcmp(packers[i].rawmode, rawmode) == 0) {
+ImagingFindPacker(const ModeID mode, const RawModeID rawmode, int *bits_out) {
+ for (size_t i = 0; i < sizeof(packers) / sizeof(*packers); i++) {
+ if (packers[i].mode == mode && packers[i].rawmode == rawmode) {
if (bits_out) {
*bits_out = packers[i].bits;
}
diff --git a/src/libImaging/Palette.c b/src/libImaging/Palette.c
index 78916bca5..371ba644b 100644
--- a/src/libImaging/Palette.c
+++ b/src/libImaging/Palette.c
@@ -21,13 +21,13 @@
#include
ImagingPalette
-ImagingPaletteNew(const char *mode) {
+ImagingPaletteNew(const ModeID mode) {
/* Create a palette object */
int i;
ImagingPalette palette;
- if (strcmp(mode, "RGB") && strcmp(mode, "RGBA")) {
+ if (mode != IMAGING_MODE_RGB && mode != IMAGING_MODE_RGBA) {
return (ImagingPalette)ImagingError_ModeError();
}
@@ -36,8 +36,7 @@ ImagingPaletteNew(const char *mode) {
return (ImagingPalette)ImagingError_MemoryError();
}
- strncpy(palette->mode, mode, IMAGING_MODE_LENGTH - 1);
- palette->mode[IMAGING_MODE_LENGTH - 1] = 0;
+ palette->mode = mode;
palette->size = 0;
for (i = 0; i < 256; i++) {
@@ -54,7 +53,7 @@ ImagingPaletteNewBrowser(void) {
int i, r, g, b;
ImagingPalette palette;
- palette = ImagingPaletteNew("RGB");
+ palette = ImagingPaletteNew(IMAGING_MODE_RGB);
if (!palette) {
return NULL;
}
@@ -148,7 +147,7 @@ ImagingPaletteDelete(ImagingPalette palette) {
#define BOX 8
-#define BOXVOLUME BOX *BOX *BOX
+#define BOXVOLUME BOX * BOX * BOX
void
ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) {
diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c
index 86085942a..25941ab3d 100644
--- a/src/libImaging/Paste.c
+++ b/src/libImaging/Paste.c
@@ -23,6 +23,18 @@
#include "Imaging.h"
+#define PREPARE_PASTE_LOOP() \
+ int y, y_end, offset; \
+ if (imOut == imIn && dy > sy) { \
+ y = ysize - 1; \
+ y_end = -1; \
+ offset = -1; \
+ } else { \
+ y = 0; \
+ y_end = ysize; \
+ offset = 1; \
+ }
+
static inline void
paste(
Imaging imOut,
@@ -37,14 +49,13 @@ paste(
) {
/* paste opaque region */
- int y;
-
dx *= pixelsize;
sx *= pixelsize;
xsize *= pixelsize;
- for (y = 0; y < ysize; y++) {
+ PREPARE_PASTE_LOOP();
+ for (; y != y_end; y += offset) {
memcpy(imOut->image[y + dy] + dx, imIn->image[y + sy] + sx, xsize);
}
}
@@ -64,12 +75,13 @@ paste_mask_1(
) {
/* paste with mode "1" mask */
- int x, y;
+ int x;
+ PREPARE_PASTE_LOOP();
if (imOut->image8) {
- int in_i16 = strncmp(imIn->mode, "I;16", 4) == 0;
- int out_i16 = strncmp(imOut->mode, "I;16", 4) == 0;
- for (y = 0; y < ysize; y++) {
+ int in_i16 = isModeI16(imIn->mode);
+ int out_i16 = isModeI16(imOut->mode);
+ for (; y != y_end; y += offset) {
UINT8 *out = imOut->image8[y + dy] + dx;
if (out_i16) {
out += dx;
@@ -97,7 +109,7 @@ paste_mask_1(
}
} else {
- for (y = 0; y < ysize; y++) {
+ for (; y != y_end; y += offset) {
INT32 *out = imOut->image32[y + dy] + dx;
INT32 *in = imIn->image32[y + sy] + sx;
UINT8 *mask = imMask->image8[y + sy] + sx;
@@ -126,11 +138,12 @@ paste_mask_L(
) {
/* paste with mode "L" matte */
- int x, y;
+ int x;
unsigned int tmp1;
+ PREPARE_PASTE_LOOP();
if (imOut->image8) {
- for (y = 0; y < ysize; y++) {
+ for (; y != y_end; y += offset) {
UINT8 *out = imOut->image8[y + dy] + dx;
UINT8 *in = imIn->image8[y + sy] + sx;
UINT8 *mask = imMask->image8[y + sy] + sx;
@@ -141,7 +154,7 @@ paste_mask_L(
}
} else {
- for (y = 0; y < ysize; y++) {
+ for (; y != y_end; y += offset) {
UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx);
UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx);
UINT8 *mask = (UINT8 *)(imMask->image8[y + sy] + sx);
@@ -174,11 +187,12 @@ paste_mask_RGBA(
) {
/* paste with mode "RGBA" matte */
- int x, y;
+ int x;
unsigned int tmp1;
+ PREPARE_PASTE_LOOP();
if (imOut->image8) {
- for (y = 0; y < ysize; y++) {
+ for (; y != y_end; y += offset) {
UINT8 *out = imOut->image8[y + dy] + dx;
UINT8 *in = imIn->image8[y + sy] + sx;
UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx * 4 + 3;
@@ -189,7 +203,7 @@ paste_mask_RGBA(
}
} else {
- for (y = 0; y < ysize; y++) {
+ for (; y != y_end; y += offset) {
UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx);
UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx);
UINT8 *mask = (UINT8 *)(imMask->image32[y + sy] + sx);
@@ -222,11 +236,12 @@ paste_mask_RGBa(
) {
/* paste with mode "RGBa" matte */
- int x, y;
+ int x;
unsigned int tmp1;
+ PREPARE_PASTE_LOOP();
if (imOut->image8) {
- for (y = 0; y < ysize; y++) {
+ for (; y != y_end; y += offset) {
UINT8 *out = imOut->image8[y + dy] + dx;
UINT8 *in = imIn->image8[y + sy] + sx;
UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx * 4 + 3;
@@ -237,7 +252,7 @@ paste_mask_RGBa(
}
} else {
- for (y = 0; y < ysize; y++) {
+ for (; y != y_end; y += offset) {
UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx);
UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx);
UINT8 *mask = (UINT8 *)(imMask->image32[y + sy] + sx);
@@ -307,31 +322,26 @@ ImagingPaste(
ImagingSectionEnter(&cookie);
paste(imOut, imIn, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
ImagingSectionLeave(&cookie);
-
- } else if (strcmp(imMask->mode, "1") == 0) {
+ } else if (imMask->mode == IMAGING_MODE_1) {
ImagingSectionEnter(&cookie);
paste_mask_1(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
ImagingSectionLeave(&cookie);
-
- } else if (strcmp(imMask->mode, "L") == 0) {
+ } else if (imMask->mode == IMAGING_MODE_L) {
ImagingSectionEnter(&cookie);
paste_mask_L(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
ImagingSectionLeave(&cookie);
-
- } else if (strcmp(imMask->mode, "LA") == 0 || strcmp(imMask->mode, "RGBA") == 0) {
+ } else if (imMask->mode == IMAGING_MODE_LA || imMask->mode == IMAGING_MODE_RGBA) {
ImagingSectionEnter(&cookie);
paste_mask_RGBA(
imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize
);
ImagingSectionLeave(&cookie);
-
- } else if (strcmp(imMask->mode, "RGBa") == 0) {
+ } else if (imMask->mode == IMAGING_MODE_RGBa) {
ImagingSectionEnter(&cookie);
paste_mask_RGBa(
imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize
);
ImagingSectionLeave(&cookie);
-
} else {
(void)ImagingError_ValueError("bad transparency mask");
return -1;
@@ -437,7 +447,7 @@ fill_mask_L(
unsigned int tmp1;
if (imOut->image8) {
- int i16 = strncmp(imOut->mode, "I;16", 4) == 0;
+ int i16 = isModeI16(imOut->mode);
for (y = 0; y < ysize; y++) {
UINT8 *out = imOut->image8[y + dy] + dx;
if (i16) {
@@ -456,9 +466,9 @@ fill_mask_L(
} else {
int alpha_channel =
- strcmp(imOut->mode, "RGBa") == 0 || strcmp(imOut->mode, "RGBA") == 0 ||
- strcmp(imOut->mode, "La") == 0 || strcmp(imOut->mode, "LA") == 0 ||
- strcmp(imOut->mode, "PA") == 0;
+ imOut->mode == IMAGING_MODE_RGBa || imOut->mode == IMAGING_MODE_RGBA ||
+ imOut->mode == IMAGING_MODE_La || imOut->mode == IMAGING_MODE_LA ||
+ imOut->mode == IMAGING_MODE_PA;
for (y = 0; y < ysize; y++) {
UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx * pixelsize;
UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx;
@@ -617,27 +627,22 @@ ImagingFill2(
ImagingSectionEnter(&cookie);
fill(imOut, ink, dx0, dy0, xsize, ysize, pixelsize);
ImagingSectionLeave(&cookie);
-
- } else if (strcmp(imMask->mode, "1") == 0) {
+ } else if (imMask->mode == IMAGING_MODE_1) {
ImagingSectionEnter(&cookie);
fill_mask_1(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
ImagingSectionLeave(&cookie);
-
- } else if (strcmp(imMask->mode, "L") == 0) {
+ } else if (imMask->mode == IMAGING_MODE_L) {
ImagingSectionEnter(&cookie);
fill_mask_L(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
ImagingSectionLeave(&cookie);
-
- } else if (strcmp(imMask->mode, "RGBA") == 0) {
+ } else if (imMask->mode == IMAGING_MODE_RGBA) {
ImagingSectionEnter(&cookie);
fill_mask_RGBA(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
ImagingSectionLeave(&cookie);
-
- } else if (strcmp(imMask->mode, "RGBa") == 0) {
+ } else if (imMask->mode == IMAGING_MODE_RGBa) {
ImagingSectionEnter(&cookie);
fill_mask_RGBa(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize);
ImagingSectionLeave(&cookie);
-
} else {
(void)ImagingError_ValueError("bad transparency mask");
return -1;
diff --git a/src/libImaging/Point.c b/src/libImaging/Point.c
index b11ea62ed..8f6d47c77 100644
--- a/src/libImaging/Point.c
+++ b/src/libImaging/Point.c
@@ -128,7 +128,7 @@ im_point_32_8(Imaging imOut, Imaging imIn, im_point_context *context) {
}
Imaging
-ImagingPoint(Imaging imIn, const char *mode, const void *table) {
+ImagingPoint(Imaging imIn, ModeID mode, const void *table) {
/* lookup table transform */
ImagingSectionCookie cookie;
@@ -140,15 +140,15 @@ ImagingPoint(Imaging imIn, const char *mode, const void *table) {
return (Imaging)ImagingError_ModeError();
}
- if (!mode) {
+ if (mode == IMAGING_MODE_UNKNOWN) {
mode = imIn->mode;
}
if (imIn->type != IMAGING_TYPE_UINT8) {
- if (imIn->type != IMAGING_TYPE_INT32 || strcmp(mode, "L") != 0) {
+ if (imIn->type != IMAGING_TYPE_INT32 || mode != IMAGING_MODE_L) {
goto mode_mismatch;
}
- } else if (!imIn->image8 && strcmp(imIn->mode, mode) != 0) {
+ } else if (!imIn->image8 && imIn->mode != mode) {
goto mode_mismatch;
}
@@ -210,8 +210,8 @@ ImagingPointTransform(Imaging imIn, double scale, double offset) {
Imaging imOut;
int x, y;
- if (!imIn || (strcmp(imIn->mode, "I") != 0 && strcmp(imIn->mode, "I;16") != 0 &&
- strcmp(imIn->mode, "F") != 0)) {
+ if (!imIn || (imIn->mode != IMAGING_MODE_I && imIn->mode != IMAGING_MODE_I_16 &&
+ imIn->mode != IMAGING_MODE_F)) {
return (Imaging)ImagingError_ModeError();
}
@@ -245,7 +245,7 @@ ImagingPointTransform(Imaging imIn, double scale, double offset) {
ImagingSectionLeave(&cookie);
break;
case IMAGING_TYPE_SPECIAL:
- if (strcmp(imIn->mode, "I;16") == 0) {
+ if (imIn->mode == IMAGING_MODE_I_16) {
ImagingSectionEnter(&cookie);
for (y = 0; y < imIn->ysize; y++) {
char *in = (char *)imIn->image[y];
diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c
index a489a882d..7e3df1808 100644
--- a/src/libImaging/Quant.c
+++ b/src/libImaging/Quant.c
@@ -1684,13 +1684,13 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
return (Imaging)ImagingError_ValueError("bad number of colors");
}
- if (strcmp(im->mode, "L") != 0 && strcmp(im->mode, "P") != 0 &&
- strcmp(im->mode, "RGB") != 0 && strcmp(im->mode, "RGBA") != 0) {
+ if (im->mode != IMAGING_MODE_L && im->mode != IMAGING_MODE_P &&
+ im->mode != IMAGING_MODE_RGB && im->mode != IMAGING_MODE_RGBA) {
return ImagingError_ModeError();
}
/* only octree and imagequant supports RGBA */
- if (!strcmp(im->mode, "RGBA") && mode != 2 && mode != 3) {
+ if (im->mode == IMAGING_MODE_RGBA && mode != 2 && mode != 3) {
return ImagingError_ModeError();
}
@@ -1708,7 +1708,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
/* FIXME: maybe we could load the hash tables directly from the
image data? */
- if (!strcmp(im->mode, "L")) {
+ if (im->mode == IMAGING_MODE_L) {
/* grayscale */
/* FIXME: converting a "L" image to "P" with 256 colors
@@ -1721,7 +1721,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
}
}
- } else if (!strcmp(im->mode, "P")) {
+ } else if (im->mode == IMAGING_MODE_P) {
/* palette */
pp = im->palette->palette;
@@ -1736,28 +1736,32 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
}
}
- } else if (!strcmp(im->mode, "RGB") || !strcmp(im->mode, "RGBA")) {
+ } else if (im->mode == IMAGING_MODE_RGB || im->mode == IMAGING_MODE_RGBA) {
/* true colour */
- withAlpha = !strcmp(im->mode, "RGBA");
+ withAlpha = im->mode == IMAGING_MODE_RGBA;
int transparency = 0;
unsigned char r = 0, g = 0, b = 0;
for (i = y = 0; y < im->ysize; y++) {
for (x = 0; x < im->xsize; x++, i++) {
p[i].v = im->image32[y][x];
- if (withAlpha && p[i].c.a == 0) {
- if (transparency == 0) {
- transparency = 1;
- r = p[i].c.r;
- g = p[i].c.g;
- b = p[i].c.b;
- } else {
- /* Set all subsequent transparent pixels
- to the same colour as the first */
- p[i].c.r = r;
- p[i].c.g = g;
- p[i].c.b = b;
+ if (withAlpha) {
+ if (p[i].c.a == 0) {
+ if (transparency == 0) {
+ transparency = 1;
+ r = p[i].c.r;
+ g = p[i].c.g;
+ b = p[i].c.b;
+ } else {
+ /* Set all subsequent transparent pixels
+ to the same colour as the first */
+ p[i].c.r = r;
+ p[i].c.g = g;
+ p[i].c.b = b;
+ }
}
+ } else {
+ p[i].c.a = 255;
}
}
}
@@ -1830,7 +1834,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
ImagingSectionLeave(&cookie);
if (result > 0) {
- imOut = ImagingNewDirty("P", im->xsize, im->ysize);
+ imOut = ImagingNewDirty(IMAGING_MODE_P, im->xsize, im->ysize);
ImagingSectionEnter(&cookie);
for (i = y = 0; y < im->ysize; y++) {
@@ -1855,7 +1859,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) {
}
if (withAlpha) {
- strcpy(imOut->palette->mode, "RGBA");
+ imOut->palette->mode = IMAGING_MODE_RGBA;
}
free(palette);
diff --git a/src/libImaging/Reduce.c b/src/libImaging/Reduce.c
index 022daa000..a4e58ced8 100644
--- a/src/libImaging/Reduce.c
+++ b/src/libImaging/Reduce.c
@@ -1452,7 +1452,7 @@ ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]) {
ImagingSectionCookie cookie;
Imaging imOut = NULL;
- if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0) {
+ if (imIn->mode == IMAGING_MODE_P || imIn->mode == IMAGING_MODE_1) {
return (Imaging)ImagingError_ModeError();
}
diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c
index b114e0023..cbd18d0c1 100644
--- a/src/libImaging/Resample.c
+++ b/src/libImaging/Resample.c
@@ -471,9 +471,9 @@ ImagingResampleHorizontal_16bpc(
int bigendian = 0;
if (
- strcmp(imIn->mode, "I;16N") == 0
+ imIn->mode == IMAGING_MODE_I_16N
#ifdef WORDS_BIGENDIAN
- || strcmp(imIn->mode, "I;16B") == 0
+ || imIn->mode == IMAGING_MODE_I_16B
#endif
) {
bigendian = 1;
@@ -511,9 +511,9 @@ ImagingResampleVertical_16bpc(
int bigendian = 0;
if (
- strcmp(imIn->mode, "I;16N") == 0
+ imIn->mode == IMAGING_MODE_I_16N
#ifdef WORDS_BIGENDIAN
- || strcmp(imIn->mode, "I;16B") == 0
+ || imIn->mode == IMAGING_MODE_I_16B
#endif
) {
bigendian = 1;
@@ -648,12 +648,12 @@ ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) {
ResampleFunction ResampleHorizontal;
ResampleFunction ResampleVertical;
- if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0) {
+ if (imIn->mode == IMAGING_MODE_P || imIn->mode == IMAGING_MODE_1) {
return (Imaging)ImagingError_ModeError();
}
if (imIn->type == IMAGING_TYPE_SPECIAL) {
- if (strncmp(imIn->mode, "I;16", 4) == 0) {
+ if (isModeI16(imIn->mode)) {
ResampleHorizontal = ImagingResampleHorizontal_16bpc;
ResampleVertical = ImagingResampleVertical_16bpc;
} else {
diff --git a/src/libImaging/SgiRleDecode.c b/src/libImaging/SgiRleDecode.c
index e60468990..a562f582c 100644
--- a/src/libImaging/SgiRleDecode.c
+++ b/src/libImaging/SgiRleDecode.c
@@ -22,7 +22,8 @@
static void
read4B(UINT32 *dest, UINT8 *buf) {
- *dest = (UINT32)((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]);
+ *dest = ((UINT32)buf[0] << 24) | ((UINT32)buf[1] << 16) | ((UINT32)buf[2] << 8) |
+ buf[3];
}
/*
diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c
index 4640f078a..c09062c92 100644
--- a/src/libImaging/Storage.c
+++ b/src/libImaging/Storage.c
@@ -42,7 +42,7 @@
*/
Imaging
-ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
+ImagingNewPrologueSubtype(const ModeID mode, int xsize, int ysize, int size) {
Imaging im;
/* linesize overflow check, roughly the current largest space req'd */
@@ -62,37 +62,37 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
im->type = IMAGING_TYPE_UINT8;
strcpy(im->arrow_band_format, "C");
- if (strcmp(mode, "1") == 0) {
+ if (mode == IMAGING_MODE_1) {
/* 1-bit images */
im->bands = im->pixelsize = 1;
im->linesize = xsize;
strcpy(im->band_names[0], "1");
- } else if (strcmp(mode, "P") == 0) {
+ } else if (mode == IMAGING_MODE_P) {
/* 8-bit palette mapped images */
im->bands = im->pixelsize = 1;
im->linesize = xsize;
- im->palette = ImagingPaletteNew("RGB");
+ im->palette = ImagingPaletteNew(IMAGING_MODE_RGB);
strcpy(im->band_names[0], "P");
- } else if (strcmp(mode, "PA") == 0) {
+ } else if (mode == IMAGING_MODE_PA) {
/* 8-bit palette with alpha */
im->bands = 2;
im->pixelsize = 4; /* store in image32 memory */
im->linesize = xsize * 4;
- im->palette = ImagingPaletteNew("RGB");
+ im->palette = ImagingPaletteNew(IMAGING_MODE_RGB);
strcpy(im->band_names[0], "P");
strcpy(im->band_names[1], "X");
strcpy(im->band_names[2], "X");
strcpy(im->band_names[3], "A");
- } else if (strcmp(mode, "L") == 0) {
+ } else if (mode == IMAGING_MODE_L) {
/* 8-bit grayscale (luminance) images */
im->bands = im->pixelsize = 1;
im->linesize = xsize;
strcpy(im->band_names[0], "L");
- } else if (strcmp(mode, "LA") == 0) {
+ } else if (mode == IMAGING_MODE_LA) {
/* 8-bit grayscale (luminance) with alpha */
im->bands = 2;
im->pixelsize = 4; /* store in image32 memory */
@@ -102,7 +102,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->band_names[2], "X");
strcpy(im->band_names[3], "A");
- } else if (strcmp(mode, "La") == 0) {
+ } else if (mode == IMAGING_MODE_La) {
/* 8-bit grayscale (luminance) with premultiplied alpha */
im->bands = 2;
im->pixelsize = 4; /* store in image32 memory */
@@ -112,7 +112,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->band_names[2], "X");
strcpy(im->band_names[3], "a");
- } else if (strcmp(mode, "F") == 0) {
+ } else if (mode == IMAGING_MODE_F) {
/* 32-bit floating point images */
im->bands = 1;
im->pixelsize = 4;
@@ -121,7 +121,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->arrow_band_format, "f");
strcpy(im->band_names[0], "F");
- } else if (strcmp(mode, "I") == 0) {
+ } else if (mode == IMAGING_MODE_I) {
/* 32-bit integer images */
im->bands = 1;
im->pixelsize = 4;
@@ -130,8 +130,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->arrow_band_format, "i");
strcpy(im->band_names[0], "I");
- } else if (strcmp(mode, "I;16") == 0 || strcmp(mode, "I;16L") == 0 ||
- strcmp(mode, "I;16B") == 0 || strcmp(mode, "I;16N") == 0) {
+ } else if (isModeI16(mode)) {
/* EXPERIMENTAL */
/* 16-bit raw integer images */
im->bands = 1;
@@ -141,7 +140,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->arrow_band_format, "s");
strcpy(im->band_names[0], "I");
- } else if (strcmp(mode, "RGB") == 0) {
+ } else if (mode == IMAGING_MODE_RGB) {
/* 24-bit true colour images */
im->bands = 3;
im->pixelsize = 4;
@@ -151,7 +150,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->band_names[2], "B");
strcpy(im->band_names[3], "X");
- } else if (strcmp(mode, "RGBX") == 0) {
+ } else if (mode == IMAGING_MODE_RGBX) {
/* 32-bit true colour images with padding */
im->bands = im->pixelsize = 4;
im->linesize = xsize * 4;
@@ -160,7 +159,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->band_names[2], "B");
strcpy(im->band_names[3], "X");
- } else if (strcmp(mode, "RGBA") == 0) {
+ } else if (mode == IMAGING_MODE_RGBA) {
/* 32-bit true colour images with alpha */
im->bands = im->pixelsize = 4;
im->linesize = xsize * 4;
@@ -169,7 +168,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->band_names[2], "B");
strcpy(im->band_names[3], "A");
- } else if (strcmp(mode, "RGBa") == 0) {
+ } else if (mode == IMAGING_MODE_RGBa) {
/* 32-bit true colour images with premultiplied alpha */
im->bands = im->pixelsize = 4;
im->linesize = xsize * 4;
@@ -178,7 +177,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->band_names[2], "B");
strcpy(im->band_names[3], "a");
- } else if (strcmp(mode, "CMYK") == 0) {
+ } else if (mode == IMAGING_MODE_CMYK) {
/* 32-bit colour separation */
im->bands = im->pixelsize = 4;
im->linesize = xsize * 4;
@@ -187,7 +186,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->band_names[2], "Y");
strcpy(im->band_names[3], "K");
- } else if (strcmp(mode, "YCbCr") == 0) {
+ } else if (mode == IMAGING_MODE_YCbCr) {
/* 24-bit video format */
im->bands = 3;
im->pixelsize = 4;
@@ -197,7 +196,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->band_names[2], "Cr");
strcpy(im->band_names[3], "X");
- } else if (strcmp(mode, "LAB") == 0) {
+ } else if (mode == IMAGING_MODE_LAB) {
/* 24-bit color, luminance, + 2 color channels */
/* L is uint8, a,b are int8 */
im->bands = 3;
@@ -208,7 +207,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
strcpy(im->band_names[2], "b");
strcpy(im->band_names[3], "X");
- } else if (strcmp(mode, "HSV") == 0) {
+ } else if (mode == IMAGING_MODE_HSV) {
/* 24-bit color, luminance, + 2 color channels */
/* L is uint8, a,b are int8 */
im->bands = 3;
@@ -225,7 +224,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
}
/* Setup image descriptor */
- strcpy(im->mode, mode);
+ im->mode = mode;
/* Pointer array (allocate at least one line, to avoid MemoryError
exceptions on platforms where calloc(0, x) returns NULL) */
@@ -257,7 +256,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
}
Imaging
-ImagingNewPrologue(const char *mode, int xsize, int ysize) {
+ImagingNewPrologue(const ModeID mode, int xsize, int ysize) {
return ImagingNewPrologueSubtype(
mode, xsize, ysize, sizeof(struct ImagingMemoryInstance)
);
@@ -594,7 +593,7 @@ ImagingBorrowArrow(
*/
Imaging
-ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) {
+ImagingNewInternal(const ModeID mode, int xsize, int ysize, int dirty) {
Imaging im;
if (xsize < 0 || ysize < 0) {
@@ -630,7 +629,7 @@ ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) {
}
Imaging
-ImagingNew(const char *mode, int xsize, int ysize) {
+ImagingNew(const ModeID mode, int xsize, int ysize) {
if (ImagingDefaultArena.use_block_allocator) {
return ImagingNewBlock(mode, xsize, ysize);
}
@@ -638,7 +637,7 @@ ImagingNew(const char *mode, int xsize, int ysize) {
}
Imaging
-ImagingNewDirty(const char *mode, int xsize, int ysize) {
+ImagingNewDirty(const ModeID mode, int xsize, int ysize) {
if (ImagingDefaultArena.use_block_allocator) {
return ImagingNewBlock(mode, xsize, ysize);
}
@@ -646,7 +645,7 @@ ImagingNewDirty(const char *mode, int xsize, int ysize) {
}
Imaging
-ImagingNewBlock(const char *mode, int xsize, int ysize) {
+ImagingNewBlock(const ModeID mode, int xsize, int ysize) {
Imaging im;
if (xsize < 0 || ysize < 0) {
@@ -668,7 +667,7 @@ ImagingNewBlock(const char *mode, int xsize, int ysize) {
Imaging
ImagingNewArrow(
- const char *mode,
+ const ModeID mode,
int xsize,
int ysize,
PyObject *schema_capsule,
@@ -741,12 +740,12 @@ ImagingNewArrow(
}
Imaging
-ImagingNew2Dirty(const char *mode, Imaging imOut, Imaging imIn) {
+ImagingNew2Dirty(const ModeID mode, Imaging imOut, Imaging imIn) {
/* allocate or validate output image */
if (imOut) {
/* make sure images match */
- if (strcmp(imOut->mode, mode) != 0 || imOut->xsize != imIn->xsize ||
+ if (imOut->mode != mode || imOut->xsize != imIn->xsize ||
imOut->ysize != imIn->ysize) {
return ImagingError_Mismatch();
}
diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c
index 71516fd1b..72e0d7b30 100644
--- a/src/libImaging/TiffDecode.c
+++ b/src/libImaging/TiffDecode.c
@@ -246,14 +246,26 @@ _pickUnpackers(
// We'll pick appropriate set of unpackers depending on planar_configuration
// It does not matter if data is RGB(A), CMYK or LUV really,
// we just copy it plane by plane
- unpackers[0] =
- ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL);
- unpackers[1] =
- ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL);
- unpackers[2] =
- ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL);
- unpackers[3] =
- ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL);
+ unpackers[0] = ImagingFindUnpacker(
+ IMAGING_MODE_RGBA,
+ bits_per_sample == 16 ? IMAGING_RAWMODE_R_16N : IMAGING_RAWMODE_R,
+ NULL
+ );
+ unpackers[1] = ImagingFindUnpacker(
+ IMAGING_MODE_RGBA,
+ bits_per_sample == 16 ? IMAGING_RAWMODE_G_16N : IMAGING_RAWMODE_G,
+ NULL
+ );
+ unpackers[2] = ImagingFindUnpacker(
+ IMAGING_MODE_RGBA,
+ bits_per_sample == 16 ? IMAGING_RAWMODE_B_16N : IMAGING_RAWMODE_B,
+ NULL
+ );
+ unpackers[3] = ImagingFindUnpacker(
+ IMAGING_MODE_RGBA,
+ bits_per_sample == 16 ? IMAGING_RAWMODE_A_16N : IMAGING_RAWMODE_A,
+ NULL
+ );
return im->bands;
} else {
@@ -644,7 +656,7 @@ ImagingLibTiffDecode(
);
TRACE(
("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n",
- im->mode,
+ getModeData(im->mode)->name,
im->type,
im->bands,
im->xsize,
@@ -755,7 +767,7 @@ ImagingLibTiffDecode(
if (!state->errcode) {
// Check if raw mode was RGBa and it was stored on separate planes
// so we have to convert it to RGBA
- if (planes > 3 && strcmp(im->mode, "RGBA") == 0) {
+ if (planes > 3 && im->mode == IMAGING_MODE_RGBA) {
uint16_t extrasamples;
uint16_t *sampleinfo;
ImagingShuffler shuffle;
@@ -767,7 +779,9 @@ ImagingLibTiffDecode(
if (extrasamples >= 1 && (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED ||
sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)) {
- shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL);
+ shuffle = ImagingFindUnpacker(
+ IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa, NULL
+ );
for (y = state->yoff; y < state->ysize; y++) {
UINT8 *ptr = (UINT8 *)im->image[y + state->yoff] +
@@ -991,7 +1005,7 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
);
TRACE(
("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n",
- im->mode,
+ getModeData(im->mode)->name,
im->type,
im->bands,
im->xsize,
diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c
index 976baa726..203bcac2c 100644
--- a/src/libImaging/Unpack.c
+++ b/src/libImaging/Unpack.c
@@ -1542,12 +1542,11 @@ band316L(UINT8 *out, const UINT8 *in, int pixels) {
}
static struct {
- const char *mode;
- const char *rawmode;
+ const ModeID mode;
+ const RawModeID rawmode;
int bits;
ImagingShuffler unpack;
} unpackers[] = {
-
/* raw mode syntax is ";" where "bits" defaults
depending on mode (1 for "1", 8 for "P" and "L", etc), and
"flags" should be given in alphabetical order. if both bits
@@ -1560,299 +1559,295 @@ static struct {
/* exception: rawmodes "I" and "F" are always native endian byte order */
/* bilevel */
- {"1", "1", 1, unpack1},
- {"1", "1;I", 1, unpack1I},
- {"1", "1;R", 1, unpack1R},
- {"1", "1;IR", 1, unpack1IR},
- {"1", "1;8", 8, unpack18},
+ {IMAGING_MODE_1, IMAGING_RAWMODE_1, 1, unpack1},
+ {IMAGING_MODE_1, IMAGING_RAWMODE_1_I, 1, unpack1I},
+ {IMAGING_MODE_1, IMAGING_RAWMODE_1_R, 1, unpack1R},
+ {IMAGING_MODE_1, IMAGING_RAWMODE_1_IR, 1, unpack1IR},
+ {IMAGING_MODE_1, IMAGING_RAWMODE_1_8, 8, unpack18},
/* grayscale */
- {"L", "L;2", 2, unpackL2},
- {"L", "L;2I", 2, unpackL2I},
- {"L", "L;2R", 2, unpackL2R},
- {"L", "L;2IR", 2, unpackL2IR},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_2, 2, unpackL2},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_2I, 2, unpackL2I},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_2R, 2, unpackL2R},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_2IR, 2, unpackL2IR},
- {"L", "L;4", 4, unpackL4},
- {"L", "L;4I", 4, unpackL4I},
- {"L", "L;4R", 4, unpackL4R},
- {"L", "L;4IR", 4, unpackL4IR},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_4, 4, unpackL4},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_4I, 4, unpackL4I},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_4R, 4, unpackL4R},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_4IR, 4, unpackL4IR},
- {"L", "L", 8, copy1},
- {"L", "L;I", 8, unpackLI},
- {"L", "L;R", 8, unpackLR},
- {"L", "L;16", 16, unpackL16},
- {"L", "L;16B", 16, unpackL16B},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L, 8, copy1},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_I, 8, unpackLI},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_R, 8, unpackLR},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_16, 16, unpackL16},
+ {IMAGING_MODE_L, IMAGING_RAWMODE_L_16B, 16, unpackL16B},
/* grayscale w. alpha */
- {"LA", "LA", 16, unpackLA},
- {"LA", "LA;L", 16, unpackLAL},
+ {IMAGING_MODE_LA, IMAGING_RAWMODE_LA, 16, unpackLA},
+ {IMAGING_MODE_LA, IMAGING_RAWMODE_LA_L, 16, unpackLAL},
/* grayscale w. alpha premultiplied */
- {"La", "La", 16, unpackLA},
+ {IMAGING_MODE_La, IMAGING_RAWMODE_La, 16, unpackLA},
/* palette */
- {"P", "P;1", 1, unpackP1},
- {"P", "P;2", 2, unpackP2},
- {"P", "P;2L", 2, unpackP2L},
- {"P", "P;4", 4, unpackP4},
- {"P", "P;4L", 4, unpackP4L},
- {"P", "P", 8, copy1},
- {"P", "P;R", 8, unpackLR},
- {"P", "L", 8, copy1},
- {"P", "PX", 16, unpackL16B},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_P_1, 1, unpackP1},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_P_2, 2, unpackP2},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_P_2L, 2, unpackP2L},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_P_4, 4, unpackP4},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_P_4L, 4, unpackP4L},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_P, 8, copy1},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_P_R, 8, unpackLR},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_L, 8, copy1},
+ {IMAGING_MODE_P, IMAGING_RAWMODE_PX, 16, unpackL16B},
/* palette w. alpha */
- {"PA", "PA", 16, unpackLA},
- {"PA", "PA;L", 16, unpackLAL},
- {"PA", "LA", 16, unpackLA},
+ {IMAGING_MODE_PA, IMAGING_RAWMODE_PA, 16, unpackLA},
+ {IMAGING_MODE_PA, IMAGING_RAWMODE_PA_L, 16, unpackLAL},
+ {IMAGING_MODE_PA, IMAGING_RAWMODE_LA, 16, unpackLA},
/* true colour */
- {"RGB", "RGB", 24, ImagingUnpackRGB},
- {"RGB", "RGB;L", 24, unpackRGBL},
- {"RGB", "RGB;R", 24, unpackRGBR},
- {"RGB", "RGB;16L", 48, unpackRGB16L},
- {"RGB", "RGB;16B", 48, unpackRGB16B},
- {"RGB", "BGR", 24, ImagingUnpackBGR},
- {"RGB", "RGB;15", 16, ImagingUnpackRGB15},
- {"RGB", "BGR;15", 16, ImagingUnpackBGR15},
- {"RGB", "RGB;16", 16, ImagingUnpackRGB16},
- {"RGB", "BGR;16", 16, ImagingUnpackBGR16},
- {"RGB", "RGBX;16L", 64, unpackRGBA16L},
- {"RGB", "RGBX;16B", 64, unpackRGBA16B},
- {"RGB", "RGB;4B", 16, ImagingUnpackRGB4B},
- {"RGB", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */
- {"RGB", "RGBX", 32, copy4},
- {"RGB", "RGBX;L", 32, unpackRGBAL},
- {"RGB", "RGBXX", 40, copy4skip1},
- {"RGB", "RGBXXX", 48, copy4skip2},
- {"RGB", "RGBA;L", 32, unpackRGBAL},
- {"RGB", "RGBA;15", 16, ImagingUnpackRGBA15},
- {"RGB", "BGRX", 32, ImagingUnpackBGRX},
- {"RGB", "BGXR", 32, ImagingUnpackBGXR},
- {"RGB", "XRGB", 32, ImagingUnpackXRGB},
- {"RGB", "XBGR", 32, ImagingUnpackXBGR},
- {"RGB", "YCC;P", 24, ImagingUnpackYCC},
- {"RGB", "R", 8, band0},
- {"RGB", "G", 8, band1},
- {"RGB", "B", 8, band2},
- {"RGB", "R;16L", 16, band016L},
- {"RGB", "G;16L", 16, band116L},
- {"RGB", "B;16L", 16, band216L},
- {"RGB", "R;16B", 16, band016B},
- {"RGB", "G;16B", 16, band116B},
- {"RGB", "B;16B", 16, band216B},
- {"RGB", "CMYK", 32, cmyk2rgb},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB, 24, ImagingUnpackRGB},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_L, 24, unpackRGBL},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_R, 24, unpackRGBR},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16L, 48, unpackRGB16L},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16B, 48, unpackRGB16B},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR, 24, ImagingUnpackBGR},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_15, 16, ImagingUnpackRGB15},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_15, 16, ImagingUnpackBGR15},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16, 16, ImagingUnpackRGB16},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_16, 16, ImagingUnpackBGR16},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16L, 64, unpackRGBA16L},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16B, 64, unpackRGBA16B},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_4B, 16, ImagingUnpackRGB4B},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGR_5, 16, ImagingUnpackBGR15}, /* compat */
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX, 32, copy4},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_L, 32, unpackRGBAL},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBXX, 40, copy4skip1},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBXXX, 48, copy4skip2},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA_L, 32, unpackRGBAL},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBA_15, 16, ImagingUnpackRGBA15},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGRX, 32, ImagingUnpackBGRX},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_BGXR, 32, ImagingUnpackBGXR},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_XRGB, 32, ImagingUnpackXRGB},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_XBGR, 32, ImagingUnpackXBGR},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_YCC_P, 24, ImagingUnpackYCC},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_R, 8, band0},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_G, 8, band1},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_B, 8, band2},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16L, 16, band016L},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16L, 16, band116L},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16L, 16, band216L},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16B, 16, band016B},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16B, 16, band116B},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16B, 16, band216B},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_CMYK, 32, cmyk2rgb},
/* true colour w. alpha */
- {"RGBA", "LA", 16, unpackRGBALA},
- {"RGBA", "LA;16B", 32, unpackRGBALA16B},
- {"RGBA", "RGBA", 32, copy4},
- {"RGBA", "RGBAX", 40, copy4skip1},
- {"RGBA", "RGBAXX", 48, copy4skip2},
- {"RGBA", "RGBa", 32, unpackRGBa},
- {"RGBA", "RGBaX", 40, unpackRGBaskip1},
- {"RGBA", "RGBaXX", 48, unpackRGBaskip2},
- {"RGBA", "RGBa;16L", 64, unpackRGBa16L},
- {"RGBA", "RGBa;16B", 64, unpackRGBa16B},
- {"RGBA", "BGR", 24, ImagingUnpackBGR},
- {"RGBA", "BGRa", 32, unpackBGRa},
- {"RGBA", "RGBA;I", 32, unpackRGBAI},
- {"RGBA", "RGBA;L", 32, unpackRGBAL},
- {"RGBA", "RGBA;15", 16, ImagingUnpackRGBA15},
- {"RGBA", "BGRA;15", 16, ImagingUnpackBGRA15},
- {"RGBA", "BGRA;15Z", 16, ImagingUnpackBGRA15Z},
- {"RGBA", "RGBA;4B", 16, ImagingUnpackRGBA4B},
- {"RGBA", "RGBA;16L", 64, unpackRGBA16L},
- {"RGBA", "RGBA;16B", 64, unpackRGBA16B},
- {"RGBA", "BGRA", 32, unpackBGRA},
- {"RGBA", "BGRA;16L", 64, unpackBGRA16L},
- {"RGBA", "BGRA;16B", 64, unpackBGRA16B},
- {"RGBA", "BGAR", 32, unpackBGAR},
- {"RGBA", "ARGB", 32, unpackARGB},
- {"RGBA", "ABGR", 32, unpackABGR},
- {"RGBA", "YCCA;P", 32, ImagingUnpackYCCA},
- {"RGBA", "R", 8, band0},
- {"RGBA", "G", 8, band1},
- {"RGBA", "B", 8, band2},
- {"RGBA", "A", 8, band3},
- {"RGBA", "R;16L", 16, band016L},
- {"RGBA", "G;16L", 16, band116L},
- {"RGBA", "B;16L", 16, band216L},
- {"RGBA", "A;16L", 16, band316L},
- {"RGBA", "R;16B", 16, band016B},
- {"RGBA", "G;16B", 16, band116B},
- {"RGBA", "B;16B", 16, band216B},
- {"RGBA", "A;16B", 16, band316B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_LA, 16, unpackRGBALA},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_LA_16B, 32, unpackRGBALA16B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA, 32, copy4},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBAX, 40, copy4skip1},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBAXX, 48, copy4skip2},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa, 32, unpackRGBa},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBaX, 40, unpackRGBaskip1},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBaXX, 48, unpackRGBaskip2},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16L, 64, unpackRGBa16L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16B, 64, unpackRGBa16B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGR, 24, ImagingUnpackBGR},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRa, 32, unpackBGRa},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_I, 32, unpackRGBAI},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_L, 32, unpackRGBAL},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_15, 16, ImagingUnpackRGBA15},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_15, 16, ImagingUnpackBGRA15},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_15Z, 16, ImagingUnpackBGRA15Z},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_4B, 16, ImagingUnpackRGBA4B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16L, 64, unpackRGBA16L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16B, 64, unpackRGBA16B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA, 32, unpackBGRA},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_16L, 64, unpackBGRA16L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGRA_16B, 64, unpackBGRA16B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_BGAR, 32, unpackBGAR},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ARGB, 32, unpackARGB},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_ABGR, 32, unpackABGR},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_YCCA_P, 32, ImagingUnpackYCCA},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R, 8, band0},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G, 8, band1},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B, 8, band2},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A, 8, band3},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16L, 16, band016L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16L, 16, band116L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16L, 16, band216L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16L, 16, band316L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16B, 16, band016B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16B, 16, band116B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16B, 16, band216B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16B, 16, band316B},
#ifdef WORDS_BIGENDIAN
- {"RGB", "RGB;16N", 48, unpackRGB16B},
- {"RGB", "RGBX;16N", 64, unpackRGBA16B},
- {"RGBA", "RGBa;16N", 64, unpackRGBa16B},
- {"RGBA", "RGBA;16N", 64, unpackRGBA16B},
- {"RGBX", "RGBX;16N", 64, unpackRGBA16B},
- {"RGB", "R;16N", 16, band016B},
- {"RGB", "G;16N", 16, band116B},
- {"RGB", "B;16N", 16, band216B},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16N, 48, unpackRGB16B},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16N, 64, unpackRGBa16B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16N, 64, unpackRGBA16B},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16B},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16N, 16, band016B},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16N, 16, band116B},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16N, 16, band216B},
- {"RGBA", "R;16N", 16, band016B},
- {"RGBA", "G;16N", 16, band116B},
- {"RGBA", "B;16N", 16, band216B},
- {"RGBA", "A;16N", 16, band316B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16N, 16, band016B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16N, 16, band116B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16N, 16, band216B},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16N, 16, band316B},
#else
- {"RGB", "RGB;16N", 48, unpackRGB16L},
- {"RGB", "RGBX;16N", 64, unpackRGBA16L},
- {"RGBA", "RGBa;16N", 64, unpackRGBa16L},
- {"RGBA", "RGBA;16N", 64, unpackRGBA16L},
- {"RGBX", "RGBX;16N", 64, unpackRGBA16L},
- {"RGB", "R;16N", 16, band016L},
- {"RGB", "G;16N", 16, band116L},
- {"RGB", "B;16N", 16, band216L},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGB_16N, 48, unpackRGB16L},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBa_16N, 64, unpackRGBa16L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_RGBA_16N, 64, unpackRGBA16L},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16N, 64, unpackRGBA16L},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_R_16N, 16, band016L},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_G_16N, 16, band116L},
+ {IMAGING_MODE_RGB, IMAGING_RAWMODE_B_16N, 16, band216L},
- {"RGBA", "R;16N", 16, band016L},
- {"RGBA", "G;16N", 16, band116L},
- {"RGBA", "B;16N", 16, band216L},
- {"RGBA", "A;16N", 16, band316L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_R_16N, 16, band016L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_G_16N, 16, band116L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_B_16N, 16, band216L},
+ {IMAGING_MODE_RGBA, IMAGING_RAWMODE_A_16N, 16, band316L},
#endif
/* true colour w. alpha premultiplied */
- {"RGBa", "RGBa", 32, copy4},
- {"RGBa", "BGRa", 32, unpackBGRA},
- {"RGBa", "aRGB", 32, unpackARGB},
- {"RGBa", "aBGR", 32, unpackABGR},
+ {IMAGING_MODE_RGBa, IMAGING_RAWMODE_RGBa, 32, copy4},
+ {IMAGING_MODE_RGBa, IMAGING_RAWMODE_BGRa, 32, unpackBGRA},
+ {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aRGB, 32, unpackARGB},
+ {IMAGING_MODE_RGBa, IMAGING_RAWMODE_aBGR, 32, unpackABGR},
/* true colour w. padding */
- {"RGBX", "RGB", 24, ImagingUnpackRGB},
- {"RGBX", "RGB;L", 24, unpackRGBL},
- {"RGBX", "RGB;16B", 48, unpackRGB16B},
- {"RGBX", "BGR", 24, ImagingUnpackBGR},
- {"RGBX", "RGB;15", 16, ImagingUnpackRGB15},
- {"RGBX", "BGR;15", 16, ImagingUnpackBGR15},
- {"RGBX", "RGB;4B", 16, ImagingUnpackRGB4B},
- {"RGBX", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */
- {"RGBX", "RGBX", 32, copy4},
- {"RGBX", "RGBXX", 40, copy4skip1},
- {"RGBX", "RGBXXX", 48, copy4skip2},
- {"RGBX", "RGBX;L", 32, unpackRGBAL},
- {"RGBX", "RGBX;16L", 64, unpackRGBA16L},
- {"RGBX", "RGBX;16B", 64, unpackRGBA16B},
- {"RGBX", "BGRX", 32, ImagingUnpackBGRX},
- {"RGBX", "XRGB", 32, ImagingUnpackXRGB},
- {"RGBX", "XBGR", 32, ImagingUnpackXBGR},
- {"RGBX", "YCC;P", 24, ImagingUnpackYCC},
- {"RGBX", "R", 8, band0},
- {"RGBX", "G", 8, band1},
- {"RGBX", "B", 8, band2},
- {"RGBX", "X", 8, band3},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB, 24, ImagingUnpackRGB},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_L, 24, unpackRGBL},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_16B, 48, unpackRGB16B},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR, 24, ImagingUnpackBGR},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_15, 16, ImagingUnpackRGB15},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR_15, 16, ImagingUnpackBGR15},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGB_4B, 16, ImagingUnpackRGB4B},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGR_5, 16, ImagingUnpackBGR15}, /* compat */
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX, 32, copy4},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBXX, 40, copy4skip1},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBXXX, 48, copy4skip2},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_L, 32, unpackRGBAL},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16L, 64, unpackRGBA16L},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_RGBX_16B, 64, unpackRGBA16B},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_BGRX, 32, ImagingUnpackBGRX},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XRGB, 32, ImagingUnpackXRGB},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_XBGR, 32, ImagingUnpackXBGR},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_YCC_P, 24, ImagingUnpackYCC},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_R, 8, band0},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_G, 8, band1},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_B, 8, band2},
+ {IMAGING_MODE_RGBX, IMAGING_RAWMODE_X, 8, band3},
/* colour separation */
- {"CMYK", "CMYK", 32, copy4},
- {"CMYK", "CMYKX", 40, copy4skip1},
- {"CMYK", "CMYKXX", 48, copy4skip2},
- {"CMYK", "CMYK;I", 32, unpackCMYKI},
- {"CMYK", "CMYK;L", 32, unpackRGBAL},
- {"CMYK", "CMYK;16L", 64, unpackRGBA16L},
- {"CMYK", "CMYK;16B", 64, unpackRGBA16B},
- {"CMYK", "C", 8, band0},
- {"CMYK", "M", 8, band1},
- {"CMYK", "Y", 8, band2},
- {"CMYK", "K", 8, band3},
- {"CMYK", "C;I", 8, band0I},
- {"CMYK", "M;I", 8, band1I},
- {"CMYK", "Y;I", 8, band2I},
- {"CMYK", "K;I", 8, band3I},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK, 32, copy4},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYKX, 40, copy4skip1},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYKXX, 48, copy4skip2},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_I, 32, unpackCMYKI},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_L, 32, unpackRGBAL},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16L, 64, unpackRGBA16L},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16B, 64, unpackRGBA16B},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C, 8, band0},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M, 8, band1},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y, 8, band2},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K, 8, band3},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_C_I, 8, band0I},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_M_I, 8, band1I},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y_I, 8, band2I},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_K_I, 8, band3I},
#ifdef WORDS_BIGENDIAN
- {"CMYK", "CMYK;16N", 64, unpackRGBA16B},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16N, 64, unpackRGBA16B},
#else
- {"CMYK", "CMYK;16N", 64, unpackRGBA16L},
+ {IMAGING_MODE_CMYK, IMAGING_RAWMODE_CMYK_16N, 64, unpackRGBA16L},
#endif
/* video (YCbCr) */
- {"YCbCr", "YCbCr", 24, ImagingUnpackRGB},
- {"YCbCr", "YCbCr;L", 24, unpackRGBL},
- {"YCbCr", "YCbCrX", 32, copy4},
- {"YCbCr", "YCbCrK", 32, copy4},
+ {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr, 24, ImagingUnpackRGB},
+ {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr_L, 24, unpackRGBL},
+ {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrX, 32, copy4},
+ {IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCrK, 32, copy4},
/* LAB Color */
- {"LAB", "LAB", 24, ImagingUnpackLAB},
- {"LAB", "L", 8, band0},
- {"LAB", "A", 8, band1},
- {"LAB", "B", 8, band2},
+ {IMAGING_MODE_LAB, IMAGING_RAWMODE_LAB, 24, ImagingUnpackLAB},
+ {IMAGING_MODE_LAB, IMAGING_RAWMODE_L, 8, band0},
+ {IMAGING_MODE_LAB, IMAGING_RAWMODE_A, 8, band1},
+ {IMAGING_MODE_LAB, IMAGING_RAWMODE_B, 8, band2},
/* HSV Color */
- {"HSV", "HSV", 24, ImagingUnpackRGB},
- {"HSV", "H", 8, band0},
- {"HSV", "S", 8, band1},
- {"HSV", "V", 8, band2},
+ {IMAGING_MODE_HSV, IMAGING_RAWMODE_HSV, 24, ImagingUnpackRGB},
+ {IMAGING_MODE_HSV, IMAGING_RAWMODE_H, 8, band0},
+ {IMAGING_MODE_HSV, IMAGING_RAWMODE_S, 8, band1},
+ {IMAGING_MODE_HSV, IMAGING_RAWMODE_V, 8, band2},
/* integer variations */
- {"I", "I", 32, copy4},
- {"I", "I;8", 8, unpackI8},
- {"I", "I;8S", 8, unpackI8S},
- {"I", "I;16", 16, unpackI16},
- {"I", "I;16S", 16, unpackI16S},
- {"I", "I;16B", 16, unpackI16B},
- {"I", "I;16BS", 16, unpackI16BS},
- {"I", "I;16N", 16, unpackI16N},
- {"I", "I;16NS", 16, unpackI16NS},
- {"I", "I;32", 32, unpackI32},
- {"I", "I;32S", 32, unpackI32S},
- {"I", "I;32B", 32, unpackI32B},
- {"I", "I;32BS", 32, unpackI32BS},
- {"I", "I;32N", 32, unpackI32N},
- {"I", "I;32NS", 32, unpackI32NS},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I, 32, copy4},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_8, 8, unpackI8},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_8S, 8, unpackI8S},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_16, 16, unpackI16},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_16S, 16, unpackI16S},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_16B, 16, unpackI16B},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_16BS, 16, unpackI16BS},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_16N, 16, unpackI16N},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_16NS, 16, unpackI16NS},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_32, 32, unpackI32},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_32S, 32, unpackI32S},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_32B, 32, unpackI32B},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_32BS, 32, unpackI32BS},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_32N, 32, unpackI32N},
+ {IMAGING_MODE_I, IMAGING_RAWMODE_I_32NS, 32, unpackI32NS},
/* floating point variations */
- {"F", "F", 32, copy4},
- {"F", "F;8", 8, unpackF8},
- {"F", "F;8S", 8, unpackF8S},
- {"F", "F;16", 16, unpackF16},
- {"F", "F;16S", 16, unpackF16S},
- {"F", "F;16B", 16, unpackF16B},
- {"F", "F;16BS", 16, unpackF16BS},
- {"F", "F;16N", 16, unpackF16N},
- {"F", "F;16NS", 16, unpackF16NS},
- {"F", "F;32", 32, unpackF32},
- {"F", "F;32S", 32, unpackF32S},
- {"F", "F;32B", 32, unpackF32B},
- {"F", "F;32BS", 32, unpackF32BS},
- {"F", "F;32N", 32, unpackF32N},
- {"F", "F;32NS", 32, unpackF32NS},
- {"F", "F;32F", 32, unpackF32F},
- {"F", "F;32BF", 32, unpackF32BF},
- {"F", "F;32NF", 32, unpackF32NF},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F, 32, copy4},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_8, 8, unpackF8},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_8S, 8, unpackF8S},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_16, 16, unpackF16},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_16S, 16, unpackF16S},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_16B, 16, unpackF16B},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_16BS, 16, unpackF16BS},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_16N, 16, unpackF16N},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_16NS, 16, unpackF16NS},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_32, 32, unpackF32},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_32S, 32, unpackF32S},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_32B, 32, unpackF32B},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_32BS, 32, unpackF32BS},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_32N, 32, unpackF32N},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NS, 32, unpackF32NS},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_32F, 32, unpackF32F},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_32BF, 32, unpackF32BF},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_32NF, 32, unpackF32NF},
#ifdef FLOAT64
- {"F", "F;64F", 64, unpackF64F},
- {"F", "F;64BF", 64, unpackF64BF},
- {"F", "F;64NF", 64, unpackF64NF},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_64F, 64, unpackF64F},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_64BF, 64, unpackF64BF},
+ {IMAGING_MODE_F, IMAGING_RAWMODE_F_64NF, 64, unpackF64NF},
#endif
/* storage modes */
- {"I;16", "I;16", 16, copy2},
- {"I;16B", "I;16B", 16, copy2},
- {"I;16L", "I;16L", 16, copy2},
- {"I;16N", "I;16N", 16, copy2},
+ {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16, 16, copy2},
+ {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16B, 16, copy2},
+ {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16L, 16, copy2},
+ {IMAGING_MODE_I_16N, IMAGING_RAWMODE_I_16N, 16, copy2},
- {"I;16", "I;16B", 16, unpackI16B_I16},
- {"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian.
- {"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian.
- {"I;16B", "I;16N", 16, unpackI16N_I16B},
+ {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16B, 16, unpackI16B_I16},
+ {IMAGING_MODE_I_16B, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16B},
+ {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16R, 16, unpackI16R_I16},
- {"I;16", "I;16R", 16, unpackI16R_I16},
+ // LibTiff native->image endian.
+ {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16},
+ {IMAGING_MODE_I_16L, IMAGING_RAWMODE_I_16N, 16, unpackI16N_I16},
- {"I;16", "I;12", 12, unpackI12_I16}, // 12 bit Tiffs stored in 16bits.
-
- {NULL} /* sentinel */
+ // 12 bit Tiffs stored in 16bits.
+ {IMAGING_MODE_I_16, IMAGING_RAWMODE_I_12, 12, unpackI12_I16}
};
ImagingShuffler
-ImagingFindUnpacker(const char *mode, const char *rawmode, int *bits_out) {
- int i;
-
- /* find a suitable pixel unpacker */
- for (i = 0; unpackers[i].rawmode; i++) {
- if (strcmp(unpackers[i].mode, mode) == 0 &&
- strcmp(unpackers[i].rawmode, rawmode) == 0) {
+ImagingFindUnpacker(const ModeID mode, const RawModeID rawmode, int *bits_out) {
+ for (size_t i = 0; i < sizeof(unpackers) / sizeof(*unpackers); i++) {
+ if (unpackers[i].mode == mode && unpackers[i].rawmode == rawmode) {
if (bits_out) {
*bits_out = unpackers[i].bits;
}
diff --git a/src/map.c b/src/map.c
index 9a3144ab9..6f66b0cc5 100644
--- a/src/map.c
+++ b/src/map.c
@@ -55,7 +55,7 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) {
PyObject *target;
Py_buffer view;
- char *mode;
+ char *mode_name;
char *codec;
Py_ssize_t offset;
int xsize, ysize;
@@ -70,7 +70,7 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) {
&ysize,
&codec,
&offset,
- &mode,
+ &mode_name,
&stride,
&ystep
)) {
@@ -82,10 +82,12 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) {
return NULL;
}
+ const ModeID mode = findModeID(mode_name);
+
if (stride <= 0) {
- if (!strcmp(mode, "L") || !strcmp(mode, "P")) {
+ if (mode == IMAGING_MODE_L || mode == IMAGING_MODE_P) {
stride = xsize;
- } else if (!strncmp(mode, "I;16", 4)) {
+ } else if (isModeI16(mode)) {
stride = xsize * 2;
} else {
stride = xsize * 4;
diff --git a/src/thirdparty/raqm/COPYING b/src/thirdparty/raqm/COPYING
index 97e2489b7..964318a8a 100644
--- a/src/thirdparty/raqm/COPYING
+++ b/src/thirdparty/raqm/COPYING
@@ -1,7 +1,7 @@
The MIT License (MIT)
Copyright © 2015 Information Technology Authority (ITA)
-Copyright © 2016-2023 Khaled Hosny
+Copyright © 2016-2025 Khaled Hosny
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/src/thirdparty/raqm/NEWS b/src/thirdparty/raqm/NEWS
index e8bf32e0b..fb432cffb 100644
--- a/src/thirdparty/raqm/NEWS
+++ b/src/thirdparty/raqm/NEWS
@@ -1,3 +1,19 @@
+Overview of changes leading to 0.10.3
+Tuesday, August 5, 2025
+====================================
+
+Fix raqm_set_text_utf8/utf16 reading beyond len for multibyte.
+
+Support building against SheenBidi 2.9.
+
+Fix deprecation warning with latest HarfBuzz.
+
+Overview of changes leading to 0.10.2
+Sunday, September 22, 2024
+====================================
+
+Fix Unicode codepoint conversion from UTF-16.
+
Overview of changes leading to 0.10.1
Wednesday, April 12, 2023
====================================
diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h
index 62d2d2064..f2dd61cf6 100644
--- a/src/thirdparty/raqm/raqm-version.h
+++ b/src/thirdparty/raqm/raqm-version.h
@@ -33,9 +33,9 @@
#define RAQM_VERSION_MAJOR 0
#define RAQM_VERSION_MINOR 10
-#define RAQM_VERSION_MICRO 1
+#define RAQM_VERSION_MICRO 3
-#define RAQM_VERSION_STRING "0.10.1"
+#define RAQM_VERSION_STRING "0.10.3"
#define RAQM_VERSION_ATLEAST(major,minor,micro) \
((major)*10000+(minor)*100+(micro) <= \
diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c
index 2b331e1af..9ecc5cac8 100644
--- a/src/thirdparty/raqm/raqm.c
+++ b/src/thirdparty/raqm/raqm.c
@@ -30,7 +30,11 @@
#include
#ifdef RAQM_SHEENBIDI
+#ifdef RAQM_SHEENBIDI_GT_2_9
+#include
+#else
#include
+#endif
#else
#ifdef HAVE_FRIBIDI_SYSTEM
#include
@@ -546,34 +550,32 @@ raqm_set_text (raqm_t *rq,
return true;
}
-static void *
-_raqm_get_utf8_codepoint (const void *str,
+static const char *
+_raqm_get_utf8_codepoint (const char *str,
uint32_t *out_codepoint)
{
- const char *s = (const char *)str;
-
- if (0xf0 == (0xf8 & s[0]))
+ if (0xf0 == (0xf8 & str[0]))
{
- *out_codepoint = ((0x07 & s[0]) << 18) | ((0x3f & s[1]) << 12) | ((0x3f & s[2]) << 6) | (0x3f & s[3]);
- s += 4;
+ *out_codepoint = ((0x07 & str[0]) << 18) | ((0x3f & str[1]) << 12) | ((0x3f & str[2]) << 6) | (0x3f & str[3]);
+ str += 4;
}
- else if (0xe0 == (0xf0 & s[0]))
+ else if (0xe0 == (0xf0 & str[0]))
{
- *out_codepoint = ((0x0f & s[0]) << 12) | ((0x3f & s[1]) << 6) | (0x3f & s[2]);
- s += 3;
+ *out_codepoint = ((0x0f & str[0]) << 12) | ((0x3f & str[1]) << 6) | (0x3f & str[2]);
+ str += 3;
}
- else if (0xc0 == (0xe0 & s[0]))
+ else if (0xc0 == (0xe0 & str[0]))
{
- *out_codepoint = ((0x1f & s[0]) << 6) | (0x3f & s[1]);
- s += 2;
+ *out_codepoint = ((0x1f & str[0]) << 6) | (0x3f & str[1]);
+ str += 2;
}
else
{
- *out_codepoint = s[0];
- s += 1;
+ *out_codepoint = str[0];
+ str += 1;
}
- return (void *)s;
+ return str;
}
static size_t
@@ -585,42 +587,41 @@ _raqm_u8_to_u32 (const char *text, size_t len, uint32_t *unicode)
while ((*in_utf8 != '\0') && (in_len < len))
{
- in_utf8 = _raqm_get_utf8_codepoint (in_utf8, out_utf32);
+ const char *out_utf8 = _raqm_get_utf8_codepoint (in_utf8, out_utf32);
+ in_len += out_utf8 - in_utf8;
+ in_utf8 = out_utf8;
++out_utf32;
- ++in_len;
}
return (out_utf32 - unicode);
}
-static void *
-_raqm_get_utf16_codepoint (const void *str,
- uint32_t *out_codepoint)
+static const uint16_t *
+_raqm_get_utf16_codepoint (const uint16_t *str,
+ uint32_t *out_codepoint)
{
- const uint16_t *s = (const uint16_t *)str;
-
- if (s[0] > 0xD800 && s[0] < 0xDBFF)
+ if (str[0] >= 0xD800 && str[0] <= 0xDBFF)
{
- if (s[1] > 0xDC00 && s[1] < 0xDFFF)
+ if (str[1] >= 0xDC00 && str[1] <= 0xDFFF)
{
- uint32_t X = ((s[0] & ((1 << 6) -1)) << 10) | (s[1] & ((1 << 10) -1));
- uint32_t W = (s[0] >> 6) & ((1 << 5) - 1);
+ uint32_t X = ((str[0] & ((1 << 6) -1)) << 10) | (str[1] & ((1 << 10) -1));
+ uint32_t W = (str[0] >> 6) & ((1 << 5) - 1);
*out_codepoint = (W+1) << 16 | X;
- s += 2;
+ str += 2;
}
else
{
/* A single high surrogate, this is an error. */
- *out_codepoint = s[0];
- s += 1;
+ *out_codepoint = str[0];
+ str += 1;
}
}
else
{
- *out_codepoint = s[0];
- s += 1;
+ *out_codepoint = str[0];
+ str += 1;
}
- return (void *)s;
+ return str;
}
static size_t
@@ -632,9 +633,10 @@ _raqm_u16_to_u32 (const uint16_t *text, size_t len, uint32_t *unicode)
while ((*in_utf16 != '\0') && (in_len < len))
{
- in_utf16 = _raqm_get_utf16_codepoint (in_utf16, out_utf32);
+ const uint16_t *out_utf16 = _raqm_get_utf16_codepoint (in_utf16, out_utf32);
+ in_len += (out_utf16 - in_utf16);
+ in_utf16 = out_utf16;
++out_utf32;
- ++in_len;
}
return (out_utf32 - unicode);
@@ -1114,12 +1116,12 @@ _raqm_set_spacing (raqm_t *rq,
{
if (_raqm_allowed_grapheme_boundary (rq->text[i], rq->text[i+1]))
{
- /* CSS word seperators, word spacing is only applied on these.*/
+ /* CSS word separators, word spacing is only applied on these.*/
if (rq->text[i] == 0x0020 || /* Space */
rq->text[i] == 0x00A0 || /* No Break Space */
rq->text[i] == 0x1361 || /* Ethiopic Word Space */
- rq->text[i] == 0x10100 || /* Aegean Word Seperator Line */
- rq->text[i] == 0x10101 || /* Aegean Word Seperator Dot */
+ rq->text[i] == 0x10100 || /* Aegean Word Separator Line */
+ rq->text[i] == 0x10101 || /* Aegean Word Separator Dot */
rq->text[i] == 0x1039F || /* Ugaric Word Divider */
rq->text[i] == 0x1091F) /* Phoenician Word Separator */
{
@@ -2167,6 +2169,10 @@ _raqm_ft_transform (int *x,
*y = vector.y;
}
+#if !HB_VERSION_ATLEAST (10, 4, 0)
+# define hb_ft_font_get_ft_face hb_ft_font_get_face
+#endif
+
static bool
_raqm_shape (raqm_t *rq)
{
@@ -2199,7 +2205,7 @@ _raqm_shape (raqm_t *rq)
hb_glyph_position_t *pos;
unsigned int len;
- FT_Get_Transform (hb_ft_font_get_face (run->font), &matrix, NULL);
+ FT_Get_Transform (hb_ft_font_get_ft_face (run->font), &matrix, NULL);
pos = hb_buffer_get_glyph_positions (run->buffer, &len);
info = hb_buffer_get_glyph_infos (run->buffer, &len);
diff --git a/tox.ini b/tox.ini
index 967d4b537..d58fd67b6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,7 +3,7 @@ requires =
tox>=4.2
env_list =
lint
- py{py3, 314, 313, 312, 311, 310, 39}
+ py{py3, 314, 313, 312, 311, 310}
[testenv]
deps =
@@ -29,7 +29,5 @@ commands =
skip_install = true
deps =
-r .ci/requirements-mypy.txt
-extras =
- typing
commands =
- mypy conftest.py selftest.py setup.py docs src winbuild Tests {posargs}
+ mypy conftest.py selftest.py setup.py checks docs src winbuild Tests {posargs}
diff --git a/wheels/dependency_licenses/ZSTD.txt b/wheels/dependency_licenses/ZSTD.txt
new file mode 100644
index 000000000..75800288c
--- /dev/null
+++ b/wheels/dependency_licenses/ZSTD.txt
@@ -0,0 +1,30 @@
+BSD License
+
+For Zstandard software
+
+Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name Facebook, nor Meta, nor the names of its contributors may
+ be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/wheels/multibuild b/wheels/multibuild
index 42d761728..647393271 160000
--- a/wheels/multibuild
+++ b/wheels/multibuild
@@ -1 +1 @@
-Subproject commit 42d761728d141d8462cd9943f4329f12fe62b155
+Subproject commit 64739327166fcad1fa41ad9b23fa910fa244c84f
diff --git a/winbuild/README.md b/winbuild/README.md
index 62345af60..db71f094e 100644
--- a/winbuild/README.md
+++ b/winbuild/README.md
@@ -16,7 +16,7 @@ For more extensive info, see the [Windows build instructions](build.rst).
Here's an example script to build on Windows:
```
-set PYTHON=C:\Python39\bin
+set PYTHON=C:\Python310\bin
cd /D C:\Pillow\winbuild
%PYTHON%\python.exe build_prepare.py -v --depends=C:\pillow-depends
build\build_dep_all.cmd
diff --git a/winbuild/build.rst b/winbuild/build.rst
index aa4677ad5..23b26c422 100644
--- a/winbuild/build.rst
+++ b/winbuild/build.rst
@@ -115,7 +115,7 @@ Example
Here's an example script to build on Windows::
- set PYTHON=C:\Python39\bin
+ set PYTHON=C:\Python310\bin
cd /D C:\Pillow\winbuild
%PYTHON%\python.exe build_prepare.py -v --depends C:\pillow-depends
build\build_dep_all.cmd
diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py
index 187d07b20..cd2ef13c1 100644
--- a/winbuild/build_prepare.py
+++ b/winbuild/build_prepare.py
@@ -113,20 +113,20 @@ ARCHITECTURES = {
}
V = {
- "BROTLI": "1.1.0",
- "FREETYPE": "2.13.3",
+ "BROTLI": "1.2.0",
+ "FREETYPE": "2.14.1",
"FRIBIDI": "1.0.16",
- "HARFBUZZ": "11.2.1",
- "JPEGTURBO": "3.1.1",
+ "HARFBUZZ": "12.2.0",
+ "JPEGTURBO": "3.1.2",
"LCMS2": "2.17",
"LIBAVIF": "1.3.0",
- "LIBIMAGEQUANT": "4.3.4",
- "LIBPNG": "1.6.49",
- "LIBWEBP": "1.5.0",
- "OPENJPEG": "2.5.3",
- "TIFF": "4.7.0",
+ "LIBIMAGEQUANT": "4.4.1",
+ "LIBPNG": "1.6.51",
+ "LIBWEBP": "1.6.0",
+ "OPENJPEG": "2.5.4",
+ "TIFF": "4.7.1",
"XZ": "5.8.1",
- "ZLIBNG": "2.2.4",
+ "ZLIBNG": "2.3.1",
}
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
@@ -149,18 +149,17 @@ DEPS: dict[str, dict[str, Any]] = {
},
"build": [
*cmds_cmake(
- ("jpeg-static", "cjpeg-static", "djpeg-static"),
+ ("jpeg-static", "djpeg-static"),
"-DENABLE_SHARED:BOOL=FALSE",
"-DWITH_JPEG8:BOOL=TRUE",
"-DWITH_CRT_DLL:BOOL=TRUE",
),
cmd_copy("jpeg-static.lib", "libjpeg.lib"),
- cmd_copy("cjpeg-static.exe", "cjpeg.exe"),
cmd_copy("djpeg-static.exe", "djpeg.exe"),
],
"headers": ["jconfig.h", r"src\j*.h"],
"libs": ["libjpeg.lib"],
- "bins": ["cjpeg.exe", "djpeg.exe"],
+ "bins": ["djpeg.exe"],
},
"zlib": {
"url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.tar.gz",
@@ -168,12 +167,12 @@ DEPS: dict[str, dict[str, Any]] = {
"license": "LICENSE.md",
"patch": {
r"CMakeLists.txt": {
- "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlib)", # noqa: E501
+ "set_target_properties(zlib-ng PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib-ng PROPERTIES OUTPUT_NAME zlib)", # noqa: E501
},
},
"build": [
*cmds_cmake(
- "zlib", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON"
+ "zlib-ng", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON"
),
],
"headers": [r"z*.h"],
@@ -229,12 +228,6 @@ DEPS: dict[str, dict[str, Any]] = {
# link against libwebp.lib
"#ifdef WEBP_SUPPORT": '#ifdef WEBP_SUPPORT\n#pragma comment(lib, "libwebp.lib")', # noqa: E501
},
- r"test\CMakeLists.txt": {
- "add_executable(test_write_read_tags ../placeholder.h)": "",
- "target_sources(test_write_read_tags PRIVATE test_write_read_tags.c)": "", # noqa: E501
- "target_link_libraries(test_write_read_tags PRIVATE tiff)": "",
- "list(APPEND simple_tests test_write_read_tags)": "",
- },
},
"build": [
*cmds_cmake(
@@ -242,7 +235,6 @@ DEPS: dict[str, dict[str, Any]] = {
"-DBUILD_SHARED_LIBS:BOOL=OFF",
"-DWebP_LIBRARY=libwebp",
'-DCMAKE_C_FLAGS="-nologo -DLZMA_API_STATIC"',
- "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
)
],
"headers": [r"libtiff\tiff*.h"],