Skip to content

Commit

Permalink
Added PyEncoder
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed Feb 24, 2022
1 parent 0919c57 commit 5cc76b0
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 70 deletions.
139 changes: 118 additions & 21 deletions Tests/test_imagefile.py
Expand Up @@ -196,6 +196,11 @@ def decode(self, buffer):
return -1, 0


class MockPyEncoder(ImageFile.PyEncoder):
def encode(self, buffer):
return 1, 1, b""


xoff, yoff, xsize, ysize = 10, 20, 100, 100


Expand All @@ -207,53 +212,58 @@ def _open(self):
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]


class TestPyDecoder:
def get_decoder(self):
decoder = MockPyDecoder(None)
class CodecsTest:
@classmethod
def setup_class(cls):
cls.decoder = MockPyDecoder(None)
cls.encoder = MockPyEncoder(None)

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

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

Image.register_decoder("MOCK", closure)
return decoder
Image.register_decoder("MOCK", decoder_closure)
Image.register_encoder("MOCK", encoder_closure)


class TestPyDecoder(CodecsTest):
def test_setimage(self):
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)
d = self.get_decoder()

im.load()

assert d.state.xoff == xoff
assert d.state.yoff == yoff
assert d.state.xsize == xsize
assert d.state.ysize == ysize
assert self.decoder.state.xoff == xoff
assert self.decoder.state.yoff == yoff
assert self.decoder.state.xsize == xsize
assert self.decoder.state.ysize == ysize

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

def test_extents_none(self):
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)
im.tile = [("MOCK", None, 32, None)]
d = self.get_decoder()

im.load()

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

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

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

with pytest.raises(ValueError):
im.load()
Expand All @@ -267,11 +277,98 @@ def test_oversize(self):

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

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

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

def test_decode(self):
decoder = ImageFile.PyDecoder(None)
with pytest.raises(NotImplementedError):
decoder.decode(None)


class TestPyEncoder(CodecsTest):
def test_setimage(self):
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)

fp = BytesIO()
ImageFile._save(
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

def test_extents_none(self):
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)
im.tile = [("MOCK", None, 32, 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

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

im = MockImageFile(buf)

fp = BytesIO()
with pytest.raises(ValueError):
ImageFile._save(
im, fp, [("MOCK", (xoff, yoff, -10, yoff + ysize), 0, "RGB")]
)

with pytest.raises(ValueError):
ImageFile._save(
im, fp, [("MOCK", (xoff, yoff, xoff + xsize, -10), 0, "RGB")]
)

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

im = MockImageFile(buf)

fp = BytesIO()
with pytest.raises(ValueError):
ImageFile._save(
im,
fp,
[("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 0, "RGB")],
)

with pytest.raises(ValueError):
ImageFile._save(
im,
fp,
[("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 0, "RGB")],
)

def test_encode(self):
encoder = ImageFile.PyEncoder(None)
with pytest.raises(NotImplementedError):
encoder.encode(None)

bytes_consumed, errcode = encoder.encode_to_pyfd()
assert bytes_consumed == 0
assert ImageFile.ERRORS[errcode] == "bad configuration"

encoder._pushes_fd = True
with pytest.raises(NotImplementedError):
encoder.encode_to_pyfd()

with pytest.raises(NotImplementedError):
encoder.encode_to_file(None, None)
2 changes: 1 addition & 1 deletion docs/handbook/appendices.rst
Expand Up @@ -8,4 +8,4 @@ Appendices

image-file-formats
text-anchors
writing-your-own-file-decoder
writing-your-own-image-plugin
Expand Up @@ -4,10 +4,9 @@ Writing Your Own Image Plugin
=============================

Pillow uses a plugin model which allows you to add your own
decoders to the library, without any changes to the library
itself. Such plugins usually have names like
:file:`XxxImagePlugin.py`, where ``Xxx`` is a unique format name
(usually an abbreviation).
decoders and encoders to the library, without any changes to the library
itself. Such plugins usually have names like :file:`XxxImagePlugin.py`,
where ``Xxx`` is a unique format name (usually an abbreviation).

.. warning:: Pillow >= 2.1.0 no longer automatically imports any file
in the Python path with a name ending in
Expand Down Expand Up @@ -413,23 +412,24 @@ value, or if there is a read error from the file. This function should
free any allocated memory and release any resources from external
libraries.

.. _file-decoders-py:
.. _file-codecs-py:

Writing Your Own File Decoder in Python
=======================================
Writing Your Own File Codec in Python
=====================================

Python file decoders should derive from
:py:class:`PIL.ImageFile.PyDecoder` and should at least override the
decode method. File decoders should be registered using
:py:meth:`PIL.Image.register_decoder`. As in the C implementation of
the file decoders, there are three stages in the lifetime of a
Python-based file decoder:
Python file decoders and encoders should derive from
:py:class:`PIL.ImageFile.PyDecoder` and :py:class:`PIL.ImageFile.PyEncoder`
respectively, and should at least override the decode or encode method.
They should be registered using :py:meth:`PIL.Image.register_decoder` and
:py:meth:`PIL.Image.register_encoder`. As in the C implementation of
the file codecs, there are three stages in the lifetime of a
Python-based file codec:

1. Setup: Pillow looks for the decoder in the registry, then
instantiates the class.

2. Decoding: The decoder instance's ``decode`` method is repeatedly
called with a buffer of data to be interpreted.

3. Cleanup: The decoder instance's ``cleanup`` method is called.
2. Transforming: The instance's ``decode`` method is repeatedly called with
a buffer of data to be interpreted, or the ``encode`` method is repeatedly
called with the size of data to be output.

3. Cleanup: The instance's ``cleanup`` method is called.
8 changes: 8 additions & 0 deletions docs/reference/ImageFile.rst
Expand Up @@ -40,8 +40,16 @@ Classes
.. autoclass:: PIL.ImageFile.Parser()
:members:

.. autoclass:: PIL.ImageFile.PyCodec()
:members:

.. autoclass:: PIL.ImageFile.PyDecoder()
:members:
:show-inheritance:

.. autoclass:: PIL.ImageFile.PyEncoder()
:members:
:show-inheritance:

.. autoclass:: PIL.ImageFile.ImageFile()
:member-order: bysource
Expand Down

0 comments on commit 5cc76b0

Please sign in to comment.