Skip to content

Commit 9e29cef

Browse files
committed
Added convert_mode param when saving
1 parent 948e303 commit 9e29cef

11 files changed

Lines changed: 139 additions & 8 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
@@ -554,6 +554,16 @@ def test_save_I(self):
554554
reloaded = Image.open(out)
555555
self.assert_image_equal(reloaded.convert('L'), im.convert('L'))
556556

557+
def test_save_wrong_modes(self):
558+
out = BytesIO()
559+
for mode in ['CMYK']:
560+
img = Image.new(mode, (20, 20))
561+
self.assertRaises(ValueError, img.save, out, "GIF")
562+
563+
for mode in ['CMYK', 'LA']:
564+
img = Image.new(mode, (20, 20))
565+
img.save(out, "GIF", convert_mode=True)
566+
557567
def test_getdata(self):
558568
# test getheader/getdata against legacy values
559569
# 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
@@ -509,10 +509,22 @@ def test_save_correct_modes(self):
509509
def test_save_wrong_modes(self):
510510
# ref https://github.com/python-pillow/Pillow/issues/2005
511511
out = BytesIO()
512-
for mode in ['LA', 'La', 'RGBA', 'RGBa', 'P']:
512+
for mode in ['LA', 'La', 'RGBA', 'RGBa', 'P', 'I']:
513513
img = Image.new(mode, (20, 20))
514514
self.assertRaises(IOError, img.save, out, "JPEG")
515515

516+
for mode in ['LA', 'RGBA', 'P', 'I']:
517+
img = Image.new(mode, (20, 20))
518+
img.save(out, "JPEG", convert_mode=True)
519+
520+
img = Image.open('Tests/images/pil123rgba.png')
521+
temp_file = self.tempfile("temp.jpg")
522+
img.save(temp_file, convert_mode=True, fill_color='red')
523+
524+
reloaded = Image.open(temp_file)
525+
target = Image.open('Tests/images/pil123rgba_red.jpg')
526+
self.assert_image_similar(reloaded, target, 4)
527+
516528
def test_save_tiff_with_dpi(self):
517529
# Arrange
518530
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
44

5+
from io import BytesIO
6+
57
try:
68
from PIL import _webp
79
HAVE_WEBP = True
@@ -44,6 +46,12 @@ def test_read_rgb(self):
4446
target = target.convert(self.rgb_mode)
4547
self.assert_image_similar(image, target, 20.0)
4648

49+
def test_save_convert_mode(self):
50+
out = BytesIO()
51+
for mode in ['CMYK', 'I', 'L', 'LA', 'P']:
52+
img = Image.new(mode, (20, 20))
53+
img.save(out, "WEBP", convert_mode=True)
54+
4755
def test_write_rgb(self):
4856
"""
4957
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

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

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

src/PIL/GifImagePlugin.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,13 @@ 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)
806+
807+
801808
# --------------------------------------------------------------------
802809
# Registry
803810

src/PIL/Image.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,12 +1903,6 @@ def save(self, fp, format=None, **params):
19031903
# may mutate self!
19041904
self.load()
19051905

1906-
save_all = False
1907-
if 'save_all' in params:
1908-
save_all = params.pop('save_all')
1909-
self.encoderinfo = params
1910-
self.encoderconfig = ()
1911-
19121906
preinit()
19131907

19141908
ext = os.path.splitext(filename)[1].lower()
@@ -1923,11 +1917,23 @@ def save(self, fp, format=None, **params):
19231917

19241918
if format.upper() not in SAVE:
19251919
init()
1920+
save_all = False
1921+
if 'save_all' in params:
1922+
save_all = params.pop('save_all')
19261923
if save_all:
19271924
save_handler = SAVE_ALL[format.upper()]
19281925
else:
19291926
save_handler = SAVE[format.upper()]
19301927

1928+
if params.get('convert_mode'):
1929+
plugin = sys.modules[save_handler.__module__]
1930+
convertedIm = self._convert_mode(plugin, params)
1931+
if convertedIm:
1932+
return convertedIm.save(fp, format, **params)
1933+
1934+
self.encoderinfo = params
1935+
self.encoderconfig = ()
1936+
19311937
if open_fp:
19321938
if params.get('append', False):
19331939
fp = builtins.open(filename, "r+b")
@@ -1943,6 +1949,37 @@ def save(self, fp, format=None, **params):
19431949
if open_fp:
19441950
fp.close()
19451951

1952+
def _convert_mode(self, plugin, params):
1953+
if not hasattr(plugin, '_convert_mode'):
1954+
return
1955+
new_mode = plugin._convert_mode(self)
1956+
if self.mode == 'LA' and new_mode == 'P':
1957+
alpha = self.getchannel('A')
1958+
# Convert the image into P mode but only use 255 colors
1959+
# in the palette out of 256.
1960+
im = self.convert('L') \
1961+
.convert('P', palette=ADAPTIVE, colors=255)
1962+
# Set all pixel values below 128 to 255, and the rest to 0.
1963+
mask = eval(alpha, lambda px: 255 if px < 128 else 0)
1964+
# Paste the color of index 255 and use alpha as a mask.
1965+
im.paste(255, mask)
1966+
# The transparency index is 255.
1967+
im.info['transparency'] = 255
1968+
return im
1969+
1970+
elif self.mode == 'I':
1971+
im = self.point([i//256 for i in range(65536)], 'L')
1972+
return im.convert(new_mode) if new_mode != 'L' else im
1973+
1974+
elif self.mode in ('RGBA', 'LA') and new_mode in ('RGB', 'L'):
1975+
fill_color = params.get('fill_color', 'white')
1976+
background = new(new_mode, self.size, fill_color)
1977+
background.paste(self, self.getchannel('A'))
1978+
return background
1979+
1980+
elif new_mode:
1981+
return self.convert(new_mode)
1982+
19461983
def seek(self, frame):
19471984
"""
19481985
Seeks to the given frame in this sequence file. If you seek

src/PIL/JpegImagePlugin.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,23 @@ 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+
new_mode = 'RGB'
799+
try:
800+
if 'A' in im.im.getpalettemode():
801+
new_mode = 'RGBA'
802+
except ValueError:
803+
pass
804+
return new_mode
805+
return {
806+
'RGBA':'RGB',
807+
'LA':'L',
808+
'I':'L'
809+
}.get(mode)
810+
811+
795812
# -------------------------------------------------------------------q-
796813
# Registry stuff
797814

src/PIL/PngImagePlugin.py

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

0 commit comments

Comments
 (0)