Skip to content

Commit fba0d01

Browse files
committed
Allow plugins to specify their supported modes
1 parent fb97583 commit fba0d01

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):
@@ -62,8 +65,7 @@ def test_width_height(self):
6265

6366
def test_invalid_image(self):
6467
if py3:
65-
import io
66-
im = io.BytesIO(b'')
68+
im = BytesIO(b'')
6769
else:
6870
import StringIO
6971
im = StringIO.StringIO('')
@@ -324,14 +326,54 @@ def test_registered_extensions(self):
324326
for ext in ['.cur', '.icns', '.tif', '.tiff']:
325327
self.assertIn(ext, extensions)
326328

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

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

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

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

src/PIL/GifImagePlugin.py

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

800800

801-
def _convert_mode(im):
802-
return {
803-
'LA':'P',
804-
'CMYK':'RGB'
805-
}.get(im.mode)
801+
def _supported_modes():
802+
return ['RGB', 'RGBA', 'P', 'I', 'F', 'LA', 'L', '1']
806803

807804

808805
# --------------------------------------------------------------------

src/PIL/Image.py

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

19371937
if params.get('convert_mode'):
19381938
plugin = sys.modules[save_handler.__module__]
1939-
converted_im = self._convert_mode(plugin, params)
1940-
if converted_im:
1941-
return converted_im.save(fp, format, **params)
1939+
if hasattr(plugin, '_supported_modes'):
1940+
modes = plugin._supported_modes()
1941+
converted_im = self._convert_mode(modes, params)
1942+
if converted_im:
1943+
return converted_im.save(fp, format, **params)
19421944

19431945
self.encoderinfo = params
19441946
self.encoderconfig = ()
@@ -1958,10 +1960,36 @@ def save(self, fp, format=None, **params):
19581960
if open_fp:
19591961
fp.close()
19601962

1961-
def _convert_mode(self, plugin, params):
1962-
if not hasattr(plugin, '_convert_mode'):
1963+
def _convert_mode(self, modes, params={}):
1964+
if not modes or self.mode in modes:
19631965
return
1964-
new_mode = plugin._convert_mode(self)
1966+
if self.mode == 'P':
1967+
preferred_modes = []
1968+
if 'A' in self.im.getpalettemode():
1969+
preferred_modes.append('RGBA')
1970+
preferred_modes.append('RGB')
1971+
else:
1972+
preferred_modes = {
1973+
'CMYK': ['RGB'],
1974+
'RGB': ['CMYK'],
1975+
'RGBX': ['RGB'],
1976+
'RGBa': ['RGBA', 'RGB'],
1977+
'RGBA': ['RGB'],
1978+
'LA': ['RGBA', 'P', 'L'],
1979+
'La': ['LA', 'L'],
1980+
'L': ['RGB'],
1981+
'F': ['I'],
1982+
'I': ['L', 'RGB'],
1983+
'1': ['L'],
1984+
'YCbCr': ['RGB'],
1985+
'LAB': ['RGB'],
1986+
'HSV': ['RGB']
1987+
}.get(self.mode, [])
1988+
for new_mode in preferred_modes:
1989+
if new_mode in modes:
1990+
break
1991+
else:
1992+
new_mode = modes[0]
19651993
if self.mode == 'LA' and new_mode == 'P':
19661994
alpha = self.getchannel('A')
19671995
# 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
@@ -793,15 +793,8 @@ def jpeg_factory(fp=None, filename=None):
793793
return im
794794

795795

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

806799

807800
# -------------------------------------------------------------------q-

src/PIL/PngImagePlugin.py

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

861861

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

867865

868866
# --------------------------------------------------------------------

src/PIL/WebPImagePlugin.py

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

310310

311-
def _convert_mode(im):
312-
mode = im.mode
313-
if mode == 'P':
314-
return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB'
315-
return {
316-
# Pillow doesn't support L modes for webp for now.
317-
'L':'RGB',
318-
'LA':'RGBA',
319-
'I':'RGB',
320-
'CMYK':'RGB'
321-
}.get(mode)
311+
def _supported_modes():
312+
return ['RGB', 'RGBA', 'RGBX', 'CMYK', 'YCbCr', 'HSV', 'I', 'F', 'P', 'LA', 'L', '1']
322313

323314

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

0 commit comments

Comments
 (0)