Merge branch 'main' into windows1co
|
@ -10,7 +10,7 @@ environment:
|
||||||
TEST_OPTIONS:
|
TEST_OPTIONS:
|
||||||
DEPLOY: YES
|
DEPLOY: YES
|
||||||
matrix:
|
matrix:
|
||||||
- PYTHON: C:/Python311
|
- PYTHON: C:/Python312
|
||||||
ARCHITECTURE: x86
|
ARCHITECTURE: x86
|
||||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||||
- PYTHON: C:/Python38-x64
|
- PYTHON: C:/Python38-x64
|
||||||
|
@ -43,7 +43,7 @@ build_script:
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- cd c:\pillow
|
- cd c:\pillow
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout'
|
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout defusedxml numpy olefile pyroma'
|
||||||
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
||||||
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
|
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
|
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
|
||||||
|
|
|
@ -28,7 +28,8 @@ fi
|
||||||
|
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python3 -m pip install --upgrade wheel
|
python3 -m pip install --upgrade wheel
|
||||||
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
# TODO Update condition when cffi supports 3.13
|
||||||
|
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
|
||||||
python3 -m pip install coverage
|
python3 -m pip install coverage
|
||||||
python3 -m pip install defusedxml
|
python3 -m pip install defusedxml
|
||||||
python3 -m pip install olefile
|
python3 -m pip install olefile
|
||||||
|
@ -38,7 +39,8 @@ python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
if [[ $(uname) != CYGWIN* ]]; then
|
||||||
python3 -m pip install numpy
|
# TODO Update condition when NumPy supports 3.13
|
||||||
|
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi
|
||||||
|
|
||||||
# PyQt6 doesn't support PyPy3
|
# PyQt6 doesn't support PyPy3
|
||||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||||
|
@ -46,6 +48,16 @@ if [[ $(uname) != CYGWIN* ]]; then
|
||||||
python3 -m pip install pyqt6
|
python3 -m pip install pyqt6
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Pyroma uses non-isolated build and fails with old setuptools
|
||||||
|
if [[
|
||||||
|
$GHA_PYTHON_VERSION == pypy3.9
|
||||||
|
|| $GHA_PYTHON_VERSION == 3.8
|
||||||
|
|| $GHA_PYTHON_VERSION == 3.9
|
||||||
|
]]; then
|
||||||
|
# To match pyproject.toml
|
||||||
|
python3 -m pip install "setuptools>=67.8"
|
||||||
|
fi
|
||||||
|
|
||||||
# webp
|
# webp
|
||||||
pushd depends && ./install_webp.sh && popd
|
pushd depends && ./install_webp.sh && popd
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ indent_style = space
|
||||||
|
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.yml]
|
[*.{toml,yml}]
|
||||||
# Two-space indentation
|
# Two-space indentation
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
|
2
.github/workflows/cifuzz.yml
vendored
|
@ -2,6 +2,8 @@ name: CIFuzz
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/cifuzz.yml"
|
- ".github/workflows/cifuzz.yml"
|
||||||
- "**.c"
|
- "**.c"
|
||||||
|
|
2
.github/workflows/docs.yml
vendored
|
@ -2,6 +2,8 @@ name: Docs
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
|
7
.github/workflows/macos-install.sh
vendored
|
@ -5,7 +5,9 @@ set -e
|
||||||
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
|
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
|
||||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||||
|
|
||||||
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
# TODO Update condition when cffi supports 3.13
|
||||||
|
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
|
||||||
|
|
||||||
python3 -m pip install coverage
|
python3 -m pip install coverage
|
||||||
python3 -m pip install defusedxml
|
python3 -m pip install defusedxml
|
||||||
python3 -m pip install olefile
|
python3 -m pip install olefile
|
||||||
|
@ -14,7 +16,8 @@ python3 -m pip install -U pytest-cov
|
||||||
python3 -m pip install -U pytest-timeout
|
python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
|
|
||||||
python3 -m pip install numpy
|
# TODO Update condition when NumPy supports 3.13
|
||||||
|
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
10
.github/workflows/test-cygwin.yml
vendored
|
@ -2,13 +2,23 @@ name: Test Cygwin
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|
12
.github/workflows/test-docker.yml
vendored
|
@ -2,13 +2,23 @@ name: Test Docker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -41,8 +51,8 @@ jobs:
|
||||||
debian-11-bullseye-amd64,
|
debian-11-bullseye-amd64,
|
||||||
debian-12-bookworm-x86,
|
debian-12-bookworm-x86,
|
||||||
debian-12-bookworm-amd64,
|
debian-12-bookworm-amd64,
|
||||||
fedora-37-amd64,
|
|
||||||
fedora-38-amd64,
|
fedora-38-amd64,
|
||||||
|
fedora-39-amd64,
|
||||||
gentoo,
|
gentoo,
|
||||||
ubuntu-20.04-focal-amd64,
|
ubuntu-20.04-focal-amd64,
|
||||||
ubuntu-22.04-jammy-amd64,
|
ubuntu-22.04-jammy-amd64,
|
||||||
|
|
10
.github/workflows/test-mingw.yml
vendored
|
@ -2,13 +2,23 @@ name: Test MinGW
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|
4
.github/workflows/test-valgrind.yml
vendored
|
@ -1,9 +1,11 @@
|
||||||
name: Test Valgrind
|
name: Test Valgrind
|
||||||
|
|
||||||
# like the docker tests, but running valgrind only on *.c/*.h changes.
|
# like the Docker tests, but running valgrind only on *.c/*.h changes.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/test-valgrind.yml"
|
- ".github/workflows/test-valgrind.yml"
|
||||||
- "**.c"
|
- "**.c"
|
||||||
|
|
15
.github/workflows/test-windows.yml
vendored
|
@ -4,11 +4,19 @@ on:
|
||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -24,7 +32,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
|
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
|
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
@ -51,14 +59,15 @@ jobs:
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
allow-prereleases: true
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: ".github/workflows/test-windows.yml"
|
cache-dependency-path: ".github/workflows/test-windows.yml"
|
||||||
|
|
||||||
- name: Print build system information
|
- name: Print build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
- name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml
|
- name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma
|
||||||
run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml
|
run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
id: install
|
id: install
|
||||||
|
|
14
.github/workflows/test.yml
vendored
|
@ -2,13 +2,23 @@ name: Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -31,7 +41,8 @@ jobs:
|
||||||
python-version: [
|
python-version: [
|
||||||
"pypy3.10",
|
"pypy3.10",
|
||||||
"pypy3.9",
|
"pypy3.9",
|
||||||
"3.12-dev",
|
"3.13",
|
||||||
|
"3.12",
|
||||||
"3.11",
|
"3.11",
|
||||||
"3.10",
|
"3.10",
|
||||||
"3.9",
|
"3.9",
|
||||||
|
@ -54,6 +65,7 @@ jobs:
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
allow-prereleases: true
|
||||||
cache: pip
|
cache: pip
|
||||||
cache-dependency-path: ".ci/*.sh"
|
cache-dependency-path: ".ci/*.sh"
|
||||||
|
|
||||||
|
|
40
.github/workflows/wheels-build.sh
vendored
Executable file
|
@ -0,0 +1,40 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||||
|
# webp, zstd, xz, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb
|
||||||
|
# libxdmcp causes an issue on macOS < 11
|
||||||
|
# curl from brew requires zstd, use system curl
|
||||||
|
# if php is installed, brew tries to reinstall these after installing openblas
|
||||||
|
# remove lcms2 and libpng to fix building openjpeg on arm64
|
||||||
|
brew remove --ignore-dependencies webp zstd xz libpng libtiff libxcb libxdmcp curl php lcms2 ghostscript
|
||||||
|
|
||||||
|
brew install pkg-config
|
||||||
|
|
||||||
|
if [[ "$PLAT" == "arm64" ]]; then
|
||||||
|
export MACOSX_DEPLOYMENT_TARGET="11.0"
|
||||||
|
else
|
||||||
|
export MACOSX_DEPLOYMENT_TARGET="10.10"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$MB_PYTHON_VERSION" == pypy3* ]]; then
|
||||||
|
MB_PYTHON_OSX_VER="10.9"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "::group::Install a virtualenv"
|
||||||
|
source wheels/multibuild/common_utils.sh
|
||||||
|
source wheels/multibuild/travis_steps.sh
|
||||||
|
python3 -m pip install virtualenv
|
||||||
|
before_install
|
||||||
|
echo "::endgroup::"
|
||||||
|
|
||||||
|
echo "::group::Build wheel"
|
||||||
|
build_wheel
|
||||||
|
ls -l "${GITHUB_WORKSPACE}/${WHEEL_SDIR}/"
|
||||||
|
echo "::endgroup::"
|
||||||
|
|
||||||
|
if [[ $MACOSX_DEPLOYMENT_TARGET != "11.0" ]]; then
|
||||||
|
echo "::group::Test wheel"
|
||||||
|
install_run
|
||||||
|
echo "::endgroup::"
|
||||||
|
fi
|
69
.github/workflows/wheels-linux.yml
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
name: Build Linux wheels
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
artifacts-name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
env:
|
||||||
|
CONFIG_PATH: "wheels/config.sh"
|
||||||
|
REPO_DIR: "."
|
||||||
|
TEST_DEPENDS: "pytest pytest-timeout"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: ${{ matrix.python }} ${{ matrix.mb-ml-libc }}${{ matrix.mb-ml-ver }}
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python: [
|
||||||
|
"pypy3.9-7.3.13",
|
||||||
|
"pypy3.10-7.3.13",
|
||||||
|
"3.8",
|
||||||
|
"3.9",
|
||||||
|
"3.10",
|
||||||
|
"3.11",
|
||||||
|
"3.12",
|
||||||
|
]
|
||||||
|
mb-ml-libc: [ "manylinux" ]
|
||||||
|
mb-ml-ver: [ 2014, "_2_28" ]
|
||||||
|
include:
|
||||||
|
- python: "3.8"
|
||||||
|
mb-ml-libc: "musllinux"
|
||||||
|
mb-ml-ver: "_1_1"
|
||||||
|
- python: "3.9"
|
||||||
|
mb-ml-libc: "musllinux"
|
||||||
|
mb-ml-ver: "_1_1"
|
||||||
|
- python: "3.10"
|
||||||
|
mb-ml-libc: "musllinux"
|
||||||
|
mb-ml-ver: "_1_1"
|
||||||
|
- python: "3.11"
|
||||||
|
mb-ml-libc: "musllinux"
|
||||||
|
mb-ml-ver: "_1_1"
|
||||||
|
- python: "3.12"
|
||||||
|
mb-ml-libc: "musllinux"
|
||||||
|
mb-ml-ver: "_1_1"
|
||||||
|
env:
|
||||||
|
MB_PYTHON_VERSION: ${{ matrix.python }}
|
||||||
|
MB_ML_LIBC: ${{ matrix.mb-ml-libc }}
|
||||||
|
MB_ML_VER: ${{ matrix.mb-ml-ver }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
- name: Build Wheel
|
||||||
|
run: .github/workflows/wheels-build.sh
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.artifacts-name }}
|
||||||
|
path: wheelhouse/*.whl
|
||||||
|
# Uncomment to get SSH access for testing
|
||||||
|
# - name: Setup tmate session
|
||||||
|
# if: failure()
|
||||||
|
# uses: mxschmitt/action-tmate@v3
|
57
.github/workflows/wheels-macos.yml
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
name: Build macOS wheels
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
artifacts-name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
env:
|
||||||
|
CONFIG_PATH: "wheels/config.sh"
|
||||||
|
REPO_DIR: "."
|
||||||
|
TEST_DEPENDS: "pytest pytest-timeout"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: ${{ matrix.python }} ${{ matrix.platform }}
|
||||||
|
runs-on: "macos-latest"
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python: [
|
||||||
|
"pypy3.9-7.3.13",
|
||||||
|
"pypy3.10-7.3.13",
|
||||||
|
"3.8",
|
||||||
|
"3.9",
|
||||||
|
"3.10",
|
||||||
|
"3.11",
|
||||||
|
"3.12",
|
||||||
|
]
|
||||||
|
platform: [ "x86_64", "arm64" ]
|
||||||
|
exclude:
|
||||||
|
- python: "pypy3.9-7.3.13"
|
||||||
|
platform: "arm64"
|
||||||
|
- python: "pypy3.10-7.3.13"
|
||||||
|
platform: "arm64"
|
||||||
|
env:
|
||||||
|
PLAT: ${{ matrix.platform }}
|
||||||
|
MB_PYTHON_VERSION: ${{ matrix.python }}
|
||||||
|
TRAVIS_OS_NAME: "osx"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
- name: Build Wheel
|
||||||
|
run: .github/workflows/wheels-build.sh
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ inputs.artifacts-name }}
|
||||||
|
path: wheelhouse/*.whl
|
||||||
|
# Uncomment to get SSH access for testing
|
||||||
|
# - name: Setup tmate session
|
||||||
|
# if: failure()
|
||||||
|
# uses: mxschmitt/action-tmate@v3
|
64
.github/workflows/wheels.yml
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
name: Wheels
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/wheels*.yml"
|
||||||
|
- "wheels/*"
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/wheels*.yml"
|
||||||
|
- "wheels/*"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
macos:
|
||||||
|
uses: ./.github/workflows/wheels-macos.yml
|
||||||
|
with:
|
||||||
|
artifacts-name: "dist"
|
||||||
|
|
||||||
|
linux:
|
||||||
|
uses: ./.github/workflows/wheels-linux.yml
|
||||||
|
with:
|
||||||
|
artifacts-name: "dist"
|
||||||
|
|
||||||
|
sdist:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
cache: pip
|
||||||
|
cache-dependency-path: "Makefile"
|
||||||
|
|
||||||
|
- run: make sdist
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
path: dist/*.tar.gz
|
||||||
|
|
||||||
|
success:
|
||||||
|
permissions:
|
||||||
|
contents: none
|
||||||
|
needs: [macos, linux, sdist]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Wheels Successful
|
||||||
|
steps:
|
||||||
|
- name: Success
|
||||||
|
run: echo Wheels Successful
|
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "multibuild"]
|
||||||
|
path = wheels/multibuild
|
||||||
|
url = https://github.com/multi-build/multibuild.git
|
|
@ -1,20 +1,14 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v3.13.0
|
rev: v0.1.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: ruff
|
||||||
args: [--py38-plus]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
rev: 23.9.1
|
rev: 23.10.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--target-version=py38]
|
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/isort
|
|
||||||
rev: 5.12.0
|
|
||||||
hooks:
|
|
||||||
- id: isort
|
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/bandit
|
- repo: https://github.com/PyCQA/bandit
|
||||||
rev: 1.7.5
|
rev: 1.7.5
|
||||||
|
@ -23,32 +17,19 @@ repos:
|
||||||
args: [--severity-level=high]
|
args: [--severity-level=high]
|
||||||
files: ^src/
|
files: ^src/
|
||||||
|
|
||||||
- repo: https://github.com/asottile/yesqa
|
|
||||||
rev: v1.5.0
|
|
||||||
hooks:
|
|
||||||
- id: yesqa
|
|
||||||
|
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||||
rev: v1.5.4
|
rev: v1.5.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/flake8
|
|
||||||
rev: 6.1.0
|
|
||||||
hooks:
|
|
||||||
- id: flake8
|
|
||||||
additional_dependencies:
|
|
||||||
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat, flake8-logging]
|
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||||
rev: v1.10.0
|
rev: v1.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: python-check-blanket-noqa
|
|
||||||
- id: rst-backticks
|
- id: rst-backticks
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v4.5.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-executables-have-shebangs
|
- id: check-executables-have-shebangs
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
|
@ -61,17 +42,17 @@ repos:
|
||||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||||
|
|
||||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||||
rev: v0.6.8
|
rev: v0.8.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: sphinx-lint
|
- id: sphinx-lint
|
||||||
|
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
rev: 1.1.0
|
rev: 1.4.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
|
|
||||||
- repo: https://github.com/abravalheri/validate-pyproject
|
- repo: https://github.com/abravalheri/validate-pyproject
|
||||||
rev: v0.14
|
rev: v0.15
|
||||||
hooks:
|
hooks:
|
||||||
- id: validate-pyproject
|
- id: validate-pyproject
|
||||||
|
|
||||||
|
|
135
.travis.yml
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
if: tag IS present OR type = api
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- CONFIG_PATH=wheels/config.sh
|
||||||
|
- REPO_DIR=.
|
||||||
|
- PLAT=aarch64
|
||||||
|
- TEST_DEPENDS=pytest-timeout
|
||||||
|
|
||||||
|
language: python
|
||||||
|
# Default Python version is usually 3.6
|
||||||
|
python: "3.12"
|
||||||
|
dist: jammy
|
||||||
|
services: docker
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- name: "3.8 Focal manylinux2014 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER=2014
|
||||||
|
- MB_PYTHON_VERSION=3.8
|
||||||
|
- name: "3.8 Focal manylinux_2_28 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER="_2_28"
|
||||||
|
- MB_PYTHON_VERSION=3.8
|
||||||
|
- name: "3.8 musllinux_1_1 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER="_1_1"
|
||||||
|
- MB_ML_LIBC="musllinux"
|
||||||
|
- MB_PYTHON_VERSION=3.8
|
||||||
|
- name: "3.9 Focal manylinux2014 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER=2014
|
||||||
|
- MB_PYTHON_VERSION=3.9
|
||||||
|
- name: "3.9 Focal manylinux_2_28 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER="_2_28"
|
||||||
|
- MB_PYTHON_VERSION=3.9
|
||||||
|
- name: "3.9 musllinux_1_1 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER="_1_1"
|
||||||
|
- MB_ML_LIBC="musllinux"
|
||||||
|
- MB_PYTHON_VERSION=3.9
|
||||||
|
- name: "3.10 Focal manylinux2014 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER=2014
|
||||||
|
- MB_PYTHON_VERSION=3.10
|
||||||
|
- name: "3.10 Focal manylinux_2_28 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER="_2_28"
|
||||||
|
- MB_PYTHON_VERSION=3.10
|
||||||
|
- name: "3.10 musllinux_1_1 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER="_1_1"
|
||||||
|
- MB_ML_LIBC="musllinux"
|
||||||
|
- MB_PYTHON_VERSION=3.10
|
||||||
|
- name: "3.11 Focal manylinux_2_28 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER=2014
|
||||||
|
- MB_PYTHON_VERSION=3.11
|
||||||
|
- name: "3.11 Focal manylinux_2_28 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER="_2_28"
|
||||||
|
- MB_PYTHON_VERSION=3.11
|
||||||
|
- name: "3.11 musllinux_1_1 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER="_1_1"
|
||||||
|
- MB_ML_LIBC="musllinux"
|
||||||
|
- MB_PYTHON_VERSION=3.11
|
||||||
|
- name: "3.12 Focal manylinux_2_28 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER=2014
|
||||||
|
- MB_PYTHON_VERSION=3.12
|
||||||
|
- name: "3.12 Focal manylinux_2_28 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER="_2_28"
|
||||||
|
- MB_PYTHON_VERSION=3.12
|
||||||
|
- name: "3.12 musllinux_1_1 aarch64"
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
env:
|
||||||
|
- MB_ML_VER="_1_1"
|
||||||
|
- MB_ML_LIBC="musllinux"
|
||||||
|
- MB_PYTHON_VERSION=3.12
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- source wheels/multibuild/common_utils.sh
|
||||||
|
- source wheels/multibuild/travis_steps.sh
|
||||||
|
- before_install
|
||||||
|
|
||||||
|
install:
|
||||||
|
- build_multilinux aarch64 build_wheel
|
||||||
|
- ls -l "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/"
|
||||||
|
|
||||||
|
script:
|
||||||
|
- install_run
|
||||||
|
|
||||||
|
# Upload wheels to GitHub Releases
|
||||||
|
deploy:
|
||||||
|
provider: releases
|
||||||
|
api_key: $GITHUB_RELEASE_TOKEN
|
||||||
|
file_glob: true
|
||||||
|
file: "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/*.whl"
|
||||||
|
on:
|
||||||
|
repo: python-pillow/Pillow
|
||||||
|
tags: true
|
||||||
|
skip_cleanup: true
|
80
CHANGES.rst
|
@ -2,9 +2,75 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
10.1.0 (unreleased)
|
10.2.0 (unreleased)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
- Implement ``streamtype=1`` option for tables-only JPEG encoding #7491
|
||||||
|
[bgilbert, radarhere]
|
||||||
|
|
||||||
|
- If save_all PNG only has one frame, do not create animated image #7522
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed frombytes() for images with a zero dimension #7493
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
10.1.0 (2023-10-15)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Added TrueType default font to allow for different sizes #7354
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed invalid argument warning #7442
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added ImageOps cover method #7412
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Catch struct.error from truncated EXIF when reading JPEG DPI #7458
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Consider default image when selecting mode for PNG save_all #7437
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support BGR;15, BGR;16 and BGR;24 access, unpacking and putdata #7303
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added CMYK to RGB unpacker #7310
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved flexibility of XMP parsing #7274
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support reading 8-bit YCbCr TIFF images #7415
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow saving I;16B images as PNG #7302
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Corrected drawing I;16 points and writing I;16 text #7257
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Set blue channel to 128 for BC5S #7413
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Increase flexibility when reading IPTC fields #7319
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Set C palette to be empty by default #7289
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added gs_binary to control Ghostscript use on all platforms #7392
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Read bounding box information from the trailer of EPS files if specified #7382
|
||||||
|
[nopperl, radarhere]
|
||||||
|
|
||||||
|
- Added reading 8-bit color DDS images #7426
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added has_transparency_data #7420
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
- Fixed bug when reading BC5S DDS images #7401
|
- Fixed bug when reading BC5S DDS images #7401
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
@ -2137,7 +2203,7 @@ Changelog (Pillow)
|
||||||
- Cache EXIF information #3498
|
- Cache EXIF information #3498
|
||||||
[Glandos]
|
[Glandos]
|
||||||
|
|
||||||
- Added transparency for all PNG greyscale modes #3744
|
- Added transparency for all PNG grayscale modes #3744
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Fix deprecation warnings in Python 3.8 #3749
|
- Fix deprecation warnings in Python 3.8 #3749
|
||||||
|
@ -4639,7 +4705,7 @@ Changelog (Pillow)
|
||||||
- Fix Bicubic interpolation #970
|
- Fix Bicubic interpolation #970
|
||||||
[homm]
|
[homm]
|
||||||
|
|
||||||
- Support for 4-bit greyscale TIFF images #980
|
- Support for 4-bit grayscale TIFF images #980
|
||||||
[hugovk]
|
[hugovk]
|
||||||
|
|
||||||
- Updated manifest #957
|
- Updated manifest #957
|
||||||
|
@ -6714,7 +6780,7 @@ The test suite includes 750 individual tests.
|
||||||
|
|
||||||
- You can now convert directly between all modes supported by
|
- You can now convert directly between all modes supported by
|
||||||
PIL. When converting colour images to "P", PIL defaults to
|
PIL. When converting colour images to "P", PIL defaults to
|
||||||
a "web" palette and dithering. When converting greyscale
|
a "web" palette and dithering. When converting grayscale
|
||||||
images to "1", PIL uses a thresholding and dithering.
|
images to "1", PIL uses a thresholding and dithering.
|
||||||
|
|
||||||
- Added a "dither" option to "convert". By default, "convert"
|
- Added a "dither" option to "convert". By default, "convert"
|
||||||
|
@ -6792,13 +6858,13 @@ The test suite includes 530 individual tests.
|
||||||
- Fixed "paste" to allow a mask also for mode "F" images.
|
- Fixed "paste" to allow a mask also for mode "F" images.
|
||||||
|
|
||||||
- The BMP driver now saves mode "1" images. When loading images, the mode
|
- The BMP driver now saves mode "1" images. When loading images, the mode
|
||||||
is set to "L" for 8-bit files with greyscale palettes, and to "P" for
|
is set to "L" for 8-bit files with grayscale palettes, and to "P" for
|
||||||
other 8-bit files.
|
other 8-bit files.
|
||||||
|
|
||||||
- The IM driver now reads and saves "1" images (file modes "0 1" or "L 1").
|
- The IM driver now reads and saves "1" images (file modes "0 1" or "L 1").
|
||||||
|
|
||||||
- The JPEG and GIF drivers now saves "1" images. For JPEG, the image
|
- The JPEG and GIF drivers now saves "1" images. For JPEG, the image
|
||||||
is saved as 8-bit greyscale (it will load as mode "L"). For GIF, the
|
is saved as 8-bit grayscale (it will load as mode "L"). For GIF, the
|
||||||
image will be loaded as a "P" image.
|
image will be loaded as a "P" image.
|
||||||
|
|
||||||
- Fixed a potential buffer overrun in the GIF encoder.
|
- Fixed a potential buffer overrun in the GIF encoder.
|
||||||
|
@ -7102,7 +7168,7 @@ The test suite includes 400 individual tests.
|
||||||
drawing capabilities can be used to render vector and metafile
|
drawing capabilities can be used to render vector and metafile
|
||||||
formats.
|
formats.
|
||||||
|
|
||||||
- Added restricted drivers for images from Image Tools (greyscale
|
- Added restricted drivers for images from Image Tools (grayscale
|
||||||
only) and LabEye/IFUNC (common interchange modes only).
|
only) and LabEye/IFUNC (common interchange modes only).
|
||||||
|
|
||||||
- Some minor improvements to the sample scripts provided in the
|
- Some minor improvements to the sample scripts provided in the
|
||||||
|
|
|
@ -5,8 +5,10 @@ include *.md
|
||||||
include *.py
|
include *.py
|
||||||
include *.rst
|
include *.rst
|
||||||
include *.sh
|
include *.sh
|
||||||
|
include *.toml
|
||||||
include *.txt
|
include *.txt
|
||||||
include *.yaml
|
include *.yaml
|
||||||
|
include .flake8
|
||||||
include LICENSE
|
include LICENSE
|
||||||
include Makefile
|
include Makefile
|
||||||
include tox.ini
|
include tox.ini
|
||||||
|
@ -29,3 +31,4 @@ global-exclude .git*
|
||||||
global-exclude *.pyc
|
global-exclude *.pyc
|
||||||
global-exclude *.so
|
global-exclude *.so
|
||||||
prune .ci
|
prune .ci
|
||||||
|
prune wheels
|
||||||
|
|
8
Makefile
|
@ -49,7 +49,7 @@ help:
|
||||||
@echo " install make and install"
|
@echo " install make and install"
|
||||||
@echo " install-coverage make and install with C coverage"
|
@echo " install-coverage make and install with C coverage"
|
||||||
@echo " lint run the lint checks"
|
@echo " lint run the lint checks"
|
||||||
@echo " lint-fix run Black and isort to (mostly) fix lint issues"
|
@echo " lint-fix run Ruff to (mostly) fix lint issues"
|
||||||
@echo " release-test run code and package tests before release"
|
@echo " release-test run code and package tests before release"
|
||||||
@echo " test run tests on installed Pillow"
|
@echo " test run tests on installed Pillow"
|
||||||
|
|
||||||
|
@ -118,6 +118,6 @@ lint:
|
||||||
.PHONY: lint-fix
|
.PHONY: lint-fix
|
||||||
lint-fix:
|
lint-fix:
|
||||||
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
|
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
|
||||||
python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort
|
python3 -m black .
|
||||||
python3 -m black --target-version py38 .
|
python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff
|
||||||
python3 -m isort .
|
python3 -m ruff --fix .
|
||||||
|
|
14
README.md
|
@ -45,12 +45,12 @@ As of 2019, Pillow development is
|
||||||
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
|
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
|
||||||
alt="AppVeyor CI build status (Windows)"
|
alt="AppVeyor CI build status (Windows)"
|
||||||
src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
|
src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
|
||||||
<a href="https://github.com/python-pillow/pillow-wheels/actions"><img
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
|
||||||
alt="GitHub Actions wheels build status (Wheels)"
|
alt="GitHub Actions build status (Wheels)"
|
||||||
src="https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
|
||||||
<a href="https://app.travis-ci.com/github/python-pillow/pillow-wheels"><img
|
<a href="https://app.travis-ci.com/github/python-pillow/Pillow"><img
|
||||||
alt="Travis CI wheels build status (aarch64)"
|
alt="Travis CI wheels build status (aarch64)"
|
||||||
src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels"></a>
|
src="https://img.shields.io/travis/com/python-pillow/Pillow/main.svg?label=aarch64%20wheels"></a>
|
||||||
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
|
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
|
||||||
alt="Code coverage"
|
alt="Code coverage"
|
||||||
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
||||||
|
@ -74,9 +74,9 @@ As of 2019, Pillow development is
|
||||||
<a href="https://pypi.org/project/Pillow/"><img
|
<a href="https://pypi.org/project/Pillow/"><img
|
||||||
alt="Number of PyPI downloads"
|
alt="Number of PyPI downloads"
|
||||||
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
||||||
<a href="https://bestpractices.coreinfrastructure.org/projects/6331"><img
|
<a href="https://www.bestpractices.dev/projects/6331"><img
|
||||||
alt="OpenSSF Best Practices"
|
alt="OpenSSF Best Practices"
|
||||||
src="https://bestpractices.coreinfrastructure.org/projects/6331/badge"></a>
|
src="https://www.bestpractices.dev/projects/6331/badge"></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
33
RELEASING.md
|
@ -10,7 +10,7 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||||
* [ ] Develop and prepare release in `main` branch.
|
* [ ] Develop and prepare release in `main` branch.
|
||||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
|
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
|
||||||
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI and GitHub Actions.
|
* [ ] Check that all of the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) and [Travis CI](https://app.travis-ci.com/github/python-pillow/pillow) jobs by manually triggering them.
|
||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||||
* [ ] Update `CHANGES.rst`.
|
* [ ] Update `CHANGES.rst`.
|
||||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||||
|
@ -20,12 +20,8 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
git tag 5.2.0
|
git tag 5.2.0
|
||||||
git push --tags
|
git push --tags
|
||||||
```
|
```
|
||||||
* [ ] Create and check source distribution:
|
* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||||
```bash
|
* [ ] Check and upload all source and binary distributions e.g.:
|
||||||
make sdist
|
|
||||||
```
|
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
|
|
||||||
* [ ] Check and upload all binaries and source distributions e.g.:
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m twine check --strict dist/*
|
python3 -m twine check --strict dist/*
|
||||||
python3 -m twine upload dist/Pillow-5.2.0*
|
python3 -m twine upload dist/Pillow-5.2.0*
|
||||||
|
@ -59,8 +55,8 @@ Released as needed for security, installation or critical bug fixes.
|
||||||
```bash
|
```bash
|
||||||
make sdist
|
make sdist
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
|
* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||||
* [ ] Check and upload all binaries and source distributions e.g.:
|
* [ ] Check and upload all source and binary distributions e.g.:
|
||||||
```bash
|
```bash
|
||||||
python3 -m twine check --strict dist/*
|
python3 -m twine check --strict dist/*
|
||||||
python3 -m twine upload dist/Pillow-5.2.1*
|
python3 -m twine upload dist/Pillow-5.2.1*
|
||||||
|
@ -90,26 +86,23 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
```bash
|
```bash
|
||||||
make sdist
|
make sdist
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
|
* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
|
||||||
```bash
|
```bash
|
||||||
git push origin 2.5.x
|
git push origin 2.5.x
|
||||||
```
|
```
|
||||||
|
|
||||||
## Binary Distributions
|
## Source and Binary Distributions
|
||||||
|
|
||||||
### macOS and Linux
|
### macOS and Linux
|
||||||
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
* [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||||
|
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/python-pillow/pillow-wheels
|
gh run download --dir dist
|
||||||
cd pillow-wheels
|
# select dist
|
||||||
./update-pillow-tag.sh [[release tag]]
|
|
||||||
```
|
|
||||||
* [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases)
|
|
||||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli) from the main repo:
|
|
||||||
```bash
|
|
||||||
gh release download --dir dist --pattern "*.whl" --repo python-pillow/pillow-wheels
|
|
||||||
```
|
```
|
||||||
|
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
|
||||||
|
and copy into `dist`.
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
|
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
|
||||||
|
|
|
@ -45,7 +45,7 @@ def test_direct():
|
||||||
|
|
||||||
assert caccess[(0, 0)] == access[(0, 0)]
|
assert caccess[(0, 0)] == access[(0, 0)]
|
||||||
|
|
||||||
print("Size: %sx%s" % im.size)
|
print(f"Size: {im.width}x{im.height}")
|
||||||
timer(iterate_get, "PyAccess - get", im.size, access)
|
timer(iterate_get, "PyAccess - get", im.size, access)
|
||||||
timer(iterate_set, "PyAccess - set", im.size, access)
|
timer(iterate_set, "PyAccess - set", im.size, access)
|
||||||
timer(iterate_get, "C-api - get", im.size, caccess)
|
timer(iterate_get, "C-api - get", im.size, caccess)
|
||||||
|
|
|
@ -95,7 +95,7 @@ def assert_image_equal(a, b, msg=None):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert False, msg or "got different content"
|
pytest.fail(msg or "got different content")
|
||||||
|
|
||||||
|
|
||||||
def assert_image_equal_tofile(a, filename, msg=None, mode=None):
|
def assert_image_equal_tofile(a, filename, msg=None, mode=None):
|
||||||
|
|
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 331 B |
Before Width: | Height: | Size: 668 B After Width: | Height: | Size: 668 B |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
BIN
Tests/images/default_font_freetype.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
BIN
Tests/images/imagedraw_default_font_size.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 180 B |
BIN
Tests/images/palette.dds
Normal file
BIN
Tests/images/truncated_exif_dpi.jpg
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
Tests/images/xmp_no_prefix.jpg
Normal file
After Width: | Height: | Size: 788 B |
BIN
Tests/images/xmp_padded.jpg
Normal file
After Width: | Height: | Size: 778 B |
BIN
Tests/images/zero_bb_eof_before_boundingbox.eps
Normal file
BIN
Tests/images/zero_bb_trailer.eps
Normal file
|
@ -15,7 +15,7 @@
|
||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
python3 setup.py build --build-base=/tmp/build install
|
python3 -m pip install .
|
||||||
|
|
||||||
# Build fuzzers in $OUT.
|
# Build fuzzers in $OUT.
|
||||||
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
|
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
|
||||||
|
|
|
@ -231,13 +231,13 @@ def test_apng_mode():
|
||||||
assert im.getpixel((0, 0)) == (0, 0, 128, 191)
|
assert im.getpixel((0, 0)) == (0, 0, 128, 191)
|
||||||
assert im.getpixel((64, 32)) == (0, 0, 128, 191)
|
assert im.getpixel((64, 32)) == (0, 0, 128, 191)
|
||||||
|
|
||||||
with Image.open("Tests/images/apng/mode_greyscale.png") as im:
|
with Image.open("Tests/images/apng/mode_grayscale.png") as im:
|
||||||
assert im.mode == "L"
|
assert im.mode == "L"
|
||||||
im.seek(im.n_frames - 1)
|
im.seek(im.n_frames - 1)
|
||||||
assert im.getpixel((0, 0)) == 128
|
assert im.getpixel((0, 0)) == 128
|
||||||
assert im.getpixel((64, 32)) == 255
|
assert im.getpixel((64, 32)) == 255
|
||||||
|
|
||||||
with Image.open("Tests/images/apng/mode_greyscale_alpha.png") as im:
|
with Image.open("Tests/images/apng/mode_grayscale_alpha.png") as im:
|
||||||
assert im.mode == "LA"
|
assert im.mode == "LA"
|
||||||
im.seek(im.n_frames - 1)
|
im.seek(im.n_frames - 1)
|
||||||
assert im.getpixel((0, 0)) == (128, 191)
|
assert im.getpixel((0, 0)) == (128, 191)
|
||||||
|
@ -350,7 +350,7 @@ def test_apng_save(tmp_path):
|
||||||
im.load()
|
im.load()
|
||||||
assert not im.is_animated
|
assert not im.is_animated
|
||||||
assert im.n_frames == 1
|
assert im.n_frames == 1
|
||||||
assert im.get_format_mimetype() == "image/apng"
|
assert im.get_format_mimetype() == "image/png"
|
||||||
assert im.info.get("default_image") is None
|
assert im.info.get("default_image") is None
|
||||||
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
@ -450,26 +450,29 @@ def test_apng_save_duration_loop(tmp_path):
|
||||||
test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150]
|
test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150]
|
||||||
)
|
)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.load()
|
|
||||||
assert im.n_frames == 1
|
assert im.n_frames == 1
|
||||||
assert im.info.get("duration") == 750
|
assert "duration" not in im.info
|
||||||
|
|
||||||
|
different_frame = Image.new("RGBA", (128, 64))
|
||||||
|
frame.save(
|
||||||
|
test_file,
|
||||||
|
save_all=True,
|
||||||
|
append_images=[frame, different_frame],
|
||||||
|
duration=[500, 100, 150],
|
||||||
|
)
|
||||||
|
with Image.open(test_file) as im:
|
||||||
|
assert im.n_frames == 2
|
||||||
|
assert im.info["duration"] == 600
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
assert im.info["duration"] == 150
|
||||||
|
|
||||||
# test info duration
|
# test info duration
|
||||||
frame.info["duration"] = 750
|
frame.info["duration"] = 300
|
||||||
frame.save(test_file, save_all=True)
|
frame.save(test_file, save_all=True, append_images=[frame, different_frame])
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert im.info.get("duration") == 750
|
assert im.n_frames == 2
|
||||||
|
assert im.info["duration"] == 600
|
||||||
|
|
||||||
def test_apng_save_duplicate_duration(tmp_path):
|
|
||||||
test_file = str(tmp_path / "temp.png")
|
|
||||||
frame = Image.new("RGB", (1, 1))
|
|
||||||
|
|
||||||
# Test a single duration is correctly combined across duplicate frames
|
|
||||||
frame.save(test_file, save_all=True, append_images=[frame, frame], duration=500)
|
|
||||||
with Image.open(test_file) as im:
|
|
||||||
assert im.n_frames == 1
|
|
||||||
assert im.info.get("duration") == 1500
|
|
||||||
|
|
||||||
|
|
||||||
def test_apng_save_disposal(tmp_path):
|
def test_apng_save_disposal(tmp_path):
|
||||||
|
@ -673,10 +676,17 @@ def test_seek_after_close():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("RGBA", "RGB", "P"))
|
@pytest.mark.parametrize("mode", ("RGBA", "RGB", "P"))
|
||||||
def test_different_modes_in_later_frames(mode, tmp_path):
|
@pytest.mark.parametrize("default_image", (True, False))
|
||||||
|
@pytest.mark.parametrize("duplicate", (True, False))
|
||||||
|
def test_different_modes_in_later_frames(mode, default_image, duplicate, tmp_path):
|
||||||
test_file = str(tmp_path / "temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
|
|
||||||
im = Image.new("L", (1, 1))
|
im = Image.new("L", (1, 1))
|
||||||
im.save(test_file, save_all=True, append_images=[Image.new(mode, (1, 1))])
|
im.save(
|
||||||
|
test_file,
|
||||||
|
save_all=True,
|
||||||
|
default_image=default_image,
|
||||||
|
append_images=[im.convert(mode) if duplicate else Image.new(mode, (1, 1), 1)],
|
||||||
|
)
|
||||||
with Image.open(test_file) as reloaded:
|
with Image.open(test_file) as reloaded:
|
||||||
assert reloaded.mode == mode
|
assert reloaded.mode == mode
|
||||||
|
|
|
@ -159,7 +159,7 @@ def test_rle8():
|
||||||
with Image.open("Tests/images/hopper_rle8.bmp") as im:
|
with Image.open("Tests/images/hopper_rle8.bmp") as im:
|
||||||
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
||||||
|
|
||||||
with Image.open("Tests/images/hopper_rle8_greyscale.bmp") as im:
|
with Image.open("Tests/images/hopper_rle8_grayscale.bmp") as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
|
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
|
||||||
|
|
||||||
# This test image has been manually hexedited
|
# This test image has been manually hexedited
|
||||||
|
|
|
@ -298,6 +298,11 @@ def test_dxt5_colorblock_alpha_issue_4142():
|
||||||
assert px[2] != 0
|
assert px[2] != 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_palette():
|
||||||
|
with Image.open("Tests/images/palette.dds") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
|
||||||
|
|
||||||
|
|
||||||
def test_unimplemented_pixel_format():
|
def test_unimplemented_pixel_format():
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
with Image.open("Tests/images/unimplemented_pixel_format.dds"):
|
with Image.open("Tests/images/unimplemented_pixel_format.dds"):
|
||||||
|
|
|
@ -8,6 +8,7 @@ from .helper import (
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
assert_image_similar_tofile,
|
assert_image_similar_tofile,
|
||||||
hopper,
|
hopper,
|
||||||
|
is_win32,
|
||||||
mark_if_feature_version,
|
mark_if_feature_version,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
)
|
)
|
||||||
|
@ -98,6 +99,20 @@ def test_load():
|
||||||
assert im.load()[0, 0] == (255, 255, 255)
|
assert im.load()[0, 0] == (255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def test_binary():
|
||||||
|
if HAS_GHOSTSCRIPT:
|
||||||
|
assert EpsImagePlugin.gs_binary is not None
|
||||||
|
else:
|
||||||
|
assert EpsImagePlugin.gs_binary is False
|
||||||
|
|
||||||
|
if not is_win32():
|
||||||
|
assert EpsImagePlugin.gs_windows_binary is None
|
||||||
|
elif not HAS_GHOSTSCRIPT:
|
||||||
|
assert EpsImagePlugin.gs_windows_binary is False
|
||||||
|
else:
|
||||||
|
assert EpsImagePlugin.gs_windows_binary is not None
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
with pytest.raises(SyntaxError):
|
with pytest.raises(SyntaxError):
|
||||||
|
@ -404,3 +419,18 @@ def test_timeout(test_file):
|
||||||
with pytest.raises(Image.UnidentifiedImageError):
|
with pytest.raises(Image.UnidentifiedImageError):
|
||||||
with Image.open(f):
|
with Image.open(f):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_bounding_box_in_trailer():
|
||||||
|
# Check bounding boxes are parsed in the same way
|
||||||
|
# when specified in the header and the trailer
|
||||||
|
with Image.open("Tests/images/zero_bb_trailer.eps") as trailer_image, Image.open(
|
||||||
|
FILE1
|
||||||
|
) as header_image:
|
||||||
|
assert trailer_image.size == header_image.size
|
||||||
|
|
||||||
|
|
||||||
|
def test_eof_before_bounding_box():
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
with Image.open("Tests/images/zero_bb_eof_before_boundingbox.eps"):
|
||||||
|
pass
|
||||||
|
|
|
@ -205,14 +205,14 @@ def test_optimize_full_l():
|
||||||
|
|
||||||
|
|
||||||
def test_optimize_if_palette_can_be_reduced_by_half():
|
def test_optimize_if_palette_can_be_reduced_by_half():
|
||||||
with Image.open("Tests/images/test.colors.gif") as im:
|
im = Image.new("P", (8, 1))
|
||||||
# Reduce dimensions because original is too big for _get_optimize()
|
im.palette = ImagePalette.raw("RGB", bytes((0, 0, 0) * 150))
|
||||||
im = im.resize((591, 443))
|
for i in range(8):
|
||||||
im_rgb = im.convert("RGB")
|
im.putpixel((i, 0), (i + 1, 0, 0))
|
||||||
|
|
||||||
for optimize, colors in ((False, 256), (True, 8)):
|
for optimize, colors in ((False, 256), (True, 8)):
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im_rgb.save(out, "GIF", optimize=optimize)
|
im.save(out, "GIF", optimize=optimize)
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
assert len(reloaded.palette.palette) // 3 == colors
|
assert len(reloaded.palette.palette) // 3 == colors
|
||||||
|
|
||||||
|
@ -590,7 +590,7 @@ def test_save_dispose(tmp_path):
|
||||||
def test_dispose2_palette(tmp_path):
|
def test_dispose2_palette(tmp_path):
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
|
|
||||||
# Four colors: white, grey, black, red
|
# Four colors: white, gray, black, red
|
||||||
circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)]
|
circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)]
|
||||||
|
|
||||||
im_list = []
|
im_list = []
|
||||||
|
@ -1180,18 +1180,17 @@ def test_palette_save_L(tmp_path):
|
||||||
|
|
||||||
|
|
||||||
def test_palette_save_P(tmp_path):
|
def test_palette_save_P(tmp_path):
|
||||||
# Pass in a different palette, then construct what the image would look like.
|
im = Image.new("P", (1, 2))
|
||||||
# Forcing a non-straight grayscale palette.
|
im.putpixel((0, 1), 1)
|
||||||
|
|
||||||
im = hopper("P")
|
|
||||||
palette = bytes(255 - i // 3 for i in range(768))
|
|
||||||
|
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
im.save(out, palette=palette)
|
im.save(out, palette=bytes((1, 2, 3, 4, 5, 6)))
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
im.putpalette(palette)
|
reloaded_rgb = reloaded.convert("RGB")
|
||||||
assert_image_equal(reloaded, im)
|
|
||||||
|
assert reloaded_rgb.getpixel((0, 0)) == (1, 2, 3)
|
||||||
|
assert reloaded_rgb.getpixel((0, 1)) == (4, 5, 6)
|
||||||
|
|
||||||
|
|
||||||
def test_palette_save_duplicate_entries(tmp_path):
|
def test_palette_save_duplicate_entries(tmp_path):
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
from io import StringIO
|
from io import BytesIO, StringIO
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, IptcImagePlugin
|
from PIL import Image, IptcImagePlugin
|
||||||
|
|
||||||
|
@ -30,6 +32,36 @@ def test_getiptcinfo_jpg_found():
|
||||||
assert iptc[(2, 101)] == b"Hungary"
|
assert iptc[(2, 101)] == b"Hungary"
|
||||||
|
|
||||||
|
|
||||||
|
def test_getiptcinfo_fotostation():
|
||||||
|
# Arrange
|
||||||
|
with open(TEST_FILE, "rb") as fp:
|
||||||
|
data = bytearray(fp.read())
|
||||||
|
data[86] = 240
|
||||||
|
f = BytesIO(data)
|
||||||
|
with Image.open(f) as im:
|
||||||
|
# Act
|
||||||
|
iptc = IptcImagePlugin.getiptcinfo(im)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
for tag in iptc.keys():
|
||||||
|
if tag[0] == 240:
|
||||||
|
return
|
||||||
|
pytest.fail("FotoStation tag not found")
|
||||||
|
|
||||||
|
|
||||||
|
def test_getiptcinfo_zero_padding():
|
||||||
|
# Arrange
|
||||||
|
with Image.open(TEST_FILE) as im:
|
||||||
|
im.info["photoshop"][0x0404] += b"\x00\x00\x00"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
iptc = IptcImagePlugin.getiptcinfo(im)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert isinstance(iptc, dict)
|
||||||
|
assert len(iptc) == 3
|
||||||
|
|
||||||
|
|
||||||
def test_getiptcinfo_tiff_none():
|
def test_getiptcinfo_tiff_none():
|
||||||
# Arrange
|
# Arrange
|
||||||
with Image.open("Tests/images/hopper.tif") as im:
|
with Image.open("Tests/images/hopper.tif") as im:
|
||||||
|
|
|
@ -767,6 +767,13 @@ class TestFileJpeg:
|
||||||
# This should return the default
|
# This should return the default
|
||||||
assert im.info.get("dpi") == (72, 72)
|
assert im.info.get("dpi") == (72, 72)
|
||||||
|
|
||||||
|
def test_dpi_exif_truncated(self):
|
||||||
|
# Arrange
|
||||||
|
with Image.open("Tests/images/truncated_exif_dpi.jpg") as im:
|
||||||
|
# Act / Assert
|
||||||
|
# This should return the default
|
||||||
|
assert im.info.get("dpi") == (72, 72)
|
||||||
|
|
||||||
def test_no_dpi_in_exif(self):
|
def test_no_dpi_in_exif(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
# This is photoshop-200dpi.jpg with resolution removed from EXIF:
|
# This is photoshop-200dpi.jpg with resolution removed from EXIF:
|
||||||
|
@ -882,7 +889,10 @@ class TestFileJpeg:
|
||||||
def test_getxmp(self):
|
def test_getxmp(self):
|
||||||
with Image.open("Tests/images/xmp_test.jpg") as im:
|
with Image.open("Tests/images/xmp_test.jpg") as im:
|
||||||
if ElementTree is None:
|
if ElementTree is None:
|
||||||
with pytest.warns(UserWarning):
|
with pytest.warns(
|
||||||
|
UserWarning,
|
||||||
|
match="XMP data cannot be read without defusedxml dependency",
|
||||||
|
):
|
||||||
assert im.getxmp() == {}
|
assert im.getxmp() == {}
|
||||||
else:
|
else:
|
||||||
xmp = im.getxmp()
|
xmp = im.getxmp()
|
||||||
|
@ -905,6 +915,28 @@ class TestFileJpeg:
|
||||||
with Image.open("Tests/images/hopper.jpg") as im:
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
assert im.getxmp() == {}
|
assert im.getxmp() == {}
|
||||||
|
|
||||||
|
def test_getxmp_no_prefix(self):
|
||||||
|
with Image.open("Tests/images/xmp_no_prefix.jpg") as im:
|
||||||
|
if ElementTree is None:
|
||||||
|
with pytest.warns(
|
||||||
|
UserWarning,
|
||||||
|
match="XMP data cannot be read without defusedxml dependency",
|
||||||
|
):
|
||||||
|
assert im.getxmp() == {}
|
||||||
|
else:
|
||||||
|
assert im.getxmp() == {"xmpmeta": {"key": "value"}}
|
||||||
|
|
||||||
|
def test_getxmp_padded(self):
|
||||||
|
with Image.open("Tests/images/xmp_padded.jpg") as im:
|
||||||
|
if ElementTree is None:
|
||||||
|
with pytest.warns(
|
||||||
|
UserWarning,
|
||||||
|
match="XMP data cannot be read without defusedxml dependency",
|
||||||
|
):
|
||||||
|
assert im.getxmp() == {}
|
||||||
|
else:
|
||||||
|
assert im.getxmp() == {"xmpmeta": None}
|
||||||
|
|
||||||
@pytest.mark.timeout(timeout=1)
|
@pytest.mark.timeout(timeout=1)
|
||||||
def test_eof(self):
|
def test_eof(self):
|
||||||
# Even though this decoder never says that it is finished
|
# Even though this decoder never says that it is finished
|
||||||
|
@ -929,6 +961,28 @@ class TestFileJpeg:
|
||||||
im.load()
|
im.load()
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||||
|
|
||||||
|
def test_separate_tables(self):
|
||||||
|
im = hopper()
|
||||||
|
data = [] # [interchange, tables-only, image-only]
|
||||||
|
for streamtype in range(3):
|
||||||
|
out = BytesIO()
|
||||||
|
im.save(out, format="JPEG", streamtype=streamtype)
|
||||||
|
data.append(out.getvalue())
|
||||||
|
|
||||||
|
# SOI, EOI
|
||||||
|
for marker in b"\xff\xd8", b"\xff\xd9":
|
||||||
|
assert marker in data[1] and marker in data[2]
|
||||||
|
# DHT, DQT
|
||||||
|
for marker in b"\xff\xc4", b"\xff\xdb":
|
||||||
|
assert marker in data[1] and marker not in data[2]
|
||||||
|
# SOF0, SOS, APP0 (JFIF header)
|
||||||
|
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
|
||||||
|
assert marker not in data[1] and marker in data[2]
|
||||||
|
|
||||||
|
with Image.open(BytesIO(data[0])) as interchange_im:
|
||||||
|
with Image.open(BytesIO(data[1] + data[2])) as combined_im:
|
||||||
|
assert_image_equal(interchange_im, combined_im)
|
||||||
|
|
||||||
def test_repr_jpeg(self):
|
def test_repr_jpeg(self):
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
||||||
|
|
|
@ -416,7 +416,7 @@ def test_plt_marker():
|
||||||
while True:
|
while True:
|
||||||
marker = out.read(2)
|
marker = out.read(2)
|
||||||
if not marker:
|
if not marker:
|
||||||
assert False, "End of stream without PLT"
|
pytest.fail("End of stream without PLT")
|
||||||
|
|
||||||
jp2_boxid = _binary.i16be(marker)
|
jp2_boxid = _binary.i16be(marker)
|
||||||
if jp2_boxid == 0xFF4F:
|
if jp2_boxid == 0xFF4F:
|
||||||
|
@ -426,7 +426,7 @@ def test_plt_marker():
|
||||||
# PLT
|
# PLT
|
||||||
return
|
return
|
||||||
elif jp2_boxid == 0xFF93:
|
elif jp2_boxid == 0xFF93:
|
||||||
assert False, "SOD without finding PLT first"
|
pytest.fail("SOD without finding PLT first")
|
||||||
|
|
||||||
hdr = out.read(2)
|
hdr = out.read(2)
|
||||||
length = _binary.i16be(hdr)
|
length = _binary.i16be(hdr)
|
||||||
|
|
|
@ -26,8 +26,7 @@ def open_with_magick(magick, tmp_path, f):
|
||||||
rc = subprocess.call(
|
rc = subprocess.call(
|
||||||
magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
|
magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
|
||||||
)
|
)
|
||||||
if rc:
|
assert not rc
|
||||||
raise OSError
|
|
||||||
return Image.open(outfile)
|
return Image.open(outfile)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -92,11 +92,11 @@ class TestFilePng:
|
||||||
assert im.format == "PNG"
|
assert im.format == "PNG"
|
||||||
assert im.get_format_mimetype() == "image/png"
|
assert im.get_format_mimetype() == "image/png"
|
||||||
|
|
||||||
for mode in ["1", "L", "P", "RGB", "I", "I;16"]:
|
for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]:
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
with Image.open(test_file) as reloaded:
|
with Image.open(test_file) as reloaded:
|
||||||
if mode == "I;16":
|
if mode in ("I;16", "I;16B"):
|
||||||
reloaded = reloaded.convert(mode)
|
reloaded = reloaded.convert(mode)
|
||||||
assert_image_equal(reloaded, im)
|
assert_image_equal(reloaded, im)
|
||||||
|
|
||||||
|
@ -297,7 +297,7 @@ class TestFilePng:
|
||||||
assert_image(im, "RGBA", (10, 10))
|
assert_image(im, "RGBA", (10, 10))
|
||||||
assert im.getcolors() == [(100, (0, 0, 0, 0))]
|
assert im.getcolors() == [(100, (0, 0, 0, 0))]
|
||||||
|
|
||||||
def test_save_greyscale_transparency(self, tmp_path):
|
def test_save_grayscale_transparency(self, tmp_path):
|
||||||
for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items():
|
for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items():
|
||||||
in_file = "Tests/images/" + mode.lower() + "_trns.png"
|
in_file = "Tests/images/" + mode.lower() + "_trns.png"
|
||||||
with Image.open(in_file) as im:
|
with Image.open(in_file) as im:
|
||||||
|
@ -665,7 +665,10 @@ class TestFilePng:
|
||||||
def test_getxmp(self):
|
def test_getxmp(self):
|
||||||
with Image.open("Tests/images/color_snakes.png") as im:
|
with Image.open("Tests/images/color_snakes.png") as im:
|
||||||
if ElementTree is None:
|
if ElementTree is None:
|
||||||
with pytest.warns(UserWarning):
|
with pytest.warns(
|
||||||
|
UserWarning,
|
||||||
|
match="XMP data cannot be read without defusedxml dependency",
|
||||||
|
):
|
||||||
assert im.getxmp() == {}
|
assert im.getxmp() == {}
|
||||||
else:
|
else:
|
||||||
xmp = im.getxmp()
|
xmp = im.getxmp()
|
||||||
|
|
|
@ -734,7 +734,10 @@ class TestFileTiff:
|
||||||
def test_getxmp(self):
|
def test_getxmp(self):
|
||||||
with Image.open("Tests/images/lab.tif") as im:
|
with Image.open("Tests/images/lab.tif") as im:
|
||||||
if ElementTree is None:
|
if ElementTree is None:
|
||||||
with pytest.warns(UserWarning):
|
with pytest.warns(
|
||||||
|
UserWarning,
|
||||||
|
match="XMP data cannot be read without defusedxml dependency",
|
||||||
|
):
|
||||||
assert im.getxmp() == {}
|
assert im.getxmp() == {}
|
||||||
else:
|
else:
|
||||||
xmp = im.getxmp()
|
xmp = im.getxmp()
|
||||||
|
|
|
@ -235,3 +235,13 @@ class TestFileWebp:
|
||||||
with Image.open(out_webp) as reloaded:
|
with Image.open(out_webp) as reloaded:
|
||||||
reloaded.load()
|
reloaded.load()
|
||||||
assert reloaded.info["duration"] == 1000
|
assert reloaded.info["duration"] == 1000
|
||||||
|
|
||||||
|
def test_roundtrip_rgba_palette(self, tmp_path):
|
||||||
|
temp_file = str(tmp_path / "temp.webp")
|
||||||
|
im = Image.new("RGBA", (1, 1)).convert("P")
|
||||||
|
assert im.mode == "P"
|
||||||
|
assert im.palette.mode == "RGBA"
|
||||||
|
im.save(temp_file)
|
||||||
|
|
||||||
|
with Image.open(temp_file) as im:
|
||||||
|
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
|
||||||
|
|
|
@ -118,7 +118,10 @@ def test_getxmp():
|
||||||
|
|
||||||
with Image.open("Tests/images/flower2.webp") as im:
|
with Image.open("Tests/images/flower2.webp") as im:
|
||||||
if ElementTree is None:
|
if ElementTree is None:
|
||||||
with pytest.warns(UserWarning):
|
with pytest.warns(
|
||||||
|
UserWarning,
|
||||||
|
match="XMP data cannot be read without defusedxml dependency",
|
||||||
|
):
|
||||||
assert im.getxmp() == {}
|
assert im.getxmp() == {}
|
||||||
else:
|
else:
|
||||||
assert (
|
assert (
|
||||||
|
|
|
@ -638,8 +638,8 @@ class TestImage:
|
||||||
im.remap_palette(None)
|
im.remap_palette(None)
|
||||||
|
|
||||||
def test_remap_palette_transparency(self):
|
def test_remap_palette_transparency(self):
|
||||||
im = Image.new("P", (1, 2))
|
im = Image.new("P", (1, 2), (0, 0, 0))
|
||||||
im.putpixel((0, 1), 1)
|
im.putpixel((0, 1), (255, 0, 0))
|
||||||
im.info["transparency"] = 0
|
im.info["transparency"] = 0
|
||||||
|
|
||||||
im_remapped = im.remap_palette([1, 0])
|
im_remapped = im.remap_palette([1, 0])
|
||||||
|
@ -906,6 +906,38 @@ class TestImage:
|
||||||
im = Image.new("RGB", size)
|
im = Image.new("RGB", size)
|
||||||
assert im.tobytes() == b""
|
assert im.tobytes() == b""
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
|
||||||
|
def test_zero_frombytes(self, size):
|
||||||
|
Image.frombytes("RGB", size, b"")
|
||||||
|
|
||||||
|
im = Image.new("RGB", size)
|
||||||
|
im.frombytes(b"")
|
||||||
|
|
||||||
|
def test_has_transparency_data(self):
|
||||||
|
for mode in ("1", "L", "P", "RGB"):
|
||||||
|
im = Image.new(mode, (1, 1))
|
||||||
|
assert not im.has_transparency_data
|
||||||
|
|
||||||
|
for mode in ("LA", "La", "PA", "RGBA", "RGBa"):
|
||||||
|
im = Image.new(mode, (1, 1))
|
||||||
|
assert im.has_transparency_data
|
||||||
|
|
||||||
|
# P mode with "transparency" info
|
||||||
|
with Image.open("Tests/images/first_frame_transparency.gif") as im:
|
||||||
|
assert "transparency" in im.info
|
||||||
|
assert im.has_transparency_data
|
||||||
|
|
||||||
|
# RGB mode with "transparency" info
|
||||||
|
with Image.open("Tests/images/rgb_trns.png") as im:
|
||||||
|
assert "transparency" in im.info
|
||||||
|
assert im.has_transparency_data
|
||||||
|
|
||||||
|
# P mode with RGBA palette
|
||||||
|
im = Image.new("RGBA", (1, 1)).convert("P")
|
||||||
|
assert im.mode == "P"
|
||||||
|
assert im.palette.mode == "RGBA"
|
||||||
|
assert im.has_transparency_data
|
||||||
|
|
||||||
def test_apply_transparency(self):
|
def test_apply_transparency(self):
|
||||||
im = Image.new("P", (1, 1))
|
im = Image.new("P", (1, 1))
|
||||||
im.putpalette((0, 0, 0, 1, 1, 1))
|
im.putpalette((0, 0, 0, 1, 1, 1))
|
||||||
|
@ -967,7 +999,7 @@ class TestImage:
|
||||||
with Image.open(os.path.join("Tests/images", path)) as im:
|
with Image.open(os.path.join("Tests/images", path)) as im:
|
||||||
try:
|
try:
|
||||||
im.load()
|
im.load()
|
||||||
assert False
|
pytest.fail()
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
buffer_overrun = str(e) == "buffer overrun when reading image file"
|
buffer_overrun = str(e) == "buffer overrun when reading image file"
|
||||||
truncated = "image file is truncated" in str(e)
|
truncated = "image file is truncated" in str(e)
|
||||||
|
@ -978,7 +1010,7 @@ class TestImage:
|
||||||
with Image.open("Tests/images/fli_overrun2.bin") as im:
|
with Image.open("Tests/images/fli_overrun2.bin") as im:
|
||||||
try:
|
try:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert False
|
pytest.fail()
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
assert str(e) == "buffer overrun when reading image file"
|
assert str(e) == "buffer overrun when reading image file"
|
||||||
|
|
||||||
|
|
|
@ -130,9 +130,16 @@ class TestImageGetPixel(AccessTest):
|
||||||
bands = Image.getmodebands(mode)
|
bands = Image.getmodebands(mode)
|
||||||
if bands == 1:
|
if bands == 1:
|
||||||
return 1
|
return 1
|
||||||
|
if mode in ("BGR;15", "BGR;16"):
|
||||||
|
# These modes have less than 8 bits per band
|
||||||
|
# So (1, 2, 3) cannot be roundtripped
|
||||||
|
return (16, 32, 49)
|
||||||
return tuple(range(1, bands + 1))
|
return tuple(range(1, bands + 1))
|
||||||
|
|
||||||
def check(self, mode, expected_color=None):
|
def check(self, mode, expected_color=None):
|
||||||
|
if self._need_cffi_access and mode.startswith("BGR;"):
|
||||||
|
pytest.skip("Support not added to deprecated module for BGR;* modes")
|
||||||
|
|
||||||
if not expected_color:
|
if not expected_color:
|
||||||
expected_color = self.color(mode)
|
expected_color = self.color(mode)
|
||||||
|
|
||||||
|
@ -203,6 +210,9 @@ class TestImageGetPixel(AccessTest):
|
||||||
"F",
|
"F",
|
||||||
"P",
|
"P",
|
||||||
"PA",
|
"PA",
|
||||||
|
"BGR;15",
|
||||||
|
"BGR;16",
|
||||||
|
"BGR;24",
|
||||||
"RGB",
|
"RGB",
|
||||||
"RGBA",
|
"RGBA",
|
||||||
"RGBX",
|
"RGBX",
|
||||||
|
|
|
@ -117,11 +117,11 @@ def test_trns_p(tmp_path):
|
||||||
f = str(tmp_path / "temp.png")
|
f = str(tmp_path / "temp.png")
|
||||||
|
|
||||||
im_l = im.convert("L")
|
im_l = im.convert("L")
|
||||||
assert im_l.info["transparency"] == 1 # undone
|
assert im_l.info["transparency"] == 0
|
||||||
im_l.save(f)
|
im_l.save(f)
|
||||||
|
|
||||||
im_rgb = im.convert("RGB")
|
im_rgb = im.convert("RGB")
|
||||||
assert im_rgb.info["transparency"] == (0, 1, 2) # undone
|
assert im_rgb.info["transparency"] == (0, 0, 0)
|
||||||
im_rgb.save(f)
|
im_rgb.save(f)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,15 @@ def test_mode_F():
|
||||||
assert list(im.getdata()) == target
|
assert list(im.getdata()) == target
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24"))
|
||||||
|
def test_mode_BGR(mode):
|
||||||
|
data = [(16, 32, 49), (32, 32, 98)]
|
||||||
|
im = Image.new(mode, (1, 2))
|
||||||
|
im.putdata(data)
|
||||||
|
|
||||||
|
assert list(im.getdata()) == data
|
||||||
|
|
||||||
|
|
||||||
def test_array_B():
|
def test_array_B():
|
||||||
# shouldn't segfault
|
# shouldn't segfault
|
||||||
# see https://github.com/python-pillow/Pillow/issues/1008
|
# see https://github.com/python-pillow/Pillow/issues/1008
|
||||||
|
|
|
@ -84,3 +84,14 @@ def test_rgba_palette(mode, palette):
|
||||||
im.putpalette(palette, mode)
|
im.putpalette(palette, mode)
|
||||||
assert im.getpalette() == [1, 2, 3]
|
assert im.getpalette() == [1, 2, 3]
|
||||||
assert im.palette.colors == {(1, 2, 3, 4): 0}
|
assert im.palette.colors == {(1, 2, 3, 4): 0}
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_palette():
|
||||||
|
im = Image.new("P", (1, 1))
|
||||||
|
assert im.getpalette() == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_undefined_palette_index():
|
||||||
|
im = Image.new("P", (1, 1), 3)
|
||||||
|
im.putpalette((1, 2, 3))
|
||||||
|
assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 0)
|
||||||
|
|
|
@ -195,7 +195,7 @@ class TestReducingGapResize:
|
||||||
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
|
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(pytest.fail.Exception):
|
||||||
assert_image_equal(ref, im)
|
assert_image_equal(ref, im)
|
||||||
|
|
||||||
assert_image_similar(ref, im, epsilon)
|
assert_image_similar(ref, im, epsilon)
|
||||||
|
@ -210,7 +210,7 @@ class TestReducingGapResize:
|
||||||
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0
|
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(pytest.fail.Exception):
|
||||||
assert_image_equal(ref, im)
|
assert_image_equal(ref, im)
|
||||||
|
|
||||||
assert_image_similar(ref, im, epsilon)
|
assert_image_similar(ref, im, epsilon)
|
||||||
|
@ -225,7 +225,7 @@ class TestReducingGapResize:
|
||||||
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0
|
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(pytest.fail.Exception):
|
||||||
assert_image_equal(ref, im)
|
assert_image_equal(ref, im)
|
||||||
|
|
||||||
assert_image_similar(ref, im, epsilon)
|
assert_image_similar(ref, im, epsilon)
|
||||||
|
|
|
@ -147,7 +147,7 @@ def test_reducing_gap_values():
|
||||||
|
|
||||||
ref = hopper()
|
ref = hopper()
|
||||||
ref.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=None)
|
ref.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=None)
|
||||||
with pytest.raises(AssertionError):
|
with pytest.raises(pytest.fail.Exception):
|
||||||
assert_image_equal(ref, im)
|
assert_image_equal(ref, im)
|
||||||
|
|
||||||
assert_image_similar(ref, im, 3.5)
|
assert_image_similar(ref, im, 3.5)
|
||||||
|
|
|
@ -10,7 +10,7 @@ GREEN = (0, 255, 0)
|
||||||
ORANGE = (255, 128, 0)
|
ORANGE = (255, 128, 0)
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
|
|
||||||
GREY = 128
|
GRAY = 128
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
def test_sanity():
|
||||||
|
@ -121,12 +121,12 @@ def test_constant():
|
||||||
im = Image.new("RGB", (20, 10))
|
im = Image.new("RGB", (20, 10))
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
new = ImageChops.constant(im, GREY)
|
new = ImageChops.constant(im, GRAY)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert new.size == im.size
|
assert new.size == im.size
|
||||||
assert new.getpixel((0, 0)) == GREY
|
assert new.getpixel((0, 0)) == GRAY
|
||||||
assert new.getpixel((19, 9)) == GREY
|
assert new.getpixel((19, 9)) == GRAY
|
||||||
|
|
||||||
|
|
||||||
def test_darker_image():
|
def test_darker_image():
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
import contextlib
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageColor, ImageDraw, ImageFont
|
from PIL import Image, ImageColor, ImageDraw, ImageFont, features
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -586,6 +587,18 @@ def test_point(points):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_point_I16():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("I;16", (1, 1))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
draw.point((0, 0), fill=0x1234)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert im.getpixel((0, 0)) == 0x1234
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("points", POINTS)
|
@pytest.mark.parametrize("points", POINTS)
|
||||||
def test_polygon(points):
|
def test_polygon(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -732,7 +745,7 @@ def test_rectangle_I16(bbox):
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.rectangle(bbox, fill="black", outline="green")
|
draw.rectangle(bbox, outline=0xFFFF)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
|
assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
|
||||||
|
@ -1341,7 +1354,33 @@ def test_setting_default_font():
|
||||||
assert draw.getfont() == font
|
assert draw.getfont() == font
|
||||||
finally:
|
finally:
|
||||||
ImageDraw.ImageDraw.font = None
|
ImageDraw.ImageDraw.font = None
|
||||||
assert isinstance(draw.getfont(), ImageFont.ImageFont)
|
assert isinstance(draw.getfont(), ImageFont.load_default().__class__)
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_font_size():
|
||||||
|
freetype_support = features.check_module("freetype2")
|
||||||
|
text = "Default font at a specific size."
|
||||||
|
|
||||||
|
im = Image.new("RGB", (220, 25))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):
|
||||||
|
draw.text((0, 0), text, font_size=16)
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
|
||||||
|
|
||||||
|
with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):
|
||||||
|
assert draw.textlength(text, font_size=16) == 216
|
||||||
|
|
||||||
|
with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):
|
||||||
|
assert draw.textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (220, 25))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):
|
||||||
|
draw.multiline_text((0, 0), text, font_size=16)
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_default_font_size.png")
|
||||||
|
|
||||||
|
with contextlib.nullcontext() if freetype_support else pytest.raises(ImportError):
|
||||||
|
assert draw.multiline_textbbox((0, 0), text, font_size=16) == (0, 3, 216, 19)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bbox", BBOX)
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
|
|
@ -141,7 +141,9 @@ def test_I16(font):
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
txt = "Hello World!"
|
txt = "Hello World!"
|
||||||
draw.text((10, 10), txt, font=font)
|
draw.text((10, 10), txt, fill=0xFFFE, font=font)
|
||||||
|
|
||||||
|
assert im.getpixel((12, 14)) == 0xFFFE
|
||||||
|
|
||||||
target = "Tests/images/transparent_background_text_L.png"
|
target = "Tests/images/transparent_background_text_L.png"
|
||||||
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
||||||
|
@ -301,8 +303,8 @@ def test_multiline_spacing(font):
|
||||||
"orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270)
|
"orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270)
|
||||||
)
|
)
|
||||||
def test_rotated_transposed_font(font, orientation):
|
def test_rotated_transposed_font(font, orientation):
|
||||||
img_grey = Image.new("L", (100, 100))
|
img_gray = Image.new("L", (100, 100))
|
||||||
draw = ImageDraw.Draw(img_grey)
|
draw = ImageDraw.Draw(img_gray)
|
||||||
word = "testing"
|
word = "testing"
|
||||||
|
|
||||||
transposed_font = ImageFont.TransposedFont(font, orientation=orientation)
|
transposed_font = ImageFont.TransposedFont(font, orientation=orientation)
|
||||||
|
@ -342,8 +344,8 @@ def test_rotated_transposed_font(font, orientation):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_unrotated_transposed_font(font, orientation):
|
def test_unrotated_transposed_font(font, orientation):
|
||||||
img_grey = Image.new("L", (100, 100))
|
img_gray = Image.new("L", (100, 100))
|
||||||
draw = ImageDraw.Draw(img_grey)
|
draw = ImageDraw.Draw(img_gray)
|
||||||
word = "testing"
|
word = "testing"
|
||||||
|
|
||||||
transposed_font = ImageFont.TransposedFont(font, orientation=orientation)
|
transposed_font = ImageFont.TransposedFont(font, orientation=orientation)
|
||||||
|
@ -451,7 +453,7 @@ def test_load_non_font_bytes():
|
||||||
|
|
||||||
def test_default_font():
|
def test_default_font():
|
||||||
# Arrange
|
# Arrange
|
||||||
txt = 'This is a "better than nothing" default font.'
|
txt = "This is a default font using FreeType support."
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
@ -459,8 +461,11 @@ def test_default_font():
|
||||||
default_font = ImageFont.load_default()
|
default_font = ImageFont.load_default()
|
||||||
draw.text((10, 10), txt, font=default_font)
|
draw.text((10, 10), txt, font=default_font)
|
||||||
|
|
||||||
|
larger_default_font = ImageFont.load_default(size=14)
|
||||||
|
draw.text((10, 60), txt, font=larger_default_font)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
assert_image_equal_tofile(im, "Tests/images/default_font_freetype.png")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", (None, "1", "RGBA"))
|
@pytest.mark.parametrize("mode", (None, "1", "RGBA"))
|
||||||
|
@ -483,14 +488,6 @@ def test_render_empty(font):
|
||||||
assert_image_equal(im, target)
|
assert_image_equal(im, target)
|
||||||
|
|
||||||
|
|
||||||
def test_unicode_pilfont():
|
|
||||||
# should not segfault, should return UnicodeDecodeError
|
|
||||||
# issue #2826
|
|
||||||
font = ImageFont.load_default()
|
|
||||||
with pytest.raises(UnicodeEncodeError):
|
|
||||||
font.getbbox("’")
|
|
||||||
|
|
||||||
|
|
||||||
def test_unicode_extended(layout_engine):
|
def test_unicode_extended(layout_engine):
|
||||||
# issue #3777
|
# issue #3777
|
||||||
text = "A\u278A\U0001F12B"
|
text = "A\u278A\U0001F12B"
|
||||||
|
@ -720,14 +717,6 @@ def test_variation_set_by_axes(font):
|
||||||
_check_text(font, "Tests/images/variation_tiny_axes.png", 32.5)
|
_check_text(font, "Tests/images/variation_tiny_axes.png", 32.5)
|
||||||
|
|
||||||
|
|
||||||
def test_textbbox_non_freetypefont():
|
|
||||||
im = Image.new("RGB", (200, 200))
|
|
||||||
d = ImageDraw.Draw(im)
|
|
||||||
default_font = ImageFont.load_default()
|
|
||||||
assert d.textlength("test", font=default_font) == 24
|
|
||||||
assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, 24, 11)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"anchor, left, top",
|
"anchor, left, top",
|
||||||
(
|
(
|
||||||
|
|
45
Tests/test_imagefontpil.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw, ImageFont, features
|
||||||
|
|
||||||
|
from .helper import assert_image_equal_tofile
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skipif(
|
||||||
|
features.check_module("freetype2"),
|
||||||
|
reason="PILfont superseded if FreeType is supported",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_font():
|
||||||
|
# Arrange
|
||||||
|
txt = 'This is a "better than nothing" default font.'
|
||||||
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
default_font = ImageFont.load_default()
|
||||||
|
draw.text((10, 10), txt, font=default_font)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/default_font.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_size_without_freetype():
|
||||||
|
with pytest.raises(ImportError):
|
||||||
|
ImageFont.load_default(size=14)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unicode():
|
||||||
|
# should not segfault, should return UnicodeDecodeError
|
||||||
|
# issue #2826
|
||||||
|
font = ImageFont.load_default()
|
||||||
|
with pytest.raises(UnicodeEncodeError):
|
||||||
|
font.getbbox("’")
|
||||||
|
|
||||||
|
|
||||||
|
def test_textbbox():
|
||||||
|
im = Image.new("RGB", (200, 200))
|
||||||
|
d = ImageDraw.Draw(im)
|
||||||
|
default_font = ImageFont.load_default()
|
||||||
|
assert d.textlength("test", font=default_font) == 24
|
||||||
|
assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, 24, 11)
|
|
@ -39,6 +39,9 @@ def test_sanity():
|
||||||
ImageOps.contain(hopper("L"), (128, 128))
|
ImageOps.contain(hopper("L"), (128, 128))
|
||||||
ImageOps.contain(hopper("RGB"), (128, 128))
|
ImageOps.contain(hopper("RGB"), (128, 128))
|
||||||
|
|
||||||
|
ImageOps.cover(hopper("L"), (128, 128))
|
||||||
|
ImageOps.cover(hopper("RGB"), (128, 128))
|
||||||
|
|
||||||
ImageOps.crop(hopper("L"), 1)
|
ImageOps.crop(hopper("L"), 1)
|
||||||
ImageOps.crop(hopper("RGB"), 1)
|
ImageOps.crop(hopper("RGB"), 1)
|
||||||
|
|
||||||
|
@ -119,6 +122,20 @@ def test_contain_round():
|
||||||
assert new_im.height == 5
|
assert new_im.height == 5
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"image_name, expected_size",
|
||||||
|
(
|
||||||
|
("colr_bungee.png", (1024, 256)), # landscape
|
||||||
|
("imagedraw_stroke_multiline.png", (256, 640)), # portrait
|
||||||
|
("hopper.png", (256, 256)), # square
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_cover(image_name, expected_size):
|
||||||
|
with Image.open("Tests/images/" + image_name) as im:
|
||||||
|
new_im = ImageOps.cover(im, (256, 256))
|
||||||
|
assert new_im.size == expected_size
|
||||||
|
|
||||||
|
|
||||||
def test_pad():
|
def test_pad():
|
||||||
# Same ratio
|
# Same ratio
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
@ -416,6 +433,12 @@ def test_exif_transpose_in_place():
|
||||||
assert_image_equal(im, expected)
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_autocontrast_unsupported_mode():
|
||||||
|
im = Image.new("RGBA", (1, 1))
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
ImageOps.autocontrast(im)
|
||||||
|
|
||||||
|
|
||||||
def test_autocontrast_cutoff():
|
def test_autocontrast_cutoff():
|
||||||
# Test the cutoff argument of autocontrast
|
# Test the cutoff argument of autocontrast
|
||||||
with Image.open("Tests/images/bw_gradient.png") as img:
|
with Image.open("Tests/images/bw_gradient.png") as img:
|
||||||
|
|
|
@ -85,7 +85,7 @@ def test_ipythonviewer():
|
||||||
test_viewer = viewer
|
test_viewer = viewer
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
assert False
|
pytest.fail()
|
||||||
|
|
||||||
im = hopper()
|
im = hopper()
|
||||||
assert test_viewer.show(im) == 1
|
assert test_viewer.show(im) == 1
|
||||||
|
|
|
@ -340,6 +340,17 @@ class TestLibUnpack:
|
||||||
self.assert_unpack("RGB", "G;16N", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0))
|
self.assert_unpack("RGB", "G;16N", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0))
|
||||||
self.assert_unpack("RGB", "B;16N", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5))
|
self.assert_unpack("RGB", "B;16N", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5))
|
||||||
|
|
||||||
|
self.assert_unpack(
|
||||||
|
"RGB", "CMYK", 4, (250, 249, 248), (242, 241, 240), (234, 233, 233)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_BGR(self):
|
||||||
|
self.assert_unpack("BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8))
|
||||||
|
self.assert_unpack(
|
||||||
|
"BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0)
|
||||||
|
)
|
||||||
|
self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9))
|
||||||
|
|
||||||
def test_RGBA(self):
|
def test_RGBA(self):
|
||||||
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
|
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
|
||||||
self.assert_unpack(
|
self.assert_unpack(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from setuptools.build_meta import * # noqa: F401, F403
|
from setuptools.build_meta import * # noqa: F403
|
||||||
from setuptools.build_meta import build_wheel
|
from setuptools.build_meta import build_wheel
|
||||||
|
|
||||||
backend_class = build_wheel.__self__.__class__
|
backend_class = build_wheel.__self__.__class__
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install libimagequant
|
# install libimagequant
|
||||||
|
|
||||||
archive=libimagequant-4.2.1
|
archive=libimagequant-4.2.2
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ The fork author's goal is to foster and support active development of PIL throug
|
||||||
|
|
||||||
.. _GitHub Actions: https://github.com/python-pillow/Pillow/actions
|
.. _GitHub Actions: https://github.com/python-pillow/Pillow/actions
|
||||||
.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow
|
.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow
|
||||||
.. _Travis CI: https://app.travis-ci.com/github/python-pillow/pillow-wheels
|
.. _Travis CI: https://app.travis-ci.com/github/python-pillow/Pillow
|
||||||
.. _GitHub: https://github.com/python-pillow/Pillow
|
.. _GitHub: https://github.com/python-pillow/Pillow
|
||||||
.. _Python Package Index: https://pypi.org/project/Pillow/
|
.. _Python Package Index: https://pypi.org/project/Pillow/
|
||||||
|
|
||||||
|
|
15
docs/conf.py
|
@ -318,14 +318,14 @@ def setup(app):
|
||||||
|
|
||||||
|
|
||||||
linkcheck_allowed_redirects = {
|
linkcheck_allowed_redirects = {
|
||||||
r"https://bestpractices.coreinfrastructure.org/projects/6331": r"https://bestpractices.coreinfrastructure.org/en/.*", # noqa: E501
|
r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*",
|
||||||
r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", # noqa: E501
|
r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg",
|
||||||
r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", # noqa: E501
|
r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*",
|
||||||
r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", # noqa: E501
|
r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest",
|
||||||
r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/",
|
r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/",
|
||||||
r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", # noqa: E501
|
r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*",
|
||||||
r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", # noqa: E501
|
r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg",
|
||||||
r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", # noqa: E501
|
r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+",
|
||||||
}
|
}
|
||||||
|
|
||||||
# sphinx.ext.extlinks
|
# sphinx.ext.extlinks
|
||||||
|
@ -338,6 +338,7 @@ extlinks = {
|
||||||
"cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"),
|
"cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"),
|
||||||
"issue": (_repo + "issues/%s", "#%s"),
|
"issue": (_repo + "issues/%s", "#%s"),
|
||||||
"pr": (_repo + "pull/%s", "#%s"),
|
"pr": (_repo + "pull/%s", "#%s"),
|
||||||
|
"pypi": ("https://pypi.org/project/%s/", "%s"),
|
||||||
}
|
}
|
||||||
|
|
||||||
# sphinxext.opengraph
|
# sphinxext.opengraph
|
||||||
|
|
|
@ -10,7 +10,7 @@ Deprecated features
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Below are features which are considered deprecated. Where appropriate,
|
Below are features which are considered deprecated. Where appropriate,
|
||||||
a ``DeprecationWarning`` is issued.
|
a :py:exc:`DeprecationWarning` is issued.
|
||||||
|
|
||||||
PSFile
|
PSFile
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
@ -267,7 +267,7 @@ ImageFile.raise_ioerror
|
||||||
.. deprecated:: 7.2.0
|
.. deprecated:: 7.2.0
|
||||||
.. versionremoved:: 9.0.0
|
.. versionremoved:: 9.0.0
|
||||||
|
|
||||||
``IOError`` was merged into ``OSError`` in Python 3.3.
|
:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3.
|
||||||
So, ``ImageFile.raise_ioerror`` has been removed.
|
So, ``ImageFile.raise_ioerror`` has been removed.
|
||||||
Use ``ImageFile.raise_oserror`` instead.
|
Use ``ImageFile.raise_oserror`` instead.
|
||||||
|
|
||||||
|
@ -293,9 +293,9 @@ im.offset
|
||||||
``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead.
|
``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead.
|
||||||
|
|
||||||
It was documented as deprecated in PIL 1.1.2,
|
It was documented as deprecated in PIL 1.1.2,
|
||||||
raised a ``DeprecationWarning`` since 1.1.5,
|
raised a :py:exc:`DeprecationWarning` since 1.1.5,
|
||||||
an ``Exception`` since Pillow 3.0.0
|
an :py:exc:`Exception` since Pillow 3.0.0
|
||||||
and ``NotImplementedError`` since 3.3.0.
|
and :py:exc:`NotImplementedError` since 3.3.0.
|
||||||
|
|
||||||
Image.fromstring, im.fromstring and im.tostring
|
Image.fromstring, im.fromstring and im.tostring
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -307,9 +307,9 @@ Image.fromstring, im.fromstring and im.tostring
|
||||||
* ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead.
|
* ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead.
|
||||||
* ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead.
|
* ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead.
|
||||||
|
|
||||||
They issued a ``DeprecationWarning`` since 2.0.0,
|
They issued a :py:exc:`DeprecationWarning` since 2.0.0,
|
||||||
an ``Exception`` since 3.0.0
|
an :py:exc:`Exception` since 3.0.0
|
||||||
and ``NotImplementedError`` since 3.3.0.
|
and :py:exc:`NotImplementedError` since 3.3.0.
|
||||||
|
|
||||||
ImageCms.CmsProfile attributes
|
ImageCms.CmsProfile attributes
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -318,7 +318,7 @@ ImageCms.CmsProfile attributes
|
||||||
.. versionremoved:: 8.0.0
|
.. versionremoved:: 8.0.0
|
||||||
|
|
||||||
Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed. From 6.0.0,
|
Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed. From 6.0.0,
|
||||||
they issued a ``DeprecationWarning``:
|
they issued a :py:exc:`DeprecationWarning`:
|
||||||
|
|
||||||
======================== ===================================================
|
======================== ===================================================
|
||||||
Removed Use instead
|
Removed Use instead
|
||||||
|
@ -442,7 +442,7 @@ PIL.OleFileIO
|
||||||
.. deprecated:: 4.0.0
|
.. deprecated:: 4.0.0
|
||||||
.. versionremoved:: 6.0.0
|
.. versionremoved:: 6.0.0
|
||||||
|
|
||||||
PIL.OleFileIO was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of
|
``PIL.OleFileIO`` was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of
|
||||||
the upstream olefile Python package, and replaced with an ``ImportError`` in 5.0.0
|
the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError` in 5.0.0
|
||||||
(2018-01). The deprecated file has now been removed from Pillow. If needed, install from
|
(2018-01). The deprecated file has now been removed from Pillow. If needed, install from
|
||||||
PyPI (eg. ``python3 -m pip install olefile``).
|
PyPI (eg. ``python3 -m pip install olefile``).
|
||||||
|
|
BIN
docs/example/image_thumbnail.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
docs/example/imageops_contain.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
docs/example/imageops_cover.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
docs/example/imageops_fit.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/example/imageops_pad.png
Normal file
After Width: | Height: | Size: 19 KiB |
|
@ -63,8 +63,35 @@ DDS
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
DDS is a popular container texture format used in video games and natively supported
|
DDS is a popular container texture format used in video games and natively supported
|
||||||
by DirectX. Uncompressed RGB and RGBA can be read, and (since 8.3.0) written. DXT1,
|
by DirectX.
|
||||||
DXT3 (since 3.4.0) and DXT5 pixel formats can be read, only in ``RGBA`` mode.
|
|
||||||
|
DXT1 and DXT5 pixel formats can be read, only in ``RGBA`` mode.
|
||||||
|
|
||||||
|
.. versionadded:: 3.4.0
|
||||||
|
DXT3 images can be read in ``RGB`` mode and DX10 images can be read in
|
||||||
|
``RGB`` and ``RGBA`` mode.
|
||||||
|
|
||||||
|
.. versionadded:: 6.0.0
|
||||||
|
Uncompressed ``RGBA`` images can be read.
|
||||||
|
|
||||||
|
|
||||||
|
.. versionadded:: 8.3.0
|
||||||
|
BC5S images can be opened in ``RGB`` mode, and uncompressed ``RGB`` images
|
||||||
|
can be read. Uncompressed data can also be saved to image files.
|
||||||
|
|
||||||
|
|
||||||
|
.. versionadded:: 9.3.0
|
||||||
|
ATI1 images can be opened in ``L`` mode and ATI2 images can be opened in
|
||||||
|
``RGB`` mode.
|
||||||
|
|
||||||
|
.. versionadded:: 9.4.0
|
||||||
|
Uncompressed ``L`` ("luminance") and ``LA`` images can be opened and saved.
|
||||||
|
|
||||||
|
|
||||||
|
.. versionadded:: 10.1.0
|
||||||
|
BC5U can be read in ``RGB`` mode, and 8-bit color indexed images can be read
|
||||||
|
in ``P`` mode.
|
||||||
|
|
||||||
|
|
||||||
DIB
|
DIB
|
||||||
^^^
|
^^^
|
||||||
|
@ -88,8 +115,13 @@ in ``L``, ``RGB`` and ``CMYK`` modes.
|
||||||
Loading
|
Loading
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
|
To use Ghostscript, Pillow searches for the "gs" executable. On Windows, it
|
||||||
|
also searches for "gswin32c" and "gswin64c". To customise this behaviour,
|
||||||
|
``EpsImagePlugin.gs_binary = "gswin64"`` will set the name of the executable to
|
||||||
|
use. ``EpsImagePlugin.gs_binary = False`` will prevent Ghostscript use.
|
||||||
|
|
||||||
If Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.load`
|
If Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.load`
|
||||||
method with the following parameters to affect how Ghostscript renders the EPS
|
method with the following parameters to affect how Ghostscript renders the EPS.
|
||||||
|
|
||||||
**scale**
|
**scale**
|
||||||
Affects the scale of the resultant rasterized image. If the EPS suggests
|
Affects the scale of the resultant rasterized image. If the EPS suggests
|
||||||
|
@ -1282,6 +1314,8 @@ Pillow reads Kodak FlashPix files. In the current version, only the highest
|
||||||
resolution image is read from the file, and the viewing transform is not taken
|
resolution image is read from the file, and the viewing transform is not taken
|
||||||
into account.
|
into account.
|
||||||
|
|
||||||
|
To enable FPX support, you must install :pypi:`olefile`.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
To enable full FlashPix support, you need to build and install the IJG JPEG
|
To enable full FlashPix support, you need to build and install the IJG JPEG
|
||||||
|
@ -1358,6 +1392,8 @@ the first sprite in the file is loaded. You can use :py:meth:`~PIL.Image.Image.s
|
||||||
|
|
||||||
Note that there may be an embedded gamma of 2.2 in MIC files.
|
Note that there may be an embedded gamma of 2.2 in MIC files.
|
||||||
|
|
||||||
|
To enable MIC support, you must install :pypi:`olefile`.
|
||||||
|
|
||||||
MPO
|
MPO
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ image. If the image was not read from a file, it is set to None. The size
|
||||||
attribute is a 2-tuple containing width and height (in pixels). The
|
attribute is a 2-tuple containing width and height (in pixels). The
|
||||||
:py:attr:`~PIL.Image.Image.mode` attribute defines the number and names of the
|
:py:attr:`~PIL.Image.Image.mode` attribute defines the number and names of the
|
||||||
bands in the image, and also the pixel type and depth. Common modes are “L”
|
bands in the image, and also the pixel type and depth. Common modes are “L”
|
||||||
(luminance) for greyscale images, “RGB” for true color images, and “CMYK” for
|
(luminance) for grayscale images, “RGB” for true color images, and “CMYK” for
|
||||||
pre-press images.
|
pre-press images.
|
||||||
|
|
||||||
If the file cannot be opened, an :py:exc:`OSError` exception is raised.
|
If the file cannot be opened, an :py:exc:`OSError` exception is raised.
|
||||||
|
@ -268,6 +268,37 @@ true, to provide for the same changes to the image's size.
|
||||||
A more general form of image transformations can be carried out via the
|
A more general form of image transformations can be carried out via the
|
||||||
:py:meth:`~PIL.Image.Image.transform` method.
|
:py:meth:`~PIL.Image.Image.transform` method.
|
||||||
|
|
||||||
|
Relative resizing
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Instead of calculating the size of the new image when resizing, you can also
|
||||||
|
choose to resize relative to a given size.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from PIL import Image, ImageOps
|
||||||
|
size = (100, 150)
|
||||||
|
with Image.open("Tests/images/hopper.png") as im:
|
||||||
|
ImageOps.contain(im, size).save("imageops_contain.png")
|
||||||
|
ImageOps.cover(im, size).save("imageops_cover.png")
|
||||||
|
ImageOps.fit(im, size).save("imageops_fit.png")
|
||||||
|
ImageOps.pad(im, size, color="#f00").save("imageops_pad.png")
|
||||||
|
|
||||||
|
# thumbnail() can also be used,
|
||||||
|
# but will modify the image object in place
|
||||||
|
im.thumbnail(size)
|
||||||
|
im.save("imageops_thumbnail.png")
|
||||||
|
|
||||||
|
+----------------+-------------------------------------------+--------------------------------------------+------------------------------------------+----------------------------------------+----------------------------------------+
|
||||||
|
| | :py:meth:`~PIL.Image.Image.thumbnail` | :py:meth:`~PIL.ImageOps.contain` | :py:meth:`~PIL.ImageOps.cover` | :py:meth:`~PIL.ImageOps.fit` | :py:meth:`~PIL.ImageOps.pad` |
|
||||||
|
+================+===========================================+============================================+==========================================+========================================+========================================+
|
||||||
|
|Given size | ``(100, 150)`` | ``(100, 150)`` | ``(100, 150)`` | ``(100, 150)`` | ``(100, 150)`` |
|
||||||
|
+----------------+-------------------------------------------+--------------------------------------------+------------------------------------------+----------------------------------------+----------------------------------------+
|
||||||
|
|Resulting image | .. image:: ../example/image_thumbnail.png | .. image:: ../example/imageops_contain.png | .. image:: ../example/imageops_cover.png | .. image:: ../example/imageops_fit.png | .. image:: ../example/imageops_pad.png |
|
||||||
|
+----------------+-------------------------------------------+--------------------------------------------+------------------------------------------+----------------------------------------+----------------------------------------+
|
||||||
|
|Resulting size | ``100×100`` | ``100×100`` | ``150×150`` | ``100×150`` | ``100×150`` |
|
||||||
|
+----------------+-------------------------------------------+--------------------------------------------+------------------------------------------+----------------------------------------+----------------------------------------+
|
||||||
|
|
||||||
.. _color-transforms:
|
.. _color-transforms:
|
||||||
|
|
||||||
Color transforms
|
Color transforms
|
||||||
|
@ -568,7 +599,7 @@ Controlling the decoder
|
||||||
Some decoders allow you to manipulate the image while reading it from a file.
|
Some decoders allow you to manipulate the image while reading it from a file.
|
||||||
This can often be used to speed up decoding when creating thumbnails (when
|
This can often be used to speed up decoding when creating thumbnails (when
|
||||||
speed is usually more important than quality) and printing to a monochrome
|
speed is usually more important than quality) and printing to a monochrome
|
||||||
laser printer (when only a greyscale version of the image is needed).
|
laser printer (when only a grayscale version of the image is needed).
|
||||||
|
|
||||||
The :py:meth:`~PIL.Image.Image.draft` method manipulates an opened but not yet
|
The :py:meth:`~PIL.Image.Image.draft` method manipulates an opened but not yet
|
||||||
loaded image so it as closely as possible matches the given mode and size. This
|
loaded image so it as closely as possible matches the given mode and size. This
|
||||||
|
|
|
@ -26,14 +26,14 @@ Pillow decodes files in two stages:
|
||||||
it.
|
it.
|
||||||
|
|
||||||
An image plugin should contain a format handler derived from the
|
An image plugin should contain a format handler derived from the
|
||||||
:py:class:`PIL.ImageFile.ImageFile` base class. This class should
|
:py:class:`PIL.ImageFile.ImageFile` base class. This class should provide an
|
||||||
provide an ``_open`` method, which reads the file header and
|
``_open`` method, which reads the file header and set at least the internal
|
||||||
sets up at least the :py:attr:`~PIL.Image.Image.mode` and
|
``_size`` and ``_mode`` attributes so that :py:attr:`~PIL.Image.Image.mode` and
|
||||||
:py:attr:`~PIL.Image.Image.size` attributes. To be able to load the
|
:py:attr:`~PIL.Image.Image.size` are populated. To be able to load the file,
|
||||||
file, the method must also create a list of ``tile`` descriptors,
|
the method must also create a list of ``tile`` descriptors, which contain a
|
||||||
which contain a decoder name, extents of the tile, and
|
decoder name, extents of the tile, and any decoder-specific data. The format
|
||||||
any decoder-specific data. The format handler class must be explicitly
|
handler class must be explicitly registered, via a call to the
|
||||||
registered, via a call to the :py:mod:`~PIL.Image` module.
|
:py:mod:`~PIL.Image` module.
|
||||||
|
|
||||||
.. note:: For performance reasons, it is important that the
|
.. note:: For performance reasons, it is important that the
|
||||||
``_open`` method quickly rejects files that do not have the
|
``_open`` method quickly rejects files that do not have the
|
||||||
|
@ -45,7 +45,7 @@ Example
|
||||||
The following plugin supports a simple format, which has a 128-byte header
|
The following plugin supports a simple format, which has a 128-byte header
|
||||||
consisting of the words “SPAM” followed by the width, height, and pixel size in
|
consisting of the words “SPAM” followed by the width, height, and pixel size in
|
||||||
bits. The header fields are separated by spaces. The image data follows
|
bits. The header fields are separated by spaces. The image data follows
|
||||||
directly after the header, and can be either bi-level, greyscale, or 24-bit
|
directly after the header, and can be either bi-level, grayscale, or 24-bit
|
||||||
true color.
|
true color.
|
||||||
|
|
||||||
**SpamImagePlugin.py**::
|
**SpamImagePlugin.py**::
|
||||||
|
@ -96,13 +96,13 @@ true color.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
The format handler must always set the
|
The format handler must always set the internal ``_size`` and ``_mode``
|
||||||
:py:attr:`~PIL.Image.Image.size` and :py:attr:`~PIL.Image.Image.mode`
|
attributes so that :py:attr:`~PIL.Image.Image.size` and
|
||||||
attributes. If these are not set, the file cannot be opened. To
|
:py:attr:`~PIL.Image.Image.mode` are populated. If these are not set, the file
|
||||||
simplify the plugin, the calling code considers exceptions like
|
cannot be opened. To simplify the plugin, the calling code considers exceptions
|
||||||
:py:exc:`SyntaxError`, :py:exc:`KeyError`, :py:exc:`IndexError`,
|
like :py:exc:`SyntaxError`, :py:exc:`KeyError`, :py:exc:`IndexError`,
|
||||||
:py:exc:`EOFError` and :py:exc:`struct.error` as a failure to identify
|
:py:exc:`EOFError` and :py:exc:`struct.error` as a failure to identify the
|
||||||
the file.
|
file.
|
||||||
|
|
||||||
Note that the image plugin must be explicitly registered using
|
Note that the image plugin must be explicitly registered using
|
||||||
:py:func:`PIL.Image.register_open`. Although not required, it is also a good
|
:py:func:`PIL.Image.register_open`. Although not required, it is also a good
|
||||||
|
@ -211,9 +211,9 @@ table describes some commonly used **raw modes**:
|
||||||
| ``1;R`` | | 1-bit reversed bilevel, stored with the leftmost pixel in the |
|
| ``1;R`` | | 1-bit reversed bilevel, stored with the leftmost pixel in the |
|
||||||
| | | least significant bit. 0 means black, 1 means white. |
|
| | | least significant bit. 0 means black, 1 means white. |
|
||||||
+-----------+-------------------------------------------------------------------+
|
+-----------+-------------------------------------------------------------------+
|
||||||
| ``L`` | 8-bit greyscale. 0 means black, 255 means white. |
|
| ``L`` | 8-bit grayscale. 0 means black, 255 means white. |
|
||||||
+-----------+-------------------------------------------------------------------+
|
+-----------+-------------------------------------------------------------------+
|
||||||
| ``L;I`` | 8-bit inverted greyscale. 0 means white, 255 means black. |
|
| ``L;I`` | 8-bit inverted grayscale. 0 means white, 255 means black. |
|
||||||
+-----------+-------------------------------------------------------------------+
|
+-----------+-------------------------------------------------------------------+
|
||||||
| ``P`` | 8-bit palette-mapped image. |
|
| ``P`` | 8-bit palette-mapped image. |
|
||||||
+-----------+-------------------------------------------------------------------+
|
+-----------+-------------------------------------------------------------------+
|
||||||
|
|
|
@ -37,12 +37,12 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
|
||||||
:target: https://ci.appveyor.com/project/python-pillow/Pillow
|
:target: https://ci.appveyor.com/project/python-pillow/Pillow
|
||||||
:alt: AppVeyor CI build status (Windows)
|
:alt: AppVeyor CI build status (Windows)
|
||||||
|
|
||||||
.. image:: https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg
|
.. image:: https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg
|
||||||
:target: https://github.com/python-pillow/pillow-wheels/actions
|
:target: https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml
|
||||||
:alt: GitHub Actions wheels build status (Wheels)
|
:alt: GitHub Actions build status (Wheels)
|
||||||
|
|
||||||
.. image:: https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels
|
.. image:: https://img.shields.io/travis/com/python-pillow/Pillow/main.svg?label=aarch64%20wheels
|
||||||
:target: https://app.travis-ci.com/github/python-pillow/pillow-wheels
|
:target: https://app.travis-ci.com/github/python-pillow/Pillow
|
||||||
:alt: Travis CI wheels build status (aarch64)
|
:alt: Travis CI wheels build status (aarch64)
|
||||||
|
|
||||||
.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg
|
.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg
|
||||||
|
@ -69,8 +69,8 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
|
||||||
:target: https://pypi.org/project/Pillow/
|
:target: https://pypi.org/project/Pillow/
|
||||||
:alt: Number of PyPI downloads
|
:alt: Number of PyPI downloads
|
||||||
|
|
||||||
.. image:: https://bestpractices.coreinfrastructure.org/projects/6331/badge
|
.. image:: https://www.bestpractices.dev/projects/6331/badge
|
||||||
:target: https://bestpractices.coreinfrastructure.org/projects/6331
|
:target: https://www.bestpractices.dev/projects/6331
|
||||||
:alt: OpenSSF Best Practices
|
:alt: OpenSSF Best Practices
|
||||||
|
|
||||||
.. image:: https://badges.gitter.im/python-pillow/Pillow.svg
|
.. image:: https://badges.gitter.im/python-pillow/Pillow.svg
|
||||||
|
|
|
@ -42,6 +42,11 @@ Install Pillow with :command:`pip`::
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python3 -m pip install --upgrade Pillow
|
python3 -m pip install --upgrade Pillow
|
||||||
|
|
||||||
|
Optionally, install :pypi:`defusedxml` for Pillow to read XMP data,
|
||||||
|
and :pypi:`olefile` for Pillow to read FPX and MIC images::
|
||||||
|
|
||||||
|
python3 -m pip install --upgrade defusedxml olefile
|
||||||
|
|
||||||
|
|
||||||
.. tab:: Linux
|
.. tab:: Linux
|
||||||
|
|
||||||
|
@ -82,6 +87,8 @@ Install Pillow with :command:`pip`::
|
||||||
|
|
||||||
.. tab:: Windows
|
.. tab:: Windows
|
||||||
|
|
||||||
|
.. warning:: Pillow > 9.5.0 no longer includes 32-bit wheels.
|
||||||
|
|
||||||
We provide Pillow binaries for Windows compiled for the matrix of
|
We provide Pillow binaries for Windows compiled for the matrix of
|
||||||
supported Pythons in 64-bit versions in the wheel format. These binaries include
|
supported Pythons in 64-bit versions in the wheel format. These binaries include
|
||||||
support for all optional libraries except libimagequant and libxcb. Raqm support
|
support for all optional libraries except libimagequant and libxcb. Raqm support
|
||||||
|
@ -154,7 +161,7 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
* **libtiff** provides compressed TIFF functionality
|
* **libtiff** provides compressed TIFF functionality
|
||||||
|
|
||||||
* Pillow has been tested with libtiff versions **3.x** and **4.0-4.5.1**
|
* Pillow has been tested with libtiff versions **3.x** and **4.0-4.6.0**
|
||||||
|
|
||||||
* **libfreetype** provides type related services
|
* **libfreetype** provides type related services
|
||||||
|
|
||||||
|
@ -180,7 +187,7 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
* **libimagequant** provides improved color quantization
|
* **libimagequant** provides improved color quantization
|
||||||
|
|
||||||
* Pillow has been tested with libimagequant **2.6-4.2.1**
|
* Pillow has been tested with libimagequant **2.6-4.2.2**
|
||||||
* Libimagequant is licensed GPLv3, which is more restrictive than
|
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||||
the Pillow license, therefore we will not be distributing binaries
|
the Pillow license, therefore we will not be distributing binaries
|
||||||
with libimagequant support enabled.
|
with libimagequant support enabled.
|
||||||
|
@ -354,7 +361,7 @@ for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no
|
||||||
additional configuration should be required. If they are installed in
|
additional configuration should be required. If they are installed in
|
||||||
a non-standard location, you may need to configure setuptools to use
|
a non-standard location, you may need to configure setuptools to use
|
||||||
those locations by editing :file:`setup.py` or
|
those locations by editing :file:`setup.py` or
|
||||||
:file:`setup.cfg`, or by adding environment variables on the command
|
:file:`pyproject.toml`, or by adding environment variables on the command
|
||||||
line::
|
line::
|
||||||
|
|
||||||
CFLAGS="-I/usr/pkg/include" python3 -m pip install --upgrade Pillow --no-binary :all:
|
CFLAGS="-I/usr/pkg/include" python3 -m pip install --upgrade Pillow --no-binary :all:
|
||||||
|
@ -454,10 +461,10 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Debian 12 Bookworm | 3.11 | x86, x86-64 |
|
| Debian 12 Bookworm | 3.11 | x86, x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Fedora 37 | 3.11 | x86-64 |
|
|
||||||
+----------------------------------+----------------------------+---------------------+
|
|
||||||
| Fedora 38 | 3.11 | x86-64 |
|
| Fedora 38 | 3.11 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
| Fedora 39 | 3.12 | x86-64 |
|
||||||
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Gentoo | 3.9 | x86-64 |
|
| Gentoo | 3.9 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
| macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||||
|
@ -476,7 +483,7 @@ These platforms are built and tested for every change.
|
||||||
| Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
| Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||||
| | 3.12, PyPy3 | |
|
| | 3.12, PyPy3 | |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| | 3.11 | x86 |
|
| | 3.12 | x86 |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| | 3.9 (MinGW) | x86-64 |
|
| | 3.9 (MinGW) | x86-64 |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
|
@ -494,91 +501,93 @@ These platforms have been reported to work at the versions mentioned.
|
||||||
Contributors please test Pillow on your platform then update this
|
Contributors please test Pillow on your platform then update this
|
||||||
document and send a pull request.
|
document and send a pull request.
|
||||||
|
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Operating system | | Tested Python | | Latest tested | | Tested |
|
| Operating system | | Tested Python | | Latest tested | | Tested |
|
||||||
| | | versions | | Pillow version | | processors |
|
| | | versions | | Pillow version | | processors |
|
||||||
+==================================+===========================+==================+==============+
|
+==================================+============================+==================+==============+
|
||||||
| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
|
| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.1.0 |arm |
|
||||||
| +---------------------------+------------------+ |
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| | 3.7 | 9.5.0 | |
|
| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
| +----------------------------+------------------+ |
|
||||||
| macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
|
| | 3.7 | 9.5.0 | |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
|
| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
|
||||||
| +---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 |
|
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
|
||||||
| +---------------------------+------------------+ |
|
| +----------------------------+------------------+--------------+
|
||||||
| | 3.6 | 8.4.0 | |
|
| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
| +----------------------------+------------------+ |
|
||||||
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 |
|
| | 3.6 | 8.4.0 | |
|
||||||
| +---------------------------+------------------+ |
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| | 3.5 | 7.2.0 | |
|
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
| +----------------------------+------------------+ |
|
||||||
| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
|
| | 3.5 | 7.2.0 | |
|
||||||
| +---------------------------+------------------+ |
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| | 2.7 | 6.0.0 | |
|
| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
|
||||||
| +---------------------------+------------------+ |
|
| +----------------------------+------------------+ |
|
||||||
| | 3.4 | 5.4.1 | |
|
| | 2.7 | 6.0.0 | |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
| +----------------------------+------------------+ |
|
||||||
| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 |
|
| | 3.4 | 5.4.1 | |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 |
|
| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 |
|
| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 |
|
||||||
| +---------------------------+------------------+ |
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| | 3.3 | 4.1.0 | |
|
| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
| +----------------------------+------------------+ |
|
||||||
| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 |
|
| | 3.3 | 4.1.0 | |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 |
|
| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Redhat Linux 6 | 2.6 | |x86 |
|
| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| CentOS 6.3 | 2.7, 3.3 | |x86 |
|
| Redhat Linux 6 | 2.6 | |x86 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| CentOS 8 | 3.9 | 9.0.0 |x86-64 |
|
| CentOS 6.3 | 2.7, 3.3 | |x86 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 |
|
| CentOS 8 | 3.9 | 9.0.0 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 |
|
| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 |
|
||||||
| | | PyPy5.3.1, PyPy3 v2.4.0 | | |
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| +---------------------------+------------------+--------------+
|
| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 |
|
||||||
| | 2.7 | 4.3.0 |x86-64 |
|
| | | PyPy5.3.1, PyPy3 v2.4.0 | | |
|
||||||
| +---------------------------+------------------+--------------+
|
| +----------------------------+------------------+--------------+
|
||||||
| | 2.7, 3.2 | 3.4.1 |ppc |
|
| | 2.7 | 4.3.0 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
| +----------------------------+------------------+--------------+
|
||||||
| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 |
|
| | 2.7, 3.2 | 3.4.1 |ppc |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 |
|
| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm |
|
| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm |
|
| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm |
|
| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm |
|
||||||
| +---------------------------+------------------+ |
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| | 2.7 | 6.2.2 | |
|
| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
| +----------------------------+------------------+ |
|
||||||
| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 |
|
| | 2.7 | 6.2.2 | |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 |
|
| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 |
|
| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 |
|
| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Windows 10 | 3.7 | 7.1.0 |x86-64 |
|
| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 |
|
| Windows 10 | 3.7 | 7.1.0 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 |
|
| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 |
|
| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 |
|
| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 |
|
| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 |
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
|
| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 |
|
||||||
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
|
|
||||||
Old Versions
|
Old Versions
|
||||||
------------
|
------------
|
||||||
|
|
|
@ -351,6 +351,8 @@ Instances of the :py:class:`Image` class have the following attributes:
|
||||||
|
|
||||||
.. seealso:: :attr:`~Image.is_animated`, :func:`~Image.seek` and :func:`~Image.tell`
|
.. seealso:: :attr:`~Image.is_animated`, :func:`~Image.seek` and :func:`~Image.tell`
|
||||||
|
|
||||||
|
.. autoattribute:: PIL.Image.Image.has_transparency_data
|
||||||
|
|
||||||
Classes
|
Classes
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ Functions
|
||||||
.. py:method:: getcolor(color, mode)
|
.. py:method:: getcolor(color, mode)
|
||||||
|
|
||||||
Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a
|
Same as :py:func:`~PIL.ImageColor.getrgb`, but converts the RGB value to a
|
||||||
greyscale value if the mode is not color or a palette image. If the string
|
grayscale value if the mode is not color or a palette image. If the string
|
||||||
cannot be parsed, this function raises a :py:exc:`ValueError` exception.
|
cannot be parsed, this function raises a :py:exc:`ValueError` exception.
|
||||||
|
|
||||||
.. versionadded:: 1.1.4
|
.. versionadded:: 1.1.4
|
||||||
|
|
|
@ -351,7 +351,7 @@ Methods
|
||||||
|
|
||||||
Draw a shape.
|
Draw a shape.
|
||||||
|
|
||||||
.. py:method:: ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False)
|
.. py:method:: ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False, font_size=None)
|
||||||
|
|
||||||
Draws the string at the given position.
|
Draws the string at the given position.
|
||||||
|
|
||||||
|
@ -416,8 +416,14 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 8.0.0
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
|
:param font_size: If ``font`` is not provided, then the size to use for the default
|
||||||
|
font.
|
||||||
|
Keyword-only argument.
|
||||||
|
|
||||||
.. py:method:: ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False)
|
.. versionadded:: 10.1.0
|
||||||
|
|
||||||
|
|
||||||
|
.. py:method:: ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False, font_size=None)
|
||||||
|
|
||||||
Draws the string at the given position.
|
Draws the string at the given position.
|
||||||
|
|
||||||
|
@ -477,7 +483,13 @@ Methods
|
||||||
|
|
||||||
.. versionadded:: 8.0.0
|
.. versionadded:: 8.0.0
|
||||||
|
|
||||||
.. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None, embedded_color=False)
|
:param font_size: If ``font`` is not provided, then the size to use for the default
|
||||||
|
font.
|
||||||
|
Keyword-only argument.
|
||||||
|
|
||||||
|
.. versionadded:: 10.1.0
|
||||||
|
|
||||||
|
.. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None, embedded_color=False, font_size=None)
|
||||||
|
|
||||||
Returns length (in pixels with 1/64 precision) of given text when rendered
|
Returns length (in pixels with 1/64 precision) of given text when rendered
|
||||||
in font with provided direction, features, and language.
|
in font with provided direction, features, and language.
|
||||||
|
@ -538,9 +550,15 @@ Methods
|
||||||
It should be a `BCP 47 language code`_.
|
It should be a `BCP 47 language code`_.
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||||
|
:param font_size: If ``font`` is not provided, then the size to use for the default
|
||||||
|
font.
|
||||||
|
Keyword-only argument.
|
||||||
|
|
||||||
|
.. versionadded:: 10.1.0
|
||||||
|
|
||||||
:return: Either width for horizontal text, or height for vertical text.
|
:return: Either width for horizontal text, or height for vertical text.
|
||||||
|
|
||||||
.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
|
.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False, font_size=None)
|
||||||
|
|
||||||
Returns bounding box (in pixels) of given text relative to given anchor
|
Returns bounding box (in pixels) of given text relative to given anchor
|
||||||
when rendered in font with provided direction, features, and language.
|
when rendered in font with provided direction, features, and language.
|
||||||
|
@ -588,9 +606,15 @@ Methods
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
:param stroke_width: The width of the text stroke.
|
:param stroke_width: The width of the text stroke.
|
||||||
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||||
|
:param font_size: If ``font`` is not provided, then the size to use for the default
|
||||||
|
font.
|
||||||
|
Keyword-only argument.
|
||||||
|
|
||||||
|
.. versionadded:: 10.1.0
|
||||||
|
|
||||||
:return: ``(left, top, right, bottom)`` bounding box
|
:return: ``(left, top, right, bottom)`` bounding box
|
||||||
|
|
||||||
.. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
|
.. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False, font_size=None)
|
||||||
|
|
||||||
Returns bounding box (in pixels) of given text relative to given anchor
|
Returns bounding box (in pixels) of given text relative to given anchor
|
||||||
when rendered in font with provided direction, features, and language.
|
when rendered in font with provided direction, features, and language.
|
||||||
|
@ -632,6 +656,12 @@ Methods
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
:param stroke_width: The width of the text stroke.
|
:param stroke_width: The width of the text stroke.
|
||||||
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||||
|
:param font_size: If ``font`` is not provided, then the size to use for the default
|
||||||
|
font.
|
||||||
|
Keyword-only argument.
|
||||||
|
|
||||||
|
.. versionadded:: 10.1.0
|
||||||
|
|
||||||
:return: ``(left, top, right, bottom)`` bounding box
|
:return: ``(left, top, right, bottom)`` bounding box
|
||||||
|
|
||||||
.. py:method:: getdraw(im=None, hints=None)
|
.. py:method:: getdraw(im=None, hints=None)
|
||||||
|
|
|
@ -58,7 +58,7 @@ method:
|
||||||
|
|
||||||
This class can be used to control the contrast of an image, similar to the
|
This class can be used to control the contrast of an image, similar to the
|
||||||
contrast control on a TV set. An
|
contrast control on a TV set. An
|
||||||
:ref:`enhancement factor <enhancement-factor>` of 0.0 gives a solid grey
|
:ref:`enhancement factor <enhancement-factor>` of 0.0 gives a solid gray
|
||||||
image, a factor of 1.0 gives the original image, and greater values
|
image, a factor of 1.0 gives the original image, and greater values
|
||||||
increase the contrast of the image.
|
increase the contrast of the image.
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ this class store bitmap fonts, and are used with the
|
||||||
|
|
||||||
PIL uses its own font file format to store bitmap fonts, limited to 256 characters. You can use
|
PIL uses its own font file format to store bitmap fonts, limited to 256 characters. You can use
|
||||||
`pilfont.py <https://github.com/python-pillow/pillow-scripts/blob/main/Scripts/pilfont.py>`_
|
`pilfont.py <https://github.com/python-pillow/pillow-scripts/blob/main/Scripts/pilfont.py>`_
|
||||||
from `pillow-scripts <https://pypi.org/project/pillow-scripts/>`_ to convert BDF and
|
from :pypi:`pillow-scripts` to convert BDF and
|
||||||
PCF font descriptors (X window font formats) to this format.
|
PCF font descriptors (X window font formats) to this format.
|
||||||
|
|
||||||
Starting with version 1.1.4, PIL can be configured to support TrueType and
|
Starting with version 1.1.4, PIL can be configured to support TrueType and
|
||||||
|
@ -20,7 +20,7 @@ the imToolkit package.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
To protect against potential DOS attacks when using arbitrary strings as
|
To protect against potential DOS attacks when using arbitrary strings as
|
||||||
text input, Pillow will raise a ``ValueError`` if the number of characters
|
text input, Pillow will raise a :py:exc:`ValueError` if the number of characters
|
||||||
is over a certain limit, :py:data:`MAX_STRING_LENGTH`.
|
is over a certain limit, :py:data:`MAX_STRING_LENGTH`.
|
||||||
|
|
||||||
This threshold can be changed by setting
|
This threshold can be changed by setting
|
||||||
|
@ -89,5 +89,5 @@ Constants
|
||||||
.. data:: MAX_STRING_LENGTH
|
.. data:: MAX_STRING_LENGTH
|
||||||
|
|
||||||
Set to 1,000,000, to protect against potential DOS attacks. Pillow will
|
Set to 1,000,000, to protect against potential DOS attacks. Pillow will
|
||||||
raise a ``ValueError`` if the number of characters is over this limit. The
|
raise a :py:exc:`ValueError` if the number of characters is over this limit. The
|
||||||
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
||||||
|
|
|
@ -72,7 +72,7 @@ pixel bits.
|
||||||
|
|
||||||
Note that the operands are converted to 32-bit signed integers before the
|
Note that the operands are converted to 32-bit signed integers before the
|
||||||
bitwise operation is applied. This means that you’ll get negative values if
|
bitwise operation is applied. This means that you’ll get negative values if
|
||||||
you invert an ordinary greyscale image. You can use the and (&) operator to
|
you invert an ordinary grayscale image. You can use the and (&) operator to
|
||||||
mask off unwanted bits.
|
mask off unwanted bits.
|
||||||
|
|
||||||
Bitwise operators don’t work on floating point images.
|
Bitwise operators don’t work on floating point images.
|
||||||
|
|
|
@ -12,14 +12,11 @@ only work on L and RGB images.
|
||||||
|
|
||||||
.. autofunction:: autocontrast
|
.. autofunction:: autocontrast
|
||||||
.. autofunction:: colorize
|
.. autofunction:: colorize
|
||||||
.. autofunction:: contain
|
|
||||||
.. autofunction:: pad
|
|
||||||
.. autofunction:: crop
|
.. autofunction:: crop
|
||||||
.. autofunction:: scale
|
.. autofunction:: scale
|
||||||
.. autofunction:: deform
|
.. autofunction:: deform
|
||||||
.. autofunction:: equalize
|
.. autofunction:: equalize
|
||||||
.. autofunction:: expand
|
.. autofunction:: expand
|
||||||
.. autofunction:: fit
|
|
||||||
.. autofunction:: flip
|
.. autofunction:: flip
|
||||||
.. autofunction:: grayscale
|
.. autofunction:: grayscale
|
||||||
.. autofunction:: invert
|
.. autofunction:: invert
|
||||||
|
@ -27,3 +24,38 @@ only work on L and RGB images.
|
||||||
.. autofunction:: posterize
|
.. autofunction:: posterize
|
||||||
.. autofunction:: solarize
|
.. autofunction:: solarize
|
||||||
.. autofunction:: exif_transpose
|
.. autofunction:: exif_transpose
|
||||||
|
|
||||||
|
.. _relative-resize:
|
||||||
|
|
||||||
|
Resize relative to a given size
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from PIL import Image, ImageOps
|
||||||
|
size = (100, 150)
|
||||||
|
with Image.open("Tests/images/hopper.png") as im:
|
||||||
|
ImageOps.contain(im, size).save("imageops_contain.png")
|
||||||
|
ImageOps.cover(im, size).save("imageops_cover.png")
|
||||||
|
ImageOps.fit(im, size).save("imageops_fit.png")
|
||||||
|
ImageOps.pad(im, size, color="#f00").save("imageops_pad.png")
|
||||||
|
|
||||||
|
# thumbnail() can also be used,
|
||||||
|
# but will modify the image object in place
|
||||||
|
im.thumbnail(size)
|
||||||
|
im.save("imageops_thumbnail.png")
|
||||||
|
|
||||||
|
+----------------+-------------------------------------------+--------------------------------------------+------------------------------------------+----------------------------------------+----------------------------------------+
|
||||||
|
| | :py:meth:`~PIL.Image.Image.thumbnail` | :py:meth:`~PIL.ImageOps.contain` | :py:meth:`~PIL.ImageOps.cover` | :py:meth:`~PIL.ImageOps.fit` | :py:meth:`~PIL.ImageOps.pad` |
|
||||||
|
+================+===========================================+============================================+==========================================+========================================+========================================+
|
||||||
|
|Given size | ``(100, 150)`` | ``(100, 150)`` | ``(100, 150)`` | ``(100, 150)`` | ``(100, 150)`` |
|
||||||
|
+----------------+-------------------------------------------+--------------------------------------------+------------------------------------------+----------------------------------------+----------------------------------------+
|
||||||
|
|Resulting image | .. image:: ../example/image_thumbnail.png | .. image:: ../example/imageops_contain.png | .. image:: ../example/imageops_cover.png | .. image:: ../example/imageops_fit.png | .. image:: ../example/imageops_pad.png |
|
||||||
|
+----------------+-------------------------------------------+--------------------------------------------+------------------------------------------+----------------------------------------+----------------------------------------+
|
||||||
|
|Resulting size | ``100×100`` | ``100×100`` | ``150×150`` | ``100×150`` | ``100×150`` |
|
||||||
|
+----------------+-------------------------------------------+--------------------------------------------+------------------------------------------+----------------------------------------+----------------------------------------+
|
||||||
|
|
||||||
|
.. autofunction:: contain
|
||||||
|
.. autofunction:: cover
|
||||||
|
.. autofunction:: fit
|
||||||
|
.. autofunction:: pad
|
||||||
|
|
|
@ -173,8 +173,8 @@ been processed before Pillow started checking for decompression bombs.
|
||||||
Added ImageFont.MAX_STRING_LENGTH
|
Added ImageFont.MAX_STRING_LENGTH
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
To protect against potential DOS attacks when using arbitrary strings as text
|
:cve:`2023-44271`: To protect against potential DOS attacks when using arbitrary strings as text
|
||||||
input, Pillow will now raise a ``ValueError`` if the number of characters
|
input, Pillow will now raise a :py:exc:`ValueError` if the number of characters
|
||||||
passed into ImageFont methods is over a certain limit,
|
passed into ImageFont methods is over a certain limit,
|
||||||
:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`.
|
:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`.
|
||||||
|
|
||||||
|
|
|
@ -1,54 +1,109 @@
|
||||||
10.1.0
|
10.1.0
|
||||||
------
|
------
|
||||||
|
|
||||||
Backwards Incompatible Changes
|
API Changes
|
||||||
==============================
|
===========
|
||||||
|
|
||||||
Setting image mode
|
Setting image mode
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
If you attempt to set the mode of an image directly, e.g.
|
If you attempt to set the mode of an image directly, e.g.
|
||||||
``im.mode = "RGBA"``, you will now receive an ``AttributeError``. This is
|
``im.mode = "RGBA"``, you will now receive an :py:exc:`AttributeError`. This is
|
||||||
not about removing existing functionality, but instead about raising an
|
not about removing existing functionality, but instead about raising an
|
||||||
explicit error to prevent later consequences. The ``convert`` method is the
|
explicit error to prevent later consequences. The ``convert`` method is the
|
||||||
correct way to change an image's mode.
|
correct way to change an image's mode.
|
||||||
|
|
||||||
Deprecations
|
Accept a list in getpixel()
|
||||||
============
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
:py:meth:`~PIL.Image.Image.getpixel` now accepts a list of coordinates, as well
|
||||||
^^^^
|
as a tuple. ::
|
||||||
|
|
||||||
TODO
|
from PIL import Image
|
||||||
|
im = Image.new("RGB", (1, 1))
|
||||||
|
im.getpixel((0, 0))
|
||||||
|
im.getpixel([0, 0])
|
||||||
|
|
||||||
API Changes
|
BoxBlur and GaussianBlur allow for different x and y radii
|
||||||
===========
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
:py:class:`~PIL.ImageFilter.BoxBlur` and
|
||||||
^^^^
|
:py:class:`~PIL.ImageFilter.GaussianBlur` now allow a sequence of x and y radii
|
||||||
|
to be specified, rather than a single number for both dimensions. ::
|
||||||
|
|
||||||
TODO
|
from PIL import ImageFilter
|
||||||
|
ImageFilter.BoxBlur((2, 5))
|
||||||
|
ImageFilter.GaussianBlur((2, 5))
|
||||||
|
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
|
||||||
TODO
|
EpsImagePlugin.gs_binary
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
``EpsImagePlugin.gs_windows_binary`` stores the name of the Ghostscript
|
||||||
|
executable on Windows. ``EpsImagePlugin.gs_binary`` has now been added for all
|
||||||
|
platforms, and can be used to customise the name of the executable, or disable
|
||||||
|
use entirely through ``EpsImagePlugin.gs_binary = False``.
|
||||||
|
|
||||||
Security
|
has_transparency_data
|
||||||
========
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
Images now have :py:attr:`~PIL.Image.Image.has_transparency_data` to indicate
|
||||||
^^^^
|
whether the image has transparency data, whether in the form of an alpha
|
||||||
|
channel, a palette with an alpha channel, or a "transparency" key in the
|
||||||
|
:py:attr:`~PIL.Image.Image.info` dictionary.
|
||||||
|
|
||||||
TODO
|
Even if this attribute is true, the image might still appear solid, if all of
|
||||||
|
the values shown within are opaque.
|
||||||
|
|
||||||
|
ImageOps.cover
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Returns a resized version of the image, so that the requested size is covered,
|
||||||
|
while maintaining the original aspect ratio.
|
||||||
|
|
||||||
|
See :ref:`relative-resize` for a comparison between this and similar ``ImageOps``
|
||||||
|
methods.
|
||||||
|
|
||||||
|
size and font_size arguments when using default font
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Pillow has had a "better than nothing" default font, which can only be drawn at
|
||||||
|
one font size. Now, if FreeType support is available, a version of
|
||||||
|
`Aileron Regular <https://dotcolon.net/font/aileron>`_ is loaded, which can be
|
||||||
|
drawn at chosen font sizes.
|
||||||
|
|
||||||
|
The following ``size`` and ``font_size`` arguments can now be used to specify a
|
||||||
|
font size for this new builtin font::
|
||||||
|
|
||||||
|
ImageFont.load_default(size=24)
|
||||||
|
draw.text((0, 0), "test", font_size=24)
|
||||||
|
draw.textlength((0, 0), "test", font_size=24)
|
||||||
|
draw.textbbox((0, 0), "test", font_size=24)
|
||||||
|
draw.multiline_text((0, 0), "test", font_size=24)
|
||||||
|
draw.multiline_textbbox((0, 0), "test", font_size=24)
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
TODO
|
Python 3.12
|
||||||
^^^^
|
^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
Pillow 10.0.0 had wheels built against Python 3.12 beta, available as a preview to help
|
||||||
|
others prepare for 3.12, and to ensure Pillow could be used immediately at the release
|
||||||
|
of 3.12.0 final (2023-10-02, :pep:`693`).
|
||||||
|
|
||||||
|
Pillow 10.1.0 now officially supports Python 3.12.
|
||||||
|
|
||||||
|
Added support for DDS BC5U and 8-bit color indexed images
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Support has been added to read BC5U DDS files as RGB images, and
|
||||||
|
PALETTEINDEXED8 DDS files as P mode images.
|
||||||
|
|
||||||
|
Support reading signed 8-bit YCbCr TIFF images
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
TIFF images with unsigned integer data, 8 bits per sample and a photometric
|
||||||
|
interpretation of YCbCr can now be read.
|
||||||
|
|
|
@ -10,7 +10,7 @@ operations. As a result PIL was unable to open them as images, requiring a wrap
|
||||||
``cStringIO`` or ``BytesIO``.
|
``cStringIO`` or ``BytesIO``.
|
||||||
|
|
||||||
Now new functionality has been added to ``Image.open()`` by way of an ``.seek(0)`` check and
|
Now new functionality has been added to ``Image.open()`` by way of an ``.seek(0)`` check and
|
||||||
catch on exception ``AttributeError`` or ``io.UnsupportedOperation``. If this is caught we
|
catch on exception :py:exc:`AttributeError` or :py:exc:`io.UnsupportedOperation`. If this is caught we
|
||||||
attempt to wrap the object using ``io.BytesIO`` (which will only work on buffer-file-like
|
attempt to wrap the object using ``io.BytesIO`` (which will only work on buffer-file-like
|
||||||
objects).
|
objects).
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ Deprecation Warning when Saving JPEGs
|
||||||
|
|
||||||
JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0
|
JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0
|
||||||
silently drops the alpha channel. With this release Pillow will now
|
silently drops the alpha channel. With this release Pillow will now
|
||||||
issue a ``DeprecationWarning`` when attempting to save a ``RGBA`` mode
|
issue a :py:exc:`DeprecationWarning` when attempting to save a ``RGBA`` mode
|
||||||
image as a JPEG. This will become an error in Pillow 4.2.
|
image as a JPEG. This will become an error in Pillow 4.2.
|
||||||
|
|
||||||
New DDS Decoders
|
New DDS Decoders
|
||||||
|
|
|
@ -17,8 +17,8 @@ Pillow 4.0 supports Python 3.6.
|
||||||
OleFileIO.py
|
OleFileIO.py
|
||||||
============
|
============
|
||||||
|
|
||||||
OleFileIO.py has been removed as a vendored file and is now installed
|
``OleFileIO.py`` has been removed as a vendored file and is now installed
|
||||||
from the upstream olefile pypi package. All internal dependencies are
|
from the upstream :pypi:`olefile` PyPI package. All internal dependencies are
|
||||||
redirected to the olefile package. Direct accesses to
|
redirected to the olefile package. Direct accesses to
|
||||||
``PIL.OlefileIO`` raises a deprecation warning, then patches the
|
``PIL.OlefileIO`` raises a deprecation warning, then patches the
|
||||||
upstream olefile into ``sys.modules`` in its place.
|
upstream olefile into ``sys.modules`` in its place.
|
||||||
|
|
|
@ -28,7 +28,7 @@ Scripts
|
||||||
|
|
||||||
The scripts formerly installed by Pillow have been split into a
|
The scripts formerly installed by Pillow have been split into a
|
||||||
separate package, pillow-scripts, living at
|
separate package, pillow-scripts, living at
|
||||||
https://github.com/python-pillow/pillow-scripts .
|
https://github.com/python-pillow/pillow-scripts.
|
||||||
|
|
||||||
|
|
||||||
API Changes
|
API Changes
|
||||||
|
@ -37,7 +37,7 @@ API Changes
|
||||||
OleFileIO.py
|
OleFileIO.py
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
The olefile module is no longer a required dependency when installing Pillow.
|
The :pypi:`olefile` module is no longer a required dependency when installing Pillow.
|
||||||
Support for plugins requiring olefile will not be loaded if it is not
|
Support for plugins requiring olefile will not be loaded if it is not
|
||||||
installed. This allows library consumers to avoid installing this dependency
|
installed. This allows library consumers to avoid installing this dependency
|
||||||
if they choose. Some library consumers have little interest in the format
|
if they choose. Some library consumers have little interest in the format
|
||||||
|
|
|
@ -8,7 +8,7 @@ Image size
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
|
|
||||||
If you attempt to set the size of an image directly, e.g.
|
If you attempt to set the size of an image directly, e.g.
|
||||||
``im.size = (100, 100)``, you will now receive an ``AttributeError``. This is
|
``im.size = (100, 100)``, you will now receive an :py:exc:`AttributeError`. This is
|
||||||
not about removing existing functionality, but instead about raising an
|
not about removing existing functionality, but instead about raising an
|
||||||
explicit error to prevent later consequences. The ``resize`` method is the
|
explicit error to prevent later consequences. The ``resize`` method is the
|
||||||
correct way to change an image's size.
|
correct way to change an image's size.
|
||||||
|
@ -16,7 +16,8 @@ correct way to change an image's size.
|
||||||
The exceptions to this are:
|
The exceptions to this are:
|
||||||
|
|
||||||
* The ICO and ICNS image formats, which use ``im.size = (100, 100)`` to select a subimage.
|
* The ICO and ICNS image formats, which use ``im.size = (100, 100)`` to select a subimage.
|
||||||
* The TIFF image format, which now has a ``DeprecationWarning`` for this action, as direct image size setting was previously necessary to work around an issue with tile extents.
|
* The TIFF image format, which now has a :py:exc:`DeprecationWarning` for this action,
|
||||||
|
as direct image size setting was previously necessary to work around an issue with tile extents.
|
||||||
|
|
||||||
|
|
||||||
API Additions
|
API Additions
|
||||||
|
|
|
@ -15,7 +15,7 @@ PNG: Handle IDAT chunks after image end
|
||||||
|
|
||||||
Some PNG images have multiple IDAT chunks. In some cases, Pillow will stop
|
Some PNG images have multiple IDAT chunks. In some cases, Pillow will stop
|
||||||
reading image data before the IDAT chunks finish. A regression caused an
|
reading image data before the IDAT chunks finish. A regression caused an
|
||||||
``EOFError`` exception when previously there was none. This is now fixed, and
|
:py:exc:`EOFError` exception when previously there was none. This is now fixed, and
|
||||||
file reading continues in case there are subsequent text chunks.
|
file reading continues in case there are subsequent text chunks.
|
||||||
|
|
||||||
PNG: MIME type
|
PNG: MIME type
|
||||||
|
@ -30,7 +30,7 @@ File closing
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
A regression caused an unsupported image file to report a
|
A regression caused an unsupported image file to report a
|
||||||
``ValueError: seek of closed file`` exception instead of an ``OSError``. This
|
``ValueError: seek of closed file`` exception instead of an :py:exc:`OSError`. This
|
||||||
has been fixed by ensuring that image plugins only close their internal ``__fp``
|
has been fixed by ensuring that image plugins only close their internal ``__fp``
|
||||||
if they are not the same as ``ImageFile``'s ``fp``, allowing each to manage their own
|
if they are not the same as ``ImageFile``'s ``fp``, allowing each to manage their own
|
||||||
file pointers.
|
file pointers.
|
||||||
|
|
|
@ -14,8 +14,8 @@ Pillow for Python 3.4 is 5.4.1.
|
||||||
Removed deprecated PIL.OleFileIO
|
Removed deprecated PIL.OleFileIO
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
PIL.OleFileIO was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of
|
``PIL.OleFileIO`` was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of
|
||||||
the upstream olefile Python package, and replaced with an ``ImportError``. The
|
the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError`. The
|
||||||
deprecated file has now been removed from Pillow. If needed, install from PyPI (eg.
|
deprecated file has now been removed from Pillow. If needed, install from PyPI (eg.
|
||||||
``python3 -m pip install olefile``).
|
``python3 -m pip install olefile``).
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ ImageCms.CmsProfile attributes
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Some attributes in ``ImageCms.CmsProfile`` have been deprecated since Pillow 3.2.0. From
|
Some attributes in ``ImageCms.CmsProfile`` have been deprecated since Pillow 3.2.0. From
|
||||||
6.0.0, they issue a ``DeprecationWarning``:
|
6.0.0, they issue a :py:exc:`DeprecationWarning`:
|
||||||
|
|
||||||
======================== ===============================
|
======================== ===============================
|
||||||
Deprecated Use instead
|
Deprecated Use instead
|
||||||
|
|
|
@ -29,10 +29,10 @@ API Additions
|
||||||
Image.entropy
|
Image.entropy
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
Calculates and returns the entropy for the image. A bilevel image (mode "1") is treated
|
Calculates and returns the entropy for the image. A bilevel image (mode "1") is treated
|
||||||
as a greyscale ("L") image by this method. If a mask is provided, the method employs
|
as a grayscale ("L") image by this method. If a mask is provided, the method employs
|
||||||
the histogram for those parts of the image where the mask image is non-zero. The mask
|
the histogram for those parts of the image where the mask image is non-zero. The mask
|
||||||
image must have the same size as the image, and be either a bi-level image (mode "1") or
|
image must have the same size as the image, and be either a bi-level image (mode "1") or
|
||||||
a greyscale image ("L").
|
a grayscale image ("L").
|
||||||
|
|
||||||
ImageGrab.grab
|
ImageGrab.grab
|
||||||
^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^
|
||||||
|
@ -58,7 +58,7 @@ file. ``ImageFont.FreeTypeFont`` has four new methods,
|
||||||
:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_name` for using named styles, and
|
:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_name` for using named styles, and
|
||||||
:py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_axes` and
|
:py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_axes` and
|
||||||
:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_axes` for using font axes
|
:py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_axes` for using font axes
|
||||||
instead. An ``IOError`` will be raised if the font is not a variation font. FreeType
|
instead. An :py:exc:`IOError` will be raised if the font is not a variation font. FreeType
|
||||||
2.9.1 or greater is required.
|
2.9.1 or greater is required.
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
|
|
|
@ -85,7 +85,7 @@ Custom unidentified image error
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Pillow will now throw a custom ``UnidentifiedImageError`` when an image cannot be
|
Pillow will now throw a custom ``UnidentifiedImageError`` when an image cannot be
|
||||||
identified. For backwards compatibility, this will inherit from ``OSError``.
|
identified. For backwards compatibility, this will inherit from :py:exc:`OSError`.
|
||||||
|
|
||||||
New argument ``reducing_gap`` for Image.resize() and Image.thumbnail() methods
|
New argument ``reducing_gap`` for Image.resize() and Image.thumbnail() methods
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|