Skip to content

Commit a0e1fde

Browse files
committed
Added PyEncoder
1 parent afb7728 commit a0e1fde

File tree

5 files changed

+233
-70
lines changed

5 files changed

+233
-70
lines changed

Tests/test_imagefile.py

Lines changed: 118 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,11 @@ def decode(self, buffer):
196196
return -1, 0
197197

198198

199+
class MockPyEncoder(ImageFile.PyEncoder):
200+
def encode(self, buffer):
201+
return 1, 1, b""
202+
203+
199204
xoff, yoff, xsize, ysize = 10, 20, 100, 100
200205

201206

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

209214

210-
class TestPyDecoder:
211-
def get_decoder(self):
212-
decoder = MockPyDecoder(None)
215+
class CodecsTest:
216+
@classmethod
217+
def setup_class(cls):
218+
cls.decoder = MockPyDecoder(None)
219+
cls.encoder = MockPyEncoder(None)
220+
221+
def decoder_closure(mode, *args):
222+
cls.decoder.__init__(mode, *args)
223+
return cls.decoder
213224

214-
def closure(mode, *args):
215-
decoder.__init__(mode, *args)
216-
return decoder
225+
def encoder_closure(mode, *args):
226+
cls.encoder.__init__(mode, *args)
227+
return cls.encoder
217228

218-
Image.register_decoder("MOCK", closure)
219-
return decoder
229+
Image.register_decoder("MOCK", decoder_closure)
230+
Image.register_encoder("MOCK", encoder_closure)
220231

232+
233+
class TestPyDecoder(CodecsTest):
221234
def test_setimage(self):
222235
buf = BytesIO(b"\x00" * 255)
223236

224237
im = MockImageFile(buf)
225-
d = self.get_decoder()
226238

227239
im.load()
228240

229-
assert d.state.xoff == xoff
230-
assert d.state.yoff == yoff
231-
assert d.state.xsize == xsize
232-
assert d.state.ysize == ysize
241+
assert self.decoder.state.xoff == xoff
242+
assert self.decoder.state.yoff == yoff
243+
assert self.decoder.state.xsize == xsize
244+
assert self.decoder.state.ysize == ysize
233245

234246
with pytest.raises(ValueError):
235-
d.set_as_raw(b"\x00")
247+
self.decoder.set_as_raw(b"\x00")
236248

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

240252
im = MockImageFile(buf)
241253
im.tile = [("MOCK", None, 32, None)]
242-
d = self.get_decoder()
243254

244255
im.load()
245256

246-
assert d.state.xoff == 0
247-
assert d.state.yoff == 0
248-
assert d.state.xsize == 200
249-
assert d.state.ysize == 200
257+
assert self.decoder.state.xoff == 0
258+
assert self.decoder.state.yoff == 0
259+
assert self.decoder.state.xsize == 200
260+
assert self.decoder.state.ysize == 200
250261

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

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

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

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

272281
with pytest.raises(ValueError):
273282
im.load()
274283

275284
im.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None)]
276285
with pytest.raises(ValueError):
277286
im.load()
287+
288+
def test_decode(self):
289+
decoder = ImageFile.PyDecoder(None)
290+
with pytest.raises(NotImplementedError):
291+
decoder.decode(None)
292+
293+
294+
class TestPyEncoder(CodecsTest):
295+
def test_setimage(self):
296+
buf = BytesIO(b"\x00" * 255)
297+
298+
im = MockImageFile(buf)
299+
300+
fp = BytesIO()
301+
ImageFile._save(
302+
im, fp, [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 0, "RGB")]
303+
)
304+
305+
assert self.encoder.state.xoff == xoff
306+
assert self.encoder.state.yoff == yoff
307+
assert self.encoder.state.xsize == xsize
308+
assert self.encoder.state.ysize == ysize
309+
310+
def test_extents_none(self):
311+
buf = BytesIO(b"\x00" * 255)
312+
313+
im = MockImageFile(buf)
314+
im.tile = [("MOCK", None, 32, None)]
315+
316+
fp = BytesIO()
317+
ImageFile._save(im, fp, [("MOCK", None, 0, "RGB")])
318+
319+
assert self.encoder.state.xoff == 0
320+
assert self.encoder.state.yoff == 0
321+
assert self.encoder.state.xsize == 200
322+
assert self.encoder.state.ysize == 200
323+
324+
def test_negsize(self):
325+
buf = BytesIO(b"\x00" * 255)
326+
327+
im = MockImageFile(buf)
328+
329+
fp = BytesIO()
330+
with pytest.raises(ValueError):
331+
ImageFile._save(
332+
im, fp, [("MOCK", (xoff, yoff, -10, yoff + ysize), 0, "RGB")]
333+
)
334+
335+
with pytest.raises(ValueError):
336+
ImageFile._save(
337+
im, fp, [("MOCK", (xoff, yoff, xoff + xsize, -10), 0, "RGB")]
338+
)
339+
340+
def test_oversize(self):
341+
buf = BytesIO(b"\x00" * 255)
342+
343+
im = MockImageFile(buf)
344+
345+
fp = BytesIO()
346+
with pytest.raises(ValueError):
347+
ImageFile._save(
348+
im,
349+
fp,
350+
[("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 0, "RGB")],
351+
)
352+
353+
with pytest.raises(ValueError):
354+
ImageFile._save(
355+
im,
356+
fp,
357+
[("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 0, "RGB")],
358+
)
359+
360+
def test_encode(self):
361+
encoder = ImageFile.PyEncoder(None)
362+
with pytest.raises(NotImplementedError):
363+
encoder.encode(None)
364+
365+
bytes_consumed, errcode = encoder.encode_to_pyfd()
366+
assert bytes_consumed == 0
367+
assert ImageFile.ERRORS[errcode] == "bad configuration"
368+
369+
encoder._pushes_fd = True
370+
with pytest.raises(NotImplementedError):
371+
encoder.encode_to_pyfd()
372+
373+
with pytest.raises(NotImplementedError):
374+
encoder.encode_to_file(None, None)

docs/handbook/appendices.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ Appendices
88

99
image-file-formats
1010
text-anchors
11-
writing-your-own-file-decoder
11+
writing-your-own-image-plugin

docs/handbook/writing-your-own-file-decoder.rst renamed to docs/handbook/writing-your-own-image-plugin.rst

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ Writing Your Own Image Plugin
44
=============================
55

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

1211
.. warning:: Pillow >= 2.1.0 no longer automatically imports any file
1312
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
413412
free any allocated memory and release any resources from external
414413
libraries.
415414

416-
.. _file-decoders-py:
415+
.. _file-codecs-py:
417416

418-
Writing Your Own File Decoder in Python
419-
=======================================
417+
Writing Your Own File Codec in Python
418+
=====================================
420419

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

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

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

435+
3. Cleanup: The instance's ``cleanup`` method is called.

docs/reference/ImageFile.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,16 @@ Classes
4040
.. autoclass:: PIL.ImageFile.Parser()
4141
:members:
4242

43+
.. autoclass:: PIL.ImageFile.PyCodec()
44+
:members:
45+
4346
.. autoclass:: PIL.ImageFile.PyDecoder()
4447
:members:
48+
:show-inheritance:
49+
50+
.. autoclass:: PIL.ImageFile.PyEncoder()
51+
:members:
52+
:show-inheritance:
4553

4654
.. autoclass:: PIL.ImageFile.ImageFile()
4755
:member-order: bysource

0 commit comments

Comments
 (0)