Skip to content

Commit b9e90c7

Browse files
committed
Allow plugins to specify their supported modes
1 parent 8dcd9df commit b9e90c7

6 files changed

Lines changed: 87 additions & 39 deletions

File tree

Tests/test_image.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from PIL import Image, TiffImagePlugin
44
from PIL._util import py3
5+
6+
from io import BytesIO
57
import os
68
import sys
79
import shutil
@@ -63,8 +65,7 @@ def test_width_height(self):
6365

6466
def test_invalid_image(self):
6567
if py3:
66-
import io
67-
im = io.BytesIO(b'')
68+
im = BytesIO(b'')
6869
else:
6970
import StringIO
7071
im = StringIO.StringIO('')
@@ -334,14 +335,54 @@ def test_registered_extensions(self):
334335
for ext in ['.cur', '.icns', '.tif', '.tiff']:
335336
self.assertIn(ext, extensions)
336337

337-
def test_no_convert_mode(self):
338-
self.assertTrue(not hasattr(TiffImagePlugin, '_convert_mode'))
338+
def test_supported_modes(self):
339+
for format in Image.MIME.keys():
340+
try:
341+
save_handler = Image.SAVE[format]
342+
except KeyError:
343+
continue
344+
plugin = sys.modules[save_handler.__module__]
345+
if not hasattr(plugin, '_supported_modes'):
346+
continue
347+
348+
# Check that the supported modes list is accurate
349+
supported_modes = plugin._supported_modes()
350+
for mode in ['1', 'L', 'P', 'RGB', 'RGBA', 'CMYK', 'YCbCr', 'LAB',
351+
'HSV', 'I', 'F', 'LA', 'La', 'RGBX', 'RGBa']:
352+
out = BytesIO()
353+
im = Image.new(mode, (100, 100))
354+
if mode in supported_modes:
355+
im.save(out, format)
356+
else:
357+
self.assertRaises(Exception, im.save, out, format)
358+
359+
def test_no_supported_modes_method(self):
360+
self.assertTrue(not hasattr(TiffImagePlugin, '_supported_modes'))
339361

340362
temp_file = self.tempfile("temp.tiff")
341363

342364
im = hopper()
343365
im.save(temp_file, convert_mode=True)
344366

367+
def test_convert_mode(self):
368+
for mode, modes in [
369+
['P', []], # no modes
370+
['P', ['P']] # same mode
371+
]:
372+
im = Image.new(mode, (100, 100))
373+
self.assertIsNone(im._convert_mode(modes))
374+
375+
for mode, modes in [
376+
['P', ['RGB']],
377+
['P', ['L']], # converting to a non-preferred mode
378+
['LA', ['P']],
379+
['I', ['L']],
380+
['RGB', ['L']],
381+
['RGB', ['CMYK']]
382+
]:
383+
im = Image.new(mode, (100, 100))
384+
self.assertIsNotNone(im._convert_mode(modes))
385+
345386
def test_effect_mandelbrot(self):
346387
# Arrange
347388
size = (512, 512)

src/PIL/GifImagePlugin.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -840,11 +840,8 @@ def write(self, data):
840840
return fp.data
841841

842842

843-
def _convert_mode(im):
844-
return {
845-
'LA':'P',
846-
'CMYK':'RGB'
847-
}.get(im.mode)
843+
def _supported_modes():
844+
return ['RGB', 'RGBA', 'P', 'I', 'F', 'LA', 'L', '1']
848845

849846

850847
# --------------------------------------------------------------------

src/PIL/Image.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1987,9 +1987,11 @@ def save(self, fp, format=None, **params):
19871987

19881988
if params.get('convert_mode'):
19891989
plugin = sys.modules[save_handler.__module__]
1990-
converted_im = self._convert_mode(plugin, params)
1991-
if converted_im:
1992-
return converted_im.save(fp, format, **params)
1990+
if hasattr(plugin, '_supported_modes'):
1991+
modes = plugin._supported_modes()
1992+
converted_im = self._convert_mode(modes, params)
1993+
if converted_im:
1994+
return converted_im.save(fp, format, **params)
19931995

19941996
self.encoderinfo = params
19951997
self.encoderconfig = ()
@@ -2009,10 +2011,36 @@ def save(self, fp, format=None, **params):
20092011
if open_fp:
20102012
fp.close()
20112013

2012-
def _convert_mode(self, plugin, params):
2013-
if not hasattr(plugin, '_convert_mode'):
2014+
def _convert_mode(self, modes, params={}):
2015+
if not modes or self.mode in modes:
20142016
return
2015-
new_mode = plugin._convert_mode(self)
2017+
if self.mode == 'P':
2018+
preferred_modes = []
2019+
if 'A' in self.im.getpalettemode():
2020+
preferred_modes.append('RGBA')
2021+
preferred_modes.append('RGB')
2022+
else:
2023+
preferred_modes = {
2024+
'CMYK': ['RGB'],
2025+
'RGB': ['CMYK'],
2026+
'RGBX': ['RGB'],
2027+
'RGBa': ['RGBA', 'RGB'],
2028+
'RGBA': ['RGB'],
2029+
'LA': ['RGBA', 'P', 'L'],
2030+
'La': ['LA', 'L'],
2031+
'L': ['RGB'],
2032+
'F': ['I'],
2033+
'I': ['L', 'RGB'],
2034+
'1': ['L'],
2035+
'YCbCr': ['RGB'],
2036+
'LAB': ['RGB'],
2037+
'HSV': ['RGB']
2038+
}.get(self.mode, [])
2039+
for new_mode in preferred_modes:
2040+
if new_mode in modes:
2041+
break
2042+
else:
2043+
new_mode = modes[0]
20162044
if self.mode == 'LA' and new_mode == 'P':
20172045
alpha = self.getchannel('A')
20182046
# Convert the image into P mode but only use 255 colors

src/PIL/JpegImagePlugin.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -837,15 +837,8 @@ def jpeg_factory(fp=None, filename=None):
837837
return im
838838

839839

840-
def _convert_mode(im):
841-
mode = im.mode
842-
if mode == 'P':
843-
return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB'
844-
return {
845-
'RGBA':'RGB',
846-
'LA':'L',
847-
'I':'L'
848-
}.get(mode)
840+
def _supported_modes():
841+
return ['RGB', 'CMYK', 'YCbCr', 'RGBX', 'L', '1']
849842

850843

851844
# ---------------------------------------------------------------------

src/PIL/PngImagePlugin.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -924,10 +924,8 @@ def append(fp, cid, *data):
924924
return fp.data
925925

926926

927-
def _convert_mode(im):
928-
return {
929-
'CMYK':'RGB'
930-
}.get(im.mode)
927+
def _supported_modes():
928+
return ['RGB', 'RGBA', 'P', 'I', 'LA', 'L', '1']
931929

932930

933931
# --------------------------------------------------------------------

src/PIL/WebPImagePlugin.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -339,17 +339,8 @@ def _save(im, fp, filename):
339339
fp.write(data)
340340

341341

342-
def _convert_mode(im):
343-
mode = im.mode
344-
if mode == 'P':
345-
return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB'
346-
return {
347-
# Pillow doesn't support L modes for webp for now.
348-
'L':'RGB',
349-
'LA':'RGBA',
350-
'I':'RGB',
351-
'CMYK':'RGB'
352-
}.get(mode)
342+
def _supported_modes():
343+
return ['RGB', 'RGBA', 'RGBa', 'RGBX', 'CMYK', 'YCbCr', 'HSV', 'I', 'F', 'P', 'LA', 'L', '1']
353344

354345

355346
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)

0 commit comments

Comments
 (0)