Merge branch 'main' into windows1co

This commit is contained in:
Andrew Murray 2023-11-14 22:07:48 +11:00
commit 41d7a3757d
185 changed files with 3695 additions and 849 deletions

View File

@ -10,7 +10,7 @@ environment:
TEST_OPTIONS:
DEPLOY: YES
matrix:
- PYTHON: C:/Python311
- PYTHON: C:/Python312
ARCHITECTURE: x86
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
- PYTHON: C:/Python38-x64
@ -43,7 +43,7 @@ build_script:
test_script:
- 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%
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'

View File

@ -28,7 +28,8 @@ fi
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade wheel
PYTHONOPTIMIZE=0 python3 -m pip install cffi
# TODO Update condition when cffi supports 3.13
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
python3 -m pip install coverage
python3 -m pip install defusedxml
python3 -m pip install olefile
@ -38,7 +39,8 @@ python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
if [[ $(uname) != CYGWIN* ]]; then
python3 -m pip install numpy
# TODO Update condition when NumPy supports 3.13
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi
# PyQt6 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
@ -46,6 +48,16 @@ if [[ $(uname) != CYGWIN* ]]; then
python3 -m pip install pyqt6
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
pushd depends && ./install_webp.sh && popd

View File

@ -13,7 +13,7 @@ indent_style = space
trim_trailing_whitespace = true
[*.yml]
[*.{toml,yml}]
# Two-space indentation
indent_size = 2

View File

@ -2,6 +2,8 @@ name: CIFuzz
on:
push:
branches:
- "**"
paths:
- ".github/workflows/cifuzz.yml"
- "**.c"

View File

@ -2,6 +2,8 @@ name: Docs
on:
push:
branches:
- "**"
paths:
- ".github/workflows/docs.yml"
- "docs/**"

View File

@ -5,7 +5,9 @@ set -e
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
PYTHONOPTIMIZE=0 python3 -m pip install cffi
# TODO Update condition when cffi supports 3.13
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi
python3 -m pip install coverage
python3 -m pip install defusedxml
python3 -m pip install olefile
@ -14,7 +16,8 @@ python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
python3 -m pip install numpy
# TODO Update condition when NumPy supports 3.13
if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi
# extra test images
pushd depends && ./install_extra_test_images.sh && popd

View File

@ -2,13 +2,23 @@ name: Test Cygwin
on:
push:
branches:
- "**"
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- ".travis.yml"
- "docs/**"
- "wheels/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- ".travis.yml"
- "docs/**"
- "wheels/**"
workflow_dispatch:
permissions:

View File

@ -2,13 +2,23 @@ name: Test Docker
on:
push:
branches:
- "**"
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- ".travis.yml"
- "docs/**"
- "wheels/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- ".travis.yml"
- "docs/**"
- "wheels/**"
workflow_dispatch:
permissions:
@ -41,8 +51,8 @@ jobs:
debian-11-bullseye-amd64,
debian-12-bookworm-x86,
debian-12-bookworm-amd64,
fedora-37-amd64,
fedora-38-amd64,
fedora-39-amd64,
gentoo,
ubuntu-20.04-focal-amd64,
ubuntu-22.04-jammy-amd64,

View File

@ -2,13 +2,23 @@ name: Test MinGW
on:
push:
branches:
- "**"
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- ".travis.yml"
- "docs/**"
- "wheels/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- ".travis.yml"
- "docs/**"
- "wheels/**"
workflow_dispatch:
permissions:

View File

@ -1,9 +1,11 @@
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:
push:
branches:
- "**"
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"

View File

@ -4,11 +4,19 @@ on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- ".travis.yml"
- "docs/**"
- "wheels/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- ".travis.yml"
- "docs/**"
- "wheels/**"
workflow_dispatch:
permissions:
@ -24,7 +32,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
timeout-minutes: 30
@ -51,14 +59,15 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: pip
cache-dependency-path: ".github/workflows/test-windows.yml"
- name: Print build system information
run: python3 .github/workflows/system-info.py
- name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml
run: 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 olefile pyroma
- name: Install dependencies
id: install

View File

@ -2,13 +2,23 @@ name: Test
on:
push:
branches:
- "**"
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- ".travis.yml"
- "docs/**"
- "wheels/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- ".github/workflows/wheels*"
- ".gitmodules"
- ".travis.yml"
- "docs/**"
- "wheels/**"
workflow_dispatch:
permissions:
@ -31,7 +41,8 @@ jobs:
python-version: [
"pypy3.10",
"pypy3.9",
"3.12-dev",
"3.13",
"3.12",
"3.11",
"3.10",
"3.9",
@ -54,6 +65,7 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
cache: pip
cache-dependency-path: ".ci/*.sh"

40
.github/workflows/wheels-build.sh vendored Executable file
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
[submodule "multibuild"]
path = wheels/multibuild
url = https://github.com/multi-build/multibuild.git

View File

@ -1,20 +1,14 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v3.13.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.4
hooks:
- id: pyupgrade
args: [--py38-plus]
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.9.1
rev: 23.10.1
hooks:
- 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
rev: 1.7.5
@ -23,32 +17,19 @@ repos:
args: [--severity-level=high]
files: ^src/
- repo: https://github.com/asottile/yesqa
rev: v1.5.0
hooks:
- id: yesqa
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.4
hooks:
- id: remove-tabs
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
rev: v1.10.0
hooks:
- id: python-check-blanket-noqa
- id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-executables-have-shebangs
- id: check-merge-conflict
@ -61,17 +42,17 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/sphinx-contrib/sphinx-lint
rev: v0.6.8
rev: v0.8.1
hooks:
- id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt
rev: 1.1.0
rev: 1.4.1
hooks:
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.14
rev: v0.15
hooks:
- id: validate-pyproject

135
.travis.yml Normal file
View 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

View File

@ -2,9 +2,75 @@
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
[radarhere]
@ -2137,7 +2203,7 @@ Changelog (Pillow)
- Cache EXIF information #3498
[Glandos]
- Added transparency for all PNG greyscale modes #3744
- Added transparency for all PNG grayscale modes #3744
[radarhere]
- Fix deprecation warnings in Python 3.8 #3749
@ -4639,7 +4705,7 @@ Changelog (Pillow)
- Fix Bicubic interpolation #970
[homm]
- Support for 4-bit greyscale TIFF images #980
- Support for 4-bit grayscale TIFF images #980
[hugovk]
- Updated manifest #957
@ -6714,7 +6780,7 @@ The test suite includes 750 individual tests.
- You can now convert directly between all modes supported by
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.
- 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.
- 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.
- 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
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.
- 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
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).
- Some minor improvements to the sample scripts provided in the

View File

@ -5,8 +5,10 @@ include *.md
include *.py
include *.rst
include *.sh
include *.toml
include *.txt
include *.yaml
include .flake8
include LICENSE
include Makefile
include tox.ini
@ -29,3 +31,4 @@ global-exclude .git*
global-exclude *.pyc
global-exclude *.so
prune .ci
prune wheels

View File

@ -49,7 +49,7 @@ help:
@echo " install make and install"
@echo " install-coverage make and install with C coverage"
@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 " test run tests on installed Pillow"
@ -118,6 +118,6 @@ lint:
.PHONY: lint-fix
lint-fix:
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 --target-version py38 .
python3 -m isort .
python3 -m black .
python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff
python3 -m ruff --fix .

View File

@ -45,12 +45,12 @@ As of 2019, Pillow development is
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
alt="AppVeyor CI build status (Windows)"
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
alt="GitHub Actions wheels build status (Wheels)"
src="https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg"></a>
<a href="https://app.travis-ci.com/github/python-pillow/pillow-wheels"><img
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
alt="GitHub Actions build status (Wheels)"
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
<a href="https://app.travis-ci.com/github/python-pillow/Pillow"><img
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
alt="Code coverage"
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
alt="Number of PyPI downloads"
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"
src="https://bestpractices.coreinfrastructure.org/projects/6331/badge"></a>
src="https://www.bestpractices.dev/projects/6331/badge"></a>
</td>
</tr>
<tr>

View File

@ -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
* [ ] 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 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`
* [ ] Update `CHANGES.rst`.
* [ ] 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 push --tags
```
* [ ] Create and check source distribution:
```bash
make sdist
```
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
* [ ] Check and upload all binaries and source distributions e.g.:
* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
* [ ] Check and upload all source and binary distributions e.g.:
```bash
python3 -m twine check --strict dist/*
python3 -m twine upload dist/Pillow-5.2.0*
@ -59,8 +55,8 @@ Released as needed for security, installation or critical bug fixes.
```bash
make sdist
```
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
* [ ] Check and upload all binaries and source distributions e.g.:
* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
* [ ] Check and upload all source and binary distributions e.g.:
```bash
python3 -m twine check --strict dist/*
python3 -m twine upload dist/Pillow-5.2.1*
@ -90,26 +86,23 @@ Released as needed privately to individual vendors for critical security-related
```bash
make sdist
```
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions)
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
```bash
git push origin 2.5.x
```
## Binary Distributions
## Source and Binary Distributions
### macOS and Linux
* [ ] 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
git clone https://github.com/python-pillow/pillow-wheels
cd pillow-wheels
./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
gh run download --dir dist
# select dist
```
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
and copy into `dist`.
### Windows
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)

View File

@ -45,7 +45,7 @@ def test_direct():
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_set, "PyAccess - set", im.size, access)
timer(iterate_get, "C-api - get", im.size, caccess)

View File

@ -95,7 +95,7 @@ def assert_image_equal(a, b, msg=None):
except Exception:
pass
assert False, msg or "got different content"
pytest.fail(msg or "got different content")
def assert_image_equal_tofile(a, filename, msg=None, mode=None):

View File

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 331 B

View File

Before

Width:  |  Height:  |  Size: 668 B

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 B

After

Width:  |  Height:  |  Size: 180 B

BIN
Tests/images/palette.dds Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

BIN
Tests/images/xmp_padded.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

Binary file not shown.

View File

@ -15,7 +15,7 @@
#
################################################################################
python3 setup.py build --build-base=/tmp/build install
python3 -m pip install .
# Build fuzzers in $OUT.
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do

View File

@ -231,13 +231,13 @@ def test_apng_mode():
assert im.getpixel((0, 0)) == (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"
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == 128
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"
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (128, 191)
@ -350,7 +350,7 @@ def test_apng_save(tmp_path):
im.load()
assert not im.is_animated
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.getpixel((0, 0)) == (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]
)
with Image.open(test_file) as im:
im.load()
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
frame.info["duration"] = 750
frame.save(test_file, save_all=True)
frame.info["duration"] = 300
frame.save(test_file, save_all=True, append_images=[frame, different_frame])
with Image.open(test_file) as im:
assert im.info.get("duration") == 750
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
assert im.n_frames == 2
assert im.info["duration"] == 600
def test_apng_save_disposal(tmp_path):
@ -673,10 +676,17 @@ def test_seek_after_close():
@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")
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:
assert reloaded.mode == mode

View File

@ -159,7 +159,7 @@ def test_rle8():
with Image.open("Tests/images/hopper_rle8.bmp") as im:
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")
# This test image has been manually hexedited

View File

@ -298,6 +298,11 @@ def test_dxt5_colorblock_alpha_issue_4142():
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():
with pytest.raises(NotImplementedError):
with Image.open("Tests/images/unimplemented_pixel_format.dds"):

View File

@ -8,6 +8,7 @@ from .helper import (
assert_image_similar,
assert_image_similar_tofile,
hopper,
is_win32,
mark_if_feature_version,
skip_unless_feature,
)
@ -98,6 +99,20 @@ def test_load():
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():
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
@ -404,3 +419,18 @@ def test_timeout(test_file):
with pytest.raises(Image.UnidentifiedImageError):
with Image.open(f):
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

View File

@ -205,14 +205,14 @@ def test_optimize_full_l():
def test_optimize_if_palette_can_be_reduced_by_half():
with Image.open("Tests/images/test.colors.gif") as im:
# Reduce dimensions because original is too big for _get_optimize()
im = im.resize((591, 443))
im_rgb = im.convert("RGB")
im = Image.new("P", (8, 1))
im.palette = ImagePalette.raw("RGB", bytes((0, 0, 0) * 150))
for i in range(8):
im.putpixel((i, 0), (i + 1, 0, 0))
for optimize, colors in ((False, 256), (True, 8)):
out = BytesIO()
im_rgb.save(out, "GIF", optimize=optimize)
im.save(out, "GIF", optimize=optimize)
with Image.open(out) as reloaded:
assert len(reloaded.palette.palette) // 3 == colors
@ -590,7 +590,7 @@ def test_save_dispose(tmp_path):
def test_dispose2_palette(tmp_path):
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)]
im_list = []
@ -1180,18 +1180,17 @@ def test_palette_save_L(tmp_path):
def test_palette_save_P(tmp_path):
# Pass in a different palette, then construct what the image would look like.
# Forcing a non-straight grayscale palette.
im = hopper("P")
palette = bytes(255 - i // 3 for i in range(768))
im = Image.new("P", (1, 2))
im.putpixel((0, 1), 1)
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:
im.putpalette(palette)
assert_image_equal(reloaded, im)
reloaded_rgb = reloaded.convert("RGB")
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):

View File

@ -1,5 +1,7 @@
import sys
from io import StringIO
from io import BytesIO, StringIO
import pytest
from PIL import Image, IptcImagePlugin
@ -30,6 +32,36 @@ def test_getiptcinfo_jpg_found():
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():
# Arrange
with Image.open("Tests/images/hopper.tif") as im:

View File

@ -767,6 +767,13 @@ class TestFileJpeg:
# This should return the default
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):
# Arrange
# This is photoshop-200dpi.jpg with resolution removed from EXIF:
@ -882,7 +889,10 @@ class TestFileJpeg:
def test_getxmp(self):
with Image.open("Tests/images/xmp_test.jpg") as im:
if ElementTree is None:
with pytest.warns(UserWarning):
with pytest.warns(
UserWarning,
match="XMP data cannot be read without defusedxml dependency",
):
assert im.getxmp() == {}
else:
xmp = im.getxmp()
@ -905,6 +915,28 @@ class TestFileJpeg:
with Image.open("Tests/images/hopper.jpg") as im:
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)
def test_eof(self):
# Even though this decoder never says that it is finished
@ -929,6 +961,28 @@ class TestFileJpeg:
im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = False
def test_separate_tables(self):
im = hopper()
data = [] # [interchange, tables-only, image-only]
for streamtype in range(3):
out = BytesIO()
im.save(out, format="JPEG", streamtype=streamtype)
data.append(out.getvalue())
# SOI, EOI
for marker in b"\xff\xd8", b"\xff\xd9":
assert marker in data[1] and marker in data[2]
# DHT, DQT
for marker in b"\xff\xc4", b"\xff\xdb":
assert marker in data[1] and marker not in data[2]
# SOF0, SOS, APP0 (JFIF header)
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
assert marker not in data[1] and marker in data[2]
with Image.open(BytesIO(data[0])) as interchange_im:
with Image.open(BytesIO(data[1] + data[2])) as combined_im:
assert_image_equal(interchange_im, combined_im)
def test_repr_jpeg(self):
im = hopper()

View File

@ -416,7 +416,7 @@ def test_plt_marker():
while True:
marker = out.read(2)
if not marker:
assert False, "End of stream without PLT"
pytest.fail("End of stream without PLT")
jp2_boxid = _binary.i16be(marker)
if jp2_boxid == 0xFF4F:
@ -426,7 +426,7 @@ def test_plt_marker():
# PLT
return
elif jp2_boxid == 0xFF93:
assert False, "SOD without finding PLT first"
pytest.fail("SOD without finding PLT first")
hdr = out.read(2)
length = _binary.i16be(hdr)

View File

@ -26,8 +26,7 @@ def open_with_magick(magick, tmp_path, f):
rc = subprocess.call(
magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
)
if rc:
raise OSError
assert not rc
return Image.open(outfile)

View File

@ -92,11 +92,11 @@ class TestFilePng:
assert im.format == "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.save(test_file)
with Image.open(test_file) as reloaded:
if mode == "I;16":
if mode in ("I;16", "I;16B"):
reloaded = reloaded.convert(mode)
assert_image_equal(reloaded, im)
@ -297,7 +297,7 @@ class TestFilePng:
assert_image(im, "RGBA", (10, 10))
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():
in_file = "Tests/images/" + mode.lower() + "_trns.png"
with Image.open(in_file) as im:
@ -665,7 +665,10 @@ class TestFilePng:
def test_getxmp(self):
with Image.open("Tests/images/color_snakes.png") as im:
if ElementTree is None:
with pytest.warns(UserWarning):
with pytest.warns(
UserWarning,
match="XMP data cannot be read without defusedxml dependency",
):
assert im.getxmp() == {}
else:
xmp = im.getxmp()

View File

@ -734,7 +734,10 @@ class TestFileTiff:
def test_getxmp(self):
with Image.open("Tests/images/lab.tif") as im:
if ElementTree is None:
with pytest.warns(UserWarning):
with pytest.warns(
UserWarning,
match="XMP data cannot be read without defusedxml dependency",
):
assert im.getxmp() == {}
else:
xmp = im.getxmp()

View File

@ -235,3 +235,13 @@ class TestFileWebp:
with Image.open(out_webp) as reloaded:
reloaded.load()
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)

View File

@ -118,7 +118,10 @@ def test_getxmp():
with Image.open("Tests/images/flower2.webp") as im:
if ElementTree is None:
with pytest.warns(UserWarning):
with pytest.warns(
UserWarning,
match="XMP data cannot be read without defusedxml dependency",
):
assert im.getxmp() == {}
else:
assert (

View File

@ -638,8 +638,8 @@ class TestImage:
im.remap_palette(None)
def test_remap_palette_transparency(self):
im = Image.new("P", (1, 2))
im.putpixel((0, 1), 1)
im = Image.new("P", (1, 2), (0, 0, 0))
im.putpixel((0, 1), (255, 0, 0))
im.info["transparency"] = 0
im_remapped = im.remap_palette([1, 0])
@ -906,6 +906,38 @@ class TestImage:
im = Image.new("RGB", size)
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):
im = Image.new("P", (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:
try:
im.load()
assert False
pytest.fail()
except OSError as e:
buffer_overrun = str(e) == "buffer overrun when reading image file"
truncated = "image file is truncated" in str(e)
@ -978,7 +1010,7 @@ class TestImage:
with Image.open("Tests/images/fli_overrun2.bin") as im:
try:
im.seek(1)
assert False
pytest.fail()
except OSError as e:
assert str(e) == "buffer overrun when reading image file"

View File

@ -130,9 +130,16 @@ class TestImageGetPixel(AccessTest):
bands = Image.getmodebands(mode)
if bands == 1:
return 1
if mode in ("BGR;15", "BGR;16"):
# These modes have less than 8 bits per band
# So (1, 2, 3) cannot be roundtripped
return (16, 32, 49)
return tuple(range(1, bands + 1))
def check(self, mode, 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:
expected_color = self.color(mode)
@ -203,6 +210,9 @@ class TestImageGetPixel(AccessTest):
"F",
"P",
"PA",
"BGR;15",
"BGR;16",
"BGR;24",
"RGB",
"RGBA",
"RGBX",

View File

@ -117,11 +117,11 @@ def test_trns_p(tmp_path):
f = str(tmp_path / "temp.png")
im_l = im.convert("L")
assert im_l.info["transparency"] == 1 # undone
assert im_l.info["transparency"] == 0
im_l.save(f)
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)

View File

@ -76,6 +76,15 @@ def test_mode_F():
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():
# shouldn't segfault
# see https://github.com/python-pillow/Pillow/issues/1008

View File

@ -84,3 +84,14 @@ def test_rgba_palette(mode, palette):
im.putpalette(palette, mode)
assert im.getpalette() == [1, 2, 3]
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)

View File

@ -195,7 +195,7 @@ class TestReducingGapResize:
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
)
with pytest.raises(AssertionError):
with pytest.raises(pytest.fail.Exception):
assert_image_equal(ref, im)
assert_image_similar(ref, im, epsilon)
@ -210,7 +210,7 @@ class TestReducingGapResize:
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0
)
with pytest.raises(AssertionError):
with pytest.raises(pytest.fail.Exception):
assert_image_equal(ref, im)
assert_image_similar(ref, im, epsilon)
@ -225,7 +225,7 @@ class TestReducingGapResize:
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0
)
with pytest.raises(AssertionError):
with pytest.raises(pytest.fail.Exception):
assert_image_equal(ref, im)
assert_image_similar(ref, im, epsilon)

View File

@ -147,7 +147,7 @@ def test_reducing_gap_values():
ref = hopper()
ref.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=None)
with pytest.raises(AssertionError):
with pytest.raises(pytest.fail.Exception):
assert_image_equal(ref, im)
assert_image_similar(ref, im, 3.5)

View File

@ -10,7 +10,7 @@ GREEN = (0, 255, 0)
ORANGE = (255, 128, 0)
WHITE = (255, 255, 255)
GREY = 128
GRAY = 128
def test_sanity():
@ -121,12 +121,12 @@ def test_constant():
im = Image.new("RGB", (20, 10))
# Act
new = ImageChops.constant(im, GREY)
new = ImageChops.constant(im, GRAY)
# Assert
assert new.size == im.size
assert new.getpixel((0, 0)) == GREY
assert new.getpixel((19, 9)) == GREY
assert new.getpixel((0, 0)) == GRAY
assert new.getpixel((19, 9)) == GRAY
def test_darker_image():

View File

@ -1,8 +1,9 @@
import contextlib
import os.path
import pytest
from PIL import Image, ImageColor, ImageDraw, ImageFont
from PIL import Image, ImageColor, ImageDraw, ImageFont, features
from .helper import (
assert_image_equal,
@ -586,6 +587,18 @@ def test_point(points):
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)
def test_polygon(points):
# Arrange
@ -732,7 +745,7 @@ def test_rectangle_I16(bbox):
draw = ImageDraw.Draw(im)
# Act
draw.rectangle(bbox, fill="black", outline="green")
draw.rectangle(bbox, outline=0xFFFF)
# Assert
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
finally:
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)

View File

@ -141,7 +141,9 @@ def test_I16(font):
draw = ImageDraw.Draw(im)
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"
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)
)
def test_rotated_transposed_font(font, orientation):
img_grey = Image.new("L", (100, 100))
draw = ImageDraw.Draw(img_grey)
img_gray = Image.new("L", (100, 100))
draw = ImageDraw.Draw(img_gray)
word = "testing"
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):
img_grey = Image.new("L", (100, 100))
draw = ImageDraw.Draw(img_grey)
img_gray = Image.new("L", (100, 100))
draw = ImageDraw.Draw(img_gray)
word = "testing"
transposed_font = ImageFont.TransposedFont(font, orientation=orientation)
@ -451,7 +453,7 @@ def test_load_non_font_bytes():
def test_default_font():
# 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))
draw = ImageDraw.Draw(im)
@ -459,8 +461,11 @@ def test_default_font():
default_font = ImageFont.load_default()
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_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"))
@ -483,14 +488,6 @@ def test_render_empty(font):
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):
# issue #3777
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)
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(
"anchor, left, top",
(

View 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)

View File

@ -39,6 +39,9 @@ def test_sanity():
ImageOps.contain(hopper("L"), (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("RGB"), 1)
@ -119,6 +122,20 @@ def test_contain_round():
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():
# Same ratio
im = hopper()
@ -416,6 +433,12 @@ def test_exif_transpose_in_place():
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():
# Test the cutoff argument of autocontrast
with Image.open("Tests/images/bw_gradient.png") as img:

View File

@ -85,7 +85,7 @@ def test_ipythonviewer():
test_viewer = viewer
break
else:
assert False
pytest.fail()
im = hopper()
assert test_viewer.show(im) == 1

View File

@ -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", "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):
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
self.assert_unpack(

View File

@ -1,6 +1,6 @@
import sys
from setuptools.build_meta import * # noqa: F401, F403
from setuptools.build_meta import * # noqa: F403
from setuptools.build_meta import build_wheel
backend_class = build_wheel.__self__.__class__

View File

@ -1,7 +1,7 @@
#!/bin/bash
# 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

View File

@ -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
.. _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
.. _Python Package Index: https://pypi.org/project/Pillow/

View File

@ -318,14 +318,14 @@ def setup(app):
linkcheck_allowed_redirects = {
r"https://bestpractices.coreinfrastructure.org/projects/6331": r"https://bestpractices.coreinfrastructure.org/en/.*", # noqa: E501
r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", # noqa: E501
r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", # noqa: E501
r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", # 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",
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",
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://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/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", # 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",
r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+",
}
# sphinx.ext.extlinks
@ -338,6 +338,7 @@ extlinks = {
"cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"),
"issue": (_repo + "issues/%s", "#%s"),
"pr": (_repo + "pull/%s", "#%s"),
"pypi": ("https://pypi.org/project/%s/", "%s"),
}
# sphinxext.opengraph

View File

@ -10,7 +10,7 @@ Deprecated features
-------------------
Below are features which are considered deprecated. Where appropriate,
a ``DeprecationWarning`` is issued.
a :py:exc:`DeprecationWarning` is issued.
PSFile
~~~~~~
@ -267,7 +267,7 @@ ImageFile.raise_ioerror
.. deprecated:: 7.2.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.
Use ``ImageFile.raise_oserror`` instead.
@ -293,9 +293,9 @@ im.offset
``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead.
It was documented as deprecated in PIL 1.1.2,
raised a ``DeprecationWarning`` since 1.1.5,
an ``Exception`` since Pillow 3.0.0
and ``NotImplementedError`` since 3.3.0.
raised a :py:exc:`DeprecationWarning` since 1.1.5,
an :py:exc:`Exception` since Pillow 3.0.0
and :py:exc:`NotImplementedError` since 3.3.0.
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.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead.
They issued a ``DeprecationWarning`` since 2.0.0,
an ``Exception`` since 3.0.0
and ``NotImplementedError`` since 3.3.0.
They issued a :py:exc:`DeprecationWarning` since 2.0.0,
an :py:exc:`Exception` since 3.0.0
and :py:exc:`NotImplementedError` since 3.3.0.
ImageCms.CmsProfile attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -318,7 +318,7 @@ ImageCms.CmsProfile attributes
.. versionremoved:: 8.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
@ -442,7 +442,7 @@ PIL.OleFileIO
.. deprecated:: 4.0.0
.. versionremoved:: 6.0.0
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
``PIL.OleFileIO`` was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of
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
PyPI (eg. ``python3 -m pip install olefile``).

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -63,8 +63,35 @@ DDS
^^^
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,
DXT3 (since 3.4.0) and DXT5 pixel formats can be read, only in ``RGBA`` mode.
by DirectX.
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
^^^
@ -88,8 +115,13 @@ in ``L``, ``RGB`` and ``CMYK`` modes.
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`
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**
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
into account.
To enable FPX support, you must install :pypi:`olefile`.
.. note::
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.
To enable MIC support, you must install :pypi:`olefile`.
MPO
^^^

View File

@ -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
: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”
(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.
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
: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
@ -568,7 +599,7 @@ Controlling the decoder
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
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
loaded image so it as closely as possible matches the given mode and size. This

View File

@ -26,14 +26,14 @@ Pillow decodes files in two stages:
it.
An image plugin should contain a format handler derived from the
:py:class:`PIL.ImageFile.ImageFile` base class. This class should
provide an ``_open`` method, which reads the file header and
sets up at least the :py:attr:`~PIL.Image.Image.mode` and
:py:attr:`~PIL.Image.Image.size` attributes. To be able to load the
file, the method must also create a list of ``tile`` descriptors,
which contain a decoder name, extents of the tile, and
any decoder-specific data. The format handler class must be explicitly
registered, via a call to the :py:mod:`~PIL.Image` module.
:py:class:`PIL.ImageFile.ImageFile` base class. This class should provide an
``_open`` method, which reads the file header and set at least the internal
``_size`` and ``_mode`` attributes so that :py:attr:`~PIL.Image.Image.mode` and
:py:attr:`~PIL.Image.Image.size` are populated. To be able to load the file,
the method must also create a list of ``tile`` descriptors, which contain a
decoder name, extents of the tile, and any decoder-specific data. The format
handler class must be explicitly registered, via a call to the
:py:mod:`~PIL.Image` module.
.. note:: For performance reasons, it is important that 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
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
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.
**SpamImagePlugin.py**::
@ -96,13 +96,13 @@ true color.
)
The format handler must always set the
:py:attr:`~PIL.Image.Image.size` and :py:attr:`~PIL.Image.Image.mode`
attributes. If these are not set, the file cannot be opened. To
simplify the plugin, the calling code considers exceptions like
:py:exc:`SyntaxError`, :py:exc:`KeyError`, :py:exc:`IndexError`,
:py:exc:`EOFError` and :py:exc:`struct.error` as a failure to identify
the file.
The format handler must always set the internal ``_size`` and ``_mode``
attributes so that :py:attr:`~PIL.Image.Image.size` and
:py:attr:`~PIL.Image.Image.mode` are populated. If these are not set, the file
cannot be opened. To simplify the plugin, the calling code considers exceptions
like :py:exc:`SyntaxError`, :py:exc:`KeyError`, :py:exc:`IndexError`,
:py:exc:`EOFError` and :py:exc:`struct.error` as a failure to identify the
file.
Note that the image plugin must be explicitly registered using
: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 |
| | | 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. |
+-----------+-------------------------------------------------------------------+

View File

@ -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
:alt: AppVeyor CI build status (Windows)
.. image:: https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg
:target: https://github.com/python-pillow/pillow-wheels/actions
:alt: GitHub Actions wheels build status (Wheels)
.. image:: https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg
:target: https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml
:alt: GitHub Actions build status (Wheels)
.. image:: https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels
:target: https://app.travis-ci.com/github/python-pillow/pillow-wheels
.. 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
:alt: Travis CI wheels build status (aarch64)
.. 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/
:alt: Number of PyPI downloads
.. image:: https://bestpractices.coreinfrastructure.org/projects/6331/badge
:target: https://bestpractices.coreinfrastructure.org/projects/6331
.. image:: https://www.bestpractices.dev/projects/6331/badge
:target: https://www.bestpractices.dev/projects/6331
:alt: OpenSSF Best Practices
.. image:: https://badges.gitter.im/python-pillow/Pillow.svg

View File

@ -42,6 +42,11 @@ Install Pillow with :command:`pip`::
python3 -m pip install --upgrade pip
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
@ -82,6 +87,8 @@ Install Pillow with :command:`pip`::
.. tab:: Windows
.. warning:: Pillow > 9.5.0 no longer includes 32-bit wheels.
We provide Pillow binaries for Windows compiled for the matrix of
supported Pythons in 64-bit versions in the wheel format. These binaries include
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
* 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
@ -180,7 +187,7 @@ Many of Pillow's features require external libraries:
* **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
the Pillow license, therefore we will not be distributing binaries
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
a non-standard location, you may need to configure setuptools to use
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::
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 |
+----------------------------------+----------------------------+---------------------+
| Fedora 37 | 3.11 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 38 | 3.11 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 39 | 3.12 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Gentoo | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 |
@ -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 |
| | 3.12, PyPy3 | |
| +----------------------------+---------------------+
| | 3.11 | x86 |
| | 3.12 | x86 |
| +----------------------------+---------------------+
| | 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
document and send a pull request.
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Operating system | | Tested Python | | Latest tested | | Tested |
| | | versions | | Pillow version | | processors |
+==================================+===========================+==================+==============+
+==================================+============================+==================+==============+
| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.1.0 |arm |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
| +---------------------------+------------------+ |
| +----------------------------+------------------+ |
| | 3.7 | 9.5.0 | |
+----------------------------------+---------------------------+------------------+--------------+
| macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
+----------------------------------+----------------------------+------------------+--------------+
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
| +---------------------------+------------------+--------------+
| +----------------------------+------------------+--------------+
| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 |
| +---------------------------+------------------+ |
| +----------------------------+------------------+ |
| | 3.6 | 8.4.0 | |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 |
| +---------------------------+------------------+ |
| +----------------------------+------------------+ |
| | 3.5 | 7.2.0 | |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
| +---------------------------+------------------+ |
| +----------------------------+------------------+ |
| | 2.7 | 6.0.0 | |
| +---------------------------+------------------+ |
| +----------------------------+------------------+ |
| | 3.4 | 5.4.1 | |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 |
| +---------------------------+------------------+ |
| +----------------------------+------------------+ |
| | 3.3 | 4.1.0 | |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Redhat Linux 6 | 2.6 | |x86 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| CentOS 6.3 | 2.7, 3.3 | |x86 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| CentOS 8 | 3.9 | 9.0.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 |
| | | PyPy5.3.1, PyPy3 v2.4.0 | | |
| +---------------------------+------------------+--------------+
| +----------------------------+------------------+--------------+
| | 2.7 | 4.3.0 |x86-64 |
| +---------------------------+------------------+--------------+
| +----------------------------+------------------+--------------+
| | 2.7, 3.2 | 3.4.1 |ppc |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm |
| +---------------------------+------------------+ |
| +----------------------------+------------------+ |
| | 2.7 | 6.2.2 | |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Windows 10 | 3.7 | 7.1.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 |
+----------------------------------+---------------------------+------------------+--------------+
+----------------------------------+----------------------------+------------------+--------------+
Old Versions
------------

View File

@ -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`
.. autoattribute:: PIL.Image.Image.has_transparency_data
Classes
-------

View File

@ -59,7 +59,7 @@ Functions
.. py:method:: getcolor(color, mode)
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.
.. versionadded:: 1.1.4

View File

@ -351,7 +351,7 @@ Methods
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.
@ -416,8 +416,14 @@ Methods
.. 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.
@ -477,7 +483,13 @@ Methods
.. 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
in font with provided direction, features, and language.
@ -538,9 +550,15 @@ Methods
It should be a `BCP 47 language code`_.
Requires libraqm.
: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.
.. 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
when rendered in font with provided direction, features, and language.
@ -588,9 +606,15 @@ Methods
Requires libraqm.
:param stroke_width: The width of the text stroke.
: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
.. 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
when rendered in font with provided direction, features, and language.
@ -632,6 +656,12 @@ Methods
Requires libraqm.
:param stroke_width: The width of the text stroke.
: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
.. py:method:: getdraw(im=None, hints=None)

View File

@ -58,7 +58,7 @@ method:
This class can be used to control the contrast of an image, similar to the
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
increase the contrast of the image.

View File

@ -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
`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.
Starting with version 1.1.4, PIL can be configured to support TrueType and
@ -20,7 +20,7 @@ the imToolkit package.
.. warning::
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`.
This threshold can be changed by setting
@ -89,5 +89,5 @@ Constants
.. data:: MAX_STRING_LENGTH
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``.

View File

@ -72,7 +72,7 @@ pixel bits.
Note that the operands are converted to 32-bit signed integers before the
bitwise operation is applied. This means that youll 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.
Bitwise operators dont work on floating point images.

View File

@ -12,14 +12,11 @@ only work on L and RGB images.
.. autofunction:: autocontrast
.. autofunction:: colorize
.. autofunction:: contain
.. autofunction:: pad
.. autofunction:: crop
.. autofunction:: scale
.. autofunction:: deform
.. autofunction:: equalize
.. autofunction:: expand
.. autofunction:: fit
.. autofunction:: flip
.. autofunction:: grayscale
.. autofunction:: invert
@ -27,3 +24,38 @@ only work on L and RGB images.
.. autofunction:: posterize
.. autofunction:: solarize
.. 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

View File

@ -173,8 +173,8 @@ been processed before Pillow started checking for decompression bombs.
Added ImageFont.MAX_STRING_LENGTH
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To protect against potential DOS attacks when using arbitrary strings as text
input, Pillow will now raise a ``ValueError`` if the number of characters
:cve:`2023-44271`: To protect against potential DOS attacks when using arbitrary strings as text
input, Pillow will now raise a :py:exc:`ValueError` if the number of characters
passed into ImageFont methods is over a certain limit,
:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`.

View File

@ -1,54 +1,109 @@
10.1.0
------
Backwards Incompatible Changes
==============================
API Changes
===========
Setting image mode
^^^^^^^^^^^^^^^^^^
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
explicit error to prevent later consequences. The ``convert`` method is the
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
=============
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
=============
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.

View File

@ -10,7 +10,7 @@ operations. As a result PIL was unable to open them as images, requiring a wrap
``cStringIO`` or ``BytesIO``.
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
objects).

View File

@ -19,7 +19,7 @@ Deprecation Warning when Saving JPEGs
JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0
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.
New DDS Decoders

View File

@ -17,8 +17,8 @@ Pillow 4.0 supports Python 3.6.
OleFileIO.py
============
OleFileIO.py has been removed as a vendored file and is now installed
from the upstream olefile pypi package. All internal dependencies are
``OleFileIO.py`` has been removed as a vendored file and is now installed
from the upstream :pypi:`olefile` PyPI package. All internal dependencies are
redirected to the olefile package. Direct accesses to
``PIL.OlefileIO`` raises a deprecation warning, then patches the
upstream olefile into ``sys.modules`` in its place.

View File

@ -37,7 +37,7 @@ API Changes
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
installed. This allows library consumers to avoid installing this dependency
if they choose. Some library consumers have little interest in the format

View File

@ -8,7 +8,7 @@ Image size
^^^^^^^^^^
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
explicit error to prevent later consequences. The ``resize`` method is the
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 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

View File

@ -15,7 +15,7 @@ PNG: Handle IDAT chunks after image end
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
``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.
PNG: MIME type
@ -30,7 +30,7 @@ File closing
^^^^^^^^^^^^
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``
if they are not the same as ``ImageFile``'s ``fp``, allowing each to manage their own
file pointers.

View File

@ -14,8 +14,8 @@ Pillow for Python 3.4 is 5.4.1.
Removed deprecated PIL.OleFileIO
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
``PIL.OleFileIO`` was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of
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.
``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
6.0.0, they issue a ``DeprecationWarning``:
6.0.0, they issue a :py:exc:`DeprecationWarning`:
======================== ===============================
Deprecated Use instead

View File

@ -29,10 +29,10 @@ API Additions
Image.entropy
^^^^^^^^^^^^^
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
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
^^^^^^^^^^^^^^
@ -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.get_variation_axes` and
: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.
Other Changes

View File

@ -85,7 +85,7 @@ Custom unidentified image error
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Some files were not shown because too many files have changed in this diff Show More