Skip to content

Commit 85e8890

Browse files
committed
Added convert_mode param when saving
1 parent 24f0bbf commit 85e8890

11 files changed

Lines changed: 135 additions & 8 deletions

Tests/images/pil123rgba_red.jpg

4.56 KB
Loading

Tests/test_file_gif.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,18 @@ def test_save_I(tmp_path):
878878
assert_image_equal(reloaded.convert("L"), im.convert("L"))
879879

880880

881+
def test_save_wrong_modes():
882+
out = BytesIO()
883+
for mode in ["CMYK"]:
884+
img = Image.new(mode, (20, 20))
885+
with pytest.raises(ValueError):
886+
img.save(out, "GIF")
887+
888+
for mode in ["CMYK", "LA"]:
889+
img = Image.new(mode, (20, 20))
890+
img.save(out, "GIF", convert_mode=True)
891+
892+
881893
def test_getdata():
882894
# Test getheader/getdata against legacy values.
883895
# Create a 'P' image with holes in the palette.

Tests/test_file_jpeg.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -647,14 +647,26 @@ def test_save_correct_modes(self):
647647
img = Image.new(mode, (20, 20))
648648
img.save(out, "JPEG")
649649

650-
def test_save_wrong_modes(self):
650+
def test_save_wrong_modes(self, tmp_path):
651651
# ref https://github.com/python-pillow/Pillow/issues/2005
652652
out = BytesIO()
653-
for mode in ["LA", "La", "RGBA", "RGBa", "P"]:
653+
for mode in ["LA", "La", "RGBA", "RGBa", "P", "I"]:
654654
img = Image.new(mode, (20, 20))
655655
with pytest.raises(OSError):
656656
img.save(out, "JPEG")
657657

658+
for mode in ["LA", "RGBA", "P", "I"]:
659+
img = Image.new(mode, (20, 20))
660+
img.save(out, "JPEG", convert_mode=True)
661+
662+
temp_file = str(tmp_path / "temp.jpg")
663+
with Image.open("Tests/images/pil123rgba.png") as img:
664+
img.save(temp_file, convert_mode=True, fill_color="red")
665+
666+
with Image.open(temp_file) as reloaded:
667+
with Image.open("Tests/images/pil123rgba_red.jpg") as target:
668+
assert_image_similar(reloaded, target, 4)
669+
658670
def test_save_tiff_with_dpi(self, tmp_path):
659671
# Arrange
660672
outfile = str(tmp_path / "temp.tif")

Tests/test_file_png.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,14 @@ def test_load_transparent_rgb(self):
239239
# image has 876 transparent pixels
240240
assert im.getchannel("A").getcolors()[0][0] == 876
241241

242+
def test_save_CMYK(self):
243+
out = BytesIO()
244+
im = Image.new("CMYK", (20, 20))
245+
with pytest.raises(IOError):
246+
im.save(out, "PNG")
247+
248+
im.save(out, "PNG", convert_mode=True)
249+
242250
def test_save_p_transparent_palette(self, tmp_path):
243251
in_file = "Tests/images/pil123p.png"
244252
with Image.open(in_file) as im:

Tests/test_file_webp.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
skip_unless_feature,
1414
)
1515

16+
from io import BytesIO
17+
1618
try:
1719
from PIL import _webp
1820

@@ -86,6 +88,12 @@ def _roundtrip(self, tmp_path, mode, epsilon, args={}):
8688
target = target.convert(self.rgb_mode)
8789
assert_image_similar(image, target, epsilon)
8890

91+
def test_save_convert_mode(self):
92+
out = BytesIO()
93+
for mode in ["CMYK", "I", "L", "LA", "P"]:
94+
img = Image.new(mode, (20, 20))
95+
img.save(out, "WEBP", convert_mode=True)
96+
8997
def test_write_rgb(self, tmp_path):
9098
"""
9199
Can we write a RGB mode file to webp without error?

Tests/test_image.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55

66
import pytest
77

8-
from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError
8+
from PIL import (
9+
Image,
10+
ImageDraw,
11+
ImagePalette,
12+
TiffImagePlugin,
13+
UnidentifiedImageError,
14+
)
915

1016
from .helper import (
1117
assert_image_equal,
@@ -407,6 +413,14 @@ def test_registered_extensions(self):
407413
for ext in [".cur", ".icns", ".tif", ".tiff"]:
408414
assert ext in extensions
409415

416+
def test_no_convert_mode(self, tmp_path):
417+
assert not hasattr(TiffImagePlugin, "_convert_mode")
418+
419+
temp_file = str(tmp_path / "temp.tiff")
420+
421+
im = hopper()
422+
im.save(temp_file, convert_mode=True)
423+
410424
def test_effect_mandelbrot(self):
411425
# Arrange
412426
size = (512, 512)

src/PIL/GifImagePlugin.py

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

912912

913+
def _convert_mode(im):
914+
return {
915+
'LA':'P',
916+
'CMYK':'RGB'
917+
}.get(im.mode)
918+
919+
913920
# --------------------------------------------------------------------
914921
# Registry
915922

src/PIL/Image.py

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

2179-
save_all = params.pop("save_all", False)
2180-
self.encoderinfo = params
2181-
self.encoderconfig = ()
2182-
21832179
preinit()
21842180

21852181
ext = os.path.splitext(filename)[1].lower()
@@ -2194,11 +2190,20 @@ def save(self, fp, format=None, **params):
21942190

21952191
if format.upper() not in SAVE:
21962192
init()
2197-
if save_all:
2193+
if params.pop('save_all', False):
21982194
save_handler = SAVE_ALL[format.upper()]
21992195
else:
22002196
save_handler = SAVE[format.upper()]
22012197

2198+
if params.get('convert_mode'):
2199+
plugin = sys.modules[save_handler.__module__]
2200+
converted_im = self._convert_mode(plugin, params)
2201+
if converted_im:
2202+
return converted_im.save(fp, format, **params)
2203+
2204+
self.encoderinfo = params
2205+
self.encoderconfig = ()
2206+
22022207
if open_fp:
22032208
if params.get("append", False):
22042209
# Open also for reading ("+"), because TIFF save_all
@@ -2214,6 +2219,37 @@ def save(self, fp, format=None, **params):
22142219
if open_fp:
22152220
fp.close()
22162221

2222+
def _convert_mode(self, plugin, params):
2223+
if not hasattr(plugin, '_convert_mode'):
2224+
return
2225+
new_mode = plugin._convert_mode(self)
2226+
if self.mode == 'LA' and new_mode == 'P':
2227+
alpha = self.getchannel('A')
2228+
# Convert the image into P mode but only use 255 colors
2229+
# in the palette out of 256.
2230+
im = self.convert('L') \
2231+
.convert('P', palette=ADAPTIVE, colors=255)
2232+
# Set all pixel values below 128 to 255, and the rest to 0.
2233+
mask = eval(alpha, lambda px: 255 if px < 128 else 0)
2234+
# Paste the color of index 255 and use alpha as a mask.
2235+
im.paste(255, mask)
2236+
# The transparency index is 255.
2237+
im.info['transparency'] = 255
2238+
return im
2239+
2240+
elif self.mode == 'I':
2241+
im = self.point([i//256 for i in range(65536)], 'L')
2242+
return im.convert(new_mode) if new_mode != 'L' else im
2243+
2244+
elif self.mode in ('RGBA', 'LA') and new_mode in ('RGB', 'L'):
2245+
fill_color = params.get('fill_color', 'white')
2246+
background = new(new_mode, self.size, fill_color)
2247+
background.paste(self, self.getchannel('A'))
2248+
return background
2249+
2250+
elif new_mode:
2251+
return self.convert(new_mode)
2252+
22172253
def seek(self, frame):
22182254
"""
22192255
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
@@ -1394,6 +1394,12 @@ def append(fp, cid, *data):
13941394
return fp.data
13951395

13961396

1397+
def _convert_mode(im):
1398+
return {
1399+
'CMYK':'RGB'
1400+
}.get(im.mode)
1401+
1402+
13971403
# --------------------------------------------------------------------
13981404
# Registry
13991405

0 commit comments

Comments
 (0)