mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-26 09:14:27 +03:00
Added PyEncoder
This commit is contained in:
parent
afb7728b8c
commit
a0e1fde1ed
|
@ -196,6 +196,11 @@ class MockPyDecoder(ImageFile.PyDecoder):
|
|||
return -1, 0
|
||||
|
||||
|
||||
class MockPyEncoder(ImageFile.PyEncoder):
|
||||
def encode(self, buffer):
|
||||
return 1, 1, b""
|
||||
|
||||
|
||||
xoff, yoff, xsize, ysize = 10, 20, 100, 100
|
||||
|
||||
|
||||
|
@ -207,53 +212,58 @@ class MockImageFile(ImageFile.ImageFile):
|
|||
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 closure(mode, *args):
|
||||
decoder.__init__(mode, *args)
|
||||
return decoder
|
||||
def decoder_closure(mode, *args):
|
||||
cls.decoder.__init__(mode, *args)
|
||||
return cls.decoder
|
||||
|
||||
Image.register_decoder("MOCK", closure)
|
||||
return 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)
|
||||
|
||||
|
||||
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()
|
||||
|
@ -267,7 +277,6 @@ class TestPyDecoder:
|
|||
|
||||
im = MockImageFile(buf)
|
||||
im.tile = [("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None)]
|
||||
self.get_decoder()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
im.load()
|
||||
|
@ -275,3 +284,91 @@ class TestPyDecoder:
|
|||
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)
|
||||
|
|
|
@ -8,4 +8,4 @@ Appendices
|
|||
|
||||
image-file-formats
|
||||
text-anchors
|
||||
writing-your-own-file-decoder
|
||||
writing-your-own-image-plugin
|
||||
|
|
|
@ -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
|
||||
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -49,7 +49,11 @@ ERRORS = {
|
|||
-8: "bad configuration",
|
||||
-9: "out of memory error",
|
||||
}
|
||||
"""Dict of known error codes returned from :meth:`.PyDecoder.decode`."""
|
||||
"""
|
||||
Dict of known error codes returned from :meth:`.PyDecoder.decode`,
|
||||
:meth:`.PyEncoder.encode` :meth:`.PyEncoder.encode_to_pyfd` and
|
||||
:meth:`.PyEncoder.encode_to_file`.
|
||||
"""
|
||||
|
||||
|
||||
#
|
||||
|
@ -577,16 +581,7 @@ class PyCodecState:
|
|||
return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize)
|
||||
|
||||
|
||||
class PyDecoder:
|
||||
"""
|
||||
Python implementation of a format decoder. Override this class and
|
||||
add the decoding logic in the :meth:`decode` method.
|
||||
|
||||
See :ref:`Writing Your Own File Decoder in Python<file-decoders-py>`
|
||||
"""
|
||||
|
||||
_pulls_fd = False
|
||||
|
||||
class PyCodec:
|
||||
def __init__(self, mode, *args):
|
||||
self.im = None
|
||||
self.state = PyCodecState()
|
||||
|
@ -596,31 +591,16 @@ class PyDecoder:
|
|||
|
||||
def init(self, args):
|
||||
"""
|
||||
Override to perform decoder specific initialization
|
||||
Override to perform codec specific initialization
|
||||
|
||||
:param args: Array of args items from the tile entry
|
||||
:returns: None
|
||||
"""
|
||||
self.args = args
|
||||
|
||||
@property
|
||||
def pulls_fd(self):
|
||||
return self._pulls_fd
|
||||
|
||||
def decode(self, buffer):
|
||||
"""
|
||||
Override to perform the decoding process.
|
||||
|
||||
:param buffer: A bytes object with the data to be decoded.
|
||||
:returns: A tuple of ``(bytes consumed, errcode)``.
|
||||
If finished with decoding return <0 for the bytes consumed.
|
||||
Err codes are from :data:`.ImageFile.ERRORS`.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
Override to perform decoder specific cleanup
|
||||
Override to perform codec specific cleanup
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
|
@ -628,16 +608,16 @@ class PyDecoder:
|
|||
|
||||
def setfd(self, fd):
|
||||
"""
|
||||
Called from ImageFile to set the python file-like object
|
||||
Called from ImageFile to set the Python file-like object
|
||||
|
||||
:param fd: A python file-like object
|
||||
:param fd: A Python file-like object
|
||||
:returns: None
|
||||
"""
|
||||
self.fd = fd
|
||||
|
||||
def setimage(self, im, extents=None):
|
||||
"""
|
||||
Called from ImageFile to set the core output image for the decoder
|
||||
Called from ImageFile to set the core output image for the codec
|
||||
|
||||
:param im: A core image object
|
||||
:param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle
|
||||
|
@ -670,6 +650,32 @@ class PyDecoder:
|
|||
):
|
||||
raise ValueError("Tile cannot extend outside image")
|
||||
|
||||
|
||||
class PyDecoder(PyCodec):
|
||||
"""
|
||||
Python implementation of a format decoder. Override this class and
|
||||
add the decoding logic in the :meth:`decode` method.
|
||||
|
||||
See :ref:`Writing Your Own File Codec in Python<file-codecs-py>`
|
||||
"""
|
||||
|
||||
_pulls_fd = False
|
||||
|
||||
@property
|
||||
def pulls_fd(self):
|
||||
return self._pulls_fd
|
||||
|
||||
def decode(self, buffer):
|
||||
"""
|
||||
Override to perform the decoding process.
|
||||
|
||||
:param buffer: A bytes object with the data to be decoded.
|
||||
:returns: A tuple of ``(bytes consumed, errcode)``.
|
||||
If finished with decoding return 0 for the bytes consumed.
|
||||
Err codes are from :data:`.ImageFile.ERRORS`.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_as_raw(self, data, rawmode=None):
|
||||
"""
|
||||
Convenience method to set the internal image from a stream of raw data
|
||||
|
@ -690,3 +696,55 @@ class PyDecoder:
|
|||
raise ValueError("not enough image data")
|
||||
if s[1] != 0:
|
||||
raise ValueError("cannot decode image data")
|
||||
|
||||
|
||||
class PyEncoder(PyCodec):
|
||||
"""
|
||||
Python implementation of a format encoder. Override this class and
|
||||
add the decoding logic in the :meth:`encode` method.
|
||||
"""
|
||||
|
||||
_pushes_fd = False
|
||||
|
||||
@property
|
||||
def pushes_fd(self):
|
||||
return self._pushes_fd
|
||||
|
||||
def encode(self, bufsize):
|
||||
"""
|
||||
Override to perform the encoding process.
|
||||
|
||||
:param bufsize: Buffer size.
|
||||
:returns: A tuple of ``(bytes encoded, errcode, bytes)``.
|
||||
If finished with encoding return 1 for the error code.
|
||||
Err codes are from :data:`.ImageFile.ERRORS`.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def encode_to_pyfd(self):
|
||||
"""
|
||||
:returns: A tuple of ``(bytes consumed, errcode)``.
|
||||
Err codes are from :data:`.ImageFile.ERRORS`.
|
||||
"""
|
||||
if not self.pushes_fd:
|
||||
return 0, -8 # bad configuration
|
||||
bytes_consumed, errcode, data = self.encode(0)
|
||||
if data:
|
||||
self.fd.write(data)
|
||||
return bytes_consumed, errcode
|
||||
|
||||
def encode_to_file(self, fh, bufsize):
|
||||
"""
|
||||
:param fh: File handle.
|
||||
:param bufsize: Buffer size.
|
||||
|
||||
:returns: If finished successfully, return 0.
|
||||
Otherwise, return an error code. Err codes are from
|
||||
:data:`.ImageFile.ERRORS`.
|
||||
"""
|
||||
errcode = 0
|
||||
while errcode == 0:
|
||||
status, errcode, buf = self.encode(bufsize)
|
||||
if status > 0:
|
||||
fh.write(buf[status:])
|
||||
return errcode
|
||||
|
|
Loading…
Reference in New Issue
Block a user