Skip to content

Commit fb9d188

Browse files
committed
Allow plugins to specify their supported modes
1 parent a05f485 commit fb9d188

6 files changed

Lines changed: 88 additions & 39 deletions

File tree

Tests/test_image.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
from PIL import Image, TiffImagePlugin
44
from PIL._util import py3
5+
6+
from io import BytesIO
57
import os
8+
import sys
69

710

811
class TestImage(PillowTestCase):
@@ -61,8 +64,7 @@ def test_width_height(self):
6164

6265
def test_invalid_image(self):
6366
if py3:
64-
import io
65-
im = io.BytesIO(b'')
67+
im = BytesIO(b'')
6668
else:
6769
import StringIO
6870
im = StringIO.StringIO('')
@@ -323,14 +325,54 @@ def test_registered_extensions(self):
323325
for ext in ['.cur', '.icns', '.tif', '.tiff']:
324326
self.assertIn(ext, extensions)
325327

326-
def test_no_convert_mode(self):
327-
self.assertTrue(not hasattr(TiffImagePlugin, '_convert_mode'))
328+
def test_supported_modes(self):
329+
for format in Image.MIME.keys():
330+
try:
331+
save_handler = Image.SAVE[format]
332+
except KeyError:
333+
continue
334+
plugin = sys.modules[save_handler.__module__]
335+
if not hasattr(plugin, '_supported_modes'):
336+
continue
337+
338+
# Check that the supported modes list is accurate
339+
supported_modes = plugin._supported_modes()
340+
for mode in ['1', 'L', 'P', 'RGB', 'RGBA', 'CMYK', 'YCbCr', 'LAB',
341+
'HSV', 'I', 'F', 'LA', 'La', 'RGBX', 'RGBa']:
342+
out = BytesIO()
343+
im = Image.new(mode, (100, 100))
344+
if mode in supported_modes:
345+
im.save(out, format)
346+
else:
347+
self.assertRaises(Exception, im.save, out, format)
348+
349+
def test_no_supported_modes_method(self):
350+
self.assertTrue(not hasattr(TiffImagePlugin, '_supported_modes'))
328351

329352
temp_file = self.tempfile("temp.tiff")
330353

331354
im = hopper()
332355
im.save(temp_file, convert_mode=True)
333356

357+
def test_convert_mode(self):
358+
for mode, modes in [
359+
['P', []], # no modes
360+
['P', ['P']] # same mode
361+
]:
362+
im = Image.new(mode, (100, 100))
363+
self.assertIsNone(im._convert_mode(modes))
364+
365+
for mode, modes in [
366+
['P', ['RGB']],
367+
['P', ['L']], # converting to a non-preferred mode
368+
['LA', ['P']],
369+
['I', ['L']],
370+
['RGB', ['L']],
371+
['RGB', ['CMYK']]
372+
]:
373+
im = Image.new(mode, (100, 100))
374+
self.assertIsNotNone(im._convert_mode(modes))
375+
334376
def test_effect_mandelbrot(self):
335377
# Arrange
336378
size = (512, 512)

src/PIL/GifImagePlugin.py

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

811811

812-
def _convert_mode(im):
813-
return {
814-
'LA':'P',
815-
'CMYK':'RGB'
816-
}.get(im.mode)
812+
def _supported_modes():
813+
return ['RGB', 'RGBA', 'P', 'I', 'F', 'LA', 'L', '1']
817814

818815

819816
# --------------------------------------------------------------------

src/PIL/Image.py

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

19561956
if params.get('convert_mode'):
19571957
plugin = sys.modules[save_handler.__module__]
1958-
converted_im = self._convert_mode(plugin, params)
1959-
if converted_im:
1960-
return converted_im.save(fp, format, **params)
1958+
if hasattr(plugin, '_supported_modes'):
1959+
modes = plugin._supported_modes()
1960+
converted_im = self._convert_mode(modes, params)
1961+
if converted_im:
1962+
return converted_im.save(fp, format, **params)
19611963

19621964
self.encoderinfo = params
19631965
self.encoderconfig = ()
@@ -1977,10 +1979,36 @@ def save(self, fp, format=None, **params):
19771979
if open_fp:
19781980
fp.close()
19791981

1980-
def _convert_mode(self, plugin, params):
1981-
if not hasattr(plugin, '_convert_mode'):
1982+
def _convert_mode(self, modes, params={}):
1983+
if not modes or self.mode in modes:
19821984
return
1983-
new_mode = plugin._convert_mode(self)
1985+
if self.mode == 'P':
1986+
preferred_modes = []
1987+
if 'A' in self.im.getpalettemode():
1988+
preferred_modes.append('RGBA')
1989+
preferred_modes.append('RGB')
1990+
else:
1991+
preferred_modes = {
1992+
'CMYK': ['RGB'],
1993+
'RGB': ['CMYK'],
1994+
'RGBX': ['RGB'],
1995+
'RGBa': ['RGBA', 'RGB'],
1996+
'RGBA': ['RGB'],
1997+
'LA': ['RGBA', 'P', 'L'],
1998+
'La': ['LA', 'L'],
1999+
'L': ['RGB'],
2000+
'F': ['I'],
2001+
'I': ['L', 'RGB'],
2002+
'1': ['L'],
2003+
'YCbCr': ['RGB'],
2004+
'LAB': ['RGB'],
2005+
'HSV': ['RGB']
2006+
}.get(self.mode, [])
2007+
for new_mode in preferred_modes:
2008+
if new_mode in modes:
2009+
break
2010+
else:
2011+
new_mode = modes[0]
19842012
if self.mode == 'LA' and new_mode == 'P':
19852013
alpha = self.getchannel('A')
19862014
# 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
@@ -792,15 +792,8 @@ def jpeg_factory(fp=None, filename=None):
792792
return im
793793

794794

795-
def _convert_mode(im):
796-
mode = im.mode
797-
if mode == 'P':
798-
return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB'
799-
return {
800-
'RGBA':'RGB',
801-
'LA':'L',
802-
'I':'L'
803-
}.get(mode)
795+
def _supported_modes():
796+
return ['RGB', 'CMYK', 'YCbCr', 'RGBX', 'L', '1']
804797

805798

806799
# ---------------------------------------------------------------------

src/PIL/PngImagePlugin.py

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

862862

863-
def _convert_mode(im):
864-
return {
865-
'CMYK':'RGB'
866-
}.get(im.mode)
863+
def _supported_modes():
864+
return ['RGB', 'RGBA', 'P', 'I', 'LA', 'L', '1']
867865

868866

869867
# --------------------------------------------------------------------

src/PIL/WebPImagePlugin.py

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

330330

331-
def _convert_mode(im):
332-
mode = im.mode
333-
if mode == 'P':
334-
return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB'
335-
return {
336-
# Pillow doesn't support L modes for webp for now.
337-
'L':'RGB',
338-
'LA':'RGBA',
339-
'I':'RGB',
340-
'CMYK':'RGB'
341-
}.get(mode)
331+
def _supported_modes():
332+
return ['RGB', 'RGBA', 'RGBa', 'RGBX', 'CMYK', 'YCbCr', 'HSV', 'I', 'F', 'P', 'LA', 'L', '1']
342333

343334

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

0 commit comments

Comments
 (0)