Merge branch 'main' into jpeg_xmp

This commit is contained in:
Andrew Murray 2023-10-06 17:23:53 +11:00
commit f24222a954
169 changed files with 2478 additions and 747 deletions

View File

@ -21,13 +21,11 @@ environment:
install:
- '%PYTHON%\%EXECUTABLE% --version'
- '%PYTHON%\%EXECUTABLE% -m pip install --upgrade pip'
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
- 7z x pillow-depends.zip -oc:\
- 7z x pillow-test-images.zip -oc:\
- mv c:\pillow-depends-main c:\pillow-depends
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
- 7z x ..\pillow-depends\nasm-2.16.01-win64.zip -oc:\
- curl -fsSL -o nasm-win64.zip https://raw.githubusercontent.com/python-pillow/pillow-depends/main/nasm-2.16.01-win64.zip
- 7z x nasm-win64.zip -oc:\
- choco install ghostscript --version=10.0.0.20230317
- path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH%
- cd c:\pillow\winbuild\

View File

@ -23,7 +23,7 @@ if [[ $(uname) != CYGWIN* ]]; then
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
sway wl-clipboard
sway wl-clipboard libopenblas-dev
fi
python3 -m pip install --upgrade pip
@ -38,11 +38,10 @@ python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
if [[ $(uname) != CYGWIN* ]]; then
# TODO Remove condition when NumPy supports 3.12
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
python3 -m pip install numpy
# PyQt6 doesn't support PyPy3
if [[ "$GHA_PYTHON_VERSION" != "3.12-dev" && $GHA_PYTHON_VERSION == 3.* ]]; then
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
python3 -m pip install pyqt6
fi

View File

@ -28,7 +28,7 @@ jobs:
name: Docs
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4

View File

@ -17,7 +17,7 @@ jobs:
name: Lint
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: pre-commit cache
uses: actions/cache@v3

View File

@ -3,6 +3,7 @@
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
python3 -m pip install coverage
@ -13,8 +14,7 @@ python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
# TODO Remove condition when NumPy supports 3.12
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
python3 -m pip install numpy
# extra test images
pushd depends && ./install_extra_test_images.sh && popd

View File

@ -10,7 +10,7 @@ on:
permissions:
contents: read
concurrency:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

View File

@ -8,7 +8,7 @@ on:
permissions:
issues: write
concurrency:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

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:
@ -36,7 +44,7 @@ jobs:
git config --global core.autocrlf input
- name: Checkout Pillow
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Cygwin
uses: cygwin/cygwin-install-action@v4
@ -76,17 +84,23 @@ jobs:
with:
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
- name: Select Python version
run: |
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
- name: Get latest NumPy version
id: latest-numpy
shell: bash.exe -eo pipefail -o igncr "{0}"
run: |
python3 -m pip list --outdated | grep numpy | sed -r 's/ +/ /g' | cut -d ' ' -f 3 | sed 's/^/version=/' >> $GITHUB_OUTPUT
- name: pip cache
uses: actions/cache@v3
with:
path: 'C:\cygwin\home\runneradmin\.cache\pip'
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }}
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }}
restore-keys: |
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
- name: Select Python version
run: |
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-
- name: Build system information
run: |
@ -96,10 +110,10 @@ jobs:
run: |
bash.exe .ci/install.sh
- name: Install a different NumPy
- name: Upgrade NumPy
shell: dash.exe -l "{0}"
run: |
python3 -m pip install -U numpy
python3 -m pip install -U "numpy<1.26"
- name: Build
shell: bash.exe -eo pipefail -o igncr "{0}"

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:
@ -59,7 +67,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build system information
run: python3 .github/workflows/system-info.py

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:
@ -34,7 +42,7 @@ jobs:
steps:
- name: Checkout Pillow
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up shell
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH

View File

@ -37,7 +37,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build system information
run: python3 .github/workflows/system-info.py

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"]
timeout-minutes: 30
@ -32,16 +40,16 @@ jobs:
steps:
- name: Checkout Pillow
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Checkout cached dependencies
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: python-pillow/pillow-depends
path: winbuild\depends
- name: Checkout extra test images
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: python-pillow/test-images
path: Tests\test-images

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:
@ -31,7 +39,7 @@ jobs:
python-version: [
"pypy3.10",
"pypy3.9",
"3.12-dev",
"3.12",
"3.11",
"3.10",
"3.9",
@ -48,7 +56,7 @@ jobs:
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4

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

42
.github/workflows/wheels.yml vendored Normal file
View File

@ -0,0 +1,42 @@
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
jobs:
macos:
uses: ./.github/workflows/wheels-macos.yml
with:
artifacts-name: "wheels"
linux:
uses: ./.github/workflows/wheels-linux.yml
with:
artifacts-name: "wheels"
success:
permissions:
contents: none
needs: [macos, linux]
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,6 +1,12 @@
repos:
- repo: https://github.com/psf/black
rev: 23.3.0
- repo: https://github.com/asottile/pyupgrade
rev: v3.13.0
hooks:
- id: pyupgrade
args: [--py38-plus]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.9.1
hooks:
- id: black
args: [--target-version=py38]
@ -23,17 +29,17 @@ repos:
- id: yesqa
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.1
rev: v1.5.4
hooks:
- id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8
additional_dependencies:
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat]
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat, flake8-logging]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
@ -44,23 +50,28 @@ repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: check-json
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
exclude: ^Tests/images/
- id: trailing-whitespace
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/sphinx-contrib/sphinx-lint
rev: v0.6.7
rev: v0.6.8
hooks:
- id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt
rev: 0.12.1
rev: 1.2.0
hooks:
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.13
rev: v0.14
hooks:
- id: validate-pyproject

135
.travis.yml Normal file
View File

@ -0,0 +1,135 @@
if: tag IS present
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.11"
dist: focal
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

@ -5,9 +5,90 @@ Changelog (Pillow)
10.1.0 (unreleased)
-------------------
- 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]
- Prevent TIFF orientation from being applied more than once #7383
[radarhere]
- Use previous pixel alpha for QOI_OP_RGB #7357
[radarhere]
- Added BC5U reading #7358
[radarhere]
- Allow getpixel() to accept a list #7355
[radarhere, homm]
- Allow GaussianBlur and BoxBlur to accept a sequence of x and y radii #7336
[radarhere]
- Expand JPEG buffer size when saving optimized or progressive #7345
[radarhere]
- Added session type check for Linux in ImageGrab.grabclipboard() #7332
[TheNooB2706, radarhere, hugovk]
- Allow "loop=None" when saving GIF images #7329
[radarhere]
- Fixed transparency when saving P mode images to PDF #7323
[radarhere]
- Added saving LA images as PDFs #7299
[radarhere]
- Set SMaskInData to 1 for PDFs with alpha #7316, #7317
[radarhere]
- Changed Image mode property to be read-only by default #7307
[radarhere]
- Silence exceptions in _repr_jpeg_ and _repr_png_ #7266
[mtreinish, radarhere]
- Do not use transparency when saving GIF if it has been removed when normalizing mode #7284
[radarhere]
- Fix missing symbols when libtiff depends on libjpeg #7270
[heitbaum]
10.0.1 (2023-09-15)
-------------------
- Updated libwebp to 1.3.2 #7395
[radarhere]
- Updated zlib to 1.3 #7344
[radarhere]
10.0.0 (2023-07-01)
-------------------
@ -5735,8 +5816,8 @@ http://svn.effbot.org/public/pil/
a polyline, independent of line angle.
- Fixed bearing calculation and clipping in the ImageFont truetype
renderer; this could lead to clipped text, or crashes in the low-
level _imagingft module. (based on input from Adam Twardoch and
renderer; this could lead to clipped text, or crashes in the low-level
_imagingft module. (based on input from Adam Twardoch and
others).
- Added ImageQt wrapper module, for converting PIL Image objects to
@ -5817,8 +5898,7 @@ http://svn.effbot.org/public/pil/
1.1.5c2 and 1.1.5 final
-----------------------
- Added experimental PERSPECTIVE transform method (from Jeff Breiden-
bach).
- Added experimental PERSPECTIVE transform method (from Jeff Breidenbach).
1.1.5c1
-------
@ -5890,8 +5970,8 @@ http://svn.effbot.org/public/pil/
- Fixed BILINEAR/BICUBIC/ANTIALIAS filtering for mode "LA".
- Added "getcolors()" method. This is similar to the existing histo-
gram method, but looks at color values instead of individual layers,
- Added "getcolors()" method. This is similar to the existing histogram
method, but looks at color values instead of individual layers,
and returns an unsorted list of (count, color) tuples.
By default, the method returns None if finds more than 256 colors.
@ -6107,8 +6187,8 @@ http://svn.effbot.org/public/pil/
- Added limited support for "bitfield compression" in BMP files
and DIB buffers, for 15-bit, 16-bit, and 32-bit images. This
also fixes a problem with ImageGrab module when copying screen-
dumps from the clipboard on 15/16/32-bit displays.
also fixes a problem with ImageGrab module when copying screendumps
from the clipboard on 15/16/32-bit displays.
- Added experimental WAL (Quake 2 textures) loader. To use this
loader, import WalImageFile and call the "open" method in that
@ -6219,8 +6299,8 @@ http://svn.effbot.org/public/pil/
1.1.3 final
-----------
- Made setup.py look for old versions of zlib. For some back-
ground, see: https://zlib.net/advisory-2002-03-11.txt
- Made setup.py look for old versions of zlib. For some background,
see: https://zlib.net/advisory-2002-03-11.txt
1.1.3c2
-------
@ -6411,8 +6491,8 @@ http://svn.effbot.org/public/pil/
supports all major PIL image modes (including F and I).
- The ImageFile module now includes a Parser class, which can
be used to incrementally decode an image file (while down-
loading it from the net, for example). See the handbook for
be used to incrementally decode an image file (while downloading
it from the net, for example). See the handbook for
details.
- "show" now converts non-standard modes to "L" or "RGB" (as
@ -6550,8 +6630,8 @@ http://svn.effbot.org/public/pil/
- The Image "transform" method now supports Image.QUAD transforms.
The data argument is an 8-tuple giving the upper left, lower
left, lower right, and upper right corner of the source quadri-
lateral. Also added Image.MESH transform which takes a list
left, lower right, and upper right corner of the source quadrilateral.
Also added Image.MESH transform which takes a list
of quadrilaterals.
- The Image "resize", "rotate", and "transform" methods now support
@ -6776,8 +6856,8 @@ The test suite includes 400 individual tests.
neither "short", "int" nor "long" are 32-bit wide.
- Added file= and data= keyword arguments to PhotoImage and BitmapImage.
This allows you to use them as drop-in replacements for the corre-
sponding Tkinter classes.
This allows you to use them as drop-in replacements for the corresponding
Tkinter classes.
- Removed bogus references to the crack coder (ImagingCrack).

View File

@ -29,3 +29,4 @@ global-exclude .git*
global-exclude *.pyc
global-exclude *.so
prune .ci
prune wheels

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>

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.
@ -99,17 +99,14 @@ Released as needed privately to individual vendors for critical security-related
## Binary Distributions
### macOS and Linux
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
* [ ] Download 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-x.y.z
```
* [ ] 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)

0
Tests/check_j2k_leaks.py Executable file → Normal file
View File

View File

@ -37,4 +37,4 @@ The Font Software may be sold as part of a larger software package but no copy o
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.

View File

@ -91,7 +91,7 @@ def assert_image_equal(a, b, msg=None):
if HAS_UPLOADER:
try:
url = test_image_results.upload(a, b)
logger.error(f"Url for test images: {url}")
logger.error("URL for test images: %s", url)
except Exception:
pass
@ -126,7 +126,7 @@ def assert_image_similar(a, b, epsilon, msg=None):
if HAS_UPLOADER:
try:
url = test_image_results.upload(a, b)
logger.error(f"Url for test images: {url}")
logger.exception("URL for test images: %s", url)
except Exception:
pass
raise e

View File

@ -22,4 +22,3 @@ and that the name of ICC shall not be used in advertising or publicity
pertaining to distribution of the software without specific, written
prior permission. ICC makes no representations about the suitability
of this software for any purpose.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 95 KiB

BIN
Tests/images/bc5u.dds Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 B

After

Width:  |  Height:  |  Size: 180 B

0
Tests/images/negative_size.ppm Executable file → Normal file
View File

BIN
Tests/images/palette.dds Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -6,6 +6,7 @@ import packaging
import pytest
from PIL import Image, features
from Tests.helper import skip_unless_feature
if sys.platform.startswith("win32"):
pytest.skip("Fuzzer is linux only", allow_module_level=True)
@ -48,6 +49,7 @@ def test_fuzz_images(path):
fuzzers.disable_decompressionbomb_error()
@skip_unless_feature("freetype2")
@pytest.mark.parametrize(
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
)

View File

@ -22,7 +22,7 @@ def test_imageops_box_blur():
def box_blur(image, radius=1, n=1):
return image._new(image.im.box_blur(radius, n))
return image._new(image.im.box_blur((radius, radius), n))
def assert_image(im, data, delta=0):

View File

@ -16,6 +16,7 @@ TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds"
TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
TEST_FILE_BC5S = "Tests/images/bc5s.dds"
TEST_FILE_BC5U = "Tests/images/bc5u.dds"
TEST_FILE_BC6H = "Tests/images/bc6h.dds"
TEST_FILE_BC6HS = "Tests/images/bc6h_sf.dds"
TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
@ -81,10 +82,18 @@ def test_sanity_ati1():
assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
def test_sanity_ati2():
"""Check ATI2 images can be opened"""
@pytest.mark.parametrize(
"image_path",
(
TEST_FILE_ATI2,
# hexeditted to use BC5U FourCC
TEST_FILE_BC5U,
),
)
def test_sanity_ati2_bc5u(image_path):
"""Check ATI2 and BC5U images can be opened"""
with Image.open(TEST_FILE_ATI2) as im:
with Image.open(image_path) as im:
im.load()
assert im.format == "DDS"
@ -289,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
@ -875,6 +875,14 @@ def test_identical_frames_to_single_frame(duration, tmp_path):
assert reread.info["duration"] == 8500
def test_loop_none(tmp_path):
out = str(tmp_path / "temp.gif")
im = Image.new("L", (100, 100), "#000")
im.save(out, loop=None)
with Image.open(out) as reread:
assert "loop" not in reread.info
def test_number_of_loops(tmp_path):
number_of_loops = 2
@ -1086,6 +1094,21 @@ def test_transparent_optimize(tmp_path):
assert reloaded.info["transparency"] == reloaded.getpixel((252, 0))
def test_removed_transparency(tmp_path):
out = str(tmp_path / "temp.gif")
im = Image.new("RGB", (256, 1))
for x in range(256):
im.putpixel((x, 0), (x, 0, 0))
im.info["transparency"] = (255, 255, 255)
with pytest.warns(UserWarning):
im.save(out)
with Image.open(out) as reloaded:
assert "transparency" not in reloaded.info
def test_rgb_transparency(tmp_path):
out = str(tmp_path / "temp.gif")
@ -1157,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,5 @@
import sys
from io import StringIO
from io import BytesIO, StringIO
from PIL import Image, IptcImagePlugin
@ -30,6 +30,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
assert False, "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

@ -214,13 +214,20 @@ class TestFileJpeg:
# Should not raise OSError for image with icc larger than image size.
im.save(
f,
format="JPEG",
progressive=True,
quality=95,
icc_profile=icc_profile,
optimize=True,
)
with Image.open("Tests/images/flower2.jpg") as im:
f = str(tmp_path / "temp2.jpg")
im.save(f, progressive=True, quality=94, icc_profile=b" " * 53955)
with Image.open("Tests/images/flower2.jpg") as im:
f = str(tmp_path / "temp3.jpg")
im.save(f, progressive=True, quality=94, exif=b" " * 43668)
def test_optimize(self):
im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), optimize=0)
@ -945,11 +952,10 @@ class TestFileJpeg:
assert repr_jpeg.format == "JPEG"
assert_image_similar(im, repr_jpeg, 17)
def test_repr_jpeg_error(self):
def test_repr_jpeg_error_returns_none(self):
im = hopper("F")
with pytest.raises(ValueError):
im._repr_jpeg_()
assert im._repr_jpeg_() is None
@pytest.mark.skipif(not is_win32(), reason="Windows only")

View File

@ -274,17 +274,15 @@ def test_sgnd(tmp_path):
assert reloaded_signed.getpixel((0, 0)) == 128
def test_rgba():
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
def test_rgba(ext):
# Arrange
with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2:
# Act
j2k.load()
jp2.load()
with Image.open("Tests/images/rgb_trns_ycbc" + ext) as im:
# Act
im.load()
# Assert
assert j2k.mode == "RGBA"
assert jp2.mode == "RGBA"
# Assert
assert im.mode == "RGBA"
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))

View File

@ -8,7 +8,7 @@ from collections import namedtuple
import pytest
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features
from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features
from PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
from .helper import (
@ -1035,7 +1035,18 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
for i in range(2, 9):
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
assert 274 in im.tag_v2
im.load()
assert 274 not in im.tag_v2
assert_image_similar(base_im, im, 0.7)
def test_exif_transpose(self):
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
for i in range(2, 9):
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
im = ImageOps.exif_transpose(im)
assert_image_similar(base_im, im, 0.7)

View File

@ -43,8 +43,25 @@ def test_save(tmp_path, mode):
@skip_unless_feature("jpg_2000")
def test_save_rgba(tmp_path):
helper_save_as_pdf(tmp_path, "RGBA")
@pytest.mark.parametrize("mode", ("LA", "RGBA"))
def test_save_alpha(tmp_path, mode):
helper_save_as_pdf(tmp_path, mode)
def test_p_alpha(tmp_path):
# Arrange
outfile = str(tmp_path / "temp.pdf")
with Image.open("Tests/images/pil123p.png") as im:
assert im.mode == "P"
assert isinstance(im.info["transparency"], bytes)
# Act
im.save(outfile)
# Assert
with open(outfile, "rb") as fp:
contents = fp.read()
assert b"\n/SMask " in contents
def test_monochrome(tmp_path):
@ -57,8 +74,8 @@ def test_monochrome(tmp_path):
def test_unsupported_mode(tmp_path):
im = hopper("LA")
outfile = str(tmp_path / "temp_LA.pdf")
im = hopper("PA")
outfile = str(tmp_path / "temp_PA.pdf")
with pytest.raises(ValueError):
im.save(outfile)

View File

@ -79,7 +79,7 @@ class TestFilePng:
def test_sanity(self, tmp_path):
# internal version number
assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib"))
assert re.search(r"\d+(\.\d+){1,3}$", features.version_codec("zlib"))
test_file = str(tmp_path / "temp.png")
@ -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)
@ -532,11 +532,10 @@ class TestFilePng:
assert repr_png.format == "PNG"
assert_image_equal(im, repr_png)
def test_repr_png_error(self):
def test_repr_png_error_returns_none(self):
im = hopper("F")
with pytest.raises(ValueError):
im._repr_png_()
assert im._repr_png_() is None
def test_chunk_order(self, tmp_path):
with Image.open("Tests/images/icc_profile.png") as im:

View File

@ -2,7 +2,7 @@ import pytest
from PIL import Image, QoiImagePlugin
from .helper import assert_image_equal_tofile, assert_image_similar_tofile
from .helper import assert_image_equal_tofile
def test_sanity():
@ -18,7 +18,7 @@ def test_sanity():
assert im.size == (162, 150)
assert im.format == "QOI"
assert_image_similar_tofile(im, "Tests/images/pil123rgba.png", 0.03)
assert_image_equal_tofile(im, "Tests/images/pil123rgba.png")
def test_invalid_file():

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

@ -135,6 +135,12 @@ class TestImage:
with pytest.raises(AttributeError):
im.size = (3, 4)
def test_set_mode(self):
im = Image.new("RGB", (1, 1))
with pytest.raises(AttributeError):
im.mode = "P"
def test_invalid_image(self):
im = io.BytesIO(b"")
with pytest.raises(UnidentifiedImageError):
@ -632,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])
@ -655,15 +661,15 @@ class TestImage:
blank_p.palette = None
blank_pa.palette = None
def _make_new(base_image, im, palette_result=None):
new_im = base_image._new(im)
assert new_im.mode == im.mode
assert new_im.size == im.size
assert new_im.info == base_image.info
def _make_new(base_image, image, palette_result=None):
new_image = base_image._new(image.im)
assert new_image.mode == image.mode
assert new_image.size == image.size
assert new_image.info == base_image.info
if palette_result is not None:
assert new_im.palette.tobytes() == palette_result.tobytes()
assert new_image.palette.tobytes() == palette_result.tobytes()
else:
assert new_im.palette is None
assert new_image.palette is None
_make_new(im, im_p, ImagePalette.ImagePalette(list(range(256)) * 3))
_make_new(im_p, im, None)
@ -900,6 +906,31 @@ class TestImage:
im = Image.new("RGB", size)
assert im.tobytes() == 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))

View File

@ -213,6 +213,10 @@ class TestImageGetPixel(AccessTest):
def test_basic(self, mode):
self.check(mode)
def test_list(self):
im = hopper()
assert im.getpixel([0, 0]) == (20, 20, 70)
@pytest.mark.parametrize("mode", ("I;16", "I;16B"))
@pytest.mark.parametrize(
"expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)

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

@ -23,9 +23,12 @@ from .helper import assert_image_equal, hopper
ImageFilter.MinFilter,
ImageFilter.ModeFilter,
ImageFilter.GaussianBlur,
ImageFilter.GaussianBlur(0),
ImageFilter.GaussianBlur(5),
ImageFilter.GaussianBlur((2, 5)),
ImageFilter.BoxBlur(0),
ImageFilter.BoxBlur(5),
ImageFilter.BoxBlur((2, 5)),
ImageFilter.UnsharpMask,
ImageFilter.UnsharpMask(10),
),
@ -185,12 +188,21 @@ def test_consistency_5x5(mode):
assert_image_equal(source.filter(kernel), reference)
def test_invalid_box_blur_filter():
@pytest.mark.parametrize(
"radius",
(
-2,
(-2, -2),
(-2, 2),
(2, -2),
),
)
def test_invalid_box_blur_filter(radius):
with pytest.raises(ValueError):
ImageFilter.BoxBlur(-2)
ImageFilter.BoxBlur(radius)
im = hopper()
box_blur_filter = ImageFilter.BoxBlur(2)
box_blur_filter.radius = -2
box_blur_filter.radius = radius
with pytest.raises(ValueError):
im.filter(box_blur_filter)

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

@ -586,6 +586,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 +744,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")
@ -1326,6 +1338,7 @@ def test_stroke_multiline():
assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
@skip_unless_feature("freetype2")
def test_setting_default_font():
# Arrange
im = Image.new("RGB", (100, 250))

View File

@ -136,7 +136,7 @@ class TestImageFile:
class DummyImageFile(ImageFile.ImageFile):
def _open(self):
self.mode = "RGB"
self._mode = "RGB"
self._size = (1, 1)
im = DummyImageFile(buf)
@ -217,7 +217,7 @@ xoff, yoff, xsize, ysize = 10, 20, 100, 100
class MockImageFile(ImageFile.ImageFile):
def _open(self):
self.rawmode = "RGBA"
self.mode = "RGBA"
self._mode = "RGBA"
self._size = (200, 200)
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]

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)

View File

@ -75,13 +75,13 @@ def test_pickle_la_mode_with_palette(tmp_path):
# Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
im.mode = "LA"
im._mode = "LA"
with open(filename, "wb") as f:
pickle.dump(im, f, protocol)
with open(filename, "rb") as f:
loaded_im = pickle.load(f)
im.mode = "PA"
im._mode = "PA"
assert im == loaded_im
@ -112,6 +112,7 @@ def helper_assert_pickled_font_images(font1, font2):
assert_image_equal(im1, im2)
@skip_unless_feature("freetype2")
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
def test_pickle_font_string(protocol):
# Arrange
@ -125,6 +126,7 @@ def test_pickle_font_string(protocol):
helper_assert_pickled_font_images(font, unpickled_font)
@skip_unless_feature("freetype2")
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
def test_pickle_font_file(tmp_path, protocol):
# Arrange

0
_custom_build/backend.py Executable file → Normal file
View File

View File

@ -1,7 +1,7 @@
#!/bin/bash
# install libimagequant
archive=libimagequant-4.2.0
archive=libimagequant-4.2.1
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

@ -11,4 +11,3 @@ pushd $archive
meson build --prefix=/usr && sudo ninja -C build install
popd

View File

@ -15,4 +15,3 @@ make && sudo make install
cd ..
popd

View File

@ -1,7 +1,7 @@
#!/bin/bash
# install webp
archive=libwebp-1.3.1
archive=libwebp-1.3.2
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

@ -2,4 +2,3 @@
pkg install -y python ndk-sysroot clang make \
libjpeg-turbo

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

@ -225,7 +225,7 @@ class DdsImageFile(ImageFile.ImageFile):
flags, height, width = struct.unpack("<3I", header.read(12))
self._size = (width, height)
self.mode = "RGBA"
self._mode = "RGBA"
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
struct.unpack("<11I", header.read(44)) # reserved

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
@ -253,7 +285,7 @@ their :py:attr:`~PIL.Image.Image.info` values.
**loop**
Integer number of times the GIF should loop. 0 means that it will loop
forever. By default, the image will not loop.
forever. If omitted or ``None``, the image will not loop.
**comment**
A comment about the image.
@ -861,6 +893,10 @@ PPM
Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I`` or
``RGB`` data.
"Raw" (P4 to P6) formats can be read, and are used when writing.
Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well.
SGI
^^^
@ -1482,7 +1518,7 @@ files. Different encoding methods are used, depending on the image mode.
unavailable
* L, RGB and CMYK mode images use JPEG encoding
* P mode images use HEX encoding
* RGBA mode images use JPEG2000 encoding
* LA and RGBA mode images use JPEG2000 encoding
.. _pdf-saving:

View File

@ -72,11 +72,11 @@ true color.
# mode setting
bits = int(header[3])
if bits == 1:
self.mode = "1"
self._mode = "1"
elif bits == 8:
self.mode = "L"
self._mode = "L"
elif bits == 24:
self.mode = "RGB"
self._mode = "RGB"
else:
msg = "unknown number of bits"
raise SyntaxError(msg)

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

View File

@ -83,10 +83,9 @@ Install Pillow with :command:`pip`::
.. tab:: Windows
We provide Pillow binaries for Windows compiled for the matrix of
supported Pythons in both 32 and 64-bit versions in the wheel format.
These binaries include support for all optional libraries except
libimagequant and libxcb. Raqm support requires
FriBiDi to be installed separately::
supported Pythons in 64-bit versions in the wheel format. These binaries include
support for all optional libraries except libimagequant and libxcb. Raqm support
requires FriBiDi to be installed separately::
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow
@ -181,7 +180,7 @@ Many of Pillow's features require external libraries:
* **libimagequant** provides improved color quantization
* Pillow has been tested with libimagequant **2.6-4.2**
* Pillow has been tested with libimagequant **2.6-4.2.1**
* Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled.
@ -499,11 +498,13 @@ These platforms have been reported to work at the versions mentioned.
| Operating system | | Tested Python | | Latest tested | | Tested |
| | | versions | | Pillow version | | processors |
+==================================+===========================+==================+==============+
| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.0 |arm |
| +---------------------------+------------------+ |
| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.5.0 | |
| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
+----------------------------------+---------------------------+------------------+--------------+
| macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
| +---------------------------+------------------+ |
| | 3.7 | 9.5.0 | |
+----------------------------------+---------------------------+------------------+--------------+
| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
+----------------------------------+---------------------------+------------------+--------------+
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
| +---------------------------+------------------+--------------+

View File

@ -1,7 +1,8 @@
Python,3.11,3.10,3.9,3.8,3.7,3.6,3.5
Pillow >= 10,Yes,Yes,Yes,Yes,,,
Pillow 9.3 - 9.5,Yes,Yes,Yes,Yes,Yes,,
Pillow 9.0 - 9.2,,Yes,Yes,Yes,Yes,,
Pillow 8.3.2 - 8.4,,Yes,Yes,Yes,Yes,Yes,
Pillow 8.0 - 8.3.1,,,Yes,Yes,Yes,Yes,
Pillow 7.0 - 7.2,,,,Yes,Yes,Yes,Yes
Python,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5
Pillow >= 10.1,Yes,Yes,Yes,Yes,Yes,,,
Pillow 10.0,,Yes,Yes,Yes,Yes,,,
Pillow 9.3 - 9.5,,Yes,Yes,Yes,Yes,Yes,,
Pillow 9.0 - 9.2,,,Yes,Yes,Yes,Yes,,
Pillow 8.3.2 - 8.4,,,Yes,Yes,Yes,Yes,Yes,
Pillow 8.0 - 8.3.1,,,,Yes,Yes,Yes,Yes,
Pillow 7.0 - 7.2,,,,,Yes,Yes,Yes,Yes

1 Python 3.12 3.11 3.10 3.9 3.8 3.7 3.6 3.5
2 Pillow >= 10 Pillow >= 10.1 Yes Yes Yes Yes Yes
3 Pillow 9.3 - 9.5 Pillow 10.0 Yes Yes Yes Yes Yes
4 Pillow 9.0 - 9.2 Pillow 9.3 - 9.5 Yes Yes Yes Yes Yes
5 Pillow 8.3.2 - 8.4 Pillow 9.0 - 9.2 Yes Yes Yes Yes Yes
6 Pillow 8.0 - 8.3.1 Pillow 8.3.2 - 8.4 Yes Yes Yes Yes Yes
7 Pillow 7.0 - 7.2 Pillow 8.0 - 8.3.1 Yes Yes Yes Yes Yes
8 Pillow 7.0 - 7.2 Yes Yes Yes Yes

View File

@ -5,4 +5,4 @@ Pillow 5.2 - 5.4,,Yes,Yes,Yes,Yes,,,Yes,,,
Pillow 5.0 - 5.1,,,Yes,Yes,Yes,,,Yes,,,
Pillow 4,,,Yes,Yes,Yes,Yes,,Yes,,,
Pillow 2 - 3,,,,Yes,Yes,Yes,Yes,Yes,Yes,,
Pillow < 2,,,,,,,,Yes,Yes,Yes,Yes
Pillow < 2,,,,,,,,Yes,Yes,Yes,Yes

1 Python 3.8 3.7 3.6 3.5 3.4 3.3 3.2 2.7 2.6 2.5 2.4
5 Pillow 5.0 - 5.1 Yes Yes Yes Yes
6 Pillow 4 Yes Yes Yes Yes Yes
7 Pillow 2 - 3 Yes Yes Yes Yes Yes Yes
8 Pillow < 2 Yes Yes Yes Yes

View File

@ -93,10 +93,14 @@ Generating images
Registering plugins
^^^^^^^^^^^^^^^^^^^
.. autofunction:: preinit
.. autofunction:: init
.. note::
These functions are for use by plugin authors. Application authors can
ignore them.
These functions are for use by plugin authors. They are called when a
plugin is loaded as part of :py:meth:`~preinit()` or :py:meth:`~init()`.
Application authors can ignore them.
.. autofunction:: register_open
.. autofunction:: register_mime
@ -347,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

@ -538,7 +538,7 @@ Methods
It should be a `BCP 47 language code`_.
Requires libraqm.
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
:return: Width for horizontal, height for vertical text.
:return: Either width for horizontal text, or height for vertical text.
.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)

View File

@ -206,4 +206,4 @@ Support reading signed 8-bit TIFF images
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TIFF images with signed integer data, 8 bits per sample and a photometric
interpretaton of BlackIsZero can now be read.
interpretation of BlackIsZero can now be read.

View File

@ -0,0 +1,14 @@
10.0.1
------
Security
========
This release addresses :cve:`2023-4863`, by providing an updated install script and
updated wheels to include libwebp 1.3.2, preventing a potential heap buffer overflow
in WebP.
Updated tests to pass with latest zlib version
==============================================
The release of zlib 1.3 caused one of the tests in the Pillow test suite to fail.

View File

@ -0,0 +1,66 @@
10.1.0
------
Backwards Incompatible 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
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
============
TODO
^^^^
TODO
API Changes
===========
TODO
^^^^
TODO
API Additions
=============
has_transparency_data
^^^^^^^^^^^^^^^^^^^^^
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.
Even if this attribute is true, the image might still appear solid, if all of
the values shown within are opaque.
Security
========
TODO
^^^^
TODO
Other Changes
=============
Added support for DDS 8-bit color indexed images
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Support has been added to read 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

@ -49,4 +49,3 @@ The external dependencies on libjpeg and zlib are now required by default.
If the headers or libraries are not found, then installation will abort
with an error. This behaviour can be disabled with the ``--disable-libjpeg``
and ``--disable-zlib`` flags.

View File

@ -34,7 +34,3 @@ image size can lead to a smaller allocation than expected, leading to
arbitrary writes.
This issue was found by Cris Neckar at Divergent Security.

View File

@ -20,5 +20,3 @@ CPython 3.6.1 to not work on installations of C-Python 3.6.0. This fix
undefines PySlice_GetIndicesEx if it exists to restore compatibility
with both 3.6.0 and 3.6.1. See https://bugs.python.org/issue29943 for
more details.

View File

@ -8,4 +8,3 @@ Fixed Windows PyPy Build
A change in the 4.2.0 cycle broke the Windows PyPy build. This has
been fixed, and PyPy is now part of the Windows CI matrix.

View File

@ -175,6 +175,3 @@ Dark theme for docs
^^^^^^^^^^^^^^^^^^^
The https://pillow.readthedocs.io documentation will use a dark theme if the user has requested the system use one. Uses the ``prefers-color-scheme`` CSS media query.

View File

@ -14,6 +14,8 @@ expected to be backported to earlier versions.
.. toctree::
:maxdepth: 2
10.1.0
10.0.1
10.0.0
9.5.0
9.4.0

View File

@ -16,6 +16,7 @@ classifiers =
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Topic :: Multimedia :: Graphics

View File

@ -39,7 +39,7 @@ TIFF_ROOT = None
ZLIB_ROOT = None
FUZZING_BUILD = "LIB_FUZZING_ENGINE" in os.environ
if sys.platform == "win32" and sys.version_info >= (3, 12):
if sys.platform == "win32" and sys.version_info >= (3, 13):
import atexit
atexit.register(

View File

@ -68,11 +68,11 @@ def bdf_char(f):
# followed by the width in x (BBw), height in y (BBh),
# and x and y displacement (BBxoff0, BByoff0)
# of the lower left corner from the origin of the character.
width, height, x_disp, y_disp = [int(p) for p in props["BBX"].split()]
width, height, x_disp, y_disp = (int(p) for p in props["BBX"].split())
# The word DWIDTH
# followed by the width in x and y of the character in device pixels.
dwx, dwy = [int(p) for p in props["DWIDTH"].split()]
dwx, dwy = (int(p) for p in props["DWIDTH"].split())
bbox = (
(dwx, dwy),

View File

@ -266,7 +266,7 @@ class BlpImageFile(ImageFile.ImageFile):
msg = f"Bad BLP magic {repr(self.magic)}"
raise BLPFormatError(msg)
self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
@ -419,9 +419,11 @@ class BLPEncoder(ImageFile.PyEncoder):
def _write_palette(self):
data = b""
palette = self.im.getpalette("RGBA", "RGBA")
for i in range(256):
for i in range(len(palette) // 4):
r, g, b, a = palette[i * 4 : (i + 1) * 4]
data += struct.pack("<4B", b, g, r, a)
while len(data) < 256 * 4:
data += b"\x00" * 4
return data
def encode(self, bufsize):
@ -442,7 +444,7 @@ class BLPEncoder(ImageFile.PyEncoder):
return len(data), 0, data
def _save(im, fp, filename, save_all=False):
def _save(im, fp, filename):
if im.mode != "P":
msg = "Unsupported BLP image mode"
raise ValueError(msg)

View File

@ -163,7 +163,7 @@ class BmpImageFile(ImageFile.ImageFile):
offset += 4 * file_info["colors"]
# ---------------------- Check bit depth for unusual unsupported values
self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
if self.mode is None:
msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
raise OSError(msg)
@ -200,7 +200,7 @@ class BmpImageFile(ImageFile.ImageFile):
and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
):
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
self.mode = "RGBA" if "A" in raw_mode else self.mode
self._mode = "RGBA" if "A" in raw_mode else self.mode
elif (
file_info["bits"] in (24, 16)
and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
@ -214,7 +214,7 @@ class BmpImageFile(ImageFile.ImageFile):
raise OSError(msg)
elif file_info["compression"] == self.RAW:
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
raw_mode, self.mode = "BGRA", "RGBA"
raw_mode, self._mode = "BGRA", "RGBA"
elif file_info["compression"] in (self.RLE8, self.RLE4):
decoder_name = "bmp_rle"
else:
@ -245,10 +245,10 @@ class BmpImageFile(ImageFile.ImageFile):
# ------- If all colors are grey, white or black, ditch palette
if greyscale:
self.mode = "1" if file_info["colors"] == 2 else "L"
self._mode = "1" if file_info["colors"] == 2 else "L"
raw_mode = self.mode
else:
self.mode = "P"
self._mode = "P"
self.palette = ImagePalette.raw(
"BGRX" if padding == 4 else "BGR", palette
)

View File

@ -46,7 +46,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
self.fp.seek(offset)
# make something up
self.mode = "F"
self._mode = "F"
self._size = 1, 1
loader = self._load()

View File

@ -13,7 +13,7 @@ Full text of the CC0 license:
import struct
from io import BytesIO
from . import Image, ImageFile
from . import Image, ImageFile, ImagePalette
from ._binary import o32le as o32
# Magic ("DDS ")
@ -128,7 +128,7 @@ class DdsImageFile(ImageFile.ImageFile):
flags, height, width = struct.unpack("<3I", header.read(12))
self._size = (width, height)
self.mode = "RGBA"
self._mode = "RGBA"
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
struct.unpack("<11I", header.read(44)) # reserved
@ -141,9 +141,9 @@ class DdsImageFile(ImageFile.ImageFile):
if pfflags & DDPF_LUMINANCE:
# Texture contains uncompressed L or LA data
if pfflags & DDPF_ALPHAPIXELS:
self.mode = "LA"
self._mode = "LA"
else:
self.mode = "L"
self._mode = "L"
self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))]
elif pfflags & DDPF_RGB:
@ -153,10 +153,14 @@ class DdsImageFile(ImageFile.ImageFile):
if pfflags & DDPF_ALPHAPIXELS:
rawmode += masks[0xFF000000]
else:
self.mode = "RGB"
self._mode = "RGB"
rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))]
elif pfflags & DDPF_PALETTEINDEXED8:
self._mode = "P"
self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
self.tile = [("raw", (0, 0) + self.size, 0, "L")]
else:
data_start = header_size + 4
n = 0
@ -172,15 +176,15 @@ class DdsImageFile(ImageFile.ImageFile):
elif fourcc == b"ATI1":
self.pixel_format = "BC4"
n = 4
self.mode = "L"
elif fourcc == b"ATI2":
self._mode = "L"
elif fourcc in (b"ATI2", b"BC5U"):
self.pixel_format = "BC5"
n = 5
self.mode = "RGB"
self._mode = "RGB"
elif fourcc == b"BC5S":
self.pixel_format = "BC5S"
n = 5
self.mode = "RGB"
self._mode = "RGB"
elif fourcc == b"DX10":
data_start += 20
# ignoring flags which pertain to volume textures and cubemaps
@ -189,19 +193,19 @@ class DdsImageFile(ImageFile.ImageFile):
if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM):
self.pixel_format = "BC5"
n = 5
self.mode = "RGB"
self._mode = "RGB"
elif dxgi_format == DXGI_FORMAT_BC5_SNORM:
self.pixel_format = "BC5S"
n = 5
self.mode = "RGB"
self._mode = "RGB"
elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
self.pixel_format = "BC6H"
n = 6
self.mode = "RGB"
self._mode = "RGB"
elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
self.pixel_format = "BC6HS"
n = 6
self.mode = "RGB"
self._mode = "RGB"
elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
self.pixel_format = "BC7"
n = 7

View File

@ -37,33 +37,39 @@ from ._deprecate import deprecate
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
gs_binary = None
gs_windows_binary = None
if sys.platform.startswith("win"):
import shutil
for binary in ("gswin32c", "gswin64c", "gs"):
if shutil.which(binary) is not None:
gs_windows_binary = binary
break
else:
gs_windows_binary = False
def has_ghostscript():
if gs_windows_binary:
return True
if not sys.platform.startswith("win"):
try:
subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL)
return True
except OSError:
# No Ghostscript
pass
return False
global gs_binary, gs_windows_binary
if gs_binary is None:
if sys.platform.startswith("win"):
if gs_windows_binary is None:
import shutil
for binary in ("gswin32c", "gswin64c", "gs"):
if shutil.which(binary) is not None:
gs_windows_binary = binary
break
else:
gs_windows_binary = False
gs_binary = gs_windows_binary
else:
try:
subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL)
gs_binary = "gs"
except OSError:
gs_binary = False
return gs_binary is not False
def Ghostscript(tile, size, fp, scale=1, transparency=False):
"""Render an image using Ghostscript"""
global gs_binary
if not has_ghostscript():
msg = "Unable to locate Ghostscript on paths"
raise OSError(msg)
# Unpack decoder tile
decoder, tile, offset, data = tile[0]
@ -113,7 +119,7 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
# Build Ghostscript command
command = [
"gs",
gs_binary,
"-q", # quiet mode
"-g%dx%d" % size, # set output geometry (pixels)
"-r%fx%f" % res, # set input DPI (dots per inch)
@ -132,19 +138,6 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
"showpage",
]
if gs_windows_binary is not None:
if not gs_windows_binary:
try:
os.unlink(outfile)
if infile_temp:
os.unlink(infile_temp)
except OSError:
pass
msg = "Unable to locate Ghostscript on paths"
raise OSError(msg)
command[0] = gs_windows_binary
# push data through Ghostscript
try:
startupinfo = None
@ -227,13 +220,15 @@ class EpsImageFile(ImageFile.ImageFile):
# go to offset - start of "%!PS"
self.fp.seek(offset)
self.mode = "RGB"
self._mode = "RGB"
self._size = None
byte_arr = bytearray(255)
bytes_mv = memoryview(byte_arr)
bytes_read = 0
reading_comments = True
reading_header_comments = True
reading_trailer_comments = False
trailer_reached = False
def check_required_header_comments():
if "PS-Adobe" not in self.info:
@ -243,6 +238,36 @@ class EpsImageFile(ImageFile.ImageFile):
msg = 'EPS header missing "%%BoundingBox" comment'
raise SyntaxError(msg)
def _read_comment(s):
nonlocal reading_trailer_comments
try:
m = split.match(s)
except re.error as e:
msg = "not an EPS file"
raise SyntaxError(msg) from e
if m:
k, v = m.group(1, 2)
self.info[k] = v
if k == "BoundingBox":
if v == "(atend)":
reading_trailer_comments = True
elif not self._size or (
trailer_reached and reading_trailer_comments
):
try:
# Note: The DSC spec says that BoundingBox
# fields should be integers, but some drivers
# put floating point values there anyway.
box = [int(float(i)) for i in v.split()]
self._size = box[2] - box[0], box[3] - box[1]
self.tile = [
("eps", (0, 0) + self.size, offset, (length, box))
]
except Exception:
pass
return True
while True:
byte = self.fp.read(1)
if byte == b"":
@ -265,9 +290,9 @@ class EpsImageFile(ImageFile.ImageFile):
msg = "not an EPS file"
raise SyntaxError(msg)
else:
if reading_comments:
if reading_header_comments:
check_required_header_comments()
reading_comments = False
reading_header_comments = False
# reset bytes_read so we can keep reading
# data until the end of the line
bytes_read = 0
@ -275,7 +300,7 @@ class EpsImageFile(ImageFile.ImageFile):
bytes_read += 1
continue
if reading_comments:
if reading_header_comments:
# Load EPS header
# if this line doesn't start with a "%",
@ -283,33 +308,11 @@ class EpsImageFile(ImageFile.ImageFile):
# then we've reached the end of the header/comments
if byte_arr[0] != ord("%") or bytes_mv[:13] == b"%%EndComments":
check_required_header_comments()
reading_comments = False
reading_header_comments = False
continue
s = str(bytes_mv[:bytes_read], "latin-1")
try:
m = split.match(s)
except re.error as e:
msg = "not an EPS file"
raise SyntaxError(msg) from e
if m:
k, v = m.group(1, 2)
self.info[k] = v
if k == "BoundingBox":
try:
# Note: The DSC spec says that BoundingBox
# fields should be integers, but some drivers
# put floating point values there anyway.
box = [int(float(i)) for i in v.split()]
self._size = box[2] - box[0], box[3] - box[1]
self.tile = [
("eps", (0, 0) + self.size, offset, (length, box))
]
except Exception:
pass
else:
if not _read_comment(s):
m = field.match(s)
if m:
k = m.group(1)
@ -339,15 +342,15 @@ class EpsImageFile(ImageFile.ImageFile):
# data start identifier (the image data follows after a single line
# consisting only of this quoted value)
image_data_values = byte_arr[11:bytes_read].split(None, 7)
columns, rows, bit_depth, mode_id = [
columns, rows, bit_depth, mode_id = (
int(value) for value in image_data_values[:4]
]
)
if bit_depth == 1:
self.mode = "1"
self._mode = "1"
elif bit_depth == 8:
try:
self.mode = self.mode_map[mode_id]
self._mode = self.mode_map[mode_id]
except ValueError:
break
else:
@ -355,7 +358,18 @@ class EpsImageFile(ImageFile.ImageFile):
self._size = columns, rows
return
elif trailer_reached and reading_trailer_comments:
# Load EPS trailer
# if this line starts with "%%EOF",
# then we've reached the end of the file
if bytes_mv[:5] == b"%%EOF":
break
s = str(bytes_mv[:bytes_read], "latin-1")
_read_comment(s)
elif bytes_mv[:9] == b"%%Trailer":
trailer_reached = True
bytes_read = 0
check_required_header_comments()
@ -391,7 +405,7 @@ class EpsImageFile(ImageFile.ImageFile):
# Load EPS via Ghostscript
if self.tile:
self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
self.mode = self.im.mode
self._mode = self.im.mode
self._size = self.im.size
self.tile = []
return Image.Image.load(self)

View File

@ -51,14 +51,14 @@ class FitsImageFile(ImageFile.ImageFile):
number_of_bits = int(headers[b"BITPIX"])
if number_of_bits == 8:
self.mode = "L"
self._mode = "L"
elif number_of_bits == 16:
self.mode = "I"
self._mode = "I"
# rawmode = "I;16S"
elif number_of_bits == 32:
self.mode = "I"
self._mode = "I"
elif number_of_bits in (-32, -64):
self.mode = "F"
self._mode = "F"
# rawmode = "F" if number_of_bits == -32 else "F;64F"
offset = math.ceil(self.fp.tell() / 2880) * 2880

View File

@ -56,7 +56,7 @@ class FliImageFile(ImageFile.ImageFile):
self.is_animated = self.n_frames > 1
# image characteristics
self.mode = "P"
self._mode = "P"
self._size = i16(s, 8), i16(s, 10)
# animation speed

View File

@ -106,7 +106,7 @@ class FpxImageFile(ImageFile.ImageFile):
# note: for now, we ignore the "uncalibrated" flag
colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
self.mode, self.rawmode = MODES[tuple(colors)]
self._mode, self.rawmode = MODES[tuple(colors)]
# load JPEG tables, if any
self.jpeg = {}

View File

@ -77,7 +77,7 @@ class FtexImageFile(ImageFile.ImageFile):
self._size = struct.unpack("<2i", self.fp.read(8))
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
self.mode = "RGB"
self._mode = "RGB"
# Only support single-format files.
# I don't know of any multi-format file.
@ -90,7 +90,7 @@ class FtexImageFile(ImageFile.ImageFile):
data = self.fp.read(mipmap_size)
if format == Format.DXT1:
self.mode = "RGBA"
self._mode = "RGBA"
self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
elif format == Format.UNCOMPRESSED:
self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]

View File

@ -73,9 +73,9 @@ class GbrImageFile(ImageFile.ImageFile):
comment = self.fp.read(comment_length)[:-1]
if color_depth == 1:
self.mode = "L"
self._mode = "L"
else:
self.mode = "RGBA"
self._mode = "RGBA"
self._size = width, height

View File

@ -51,7 +51,7 @@ class GdImageFile(ImageFile.ImageFile):
msg = "Not a valid GD 2.x .gd file"
raise SyntaxError(msg)
self.mode = "L" # FIXME: "P"
self._mode = "L" # FIXME: "P"
self._size = i16(s, 2), i16(s, 4)
true_color = s[6]

View File

@ -304,11 +304,11 @@ class GifImageFile(ImageFile.ImageFile):
if frame == 0:
if self._frame_palette:
if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
self.mode = "RGBA" if frame_transparency is not None else "RGB"
self._mode = "RGBA" if frame_transparency is not None else "RGB"
else:
self.mode = "P"
self._mode = "P"
else:
self.mode = "L"
self._mode = "L"
if not palette and self.global_palette:
from copy import copy
@ -325,10 +325,10 @@ class GifImageFile(ImageFile.ImageFile):
if "transparency" in self.info:
self.im.putpalettealpha(self.info["transparency"], 0)
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
self.mode = "RGBA"
self._mode = "RGBA"
del self.info["transparency"]
else:
self.mode = "RGB"
self._mode = "RGB"
self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
def _rgb(color):
@ -424,7 +424,7 @@ class GifImageFile(ImageFile.ImageFile):
self.im.putpalette(*self._frame_palette.getdata())
else:
self.im = None
self.mode = temp_mode
self._mode = temp_mode
self._frame_palette = None
super().load_prepare()
@ -434,9 +434,9 @@ class GifImageFile(ImageFile.ImageFile):
if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
if self._frame_transparency is not None:
self.im.putpalettealpha(self._frame_transparency, 0)
self.mode = "RGBA"
self._mode = "RGBA"
else:
self.mode = "RGB"
self._mode = "RGB"
self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG)
return
if not self._prev_im:
@ -449,7 +449,7 @@ class GifImageFile(ImageFile.ImageFile):
frame_im = self._crop(frame_im, self.dispose_extent)
self.im = self._prev_im
self.mode = self.im.mode
self._mode = self.im.mode
if frame_im.mode == "RGBA":
self.im.paste(frame_im, self.dispose_extent, frame_im)
else:
@ -683,11 +683,7 @@ def get_interlace(im):
def _write_local_header(fp, im, offset, flags):
transparent_color_exists = False
try:
if "transparency" in im.encoderinfo:
transparency = im.encoderinfo["transparency"]
else:
transparency = im.info["transparency"]
transparency = int(transparency)
transparency = int(im.encoderinfo["transparency"])
except (KeyError, ValueError):
pass
else:
@ -916,7 +912,7 @@ def _get_global_header(im, info):
info
and (
"transparency" in info
or "loop" in info
or info.get("loop") is not None
or info.get("duration")
or info.get("comment")
)
@ -941,7 +937,7 @@ def _get_global_header(im, info):
# Global Color Table
_get_header_palette(palette_bytes),
]
if "loop" in info:
if info.get("loop") is not None:
header.append(
b"!"
+ o8(255) # extension intro

View File

@ -46,7 +46,7 @@ class GribStubImageFile(ImageFile.StubImageFile):
self.fp.seek(offset)
# make something up
self.mode = "F"
self._mode = "F"
self._size = 1, 1
loader = self._load()

View File

@ -46,7 +46,7 @@ class HDF5StubImageFile(ImageFile.StubImageFile):
self.fp.seek(offset)
# make something up
self.mode = "F"
self._mode = "F"
self._size = 1, 1
loader = self._load()

View File

@ -253,7 +253,7 @@ class IcnsImageFile(ImageFile.ImageFile):
def _open(self):
self.icns = IcnsFile(self.fp)
self.mode = "RGBA"
self._mode = "RGBA"
self.info["sizes"] = self.icns.itersizes()
self.best_size = self.icns.bestsize()
self.size = (
@ -305,7 +305,7 @@ class IcnsImageFile(ImageFile.ImageFile):
px = im.load()
self.im = im.im
self.mode = im.mode
self._mode = im.mode
self.size = im.size
return px

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