mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-02 19:40:10 +03:00
Compare commits
No commits in common. "main" and "11.3.0" have entirely different histories.
|
@ -13,21 +13,24 @@ aptget_update()
|
|||
return 1
|
||||
fi
|
||||
}
|
||||
aptget_update || aptget_update retry || aptget_update retry
|
||||
if [[ $(uname) != CYGWIN* ]]; then
|
||||
aptget_update || aptget_update retry || aptget_update retry
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev libtiff-dev python3-tk\
|
||||
ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
|
||||
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
||||
sway wl-clipboard libopenblas-dev nasm
|
||||
if [[ $(uname) != CYGWIN* ]]; then
|
||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev libtiff-dev python3-tk\
|
||||
ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
|
||||
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
||||
sway wl-clipboard libopenblas-dev nasm
|
||||
fi
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade wheel
|
||||
python3 -m pip install coverage
|
||||
python3 -m pip install defusedxml
|
||||
python3 -m pip install ipython
|
||||
python3 -m pip install numpy
|
||||
python3 -m pip install olefile
|
||||
python3 -m pip install -U pytest
|
||||
python3 -m pip install -U pytest-cov
|
||||
|
@ -37,24 +40,36 @@ python3 -m pip install pyroma
|
|||
# fails on beta 3.14 and PyPy
|
||||
python3 -m pip install --only-binary=:all: pyarrow || true
|
||||
|
||||
# PyQt6 doesn't support PyPy3
|
||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
||||
# TODO Update condition when pyqt6 supports free-threading
|
||||
if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi
|
||||
if [[ $(uname) != CYGWIN* ]]; then
|
||||
python3 -m pip install numpy
|
||||
|
||||
# PyQt6 doesn't support PyPy3
|
||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
||||
# TODO Update condition when pyqt6 supports free-threading
|
||||
if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi
|
||||
fi
|
||||
|
||||
# Pyroma uses non-isolated build and fails with old setuptools
|
||||
if [[ $GHA_PYTHON_VERSION == 3.9 ]]; then
|
||||
# To match pyproject.toml
|
||||
python3 -m pip install "setuptools>=77"
|
||||
fi
|
||||
|
||||
# webp
|
||||
pushd depends && ./install_webp.sh && popd
|
||||
|
||||
# libimagequant
|
||||
pushd depends && ./install_imagequant.sh && popd
|
||||
|
||||
# raqm
|
||||
pushd depends && ./install_raqm.sh && popd
|
||||
|
||||
# libavif
|
||||
pushd depends && ./install_libavif.sh && popd
|
||||
|
||||
# extra test images
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
else
|
||||
cd depends && ./install_extra_test_images.sh && cd ..
|
||||
fi
|
||||
|
||||
# webp
|
||||
pushd depends && ./install_webp.sh && popd
|
||||
|
||||
# libimagequant
|
||||
pushd depends && ./install_imagequant.sh && popd
|
||||
|
||||
# raqm
|
||||
pushd depends && ./install_raqm.sh && popd
|
||||
|
||||
# libavif
|
||||
pushd depends && ./install_libavif.sh && popd
|
||||
|
||||
# extra test images
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
|
|
@ -1 +1 @@
|
|||
cibuildwheel==3.1.2
|
||||
cibuildwheel==3.0.0
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
mypy==1.17.0
|
||||
mypy==1.16.1
|
||||
IceSpringPySideStubs-PyQt6
|
||||
IceSpringPySideStubs-PySide6
|
||||
ipython
|
||||
numpy
|
||||
packaging
|
||||
pyarrow-stubs
|
||||
pybind11
|
||||
pytest
|
||||
sphinx
|
||||
types-atheris
|
||||
|
|
1
.github/mergify.yml
vendored
1
.github/mergify.yml
vendored
|
@ -8,6 +8,7 @@ pull_request_rules:
|
|||
- status-success=Docker Test Successful
|
||||
- status-success=Windows Test Successful
|
||||
- status-success=MinGW
|
||||
- status-success=Cygwin Test Successful
|
||||
actions:
|
||||
merge:
|
||||
method: merge
|
||||
|
|
154
.github/workflows/test-cygwin.yml
vendored
Normal file
154
.github/workflows/test-cygwin.yml
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
name: Test Cygwin
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
COVERAGE_CORE: sysmon
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-minor-version: [9]
|
||||
|
||||
timeout-minutes: 40
|
||||
|
||||
name: Python 3.${{ matrix.python-minor-version }}
|
||||
|
||||
steps:
|
||||
- name: Fix line endings
|
||||
run: |
|
||||
git config --global core.autocrlf input
|
||||
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Cygwin
|
||||
uses: cygwin/cygwin-install-action@v5
|
||||
with:
|
||||
packages: >
|
||||
gcc-g++
|
||||
ghostscript
|
||||
git
|
||||
ImageMagick
|
||||
jpeg
|
||||
libfreetype-devel
|
||||
libimagequant-devel
|
||||
libjpeg-devel
|
||||
liblapack-devel
|
||||
liblcms2-devel
|
||||
libopenjp2-devel
|
||||
libraqm-devel
|
||||
libtiff-devel
|
||||
libwebp-devel
|
||||
libxcb-devel
|
||||
libxcb-xinerama0
|
||||
make
|
||||
netpbm
|
||||
perl
|
||||
python3${{ matrix.python-minor-version }}-cython
|
||||
python3${{ matrix.python-minor-version }}-devel
|
||||
python3${{ matrix.python-minor-version }}-ipython
|
||||
python3${{ matrix.python-minor-version }}-numpy
|
||||
python3${{ matrix.python-minor-version }}-sip
|
||||
python3${{ matrix.python-minor-version }}-tkinter
|
||||
wget
|
||||
xorg-server-extra
|
||||
zlib-devel
|
||||
|
||||
- name: Add Lapack to PATH
|
||||
uses: egor-tensin/cleanup-path@v4
|
||||
with:
|
||||
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
|
||||
|
||||
- name: Select Python version
|
||||
run: |
|
||||
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
|
||||
|
||||
- name: pip cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
||||
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
|
||||
|
||||
- name: Build system information
|
||||
run: |
|
||||
dash.exe -c "python3 .github/workflows/system-info.py"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bash.exe .ci/install.sh
|
||||
|
||||
- name: Build
|
||||
shell: bash.exe -eo pipefail -o igncr "{0}"
|
||||
run: |
|
||||
.ci/build.sh
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
bash.exe xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh
|
||||
|
||||
- name: Prepare to upload errors
|
||||
if: failure()
|
||||
run: |
|
||||
dash.exe -c "mkdir -p Tests/errors"
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
path: Tests/errors
|
||||
|
||||
- name: After success
|
||||
run: |
|
||||
bash.exe .ci/after_success.sh
|
||||
rm C:\cygwin\bin\bash.EXE
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: ./coverage.xml
|
||||
flags: GHA_Cygwin
|
||||
name: Cygwin Python 3.${{ matrix.python-minor-version }}
|
||||
token: ${{ secrets.CODECOV_ORG_TOKEN }}
|
||||
|
||||
success:
|
||||
permissions:
|
||||
contents: none
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
name: Cygwin Test Successful
|
||||
steps:
|
||||
- name: Success
|
||||
run: echo Cygwin Test Successful
|
4
.github/workflows/test-windows.yml
vendored
4
.github/workflows/test-windows.yml
vendored
|
@ -35,11 +35,11 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["pypy3.11", "3.11", "3.12", "3.13", "3.14"]
|
||||
python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"]
|
||||
architecture: ["x64"]
|
||||
include:
|
||||
# Test the oldest Python on 32-bit
|
||||
- { python-version: "3.10", architecture: "x86" }
|
||||
- { python-version: "3.9", architecture: "x86" }
|
||||
|
||||
timeout-minutes: 45
|
||||
|
||||
|
|
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
|
@ -42,6 +42,7 @@ jobs:
|
|||
]
|
||||
python-version: [
|
||||
"pypy3.11",
|
||||
"pypy3.10",
|
||||
"3.14t",
|
||||
"3.14",
|
||||
"3.13t",
|
||||
|
@ -49,17 +50,18 @@ jobs:
|
|||
"3.12",
|
||||
"3.11",
|
||||
"3.10",
|
||||
"3.9",
|
||||
]
|
||||
include:
|
||||
- { python-version: "3.12", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
||||
- { python-version: "3.11", PYTHONOPTIMIZE: 2 }
|
||||
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
||||
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
|
||||
# Free-threaded
|
||||
- { python-version: "3.14t", disable-gil: true }
|
||||
- { python-version: "3.13t", disable-gil: true }
|
||||
# Intel
|
||||
- { os: "macos-13", python-version: "3.10" }
|
||||
# M1 only available for 3.10+
|
||||
- { os: "macos-13", python-version: "3.9" }
|
||||
exclude:
|
||||
- { os: "macos-latest", python-version: "3.10" }
|
||||
- { os: "macos-latest", python-version: "3.9" }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
|
|
60
.github/workflows/wheels-dependencies.sh
vendored
60
.github/workflows/wheels-dependencies.sh
vendored
|
@ -60,7 +60,7 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then
|
|||
# on using the Xcode builder, which isn't very helpful for most of Pillow's
|
||||
# dependencies. Therefore, we lean on the OSX configurations, plus CC, CFLAGS
|
||||
# etc. to ensure the right sysroot is selected.
|
||||
HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO -DENABLE_SHARED=NO"
|
||||
HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO"
|
||||
|
||||
# Meson needs to be pointed at a cross-platform configuration file
|
||||
# This will be generated once CC etc. have been evaluated.
|
||||
|
@ -95,7 +95,7 @@ ARCHIVE_SDIR=pillow-depends-main
|
|||
# you change those versions, ensure the patch is also updated.
|
||||
FREETYPE_VERSION=2.13.3
|
||||
HARFBUZZ_VERSION=11.2.1
|
||||
LIBPNG_VERSION=1.6.50
|
||||
LIBPNG_VERSION=1.6.49
|
||||
JPEGTURBO_VERSION=3.1.1
|
||||
OPENJPEG_VERSION=2.5.3
|
||||
XZ_VERSION=5.8.1
|
||||
|
@ -103,7 +103,7 @@ TIFF_VERSION=4.7.0
|
|||
LCMS2_VERSION=2.17
|
||||
ZLIB_VERSION=1.3.1
|
||||
ZLIB_NG_VERSION=2.2.4
|
||||
LIBWEBP_VERSION=1.6.0
|
||||
LIBWEBP_VERSION=1.5.0 # Patched; next release won't need patching. See patch file.
|
||||
BZIP2_VERSION=1.0.8
|
||||
LIBXCB_VERSION=1.17.0
|
||||
BROTLI_VERSION=1.1.0 # Patched; next release won't need patching. See patch file.
|
||||
|
@ -186,43 +186,30 @@ function build_libavif {
|
|||
|
||||
python3 -m pip install meson ninja
|
||||
|
||||
if ([[ "$PLAT" == "x86_64" ]] && [[ -z "$IOS_SDK" ]]) || [ -n "$SANITIZER" ]; then
|
||||
if [[ "$PLAT" == "x86_64" ]] || [ -n "$SANITIZER" ]; then
|
||||
build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
|
||||
fi
|
||||
|
||||
local build_type=MinSizeRel
|
||||
local build_shared=ON
|
||||
local lto=ON
|
||||
|
||||
local libavif_cmake_flags
|
||||
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
if [ -n "$IS_MACOS" ]; then
|
||||
lto=OFF
|
||||
libavif_cmake_flags=(
|
||||
-DCMAKE_C_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
|
||||
-DCMAKE_CXX_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
|
||||
-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,-S,-x,-dead_strip_dylibs" \
|
||||
)
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
build_shared=OFF
|
||||
fi
|
||||
else
|
||||
if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "x86_64" ]]; then
|
||||
build_type=Release
|
||||
fi
|
||||
libavif_cmake_flags=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now")
|
||||
fi
|
||||
if [[ -n "$IOS_SDK" ]] && [[ "$PLAT" == "x86_64" ]]; then
|
||||
libavif_cmake_flags+=(-DAOM_TARGET_CPU=generic)
|
||||
else
|
||||
libavif_cmake_flags+=(
|
||||
-DAVIF_CODEC_AOM_DECODE=OFF \
|
||||
-DAVIF_CODEC_DAV1D=LOCAL
|
||||
)
|
||||
fi
|
||||
|
||||
local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz)
|
||||
|
||||
# CONFIG_AV1_HIGHBITDEPTH=0 is a flag for libaom (included as a subproject
|
||||
# of libavif) that disables support for encoding high bit depth images.
|
||||
(cd $out_dir \
|
||||
|
@ -230,27 +217,20 @@ function build_libavif {
|
|||
-DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \
|
||||
-DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib \
|
||||
-DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \
|
||||
-DBUILD_SHARED_LIBS=$build_shared \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DAVIF_LIBSHARPYUV=LOCAL \
|
||||
-DAVIF_LIBYUV=LOCAL \
|
||||
-DAVIF_CODEC_AOM=LOCAL \
|
||||
-DCONFIG_AV1_HIGHBITDEPTH=0 \
|
||||
-DAVIF_CODEC_AOM_DECODE=OFF \
|
||||
-DAVIF_CODEC_DAV1D=LOCAL \
|
||||
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=$lto \
|
||||
-DCMAKE_C_VISIBILITY_PRESET=hidden \
|
||||
-DCMAKE_CXX_VISIBILITY_PRESET=hidden \
|
||||
-DCMAKE_BUILD_TYPE=$build_type \
|
||||
"${libavif_cmake_flags[@]}" \
|
||||
$HOST_CMAKE_FLAGS . )
|
||||
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
# libavif's CMake configuration generates a meson cross file... but it
|
||||
# doesn't work for iOS cross-compilation. Copy in Pillow-generated
|
||||
# meson-cross config to replace the cmake-generated version.
|
||||
cp $WORKDIR/meson-cross.txt $out_dir/crossfile-apple.meson
|
||||
fi
|
||||
|
||||
(cd $out_dir && make install)
|
||||
|
||||
. \
|
||||
&& make install)
|
||||
touch libavif-stamp
|
||||
}
|
||||
|
||||
|
@ -288,7 +268,10 @@ function build {
|
|||
build_tiff
|
||||
fi
|
||||
|
||||
build_libavif
|
||||
if [[ -z "$IOS_SDK" ]]; then
|
||||
# Short term workaround; don't build libavif on iOS
|
||||
build_libavif
|
||||
fi
|
||||
build_libpng
|
||||
build_lcms2
|
||||
build_openjpeg
|
||||
|
@ -297,11 +280,7 @@ function build {
|
|||
if [[ -n "$IS_MACOS" ]]; then
|
||||
webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names"
|
||||
fi
|
||||
webp_ldflags=""
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
webp_ldflags="$webp_ldflags -llzma -lz"
|
||||
fi
|
||||
CFLAGS="$CFLAGS $webp_cflags" LDFLAGS="$LDFLAGS $webp_ldflags" build_simple libwebp $LIBWEBP_VERSION \
|
||||
CFLAGS="$CFLAGS $webp_cflags" build_simple libwebp $LIBWEBP_VERSION \
|
||||
https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \
|
||||
--enable-libwebpmux --enable-libwebpdemux
|
||||
|
||||
|
@ -401,15 +380,6 @@ fi
|
|||
|
||||
wrap_wheel_builder build
|
||||
|
||||
# A safety catch for iOS. iOS can't use dynamic libraries, but clang will prefer
|
||||
# to link dynamic libraries to static libraries. The only way to reliably
|
||||
# prevent this is to not have dynamic libraries available in the first place.
|
||||
# The build process *shouldn't* generate any dylibs... but just in case, purge
|
||||
# any dylibs that *have* been installed into the build prefix directory.
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
find "$BUILD_PREFIX" -name "*.dylib" -exec rm -rf {} \;
|
||||
fi
|
||||
|
||||
# Return to the project root to finish the build
|
||||
popd > /dev/null
|
||||
|
||||
|
|
4
.github/workflows/wheels.yml
vendored
4
.github/workflows/wheels.yml
vendored
|
@ -77,22 +77,22 @@ jobs:
|
|||
platform: linux
|
||||
os: ubuntu-latest
|
||||
cibw_arch: x86_64
|
||||
manylinux: "manylinux2014"
|
||||
- name: "manylinux_2_28 x86_64"
|
||||
platform: linux
|
||||
os: ubuntu-latest
|
||||
cibw_arch: x86_64
|
||||
build: "*manylinux*"
|
||||
manylinux: "manylinux_2_28"
|
||||
- name: "manylinux2014 and musllinux aarch64"
|
||||
platform: linux
|
||||
os: ubuntu-24.04-arm
|
||||
cibw_arch: aarch64
|
||||
manylinux: "manylinux2014"
|
||||
- name: "manylinux_2_28 aarch64"
|
||||
platform: linux
|
||||
os: ubuntu-24.04-arm
|
||||
cibw_arch: aarch64
|
||||
build: "*manylinux*"
|
||||
manylinux: "manylinux_2_28"
|
||||
- name: "iOS arm64 device"
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.2
|
||||
rev: v0.12.0
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [--exit-non-zero-on-fix]
|
||||
|
@ -11,7 +11,7 @@ repos:
|
|||
- id: black
|
||||
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.8.6
|
||||
rev: 1.8.5
|
||||
hooks:
|
||||
- id: bandit
|
||||
args: [--severity-level=high]
|
||||
|
@ -24,7 +24,7 @@ repos:
|
|||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v20.1.7
|
||||
rev: v20.1.6
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types: [c]
|
||||
|
@ -51,14 +51,14 @@ repos:
|
|||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.33.2
|
||||
rev: 0.33.1
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
- id: check-readthedocs
|
||||
- id: check-renovate
|
||||
|
||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||
rev: v1.11.0
|
||||
rev: v1.9.0
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
|
|
12
MANIFEST.in
12
MANIFEST.in
|
@ -13,7 +13,6 @@ include LICENSE
|
|||
include Makefile
|
||||
include tox.ini
|
||||
graft Tests
|
||||
graft Tests/images
|
||||
graft checks
|
||||
graft patches
|
||||
graft src
|
||||
|
@ -29,19 +28,8 @@ exclude .editorconfig
|
|||
exclude .readthedocs.yml
|
||||
exclude codecov.yml
|
||||
exclude renovate.json
|
||||
exclude Tests/images/README.md
|
||||
exclude Tests/images/crash*.tif
|
||||
exclude Tests/images/string_dimension.tiff
|
||||
global-exclude .git*
|
||||
global-exclude *.pyc
|
||||
global-exclude *.so
|
||||
prune .ci
|
||||
prune wheels
|
||||
prune winbuild/build
|
||||
prune winbuild/depends
|
||||
prune Tests/errors
|
||||
prune Tests/images/jpeg2000
|
||||
prune Tests/images/msp
|
||||
prune Tests/images/picins
|
||||
prune Tests/images/sunraster
|
||||
prune Tests/test-images
|
||||
|
|
|
@ -36,6 +36,9 @@ As of 2019, Pillow development is
|
|||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml"><img
|
||||
alt="GitHub Actions build status (Test MinGW)"
|
||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20MinGW/badge.svg"></a>
|
||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-cygwin.yml"><img
|
||||
alt="GitHub Actions build status (Test Cygwin)"
|
||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20Cygwin/badge.svg"></a>
|
||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml"><img
|
||||
alt="GitHub Actions build status (Test Docker)"
|
||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
|
||||
|
|
|
@ -10,20 +10,17 @@ import shutil
|
|||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from collections.abc import Sequence
|
||||
from functools import lru_cache
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
from PIL import Image, ImageFile, ImageMath, features
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Sequence
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
uploader = None
|
||||
|
@ -274,13 +271,17 @@ def _cached_hopper(mode: str) -> Image.Image:
|
|||
im = hopper("L")
|
||||
else:
|
||||
im = hopper()
|
||||
try:
|
||||
im = im.convert(mode)
|
||||
except ImportError:
|
||||
if mode == "LAB":
|
||||
im = Image.open("Tests/images/hopper.Lab.tif")
|
||||
else:
|
||||
raise
|
||||
if mode.startswith("BGR;"):
|
||||
with pytest.warns(DeprecationWarning, match="BGR;"):
|
||||
im = im.convert(mode)
|
||||
else:
|
||||
try:
|
||||
im = im.convert(mode)
|
||||
except ImportError:
|
||||
if mode == "LAB":
|
||||
im = Image.open("Tests/images/hopper.Lab.tif")
|
||||
else:
|
||||
raise
|
||||
return im
|
||||
|
||||
|
||||
|
@ -294,6 +295,16 @@ def djpeg_available() -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def cjpeg_available() -> bool:
|
||||
if shutil.which("cjpeg"):
|
||||
try:
|
||||
subprocess.check_call(["cjpeg", "-version"])
|
||||
return True
|
||||
except subprocess.CalledProcessError: # pragma: no cover
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def netpbm_available() -> bool:
|
||||
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
|
||||
|
||||
|
|
Binary file not shown.
|
@ -9,9 +9,9 @@ from PIL import _deprecate
|
|||
"version, expected",
|
||||
[
|
||||
(
|
||||
13,
|
||||
"Old thing is deprecated and will be removed in Pillow 13 "
|
||||
r"\(2026-10-15\)\. Use new thing instead\.",
|
||||
12,
|
||||
"Old thing is deprecated and will be removed in Pillow 12 "
|
||||
r"\(2025-10-15\)\. Use new thing instead\.",
|
||||
),
|
||||
(
|
||||
None,
|
||||
|
@ -53,18 +53,18 @@ def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
|
|||
|
||||
def test_plural() -> None:
|
||||
expected = (
|
||||
r"Old things are deprecated and will be removed in Pillow 13 \(2026-10-15\)\. "
|
||||
r"Old things are deprecated and will be removed in Pillow 12 \(2025-10-15\)\. "
|
||||
r"Use new thing instead\."
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=expected):
|
||||
_deprecate.deprecate("Old things", 13, "new thing", plural=True)
|
||||
_deprecate.deprecate("Old things", 12, "new thing", plural=True)
|
||||
|
||||
|
||||
def test_replacement_and_action() -> None:
|
||||
expected = "Use only one of 'replacement' and 'action'"
|
||||
with pytest.raises(ValueError, match=expected):
|
||||
_deprecate.deprecate(
|
||||
"Old thing", 13, replacement="new thing", action="Upgrade to new thing"
|
||||
"Old thing", 12, replacement="new thing", action="Upgrade to new thing"
|
||||
)
|
||||
|
||||
|
||||
|
@ -77,16 +77,16 @@ def test_replacement_and_action() -> None:
|
|||
)
|
||||
def test_action(action: str) -> None:
|
||||
expected = (
|
||||
r"Old thing is deprecated and will be removed in Pillow 13 \(2026-10-15\)\. "
|
||||
r"Old thing is deprecated and will be removed in Pillow 12 \(2025-10-15\)\. "
|
||||
r"Upgrade to new thing\."
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=expected):
|
||||
_deprecate.deprecate("Old thing", 13, action=action)
|
||||
_deprecate.deprecate("Old thing", 12, action=action)
|
||||
|
||||
|
||||
def test_no_replacement_or_action() -> None:
|
||||
expected = (
|
||||
r"Old thing is deprecated and will be removed in Pillow 13 \(2026-10-15\)"
|
||||
r"Old thing is deprecated and will be removed in Pillow 12 \(2025-10-15\)"
|
||||
)
|
||||
with pytest.warns(DeprecationWarning, match=expected):
|
||||
_deprecate.deprecate("Old thing", 13)
|
||||
_deprecate.deprecate("Old thing", 12)
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import io
|
||||
import re
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -9,10 +10,6 @@ from PIL import features
|
|||
|
||||
from .helper import skip_unless_feature
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
def test_check() -> None:
|
||||
# Check the correctness of the convenience function
|
||||
|
@ -21,7 +18,11 @@ def test_check() -> None:
|
|||
for codec in features.codecs:
|
||||
assert features.check_codec(codec) == features.check(codec)
|
||||
for feature in features.features:
|
||||
assert features.check_feature(feature) == features.check(feature)
|
||||
if "webp" in feature:
|
||||
with pytest.warns(DeprecationWarning, match="webp"):
|
||||
assert features.check_feature(feature) == features.check(feature)
|
||||
else:
|
||||
assert features.check_feature(feature) == features.check(feature)
|
||||
|
||||
|
||||
def test_version() -> None:
|
||||
|
@ -47,7 +48,26 @@ def test_version() -> None:
|
|||
for codec in features.codecs:
|
||||
test(codec, features.version_codec)
|
||||
for feature in features.features:
|
||||
test(feature, features.version_feature)
|
||||
if "webp" in feature:
|
||||
with pytest.warns(DeprecationWarning, match="webp"):
|
||||
test(feature, features.version_feature)
|
||||
else:
|
||||
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")
|
||||
|
@ -107,25 +127,6 @@ def test_unsupported_module() -> None:
|
|||
features.version_module(module)
|
||||
|
||||
|
||||
def test_unsupported_feature() -> None:
|
||||
# Arrange
|
||||
feature = "unsupported_feature"
|
||||
# Act / Assert
|
||||
with pytest.raises(ValueError):
|
||||
features.check_feature(feature)
|
||||
with pytest.raises(ValueError):
|
||||
features.version_feature(feature)
|
||||
|
||||
|
||||
def test_unsupported_version() -> None:
|
||||
assert features.version("unsupported_version") is None
|
||||
|
||||
|
||||
def test_modulenotfound(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(features, "features", {"test": ("PIL._test", "", "")})
|
||||
assert features.check_feature("test") is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("supported_formats", (True, False))
|
||||
def test_pilinfo(supported_formats: bool) -> None:
|
||||
buf = io.StringIO()
|
||||
|
|
|
@ -380,28 +380,21 @@ def test_palette() -> None:
|
|||
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
|
||||
|
||||
|
||||
def test_unsupported_header_size() -> None:
|
||||
with pytest.raises(OSError, match="Unsupported header size 0"):
|
||||
with Image.open(BytesIO(b"DDS " + b"\x00" * 4)):
|
||||
pass
|
||||
|
||||
|
||||
def test_unsupported_bitcount() -> None:
|
||||
with pytest.raises(OSError, match="Unsupported bitcount 24 for 131072"):
|
||||
with pytest.raises(OSError):
|
||||
with Image.open("Tests/images/unsupported_bitcount.dds"):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_file, message",
|
||||
"test_file",
|
||||
(
|
||||
("Tests/images/unimplemented_dxgi_format.dds", "Unimplemented DXGI format 93"),
|
||||
("Tests/images/unimplemented_pixel_format.dds", "Unimplemented pixel format 0"),
|
||||
("Tests/images/unimplemented_pfflags.dds", "Unknown pixel format flags 8"),
|
||||
"Tests/images/unimplemented_dxgi_format.dds",
|
||||
"Tests/images/unimplemented_pfflags.dds",
|
||||
),
|
||||
)
|
||||
def test_not_implemented(test_file: str, message: str) -> None:
|
||||
with pytest.raises(NotImplementedError, match=message):
|
||||
def test_not_implemented(test_file: str) -> None:
|
||||
with pytest.raises(NotImplementedError):
|
||||
with Image.open(test_file):
|
||||
pass
|
||||
|
||||
|
|
|
@ -93,11 +93,21 @@ def test_sizes() -> None:
|
|||
with Image.open(TEST_FILE) as im:
|
||||
assert isinstance(im, IcnsImagePlugin.IcnsImageFile)
|
||||
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
|
||||
im.size = (w, h)
|
||||
im.load(scale=r)
|
||||
assert im.mode == "RGBA"
|
||||
assert im.size == (w * r, h * r)
|
||||
assert im.size == (wr, hr)
|
||||
|
||||
# Check that we cannot load an incorrect size
|
||||
with pytest.raises(ValueError):
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
import sys
|
||||
from io import BytesIO, StringIO
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags
|
||||
|
||||
|
@ -98,3 +101,35 @@ def test_getiptcinfo_tiff_none() -> None:
|
|||
|
||||
# Assert
|
||||
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"
|
||||
|
|
|
@ -26,6 +26,7 @@ from .helper import (
|
|||
assert_image_equal_tofile,
|
||||
assert_image_similar,
|
||||
assert_image_similar_tofile,
|
||||
cjpeg_available,
|
||||
djpeg_available,
|
||||
hopper,
|
||||
is_win32,
|
||||
|
@ -730,6 +731,14 @@ class TestFileJpeg:
|
|||
img.load_djpeg()
|
||||
assert_image_similar_tofile(img, TEST_FILE, 5)
|
||||
|
||||
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
|
||||
def test_save_cjpeg(self, tmp_path: Path) -> None:
|
||||
with Image.open(TEST_FILE) as img:
|
||||
tempfile = str(tmp_path / "temp.jpg")
|
||||
JpegImagePlugin._save_cjpeg(img, BytesIO(), tempfile)
|
||||
# Default save quality is 75%, so a tiny bit of difference is alright
|
||||
assert_image_similar_tofile(img, tempfile, 17)
|
||||
|
||||
def test_no_duplicate_0x1001_tag(self) -> None:
|
||||
# Arrange
|
||||
tag_ids = {v: k for k, v in ExifTags.TAGS.items()}
|
||||
|
@ -1106,6 +1115,14 @@ class TestFileJpeg:
|
|||
|
||||
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")
|
||||
@skip_unless_feature("jpg")
|
||||
|
|
|
@ -256,7 +256,19 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
|
||||
im.save(out, tiffinfo=new_ifd)
|
||||
|
||||
@pytest.mark.parametrize("libtiff", (True, False))
|
||||
@pytest.mark.parametrize(
|
||||
"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(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
|
||||
) -> None:
|
||||
|
@ -712,7 +724,8 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
|
||||
with Image.open(out) as reloaded:
|
||||
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
|
||||
assert reloaded.tag_v2[34665] == 125456
|
||||
if Image.core.libtiff_support_custom_tags:
|
||||
assert reloaded.tag_v2[34665] == 125456
|
||||
|
||||
def test_crashing_metadata(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
|
@ -764,7 +777,19 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
assert icc_libtiff is not None
|
||||
assert icc == icc_libtiff
|
||||
|
||||
@pytest.mark.parametrize("libtiff", (True, False))
|
||||
@pytest.mark.parametrize(
|
||||
"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(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, libtiff: bool
|
||||
) -> None:
|
||||
|
@ -873,8 +898,8 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
assert im.mode == "RGB"
|
||||
assert im.size == (128, 128)
|
||||
assert im.format == "TIFF"
|
||||
with hopper() as im2:
|
||||
assert_image_similar(im, im2, 5)
|
||||
im2 = hopper()
|
||||
assert_image_similar(im, im2, 5)
|
||||
except OSError:
|
||||
captured = capfd.readouterr()
|
||||
if "LZMA compression support is not configured" in captured.err:
|
||||
|
|
|
@ -44,18 +44,6 @@ def test_load_zero_inch() -> None:
|
|||
pass
|
||||
|
||||
|
||||
def test_load_unsupported_wmf() -> None:
|
||||
b = BytesIO(b"\xd7\xcd\xc6\x9a\x00\x00" + b"\x01" * 10)
|
||||
with pytest.raises(SyntaxError, match="Unsupported WMF file format"):
|
||||
WmfImagePlugin.WmfStubImageFile(b)
|
||||
|
||||
|
||||
def test_load_unsupported() -> None:
|
||||
b = BytesIO(b"\x01\x00\x00\x00")
|
||||
with pytest.raises(SyntaxError, match="Unsupported file format"):
|
||||
WmfImagePlugin.WmfStubImageFile(b)
|
||||
|
||||
|
||||
def test_render() -> None:
|
||||
with open("Tests/images/drawing.emf", "rb") as fp:
|
||||
data = fp.read()
|
||||
|
|
|
@ -2,15 +2,12 @@ from __future__ import annotations
|
|||
|
||||
import colorsys
|
||||
import itertools
|
||||
from typing import Callable
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .helper import assert_image_similar, hopper
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
def int_to_float(i: int) -> float:
|
||||
return i / 255
|
||||
|
|
|
@ -30,6 +30,7 @@ from .helper import (
|
|||
assert_image_similar_tofile,
|
||||
assert_not_all_same,
|
||||
hopper,
|
||||
is_big_endian,
|
||||
is_win32,
|
||||
mark_if_feature_version,
|
||||
skip_unless_feature,
|
||||
|
@ -49,10 +50,19 @@ except ImportError:
|
|||
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:
|
||||
@pytest.mark.parametrize("mode", Image.MODES)
|
||||
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
|
||||
def test_image_modes_success(self, mode: str) -> None:
|
||||
Image.new(mode, (1, 1))
|
||||
helper_image_new(mode, (1, 1))
|
||||
|
||||
@pytest.mark.parametrize("mode", ("", "bad", "very very long"))
|
||||
def test_image_modes_fail(self, mode: str) -> None:
|
||||
|
@ -1132,29 +1142,39 @@ class TestImage:
|
|||
assert len(caplog.records) == 0
|
||||
assert im.fp is None
|
||||
|
||||
def test_deprecation(self) -> None:
|
||||
with pytest.warns(DeprecationWarning, match="Image.isImageType"):
|
||||
assert not Image.isImageType(None)
|
||||
|
||||
|
||||
class TestImageBytes:
|
||||
@pytest.mark.parametrize("mode", Image.MODES)
|
||||
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
|
||||
def test_roundtrip_bytes_constructor(self, mode: str) -> None:
|
||||
im = hopper(mode)
|
||||
source_bytes = im.tobytes()
|
||||
|
||||
reloaded = Image.frombytes(mode, im.size, source_bytes)
|
||||
if mode.startswith("BGR;"):
|
||||
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
|
||||
|
||||
@pytest.mark.parametrize("mode", Image.MODES)
|
||||
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
|
||||
def test_roundtrip_bytes_method(self, mode: str) -> None:
|
||||
im = hopper(mode)
|
||||
source_bytes = im.tobytes()
|
||||
|
||||
reloaded = Image.new(mode, im.size)
|
||||
reloaded = helper_image_new(mode, im.size)
|
||||
reloaded.frombytes(source_bytes)
|
||||
assert reloaded.tobytes() == source_bytes
|
||||
|
||||
@pytest.mark.parametrize("mode", Image.MODES)
|
||||
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
|
||||
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)
|
||||
reloaded = Image.new(mode, im.size)
|
||||
reloaded = helper_image_new(mode, im.size)
|
||||
reloaded.putdata(im.getdata())
|
||||
assert_image_equal(im, reloaded)
|
||||
|
||||
|
|
|
@ -123,6 +123,10 @@ class TestImageGetPixel:
|
|||
bands = Image.getmodebands(mode)
|
||||
if bands == 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))
|
||||
|
||||
def check(self, mode: str, expected_color_int: int | None = None) -> None:
|
||||
|
@ -187,6 +191,11 @@ class TestImageGetPixel:
|
|||
def test_basic(self, mode: str) -> None:
|
||||
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:
|
||||
im = hopper()
|
||||
assert im.getpixel([0, 0]) == (20, 20, 70)
|
||||
|
@ -209,7 +218,7 @@ class TestImageGetPixel:
|
|||
|
||||
|
||||
class TestImagePutPixelError:
|
||||
IMAGE_MODES1 = ["LA", "RGB", "RGBA"]
|
||||
IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"]
|
||||
IMAGE_MODES2 = ["L", "I", "I;16"]
|
||||
INVALID_TYPES = ["foo", 1.0, None]
|
||||
|
||||
|
@ -225,6 +234,11 @@ class TestImagePutPixelError:
|
|||
(
|
||||
("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"),
|
||||
(
|
||||
"BGR;15",
|
||||
(0, 2),
|
||||
"color must be int, or tuple of one or three elements",
|
||||
),
|
||||
(
|
||||
"RGB",
|
||||
(0, 2, 5),
|
||||
|
@ -315,10 +329,3 @@ int main(int argc, char* argv[])
|
|||
process = subprocess.Popen(["embed_pil.exe"], env=env)
|
||||
process.communicate()
|
||||
assert process.returncode == 0
|
||||
|
||||
def teardown_method(self) -> None:
|
||||
try:
|
||||
os.remove("embed_pil.c")
|
||||
except FileNotFoundError:
|
||||
# If the test was skipped or failed, the file won't exist
|
||||
pass
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from .helper import hopper
|
||||
|
||||
|
||||
|
@ -8,3 +10,10 @@ def test_sanity() -> None:
|
|||
|
||||
type_repr = repr(type(im.getim()))
|
||||
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"}
|
||||
|
|
|
@ -10,12 +10,9 @@ def test_histogram() -> None:
|
|||
|
||||
assert histogram("1") == (256, 0, 10994)
|
||||
assert histogram("L") == (256, 0, 662)
|
||||
assert histogram("LA") == (512, 0, 16384)
|
||||
assert histogram("La") == (512, 0, 16384)
|
||||
assert histogram("I") == (256, 0, 662)
|
||||
assert histogram("F") == (256, 0, 662)
|
||||
assert histogram("P") == (256, 0, 1551)
|
||||
assert histogram("PA") == (512, 0, 16384)
|
||||
assert histogram("RGB") == (768, 4, 675)
|
||||
assert histogram("RGBA") == (1024, 0, 16384)
|
||||
assert histogram("CMYK") == (1024, 0, 16384)
|
||||
|
|
|
@ -78,6 +78,16 @@ def test_mode_F() -> None:
|
|||
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:
|
||||
# shouldn't segfault
|
||||
# see https://github.com/python-pillow/Pillow/issues/1008
|
||||
|
|
|
@ -324,7 +324,7 @@ class TestImageResize:
|
|||
im = hopper(mode)
|
||||
assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
|
||||
|
||||
@pytest.mark.parametrize("mode", ("1", "P"))
|
||||
@pytest.mark.parametrize("mode", ("1", "P", "BGR;15", "BGR;16"))
|
||||
def test_default_filter_nearest(self, mode: str) -> None:
|
||||
im = hopper(mode)
|
||||
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -8,10 +9,6 @@ from PIL import Image, ImageTransform
|
|||
|
||||
from .helper import assert_image_equal, assert_image_similar, hopper
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
class TestImageTransform:
|
||||
def test_sanity(self) -> None:
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
|
||||
from PIL import Image, ImageChops
|
||||
|
||||
from .helper import assert_image_equal, hopper
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
BLACK = (0, 0, 0)
|
||||
BROWN = (127, 64, 0)
|
||||
CYAN = (0, 255, 255)
|
||||
|
|
|
@ -7,7 +7,7 @@ import shutil
|
|||
import sys
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Literal, cast
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -31,9 +31,6 @@ except ImportError:
|
|||
# Skipped via setup_module()
|
||||
pass
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
SRGB = "Tests/icc/sRGB_IEC61966-2-1_black_scaled.icc"
|
||||
HAVE_PROFILE = os.path.exists(SRGB)
|
||||
|
@ -57,6 +54,10 @@ def skip_missing() -> None:
|
|||
def test_sanity() -> None:
|
||||
# basic smoke test.
|
||||
# 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
|
||||
version = features.version_module("littlecms2")
|
||||
|
@ -211,10 +212,9 @@ def test_exceptions() -> None:
|
|||
ImageCms.getProfileName(None) # type: ignore[arg-type]
|
||||
skip_missing()
|
||||
|
||||
with pytest.raises(
|
||||
ImageCms.PyCMSError,
|
||||
match="'NoneType' object cannot be interpreted as an integer",
|
||||
):
|
||||
# Python <= 3.9: "an integer is required (got type NoneType)"
|
||||
# Python > 3.9: "'NoneType' object cannot be interpreted as an integer"
|
||||
with pytest.raises(ImageCms.PyCMSError, match="integer"):
|
||||
ImageCms.isIntentSupported(SRGB, None, None) # type: ignore[arg-type]
|
||||
|
||||
|
||||
|
@ -677,6 +677,12 @@ def test_auxiliary_channels_isolated() -> None:
|
|||
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"))
|
||||
def test_rgb_lab(mode: str) -> None:
|
||||
im = Image.new(mode, (1, 1))
|
||||
|
@ -697,14 +703,15 @@ def test_cmyk_lab() -> None:
|
|||
|
||||
|
||||
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="ImageCms.ImageCmsProfile.product_name"
|
||||
):
|
||||
profile.product_name
|
||||
with pytest.warns(
|
||||
DeprecationWarning, match="ImageCms.ImageCmsProfile.product_info"
|
||||
):
|
||||
profile.product_info
|
||||
with pytest.raises(AttributeError):
|
||||
profile.this_attribute_does_not_exist
|
||||
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")
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
from collections.abc import Sequence
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageColor, ImageDraw, ImageFont, features
|
||||
from PIL._typing import Coords
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -14,12 +17,6 @@ from .helper import (
|
|||
skip_unless_feature,
|
||||
)
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Sequence
|
||||
|
||||
from PIL._typing import Coords
|
||||
|
||||
BLACK = (0, 0, 0)
|
||||
WHITE = (255, 255, 255)
|
||||
GRAY = (190, 190, 190)
|
||||
|
@ -1735,3 +1732,8 @@ def test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None:
|
|||
draw.rectangle(xy)
|
||||
with pytest.raises(ValueError):
|
||||
draw.rounded_rectangle(xy)
|
||||
|
||||
|
||||
def test_getdraw() -> None:
|
||||
with pytest.warns(DeprecationWarning, match="'hints' parameter"):
|
||||
ImageDraw.getdraw(None, [])
|
||||
|
|
|
@ -151,6 +151,11 @@ class TestImageFile:
|
|||
# Despite multiple tiles, assert only one tile caused a read of maxblock size
|
||||
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:
|
||||
with pytest.raises(TypeError):
|
||||
parser = ImageFile.Parser()
|
||||
|
|
|
@ -11,6 +11,7 @@ from pathlib import Path
|
|||
from typing import Any, BinaryIO
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont, features
|
||||
from PIL._typing import StrOrBytesPath
|
||||
|
@ -690,6 +691,16 @@ def test_complex_font_settings() -> 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):
|
||||
font.get_variation_names()
|
||||
with pytest.raises(OSError):
|
||||
|
@ -752,6 +763,14 @@ def _check_text(font: ImageFont.FreeTypeFont, path: str, epsilon: float) -> 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):
|
||||
font.set_variation_by_name("Bold")
|
||||
|
||||
|
@ -771,6 +790,14 @@ def test_variation_set_by_name(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):
|
||||
font.set_variation_by_axes([500, 50])
|
||||
|
||||
|
@ -1182,3 +1209,15 @@ def test_invalid_truetype_sizes_raise_valueerror(
|
|||
) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
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)
|
||||
|
|
|
@ -4,7 +4,7 @@ from typing import Any
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageMath, _imagingmath
|
||||
from PIL import Image, ImageMath
|
||||
|
||||
|
||||
def pixel(im: Image.Image | int) -> str | int:
|
||||
|
@ -55,6 +55,11 @@ 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:
|
||||
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, **images)) == "I -1"
|
||||
|
||||
|
@ -500,31 +505,3 @@ def test_logical_not_equal() -> None:
|
|||
)
|
||||
== "I 1"
|
||||
)
|
||||
|
||||
|
||||
def test_reflected_operands() -> None:
|
||||
assert pixel(ImageMath.lambda_eval(lambda args: 1 + args["A"], **images)) == "I 2"
|
||||
assert pixel(ImageMath.lambda_eval(lambda args: 1 - args["A"], **images)) == "I 0"
|
||||
assert pixel(ImageMath.lambda_eval(lambda args: 1 * args["A"], **images)) == "I 1"
|
||||
assert pixel(ImageMath.lambda_eval(lambda args: 1 / args["A"], **images)) == "I 1"
|
||||
assert pixel(ImageMath.lambda_eval(lambda args: 1 % args["A"], **images)) == "I 0"
|
||||
assert pixel(ImageMath.lambda_eval(lambda args: 1 ** args["A"], **images)) == "I 1"
|
||||
assert pixel(ImageMath.lambda_eval(lambda args: 1 & args["A"], **images)) == "I 1"
|
||||
assert pixel(ImageMath.lambda_eval(lambda args: 1 | args["A"], **images)) == "I 1"
|
||||
assert pixel(ImageMath.lambda_eval(lambda args: 1 ^ args["A"], **images)) == "I 0"
|
||||
|
||||
|
||||
def test_unsupported_mode() -> None:
|
||||
im = Image.new("RGB", (1, 1))
|
||||
with pytest.raises(ValueError, match="unsupported mode: RGB"):
|
||||
ImageMath.lambda_eval(lambda args: args["im"] + 1, im=im)
|
||||
|
||||
|
||||
def test_bad_operand_type(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delattr(_imagingmath, "abs_I")
|
||||
with pytest.raises(TypeError, match="bad operand type for 'abs'"):
|
||||
ImageMath.lambda_eval(lambda args: abs(args["I"]), I=I)
|
||||
|
||||
monkeypatch.delattr(_imagingmath, "max_F")
|
||||
with pytest.raises(TypeError, match="bad operand type for 'max'"):
|
||||
ImageMath.lambda_eval(lambda args: args["max"](args["I"], args["F"]), I=I, F=F)
|
||||
|
|
|
@ -35,6 +35,16 @@ def test_sanity() -> None:
|
|||
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:
|
||||
assert pixel(ImageMath.unsafe_eval("-A", **images)) == "I -1"
|
||||
assert pixel(ImageMath.unsafe_eval("+B", **images)) == "L 2"
|
||||
|
|
|
@ -361,6 +361,18 @@ class TestLibUnpack:
|
|||
"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:
|
||||
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
|
||||
self.assert_unpack(
|
||||
|
|
|
@ -9,30 +9,9 @@ from PIL import __version__
|
|||
pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed")
|
||||
|
||||
|
||||
def map_metadata_keys(metadata):
|
||||
# Convert installed wheel metadata into canonical Core Metadata 2.4 format.
|
||||
# This was a utility method in pyroma 4.3.3; it was removed in 5.0.
|
||||
# This implementation is constructed from the relevant logic from
|
||||
# Pyroma 5.0's `build_metadata()` implementation. This has been submitted
|
||||
# upstream to Pyroma as https://github.com/regebro/pyroma/pull/116,
|
||||
# so it may be possible to simplify this test in future.
|
||||
data = {}
|
||||
for key in set(metadata.keys()):
|
||||
value = metadata.get_all(key)
|
||||
key = pyroma.projectdata.normalize(key)
|
||||
|
||||
if len(value) == 1:
|
||||
value = value[0]
|
||||
if value.strip() == "UNKNOWN":
|
||||
continue
|
||||
|
||||
data[key] = value
|
||||
return data
|
||||
|
||||
|
||||
def test_pyroma() -> None:
|
||||
# Arrange
|
||||
data = map_metadata_keys(metadata("Pillow"))
|
||||
data = pyroma.projectdata.map_metadata_keys(metadata("Pillow"))
|
||||
|
||||
# Act
|
||||
rating = pyroma.ratings.rate(data)
|
||||
|
@ -46,5 +25,11 @@ def test_pyroma() -> None:
|
|||
)
|
||||
|
||||
else:
|
||||
# Should have a perfect score
|
||||
assert rating == (10, [])
|
||||
# Should have a perfect score, but pyroma does not support PEP 639 yet.
|
||||
assert rating == (
|
||||
9,
|
||||
[
|
||||
"Your package does neither have a license field "
|
||||
"nor any license classifiers."
|
||||
],
|
||||
)
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageQt
|
||||
|
@ -8,8 +11,18 @@ from .helper import assert_image_equal_tofile, assert_image_similar, hopper
|
|||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
import PyQt6
|
||||
import PySide6
|
||||
|
||||
QApplication = Union[PyQt6.QtWidgets.QApplication, PySide6.QtWidgets.QApplication]
|
||||
QHBoxLayout = Union[PyQt6.QtWidgets.QHBoxLayout, PySide6.QtWidgets.QHBoxLayout]
|
||||
QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage]
|
||||
QLabel = Union[PyQt6.QtWidgets.QLabel, PySide6.QtWidgets.QLabel]
|
||||
QPainter = Union[PyQt6.QtGui.QPainter, PySide6.QtGui.QPainter]
|
||||
QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap]
|
||||
QPoint = Union[PyQt6.QtCore.QPoint, PySide6.QtCore.QPoint]
|
||||
QRegion = Union[PyQt6.QtGui.QRegion, PySide6.QtGui.QRegion]
|
||||
QWidget = Union[PyQt6.QtWidgets.QWidget, PySide6.QtWidgets.QWidget]
|
||||
|
||||
if ImageQt.qt_is_installed:
|
||||
from PIL.ImageQt import QPixmap
|
||||
|
@ -19,16 +32,11 @@ if ImageQt.qt_is_installed:
|
|||
from PyQt6.QtGui import QImage, QPainter, QRegion
|
||||
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
||||
elif ImageQt.qt_version == "side6":
|
||||
from PySide6.QtCore import QPoint # type: ignore[assignment]
|
||||
from PySide6.QtGui import QImage, QPainter, QRegion # type: ignore[assignment]
|
||||
from PySide6.QtWidgets import ( # type: ignore[assignment]
|
||||
QApplication,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QWidget,
|
||||
)
|
||||
from PySide6.QtCore import QPoint
|
||||
from PySide6.QtGui import QImage, QPainter, QRegion
|
||||
from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
||||
|
||||
class Example(QWidget):
|
||||
class Example(QWidget): # type: ignore[misc]
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
|
@ -39,9 +47,9 @@ if ImageQt.qt_is_installed:
|
|||
pixmap1 = getattr(ImageQt.QPixmap, "fromImage")(qimage)
|
||||
|
||||
# hbox
|
||||
QHBoxLayout(self)
|
||||
QHBoxLayout(self) # type: ignore[operator]
|
||||
|
||||
lbl = QLabel(self)
|
||||
lbl = QLabel(self) # type: ignore[operator]
|
||||
# Segfault in the problem
|
||||
lbl.setPixmap(pixmap1.copy())
|
||||
|
||||
|
@ -55,7 +63,7 @@ def roundtrip(expected: Image.Image) -> None:
|
|||
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
|
||||
def test_sanity(tmp_path: Path) -> None:
|
||||
# Segfault test
|
||||
app: QApplication | None = QApplication([])
|
||||
app: QApplication | None = QApplication([]) # type: ignore[operator]
|
||||
ex = Example()
|
||||
assert app # Silence warning
|
||||
assert ex # Silence warning
|
||||
|
@ -76,11 +84,11 @@ def test_sanity(tmp_path: Path) -> None:
|
|||
imageqt = ImageQt.ImageQt(im)
|
||||
data = getattr(QPixmap, "fromImage")(imageqt)
|
||||
qt_format = getattr(QImage, "Format") if ImageQt.qt_version == "6" else QImage
|
||||
qimage = QImage(128, 128, getattr(qt_format, "Format_ARGB32"))
|
||||
painter = QPainter(qimage)
|
||||
image_label = QLabel()
|
||||
qimage = QImage(128, 128, getattr(qt_format, "Format_ARGB32")) # type: ignore[operator]
|
||||
painter = QPainter(qimage) # type: ignore[operator]
|
||||
image_label = QLabel() # type: ignore[operator]
|
||||
image_label.setPixmap(data)
|
||||
image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128))
|
||||
image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128)) # type: ignore[operator]
|
||||
painter.end()
|
||||
rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png")
|
||||
qimage.save(rendered_tempfile)
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import ImageQt
|
||||
|
||||
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
||||
)
|
||||
|
@ -23,7 +21,7 @@ def test_sanity(mode: str, tmp_path: Path) -> None:
|
|||
src = hopper(mode)
|
||||
data = ImageQt.toqimage(src)
|
||||
|
||||
assert isinstance(data, QImage)
|
||||
assert isinstance(data, QImage) # type: ignore[arg-type, misc]
|
||||
assert not data.isNull()
|
||||
|
||||
# reload directly from the qimage
|
||||
|
|
|
@ -2,18 +2,14 @@ from __future__ import annotations
|
|||
|
||||
import shutil
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import IO, Callable
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import GifImagePlugin, Image, JpegImagePlugin
|
||||
|
||||
from .helper import djpeg_available, is_win32, netpbm_available
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import IO
|
||||
from .helper import cjpeg_available, djpeg_available, is_win32, netpbm_available
|
||||
|
||||
TEST_JPG = "Tests/images/hopper.jpg"
|
||||
TEST_GIF = "Tests/images/hopper.gif"
|
||||
|
@ -46,6 +42,11 @@ class TestShellInjection:
|
|||
assert isinstance(im, JpegImagePlugin.JpegImageFile)
|
||||
im.load_djpeg()
|
||||
|
||||
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
|
||||
def test_save_cjpeg_filename(self, tmp_path: Path) -> None:
|
||||
with Image.open(TEST_JPG) as im:
|
||||
self.assert_save_filename_check(tmp_path, im, JpegImagePlugin._save_cjpeg)
|
||||
|
||||
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
|
||||
def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None:
|
||||
with Image.open(TEST_GIF) as im:
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from typing import Any, Callable
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
|
@ -25,7 +25,8 @@ def test_wheel_modules() -> None:
|
|||
|
||||
elif sys.platform == "ios":
|
||||
# tkinter is not available on iOS
|
||||
expected_modules.remove("tkinter")
|
||||
# libavif is not available on iOS (for now)
|
||||
expected_modules -= {"tkinter", "avif"}
|
||||
|
||||
assert set(features.get_supported_modules()) == expected_modules
|
||||
|
||||
|
@ -38,6 +39,9 @@ def test_wheel_codecs() -> None:
|
|||
|
||||
def test_wheel_features() -> None:
|
||||
expected_features = {
|
||||
"webp_anim",
|
||||
"webp_mux",
|
||||
"transp_webp",
|
||||
"raqm",
|
||||
"fribidi",
|
||||
"harfbuzz",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
# install webp
|
||||
|
||||
archive=libwebp-1.6.0
|
||||
archive=libwebp-1.5.0
|
||||
|
||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||
|
||||
|
|
|
@ -12,6 +12,169 @@ Deprecated features
|
|||
Below are features which are considered deprecated. Where appropriate,
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. deprecated:: 10.4.0
|
||||
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -52,201 +215,18 @@ another mode before saving::
|
|||
im = Image.new("I", (1, 1))
|
||||
im.convert("I;16").save("out.png")
|
||||
|
||||
ImageCms.ImageCmsProfile.product_name and .product_info
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. deprecated:: 12.0.0
|
||||
|
||||
``ImageCms.ImageCmsProfile.product_name`` and the corresponding
|
||||
``.product_info`` attributes have been deprecated, and will be removed in
|
||||
Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
|
||||
|
||||
Removed features
|
||||
----------------
|
||||
|
||||
Deprecated features are only removed in major releases after an appropriate
|
||||
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
|
||||
.. versionremoved:: 12.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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 11.0.0
|
||||
|
||||
``TiffImagePlugin.IFD_LEGACY_API`` has been removed, as it was an unused setting.
|
||||
``TiffImagePlugin.IFD_LEGACY_API`` was removed, as it was an unused setting.
|
||||
|
||||
PSFile
|
||||
~~~~~~
|
||||
|
|
|
@ -101,28 +101,6 @@ Palette
|
|||
The palette mode (``P``) uses a color palette to define the actual color for
|
||||
each pixel.
|
||||
|
||||
.. _colors:
|
||||
|
||||
Colors
|
||||
------
|
||||
|
||||
To specify colors, you can use tuples with a value for each channel in the image, e.g.
|
||||
``Image.new("RGB", (1, 1), (255, 0, 0))``.
|
||||
|
||||
If an image has a single channel, you can use a single number instead, e.g.
|
||||
``Image.new("L", (1, 1), 255)``. For "F" mode images, floating point values are also
|
||||
accepted. In the case of "P" mode images, these will be indexes for the color palette.
|
||||
|
||||
If a single value is used for an image with more than one channel, it will still be
|
||||
parsed::
|
||||
|
||||
>>> from PIL import Image
|
||||
>>> im = Image.new("RGBA", (1, 1), 0x04030201)
|
||||
>>> im.getpixel((0, 0))
|
||||
(1, 2, 3, 4)
|
||||
|
||||
Some methods accept other forms, such as color names. See :ref:`color-names`.
|
||||
|
||||
Info
|
||||
----
|
||||
|
||||
|
|
|
@ -29,6 +29,10 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
|
|||
:target: https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml
|
||||
:alt: GitHub Actions build status (Test MinGW)
|
||||
|
||||
.. image:: https://github.com/python-pillow/Pillow/workflows/Test%20Cygwin/badge.svg
|
||||
:target: https://github.com/python-pillow/Pillow/actions/workflows/test-cygwin.yml
|
||||
:alt: GitHub Actions build status (Test Cygwin)
|
||||
|
||||
.. image:: https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg
|
||||
:target: https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml
|
||||
:alt: GitHub Actions build status (Wheels)
|
||||
|
|
|
@ -44,7 +44,7 @@ Many of Pillow's features require external libraries:
|
|||
|
||||
* **libtiff** provides compressed TIFF functionality
|
||||
|
||||
* Pillow has been tested with libtiff versions **4.0-4.7.0**
|
||||
* Pillow has been tested with libtiff versions **3.x** and **4.0-4.7.0**
|
||||
|
||||
* **libfreetype** provides type related services
|
||||
|
||||
|
@ -276,9 +276,10 @@ Build options
|
|||
|
||||
* Config setting: ``-C parallel=n``. Can also be given
|
||||
with environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
|
||||
multiprocessing to build the extensions. Setting ``-C parallel=n``
|
||||
multiprocessing to build the extension. Setting ``-C parallel=n``
|
||||
sets the number of CPUs to use to ``n``, or can disable parallel building by
|
||||
using a setting of 1. By default, it uses as many CPUs as are present.
|
||||
using a setting of 1. By default, it uses 4 CPUs, or if 4 are not
|
||||
available, as many as are present.
|
||||
|
||||
* Config settings: ``-C zlib=disable``, ``-C jpeg=disable``,
|
||||
``-C tiff=disable``, ``-C freetype=disable``, ``-C raqm=disable``,
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
Python,3.14,3.13,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5
|
||||
Pillow 12,Yes,Yes,Yes,Yes,Yes,,,,,
|
||||
Pillow 11,,Yes,Yes,Yes,Yes,Yes,,,,
|
||||
Pillow 10.1 - 10.4,,,Yes,Yes,Yes,Yes,Yes,,,
|
||||
Pillow 10.0,,,,Yes,Yes,Yes,Yes,,,
|
||||
Pillow 9.3 - 9.5,,,,Yes,Yes,Yes,Yes,Yes,,
|
||||
Pillow 9.0 - 9.2,,,,,Yes,Yes,Yes,Yes,,
|
||||
Pillow 8.3.2 - 8.4,,,,,Yes,Yes,Yes,Yes,Yes,
|
||||
Pillow 8.0 - 8.3.1,,,,,,Yes,Yes,Yes,Yes,
|
||||
Pillow 7.0 - 7.2,,,,,,,Yes,Yes,Yes,Yes
|
||||
Python,3.13,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5
|
||||
Pillow >= 11,Yes,Yes,Yes,Yes,Yes,,,,
|
||||
Pillow 10.1 - 10.4,,Yes,Yes,Yes,Yes,Yes,,,
|
||||
Pillow 10.0,,,Yes,Yes,Yes,Yes,,,
|
||||
Pillow 9.3 - 9.5,,,Yes,Yes,Yes,Yes,Yes,,
|
||||
Pillow 9.0 - 9.2,,,,Yes,Yes,Yes,Yes,,
|
||||
Pillow 8.3.2 - 8.4,,,,Yes,Yes,Yes,Yes,Yes,
|
||||
Pillow 8.0 - 8.3.1,,,,,Yes,Yes,Yes,Yes,
|
||||
Pillow 7.0 - 7.2,,,,,,Yes,Yes,Yes,Yes
|
||||
|
|
|
|
@ -19,13 +19,13 @@ These platforms are built and tested for every change.
|
|||
+==================================+============================+=====================+
|
||||
| Alpine | 3.12 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Amazon Linux 2 | 3.10 | x86-64 |
|
||||
| Amazon Linux 2 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Amazon Linux 2023 | 3.11 | x86-64 |
|
||||
| Amazon Linux 2023 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Arch | 3.13 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| CentOS Stream 9 | 3.10 | x86-64 |
|
||||
| CentOS Stream 9 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| CentOS Stream 10 | 3.12 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
|
@ -37,25 +37,27 @@ These platforms are built and tested for every change.
|
|||
+----------------------------------+----------------------------+---------------------+
|
||||
| Gentoo | 3.12 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 13 Ventura | 3.10 | x86-64 |
|
||||
| macOS 13 Ventura | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 14 Sonoma | 3.11, 3.12, 3.13, 3.14 | arm64 |
|
||||
| | PyPy3 | |
|
||||
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
|
||||
| | 3.14, PyPy3 | |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 24.04 LTS (Noble) | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
||||
| | 3.14, PyPy3 | |
|
||||
| Ubuntu Linux 24.04 LTS (Noble) | 3.9, 3.10, 3.11, | x86-64 |
|
||||
| | 3.12, 3.13, 3.14, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.12 | arm64v8, ppc64le, |
|
||||
| | | s390x |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2022 | 3.10 | x86 |
|
||||
| Windows Server 2022 | 3.9 | x86 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.11, 3.12, 3.13, 3.14, | x86-64 |
|
||||
| | PyPy3 | |
|
||||
| | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
||||
| | 3.14, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.12 (MinGW) | x86-64 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.9 (Cygwin) | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
|
||||
|
||||
|
@ -73,7 +75,7 @@ These platforms have been reported to work at the versions mentioned.
|
|||
| Operating system | | Tested Python | | Latest tested | | Tested |
|
||||
| | | versions | | Pillow version | | processors |
|
||||
+==================================+============================+==================+==============+
|
||||
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.3.0 |arm |
|
||||
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.2.1 |arm |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 3.8 | 10.4.0 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
|
|
|
@ -56,6 +56,7 @@ Functions
|
|||
.. autofunction:: get_display_profile
|
||||
.. autofunction:: isIntentSupported
|
||||
.. autofunction:: profileToProfile
|
||||
.. autofunction:: versions
|
||||
|
||||
CmsProfile
|
||||
----------
|
||||
|
|
|
@ -45,7 +45,9 @@ Colors
|
|||
^^^^^^
|
||||
|
||||
To specify colors, you can use numbers or tuples just as you would use with
|
||||
:py:meth:`PIL.Image.new`. See :ref:`colors` for more information.
|
||||
:py:meth:`PIL.Image.new` or :py:meth:`PIL.Image.Image.putpixel`. For “1”,
|
||||
“L”, and “I” images, use integers. For “RGB” images, use a 3-tuple containing
|
||||
integer values. For “F” images, use integer or floating point values.
|
||||
|
||||
For palette images (mode “P”), use integers as color indexes. In 1.1.4 and
|
||||
later, you can also use RGB 3-tuples or color names (see below). The drawing
|
||||
|
|
|
@ -59,7 +59,7 @@ Access using negative indexes is also possible. ::
|
|||
|
||||
Modifies the pixel at x,y. The color is given as a single
|
||||
numerical value for single band images, and a tuple for
|
||||
multi-band images. See :ref:`colors` for more information.
|
||||
multi-band images.
|
||||
|
||||
:param xy: The pixel coordinate, given as (x, y).
|
||||
:param color: The pixel value according to its mode,
|
||||
|
|
|
@ -21,7 +21,9 @@ with any Arrow provider or consumer in the Python ecosystem.
|
|||
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,
|
||||
with each pixel corresponding to the appropriate Arrow type.
|
||||
|
|
|
@ -60,6 +60,9 @@ 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.
|
||||
* ``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.
|
||||
* ``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.version_feature
|
||||
|
|
|
@ -53,6 +53,11 @@ on some Python versions.
|
|||
|
||||
An object that supports the read method.
|
||||
|
||||
.. py:data:: TypeGuard
|
||||
:value: typing.TypeGuard
|
||||
|
||||
See :py:obj:`typing.TypeGuard`.
|
||||
|
||||
:mod:`~PIL._util` module
|
||||
------------------------
|
||||
|
||||
|
|
|
@ -69,14 +69,12 @@ AVIF support in wheels
|
|||
|
||||
Support for reading and writing AVIF images is now included in Pillow's wheels, except
|
||||
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
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
12.0.0
|
||||
------
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
:cve:`YYYY-XXXXX`: TODO
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Backwards incompatible changes
|
||||
==============================
|
||||
|
||||
Python 3.9
|
||||
^^^^^^^^^^
|
||||
|
||||
Pillow has dropped support for Python 3.9,
|
||||
which reached end-of-life in October 2025.
|
||||
|
||||
ImageFile.raise_oserror
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``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
|
||||
============
|
||||
|
||||
ImageCms.ImageCmsProfile.product_name and .product_info
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``ImageCms.ImageCmsProfile.product_name`` and the corresponding
|
||||
``.product_info`` attributes have been deprecated, and will be removed in
|
||||
Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
|
||||
|
||||
API changes
|
||||
===========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API additions
|
||||
=============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Other changes
|
||||
=============
|
||||
|
||||
Python 3.14
|
||||
^^^^^^^^^^^
|
||||
|
||||
Pillow 11.3.0 had wheels built against Python 3.14 beta, available as a preview to help
|
||||
others prepare for 3.14, and to ensure Pillow could be used immediately at the release
|
||||
of 3.14.0 final (2025-10-07, :pep:`745`).
|
||||
|
||||
Pillow 12.0.0 now officially supports Python 3.14.
|
|
@ -14,7 +14,6 @@ expected to be backported to earlier versions.
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
12.0.0
|
||||
11.3.0
|
||||
11.2.1
|
||||
11.1.0
|
||||
|
|
|
@ -20,8 +20,6 @@ Backwards incompatible changes
|
|||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Deprecations
|
||||
============
|
||||
|
||||
|
|
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
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
[build-system]
|
||||
build-backend = "backend"
|
||||
requires = [
|
||||
"pybind11",
|
||||
"setuptools>=77",
|
||||
]
|
||||
backend-path = [
|
||||
|
@ -10,7 +9,7 @@ backend-path = [
|
|||
|
||||
[project]
|
||||
name = "pillow"
|
||||
description = "Python Imaging Library (fork)"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"Imaging",
|
||||
|
@ -20,15 +19,15 @@ license-files = [ "LICENSE" ]
|
|||
authors = [
|
||||
{ name = "Jeffrey A. Clark", email = "aclark@aclark.net" },
|
||||
]
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.9"
|
||||
classifiers = [
|
||||
"Development Status :: 6 - Mature",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
"Topic :: Multimedia :: Graphics",
|
||||
|
@ -67,7 +66,7 @@ optional-dependencies.tests = [
|
|||
"markdown2",
|
||||
"olefile",
|
||||
"packaging",
|
||||
"pyroma>=5",
|
||||
"pyroma",
|
||||
"pytest",
|
||||
"pytest-cov",
|
||||
"pytest-timeout",
|
||||
|
@ -75,6 +74,9 @@ optional-dependencies.tests = [
|
|||
"trove-classifiers>=2024.10.12",
|
||||
]
|
||||
|
||||
optional-dependencies.typing = [
|
||||
"typing-extensions; python_version<'3.10'",
|
||||
]
|
||||
optional-dependencies.xmp = [
|
||||
"defusedxml",
|
||||
]
|
||||
|
@ -185,8 +187,8 @@ lint.ignore = [
|
|||
"PT011", # pytest-raises-too-broad
|
||||
"PT012", # pytest-raises-with-multiple-statements
|
||||
"PT017", # pytest-assert-in-except
|
||||
"PYI026", # flake8-pyi: typing.TypeAlias added in Python 3.10
|
||||
"PYI034", # flake8-pyi: typing.Self added in Python 3.11
|
||||
"UP038", # pyupgrade: deprecated rule
|
||||
]
|
||||
lint.per-file-ignores."Tests/oss-fuzz/fuzz_font.py" = [
|
||||
"I002",
|
||||
|
@ -203,7 +205,7 @@ lint.isort.required-imports = [
|
|||
]
|
||||
|
||||
[tool.pyproject-fmt]
|
||||
max_supported_python = "3.14"
|
||||
max_supported_python = "3.13"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "-ra --color=auto"
|
||||
|
@ -212,7 +214,7 @@ testpaths = [
|
|||
]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.10"
|
||||
python_version = "3.9"
|
||||
pretty = true
|
||||
disallow_any_generics = true
|
||||
enable_error_code = "ignore-without-code"
|
||||
|
|
23
setup.py
23
setup.py
|
@ -17,20 +17,9 @@ import sys
|
|||
import warnings
|
||||
from collections.abc import Iterator
|
||||
|
||||
from pybind11.setup_helpers import ParallelCompile
|
||||
from setuptools import Extension, setup
|
||||
from setuptools.command.build_ext import build_ext
|
||||
|
||||
configuration: dict[str, list[str]] = {}
|
||||
|
||||
# parse configuration from _custom_build/backend.py
|
||||
while sys.argv[-1].startswith("--pillow-configuration="):
|
||||
_, key, value = sys.argv.pop().split("=", 2)
|
||||
configuration.setdefault(key, []).append(value)
|
||||
|
||||
default = int(configuration.get("parallel", ["0"])[-1])
|
||||
ParallelCompile("MAX_CONCURRENCY", default).install()
|
||||
|
||||
|
||||
def get_version() -> str:
|
||||
version_file = "src/PIL/_version.py"
|
||||
|
@ -38,6 +27,9 @@ def get_version() -> str:
|
|||
return f.read().split('"')[1]
|
||||
|
||||
|
||||
configuration: dict[str, list[str]] = {}
|
||||
|
||||
|
||||
PILLOW_VERSION = get_version()
|
||||
AVIF_ROOT = None
|
||||
FREETYPE_ROOT = None
|
||||
|
@ -394,7 +386,9 @@ class pil_build_ext(build_ext):
|
|||
cpu_count = os.cpu_count()
|
||||
if cpu_count is not None:
|
||||
try:
|
||||
self.parallel = int(os.environ.get("MAX_CONCURRENCY", cpu_count))
|
||||
self.parallel = int(
|
||||
os.environ.get("MAX_CONCURRENCY", min(4, cpu_count))
|
||||
)
|
||||
except TypeError:
|
||||
pass
|
||||
for x in self.feature:
|
||||
|
@ -1089,6 +1083,11 @@ ext_modules = [
|
|||
]
|
||||
|
||||
|
||||
# parse configuration from _custom_build/backend.py
|
||||
while sys.argv[-1].startswith("--pillow-configuration="):
|
||||
_, key, value = sys.argv.pop().split("=", 2)
|
||||
configuration.setdefault(key, []).append(value)
|
||||
|
||||
try:
|
||||
setup(
|
||||
cmdclass={"build_ext": pil_build_ext},
|
||||
|
|
|
@ -31,7 +31,7 @@ import os
|
|||
import subprocess
|
||||
from enum import IntEnum
|
||||
from functools import cached_property
|
||||
from typing import Any, NamedTuple, cast
|
||||
from typing import IO, Any, Literal, NamedTuple, Union, cast
|
||||
|
||||
from . import (
|
||||
Image,
|
||||
|
@ -49,8 +49,6 @@ from ._util import DeferredError
|
|||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from typing import IO, Literal
|
||||
|
||||
from . import _imaging
|
||||
from ._typing import Buffer
|
||||
|
||||
|
@ -537,7 +535,7 @@ def _normalize_mode(im: Image.Image) -> Image.Image:
|
|||
return im.convert("L")
|
||||
|
||||
|
||||
_Palette = bytes | bytearray | list[int] | ImagePalette.ImagePalette
|
||||
_Palette = Union[bytes, bytearray, list[int], ImagePalette.ImagePalette]
|
||||
|
||||
|
||||
def _normalize_palette(
|
||||
|
|
|
@ -21,14 +21,10 @@ See the GIMP distribution for more information.)
|
|||
from __future__ import annotations
|
||||
|
||||
from math import log, pi, sin, sqrt
|
||||
from typing import IO, Callable
|
||||
|
||||
from ._binary import o8
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from typing import IO
|
||||
|
||||
EPSILON = 1e-10
|
||||
"""""" # Enable auto-doc for data member
|
||||
|
||||
|
|
|
@ -17,10 +17,7 @@ from __future__ import annotations
|
|||
|
||||
import re
|
||||
from io import BytesIO
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from typing import IO
|
||||
from typing import IO
|
||||
|
||||
|
||||
class GimpPaletteFile:
|
||||
|
|
|
@ -25,6 +25,7 @@ import sys
|
|||
from typing import IO
|
||||
|
||||
from . import Image, ImageFile, PngImagePlugin, features
|
||||
from ._deprecate import deprecate
|
||||
|
||||
enable_jpeg2k = features.check_codec("jpg_2000")
|
||||
if enable_jpeg2k:
|
||||
|
@ -274,25 +275,34 @@ class IcnsImageFile(ImageFile.ImageFile):
|
|||
self.best_size[1] * self.best_size[2],
|
||||
)
|
||||
|
||||
@property
|
||||
def size(self) -> tuple[int, int]:
|
||||
@property # type: ignore[override]
|
||||
def size(self) -> tuple[int, int] | tuple[int, int, int]:
|
||||
return self._size
|
||||
|
||||
@size.setter
|
||||
def size(self, value: tuple[int, int]) -> None:
|
||||
# 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
|
||||
def size(self, value: tuple[int, int] | tuple[int, int, int]) -> None:
|
||||
if len(value) == 3:
|
||||
deprecate("Setting size to (width, height, scale)", 12, "load(scale)")
|
||||
if value in self.info["sizes"]:
|
||||
self._size = value # type: ignore[assignment]
|
||||
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"
|
||||
raise ValueError(msg)
|
||||
|
||||
def load(self, scale: int | None = None) -> Image.core.PixelAccess | None:
|
||||
if scale is not None:
|
||||
if scale is not None or len(self.size) == 3:
|
||||
if scale is None and len(self.size) == 3:
|
||||
scale = self.size[2]
|
||||
assert scale is not None
|
||||
width, height = self.size[:2]
|
||||
self.size = width * scale, height * scale
|
||||
self.best_size = width, height, scale
|
||||
|
|
|
@ -38,9 +38,10 @@ import struct
|
|||
import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections.abc import MutableMapping
|
||||
from collections.abc import Callable, Iterator, MutableMapping, Sequence
|
||||
from enum import IntEnum
|
||||
from typing import IO, Protocol, cast
|
||||
from types import ModuleType
|
||||
from typing import IO, Any, Literal, Protocol, cast
|
||||
|
||||
# VERSION was removed in Pillow 6.0.0.
|
||||
# PILLOW_VERSION was removed in Pillow 9.0.0.
|
||||
|
@ -63,12 +64,6 @@ try:
|
|||
except ImportError:
|
||||
ElementTree = None
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterator, Sequence
|
||||
from types import ModuleType
|
||||
from typing import Any, Literal
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -120,6 +115,21 @@ except ImportError as v:
|
|||
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
|
||||
|
||||
|
@ -209,7 +219,7 @@ if TYPE_CHECKING:
|
|||
from IPython.lib.pretty import PrettyPrinter
|
||||
|
||||
from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin
|
||||
from ._typing import CapsuleType, NumpyArray, StrOrBytesPath
|
||||
from ._typing import CapsuleType, NumpyArray, StrOrBytesPath, TypeGuard
|
||||
ID: list[str] = []
|
||||
OPEN: dict[
|
||||
str,
|
||||
|
@ -970,6 +980,9 @@ class Image:
|
|||
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||
"""
|
||||
|
||||
if mode in ("BGR;15", "BGR;16", "BGR;24"):
|
||||
deprecate(mode, 12)
|
||||
|
||||
self.load()
|
||||
|
||||
has_transparency = "transparency" in self.info
|
||||
|
@ -1735,10 +1748,9 @@ class Image:
|
|||
details).
|
||||
|
||||
Instead of an image, the source can be a integer or tuple
|
||||
containing pixel values. The method then fills the region
|
||||
with the given color. When creating RGB images, you can
|
||||
also use color strings as supported by the ImageColor module. See
|
||||
:ref:`colors` for more information.
|
||||
containing pixel values. The method then fills the region
|
||||
with the given color. When creating RGB images, you can
|
||||
also use color strings as supported by the ImageColor module.
|
||||
|
||||
If a mask is given, this method updates only the regions
|
||||
indicated by the mask. You can use either "1", "L", "LA", "RGBA"
|
||||
|
@ -1994,8 +2006,7 @@ class Image:
|
|||
sequence ends. The scale and offset values are used to adjust the
|
||||
sequence values: **pixel = value*scale + offset**.
|
||||
|
||||
:param data: A flattened sequence object. See :ref:`colors` for more
|
||||
information about values.
|
||||
:param data: A flattened sequence object.
|
||||
:param scale: An optional scale value. The default is 1.0.
|
||||
:param offset: An optional offset value. The default is 0.0.
|
||||
"""
|
||||
|
@ -2054,7 +2065,7 @@ class Image:
|
|||
Modifies the pixel at the given position. The color is given as
|
||||
a single numerical value for single-band images, and a tuple for
|
||||
multi-band images. In addition to this, RGB and RGBA tuples are
|
||||
accepted for P and PA images. See :ref:`colors` for more information.
|
||||
accepted for P and PA images.
|
||||
|
||||
Note that this method is relatively slow. For more extensive changes,
|
||||
use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw`
|
||||
|
@ -2218,6 +2229,8 @@ class Image:
|
|||
:py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`,
|
||||
:py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`.
|
||||
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.BICUBIC`. See: :ref:`concept-filters`.
|
||||
:param box: An optional 4-tuple of floats providing
|
||||
|
@ -2240,7 +2253,8 @@ class Image:
|
|||
"""
|
||||
|
||||
if resample is None:
|
||||
resample = Resampling.BICUBIC
|
||||
bgr = self.mode.startswith("BGR;")
|
||||
resample = Resampling.NEAREST if bgr else Resampling.BICUBIC
|
||||
elif resample not in (
|
||||
Resampling.NEAREST,
|
||||
Resampling.BILINEAR,
|
||||
|
@ -3062,15 +3076,18 @@ def new(
|
|||
:param mode: The mode to use for the new image. See:
|
||||
:ref:`concept-modes`.
|
||||
:param size: A 2-tuple, containing (width, height) in pixels.
|
||||
:param color: What color to use for the image. Default is black. If given,
|
||||
this should be a single integer or floating point value for single-band
|
||||
modes, and a tuple for multi-band modes (one value per band). When
|
||||
creating RGB or HSV images, you can also use color strings as supported
|
||||
by the ImageColor module. See :ref:`colors` for more information. If the
|
||||
color is None, the image is not initialised.
|
||||
:param color: What color to use for the image. Default is black.
|
||||
If given, this should be a single integer or floating point value
|
||||
for single-band modes, and a tuple for multi-band modes (one value
|
||||
per band). When creating RGB or HSV images, you can also use color
|
||||
strings as supported by the ImageColor module. If the color is
|
||||
None, the image is not initialised.
|
||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||
"""
|
||||
|
||||
if mode in ("BGR;15", "BGR;16", "BGR;24"):
|
||||
deprecate(mode, 12)
|
||||
|
||||
_check_size(size)
|
||||
|
||||
if color is None:
|
||||
|
|
|
@ -25,7 +25,7 @@ from enum import IntEnum, IntFlag
|
|||
from functools import reduce
|
||||
from typing import Any, Literal, SupportsFloat, SupportsInt, Union
|
||||
|
||||
from . import Image
|
||||
from . import Image, __version__
|
||||
from ._deprecate import deprecate
|
||||
from ._typing import SupportsRead
|
||||
|
||||
|
@ -108,6 +108,20 @@ pyCMS
|
|||
_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)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------.
|
||||
|
||||
|
||||
|
@ -234,7 +248,9 @@ class ImageCmsProfile:
|
|||
low-level profile object
|
||||
|
||||
"""
|
||||
self.filename: str | None = None
|
||||
self.filename = None
|
||||
self.product_name = None # profile.product_name
|
||||
self.product_info = None # profile.product_info
|
||||
|
||||
if isinstance(profile, str):
|
||||
if sys.platform == "win32":
|
||||
|
@ -255,13 +271,6 @@ class ImageCmsProfile:
|
|||
msg = "Invalid type for Profile" # type: ignore[unreachable]
|
||||
raise TypeError(msg)
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
if name in ("product_name", "product_info"):
|
||||
deprecate(f"ImageCms.ImageCmsProfile.{name}", 13)
|
||||
return None
|
||||
msg = f"'{self.__class__.__name__}' object has no attribute '{name}'"
|
||||
raise AttributeError(msg)
|
||||
|
||||
def tobytes(self) -> bytes:
|
||||
"""
|
||||
Returns the profile in a format suitable for embedding in
|
||||
|
@ -292,6 +301,31 @@ class ImageCmsTransform(Image.ImagePointHandler):
|
|||
proof_intent: Intent = Intent.ABSOLUTE_COLORIMETRIC,
|
||||
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:
|
||||
self.transform = core.buildTransform(
|
||||
input.profile, output.profile, input_mode, output_mode, intent, flags
|
||||
|
@ -1074,3 +1108,16 @@ def isIntentSupported(
|
|||
return -1
|
||||
except (AttributeError, OSError, TypeError, ValueError) as 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__
|
||||
|
|
|
@ -34,23 +34,21 @@ from __future__ import annotations
|
|||
import math
|
||||
import struct
|
||||
from collections.abc import Sequence
|
||||
from typing import cast
|
||||
from types import ModuleType
|
||||
from typing import Any, AnyStr, Callable, Union, cast
|
||||
|
||||
from . import Image, ImageColor
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from types import ModuleType
|
||||
from typing import Any, AnyStr
|
||||
|
||||
from . import ImageDraw2, ImageFont
|
||||
from ._typing import Coords
|
||||
from ._deprecate import deprecate
|
||||
from ._typing import Coords
|
||||
|
||||
# experimental access to the outline API
|
||||
Outline: Callable[[], Image.core._Outline] = Image.core.outline
|
||||
|
||||
_Ink = float | tuple[int, ...] | str
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from . import ImageDraw2, ImageFont
|
||||
|
||||
_Ink = Union[float, tuple[int, ...], str]
|
||||
|
||||
"""
|
||||
A simple 2D drawing interface for PIL images.
|
||||
|
@ -1011,11 +1009,16 @@ def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
|
|||
return ImageDraw(im, mode)
|
||||
|
||||
|
||||
def getdraw(im: Image.Image | None = None) -> tuple[ImageDraw2.Draw | None, ModuleType]:
|
||||
def getdraw(
|
||||
im: Image.Image | None = None, hints: list[str] | None = None
|
||||
) -> tuple[ImageDraw2.Draw | None, ModuleType]:
|
||||
"""
|
||||
:param im: The image to draw in.
|
||||
:param hints: An optional list of hints. Deprecated.
|
||||
:returns: A (drawing context, drawing resource factory) tuple.
|
||||
"""
|
||||
if hints is not None:
|
||||
deprecate("'hints' parameter", 12)
|
||||
from . import ImageDraw2
|
||||
|
||||
draw = ImageDraw2.Draw(im) if im is not None else None
|
||||
|
|
|
@ -37,6 +37,7 @@ import struct
|
|||
from typing import IO, Any, NamedTuple, cast
|
||||
|
||||
from . import ExifTags, Image
|
||||
from ._deprecate import deprecate
|
||||
from ._util import DeferredError, is_path
|
||||
|
||||
TYPE_CHECKING = False
|
||||
|
@ -82,6 +83,16 @@ def _get_oserror(error: int, *, encoder: bool) -> OSError:
|
|||
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:
|
||||
# sort on offset
|
||||
return t[2]
|
||||
|
|
|
@ -19,14 +19,11 @@ from __future__ import annotations
|
|||
import abc
|
||||
import functools
|
||||
from collections.abc import Sequence
|
||||
from typing import cast
|
||||
from types import ModuleType
|
||||
from typing import Any, Callable, cast
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
|
||||
from . import _imaging
|
||||
from ._typing import NumpyArray
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ from io import BytesIO
|
|||
from types import ModuleType
|
||||
from typing import IO, Any, BinaryIO, TypedDict, cast
|
||||
|
||||
from . import Image
|
||||
from . import Image, features
|
||||
from ._typing import StrOrBytesPath
|
||||
from ._util import DeferredError, is_path
|
||||
|
||||
|
@ -236,6 +236,21 @@ class FreeTypeFont:
|
|||
self.index = index
|
||||
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):
|
||||
layout_engine = Layout.BASIC
|
||||
if core.HAVE_RAQM:
|
||||
|
|
|
@ -17,14 +17,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import builtins
|
||||
from types import CodeType
|
||||
from typing import Any, Callable
|
||||
|
||||
from . import Image, _imagingmath
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from types import CodeType
|
||||
from typing import Any
|
||||
from ._deprecate import deprecate
|
||||
|
||||
|
||||
class _Operand:
|
||||
|
@ -236,7 +233,11 @@ ops = {
|
|||
}
|
||||
|
||||
|
||||
def lambda_eval(expression: Callable[[dict[str, Any]], Any], **kw: Any) -> Any:
|
||||
def lambda_eval(
|
||||
expression: Callable[[dict[str, Any]], Any],
|
||||
options: dict[str, Any] = {},
|
||||
**kw: Any,
|
||||
) -> Any:
|
||||
"""
|
||||
Returns the result of an image function.
|
||||
|
||||
|
@ -245,13 +246,23 @@ def lambda_eval(expression: Callable[[dict[str, Any]], Any], **kw: Any) -> Any:
|
|||
:py:func:`~PIL.Image.merge` function.
|
||||
|
||||
: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.
|
||||
:return: The expression result. This is usually an image object, but can
|
||||
also be an integer, a floating point value, or a pixel tuple,
|
||||
depending on the expression.
|
||||
"""
|
||||
|
||||
if options:
|
||||
deprecate(
|
||||
"ImageMath.lambda_eval options",
|
||||
12,
|
||||
"ImageMath.lambda_eval keyword arguments",
|
||||
)
|
||||
|
||||
args: dict[str, Any] = ops.copy()
|
||||
args.update(options)
|
||||
args.update(kw)
|
||||
for k, v in args.items():
|
||||
if isinstance(v, Image.Image):
|
||||
|
@ -264,7 +275,11 @@ def lambda_eval(expression: Callable[[dict[str, Any]], Any], **kw: Any) -> Any:
|
|||
return out
|
||||
|
||||
|
||||
def unsafe_eval(expression: str, **kw: Any) -> Any:
|
||||
def unsafe_eval(
|
||||
expression: str,
|
||||
options: dict[str, Any] = {},
|
||||
**kw: Any,
|
||||
) -> Any:
|
||||
"""
|
||||
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
|
||||
|
@ -276,19 +291,29 @@ def unsafe_eval(expression: str, **kw: Any) -> Any:
|
|||
:py:func:`~PIL.Image.merge` function.
|
||||
|
||||
: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.
|
||||
: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.
|
||||
"""
|
||||
|
||||
if options:
|
||||
deprecate(
|
||||
"ImageMath.unsafe_eval options",
|
||||
12,
|
||||
"ImageMath.unsafe_eval keyword arguments",
|
||||
)
|
||||
|
||||
# build execution namespace
|
||||
args: dict[str, Any] = ops.copy()
|
||||
for k in kw:
|
||||
for k in [*options, *kw]:
|
||||
if "__" in k or hasattr(builtins, k):
|
||||
msg = f"'{k}' not allowed"
|
||||
raise ValueError(msg)
|
||||
|
||||
args.update(options)
|
||||
args.update(kw)
|
||||
for k, v in args.items():
|
||||
if isinstance(v, Image.Image):
|
||||
|
@ -312,3 +337,32 @@ def unsafe_eval(expression: str, **kw: Any) -> Any:
|
|||
return out.im
|
||||
except AttributeError:
|
||||
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,6 +18,8 @@ import sys
|
|||
from functools import lru_cache
|
||||
from typing import NamedTuple
|
||||
|
||||
from ._deprecate import deprecate
|
||||
|
||||
|
||||
class ModeDescriptor(NamedTuple):
|
||||
"""Wrapper for mode strings."""
|
||||
|
@ -55,11 +57,16 @@ def getmode(mode: str) -> ModeDescriptor:
|
|||
"HSV": ("RGB", "L", ("H", "S", "V"), "|u1"),
|
||||
# extra experimental modes
|
||||
"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"),
|
||||
"PA": ("RGB", "L", ("P", "A"), "|u1"),
|
||||
}
|
||||
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]
|
||||
return ModeDescriptor(mode, bands, base_mode, base_type, type_str)
|
||||
|
||||
|
|
|
@ -19,18 +19,23 @@ from __future__ import annotations
|
|||
|
||||
import sys
|
||||
from io import BytesIO
|
||||
from typing import Any, Callable, Union
|
||||
|
||||
from . import Image
|
||||
from ._util import is_path
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
import PyQt6
|
||||
import PySide6
|
||||
|
||||
from . import ImageFile
|
||||
|
||||
QBuffer: type
|
||||
QByteArray = Union[PyQt6.QtCore.QByteArray, PySide6.QtCore.QByteArray]
|
||||
QIODevice = Union[PyQt6.QtCore.QIODevice, PySide6.QtCore.QIODevice]
|
||||
QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage]
|
||||
QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap]
|
||||
|
||||
qt_version: str | None
|
||||
qt_versions = [
|
||||
|
@ -44,15 +49,11 @@ for version, qt_module in qt_versions:
|
|||
try:
|
||||
qRgba: Callable[[int, int, int, int], int]
|
||||
if qt_module == "PyQt6":
|
||||
from PyQt6.QtCore import QBuffer, QByteArray, QIODevice
|
||||
from PyQt6.QtCore import QBuffer, QIODevice
|
||||
from PyQt6.QtGui import QImage, QPixmap, qRgba
|
||||
elif qt_module == "PySide6":
|
||||
from PySide6.QtCore import ( # type: ignore[assignment]
|
||||
QBuffer,
|
||||
QByteArray,
|
||||
QIODevice,
|
||||
)
|
||||
from PySide6.QtGui import QImage, QPixmap, qRgba # type: ignore[assignment]
|
||||
from PySide6.QtCore import QBuffer, QIODevice
|
||||
from PySide6.QtGui import QImage, QPixmap, qRgba
|
||||
except (ImportError, RuntimeError):
|
||||
continue
|
||||
qt_is_installed = True
|
||||
|
@ -182,7 +183,7 @@ def _toqclass_helper(im: Image.Image | str | QByteArray) -> dict[str, Any]:
|
|||
|
||||
if qt_is_installed:
|
||||
|
||||
class ImageQt(QImage):
|
||||
class ImageQt(QImage): # type: ignore[misc]
|
||||
def __init__(self, im: Image.Image | str | QByteArray) -> None:
|
||||
"""
|
||||
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
|
||||
|
|
|
@ -16,11 +16,9 @@
|
|||
##
|
||||
from __future__ import annotations
|
||||
|
||||
from . import Image
|
||||
from typing import Callable
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from . import Image
|
||||
|
||||
|
||||
class Iterator:
|
||||
|
|
|
@ -16,16 +16,26 @@
|
|||
#
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from io import BytesIO
|
||||
from typing import cast
|
||||
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i16be as i16
|
||||
from ._binary import i32be as i32
|
||||
from ._deprecate import deprecate
|
||||
|
||||
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
|
||||
|
||||
|
@ -38,6 +48,20 @@ def _i8(c: int | bytes) -> int:
|
|||
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
|
||||
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
|
||||
|
|
|
@ -18,15 +18,11 @@ from __future__ import annotations
|
|||
import io
|
||||
import os
|
||||
import struct
|
||||
from typing import cast
|
||||
from collections.abc import Callable
|
||||
from typing import IO, cast
|
||||
|
||||
from . import Image, ImageFile, ImagePalette, _binary
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from typing import IO
|
||||
|
||||
|
||||
class BoxReader:
|
||||
"""
|
||||
|
|
|
@ -42,18 +42,18 @@ import subprocess
|
|||
import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
from typing import IO, Any
|
||||
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i16be as i16
|
||||
from ._binary import i32be as i32
|
||||
from ._binary import o8
|
||||
from ._binary import o16be as o16
|
||||
from ._deprecate import deprecate
|
||||
from .JpegPresets import presets
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from typing import IO, Any
|
||||
|
||||
from .MpoImagePlugin import MpoImageFile
|
||||
|
||||
#
|
||||
|
@ -393,6 +393,12 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
|
||||
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]:
|
||||
return super().__getstate__() + [self.layers, self.layer]
|
||||
|
||||
|
@ -846,6 +852,16 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
)
|
||||
|
||||
|
||||
def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||
# ALTERNATIVE: handle JPEGs via the IJG command line utilities.
|
||||
tempfile = im._dump()
|
||||
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
|
||||
try:
|
||||
os.unlink(tempfile)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
##
|
||||
# Factory for making JPEG and MPO instances
|
||||
def jpeg_factory(
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from typing import BinaryIO, Callable
|
||||
|
||||
from . import FontFile, Image
|
||||
from ._binary import i8
|
||||
|
@ -26,11 +27,6 @@ from ._binary import i16le as l16
|
|||
from ._binary import i32be as b32
|
||||
from ._binary import i32le as l32
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from typing import BinaryIO
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# declarations
|
||||
|
||||
|
|
|
@ -8,15 +8,7 @@ import os
|
|||
import re
|
||||
import time
|
||||
import zlib
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from typing import IO
|
||||
|
||||
_DictBase = collections.UserDict[str | bytes, Any]
|
||||
else:
|
||||
_DictBase = collections.UserDict
|
||||
from typing import IO, Any, NamedTuple, Union
|
||||
|
||||
|
||||
# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
|
||||
|
@ -259,6 +251,13 @@ class PdfArray(list[Any]):
|
|||
return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"
|
||||
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
_DictBase = collections.UserDict[Union[str, bytes], Any]
|
||||
else:
|
||||
_DictBase = collections.UserDict
|
||||
|
||||
|
||||
class PdfDict(_DictBase):
|
||||
def __setattr__(self, key: str, value: Any) -> None:
|
||||
if key == "data":
|
||||
|
|
|
@ -38,8 +38,9 @@ import re
|
|||
import struct
|
||||
import warnings
|
||||
import zlib
|
||||
from collections.abc import Callable
|
||||
from enum import IntEnum
|
||||
from typing import IO, NamedTuple, cast
|
||||
from typing import IO, Any, NamedTuple, NoReturn, cast
|
||||
|
||||
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
|
||||
from ._binary import i16be as i16
|
||||
|
@ -52,9 +53,6 @@ from ._util import DeferredError
|
|||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from typing import Any, NoReturn
|
||||
|
||||
from . import _imaging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
|
@ -47,24 +47,23 @@ import math
|
|||
import os
|
||||
import struct
|
||||
import warnings
|
||||
from collections.abc import Callable, MutableMapping
|
||||
from collections.abc import Iterator, MutableMapping
|
||||
from fractions import Fraction
|
||||
from numbers import Number, Rational
|
||||
from typing import IO, Any, cast
|
||||
from typing import IO, Any, Callable, NoReturn, cast
|
||||
|
||||
from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags
|
||||
from ._binary import i16be as i16
|
||||
from ._binary import i32be as i32
|
||||
from ._binary import o8
|
||||
from ._deprecate import deprecate
|
||||
from ._typing import StrOrBytesPath
|
||||
from ._util import DeferredError, is_path
|
||||
from .TiffTags import TYPES
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
from typing import NoReturn
|
||||
|
||||
from ._typing import Buffer, IntegralLike, StrOrBytesPath
|
||||
from ._typing import Buffer, IntegralLike
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -285,6 +284,9 @@ PREFIXES = [
|
|||
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:
|
||||
return prefix.startswith(tuple(PREFIXES))
|
||||
|
@ -1932,6 +1934,9 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
# Custom items are supported for int, float, unicode, string and byte
|
||||
# values. Other types and tuples require a tagtype.
|
||||
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:
|
||||
types[tag] = TiffTags.LONG8
|
||||
elif tag in ifd.tagtype:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
from typing import IO, Any
|
||||
|
||||
from . import Image, ImageFile
|
||||
|
||||
|
@ -11,9 +12,6 @@ try:
|
|||
except ImportError:
|
||||
SUPPORTED = False
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from typing import IO, Any
|
||||
|
||||
_VP8_MODES_BY_IDENTIFIER = {
|
||||
b"VP8 ": "RGB",
|
||||
|
|
|
@ -46,6 +46,8 @@ def deprecate(
|
|||
elif when <= int(__version__.split(".")[0]):
|
||||
msg = f"{deprecated} {is_} deprecated and should be removed."
|
||||
raise RuntimeError(msg)
|
||||
elif when == 12:
|
||||
removed = "Pillow 12 (2025-10-15)"
|
||||
elif when == 13:
|
||||
removed = "Pillow 13 (2026-10-15)"
|
||||
else:
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import datetime
|
||||
import sys
|
||||
from typing import Literal, SupportsFloat, TypeAlias, TypedDict
|
||||
from typing import Literal, SupportsFloat, TypedDict
|
||||
|
||||
from ._typing import CapsuleType
|
||||
|
||||
littlecms_version: str | None
|
||||
|
||||
_Tuple3f: TypeAlias = tuple[float, float, float]
|
||||
_Tuple2x3f: TypeAlias = tuple[_Tuple3f, _Tuple3f]
|
||||
_Tuple3x3f: TypeAlias = tuple[_Tuple3f, _Tuple3f, _Tuple3f]
|
||||
_Tuple3f = tuple[float, float, float]
|
||||
_Tuple2x3f = tuple[_Tuple3f, _Tuple3f]
|
||||
_Tuple3x3f = tuple[_Tuple3f, _Tuple3f, _Tuple3f]
|
||||
|
||||
class _IccMeasurementCondition(TypedDict):
|
||||
observer: int
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from typing import Any, Callable
|
||||
|
||||
from . import ImageFont, _imaging
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
import os
|
||||
import sys
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Protocol, TypeVar
|
||||
from typing import Any, Protocol, TypeVar, Union
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
|
@ -12,8 +12,8 @@ if TYPE_CHECKING:
|
|||
try:
|
||||
import numpy.typing as npt
|
||||
|
||||
NumpyArray = npt.NDArray[Any]
|
||||
except ImportError:
|
||||
NumpyArray = npt.NDArray[Any] # requires numpy>=1.21
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
|
@ -26,8 +26,19 @@ if sys.version_info >= (3, 12):
|
|||
else:
|
||||
Buffer = Any
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from typing import TypeGuard
|
||||
else:
|
||||
try:
|
||||
from typing_extensions import TypeGuard
|
||||
except ImportError:
|
||||
|
||||
Coords = Sequence[float] | Sequence[Sequence[float]]
|
||||
class TypeGuard: # type: ignore[no-redef]
|
||||
def __class_getitem__(cls, item: Any) -> type[bool]:
|
||||
return bool
|
||||
|
||||
|
||||
Coords = Union[Sequence[float], Sequence[Sequence[float]]]
|
||||
|
||||
|
||||
_T_co = TypeVar("_T_co", covariant=True)
|
||||
|
@ -37,7 +48,7 @@ class SupportsRead(Protocol[_T_co]):
|
|||
def read(self, length: int = ..., /) -> _T_co: ...
|
||||
|
||||
|
||||
StrOrBytesPath = str | bytes | os.PathLike[str] | os.PathLike[bytes]
|
||||
StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]
|
||||
|
||||
|
||||
__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead"]
|
||||
__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"]
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, NoReturn
|
||||
|
||||
TYPE_CHECKING = False
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, NoReturn, TypeGuard
|
||||
|
||||
from ._typing import StrOrBytesPath
|
||||
from ._typing import StrOrBytesPath, TypeGuard
|
||||
|
||||
|
||||
def is_path(f: Any) -> TypeGuard[StrOrBytesPath]:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Master version for Pillow
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = "12.0.0.dev0"
|
||||
__version__ = "11.3.0"
|
||||
|
|
|
@ -9,6 +9,7 @@ from typing import IO
|
|||
import PIL
|
||||
|
||||
from . import Image
|
||||
from ._deprecate import deprecate
|
||||
|
||||
modules = {
|
||||
"pil": ("PIL._imaging", "PILLOW_VERSION"),
|
||||
|
@ -119,7 +120,10 @@ def get_supported_codecs() -> list[str]:
|
|||
return [f for f in codecs if check_codec(f)]
|
||||
|
||||
|
||||
features: dict[str, tuple[str, str, 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"),
|
||||
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
|
||||
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
|
||||
|
@ -145,8 +149,12 @@ def check_feature(feature: str) -> bool | None:
|
|||
|
||||
module, flag, ver = features[feature]
|
||||
|
||||
if isinstance(flag, bool):
|
||||
deprecate(f'check_feature("{feature}")', 12)
|
||||
try:
|
||||
imported_module = __import__(module, fromlist=["PIL"])
|
||||
if isinstance(flag, bool):
|
||||
return flag
|
||||
return getattr(imported_module, flag)
|
||||
except ModuleNotFoundError:
|
||||
return None
|
||||
|
@ -176,7 +184,17 @@ def get_supported_features() -> list[str]:
|
|||
"""
|
||||
:returns: A list of all supported features.
|
||||
"""
|
||||
return [f for f in features if check_feature(f)]
|
||||
supported_features = []
|
||||
for f, (module, flag, _) in features.items():
|
||||
if flag is True:
|
||||
for feature, (feature_module, _) in modules.items():
|
||||
if feature_module == module:
|
||||
if check_module(feature):
|
||||
supported_features.append(f)
|
||||
break
|
||||
elif check_feature(f):
|
||||
supported_features.append(f)
|
||||
return supported_features
|
||||
|
||||
|
||||
def check(feature: str) -> bool | None:
|
||||
|
|
140
src/_imaging.c
140
src/_imaging.c
|
@ -681,6 +681,30 @@ getink(PyObject *color, Imaging im, char *ink) {
|
|||
} else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1626,33 +1650,54 @@ _putdata(ImagingObject *self, PyObject *args) {
|
|||
return NULL;
|
||||
}
|
||||
double value;
|
||||
int bigendian = 0;
|
||||
if (image->type == IMAGING_TYPE_SPECIAL) {
|
||||
// I;16*
|
||||
if (
|
||||
strcmp(image->mode, "I;16B") == 0
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
|| strcmp(image->mode, "I;16N") == 0
|
||||
#endif
|
||||
) {
|
||||
bigendian = 1;
|
||||
}
|
||||
}
|
||||
for (i = x = y = 0; i < n; i++) {
|
||||
set_value_to_item(seq, i);
|
||||
if (scale != 1.0 || offset != 0.0) {
|
||||
value = value * scale + offset;
|
||||
}
|
||||
if (image->bands == 1) {
|
||||
int bigendian = 0;
|
||||
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);
|
||||
// I;16*
|
||||
if (
|
||||
strcmp(image->mode, "I;16B") == 0
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
|| strcmp(image->mode, "I;16N") == 0
|
||||
#endif
|
||||
) {
|
||||
bigendian = 1;
|
||||
}
|
||||
}
|
||||
if (++x >= (int)image->xsize) {
|
||||
x = 0, y++;
|
||||
for (i = x = y = 0; i < n; i++) {
|
||||
set_value_to_item(seq, i);
|
||||
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 {
|
||||
// BGR;*
|
||||
int b;
|
||||
for (i = x = y = 0; i < n; i++) {
|
||||
char ink[4];
|
||||
|
||||
op = PySequence_Fast_GET_ITEM(seq, i);
|
||||
if (!op || !getink(op, image, ink)) {
|
||||
Py_DECREF(seq);
|
||||
return NULL;
|
||||
}
|
||||
/* 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 */
|
||||
|
@ -3724,6 +3769,18 @@ _getattr_bands(ImagingObject *self, void *closure) {
|
|||
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
|
||||
_ptr_destructor(PyObject *capsule) {
|
||||
PyObject *self = (PyObject *)PyCapsule_GetContext(capsule);
|
||||
|
@ -3738,6 +3795,27 @@ _getattr_ptr(ImagingObject *self, void *closure) {
|
|||
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 *
|
||||
_getattr_readonly(ImagingObject *self, void *closure) {
|
||||
return PyLong_FromLong(self->image->read_only);
|
||||
|
@ -3747,7 +3825,9 @@ static struct PyGetSetDef getsetters[] = {
|
|||
{"mode", (getter)_getattr_mode},
|
||||
{"size", (getter)_getattr_size},
|
||||
{"bands", (getter)_getattr_bands},
|
||||
{"id", (getter)_getattr_id},
|
||||
{"ptr", (getter)_getattr_ptr},
|
||||
{"unsafe_ptrs", (getter)_getattr_unsafe_ptrs},
|
||||
{"readonly", (getter)_getattr_readonly},
|
||||
{NULL}
|
||||
};
|
||||
|
@ -4352,6 +4432,16 @@ setup_module(PyObject *m) {
|
|||
PyObject *v = PyUnicode_FromString(ImagingTiffVersion());
|
||||
PyDict_SetItemString(d, "libtiff_version", v ? v : Py_None);
|
||||
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
|
||||
|
||||
|
|
|
@ -82,6 +82,31 @@ get_pixel_16B(Imaging im, int x, int y, void *color) {
|
|||
#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
|
||||
get_pixel_32(Imaging im, int x, int y, void *color) {
|
||||
memcpy(color, &im->image32[y][x], sizeof(INT32));
|
||||
|
@ -129,6 +154,16 @@ put_pixel_16B(Imaging im, int x, int y, const void *color) {
|
|||
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
|
||||
put_pixel_32L(Imaging im, int x, int y, const void *color) {
|
||||
memcpy(&im->image8[y][x * 4], color, 4);
|
||||
|
@ -177,6 +212,9 @@ ImagingAccessInit(void) {
|
|||
ADD("F", get_pixel_32, put_pixel_32);
|
||||
ADD("P", get_pixel_8, put_pixel_8);
|
||||
ADD("PA", get_pixel_32_2bands, put_pixel_32);
|
||||
ADD("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("RGBA", get_pixel_32, put_pixel_32);
|
||||
ADD("RGBa", get_pixel_32, put_pixel_32);
|
||||
|
|
|
@ -277,6 +277,38 @@ 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
|
||||
rgb2hsv_row(UINT8 *out, const UINT8 *in) { // following colorsys.py
|
||||
float h, s, rc, gc, bc, cr;
|
||||
|
@ -939,6 +971,9 @@ static struct {
|
|||
{"RGB", "I;16N", rgb2i16l},
|
||||
#endif
|
||||
{"RGB", "F", rgb2f},
|
||||
{"RGB", "BGR;15", rgb2bgr15},
|
||||
{"RGB", "BGR;16", rgb2bgr16},
|
||||
{"RGB", "BGR;24", rgb2bgr24},
|
||||
{"RGB", "RGBA", rgb2rgba},
|
||||
{"RGB", "RGBa", rgb2rgba},
|
||||
{"RGB", "RGBX", rgb2rgba},
|
||||
|
|
|
@ -132,15 +132,11 @@ ImagingGetHistogram(Imaging im, Imaging imMask, void *minmax) {
|
|||
ImagingSectionEnter(&cookie);
|
||||
for (y = 0; y < im->ysize; y++) {
|
||||
UINT8 *in = (UINT8 *)im->image[y];
|
||||
for (x = 0; x < im->xsize; x++, in += 4) {
|
||||
h->histogram[*in]++;
|
||||
if (im->bands == 2) {
|
||||
h->histogram[*(in + 3) + 256]++;
|
||||
} else {
|
||||
h->histogram[*(in + 1) + 256]++;
|
||||
h->histogram[*(in + 2) + 512]++;
|
||||
h->histogram[*(in + 3) + 768]++;
|
||||
}
|
||||
for (x = 0; x < im->xsize; x++) {
|
||||
h->histogram[(*in++)]++;
|
||||
h->histogram[(*in++) + 256]++;
|
||||
h->histogram[(*in++) + 512]++;
|
||||
h->histogram[(*in++) + 768]++;
|
||||
}
|
||||
}
|
||||
ImagingSectionLeave(&cookie);
|
||||
|
|
|
@ -471,6 +471,12 @@ copy2(UINT8 *out, const UINT8 *in, int pixels) {
|
|||
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
|
||||
copy4(UINT8 *out, const UINT8 *in, int pixels) {
|
||||
/* RGBA, CMYK quadruples */
|
||||
|
@ -651,6 +657,9 @@ static struct {
|
|||
{"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian.
|
||||
{"I;16L", "I;16N", 16, packI16N_I16},
|
||||
{"I;16B", "I;16N", 16, packI16N_I16B},
|
||||
{"BGR;15", "BGR;15", 16, copy2},
|
||||
{"BGR;16", "BGR;16", 16, copy2},
|
||||
{"BGR;24", "BGR;24", 24, copy3},
|
||||
|
||||
{NULL} /* sentinel */
|
||||
};
|
||||
|
|
|
@ -151,6 +151,36 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) {
|
|||
strcpy(im->band_names[2], "B");
|
||||
strcpy(im->band_names[3], "X");
|
||||
|
||||
} else if (strcmp(mode, "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) {
|
||||
/* 32-bit true colour images with padding */
|
||||
im->bands = im->pixelsize = 4;
|
||||
|
|
|
@ -884,6 +884,7 @@ ImagingLibTiffMergeFieldInfo(
|
|||
// Refer to libtiff docs (http://www.simplesystems.org/libtiff/addingtags.html)
|
||||
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
|
||||
uint32_t n;
|
||||
int status = 0;
|
||||
|
||||
// custom fields added with ImagingLibTiffMergeFieldInfo are only used for
|
||||
// decoding, ignore readcount;
|
||||
|
@ -906,7 +907,14 @@ ImagingLibTiffMergeFieldInfo(
|
|||
|
||||
n = sizeof(info) / sizeof(info[0]);
|
||||
|
||||
return TIFFMergeFieldInfo(clientstate->tiff, info, n);
|
||||
// Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7
|
||||
#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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user