mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-10 16:22:22 +03:00
Merge branch 'main' into progress
This commit is contained in:
commit
23373c8a7e
|
@ -18,6 +18,5 @@ exclude_also =
|
||||||
|
|
||||||
[run]
|
[run]
|
||||||
omit =
|
omit =
|
||||||
Tests/32bit_segfault_check.py
|
checks/*.py
|
||||||
Tests/check_*.py
|
|
||||||
Tests/createfontdatachunk.py
|
Tests/createfontdatachunk.py
|
||||||
|
|
207
.github/workflows/wheels-dependencies.sh
vendored
207
.github/workflows/wheels-dependencies.sh
vendored
|
@ -1,42 +1,98 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Setup that needs to be done before multibuild utils are invoked
|
# Safety check - Pillow builds require that CIBW_ARCHS is set, and that it only
|
||||||
PROJECTDIR=$(pwd)
|
# contains a single value (even though cibuildwheel allows multiple values in
|
||||||
if [[ "$(uname -s)" == "Darwin" ]]; then
|
# CIBW_ARCHS). This check doesn't work on Linux because of how the CIBW_ARCHS
|
||||||
# Safety check - macOS builds require that CIBW_ARCHS is set, and that it
|
# variable is exposed.
|
||||||
# only contains a single value (even though cibuildwheel allows multiple
|
function check_cibw_archs {
|
||||||
# values in CIBW_ARCHS).
|
|
||||||
if [[ -z "$CIBW_ARCHS" ]]; then
|
if [[ -z "$CIBW_ARCHS" ]]; then
|
||||||
echo "ERROR: Pillow macOS builds require CIBW_ARCHS be defined."
|
echo "ERROR: Pillow builds require CIBW_ARCHS be defined."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [[ "$CIBW_ARCHS" == *" "* ]]; then
|
if [[ "$CIBW_ARCHS" == *" "* ]]; then
|
||||||
echo "ERROR: Pillow macOS builds only support a single architecture in CIBW_ARCHS."
|
echo "ERROR: Pillow builds only support a single architecture in CIBW_ARCHS."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setup that needs to be done before multibuild utils are invoked. Process
|
||||||
|
# potential cross-build platforms before native platforms to ensure that we pick
|
||||||
|
# up the cross environment.
|
||||||
|
PROJECTDIR=$(pwd)
|
||||||
|
if [[ "$CIBW_PLATFORM" == "ios" ]]; then
|
||||||
|
check_cibw_archs
|
||||||
|
# On iOS, CIBW_ARCHS is actually a multi-arch - arm64_iphoneos,
|
||||||
|
# arm64_iphonesimulator or x86_64_iphonesimulator. Split into the CPU
|
||||||
|
# platform, and the iOS SDK.
|
||||||
|
PLAT=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\1/")
|
||||||
|
IOS_SDK=$(echo $CIBW_ARCHS | sed "s/\(.*\)_\(.*\)/\2/")
|
||||||
|
|
||||||
|
# Build iOS builds in `build/iphoneos` or `build/iphonesimulator`
|
||||||
|
# (depending on the build target). Install them into `build/deps/iphoneos`
|
||||||
|
# or `build/deps/iphonesimulator`
|
||||||
|
WORKDIR=$(pwd)/build/$IOS_SDK
|
||||||
|
BUILD_PREFIX=$(pwd)/build/deps/$IOS_SDK
|
||||||
|
PATCH_DIR=$(pwd)/patches/iOS
|
||||||
|
|
||||||
|
# GNU tooling insists on using aarch64 rather than arm64
|
||||||
|
if [[ $PLAT == "arm64" ]]; then
|
||||||
|
GNU_ARCH=aarch64
|
||||||
|
else
|
||||||
|
GNU_ARCH=x86_64
|
||||||
|
fi
|
||||||
|
|
||||||
|
IOS_SDK_PATH=$(xcrun --sdk $IOS_SDK --show-sdk-path)
|
||||||
|
CMAKE_SYSTEM_NAME=iOS
|
||||||
|
IOS_HOST_TRIPLE=$PLAT-apple-ios$IPHONEOS_DEPLOYMENT_TARGET
|
||||||
|
if [[ "$IOS_SDK" == "iphonesimulator" ]]; then
|
||||||
|
IOS_HOST_TRIPLE=$IOS_HOST_TRIPLE-simulator
|
||||||
|
fi
|
||||||
|
|
||||||
|
# GNU Autotools doesn't recognize the existence of arm64-apple-ios-simulator
|
||||||
|
# as a valid host. However, the only difference between arm64-apple-ios and
|
||||||
|
# arm64-apple-ios-simulator is the choice of sysroot, and that is
|
||||||
|
# coordinated by CC, CFLAGS etc. From the perspective of configure, the two
|
||||||
|
# platforms are identical, so we can use arm64-apple-ios consistently.
|
||||||
|
# This (mostly) avoids us needing to patch config.sub in dependency sources.
|
||||||
|
HOST_CONFIGURE_FLAGS="--disable-shared --enable-static --host=$GNU_ARCH-apple-ios --build=$GNU_ARCH-apple-darwin"
|
||||||
|
|
||||||
|
# CMake has native support for iOS. However, most of that support is based
|
||||||
|
# on using the Xcode builder, which isn't very helpful for most of Pillow's
|
||||||
|
# dependencies. Therefore, we lean on the OSX configurations, plus CC, CFLAGS
|
||||||
|
# etc. to ensure the right sysroot is selected.
|
||||||
|
HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO"
|
||||||
|
|
||||||
|
# Meson needs to be pointed at a cross-platform configuration file
|
||||||
|
# This will be generated once CC etc. have been evaluated.
|
||||||
|
HOST_MESON_FLAGS="--cross-file $WORKDIR/meson-cross.txt -Dprefer_static=true -Ddefault_library=static"
|
||||||
|
|
||||||
|
elif [[ "$(uname -s)" == "Darwin" ]]; then
|
||||||
|
check_cibw_archs
|
||||||
# Build macOS dependencies in `build/darwin`
|
# Build macOS dependencies in `build/darwin`
|
||||||
# Install them into `build/deps/darwin`
|
# Install them into `build/deps/darwin`
|
||||||
|
PLAT=$CIBW_ARCHS
|
||||||
WORKDIR=$(pwd)/build/darwin
|
WORKDIR=$(pwd)/build/darwin
|
||||||
BUILD_PREFIX=$(pwd)/build/deps/darwin
|
BUILD_PREFIX=$(pwd)/build/deps/darwin
|
||||||
else
|
else
|
||||||
# Build prefix will default to /usr/local
|
# Build prefix will default to /usr/local
|
||||||
|
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
|
||||||
WORKDIR=$(pwd)/build
|
WORKDIR=$(pwd)/build
|
||||||
MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
|
MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
|
||||||
MB_ML_VER=${AUDITWHEEL_POLICY:9}
|
MB_ML_VER=${AUDITWHEEL_POLICY:9}
|
||||||
fi
|
fi
|
||||||
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
|
|
||||||
|
|
||||||
# Define custom utilities
|
# Define custom utilities
|
||||||
source wheels/multibuild/common_utils.sh
|
source wheels/multibuild/common_utils.sh
|
||||||
source wheels/multibuild/library_builders.sh
|
source wheels/multibuild/library_builders.sh
|
||||||
if [ -z "$IS_MACOS" ]; then
|
if [[ -z "$IS_MACOS" ]]; then
|
||||||
source wheels/multibuild/manylinux_utils.sh
|
source wheels/multibuild/manylinux_utils.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ARCHIVE_SDIR=pillow-depends-main
|
ARCHIVE_SDIR=pillow-depends-main
|
||||||
|
|
||||||
# Package versions for fresh source builds
|
# 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
|
FREETYPE_VERSION=2.13.3
|
||||||
HARFBUZZ_VERSION=11.2.1
|
HARFBUZZ_VERSION=11.2.1
|
||||||
LIBPNG_VERSION=1.6.49
|
LIBPNG_VERSION=1.6.49
|
||||||
|
@ -47,32 +103,58 @@ TIFF_VERSION=4.7.0
|
||||||
LCMS2_VERSION=2.17
|
LCMS2_VERSION=2.17
|
||||||
ZLIB_VERSION=1.3.1
|
ZLIB_VERSION=1.3.1
|
||||||
ZLIB_NG_VERSION=2.2.4
|
ZLIB_NG_VERSION=2.2.4
|
||||||
LIBWEBP_VERSION=1.5.0
|
LIBWEBP_VERSION=1.5.0 # Patched; next release won't need patching. See patch file.
|
||||||
BZIP2_VERSION=1.0.8
|
BZIP2_VERSION=1.0.8
|
||||||
LIBXCB_VERSION=1.17.0
|
LIBXCB_VERSION=1.17.0
|
||||||
BROTLI_VERSION=1.1.0
|
BROTLI_VERSION=1.1.0 # Patched; next release won't need patching. See patch file.
|
||||||
LIBAVIF_VERSION=1.3.0
|
LIBAVIF_VERSION=1.3.0
|
||||||
|
|
||||||
function build_pkg_config {
|
function build_pkg_config {
|
||||||
if [ -e pkg-config-stamp ]; then return; fi
|
if [ -e pkg-config-stamp ]; then return; fi
|
||||||
# This essentially duplicates the Homebrew recipe
|
# This essentially duplicates the Homebrew recipe.
|
||||||
CFLAGS="$CFLAGS -Wno-int-conversion" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
|
# On iOS, we need a binary that can be executed on the build machine; but we
|
||||||
|
# can create a host-specific pc-path to store iOS .pc files. To ensure a
|
||||||
|
# macOS-compatible build, we temporarily clear environment flags that set
|
||||||
|
# iOS-specific values.
|
||||||
|
if [[ -n "$IOS_SDK" ]]; then
|
||||||
|
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
|
||||||
|
ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET
|
||||||
|
unset HOST_CONFIGURE_FLAGS
|
||||||
|
unset IPHONEOS_DEPLOYMENT_TARGET
|
||||||
|
fi
|
||||||
|
|
||||||
|
CFLAGS="$CFLAGS -Wno-int-conversion" CPPFLAGS="" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \
|
||||||
--disable-debug --disable-host-tool --with-internal-glib \
|
--disable-debug --disable-host-tool --with-internal-glib \
|
||||||
--with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \
|
--with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \
|
||||||
--with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include
|
--with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include
|
||||||
|
|
||||||
|
if [[ -n "$IOS_SDK" ]]; then
|
||||||
|
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET=$ORIGINAL_IPHONEOS_DEPLOYMENT_TARGET
|
||||||
|
fi;
|
||||||
|
|
||||||
export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
|
export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config
|
||||||
touch pkg-config-stamp
|
touch pkg-config-stamp
|
||||||
}
|
}
|
||||||
|
|
||||||
function build_zlib_ng {
|
function build_zlib_ng {
|
||||||
if [ -e zlib-stamp ]; then return; fi
|
if [ -e zlib-stamp ]; then return; fi
|
||||||
|
# zlib-ng uses a "configure" script, but it's not a GNU autotools script, so
|
||||||
|
# it doesn't honor the usual flags. Temporarily disable any
|
||||||
|
# cross-compilation flags.
|
||||||
|
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
|
||||||
|
unset HOST_CONFIGURE_FLAGS
|
||||||
|
|
||||||
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat
|
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat
|
||||||
|
|
||||||
if [ -n "$IS_MACOS" ]; then
|
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
|
# 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
|
# @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
|
# DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an
|
||||||
# option to control the install_name.
|
# 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
|
install_name_tool -id $BUILD_PREFIX/lib/libz.1.dylib $BUILD_PREFIX/lib/libz.1.dylib
|
||||||
fi
|
fi
|
||||||
touch zlib-stamp
|
touch zlib-stamp
|
||||||
|
@ -82,7 +164,7 @@ function build_brotli {
|
||||||
if [ -e brotli-stamp ]; then return; fi
|
if [ -e brotli-stamp ]; then return; fi
|
||||||
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
|
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
|
||||||
(cd $out_dir \
|
(cd $out_dir \
|
||||||
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
|
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \
|
||||||
&& make install)
|
&& make install)
|
||||||
touch brotli-stamp
|
touch brotli-stamp
|
||||||
}
|
}
|
||||||
|
@ -93,7 +175,7 @@ function build_harfbuzz {
|
||||||
|
|
||||||
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
|
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
|
||||||
(cd $out_dir \
|
(cd $out_dir \
|
||||||
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled)
|
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=minsize -Dfreetype=enabled -Dglib=disabled -Dtests=disabled $HOST_MESON_FLAGS)
|
||||||
(cd $out_dir/build \
|
(cd $out_dir/build \
|
||||||
&& meson install)
|
&& meson install)
|
||||||
touch harfbuzz-stamp
|
touch harfbuzz-stamp
|
||||||
|
@ -164,19 +246,19 @@ function build {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
|
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
|
||||||
if [ -n "$IS_MACOS" ]; then
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
|
build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
|
||||||
build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib
|
build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib
|
||||||
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
|
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
|
||||||
else
|
else
|
||||||
sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
|
sed "s/\${pc_sysrootdir\}//" $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
|
||||||
fi
|
fi
|
||||||
build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
|
build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib
|
||||||
|
|
||||||
build_libjpeg_turbo
|
build_libjpeg_turbo
|
||||||
if [ -n "$IS_MACOS" ]; then
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
# Custom tiff build to include jpeg; by default, configure won't include
|
# Custom tiff build to include jpeg; by default, configure won't include
|
||||||
# headers/libs in the custom macOS prefix. Explicitly disable webp,
|
# headers/libs in the custom macOS/iOS prefix. Explicitly disable webp,
|
||||||
# libdeflate and zstd, because on x86_64 macs, it will pick up the
|
# libdeflate and zstd, because on x86_64 macs, it will pick up the
|
||||||
# Homebrew versions of those libraries from /usr/local.
|
# Homebrew versions of those libraries from /usr/local.
|
||||||
build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \
|
build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \
|
||||||
|
@ -186,7 +268,10 @@ function build {
|
||||||
build_tiff
|
build_tiff
|
||||||
fi
|
fi
|
||||||
|
|
||||||
build_libavif
|
if [[ -z "$IOS_SDK" ]]; then
|
||||||
|
# Short term workaround; don't build libavif on iOS
|
||||||
|
build_libavif
|
||||||
|
fi
|
||||||
build_libpng
|
build_libpng
|
||||||
build_lcms2
|
build_lcms2
|
||||||
build_openjpeg
|
build_openjpeg
|
||||||
|
@ -201,14 +286,44 @@ function build {
|
||||||
|
|
||||||
build_brotli
|
build_brotli
|
||||||
|
|
||||||
if [ -n "$IS_MACOS" ]; then
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
# Custom freetype build
|
# Custom freetype build
|
||||||
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
|
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
|
||||||
else
|
else
|
||||||
build_freetype
|
build_freetype
|
||||||
fi
|
fi
|
||||||
|
|
||||||
build_harfbuzz
|
if [[ -z "$IOS_SDK" ]]; then
|
||||||
|
# On iOS, there's no vendor-provided raqm, and we can't ship it due to
|
||||||
|
# licensing, so there's no point building harfbuzz.
|
||||||
|
build_harfbuzz
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_meson_cross_config {
|
||||||
|
cat << EOF > $WORKDIR/meson-cross.txt
|
||||||
|
[binaries]
|
||||||
|
pkg-config = '$BUILD_PREFIX/bin/pkg-config'
|
||||||
|
cmake = '$(which cmake)'
|
||||||
|
c = '$CC'
|
||||||
|
cpp = '$CXX'
|
||||||
|
strip = '$STRIP'
|
||||||
|
|
||||||
|
[built-in options]
|
||||||
|
c_args = '$CFLAGS -I$BUILD_PREFIX/include'
|
||||||
|
cpp_args = '$CXXFLAGS -I$BUILD_PREFIX/include'
|
||||||
|
c_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
|
||||||
|
cpp_link_args = '$CFLAGS -L$BUILD_PREFIX/lib'
|
||||||
|
|
||||||
|
[host_machine]
|
||||||
|
system = 'darwin'
|
||||||
|
subsystem = 'ios'
|
||||||
|
kernel = 'xnu'
|
||||||
|
cpu_family = '$(uname -m)'
|
||||||
|
cpu = '$(uname -m)'
|
||||||
|
endian = 'little'
|
||||||
|
|
||||||
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
# Perform all dependency builds in the build subfolder.
|
# Perform all dependency builds in the build subfolder.
|
||||||
|
@ -227,24 +342,40 @@ if [[ ! -d $WORKDIR/pillow-depends-main ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$IS_MACOS" ]]; then
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
# Homebrew (or similar packaging environments) install can contain some of
|
|
||||||
# the libraries that we're going to build. However, they may be compiled
|
|
||||||
# with a MACOSX_DEPLOYMENT_TARGET that doesn't match what we want to use,
|
|
||||||
# and they may bring in other dependencies that we don't want. The same will
|
|
||||||
# be true of any other locations on the path. To avoid conflicts, strip the
|
|
||||||
# path down to the bare minimum (which, on macOS, won't include any
|
|
||||||
# development dependencies).
|
|
||||||
export PATH="$BUILD_PREFIX/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
|
|
||||||
export CMAKE_PREFIX_PATH=$BUILD_PREFIX
|
|
||||||
|
|
||||||
# Ensure the basic structure of the build prefix directory exists.
|
# Ensure the basic structure of the build prefix directory exists.
|
||||||
mkdir -p "$BUILD_PREFIX/bin"
|
mkdir -p "$BUILD_PREFIX/bin"
|
||||||
mkdir -p "$BUILD_PREFIX/lib"
|
mkdir -p "$BUILD_PREFIX/lib"
|
||||||
|
|
||||||
# Ensure pkg-config is available
|
# Ensure pkg-config is available. This is done *before* setting CC, CFLAGS
|
||||||
|
# etc. to ensure that the build is *always* a macOS build, even when building
|
||||||
|
# for iOS.
|
||||||
build_pkg_config
|
build_pkg_config
|
||||||
# Ensure cmake is available
|
|
||||||
|
# Ensure cmake is available, and that the default prefix used by CMake is
|
||||||
|
# the build prefix
|
||||||
python3 -m pip install cmake
|
python3 -m pip install cmake
|
||||||
|
export CMAKE_PREFIX_PATH=$BUILD_PREFIX
|
||||||
|
|
||||||
|
if [[ -n "$IOS_SDK" ]]; then
|
||||||
|
export AR="$(xcrun --find --sdk $IOS_SDK ar)"
|
||||||
|
export CPP="$(xcrun --find --sdk $IOS_SDK clang) -E"
|
||||||
|
export CC=$(xcrun --find --sdk $IOS_SDK clang)
|
||||||
|
export CXX=$(xcrun --find --sdk $IOS_SDK clang++)
|
||||||
|
export LD=$(xcrun --find --sdk $IOS_SDK ld)
|
||||||
|
export STRIP=$(xcrun --find --sdk $IOS_SDK strip)
|
||||||
|
|
||||||
|
CPPFLAGS="$CPPFLAGS --sysroot=$IOS_SDK_PATH"
|
||||||
|
CFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
|
||||||
|
CXXFLAGS="-target $IOS_HOST_TRIPLE --sysroot=$IOS_SDK_PATH -mios-version-min=$IPHONEOS_DEPLOYMENT_TARGET"
|
||||||
|
|
||||||
|
# Having IPHONEOS_DEPLOYMENT_TARGET in the environment causes problems
|
||||||
|
# with some cross-building toolchains, because it introduces implicit
|
||||||
|
# behavior into clang.
|
||||||
|
unset IPHONEOS_DEPLOYMENT_TARGET
|
||||||
|
|
||||||
|
# Now that we know CC etc., we can create a meson cross-configuration file
|
||||||
|
create_meson_cross_config
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
wrap_wheel_builder build
|
wrap_wheel_builder build
|
||||||
|
|
5
.github/workflows/wheels-test.ps1
vendored
5
.github/workflows/wheels-test.ps1
vendored
|
@ -15,15 +15,12 @@ if (Test-Path $venv\Scripts\pypy.exe) {
|
||||||
$python = "python.exe"
|
$python = "python.exe"
|
||||||
}
|
}
|
||||||
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
|
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
|
||||||
if ("$venv" -like "*\cibw-run-*-win_amd64\*") {
|
|
||||||
& $venv\Scripts\$python -m pip install numpy
|
|
||||||
}
|
|
||||||
cd $pillow
|
cd $pillow
|
||||||
& $venv\Scripts\$python -VV
|
& $venv\Scripts\$python -VV
|
||||||
if (!$?) { exit $LASTEXITCODE }
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
& $venv\Scripts\$python selftest.py
|
& $venv\Scripts\$python selftest.py
|
||||||
if (!$?) { exit $LASTEXITCODE }
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
& $venv\Scripts\$python -m pytest -vv -x Tests\check_wheel.py
|
& $venv\Scripts\$python -m pytest -vv -x checks\check_wheel.py
|
||||||
if (!$?) { exit $LASTEXITCODE }
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
& $venv\Scripts\$python -m pytest -vv -x Tests
|
& $venv\Scripts\$python -m pytest -vv -x Tests
|
||||||
if (!$?) { exit $LASTEXITCODE }
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
|
|
4
.github/workflows/wheels-test.sh
vendored
4
.github/workflows/wheels-test.sh
vendored
|
@ -25,8 +25,6 @@ else
|
||||||
yum install -y fribidi
|
yum install -y fribidi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 -m pip install numpy
|
|
||||||
|
|
||||||
if [ ! -d "test-images-main" ]; then
|
if [ ! -d "test-images-main" ]; then
|
||||||
curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
|
curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
|
||||||
unzip pillow-test-images.zip
|
unzip pillow-test-images.zip
|
||||||
|
@ -35,5 +33,5 @@ fi
|
||||||
|
|
||||||
# Runs tests
|
# Runs tests
|
||||||
python3 selftest.py
|
python3 selftest.py
|
||||||
python3 -m pytest -vv -x Tests/check_wheel.py
|
python3 -m pytest -vv -x checks/check_wheel.py
|
||||||
python3 -m pytest -vv -x
|
python3 -m pytest -vv -x
|
||||||
|
|
23
.github/workflows/wheels.yml
vendored
23
.github/workflows/wheels.yml
vendored
|
@ -51,40 +51,60 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- name: "macOS 10.10 x86_64"
|
- name: "macOS 10.10 x86_64"
|
||||||
|
platform: macos
|
||||||
os: macos-13
|
os: macos-13
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
build: "cp3{9,10,11}*"
|
build: "cp3{9,10,11}*"
|
||||||
macosx_deployment_target: "10.10"
|
macosx_deployment_target: "10.10"
|
||||||
- name: "macOS 10.13 x86_64"
|
- name: "macOS 10.13 x86_64"
|
||||||
|
platform: macos
|
||||||
os: macos-13
|
os: macos-13
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
build: "cp3{12,13,14}*"
|
build: "cp3{12,13,14}*"
|
||||||
macosx_deployment_target: "10.13"
|
macosx_deployment_target: "10.13"
|
||||||
- name: "macOS 10.15 x86_64"
|
- name: "macOS 10.15 x86_64"
|
||||||
|
platform: macos
|
||||||
os: macos-13
|
os: macos-13
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
build: "pp3*"
|
build: "pp3*"
|
||||||
macosx_deployment_target: "10.15"
|
macosx_deployment_target: "10.15"
|
||||||
- name: "macOS arm64"
|
- name: "macOS arm64"
|
||||||
|
platform: macos
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
cibw_arch: arm64
|
cibw_arch: arm64
|
||||||
macosx_deployment_target: "11.0"
|
macosx_deployment_target: "11.0"
|
||||||
- name: "manylinux2014 and musllinux x86_64"
|
- name: "manylinux2014 and musllinux x86_64"
|
||||||
|
platform: linux
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
- name: "manylinux_2_28 x86_64"
|
- name: "manylinux_2_28 x86_64"
|
||||||
|
platform: linux
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
build: "*manylinux*"
|
build: "*manylinux*"
|
||||||
manylinux: "manylinux_2_28"
|
manylinux: "manylinux_2_28"
|
||||||
- name: "manylinux2014 and musllinux aarch64"
|
- name: "manylinux2014 and musllinux aarch64"
|
||||||
|
platform: linux
|
||||||
os: ubuntu-24.04-arm
|
os: ubuntu-24.04-arm
|
||||||
cibw_arch: aarch64
|
cibw_arch: aarch64
|
||||||
- name: "manylinux_2_28 aarch64"
|
- name: "manylinux_2_28 aarch64"
|
||||||
|
platform: linux
|
||||||
os: ubuntu-24.04-arm
|
os: ubuntu-24.04-arm
|
||||||
cibw_arch: aarch64
|
cibw_arch: aarch64
|
||||||
build: "*manylinux*"
|
build: "*manylinux*"
|
||||||
manylinux: "manylinux_2_28"
|
manylinux: "manylinux_2_28"
|
||||||
|
- name: "iOS arm64 device"
|
||||||
|
platform: ios
|
||||||
|
os: macos-latest
|
||||||
|
cibw_arch: arm64_iphoneos
|
||||||
|
- name: "iOS arm64 simulator"
|
||||||
|
platform: ios
|
||||||
|
os: macos-latest
|
||||||
|
cibw_arch: arm64_iphonesimulator
|
||||||
|
- name: "iOS x86_64 simulator"
|
||||||
|
platform: ios
|
||||||
|
os: macos-13
|
||||||
|
cibw_arch: x86_64_iphonesimulator
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
@ -103,6 +123,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
python3 -m cibuildwheel --output-dir wheelhouse
|
python3 -m cibuildwheel --output-dir wheelhouse
|
||||||
env:
|
env:
|
||||||
|
CIBW_PLATFORM: ${{ matrix.platform }}
|
||||||
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||||
CIBW_BUILD: ${{ matrix.build }}
|
CIBW_BUILD: ${{ matrix.build }}
|
||||||
CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
|
CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
|
||||||
|
@ -114,7 +135,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist-${{ matrix.os }}${{ matrix.macosx_deployment_target && format('-{0}', matrix.macosx_deployment_target) }}-${{ matrix.cibw_arch }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }}
|
name: dist-${{ matrix.name }}
|
||||||
path: ./wheelhouse/*.whl
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.12.0
|
rev: v0.12.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
args: [--exit-non-zero-on-fix]
|
args: [--exit-non-zero-on-fix]
|
||||||
|
@ -11,7 +11,7 @@ repos:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/bandit
|
- repo: https://github.com/PyCQA/bandit
|
||||||
rev: 1.8.5
|
rev: 1.8.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: bandit
|
- id: bandit
|
||||||
args: [--severity-level=high]
|
args: [--severity-level=high]
|
||||||
|
@ -21,10 +21,10 @@ repos:
|
||||||
rev: v1.5.5
|
rev: v1.5.5
|
||||||
hooks:
|
hooks:
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: v20.1.6
|
rev: v20.1.7
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
types: [c]
|
types: [c]
|
||||||
|
@ -46,19 +46,19 @@ repos:
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
args: [--allow-multiple-documents]
|
args: [--allow-multiple-documents]
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
exclude: ^Tests/images/
|
exclude: ^Tests/images/|\.patch$
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
|
||||||
|
|
||||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||||
rev: 0.33.1
|
rev: 0.33.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-github-workflows
|
- id: check-github-workflows
|
||||||
- id: check-readthedocs
|
- id: check-readthedocs
|
||||||
- id: check-renovate
|
- id: check-renovate
|
||||||
|
|
||||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||||
rev: v1.9.0
|
rev: v1.11.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: zizmor
|
- id: zizmor
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@ include LICENSE
|
||||||
include Makefile
|
include Makefile
|
||||||
include tox.ini
|
include tox.ini
|
||||||
graft Tests
|
graft Tests
|
||||||
|
graft checks
|
||||||
|
graft patches
|
||||||
graft src
|
graft src
|
||||||
graft depends
|
graft depends
|
||||||
graft winbuild
|
graft winbuild
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -75,7 +75,7 @@ debug:
|
||||||
|
|
||||||
.PHONY: release-test
|
.PHONY: release-test
|
||||||
release-test:
|
release-test:
|
||||||
python3 Tests/check_release_notes.py
|
python3 checks/check_release_notes.py
|
||||||
python3 -m pip install -e .[tests]
|
python3 -m pip install -e .[tests]
|
||||||
python3 selftest.py
|
python3 selftest.py
|
||||||
python3 -m pytest Tests
|
python3 -m pytest Tests
|
||||||
|
|
|
@ -271,17 +271,13 @@ def _cached_hopper(mode: str) -> Image.Image:
|
||||||
im = hopper("L")
|
im = hopper("L")
|
||||||
else:
|
else:
|
||||||
im = hopper()
|
im = hopper()
|
||||||
if mode.startswith("BGR;"):
|
try:
|
||||||
with pytest.warns(DeprecationWarning, match="BGR;"):
|
im = im.convert(mode)
|
||||||
im = im.convert(mode)
|
except ImportError:
|
||||||
else:
|
if mode == "LAB":
|
||||||
try:
|
im = Image.open("Tests/images/hopper.Lab.tif")
|
||||||
im = im.convert(mode)
|
else:
|
||||||
except ImportError:
|
raise
|
||||||
if mode == "LAB":
|
|
||||||
im = Image.open("Tests/images/hopper.Lab.tif")
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
BIN
Tests/images/no_palette_with_transparency_after_rgb.gif
Normal file
BIN
Tests/images/no_palette_with_transparency_after_rgb.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -10,8 +10,9 @@ import pytest
|
||||||
from PIL import Image, features
|
from PIL import Image, features
|
||||||
from Tests.helper import skip_unless_feature
|
from Tests.helper import skip_unless_feature
|
||||||
|
|
||||||
if sys.platform.startswith("win32"):
|
if sys.platform.startswith("win32") or sys.platform == "ios":
|
||||||
pytest.skip("Fuzzer is linux only", allow_module_level=True)
|
pytest.skip("Fuzzer doesn't run on Windows or iOS", allow_module_level=True)
|
||||||
|
|
||||||
libjpeg_turbo_version = features.version("libjpeg_turbo")
|
libjpeg_turbo_version = features.version("libjpeg_turbo")
|
||||||
if libjpeg_turbo_version is not None:
|
if libjpeg_turbo_version is not None:
|
||||||
version = packaging.version.parse(libjpeg_turbo_version)
|
version = packaging.version.parse(libjpeg_turbo_version)
|
||||||
|
|
|
@ -9,9 +9,9 @@ from PIL import _deprecate
|
||||||
"version, expected",
|
"version, expected",
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
12,
|
13,
|
||||||
"Old thing is deprecated and will be removed in Pillow 12 "
|
"Old thing is deprecated and will be removed in Pillow 13 "
|
||||||
r"\(2025-10-15\)\. Use new thing instead\.",
|
r"\(2026-10-15\)\. Use new thing instead\.",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
None,
|
None,
|
||||||
|
@ -53,18 +53,18 @@ def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
|
||||||
|
|
||||||
def test_plural() -> None:
|
def test_plural() -> None:
|
||||||
expected = (
|
expected = (
|
||||||
r"Old things are deprecated and will be removed in Pillow 12 \(2025-10-15\)\. "
|
r"Old things are deprecated and will be removed in Pillow 13 \(2026-10-15\)\. "
|
||||||
r"Use new thing instead\."
|
r"Use new thing instead\."
|
||||||
)
|
)
|
||||||
with pytest.warns(DeprecationWarning, match=expected):
|
with pytest.warns(DeprecationWarning, match=expected):
|
||||||
_deprecate.deprecate("Old things", 12, "new thing", plural=True)
|
_deprecate.deprecate("Old things", 13, "new thing", plural=True)
|
||||||
|
|
||||||
|
|
||||||
def test_replacement_and_action() -> None:
|
def test_replacement_and_action() -> None:
|
||||||
expected = "Use only one of 'replacement' and 'action'"
|
expected = "Use only one of 'replacement' and 'action'"
|
||||||
with pytest.raises(ValueError, match=expected):
|
with pytest.raises(ValueError, match=expected):
|
||||||
_deprecate.deprecate(
|
_deprecate.deprecate(
|
||||||
"Old thing", 12, replacement="new thing", action="Upgrade to new thing"
|
"Old thing", 13, replacement="new thing", action="Upgrade to new thing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,16 +77,16 @@ def test_replacement_and_action() -> None:
|
||||||
)
|
)
|
||||||
def test_action(action: str) -> None:
|
def test_action(action: str) -> None:
|
||||||
expected = (
|
expected = (
|
||||||
r"Old thing is deprecated and will be removed in Pillow 12 \(2025-10-15\)\. "
|
r"Old thing is deprecated and will be removed in Pillow 13 \(2026-10-15\)\. "
|
||||||
r"Upgrade to new thing\."
|
r"Upgrade to new thing\."
|
||||||
)
|
)
|
||||||
with pytest.warns(DeprecationWarning, match=expected):
|
with pytest.warns(DeprecationWarning, match=expected):
|
||||||
_deprecate.deprecate("Old thing", 12, action=action)
|
_deprecate.deprecate("Old thing", 13, action=action)
|
||||||
|
|
||||||
|
|
||||||
def test_no_replacement_or_action() -> None:
|
def test_no_replacement_or_action() -> None:
|
||||||
expected = (
|
expected = (
|
||||||
r"Old thing is deprecated and will be removed in Pillow 12 \(2025-10-15\)"
|
r"Old thing is deprecated and will be removed in Pillow 13 \(2026-10-15\)"
|
||||||
)
|
)
|
||||||
with pytest.warns(DeprecationWarning, match=expected):
|
with pytest.warns(DeprecationWarning, match=expected):
|
||||||
_deprecate.deprecate("Old thing", 12)
|
_deprecate.deprecate("Old thing", 13)
|
||||||
|
|
|
@ -55,21 +55,6 @@ def test_version() -> None:
|
||||||
test(feature, features.version_feature)
|
test(feature, features.version_feature)
|
||||||
|
|
||||||
|
|
||||||
def test_webp_transparency() -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="transp_webp"):
|
|
||||||
assert (features.check("transp_webp") or False) == features.check_module("webp")
|
|
||||||
|
|
||||||
|
|
||||||
def test_webp_mux() -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="webp_mux"):
|
|
||||||
assert (features.check("webp_mux") or False) == features.check_module("webp")
|
|
||||||
|
|
||||||
|
|
||||||
def test_webp_anim() -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="webp_anim"):
|
|
||||||
assert (features.check("webp_anim") or False) == features.check_module("webp")
|
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("libjpeg_turbo")
|
@skip_unless_feature("libjpeg_turbo")
|
||||||
def test_libjpeg_turbo_version() -> None:
|
def test_libjpeg_turbo_version() -> None:
|
||||||
version = features.version("libjpeg_turbo")
|
version = features.version("libjpeg_turbo")
|
||||||
|
|
|
@ -101,6 +101,18 @@ def test_l_mode_after_rgb() -> None:
|
||||||
assert im.mode == "RGB"
|
assert im.mode == "RGB"
|
||||||
|
|
||||||
|
|
||||||
|
def test_l_mode_transparency_after_rgb() -> None:
|
||||||
|
with Image.open("Tests/images/no_palette_with_transparency_after_rgb.gif") as im:
|
||||||
|
expected = im.convert("RGB")
|
||||||
|
d = ImageDraw.Draw(expected)
|
||||||
|
d.rectangle([(0, 0), (64, 128)], fill="#000")
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
assert im.mode == "RGB"
|
||||||
|
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_palette_not_needed_for_second_frame() -> None:
|
def test_palette_not_needed_for_second_frame() -> None:
|
||||||
with Image.open("Tests/images/palette_not_needed_for_second_frame.gif") as im:
|
with Image.open("Tests/images/palette_not_needed_for_second_frame.gif") as im:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
|
|
|
@ -93,21 +93,11 @@ def test_sizes() -> None:
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
assert isinstance(im, IcnsImagePlugin.IcnsImageFile)
|
assert isinstance(im, IcnsImagePlugin.IcnsImageFile)
|
||||||
for w, h, r in im.info["sizes"]:
|
for w, h, r in im.info["sizes"]:
|
||||||
wr = w * r
|
|
||||||
hr = h * r
|
|
||||||
with pytest.warns(
|
|
||||||
DeprecationWarning, match=r"Setting size to \(width, height, scale\)"
|
|
||||||
):
|
|
||||||
im.size = (w, h, r)
|
|
||||||
im.load()
|
|
||||||
assert im.mode == "RGBA"
|
|
||||||
assert im.size == (wr, hr)
|
|
||||||
|
|
||||||
# Test using load() with scale
|
# Test using load() with scale
|
||||||
im.size = (w, h)
|
im.size = (w, h)
|
||||||
im.load(scale=r)
|
im.load(scale=r)
|
||||||
assert im.mode == "RGBA"
|
assert im.mode == "RGBA"
|
||||||
assert im.size == (wr, hr)
|
assert im.size == (w * r, h * r)
|
||||||
|
|
||||||
# Check that we cannot load an incorrect size
|
# Check that we cannot load an incorrect size
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
from io import BytesIO
|
||||||
from io import BytesIO, StringIO
|
|
||||||
|
|
||||||
import pytest
|
from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags
|
||||||
|
|
||||||
from PIL import Image, IptcImagePlugin
|
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
@ -78,13 +75,19 @@ def test_getiptcinfo_zero_padding() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_getiptcinfo_tiff() -> None:
|
def test_getiptcinfo_tiff() -> None:
|
||||||
# Arrange
|
expected = {(1, 90): b"\x1b%G", (2, 0): b"\xcf\xc0"}
|
||||||
|
|
||||||
with Image.open("Tests/images/hopper.Lab.tif") as im:
|
with Image.open("Tests/images/hopper.Lab.tif") as im:
|
||||||
# Act
|
|
||||||
iptc = IptcImagePlugin.getiptcinfo(im)
|
iptc = IptcImagePlugin.getiptcinfo(im)
|
||||||
|
|
||||||
# Assert
|
assert iptc == expected
|
||||||
assert iptc == {(1, 90): b"\x1b%G", (2, 0): b"\xcf\xc0"}
|
|
||||||
|
# Test with LONG tag type
|
||||||
|
with Image.open("Tests/images/hopper.Lab.tif") as im:
|
||||||
|
im.tag_v2.tagtype[TiffImagePlugin.IPTC_NAA_CHUNK] = TiffTags.LONG
|
||||||
|
iptc = IptcImagePlugin.getiptcinfo(im)
|
||||||
|
|
||||||
|
assert iptc == expected
|
||||||
|
|
||||||
|
|
||||||
def test_getiptcinfo_tiff_none() -> None:
|
def test_getiptcinfo_tiff_none() -> None:
|
||||||
|
@ -95,35 +98,3 @@ def test_getiptcinfo_tiff_none() -> None:
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert iptc is None
|
assert iptc is None
|
||||||
|
|
||||||
|
|
||||||
def test_i() -> None:
|
|
||||||
# Arrange
|
|
||||||
c = b"a"
|
|
||||||
|
|
||||||
# Act
|
|
||||||
with pytest.warns(DeprecationWarning, match="IptcImagePlugin.i"):
|
|
||||||
ret = IptcImagePlugin.i(c)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert ret == 97
|
|
||||||
|
|
||||||
|
|
||||||
def test_dump(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
||||||
# Arrange
|
|
||||||
c = b"abc"
|
|
||||||
# Temporarily redirect stdout
|
|
||||||
mystdout = StringIO()
|
|
||||||
monkeypatch.setattr(sys, "stdout", mystdout)
|
|
||||||
|
|
||||||
# Act
|
|
||||||
with pytest.warns(DeprecationWarning, match="IptcImagePlugin.dump"):
|
|
||||||
IptcImagePlugin.dump(c)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert mystdout.getvalue() == "61 62 63 \n"
|
|
||||||
|
|
||||||
|
|
||||||
def test_pad_deprecation() -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="IptcImagePlugin.PAD"):
|
|
||||||
assert IptcImagePlugin.PAD == b"\0\0\0\0"
|
|
||||||
|
|
|
@ -611,6 +611,24 @@ class TestFileJpeg:
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
for quality in range(101):
|
||||||
|
qtable_from_qtable_quality = self.roundtrip(
|
||||||
|
im,
|
||||||
|
qtables={0: standard_l_qtable, 1: standard_chrominance_qtable},
|
||||||
|
quality=quality,
|
||||||
|
).quantization
|
||||||
|
|
||||||
|
qtable_from_quality = self.roundtrip(im, quality=quality).quantization
|
||||||
|
|
||||||
|
if features.check_feature("libjpeg_turbo"):
|
||||||
|
assert qtable_from_qtable_quality == qtable_from_quality
|
||||||
|
else:
|
||||||
|
assert qtable_from_qtable_quality[0] == qtable_from_quality[0]
|
||||||
|
assert (
|
||||||
|
qtable_from_qtable_quality[1][1:] == qtable_from_quality[1][1:]
|
||||||
|
)
|
||||||
|
|
||||||
# list of qtable lists
|
# list of qtable lists
|
||||||
assert_image_similar(
|
assert_image_similar(
|
||||||
im,
|
im,
|
||||||
|
@ -1097,14 +1115,6 @@ class TestFileJpeg:
|
||||||
|
|
||||||
assert im._repr_jpeg_() is None
|
assert im._repr_jpeg_() is None
|
||||||
|
|
||||||
def test_deprecation(self) -> None:
|
|
||||||
with Image.open(TEST_FILE) as im:
|
|
||||||
assert isinstance(im, JpegImagePlugin.JpegImageFile)
|
|
||||||
with pytest.warns(DeprecationWarning, match="huffman_ac"):
|
|
||||||
assert im.huffman_ac == {}
|
|
||||||
with pytest.warns(DeprecationWarning, match="huffman_dc"):
|
|
||||||
assert im.huffman_dc == {}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not is_win32(), reason="Windows only")
|
@pytest.mark.skipif(not is_win32(), reason="Windows only")
|
||||||
@skip_unless_feature("jpg")
|
@skip_unless_feature("jpg")
|
||||||
|
|
|
@ -256,19 +256,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
im.save(out, tiffinfo=new_ifd)
|
im.save(out, tiffinfo=new_ifd)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize("libtiff", (True, False))
|
||||||
"libtiff",
|
|
||||||
(
|
|
||||||
pytest.param(
|
|
||||||
True,
|
|
||||||
marks=pytest.mark.skipif(
|
|
||||||
not getattr(Image.core, "libtiff_support_custom_tags", False),
|
|
||||||
reason="Custom tags not supported by older libtiff",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
False,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_custom_metadata(
|
def test_custom_metadata(
|
||||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
|
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -724,8 +712,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
|
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
|
||||||
if Image.core.libtiff_support_custom_tags:
|
assert reloaded.tag_v2[34665] == 125456
|
||||||
assert reloaded.tag_v2[34665] == 125456
|
|
||||||
|
|
||||||
def test_crashing_metadata(
|
def test_crashing_metadata(
|
||||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||||
|
@ -777,19 +764,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
assert icc_libtiff is not None
|
assert icc_libtiff is not None
|
||||||
assert icc == icc_libtiff
|
assert icc == icc_libtiff
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize("libtiff", (True, False))
|
||||||
"libtiff",
|
|
||||||
(
|
|
||||||
pytest.param(
|
|
||||||
True,
|
|
||||||
marks=pytest.mark.skipif(
|
|
||||||
not getattr(Image.core, "libtiff_support_custom_tags", False),
|
|
||||||
reason="Custom tags not supported by older libtiff",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
False,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_write_icc(
|
def test_write_icc(
|
||||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
|
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
@ -27,6 +27,6 @@ def test_valid_file() -> None:
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert im.format == "MCIDAS"
|
assert im.format == "MCIDAS"
|
||||||
assert im.mode == "I"
|
assert im.mode == "I;16B"
|
||||||
assert im.size == (1800, 400)
|
assert im.size == (1800, 400)
|
||||||
assert_image_equal_tofile(im, saved_file)
|
assert_image_equal_tofile(im, saved_file)
|
||||||
|
|
|
@ -359,13 +359,24 @@ def test_save_all_progress() -> None:
|
||||||
def test_save_xmp() -> None:
|
def test_save_xmp() -> None:
|
||||||
im = Image.new("RGB", (1, 1))
|
im = Image.new("RGB", (1, 1))
|
||||||
im2 = Image.new("RGB", (1, 1), "#f00")
|
im2 = Image.new("RGB", (1, 1), "#f00")
|
||||||
|
|
||||||
|
def roundtrip_xmp() -> list[Any]:
|
||||||
|
im_reloaded = roundtrip(im, xmp=b"Default", save_all=True, append_images=[im2])
|
||||||
|
xmp = [im_reloaded.info["xmp"]]
|
||||||
|
im_reloaded.seek(1)
|
||||||
|
return xmp + [im_reloaded.info["xmp"]]
|
||||||
|
|
||||||
|
# Use the save parameters for all frames by default
|
||||||
|
assert roundtrip_xmp() == [b"Default", b"Default"]
|
||||||
|
|
||||||
|
# Specify a value for the first frame
|
||||||
|
im.encoderinfo = {"xmp": b"First frame"}
|
||||||
|
assert roundtrip_xmp() == [b"First frame", b"Default"]
|
||||||
|
del im.encoderinfo
|
||||||
|
|
||||||
|
# Specify value for the second frame
|
||||||
im2.encoderinfo = {"xmp": b"Second frame"}
|
im2.encoderinfo = {"xmp": b"Second frame"}
|
||||||
im_reloaded = roundtrip(im, xmp=b"First frame", save_all=True, append_images=[im2])
|
assert roundtrip_xmp() == [b"Default", b"Second frame"]
|
||||||
|
|
||||||
# Test that encoderinfo is unchanged
|
# Test that encoderinfo is unchanged
|
||||||
assert im2.encoderinfo == {"xmp": b"Second frame"}
|
assert im2.encoderinfo == {"xmp": b"Second frame"}
|
||||||
|
|
||||||
assert im_reloaded.info["xmp"] == b"First frame"
|
|
||||||
|
|
||||||
im_reloaded.seek(1)
|
|
||||||
assert im_reloaded.info["xmp"] == b"Second frame"
|
|
||||||
|
|
|
@ -681,16 +681,21 @@ class TestFileTiff:
|
||||||
assert im.tag_v2[278] == 256
|
assert im.tag_v2[278] == 256
|
||||||
|
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
im.encoderinfo = {"tiffinfo": {278: 100}}
|
||||||
im2 = Image.new("L", (128, 128))
|
im2 = Image.new("L", (128, 128))
|
||||||
im2.encoderinfo = {"tiffinfo": {278: 256}}
|
im3 = im2.copy()
|
||||||
im.save(outfile, save_all=True, append_images=[im2])
|
im3.encoderinfo = {"tiffinfo": {278: 300}}
|
||||||
|
im.save(outfile, save_all=True, tiffinfo={278: 200}, append_images=[im2, im3])
|
||||||
|
|
||||||
with Image.open(outfile) as im:
|
with Image.open(outfile) as im:
|
||||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||||
assert im.tag_v2[278] == 128
|
assert im.tag_v2[278] == 100
|
||||||
|
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.tag_v2[278] == 256
|
assert im.tag_v2[278] == 200
|
||||||
|
|
||||||
|
im.seek(2)
|
||||||
|
assert im.tag_v2[278] == 300
|
||||||
|
|
||||||
def test_strip_raw(self) -> None:
|
def test_strip_raw(self) -> None:
|
||||||
infile = "Tests/images/tiff_strip_raw.tif"
|
infile = "Tests/images/tiff_strip_raw.tif"
|
||||||
|
|
|
@ -30,7 +30,6 @@ from .helper import (
|
||||||
assert_image_similar_tofile,
|
assert_image_similar_tofile,
|
||||||
assert_not_all_same,
|
assert_not_all_same,
|
||||||
hopper,
|
hopper,
|
||||||
is_big_endian,
|
|
||||||
is_win32,
|
is_win32,
|
||||||
mark_if_feature_version,
|
mark_if_feature_version,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
|
@ -50,19 +49,10 @@ except ImportError:
|
||||||
PrettyPrinter = None
|
PrettyPrinter = None
|
||||||
|
|
||||||
|
|
||||||
# Deprecation helper
|
|
||||||
def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image:
|
|
||||||
if mode.startswith("BGR;"):
|
|
||||||
with pytest.warns(DeprecationWarning, match="BGR;"):
|
|
||||||
return Image.new(mode, size)
|
|
||||||
else:
|
|
||||||
return Image.new(mode, size)
|
|
||||||
|
|
||||||
|
|
||||||
class TestImage:
|
class TestImage:
|
||||||
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
|
@pytest.mark.parametrize("mode", Image.MODES)
|
||||||
def test_image_modes_success(self, mode: str) -> None:
|
def test_image_modes_success(self, mode: str) -> None:
|
||||||
helper_image_new(mode, (1, 1))
|
Image.new(mode, (1, 1))
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("", "bad", "very very long"))
|
@pytest.mark.parametrize("mode", ("", "bad", "very very long"))
|
||||||
def test_image_modes_fail(self, mode: str) -> None:
|
def test_image_modes_fail(self, mode: str) -> None:
|
||||||
|
@ -160,6 +150,10 @@ class TestImage:
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
im.mode = "P" # type: ignore[misc]
|
im.mode = "P" # type: ignore[misc]
|
||||||
|
|
||||||
|
def test_empty_path(self) -> None:
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
Image.open("")
|
||||||
|
|
||||||
def test_invalid_image(self) -> None:
|
def test_invalid_image(self) -> None:
|
||||||
im = io.BytesIO(b"")
|
im = io.BytesIO(b"")
|
||||||
with pytest.raises(UnidentifiedImageError):
|
with pytest.raises(UnidentifiedImageError):
|
||||||
|
@ -1138,39 +1132,29 @@ class TestImage:
|
||||||
assert len(caplog.records) == 0
|
assert len(caplog.records) == 0
|
||||||
assert im.fp is None
|
assert im.fp is None
|
||||||
|
|
||||||
def test_deprecation(self) -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="Image.isImageType"):
|
|
||||||
assert not Image.isImageType(None)
|
|
||||||
|
|
||||||
|
|
||||||
class TestImageBytes:
|
class TestImageBytes:
|
||||||
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
|
@pytest.mark.parametrize("mode", Image.MODES)
|
||||||
def test_roundtrip_bytes_constructor(self, mode: str) -> None:
|
def test_roundtrip_bytes_constructor(self, mode: str) -> None:
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
source_bytes = im.tobytes()
|
source_bytes = im.tobytes()
|
||||||
|
|
||||||
if mode.startswith("BGR;"):
|
reloaded = Image.frombytes(mode, im.size, source_bytes)
|
||||||
with pytest.warns(DeprecationWarning, match=mode):
|
|
||||||
reloaded = Image.frombytes(mode, im.size, source_bytes)
|
|
||||||
else:
|
|
||||||
reloaded = Image.frombytes(mode, im.size, source_bytes)
|
|
||||||
assert reloaded.tobytes() == source_bytes
|
assert reloaded.tobytes() == source_bytes
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
|
@pytest.mark.parametrize("mode", Image.MODES)
|
||||||
def test_roundtrip_bytes_method(self, mode: str) -> None:
|
def test_roundtrip_bytes_method(self, mode: str) -> None:
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
source_bytes = im.tobytes()
|
source_bytes = im.tobytes()
|
||||||
|
|
||||||
reloaded = helper_image_new(mode, im.size)
|
reloaded = Image.new(mode, im.size)
|
||||||
reloaded.frombytes(source_bytes)
|
reloaded.frombytes(source_bytes)
|
||||||
assert reloaded.tobytes() == source_bytes
|
assert reloaded.tobytes() == source_bytes
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
|
@pytest.mark.parametrize("mode", Image.MODES)
|
||||||
def test_getdata_putdata(self, mode: str) -> None:
|
def test_getdata_putdata(self, mode: str) -> None:
|
||||||
if is_big_endian() and mode == "BGR;15":
|
|
||||||
pytest.xfail("Known failure of BGR;15 on big-endian")
|
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
reloaded = helper_image_new(mode, im.size)
|
reloaded = Image.new(mode, im.size)
|
||||||
reloaded.putdata(im.getdata())
|
reloaded.putdata(im.getdata())
|
||||||
assert_image_equal(im, reloaded)
|
assert_image_equal(im, reloaded)
|
||||||
|
|
||||||
|
|
|
@ -123,10 +123,6 @@ class TestImageGetPixel:
|
||||||
bands = Image.getmodebands(mode)
|
bands = Image.getmodebands(mode)
|
||||||
if bands == 1:
|
if bands == 1:
|
||||||
return 1
|
return 1
|
||||||
if mode in ("BGR;15", "BGR;16"):
|
|
||||||
# These modes have less than 8 bits per band,
|
|
||||||
# so (1, 2, 3) cannot be roundtripped.
|
|
||||||
return (16, 32, 49)
|
|
||||||
return tuple(range(1, bands + 1))
|
return tuple(range(1, bands + 1))
|
||||||
|
|
||||||
def check(self, mode: str, expected_color_int: int | None = None) -> None:
|
def check(self, mode: str, expected_color_int: int | None = None) -> None:
|
||||||
|
@ -191,11 +187,6 @@ class TestImageGetPixel:
|
||||||
def test_basic(self, mode: str) -> None:
|
def test_basic(self, mode: str) -> None:
|
||||||
self.check(mode)
|
self.check(mode)
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24"))
|
|
||||||
def test_deprecated(self, mode: str) -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="BGR;"):
|
|
||||||
self.check(mode)
|
|
||||||
|
|
||||||
def test_list(self) -> None:
|
def test_list(self) -> None:
|
||||||
im = hopper()
|
im = hopper()
|
||||||
assert im.getpixel([0, 0]) == (20, 20, 70)
|
assert im.getpixel([0, 0]) == (20, 20, 70)
|
||||||
|
@ -218,7 +209,7 @@ class TestImageGetPixel:
|
||||||
|
|
||||||
|
|
||||||
class TestImagePutPixelError:
|
class TestImagePutPixelError:
|
||||||
IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"]
|
IMAGE_MODES1 = ["LA", "RGB", "RGBA"]
|
||||||
IMAGE_MODES2 = ["L", "I", "I;16"]
|
IMAGE_MODES2 = ["L", "I", "I;16"]
|
||||||
INVALID_TYPES = ["foo", 1.0, None]
|
INVALID_TYPES = ["foo", 1.0, None]
|
||||||
|
|
||||||
|
@ -234,11 +225,6 @@ class TestImagePutPixelError:
|
||||||
(
|
(
|
||||||
("L", (0, 2), "color must be int or single-element tuple"),
|
("L", (0, 2), "color must be int or single-element tuple"),
|
||||||
("LA", (0, 3), "color must be int, or tuple of one or two elements"),
|
("LA", (0, 3), "color must be int, or tuple of one or two elements"),
|
||||||
(
|
|
||||||
"BGR;15",
|
|
||||||
(0, 2),
|
|
||||||
"color must be int, or tuple of one or three elements",
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
"RGB",
|
"RGB",
|
||||||
(0, 2, 5),
|
(0, 2, 5),
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from .helper import hopper
|
from .helper import hopper
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,10 +8,3 @@ def test_sanity() -> None:
|
||||||
|
|
||||||
type_repr = repr(type(im.getim()))
|
type_repr = repr(type(im.getim()))
|
||||||
assert "PyCapsule" in type_repr
|
assert "PyCapsule" in type_repr
|
||||||
|
|
||||||
with pytest.warns(DeprecationWarning, match="id property"):
|
|
||||||
assert isinstance(im.im.id, int)
|
|
||||||
|
|
||||||
with pytest.warns(DeprecationWarning, match="unsafe_ptrs property"):
|
|
||||||
ptrs = dict(im.im.unsafe_ptrs)
|
|
||||||
assert ptrs.keys() == {"image8", "image32", "image"}
|
|
||||||
|
|
|
@ -78,16 +78,6 @@ def test_mode_F() -> None:
|
||||||
assert list(im.getdata()) == target
|
assert list(im.getdata()) == target
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24"))
|
|
||||||
def test_mode_BGR(mode: str) -> None:
|
|
||||||
data = [(16, 32, 49), (32, 32, 98)]
|
|
||||||
with pytest.warns(DeprecationWarning, match=mode):
|
|
||||||
im = Image.new(mode, (1, 2))
|
|
||||||
im.putdata(data)
|
|
||||||
|
|
||||||
assert list(im.getdata()) == data
|
|
||||||
|
|
||||||
|
|
||||||
def test_array_B() -> None:
|
def test_array_B() -> None:
|
||||||
# shouldn't segfault
|
# shouldn't segfault
|
||||||
# see https://github.com/python-pillow/Pillow/issues/1008
|
# see https://github.com/python-pillow/Pillow/issues/1008
|
||||||
|
|
|
@ -324,7 +324,7 @@ class TestImageResize:
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
|
assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("1", "P", "BGR;15", "BGR;16"))
|
@pytest.mark.parametrize("mode", ("1", "P"))
|
||||||
def test_default_filter_nearest(self, mode: str) -> None:
|
def test_default_filter_nearest(self, mode: str) -> None:
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
|
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
|
||||||
|
|
|
@ -54,10 +54,6 @@ def skip_missing() -> None:
|
||||||
def test_sanity() -> None:
|
def test_sanity() -> None:
|
||||||
# basic smoke test.
|
# basic smoke test.
|
||||||
# this mostly follows the cms_test outline.
|
# this mostly follows the cms_test outline.
|
||||||
with pytest.warns(DeprecationWarning, match="PIL.ImageCms.versions"):
|
|
||||||
v = ImageCms.versions() # should return four strings
|
|
||||||
assert v[0] == "1.0.0 pil"
|
|
||||||
assert list(map(type, v)) == [str, str, str, str]
|
|
||||||
|
|
||||||
# internal version number
|
# internal version number
|
||||||
version = features.version_module("littlecms2")
|
version = features.version_module("littlecms2")
|
||||||
|
@ -677,12 +673,6 @@ def test_auxiliary_channels_isolated() -> None:
|
||||||
assert_image_equal(test_image.convert(dst_format[2]), reference_image)
|
assert_image_equal(test_image.convert(dst_format[2]), reference_image)
|
||||||
|
|
||||||
|
|
||||||
def test_long_modes() -> None:
|
|
||||||
p = ImageCms.getOpenProfile("Tests/icc/sGrey-v2-nano.icc")
|
|
||||||
with pytest.warns(DeprecationWarning, match="ABCDEFGHI"):
|
|
||||||
ImageCms.buildTransform(p, p, "ABCDEFGHI", "ABCDEFGHI")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
|
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
|
||||||
def test_rgb_lab(mode: str) -> None:
|
def test_rgb_lab(mode: str) -> None:
|
||||||
im = Image.new(mode, (1, 1))
|
im = Image.new(mode, (1, 1))
|
||||||
|
@ -700,18 +690,3 @@ def test_cmyk_lab() -> None:
|
||||||
im = Image.new("CMYK", (1, 1))
|
im = Image.new("CMYK", (1, 1))
|
||||||
converted_im = im.convert("LAB")
|
converted_im = im.convert("LAB")
|
||||||
assert converted_im.getpixel((0, 0)) == (255, 128, 128)
|
assert converted_im.getpixel((0, 0)) == (255, 128, 128)
|
||||||
|
|
||||||
|
|
||||||
def test_deprecation() -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="ImageCms.DESCRIPTION"):
|
|
||||||
assert ImageCms.DESCRIPTION.strip().startswith("pyCMS")
|
|
||||||
with pytest.warns(DeprecationWarning, match="ImageCms.VERSION"):
|
|
||||||
assert ImageCms.VERSION == "1.0.0 pil"
|
|
||||||
with pytest.warns(DeprecationWarning, match="ImageCms.FLAGS"):
|
|
||||||
assert isinstance(ImageCms.FLAGS, dict)
|
|
||||||
|
|
||||||
profile = ImageCmsProfile(ImageCms.createProfile("sRGB"))
|
|
||||||
with pytest.warns(DeprecationWarning, match="RGBA;16B"):
|
|
||||||
ImageCms.ImageCmsTransform(profile, profile, "RGBA;16B", "RGB")
|
|
||||||
with pytest.warns(DeprecationWarning, match="RGBA;16B"):
|
|
||||||
ImageCms.ImageCmsTransform(profile, profile, "RGB", "RGBA;16B")
|
|
||||||
|
|
|
@ -1732,8 +1732,3 @@ def test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None:
|
||||||
draw.rectangle(xy)
|
draw.rectangle(xy)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
draw.rounded_rectangle(xy)
|
draw.rounded_rectangle(xy)
|
||||||
|
|
||||||
|
|
||||||
def test_getdraw() -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="'hints' parameter"):
|
|
||||||
ImageDraw.getdraw(None, [])
|
|
||||||
|
|
|
@ -151,11 +151,6 @@ class TestImageFile:
|
||||||
# Despite multiple tiles, assert only one tile caused a read of maxblock size
|
# Despite multiple tiles, assert only one tile caused a read of maxblock size
|
||||||
assert reads.count(im.decodermaxblock) == 1
|
assert reads.count(im.decodermaxblock) == 1
|
||||||
|
|
||||||
def test_raise_oserror(self) -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="raise_oserror"):
|
|
||||||
with pytest.raises(OSError):
|
|
||||||
ImageFile.raise_oserror(1)
|
|
||||||
|
|
||||||
def test_raise_typeerror(self) -> None:
|
def test_raise_typeerror(self) -> None:
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
parser = ImageFile.Parser()
|
parser = ImageFile.Parser()
|
||||||
|
|
|
@ -11,7 +11,6 @@ from pathlib import Path
|
||||||
from typing import Any, BinaryIO
|
from typing import Any, BinaryIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont, features
|
from PIL import Image, ImageDraw, ImageFont, features
|
||||||
from PIL._typing import StrOrBytesPath
|
from PIL._typing import StrOrBytesPath
|
||||||
|
@ -691,16 +690,6 @@ def test_complex_font_settings() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_variation_get(font: ImageFont.FreeTypeFont) -> None:
|
def test_variation_get(font: ImageFont.FreeTypeFont) -> None:
|
||||||
version = features.version_module("freetype2")
|
|
||||||
assert version is not None
|
|
||||||
freetype = parse_version(version)
|
|
||||||
if freetype < parse_version("2.9.1"):
|
|
||||||
with pytest.raises(NotImplementedError):
|
|
||||||
font.get_variation_names()
|
|
||||||
with pytest.raises(NotImplementedError):
|
|
||||||
font.get_variation_axes()
|
|
||||||
return
|
|
||||||
|
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
font.get_variation_names()
|
font.get_variation_names()
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
|
@ -763,14 +752,6 @@ def _check_text(font: ImageFont.FreeTypeFont, path: str, epsilon: float) -> None
|
||||||
|
|
||||||
|
|
||||||
def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:
|
def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:
|
||||||
version = features.version_module("freetype2")
|
|
||||||
assert version is not None
|
|
||||||
freetype = parse_version(version)
|
|
||||||
if freetype < parse_version("2.9.1"):
|
|
||||||
with pytest.raises(NotImplementedError):
|
|
||||||
font.set_variation_by_name("Bold")
|
|
||||||
return
|
|
||||||
|
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
font.set_variation_by_name("Bold")
|
font.set_variation_by_name("Bold")
|
||||||
|
|
||||||
|
@ -790,14 +771,6 @@ def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None:
|
def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None:
|
||||||
version = features.version_module("freetype2")
|
|
||||||
assert version is not None
|
|
||||||
freetype = parse_version(version)
|
|
||||||
if freetype < parse_version("2.9.1"):
|
|
||||||
with pytest.raises(NotImplementedError):
|
|
||||||
font.set_variation_by_axes([100])
|
|
||||||
return
|
|
||||||
|
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
font.set_variation_by_axes([500, 50])
|
font.set_variation_by_axes([500, 50])
|
||||||
|
|
||||||
|
@ -1209,15 +1182,3 @@ def test_invalid_truetype_sizes_raise_valueerror(
|
||||||
) -> None:
|
) -> None:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine)
|
ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine)
|
||||||
|
|
||||||
|
|
||||||
def test_freetype_deprecation(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
||||||
# Arrange: mock features.version_module to return fake FreeType version
|
|
||||||
def fake_version_module(module: str) -> str:
|
|
||||||
return "2.9.0"
|
|
||||||
|
|
||||||
monkeypatch.setattr(features, "version_module", fake_version_module)
|
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
with pytest.warns(DeprecationWarning, match="FreeType 2.9.0"):
|
|
||||||
ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
|
||||||
|
|
|
@ -2,8 +2,6 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from PIL import Image, ImageMath
|
from PIL import Image, ImageMath
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,11 +53,6 @@ def test_sanity() -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_options_deprecated() -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="ImageMath.lambda_eval options"):
|
|
||||||
assert ImageMath.lambda_eval(lambda args: 1, images) == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_ops() -> None:
|
def test_ops() -> None:
|
||||||
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, **images)) == "I -1"
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, **images)) == "I -1"
|
||||||
|
|
||||||
|
|
|
@ -35,16 +35,6 @@ def test_sanity() -> None:
|
||||||
assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", **images)) == "I 3"
|
assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", **images)) == "I 3"
|
||||||
|
|
||||||
|
|
||||||
def test_eval_deprecated() -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="ImageMath.eval"):
|
|
||||||
assert ImageMath.eval("1") == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_options_deprecated() -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="ImageMath.unsafe_eval options"):
|
|
||||||
assert ImageMath.unsafe_eval("1", images) == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_ops() -> None:
|
def test_ops() -> None:
|
||||||
assert pixel(ImageMath.unsafe_eval("-A", **images)) == "I -1"
|
assert pixel(ImageMath.unsafe_eval("-A", **images)) == "I -1"
|
||||||
assert pixel(ImageMath.unsafe_eval("+B", **images)) == "L 2"
|
assert pixel(ImageMath.unsafe_eval("+B", **images)) == "L 2"
|
||||||
|
|
|
@ -361,18 +361,6 @@ class TestLibUnpack:
|
||||||
"RGB", "CMYK", 4, (250, 249, 248), (242, 241, 240), (234, 233, 233)
|
"RGB", "CMYK", 4, (250, 249, 248), (242, 241, 240), (234, 233, 233)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_BGR(self) -> None:
|
|
||||||
with pytest.warns(DeprecationWarning, match="BGR;15"):
|
|
||||||
self.assert_unpack(
|
|
||||||
"BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8)
|
|
||||||
)
|
|
||||||
with pytest.warns(DeprecationWarning, match="BGR;16"):
|
|
||||||
self.assert_unpack(
|
|
||||||
"BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0)
|
|
||||||
)
|
|
||||||
with pytest.warns(DeprecationWarning, match="BGR;24"):
|
|
||||||
self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9))
|
|
||||||
|
|
||||||
def test_RGBA(self) -> None:
|
def test_RGBA(self) -> None:
|
||||||
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
|
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
|
|
|
@ -7,6 +7,7 @@ import sys
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.platform == "ios", reason="Processes not supported on iOS")
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"args, report",
|
"args, report",
|
||||||
((["PIL"], False), (["PIL", "--report"], True), (["PIL.report"], True)),
|
((["PIL"], False), (["PIL", "--report"], True), (["PIL.report"], True)),
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from importlib.metadata import metadata
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import __version__
|
from PIL import __version__
|
||||||
|
@ -9,7 +11,7 @@ pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed")
|
||||||
|
|
||||||
def test_pyroma() -> None:
|
def test_pyroma() -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
data = pyroma.projectdata.get_data(".")
|
data = pyroma.projectdata.map_metadata_keys(metadata("Pillow"))
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
rating = pyroma.ratings.rate(data)
|
rating = pyroma.ratings.rate(data)
|
||||||
|
@ -23,11 +25,5 @@ def test_pyroma() -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Should have a perfect score, but pyroma does not support PEP 639 yet.
|
# Should have a perfect score
|
||||||
assert rating == (
|
assert rating == (10, [])
|
||||||
9,
|
|
||||||
[
|
|
||||||
"Your package does neither have a license field "
|
|
||||||
"nor any license classifiers."
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ iterations = 5000
|
||||||
When run on a system without the jpeg leak fixes,
|
When run on a system without the jpeg leak fixes,
|
||||||
the valgrind runs look like this.
|
the valgrind runs look like this.
|
||||||
|
|
||||||
valgrind --tool=massif python test-installed.py -s -v Tests/check_jpeg_leaks.py
|
valgrind --tool=massif python test-installed.py -s -v checks/check_jpeg_leaks.py
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -4,8 +4,7 @@ import platform
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PIL import features
|
from PIL import features
|
||||||
|
from Tests.helper import is_pypy
|
||||||
from .helper import is_pypy
|
|
||||||
|
|
||||||
|
|
||||||
def test_wheel_modules() -> None:
|
def test_wheel_modules() -> None:
|
||||||
|
@ -24,6 +23,11 @@ def test_wheel_modules() -> None:
|
||||||
if platform.machine() == "ARM64":
|
if platform.machine() == "ARM64":
|
||||||
expected_modules.remove("avif")
|
expected_modules.remove("avif")
|
||||||
|
|
||||||
|
elif sys.platform == "ios":
|
||||||
|
# tkinter is not available on iOS
|
||||||
|
# libavif is not available on iOS (for now)
|
||||||
|
expected_modules -= {"tkinter", "avif"}
|
||||||
|
|
||||||
assert set(features.get_supported_modules()) == expected_modules
|
assert set(features.get_supported_modules()) == expected_modules
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,9 +39,6 @@ def test_wheel_codecs() -> None:
|
||||||
|
|
||||||
def test_wheel_features() -> None:
|
def test_wheel_features() -> None:
|
||||||
expected_features = {
|
expected_features = {
|
||||||
"webp_anim",
|
|
||||||
"webp_mux",
|
|
||||||
"transp_webp",
|
|
||||||
"raqm",
|
"raqm",
|
||||||
"fribidi",
|
"fribidi",
|
||||||
"harfbuzz",
|
"harfbuzz",
|
||||||
|
@ -50,5 +51,9 @@ def test_wheel_features() -> None:
|
||||||
expected_features.remove("xcb")
|
expected_features.remove("xcb")
|
||||||
elif sys.platform == "darwin" and not is_pypy() and platform.processor() != "arm":
|
elif sys.platform == "darwin" and not is_pypy() and platform.processor() != "arm":
|
||||||
expected_features.remove("zlib_ng")
|
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.
|
||||||
|
expected_features -= {"raqm", "fribidi", "harfbuzz"}
|
||||||
|
|
||||||
assert set(features.get_supported_features()) == expected_features
|
assert set(features.get_supported_features()) == expected_features
|
|
@ -16,6 +16,5 @@ coverage:
|
||||||
|
|
||||||
# Matches 'omit:' in .coveragerc
|
# Matches 'omit:' in .coveragerc
|
||||||
ignore:
|
ignore:
|
||||||
- "Tests/32bit_segfault_check.py"
|
- "checks/*.py"
|
||||||
- "Tests/check_*.py"
|
|
||||||
- "Tests/createfontdatachunk.py"
|
- "Tests/createfontdatachunk.py"
|
||||||
|
|
|
@ -12,96 +12,6 @@ Deprecated features
|
||||||
Below are features which are considered deprecated. Where appropriate,
|
Below are features which are considered deprecated. Where appropriate,
|
||||||
a :py:exc:`DeprecationWarning` is issued.
|
a :py:exc:`DeprecationWarning` is issued.
|
||||||
|
|
||||||
ImageFile.raise_oserror
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. deprecated:: 10.2.0
|
|
||||||
|
|
||||||
``ImageFile.raise_oserror()`` has been deprecated and will be removed in Pillow
|
|
||||||
12.0.0 (2025-10-15). The function is undocumented and is only useful for translating
|
|
||||||
error codes returned by a codec's ``decode()`` method, which ImageFile already does
|
|
||||||
automatically.
|
|
||||||
|
|
||||||
IptcImageFile helper functions
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. deprecated:: 10.2.0
|
|
||||||
|
|
||||||
The functions ``IptcImageFile.dump`` and ``IptcImageFile.i``, and the constant
|
|
||||||
``IptcImageFile.PAD`` have been deprecated and will be removed in Pillow
|
|
||||||
12.0.0 (2025-10-15). These are undocumented helper functions intended
|
|
||||||
for internal use, so there is no replacement. They can each be replaced
|
|
||||||
by a single line of code using builtin functions in Python.
|
|
||||||
|
|
||||||
ImageCms constants and versions() function
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. deprecated:: 10.3.0
|
|
||||||
|
|
||||||
A number of constants and a function in :py:mod:`.ImageCms` have been deprecated.
|
|
||||||
This includes a table of flags based on LittleCMS version 1 which has been
|
|
||||||
replaced with a new class :py:class:`.ImageCms.Flags` based on LittleCMS 2 flags.
|
|
||||||
|
|
||||||
============================================ ====================================================
|
|
||||||
Deprecated Use instead
|
|
||||||
============================================ ====================================================
|
|
||||||
``ImageCms.DESCRIPTION`` No replacement
|
|
||||||
``ImageCms.VERSION`` ``PIL.__version__``
|
|
||||||
``ImageCms.FLAGS["MATRIXINPUT"]`` :py:attr:`.ImageCms.Flags.CLUT_POST_LINEARIZATION`
|
|
||||||
``ImageCms.FLAGS["MATRIXOUTPUT"]`` :py:attr:`.ImageCms.Flags.FORCE_CLUT`
|
|
||||||
``ImageCms.FLAGS["MATRIXONLY"]`` No replacement
|
|
||||||
``ImageCms.FLAGS["NOWHITEONWHITEFIXUP"]`` :py:attr:`.ImageCms.Flags.NOWHITEONWHITEFIXUP`
|
|
||||||
``ImageCms.FLAGS["NOPRELINEARIZATION"]`` :py:attr:`.ImageCms.Flags.CLUT_PRE_LINEARIZATION`
|
|
||||||
``ImageCms.FLAGS["GUESSDEVICECLASS"]`` :py:attr:`.ImageCms.Flags.GUESSDEVICECLASS`
|
|
||||||
``ImageCms.FLAGS["NOTCACHE"]`` :py:attr:`.ImageCms.Flags.NOCACHE`
|
|
||||||
``ImageCms.FLAGS["NOTPRECALC"]`` :py:attr:`.ImageCms.Flags.NOOPTIMIZE`
|
|
||||||
``ImageCms.FLAGS["NULLTRANSFORM"]`` :py:attr:`.ImageCms.Flags.NULLTRANSFORM`
|
|
||||||
``ImageCms.FLAGS["HIGHRESPRECALC"]`` :py:attr:`.ImageCms.Flags.HIGHRESPRECALC`
|
|
||||||
``ImageCms.FLAGS["LOWRESPRECALC"]`` :py:attr:`.ImageCms.Flags.LOWRESPRECALC`
|
|
||||||
``ImageCms.FLAGS["GAMUTCHECK"]`` :py:attr:`.ImageCms.Flags.GAMUTCHECK`
|
|
||||||
``ImageCms.FLAGS["WHITEBLACKCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION`
|
|
||||||
``ImageCms.FLAGS["BLACKPOINTCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION`
|
|
||||||
``ImageCms.FLAGS["SOFTPROOFING"]`` :py:attr:`.ImageCms.Flags.SOFTPROOFING`
|
|
||||||
``ImageCms.FLAGS["PRESERVEBLACK"]`` :py:attr:`.ImageCms.Flags.NONEGATIVES`
|
|
||||||
``ImageCms.FLAGS["NODEFAULTRESOURCEDEF"]`` :py:attr:`.ImageCms.Flags.NODEFAULTRESOURCEDEF`
|
|
||||||
``ImageCms.FLAGS["GRIDPOINTS"]`` :py:attr:`.ImageCms.Flags.GRIDPOINTS()`
|
|
||||||
``ImageCms.versions()`` :py:func:`PIL.features.version_module` with
|
|
||||||
``feature="littlecms2"``, :py:data:`sys.version` or
|
|
||||||
:py:data:`sys.version_info`, and ``PIL.__version__``
|
|
||||||
============================================ ====================================================
|
|
||||||
|
|
||||||
ImageMath eval()
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. deprecated:: 10.3.0
|
|
||||||
|
|
||||||
``ImageMath.eval()`` has been deprecated. Use :py:meth:`~PIL.ImageMath.lambda_eval` or
|
|
||||||
:py:meth:`~PIL.ImageMath.unsafe_eval` instead.
|
|
||||||
|
|
||||||
BGR;15, BGR 16 and BGR;24
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. deprecated:: 10.4.0
|
|
||||||
|
|
||||||
The experimental BGR;15, BGR;16 and BGR;24 modes have been deprecated.
|
|
||||||
|
|
||||||
Non-image modes in ImageCms
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. deprecated:: 10.4.0
|
|
||||||
|
|
||||||
The use in :py:mod:`.ImageCms` of input modes and output modes that are not Pillow
|
|
||||||
image modes has been deprecated. Defaulting to "L" or "1" if the mode cannot be mapped
|
|
||||||
is also deprecated.
|
|
||||||
|
|
||||||
Support for LibTIFF earlier than 4
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. deprecated:: 10.4.0
|
|
||||||
|
|
||||||
Support for LibTIFF earlier than version 4 has been deprecated.
|
|
||||||
Upgrade to a newer version of LibTIFF instead.
|
|
||||||
|
|
||||||
ImageDraw.getdraw hints parameter
|
ImageDraw.getdraw hints parameter
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -109,72 +19,6 @@ ImageDraw.getdraw hints parameter
|
||||||
|
|
||||||
The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated.
|
The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated.
|
||||||
|
|
||||||
FreeType 2.9.0
|
|
||||||
^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. deprecated:: 11.0.0
|
|
||||||
|
|
||||||
Support for FreeType 2.9.0 is deprecated and will be removed in Pillow 12.0.0
|
|
||||||
(2025-10-15), when FreeType 2.9.1 will be the minimum supported.
|
|
||||||
|
|
||||||
We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe
|
|
||||||
vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
|
|
||||||
|
|
||||||
.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/
|
|
||||||
|
|
||||||
ICNS (width, height, scale) sizes
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. deprecated:: 11.0.0
|
|
||||||
|
|
||||||
Setting an ICNS image size to ``(width, height, scale)`` before loading has been
|
|
||||||
deprecated. Instead, ``load(scale)`` can be used.
|
|
||||||
|
|
||||||
Image isImageType()
|
|
||||||
^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. deprecated:: 11.0.0
|
|
||||||
|
|
||||||
``Image.isImageType(im)`` has been deprecated. Use ``isinstance(im, Image.Image)``
|
|
||||||
instead.
|
|
||||||
|
|
||||||
ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. deprecated:: 11.0.0
|
|
||||||
|
|
||||||
The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and
|
|
||||||
:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more keyword
|
|
||||||
arguments can be used instead.
|
|
||||||
|
|
||||||
JpegImageFile.huffman_ac and JpegImageFile.huffman_dc
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. deprecated:: 11.0.0
|
|
||||||
|
|
||||||
The ``huffman_ac`` and ``huffman_dc`` dictionaries on JPEG images were unused. They
|
|
||||||
have been deprecated, and will be removed in Pillow 12 (2025-10-15).
|
|
||||||
|
|
||||||
Specific WebP feature checks
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. deprecated:: 11.0.0
|
|
||||||
|
|
||||||
``features.check("transp_webp")``, ``features.check("webp_mux")`` and
|
|
||||||
``features.check("webp_anim")`` are now deprecated. They will always return
|
|
||||||
``True`` if the WebP module is installed, until they are removed in Pillow
|
|
||||||
12.0.0 (2025-10-15).
|
|
||||||
|
|
||||||
Get internal pointers to objects
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
.. deprecated:: 11.0.0
|
|
||||||
|
|
||||||
``Image.core.ImagingCore.id`` and ``Image.core.ImagingCore.unsafe_ptrs`` have been
|
|
||||||
deprecated and will be removed in Pillow 12 (2025-10-15). They were used for obtaining
|
|
||||||
raw pointers to ``ImagingCore`` internals. To interact with C code, you can use
|
|
||||||
``Image.Image.getim()``, which returns a ``Capsule`` object.
|
|
||||||
|
|
||||||
ExifTags.IFD.Makernote
|
ExifTags.IFD.Makernote
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -221,12 +65,185 @@ Removed features
|
||||||
Deprecated features are only removed in major releases after an appropriate
|
Deprecated features are only removed in major releases after an appropriate
|
||||||
period of deprecation has passed.
|
period of deprecation has passed.
|
||||||
|
|
||||||
|
ImageFile.raise_oserror
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 10.2.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
``ImageFile.raise_oserror()`` has been removed. The function was undocumented and was
|
||||||
|
only useful for translating error codes returned by a codec's ``decode()`` method,
|
||||||
|
which ImageFile already did automatically.
|
||||||
|
|
||||||
|
IptcImageFile helper functions
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 10.2.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
The functions ``IptcImageFile.dump`` and ``IptcImageFile.i``, and the constant
|
||||||
|
``IptcImageFile.PAD`` have been removed. These were undocumented helper functions
|
||||||
|
intended for internal use, so there is no replacement. They can each be replaced by a
|
||||||
|
single line of code using builtin functions in Python.
|
||||||
|
|
||||||
|
ImageCms constants and versions() function
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 10.3.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
A number of constants and a function in :py:mod:`.ImageCms` have been removed. This
|
||||||
|
includes a table of flags based on LittleCMS version 1 which has been replaced with a
|
||||||
|
new class :py:class:`.ImageCms.Flags` based on LittleCMS 2 flags.
|
||||||
|
|
||||||
|
============================================ ====================================================
|
||||||
|
Deprecated Use instead
|
||||||
|
============================================ ====================================================
|
||||||
|
``ImageCms.DESCRIPTION`` No replacement
|
||||||
|
``ImageCms.VERSION`` ``PIL.__version__``
|
||||||
|
``ImageCms.FLAGS["MATRIXINPUT"]`` :py:attr:`.ImageCms.Flags.CLUT_POST_LINEARIZATION`
|
||||||
|
``ImageCms.FLAGS["MATRIXOUTPUT"]`` :py:attr:`.ImageCms.Flags.FORCE_CLUT`
|
||||||
|
``ImageCms.FLAGS["MATRIXONLY"]`` No replacement
|
||||||
|
``ImageCms.FLAGS["NOWHITEONWHITEFIXUP"]`` :py:attr:`.ImageCms.Flags.NOWHITEONWHITEFIXUP`
|
||||||
|
``ImageCms.FLAGS["NOPRELINEARIZATION"]`` :py:attr:`.ImageCms.Flags.CLUT_PRE_LINEARIZATION`
|
||||||
|
``ImageCms.FLAGS["GUESSDEVICECLASS"]`` :py:attr:`.ImageCms.Flags.GUESSDEVICECLASS`
|
||||||
|
``ImageCms.FLAGS["NOTCACHE"]`` :py:attr:`.ImageCms.Flags.NOCACHE`
|
||||||
|
``ImageCms.FLAGS["NOTPRECALC"]`` :py:attr:`.ImageCms.Flags.NOOPTIMIZE`
|
||||||
|
``ImageCms.FLAGS["NULLTRANSFORM"]`` :py:attr:`.ImageCms.Flags.NULLTRANSFORM`
|
||||||
|
``ImageCms.FLAGS["HIGHRESPRECALC"]`` :py:attr:`.ImageCms.Flags.HIGHRESPRECALC`
|
||||||
|
``ImageCms.FLAGS["LOWRESPRECALC"]`` :py:attr:`.ImageCms.Flags.LOWRESPRECALC`
|
||||||
|
``ImageCms.FLAGS["GAMUTCHECK"]`` :py:attr:`.ImageCms.Flags.GAMUTCHECK`
|
||||||
|
``ImageCms.FLAGS["WHITEBLACKCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION`
|
||||||
|
``ImageCms.FLAGS["BLACKPOINTCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION`
|
||||||
|
``ImageCms.FLAGS["SOFTPROOFING"]`` :py:attr:`.ImageCms.Flags.SOFTPROOFING`
|
||||||
|
``ImageCms.FLAGS["PRESERVEBLACK"]`` :py:attr:`.ImageCms.Flags.NONEGATIVES`
|
||||||
|
``ImageCms.FLAGS["NODEFAULTRESOURCEDEF"]`` :py:attr:`.ImageCms.Flags.NODEFAULTRESOURCEDEF`
|
||||||
|
``ImageCms.FLAGS["GRIDPOINTS"]`` :py:attr:`.ImageCms.Flags.GRIDPOINTS()`
|
||||||
|
``ImageCms.versions()`` :py:func:`PIL.features.version_module` with
|
||||||
|
``feature="littlecms2"``, :py:data:`sys.version` or
|
||||||
|
:py:data:`sys.version_info`, and ``PIL.__version__``
|
||||||
|
============================================ ====================================================
|
||||||
|
|
||||||
|
ImageMath eval()
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 10.3.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
``ImageMath.eval()`` has been removed. Use :py:meth:`~PIL.ImageMath.lambda_eval` or
|
||||||
|
:py:meth:`~PIL.ImageMath.unsafe_eval` instead.
|
||||||
|
|
||||||
|
BGR;15, BGR 16 and BGR;24
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 10.4.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
The experimental BGR;15, BGR;16 and BGR;24 modes have been removed.
|
||||||
|
|
||||||
|
Non-image modes in ImageCms
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 10.4.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
The use in :py:mod:`.ImageCms` of input modes and output modes that are not Pillow
|
||||||
|
image modes has been removed. Defaulting to "L" or "1" if the mode cannot be mapped has
|
||||||
|
also been removed.
|
||||||
|
|
||||||
|
Support for LibTIFF earlier than 4
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 10.4.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
Support for LibTIFF earlier than version 4 has been removed.
|
||||||
|
Upgrade to a newer version of LibTIFF instead.
|
||||||
|
|
||||||
|
ImageDraw.getdraw hints parameter
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 10.4.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been removed.
|
||||||
|
|
||||||
|
FreeType 2.9.0
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.0.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
Support for FreeType 2.9.0 has been removed. FreeType 2.9.1 is the minimum version
|
||||||
|
supported.
|
||||||
|
|
||||||
|
We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe
|
||||||
|
vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
|
||||||
|
|
||||||
|
.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/
|
||||||
|
|
||||||
|
ICNS (width, height, scale) sizes
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.0.0
|
||||||
|
|
||||||
|
Setting an ICNS image size to ``(width, height, scale)`` before loading has been
|
||||||
|
removed. Instead, ``load(scale)`` can be used.
|
||||||
|
|
||||||
|
Image isImageType()
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.0.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
``Image.isImageType(im)`` has been removed. Use ``isinstance(im, Image.Image)``
|
||||||
|
instead.
|
||||||
|
|
||||||
|
ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.0.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and
|
||||||
|
:py:meth:`~PIL.ImageMath.unsafe_eval()` has been removed. One or more keyword
|
||||||
|
arguments can be used instead.
|
||||||
|
|
||||||
|
JpegImageFile.huffman_ac and JpegImageFile.huffman_dc
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.0.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
The ``huffman_ac`` and ``huffman_dc`` dictionaries on JPEG images were unused. They
|
||||||
|
have been removed.
|
||||||
|
|
||||||
|
Specific WebP feature checks
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.0.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
``features.check("transp_webp")``, ``features.check("webp_mux")`` and
|
||||||
|
``features.check("webp_anim")`` have been removed.
|
||||||
|
|
||||||
|
Get internal pointers to objects
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.0.0
|
||||||
|
.. versionremoved:: 12.0.0
|
||||||
|
|
||||||
|
``Image.core.ImagingCore.id`` and ``Image.core.ImagingCore.unsafe_ptrs`` have been
|
||||||
|
removed. They were used for obtaining raw pointers to ``ImagingCore`` internals. To
|
||||||
|
interact with C code, you can use ``Image.Image.getim()``, which returns a ``Capsule``
|
||||||
|
object.
|
||||||
|
|
||||||
TiffImagePlugin IFD_LEGACY_API
|
TiffImagePlugin IFD_LEGACY_API
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. versionremoved:: 11.0.0
|
.. versionremoved:: 11.0.0
|
||||||
|
|
||||||
``TiffImagePlugin.IFD_LEGACY_API`` was removed, as it was an unused setting.
|
``TiffImagePlugin.IFD_LEGACY_API`` has been removed, as it was an unused setting.
|
||||||
|
|
||||||
PSFile
|
PSFile
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
|
@ -557,6 +557,8 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
hardly any gain in image quality. The value ``keep`` is only valid for JPEG
|
hardly any gain in image quality. The value ``keep`` is only valid for JPEG
|
||||||
files and will retain the original image quality level, subsampling, and
|
files and will retain the original image quality level, subsampling, and
|
||||||
qtables.
|
qtables.
|
||||||
|
For more information on how qtables are modified based on the quality parameter,
|
||||||
|
see the qtables section.
|
||||||
|
|
||||||
**optimize**
|
**optimize**
|
||||||
If present and true, indicates that the encoder should make an extra pass
|
If present and true, indicates that the encoder should make an extra pass
|
||||||
|
@ -622,6 +624,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
range(len(keys))) of lists of 64 integers. There must be
|
range(len(keys))) of lists of 64 integers. There must be
|
||||||
between 2 and 4 tables.
|
between 2 and 4 tables.
|
||||||
|
|
||||||
|
If a quality parameter is provided, the qtables will be adjusted accordingly.
|
||||||
|
By default, the qtables are based on a standard JPEG table with a quality of 50.
|
||||||
|
The qtable values will be reduced if the quality is higher than 50 and increased
|
||||||
|
if the quality is lower than 50.
|
||||||
|
|
||||||
.. versionadded:: 2.5.0
|
.. versionadded:: 2.5.0
|
||||||
|
|
||||||
**streamtype**
|
**streamtype**
|
||||||
|
|
|
@ -75,7 +75,7 @@ These platforms have been reported to work at the versions mentioned.
|
||||||
| Operating system | | Tested Python | | Latest tested | | Tested |
|
| Operating system | | Tested Python | | Latest tested | | Tested |
|
||||||
| | | versions | | Pillow version | | processors |
|
| | | versions | | Pillow version | | processors |
|
||||||
+==================================+============================+==================+==============+
|
+==================================+============================+==================+==============+
|
||||||
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.2.1 |arm |
|
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
|
||||||
| +----------------------------+------------------+ |
|
| +----------------------------+------------------+ |
|
||||||
| | 3.8 | 10.4.0 | |
|
| | 3.8 | 10.4.0 | |
|
||||||
+----------------------------------+----------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
|
|
|
@ -56,7 +56,6 @@ Functions
|
||||||
.. autofunction:: get_display_profile
|
.. autofunction:: get_display_profile
|
||||||
.. autofunction:: isIntentSupported
|
.. autofunction:: isIntentSupported
|
||||||
.. autofunction:: profileToProfile
|
.. autofunction:: profileToProfile
|
||||||
.. autofunction:: versions
|
|
||||||
|
|
||||||
CmsProfile
|
CmsProfile
|
||||||
----------
|
----------
|
||||||
|
|
|
@ -21,9 +21,7 @@ with any Arrow provider or consumer in the Python ecosystem.
|
||||||
Data formats
|
Data formats
|
||||||
============
|
============
|
||||||
|
|
||||||
Pillow currently supports exporting Arrow images in all modes
|
Pillow currently supports exporting Arrow images in all modes.
|
||||||
**except** for ``BGR;15``, ``BGR;16`` and ``BGR;24``. This is due to
|
|
||||||
line-length packing in these modes making for non-continuous memory.
|
|
||||||
|
|
||||||
For single-band images, the exported array is width*height elements,
|
For single-band images, the exported array is width*height elements,
|
||||||
with each pixel corresponding to the appropriate Arrow type.
|
with each pixel corresponding to the appropriate Arrow type.
|
||||||
|
|
|
@ -60,9 +60,6 @@ Support for the following features can be checked:
|
||||||
* ``raqm``: Raqm library, required for ``ImageFont.Layout.RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer.
|
* ``raqm``: Raqm library, required for ``ImageFont.Layout.RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer.
|
||||||
* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available.
|
* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available.
|
||||||
* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library.
|
* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library.
|
||||||
* ``transp_webp``: Deprecated. Always ``True`` if WebP module is installed.
|
|
||||||
* ``webp_mux``: Deprecated. Always ``True`` if WebP module is installed.
|
|
||||||
* ``webp_anim``: Deprecated. Always ``True`` if WebP module is installed.
|
|
||||||
|
|
||||||
.. autofunction:: PIL.features.check_feature
|
.. autofunction:: PIL.features.check_feature
|
||||||
.. autofunction:: PIL.features.version_feature
|
.. autofunction:: PIL.features.version_feature
|
||||||
|
|
|
@ -68,7 +68,15 @@ AVIF support in wheels
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Support for reading and writing AVIF images is now included in Pillow's wheels, except
|
Support for reading and writing AVIF images is now included in Pillow's wheels, except
|
||||||
for Windows ARM64. libaom is available as an encoder and dav1d as a decoder.
|
for Windows ARM64 and iOS. libaom is available as an encoder and dav1d as a decoder.
|
||||||
|
(Thank you Frankie Dintino and Andrew Murray!)
|
||||||
|
|
||||||
|
iOS
|
||||||
|
^^^
|
||||||
|
|
||||||
|
Pillow now provides wheels that can be used on iOS ARM64 devices, and on the iOS
|
||||||
|
simulator on ARM64 and x86_64. Currently, only Python 3.13 wheels are available.
|
||||||
|
(Thank you Russell Keith-Magee and Andrew Murray!)
|
||||||
|
|
||||||
Python 3.14 beta
|
Python 3.14 beta
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
140
docs/releasenotes/12.0.0.rst
Normal file
140
docs/releasenotes/12.0.0.rst
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
12.0.0
|
||||||
|
------
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
:cve:`YYYY-XXXXX`: TODO
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Backwards incompatible changes
|
||||||
|
==============================
|
||||||
|
|
||||||
|
ImageFile.raise_oserror
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``ImageFile.raise_oserror()`` has been removed. The function was undocumented and was
|
||||||
|
only useful for translating error codes returned by a codec's ``decode()`` method,
|
||||||
|
which ImageFile already did automatically.
|
||||||
|
|
||||||
|
IptcImageFile helper functions
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The functions ``IptcImageFile.dump`` and ``IptcImageFile.i``, and the constant
|
||||||
|
``IptcImageFile.PAD`` have been removed. These were undocumented helper functions
|
||||||
|
intended for internal use, so there is no replacement. They can each be replaced by a
|
||||||
|
single line of code using builtin functions in Python.
|
||||||
|
|
||||||
|
ImageCms constants and versions() function
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
A number of constants and a function in :py:mod:`.ImageCms` have been removed. This
|
||||||
|
includes a table of flags based on LittleCMS version 1 which has been replaced with a
|
||||||
|
new class :py:class:`.ImageCms.Flags` based on LittleCMS 2 flags.
|
||||||
|
|
||||||
|
============================================ ====================================================
|
||||||
|
Deprecated Use instead
|
||||||
|
============================================ ====================================================
|
||||||
|
``ImageCms.DESCRIPTION`` No replacement
|
||||||
|
``ImageCms.VERSION`` ``PIL.__version__``
|
||||||
|
``ImageCms.FLAGS["MATRIXINPUT"]`` :py:attr:`.ImageCms.Flags.CLUT_POST_LINEARIZATION`
|
||||||
|
``ImageCms.FLAGS["MATRIXOUTPUT"]`` :py:attr:`.ImageCms.Flags.FORCE_CLUT`
|
||||||
|
``ImageCms.FLAGS["MATRIXONLY"]`` No replacement
|
||||||
|
``ImageCms.FLAGS["NOWHITEONWHITEFIXUP"]`` :py:attr:`.ImageCms.Flags.NOWHITEONWHITEFIXUP`
|
||||||
|
``ImageCms.FLAGS["NOPRELINEARIZATION"]`` :py:attr:`.ImageCms.Flags.CLUT_PRE_LINEARIZATION`
|
||||||
|
``ImageCms.FLAGS["GUESSDEVICECLASS"]`` :py:attr:`.ImageCms.Flags.GUESSDEVICECLASS`
|
||||||
|
``ImageCms.FLAGS["NOTCACHE"]`` :py:attr:`.ImageCms.Flags.NOCACHE`
|
||||||
|
``ImageCms.FLAGS["NOTPRECALC"]`` :py:attr:`.ImageCms.Flags.NOOPTIMIZE`
|
||||||
|
``ImageCms.FLAGS["NULLTRANSFORM"]`` :py:attr:`.ImageCms.Flags.NULLTRANSFORM`
|
||||||
|
``ImageCms.FLAGS["HIGHRESPRECALC"]`` :py:attr:`.ImageCms.Flags.HIGHRESPRECALC`
|
||||||
|
``ImageCms.FLAGS["LOWRESPRECALC"]`` :py:attr:`.ImageCms.Flags.LOWRESPRECALC`
|
||||||
|
``ImageCms.FLAGS["GAMUTCHECK"]`` :py:attr:`.ImageCms.Flags.GAMUTCHECK`
|
||||||
|
``ImageCms.FLAGS["WHITEBLACKCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION`
|
||||||
|
``ImageCms.FLAGS["BLACKPOINTCOMPENSATION"]`` :py:attr:`.ImageCms.Flags.BLACKPOINTCOMPENSATION`
|
||||||
|
``ImageCms.FLAGS["SOFTPROOFING"]`` :py:attr:`.ImageCms.Flags.SOFTPROOFING`
|
||||||
|
``ImageCms.FLAGS["PRESERVEBLACK"]`` :py:attr:`.ImageCms.Flags.NONEGATIVES`
|
||||||
|
``ImageCms.FLAGS["NODEFAULTRESOURCEDEF"]`` :py:attr:`.ImageCms.Flags.NODEFAULTRESOURCEDEF`
|
||||||
|
``ImageCms.FLAGS["GRIDPOINTS"]`` :py:attr:`.ImageCms.Flags.GRIDPOINTS()`
|
||||||
|
``ImageCms.versions()`` :py:func:`PIL.features.version_module` with
|
||||||
|
``feature="littlecms2"``, :py:data:`sys.version` or
|
||||||
|
:py:data:`sys.version_info`, and ``PIL.__version__``
|
||||||
|
============================================ ====================================================
|
||||||
|
|
||||||
|
ImageMath eval()
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``ImageMath.eval()`` has been removed. Use :py:meth:`~PIL.ImageMath.lambda_eval` or
|
||||||
|
:py:meth:`~PIL.ImageMath.unsafe_eval` instead.
|
||||||
|
|
||||||
|
BGR;15, BGR 16 and BGR;24
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The experimental BGR;15, BGR;16 and BGR;24 modes have been removed.
|
||||||
|
|
||||||
|
Non-image modes in ImageCms
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The use in :py:mod:`.ImageCms` of input modes and output modes that are not Pillow
|
||||||
|
image modes has been removed. Defaulting to "L" or "1" if the mode cannot be mapped has
|
||||||
|
also been removed.
|
||||||
|
|
||||||
|
Support for LibTIFF earlier than 4
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Support for LibTIFF earlier than version 4 has been removed.
|
||||||
|
Upgrade to a newer version of LibTIFF instead.
|
||||||
|
|
||||||
|
ImageDraw.getdraw hints parameter
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been removed.
|
||||||
|
|
||||||
|
FreeType 2.9.0
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Support for FreeType 2.9.0 has been removed. FreeType 2.9.1 is the minimum version
|
||||||
|
supported.
|
||||||
|
|
||||||
|
We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe
|
||||||
|
vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
|
||||||
|
|
||||||
|
.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
============
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
API changes
|
||||||
|
===========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
API additions
|
||||||
|
=============
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Other changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
|
@ -14,6 +14,7 @@ expected to be backported to earlier versions.
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
12.0.0
|
||||||
11.3.0
|
11.3.0
|
||||||
11.2.1
|
11.2.1
|
||||||
11.1.0
|
11.1.0
|
||||||
|
|
|
@ -20,6 +20,8 @@ Backwards incompatible changes
|
||||||
TODO
|
TODO
|
||||||
^^^^
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
|
14
patches/README.md
Normal file
14
patches/README.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
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.
|
46
patches/iOS/brotli-1.1.0.tar.gz.patch
Normal file
46
patches/iOS/brotli-1.1.0.tar.gz.patch
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# 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 <endian.h>
|
||||||
|
#elif defined(OS_FREEBSD)
|
||||||
|
#include <machine/endian.h>
|
||||||
|
-#elif defined(OS_MACOSX)
|
||||||
|
+#elif defined(OS_MACOSX) || defined(OS_IOS)
|
||||||
|
#include <machine/endian.h>
|
||||||
|
/* Let's try and follow the Linux convention */
|
||||||
|
#define BROTLI_X_BYTE_ORDER BYTE_ORDER
|
42
patches/iOS/libwebp-1.5.0.tar.gz.patch
Normal file
42
patches/iOS/libwebp-1.5.0.tar.gz.patch
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# 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
|
||||||
|
|
|
@ -9,7 +9,7 @@ backend-path = [
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pillow"
|
name = "pillow"
|
||||||
description = "Python Imaging Library (Fork)"
|
description = "Python Imaging Library (fork)"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = [
|
keywords = [
|
||||||
"Imaging",
|
"Imaging",
|
||||||
|
@ -103,15 +103,55 @@ before-all = ".github/workflows/wheels-dependencies.sh"
|
||||||
build-verbosity = 1
|
build-verbosity = 1
|
||||||
|
|
||||||
config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable"
|
config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable"
|
||||||
# Disable platform guessing on macOS
|
|
||||||
macos.config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable platform-guessing=disable"
|
|
||||||
|
|
||||||
test-command = "cd {project} && .github/workflows/wheels-test.sh"
|
test-command = "cd {project} && .github/workflows/wheels-test.sh"
|
||||||
test-extras = "tests"
|
test-extras = "tests"
|
||||||
|
test-requires = [
|
||||||
|
"numpy",
|
||||||
|
]
|
||||||
|
xbuild-tools = [ ]
|
||||||
|
|
||||||
|
[tool.cibuildwheel.macos]
|
||||||
|
# Disable platform guessing on macOS to avoid picking up Homebrew etc.
|
||||||
|
config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable platform-guessing=disable"
|
||||||
|
|
||||||
[tool.cibuildwheel.macos.environment]
|
[tool.cibuildwheel.macos.environment]
|
||||||
|
# Isolate macOS build environment from Homebrew etc.
|
||||||
PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
|
PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
|
||||||
|
|
||||||
|
[tool.cibuildwheel.ios]
|
||||||
|
# Disable platform guessing on iOS, and disable raqm (since there won't be a
|
||||||
|
# vendor version, and we can't distribute it due to licensing)
|
||||||
|
config-settings = "raqm=disable imagequant=disable platform-guessing=disable"
|
||||||
|
|
||||||
|
# iOS needs to be given a specific pytest invocation and list of test sources.
|
||||||
|
test-sources = [
|
||||||
|
"checks",
|
||||||
|
"Tests",
|
||||||
|
"selftest.py",
|
||||||
|
]
|
||||||
|
test-command = [
|
||||||
|
"python -m selftest",
|
||||||
|
"python -m pytest -vv -x -W always checks/check_wheel.py Tests",
|
||||||
|
]
|
||||||
|
|
||||||
|
# There's no numpy wheel for iOS (yet...)
|
||||||
|
test-requires = [ ]
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
# iOS environment is isolated by cibuildwheel, but needs the dependencies
|
||||||
|
select = "*_iphoneos"
|
||||||
|
environment.PATH = "$(pwd)/build/deps/iphoneos/bin:$PATH"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
# iOS simulator environment is isolated by cibuildwheel, but needs the dependencies
|
||||||
|
select = "*_iphonesimulator"
|
||||||
|
environment.PATH = "$(pwd)/build/deps/iphonesimulator/bin:$PATH"
|
||||||
|
|
||||||
|
[[tool.cibuildwheel.overrides]]
|
||||||
|
select = "*-win32"
|
||||||
|
test-requires = [ ]
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
exclude = "wheels/multibuild"
|
exclude = "wheels/multibuild"
|
||||||
|
|
||||||
|
@ -168,7 +208,7 @@ lint.isort.required-imports = [
|
||||||
max_supported_python = "3.13"
|
max_supported_python = "3.13"
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "-ra --color=yes"
|
addopts = "-ra --color=auto"
|
||||||
testpaths = [
|
testpaths = [
|
||||||
"Tests",
|
"Tests",
|
||||||
]
|
]
|
||||||
|
|
39
setup.py
39
setup.py
|
@ -473,6 +473,19 @@ class pil_build_ext(build_ext):
|
||||||
sdk_path = commandlinetools_sdk_path
|
sdk_path = commandlinetools_sdk_path
|
||||||
return sdk_path
|
return sdk_path
|
||||||
|
|
||||||
|
def get_ios_sdk_path(self) -> str:
|
||||||
|
try:
|
||||||
|
sdk = sys.implementation._multiarch.split("-")[-1]
|
||||||
|
_dbg("Using %s SDK", sdk)
|
||||||
|
return (
|
||||||
|
subprocess.check_output(["xcrun", "--show-sdk-path", "--sdk", sdk])
|
||||||
|
.strip()
|
||||||
|
.decode("latin1")
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
msg = "Unable to identify location of iOS SDK."
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
def build_extensions(self) -> None:
|
def build_extensions(self) -> None:
|
||||||
library_dirs: list[str] = []
|
library_dirs: list[str] = []
|
||||||
include_dirs: list[str] = []
|
include_dirs: list[str] = []
|
||||||
|
@ -622,6 +635,18 @@ class pil_build_ext(build_ext):
|
||||||
|
|
||||||
for extension in self.extensions:
|
for extension in self.extensions:
|
||||||
extension.extra_compile_args = ["-Wno-nullability-completeness"]
|
extension.extra_compile_args = ["-Wno-nullability-completeness"]
|
||||||
|
|
||||||
|
elif sys.platform == "ios":
|
||||||
|
# Add the iOS SDK path.
|
||||||
|
sdk_path = self.get_ios_sdk_path()
|
||||||
|
|
||||||
|
# Add the iOS SDK path.
|
||||||
|
_add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib"))
|
||||||
|
_add_directory(include_dirs, os.path.join(sdk_path, "usr", "include"))
|
||||||
|
|
||||||
|
for extension in self.extensions:
|
||||||
|
extension.extra_compile_args = ["-Wno-nullability-completeness"]
|
||||||
|
|
||||||
elif sys.platform.startswith(("linux", "gnu", "freebsd")):
|
elif sys.platform.startswith(("linux", "gnu", "freebsd")):
|
||||||
for dirname in _find_library_dirs_ldconfig():
|
for dirname in _find_library_dirs_ldconfig():
|
||||||
_add_directory(library_dirs, dirname)
|
_add_directory(library_dirs, dirname)
|
||||||
|
@ -877,6 +902,9 @@ class pil_build_ext(build_ext):
|
||||||
# so we have to guess; by default it is defined in all Windows builds.
|
# so we have to guess; by default it is defined in all Windows builds.
|
||||||
# See #4237, #5243, #5359 for more information.
|
# See #4237, #5243, #5359 for more information.
|
||||||
defs.append(("USE_WIN32_FILEIO", None))
|
defs.append(("USE_WIN32_FILEIO", None))
|
||||||
|
elif sys.platform == "ios":
|
||||||
|
# Ensure transitive dependencies are linked.
|
||||||
|
libs.append("lzma")
|
||||||
if feature.get("jpeg"):
|
if feature.get("jpeg"):
|
||||||
libs.append(feature.get("jpeg"))
|
libs.append(feature.get("jpeg"))
|
||||||
defs.append(("HAVE_LIBJPEG", None))
|
defs.append(("HAVE_LIBJPEG", None))
|
||||||
|
@ -893,6 +921,9 @@ class pil_build_ext(build_ext):
|
||||||
defs.append(("HAVE_LIBIMAGEQUANT", None))
|
defs.append(("HAVE_LIBIMAGEQUANT", None))
|
||||||
if feature.get("xcb"):
|
if feature.get("xcb"):
|
||||||
libs.append(feature.get("xcb"))
|
libs.append(feature.get("xcb"))
|
||||||
|
if sys.platform == "ios":
|
||||||
|
# Ensure transitive dependencies are linked.
|
||||||
|
libs.append("Xau")
|
||||||
defs.append(("HAVE_XCB", None))
|
defs.append(("HAVE_XCB", None))
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
libs.extend(["kernel32", "user32", "gdi32"])
|
libs.extend(["kernel32", "user32", "gdi32"])
|
||||||
|
@ -924,6 +955,11 @@ class pil_build_ext(build_ext):
|
||||||
libs.append(feature.get("fribidi"))
|
libs.append(feature.get("fribidi"))
|
||||||
else: # building FriBiDi shim from src/thirdparty
|
else: # building FriBiDi shim from src/thirdparty
|
||||||
srcs.append("src/thirdparty/fribidi-shim/fribidi.c")
|
srcs.append("src/thirdparty/fribidi-shim/fribidi.c")
|
||||||
|
|
||||||
|
if sys.platform == "ios":
|
||||||
|
# Ensure transitive dependencies are linked.
|
||||||
|
libs.extend(["z", "bz2", "brotlicommon", "brotlidec", "png"])
|
||||||
|
|
||||||
self._update_extension("PIL._imagingft", libs, defs, srcs)
|
self._update_extension("PIL._imagingft", libs, defs, srcs)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -940,6 +976,9 @@ class pil_build_ext(build_ext):
|
||||||
webp = feature.get("webp")
|
webp = feature.get("webp")
|
||||||
if isinstance(webp, str):
|
if isinstance(webp, str):
|
||||||
libs = [webp, webp + "mux", webp + "demux"]
|
libs = [webp, webp + "mux", webp + "demux"]
|
||||||
|
if sys.platform == "ios":
|
||||||
|
# Ensure transitive dependencies are linked.
|
||||||
|
libs.append("sharpyuv")
|
||||||
self._update_extension("PIL._webp", libs)
|
self._update_extension("PIL._webp", libs)
|
||||||
else:
|
else:
|
||||||
self._remove_extension("PIL._webp")
|
self._remove_extension("PIL._webp")
|
||||||
|
|
|
@ -479,8 +479,11 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self._prev_im = expanded_im
|
self._prev_im = expanded_im
|
||||||
assert self._prev_im is not None
|
assert self._prev_im is not None
|
||||||
if self._frame_transparency is not None:
|
if self._frame_transparency is not None:
|
||||||
self.im.putpalettealpha(self._frame_transparency, 0)
|
if self.mode == "L":
|
||||||
frame_im = self.im.convert("RGBA")
|
frame_im = self.im.convert_transparent("LA", self._frame_transparency)
|
||||||
|
else:
|
||||||
|
self.im.putpalettealpha(self._frame_transparency, 0)
|
||||||
|
frame_im = self.im.convert("RGBA")
|
||||||
else:
|
else:
|
||||||
frame_im = self.im.convert("RGB")
|
frame_im = self.im.convert("RGB")
|
||||||
|
|
||||||
|
@ -489,7 +492,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self.im = self._prev_im
|
self.im = self._prev_im
|
||||||
self._mode = self.im.mode
|
self._mode = self.im.mode
|
||||||
if frame_im.mode == "RGBA":
|
if frame_im.mode in ("LA", "RGBA"):
|
||||||
self.im.paste(frame_im, self.dispose_extent, frame_im)
|
self.im.paste(frame_im, self.dispose_extent, frame_im)
|
||||||
else:
|
else:
|
||||||
self.im.paste(frame_im, self.dispose_extent)
|
self.im.paste(frame_im, self.dispose_extent)
|
||||||
|
|
|
@ -25,7 +25,6 @@ import sys
|
||||||
from typing import IO
|
from typing import IO
|
||||||
|
|
||||||
from . import Image, ImageFile, PngImagePlugin, features
|
from . import Image, ImageFile, PngImagePlugin, features
|
||||||
from ._deprecate import deprecate
|
|
||||||
|
|
||||||
enable_jpeg2k = features.check_codec("jpg_2000")
|
enable_jpeg2k = features.check_codec("jpg_2000")
|
||||||
if enable_jpeg2k:
|
if enable_jpeg2k:
|
||||||
|
@ -275,34 +274,25 @@ class IcnsImageFile(ImageFile.ImageFile):
|
||||||
self.best_size[1] * self.best_size[2],
|
self.best_size[1] * self.best_size[2],
|
||||||
)
|
)
|
||||||
|
|
||||||
@property # type: ignore[override]
|
@property
|
||||||
def size(self) -> tuple[int, int] | tuple[int, int, int]:
|
def size(self) -> tuple[int, int]:
|
||||||
return self._size
|
return self._size
|
||||||
|
|
||||||
@size.setter
|
@size.setter
|
||||||
def size(self, value: tuple[int, int] | tuple[int, int, int]) -> None:
|
def size(self, value: tuple[int, int]) -> None:
|
||||||
if len(value) == 3:
|
# Check that a matching size exists,
|
||||||
deprecate("Setting size to (width, height, scale)", 12, "load(scale)")
|
# or that there is a scale that would create a size that matches
|
||||||
if value in self.info["sizes"]:
|
for size in self.info["sizes"]:
|
||||||
self._size = value # type: ignore[assignment]
|
simple_size = size[0] * size[2], size[1] * size[2]
|
||||||
|
scale = simple_size[0] // value[0]
|
||||||
|
if simple_size[1] / value[1] == scale:
|
||||||
|
self._size = value
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
# Check that a matching size exists,
|
|
||||||
# or that there is a scale that would create a size that matches
|
|
||||||
for size in self.info["sizes"]:
|
|
||||||
simple_size = size[0] * size[2], size[1] * size[2]
|
|
||||||
scale = simple_size[0] // value[0]
|
|
||||||
if simple_size[1] / value[1] == scale:
|
|
||||||
self._size = value
|
|
||||||
return
|
|
||||||
msg = "This is not one of the allowed sizes of this image"
|
msg = "This is not one of the allowed sizes of this image"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
def load(self, scale: int | None = None) -> Image.core.PixelAccess | None:
|
def load(self, scale: int | None = None) -> Image.core.PixelAccess | None:
|
||||||
if scale is not None or len(self.size) == 3:
|
if scale is not None:
|
||||||
if scale is None and len(self.size) == 3:
|
|
||||||
scale = self.size[2]
|
|
||||||
assert scale is not None
|
|
||||||
width, height = self.size[:2]
|
width, height = self.size[:2]
|
||||||
self.size = width * scale, height * scale
|
self.size = width * scale, height * scale
|
||||||
self.best_size = width, height, scale
|
self.best_size = width, height, scale
|
||||||
|
|
|
@ -115,21 +115,6 @@ except ImportError as v:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def isImageType(t: Any) -> TypeGuard[Image]:
|
|
||||||
"""
|
|
||||||
Checks if an object is an image object.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This function is for internal use only.
|
|
||||||
|
|
||||||
:param t: object to check if it's an image
|
|
||||||
:returns: True if the object is an image
|
|
||||||
"""
|
|
||||||
deprecate("Image.isImageType(im)", 12, "isinstance(im, Image.Image)")
|
|
||||||
return hasattr(t, "im")
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Constants
|
# Constants
|
||||||
|
|
||||||
|
@ -219,7 +204,7 @@ if TYPE_CHECKING:
|
||||||
from IPython.lib.pretty import PrettyPrinter
|
from IPython.lib.pretty import PrettyPrinter
|
||||||
|
|
||||||
from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin
|
from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin
|
||||||
from ._typing import CapsuleType, NumpyArray, StrOrBytesPath, TypeGuard
|
from ._typing import CapsuleType, NumpyArray, StrOrBytesPath
|
||||||
ID: list[str] = []
|
ID: list[str] = []
|
||||||
OPEN: dict[
|
OPEN: dict[
|
||||||
str,
|
str,
|
||||||
|
@ -980,9 +965,6 @@ class Image:
|
||||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if mode in ("BGR;15", "BGR;16", "BGR;24"):
|
|
||||||
deprecate(mode, 12)
|
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
has_transparency = "transparency" in self.info
|
has_transparency = "transparency" in self.info
|
||||||
|
@ -2229,8 +2211,6 @@ class Image:
|
||||||
:py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`,
|
:py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`,
|
||||||
:py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`.
|
:py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`.
|
||||||
If the image has mode "1" or "P", it is always set to
|
If the image has mode "1" or "P", it is always set to
|
||||||
:py:data:`Resampling.NEAREST`. If the image mode is "BGR;15",
|
|
||||||
"BGR;16" or "BGR;24", then the default filter is
|
|
||||||
:py:data:`Resampling.NEAREST`. Otherwise, the default filter is
|
:py:data:`Resampling.NEAREST`. Otherwise, the default filter is
|
||||||
:py:data:`Resampling.BICUBIC`. See: :ref:`concept-filters`.
|
:py:data:`Resampling.BICUBIC`. See: :ref:`concept-filters`.
|
||||||
:param box: An optional 4-tuple of floats providing
|
:param box: An optional 4-tuple of floats providing
|
||||||
|
@ -2253,8 +2233,7 @@ class Image:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if resample is None:
|
if resample is None:
|
||||||
bgr = self.mode.startswith("BGR;")
|
resample = Resampling.BICUBIC
|
||||||
resample = Resampling.NEAREST if bgr else Resampling.BICUBIC
|
|
||||||
elif resample not in (
|
elif resample not in (
|
||||||
Resampling.NEAREST,
|
Resampling.NEAREST,
|
||||||
Resampling.BILINEAR,
|
Resampling.BILINEAR,
|
||||||
|
@ -2556,8 +2535,9 @@ class Image:
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
save_all = params.pop("save_all", None)
|
save_all = params.pop("save_all", None)
|
||||||
|
self._default_encoderinfo = params
|
||||||
encoderinfo = getattr(self, "encoderinfo", {})
|
encoderinfo = getattr(self, "encoderinfo", {})
|
||||||
self.encoderinfo = {**encoderinfo, **params}
|
self._attach_default_encoderinfo(self)
|
||||||
self.encoderconfig: tuple[Any, ...] = ()
|
self.encoderconfig: tuple[Any, ...] = ()
|
||||||
|
|
||||||
if format.upper() not in SAVE:
|
if format.upper() not in SAVE:
|
||||||
|
@ -2599,6 +2579,11 @@ class Image:
|
||||||
if open_fp:
|
if open_fp:
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|
||||||
|
def _attach_default_encoderinfo(self, im: Image) -> dict[str, Any]:
|
||||||
|
encoderinfo = getattr(self, "encoderinfo", {})
|
||||||
|
self.encoderinfo = {**im._default_encoderinfo, **encoderinfo}
|
||||||
|
return encoderinfo
|
||||||
|
|
||||||
def _save_all_progress(
|
def _save_all_progress(
|
||||||
self,
|
self,
|
||||||
progress,
|
progress,
|
||||||
|
@ -3099,9 +3084,6 @@ def new(
|
||||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if mode in ("BGR;15", "BGR;16", "BGR;24"):
|
|
||||||
deprecate(mode, 12)
|
|
||||||
|
|
||||||
_check_size(size)
|
_check_size(size)
|
||||||
|
|
||||||
if color is None:
|
if color is None:
|
||||||
|
@ -3524,8 +3506,6 @@ def open(
|
||||||
filename: str | bytes = ""
|
filename: str | bytes = ""
|
||||||
if is_path(fp):
|
if is_path(fp):
|
||||||
filename = os.fspath(fp)
|
filename = os.fspath(fp)
|
||||||
|
|
||||||
if filename:
|
|
||||||
fp = builtins.open(filename, "rb")
|
fp = builtins.open(filename, "rb")
|
||||||
exclusive_fp = True
|
exclusive_fp = True
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -23,10 +23,9 @@ import operator
|
||||||
import sys
|
import sys
|
||||||
from enum import IntEnum, IntFlag
|
from enum import IntEnum, IntFlag
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from typing import Any, Literal, SupportsFloat, SupportsInt, Union
|
from typing import Literal, SupportsFloat, SupportsInt, Union
|
||||||
|
|
||||||
from . import Image, __version__
|
from . import Image
|
||||||
from ._deprecate import deprecate
|
|
||||||
from ._typing import SupportsRead
|
from ._typing import SupportsRead
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -108,20 +107,6 @@ pyCMS
|
||||||
_VERSION = "1.0.0 pil"
|
_VERSION = "1.0.0 pil"
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name: str) -> Any:
|
|
||||||
if name == "DESCRIPTION":
|
|
||||||
deprecate("PIL.ImageCms.DESCRIPTION", 12)
|
|
||||||
return _DESCRIPTION
|
|
||||||
elif name == "VERSION":
|
|
||||||
deprecate("PIL.ImageCms.VERSION", 12)
|
|
||||||
return _VERSION
|
|
||||||
elif name == "FLAGS":
|
|
||||||
deprecate("PIL.ImageCms.FLAGS", 12, "PIL.ImageCms.Flags")
|
|
||||||
return _FLAGS
|
|
||||||
msg = f"module '{__name__}' has no attribute '{name}'"
|
|
||||||
raise AttributeError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------.
|
# --------------------------------------------------------------------.
|
||||||
|
|
||||||
|
|
||||||
|
@ -301,31 +286,6 @@ class ImageCmsTransform(Image.ImagePointHandler):
|
||||||
proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC,
|
proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC,
|
||||||
flags: Flags = Flags.NONE,
|
flags: Flags = Flags.NONE,
|
||||||
):
|
):
|
||||||
supported_modes = (
|
|
||||||
"RGB",
|
|
||||||
"RGBA",
|
|
||||||
"RGBX",
|
|
||||||
"CMYK",
|
|
||||||
"I;16",
|
|
||||||
"I;16L",
|
|
||||||
"I;16B",
|
|
||||||
"YCbCr",
|
|
||||||
"LAB",
|
|
||||||
"L",
|
|
||||||
"1",
|
|
||||||
)
|
|
||||||
for mode in (input_mode, output_mode):
|
|
||||||
if mode not in supported_modes:
|
|
||||||
deprecate(
|
|
||||||
mode,
|
|
||||||
12,
|
|
||||||
{
|
|
||||||
"L;16": "I;16 or I;16L",
|
|
||||||
"L:16B": "I;16B",
|
|
||||||
"YCCA": "YCbCr",
|
|
||||||
"YCC": "YCbCr",
|
|
||||||
}.get(mode),
|
|
||||||
)
|
|
||||||
if proof is None:
|
if proof is None:
|
||||||
self.transform = core.buildTransform(
|
self.transform = core.buildTransform(
|
||||||
input.profile, output.profile, input_mode, output_mode, intent, flags
|
input.profile, output.profile, input_mode, output_mode, intent, flags
|
||||||
|
@ -1108,16 +1068,3 @@ def isIntentSupported(
|
||||||
return -1
|
return -1
|
||||||
except (AttributeError, OSError, TypeError, ValueError) as v:
|
except (AttributeError, OSError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v) from v
|
raise PyCMSError(v) from v
|
||||||
|
|
||||||
|
|
||||||
def versions() -> tuple[str, str | None, str, str]:
|
|
||||||
"""
|
|
||||||
(pyCMS) Fetches versions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
deprecate(
|
|
||||||
"PIL.ImageCms.versions()",
|
|
||||||
12,
|
|
||||||
'(PIL.features.version("littlecms2"), sys.version, PIL.__version__)',
|
|
||||||
)
|
|
||||||
return _VERSION, core.littlecms_version, sys.version.split()[0], __version__
|
|
||||||
|
|
|
@ -38,7 +38,6 @@ from types import ModuleType
|
||||||
from typing import Any, AnyStr, Callable, Union, cast
|
from typing import Any, AnyStr, Callable, Union, cast
|
||||||
|
|
||||||
from . import Image, ImageColor
|
from . import Image, ImageColor
|
||||||
from ._deprecate import deprecate
|
|
||||||
from ._typing import Coords
|
from ._typing import Coords
|
||||||
|
|
||||||
# experimental access to the outline API
|
# experimental access to the outline API
|
||||||
|
@ -1009,16 +1008,11 @@ def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
|
||||||
return ImageDraw(im, mode)
|
return ImageDraw(im, mode)
|
||||||
|
|
||||||
|
|
||||||
def getdraw(
|
def getdraw(im: Image.Image | None = None) -> tuple[ImageDraw2.Draw | None, ModuleType]:
|
||||||
im: Image.Image | None = None, hints: list[str] | None = None
|
|
||||||
) -> tuple[ImageDraw2.Draw | None, ModuleType]:
|
|
||||||
"""
|
"""
|
||||||
:param im: The image to draw in.
|
:param im: The image to draw in.
|
||||||
:param hints: An optional list of hints. Deprecated.
|
|
||||||
:returns: A (drawing context, drawing resource factory) tuple.
|
:returns: A (drawing context, drawing resource factory) tuple.
|
||||||
"""
|
"""
|
||||||
if hints is not None:
|
|
||||||
deprecate("'hints' parameter", 12)
|
|
||||||
from . import ImageDraw2
|
from . import ImageDraw2
|
||||||
|
|
||||||
draw = ImageDraw2.Draw(im) if im is not None else None
|
draw = ImageDraw2.Draw(im) if im is not None else None
|
||||||
|
|
|
@ -37,7 +37,6 @@ import struct
|
||||||
from typing import IO, Any, NamedTuple, cast
|
from typing import IO, Any, NamedTuple, cast
|
||||||
|
|
||||||
from . import ExifTags, Image
|
from . import ExifTags, Image
|
||||||
from ._deprecate import deprecate
|
|
||||||
from ._util import DeferredError, is_path
|
from ._util import DeferredError, is_path
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
|
@ -83,16 +82,6 @@ def _get_oserror(error: int, *, encoder: bool) -> OSError:
|
||||||
return OSError(msg)
|
return OSError(msg)
|
||||||
|
|
||||||
|
|
||||||
def raise_oserror(error: int) -> OSError:
|
|
||||||
deprecate(
|
|
||||||
"raise_oserror",
|
|
||||||
12,
|
|
||||||
action="It is only useful for translating error codes returned by a codec's "
|
|
||||||
"decode() method, which ImageFile already does automatically.",
|
|
||||||
)
|
|
||||||
raise _get_oserror(error, encoder=False)
|
|
||||||
|
|
||||||
|
|
||||||
def _tilesort(t: _Tile) -> int:
|
def _tilesort(t: _Tile) -> int:
|
||||||
# sort on offset
|
# sort on offset
|
||||||
return t[2]
|
return t[2]
|
||||||
|
|
|
@ -36,7 +36,7 @@ from io import BytesIO
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import IO, Any, BinaryIO, TypedDict, cast
|
from typing import IO, Any, BinaryIO, TypedDict, cast
|
||||||
|
|
||||||
from . import Image, features
|
from . import Image
|
||||||
from ._typing import StrOrBytesPath
|
from ._typing import StrOrBytesPath
|
||||||
from ._util import DeferredError, is_path
|
from ._util import DeferredError, is_path
|
||||||
|
|
||||||
|
@ -236,21 +236,6 @@ class FreeTypeFont:
|
||||||
self.index = index
|
self.index = index
|
||||||
self.encoding = encoding
|
self.encoding = encoding
|
||||||
|
|
||||||
try:
|
|
||||||
from packaging.version import parse as parse_version
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if freetype_version := features.version_module("freetype2"):
|
|
||||||
if parse_version(freetype_version) < parse_version("2.9.1"):
|
|
||||||
warnings.warn(
|
|
||||||
"Support for FreeType 2.9.0 is deprecated and will be removed "
|
|
||||||
"in Pillow 12 (2025-10-15). Please upgrade to FreeType 2.9.1 "
|
|
||||||
"or newer, preferably FreeType 2.10.4 which fixes "
|
|
||||||
"CVE-2020-15999.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
|
|
||||||
if layout_engine not in (Layout.BASIC, Layout.RAQM):
|
if layout_engine not in (Layout.BASIC, Layout.RAQM):
|
||||||
layout_engine = Layout.BASIC
|
layout_engine = Layout.BASIC
|
||||||
if core.HAVE_RAQM:
|
if core.HAVE_RAQM:
|
||||||
|
|
|
@ -21,7 +21,6 @@ from types import CodeType
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
from . import Image, _imagingmath
|
from . import Image, _imagingmath
|
||||||
from ._deprecate import deprecate
|
|
||||||
|
|
||||||
|
|
||||||
class _Operand:
|
class _Operand:
|
||||||
|
@ -233,11 +232,7 @@ ops = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def lambda_eval(
|
def lambda_eval(expression: Callable[[dict[str, Any]], Any], **kw: Any) -> Any:
|
||||||
expression: Callable[[dict[str, Any]], Any],
|
|
||||||
options: dict[str, Any] = {},
|
|
||||||
**kw: Any,
|
|
||||||
) -> Any:
|
|
||||||
"""
|
"""
|
||||||
Returns the result of an image function.
|
Returns the result of an image function.
|
||||||
|
|
||||||
|
@ -246,23 +241,13 @@ def lambda_eval(
|
||||||
:py:func:`~PIL.Image.merge` function.
|
:py:func:`~PIL.Image.merge` function.
|
||||||
|
|
||||||
:param expression: A function that receives a dictionary.
|
:param expression: A function that receives a dictionary.
|
||||||
:param options: Values to add to the function's dictionary. Deprecated.
|
|
||||||
You can instead use one or more keyword arguments.
|
|
||||||
:param **kw: Values to add to the function's dictionary.
|
:param **kw: Values to add to the function's dictionary.
|
||||||
:return: The expression result. This is usually an image object, but can
|
:return: The expression result. This is usually an image object, but can
|
||||||
also be an integer, a floating point value, or a pixel tuple,
|
also be an integer, a floating point value, or a pixel tuple,
|
||||||
depending on the expression.
|
depending on the expression.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if options:
|
|
||||||
deprecate(
|
|
||||||
"ImageMath.lambda_eval options",
|
|
||||||
12,
|
|
||||||
"ImageMath.lambda_eval keyword arguments",
|
|
||||||
)
|
|
||||||
|
|
||||||
args: dict[str, Any] = ops.copy()
|
args: dict[str, Any] = ops.copy()
|
||||||
args.update(options)
|
|
||||||
args.update(kw)
|
args.update(kw)
|
||||||
for k, v in args.items():
|
for k, v in args.items():
|
||||||
if isinstance(v, Image.Image):
|
if isinstance(v, Image.Image):
|
||||||
|
@ -275,11 +260,7 @@ def lambda_eval(
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def unsafe_eval(
|
def unsafe_eval(expression: str, **kw: Any) -> Any:
|
||||||
expression: str,
|
|
||||||
options: dict[str, Any] = {},
|
|
||||||
**kw: Any,
|
|
||||||
) -> Any:
|
|
||||||
"""
|
"""
|
||||||
Evaluates an image expression. This uses Python's ``eval()`` function to process
|
Evaluates an image expression. This uses Python's ``eval()`` function to process
|
||||||
the expression string, and carries the security risks of doing so. It is not
|
the expression string, and carries the security risks of doing so. It is not
|
||||||
|
@ -291,29 +272,19 @@ def unsafe_eval(
|
||||||
:py:func:`~PIL.Image.merge` function.
|
:py:func:`~PIL.Image.merge` function.
|
||||||
|
|
||||||
:param expression: A string containing a Python-style expression.
|
:param expression: A string containing a Python-style expression.
|
||||||
:param options: Values to add to the evaluation context. Deprecated.
|
|
||||||
You can instead use one or more keyword arguments.
|
|
||||||
:param **kw: Values to add to the evaluation context.
|
:param **kw: Values to add to the evaluation context.
|
||||||
:return: The evaluated expression. This is usually an image object, but can
|
:return: The evaluated expression. This is usually an image object, but can
|
||||||
also be an integer, a floating point value, or a pixel tuple,
|
also be an integer, a floating point value, or a pixel tuple,
|
||||||
depending on the expression.
|
depending on the expression.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if options:
|
|
||||||
deprecate(
|
|
||||||
"ImageMath.unsafe_eval options",
|
|
||||||
12,
|
|
||||||
"ImageMath.unsafe_eval keyword arguments",
|
|
||||||
)
|
|
||||||
|
|
||||||
# build execution namespace
|
# build execution namespace
|
||||||
args: dict[str, Any] = ops.copy()
|
args: dict[str, Any] = ops.copy()
|
||||||
for k in [*options, *kw]:
|
for k in kw:
|
||||||
if "__" in k or hasattr(builtins, k):
|
if "__" in k or hasattr(builtins, k):
|
||||||
msg = f"'{k}' not allowed"
|
msg = f"'{k}' not allowed"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
args.update(options)
|
|
||||||
args.update(kw)
|
args.update(kw)
|
||||||
for k, v in args.items():
|
for k, v in args.items():
|
||||||
if isinstance(v, Image.Image):
|
if isinstance(v, Image.Image):
|
||||||
|
@ -337,32 +308,3 @@ def unsafe_eval(
|
||||||
return out.im
|
return out.im
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def eval(
|
|
||||||
expression: str,
|
|
||||||
_dict: dict[str, Any] = {},
|
|
||||||
**kw: Any,
|
|
||||||
) -> Any:
|
|
||||||
"""
|
|
||||||
Evaluates an image expression.
|
|
||||||
|
|
||||||
Deprecated. Use lambda_eval() or unsafe_eval() instead.
|
|
||||||
|
|
||||||
:param expression: A string containing a Python-style expression.
|
|
||||||
:param _dict: Values to add to the evaluation context. You
|
|
||||||
can either use a dictionary, or one or more keyword
|
|
||||||
arguments.
|
|
||||||
:return: The evaluated expression. This is usually an image object, but can
|
|
||||||
also be an integer, a floating point value, or a pixel tuple,
|
|
||||||
depending on the expression.
|
|
||||||
|
|
||||||
.. deprecated:: 10.3.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
deprecate(
|
|
||||||
"ImageMath.eval",
|
|
||||||
12,
|
|
||||||
"ImageMath.lambda_eval or ImageMath.unsafe_eval",
|
|
||||||
)
|
|
||||||
return unsafe_eval(expression, _dict, **kw)
|
|
||||||
|
|
|
@ -18,8 +18,6 @@ import sys
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
|
|
||||||
from ._deprecate import deprecate
|
|
||||||
|
|
||||||
|
|
||||||
class ModeDescriptor(NamedTuple):
|
class ModeDescriptor(NamedTuple):
|
||||||
"""Wrapper for mode strings."""
|
"""Wrapper for mode strings."""
|
||||||
|
@ -57,16 +55,11 @@ def getmode(mode: str) -> ModeDescriptor:
|
||||||
"HSV": ("RGB", "L", ("H", "S", "V"), "|u1"),
|
"HSV": ("RGB", "L", ("H", "S", "V"), "|u1"),
|
||||||
# extra experimental modes
|
# extra experimental modes
|
||||||
"RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"),
|
"RGBa": ("RGB", "L", ("R", "G", "B", "a"), "|u1"),
|
||||||
"BGR;15": ("RGB", "L", ("B", "G", "R"), "|u1"),
|
|
||||||
"BGR;16": ("RGB", "L", ("B", "G", "R"), "|u1"),
|
|
||||||
"BGR;24": ("RGB", "L", ("B", "G", "R"), "|u1"),
|
|
||||||
"LA": ("L", "L", ("L", "A"), "|u1"),
|
"LA": ("L", "L", ("L", "A"), "|u1"),
|
||||||
"La": ("L", "L", ("L", "a"), "|u1"),
|
"La": ("L", "L", ("L", "a"), "|u1"),
|
||||||
"PA": ("RGB", "L", ("P", "A"), "|u1"),
|
"PA": ("RGB", "L", ("P", "A"), "|u1"),
|
||||||
}
|
}
|
||||||
if mode in modes:
|
if mode in modes:
|
||||||
if mode in ("BGR;15", "BGR;16", "BGR;24"):
|
|
||||||
deprecate(mode, 12)
|
|
||||||
base_mode, base_type, bands, type_str = modes[mode]
|
base_mode, base_type, bands, type_str = modes[mode]
|
||||||
return ModeDescriptor(mode, bands, base_mode, base_type, type_str)
|
return ModeDescriptor(mode, bands, base_mode, base_type, type_str)
|
||||||
|
|
||||||
|
|
|
@ -16,26 +16,16 @@
|
||||||
#
|
#
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
from ._deprecate import deprecate
|
|
||||||
|
|
||||||
COMPRESSION = {1: "raw", 5: "jpeg"}
|
COMPRESSION = {1: "raw", 5: "jpeg"}
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name: str) -> bytes:
|
|
||||||
if name == "PAD":
|
|
||||||
deprecate("IptcImagePlugin.PAD", 12)
|
|
||||||
return b"\0\0\0\0"
|
|
||||||
msg = f"module '{__name__}' has no attribute '{name}'"
|
|
||||||
raise AttributeError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Helpers
|
# Helpers
|
||||||
|
|
||||||
|
@ -48,20 +38,6 @@ def _i8(c: int | bytes) -> int:
|
||||||
return c if isinstance(c, int) else c[0]
|
return c if isinstance(c, int) else c[0]
|
||||||
|
|
||||||
|
|
||||||
def i(c: bytes) -> int:
|
|
||||||
""".. deprecated:: 10.2.0"""
|
|
||||||
deprecate("IptcImagePlugin.i", 12)
|
|
||||||
return _i(c)
|
|
||||||
|
|
||||||
|
|
||||||
def dump(c: Sequence[int | bytes]) -> None:
|
|
||||||
""".. deprecated:: 10.2.0"""
|
|
||||||
deprecate("IptcImagePlugin.dump", 12)
|
|
||||||
for i in c:
|
|
||||||
print(f"{_i8(i):02x}", end=" ")
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
|
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
|
||||||
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
|
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
|
||||||
|
@ -220,7 +196,7 @@ def getiptcinfo(
|
||||||
# get raw data from the IPTC/NAA tag (PhotoShop tags the data
|
# get raw data from the IPTC/NAA tag (PhotoShop tags the data
|
||||||
# as 4-byte integers, so we cannot use the get method...)
|
# as 4-byte integers, so we cannot use the get method...)
|
||||||
try:
|
try:
|
||||||
data = im.tag_v2[TiffImagePlugin.IPTC_NAA_CHUNK]
|
data = im.tag_v2._tagdata[TiffImagePlugin.IPTC_NAA_CHUNK]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,6 @@ from ._binary import i16be as i16
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from ._binary import o16be as o16
|
from ._binary import o16be as o16
|
||||||
from ._deprecate import deprecate
|
|
||||||
from .JpegPresets import presets
|
from .JpegPresets import presets
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
|
@ -393,12 +392,6 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self._read_dpi_from_exif()
|
self._read_dpi_from_exif()
|
||||||
|
|
||||||
def __getattr__(self, name: str) -> Any:
|
|
||||||
if name in ("huffman_ac", "huffman_dc"):
|
|
||||||
deprecate(name, 12)
|
|
||||||
return getattr(self, "_" + name)
|
|
||||||
raise AttributeError(name)
|
|
||||||
|
|
||||||
def __getstate__(self) -> list[Any]:
|
def __getstate__(self) -> list[Any]:
|
||||||
return super().__getstate__() + [self.layers, self.layer]
|
return super().__getstate__() + [self.layers, self.layer]
|
||||||
|
|
||||||
|
|
|
@ -50,9 +50,7 @@ class McIdasImageFile(ImageFile.ImageFile):
|
||||||
if w[11] == 1:
|
if w[11] == 1:
|
||||||
mode = rawmode = "L"
|
mode = rawmode = "L"
|
||||||
elif w[11] == 2:
|
elif w[11] == 2:
|
||||||
# FIXME: add memory map support
|
mode = rawmode = "I;16B"
|
||||||
mode = "I"
|
|
||||||
rawmode = "I;16B"
|
|
||||||
elif w[11] == 4:
|
elif w[11] == 4:
|
||||||
# FIXME: add memory map support
|
# FIXME: add memory map support
|
||||||
mode = "I"
|
mode = "I"
|
||||||
|
|
|
@ -73,7 +73,9 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
JpegImagePlugin._save(im_frame, fp, filename)
|
JpegImagePlugin._save(im_frame, fp, filename)
|
||||||
offsets.append(fp.tell())
|
offsets.append(fp.tell())
|
||||||
else:
|
else:
|
||||||
|
encoderinfo = im_frame._attach_default_encoderinfo(im)
|
||||||
im_frame.save(fp, "JPEG")
|
im_frame.save(fp, "JPEG")
|
||||||
|
im_frame.encoderinfo = encoderinfo
|
||||||
offsets.append(fp.tell() - offsets[-1])
|
offsets.append(fp.tell() - offsets[-1])
|
||||||
if progress:
|
if progress:
|
||||||
completed += 1
|
completed += 1
|
||||||
|
|
|
@ -56,7 +56,6 @@ from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from ._deprecate import deprecate
|
|
||||||
from ._typing import StrOrBytesPath
|
from ._typing import StrOrBytesPath
|
||||||
from ._util import DeferredError, is_path
|
from ._util import DeferredError, is_path
|
||||||
from .TiffTags import TYPES
|
from .TiffTags import TYPES
|
||||||
|
@ -284,9 +283,6 @@ PREFIXES = [
|
||||||
b"II\x2b\x00", # BigTIFF with little-endian byte order
|
b"II\x2b\x00", # BigTIFF with little-endian byte order
|
||||||
]
|
]
|
||||||
|
|
||||||
if not getattr(Image.core, "libtiff_support_custom_tags", True):
|
|
||||||
deprecate("Support for LibTIFF earlier than version 4", 12)
|
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
return prefix.startswith(tuple(PREFIXES))
|
return prefix.startswith(tuple(PREFIXES))
|
||||||
|
@ -1934,9 +1930,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
# Custom items are supported for int, float, unicode, string and byte
|
# Custom items are supported for int, float, unicode, string and byte
|
||||||
# values. Other types and tuples require a tagtype.
|
# values. Other types and tuples require a tagtype.
|
||||||
if tag not in TiffTags.LIBTIFF_CORE:
|
if tag not in TiffTags.LIBTIFF_CORE:
|
||||||
if not getattr(Image.core, "libtiff_support_custom_tags", False):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if tag in TiffTags.TAGS_V2_GROUPS:
|
if tag in TiffTags.TAGS_V2_GROUPS:
|
||||||
types[tag] = TiffTags.LONG8
|
types[tag] = TiffTags.LONG8
|
||||||
elif tag in ifd.tagtype:
|
elif tag in ifd.tagtype:
|
||||||
|
@ -2318,8 +2311,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
try:
|
try:
|
||||||
with AppendingTiffWriter(fp) as tf:
|
with AppendingTiffWriter(fp) as tf:
|
||||||
for i, seq in enumerate(im_sequences):
|
for i, seq in enumerate(im_sequences):
|
||||||
if not hasattr(seq, "encoderinfo"):
|
encoderinfo = seq._attach_default_encoderinfo(im)
|
||||||
seq.encoderinfo = {}
|
|
||||||
if not hasattr(seq, "encoderconfig"):
|
if not hasattr(seq, "encoderconfig"):
|
||||||
seq.encoderconfig = ()
|
seq.encoderconfig = ()
|
||||||
nfr = getattr(seq, "n_frames", 1)
|
nfr = getattr(seq, "n_frames", 1)
|
||||||
|
@ -2333,6 +2325,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
im._save_all_progress(progress, seq, i, completed, total)
|
im._save_all_progress(progress, seq, i, completed, total)
|
||||||
|
|
||||||
tf.newFrame()
|
tf.newFrame()
|
||||||
|
seq.encoderinfo = encoderinfo
|
||||||
finally:
|
finally:
|
||||||
im.seek(cur_idx)
|
im.seek(cur_idx)
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,6 @@ def deprecate(
|
||||||
elif when <= int(__version__.split(".")[0]):
|
elif when <= int(__version__.split(".")[0]):
|
||||||
msg = f"{deprecated} {is_} deprecated and should be removed."
|
msg = f"{deprecated} {is_} deprecated and should be removed."
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
elif when == 12:
|
|
||||||
removed = "Pillow 12 (2025-10-15)"
|
|
||||||
elif when == 13:
|
elif when == 13:
|
||||||
removed = "Pillow 13 (2026-10-15)"
|
removed = "Pillow 13 (2026-10-15)"
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Master version for Pillow
|
# Master version for Pillow
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
__version__ = "11.3.0.dev0"
|
__version__ = "12.0.0.dev0"
|
||||||
|
|
|
@ -121,9 +121,6 @@ def get_supported_codecs() -> list[str]:
|
||||||
|
|
||||||
|
|
||||||
features: dict[str, tuple[str, str | bool, str | None]] = {
|
features: dict[str, tuple[str, str | bool, str | None]] = {
|
||||||
"webp_anim": ("PIL._webp", True, None),
|
|
||||||
"webp_mux": ("PIL._webp", True, None),
|
|
||||||
"transp_webp": ("PIL._webp", True, None),
|
|
||||||
"raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
|
"raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
|
||||||
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
|
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
|
||||||
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
|
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
|
||||||
|
|
136
src/_imaging.c
136
src/_imaging.c
|
@ -681,30 +681,6 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
} else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
|
} else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (!strcmp(im->mode, "BGR;15")) {
|
|
||||||
UINT16 v = ((((UINT16)r) << 7) & 0x7c00) +
|
|
||||||
((((UINT16)g) << 2) & 0x03e0) +
|
|
||||||
((((UINT16)b) >> 3) & 0x001f);
|
|
||||||
|
|
||||||
ink[0] = (UINT8)v;
|
|
||||||
ink[1] = (UINT8)(v >> 8);
|
|
||||||
ink[2] = ink[3] = 0;
|
|
||||||
return ink;
|
|
||||||
} else if (!strcmp(im->mode, "BGR;16")) {
|
|
||||||
UINT16 v = ((((UINT16)r) << 8) & 0xf800) +
|
|
||||||
((((UINT16)g) << 3) & 0x07e0) +
|
|
||||||
((((UINT16)b) >> 3) & 0x001f);
|
|
||||||
ink[0] = (UINT8)v;
|
|
||||||
ink[1] = (UINT8)(v >> 8);
|
|
||||||
ink[2] = ink[3] = 0;
|
|
||||||
return ink;
|
|
||||||
} else if (!strcmp(im->mode, "BGR;24")) {
|
|
||||||
ink[0] = (UINT8)b;
|
|
||||||
ink[1] = (UINT8)g;
|
|
||||||
ink[2] = (UINT8)r;
|
|
||||||
ink[3] = 0;
|
|
||||||
return ink;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1650,54 +1626,33 @@ _putdata(ImagingObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
double value;
|
double value;
|
||||||
if (image->bands == 1) {
|
int bigendian = 0;
|
||||||
int bigendian = 0;
|
if (image->type == IMAGING_TYPE_SPECIAL) {
|
||||||
if (image->type == IMAGING_TYPE_SPECIAL) {
|
// I;16*
|
||||||
// I;16*
|
if (
|
||||||
if (
|
strcmp(image->mode, "I;16B") == 0
|
||||||
strcmp(image->mode, "I;16B") == 0
|
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(image->mode, "I;16N") == 0
|
|| strcmp(image->mode, "I;16N") == 0
|
||||||
#endif
|
#endif
|
||||||
) {
|
) {
|
||||||
bigendian = 1;
|
bigendian = 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (i = x = y = 0; i < n; i++) {
|
}
|
||||||
set_value_to_item(seq, i);
|
for (i = x = y = 0; i < n; i++) {
|
||||||
if (scale != 1.0 || offset != 0.0) {
|
set_value_to_item(seq, i);
|
||||||
value = value * scale + offset;
|
if (scale != 1.0 || offset != 0.0) {
|
||||||
}
|
value = value * scale + offset;
|
||||||
if (image->type == IMAGING_TYPE_SPECIAL) {
|
|
||||||
image->image8[y][x * 2 + (bigendian ? 1 : 0)] =
|
|
||||||
CLIP8((int)value % 256);
|
|
||||||
image->image8[y][x * 2 + (bigendian ? 0 : 1)] =
|
|
||||||
CLIP8((int)value >> 8);
|
|
||||||
} else {
|
|
||||||
image->image8[y][x] = (UINT8)CLIP8(value);
|
|
||||||
}
|
|
||||||
if (++x >= (int)image->xsize) {
|
|
||||||
x = 0, y++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
if (image->type == IMAGING_TYPE_SPECIAL) {
|
||||||
// BGR;*
|
image->image8[y][x * 2 + (bigendian ? 1 : 0)] =
|
||||||
int b;
|
CLIP8((int)value % 256);
|
||||||
for (i = x = y = 0; i < n; i++) {
|
image->image8[y][x * 2 + (bigendian ? 0 : 1)] =
|
||||||
char ink[4];
|
CLIP8((int)value >> 8);
|
||||||
|
} else {
|
||||||
op = PySequence_Fast_GET_ITEM(seq, i);
|
image->image8[y][x] = (UINT8)CLIP8(value);
|
||||||
if (!op || !getink(op, image, ink)) {
|
}
|
||||||
Py_DECREF(seq);
|
if (++x >= (int)image->xsize) {
|
||||||
return NULL;
|
x = 0, y++;
|
||||||
}
|
|
||||||
/* FIXME: what about scale and offset? */
|
|
||||||
for (b = 0; b < image->pixelsize; b++) {
|
|
||||||
image->image8[y][x * image->pixelsize + b] = ink[b];
|
|
||||||
}
|
|
||||||
if (++x >= (int)image->xsize) {
|
|
||||||
x = 0, y++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PyErr_Clear(); /* Avoid weird exceptions */
|
PyErr_Clear(); /* Avoid weird exceptions */
|
||||||
|
@ -3769,18 +3724,6 @@ _getattr_bands(ImagingObject *self, void *closure) {
|
||||||
return PyLong_FromLong(self->image->bands);
|
return PyLong_FromLong(self->image->bands);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
_getattr_id(ImagingObject *self, void *closure) {
|
|
||||||
if (PyErr_WarnEx(
|
|
||||||
PyExc_DeprecationWarning,
|
|
||||||
"id property is deprecated and will be removed in Pillow 12 (2025-10-15)",
|
|
||||||
1
|
|
||||||
) < 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return PyLong_FromSsize_t((Py_ssize_t)self->image);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
_ptr_destructor(PyObject *capsule) {
|
_ptr_destructor(PyObject *capsule) {
|
||||||
PyObject *self = (PyObject *)PyCapsule_GetContext(capsule);
|
PyObject *self = (PyObject *)PyCapsule_GetContext(capsule);
|
||||||
|
@ -3795,27 +3738,6 @@ _getattr_ptr(ImagingObject *self, void *closure) {
|
||||||
return capsule;
|
return capsule;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
_getattr_unsafe_ptrs(ImagingObject *self, void *closure) {
|
|
||||||
if (PyErr_WarnEx(
|
|
||||||
PyExc_DeprecationWarning,
|
|
||||||
"unsafe_ptrs property is deprecated and will be removed in Pillow 12 "
|
|
||||||
"(2025-10-15)",
|
|
||||||
1
|
|
||||||
) < 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return Py_BuildValue(
|
|
||||||
"(sn)(sn)(sn)",
|
|
||||||
"image8",
|
|
||||||
self->image->image8,
|
|
||||||
"image32",
|
|
||||||
self->image->image32,
|
|
||||||
"image",
|
|
||||||
self->image->image
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_getattr_readonly(ImagingObject *self, void *closure) {
|
_getattr_readonly(ImagingObject *self, void *closure) {
|
||||||
return PyLong_FromLong(self->image->read_only);
|
return PyLong_FromLong(self->image->read_only);
|
||||||
|
@ -3825,9 +3747,7 @@ static struct PyGetSetDef getsetters[] = {
|
||||||
{"mode", (getter)_getattr_mode},
|
{"mode", (getter)_getattr_mode},
|
||||||
{"size", (getter)_getattr_size},
|
{"size", (getter)_getattr_size},
|
||||||
{"bands", (getter)_getattr_bands},
|
{"bands", (getter)_getattr_bands},
|
||||||
{"id", (getter)_getattr_id},
|
|
||||||
{"ptr", (getter)_getattr_ptr},
|
{"ptr", (getter)_getattr_ptr},
|
||||||
{"unsafe_ptrs", (getter)_getattr_unsafe_ptrs},
|
|
||||||
{"readonly", (getter)_getattr_readonly},
|
{"readonly", (getter)_getattr_readonly},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
@ -4432,16 +4352,6 @@ setup_module(PyObject *m) {
|
||||||
PyObject *v = PyUnicode_FromString(ImagingTiffVersion());
|
PyObject *v = PyUnicode_FromString(ImagingTiffVersion());
|
||||||
PyDict_SetItemString(d, "libtiff_version", v ? v : Py_None);
|
PyDict_SetItemString(d, "libtiff_version", v ? v : Py_None);
|
||||||
Py_XDECREF(v);
|
Py_XDECREF(v);
|
||||||
|
|
||||||
// Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7
|
|
||||||
PyObject *support_custom_tags;
|
|
||||||
#if TIFFLIB_VERSION >= 20111221 && TIFFLIB_VERSION != 20120218 && \
|
|
||||||
TIFFLIB_VERSION != 20120922
|
|
||||||
support_custom_tags = Py_True;
|
|
||||||
#else
|
|
||||||
support_custom_tags = Py_False;
|
|
||||||
#endif
|
|
||||||
PyDict_SetItemString(d, "libtiff_support_custom_tags", support_custom_tags);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -82,31 +82,6 @@ get_pixel_16B(Imaging im, int x, int y, void *color) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
get_pixel_BGR15(Imaging im, int x, int y, void *color) {
|
|
||||||
UINT8 *in = (UINT8 *)&im->image8[y][x * 2];
|
|
||||||
UINT16 pixel = in[0] + (in[1] << 8);
|
|
||||||
char *out = color;
|
|
||||||
out[0] = (pixel & 31) * 255 / 31;
|
|
||||||
out[1] = ((pixel >> 5) & 31) * 255 / 31;
|
|
||||||
out[2] = ((pixel >> 10) & 31) * 255 / 31;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
get_pixel_BGR16(Imaging im, int x, int y, void *color) {
|
|
||||||
UINT8 *in = (UINT8 *)&im->image8[y][x * 2];
|
|
||||||
UINT16 pixel = in[0] + (in[1] << 8);
|
|
||||||
char *out = color;
|
|
||||||
out[0] = (pixel & 31) * 255 / 31;
|
|
||||||
out[1] = ((pixel >> 5) & 63) * 255 / 63;
|
|
||||||
out[2] = ((pixel >> 11) & 31) * 255 / 31;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
get_pixel_BGR24(Imaging im, int x, int y, void *color) {
|
|
||||||
memcpy(color, &im->image8[y][x * 3], sizeof(UINT8) * 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
get_pixel_32(Imaging im, int x, int y, void *color) {
|
get_pixel_32(Imaging im, int x, int y, void *color) {
|
||||||
memcpy(color, &im->image32[y][x], sizeof(INT32));
|
memcpy(color, &im->image32[y][x], sizeof(INT32));
|
||||||
|
@ -154,16 +129,6 @@ put_pixel_16B(Imaging im, int x, int y, const void *color) {
|
||||||
out[1] = in[0];
|
out[1] = in[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
put_pixel_BGR1516(Imaging im, int x, int y, const void *color) {
|
|
||||||
memcpy(&im->image8[y][x * 2], color, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
put_pixel_BGR24(Imaging im, int x, int y, const void *color) {
|
|
||||||
memcpy(&im->image8[y][x * 3], color, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
put_pixel_32L(Imaging im, int x, int y, const void *color) {
|
put_pixel_32L(Imaging im, int x, int y, const void *color) {
|
||||||
memcpy(&im->image8[y][x * 4], color, 4);
|
memcpy(&im->image8[y][x * 4], color, 4);
|
||||||
|
@ -212,9 +177,6 @@ ImagingAccessInit(void) {
|
||||||
ADD("F", get_pixel_32, put_pixel_32);
|
ADD("F", get_pixel_32, put_pixel_32);
|
||||||
ADD("P", get_pixel_8, put_pixel_8);
|
ADD("P", get_pixel_8, put_pixel_8);
|
||||||
ADD("PA", get_pixel_32_2bands, put_pixel_32);
|
ADD("PA", get_pixel_32_2bands, put_pixel_32);
|
||||||
ADD("BGR;15", get_pixel_BGR15, put_pixel_BGR1516);
|
|
||||||
ADD("BGR;16", get_pixel_BGR16, put_pixel_BGR1516);
|
|
||||||
ADD("BGR;24", get_pixel_BGR24, put_pixel_BGR24);
|
|
||||||
ADD("RGB", get_pixel_32, 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("RGBa", get_pixel_32, put_pixel_32);
|
ADD("RGBa", get_pixel_32, put_pixel_32);
|
||||||
|
|
|
@ -277,38 +277,6 @@ rgb2f(UINT8 *out_, const UINT8 *in, int xsize) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
rgb2bgr15(UINT8 *out_, const UINT8 *in, int xsize) {
|
|
||||||
int x;
|
|
||||||
for (x = 0; x < xsize; x++, in += 4, out_ += 2) {
|
|
||||||
UINT16 v = ((((UINT16)in[0]) << 7) & 0x7c00) +
|
|
||||||
((((UINT16)in[1]) << 2) & 0x03e0) +
|
|
||||||
((((UINT16)in[2]) >> 3) & 0x001f);
|
|
||||||
memcpy(out_, &v, sizeof(v));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
rgb2bgr16(UINT8 *out_, const UINT8 *in, int xsize) {
|
|
||||||
int x;
|
|
||||||
for (x = 0; x < xsize; x++, in += 4, out_ += 2) {
|
|
||||||
UINT16 v = ((((UINT16)in[0]) << 8) & 0xf800) +
|
|
||||||
((((UINT16)in[1]) << 3) & 0x07e0) +
|
|
||||||
((((UINT16)in[2]) >> 3) & 0x001f);
|
|
||||||
memcpy(out_, &v, sizeof(v));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
rgb2bgr24(UINT8 *out, const UINT8 *in, int xsize) {
|
|
||||||
int x;
|
|
||||||
for (x = 0; x < xsize; x++, in += 4) {
|
|
||||||
*out++ = in[2];
|
|
||||||
*out++ = in[1];
|
|
||||||
*out++ = in[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
rgb2hsv_row(UINT8 *out, const UINT8 *in) { // following colorsys.py
|
rgb2hsv_row(UINT8 *out, const UINT8 *in) { // following colorsys.py
|
||||||
float h, s, rc, gc, bc, cr;
|
float h, s, rc, gc, bc, cr;
|
||||||
|
@ -971,9 +939,6 @@ static struct {
|
||||||
{"RGB", "I;16N", rgb2i16l},
|
{"RGB", "I;16N", rgb2i16l},
|
||||||
#endif
|
#endif
|
||||||
{"RGB", "F", rgb2f},
|
{"RGB", "F", rgb2f},
|
||||||
{"RGB", "BGR;15", rgb2bgr15},
|
|
||||||
{"RGB", "BGR;16", rgb2bgr16},
|
|
||||||
{"RGB", "BGR;24", rgb2bgr24},
|
|
||||||
{"RGB", "RGBA", rgb2rgba},
|
{"RGB", "RGBA", rgb2rgba},
|
||||||
{"RGB", "RGBa", rgb2rgba},
|
{"RGB", "RGBa", rgb2rgba},
|
||||||
{"RGB", "RGBX", rgb2rgba},
|
{"RGB", "RGBX", rgb2rgba},
|
||||||
|
|
|
@ -175,18 +175,21 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
/* Use custom quantization tables */
|
/* Use custom quantization tables */
|
||||||
if (context->qtables) {
|
if (context->qtables) {
|
||||||
int i;
|
int i;
|
||||||
int quality = 100;
|
int quality = 50;
|
||||||
int last_q = 0;
|
int last_q = 0;
|
||||||
|
boolean force_baseline = FALSE;
|
||||||
if (context->quality != -1) {
|
if (context->quality != -1) {
|
||||||
quality = context->quality;
|
quality = context->quality;
|
||||||
|
force_baseline = TRUE;
|
||||||
}
|
}
|
||||||
|
int scale_factor = jpeg_quality_scaling(quality);
|
||||||
for (i = 0; i < context->qtablesLen; i++) {
|
for (i = 0; i < context->qtablesLen; i++) {
|
||||||
jpeg_add_quant_table(
|
jpeg_add_quant_table(
|
||||||
&context->cinfo,
|
&context->cinfo,
|
||||||
i,
|
i,
|
||||||
&context->qtables[i * DCTSIZE2],
|
&context->qtables[i * DCTSIZE2],
|
||||||
quality,
|
scale_factor,
|
||||||
FALSE
|
force_baseline
|
||||||
);
|
);
|
||||||
context->cinfo.comp_info[i].quant_tbl_no = i;
|
context->cinfo.comp_info[i].quant_tbl_no = i;
|
||||||
last_q = i;
|
last_q = i;
|
||||||
|
@ -195,7 +198,11 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
// jpeg_set_defaults created two qtables internally, but we only
|
// jpeg_set_defaults created two qtables internally, but we only
|
||||||
// wanted one.
|
// wanted one.
|
||||||
jpeg_add_quant_table(
|
jpeg_add_quant_table(
|
||||||
&context->cinfo, 1, &context->qtables[0], quality, FALSE
|
&context->cinfo,
|
||||||
|
1,
|
||||||
|
&context->qtables[0],
|
||||||
|
scale_factor,
|
||||||
|
force_baseline
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (i = last_q; i < context->cinfo.num_components; i++) {
|
for (i = last_q; i < context->cinfo.num_components; i++) {
|
||||||
|
|
|
@ -471,12 +471,6 @@ copy2(UINT8 *out, const UINT8 *in, int pixels) {
|
||||||
memcpy(out, in, pixels * 2);
|
memcpy(out, in, pixels * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
copy3(UINT8 *out, const UINT8 *in, int pixels) {
|
|
||||||
/* BGR;24, etc */
|
|
||||||
memcpy(out, in, pixels * 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
copy4(UINT8 *out, const UINT8 *in, int pixels) {
|
copy4(UINT8 *out, const UINT8 *in, int pixels) {
|
||||||
/* RGBA, CMYK quadruples */
|
/* RGBA, CMYK quadruples */
|
||||||
|
@ -657,9 +651,6 @@ static struct {
|
||||||
{"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian.
|
{"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian.
|
||||||
{"I;16L", "I;16N", 16, packI16N_I16},
|
{"I;16L", "I;16N", 16, packI16N_I16},
|
||||||
{"I;16B", "I;16N", 16, packI16N_I16B},
|
{"I;16B", "I;16N", 16, packI16N_I16B},
|
||||||
{"BGR;15", "BGR;15", 16, copy2},
|
|
||||||
{"BGR;16", "BGR;16", 16, copy2},
|
|
||||||
{"BGR;24", "BGR;24", 24, copy3},
|
|
||||||
|
|
||||||
{NULL} /* sentinel */
|
{NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
|
@ -151,36 +151,6 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
|
||||||
strcpy(im->band_names[2], "B");
|
strcpy(im->band_names[2], "B");
|
||||||
strcpy(im->band_names[3], "X");
|
strcpy(im->band_names[3], "X");
|
||||||
|
|
||||||
} else if (strcmp(mode, "BGR;15") == 0) {
|
|
||||||
/* EXPERIMENTAL */
|
|
||||||
/* 15-bit reversed true colour */
|
|
||||||
im->bands = 3;
|
|
||||||
im->pixelsize = 2;
|
|
||||||
im->linesize = (xsize * 2 + 3) & -4;
|
|
||||||
im->type = IMAGING_TYPE_SPECIAL;
|
|
||||||
/* not allowing arrow due to line length packing */
|
|
||||||
strcpy(im->arrow_band_format, "");
|
|
||||||
|
|
||||||
} else if (strcmp(mode, "BGR;16") == 0) {
|
|
||||||
/* EXPERIMENTAL */
|
|
||||||
/* 16-bit reversed true colour */
|
|
||||||
im->bands = 3;
|
|
||||||
im->pixelsize = 2;
|
|
||||||
im->linesize = (xsize * 2 + 3) & -4;
|
|
||||||
im->type = IMAGING_TYPE_SPECIAL;
|
|
||||||
/* not allowing arrow due to line length packing */
|
|
||||||
strcpy(im->arrow_band_format, "");
|
|
||||||
|
|
||||||
} else if (strcmp(mode, "BGR;24") == 0) {
|
|
||||||
/* EXPERIMENTAL */
|
|
||||||
/* 24-bit reversed true colour */
|
|
||||||
im->bands = 3;
|
|
||||||
im->pixelsize = 3;
|
|
||||||
im->linesize = (xsize * 3 + 3) & -4;
|
|
||||||
im->type = IMAGING_TYPE_SPECIAL;
|
|
||||||
/* not allowing arrow due to line length packing */
|
|
||||||
strcpy(im->arrow_band_format, "");
|
|
||||||
|
|
||||||
} else if (strcmp(mode, "RGBX") == 0) {
|
} else if (strcmp(mode, "RGBX") == 0) {
|
||||||
/* 32-bit true colour images with padding */
|
/* 32-bit true colour images with padding */
|
||||||
im->bands = im->pixelsize = 4;
|
im->bands = im->pixelsize = 4;
|
||||||
|
|
|
@ -884,7 +884,6 @@ ImagingLibTiffMergeFieldInfo(
|
||||||
// Refer to libtiff docs (http://www.simplesystems.org/libtiff/addingtags.html)
|
// Refer to libtiff docs (http://www.simplesystems.org/libtiff/addingtags.html)
|
||||||
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
|
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
|
||||||
uint32_t n;
|
uint32_t n;
|
||||||
int status = 0;
|
|
||||||
|
|
||||||
// custom fields added with ImagingLibTiffMergeFieldInfo are only used for
|
// custom fields added with ImagingLibTiffMergeFieldInfo are only used for
|
||||||
// decoding, ignore readcount;
|
// decoding, ignore readcount;
|
||||||
|
@ -907,14 +906,7 @@ ImagingLibTiffMergeFieldInfo(
|
||||||
|
|
||||||
n = sizeof(info) / sizeof(info[0]);
|
n = sizeof(info) / sizeof(info[0]);
|
||||||
|
|
||||||
// Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7
|
return TIFFMergeFieldInfo(clientstate->tiff, info, n);
|
||||||
#if TIFFLIB_VERSION >= 20111221 && TIFFLIB_VERSION != 20120218 && \
|
|
||||||
TIFFLIB_VERSION != 20120922
|
|
||||||
status = TIFFMergeFieldInfo(clientstate->tiff, info, n);
|
|
||||||
#else
|
|
||||||
TIFFMergeFieldInfo(clientstate->tiff, info, n);
|
|
||||||
#endif
|
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|
|
@ -1284,12 +1284,6 @@ copy2(UINT8 *out, const UINT8 *in, int pixels) {
|
||||||
memcpy(out, in, pixels * 2);
|
memcpy(out, in, pixels * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
copy3(UINT8 *out, const UINT8 *in, int pixels) {
|
|
||||||
/* BGR;24 */
|
|
||||||
memcpy(out, in, pixels * 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
copy4(UINT8 *out, const UINT8 *in, int pixels) {
|
copy4(UINT8 *out, const UINT8 *in, int pixels) {
|
||||||
/* RGBA, CMYK quadruples */
|
/* RGBA, CMYK quadruples */
|
||||||
|
@ -1649,10 +1643,6 @@ static struct {
|
||||||
{"RGB", "B;16B", 16, band216B},
|
{"RGB", "B;16B", 16, band216B},
|
||||||
{"RGB", "CMYK", 32, cmyk2rgb},
|
{"RGB", "CMYK", 32, cmyk2rgb},
|
||||||
|
|
||||||
{"BGR;15", "BGR;15", 16, copy2},
|
|
||||||
{"BGR;16", "BGR;16", 16, copy2},
|
|
||||||
{"BGR;24", "BGR;24", 24, copy3},
|
|
||||||
|
|
||||||
/* true colour w. alpha */
|
/* true colour w. alpha */
|
||||||
{"RGBA", "LA", 16, unpackRGBALA},
|
{"RGBA", "LA", 16, unpackRGBALA},
|
||||||
{"RGBA", "LA;16B", 32, unpackRGBALA16B},
|
{"RGBA", "LA;16B", 32, unpackRGBALA16B},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user