Skip to content

Commit

Permalink
Merge branch 'main' into use-ptr
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed Sep 7, 2024
2 parents 8833548 + 6377321 commit 8dcf229
Show file tree
Hide file tree
Showing 65 changed files with 477 additions and 191 deletions.
1 change: 1 addition & 0 deletions .ci/requirements-mypy.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ numpy
packaging
pytest
sphinx
types-atheris
types-defusedxml
types-olefile
types-setuptools
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.0
rev: v0.6.3
hooks:
- id: ruff
args: [--exit-non-zero-on-fix]
Expand Down Expand Up @@ -50,7 +50,7 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/

- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.29.1
rev: 0.29.2
hooks:
- id: check-github-workflows
- id: check-readthedocs
Expand Down
27 changes: 27 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,33 @@ Changelog (Pillow)
11.0.0 (unreleased)
-------------------

- Return early from BoxBlur if either width or height is zero #8347
[radarhere]

- Check text is either string or bytes #8308
[radarhere]

- Added writing XMP bytes to JPEG #8286
[radarhere]

- Support JPEG2000 RGBA palettes #8256
[radarhere]

- Expand C image to match GIF frame image size #8237
[radarhere]

- Allow saving I;16 images as PPM #8231
[radarhere]

- When IFD is missing, connect get_ifd() dictionary to Exif #8230
[radarhere]

- Skip truncated ICO mask if LOAD_TRUNCATED_IMAGES is enabled #8180
[radarhere]

- Treat unknown JPEG2000 colorspace as unspecified #8343
[radarhere]

- Updated error message when saving WebP with invalid width or height #8322
[radarhere, hugovk]

Expand Down
Binary file added Tests/images/test_extents_transparency.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion Tests/oss-fuzz/fuzz_font.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@


import atheris
from atheris.import_hook import instrument_imports

with atheris.instrument_imports():
with instrument_imports():
import sys

import fuzzers
Expand Down
3 changes: 2 additions & 1 deletion Tests/oss-fuzz/fuzz_pillow.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@


import atheris
from atheris.import_hook import instrument_imports

with atheris.instrument_imports():
with instrument_imports():
import sys

import fuzzers
Expand Down
2 changes: 1 addition & 1 deletion Tests/oss-fuzz/python.supp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
<py3_8_encode_current_locale>
<py3_10_encode_current_locale>
Memcheck:Cond
...
fun:encode_current_locale
Expand Down
5 changes: 5 additions & 0 deletions Tests/test_box_blur.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ def test_color_modes() -> None:
box_blur(sample.convert("YCbCr"))


@pytest.mark.parametrize("size", ((0, 1), (1, 0)))
def test_zero_dimension(size: tuple[int, int]) -> None:
assert box_blur(Image.new("L", size)).size == size


def test_radius_0() -> None:
assert_blur(
sample,
Expand Down
39 changes: 31 additions & 8 deletions Tests/test_file_gif.py
Original file line number Diff line number Diff line change
Expand Up @@ -1378,16 +1378,39 @@ def test_lzw_bits() -> None:
im.load()


def test_extents() -> None:
with Image.open("Tests/images/test_extents.gif") as im:
assert im.size == (100, 100)
@pytest.mark.parametrize(
"test_file, loading_strategy",
(
("test_extents.gif", GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST),
(
"test_extents.gif",
GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY,
),
(
"test_extents_transparency.gif",
GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST,
),
),
)
def test_extents(
test_file: str, loading_strategy: GifImagePlugin.LoadingStrategy
) -> None:
GifImagePlugin.LOADING_STRATEGY = loading_strategy
try:
with Image.open("Tests/images/" + test_file) as im:
assert im.size == (100, 100)

# Check that n_frames does not change the size
assert im.n_frames == 2
assert im.size == (100, 100)
# Check that n_frames does not change the size
assert im.n_frames == 2
assert im.size == (100, 100)

im.seek(1)
assert im.size == (150, 150)
im.seek(1)
assert im.size == (150, 150)

im.load()
assert im.im.size == (150, 150)
finally:
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_AFTER_FIRST


def test_missing_background() -> None:
Expand Down
28 changes: 27 additions & 1 deletion Tests/test_file_ico.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import pytest

from PIL import IcoImagePlugin, Image, ImageDraw
from PIL import IcoImagePlugin, Image, ImageDraw, ImageFile

from .helper import assert_image_equal, assert_image_equal_tofile, hopper

Expand Down Expand Up @@ -241,3 +241,29 @@ def test_draw_reloaded(tmp_path: Path) -> None:

with Image.open(outfile) as im:
assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico")


def test_truncated_mask() -> None:
# 1 bpp
with open("Tests/images/hopper_mask.ico", "rb") as fp:
data = fp.read()

ImageFile.LOAD_TRUNCATED_IMAGES = True
data = data[:-3]

try:
with Image.open(io.BytesIO(data)) as im:
with Image.open("Tests/images/hopper_mask.png") as expected:
assert im.mode == "1"

# 32 bpp
output = io.BytesIO()
expected = hopper("RGBA")
expected.save(output, "ico", bitmap_format="bmp")

data = output.getvalue()[:-1]

with Image.open(io.BytesIO(data)) as im:
assert im.mode == "RGB"
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False
15 changes: 15 additions & 0 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,21 @@ def test_getxmp_padded(self) -> None:
else:
assert im.getxmp() == {"xmpmeta": None}

def test_save_xmp(self, tmp_path: Path) -> None:
f = str(tmp_path / "temp.jpg")
im = hopper()
im.save(f, xmp=b"XMP test")
with Image.open(f) as reloaded:
assert reloaded.info["xmp"] == b"XMP test"

im.info["xmp"] = b"1" * 65504
im.save(f)
with Image.open(f) as reloaded:
assert reloaded.info["xmp"] == b"1" * 65504

with pytest.raises(ValueError):
im.save(f, xmp=b"1" * 65505)

@pytest.mark.timeout(timeout=1)
def test_eof(self) -> None:
# Even though this decoder never says that it is finished
Expand Down
16 changes: 16 additions & 0 deletions Tests/test_file_jpeg2k.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ def test_restricted_icc_profile() -> None:
ImageFile.LOAD_TRUNCATED_IMAGES = False


@pytest.mark.skipif(
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
)
def test_unknown_colorspace() -> None:
with Image.open(f"{EXTRA_DIR}/file8.jp2") as im:
im.load()
assert im.mode == "L"


def test_header_errors() -> None:
for path in (
"Tests/images/invalid_header_length.jp2",
Expand Down Expand Up @@ -391,6 +400,13 @@ def test_pclr() -> None:
assert len(im.palette.colors) == 256
assert im.palette.colors[(255, 255, 255)] == 0

with Image.open(
f"{EXTRA_DIR}/147af3f1083de4393666b7d99b01b58b_signal_sigsegv_130c531_6155_5136.jp2"
) as im:
assert im.mode == "P"
assert len(im.palette.colors) == 139
assert im.palette.colors[(0, 0, 0, 0)] == 0


def test_comment() -> None:
with Image.open("Tests/images/comment.jp2") as im:
Expand Down
2 changes: 2 additions & 0 deletions Tests/test_file_ppm.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ def test_16bit_pgm_write(tmp_path: Path) -> None:
with Image.open("Tests/images/16_bit_binary.pgm") as im:
filename = str(tmp_path / "temp.pgm")
im.save(filename, "PPM")
assert_image_equal_tofile(im, filename)

im.convert("I;16").save(filename, "PPM")
assert_image_equal_tofile(im, filename)


Expand Down
8 changes: 8 additions & 0 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,14 @@ def test_empty_exif(self) -> None:
exif.load(b"Exif\x00\x00")
assert not dict(exif)

def test_empty_get_ifd(self) -> None:
exif = Image.Exif()
ifd = exif.get_ifd(0x8769)
assert ifd == {}

ifd[36864] = b"0220"
assert exif.get_ifd(0x8769) == {36864: b"0220"}

@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_image_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test(mode: str) -> tuple[tuple[int, ...], str, int]:

def test_with_dtype(dtype: npt.DTypeLike) -> None:
ai = numpy.array(im, dtype=dtype)
assert ai.dtype == dtype
assert ai.dtype.type is dtype

# assert test("1") == ((100, 128), '|b1', 1600))
assert test("L") == ((100, 128), "|u1", 12800)
Expand Down
24 changes: 17 additions & 7 deletions Tests/test_imagefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@ def _open(self) -> None:
self.rawmode = "RGBA"
self._mode = "RGBA"
self._size = (200, 200)
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
self.tile = [
ImageFile._Tile("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)
]


class CodecsTest:
Expand Down Expand Up @@ -268,7 +270,7 @@ def test_extents_none(self) -> None:
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)
im.tile = [("MOCK", None, 32, None)]
im.tile = [ImageFile._Tile("MOCK", None, 32, None)]

im.load()

Expand All @@ -281,25 +283,33 @@ def test_negsize(self) -> None:
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)
im.tile = [("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)]
im.tile = [ImageFile._Tile("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)]

with pytest.raises(ValueError):
im.load()

im.tile = [("MOCK", (xoff, yoff, xoff + xsize, -10), 32, None)]
im.tile = [ImageFile._Tile("MOCK", (xoff, yoff, xoff + xsize, -10), 32, None)]
with pytest.raises(ValueError):
im.load()

def test_oversize(self) -> None:
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)
im.tile = [("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None)]
im.tile = [
ImageFile._Tile(
"MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None
)
]

with pytest.raises(ValueError):
im.load()

im.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None)]
im.tile = [
ImageFile._Tile(
"MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None
)
]
with pytest.raises(ValueError):
im.load()

Expand Down Expand Up @@ -336,7 +346,7 @@ def test_extents_none(self) -> None:
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)
im.tile = [("MOCK", None, 32, None)]
im.tile = [ImageFile._Tile("MOCK", None, 32, None)]

fp = BytesIO()
ImageFile._save(im, fp, [ImageFile._Tile("MOCK", None, 0, "RGB")])
Expand Down
3 changes: 3 additions & 0 deletions Tests/test_imagefont.py
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,9 @@ def test_bytes(font: ImageFont.FreeTypeFont) -> None:
)
assert font.getmask2(b"test")[1] == font.getmask2("test")[1]

with pytest.raises(TypeError):
font.getlength((0, 0)) # type: ignore[arg-type]


@pytest.mark.parametrize(
"test_file",
Expand Down
2 changes: 1 addition & 1 deletion depends/install_imagequant.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# install libimagequant

archive_name=libimagequant
archive_version=4.3.1
archive_version=4.3.3

archive=$archive_name-$archive_version

Expand Down
4 changes: 3 additions & 1 deletion docs/example/DdsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,9 @@ def _open(self) -> None:
msg = f"Unimplemented pixel format {repr(fourcc)}"
raise NotImplementedError(msg)

self.tile = [(self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
self.tile = [
ImageFile._Tile(self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))
]

def load_seek(self, pos: int) -> None:
pass
Expand Down
2 changes: 1 addition & 1 deletion docs/installation/building-from-source.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Many of Pillow's features require external libraries:

* **libimagequant** provides improved color quantization

* Pillow has been tested with libimagequant **2.6-4.3.1**
* Pillow has been tested with libimagequant **2.6-4.3.3**
* Libimagequant is licensed GPLv3, which is more restrictive than
the Pillow license, therefore we will not be distributing binaries
with libimagequant support enabled.
Expand Down
4 changes: 4 additions & 0 deletions docs/reference/internal_modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ Internal Modules
Provides a convenient way to import type hints that are not available
on some Python versions.

.. py:class:: IntegralLike
Typing alias.

.. py:class:: NumpyArray
Typing alias.
Expand Down
4 changes: 0 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,3 @@ follow_imports = "silent"
warn_redundant_casts = true
warn_unreachable = true
warn_unused_ignores = true
exclude = [
'^Tests/oss-fuzz/fuzz_font.py$',
'^Tests/oss-fuzz/fuzz_pillow.py$',
]
Loading

0 comments on commit 8dcf229

Please sign in to comment.