mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-04 21:50:54 +03:00
Added PyEncoder
This commit is contained in:
parent
afb7728b8c
commit
a0e1fde1ed
|
@ -196,6 +196,11 @@ class MockPyDecoder(ImageFile.PyDecoder):
|
||||||
return -1, 0
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
|
class MockPyEncoder(ImageFile.PyEncoder):
|
||||||
|
def encode(self, buffer):
|
||||||
|
return 1, 1, b""
|
||||||
|
|
||||||
|
|
||||||
xoff, yoff, xsize, ysize = 10, 20, 100, 100
|
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)]
|
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
|
||||||
|
|
||||||
|
|
||||||
class TestPyDecoder:
|
class CodecsTest:
|
||||||
def get_decoder(self):
|
@classmethod
|
||||||
decoder = MockPyDecoder(None)
|
def setup_class(cls):
|
||||||
|
cls.decoder = MockPyDecoder(None)
|
||||||
|
cls.encoder = MockPyEncoder(None)
|
||||||
|
|
||||||
def closure(mode, *args):
|
def decoder_closure(mode, *args):
|
||||||
decoder.__init__(mode, *args)
|
cls.decoder.__init__(mode, *args)
|
||||||
return decoder
|
return cls.decoder
|
||||||
|
|
||||||
Image.register_decoder("MOCK", closure)
|
def encoder_closure(mode, *args):
|
||||||
return decoder
|
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):
|
def test_setimage(self):
|
||||||
buf = BytesIO(b"\x00" * 255)
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
im = MockImageFile(buf)
|
im = MockImageFile(buf)
|
||||||
d = self.get_decoder()
|
|
||||||
|
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert d.state.xoff == xoff
|
assert self.decoder.state.xoff == xoff
|
||||||
assert d.state.yoff == yoff
|
assert self.decoder.state.yoff == yoff
|
||||||
assert d.state.xsize == xsize
|
assert self.decoder.state.xsize == xsize
|
||||||
assert d.state.ysize == ysize
|
assert self.decoder.state.ysize == ysize
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
d.set_as_raw(b"\x00")
|
self.decoder.set_as_raw(b"\x00")
|
||||||
|
|
||||||
def test_extents_none(self):
|
def test_extents_none(self):
|
||||||
buf = BytesIO(b"\x00" * 255)
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
im = MockImageFile(buf)
|
im = MockImageFile(buf)
|
||||||
im.tile = [("MOCK", None, 32, None)]
|
im.tile = [("MOCK", None, 32, None)]
|
||||||
d = self.get_decoder()
|
|
||||||
|
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert d.state.xoff == 0
|
assert self.decoder.state.xoff == 0
|
||||||
assert d.state.yoff == 0
|
assert self.decoder.state.yoff == 0
|
||||||
assert d.state.xsize == 200
|
assert self.decoder.state.xsize == 200
|
||||||
assert d.state.ysize == 200
|
assert self.decoder.state.ysize == 200
|
||||||
|
|
||||||
def test_negsize(self):
|
def test_negsize(self):
|
||||||
buf = BytesIO(b"\x00" * 255)
|
buf = BytesIO(b"\x00" * 255)
|
||||||
|
|
||||||
im = MockImageFile(buf)
|
im = MockImageFile(buf)
|
||||||
im.tile = [("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)]
|
im.tile = [("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)]
|
||||||
self.get_decoder()
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.load()
|
im.load()
|
||||||
|
@ -267,7 +277,6 @@ class TestPyDecoder:
|
||||||
|
|
||||||
im = MockImageFile(buf)
|
im = MockImageFile(buf)
|
||||||
im.tile = [("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None)]
|
im.tile = [("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None)]
|
||||||
self.get_decoder()
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.load()
|
im.load()
|
||||||
|
@ -275,3 +284,91 @@ class TestPyDecoder:
|
||||||
im.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None)]
|
im.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None)]
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.load()
|
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
|
image-file-formats
|
||||||
text-anchors
|
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
|
Pillow uses a plugin model which allows you to add your own
|
||||||
decoders to the library, without any changes to the library
|
decoders and encoders to the library, without any changes to the library
|
||||||
itself. Such plugins usually have names like
|
itself. Such plugins usually have names like :file:`XxxImagePlugin.py`,
|
||||||
:file:`XxxImagePlugin.py`, where ``Xxx`` is a unique format name
|
where ``Xxx`` is a unique format name (usually an abbreviation).
|
||||||
(usually an abbreviation).
|
|
||||||
|
|
||||||
.. warning:: Pillow >= 2.1.0 no longer automatically imports any file
|
.. warning:: Pillow >= 2.1.0 no longer automatically imports any file
|
||||||
in the Python path with a name ending in
|
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
|
free any allocated memory and release any resources from external
|
||||||
libraries.
|
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
|
Python file decoders and encoders should derive from
|
||||||
:py:class:`PIL.ImageFile.PyDecoder` and should at least override the
|
:py:class:`PIL.ImageFile.PyDecoder` and :py:class:`PIL.ImageFile.PyEncoder`
|
||||||
decode method. File decoders should be registered using
|
respectively, and should at least override the decode or encode method.
|
||||||
:py:meth:`PIL.Image.register_decoder`. As in the C implementation of
|
They should be registered using :py:meth:`PIL.Image.register_decoder` and
|
||||||
the file decoders, there are three stages in the lifetime of a
|
:py:meth:`PIL.Image.register_encoder`. As in the C implementation of
|
||||||
Python-based file decoder:
|
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
|
1. Setup: Pillow looks for the decoder in the registry, then
|
||||||
instantiates the class.
|
instantiates the class.
|
||||||
|
|
||||||
2. Decoding: The decoder instance's ``decode`` method is repeatedly
|
2. Transforming: The instance's ``decode`` method is repeatedly called with
|
||||||
called with a buffer of data to be interpreted.
|
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 decoder instance's ``cleanup`` method is called.
|
|
||||||
|
|
||||||
|
3. Cleanup: The instance's ``cleanup`` method is called.
|
|
@ -40,8 +40,16 @@ Classes
|
||||||
.. autoclass:: PIL.ImageFile.Parser()
|
.. autoclass:: PIL.ImageFile.Parser()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: PIL.ImageFile.PyCodec()
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: PIL.ImageFile.PyDecoder()
|
.. autoclass:: PIL.ImageFile.PyDecoder()
|
||||||
:members:
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: PIL.ImageFile.PyEncoder()
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
.. autoclass:: PIL.ImageFile.ImageFile()
|
.. autoclass:: PIL.ImageFile.ImageFile()
|
||||||
:member-order: bysource
|
:member-order: bysource
|
||||||
|
|
|
@ -49,7 +49,11 @@ ERRORS = {
|
||||||
-8: "bad configuration",
|
-8: "bad configuration",
|
||||||
-9: "out of memory error",
|
-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)
|
return (self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize)
|
||||||
|
|
||||||
|
|
||||||
class PyDecoder:
|
class 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 Decoder in Python<file-decoders-py>`
|
|
||||||
"""
|
|
||||||
|
|
||||||
_pulls_fd = False
|
|
||||||
|
|
||||||
def __init__(self, mode, *args):
|
def __init__(self, mode, *args):
|
||||||
self.im = None
|
self.im = None
|
||||||
self.state = PyCodecState()
|
self.state = PyCodecState()
|
||||||
|
@ -596,31 +591,16 @@ class PyDecoder:
|
||||||
|
|
||||||
def init(self, args):
|
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
|
:param args: Array of args items from the tile entry
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
self.args = args
|
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):
|
def cleanup(self):
|
||||||
"""
|
"""
|
||||||
Override to perform decoder specific cleanup
|
Override to perform codec specific cleanup
|
||||||
|
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
|
@ -628,16 +608,16 @@ class PyDecoder:
|
||||||
|
|
||||||
def setfd(self, fd):
|
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
|
:returns: None
|
||||||
"""
|
"""
|
||||||
self.fd = fd
|
self.fd = fd
|
||||||
|
|
||||||
def setimage(self, im, extents=None):
|
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 im: A core image object
|
||||||
:param extents: a 4 tuple of (x0, y0, x1, y1) defining the rectangle
|
: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")
|
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):
|
def set_as_raw(self, data, rawmode=None):
|
||||||
"""
|
"""
|
||||||
Convenience method to set the internal image from a stream of raw data
|
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")
|
raise ValueError("not enough image data")
|
||||||
if s[1] != 0:
|
if s[1] != 0:
|
||||||
raise ValueError("cannot decode image data")
|
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