Skip to content

Commit 8dcd9df

Browse files
committed
Added convert_mode param when saving
1 parent 70b97e7 commit 8dcd9df

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
@@ -630,6 +630,16 @@ def test_save_I(self):
630630
reloaded = Image.open(out)
631631
self.assert_image_equal(reloaded.convert('L'), im.convert('L'))
632632

633+
def test_save_wrong_modes(self):
634+
out = BytesIO()
635+
for mode in ['CMYK']:
636+
img = Image.new(mode, (20, 20))
637+
self.assertRaises(ValueError, img.save, out, "GIF")
638+
639+
for mode in ['CMYK', 'LA']:
640+
img = Image.new(mode, (20, 20))
641+
img.save(out, "GIF", convert_mode=True)
642+
633643
def test_getdata(self):
634644
# test getheader/getdata against legacy values
635645
# 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: 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
HAVE_WEBP = True
@@ -53,6 +55,12 @@ def test_read_rgb(self):
5355
self.assert_image_similar_tofile(
5456
image, 'Tests/images/hopper_webp_bits.ppm', 1.0)
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
@@ -334,6 +334,14 @@ def test_registered_extensions(self):
334334
for ext in ['.cur', '.icns', '.tif', '.tiff']:
335335
self.assertIn(ext, extensions)
336336

337+
def test_no_convert_mode(self):
338+
self.assertTrue(not hasattr(TiffImagePlugin, '_convert_mode'))
339+
340+
temp_file = self.tempfile("temp.tiff")
341+
342+
im = hopper()
343+
im.save(temp_file, convert_mode=True)
344+
337345
def test_effect_mandelbrot(self):
338346
# Arrange
339347
size = (512, 512)

src/PIL/GifImagePlugin.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,13 @@ def write(self, data):
840840
return fp.data
841841

842842

843+
def _convert_mode(im):
844+
return {
845+
'LA':'P',
846+
'CMYK':'RGB'
847+
}.get(im.mode)
848+
849+
843850
# --------------------------------------------------------------------
844851
# Registry
845852

src/PIL/Image.py

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

1969-
save_all = params.pop('save_all', False)
1970-
self.encoderinfo = params
1971-
self.encoderconfig = ()
1972-
19731969
preinit()
19741970

19751971
ext = os.path.splitext(filename)[1].lower()
@@ -1984,11 +1980,20 @@ def save(self, fp, format=None, **params):
19841980

19851981
if format.upper() not in SAVE:
19861982
init()
1987-
if save_all:
1983+
if params.pop('save_all', False):
19881984
save_handler = SAVE_ALL[format.upper()]
19891985
else:
19901986
save_handler = SAVE[format.upper()]
19911987

1988+
if params.get('convert_mode'):
1989+
plugin = sys.modules[save_handler.__module__]
1990+
converted_im = self._convert_mode(plugin, params)
1991+
if converted_im:
1992+
return converted_im.save(fp, format, **params)
1993+
1994+
self.encoderinfo = params
1995+
self.encoderconfig = ()
1996+
19921997
if open_fp:
19931998
if params.get('append', False):
19941999
fp = builtins.open(filename, "r+b")
@@ -2004,6 +2009,37 @@ def save(self, fp, format=None, **params):
20042009
if open_fp:
20052010
fp.close()
20062011

2012+
def _convert_mode(self, plugin, params):
2013+
if not hasattr(plugin, '_convert_mode'):
2014+
return
2015+
new_mode = plugin._convert_mode(self)
2016+
if self.mode == 'LA' and new_mode == 'P':
2017+
alpha = self.getchannel('A')
2018+
# Convert the image into P mode but only use 255 colors
2019+
# in the palette out of 256.
2020+
im = self.convert('L') \
2021+
.convert('P', palette=ADAPTIVE, colors=255)
2022+
# Set all pixel values below 128 to 255, and the rest to 0.
2023+
mask = eval(alpha, lambda px: 255 if px < 128 else 0)
2024+
# Paste the color of index 255 and use alpha as a mask.
2025+
im.paste(255, mask)
2026+
# The transparency index is 255.
2027+
im.info['transparency'] = 255
2028+
return im
2029+
2030+
elif self.mode == 'I':
2031+
im = self.point([i//256 for i in range(65536)], 'L')
2032+
return im.convert(new_mode) if new_mode != 'L' else im
2033+
2034+
elif self.mode in ('RGBA', 'LA') and new_mode in ('RGB', 'L'):
2035+
fill_color = params.get('fill_color', 'white')
2036+
background = new(new_mode, self.size, fill_color)
2037+
background.paste(self, self.getchannel('A'))
2038+
return background
2039+
2040+
elif new_mode:
2041+
return self.convert(new_mode)
2042+
20072043
def seek(self, frame):
20082044
"""
20092045
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
@@ -837,6 +837,17 @@ def jpeg_factory(fp=None, filename=None):
837837
return im
838838

839839

840+
def _convert_mode(im):
841+
mode = im.mode
842+
if mode == 'P':
843+
return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB'
844+
return {
845+
'RGBA':'RGB',
846+
'LA':'L',
847+
'I':'L'
848+
}.get(mode)
849+
850+
840851
# ---------------------------------------------------------------------
841852
# Registry stuff
842853

src/PIL/PngImagePlugin.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,12 @@ def append(fp, cid, *data):
924924
return fp.data
925925

926926

927+
def _convert_mode(im):
928+
return {
929+
'CMYK':'RGB'
930+
}.get(im.mode)
931+
932+
927933
# --------------------------------------------------------------------
928934
# Registry
929935

0 commit comments

Comments
 (0)