Merge pull request #7801 from radarhere/codecs

This commit is contained in:
Hugo van Kemenade 2024-03-11 17:38:12 +02:00 committed by GitHub
commit 3cdd49f31f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 45 additions and 57 deletions

View File

@ -993,13 +993,7 @@ class TestFileJpeg:
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 = [

View File

@ -16,6 +16,7 @@ from PIL import (
ExifTags,
Image,
ImageDraw,
ImageFile,
ImagePalette,
UnidentifiedImageError,
features,
@ -1041,25 +1042,20 @@ class TestImage:
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):

View File

@ -1,6 +1,7 @@
from __future__ import annotations
from io import BytesIO
from typing import Any
import pytest
@ -201,12 +202,22 @@ class TestImageFile:
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""
@ -228,19 +239,8 @@ class MockImageFile(ImageFile.ImageFile):
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):
@ -251,13 +251,13 @@ class TestPyDecoder(CodecsTest):
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)
@ -267,10 +267,10 @@ class TestPyDecoder(CodecsTest):
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)
@ -315,10 +315,10 @@ class TestPyEncoder(CodecsTest):
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)
@ -329,10 +329,10 @@ class TestPyEncoder(CodecsTest):
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)
@ -340,12 +340,12 @@ class TestPyEncoder(CodecsTest):
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(

View File

@ -229,8 +229,8 @@ MIME: dict[str, str] = {}
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
@ -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
"""