Skip to content

Commit bd286dd

Browse files
committed
Allow plugins to specify their supported modes
1 parent 8121e49 commit bd286dd

6 files changed

Lines changed: 113 additions & 53 deletions

File tree

Tests/test_image.py

Lines changed: 45 additions & 5 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
@@ -80,9 +82,7 @@ def test_width_height(self):
8082

8183
def test_invalid_image(self):
8284
if py3:
83-
import io
84-
85-
im = io.BytesIO(b"")
85+
im = BytesIO(b"")
8686
else:
8787
import StringIO
8888

@@ -359,14 +359,54 @@ def test_registered_extensions(self):
359359
for ext in [".cur", ".icns", ".tif", ".tiff"]:
360360
self.assertIn(ext, extensions)
361361

362-
def test_no_convert_mode(self):
363-
self.assertTrue(not hasattr(TiffImagePlugin, '_convert_mode'))
362+
def test_supported_modes(self):
363+
for format in Image.MIME.keys():
364+
try:
365+
save_handler = Image.SAVE[format]
366+
except KeyError:
367+
continue
368+
plugin = sys.modules[save_handler.__module__]
369+
if not hasattr(plugin, '_supported_modes'):
370+
continue
371+
372+
# Check that the supported modes list is accurate
373+
supported_modes = plugin._supported_modes()
374+
for mode in ['1', 'L', 'P', 'RGB', 'RGBA', 'CMYK', 'YCbCr', 'LAB',
375+
'HSV', 'I', 'F', 'LA', 'La', 'RGBX', 'RGBa']:
376+
out = BytesIO()
377+
im = Image.new(mode, (100, 100))
378+
if mode in supported_modes:
379+
im.save(out, format)
380+
else:
381+
self.assertRaises(Exception, im.save, out, format)
382+
383+
def test_no_supported_modes_method(self):
384+
self.assertTrue(not hasattr(TiffImagePlugin, '_supported_modes'))
364385

365386
temp_file = self.tempfile("temp.tiff")
366387

367388
im = hopper()
368389
im.save(temp_file, convert_mode=True)
369390

391+
def test_convert_mode(self):
392+
for mode, modes in [
393+
['P', []], # no modes
394+
['P', ['P']] # same mode
395+
]:
396+
im = Image.new(mode, (100, 100))
397+
self.assertIsNone(im._convert_mode(modes))
398+
399+
for mode, modes in [
400+
['P', ['RGB']],
401+
['P', ['L']], # converting to a non-preferred mode
402+
['LA', ['P']],
403+
['I', ['L']],
404+
['RGB', ['L']],
405+
['RGB', ['CMYK']]
406+
]:
407+
im = Image.new(mode, (100, 100))
408+
self.assertIsNotNone(im._convert_mode(modes))
409+
370410
def test_effect_mandelbrot(self):
371411
# Arrange
372412
size = (512, 512)

src/PIL/GifImagePlugin.py

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

845845

846-
def _convert_mode(im):
847-
return {
848-
'LA':'P',
849-
'CMYK':'RGB'
850-
}.get(im.mode)
846+
def _supported_modes():
847+
return ["RGB", "RGBA", "P", "I", "F", "LA", "L", "1"]
851848

852849

853850
# --------------------------------------------------------------------

src/PIL/Image.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2040,16 +2040,18 @@ def save(self, fp, format=None, **params):
20402040

20412041
if format.upper() not in SAVE:
20422042
init()
2043-
if params.pop('save_all', False):
2043+
if params.pop("save_all", False):
20442044
save_handler = SAVE_ALL[format.upper()]
20452045
else:
20462046
save_handler = SAVE[format.upper()]
20472047

2048-
if params.get('convert_mode'):
2048+
if params.get("convert_mode"):
20492049
plugin = sys.modules[save_handler.__module__]
2050-
converted_im = self._convert_mode(plugin, params)
2051-
if converted_im:
2052-
return converted_im.save(fp, format, **params)
2050+
if hasattr(plugin, "_supported_modes"):
2051+
modes = plugin._supported_modes()
2052+
converted_im = self._convert_mode(modes, params)
2053+
if converted_im:
2054+
return converted_im.save(fp, format, **params)
20532055

20542056
self.encoderinfo = params
20552057
self.encoderconfig = ()
@@ -2069,32 +2071,57 @@ def save(self, fp, format=None, **params):
20692071
if open_fp:
20702072
fp.close()
20712073

2072-
def _convert_mode(self, plugin, params):
2073-
if not hasattr(plugin, '_convert_mode'):
2074+
def _convert_mode(self, modes, params={}):
2075+
if not modes or self.mode in modes:
20742076
return
2075-
new_mode = plugin._convert_mode(self)
2076-
if self.mode == 'LA' and new_mode == 'P':
2077-
alpha = self.getchannel('A')
2077+
if self.mode == "P":
2078+
preferred_modes = []
2079+
if "A" in self.im.getpalettemode():
2080+
preferred_modes.append("RGBA")
2081+
preferred_modes.append("RGB")
2082+
else:
2083+
preferred_modes = {
2084+
"CMYK": ["RGB"],
2085+
"RGB": ["CMYK"],
2086+
"RGBX": ["RGB"],
2087+
"RGBa": ["RGBA", "RGB"],
2088+
"RGBA": ["RGB"],
2089+
"LA": ["RGBA", "P", "L"],
2090+
"La": ["LA", "L"],
2091+
"L": ["RGB"],
2092+
"F": ["I"],
2093+
"I": ["L", "RGB"],
2094+
"1": ["L"],
2095+
"YCbCr": ["RGB"],
2096+
"LAB": ["RGB"],
2097+
"HSV": ["RGB"],
2098+
}.get(self.mode, [])
2099+
for new_mode in preferred_modes:
2100+
if new_mode in modes:
2101+
break
2102+
else:
2103+
new_mode = modes[0]
2104+
if self.mode == "LA" and new_mode == "P":
2105+
alpha = self.getchannel("A")
20782106
# Convert the image into P mode but only use 255 colors
20792107
# in the palette out of 256.
2080-
im = self.convert('L') \
2081-
.convert('P', palette=ADAPTIVE, colors=255)
2108+
im = self.convert("L").convert("P", palette=ADAPTIVE, colors=255)
20822109
# Set all pixel values below 128 to 255, and the rest to 0.
20832110
mask = eval(alpha, lambda px: 255 if px < 128 else 0)
20842111
# Paste the color of index 255 and use alpha as a mask.
20852112
im.paste(255, mask)
20862113
# The transparency index is 255.
2087-
im.info['transparency'] = 255
2114+
im.info["transparency"] = 255
20882115
return im
20892116

2090-
elif self.mode == 'I':
2091-
im = self.point([i//256 for i in range(65536)], 'L')
2092-
return im.convert(new_mode) if new_mode != 'L' else im
2117+
elif self.mode == "I":
2118+
im = self.point([i // 256 for i in range(65536)], "L")
2119+
return im.convert(new_mode) if new_mode != "L" else im
20932120

2094-
elif self.mode in ('RGBA', 'LA') and new_mode in ('RGB', 'L'):
2095-
fill_color = params.get('fill_color', 'white')
2121+
elif self.mode in ("RGBA", "LA") and new_mode in ("RGB", "L"):
2122+
fill_color = params.get("fill_color", "white")
20962123
background = new(new_mode, self.size, fill_color)
2097-
background.paste(self, self.getchannel('A'))
2124+
background.paste(self, self.getchannel("A"))
20982125
return background
20992126

21002127
elif new_mode:

src/PIL/JpegImagePlugin.py

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

817817

818-
def _convert_mode(im):
819-
mode = im.mode
820-
if mode == 'P':
821-
return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB'
822-
return {
823-
'RGBA':'RGB',
824-
'LA':'L',
825-
'I':'L'
826-
}.get(mode)
818+
def _supported_modes():
819+
return ["RGB", "CMYK", "YCbCr", "RGBX", "L", "1"]
827820

828821

829822
# ---------------------------------------------------------------------

src/PIL/PngImagePlugin.py

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

948948

949-
def _convert_mode(im):
950-
return {
951-
'CMYK':'RGB'
952-
}.get(im.mode)
949+
def _supported_modes():
950+
return ["RGB", "RGBA", "P", "I", "LA", "L", "1"]
953951

954952

955953
# --------------------------------------------------------------------

src/PIL/WebPImagePlugin.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -350,17 +350,22 @@ def _save(im, fp, filename):
350350
fp.write(data)
351351

352352

353-
def _convert_mode(im):
354-
mode = im.mode
355-
if mode == 'P':
356-
return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB'
357-
return {
358-
# Pillow doesn't support L modes for webp for now.
359-
'L':'RGB',
360-
'LA':'RGBA',
361-
'I':'RGB',
362-
'CMYK':'RGB'
363-
}.get(mode)
353+
def _supported_modes():
354+
return [
355+
"RGB",
356+
"RGBA",
357+
"RGBa",
358+
"RGBX",
359+
"CMYK",
360+
"YCbCr",
361+
"HSV",
362+
"I",
363+
"F",
364+
"P",
365+
"LA",
366+
"L",
367+
"1",
368+
]
364369

365370

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

0 commit comments

Comments
 (0)