mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-02 19:03:24 +03:00
Merge branch 'main' into progress
This commit is contained in:
commit
d52b3a2ce1
|
@ -1,3 +1,10 @@
|
||||||
|
skip_commits:
|
||||||
|
files:
|
||||||
|
- ".github/**"
|
||||||
|
- ".gitmodules"
|
||||||
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
|
|
||||||
version: '{build}'
|
version: '{build}'
|
||||||
clone_folder: c:\pillow
|
clone_folder: c:\pillow
|
||||||
init:
|
init:
|
||||||
|
@ -27,7 +34,7 @@ install:
|
||||||
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
|
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
|
||||||
- curl -fsSL -o nasm-win64.zip https://raw.githubusercontent.com/python-pillow/pillow-depends/main/nasm-2.16.01-win64.zip
|
- 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:\
|
- 7z x nasm-win64.zip -oc:\
|
||||||
- choco install ghostscript --version=10.0.0.20230317
|
- choco install ghostscript --version=10.3.0
|
||||||
- path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH%
|
- path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH%
|
||||||
- cd c:\pillow\winbuild\
|
- cd c:\pillow\winbuild\
|
||||||
- ps: |
|
- ps: |
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
cibuildwheel==2.16.5
|
cibuildwheel==2.17.0
|
||||||
|
|
15
.github/ISSUE_TEMPLATE/ISSUE_REPORT.md
vendored
15
.github/ISSUE_TEMPLATE/ISSUE_REPORT.md
vendored
|
@ -48,6 +48,21 @@ Thank you.
|
||||||
* Python:
|
* Python:
|
||||||
* Pillow:
|
* Pillow:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Please paste here the output of running:
|
||||||
|
|
||||||
|
python3 -m PIL.report
|
||||||
|
or
|
||||||
|
python3 -m PIL --report
|
||||||
|
|
||||||
|
Or the output of the following Python code:
|
||||||
|
|
||||||
|
from PIL import report
|
||||||
|
# or
|
||||||
|
from PIL import features
|
||||||
|
features.pilinfo(supported_formats=False)
|
||||||
|
```
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
|
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
|
||||||
|
|
||||||
|
|
20
.github/workflows/test-cygwin.yml
vendored
20
.github/workflows/test-cygwin.yml
vendored
|
@ -50,7 +50,7 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Cygwin
|
- name: Install Cygwin
|
||||||
uses: egor-tensin/setup-cygwin@v4
|
uses: cygwin/cygwin-install-action@v4
|
||||||
with:
|
with:
|
||||||
packages: >
|
packages: >
|
||||||
gcc-g++
|
gcc-g++
|
||||||
|
@ -71,7 +71,6 @@ jobs:
|
||||||
make
|
make
|
||||||
netpbm
|
netpbm
|
||||||
perl
|
perl
|
||||||
python39=3.9.16-1
|
|
||||||
python3${{ matrix.python-minor-version }}-cffi
|
python3${{ matrix.python-minor-version }}-cffi
|
||||||
python3${{ matrix.python-minor-version }}-cython
|
python3${{ matrix.python-minor-version }}-cython
|
||||||
python3${{ matrix.python-minor-version }}-devel
|
python3${{ matrix.python-minor-version }}-devel
|
||||||
|
@ -89,21 +88,15 @@ jobs:
|
||||||
|
|
||||||
- name: Select Python version
|
- name: Select Python version
|
||||||
run: |
|
run: |
|
||||||
ln -sf c:/tools/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/tools/cygwin/bin/python3
|
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
|
- name: pip cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
||||||
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }}
|
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-
|
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: |
|
run: |
|
||||||
|
@ -113,11 +106,6 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
bash.exe .ci/install.sh
|
bash.exe .ci/install.sh
|
||||||
|
|
||||||
- name: Upgrade NumPy
|
|
||||||
shell: dash.exe -l "{0}"
|
|
||||||
run: |
|
|
||||||
python3 -m pip install -U "numpy<1.26"
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: bash.exe -eo pipefail -o igncr "{0}"
|
shell: bash.exe -eo pipefail -o igncr "{0}"
|
||||||
run: |
|
run: |
|
||||||
|
|
4
.github/workflows/test-windows.yml
vendored
4
.github/workflows/test-windows.yml
vendored
|
@ -35,7 +35,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13.0-alpha.3"]
|
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||||
|
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ jobs:
|
||||||
choco install nasm --no-progress
|
choco install nasm --no-progress
|
||||||
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
choco install ghostscript --version=10.0.0.20230317 --no-progress
|
choco install ghostscript --version=10.3.0 --no-progress
|
||||||
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
|
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
# Install extra test images
|
# Install extra test images
|
||||||
|
|
4
.github/workflows/wheels-dependencies.sh
vendored
4
.github/workflows/wheels-dependencies.sh
vendored
|
@ -16,7 +16,7 @@ ARCHIVE_SDIR=pillow-depends-main
|
||||||
|
|
||||||
# Package versions for fresh source builds
|
# Package versions for fresh source builds
|
||||||
FREETYPE_VERSION=2.13.2
|
FREETYPE_VERSION=2.13.2
|
||||||
HARFBUZZ_VERSION=8.3.0
|
HARFBUZZ_VERSION=8.4.0
|
||||||
LIBPNG_VERSION=1.6.43
|
LIBPNG_VERSION=1.6.43
|
||||||
JPEGTURBO_VERSION=3.0.2
|
JPEGTURBO_VERSION=3.0.2
|
||||||
OPENJPEG_VERSION=2.5.2
|
OPENJPEG_VERSION=2.5.2
|
||||||
|
@ -72,7 +72,7 @@ function build {
|
||||||
|
|
||||||
build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto
|
build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto
|
||||||
if [ -n "$IS_MACOS" ]; then
|
if [ -n "$IS_MACOS" ]; then
|
||||||
build_simple xorgproto 2023.2 https://www.x.org/pub/individual/proto
|
build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
|
||||||
build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib
|
build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib
|
||||||
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
|
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
|
||||||
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||||
|
|
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
|
@ -5,6 +5,7 @@ on:
|
||||||
paths:
|
paths:
|
||||||
- ".ci/requirements-cibw.txt"
|
- ".ci/requirements-cibw.txt"
|
||||||
- ".github/workflows/wheel*"
|
- ".github/workflows/wheel*"
|
||||||
|
- "setup.py"
|
||||||
- "wheels/*"
|
- "wheels/*"
|
||||||
- "winbuild/build_prepare.py"
|
- "winbuild/build_prepare.py"
|
||||||
- "winbuild/fribidi.cmake"
|
- "winbuild/fribidi.cmake"
|
||||||
|
@ -14,6 +15,7 @@ on:
|
||||||
paths:
|
paths:
|
||||||
- ".ci/requirements-cibw.txt"
|
- ".ci/requirements-cibw.txt"
|
||||||
- ".github/workflows/wheel*"
|
- ".github/workflows/wheel*"
|
||||||
|
- "setup.py"
|
||||||
- "wheels/*"
|
- "wheels/*"
|
||||||
- "winbuild/build_prepare.py"
|
- "winbuild/build_prepare.py"
|
||||||
- "winbuild/fribidi.cmake"
|
- "winbuild/fribidi.cmake"
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.2.0
|
rev: v0.3.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--exit-non-zero-on-fix]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
rev: 24.1.1
|
rev: 24.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/bandit
|
- repo: https://github.com/PyCQA/bandit
|
||||||
rev: 1.7.7
|
rev: 1.7.8
|
||||||
hooks:
|
hooks:
|
||||||
- id: bandit
|
- id: bandit
|
||||||
args: [--severity-level=high]
|
args: [--severity-level=high]
|
||||||
files: ^src/
|
files: ^src/
|
||||||
|
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||||
rev: v1.5.4
|
rev: v1.5.5
|
||||||
hooks:
|
hooks:
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||||
|
@ -42,6 +42,13 @@ repos:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||||
|
|
||||||
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||||
|
rev: 0.28.1
|
||||||
|
hooks:
|
||||||
|
- id: check-github-workflows
|
||||||
|
- id: check-readthedocs
|
||||||
|
- id: check-renovate
|
||||||
|
|
||||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||||
rev: v0.9.1
|
rev: v0.9.1
|
||||||
hooks:
|
hooks:
|
||||||
|
@ -62,5 +69,10 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: tox-ini-fmt
|
- id: tox-ini-fmt
|
||||||
|
|
||||||
|
- repo: meta
|
||||||
|
hooks:
|
||||||
|
- id: check-hooks-apply
|
||||||
|
- id: check-useless-excludes
|
||||||
|
|
||||||
ci:
|
ci:
|
||||||
autoupdate_schedule: monthly
|
autoupdate_schedule: monthly
|
||||||
|
|
90
CHANGES.rst
90
CHANGES.rst
|
@ -2,9 +2,93 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
10.3.0 (unreleased)
|
10.3.0 (2024-04-01)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
- CVE-2024-28219: Use ``strncpy`` to avoid buffer overflow #7928
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Deprecate ``eval()``, replacing it with ``lambda_eval()`` and ``unsafe_eval()`` #7927
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Raise ``ValueError`` if seeking to greater than offset-sized integer in TIFF #7883
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Add ``--report`` argument to ``__main__.py`` to omit supported formats #7818
|
||||||
|
[nulano, radarhere, hugovk]
|
||||||
|
|
||||||
|
- Added RGB to I;16, I;16L, I;16B and I;16N conversion #7918, #7920
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix editable installation with custom build backend and configuration options #7658
|
||||||
|
[nulano, radarhere]
|
||||||
|
|
||||||
|
- Fix putdata() for I;16N on big-endian #7209
|
||||||
|
[Yay295, hugovk, radarhere]
|
||||||
|
|
||||||
|
- Determine MPO size from markers, not EXIF data #7884
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved conversion from RGB to RGBa, LA and La #7888
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support FITS images with GZIP_1 compression #7894
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use I;16 mode for 9-bit JPEG 2000 images #7900
|
||||||
|
[scaramallion, radarhere]
|
||||||
|
|
||||||
|
- Raise ValueError if kmeans is negative #7891
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Remove TIFF tag OSUBFILETYPE when saving using libtiff #7893
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Raise ValueError for negative values when loading P1-P3 PPM images #7882
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added reading of JPEG2000 palettes #7870
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added alpha_quality argument when saving WebP images #7872
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed joined corners for ImageDraw rounded_rectangle() non-integer dimensions #7881
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Stop reading EPS image at EOF marker #7753
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- PSD layer co-ordinates may be negative #7706
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use subprocess with CREATE_NO_WINDOW flag in ImageShow WindowsViewer #7791
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- When saving GIF frame that restores to background color, do not fill identical pixels #7788
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed reading PNG iCCP compression method #7823
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow writing IFDRational to UNDEFINED tag #7840
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix logged tag name when loading Exif data #7842
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use maximum frame size in IHDR chunk when saving APNG images #7821
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Prevent opening P TGA images without a palette #7797
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use palette when loading ICO images #7798
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use consistent arguments for load_read and load_seek #7713
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Turn off nullability warnings for macOS SDK #7827
|
- Turn off nullability warnings for macOS SDK #7827
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
@ -4262,7 +4346,7 @@ Changelog (Pillow)
|
||||||
- Documentation changes, URL update, transpose, release checklist
|
- Documentation changes, URL update, transpose, release checklist
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Fixed saving to nonexistant files specified by pathlib.Path objects #1748 (fixes #1747)
|
- Fixed saving to nonexistent files specified by pathlib.Path objects #1748 (fixes #1747)
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
- Round Image.crop arguments to the nearest integer #1745 (fixes #1744)
|
- Round Image.crop arguments to the nearest integer #1745 (fixes #1744)
|
||||||
|
@ -7473,7 +7557,7 @@ The test suite includes 400 individual tests.
|
||||||
- A handbook is available (distributed separately).
|
- A handbook is available (distributed separately).
|
||||||
|
|
||||||
- The coordinate system is changed so that (0,0) is now located
|
- The coordinate system is changed so that (0,0) is now located
|
||||||
in the upper left corner. This is in compliancy with ISO 12087
|
in the upper left corner. This is in compliance with ISO 12087
|
||||||
and 90% of all other image processing and graphics libraries.
|
and 90% of all other image processing and graphics libraries.
|
||||||
|
|
||||||
- Modes "1" (bilevel) and "P" (palette) have been introduced. Note
|
- Modes "1" (bilevel) and "P" (palette) have been introduced. Note
|
||||||
|
|
4
LICENSE
4
LICENSE
|
@ -1,11 +1,11 @@
|
||||||
The Python Imaging Library (PIL) is
|
The Python Imaging Library (PIL) is
|
||||||
|
|
||||||
Copyright © 1997-2011 by Secret Labs AB
|
Copyright © 1997-2011 by Secret Labs AB
|
||||||
Copyright © 1995-2011 by Fredrik Lundh
|
Copyright © 1995-2011 by Fredrik Lundh and contributors
|
||||||
|
|
||||||
Pillow is the friendly PIL fork. It is
|
Pillow is the friendly PIL fork. It is
|
||||||
|
|
||||||
Copyright © 2010-2024 by Jeffrey A. Clark (Alex) and contributors.
|
Copyright © 2010-2024 by Jeffrey A. Clark and contributors
|
||||||
|
|
||||||
Like PIL, Pillow is licensed under the open source HPND License:
|
Like PIL, Pillow is licensed under the open source HPND License:
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
## Python Imaging Library (Fork)
|
## Python Imaging Library (Fork)
|
||||||
|
|
||||||
Pillow is the friendly PIL fork by [Jeffrey A. Clark (Alex) and
|
Pillow is the friendly PIL fork by [Jeffrey A. Clark and
|
||||||
contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
|
contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
|
||||||
PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
|
PIL is the Python Imaging Library by Fredrik Lundh and contributors.
|
||||||
As of 2019, Pillow development is
|
As of 2019, Pillow development is
|
||||||
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
|
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,8 @@ def timer(func, label, *args) -> None:
|
||||||
break
|
break
|
||||||
endtime = time.time()
|
endtime = time.time()
|
||||||
print(
|
print(
|
||||||
"{}: completed {} iterations in {:.4f}s, {:.6f}s per iteration".format(
|
f"{label}: completed {x + 1} iterations in {endtime - starttime:.4f}s, "
|
||||||
label, x + 1, endtime - starttime, (endtime - starttime) / (x + 1.0)
|
f"{(endtime - starttime) / (x + 1.0):.6f}s per iteration"
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from functools import lru_cache
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Any, Callable, Sequence
|
from typing import Any, Callable, Sequence
|
||||||
|
|
||||||
|
@ -114,7 +115,9 @@ def assert_image_similar(
|
||||||
|
|
||||||
diff = 0
|
diff = 0
|
||||||
for ach, bch in zip(a.split(), b.split()):
|
for ach, bch in zip(a.split(), b.split()):
|
||||||
chdiff = ImageMath.eval("abs(a - b)", a=ach, b=bch).convert("L")
|
chdiff = ImageMath.lambda_eval(
|
||||||
|
lambda args: abs(args["a"] - args["b"]), a=ach, b=bch
|
||||||
|
).convert("L")
|
||||||
diff += sum(i * num for i, num in enumerate(chdiff.histogram()))
|
diff += sum(i * num for i, num in enumerate(chdiff.histogram()))
|
||||||
|
|
||||||
ave_diff = diff / (a.size[0] * a.size[1])
|
ave_diff = diff / (a.size[0] * a.size[1])
|
||||||
|
@ -250,25 +253,27 @@ def tostring(im: Image.Image, string_format: str, **options: Any) -> bytes:
|
||||||
return out.getvalue()
|
return out.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def hopper(mode: str | None = None, cache: dict[str, Image.Image] = {}) -> Image.Image:
|
def hopper(mode: str | None = None) -> Image.Image:
|
||||||
|
# Use caching to reduce reading from disk, but return a copy
|
||||||
|
# so that the cached image isn't modified by the tests
|
||||||
|
# (for fast, isolated, repeatable tests).
|
||||||
|
|
||||||
if mode is None:
|
if mode is None:
|
||||||
# Always return fresh not-yet-loaded version of image.
|
# Always return fresh not-yet-loaded version of image.
|
||||||
# Operations on not-yet-loaded images is separate class of errors
|
# Operations on not-yet-loaded images are a separate class of errors
|
||||||
# what we should catch.
|
# that we should catch.
|
||||||
return Image.open("Tests/images/hopper.ppm")
|
return Image.open("Tests/images/hopper.ppm")
|
||||||
# Use caching to reduce reading from disk but so an original copy is
|
|
||||||
# returned each time and the cached image isn't modified by tests
|
return _cached_hopper(mode).copy()
|
||||||
# (for fast, isolated, repeatable tests).
|
|
||||||
im = cache.get(mode)
|
|
||||||
if im is None:
|
@lru_cache
|
||||||
if mode == "F":
|
def _cached_hopper(mode: str) -> Image.Image:
|
||||||
im = hopper("L").convert(mode)
|
if mode == "F":
|
||||||
elif mode[:4] == "I;16":
|
im = hopper("L")
|
||||||
im = hopper("I").convert(mode)
|
else:
|
||||||
else:
|
im = hopper()
|
||||||
im = hopper().convert(mode)
|
return im.convert(mode)
|
||||||
cache[mode] = im
|
|
||||||
return im.copy()
|
|
||||||
|
|
||||||
|
|
||||||
def djpeg_available() -> bool:
|
def djpeg_available() -> bool:
|
||||||
|
|
BIN
Tests/icc/sGrey-v2-nano.icc
Normal file
BIN
Tests/icc/sGrey-v2-nano.icc
Normal file
Binary file not shown.
BIN
Tests/images/9bit.j2k
Normal file
BIN
Tests/images/9bit.j2k
Normal file
Binary file not shown.
BIN
Tests/images/m13.fits
Normal file
BIN
Tests/images/m13.fits
Normal file
Binary file not shown.
366
Tests/images/m13_gzip.fits
Normal file
366
Tests/images/m13_gzip.fits
Normal file
File diff suppressed because one or more lines are too long
BIN
Tests/images/seek_too_large.tif
Normal file
BIN
Tests/images/seek_too_large.tif
Normal file
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
@ -7,7 +7,7 @@ import fuzzers
|
||||||
import packaging
|
import packaging
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, features
|
from PIL import Image, UnidentifiedImageError, features
|
||||||
from Tests.helper import skip_unless_feature
|
from Tests.helper import skip_unless_feature
|
||||||
|
|
||||||
if sys.platform.startswith("win32"):
|
if sys.platform.startswith("win32"):
|
||||||
|
@ -43,7 +43,7 @@ def test_fuzz_images(path: str) -> None:
|
||||||
except (
|
except (
|
||||||
Image.DecompressionBombError,
|
Image.DecompressionBombError,
|
||||||
Image.DecompressionBombWarning,
|
Image.DecompressionBombWarning,
|
||||||
Image.UnidentifiedImageError,
|
UnidentifiedImageError,
|
||||||
):
|
):
|
||||||
# Known Image.* exceptions
|
# Known Image.* exceptions
|
||||||
assert True
|
assert True
|
||||||
|
|
|
@ -117,9 +117,10 @@ def test_unsupported_module() -> None:
|
||||||
features.version_module(module)
|
features.version_module(module)
|
||||||
|
|
||||||
|
|
||||||
def test_pilinfo() -> None:
|
@pytest.mark.parametrize("supported_formats", (True, False))
|
||||||
|
def test_pilinfo(supported_formats) -> None:
|
||||||
buf = io.StringIO()
|
buf = io.StringIO()
|
||||||
features.pilinfo(buf)
|
features.pilinfo(buf, supported_formats=supported_formats)
|
||||||
out = buf.getvalue()
|
out = buf.getvalue()
|
||||||
lines = out.splitlines()
|
lines = out.splitlines()
|
||||||
assert lines[0] == "-" * 68
|
assert lines[0] == "-" * 68
|
||||||
|
@ -129,9 +130,15 @@ def test_pilinfo() -> None:
|
||||||
while lines[0].startswith(" "):
|
while lines[0].startswith(" "):
|
||||||
lines = lines[1:]
|
lines = lines[1:]
|
||||||
assert lines[0] == "-" * 68
|
assert lines[0] == "-" * 68
|
||||||
assert lines[1].startswith("Python modules loaded from ")
|
assert lines[1].startswith("Python executable is")
|
||||||
assert lines[2].startswith("Binary modules loaded from ")
|
lines = lines[2:]
|
||||||
assert lines[3] == "-" * 68
|
if lines[0].startswith("Environment Python files loaded from"):
|
||||||
|
lines = lines[1:]
|
||||||
|
assert lines[0].startswith("System Python files loaded from")
|
||||||
|
assert lines[1] == "-" * 68
|
||||||
|
assert lines[2].startswith("Python Pillow modules loaded from ")
|
||||||
|
assert lines[3].startswith("Binary Pillow modules loaded from ")
|
||||||
|
assert lines[4] == "-" * 68
|
||||||
jpeg = (
|
jpeg = (
|
||||||
"\n"
|
"\n"
|
||||||
+ "-" * 68
|
+ "-" * 68
|
||||||
|
@ -142,4 +149,4 @@ def test_pilinfo() -> None:
|
||||||
+ "-" * 68
|
+ "-" * 68
|
||||||
+ "\n"
|
+ "\n"
|
||||||
)
|
)
|
||||||
assert jpeg in out
|
assert supported_formats == (jpeg in out)
|
||||||
|
|
|
@ -5,7 +5,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import EpsImagePlugin, Image, features
|
from PIL import EpsImagePlugin, Image, UnidentifiedImageError, features
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
|
@ -419,7 +419,7 @@ def test_emptyline() -> None:
|
||||||
)
|
)
|
||||||
def test_timeout(test_file: str) -> None:
|
def test_timeout(test_file: str) -> None:
|
||||||
with open(test_file, "rb") as f:
|
with open(test_file, "rb") as f:
|
||||||
with pytest.raises(Image.UnidentifiedImageError):
|
with pytest.raises(UnidentifiedImageError):
|
||||||
with Image.open(f):
|
with Image.open(f):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import FitsImagePlugin, Image
|
from PIL import FitsImagePlugin, Image
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
||||||
|
|
||||||
TEST_FILE = "Tests/images/hopper.fits"
|
TEST_FILE = "Tests/images/hopper.fits"
|
||||||
|
|
||||||
|
@ -22,6 +22,11 @@ def test_open() -> None:
|
||||||
assert_image_equal(im, hopper("L"))
|
assert_image_equal(im, hopper("L"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_gzip1() -> None:
|
||||||
|
with Image.open("Tests/images/m13_gzip.fits") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/m13.fits")
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file() -> None:
|
def test_invalid_file() -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
|
@ -825,7 +825,7 @@ class TestFileJpeg:
|
||||||
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
|
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
# "When the image resolution is unknown, 72 [dpi] is designated."
|
# "When the image resolution is unknown, 72 [dpi] is designated."
|
||||||
# https://exiv2.org/tags.html
|
# https://web.archive.org/web/20240227115053/https://exiv2.org/tags.html
|
||||||
assert im.info.get("dpi") == (72, 72)
|
assert im.info.get("dpi") == (72, 72)
|
||||||
|
|
||||||
def test_invalid_exif(self) -> None:
|
def test_invalid_exif(self) -> None:
|
||||||
|
|
|
@ -364,6 +364,16 @@ def test_subsampling_decode(name: str) -> None:
|
||||||
assert_image_similar(im, expected, epsilon)
|
assert_image_similar(im, expected, epsilon)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
|
||||||
|
)
|
||||||
|
def test_pclr() -> None:
|
||||||
|
with Image.open(f"{EXTRA_DIR}/issue104_jpxstream.jp2") as im:
|
||||||
|
assert im.mode == "P"
|
||||||
|
assert len(im.palette.colors) == 256
|
||||||
|
assert im.palette.colors[(255, 255, 255)] == 0
|
||||||
|
|
||||||
|
|
||||||
def test_comment() -> None:
|
def test_comment() -> None:
|
||||||
with Image.open("Tests/images/comment.jp2") as im:
|
with Image.open("Tests/images/comment.jp2") as im:
|
||||||
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
|
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
|
||||||
|
@ -436,3 +446,9 @@ def test_plt_marker() -> None:
|
||||||
hdr = out.read(2)
|
hdr = out.read(2)
|
||||||
length = _binary.i16be(hdr)
|
length = _binary.i16be(hdr)
|
||||||
out.seek(length - 2, os.SEEK_CUR)
|
out.seek(length - 2, os.SEEK_CUR)
|
||||||
|
|
||||||
|
|
||||||
|
def test_9bit():
|
||||||
|
with Image.open("Tests/images/9bit.j2k") as im:
|
||||||
|
assert im.mode == "I;16"
|
||||||
|
assert im.size == (128, 128)
|
||||||
|
|
|
@ -6,13 +6,13 @@ import itertools
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from collections import namedtuple
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features
|
from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features
|
||||||
from PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
|
from PIL.TiffImagePlugin import OSUBFILETYPE, SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -243,36 +243,40 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = False
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
||||||
|
|
||||||
def test_custom_metadata(self, tmp_path: Path) -> None:
|
def test_custom_metadata(self, tmp_path: Path) -> None:
|
||||||
tc = namedtuple("tc", "value,type,supported_by_default")
|
class Tc(NamedTuple):
|
||||||
|
value: Any
|
||||||
|
type: int
|
||||||
|
supported_by_default: bool
|
||||||
|
|
||||||
custom = {
|
custom = {
|
||||||
37000 + k: v
|
37000 + k: v
|
||||||
for k, v in enumerate(
|
for k, v in enumerate(
|
||||||
[
|
[
|
||||||
tc(4, TiffTags.SHORT, True),
|
Tc(4, TiffTags.SHORT, True),
|
||||||
tc(123456789, TiffTags.LONG, True),
|
Tc(123456789, TiffTags.LONG, True),
|
||||||
tc(-4, TiffTags.SIGNED_BYTE, False),
|
Tc(-4, TiffTags.SIGNED_BYTE, False),
|
||||||
tc(-4, TiffTags.SIGNED_SHORT, False),
|
Tc(-4, TiffTags.SIGNED_SHORT, False),
|
||||||
tc(-123456789, TiffTags.SIGNED_LONG, False),
|
Tc(-123456789, TiffTags.SIGNED_LONG, False),
|
||||||
tc(TiffImagePlugin.IFDRational(4, 7), TiffTags.RATIONAL, True),
|
Tc(TiffImagePlugin.IFDRational(4, 7), TiffTags.RATIONAL, True),
|
||||||
tc(4.25, TiffTags.FLOAT, True),
|
Tc(4.25, TiffTags.FLOAT, True),
|
||||||
tc(4.25, TiffTags.DOUBLE, True),
|
Tc(4.25, TiffTags.DOUBLE, True),
|
||||||
tc("custom tag value", TiffTags.ASCII, True),
|
Tc("custom tag value", TiffTags.ASCII, True),
|
||||||
tc(b"custom tag value", TiffTags.BYTE, True),
|
Tc(b"custom tag value", TiffTags.BYTE, True),
|
||||||
tc((4, 5, 6), TiffTags.SHORT, True),
|
Tc((4, 5, 6), TiffTags.SHORT, True),
|
||||||
tc((123456789, 9, 34, 234, 219387, 92432323), TiffTags.LONG, True),
|
Tc((123456789, 9, 34, 234, 219387, 92432323), TiffTags.LONG, True),
|
||||||
tc((-4, 9, 10), TiffTags.SIGNED_BYTE, False),
|
Tc((-4, 9, 10), TiffTags.SIGNED_BYTE, False),
|
||||||
tc((-4, 5, 6), TiffTags.SIGNED_SHORT, False),
|
Tc((-4, 5, 6), TiffTags.SIGNED_SHORT, False),
|
||||||
tc(
|
Tc(
|
||||||
(-123456789, 9, 34, 234, 219387, -92432323),
|
(-123456789, 9, 34, 234, 219387, -92432323),
|
||||||
TiffTags.SIGNED_LONG,
|
TiffTags.SIGNED_LONG,
|
||||||
False,
|
False,
|
||||||
),
|
),
|
||||||
tc((4.25, 5.25), TiffTags.FLOAT, True),
|
Tc((4.25, 5.25), TiffTags.FLOAT, True),
|
||||||
tc((4.25, 5.25), TiffTags.DOUBLE, True),
|
Tc((4.25, 5.25), TiffTags.DOUBLE, True),
|
||||||
# array of TIFF_BYTE requires bytes instead of tuple for backwards
|
# array of TIFF_BYTE requires bytes instead of tuple for backwards
|
||||||
# compatibility
|
# compatibility
|
||||||
tc(bytes([4]), TiffTags.BYTE, True),
|
Tc(bytes([4]), TiffTags.BYTE, True),
|
||||||
tc(bytes((4, 9, 10)), TiffTags.BYTE, True),
|
Tc(bytes((4, 9, 10)), TiffTags.BYTE, True),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -325,6 +329,12 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
)
|
)
|
||||||
TiffImagePlugin.WRITE_LIBTIFF = False
|
TiffImagePlugin.WRITE_LIBTIFF = False
|
||||||
|
|
||||||
|
def test_osubfiletype(self, tmp_path: Path) -> None:
|
||||||
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
with Image.open("Tests/images/g4_orientation_6.tif") as im:
|
||||||
|
im.tag_v2[OSUBFILETYPE] = 1
|
||||||
|
im.save(outfile)
|
||||||
|
|
||||||
def test_subifd(self, tmp_path: Path) -> None:
|
def test_subifd(self, tmp_path: Path) -> None:
|
||||||
outfile = str(tmp_path / "temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
with Image.open("Tests/images/g4_orientation_6.tif") as im:
|
with Image.open("Tests/images/g4_orientation_6.tif") as im:
|
||||||
|
|
|
@ -93,7 +93,7 @@ def test_exif(test_file: str) -> None:
|
||||||
|
|
||||||
def test_frame_size() -> None:
|
def test_frame_size() -> None:
|
||||||
# This image has been hexedited to contain a different size
|
# This image has been hexedited to contain a different size
|
||||||
# in the EXIF data of the second frame
|
# in the SOF marker of the second frame
|
||||||
with Image.open("Tests/images/sugarshack_frame_size.mpo") as im:
|
with Image.open("Tests/images/sugarshack_frame_size.mpo") as im:
|
||||||
assert im.size == (640, 480)
|
assert im.size == (640, 480)
|
||||||
|
|
||||||
|
|
|
@ -241,13 +241,23 @@ def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
|
def test_plain_ppm_value_negative(tmp_path: Path) -> None:
|
||||||
|
path = str(tmp_path / "temp.ppm")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(b"P3\n128 128\n255\n-1")
|
||||||
|
|
||||||
|
with Image.open(path) as im:
|
||||||
|
with pytest.raises(ValueError, match="Channel value is negative"):
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
def test_plain_ppm_value_too_large(tmp_path: Path) -> None:
|
def test_plain_ppm_value_too_large(tmp_path: Path) -> None:
|
||||||
path = str(tmp_path / "temp.ppm")
|
path = str(tmp_path / "temp.ppm")
|
||||||
with open(path, "wb") as f:
|
with open(path, "wb") as f:
|
||||||
f.write(b"P3\n128 128\n255\n256")
|
f.write(b"P3\n128 128\n255\n256")
|
||||||
|
|
||||||
with Image.open(path) as im:
|
with Image.open(path) as im:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Channel value too large"):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, PsdImagePlugin
|
from PIL import Image, PsdImagePlugin, UnidentifiedImageError
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile, assert_image_similar, hopper, is_pypy
|
from .helper import assert_image_equal_tofile, assert_image_similar, hopper, is_pypy
|
||||||
|
|
||||||
|
@ -152,11 +152,11 @@ def test_combined_larger_than_size() -> None:
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd",
|
"Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd",
|
||||||
Image.UnidentifiedImageError,
|
UnidentifiedImageError,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd",
|
"Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd",
|
||||||
Image.UnidentifiedImageError,
|
UnidentifiedImageError,
|
||||||
),
|
),
|
||||||
("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError),
|
("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError),
|
||||||
("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError),
|
("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError),
|
||||||
|
|
|
@ -113,6 +113,10 @@ class TestFileTiff:
|
||||||
outfile = str(tmp_path / "temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
|
im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
|
||||||
|
|
||||||
|
def test_seek_too_large(self):
|
||||||
|
with pytest.raises(ValueError, match="Unable to seek to frame"):
|
||||||
|
Image.open("Tests/images/seek_too_large.tif")
|
||||||
|
|
||||||
def test_set_legacy_api(self) -> None:
|
def test_set_legacy_api(self) -> None:
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
with pytest.raises(Exception) as e:
|
with pytest.raises(Exception) as e:
|
||||||
|
|
|
@ -151,3 +151,15 @@ def test_write_unsupported_mode_PA(tmp_path: Path) -> None:
|
||||||
target = im.convert("RGBA")
|
target = im.convert("RGBA")
|
||||||
|
|
||||||
assert_image_similar(image, target, 25.0)
|
assert_image_similar(image, target, 25.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_alpha_quality(tmp_path: Path) -> None:
|
||||||
|
with Image.open("Tests/images/transparent.png") as im:
|
||||||
|
out = str(tmp_path / "temp.webp")
|
||||||
|
im.save(out)
|
||||||
|
|
||||||
|
out_quality = str(tmp_path / "quality.webp")
|
||||||
|
im.save(out_quality, alpha_quality=50)
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
with Image.open(out_quality) as reloaded_quality:
|
||||||
|
assert reloaded.tobytes() != reloaded_quality.tobytes()
|
||||||
|
|
|
@ -188,3 +188,21 @@ def test_seek_errors() -> None:
|
||||||
|
|
||||||
with pytest.raises(EOFError):
|
with pytest.raises(EOFError):
|
||||||
im.seek(42)
|
im.seek(42)
|
||||||
|
|
||||||
|
|
||||||
|
def test_alpha_quality(tmp_path: Path) -> None:
|
||||||
|
with Image.open("Tests/images/transparent.png") as im:
|
||||||
|
first_frame = Image.new("L", im.size)
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.webp")
|
||||||
|
first_frame.save(out, save_all=True, append_images=[im])
|
||||||
|
|
||||||
|
out_quality = str(tmp_path / "quality.webp")
|
||||||
|
first_frame.save(
|
||||||
|
out_quality, save_all=True, append_images=[im], alpha_quality=50
|
||||||
|
)
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
reloaded.seek(1)
|
||||||
|
with Image.open(out_quality) as reloaded_quality:
|
||||||
|
reloaded_quality.seek(1)
|
||||||
|
assert reloaded.tobytes() != reloaded_quality.tobytes()
|
||||||
|
|
|
@ -33,36 +33,38 @@ from .helper import (
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# name, pixel size
|
||||||
|
image_modes = (
|
||||||
|
("1", 1),
|
||||||
|
("L", 1),
|
||||||
|
("LA", 4),
|
||||||
|
("La", 4),
|
||||||
|
("P", 1),
|
||||||
|
("PA", 4),
|
||||||
|
("F", 4),
|
||||||
|
("I", 4),
|
||||||
|
("I;16", 2),
|
||||||
|
("I;16L", 2),
|
||||||
|
("I;16B", 2),
|
||||||
|
("I;16N", 2),
|
||||||
|
("RGB", 4),
|
||||||
|
("RGBA", 4),
|
||||||
|
("RGBa", 4),
|
||||||
|
("RGBX", 4),
|
||||||
|
("BGR;15", 2),
|
||||||
|
("BGR;16", 2),
|
||||||
|
("BGR;24", 3),
|
||||||
|
("CMYK", 4),
|
||||||
|
("YCbCr", 4),
|
||||||
|
("HSV", 4),
|
||||||
|
("LAB", 4),
|
||||||
|
)
|
||||||
|
|
||||||
|
image_mode_names = [name for name, _ in image_modes]
|
||||||
|
|
||||||
|
|
||||||
class TestImage:
|
class TestImage:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize("mode", image_mode_names)
|
||||||
"mode",
|
|
||||||
(
|
|
||||||
"1",
|
|
||||||
"P",
|
|
||||||
"PA",
|
|
||||||
"L",
|
|
||||||
"LA",
|
|
||||||
"La",
|
|
||||||
"F",
|
|
||||||
"I",
|
|
||||||
"I;16",
|
|
||||||
"I;16L",
|
|
||||||
"I;16B",
|
|
||||||
"I;16N",
|
|
||||||
"RGB",
|
|
||||||
"RGBX",
|
|
||||||
"RGBA",
|
|
||||||
"RGBa",
|
|
||||||
"BGR;15",
|
|
||||||
"BGR;16",
|
|
||||||
"BGR;24",
|
|
||||||
"CMYK",
|
|
||||||
"YCbCr",
|
|
||||||
"LAB",
|
|
||||||
"HSV",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_image_modes_success(self, mode: str) -> None:
|
def test_image_modes_success(self, mode: str) -> None:
|
||||||
Image.new(mode, (1, 1))
|
Image.new(mode, (1, 1))
|
||||||
|
|
||||||
|
@ -1042,6 +1044,35 @@ class TestImage:
|
||||||
assert im.fp is None
|
assert im.fp is None
|
||||||
|
|
||||||
|
|
||||||
|
class TestImageBytes:
|
||||||
|
@pytest.mark.parametrize("mode", image_mode_names)
|
||||||
|
def test_roundtrip_bytes_constructor(self, mode: str) -> None:
|
||||||
|
im = hopper(mode)
|
||||||
|
source_bytes = im.tobytes()
|
||||||
|
|
||||||
|
reloaded = Image.frombytes(mode, im.size, source_bytes)
|
||||||
|
assert reloaded.tobytes() == source_bytes
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", image_mode_names)
|
||||||
|
def test_roundtrip_bytes_method(self, mode: str) -> None:
|
||||||
|
im = hopper(mode)
|
||||||
|
source_bytes = im.tobytes()
|
||||||
|
|
||||||
|
reloaded = Image.new(mode, im.size)
|
||||||
|
reloaded.frombytes(source_bytes)
|
||||||
|
assert reloaded.tobytes() == source_bytes
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(("mode", "pixelsize"), image_modes)
|
||||||
|
def test_getdata_putdata(self, mode: str, pixelsize: int) -> None:
|
||||||
|
im = Image.new(mode, (2, 2))
|
||||||
|
source_bytes = bytes(range(im.width * im.height * pixelsize))
|
||||||
|
im.frombytes(source_bytes)
|
||||||
|
|
||||||
|
reloaded = Image.new(mode, im.size)
|
||||||
|
reloaded.putdata(im.getdata())
|
||||||
|
assert_image_equal(im, reloaded)
|
||||||
|
|
||||||
|
|
||||||
class MockEncoder(ImageFile.PyEncoder):
|
class MockEncoder(ImageFile.PyEncoder):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,14 @@ def test_trns_RGB(tmp_path: Path) -> None:
|
||||||
assert im_l.info["transparency"] == im_l.getpixel((0, 0)) # undone
|
assert im_l.info["transparency"] == im_l.getpixel((0, 0)) # undone
|
||||||
im_l.save(f)
|
im_l.save(f)
|
||||||
|
|
||||||
|
im_la = im.convert("LA")
|
||||||
|
assert "transparency" not in im_la.info
|
||||||
|
im_la.save(f)
|
||||||
|
|
||||||
|
im_la = im.convert("La")
|
||||||
|
assert "transparency" not in im_la.info
|
||||||
|
assert im_la.getpixel((0, 0)) == (0, 0)
|
||||||
|
|
||||||
im_p = im.convert("P")
|
im_p = im.convert("P")
|
||||||
assert "transparency" in im_p.info
|
assert "transparency" in im_p.info
|
||||||
im_p.save(f)
|
im_p.save(f)
|
||||||
|
@ -191,6 +199,10 @@ def test_trns_RGB(tmp_path: Path) -> None:
|
||||||
assert "transparency" not in im_rgba.info
|
assert "transparency" not in im_rgba.info
|
||||||
im_rgba.save(f)
|
im_rgba.save(f)
|
||||||
|
|
||||||
|
im_rgba = im.convert("RGBa")
|
||||||
|
assert "transparency" not in im_rgba.info
|
||||||
|
assert im_rgba.getpixel((0, 0)) == (0, 0, 0, 0)
|
||||||
|
|
||||||
im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.Palette.ADAPTIVE)
|
im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.Palette.ADAPTIVE)
|
||||||
assert "transparency" not in im_p.info
|
assert "transparency" not in im_p.info
|
||||||
im_p.save(f)
|
im_p.save(f)
|
||||||
|
|
|
@ -16,11 +16,13 @@ pytestmark = pytest.mark.skipif(
|
||||||
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
||||||
)
|
)
|
||||||
|
|
||||||
ims = [
|
ims: list[Image.Image] = []
|
||||||
hopper(),
|
|
||||||
Image.open("Tests/images/transparent.png"),
|
|
||||||
Image.open("Tests/images/7x13.png"),
|
def setup_module() -> None:
|
||||||
]
|
ims.append(hopper())
|
||||||
|
ims.append(Image.open("Tests/images/transparent.png"))
|
||||||
|
ims.append(Image.open("Tests/images/7x13.png"))
|
||||||
|
|
||||||
|
|
||||||
def teardown_module() -> None:
|
def teardown_module() -> None:
|
||||||
|
|
|
@ -94,6 +94,19 @@ def test_quantize_dither_diff() -> None:
|
||||||
assert dither.tobytes() != nodither.tobytes()
|
assert dither.tobytes() != nodither.tobytes()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"method", (Image.Quantize.MEDIANCUT, Image.Quantize.MAXCOVERAGE)
|
||||||
|
)
|
||||||
|
def test_quantize_kmeans(method) -> None:
|
||||||
|
im = hopper()
|
||||||
|
no_kmeans = im.quantize(kmeans=0, method=method)
|
||||||
|
kmeans = im.quantize(kmeans=1, method=method)
|
||||||
|
assert kmeans.tobytes() != no_kmeans.tobytes()
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im.quantize(kmeans=-1, method=method)
|
||||||
|
|
||||||
|
|
||||||
def test_colors() -> None:
|
def test_colors() -> None:
|
||||||
im = hopper()
|
im = hopper()
|
||||||
colors = 2
|
colors = 2
|
||||||
|
|
|
@ -186,7 +186,9 @@ def assert_compare_images(
|
||||||
|
|
||||||
bands = ImageMode.getmode(a.mode).bands
|
bands = ImageMode.getmode(a.mode).bands
|
||||||
for band, ach, bch in zip(bands, a.split(), b.split()):
|
for band, ach, bch in zip(bands, a.split(), b.split()):
|
||||||
ch_diff = ImageMath.eval("convert(abs(a - b), 'L')", a=ach, b=bch)
|
ch_diff = ImageMath.lambda_eval(
|
||||||
|
lambda args: args["convert"](abs(args["a"] - args["b"]), "L"), a=ach, b=bch
|
||||||
|
)
|
||||||
ch_hist = ch_diff.histogram()
|
ch_hist = ch_diff.histogram()
|
||||||
|
|
||||||
average_diff = sum(i * num for i, num in enumerate(ch_hist)) / (
|
average_diff = sum(i * num for i, num in enumerate(ch_hist)) / (
|
||||||
|
|
|
@ -4,13 +4,14 @@ import datetime
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageMode, features
|
from PIL import Image, ImageMode, ImageWin, features
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image,
|
assert_image,
|
||||||
|
@ -18,6 +19,7 @@ from .helper import (
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
assert_image_similar_tofile,
|
assert_image_similar_tofile,
|
||||||
hopper,
|
hopper,
|
||||||
|
is_pypy,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -213,6 +215,10 @@ def test_display_profile() -> None:
|
||||||
# try fetching the profile for the current display device
|
# try fetching the profile for the current display device
|
||||||
ImageCms.get_display_profile()
|
ImageCms.get_display_profile()
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
ImageCms.get_display_profile(ImageWin.HDC(0))
|
||||||
|
ImageCms.get_display_profile(ImageWin.HWND(0))
|
||||||
|
|
||||||
|
|
||||||
def test_lab_color_profile() -> None:
|
def test_lab_color_profile() -> None:
|
||||||
ImageCms.createProfile("LAB", 5000)
|
ImageCms.createProfile("LAB", 5000)
|
||||||
|
@ -496,16 +502,34 @@ def test_non_ascii_path(tmp_path: Path) -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_profile_typesafety() -> None:
|
def test_profile_typesafety() -> None:
|
||||||
"""Profile init type safety
|
# does not segfault
|
||||||
|
|
||||||
prepatch, these would segfault, postpatch they should emit a typeerror
|
|
||||||
"""
|
|
||||||
|
|
||||||
with pytest.raises(TypeError, match="Invalid type for Profile"):
|
with pytest.raises(TypeError, match="Invalid type for Profile"):
|
||||||
ImageCms.ImageCmsProfile(0).tobytes()
|
ImageCms.ImageCmsProfile(0).tobytes()
|
||||||
with pytest.raises(TypeError, match="Invalid type for Profile"):
|
with pytest.raises(TypeError, match="Invalid type for Profile"):
|
||||||
ImageCms.ImageCmsProfile(1).tobytes()
|
ImageCms.ImageCmsProfile(1).tobytes()
|
||||||
|
|
||||||
|
# also check core function
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
ImageCms.core.profile_tobytes(0)
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
ImageCms.core.profile_tobytes(1)
|
||||||
|
|
||||||
|
if not is_pypy():
|
||||||
|
# core profile should not be directly instantiable
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
ImageCms.core.CmsProfile()
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
ImageCms.core.CmsProfile(0)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(is_pypy(), reason="fails on PyPy")
|
||||||
|
def test_transform_typesafety() -> None:
|
||||||
|
# core transform should not be directly instantiable
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
ImageCms.core.CmsTransform()
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
ImageCms.core.CmsTransform(0)
|
||||||
|
|
||||||
|
|
||||||
def assert_aux_channel_preserved(
|
def assert_aux_channel_preserved(
|
||||||
mode: str, transform_in_place: bool, preserved_channel: str
|
mode: str, transform_in_place: bool, preserved_channel: str
|
||||||
|
@ -637,6 +661,11 @@ def test_auxiliary_channels_isolated() -> None:
|
||||||
assert_image_equal(test_image.convert(dst_format[2]), reference_image)
|
assert_image_equal(test_image.convert(dst_format[2]), reference_image)
|
||||||
|
|
||||||
|
|
||||||
|
def test_long_modes() -> None:
|
||||||
|
p = ImageCms.getOpenProfile("Tests/icc/sGrey-v2-nano.icc")
|
||||||
|
ImageCms.buildTransform(p, p, "ABCDEFGHI", "ABCDEFGHI")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
|
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
|
||||||
def test_rgb_lab(mode: str) -> None:
|
def test_rgb_lab(mode: str) -> None:
|
||||||
im = Image.new(mode, (1, 1))
|
im = Image.new(mode, (1, 1))
|
||||||
|
|
|
@ -868,8 +868,10 @@ def test_rounded_rectangle_zero_radius(bbox: Coords) -> None:
|
||||||
[
|
[
|
||||||
((20, 10, 80, 90), "x"),
|
((20, 10, 80, 90), "x"),
|
||||||
((20, 10, 81, 90), "x_odd"),
|
((20, 10, 81, 90), "x_odd"),
|
||||||
|
((20, 10, 81.1, 90), "x_odd"),
|
||||||
((10, 20, 90, 80), "y"),
|
((10, 20, 90, 80), "y"),
|
||||||
((10, 20, 90, 81), "y_odd"),
|
((10, 20, 90, 81), "y_odd"),
|
||||||
|
((10, 20, 90, 81.1), "y_odd"),
|
||||||
((20, 20, 80, 80), "both"),
|
((20, 20, 80, 80), "both"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,214 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from PIL import Image, ImageMath
|
|
||||||
|
|
||||||
|
|
||||||
def pixel(im: Image.Image | int) -> str | int:
|
|
||||||
if isinstance(im, int):
|
|
||||||
return int(im) # hack to deal with booleans
|
|
||||||
|
|
||||||
return f"{im.mode} {repr(im.getpixel((0, 0)))}"
|
|
||||||
|
|
||||||
|
|
||||||
A = Image.new("L", (1, 1), 1)
|
|
||||||
B = Image.new("L", (1, 1), 2)
|
|
||||||
Z = Image.new("L", (1, 1), 0) # Z for zero
|
|
||||||
F = Image.new("F", (1, 1), 3)
|
|
||||||
I = Image.new("I", (1, 1), 4) # noqa: E741
|
|
||||||
|
|
||||||
A2 = A.resize((2, 2))
|
|
||||||
B2 = B.resize((2, 2))
|
|
||||||
|
|
||||||
images = {"A": A, "B": B, "F": F, "I": I}
|
|
||||||
|
|
||||||
|
|
||||||
def test_sanity() -> None:
|
|
||||||
assert ImageMath.eval("1") == 1
|
|
||||||
assert ImageMath.eval("1+A", A=2) == 3
|
|
||||||
assert pixel(ImageMath.eval("A+B", A=A, B=B)) == "I 3"
|
|
||||||
assert pixel(ImageMath.eval("A+B", images)) == "I 3"
|
|
||||||
assert pixel(ImageMath.eval("float(A)+B", images)) == "F 3.0"
|
|
||||||
assert pixel(ImageMath.eval("int(float(A)+B)", images)) == "I 3"
|
|
||||||
|
|
||||||
|
|
||||||
def test_ops() -> None:
|
|
||||||
assert pixel(ImageMath.eval("-A", images)) == "I -1"
|
|
||||||
assert pixel(ImageMath.eval("+B", images)) == "L 2"
|
|
||||||
|
|
||||||
assert pixel(ImageMath.eval("A+B", images)) == "I 3"
|
|
||||||
assert pixel(ImageMath.eval("A-B", images)) == "I -1"
|
|
||||||
assert pixel(ImageMath.eval("A*B", images)) == "I 2"
|
|
||||||
assert pixel(ImageMath.eval("A/B", images)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B**2", images)) == "I 4"
|
|
||||||
assert pixel(ImageMath.eval("B**33", images)) == "I 2147483647"
|
|
||||||
|
|
||||||
assert pixel(ImageMath.eval("float(A)+B", images)) == "F 3.0"
|
|
||||||
assert pixel(ImageMath.eval("float(A)-B", images)) == "F -1.0"
|
|
||||||
assert pixel(ImageMath.eval("float(A)*B", images)) == "F 2.0"
|
|
||||||
assert pixel(ImageMath.eval("float(A)/B", images)) == "F 0.5"
|
|
||||||
assert pixel(ImageMath.eval("float(B)**2", images)) == "F 4.0"
|
|
||||||
assert pixel(ImageMath.eval("float(B)**33", images)) == "F 8589934592.0"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"expression",
|
|
||||||
(
|
|
||||||
"exec('pass')",
|
|
||||||
"(lambda: exec('pass'))()",
|
|
||||||
"(lambda: (lambda: exec('pass'))())()",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_prevent_exec(expression: str) -> None:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
ImageMath.eval(expression)
|
|
||||||
|
|
||||||
|
|
||||||
def test_prevent_double_underscores() -> None:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
ImageMath.eval("1", {"__": None})
|
|
||||||
|
|
||||||
|
|
||||||
def test_prevent_builtins() -> None:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
ImageMath.eval("(lambda: exec('exit()'))()", {"exec": None})
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical() -> None:
|
|
||||||
assert pixel(ImageMath.eval("not A", images)) == 0
|
|
||||||
assert pixel(ImageMath.eval("A and B", images)) == "L 2"
|
|
||||||
assert pixel(ImageMath.eval("A or B", images)) == "L 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_convert() -> None:
|
|
||||||
assert pixel(ImageMath.eval("convert(A+B, 'L')", images)) == "L 3"
|
|
||||||
assert pixel(ImageMath.eval("convert(A+B, '1')", images)) == "1 0"
|
|
||||||
assert pixel(ImageMath.eval("convert(A+B, 'RGB')", images)) == "RGB (3, 3, 3)"
|
|
||||||
|
|
||||||
|
|
||||||
def test_compare() -> None:
|
|
||||||
assert pixel(ImageMath.eval("min(A, B)", images)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("max(A, B)", images)) == "I 2"
|
|
||||||
assert pixel(ImageMath.eval("A == 1", images)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A == 2", images)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_one_image_larger() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A+B", A=A2, B=B)) == "I 3"
|
|
||||||
assert pixel(ImageMath.eval("A+B", A=A, B=B2)) == "I 3"
|
|
||||||
|
|
||||||
|
|
||||||
def test_abs() -> None:
|
|
||||||
assert pixel(ImageMath.eval("abs(A)", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("abs(B)", B=B)) == "I 2"
|
|
||||||
|
|
||||||
|
|
||||||
def test_binary_mod() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A%A", A=A)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B%B", B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A%B", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B%A", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z%A", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z%B", B=B, Z=Z)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_invert() -> None:
|
|
||||||
assert pixel(ImageMath.eval("~Z", Z=Z)) == "I -1"
|
|
||||||
assert pixel(ImageMath.eval("~A", A=A)) == "I -2"
|
|
||||||
assert pixel(ImageMath.eval("~B", B=B)) == "I -3"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_and() -> None:
|
|
||||||
assert pixel(ImageMath.eval("Z&Z", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z&A", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A&Z", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A&A", A=A, Z=Z)) == "I 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_or() -> None:
|
|
||||||
assert pixel(ImageMath.eval("Z|Z", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z|A", A=A, Z=Z)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A|Z", A=A, Z=Z)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A|A", A=A, Z=Z)) == "I 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_xor() -> None:
|
|
||||||
assert pixel(ImageMath.eval("Z^Z", A=A, Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z^A", A=A, Z=Z)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A^Z", A=A, Z=Z)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A^A", A=A, Z=Z)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_leftshift() -> None:
|
|
||||||
assert pixel(ImageMath.eval("Z<<0", Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z<<1", Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A<<0", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A<<1", A=A)) == "I 2"
|
|
||||||
|
|
||||||
|
|
||||||
def test_bitwise_rightshift() -> None:
|
|
||||||
assert pixel(ImageMath.eval("Z>>0", Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("Z>>1", Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A>>0", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A>>1", A=A)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_eq() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A==A", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B==B", B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A==B", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B==A", A=A, B=B)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_ne() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A!=A", A=A)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B!=B", B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A!=B", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B!=A", A=A, B=B)) == "I 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_lt() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A<A", A=A)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B<B", B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A<B", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B<A", A=A, B=B)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_le() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A<=A", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B<=B", B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A<=B", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B<=A", A=A, B=B)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_gt() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A>A", A=A)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B>B", B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("A>B", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B>A", A=A, B=B)) == "I 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_ge() -> None:
|
|
||||||
assert pixel(ImageMath.eval("A>=A", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("B>=B", B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("A>=B", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("B>=A", A=A, B=B)) == "I 1"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_equal() -> None:
|
|
||||||
assert pixel(ImageMath.eval("equal(A, A)", A=A)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("equal(B, B)", B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("equal(Z, Z)", Z=Z)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("equal(A, B)", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("equal(B, A)", A=A, B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("equal(A, Z)", A=A, Z=Z)) == "I 0"
|
|
||||||
|
|
||||||
|
|
||||||
def test_logical_not_equal() -> None:
|
|
||||||
assert pixel(ImageMath.eval("notequal(A, A)", A=A)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("notequal(B, B)", B=B)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("notequal(Z, Z)", Z=Z)) == "I 0"
|
|
||||||
assert pixel(ImageMath.eval("notequal(A, B)", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("notequal(B, A)", A=A, B=B)) == "I 1"
|
|
||||||
assert pixel(ImageMath.eval("notequal(A, Z)", A=A, Z=Z)) == "I 1"
|
|
496
Tests/test_imagemath_lambda_eval.py
Normal file
496
Tests/test_imagemath_lambda_eval.py
Normal file
|
@ -0,0 +1,496 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from PIL import Image, ImageMath
|
||||||
|
|
||||||
|
|
||||||
|
def pixel(im: Image.Image | int) -> str | int:
|
||||||
|
if isinstance(im, int):
|
||||||
|
return int(im) # hack to deal with booleans
|
||||||
|
|
||||||
|
return f"{im.mode} {repr(im.getpixel((0, 0)))}"
|
||||||
|
|
||||||
|
|
||||||
|
A = Image.new("L", (1, 1), 1)
|
||||||
|
B = Image.new("L", (1, 1), 2)
|
||||||
|
Z = Image.new("L", (1, 1), 0) # Z for zero
|
||||||
|
F = Image.new("F", (1, 1), 3)
|
||||||
|
I = Image.new("I", (1, 1), 4) # noqa: E741
|
||||||
|
|
||||||
|
A2 = A.resize((2, 2))
|
||||||
|
B2 = B.resize((2, 2))
|
||||||
|
|
||||||
|
images = {"A": A, "B": B, "F": F, "I": I}
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity() -> None:
|
||||||
|
assert ImageMath.lambda_eval(lambda args: 1) == 1
|
||||||
|
assert ImageMath.lambda_eval(lambda args: 1 + args["A"], A=2) == 3
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], A=A, B=B))
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images))
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["float"](args["A"]) + args["B"], images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "F 3.0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["int"](args["float"](args["A"]) + args["B"]), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ops() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, images)) == "I -1"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images))
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], images))
|
||||||
|
== "I -1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], images))
|
||||||
|
== "I 2"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], images))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, images)) == "I 4"
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, images))
|
||||||
|
== "I 2147483647"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["float"](args["A"]) + args["B"], images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "F 3.0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["float"](args["A"]) - args["B"], images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "F -1.0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["float"](args["A"]) * args["B"], images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "F 2.0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["float"](args["A"]) / args["B"], images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "F 0.5"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, images))
|
||||||
|
== "F 4.0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, images)
|
||||||
|
)
|
||||||
|
== "F 8589934592.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], images)) == 0
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], images))
|
||||||
|
== "L 2"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], images))
|
||||||
|
== "L 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_convert() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["convert"](args["A"] + args["B"], "L"), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "L 3"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["convert"](args["A"] + args["B"], "1"), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "1 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["convert"](args["A"] + args["B"], "RGB"), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "RGB (3, 3, 3)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_compare() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["min"](args["A"], args["B"]), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["max"](args["A"], args["B"]), images
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 2"
|
||||||
|
)
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, images)) == "I 1"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, images)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_one_image_larger() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], A=A2, B=B))
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], A=A, B=B2))
|
||||||
|
== "I 3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_abs() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: abs(args["A"]), A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: abs(args["B"]), B=B)) == "I 2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_binary_mod() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] % args["A"], A=A)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] % args["B"], B=B)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] % args["B"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] % args["A"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] % args["A"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] % args["B"], B=B, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_invert() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: ~args["Z"], Z=Z)) == "I -1"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: ~args["A"], A=A)) == "I -2"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: ~args["B"], B=B)) == "I -3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_and() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] & args["Z"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] & args["A"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] & args["Z"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] & args["A"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_or() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] | args["Z"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] | args["A"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] | args["Z"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] | args["A"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_xor() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] ^ args["Z"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["Z"] ^ args["A"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] ^ args["Z"], A=A, Z=Z))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] ^ args["A"], A=A, Z=Z))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_leftshift() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["Z"] << 0, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["Z"] << 1, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] << 0, A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] << 1, A=A)) == "I 2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_rightshift() -> None:
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["Z"] >> 0, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["Z"] >> 1, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] >> 0, A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] >> 1, A=A)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_eq() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] == args["A"], A=A)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] == args["B"], B=B)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] == args["B"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] == args["A"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_ne() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] != args["A"], A=A)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] != args["B"], B=B)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] != args["B"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] != args["A"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_lt() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] < args["A"], A=A)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] < args["B"], B=B)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] < args["B"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] < args["A"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_le() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] <= args["A"], A=A)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] <= args["B"], B=B)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] <= args["B"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] <= args["A"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_gt() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] > args["A"], A=A)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] > args["B"], B=B)) == "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] > args["B"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] > args["A"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_ge() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] >= args["A"], A=A)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] >= args["B"], B=B)) == "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] >= args["B"], A=A, B=B))
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] >= args["A"], A=A, B=B))
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_equal() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(lambda args: args["equal"](args["A"], args["A"]), A=A)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(lambda args: args["equal"](args["B"], args["B"]), B=B)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(lambda args: args["equal"](args["Z"], args["Z"]), Z=Z)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["equal"](args["A"], args["B"]), A=A, B=B
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["equal"](args["B"], args["A"]), A=A, B=B
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["equal"](args["A"], args["Z"]), A=A, Z=Z
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_not_equal() -> None:
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["A"], args["A"]), A=A
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["B"], args["B"]), B=B
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["Z"], args["Z"]), Z=Z
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 0"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["A"], args["B"]), A=A, B=B
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["B"], args["A"]), A=A, B=B
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
pixel(
|
||||||
|
ImageMath.lambda_eval(
|
||||||
|
lambda args: args["notequal"](args["A"], args["Z"]), A=A, Z=Z
|
||||||
|
)
|
||||||
|
)
|
||||||
|
== "I 1"
|
||||||
|
)
|
221
Tests/test_imagemath_unsafe_eval.py
Normal file
221
Tests/test_imagemath_unsafe_eval.py
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import Image, ImageMath
|
||||||
|
|
||||||
|
|
||||||
|
def pixel(im: Image.Image | int) -> str | int:
|
||||||
|
if isinstance(im, int):
|
||||||
|
return int(im) # hack to deal with booleans
|
||||||
|
|
||||||
|
return f"{im.mode} {repr(im.getpixel((0, 0)))}"
|
||||||
|
|
||||||
|
|
||||||
|
A = Image.new("L", (1, 1), 1)
|
||||||
|
B = Image.new("L", (1, 1), 2)
|
||||||
|
Z = Image.new("L", (1, 1), 0) # Z for zero
|
||||||
|
F = Image.new("F", (1, 1), 3)
|
||||||
|
I = Image.new("I", (1, 1), 4) # noqa: E741
|
||||||
|
|
||||||
|
A2 = A.resize((2, 2))
|
||||||
|
B2 = B.resize((2, 2))
|
||||||
|
|
||||||
|
images = {"A": A, "B": B, "F": F, "I": I}
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity() -> None:
|
||||||
|
assert ImageMath.unsafe_eval("1") == 1
|
||||||
|
assert ImageMath.unsafe_eval("1+A", A=2) == 3
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A+B", A=A, B=B)) == "I 3"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", images)) == "I 3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_eval_deprecated() -> None:
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert ImageMath.eval("1") == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_ops() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("-A", images)) == "I -1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("+B", images)) == "L 2"
|
||||||
|
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A-B", images)) == "I -1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A*B", images)) == "I 2"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A/B", images)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B**2", images)) == "I 4"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B**33", images)) == "I 2147483647"
|
||||||
|
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(A)-B", images)) == "F -1.0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(A)*B", images)) == "F 2.0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(A)/B", images)) == "F 0.5"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(B)**2", images)) == "F 4.0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("float(B)**33", images)) == "F 8589934592.0"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"expression",
|
||||||
|
(
|
||||||
|
"exec('pass')",
|
||||||
|
"(lambda: exec('pass'))()",
|
||||||
|
"(lambda: (lambda: exec('pass'))())()",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_prevent_exec(expression: str) -> None:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageMath.unsafe_eval(expression)
|
||||||
|
|
||||||
|
|
||||||
|
def test_prevent_double_underscores() -> None:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageMath.unsafe_eval("1", {"__": None})
|
||||||
|
|
||||||
|
|
||||||
|
def test_prevent_builtins() -> None:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageMath.unsafe_eval("(lambda: exec('exit()'))()", {"exec": None})
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("not A", images)) == 0
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A and B", images)) == "L 2"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A or B", images)) == "L 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_convert() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", images)) == "L 3"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", images)) == "1 0"
|
||||||
|
assert (
|
||||||
|
pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", images)) == "RGB (3, 3, 3)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_compare() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("min(A, B)", images)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("max(A, B)", images)) == "I 2"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A == 1", images)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A == 2", images)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_one_image_larger() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A+B", A=A2, B=B)) == "I 3"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A+B", A=A, B=B2)) == "I 3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_abs() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("abs(A)", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("abs(B)", B=B)) == "I 2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_binary_mod() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A%A", A=A)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B%B", B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A%B", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B%A", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z%A", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z%B", B=B, Z=Z)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_invert() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("~Z", Z=Z)) == "I -1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("~A", A=A)) == "I -2"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("~B", B=B)) == "I -3"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_and() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z&Z", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z&A", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A&Z", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A&A", A=A, Z=Z)) == "I 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_or() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z|Z", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z|A", A=A, Z=Z)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A|Z", A=A, Z=Z)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A|A", A=A, Z=Z)) == "I 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_xor() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z^Z", A=A, Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z^A", A=A, Z=Z)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A^Z", A=A, Z=Z)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A^A", A=A, Z=Z)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_leftshift() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z<<0", Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z<<1", Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<<0", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<<1", A=A)) == "I 2"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bitwise_rightshift() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z>>0", Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("Z>>1", Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>>0", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>>1", A=A)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_eq() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A==A", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B==B", B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A==B", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B==A", A=A, B=B)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_ne() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A!=A", A=A)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B!=B", B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A!=B", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B!=A", A=A, B=B)) == "I 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_lt() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<A", A=A)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B<B", B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<B", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B<A", A=A, B=B)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_le() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<=A", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B<=B", B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A<=B", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B<=A", A=A, B=B)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_gt() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>A", A=A)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B>B", B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>B", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B>A", A=A, B=B)) == "I 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_ge() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>=A", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B>=B", B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("A>=B", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("B>=A", A=A, B=B)) == "I 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_equal() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(A, A)", A=A)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(B, B)", B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(Z, Z)", Z=Z)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(A, B)", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(B, A)", A=A, B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("equal(A, Z)", A=A, Z=Z)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_logical_not_equal() -> None:
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(A, A)", A=A)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(B, B)", B=B)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(Z, Z)", Z=Z)) == "I 0"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(A, B)", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(B, A)", A=A, B=B)) == "I 1"
|
||||||
|
assert pixel(ImageMath.unsafe_eval("notequal(A, Z)", A=A, Z=Z)) == "I 1"
|
|
@ -15,7 +15,7 @@ class TestLibPack:
|
||||||
mode: str,
|
mode: str,
|
||||||
rawmode: str,
|
rawmode: str,
|
||||||
data: int | bytes,
|
data: int | bytes,
|
||||||
*pixels: int | float | tuple[int, ...],
|
*pixels: float | tuple[int, ...],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
data - either raw bytes with data or just number of bytes in rawmode.
|
data - either raw bytes with data or just number of bytes in rawmode.
|
||||||
|
@ -239,7 +239,7 @@ class TestLibUnpack:
|
||||||
mode: str,
|
mode: str,
|
||||||
rawmode: str,
|
rawmode: str,
|
||||||
data: int | bytes,
|
data: int | bytes,
|
||||||
*pixels: int | float | tuple[int, ...],
|
*pixels: float | tuple[int, ...],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
data - either raw bytes with data or just number of bytes in rawmode.
|
data - either raw bytes with data or just number of bytes in rawmode.
|
||||||
|
|
|
@ -19,7 +19,7 @@ from PIL import Image
|
||||||
# 7
|
# 7
|
||||||
# 160
|
# 160
|
||||||
|
|
||||||
# one of string.whitespace is not freely convertable into ascii.
|
# one of string.whitespace is not freely convertible into ascii.
|
||||||
|
|
||||||
path = "Tests/images/hopper.jpg"
|
path = "Tests/images/hopper.jpg"
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,16 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
def test_main() -> None:
|
|
||||||
out = subprocess.check_output([sys.executable, "-m", "PIL"]).decode("utf-8")
|
@pytest.mark.parametrize(
|
||||||
|
"args, report",
|
||||||
|
((["PIL"], False), (["PIL", "--report"], True), (["PIL.report"], True)),
|
||||||
|
)
|
||||||
|
def test_main(args, report) -> None:
|
||||||
|
args = [sys.executable, "-m"] + args
|
||||||
|
out = subprocess.check_output(args).decode("utf-8")
|
||||||
lines = out.splitlines()
|
lines = out.splitlines()
|
||||||
assert lines[0] == "-" * 68
|
assert lines[0] == "-" * 68
|
||||||
assert lines[1].startswith("Pillow ")
|
assert lines[1].startswith("Pillow ")
|
||||||
|
@ -15,9 +22,15 @@ def test_main() -> None:
|
||||||
while lines[0].startswith(" "):
|
while lines[0].startswith(" "):
|
||||||
lines = lines[1:]
|
lines = lines[1:]
|
||||||
assert lines[0] == "-" * 68
|
assert lines[0] == "-" * 68
|
||||||
assert lines[1].startswith("Python modules loaded from ")
|
assert lines[1].startswith("Python executable is")
|
||||||
assert lines[2].startswith("Binary modules loaded from ")
|
lines = lines[2:]
|
||||||
assert lines[3] == "-" * 68
|
if lines[0].startswith("Environment Python files loaded from"):
|
||||||
|
lines = lines[1:]
|
||||||
|
assert lines[0].startswith("System Python files loaded from")
|
||||||
|
assert lines[1] == "-" * 68
|
||||||
|
assert lines[2].startswith("Python Pillow modules loaded from ")
|
||||||
|
assert lines[3].startswith("Binary Pillow modules loaded from ")
|
||||||
|
assert lines[4] == "-" * 68
|
||||||
jpeg = (
|
jpeg = (
|
||||||
os.linesep
|
os.linesep
|
||||||
+ "-" * 68
|
+ "-" * 68
|
||||||
|
@ -31,4 +44,4 @@ def test_main() -> None:
|
||||||
+ "-" * 68
|
+ "-" * 68
|
||||||
+ os.linesep
|
+ os.linesep
|
||||||
)
|
)
|
||||||
assert jpeg in out
|
assert report == (jpeg not in out)
|
||||||
|
|
|
@ -11,41 +11,12 @@ backend_class = build_wheel.__self__.__class__
|
||||||
class _CustomBuildMetaBackend(backend_class):
|
class _CustomBuildMetaBackend(backend_class):
|
||||||
def run_setup(self, setup_script="setup.py"):
|
def run_setup(self, setup_script="setup.py"):
|
||||||
if self.config_settings:
|
if self.config_settings:
|
||||||
|
for key, values in self.config_settings.items():
|
||||||
|
if not isinstance(values, list):
|
||||||
|
values = [values]
|
||||||
|
for value in values:
|
||||||
|
sys.argv.append(f"--pillow-configuration={key}={value}")
|
||||||
|
|
||||||
def config_has(key, value):
|
|
||||||
settings = self.config_settings.get(key)
|
|
||||||
if settings:
|
|
||||||
if not isinstance(settings, list):
|
|
||||||
settings = [settings]
|
|
||||||
return value in settings
|
|
||||||
|
|
||||||
flags = []
|
|
||||||
for dependency in (
|
|
||||||
"zlib",
|
|
||||||
"jpeg",
|
|
||||||
"tiff",
|
|
||||||
"freetype",
|
|
||||||
"raqm",
|
|
||||||
"lcms",
|
|
||||||
"webp",
|
|
||||||
"webpmux",
|
|
||||||
"jpeg2000",
|
|
||||||
"imagequant",
|
|
||||||
"xcb",
|
|
||||||
):
|
|
||||||
if config_has(dependency, "enable"):
|
|
||||||
flags.append("--enable-" + dependency)
|
|
||||||
elif config_has(dependency, "disable"):
|
|
||||||
flags.append("--disable-" + dependency)
|
|
||||||
for dependency in ("raqm", "fribidi"):
|
|
||||||
if config_has(dependency, "vendor"):
|
|
||||||
flags.append("--vendor-" + dependency)
|
|
||||||
if self.config_settings.get("platform-guessing") == "disable":
|
|
||||||
flags.append("--disable-platform-guessing")
|
|
||||||
if self.config_settings.get("debug") == "true":
|
|
||||||
flags.append("--debug")
|
|
||||||
if flags:
|
|
||||||
sys.argv = sys.argv[:1] + ["build_ext"] + flags + sys.argv[1:]
|
|
||||||
return super().run_setup(setup_script)
|
return super().run_setup(setup_script)
|
||||||
|
|
||||||
def build_wheel(
|
def build_wheel(
|
||||||
|
@ -54,5 +25,15 @@ class _CustomBuildMetaBackend(backend_class):
|
||||||
self.config_settings = config_settings
|
self.config_settings = config_settings
|
||||||
return super().build_wheel(wheel_directory, config_settings, metadata_directory)
|
return super().build_wheel(wheel_directory, config_settings, metadata_directory)
|
||||||
|
|
||||||
|
def build_editable(
|
||||||
|
self, wheel_directory, config_settings=None, metadata_directory=None
|
||||||
|
):
|
||||||
|
self.config_settings = config_settings
|
||||||
|
return super().build_editable(
|
||||||
|
wheel_directory, config_settings, metadata_directory
|
||||||
|
)
|
||||||
|
|
||||||
build_wheel = _CustomBuildMetaBackend().build_wheel
|
|
||||||
|
_backend = _CustomBuildMetaBackend()
|
||||||
|
build_wheel = _backend.build_wheel
|
||||||
|
build_editable = _backend.build_editable
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
The Python Imaging Library (PIL) is
|
The Python Imaging Library (PIL) is
|
||||||
|
|
||||||
Copyright © 1997-2011 by Secret Labs AB
|
Copyright © 1997-2011 by Secret Labs AB
|
||||||
Copyright © 1995-2011 by Fredrik Lundh
|
Copyright © 1995-2011 by Fredrik Lundh and contributors
|
||||||
|
|
||||||
Pillow is the friendly PIL fork. It is
|
Pillow is the friendly PIL fork. It is
|
||||||
|
|
||||||
Copyright © 2010-2024 by Jeffrey A. Clark (Alex) and contributors
|
Copyright © 2010-2024 by Jeffrey A. Clark and contributors
|
||||||
|
|
||||||
Like PIL, Pillow is licensed under the open source PIL
|
Like PIL, Pillow is licensed under the open source PIL
|
||||||
Software License:
|
Software License:
|
||||||
|
|
16
docs/conf.py
16
docs/conf.py
|
@ -54,9 +54,10 @@ master_doc = "index"
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = "Pillow (PIL Fork)"
|
project = "Pillow (PIL Fork)"
|
||||||
copyright = (
|
copyright = (
|
||||||
"1995-2011 Fredrik Lundh, 2010-2024 Jeffrey A. Clark (Alex) and contributors"
|
"1995-2011 Fredrik Lundh and contributors, "
|
||||||
|
"2010-2024 Jeffrey A. Clark and contributors."
|
||||||
)
|
)
|
||||||
author = "Fredrik Lundh, Jeffrey A. Clark (Alex), contributors"
|
author = "Fredrik Lundh (PIL), Jeffrey A. Clark (Pillow)"
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
@ -120,7 +121,12 @@ nitpicky = True
|
||||||
# generating warnings in “nitpicky mode”. Note that type should include the domain name
|
# generating warnings in “nitpicky mode”. Note that type should include the domain name
|
||||||
# if present. Example entries would be ('py:func', 'int') or
|
# if present. Example entries would be ('py:func', 'int') or
|
||||||
# ('envvar', 'LD_LIBRARY_PATH').
|
# ('envvar', 'LD_LIBRARY_PATH').
|
||||||
# nitpick_ignore = []
|
nitpick_ignore = [
|
||||||
|
# Sphinx does not understand typing.Literal[-1]
|
||||||
|
# Will be fixed in a future version.
|
||||||
|
# https://github.com/sphinx-doc/sphinx/pull/11904
|
||||||
|
("py:obj", "typing.Literal[-1, 1]"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
# -- Options for HTML output ----------------------------------------------
|
||||||
|
@ -252,7 +258,7 @@ latex_documents = [
|
||||||
master_doc,
|
master_doc,
|
||||||
"PillowPILFork.tex",
|
"PillowPILFork.tex",
|
||||||
"Pillow (PIL Fork) Documentation",
|
"Pillow (PIL Fork) Documentation",
|
||||||
"Jeffrey A. Clark (Alex)",
|
"Jeffrey A. Clark",
|
||||||
"manual",
|
"manual",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
@ -302,7 +308,7 @@ texinfo_documents = [
|
||||||
"Pillow (PIL Fork) Documentation",
|
"Pillow (PIL Fork) Documentation",
|
||||||
author,
|
author,
|
||||||
"PillowPILFork",
|
"PillowPILFork",
|
||||||
"Pillow is the friendly PIL fork by Jeffrey A. Clark (Alex) and contributors.",
|
"Pillow is the friendly PIL fork by Jeffrey A. Clark and contributors.",
|
||||||
"Miscellaneous",
|
"Miscellaneous",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -92,6 +92,14 @@ Deprecated Use instead
|
||||||
:py:data:`sys.version_info`, and ``PIL.__version__``
|
:py:data:`sys.version_info`, and ``PIL.__version__``
|
||||||
============================================ ====================================================
|
============================================ ====================================================
|
||||||
|
|
||||||
|
ImageMath eval()
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 10.3.0
|
||||||
|
|
||||||
|
``ImageMath.eval()`` has been deprecated. Use :py:meth:`~PIL.ImageMath.lambda_eval` or
|
||||||
|
:py:meth:`~PIL.ImageMath.unsafe_eval` instead.
|
||||||
|
|
||||||
Removed features
|
Removed features
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -1234,11 +1234,15 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
If present and true, instructs the WebP writer to use lossless compression.
|
If present and true, instructs the WebP writer to use lossless compression.
|
||||||
|
|
||||||
**quality**
|
**quality**
|
||||||
Integer, 0-100, Defaults to 80. For lossy, 0 gives the smallest
|
Integer, 0-100, defaults to 80. For lossy, 0 gives the smallest
|
||||||
size and 100 the largest. For lossless, this parameter is the amount
|
size and 100 the largest. For lossless, this parameter is the amount
|
||||||
of effort put into the compression: 0 is the fastest, but gives larger
|
of effort put into the compression: 0 is the fastest, but gives larger
|
||||||
files compared to the slowest, but best, 100.
|
files compared to the slowest, but best, 100.
|
||||||
|
|
||||||
|
**alpha_quality**
|
||||||
|
Integer, 0-100, defaults to 100. For lossy compression only. 0 gives the
|
||||||
|
smallest size and 100 is lossless.
|
||||||
|
|
||||||
**method**
|
**method**
|
||||||
Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 4.
|
Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 4.
|
||||||
|
|
||||||
|
@ -1335,7 +1339,8 @@ FITS
|
||||||
|
|
||||||
.. versionadded:: 9.1.0
|
.. versionadded:: 9.1.0
|
||||||
|
|
||||||
Pillow identifies and reads FITS files, commonly used for astronomy.
|
Pillow identifies and reads FITS files, commonly used for astronomy. Uncompressed and
|
||||||
|
GZIP_1 compressed images can be read.
|
||||||
|
|
||||||
FLI, FLC
|
FLI, FLC
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
@ -1351,9 +1356,8 @@ The :py:meth:`~PIL.Image.open` method sets the following
|
||||||
FPX
|
FPX
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
Pillow reads Kodak FlashPix files. In the current version, only the highest
|
Pillow reads Kodak FlashPix files. Only the highest resolution image is read from the
|
||||||
resolution image is read from the file, and the viewing transform is not taken
|
file, and the viewing transform is not taken into account.
|
||||||
into account.
|
|
||||||
|
|
||||||
To enable FPX support, you must install :pypi:`olefile`.
|
To enable FPX support, you must install :pypi:`olefile`.
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Pillow
|
Pillow
|
||||||
======
|
======
|
||||||
|
|
||||||
Pillow is the friendly PIL fork by `Jeffrey A. Clark (Alex) and contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and contributors.
|
Pillow is the friendly PIL fork by `Jeffrey A. Clark and contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and contributors.
|
||||||
|
|
||||||
Pillow for enterprise is available via the Tidelift Subscription. `Learn more <https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=docs&utm_campaign=enterprise>`_.
|
Pillow for enterprise is available via the Tidelift Subscription. `Learn more <https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=docs&utm_campaign=enterprise>`_.
|
||||||
|
|
||||||
|
|
|
@ -266,9 +266,10 @@ After navigating to the Pillow directory, run::
|
||||||
Build Options
|
Build Options
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
* Environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
|
* Config setting: ``-C parallel=n``. Can also be given
|
||||||
multiprocessing to build the extension. Setting ``MAX_CONCURRENCY``
|
with environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
|
||||||
sets the number of CPUs to use, or can disable parallel building by
|
multiprocessing to build the extension. Setting ``-C parallel=n``
|
||||||
|
sets the number of CPUs to use to ``n``, or can disable parallel building by
|
||||||
using a setting of 1. By default, it uses 4 CPUs, or if 4 are not
|
using a setting of 1. By default, it uses 4 CPUs, or if 4 are not
|
||||||
available, as many as are present.
|
available, as many as are present.
|
||||||
|
|
||||||
|
@ -293,14 +294,13 @@ Build Options
|
||||||
used to compile the standard Pillow wheels. Compiling libraqm requires
|
used to compile the standard Pillow wheels. Compiling libraqm requires
|
||||||
a C99-compliant compiler.
|
a C99-compliant compiler.
|
||||||
|
|
||||||
* Build flag: ``-C platform-guessing=disable``. Skips all of the
|
* Config setting: ``-C platform-guessing=disable``. Skips all of the
|
||||||
platform dependent guessing of include and library directories for
|
platform dependent guessing of include and library directories for
|
||||||
automated build systems that configure the proper paths in the
|
automated build systems that configure the proper paths in the
|
||||||
environment variables (e.g. Buildroot).
|
environment variables (e.g. Buildroot).
|
||||||
|
|
||||||
* Build flag: ``-C debug=true``. Adds a debugging flag to the include and
|
* Config setting: ``-C debug=true``. Adds a debugging flag to the include and
|
||||||
library search process to dump all paths searched for and found to
|
library search process to dump all paths searched for and found to stdout.
|
||||||
stdout.
|
|
||||||
|
|
||||||
|
|
||||||
Sample usage::
|
Sample usage::
|
||||||
|
|
|
@ -73,7 +73,7 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
:canonical: PIL._imagingcms.CmsProfile
|
:canonical: PIL._imagingcms.CmsProfile
|
||||||
|
|
||||||
.. py:attribute:: creation_date
|
.. py:attribute:: creation_date
|
||||||
:type: Optional[datetime.datetime]
|
:type: datetime.datetime | None
|
||||||
|
|
||||||
Date and time this profile was first created (see 7.2.1 of ICC.1:2010).
|
Date and time this profile was first created (see 7.2.1 of ICC.1:2010).
|
||||||
|
|
||||||
|
@ -156,58 +156,58 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
not been calculated (see 7.2.18 of ICC.1:2010).
|
not been calculated (see 7.2.18 of ICC.1:2010).
|
||||||
|
|
||||||
.. py:attribute:: copyright
|
.. py:attribute:: copyright
|
||||||
:type: Optional[str]
|
:type: str | None
|
||||||
|
|
||||||
The text copyright information for the profile (see 9.2.21 of ICC.1:2010).
|
The text copyright information for the profile (see 9.2.21 of ICC.1:2010).
|
||||||
|
|
||||||
.. py:attribute:: manufacturer
|
.. py:attribute:: manufacturer
|
||||||
:type: Optional[str]
|
:type: str | None
|
||||||
|
|
||||||
The (English) display string for the device manufacturer (see
|
The (English) display string for the device manufacturer (see
|
||||||
9.2.22 of ICC.1:2010).
|
9.2.22 of ICC.1:2010).
|
||||||
|
|
||||||
.. py:attribute:: model
|
.. py:attribute:: model
|
||||||
:type: Optional[str]
|
:type: str | None
|
||||||
|
|
||||||
The (English) display string for the device model of the device
|
The (English) display string for the device model of the device
|
||||||
for which this profile is created (see 9.2.23 of ICC.1:2010).
|
for which this profile is created (see 9.2.23 of ICC.1:2010).
|
||||||
|
|
||||||
.. py:attribute:: profile_description
|
.. py:attribute:: profile_description
|
||||||
:type: Optional[str]
|
:type: str | None
|
||||||
|
|
||||||
The (English) display string for the profile description (see
|
The (English) display string for the profile description (see
|
||||||
9.2.41 of ICC.1:2010).
|
9.2.41 of ICC.1:2010).
|
||||||
|
|
||||||
.. py:attribute:: target
|
.. py:attribute:: target
|
||||||
:type: Optional[str]
|
:type: str | None
|
||||||
|
|
||||||
The name of the registered characterization data set, or the
|
The name of the registered characterization data set, or the
|
||||||
measurement data for a characterization target (see 9.2.14 of
|
measurement data for a characterization target (see 9.2.14 of
|
||||||
ICC.1:2010).
|
ICC.1:2010).
|
||||||
|
|
||||||
.. py:attribute:: red_colorant
|
.. py:attribute:: red_colorant
|
||||||
:type: Optional[tuple[tuple[float]]]
|
:type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
|
||||||
|
|
||||||
The first column in the matrix used in matrix/TRC transforms (see 9.2.44 of ICC.1:2010).
|
The first column in the matrix used in matrix/TRC transforms (see 9.2.44 of ICC.1:2010).
|
||||||
|
|
||||||
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
||||||
|
|
||||||
.. py:attribute:: green_colorant
|
.. py:attribute:: green_colorant
|
||||||
:type: Optional[tuple[tuple[float]]]
|
:type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
|
||||||
|
|
||||||
The second column in the matrix used in matrix/TRC transforms (see 9.2.30 of ICC.1:2010).
|
The second column in the matrix used in matrix/TRC transforms (see 9.2.30 of ICC.1:2010).
|
||||||
|
|
||||||
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
||||||
|
|
||||||
.. py:attribute:: blue_colorant
|
.. py:attribute:: blue_colorant
|
||||||
:type: Optional[tuple[tuple[float]]]
|
:type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
|
||||||
|
|
||||||
The third column in the matrix used in matrix/TRC transforms (see 9.2.4 of ICC.1:2010).
|
The third column in the matrix used in matrix/TRC transforms (see 9.2.4 of ICC.1:2010).
|
||||||
|
|
||||||
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
||||||
|
|
||||||
.. py:attribute:: luminance
|
.. py:attribute:: luminance
|
||||||
:type: Optional[tuple[tuple[float]]]
|
:type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
|
||||||
|
|
||||||
The absolute luminance of emissive devices in candelas per square
|
The absolute luminance of emissive devices in candelas per square
|
||||||
metre as described by the Y channel (see 9.2.32 of ICC.1:2010).
|
metre as described by the Y channel (see 9.2.32 of ICC.1:2010).
|
||||||
|
@ -215,7 +215,7 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
||||||
|
|
||||||
.. py:attribute:: chromaticity
|
.. py:attribute:: chromaticity
|
||||||
:type: Optional[tuple[tuple[float]]]
|
:type: tuple[tuple[float, float, float], tuple[float, float, float], tuple[float, float, float]] | None
|
||||||
|
|
||||||
The data of the phosphor/colorant chromaticity set used (red,
|
The data of the phosphor/colorant chromaticity set used (red,
|
||||||
green and blue channels, see 9.2.16 of ICC.1:2010).
|
green and blue channels, see 9.2.16 of ICC.1:2010).
|
||||||
|
@ -223,7 +223,7 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
The value is in the format ``((x, y, Y), (x, y, Y), (x, y, Y))``, if available.
|
The value is in the format ``((x, y, Y), (x, y, Y), (x, y, Y))``, if available.
|
||||||
|
|
||||||
.. py:attribute:: chromatic_adaption
|
.. py:attribute:: chromatic_adaption
|
||||||
:type: tuple[tuple[float]]
|
:type: tuple[tuple[tuple[float, float, float], tuple[float, float, float], tuple[float, float, float]], tuple[tuple[float, float, float], tuple[float, float, float], tuple[float, float, float]]] | None
|
||||||
|
|
||||||
The chromatic adaption matrix converts a color measured using the
|
The chromatic adaption matrix converts a color measured using the
|
||||||
actual illumination conditions and relative to the actual adopted
|
actual illumination conditions and relative to the actual adopted
|
||||||
|
@ -249,34 +249,34 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
9.2.19 of ICC.1:2010).
|
9.2.19 of ICC.1:2010).
|
||||||
|
|
||||||
.. py:attribute:: colorimetric_intent
|
.. py:attribute:: colorimetric_intent
|
||||||
:type: Optional[str]
|
:type: str | None
|
||||||
|
|
||||||
4-character string (padded with whitespace) identifying the image
|
4-character string (padded with whitespace) identifying the image
|
||||||
state of PCS colorimetry produced using the colorimetric intent
|
state of PCS colorimetry produced using the colorimetric intent
|
||||||
transforms (see 9.2.20 of ICC.1:2010 for details).
|
transforms (see 9.2.20 of ICC.1:2010 for details).
|
||||||
|
|
||||||
.. py:attribute:: perceptual_rendering_intent_gamut
|
.. py:attribute:: perceptual_rendering_intent_gamut
|
||||||
:type: Optional[str]
|
:type: str | None
|
||||||
|
|
||||||
4-character string (padded with whitespace) identifying the (one)
|
4-character string (padded with whitespace) identifying the (one)
|
||||||
standard reference medium gamut (see 9.2.37 of ICC.1:2010 for
|
standard reference medium gamut (see 9.2.37 of ICC.1:2010 for
|
||||||
details).
|
details).
|
||||||
|
|
||||||
.. py:attribute:: saturation_rendering_intent_gamut
|
.. py:attribute:: saturation_rendering_intent_gamut
|
||||||
:type: Optional[str]
|
:type: str | None
|
||||||
|
|
||||||
4-character string (padded with whitespace) identifying the (one)
|
4-character string (padded with whitespace) identifying the (one)
|
||||||
standard reference medium gamut (see 9.2.37 of ICC.1:2010 for
|
standard reference medium gamut (see 9.2.37 of ICC.1:2010 for
|
||||||
details).
|
details).
|
||||||
|
|
||||||
.. py:attribute:: technology
|
.. py:attribute:: technology
|
||||||
:type: Optional[str]
|
:type: str | None
|
||||||
|
|
||||||
4-character string (padded with whitespace) identifying the device
|
4-character string (padded with whitespace) identifying the device
|
||||||
technology (see 9.2.47 of ICC.1:2010 for details).
|
technology (see 9.2.47 of ICC.1:2010 for details).
|
||||||
|
|
||||||
.. py:attribute:: media_black_point
|
.. py:attribute:: media_black_point
|
||||||
:type: Optional[tuple[tuple[float]]]
|
:type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
|
||||||
|
|
||||||
This tag specifies the media black point and is used for
|
This tag specifies the media black point and is used for
|
||||||
generating absolute colorimetry.
|
generating absolute colorimetry.
|
||||||
|
@ -287,19 +287,19 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
||||||
|
|
||||||
.. py:attribute:: media_white_point_temperature
|
.. py:attribute:: media_white_point_temperature
|
||||||
:type: Optional[float]
|
:type: float | None
|
||||||
|
|
||||||
Calculates the white point temperature (see the LCMS documentation
|
Calculates the white point temperature (see the LCMS documentation
|
||||||
for more information).
|
for more information).
|
||||||
|
|
||||||
.. py:attribute:: viewing_condition
|
.. py:attribute:: viewing_condition
|
||||||
:type: Optional[str]
|
:type: str | None
|
||||||
|
|
||||||
The (English) display string for the viewing conditions (see
|
The (English) display string for the viewing conditions (see
|
||||||
9.2.48 of ICC.1:2010).
|
9.2.48 of ICC.1:2010).
|
||||||
|
|
||||||
.. py:attribute:: screening_description
|
.. py:attribute:: screening_description
|
||||||
:type: Optional[str]
|
:type: str | None
|
||||||
|
|
||||||
The (English) display string for the screening conditions.
|
The (English) display string for the screening conditions.
|
||||||
|
|
||||||
|
@ -307,21 +307,21 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
version 4.
|
version 4.
|
||||||
|
|
||||||
.. py:attribute:: red_primary
|
.. py:attribute:: red_primary
|
||||||
:type: Optional[tuple[tuple[float]]]
|
:type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
|
||||||
|
|
||||||
The XYZ-transformed of the RGB primary color red (1, 0, 0).
|
The XYZ-transformed of the RGB primary color red (1, 0, 0).
|
||||||
|
|
||||||
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
||||||
|
|
||||||
.. py:attribute:: green_primary
|
.. py:attribute:: green_primary
|
||||||
:type: Optional[tuple[tuple[float]]]
|
:type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
|
||||||
|
|
||||||
The XYZ-transformed of the RGB primary color green (0, 1, 0).
|
The XYZ-transformed of the RGB primary color green (0, 1, 0).
|
||||||
|
|
||||||
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
The value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
|
||||||
|
|
||||||
.. py:attribute:: blue_primary
|
.. py:attribute:: blue_primary
|
||||||
:type: Optional[tuple[tuple[float]]]
|
:type: tuple[tuple[float, float, float], tuple[float, float, float]] | None
|
||||||
|
|
||||||
The XYZ-transformed of the RGB primary color blue (0, 0, 1).
|
The XYZ-transformed of the RGB primary color blue (0, 0, 1).
|
||||||
|
|
||||||
|
@ -334,7 +334,7 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
documentation on LCMS).
|
documentation on LCMS).
|
||||||
|
|
||||||
.. py:attribute:: clut
|
.. py:attribute:: clut
|
||||||
:type: dict[tuple[bool]]
|
:type: dict[int, tuple[bool, bool, bool]] | None
|
||||||
|
|
||||||
Returns a dictionary of all supported intents and directions for
|
Returns a dictionary of all supported intents and directions for
|
||||||
the CLUT model.
|
the CLUT model.
|
||||||
|
@ -353,7 +353,7 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
that intent is supported for that direction.
|
that intent is supported for that direction.
|
||||||
|
|
||||||
.. py:attribute:: intent_supported
|
.. py:attribute:: intent_supported
|
||||||
:type: dict[tuple[bool]]
|
:type: dict[int, tuple[bool, bool, bool]] | None
|
||||||
|
|
||||||
Returns a dictionary of all supported intents and directions.
|
Returns a dictionary of all supported intents and directions.
|
||||||
|
|
||||||
|
@ -372,7 +372,7 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
|
|
||||||
There is one function defined on the class:
|
There is one function defined on the class:
|
||||||
|
|
||||||
.. py:method:: is_intent_supported(intent, direction)
|
.. py:method:: is_intent_supported(intent: int, direction: int, /)
|
||||||
|
|
||||||
Returns if the intent is supported for the given direction.
|
Returns if the intent is supported for the given direction.
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,7 @@ Example: Filter an image
|
||||||
Filters
|
Filters
|
||||||
-------
|
-------
|
||||||
|
|
||||||
The current version of the library provides the following set of predefined
|
Pillow provides the following set of predefined image enhancement filters:
|
||||||
image enhancement filters:
|
|
||||||
|
|
||||||
* **BLUR**
|
* **BLUR**
|
||||||
* **CONTOUR**
|
* **CONTOUR**
|
||||||
|
|
|
@ -4,9 +4,12 @@
|
||||||
:py:mod:`~PIL.ImageMath` Module
|
:py:mod:`~PIL.ImageMath` Module
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
The :py:mod:`~PIL.ImageMath` module can be used to evaluate “image expressions”. The
|
The :py:mod:`~PIL.ImageMath` module can be used to evaluate “image expressions”, that
|
||||||
module provides a single :py:meth:`~PIL.ImageMath.eval` function, which takes
|
can take a number of images and generate a result.
|
||||||
an expression string and one or more images.
|
|
||||||
|
:py:mod:`~PIL.ImageMath` only supports single-layer images. To process multi-band
|
||||||
|
images, use the :py:meth:`~PIL.Image.Image.split` method or :py:func:`~PIL.Image.merge`
|
||||||
|
function.
|
||||||
|
|
||||||
Example: Using the :py:mod:`~PIL.ImageMath` module
|
Example: Using the :py:mod:`~PIL.ImageMath` module
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
@ -17,35 +20,69 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
|
||||||
|
|
||||||
with Image.open("image1.jpg") as im1:
|
with Image.open("image1.jpg") as im1:
|
||||||
with Image.open("image2.jpg") as im2:
|
with Image.open("image2.jpg") as im2:
|
||||||
|
out = ImageMath.lambda_eval(
|
||||||
|
lambda args: args["convert"](args["min"](args["a"], args["b"]), 'L'),
|
||||||
|
a=im1,
|
||||||
|
b=im2
|
||||||
|
)
|
||||||
|
out = ImageMath.unsafe_eval(
|
||||||
|
"convert(min(a, b), 'L')",
|
||||||
|
a=im1,
|
||||||
|
b=im2
|
||||||
|
)
|
||||||
|
|
||||||
out = ImageMath.eval("convert(min(a, b), 'L')", a=im1, b=im2)
|
.. py:function:: lambda_eval(expression, environment)
|
||||||
out.save("result.png")
|
|
||||||
|
|
||||||
.. py:function:: eval(expression, environment)
|
Returns the result of an image function.
|
||||||
|
|
||||||
Evaluate expression in the given environment.
|
:param expression: A function that receives a dictionary.
|
||||||
|
:param options: Values to add to the function's dictionary, mapping image
|
||||||
|
names to Image instances. You can use one or more keyword
|
||||||
|
arguments instead of a dictionary, as shown in the above
|
||||||
|
example. Note that the names must be valid Python
|
||||||
|
identifiers.
|
||||||
|
:return: An image, an integer value, a floating point value,
|
||||||
|
or a pixel tuple, depending on the expression.
|
||||||
|
|
||||||
In the current version, :py:mod:`~PIL.ImageMath` only supports
|
.. py:function:: unsafe_eval(expression, environment)
|
||||||
single-layer images. To process multi-band images, use the
|
|
||||||
:py:meth:`~PIL.Image.Image.split` method or :py:func:`~PIL.Image.merge`
|
Evaluates an image expression.
|
||||||
function.
|
|
||||||
|
.. danger::
|
||||||
|
This uses Python's ``eval()`` function to process the expression string,
|
||||||
|
and carries the security risks of doing so. It is not
|
||||||
|
recommended to process expressions without considering this.
|
||||||
|
:py:meth:`lambda_eval` is a more secure alternative.
|
||||||
|
|
||||||
|
:py:mod:`~PIL.ImageMath` only supports single-layer images. To process multi-band
|
||||||
|
images, use the :py:meth:`~PIL.Image.Image.split` method or
|
||||||
|
:py:func:`~PIL.Image.merge` function.
|
||||||
|
|
||||||
:param expression: A string which uses the standard Python expression
|
:param expression: A string which uses the standard Python expression
|
||||||
syntax. In addition to the standard operators, you can
|
syntax. In addition to the standard operators, you can
|
||||||
also use the functions described below.
|
also use the functions described below.
|
||||||
:param environment: A dictionary that maps image names to Image instances.
|
:param options: Values to add to the function's dictionary, mapping image
|
||||||
You can use one or more keyword arguments instead of a
|
names to Image instances. You can use one or more keyword
|
||||||
dictionary, as shown in the above example. Note that
|
arguments instead of a dictionary, as shown in the above
|
||||||
the names must be valid Python identifiers.
|
example. Note that the names must be valid Python
|
||||||
|
identifiers.
|
||||||
:return: An image, an integer value, a floating point value,
|
:return: An image, an integer value, a floating point value,
|
||||||
or a pixel tuple, depending on the expression.
|
or a pixel tuple, depending on the expression.
|
||||||
|
|
||||||
Expression syntax
|
Expression syntax
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Expressions are standard Python expressions, but they’re evaluated in a
|
* :py:meth:`lambda_eval` expressions are functions that receive a dictionary
|
||||||
non-standard environment. You can use PIL methods as usual, plus the following
|
containing images and operators.
|
||||||
set of operators and functions:
|
|
||||||
|
* :py:meth:`unsafe_eval` expressions are standard Python expressions,
|
||||||
|
but they’re evaluated in a non-standard environment.
|
||||||
|
|
||||||
|
.. danger::
|
||||||
|
:py:meth:`unsafe_eval` uses Python's ``eval()`` function to process the
|
||||||
|
expression string, and carries the security risks of doing so.
|
||||||
|
It is not recommended to process expressions without considering this.
|
||||||
|
:py:meth:`lambda_eval` is a more secure alternative.
|
||||||
|
|
||||||
Standard Operators
|
Standard Operators
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -21,8 +21,8 @@ vector data. Path objects can be passed to the methods on the
|
||||||
|
|
||||||
The path object implements most parts of the Python sequence interface, and
|
The path object implements most parts of the Python sequence interface, and
|
||||||
behaves like a list of (x, y) pairs. You can use len(), item access, and
|
behaves like a list of (x, y) pairs. You can use len(), item access, and
|
||||||
slicing as usual. However, the current version does not support slice
|
slicing as usual. However, this does not support slice assignment, or item
|
||||||
assignment, or item and slice deletion.
|
and slice deletion.
|
||||||
|
|
||||||
:param xy: A sequence. The sequence can contain 2-tuples [(x, y), ...]
|
:param xy: A sequence. The sequence can contain 2-tuples [(x, y), ...]
|
||||||
or a flat list of numbers [x, y, ...].
|
or a flat list of numbers [x, y, ...].
|
||||||
|
|
|
@ -1,6 +1,33 @@
|
||||||
10.0.0
|
10.0.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
Limit size even if one dimension is zero
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When performing decompression bomb checks, Pillow did not reject images with
|
||||||
|
excessive width and zero height, or zero width and excessive height. That has
|
||||||
|
now been fixed.
|
||||||
|
|
||||||
|
This effectively dates to the PIL fork, since problem images would still have
|
||||||
|
been processed before Pillow started checking for decompression bombs.
|
||||||
|
|
||||||
|
.. _Added ImageFont.MAX_STRING_LENGTH:
|
||||||
|
|
||||||
|
:cve:`2023-44271`: Added ImageFont.MAX_STRING_LENGTH
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
This threshold can be changed by setting
|
||||||
|
:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It can be disabled by setting
|
||||||
|
``ImageFont.MAX_STRING_LENGTH = None``.
|
||||||
|
|
||||||
Backwards Incompatible Changes
|
Backwards Incompatible Changes
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
|
@ -157,31 +184,6 @@ Added ``alpha_only`` argument to ``getbbox()``
|
||||||
and the image has an alpha channel, trim transparent pixels. Otherwise, trim
|
and the image has an alpha channel, trim transparent pixels. Otherwise, trim
|
||||||
pixels when all channels are zero.
|
pixels when all channels are zero.
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
Limit size even if one dimension is zero
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
When performing decompression bomb checks, Pillow did not reject images with
|
|
||||||
excessive width and zero height, or zero width and excessive height. That has
|
|
||||||
now been fixed.
|
|
||||||
|
|
||||||
This effectively dates to the PIL fork, since problem images would still have
|
|
||||||
been processed before Pillow started checking for decompression bombs.
|
|
||||||
|
|
||||||
Added ImageFont.MAX_STRING_LENGTH
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
: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`.
|
|
||||||
|
|
||||||
This threshold can be changed by setting
|
|
||||||
:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It can be disabled by setting
|
|
||||||
``ImageFont.MAX_STRING_LENGTH = None``.
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,17 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
This release addresses :cve:`2023-4863`, by providing an updated install script and
|
:cve:`2023-4863`: Updated install script and updated wheels
|
||||||
updated wheels to include libwebp 1.3.2, preventing a potential heap buffer overflow
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
in WebP.
|
|
||||||
|
This release provides an updated install script and updated wheels to
|
||||||
|
include libwebp 1.3.2, preventing a potential heap buffer overflow in
|
||||||
|
WebP.
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
Updated tests to pass with latest zlib version
|
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.
|
The release of zlib 1.3 caused one of the tests in the Pillow test suite to fail.
|
||||||
|
|
|
@ -1,6 +1,38 @@
|
||||||
10.2.0
|
10.2.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
ImageFont.getmask: Applied ImageFont.MAX_STRING_LENGTH
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
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
|
||||||
|
:py:meth:`PIL.ImageFont.ImageFont.getmask` is over a certain limit,
|
||||||
|
:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`.
|
||||||
|
|
||||||
|
This threshold can be changed by setting :py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It
|
||||||
|
can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
||||||
|
|
||||||
|
A decompression bomb check has also been added to
|
||||||
|
:py:meth:`PIL.ImageFont.ImageFont.getmask`.
|
||||||
|
|
||||||
|
ImageFont.getmask: Trim glyph size
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
To protect against potential DOS attacks when using PIL fonts,
|
||||||
|
:py:class:`PIL.ImageFont.ImageFont` now trims the size of individual glyphs so that
|
||||||
|
they do not extend beyond the bitmap image.
|
||||||
|
|
||||||
|
:cve:`2023-50447`: ImageMath.eval: Restricted environment keys
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If an attacker has control over the keys passed to the
|
||||||
|
``environment`` argument of :py:meth:`!PIL.ImageMath.eval`, they may be able to execute
|
||||||
|
arbitrary code. To prevent this, keys matching the names of builtins and keys
|
||||||
|
containing double underscores will now raise a :py:exc:`ValueError`.
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
@ -63,38 +95,6 @@ JPEG tables-only streamtype
|
||||||
When saving JPEG files, ``streamtype`` can now be set to 1, for tables-only. This will
|
When saving JPEG files, ``streamtype`` can now be set to 1, for tables-only. This will
|
||||||
output only the quantization and Huffman tables for the image.
|
output only the quantization and Huffman tables for the image.
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
ImageFont.getmask: Applied ImageFont.MAX_STRING_LENGTH
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
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
|
|
||||||
:py:meth:`PIL.ImageFont.ImageFont.getmask` is over a certain limit,
|
|
||||||
:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`.
|
|
||||||
|
|
||||||
This threshold can be changed by setting :py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It
|
|
||||||
can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
|
||||||
|
|
||||||
A decompression bomb check has also been added to
|
|
||||||
:py:meth:`PIL.ImageFont.ImageFont.getmask`.
|
|
||||||
|
|
||||||
ImageFont.getmask: Trim glyph size
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
To protect against potential DOS attacks when using PIL fonts,
|
|
||||||
:py:class:`PIL.ImageFont.ImageFont` now trims the size of individual glyphs so that
|
|
||||||
they do not extend beyond the bitmap image.
|
|
||||||
|
|
||||||
ImageMath.eval: Restricted environment keys
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
:cve:`2023-50447`: If an attacker has control over the keys passed to the
|
|
||||||
``environment`` argument of :py:meth:`PIL.ImageMath.eval`, they may be able to execute
|
|
||||||
arbitrary code. To prevent this, keys matching the names of builtins and keys
|
|
||||||
containing double underscores will now raise a :py:exc:`ValueError`.
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,24 @@
|
||||||
10.3.0
|
10.3.0
|
||||||
------
|
------
|
||||||
|
|
||||||
Backwards Incompatible Changes
|
Security
|
||||||
==============================
|
========
|
||||||
|
|
||||||
TODO
|
ImageMath eval()
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. danger::
|
||||||
|
``ImageMath.eval()`` uses Python's ``eval()`` function to process the expression
|
||||||
|
string, and carries the security risks of doing so. A direct replacement for this is
|
||||||
|
the new :py:meth:`~PIL.ImageMath.unsafe_eval`, but that carries the same risks. It is
|
||||||
|
not recommended to process expressions without considering this.
|
||||||
|
:py:meth:`~PIL.ImageMath.lambda_eval` is a more secure alternative.
|
||||||
|
|
||||||
|
:cve:`2024-28219`: Fix buffer overflow in ``_imagingcms.c``
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In ``_imagingcms.c``, two ``strcpy`` calls were able to copy too much data into fixed
|
||||||
|
length strings. This has been fixed by using ``strncpy`` instead.
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
@ -45,13 +58,34 @@ Deprecated Use instead
|
||||||
:py:data:`sys.version_info`, and ``PIL.__version__``
|
:py:data:`sys.version_info`, and ``PIL.__version__``
|
||||||
============================================ ====================================================
|
============================================ ====================================================
|
||||||
|
|
||||||
|
ImageMath.eval()
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``ImageMath.eval()`` has been deprecated. Use :py:meth:`~PIL.ImageMath.lambda_eval` or
|
||||||
|
:py:meth:`~PIL.ImageMath.unsafe_eval` instead. See earlier security notes for more
|
||||||
|
information.
|
||||||
|
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
|
||||||
TODO
|
Added alpha_quality argument when saving WebP images
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
When saving WebP images, an ``alpha_quality`` argument can be passed to the encoder. It
|
||||||
|
is an integer value between 0 to 100, where values other than 100 will provide lossy
|
||||||
|
compression.
|
||||||
|
|
||||||
|
Negative kmeans error
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When calling :py:meth:`~PIL.Image.Image.quantize`, a negative ``kmeans`` will now
|
||||||
|
raise a :py:exc:`ValueError`, unless a palette is supplied to make the value redundant.
|
||||||
|
|
||||||
|
Negative P1-P3 PPM value error
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If a P1-P3 PPM image contains a negative value, a :py:exc:`ValueError` will now be
|
||||||
|
raised.
|
||||||
|
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
@ -63,14 +97,6 @@ Added PerspectiveTransform
|
||||||
that all of the :py:data:`~PIL.Image.Transform` values now have a corresponding
|
that all of the :py:data:`~PIL.Image.Transform` values now have a corresponding
|
||||||
subclass of :py:class:`~PIL.ImageTransform.Transform`.
|
subclass of :py:class:`~PIL.ImageTransform.Transform`.
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
@ -85,3 +111,9 @@ Release GIL when fetching WebP frames
|
||||||
|
|
||||||
Python's Global Interpreter Lock is now released when fetching WebP frames from
|
Python's Global Interpreter Lock is now released when fetching WebP frames from
|
||||||
the libwebp decoder.
|
the libwebp decoder.
|
||||||
|
|
||||||
|
Type hints
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
Pillow now has type hints for a large part of its modules, and the package
|
||||||
|
includes a ``py.typed`` file and the ``Typing :: Typed`` Trove classifier.
|
||||||
|
|
26
docs/releasenotes/2.3.1.rst
Normal file
26
docs/releasenotes/2.3.1.rst
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
2.3.1
|
||||||
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
These issues were reported in
|
||||||
|
`Debian bug #737059 <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=737059>`_.
|
||||||
|
|
||||||
|
:cve:`2014-1932`: Fix insecure use of :py:func:`tempfile.mktemp`
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The (1) ``load_djpeg`` function in ``JpegImagePlugin.py``, (2) Ghostscript function
|
||||||
|
in ``EpsImagePlugin.py``, (3) ``load`` function in ``IptcImagePlugin.py``, and (4)
|
||||||
|
``_copy`` function in ``Image.py`` in
|
||||||
|
Pillow before 2.3.1 do not properly create temporary files, which allow
|
||||||
|
local users to overwrite arbitrary files and obtain sensitive information via a
|
||||||
|
symlink attack on the temporary file.
|
||||||
|
|
||||||
|
:cve:`2014-1933`: Fix insecure use of :py:func:`tempfile.mktemp`
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The (1) ``JpegImagePlugin.py`` and (2) ``EpsImagePlugin.py`` scripts in
|
||||||
|
Pillow before 2.3.1 uses the names of
|
||||||
|
temporary files on the command line, which makes it easier for local users to
|
||||||
|
conduct symlink attacks by listing the processes.
|
14
docs/releasenotes/2.3.2.rst
Normal file
14
docs/releasenotes/2.3.2.rst
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
2.3.2
|
||||||
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
:cve:`2014-3589`: Fix DOS attack
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``PIL/IcnsImagePlugin.py`` in Pillow before 2.3.2 and
|
||||||
|
2.5.x before 2.5.2 allows remote attackers to cause a denial of service via a crafted
|
||||||
|
block size.
|
||||||
|
|
||||||
|
Found and reported by Andrew Drake of `Dropbox <https://www.dropbox.com/>`__.
|
14
docs/releasenotes/2.5.2.rst
Normal file
14
docs/releasenotes/2.5.2.rst
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
2.5.2
|
||||||
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
:cve:`2014-3589`: Fix DOS attack
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``PIL/IcnsImagePlugin.py`` in Pillow before 2.3.2 and
|
||||||
|
2.5.x before 2.5.2 allows remote attackers to cause a denial of service via a crafted
|
||||||
|
block size.
|
||||||
|
|
||||||
|
Found and reported by Andrew Drake of `Dropbox <https://www.dropbox.com/>`__.
|
14
docs/releasenotes/2.6.0.rst
Normal file
14
docs/releasenotes/2.6.0.rst
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
2.6.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
:cve:`2014-3589`: Fix DOS attack
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``PIL/IcnsImagePlugin.py`` in Pillow before 2.3.2 and
|
||||||
|
2.5.x before 2.5.2 allows remote attackers to cause a denial of service via a crafted
|
||||||
|
block size.
|
||||||
|
|
||||||
|
Found and reported by Andrew Drake of `Dropbox <https://www.dropbox.com/>`__.
|
|
@ -1,15 +1,14 @@
|
||||||
2.7.0
|
2.7.0
|
||||||
=====
|
-----
|
||||||
|
|
||||||
Sane Plugin
|
Sane Plugin
|
||||||
-----------
|
^^^^^^^^^^^
|
||||||
|
|
||||||
The Sane plugin has now been split into its own repo:
|
The Sane plugin has now been split into its own repo:
|
||||||
https://github.com/python-pillow/Sane .
|
https://github.com/python-pillow/Sane .
|
||||||
|
|
||||||
|
|
||||||
Png text chunk size limits
|
Png text chunk size limits
|
||||||
--------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
To prevent potential denial of service attacks using compressed text
|
To prevent potential denial of service attacks using compressed text
|
||||||
chunks, there are now limits to the decompressed size of text chunks
|
chunks, there are now limits to the decompressed size of text chunks
|
||||||
|
@ -24,7 +23,7 @@ default. The total decompressed size of all text chunks is limited to
|
||||||
know that there are large text blocks that are desired.
|
know that there are large text blocks that are desired.
|
||||||
|
|
||||||
Image resizing filters
|
Image resizing filters
|
||||||
----------------------
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Image resizing methods :py:meth:`~PIL.Image.Image.resize` and
|
Image resizing methods :py:meth:`~PIL.Image.Image.resize` and
|
||||||
:py:meth:`~PIL.Image.Image.thumbnail` take a ``resample`` argument, which tells
|
:py:meth:`~PIL.Image.Image.thumbnail` take a ``resample`` argument, which tells
|
||||||
|
@ -33,7 +32,7 @@ which filter should be used for resampling. Possible values are:
|
||||||
were changed in this version.
|
were changed in this version.
|
||||||
|
|
||||||
Bicubic and bilinear downscaling
|
Bicubic and bilinear downscaling
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
From the beginning ``BILINEAR`` and ``BICUBIC`` filters were based on affine
|
From the beginning ``BILINEAR`` and ``BICUBIC`` filters were based on affine
|
||||||
transformations and used a fixed number of pixels from the source image for
|
transformations and used a fixed number of pixels from the source image for
|
||||||
|
@ -50,7 +49,7 @@ If you have previously used any tricks to maintain quality when downscaling with
|
||||||
steps), they are unnecessary now.
|
steps), they are unnecessary now.
|
||||||
|
|
||||||
Antialias renamed to Lanczos
|
Antialias renamed to Lanczos
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
++++++++++++++++++++++++++++
|
||||||
|
|
||||||
A new ``LANCZOS`` constant was added instead of ``ANTIALIAS``.
|
A new ``LANCZOS`` constant was added instead of ``ANTIALIAS``.
|
||||||
|
|
||||||
|
@ -64,19 +63,19 @@ The ``ANTIALIAS`` constant is left for backward compatibility and is an alias
|
||||||
for ``LANCZOS``.
|
for ``LANCZOS``.
|
||||||
|
|
||||||
Lanczos upscaling quality
|
Lanczos upscaling quality
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
+++++++++++++++++++++++++
|
||||||
|
|
||||||
The image upscaling quality with ``LANCZOS`` filter was almost the same as
|
The image upscaling quality with ``LANCZOS`` filter was almost the same as
|
||||||
``BILINEAR`` due to a bug. This has been fixed.
|
``BILINEAR`` due to a bug. This has been fixed.
|
||||||
|
|
||||||
Bicubic upscaling quality
|
Bicubic upscaling quality
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
+++++++++++++++++++++++++
|
||||||
|
|
||||||
The ``BICUBIC`` filter for affine transformations produced sharp, slightly
|
The ``BICUBIC`` filter for affine transformations produced sharp, slightly
|
||||||
pixelated image for upscaling. Bicubic for convolutions is more soft.
|
pixelated image for upscaling. Bicubic for convolutions is more soft.
|
||||||
|
|
||||||
Resize performance
|
Resize performance
|
||||||
^^^^^^^^^^^^^^^^^^
|
++++++++++++++++++
|
||||||
|
|
||||||
In most cases, convolution is more a expensive algorithm for downscaling
|
In most cases, convolution is more a expensive algorithm for downscaling
|
||||||
because it takes into account all the pixels of source image. Therefore
|
because it takes into account all the pixels of source image. Therefore
|
||||||
|
@ -93,7 +92,7 @@ The upscaling performance of the ``LANCZOS`` filter has remained the same. For
|
||||||
times.
|
times.
|
||||||
|
|
||||||
Default filter for thumbnails
|
Default filter for thumbnails
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
+++++++++++++++++++++++++++++
|
||||||
|
|
||||||
In Pillow 2.5 the default filter for :py:meth:`~PIL.Image.Image.thumbnail` was
|
In Pillow 2.5 the default filter for :py:meth:`~PIL.Image.Image.thumbnail` was
|
||||||
changed from ``NEAREST`` to ``ANTIALIAS``. Antialias was chosen because all the
|
changed from ``NEAREST`` to ``ANTIALIAS``. Antialias was chosen because all the
|
||||||
|
@ -103,7 +102,7 @@ other filters gave poor quality for reduction. Starting from Pillow 2.7.0,
|
||||||
uses supersampling internally, not convolutions.
|
uses supersampling internally, not convolutions.
|
||||||
|
|
||||||
Image transposition
|
Image transposition
|
||||||
-------------------
|
+++++++++++++++++++
|
||||||
|
|
||||||
A new method ``TRANSPOSE`` has been added for the
|
A new method ``TRANSPOSE`` has been added for the
|
||||||
:py:meth:`~PIL.Image.Image.transpose` operation in addition to
|
:py:meth:`~PIL.Image.Image.transpose` operation in addition to
|
||||||
|
@ -115,7 +114,7 @@ The speed of ``ROTATE_90``, ``ROTATE_270`` and ``TRANSPOSE`` has been significan
|
||||||
improved for large images which don't fit in the processor cache.
|
improved for large images which don't fit in the processor cache.
|
||||||
|
|
||||||
Gaussian blur and unsharp mask
|
Gaussian blur and unsharp mask
|
||||||
------------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The :py:meth:`~PIL.ImageFilter.GaussianBlur` implementation has been replaced
|
The :py:meth:`~PIL.ImageFilter.GaussianBlur` implementation has been replaced
|
||||||
with a sequential application of box filters. The new implementation is based on
|
with a sequential application of box filters. The new implementation is based on
|
||||||
|
@ -125,7 +124,7 @@ implementations use Gaussian blur internally, all changes from this chapter
|
||||||
are also applicable to it.
|
are also applicable to it.
|
||||||
|
|
||||||
Blur radius
|
Blur radius
|
||||||
^^^^^^^^^^^
|
+++++++++++
|
||||||
|
|
||||||
There was an error in the previous version of Pillow, where blur radius (the
|
There was an error in the previous version of Pillow, where blur radius (the
|
||||||
standard deviation of Gaussian) actually meant blur diameter. For example, to
|
standard deviation of Gaussian) actually meant blur diameter. For example, to
|
||||||
|
@ -136,7 +135,7 @@ If you used a Gaussian blur with some radius value, you need to divide this
|
||||||
value by two.
|
value by two.
|
||||||
|
|
||||||
Blur performance
|
Blur performance
|
||||||
^^^^^^^^^^^^^^^^
|
++++++++++++++++
|
||||||
|
|
||||||
Box filter computation time is constant relative to the radius and depends
|
Box filter computation time is constant relative to the radius and depends
|
||||||
on source image size only. Because the new Gaussian blur implementation
|
on source image size only. Because the new Gaussian blur implementation
|
||||||
|
@ -148,7 +147,7 @@ second for radius 1, 3.6 seconds for radius 10 and 17 seconds for 50, now blur
|
||||||
with any radius on same image is executed for 0.2 seconds.
|
with any radius on same image is executed for 0.2 seconds.
|
||||||
|
|
||||||
Blur quality
|
Blur quality
|
||||||
^^^^^^^^^^^^
|
++++++++++++
|
||||||
|
|
||||||
The previous implementation takes into account only source pixels within
|
The previous implementation takes into account only source pixels within
|
||||||
2 * standard deviation radius for every destination pixel. This was not enough,
|
2 * standard deviation radius for every destination pixel. This was not enough,
|
||||||
|
@ -157,7 +156,7 @@ so the quality was worse compared to other Gaussian blur software.
|
||||||
The new implementation does not have this drawback.
|
The new implementation does not have this drawback.
|
||||||
|
|
||||||
TIFF Parameter Changes
|
TIFF Parameter Changes
|
||||||
----------------------
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Several kwarg parameters for saving TIFF images were previously
|
Several kwarg parameters for saving TIFF images were previously
|
||||||
specified as strings with included spaces (e.g. 'x resolution'). This
|
specified as strings with included spaces (e.g. 'x resolution'). This
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
2.8.0
|
2.8.0
|
||||||
=====
|
-----
|
||||||
|
|
||||||
Open HTTP response objects with Image.open
|
Open HTTP response objects with Image.open
|
||||||
------------------------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
HTTP response objects returned from ``urllib2.urlopen(url)`` or
|
HTTP response objects returned from ``urllib2.urlopen(url)`` or
|
||||||
``requests.get(url, stream=True).raw`` are 'file-like' but do not support ``.seek()``
|
``requests.get(url, stream=True).raw`` are 'file-like' but do not support ``.seek()``
|
||||||
|
|
|
@ -1,9 +1,28 @@
|
||||||
|
|
||||||
3.0.0
|
3.0.0
|
||||||
=====
|
-----
|
||||||
|
|
||||||
|
Backwards Incompatible Changes
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Several methods that have been marked as deprecated for many releases
|
||||||
|
have been removed in this release:
|
||||||
|
|
||||||
|
* ``Image.tostring()``
|
||||||
|
* ``Image.fromstring()``
|
||||||
|
* ``Image.offset()``
|
||||||
|
* ``ImageDraw.setink()``
|
||||||
|
* ``ImageDraw.setfill()``
|
||||||
|
* The ``ImageFileIO`` module
|
||||||
|
* The ``ImageFont.FreeTypeFont`` and ``ImageFont.truetype`` ``file`` keyword arg
|
||||||
|
* The ``ImagePalette`` private ``_make`` functions
|
||||||
|
* ``ImageWin.fromstring()``
|
||||||
|
* ``ImageWin.tostring()``
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
Saving Multipage Images
|
Saving Multipage Images
|
||||||
-----------------------
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
There is now support for saving multipage images in the ``GIF`` and
|
There is now support for saving multipage images in the ``GIF`` and
|
||||||
``PDF`` formats. To enable this functionality, pass in ``save_all=True``
|
``PDF`` formats. To enable this functionality, pass in ``save_all=True``
|
||||||
|
@ -12,7 +31,7 @@ as a keyword argument to the save::
|
||||||
im.save('test.pdf', save_all=True)
|
im.save('test.pdf', save_all=True)
|
||||||
|
|
||||||
Tiff ImageFileDirectory Rewrite
|
Tiff ImageFileDirectory Rewrite
|
||||||
-------------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The Tiff ImageFileDirectory metadata code has been rewritten. Where
|
The Tiff ImageFileDirectory metadata code has been rewritten. Where
|
||||||
previously it returned a somewhat arbitrary set of values and tuples,
|
previously it returned a somewhat arbitrary set of values and tuples,
|
||||||
|
@ -25,25 +44,8 @@ structures will be deprecated at some point in the future. When
|
||||||
saving Tiff metadata, new code should use the
|
saving Tiff metadata, new code should use the
|
||||||
TiffImagePlugin.ImageFileDirectory_v2 class.
|
TiffImagePlugin.ImageFileDirectory_v2 class.
|
||||||
|
|
||||||
Deprecated Methods
|
LibJpeg and Zlib are required by default
|
||||||
------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Several methods that have been marked as deprecated for many releases
|
|
||||||
have been removed in this release::
|
|
||||||
|
|
||||||
Image.tostring()
|
|
||||||
Image.fromstring()
|
|
||||||
Image.offset()
|
|
||||||
ImageDraw.setink()
|
|
||||||
ImageDraw.setfill()
|
|
||||||
The ImageFileIO module
|
|
||||||
The ImageFont.FreeTypeFont and ImageFont.truetype ``file`` keyword arg
|
|
||||||
The ImagePalette private _make functions
|
|
||||||
ImageWin.fromstring()
|
|
||||||
ImageWin.tostring()
|
|
||||||
|
|
||||||
LibJpeg and Zlib are Required by Default
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
The external dependencies on libjpeg and zlib are now required by default.
|
The external dependencies on libjpeg and zlib are now required by default.
|
||||||
If the headers or libraries are not found, then installation will abort
|
If the headers or libraries are not found, then installation will abort
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
|
|
||||||
3.1.0
|
3.1.0
|
||||||
=====
|
-----
|
||||||
|
|
||||||
ImageDraw arc, chord and pieslice can now use floats
|
ImageDraw arc, chord and pieslice can now use floats
|
||||||
----------------------------------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
There is no longer a need to ensure that the start and end arguments for ``arc``,
|
There is no longer a need to ensure that the start and end arguments for ``arc``,
|
||||||
``chord`` and ``pieslice`` are integers.
|
``chord`` and ``pieslice`` are integers.
|
||||||
|
@ -12,7 +11,7 @@ Note that these numbers are not simply rounded internally, but are actually
|
||||||
utilised in the drawing process.
|
utilised in the drawing process.
|
||||||
|
|
||||||
Consistent multiline text spacing
|
Consistent multiline text spacing
|
||||||
---------------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
When using the ``ImageDraw`` multiline methods, the spacing between
|
When using the ``ImageDraw`` multiline methods, the spacing between
|
||||||
lines was inconsistent, based on the combination on ascenders and
|
lines was inconsistent, based on the combination on ascenders and
|
||||||
|
@ -24,7 +23,7 @@ not the absolute height of each line.
|
||||||
There is also now a default spacing of 4px between lines.
|
There is also now a default spacing of 4px between lines.
|
||||||
|
|
||||||
Exif, Jpeg and Tiff Metadata
|
Exif, Jpeg and Tiff Metadata
|
||||||
----------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
There were major changes in the TIFF ImageFileDirectory support in
|
There were major changes in the TIFF ImageFileDirectory support in
|
||||||
Pillow 3.0 that led to a number of regressions. Some of them have been
|
Pillow 3.0 that led to a number of regressions. Some of them have been
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
|
||||||
3.1.1
|
3.1.1
|
||||||
=====
|
-----
|
||||||
|
|
||||||
CVE-2016-0740 -- Buffer overflow in TiffDecode.c
|
Security
|
||||||
------------------------------------------------
|
========
|
||||||
|
|
||||||
|
:cve:`2016-0740`: Buffer overflow in ``TiffDecode.c``
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Pillow 3.1.0 and earlier when linked against libtiff >= 4.0.0 on x64
|
Pillow 3.1.0 and earlier when linked against libtiff >= 4.0.0 on x64
|
||||||
may overflow a buffer when reading a specially crafted tiff file (:cve:`2016-0740`).
|
may overflow a buffer when reading a specially crafted tiff file.
|
||||||
|
|
||||||
Specifically, libtiff >= 4.0.0 changed the return type of
|
Specifically, libtiff >= 4.0.0 changed the return type of
|
||||||
``TIFFScanlineSize`` from ``int32`` to machine dependent
|
``TIFFScanlineSize`` from ``int32`` to machine dependent
|
||||||
|
@ -19,9 +21,8 @@ image data over 64k is written over the heap, causing a segfault.
|
||||||
|
|
||||||
This issue was found by security researcher FourOne.
|
This issue was found by security researcher FourOne.
|
||||||
|
|
||||||
|
:cve:`2016-0775`: Buffer overflow in ``FliDecode.c``
|
||||||
CVE-2016-0775 -- Buffer overflow in FliDecode.c
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
-----------------------------------------------
|
|
||||||
|
|
||||||
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
||||||
release, FliDecode.c has a buffer overflow error (:cve:`2016-0775`).
|
release, FliDecode.c has a buffer overflow error (:cve:`2016-0775`).
|
||||||
|
@ -49,8 +50,8 @@ off the end of the memory buffer, causing a segfault.
|
||||||
|
|
||||||
This issue was found by Alyssa Besseling at Atlassian.
|
This issue was found by Alyssa Besseling at Atlassian.
|
||||||
|
|
||||||
CVE-2016-2533 -- Buffer overflow in PcdDecode.c
|
:cve:`2016-2533`: Buffer overflow in ``PcdDecode.c``
|
||||||
-----------------------------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
||||||
release, ``PcdDecode.c`` has a buffer overflow error (:cve:`2016-2533`).
|
release, ``PcdDecode.c`` has a buffer overflow error (:cve:`2016-2533`).
|
||||||
|
@ -61,8 +62,8 @@ assuming 4 bytes per pixel. This writes 768 bytes beyond the end of
|
||||||
the buffer into other Python object storage. In some cases, this
|
the buffer into other Python object storage. In some cases, this
|
||||||
causes a segfault, in others an internal Python malloc error.
|
causes a segfault, in others an internal Python malloc error.
|
||||||
|
|
||||||
Integer overflow in Resample.c
|
Integer overflow in ``Resample.c``
|
||||||
------------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
If a large value was passed into the new size for an image, it is
|
If a large value was passed into the new size for an image, it is
|
||||||
possible to overflow an ``int32`` value passed into malloc.
|
possible to overflow an ``int32`` value passed into malloc.
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
|
|
||||||
3.1.2
|
3.1.2
|
||||||
=====
|
-----
|
||||||
|
|
||||||
CVE-2016-3076 -- Buffer overflow in Jpeg2KEncode.c
|
Security
|
||||||
--------------------------------------------------
|
========
|
||||||
|
|
||||||
Pillow between 2.5.0 and 3.1.1 may overflow a buffer when writing
|
:cve:`2016-3076`: Buffer overflow in Jpeg2KEncode.c
|
||||||
large Jpeg2000 files, allowing for code execution or other memory
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
corruption (:cve:`2016-3076`).
|
|
||||||
|
Pillow between 2.5.0 and 3.1.1 may overflow a buffer
|
||||||
|
when writing large Jpeg2000 files, allowing for code execution or other
|
||||||
|
memory corruption.
|
||||||
|
|
||||||
This occurs specifically in the function ``j2k_encode_entry``, at the line:
|
This occurs specifically in the function ``j2k_encode_entry``, at the line:
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
|
|
||||||
3.2.0
|
3.2.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
New DDS and FTEX Image Plugins
|
New DDS and FTEX Image Plugins
|
||||||
==============================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The ``DdsImagePlugin`` reading DXT1 and DXT5 encoded ``.dds`` images was
|
The ``DdsImagePlugin`` reading DXT1 and DXT5 encoded ``.dds`` images was
|
||||||
added. DXT3 images are not currently supported.
|
added. DXT3 images are not currently supported.
|
||||||
|
@ -14,13 +13,13 @@ per file, in the ``.ftc`` (compressed) and ``.ftu`` (uncompressed)
|
||||||
formats.
|
formats.
|
||||||
|
|
||||||
Updates to the GbrImagePlugin
|
Updates to the GbrImagePlugin
|
||||||
=============================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The ``GbrImagePlugin`` (GIMP brush format) has been updated to fix
|
The ``GbrImagePlugin`` (GIMP brush format) has been updated to fix
|
||||||
support for version 1 files and add support for version 2 files.
|
support for version 1 files and add support for version 2 files.
|
||||||
|
|
||||||
Passthrough Parameters for ImageDraw.text
|
Passthrough Parameters for ImageDraw.text
|
||||||
=========================================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
``ImageDraw.multiline_text`` and ``ImageDraw.multiline_size`` take extra
|
``ImageDraw.multiline_text`` and ``ImageDraw.multiline_size`` take extra
|
||||||
spacing parameters above what are used in ``ImageDraw.text`` and
|
spacing parameters above what are used in ``ImageDraw.text`` and
|
||||||
|
@ -29,7 +28,7 @@ spacing parameters above what are used in ``ImageDraw.text`` and
|
||||||
to the corresponding multiline functions.
|
to the corresponding multiline functions.
|
||||||
|
|
||||||
ImageSequence.Iterator changes
|
ImageSequence.Iterator changes
|
||||||
==============================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
``ImageSequence.Iterator`` is now an actual iterator implementing the
|
``ImageSequence.Iterator`` is now an actual iterator implementing the
|
||||||
Iterator protocol. It is also now possible to seek to the first image
|
Iterator protocol. It is also now possible to seek to the first image
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Libimagequant support
|
Libimagequant support
|
||||||
=====================
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
There is now support for using libimagequant as a higher quality
|
There is now support for using libimagequant as a higher quality
|
||||||
quantization option in ``Image.quantize()`` on Unix-like
|
quantization option in ``Image.quantize()`` on Unix-like
|
||||||
|
@ -12,21 +12,20 @@ differences.
|
||||||
|
|
||||||
|
|
||||||
New Setup.py options
|
New Setup.py options
|
||||||
====================
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
There are two new options to control the ``build_ext`` task in ``setup.py``:
|
There are two new options to control the ``build_ext`` task in ``setup.py``:
|
||||||
|
|
||||||
* ``--debug`` dumps all of the directories and files that are
|
* ``--debug`` dumps all of the directories and files that are
|
||||||
checked when searching for libraries or headers when building the
|
checked when searching for libraries or headers when building the
|
||||||
extensions.
|
extensions.
|
||||||
* ``--disable-platform-guessing`` removes many of the directories
|
* ``--disable-platform-guessing`` removes many of the directories
|
||||||
that are checked for libraries and headers for build systems or
|
that are checked for libraries and headers for build systems or
|
||||||
cross compilers that specify that information in via environment
|
cross compilers that specify that information in via environment
|
||||||
variables.
|
variables.
|
||||||
|
|
||||||
|
|
||||||
Resizing
|
Resizing
|
||||||
========
|
^^^^^^^^
|
||||||
|
|
||||||
Image resampling for 8-bit per channel images was rewritten using only integer
|
Image resampling for 8-bit per channel images was rewritten using only integer
|
||||||
computings. This is faster on most platforms and doesn't introduce precision
|
computings. This is faster on most platforms and doesn't introduce precision
|
||||||
|
@ -36,19 +35,17 @@ makes resampling 60% faster on average.
|
||||||
Color calculation for images in the ``LA`` mode on semitransparent pixels
|
Color calculation for images in the ``LA`` mode on semitransparent pixels
|
||||||
was fixed.
|
was fixed.
|
||||||
|
|
||||||
|
|
||||||
Rotation
|
Rotation
|
||||||
========
|
^^^^^^^^
|
||||||
|
|
||||||
Rotation for angles divisible by 90 degrees now always uses transposition.
|
Rotation for angles divisible by 90 degrees now always uses transposition.
|
||||||
This greatly improves both quality and performance in this case.
|
This greatly improves both quality and performance in this case.
|
||||||
Also, the bug with wrong image size calculation when rotating by 90 degrees
|
Also, the bug with wrong image size calculation when rotating by 90 degrees
|
||||||
was fixed.
|
was fixed.
|
||||||
|
|
||||||
|
|
||||||
Image Metadata
|
Image Metadata
|
||||||
==============
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The return type for binary data in version 2 Exif and Tiff metadata
|
The return type for binary data in version 2 Exif and Tiff metadata
|
||||||
has been changed from a tuple of integers to bytes. This is a change
|
has been changed from a tuple of integers to bytes. This is a change
|
||||||
from the behavior since ``3.0.0``.
|
from the behavior since 3.0.0.
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
|
|
||||||
3.3.2
|
3.3.2
|
||||||
=====
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
Integer overflow in Map.c
|
Integer overflow in Map.c
|
||||||
-------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Pillow prior to 3.3.2 may experience integer overflow errors in map.c
|
Pillow prior to 3.3.2 may experience integer overflow errors in map.c
|
||||||
when reading specially crafted image files. This may lead to memory
|
when reading specially crafted image files. This may lead to memory
|
||||||
|
@ -26,7 +28,7 @@ memory without duplicating the image first.
|
||||||
This issue was found by Cris Neckar at Divergent Security.
|
This issue was found by Cris Neckar at Divergent Security.
|
||||||
|
|
||||||
Sign Extension in Storage.c
|
Sign Extension in Storage.c
|
||||||
---------------------------
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Pillow prior to 3.3.2 and PIL 1.1.7 (at least) do not check for
|
Pillow prior to 3.3.2 and PIL 1.1.7 (at least) do not check for
|
||||||
negative image sizes in ``ImagingNew`` in ``Storage.c``. A negative
|
negative image sizes in ``ImagingNew`` in ``Storage.c``. A negative
|
||||||
|
|
|
@ -1,9 +1,32 @@
|
||||||
|
|
||||||
3.4.0
|
3.4.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
Backwards Incompatible Changes
|
||||||
|
==============================
|
||||||
|
|
||||||
|
Image.core.open_ppm removed
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The nominally private/debugging function ``Image.core.open_ppm`` has
|
||||||
|
been removed. If you were using this function, please use
|
||||||
|
``Image.open`` instead.
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
============
|
||||||
|
|
||||||
|
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 :py:exc:`DeprecationWarning` when attempting to save a ``RGBA`` mode
|
||||||
|
image as a JPEG. This will become an error in Pillow 4.2.
|
||||||
|
|
||||||
|
API Additions
|
||||||
|
=============
|
||||||
|
|
||||||
New resizing filters
|
New resizing filters
|
||||||
====================
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Two new filters available for ``Image.resize()`` and ``Image.thumbnail()``
|
Two new filters available for ``Image.resize()`` and ``Image.thumbnail()``
|
||||||
functions: ``BOX`` and ``HAMMING``. ``BOX`` is the high-performance filter with
|
functions: ``BOX`` and ``HAMMING``. ``BOX`` is the high-performance filter with
|
||||||
|
@ -14,23 +37,15 @@ two times shorter window than ``BILINEAR``. It can be used for image reduction
|
||||||
providing the image downscaling quality comparable to ``BICUBIC``.
|
providing the image downscaling quality comparable to ``BICUBIC``.
|
||||||
Both new filters don't show good quality for the image upscaling.
|
Both new filters don't show good quality for the image upscaling.
|
||||||
|
|
||||||
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 :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
|
New DDS Decoders
|
||||||
================
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Pillow can now decode DXT3 images, as well as the previously supported
|
Pillow can now decode DXT3 images, as well as the previously supported
|
||||||
DXT1 and DXT5 formats. All three formats are now decoded in C code for
|
DXT1 and DXT5 formats. All three formats are now decoded in C code for
|
||||||
better performance.
|
better performance.
|
||||||
|
|
||||||
Append images to GIF
|
Append images to GIF
|
||||||
====================
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Additional frames can now be appended when saving a GIF file, through the
|
Additional frames can now be appended when saving a GIF file, through the
|
||||||
``append_images`` argument. The new frames are passed in as a list of images,
|
``append_images`` argument. The new frames are passed in as a list of images,
|
||||||
|
@ -42,16 +57,9 @@ in effect, e.g.::
|
||||||
im.save(out, save_all=True, append_images=[im1, im2, ...])
|
im.save(out, save_all=True, append_images=[im1, im2, ...])
|
||||||
|
|
||||||
Save multiple frame TIFF
|
Save multiple frame TIFF
|
||||||
========================
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Multiple frames can now be saved in a TIFF file by using the ``save_all`` option.
|
Multiple frames can now be saved in a TIFF file by using the ``save_all`` option.
|
||||||
e.g.::
|
e.g.::
|
||||||
|
|
||||||
im.save("filename.tiff", format="TIFF", save_all=True)
|
im.save("filename.tiff", format="TIFF", save_all=True)
|
||||||
|
|
||||||
Image.core.open_ppm removed
|
|
||||||
===========================
|
|
||||||
|
|
||||||
The nominally private/debugging function ``Image.core.open_ppm`` has
|
|
||||||
been removed. If you were using this function, please use
|
|
||||||
``Image.open`` instead.
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Python 2.6 and 3.2 Dropped
|
Python 2.6 and 3.2 Dropped
|
||||||
==========================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Pillow 4.0 no longer supports Python 2.6 and 3.2. We will not be
|
Pillow 4.0 no longer supports Python 2.6 and 3.2. We will not be
|
||||||
creating binaries, testing, or retaining compatibility with these
|
creating binaries, testing, or retaining compatibility with these
|
||||||
|
@ -10,12 +10,12 @@ releases. This release removes some workarounds for those Python
|
||||||
releases, so the final working version of Pillow on 2.6 or 3.2 is 3.4.2.
|
releases, so the final working version of Pillow on 2.6 or 3.2 is 3.4.2.
|
||||||
|
|
||||||
Support added for Python 3.6
|
Support added for Python 3.6
|
||||||
============================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Pillow 4.0 supports Python 3.6.
|
Pillow 4.0 supports Python 3.6.
|
||||||
|
|
||||||
OleFileIO.py
|
OleFileIO.py
|
||||||
============
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
``OleFileIO.py`` has been removed as a vendored file and is now installed
|
``OleFileIO.py`` has been removed as a vendored file and is now installed
|
||||||
from the upstream :pypi:`olefile` PyPI package. All internal dependencies are
|
from the upstream :pypi:`olefile` PyPI package. All internal dependencies are
|
||||||
|
@ -24,19 +24,19 @@ redirected to the olefile package. Direct accesses to
|
||||||
upstream olefile into ``sys.modules`` in its place.
|
upstream olefile into ``sys.modules`` in its place.
|
||||||
|
|
||||||
SGI image save
|
SGI image save
|
||||||
==============
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
It is now possible to save images in modes ``L``, ``RGB``, and
|
It is now possible to save images in modes ``L``, ``RGB``, and
|
||||||
``RGBA`` to the uncompressed SGI image format.
|
``RGBA`` to the uncompressed SGI image format.
|
||||||
|
|
||||||
Zero sized images
|
Zero sized images
|
||||||
=================
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Pillow 3.4.0 removed support for creating images with (0,0) size. This
|
Pillow 3.4.0 removed support for creating images with (0,0) size. This
|
||||||
has been reenabled, restoring pre 3.4 behavior.
|
has been reenabled, restoring pre 3.4 behavior.
|
||||||
|
|
||||||
Internal handles_eof flag
|
Internal handles_eof flag
|
||||||
=========================
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The ``handles_eof flag`` for decoding images has been removed, as there
|
The ``handles_eof flag`` for decoding images has been removed, as there
|
||||||
were no internal users of the flag. Anyone maintaining image decoders
|
were no internal users of the flag. Anyone maintaining image decoders
|
||||||
|
@ -44,7 +44,7 @@ outside of the Pillow source tree should consider using the cleanup
|
||||||
function pointers instead.
|
function pointers instead.
|
||||||
|
|
||||||
Image.core.stretch removed
|
Image.core.stretch removed
|
||||||
==========================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The stretch function on the core image object has been removed. This
|
The stretch function on the core image object has been removed. This
|
||||||
used to be for enlarging the image, but has been aliased to resize
|
used to be for enlarging the image, but has been aliased to resize
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
4.1.0
|
4.1.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Removed Deprecated Items
|
Deprecations
|
||||||
========================
|
============
|
||||||
|
|
||||||
Several deprecated items have been removed.
|
Several deprecated items have been removed.
|
||||||
|
|
||||||
|
@ -15,8 +15,11 @@ Several deprecated items have been removed.
|
||||||
``PIL.ImageDraw.ImageDraw.setfont`` have been removed.
|
``PIL.ImageDraw.ImageDraw.setfont`` have been removed.
|
||||||
|
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
Closing Files When Opening Images
|
Closing Files When Opening Images
|
||||||
=================================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The file handling when opening images has been overhauled. Previously,
|
The file handling when opening images has been overhauled. Previously,
|
||||||
Pillow would attempt to close some, but not all image formats
|
Pillow would attempt to close some, but not all image formats
|
||||||
|
@ -38,9 +41,8 @@ is specified:
|
||||||
the underlying file until we are done with the image. The mapping
|
the underlying file until we are done with the image. The mapping
|
||||||
will be closed in the ``close`` or ``__del__`` method.
|
will be closed in the ``close`` or ``__del__`` method.
|
||||||
|
|
||||||
|
|
||||||
Changes to GIF Handling When Saving
|
Changes to GIF Handling When Saving
|
||||||
===================================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The :py:class:`PIL.GifImagePlugin` code has been refactored to fix the flow when
|
The :py:class:`PIL.GifImagePlugin` code has been refactored to fix the flow when
|
||||||
saving images. There are two external changes that arise from this:
|
saving images. There are two external changes that arise from this:
|
||||||
|
@ -56,14 +58,14 @@ This refactor fixed some bugs with palette handling when saving
|
||||||
multiple frame GIFs.
|
multiple frame GIFs.
|
||||||
|
|
||||||
New Method: Image.remap_palette
|
New Method: Image.remap_palette
|
||||||
===============================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The method :py:meth:`PIL.Image.Image.remap_palette()` has been
|
The method :py:meth:`PIL.Image.Image.remap_palette()` has been
|
||||||
added. This method was hoisted from the GifImagePlugin code used to
|
added. This method was hoisted from the GifImagePlugin code used to
|
||||||
optimize the palette.
|
optimize the palette.
|
||||||
|
|
||||||
Added Decoder Registry and Support for Python Based Decoders
|
Added Decoder Registry and Support for Python Based Decoders
|
||||||
============================================================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
There is now a decoder registry similar to the image plugin
|
There is now a decoder registry similar to the image plugin
|
||||||
registries. Image plugins can register a decoder, and it will be
|
registries. Image plugins can register a decoder, and it will be
|
||||||
|
@ -73,7 +75,7 @@ their C based counterparts, they may be easier and quicker to develop
|
||||||
or safer to run.
|
or safer to run.
|
||||||
|
|
||||||
Tests
|
Tests
|
||||||
=====
|
^^^^^
|
||||||
|
|
||||||
Many tests have been added, including correctness tests for image
|
Many tests have been added, including correctness tests for image
|
||||||
formats that have been previously untested.
|
formats that have been previously untested.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Fix Regression with reading DPI from EXIF data
|
Fix Regression with reading DPI from EXIF data
|
||||||
==============================================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Some JPEG images don't contain DPI information in the image metadata,
|
Some JPEG images don't contain DPI information in the image metadata,
|
||||||
but do contain it in the EXIF data. A patch was added in 4.1.0 to read
|
but do contain it in the EXIF data. A patch was added in 4.1.0 to read
|
||||||
|
@ -10,9 +10,8 @@ from the EXIF data, but it did not accept all possible types that
|
||||||
could be included there. This fix adds the ability to read ints as
|
could be included there. This fix adds the ability to read ints as
|
||||||
well as rational values.
|
well as rational values.
|
||||||
|
|
||||||
|
|
||||||
Incompatibility between 3.6.0 and 3.6.1
|
Incompatibility between 3.6.0 and 3.6.1
|
||||||
=======================================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
CPython 3.6.1 added a new symbol, PySlice_GetIndicesEx, which was not
|
CPython 3.6.1 added a new symbol, PySlice_GetIndicesEx, which was not
|
||||||
present in 3.6.0. This had the effect of causing binaries compiled on
|
present in 3.6.0. This had the effect of causing binaries compiled on
|
||||||
|
|
|
@ -1,37 +1,11 @@
|
||||||
4.2.0
|
4.2.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Added Complex Text Rendering
|
Backwards Incompatible Changes
|
||||||
============================
|
==============================
|
||||||
|
|
||||||
Pillow now supports complex text rendering for scripts requiring glyph
|
Several deprecated items have been removed
|
||||||
composition and bidirectional flow. This optional feature adds three
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
dependencies: harfbuzz, fribidi, and raqm. See the :doc:`install documentation
|
|
||||||
<../installation>` for further details. This feature is tested and works on
|
|
||||||
Unix and Mac, but has not yet been built on Windows platforms.
|
|
||||||
|
|
||||||
New Optional Parameters
|
|
||||||
=======================
|
|
||||||
|
|
||||||
* :py:meth:`PIL.ImageDraw.floodfill` has a new optional parameter:
|
|
||||||
threshold. This specifies a tolerance for the color to replace with
|
|
||||||
the flood fill.
|
|
||||||
|
|
||||||
* The TIFF and PDF image writers now support the ``append_images``
|
|
||||||
optional parameter for specifying additional images to create
|
|
||||||
multipage outputs.
|
|
||||||
|
|
||||||
New DecompressionBomb Warning
|
|
||||||
=============================
|
|
||||||
|
|
||||||
:py:meth:`PIL.Image.Image.crop` now may raise a DecompressionBomb
|
|
||||||
warning if the crop region enlarges the image over the threshold
|
|
||||||
specified by :py:data:`PIL.Image.MAX_IMAGE_PIXELS`.
|
|
||||||
|
|
||||||
Removed Deprecated Items
|
|
||||||
========================
|
|
||||||
|
|
||||||
Several deprecated items have been removed.
|
|
||||||
|
|
||||||
* The methods ``PIL.ImageWin.Dib.fromstring``,
|
* The methods ``PIL.ImageWin.Dib.fromstring``,
|
||||||
``PIL.ImageWin.Dib.tostring`` and
|
``PIL.ImageWin.Dib.tostring`` and
|
||||||
|
@ -44,8 +18,38 @@ Several deprecated items have been removed.
|
||||||
an :py:exc:`IOError` is raised.
|
an :py:exc:`IOError` is raised.
|
||||||
|
|
||||||
Removed Core Image Function
|
Removed Core Image Function
|
||||||
===========================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The unused function ``Image.core.new_array`` was removed. This is an
|
The unused function ``Image.core.new_array`` was removed. This is an
|
||||||
internal function that should not have been used by user code, but it
|
internal function that should not have been used by user code, but it
|
||||||
was accessible from the python layer.
|
was accessible from the python layer.
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Added Complex Text Rendering
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Pillow now supports complex text rendering for scripts requiring glyph
|
||||||
|
composition and bidirectional flow. This optional feature adds three
|
||||||
|
dependencies: harfbuzz, fribidi, and raqm. See the :doc:`install documentation
|
||||||
|
<../installation>` for further details. This feature is tested and works on
|
||||||
|
Unix and Mac, but has not yet been built on Windows platforms.
|
||||||
|
|
||||||
|
New Optional Parameters
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* :py:meth:`PIL.ImageDraw.floodfill` has a new optional parameter:
|
||||||
|
threshold. This specifies a tolerance for the color to replace with
|
||||||
|
the flood fill.
|
||||||
|
|
||||||
|
* The TIFF and PDF image writers now support the ``append_images``
|
||||||
|
optional parameter for specifying additional images to create
|
||||||
|
multipage outputs.
|
||||||
|
|
||||||
|
New DecompressionBomb Warning
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
:py:meth:`PIL.Image.Image.crop` now may raise a DecompressionBomb
|
||||||
|
warning if the crop region enlarges the image over the threshold
|
||||||
|
specified by :py:data:`PIL.Image.MAX_IMAGE_PIXELS`.
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
There are no functional changes in this release.
|
There are no functional changes in this release.
|
||||||
|
|
||||||
Fixed Windows PyPy Build
|
Fixed Windows PyPy Build
|
||||||
========================
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
A change in the 4.2.0 cycle broke the Windows PyPy build. This has
|
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.
|
been fixed, and PyPy is now part of the Windows CI matrix.
|
||||||
|
|
|
@ -1,15 +1,6 @@
|
||||||
5.1.0
|
5.1.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
New File Format
|
|
||||||
===============
|
|
||||||
|
|
||||||
BLP File Format
|
|
||||||
^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Pillow now supports reading the BLP "Blizzard Mipmap" file format used
|
|
||||||
for tiles in Blizzard's engine.
|
|
||||||
|
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
@ -21,12 +12,21 @@ and ``CMYK`` with up to 6 8-bit channels, discarding any extra
|
||||||
channels if the content is tagged as UNSPECIFIED. Pillow still does
|
channels if the content is tagged as UNSPECIFIED. Pillow still does
|
||||||
not store more than 4 8-bit channels of image data.
|
not store more than 4 8-bit channels of image data.
|
||||||
|
|
||||||
|
API Additions
|
||||||
|
=============
|
||||||
|
|
||||||
Append to PDF Files
|
Append to PDF Files
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Images can now be appended to PDF files in place by passing in
|
Images can now be appended to PDF files in place by passing in
|
||||||
``append=True`` when saving the image.
|
``append=True`` when saving the image.
|
||||||
|
|
||||||
|
New BLP File Format
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Pillow now supports reading the BLP "Blizzard Mipmap" file format used
|
||||||
|
for tiles in Blizzard's engine.
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,53 @@
|
||||||
6.2.0
|
6.2.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
This release catches several buffer overruns and fixes :cve:`2019-16865`.
|
||||||
|
|
||||||
|
Buffer overruns
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In ``RawDecode.c``, an error is now thrown if skip is calculated to be less than
|
||||||
|
zero. It is intended to skip padding between lines, not to go backwards.
|
||||||
|
|
||||||
|
In ``PsdImagePlugin``, if the combined sizes of the individual parts is larger than
|
||||||
|
the declared size of the extra data field, then it looked for the next layer by
|
||||||
|
seeking backwards. This is now corrected by seeking to (the start of the layer
|
||||||
|
+ the size of the extra data field) instead of (the read parts of the layer +
|
||||||
|
the rest of the layer).
|
||||||
|
|
||||||
|
Decompression bomb checks have been added to GIF and ICO formats.
|
||||||
|
|
||||||
|
An error is now raised if a TIFF dimension is a string, rather than trying to
|
||||||
|
perform operations on it.
|
||||||
|
|
||||||
|
:cve:`2019-16865`: Fix DOS attack
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The CVE is regarding DOS problems, such as consuming large amounts of memory,
|
||||||
|
or taking a large amount of time to process an image.
|
||||||
|
|
||||||
|
API Changes
|
||||||
|
===========
|
||||||
|
|
||||||
|
Image.getexif
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
To allow for lazy loading of Exif data, ``Image.getexif()`` now returns a
|
||||||
|
shared instance of ``Image.Exif``.
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Image.frombuffer
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
There has been a longstanding warning that the defaults of ``Image.frombuffer``
|
||||||
|
may change in the future for the "raw" decoder. The change will now take place
|
||||||
|
in Pillow 7.0.
|
||||||
|
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
@ -46,46 +93,6 @@ ImageGrab on multi-monitor Windows
|
||||||
An ``all_screens`` argument has been added to ``ImageGrab.grab``. If ``True``,
|
An ``all_screens`` argument has been added to ``ImageGrab.grab``. If ``True``,
|
||||||
all monitors will be included in the created image.
|
all monitors will be included in the created image.
|
||||||
|
|
||||||
API Changes
|
|
||||||
===========
|
|
||||||
|
|
||||||
Image.getexif
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
To allow for lazy loading of Exif data, ``Image.getexif()`` now returns a
|
|
||||||
shared instance of ``Image.Exif``.
|
|
||||||
|
|
||||||
Deprecations
|
|
||||||
^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Image.frombuffer
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
There has been a longstanding warning that the defaults of ``Image.frombuffer``
|
|
||||||
may change in the future for the "raw" decoder. The change will now take place
|
|
||||||
in Pillow 7.0.
|
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
This release catches several buffer overruns, as well as addressing
|
|
||||||
:cve:`2019-16865`. The CVE is regarding DOS problems, such as consuming large
|
|
||||||
amounts of memory, or taking a large amount of time to process an image.
|
|
||||||
|
|
||||||
In RawDecode.c, an error is now thrown if skip is calculated to be less than
|
|
||||||
zero. It is intended to skip padding between lines, not to go backwards.
|
|
||||||
|
|
||||||
In PsdImagePlugin, if the combined sizes of the individual parts is larger than
|
|
||||||
the declared size of the extra data field, then it looked for the next layer by
|
|
||||||
seeking backwards. This is now corrected by seeking to (the start of the layer
|
|
||||||
+ the size of the extra data field) instead of (the read parts of the layer +
|
|
||||||
the rest of the layer).
|
|
||||||
|
|
||||||
Decompression bomb checks have been added to GIF and ICO formats.
|
|
||||||
|
|
||||||
An error is now raised if a TIFF dimension is a string, rather than trying to
|
|
||||||
perform operations on it.
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,6 @@ Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Support added for Python 3.8
|
Support added for Python 3.8
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,17 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
This release addresses several security problems.
|
This release fixes several buffer overflow issues and a DOS attack vulnerability.
|
||||||
|
|
||||||
:cve:`2019-19911` is regarding FPX images. If an image reports that it has a large
|
:cve:`2020-5310`, :cve:`2020-5311`, :cve:`2020-5312`, :cve:`2020-5313`: Overflow checks added
|
||||||
number of bands, a large amount of resources will be used when trying to process the
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
image. This is fixed by limiting the number of bands to those usable by Pillow.
|
|
||||||
|
|
||||||
Buffer overruns were found when processing an SGI (:cve:`2020-5311`),
|
Overflow checks have been added when calculating the size of a memory block to be reallocated
|
||||||
PCX (:cve:`2020-5312`) or FLI image (:cve:`2020-5313`). Checks have been added
|
in the processing of TIFF, SGI, PCX and FLI images.
|
||||||
to prevent this.
|
|
||||||
|
|
||||||
:cve:`2020-5310`: Overflow checks have been added when calculating the size of a
|
:cve:`2019-19911`: DOS attack vulnerability
|
||||||
memory block to be reallocated in the processing of a TIFF image.
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If an FPX image reports that it has a large number of bands, a large amount of
|
||||||
|
resources will be used when trying to process the image. This is fixed by
|
||||||
|
limiting the number of bands to those usable by Pillow.
|
||||||
|
|
|
@ -1,6 +1,40 @@
|
||||||
7.1.0
|
7.1.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
This release includes many security fixes.
|
||||||
|
|
||||||
|
:cve:`2020-10177`: Multiple out-of-bounds reads in FLI decoding
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Pillow before 7.1.0 has multiple out-of-bounds reads in ``libImaging/FliDecode.c``.
|
||||||
|
|
||||||
|
:cve:`2020-10378`: Bounds overflow in PCX decoding
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In ``libImaging/PcxDecode.c`` in Pillow before 7.1.0, an out-of-bounds read can occur
|
||||||
|
when reading PCX files where ``state->shuffle`` is instructed to read beyond
|
||||||
|
``state->buffer``.
|
||||||
|
|
||||||
|
:cve:`2020-10379`: Two buffer overflows in TIFF decoding
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In Pillow before 7.1.0, there are two buffer overflows in ``libImaging/TiffDecode.c``.
|
||||||
|
|
||||||
|
:cve:`2020-10994`: Bounds overflow in JPEG 2000 decoding
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In ``libImaging/Jpeg2KDecode.c`` in Pillow before 7.1.0, there are multiple
|
||||||
|
out-of-bounds reads via a crafted JP2 file.
|
||||||
|
|
||||||
|
:cve:`2020-11538`: Buffer overflow in SGI-RLE decoding
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In ``libImaging/SgiRleDecode.c`` in Pillow through 7.0.0, a number of out-of-bounds
|
||||||
|
reads exist in the parsing of SGI image files, a different issue than :cve:`2020-5311`.
|
||||||
|
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
@ -67,17 +101,6 @@ Passing a different value on Windows or macOS will force taking a snapshot
|
||||||
using the selected X server; pass an empty string to use the default X server.
|
using the selected X server; pass an empty string to use the default X server.
|
||||||
XCB support is not included in pre-compiled wheels for Windows and macOS.
|
XCB support is not included in pre-compiled wheels for Windows and macOS.
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
This release includes security fixes.
|
|
||||||
|
|
||||||
* :cve:`2020-10177` Fix multiple out-of-bounds reads in FLI decoding
|
|
||||||
* :cve:`2020-10378` Fix bounds overflow in PCX decoding
|
|
||||||
* :cve:`2020-10379` Fix two buffer overflows in TIFF decoding
|
|
||||||
* :cve:`2020-10994` Fix bounds overflow in JPEG 2000 decoding
|
|
||||||
* :cve:`2020-11538` Fix buffer overflow in SGI-RLE decoding
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Fix regression seeking PNG files
|
Fix regression seeking PNG files
|
||||||
================================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This fixes a regression introduced in 7.1.0 when adding support for APNG files when calling
|
This fixes a regression introduced in 7.1.0 when adding support for APNG files when calling
|
||||||
``seek`` and ``tell``:
|
``seek`` and ``tell``:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Fix another regression seeking PNG files
|
Fix another regression seeking PNG files
|
||||||
========================================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This fixes a regression introduced in 7.1.0 when adding support for APNG files.
|
This fixes a regression introduced in 7.1.0 when adding support for APNG files.
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,13 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
Update FreeType used in binary wheels to `2.10.4`_ to fix :cve:`2020-15999`:
|
:cve:`2020-15999`: Update FreeType in wheels to `2.10.4`_
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
- A heap buffer overflow has been found in the handling of embedded PNG bitmaps,
|
* A heap buffer overflow has been found in the handling of embedded PNG bitmaps,
|
||||||
introduced in FreeType version 2.6.
|
introduced in FreeType version 2.6.
|
||||||
|
|
||||||
If you use option ``FT_CONFIG_OPTION_USE_PNG`` you should upgrade immediately.
|
* If you use option ``FT_CONFIG_OPTION_USE_PNG`` you should upgrade immediately.
|
||||||
|
|
||||||
We strongly recommend updating to Pillow 8.0.1 if you are using Pillow 8.0.0, which improved support for bitmap fonts.
|
We strongly recommend updating to Pillow 8.0.1 if you are using Pillow 8.0.0, which improved support for bitmap fonts.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,50 @@
|
||||||
8.1.0
|
8.1.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
This release includes security fixes.
|
||||||
|
|
||||||
|
* An out-of-bounds read when saving TIFFs with custom metadata through LibTIFF
|
||||||
|
* An out-of-bounds read when saving a GIF of 1px width
|
||||||
|
|
||||||
|
:cve:`2020-35653`: Buffer read overrun in PCX decoding
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The PCX image decoder used the reported image stride to calculate
|
||||||
|
the row buffer, rather than calculating it from the image size. This issue dates back
|
||||||
|
to the PIL fork. Thanks to Google's `OSS-Fuzz`_ project for finding this.
|
||||||
|
|
||||||
|
:cve:`2020-35654`: TIFF out-of-bounds write error
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Out-of-bounds write in ``TiffDecode.c`` when reading corrupt YCbCr
|
||||||
|
files in some LibTIFF versions (4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04).
|
||||||
|
In some cases LibTIFF's interpretation of the file is different when reading in RGBA mode,
|
||||||
|
leading to an out-of-bounds write in ``TiffDecode.c``. This potentially affects Pillow
|
||||||
|
versions from 6.0.0 to 8.0.1, depending on the version of LibTIFF. This was reported through
|
||||||
|
`Tidelift`_.
|
||||||
|
|
||||||
|
:cve:`2020-35655`: SGI Decode buffer overrun
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
4 byte read overflow in ``SgiRleDecode.c``, where the code was not correctly
|
||||||
|
checking the offsets and length tables. Independently reported through `Tidelift`_ and Google's
|
||||||
|
`OSS-Fuzz`_. This vulnerability covers Pillow versions 4.3.0->8.0.1.
|
||||||
|
|
||||||
|
.. _Tidelift: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pillow&utm_medium=referral&utm_campaign=docs
|
||||||
|
.. _OSS-Fuzz: https://github.com/google/oss-fuzz
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
OpenJPEG in the macOS and Linux wheels has been updated from 2.3.1 to 2.4.0, including
|
||||||
|
security fixes.
|
||||||
|
|
||||||
|
LibTIFF in the macOS and Linux wheels has been updated from 4.1.0 to 4.2.0, including
|
||||||
|
security fixes discovered by fuzzers.
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
@ -33,46 +77,6 @@ With this release, a list of images can be provided to the ``append_images`` par
|
||||||
when saving, to replace the scaled down versions. This is the same functionality that
|
when saving, to replace the scaled down versions. This is the same functionality that
|
||||||
already exists for the ICNS format.
|
already exists for the ICNS format.
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
This release includes security fixes.
|
|
||||||
|
|
||||||
* An out-of-bounds read when saving TIFFs with custom metadata through LibTIFF
|
|
||||||
* An out-of-bounds read when saving a GIF of 1px width
|
|
||||||
* :cve:`2020-35653` Buffer read overrun in PCX decoding
|
|
||||||
|
|
||||||
The PCX image decoder used the reported image stride to calculate the row buffer,
|
|
||||||
rather than calculating it from the image size. This issue dates back to the PIL fork.
|
|
||||||
Thanks to Google's `OSS-Fuzz`_ project for finding this.
|
|
||||||
|
|
||||||
* :cve:`2020-35654` Fix TIFF out-of-bounds write error
|
|
||||||
|
|
||||||
Out-of-bounds write in ``TiffDecode.c`` when reading corrupt YCbCr files in some
|
|
||||||
LibTIFF versions (4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04). In some cases
|
|
||||||
LibTIFF's interpretation of the file is different when reading in RGBA mode, leading to
|
|
||||||
an out-of-bounds write in ``TiffDecode.c``. This potentially affects Pillow versions
|
|
||||||
from 6.0.0 to 8.0.1, depending on the version of LibTIFF. This was reported through
|
|
||||||
`Tidelift`_.
|
|
||||||
|
|
||||||
* :cve:`2020-35655` Fix for SGI Decode buffer overrun
|
|
||||||
|
|
||||||
4 byte read overflow in ``SgiRleDecode.c``, where the code was not correctly checking the
|
|
||||||
offsets and length tables. Independently reported through `Tidelift`_ and Google's
|
|
||||||
`OSS-Fuzz`_. This vulnerability covers Pillow versions 4.3.0->8.0.1.
|
|
||||||
|
|
||||||
.. _Tidelift: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pillow&utm_medium=referral&utm_campaign=docs
|
|
||||||
.. _OSS-Fuzz: https://github.com/google/oss-fuzz
|
|
||||||
|
|
||||||
Dependencies
|
|
||||||
^^^^^^^^^^^^
|
|
||||||
|
|
||||||
OpenJPEG in the macOS and Linux wheels has been updated from 2.3.1 to 2.4.0, including
|
|
||||||
security fixes.
|
|
||||||
|
|
||||||
LibTIFF in the macOS and Linux wheels has been updated from 4.1.0 to 4.2.0, including
|
|
||||||
security fixes discovered by fuzzers.
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -4,21 +4,33 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
:cve:`2021-25289`: The previous fix for :cve:`2020-35654` was insufficient
|
:cve:`2021-25289`: Correct the fix for :cve:`2020-35654`
|
||||||
due to incorrect error checking in ``TiffDecode.c``.
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
:cve:`2021-25290`: In ``TiffDecode.c``, there is a negative-offset ``memcpy``
|
The previous fix for :cve:`2020-35654` was insufficient due to incorrect
|
||||||
with an invalid size.
|
error checking in ``TiffDecode.c``.
|
||||||
|
|
||||||
:cve:`2021-25291`: In ``TiffDecode.c``, invalid tile boundaries could lead to
|
:cve:`2021-25290`: Fix buffer overflow in ``TiffDecode.c``
|
||||||
an out-of-bounds read in ``TIFFReadRGBATile``.
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
:cve:`2021-25292`: The PDF parser has a catastrophic backtracking regex
|
In ``TiffDecode.c``, there is a negative-offset ``memcpy`` with an invalid size.
|
||||||
that could be used as a DOS attack.
|
|
||||||
|
|
||||||
:cve:`2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``,
|
:cve:`2021-25291`: Fix buffer overflow in ``TIFFReadRGBATile``
|
||||||
since Pillow 4.3.0.
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In ``TiffDecode.c``, invalid tile boundaries could lead to an out-of-bounds
|
||||||
|
read in ``TIFFReadRGBATile``.
|
||||||
|
|
||||||
|
:cve:`2021-25292`: Fix DOS attack
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The PDF parser has a catastrophic backtracking regex that could be used as a
|
||||||
|
DOS attack.
|
||||||
|
|
||||||
|
:cve:`2021-25293`: Fix buffer overflow in ``SgiRleDecode.c``
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
There is an out-of-bounds read in ``SgiRleDecode.c`` since Pillow 4.3.0.
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -4,9 +4,12 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
There is an exhaustion of memory DOS in the BLP (:cve:`2021-27921`),
|
:cve:`2021-27921`, :cve:`2021-27922`, :cve:`2021-27923`: Fix DOS attacks
|
||||||
ICNS (:cve:`2021-27922`) and ICO (:cve:`2021-27923`) container formats
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
There is an exhaustion of memory DOS attack in BLP, ICNS, ICO images
|
||||||
where Pillow did not properly check the reported size of the contained image.
|
where Pillow did not properly check the reported size of the contained image.
|
||||||
These images could cause arbitrarily large memory allocations. This was reported
|
These images could cause arbitrarily large memory allocations.
|
||||||
by Jiayi Lin, Luke Shaffer, Xinran Xie, and Akshay Ajayan of
|
|
||||||
`Arizona State University <https://www.asu.edu/>`_.
|
These issues were reported by Jiayi Lin, Luke Shaffer, Xinran Xie and
|
||||||
|
Akshay Ajayan of `Arizona State University <https://www.asu.edu/>`_.
|
||||||
|
|
|
@ -1,6 +1,60 @@
|
||||||
8.2.0
|
8.2.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
These issues were all found with `OSS-Fuzz`_.
|
||||||
|
|
||||||
|
:cve:`2021-25287`, :cve:`2021-25288`: OOB read in Jpeg2KDecode
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* For J2k images with multiple bands, it's legal to have different widths for each band,
|
||||||
|
e.g. 1 byte for ``L``, 4 bytes for ``A``.
|
||||||
|
* This dates to Pillow 2.4.0.
|
||||||
|
|
||||||
|
:cve:`2021-28675`: DOS attack in PsdImagePlugin
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* :py:class:`.PsdImagePlugin.PsdImageFile` did not sanity check the number of input
|
||||||
|
layers with regard to the size of the data block, this could lead to a
|
||||||
|
denial-of-service on :py:meth:`~PIL.Image.open` prior to
|
||||||
|
:py:meth:`~PIL.Image.Image.load`.
|
||||||
|
* This dates to the PIL fork.
|
||||||
|
|
||||||
|
:cve:`2021-28676`: FLI image DOS attack
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* ``FliDecode.c`` did not properly check that the block advance was non-zero,
|
||||||
|
potentially leading to an infinite loop on load.
|
||||||
|
* This dates to the PIL fork.
|
||||||
|
|
||||||
|
:cve:`2021-28677`: EPS DOS on _open
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* The readline used in EPS has to deal with any combination of ``\r`` and ``\n`` as line
|
||||||
|
endings. It accidentally used a quadratic method of accumulating lines while looking
|
||||||
|
for a line ending.
|
||||||
|
* A malicious EPS file could use this to perform a denial-of-service of Pillow in the
|
||||||
|
open phase, before an image was accepted for opening.
|
||||||
|
* This dates to the PIL fork.
|
||||||
|
|
||||||
|
:cve:`2021-28678`: BLP DOS attack
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* ``BlpImagePlugin`` did not properly check that reads after jumping to file offsets
|
||||||
|
returned data. This could lead to a denial-of-service where the decoder could be run a
|
||||||
|
large number of times on empty data.
|
||||||
|
* This dates to Pillow 5.1.0.
|
||||||
|
|
||||||
|
Fix memory DOS in ImageFont
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* A corrupt or specially crafted TTF font could have font metrics that lead to
|
||||||
|
unreasonably large sizes when rendering text in font. ``ImageFont.py`` did not check
|
||||||
|
the image size before allocating memory for it.
|
||||||
|
* This dates to the PIL fork.
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
@ -123,61 +177,6 @@ be specified through a keyword argument::
|
||||||
|
|
||||||
im.save("out.tif", icc_profile=...)
|
im.save("out.tif", icc_profile=...)
|
||||||
|
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
These were all found with `OSS-Fuzz`_.
|
|
||||||
|
|
||||||
:cve:`2021-25287`, :cve:`2021-25288`: Fix OOB read in Jpeg2KDecode
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
* For J2k images with multiple bands, it's legal to have different widths for each band,
|
|
||||||
e.g. 1 byte for ``L``, 4 bytes for ``A``.
|
|
||||||
* This dates to Pillow 2.4.0.
|
|
||||||
|
|
||||||
:cve:`2021-28675`: Fix DOS in PsdImagePlugin
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
* :py:class:`.PsdImagePlugin.PsdImageFile` did not sanity check the number of input
|
|
||||||
layers with regard to the size of the data block, this could lead to a
|
|
||||||
denial-of-service on :py:meth:`~PIL.Image.open` prior to
|
|
||||||
:py:meth:`~PIL.Image.Image.load`.
|
|
||||||
* This dates to the PIL fork.
|
|
||||||
|
|
||||||
:cve:`2021-28676`: Fix FLI DOS
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
* ``FliDecode.c`` did not properly check that the block advance was non-zero,
|
|
||||||
potentially leading to an infinite loop on load.
|
|
||||||
* This dates to the PIL fork.
|
|
||||||
|
|
||||||
:cve:`2021-28677`: Fix EPS DOS on _open
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
* The readline used in EPS has to deal with any combination of ``\r`` and ``\n`` as line
|
|
||||||
endings. It accidentally used a quadratic method of accumulating lines while looking
|
|
||||||
for a line ending.
|
|
||||||
* A malicious EPS file could use this to perform a denial-of-service of Pillow in the
|
|
||||||
open phase, before an image was accepted for opening.
|
|
||||||
* This dates to the PIL fork.
|
|
||||||
|
|
||||||
:cve:`2021-28678`: Fix BLP DOS
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
* ``BlpImagePlugin`` did not properly check that reads after jumping to file offsets
|
|
||||||
returned data. This could lead to a denial-of-service where the decoder could be run a
|
|
||||||
large number of times on empty data.
|
|
||||||
* This dates to Pillow 5.1.0.
|
|
||||||
|
|
||||||
Fix memory DOS in ImageFont
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
* A corrupt or specially crafted TTF font could have font metrics that lead to
|
|
||||||
unreasonably large sizes when rendering text in font. ``ImageFont.py`` did not check
|
|
||||||
the image size before allocating memory for it.
|
|
||||||
* This dates to the PIL fork.
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,27 @@
|
||||||
8.3.0
|
8.3.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
:cve:`2021-34552`: Fix buffer overflow
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
PIL since 1.1.4 and Pillow since 1.0 allowed parameters passed into a convert
|
||||||
|
function to trigger buffer overflow in ``Convert.c``.
|
||||||
|
|
||||||
|
Parsing XML
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
Pillow previously parsed XMP data using Python's ``xml`` module. However, this module
|
||||||
|
is not secure.
|
||||||
|
|
||||||
|
- :py:meth:`~PIL.Image.Image.getexif` has used ``xml`` to potentially retrieve
|
||||||
|
orientation data since Pillow 7.2.0. It has been refactored to use ``re`` instead.
|
||||||
|
- :py:meth:`~PIL.JpegImagePlugin.JpegImageFile.getxmp` was added in Pillow 8.2.0. It
|
||||||
|
will now use ``defusedxml`` instead. If the dependency is not present, an empty
|
||||||
|
dictionary will be returned and a warning raised.
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
@ -79,28 +100,6 @@ format, through the new ``bitmap_format`` argument::
|
||||||
|
|
||||||
im.save("out.ico", bitmap_format="bmp")
|
im.save("out.ico", bitmap_format="bmp")
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
Buffer overflow
|
|
||||||
^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
This release addresses :cve:`2021-34552`. PIL since 1.1.4 and Pillow since 1.0
|
|
||||||
allowed parameters passed into a convert function to trigger buffer overflow in
|
|
||||||
Convert.c.
|
|
||||||
|
|
||||||
Parsing XML
|
|
||||||
^^^^^^^^^^^
|
|
||||||
|
|
||||||
Pillow previously parsed XMP data using Python's ``xml`` module. However, this module
|
|
||||||
is not secure.
|
|
||||||
|
|
||||||
- :py:meth:`~PIL.Image.Image.getexif` has used ``xml`` to potentially retrieve
|
|
||||||
orientation data since Pillow 7.2.0. It has been refactored to use ``re`` instead.
|
|
||||||
- :py:meth:`~PIL.JpegImagePlugin.JpegImageFile.getxmp` was added in Pillow 8.2.0. It
|
|
||||||
will now use ``defusedxml`` instead. If the dependency is not present, an empty
|
|
||||||
dictionary will be returned and a warning raised.
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Fixed regression converting to NumPy arrays
|
Fixed regression converting to NumPy arrays
|
||||||
===========================================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This fixes a regression introduced in 8.3.0 when converting an image to a NumPy array
|
This fixes a regression introduced in 8.3.0 when converting an image to a NumPy array
|
||||||
with a ``dtype`` argument.
|
with a ``dtype`` argument.
|
||||||
|
@ -19,7 +19,7 @@ with a ``dtype`` argument.
|
||||||
>>>
|
>>>
|
||||||
|
|
||||||
Catch OSError when checking if destination is sys.stdout
|
Catch OSError when checking if destination is sys.stdout
|
||||||
========================================================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
In 8.3.0, a check to see if the destination was ``sys.stdout`` when saving an image was
|
In 8.3.0, a check to see if the destination was ``sys.stdout`` when saving an image was
|
||||||
updated. This lead to an :py:exc:`OSError` being raised if the environment restricted
|
updated. This lead to an :py:exc:`OSError` being raised if the environment restricted
|
||||||
|
@ -28,7 +28,7 @@ access.
|
||||||
The :py:exc:`OSError` is now silently caught.
|
The :py:exc:`OSError` is now silently caught.
|
||||||
|
|
||||||
Fixed removing orientation in ImageOps.exif_transpose
|
Fixed removing orientation in ImageOps.exif_transpose
|
||||||
=====================================================
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
In 8.3.0, :py:meth:`~PIL.ImageOps.exif_transpose` was changed to ensure that the
|
In 8.3.0, :py:meth:`~PIL.ImageOps.exif_transpose` was changed to ensure that the
|
||||||
original image EXIF data was not modified, and the orientation was only removed from
|
original image EXIF data was not modified, and the orientation was only removed from
|
||||||
|
|
|
@ -4,14 +4,21 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
* :cve:`2021-23437`: Avoid a potential ReDoS (regular expression denial of service)
|
:cve:`2021-23437`: Avoid potential ReDoS (regular expression denial of service)
|
||||||
in :py:class:`~PIL.ImageColor`'s :py:meth:`~PIL.ImageColor.getrgb` by raising
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
:py:exc:`ValueError` if the color specifier is too long. Present since Pillow 5.2.0.
|
|
||||||
|
|
||||||
* Fix 6-byte out-of-bounds (OOB) read. The previous bounds check in ``FliDecode.c``
|
Avoid a potential ReDoS (regular expression denial of service) in :py:class:`~PIL.ImageColor`'s
|
||||||
incorrectly calculated the required read buffer size when copying a chunk, potentially
|
:py:meth:`~PIL.ImageColor.getrgb` by raising :py:exc:`ValueError` if the color specifier is
|
||||||
reading six extra bytes off the end of the allocated buffer from the heap. Present
|
too long. Present since Pillow 5.2.0.
|
||||||
since Pillow 7.1.0. This bug was found by Google's `OSS-Fuzz`_ `CIFuzz`_ runs.
|
|
||||||
|
Fix 6-byte out-of-bounds (OOB) read
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Fix 6-byte out-of-bounds (OOB) read. The previous bounds check in ``FliDecode.c`` incorrectly
|
||||||
|
calculated the required read buffer size when copying a chunk, potentially reading six extra
|
||||||
|
bytes off the end of the allocated buffer from the heap. Present since Pillow 7.1.0.
|
||||||
|
|
||||||
|
This bug was found by Google's `OSS-Fuzz`_ `CIFuzz`_ runs.
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
8.4.0
|
8.4.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
API Changes
|
|
||||||
===========
|
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
^^^^^^^^^^^^
|
============
|
||||||
|
|
||||||
ImagePalette size parameter
|
ImagePalette size parameter
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The ``size`` parameter will be removed in Pillow 10.0.0 (2023-07-01).
|
The ``size`` parameter will be removed in Pillow 10.0.0 (2023-07-01).
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,41 @@ success of Python.
|
||||||
|
|
||||||
Thank you, Fredrik.
|
Thank you, Fredrik.
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
Ensure JpegImagePlugin stops at the end of a truncated file
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``JpegImagePlugin`` may append an EOF marker to the end of a truncated file, so that
|
||||||
|
the last segment of the data will still be processed by the decoder.
|
||||||
|
|
||||||
|
If the EOF marker is not detected as such however, this could lead to an infinite
|
||||||
|
loop where ``JpegImagePlugin`` keeps trying to end the file.
|
||||||
|
|
||||||
|
Remove consecutive duplicate tiles that only differ by their offset
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
To prevent attempts to slow down loading times for images, if an image has consecutive
|
||||||
|
duplicate tiles that only differ by their offset, only load the last tile. Credit to
|
||||||
|
Google's `OSS-Fuzz`_ project for finding this issue.
|
||||||
|
|
||||||
|
:cve:`2022-22817`: Restrict builtins available to ImageMath.eval
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
To limit :py:class:`PIL.ImageMath` to working with images, Pillow
|
||||||
|
will now restrict the builtins available to :py:meth:`!PIL.ImageMath.eval`. This will
|
||||||
|
help prevent problems arising if users evaluate arbitrary expressions, such as
|
||||||
|
``ImageMath.eval("exec(exit())")``.
|
||||||
|
|
||||||
|
:cve:`2022-22815`, :cve:`2022-22816`: ImagePath.Path array handling
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
:cve:`2022-22815` (:cwe:`126`) and :cve:`2022-22816` (:cwe:`665`) were found when
|
||||||
|
initializing ``ImagePath.Path``.
|
||||||
|
|
||||||
|
.. _OSS-Fuzz: https://github.com/google/oss-fuzz
|
||||||
|
|
||||||
Backwards Incompatible Changes
|
Backwards Incompatible Changes
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
|
@ -97,41 +132,6 @@ Support has been added for the "title" argument in
|
||||||
argument will also now be supported, e.g. ``im.show(title="My Image")`` and
|
argument will also now be supported, e.g. ``im.show(title="My Image")`` and
|
||||||
``ImageShow.show(im, title="My Image")``.
|
``ImageShow.show(im, title="My Image")``.
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
Ensure JpegImagePlugin stops at the end of a truncated file
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
``JpegImagePlugin`` may append an EOF marker to the end of a truncated file, so that
|
|
||||||
the last segment of the data will still be processed by the decoder.
|
|
||||||
|
|
||||||
If the EOF marker is not detected as such however, this could lead to an infinite
|
|
||||||
loop where ``JpegImagePlugin`` keeps trying to end the file.
|
|
||||||
|
|
||||||
Remove consecutive duplicate tiles that only differ by their offset
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
To prevent attempts to slow down loading times for images, if an image has consecutive
|
|
||||||
duplicate tiles that only differ by their offset, only load the last tile. Credit to
|
|
||||||
Google's `OSS-Fuzz`_ project for finding this issue.
|
|
||||||
|
|
||||||
Restrict builtins available to ImageMath.eval
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
:cve:`2022-22817`: To limit :py:class:`PIL.ImageMath` to working with images, Pillow
|
|
||||||
will now restrict the builtins available to :py:meth:`PIL.ImageMath.eval`. This will
|
|
||||||
help prevent problems arising if users evaluate arbitrary expressions, such as
|
|
||||||
``ImageMath.eval("exec(exit())")``.
|
|
||||||
|
|
||||||
Fixed ImagePath.Path array handling
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
:cve:`2022-22815` (:cwe:`126`) and :cve:`2022-22816` (:cwe:`665`) were
|
|
||||||
found when initializing ``ImagePath.Path``.
|
|
||||||
|
|
||||||
.. _OSS-Fuzz: https://github.com/google/oss-fuzz
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,20 @@ Security
|
||||||
|
|
||||||
This release addresses several security problems.
|
This release addresses several security problems.
|
||||||
|
|
||||||
:cve:`2022-24303`: If the path to the temporary directory on Linux or macOS
|
:cve:`2022-24303`: Temp image removal
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
If the path to the temporary directory on Linux or macOS
|
||||||
contained a space, this would break removal of the temporary image file after
|
contained a space, this would break removal of the temporary image file after
|
||||||
``im.show()`` (and related actions), and potentially remove an unrelated file. This
|
``im.show()`` (and related actions), and potentially remove an unrelated file. This
|
||||||
has been present since PIL.
|
has been present since PIL.
|
||||||
|
|
||||||
:cve:`2022-22817`: While Pillow 9.0 restricted top-level builtins available to
|
:cve:`2022-22817`: Restrict lambda expressions
|
||||||
:py:meth:`PIL.ImageMath.eval`, it did not prevent builtins available to lambda
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
expressions. These are now also restricted.
|
|
||||||
|
While Pillow 9.0 restricted top-level builtins available to
|
||||||
|
:py:meth:`!PIL.ImageMath.eval`, it did not prevent builtins
|
||||||
|
available to lambda expressions. These are now also restricted.
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -1,49 +1,6 @@
|
||||||
9.1.0
|
9.1.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
API Changes
|
|
||||||
===========
|
|
||||||
|
|
||||||
Raise an error when performing a negative crop
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now
|
|
||||||
it will raise a :py:exc:`ValueError`, to help reduce confusion if a user has unintentionally
|
|
||||||
provided the wrong arguments.
|
|
||||||
|
|
||||||
Added specific error if path coordinate type is incorrect
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Rather than returning a :py:exc:`SystemError`, passing the incorrect types of coordinates into
|
|
||||||
a path will now raise a more specific :py:exc:`ValueError`, with the message "incorrect
|
|
||||||
coordinate type".
|
|
||||||
|
|
||||||
Replace requirements.txt with extras
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
Rather than installing all dependencies for docs and tests via ``requirements.txt``,
|
|
||||||
``extras_require`` is used instead. This installs only those needed and at the same
|
|
||||||
time as installing Pillow.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
# Install with dependencies for tests:
|
|
||||||
python3 -m pip install .[tests]
|
|
||||||
|
|
||||||
# Or for building docs:
|
|
||||||
python3 -m pip install .[docs]
|
|
||||||
|
|
||||||
# Or for all:
|
|
||||||
python3 -m pip install .[docs,tests]
|
|
||||||
|
|
||||||
On macOS, the last argument may need to be wrapped in quotes, e.g.
|
|
||||||
``python3 -m pip install ".[tests]"``
|
|
||||||
|
|
||||||
Therefore ``requirements.txt`` has been removed along with the ``make install-req``
|
|
||||||
command for installing its contents.
|
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
@ -137,6 +94,49 @@ The stub image plugin ``FitsStubImagePlugin`` has been deprecated and will be re
|
||||||
Pillow 10.0.0 (2023-07-01). FITS images can be read without a handler through
|
Pillow 10.0.0 (2023-07-01). FITS images can be read without a handler through
|
||||||
:mod:`~PIL.FitsImagePlugin` instead.
|
:mod:`~PIL.FitsImagePlugin` instead.
|
||||||
|
|
||||||
|
API Changes
|
||||||
|
===========
|
||||||
|
|
||||||
|
Raise an error when performing a negative crop
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now
|
||||||
|
it will raise a :py:exc:`ValueError`, to help reduce confusion if a user has unintentionally
|
||||||
|
provided the wrong arguments.
|
||||||
|
|
||||||
|
Added specific error if path coordinate type is incorrect
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Rather than returning a :py:exc:`SystemError`, passing the incorrect types of coordinates into
|
||||||
|
a path will now raise a more specific :py:exc:`ValueError`, with the message "incorrect
|
||||||
|
coordinate type".
|
||||||
|
|
||||||
|
Replace requirements.txt with extras
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Rather than installing all dependencies for docs and tests via ``requirements.txt``,
|
||||||
|
``extras_require`` is used instead. This installs only those needed and at the same
|
||||||
|
time as installing Pillow.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
# Install with dependencies for tests:
|
||||||
|
python3 -m pip install .[tests]
|
||||||
|
|
||||||
|
# Or for building docs:
|
||||||
|
python3 -m pip install .[docs]
|
||||||
|
|
||||||
|
# Or for all:
|
||||||
|
python3 -m pip install .[docs,tests]
|
||||||
|
|
||||||
|
On macOS, the last argument may need to be wrapped in quotes, e.g.
|
||||||
|
``python3 -m pip install ".[tests]"``
|
||||||
|
|
||||||
|
Therefore ``requirements.txt`` has been removed along with the ``make install-req``
|
||||||
|
command for installing its contents.
|
||||||
|
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,19 @@
|
||||||
Security
|
Security
|
||||||
========
|
========
|
||||||
|
|
||||||
This release addresses several security problems.
|
This release addresses several security issues.
|
||||||
|
|
||||||
:cve:`2022-30595`: When reading a TGA file with RLE packets that cross scan lines,
|
:cve:`2022-30595`: Heap buffer overflow
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When reading a TGA file with RLE packets that cross scan lines,
|
||||||
Pillow reads the information past the end of the first line without deducting that
|
Pillow reads the information past the end of the first line without deducting that
|
||||||
from the length of the remaining file data. This vulnerability was introduced in Pillow
|
from the length of the remaining file data. This vulnerability was introduced in Pillow
|
||||||
9.1.0, and can cause a heap buffer overflow.
|
9.1.0, and can cause a heap buffer overflow.
|
||||||
|
|
||||||
|
Decompression bomb check fix
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Opening an image with a zero or negative height has been found to bypass a
|
Opening an image with a zero or negative height has been found to bypass a
|
||||||
decompression bomb check. This will now raise a :py:exc:`SyntaxError` instead, in turn
|
decompression bomb check. This will now raise a :py:exc:`SyntaxError` instead, in turn
|
||||||
raising a ``PIL.UnidentifiedImageError``.
|
raising a ``PIL.UnidentifiedImageError``.
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
9.2.0
|
9.2.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
An additional decompression bomb check has been added for the GIF format.
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
@ -132,11 +137,6 @@ with "transparency" in ``im.info``, and apply the transparency to the palette in
|
||||||
The image's palette mode will become "RGBA", and "transparency" will be removed from
|
The image's palette mode will become "RGBA", and "transparency" will be removed from
|
||||||
``im.info``.
|
``im.info``.
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
An additional decompression bomb check has been added for the GIF format.
|
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user