Skip to content

Commit 8121e49

Browse files
committed
Added convert_mode param when saving
1 parent f5cd8b4 commit 8121e49

11 files changed

Lines changed: 125 additions & 7 deletions

Tests/images/pil123rgba_red.jpg

4.56 KB
Loading

Tests/test_file_gif.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,16 @@ def test_save_I(self):
613613
reloaded = Image.open(out)
614614
self.assert_image_equal(reloaded.convert("L"), im.convert("L"))
615615

616+
def test_save_wrong_modes(self):
617+
out = BytesIO()
618+
for mode in ['CMYK']:
619+
img = Image.new(mode, (20, 20))
620+
self.assertRaises(ValueError, img.save, out, "GIF")
621+
622+
for mode in ['CMYK', 'LA']:
623+
img = Image.new(mode, (20, 20))
624+
img.save(out, "GIF", convert_mode=True)
625+
616626
def test_getdata(self):
617627
# test getheader/getdata against legacy values
618628
# Create a 'P' image with holes in the palette

Tests/test_file_jpeg.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,10 +532,22 @@ def test_save_correct_modes(self):
532532
def test_save_wrong_modes(self):
533533
# ref https://github.com/python-pillow/Pillow/issues/2005
534534
out = BytesIO()
535-
for mode in ["LA", "La", "RGBA", "RGBa", "P"]:
535+
for mode in ["LA", "La", "RGBA", "RGBa", "P", "I"]:
536536
img = Image.new(mode, (20, 20))
537537
self.assertRaises(IOError, img.save, out, "JPEG")
538538

539+
for mode in ['LA', 'RGBA', 'P', 'I']:
540+
img = Image.new(mode, (20, 20))
541+
img.save(out, "JPEG", convert_mode=True)
542+
543+
img = Image.open('Tests/images/pil123rgba.png')
544+
temp_file = self.tempfile("temp.jpg")
545+
img.save(temp_file, convert_mode=True, fill_color='red')
546+
547+
reloaded = Image.open(temp_file)
548+
target = Image.open('Tests/images/pil123rgba_red.jpg')
549+
self.assert_image_similar(reloaded, target, 4)
550+
539551
def test_save_tiff_with_dpi(self):
540552
# Arrange
541553
outfile = self.tempfile("temp.tif")

Tests/test_file_png.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,13 @@ def test_load_transparent_rgb(self):
233233
# image has 876 transparent pixels
234234
self.assertEqual(im.getchannel("A").getcolors()[0][0], 876)
235235

236+
def test_save_CMYK(self):
237+
out = BytesIO()
238+
img = Image.new('CMYK', (20, 20))
239+
self.assertRaises(IOError, img.save, out, "PNG")
240+
241+
img.save(out, "PNG", convert_mode=True)
242+
236243
def test_save_p_transparent_palette(self):
237244
in_file = "Tests/images/pil123p.png"
238245
im = Image.open(in_file)

Tests/test_file_webp.py

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

33
from PIL import Image, WebPImagePlugin
44

5+
from io import BytesIO
6+
57
try:
68
from PIL import _webp
79

@@ -53,6 +55,12 @@ def test_read_rgb(self):
5355
image, "Tests/images/hopper_webp_bits.ppm", 1.0
5456
)
5557

58+
def test_save_convert_mode(self):
59+
out = BytesIO()
60+
for mode in ['CMYK', 'I', 'L', 'LA', 'P']:
61+
img = Image.new(mode, (20, 20))
62+
img.save(out, "WEBP", convert_mode=True)
63+
5664
def test_write_rgb(self):
5765
"""
5866
Can we write a RGB mode file to webp without error.

Tests/test_image.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .helper import unittest, PillowTestCase, hopper
22

3-
from PIL import Image
3+
from PIL import Image, TiffImagePlugin
44
from PIL._util import py3
55
import os
66
import sys
@@ -359,6 +359,14 @@ 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'))
364+
365+
temp_file = self.tempfile("temp.tiff")
366+
367+
im = hopper()
368+
im.save(temp_file, convert_mode=True)
369+
362370
def test_effect_mandelbrot(self):
363371
# Arrange
364372
size = (512, 512)

src/PIL/GifImagePlugin.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,13 @@ 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)
851+
852+
846853
# --------------------------------------------------------------------
847854
# Registry
848855

src/PIL/Image.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,10 +2026,6 @@ def save(self, fp, format=None, **params):
20262026
# may mutate self!
20272027
self._ensure_mutable()
20282028

2029-
save_all = params.pop("save_all", False)
2030-
self.encoderinfo = params
2031-
self.encoderconfig = ()
2032-
20332029
preinit()
20342030

20352031
ext = os.path.splitext(filename)[1].lower()
@@ -2044,11 +2040,20 @@ def save(self, fp, format=None, **params):
20442040

20452041
if format.upper() not in SAVE:
20462042
init()
2047-
if save_all:
2043+
if params.pop('save_all', False):
20482044
save_handler = SAVE_ALL[format.upper()]
20492045
else:
20502046
save_handler = SAVE[format.upper()]
20512047

2048+
if params.get('convert_mode'):
2049+
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)
2053+
2054+
self.encoderinfo = params
2055+
self.encoderconfig = ()
2056+
20522057
if open_fp:
20532058
if params.get("append", False):
20542059
fp = builtins.open(filename, "r+b")
@@ -2064,6 +2069,37 @@ def save(self, fp, format=None, **params):
20642069
if open_fp:
20652070
fp.close()
20662071

2072+
def _convert_mode(self, plugin, params):
2073+
if not hasattr(plugin, '_convert_mode'):
2074+
return
2075+
new_mode = plugin._convert_mode(self)
2076+
if self.mode == 'LA' and new_mode == 'P':
2077+
alpha = self.getchannel('A')
2078+
# Convert the image into P mode but only use 255 colors
2079+
# in the palette out of 256.
2080+
im = self.convert('L') \
2081+
.convert('P', palette=ADAPTIVE, colors=255)
2082+
# Set all pixel values below 128 to 255, and the rest to 0.
2083+
mask = eval(alpha, lambda px: 255 if px < 128 else 0)
2084+
# Paste the color of index 255 and use alpha as a mask.
2085+
im.paste(255, mask)
2086+
# The transparency index is 255.
2087+
im.info['transparency'] = 255
2088+
return im
2089+
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
2093+
2094+
elif self.mode in ('RGBA', 'LA') and new_mode in ('RGB', 'L'):
2095+
fill_color = params.get('fill_color', 'white')
2096+
background = new(new_mode, self.size, fill_color)
2097+
background.paste(self, self.getchannel('A'))
2098+
return background
2099+
2100+
elif new_mode:
2101+
return self.convert(new_mode)
2102+
20672103
def seek(self, frame):
20682104
"""
20692105
Seeks to the given frame in this sequence file. If you seek

src/PIL/JpegImagePlugin.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,17 @@ 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)
827+
828+
818829
# ---------------------------------------------------------------------
819830
# Registry stuff
820831

src/PIL/PngImagePlugin.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,12 @@ 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)
953+
954+
949955
# --------------------------------------------------------------------
950956
# Registry
951957

0 commit comments

Comments
 (0)