Skip to content

Commit

Permalink
Merge pull request #7801 from radarhere/codecs
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk committed Mar 11, 2024
2 parents d8c8075 + 3199c0e commit 3cdd49f
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 57 deletions.
8 changes: 1 addition & 7 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -993,13 +993,7 @@ class InfiniteMockPyDecoder(ImageFile.PyDecoder):
def decode(self, buffer: bytes) -> tuple[int, int]:
return 0, 0

decoder = InfiniteMockPyDecoder(None)

def closure(mode: str, *args) -> InfiniteMockPyDecoder:
decoder.__init__(mode, *args)
return decoder

Image.register_decoder("INFINITE", closure)
Image.register_decoder("INFINITE", InfiniteMockPyDecoder)

with Image.open(TEST_FILE) as im:
im.tile = [
Expand Down
16 changes: 6 additions & 10 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
ExifTags,
Image,
ImageDraw,
ImageFile,
ImagePalette,
UnidentifiedImageError,
features,
Expand Down Expand Up @@ -1041,25 +1042,20 @@ def test_close_graceful(self, caplog: pytest.LogCaptureFixture) -> None:
assert im.fp is None


class MockEncoder:
args: tuple[str, ...]


def mock_encode(*args: str) -> MockEncoder:
encoder = MockEncoder()
encoder.args = args
return encoder
class MockEncoder(ImageFile.PyEncoder):
pass


class TestRegistry:
def test_encode_registry(self) -> None:
Image.register_encoder("MOCK", mock_encode)
Image.register_encoder("MOCK", MockEncoder)
assert "MOCK" in Image.ENCODERS

enc = Image._getencoder("RGB", "MOCK", ("args",), extra=("extra",))

assert isinstance(enc, MockEncoder)
assert enc.args == ("RGB", "args", "extra")
assert enc.mode == "RGB"
assert enc.args == ("args", "extra")

def test_encode_registry_fail(self) -> None:
with pytest.raises(OSError):
Expand Down
64 changes: 32 additions & 32 deletions Tests/test_imagefile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from io import BytesIO
from typing import Any

import pytest

Expand Down Expand Up @@ -201,12 +202,22 @@ def test_broken_datastream_without_errors(self) -> None:


class MockPyDecoder(ImageFile.PyDecoder):
def __init__(self, mode: str, *args: Any) -> None:
MockPyDecoder.last = self

super().__init__(mode, *args)

def decode(self, buffer):
# eof
return -1, 0


class MockPyEncoder(ImageFile.PyEncoder):
def __init__(self, mode: str, *args: Any) -> None:
MockPyEncoder.last = self

super().__init__(mode, *args)

def encode(self, buffer):
return 1, 1, b""

Expand All @@ -228,19 +239,8 @@ def _open(self) -> None:
class CodecsTest:
@classmethod
def setup_class(cls) -> None:
cls.decoder = MockPyDecoder(None)
cls.encoder = MockPyEncoder(None)

def decoder_closure(mode, *args):
cls.decoder.__init__(mode, *args)
return cls.decoder

def encoder_closure(mode, *args):
cls.encoder.__init__(mode, *args)
return cls.encoder

Image.register_decoder("MOCK", decoder_closure)
Image.register_encoder("MOCK", encoder_closure)
Image.register_decoder("MOCK", MockPyDecoder)
Image.register_encoder("MOCK", MockPyEncoder)


class TestPyDecoder(CodecsTest):
Expand All @@ -251,13 +251,13 @@ def test_setimage(self) -> None:

im.load()

assert self.decoder.state.xoff == xoff
assert self.decoder.state.yoff == yoff
assert self.decoder.state.xsize == xsize
assert self.decoder.state.ysize == ysize
assert MockPyDecoder.last.state.xoff == xoff
assert MockPyDecoder.last.state.yoff == yoff
assert MockPyDecoder.last.state.xsize == xsize
assert MockPyDecoder.last.state.ysize == ysize

with pytest.raises(ValueError):
self.decoder.set_as_raw(b"\x00")
MockPyDecoder.last.set_as_raw(b"\x00")

def test_extents_none(self) -> None:
buf = BytesIO(b"\x00" * 255)
Expand All @@ -267,10 +267,10 @@ def test_extents_none(self) -> None:

im.load()

assert self.decoder.state.xoff == 0
assert self.decoder.state.yoff == 0
assert self.decoder.state.xsize == 200
assert self.decoder.state.ysize == 200
assert MockPyDecoder.last.state.xoff == 0
assert MockPyDecoder.last.state.yoff == 0
assert MockPyDecoder.last.state.xsize == 200
assert MockPyDecoder.last.state.ysize == 200

def test_negsize(self) -> None:
buf = BytesIO(b"\x00" * 255)
Expand Down Expand Up @@ -315,10 +315,10 @@ def test_setimage(self) -> None:
im, fp, [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 0, "RGB")]
)

assert self.encoder.state.xoff == xoff
assert self.encoder.state.yoff == yoff
assert self.encoder.state.xsize == xsize
assert self.encoder.state.ysize == ysize
assert MockPyEncoder.last.state.xoff == xoff
assert MockPyEncoder.last.state.yoff == yoff
assert MockPyEncoder.last.state.xsize == xsize
assert MockPyEncoder.last.state.ysize == ysize

def test_extents_none(self) -> None:
buf = BytesIO(b"\x00" * 255)
Expand All @@ -329,23 +329,23 @@ def test_extents_none(self) -> None:
fp = BytesIO()
ImageFile._save(im, fp, [("MOCK", None, 0, "RGB")])

assert self.encoder.state.xoff == 0
assert self.encoder.state.yoff == 0
assert self.encoder.state.xsize == 200
assert self.encoder.state.ysize == 200
assert MockPyEncoder.last.state.xoff == 0
assert MockPyEncoder.last.state.yoff == 0
assert MockPyEncoder.last.state.xsize == 200
assert MockPyEncoder.last.state.ysize == 200

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

im = MockImageFile(buf)

fp = BytesIO()
self.encoder.cleanup_called = False
MockPyEncoder.last = None
with pytest.raises(ValueError):
ImageFile._save(
im, fp, [("MOCK", (xoff, yoff, -10, yoff + ysize), 0, "RGB")]
)
assert self.encoder.cleanup_called
assert MockPyEncoder.last.cleanup_called

with pytest.raises(ValueError):
ImageFile._save(
Expand Down
14 changes: 6 additions & 8 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ class Quantize(IntEnum):
SAVE: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {}
SAVE_ALL: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {}
EXTENSION: dict[str, str] = {}
DECODERS: dict[str, object] = {}
ENCODERS: dict[str, object] = {}
DECODERS: dict[str, type[ImageFile.PyDecoder]] = {}
ENCODERS: dict[str, type[ImageFile.PyEncoder]] = {}

# --------------------------------------------------------------------
# Modes
Expand Down Expand Up @@ -3526,28 +3526,26 @@ def registered_extensions():
return EXTENSION


def register_decoder(name: str, decoder) -> None:
def register_decoder(name: str, decoder: type[ImageFile.PyDecoder]) -> None:
"""
Registers an image decoder. This function should not be
used in application code.
:param name: The name of the decoder
:param decoder: A callable(mode, args) that returns an
ImageFile.PyDecoder object
:param decoder: An ImageFile.PyDecoder object
.. versionadded:: 4.1.0
"""
DECODERS[name] = decoder


def register_encoder(name, encoder):
def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None:
"""
Registers an image encoder. This function should not be
used in application code.
:param name: The name of the encoder
:param encoder: A callable(mode, args) that returns an
ImageFile.PyEncoder object
:param encoder: An ImageFile.PyEncoder object
.. versionadded:: 4.1.0
"""
Expand Down

0 comments on commit 3cdd49f

Please sign in to comment.