Merge branch 'main' into progress

This commit is contained in:
Andrew Murray 2024-04-02 09:16:02 +11:00 committed by GitHub
commit d52b3a2ce1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
146 changed files with 3209 additions and 1362 deletions

View File

@ -1,3 +1,10 @@
skip_commits:
files:
- ".github/**"
- ".gitmodules"
- "docs/**"
- "wheels/**"
version: '{build}'
clone_folder: c:\pillow
init:
@ -27,7 +34,7 @@ install:
- 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
- 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%
- cd c:\pillow\winbuild\
- ps: |

View File

@ -1 +1 @@
cibuildwheel==2.16.5
cibuildwheel==2.17.0

View File

@ -48,6 +48,21 @@ Thank you.
* Python:
* 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.

View File

@ -50,7 +50,7 @@ jobs:
uses: actions/checkout@v4
- name: Install Cygwin
uses: egor-tensin/setup-cygwin@v4
uses: cygwin/cygwin-install-action@v4
with:
packages: >
gcc-g++
@ -71,7 +71,6 @@ jobs:
make
netpbm
perl
python39=3.9.16-1
python3${{ matrix.python-minor-version }}-cffi
python3${{ matrix.python-minor-version }}-cython
python3${{ matrix.python-minor-version }}-devel
@ -89,21 +88,15 @@ jobs:
- name: Select Python version
run: |
ln -sf c:/tools/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/tools/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
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
- name: pip cache
uses: actions/cache@v4
with:
path: 'C:\cygwin\home\runneradmin\.cache\pip'
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-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: |
${{ 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
run: |
@ -113,11 +106,6 @@ jobs:
run: |
bash.exe .ci/install.sh
- name: Upgrade NumPy
shell: dash.exe -l "{0}"
run: |
python3 -m pip install -U "numpy<1.26"
- name: Build
shell: bash.exe -eo pipefail -o igncr "{0}"
run: |

View File

@ -35,7 +35,7 @@ jobs:
strategy:
fail-fast: false
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
@ -86,7 +86,7 @@ jobs:
choco install nasm --no-progress
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
# Install extra test images

View File

@ -16,7 +16,7 @@ ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds
FREETYPE_VERSION=2.13.2
HARFBUZZ_VERSION=8.3.0
HARFBUZZ_VERSION=8.4.0
LIBPNG_VERSION=1.6.43
JPEGTURBO_VERSION=3.0.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
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 libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
if [[ "$CIBW_ARCHS" == "arm64" ]]; then

View File

@ -5,6 +5,7 @@ on:
paths:
- ".ci/requirements-cibw.txt"
- ".github/workflows/wheel*"
- "setup.py"
- "wheels/*"
- "winbuild/build_prepare.py"
- "winbuild/fribidi.cmake"
@ -14,6 +15,7 @@ on:
paths:
- ".ci/requirements-cibw.txt"
- ".github/workflows/wheel*"
- "setup.py"
- "wheels/*"
- "winbuild/build_prepare.py"
- "winbuild/fribidi.cmake"

View File

@ -1,24 +1,24 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.0
rev: v0.3.4
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.1.1
rev: 24.3.0
hooks:
- id: black
- repo: https://github.com/PyCQA/bandit
rev: 1.7.7
rev: 1.7.8
hooks:
- id: bandit
args: [--severity-level=high]
files: ^src/
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.4
rev: v1.5.5
hooks:
- id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
@ -42,6 +42,13 @@ repos:
- id: trailing-whitespace
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
rev: v0.9.1
hooks:
@ -62,5 +69,10 @@ repos:
hooks:
- id: tox-ini-fmt
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes
ci:
autoupdate_schedule: monthly

View File

@ -2,9 +2,93 @@
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
[radarhere]
@ -4262,7 +4346,7 @@ Changelog (Pillow)
- Documentation changes, URL update, transpose, release checklist
[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]
- 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).
- 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.
- Modes "1" (bilevel) and "P" (palette) have been introduced. Note

View File

@ -1,11 +1,11 @@
The Python Imaging Library (PIL) is
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
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:

View File

@ -6,9 +6,9 @@
## 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).
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
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).

View File

@ -32,9 +32,8 @@ def timer(func, label, *args) -> None:
break
endtime = time.time()
print(
"{}: completed {} iterations in {:.4f}s, {:.6f}s per iteration".format(
label, x + 1, endtime - starttime, (endtime - starttime) / (x + 1.0)
)
f"{label}: completed {x + 1} iterations in {endtime - starttime:.4f}s, "
f"{(endtime - starttime) / (x + 1.0):.6f}s per iteration"
)

View File

@ -11,6 +11,7 @@ import subprocess
import sys
import sysconfig
import tempfile
from functools import lru_cache
from io import BytesIO
from typing import Any, Callable, Sequence
@ -114,7 +115,9 @@ def assert_image_similar(
diff = 0
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()))
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()
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:
# Always return fresh not-yet-loaded version of image.
# Operations on not-yet-loaded images is separate class of errors
# what we should catch.
# Operations on not-yet-loaded images are a separate class of errors
# that we should catch.
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
# (for fast, isolated, repeatable tests).
im = cache.get(mode)
if im is None:
if mode == "F":
im = hopper("L").convert(mode)
elif mode[:4] == "I;16":
im = hopper("I").convert(mode)
else:
im = hopper().convert(mode)
cache[mode] = im
return im.copy()
return _cached_hopper(mode).copy()
@lru_cache
def _cached_hopper(mode: str) -> Image.Image:
if mode == "F":
im = hopper("L")
else:
im = hopper()
return im.convert(mode)
def djpeg_available() -> bool:

BIN
Tests/icc/sGrey-v2-nano.icc Normal file

Binary file not shown.

BIN
Tests/images/9bit.j2k Normal file

Binary file not shown.

BIN
Tests/images/m13.fits Normal file

Binary file not shown.

366
Tests/images/m13_gzip.fits Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View File

@ -7,7 +7,7 @@ import fuzzers
import packaging
import pytest
from PIL import Image, features
from PIL import Image, UnidentifiedImageError, features
from Tests.helper import skip_unless_feature
if sys.platform.startswith("win32"):
@ -43,7 +43,7 @@ def test_fuzz_images(path: str) -> None:
except (
Image.DecompressionBombError,
Image.DecompressionBombWarning,
Image.UnidentifiedImageError,
UnidentifiedImageError,
):
# Known Image.* exceptions
assert True

View File

@ -117,9 +117,10 @@ def test_unsupported_module() -> None:
features.version_module(module)
def test_pilinfo() -> None:
@pytest.mark.parametrize("supported_formats", (True, False))
def test_pilinfo(supported_formats) -> None:
buf = io.StringIO()
features.pilinfo(buf)
features.pilinfo(buf, supported_formats=supported_formats)
out = buf.getvalue()
lines = out.splitlines()
assert lines[0] == "-" * 68
@ -129,9 +130,15 @@ def test_pilinfo() -> None:
while lines[0].startswith(" "):
lines = lines[1:]
assert lines[0] == "-" * 68
assert lines[1].startswith("Python modules loaded from ")
assert lines[2].startswith("Binary modules loaded from ")
assert lines[3] == "-" * 68
assert lines[1].startswith("Python executable is")
lines = lines[2:]
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 = (
"\n"
+ "-" * 68
@ -142,4 +149,4 @@ def test_pilinfo() -> None:
+ "-" * 68
+ "\n"
)
assert jpeg in out
assert supported_formats == (jpeg in out)

View File

@ -5,7 +5,7 @@ from pathlib import Path
import pytest
from PIL import EpsImagePlugin, Image, features
from PIL import EpsImagePlugin, Image, UnidentifiedImageError, features
from .helper import (
assert_image_similar,
@ -419,7 +419,7 @@ def test_emptyline() -> None:
)
def test_timeout(test_file: str) -> None:
with open(test_file, "rb") as f:
with pytest.raises(Image.UnidentifiedImageError):
with pytest.raises(UnidentifiedImageError):
with Image.open(f):
pass

View File

@ -6,7 +6,7 @@ import pytest
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"
@ -22,6 +22,11 @@ def test_open() -> None:
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:
# Arrange
invalid_file = "Tests/images/flower.jpg"

View File

@ -825,7 +825,7 @@ class TestFileJpeg:
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
# Act / Assert
# "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)
def test_invalid_exif(self) -> None:

View File

@ -364,6 +364,16 @@ def test_subsampling_decode(name: str) -> None:
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:
with Image.open("Tests/images/comment.jp2") as im:
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)
length = _binary.i16be(hdr)
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)

View File

@ -6,13 +6,13 @@ import itertools
import os
import re
import sys
from collections import namedtuple
from pathlib import Path
from typing import Any, NamedTuple
import pytest
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 (
assert_image_equal,
@ -243,36 +243,40 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.WRITE_LIBTIFF = False
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 = {
37000 + k: v
for k, v in enumerate(
[
tc(4, TiffTags.SHORT, True),
tc(123456789, TiffTags.LONG, True),
tc(-4, TiffTags.SIGNED_BYTE, False),
tc(-4, TiffTags.SIGNED_SHORT, False),
tc(-123456789, TiffTags.SIGNED_LONG, False),
tc(TiffImagePlugin.IFDRational(4, 7), TiffTags.RATIONAL, True),
tc(4.25, TiffTags.FLOAT, True),
tc(4.25, TiffTags.DOUBLE, True),
tc("custom tag value", TiffTags.ASCII, True),
tc(b"custom tag value", TiffTags.BYTE, True),
tc((4, 5, 6), TiffTags.SHORT, True),
tc((123456789, 9, 34, 234, 219387, 92432323), TiffTags.LONG, True),
tc((-4, 9, 10), TiffTags.SIGNED_BYTE, False),
tc((-4, 5, 6), TiffTags.SIGNED_SHORT, False),
tc(
Tc(4, TiffTags.SHORT, True),
Tc(123456789, TiffTags.LONG, True),
Tc(-4, TiffTags.SIGNED_BYTE, False),
Tc(-4, TiffTags.SIGNED_SHORT, False),
Tc(-123456789, TiffTags.SIGNED_LONG, False),
Tc(TiffImagePlugin.IFDRational(4, 7), TiffTags.RATIONAL, True),
Tc(4.25, TiffTags.FLOAT, True),
Tc(4.25, TiffTags.DOUBLE, True),
Tc("custom tag value", TiffTags.ASCII, True),
Tc(b"custom tag value", TiffTags.BYTE, True),
Tc((4, 5, 6), TiffTags.SHORT, True),
Tc((123456789, 9, 34, 234, 219387, 92432323), TiffTags.LONG, True),
Tc((-4, 9, 10), TiffTags.SIGNED_BYTE, False),
Tc((-4, 5, 6), TiffTags.SIGNED_SHORT, False),
Tc(
(-123456789, 9, 34, 234, 219387, -92432323),
TiffTags.SIGNED_LONG,
False,
),
tc((4.25, 5.25), TiffTags.FLOAT, True),
tc((4.25, 5.25), TiffTags.DOUBLE, True),
Tc((4.25, 5.25), TiffTags.FLOAT, True),
Tc((4.25, 5.25), TiffTags.DOUBLE, True),
# array of TIFF_BYTE requires bytes instead of tuple for backwards
# compatibility
tc(bytes([4]), TiffTags.BYTE, True),
tc(bytes((4, 9, 10)), TiffTags.BYTE, True),
Tc(bytes([4]), TiffTags.BYTE, True),
Tc(bytes((4, 9, 10)), TiffTags.BYTE, True),
]
)
}
@ -325,6 +329,12 @@ class TestFileLibTiff(LibTiffTestCase):
)
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:
outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/g4_orientation_6.tif") as im:

View File

@ -93,7 +93,7 @@ def test_exif(test_file: str) -> None:
def test_frame_size() -> None:
# 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:
assert im.size == (640, 480)

View File

@ -241,13 +241,23 @@ def test_plain_ppm_token_too_long(tmp_path: Path, data: bytes) -> None:
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:
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(b"P3\n128 128\n255\n256")
with Image.open(path) as im:
with pytest.raises(ValueError):
with pytest.raises(ValueError, match="Channel value too large"):
im.load()

View File

@ -4,7 +4,7 @@ import warnings
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
@ -152,11 +152,11 @@ def test_combined_larger_than_size() -> None:
[
(
"Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd",
Image.UnidentifiedImageError,
UnidentifiedImageError,
),
(
"Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd",
Image.UnidentifiedImageError,
UnidentifiedImageError,
),
("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError),
("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError),

View File

@ -113,6 +113,10 @@ class TestFileTiff:
outfile = str(tmp_path / "temp.tif")
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:
ifd = TiffImagePlugin.ImageFileDirectory_v2()
with pytest.raises(Exception) as e:

View File

@ -151,3 +151,15 @@ def test_write_unsupported_mode_PA(tmp_path: Path) -> None:
target = im.convert("RGBA")
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()

View File

@ -188,3 +188,21 @@ def test_seek_errors() -> None:
with pytest.raises(EOFError):
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()

View File

@ -33,36 +33,38 @@ from .helper import (
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:
@pytest.mark.parametrize(
"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",
),
)
@pytest.mark.parametrize("mode", image_mode_names)
def test_image_modes_success(self, mode: str) -> None:
Image.new(mode, (1, 1))
@ -1042,6 +1044,35 @@ class TestImage:
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):
pass

View File

@ -183,6 +183,14 @@ def test_trns_RGB(tmp_path: Path) -> None:
assert im_l.info["transparency"] == im_l.getpixel((0, 0)) # undone
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")
assert "transparency" in im_p.info
im_p.save(f)
@ -191,6 +199,10 @@ def test_trns_RGB(tmp_path: Path) -> None:
assert "transparency" not in im_rgba.info
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)
assert "transparency" not in im_p.info
im_p.save(f)

View File

@ -16,11 +16,13 @@ pytestmark = pytest.mark.skipif(
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
)
ims = [
hopper(),
Image.open("Tests/images/transparent.png"),
Image.open("Tests/images/7x13.png"),
]
ims: list[Image.Image] = []
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:

View File

@ -94,6 +94,19 @@ def test_quantize_dither_diff() -> None:
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:
im = hopper()
colors = 2

View File

@ -186,7 +186,9 @@ def assert_compare_images(
bands = ImageMode.getmode(a.mode).bands
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()
average_diff = sum(i * num for i, num in enumerate(ch_hist)) / (

View File

@ -4,13 +4,14 @@ import datetime
import os
import re
import shutil
import sys
from io import BytesIO
from pathlib import Path
from typing import Any
import pytest
from PIL import Image, ImageMode, features
from PIL import Image, ImageMode, ImageWin, features
from .helper import (
assert_image,
@ -18,6 +19,7 @@ from .helper import (
assert_image_similar,
assert_image_similar_tofile,
hopper,
is_pypy,
)
try:
@ -213,6 +215,10 @@ def test_display_profile() -> None:
# try fetching the profile for the current display device
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:
ImageCms.createProfile("LAB", 5000)
@ -496,16 +502,34 @@ def test_non_ascii_path(tmp_path: Path) -> None:
def test_profile_typesafety() -> None:
"""Profile init type safety
prepatch, these would segfault, postpatch they should emit a typeerror
"""
# does not segfault
with pytest.raises(TypeError, match="Invalid type for Profile"):
ImageCms.ImageCmsProfile(0).tobytes()
with pytest.raises(TypeError, match="Invalid type for Profile"):
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(
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)
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"))
def test_rgb_lab(mode: str) -> None:
im = Image.new(mode, (1, 1))

View File

@ -868,8 +868,10 @@ def test_rounded_rectangle_zero_radius(bbox: Coords) -> None:
[
((20, 10, 80, 90), "x"),
((20, 10, 81, 90), "x_odd"),
((20, 10, 81.1, 90), "x_odd"),
((10, 20, 90, 80), "y"),
((10, 20, 90, 81), "y_odd"),
((10, 20, 90, 81.1), "y_odd"),
((20, 20, 80, 80), "both"),
],
)

View File

@ -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"

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

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

View File

@ -15,7 +15,7 @@ class TestLibPack:
mode: str,
rawmode: str,
data: int | bytes,
*pixels: int | float | tuple[int, ...],
*pixels: float | tuple[int, ...],
) -> None:
"""
data - either raw bytes with data or just number of bytes in rawmode.
@ -239,7 +239,7 @@ class TestLibUnpack:
mode: str,
rawmode: str,
data: int | bytes,
*pixels: int | float | tuple[int, ...],
*pixels: float | tuple[int, ...],
) -> None:
"""
data - either raw bytes with data or just number of bytes in rawmode.

View File

@ -19,7 +19,7 @@ from PIL import Image
# 7
# 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"

View File

@ -4,9 +4,16 @@ import os
import subprocess
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()
assert lines[0] == "-" * 68
assert lines[1].startswith("Pillow ")
@ -15,9 +22,15 @@ def test_main() -> None:
while lines[0].startswith(" "):
lines = lines[1:]
assert lines[0] == "-" * 68
assert lines[1].startswith("Python modules loaded from ")
assert lines[2].startswith("Binary modules loaded from ")
assert lines[3] == "-" * 68
assert lines[1].startswith("Python executable is")
lines = lines[2:]
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 = (
os.linesep
+ "-" * 68
@ -31,4 +44,4 @@ def test_main() -> None:
+ "-" * 68
+ os.linesep
)
assert jpeg in out
assert report == (jpeg not in out)

View File

@ -11,41 +11,12 @@ backend_class = build_wheel.__self__.__class__
class _CustomBuildMetaBackend(backend_class):
def run_setup(self, setup_script="setup.py"):
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)
def build_wheel(
@ -54,5 +25,15 @@ class _CustomBuildMetaBackend(backend_class):
self.config_settings = config_settings
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

View File

@ -1,11 +1,11 @@
The Python Imaging Library (PIL) is
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
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
Software License:

View File

@ -54,9 +54,10 @@ master_doc = "index"
# General information about the project.
project = "Pillow (PIL Fork)"
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
# |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
# if present. Example entries would be ('py:func', 'int') or
# ('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 ----------------------------------------------
@ -252,7 +258,7 @@ latex_documents = [
master_doc,
"PillowPILFork.tex",
"Pillow (PIL Fork) Documentation",
"Jeffrey A. Clark (Alex)",
"Jeffrey A. Clark",
"manual",
)
]
@ -302,7 +308,7 @@ texinfo_documents = [
"Pillow (PIL Fork) Documentation",
author,
"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",
)
]

View File

@ -92,6 +92,14 @@ Deprecated Use instead
: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
----------------

View File

@ -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.
**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
of effort put into the compression: 0 is the fastest, but gives larger
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**
Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 4.
@ -1335,7 +1339,8 @@ FITS
.. 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
^^^^^^^^
@ -1351,9 +1356,8 @@ The :py:meth:`~PIL.Image.open` method sets the following
FPX
^^^
Pillow reads Kodak FlashPix files. In the current version, only the highest
resolution image is read from the file, and the viewing transform is not taken
into account.
Pillow reads Kodak FlashPix files. Only the highest resolution image is read from the
file, and the viewing transform is not taken into account.
To enable FPX support, you must install :pypi:`olefile`.

View File

@ -1,7 +1,7 @@
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>`_.

View File

@ -266,9 +266,10 @@ After navigating to the Pillow directory, run::
Build Options
^^^^^^^^^^^^^
* Environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
multiprocessing to build the extension. Setting ``MAX_CONCURRENCY``
sets the number of CPUs to use, or can disable parallel building by
* Config setting: ``-C parallel=n``. Can also be given
with environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
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
available, as many as are present.
@ -293,14 +294,13 @@ Build Options
used to compile the standard Pillow wheels. Compiling libraqm requires
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
automated build systems that configure the proper paths in the
environment variables (e.g. Buildroot).
* Build flag: ``-C debug=true``. Adds a debugging flag to the include and
library search process to dump all paths searched for and found to
stdout.
* 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 stdout.
Sample usage::

View File

@ -73,7 +73,7 @@ can be easily displayed in a chromaticity diagram, for example).
:canonical: PIL._imagingcms.CmsProfile
.. 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).
@ -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).
.. py:attribute:: copyright
:type: Optional[str]
:type: str | None
The text copyright information for the profile (see 9.2.21 of ICC.1:2010).
.. py:attribute:: manufacturer
:type: Optional[str]
:type: str | None
The (English) display string for the device manufacturer (see
9.2.22 of ICC.1:2010).
.. py:attribute:: model
:type: Optional[str]
:type: str | None
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).
.. py:attribute:: profile_description
:type: Optional[str]
:type: str | None
The (English) display string for the profile description (see
9.2.41 of ICC.1:2010).
.. py:attribute:: target
:type: Optional[str]
:type: str | None
The name of the registered characterization data set, or the
measurement data for a characterization target (see 9.2.14 of
ICC.1:2010).
.. 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 value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. 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 value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. 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 value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. 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
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.
.. 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,
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.
.. 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
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).
.. py:attribute:: colorimetric_intent
:type: Optional[str]
:type: str | None
4-character string (padded with whitespace) identifying the image
state of PCS colorimetry produced using the colorimetric intent
transforms (see 9.2.20 of ICC.1:2010 for details).
.. py:attribute:: perceptual_rendering_intent_gamut
:type: Optional[str]
:type: str | None
4-character string (padded with whitespace) identifying the (one)
standard reference medium gamut (see 9.2.37 of ICC.1:2010 for
details).
.. py:attribute:: saturation_rendering_intent_gamut
:type: Optional[str]
:type: str | None
4-character string (padded with whitespace) identifying the (one)
standard reference medium gamut (see 9.2.37 of ICC.1:2010 for
details).
.. py:attribute:: technology
:type: Optional[str]
:type: str | None
4-character string (padded with whitespace) identifying the device
technology (see 9.2.47 of ICC.1:2010 for details).
.. 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
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.
.. py:attribute:: media_white_point_temperature
:type: Optional[float]
:type: float | None
Calculates the white point temperature (see the LCMS documentation
for more information).
.. py:attribute:: viewing_condition
:type: Optional[str]
:type: str | None
The (English) display string for the viewing conditions (see
9.2.48 of ICC.1:2010).
.. py:attribute:: screening_description
:type: Optional[str]
:type: str | None
The (English) display string for the screening conditions.
@ -307,21 +307,21 @@ can be easily displayed in a chromaticity diagram, for example).
version 4.
.. 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 value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. 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 value is in the format ``((X, Y, Z), (x, y, Y))``, if available.
.. 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).
@ -334,7 +334,7 @@ can be easily displayed in a chromaticity diagram, for example).
documentation on LCMS).
.. 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
the CLUT model.
@ -353,7 +353,7 @@ can be easily displayed in a chromaticity diagram, for example).
that intent is supported for that direction.
.. 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.
@ -372,7 +372,7 @@ can be easily displayed in a chromaticity diagram, for example).
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.

View File

@ -23,8 +23,7 @@ Example: Filter an image
Filters
-------
The current version of the library provides the following set of predefined
image enhancement filters:
Pillow provides the following set of predefined image enhancement filters:
* **BLUR**
* **CONTOUR**

View File

@ -4,9 +4,12 @@
:py:mod:`~PIL.ImageMath` Module
===============================
The :py:mod:`~PIL.ImageMath` module can be used to evaluate “image expressions”. The
module provides a single :py:meth:`~PIL.ImageMath.eval` function, which takes
an expression string and one or more images.
The :py:mod:`~PIL.ImageMath` module can be used to evaluate “image expressions”, that
can take a number of images and generate a result.
: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
--------------------------------------------------
@ -17,35 +20,69 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
with Image.open("image1.jpg") as im1:
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)
out.save("result.png")
.. py:function:: lambda_eval(expression, environment)
.. 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
single-layer images. To process multi-band images, use the
:py:meth:`~PIL.Image.Image.split` method or :py:func:`~PIL.Image.merge`
function.
.. py:function:: unsafe_eval(expression, environment)
Evaluates an image expression.
.. 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
syntax. In addition to the standard operators, you can
also use the functions described below.
:param environment: A dictionary that maps 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.
: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.
Expression syntax
-----------------
Expressions are standard Python expressions, but theyre evaluated in a
non-standard environment. You can use PIL methods as usual, plus the following
set of operators and functions:
* :py:meth:`lambda_eval` expressions are functions that receive a dictionary
containing images and operators.
* :py:meth:`unsafe_eval` expressions are standard Python expressions,
but theyre 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
^^^^^^^^^^^^^^^^^^

View File

@ -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
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
assignment, or item and slice deletion.
slicing as usual. However, this does not support slice assignment, or item
and slice deletion.
:param xy: A sequence. The sequence can contain 2-tuples [(x, y), ...]
or a flat list of numbers [x, y, ...].

View File

@ -1,6 +1,33 @@
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
==============================
@ -157,31 +184,6 @@ Added ``alpha_only`` argument to ``getbbox()``
and the image has an alpha channel, trim transparent pixels. Otherwise, trim
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
=============

View File

@ -4,11 +4,17 @@
Security
========
This release addresses :cve:`2023-4863`, by providing an updated install script and
updated wheels to include libwebp 1.3.2, preventing a potential heap buffer overflow
in WebP.
:cve:`2023-4863`: Updated install script and updated wheels
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
==============================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The release of zlib 1.3 caused one of the tests in the Pillow test suite to fail.

View File

@ -1,6 +1,38 @@
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
============
@ -63,38 +95,6 @@ JPEG tables-only streamtype
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.
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
=============

View File

@ -1,11 +1,24 @@
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
============
@ -45,13 +58,34 @@ Deprecated Use instead
: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
===========
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
=============
@ -63,14 +97,6 @@ Added PerspectiveTransform
that all of the :py:data:`~PIL.Image.Transform` values now have a corresponding
subclass of :py:class:`~PIL.ImageTransform.Transform`.
Security
========
TODO
^^^^
TODO
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
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.

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

View 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/>`__.

View 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/>`__.

View 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/>`__.

View File

@ -1,15 +1,14 @@
2.7.0
=====
-----
Sane Plugin
-----------
^^^^^^^^^^^
The Sane plugin has now been split into its own repo:
https://github.com/python-pillow/Sane .
Png text chunk size limits
--------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^
To prevent potential denial of service attacks using compressed text
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.
Image resizing filters
----------------------
^^^^^^^^^^^^^^^^^^^^^^
Image resizing methods :py:meth:`~PIL.Image.Image.resize` and
: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.
Bicubic and bilinear downscaling
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++++++++++++++++++++++++++++++++
From the beginning ``BILINEAR`` and ``BICUBIC`` filters were based on affine
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.
Antialias renamed to Lanczos
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++++++++++++++++++++++++++++
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``.
Lanczos upscaling quality
^^^^^^^^^^^^^^^^^^^^^^^^^
+++++++++++++++++++++++++
The image upscaling quality with ``LANCZOS`` filter was almost the same as
``BILINEAR`` due to a bug. This has been fixed.
Bicubic upscaling quality
^^^^^^^^^^^^^^^^^^^^^^^^^
+++++++++++++++++++++++++
The ``BICUBIC`` filter for affine transformations produced sharp, slightly
pixelated image for upscaling. Bicubic for convolutions is more soft.
Resize performance
^^^^^^^^^^^^^^^^^^
++++++++++++++++++
In most cases, convolution is more a expensive algorithm for downscaling
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.
Default filter for thumbnails
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+++++++++++++++++++++++++++++
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
@ -103,7 +102,7 @@ other filters gave poor quality for reduction. Starting from Pillow 2.7.0,
uses supersampling internally, not convolutions.
Image transposition
-------------------
+++++++++++++++++++
A new method ``TRANSPOSE`` has been added for the
: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.
Gaussian blur and unsharp mask
------------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :py:meth:`~PIL.ImageFilter.GaussianBlur` implementation has been replaced
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.
Blur radius
^^^^^^^^^^^
+++++++++++
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
@ -136,7 +135,7 @@ If you used a Gaussian blur with some radius value, you need to divide this
value by two.
Blur performance
^^^^^^^^^^^^^^^^
++++++++++++++++
Box filter computation time is constant relative to the radius and depends
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.
Blur quality
^^^^^^^^^^^^
++++++++++++
The previous implementation takes into account only source pixels within
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.
TIFF Parameter Changes
----------------------
^^^^^^^^^^^^^^^^^^^^^^
Several kwarg parameters for saving TIFF images were previously
specified as strings with included spaces (e.g. 'x resolution'). This

View File

@ -1,8 +1,8 @@
2.8.0
=====
-----
Open HTTP response objects with Image.open
------------------------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
HTTP response objects returned from ``urllib2.urlopen(url)`` or
``requests.get(url, stream=True).raw`` are 'file-like' but do not support ``.seek()``

View File

@ -1,9 +1,28 @@
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
-----------------------
^^^^^^^^^^^^^^^^^^^^^^^
There is now support for saving multipage images in the ``GIF`` and
``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)
Tiff ImageFileDirectory Rewrite
-------------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The Tiff ImageFileDirectory metadata code has been rewritten. Where
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
TiffImagePlugin.ImageFileDirectory_v2 class.
Deprecated Methods
------------------
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
----------------------------------------
LibJpeg and Zlib are 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

View File

@ -1,9 +1,8 @@
3.1.0
=====
-----
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``,
``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.
Consistent multiline text spacing
---------------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When using the ``ImageDraw`` multiline methods, the spacing between
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.
Exif, Jpeg and Tiff Metadata
----------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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

View File

@ -1,12 +1,14 @@
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
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
``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.
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
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.
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
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
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
possible to overflow an ``int32`` value passed into malloc.

View File

@ -1,13 +1,15 @@
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
large Jpeg2000 files, allowing for code execution or other memory
corruption (:cve:`2016-3076`).
:cve:`2016-3076`: Buffer overflow in Jpeg2KEncode.c
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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:

View File

@ -1,9 +1,8 @@
3.2.0
-----
New DDS and FTEX Image Plugins
==============================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``DdsImagePlugin`` reading DXT1 and DXT5 encoded ``.dds`` images was
added. DXT3 images are not currently supported.
@ -14,13 +13,13 @@ per file, in the ``.ftc`` (compressed) and ``.ftu`` (uncompressed)
formats.
Updates to the GbrImagePlugin
=============================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``GbrImagePlugin`` (GIMP brush format) has been updated to fix
support for version 1 files and add support for version 2 files.
Passthrough Parameters for ImageDraw.text
=========================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``ImageDraw.multiline_text`` and ``ImageDraw.multiline_size`` take extra
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.
ImageSequence.Iterator changes
==============================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``ImageSequence.Iterator`` is now an actual iterator implementing the
Iterator protocol. It is also now possible to seek to the first image

View File

@ -2,7 +2,7 @@
-----
Libimagequant support
=====================
^^^^^^^^^^^^^^^^^^^^^
There is now support for using libimagequant as a higher quality
quantization option in ``Image.quantize()`` on Unix-like
@ -12,21 +12,20 @@ differences.
New Setup.py options
====================
^^^^^^^^^^^^^^^^^^^^
There are two new options to control the ``build_ext`` task in ``setup.py``:
* ``--debug`` dumps all of the directories and files that are
checked when searching for libraries or headers when building the
extensions.
* ``--disable-platform-guessing`` removes many of the directories
that are checked for libraries and headers for build systems or
cross compilers that specify that information in via environment
variables.
* ``--debug`` dumps all of the directories and files that are
checked when searching for libraries or headers when building the
extensions.
* ``--disable-platform-guessing`` removes many of the directories
that are checked for libraries and headers for build systems or
cross compilers that specify that information in via environment
variables.
Resizing
========
^^^^^^^^
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
@ -36,19 +35,17 @@ makes resampling 60% faster on average.
Color calculation for images in the ``LA`` mode on semitransparent pixels
was fixed.
Rotation
========
^^^^^^^^
Rotation for angles divisible by 90 degrees now always uses transposition.
This greatly improves both quality and performance in this case.
Also, the bug with wrong image size calculation when rotating by 90 degrees
was fixed.
Image 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
from the behavior since ``3.0.0``.
from the behavior since 3.0.0.

View File

@ -1,9 +1,11 @@
3.3.2
=====
-----
Security
========
Integer overflow 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
@ -26,7 +28,7 @@ memory without duplicating the image first.
This issue was found by Cris Neckar at Divergent Security.
Sign Extension in Storage.c
---------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^
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

View File

@ -1,9 +1,32 @@
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
====================
^^^^^^^^^^^^^^^^^^^^
Two new filters available for ``Image.resize()`` and ``Image.thumbnail()``
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``.
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
================
^^^^^^^^^^^^^^^^
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
better performance.
Append images to GIF
====================
^^^^^^^^^^^^^^^^^^^^
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,
@ -42,16 +57,9 @@ in effect, e.g.::
im.save(out, save_all=True, append_images=[im1, im2, ...])
Save multiple frame TIFF
========================
^^^^^^^^^^^^^^^^^^^^^^^^
Multiple frames can now be saved in a TIFF file by using the ``save_all`` option.
e.g.::
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.

View File

@ -2,7 +2,7 @@
-----
Python 2.6 and 3.2 Dropped
==========================
^^^^^^^^^^^^^^^^^^^^^^^^^^
Pillow 4.0 no longer supports Python 2.6 and 3.2. We will not be
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.
Support added for Python 3.6
============================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Pillow 4.0 supports Python 3.6.
OleFileIO.py
============
^^^^^^^^^^^^
``OleFileIO.py`` has been removed as a vendored file and is now installed
from the upstream :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.
SGI image save
==============
^^^^^^^^^^^^^^
It is now possible to save images in modes ``L``, ``RGB``, and
``RGBA`` to the uncompressed SGI image format.
Zero sized images
=================
^^^^^^^^^^^^^^^^^
Pillow 3.4.0 removed support for creating images with (0,0) size. This
has been reenabled, restoring pre 3.4 behavior.
Internal handles_eof flag
=========================
^^^^^^^^^^^^^^^^^^^^^^^^^
The ``handles_eof flag`` for decoding images has been removed, as there
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.
Image.core.stretch removed
==========================
^^^^^^^^^^^^^^^^^^^^^^^^^^
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

View File

@ -1,8 +1,8 @@
4.1.0
-----
Removed Deprecated Items
========================
Deprecations
============
Several deprecated items have been removed.
@ -15,8 +15,11 @@ Several deprecated items have been removed.
``PIL.ImageDraw.ImageDraw.setfont`` have been removed.
Other Changes
=============
Closing Files When Opening Images
=================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The file handling when opening images has been overhauled. Previously,
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
will be closed in the ``close`` or ``__del__`` method.
Changes to GIF Handling When Saving
===================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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:
@ -56,14 +58,14 @@ This refactor fixed some bugs with palette handling when saving
multiple frame GIFs.
New Method: Image.remap_palette
===============================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The method :py:meth:`PIL.Image.Image.remap_palette()` has been
added. This method was hoisted from the GifImagePlugin code used to
optimize the palette.
Added Decoder Registry and Support for Python Based Decoders
============================================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There is now a decoder registry similar to the image plugin
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.
Tests
=====
^^^^^
Many tests have been added, including correctness tests for image
formats that have been previously untested.

View File

@ -2,7 +2,7 @@
-----
Fix Regression with reading DPI from EXIF data
==============================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
@ -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
well as rational values.
Incompatibility between 3.6.0 and 3.6.1
=======================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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

View File

@ -1,37 +1,11 @@
4.2.0
-----
Added Complex Text Rendering
============================
Backwards Incompatible Changes
==============================
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`.
Removed Deprecated Items
========================
Several deprecated items have been removed.
Several deprecated items have been removed
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* The methods ``PIL.ImageWin.Dib.fromstring``,
``PIL.ImageWin.Dib.tostring`` and
@ -44,8 +18,38 @@ Several deprecated items have been removed.
an :py:exc:`IOError` is raised.
Removed Core Image Function
===========================
^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
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`.

View File

@ -4,7 +4,7 @@
There are no functional changes in this release.
Fixed Windows PyPy Build
========================
^^^^^^^^^^^^^^^^^^^^^^^^
A change in the 4.2.0 cycle broke the Windows PyPy build. This has
been fixed, and PyPy is now part of the Windows CI matrix.

View File

@ -1,15 +1,6 @@
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
===========
@ -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
not store more than 4 8-bit channels of image data.
API Additions
=============
Append to PDF Files
^^^^^^^^^^^^^^^^^^^
Images can now be appended to PDF files in place by passing in
``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
=============

View File

@ -1,6 +1,53 @@
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
=============
@ -46,46 +93,6 @@ ImageGrab on multi-monitor Windows
An ``all_screens`` argument has been added to ``ImageGrab.grab``. If ``True``,
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
=============

View File

@ -18,8 +18,6 @@ Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python
Other Changes
=============
Support added for Python 3.8
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -4,15 +4,17 @@
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
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.
:cve:`2020-5310`, :cve:`2020-5311`, :cve:`2020-5312`, :cve:`2020-5313`: Overflow checks added
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Buffer overruns were found when processing an SGI (:cve:`2020-5311`),
PCX (:cve:`2020-5312`) or FLI image (:cve:`2020-5313`). Checks have been added
to prevent this.
Overflow checks have been added when calculating the size of a memory block to be reallocated
in the processing of TIFF, SGI, PCX and FLI images.
:cve:`2020-5310`: Overflow checks have been added when calculating the size of a
memory block to be reallocated in the processing of a TIFF image.
:cve:`2019-19911`: DOS attack vulnerability
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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.

View File

@ -1,6 +1,40 @@
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
===========
@ -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.
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
=============

View File

@ -2,7 +2,7 @@
-----
Fix regression seeking PNG files
================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This fixes a regression introduced in 7.1.0 when adding support for APNG files when calling
``seek`` and ``tell``:

View File

@ -2,7 +2,7 @@
-----
Fix another regression seeking PNG files
========================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This fixes a regression introduced in 7.1.0 when adding support for APNG files.

View File

@ -4,12 +4,13 @@
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,
introduced in FreeType version 2.6.
* A heap buffer overflow has been found in the handling of embedded PNG bitmaps,
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.

View File

@ -1,6 +1,50 @@
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
============
@ -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
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
=============

View File

@ -4,21 +4,33 @@
Security
========
:cve:`2021-25289`: The previous fix for :cve:`2020-35654` was insufficient
due to incorrect error checking in ``TiffDecode.c``.
:cve:`2021-25289`: Correct the fix for :cve:`2020-35654`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cve:`2021-25290`: In ``TiffDecode.c``, there is a negative-offset ``memcpy``
with an invalid size.
The previous fix for :cve:`2020-35654` was insufficient due to incorrect
error checking in ``TiffDecode.c``.
:cve:`2021-25291`: In ``TiffDecode.c``, invalid tile boundaries could lead to
an out-of-bounds read in ``TIFFReadRGBATile``.
:cve:`2021-25290`: Fix buffer overflow in ``TiffDecode.c``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cve:`2021-25292`: The PDF parser has a catastrophic backtracking regex
that could be used as a DOS attack.
In ``TiffDecode.c``, there is a negative-offset ``memcpy`` with an invalid size.
:cve:`2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``,
since Pillow 4.3.0.
:cve:`2021-25291`: Fix buffer overflow in ``TIFFReadRGBATile``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
=============

View File

@ -4,9 +4,12 @@
Security
========
There is an exhaustion of memory DOS in the BLP (:cve:`2021-27921`),
ICNS (:cve:`2021-27922`) and ICO (:cve:`2021-27923`) container formats
:cve:`2021-27921`, :cve:`2021-27922`, :cve:`2021-27923`: Fix DOS attacks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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.
These images could cause arbitrarily large memory allocations. This was reported
by Jiayi Lin, Luke Shaffer, Xinran Xie, and Akshay Ajayan of
`Arizona State University <https://www.asu.edu/>`_.
These images could cause arbitrarily large memory allocations.
These issues were reported by Jiayi Lin, Luke Shaffer, Xinran Xie and
Akshay Ajayan of `Arizona State University <https://www.asu.edu/>`_.

View File

@ -1,6 +1,60 @@
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
============
@ -123,61 +177,6 @@ be specified through a keyword argument::
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
=============

View File

@ -1,6 +1,27 @@
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
============
@ -79,28 +100,6 @@ format, through the new ``bitmap_format`` argument::
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
=============

View File

@ -2,7 +2,7 @@
-----
Fixed regression converting to NumPy arrays
===========================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This fixes a regression introduced in 8.3.0 when converting an image to a NumPy array
with a ``dtype`` argument.
@ -19,7 +19,7 @@ with a ``dtype`` argument.
>>>
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
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.
Fixed removing orientation in ImageOps.exif_transpose
=====================================================
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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

View File

@ -4,14 +4,21 @@
Security
========
* :cve:`2021-23437`: Avoid a 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.
:cve:`2021-23437`: Avoid potential ReDoS (regular expression denial of service)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* 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.
Avoid a 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
=============

View File

@ -1,14 +1,11 @@
8.4.0
-----
API Changes
===========
Deprecations
^^^^^^^^^^^^
============
ImagePalette size parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``size`` parameter will be removed in Pillow 10.0.0 (2023-07-01).

View File

@ -24,6 +24,41 @@ success of Python.
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
==============================
@ -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
``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
=============

View File

@ -6,14 +6,20 @@ Security
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
``im.show()`` (and related actions), and potentially remove an unrelated file. This
has been present since PIL.
:cve:`2022-22817`: 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.
:cve:`2022-22817`: Restrict lambda expressions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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
=============

View File

@ -1,49 +1,6 @@
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
============
@ -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
: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
=============

View File

@ -4,13 +4,19 @@
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
from the length of the remaining file data. This vulnerability was introduced in Pillow
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
decompression bomb check. This will now raise a :py:exc:`SyntaxError` instead, in turn
raising a ``PIL.UnidentifiedImageError``.

View File

@ -1,6 +1,11 @@
9.2.0
-----
Security
========
An additional decompression bomb check has been added for the GIF format.
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
``im.info``.
Security
========
An additional decompression bomb check has been added for the GIF format.
Other Changes
=============

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