Skip to content

Commit 7b04d28

Browse files
committed
Added convert_mode param when saving
1 parent 3437d5f commit 7b04d28

11 files changed

Lines changed: 126 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
@@ -566,6 +566,16 @@ def test_save_I(self):
566566
reloaded = Image.open(out)
567567
self.assert_image_equal(reloaded.convert('L'), im.convert('L'))
568568

569+
def test_save_wrong_modes(self):
570+
out = BytesIO()
571+
for mode in ['CMYK']:
572+
img = Image.new(mode, (20, 20))
573+
self.assertRaises(ValueError, img.save, out, "GIF")
574+
575+
for mode in ['CMYK', 'LA']:
576+
img = Image.new(mode, (20, 20))
577+
img.save(out, "GIF", convert_mode=True)
578+
569579
def test_getdata(self):
570580
# test getheader/getdata against legacy values
571581
# 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
@@ -507,10 +507,22 @@ def test_save_correct_modes(self):
507507
def test_save_wrong_modes(self):
508508
# ref https://github.com/python-pillow/Pillow/issues/2005
509509
out = BytesIO()
510-
for mode in ['LA', 'La', 'RGBA', 'RGBa', 'P']:
510+
for mode in ['LA', 'La', 'RGBA', 'RGBa', 'P', 'I']:
511511
img = Image.new(mode, (20, 20))
512512
self.assertRaises(IOError, img.save, out, "JPEG")
513513

514+
for mode in ['LA', 'RGBA', 'P', 'I']:
515+
img = Image.new(mode, (20, 20))
516+
img.save(out, "JPEG", convert_mode=True)
517+
518+
img = Image.open('Tests/images/pil123rgba.png')
519+
temp_file = self.tempfile("temp.jpg")
520+
img.save(temp_file, convert_mode=True, fill_color='red')
521+
522+
reloaded = Image.open(temp_file)
523+
target = Image.open('Tests/images/pil123rgba_red.jpg')
524+
self.assert_image_similar(reloaded, target, 4)
525+
514526
def test_save_tiff_with_dpi(self):
515527
# Arrange
516528
outfile = self.tempfile("temp.tif")

Tests/test_file_png.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,13 @@ def test_load_transparent_rgb(self):
227227
# image has 876 transparent pixels
228228
self.assertEqual(im.getchannel('A').getcolors()[0][0], 876)
229229

230+
def test_save_CMYK(self):
231+
out = BytesIO()
232+
img = Image.new('CMYK', (20, 20))
233+
self.assertRaises(IOError, img.save, out, "PNG")
234+
235+
img.save(out, "PNG", convert_mode=True)
236+
230237
def test_save_p_transparent_palette(self):
231238
in_file = "Tests/images/pil123p.png"
232239
im = Image.open(in_file)

Tests/test_file_webp.py

Lines changed: 9 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
HAVE_WEBP = True
@@ -53,6 +55,13 @@ def test_read_rgb(self):
5355
self.assert_image_similar_tofile(
5456
image, 'Tests/images/hopper_webp_bits.ppm', 1.0)
5557

58+
@unittest.skipIf(not HAVE_WEBP, "WebP support not installed")
59+
def test_save_convert_mode(self):
60+
out = BytesIO()
61+
for mode in ['CMYK', 'I', 'L', 'LA', 'P']:
62+
img = Image.new(mode, (20, 20))
63+
img.save(out, "WEBP", convert_mode=True)
64+
5665
@unittest.skipIf(not HAVE_WEBP, "WebP support not installed")
5766
def test_write_rgb(self):
5867
"""

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

@@ -323,6 +323,14 @@ def test_registered_extensions(self):
323323
for ext in ['.cur', '.icns', '.tif', '.tiff']:
324324
self.assertIn(ext, extensions)
325325

326+
def test_no_convert_mode(self):
327+
self.assertTrue(not hasattr(TiffImagePlugin, '_convert_mode'))
328+
329+
temp_file = self.tempfile("temp.tiff")
330+
331+
im = hopper()
332+
im.save(temp_file, convert_mode=True)
333+
326334
def test_effect_mandelbrot(self):
327335
# Arrange
328336
size = (512, 512)

src/PIL/GifImagePlugin.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,13 @@ 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)
817+
818+
812819
# --------------------------------------------------------------------
813820
# Registry
814821

src/PIL/Image.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,10 +1934,6 @@ def save(self, fp, format=None, **params):
19341934
# may mutate self!
19351935
self.load()
19361936

1937-
save_all = params.pop('save_all', False)
1938-
self.encoderinfo = params
1939-
self.encoderconfig = ()
1940-
19411937
preinit()
19421938

19431939
ext = os.path.splitext(filename)[1].lower()
@@ -1952,11 +1948,20 @@ def save(self, fp, format=None, **params):
19521948

19531949
if format.upper() not in SAVE:
19541950
init()
1955-
if save_all:
1951+
if params.pop('save_all', False):
19561952
save_handler = SAVE_ALL[format.upper()]
19571953
else:
19581954
save_handler = SAVE[format.upper()]
19591955

1956+
if params.get('convert_mode'):
1957+
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)
1961+
1962+
self.encoderinfo = params
1963+
self.encoderconfig = ()
1964+
19601965
if open_fp:
19611966
if params.get('append', False):
19621967
fp = builtins.open(filename, "r+b")
@@ -1972,6 +1977,37 @@ def save(self, fp, format=None, **params):
19721977
if open_fp:
19731978
fp.close()
19741979

1980+
def _convert_mode(self, plugin, params):
1981+
if not hasattr(plugin, '_convert_mode'):
1982+
return
1983+
new_mode = plugin._convert_mode(self)
1984+
if self.mode == 'LA' and new_mode == 'P':
1985+
alpha = self.getchannel('A')
1986+
# Convert the image into P mode but only use 255 colors
1987+
# in the palette out of 256.
1988+
im = self.convert('L') \
1989+
.convert('P', palette=ADAPTIVE, colors=255)
1990+
# Set all pixel values below 128 to 255, and the rest to 0.
1991+
mask = eval(alpha, lambda px: 255 if px < 128 else 0)
1992+
# Paste the color of index 255 and use alpha as a mask.
1993+
im.paste(255, mask)
1994+
# The transparency index is 255.
1995+
im.info['transparency'] = 255
1996+
return im
1997+
1998+
elif self.mode == 'I':
1999+
im = self.point([i//256 for i in range(65536)], 'L')
2000+
return im.convert(new_mode) if new_mode != 'L' else im
2001+
2002+
elif self.mode in ('RGBA', 'LA') and new_mode in ('RGB', 'L'):
2003+
fill_color = params.get('fill_color', 'white')
2004+
background = new(new_mode, self.size, fill_color)
2005+
background.paste(self, self.getchannel('A'))
2006+
return background
2007+
2008+
elif new_mode:
2009+
return self.convert(new_mode)
2010+
19752011
def seek(self, frame):
19762012
"""
19772013
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
@@ -792,6 +792,17 @@ 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)
804+
805+
795806
# ---------------------------------------------------------------------
796807
# Registry stuff
797808

src/PIL/PngImagePlugin.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,12 @@ 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)
867+
868+
863869
# --------------------------------------------------------------------
864870
# Registry
865871

0 commit comments

Comments
 (0)