mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-23 07:10:33 +03:00
Allow plugins to specify their supported modes
This commit is contained in:
parent
8fab24c8ab
commit
dac567f42b
|
@ -672,8 +672,7 @@ class TestFileJpeg:
|
|||
img.save(temp_file, convert_mode=True, fill_color="red")
|
||||
|
||||
with Image.open(temp_file) as reloaded:
|
||||
with Image.open("Tests/images/pil123rgba_red.jpg") as target:
|
||||
assert_image_similar(reloaded, target, 4)
|
||||
assert_image_similar_tofile(reloaded, "Tests/images/pil123rgba_red.jpg", 4)
|
||||
|
||||
def test_save_tiff_with_dpi(self, tmp_path):
|
||||
# Arrange
|
||||
|
|
|
@ -15,8 +15,6 @@ from .helper import (
|
|||
skip_unless_feature,
|
||||
)
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
try:
|
||||
from PIL import _webp
|
||||
|
||||
|
@ -91,7 +89,7 @@ class TestFileWebp:
|
|||
assert_image_similar(image, target, epsilon)
|
||||
|
||||
def test_save_convert_mode(self):
|
||||
out = BytesIO()
|
||||
out = io.BytesIO()
|
||||
for mode in ["CMYK", "I", "L", "LA", "P"]:
|
||||
img = Image.new(mode, (20, 20))
|
||||
img.save(out, "WEBP", convert_mode=True)
|
||||
|
|
|
@ -7,13 +7,7 @@ import warnings
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import (
|
||||
Image,
|
||||
ImageDraw,
|
||||
ImagePalette,
|
||||
TiffImagePlugin,
|
||||
UnidentifiedImageError,
|
||||
)
|
||||
from PIL import Image, ImageDraw, ImagePalette, TiffImagePlugin, UnidentifiedImageError
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -138,8 +132,6 @@ class TestImage:
|
|||
im.size = (3, 4)
|
||||
|
||||
def test_invalid_image(self):
|
||||
import io
|
||||
|
||||
im = io.BytesIO(b"")
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
with Image.open(im):
|
||||
|
@ -430,14 +422,67 @@ class TestImage:
|
|||
for ext in [".cur", ".icns", ".tif", ".tiff"]:
|
||||
assert ext in extensions
|
||||
|
||||
def test_no_convert_mode(self, tmp_path):
|
||||
assert not hasattr(TiffImagePlugin, "_convert_mode")
|
||||
def test_supported_modes(self):
|
||||
for format in Image.MIME.keys():
|
||||
try:
|
||||
save_handler = Image.SAVE[format]
|
||||
except KeyError:
|
||||
continue
|
||||
plugin = sys.modules[save_handler.__module__]
|
||||
if not hasattr(plugin, "_supported_modes"):
|
||||
continue
|
||||
|
||||
# Check that the supported modes list is accurate
|
||||
supported_modes = plugin._supported_modes()
|
||||
for mode in [
|
||||
"1",
|
||||
"L",
|
||||
"P",
|
||||
"RGB",
|
||||
"RGBA",
|
||||
"CMYK",
|
||||
"YCbCr",
|
||||
"LAB",
|
||||
"HSV",
|
||||
"I",
|
||||
"F",
|
||||
"LA",
|
||||
"La",
|
||||
"RGBX",
|
||||
"RGBa",
|
||||
]:
|
||||
out = io.BytesIO()
|
||||
im = Image.new(mode, (100, 100))
|
||||
if mode in supported_modes:
|
||||
im.save(out, format)
|
||||
else:
|
||||
with pytest.raises(Exception):
|
||||
im.save(out, format)
|
||||
|
||||
def test_no_supported_modes_method(self, tmp_path):
|
||||
assert not hasattr(TiffImagePlugin, "_supported_modes")
|
||||
|
||||
temp_file = str(tmp_path / "temp.tiff")
|
||||
|
||||
im = hopper()
|
||||
im.save(temp_file, convert_mode=True)
|
||||
|
||||
def test_convert_mode(self):
|
||||
for mode, modes in [["P", []], ["P", ["P"]]]: # no modes, same mode
|
||||
im = Image.new(mode, (100, 100))
|
||||
assert im._convert_mode(modes) is None
|
||||
|
||||
for mode, modes in [
|
||||
["P", ["RGB"]],
|
||||
["P", ["L"]], # converting to a non-preferred mode
|
||||
["LA", ["P"]],
|
||||
["I", ["L"]],
|
||||
["RGB", ["L"]],
|
||||
["RGB", ["CMYK"]],
|
||||
]:
|
||||
im = Image.new(mode, (100, 100))
|
||||
assert im._convert_mode(modes) is not None
|
||||
|
||||
def test_effect_mandelbrot(self):
|
||||
# Arrange
|
||||
size = (512, 512)
|
||||
|
|
|
@ -1022,11 +1022,8 @@ def getdata(im, offset=(0, 0), **params):
|
|||
return fp.data
|
||||
|
||||
|
||||
def _convert_mode(im):
|
||||
return {
|
||||
'LA':'P',
|
||||
'CMYK':'RGB'
|
||||
}.get(im.mode)
|
||||
def _supported_modes():
|
||||
return ["RGB", "RGBA", "P", "I", "F", "LA", "L", "1"]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -2277,16 +2277,18 @@ class Image:
|
|||
|
||||
if format.upper() not in SAVE:
|
||||
init()
|
||||
if params.pop('save_all', False):
|
||||
if params.pop("save_all", False):
|
||||
save_handler = SAVE_ALL[format.upper()]
|
||||
else:
|
||||
save_handler = SAVE[format.upper()]
|
||||
|
||||
if params.get('convert_mode'):
|
||||
if params.get("convert_mode"):
|
||||
plugin = sys.modules[save_handler.__module__]
|
||||
converted_im = self._convert_mode(plugin, params)
|
||||
if converted_im:
|
||||
return converted_im.save(fp, format, **params)
|
||||
if hasattr(plugin, "_supported_modes"):
|
||||
modes = plugin._supported_modes()
|
||||
converted_im = self._convert_mode(modes, params)
|
||||
if converted_im:
|
||||
return converted_im.save(fp, format, **params)
|
||||
|
||||
self.encoderinfo = params
|
||||
self.encoderconfig = ()
|
||||
|
@ -2315,32 +2317,57 @@ class Image:
|
|||
if open_fp:
|
||||
fp.close()
|
||||
|
||||
def _convert_mode(self, plugin, params):
|
||||
if not hasattr(plugin, '_convert_mode'):
|
||||
def _convert_mode(self, modes, params={}):
|
||||
if not modes or self.mode in modes:
|
||||
return
|
||||
new_mode = plugin._convert_mode(self)
|
||||
if self.mode == 'LA' and new_mode == 'P':
|
||||
alpha = self.getchannel('A')
|
||||
if self.mode == "P":
|
||||
preferred_modes = []
|
||||
if "A" in self.im.getpalettemode():
|
||||
preferred_modes.append("RGBA")
|
||||
preferred_modes.append("RGB")
|
||||
else:
|
||||
preferred_modes = {
|
||||
"CMYK": ["RGB"],
|
||||
"RGB": ["CMYK"],
|
||||
"RGBX": ["RGB"],
|
||||
"RGBa": ["RGBA", "RGB"],
|
||||
"RGBA": ["RGB"],
|
||||
"LA": ["RGBA", "P", "L"],
|
||||
"La": ["LA", "L"],
|
||||
"L": ["RGB"],
|
||||
"F": ["I"],
|
||||
"I": ["L", "RGB"],
|
||||
"1": ["L"],
|
||||
"YCbCr": ["RGB"],
|
||||
"LAB": ["RGB"],
|
||||
"HSV": ["RGB"],
|
||||
}.get(self.mode, [])
|
||||
for new_mode in preferred_modes:
|
||||
if new_mode in modes:
|
||||
break
|
||||
else:
|
||||
new_mode = modes[0]
|
||||
if self.mode == "LA" and new_mode == "P":
|
||||
alpha = self.getchannel("A")
|
||||
# Convert the image into P mode but only use 255 colors
|
||||
# in the palette out of 256.
|
||||
im = self.convert('L') \
|
||||
.convert('P', palette=Palette.ADAPTIVE, colors=255)
|
||||
im = self.convert("L").convert("P", palette=Palette.ADAPTIVE, colors=255)
|
||||
# Set all pixel values below 128 to 255, and the rest to 0.
|
||||
mask = eval(alpha, lambda px: 255 if px < 128 else 0)
|
||||
# Paste the color of index 255 and use alpha as a mask.
|
||||
im.paste(255, mask)
|
||||
# The transparency index is 255.
|
||||
im.info['transparency'] = 255
|
||||
im.info["transparency"] = 255
|
||||
return im
|
||||
|
||||
elif self.mode == 'I':
|
||||
im = self.point([i//256 for i in range(65536)], 'L')
|
||||
return im.convert(new_mode) if new_mode != 'L' else im
|
||||
elif self.mode == "I":
|
||||
im = self.point([i // 256 for i in range(65536)], "L")
|
||||
return im.convert(new_mode) if new_mode != "L" else im
|
||||
|
||||
elif self.mode in ('RGBA', 'LA') and new_mode in ('RGB', 'L'):
|
||||
fill_color = params.get('fill_color', 'white')
|
||||
elif self.mode in ("RGBA", "LA") and new_mode in ("RGB", "L"):
|
||||
fill_color = params.get("fill_color", "white")
|
||||
background = new(new_mode, self.size, fill_color)
|
||||
background.paste(self, self.getchannel('A'))
|
||||
background.paste(self, self.getchannel("A"))
|
||||
return background
|
||||
|
||||
elif new_mode:
|
||||
|
|
|
@ -819,15 +819,8 @@ def jpeg_factory(fp=None, filename=None):
|
|||
return im
|
||||
|
||||
|
||||
def _convert_mode(im):
|
||||
mode = im.mode
|
||||
if mode == 'P':
|
||||
return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB'
|
||||
return {
|
||||
'RGBA':'RGB',
|
||||
'LA':'L',
|
||||
'I':'L'
|
||||
}.get(mode)
|
||||
def _supported_modes():
|
||||
return ["RGB", "CMYK", "YCbCr", "RGBX", "L", "1"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
|
|
@ -1420,10 +1420,8 @@ def getchunks(im, **params):
|
|||
return fp.data
|
||||
|
||||
|
||||
def _convert_mode(im):
|
||||
return {
|
||||
'CMYK':'RGB'
|
||||
}.get(im.mode)
|
||||
def _supported_modes():
|
||||
return ["RGB", "RGBA", "P", "I", "LA", "L", "1"]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -344,17 +344,22 @@ def _save(im, fp, filename):
|
|||
fp.write(data)
|
||||
|
||||
|
||||
def _convert_mode(im):
|
||||
mode = im.mode
|
||||
if mode == 'P':
|
||||
return 'RGBA' if 'A' in im.im.getpalettemode() else 'RGB'
|
||||
return {
|
||||
# Pillow doesn't support L modes for webp for now.
|
||||
'L':'RGB',
|
||||
'LA':'RGBA',
|
||||
'I':'RGB',
|
||||
'CMYK':'RGB'
|
||||
}.get(mode)
|
||||
def _supported_modes():
|
||||
return [
|
||||
"RGB",
|
||||
"RGBA",
|
||||
"RGBa",
|
||||
"RGBX",
|
||||
"CMYK",
|
||||
"YCbCr",
|
||||
"HSV",
|
||||
"I",
|
||||
"F",
|
||||
"P",
|
||||
"LA",
|
||||
"L",
|
||||
"1",
|
||||
]
|
||||
|
||||
|
||||
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
|
||||
|
|
Loading…
Reference in New Issue
Block a user