mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-10 08:12:33 +03:00
Merge 5a3d3c6f28
into 329d6a6a62
This commit is contained in:
commit
6043493943
BIN
Tests/images/pil123rgba_red.jpg
Normal file
BIN
Tests/images/pil123rgba_red.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
|
@ -1411,6 +1411,18 @@ def test_save_I(tmp_path: Path) -> None:
|
|||
assert_image_equal(reloaded.convert("L"), im.convert("L"))
|
||||
|
||||
|
||||
def test_save_wrong_modes() -> None:
|
||||
out = BytesIO()
|
||||
for mode in ["CMYK"]:
|
||||
img = Image.new(mode, (20, 20))
|
||||
with pytest.raises(ValueError):
|
||||
img.save(out, "GIF")
|
||||
|
||||
for mode in ["CMYK", "LA"]:
|
||||
img = Image.new(mode, (20, 20))
|
||||
img.save(out, "GIF", convert_mode=True)
|
||||
|
||||
|
||||
def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# Test getheader/getdata against legacy values.
|
||||
# Create a 'P' image with holes in the palette.
|
||||
|
|
|
@ -778,13 +778,24 @@ class TestFileJpeg:
|
|||
img = Image.new(mode, (20, 20))
|
||||
img.save(out, "JPEG")
|
||||
|
||||
@pytest.mark.parametrize("mode", ("LA", "La", "RGBA", "RGBa", "P"))
|
||||
def test_save_wrong_modes(self, mode: str) -> None:
|
||||
def test_save_wrong_modes(self, tmp_path: Path) -> None:
|
||||
# ref https://github.com/python-pillow/Pillow/issues/2005
|
||||
out = BytesIO()
|
||||
img = Image.new(mode, (20, 20))
|
||||
with pytest.raises(OSError):
|
||||
img.save(out, "JPEG")
|
||||
for mode in ["LA", "La", "RGBA", "RGBa", "P", "I"]:
|
||||
img = Image.new(mode, (20, 20))
|
||||
with pytest.raises(OSError):
|
||||
img.save(out, "JPEG")
|
||||
|
||||
for mode in ["LA", "RGBA", "P", "I"]:
|
||||
img = Image.new(mode, (20, 20))
|
||||
img.save(out, "JPEG", convert_mode=True)
|
||||
|
||||
temp_file = tmp_path / "temp.jpg"
|
||||
with Image.open("Tests/images/pil123rgba.png") as img:
|
||||
img.save(temp_file, convert_mode=True, fill_color="red")
|
||||
|
||||
with Image.open(temp_file) as reloaded:
|
||||
assert_image_similar_tofile(reloaded, "Tests/images/pil123rgba_red.jpg", 4)
|
||||
|
||||
def test_save_tiff_with_dpi(self, tmp_path: Path) -> None:
|
||||
# Arrange
|
||||
|
|
|
@ -243,6 +243,14 @@ class TestFilePng:
|
|||
# image has 876 transparent pixels
|
||||
assert im.getchannel("A").getcolors()[0][0] == 876
|
||||
|
||||
def test_save_CMYK(self) -> None:
|
||||
out = BytesIO()
|
||||
im = Image.new("CMYK", (20, 20))
|
||||
with pytest.raises(IOError):
|
||||
im.save(out, "PNG")
|
||||
|
||||
im.save(out, "PNG", convert_mode=True)
|
||||
|
||||
def test_save_p_transparent_palette(self, tmp_path: Path) -> None:
|
||||
in_file = "Tests/images/pil123p.png"
|
||||
with Image.open(in_file) as im:
|
||||
|
|
|
@ -94,6 +94,12 @@ class TestFileWebp:
|
|||
target = target.convert(self.rgb_mode)
|
||||
assert_image_similar(image, target, epsilon)
|
||||
|
||||
def test_save_convert_mode(self) -> None:
|
||||
out = io.BytesIO()
|
||||
for mode in ["CMYK", "I", "L", "LA", "P"]:
|
||||
img = Image.new(mode, (20, 20))
|
||||
img.save(out, "WEBP", convert_mode=True)
|
||||
|
||||
def test_write_rgb(self, tmp_path: Path) -> None:
|
||||
"""
|
||||
Can we write a RGB mode file to webp without error?
|
||||
|
|
|
@ -19,6 +19,7 @@ from PIL import (
|
|||
ImageDraw,
|
||||
ImageFile,
|
||||
ImagePalette,
|
||||
TiffImagePlugin,
|
||||
UnidentifiedImageError,
|
||||
features,
|
||||
)
|
||||
|
@ -475,6 +476,77 @@ class TestImage:
|
|||
for ext in [".cur", ".icns", ".tif", ".tiff"]:
|
||||
assert ext in extensions
|
||||
|
||||
def test_supported_modes(self) -> None:
|
||||
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: Path) -> None:
|
||||
assert not hasattr(TiffImagePlugin, "_supported_modes")
|
||||
|
||||
temp_file = tmp_path / "temp.tiff"
|
||||
|
||||
im = hopper()
|
||||
im.save(temp_file, convert_mode=True)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mode, modes",
|
||||
(
|
||||
("P", ["RGB"]),
|
||||
("P", ["L"]), # converting to a non-preferred mode
|
||||
("LA", ["P"]),
|
||||
("I", ["L"]),
|
||||
("RGB", ["L"]),
|
||||
("RGB", ["CMYK"]),
|
||||
),
|
||||
)
|
||||
def test_convert_mode(self, mode: str, modes: list[str]) -> None:
|
||||
im = Image.new(mode, (100, 100))
|
||||
assert im._convert_mode(modes) is not None
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mode, modes",
|
||||
(
|
||||
("P", []), # no mode
|
||||
("P", ["P"]), # same mode
|
||||
),
|
||||
)
|
||||
def test_convert_mode_noop(self, mode: str, modes: list[str]) -> None:
|
||||
im = Image.new(mode, (100, 100))
|
||||
assert im._convert_mode(modes) is None
|
||||
|
||||
def test_effect_mandelbrot(self) -> None:
|
||||
# Arrange
|
||||
size = (512, 512)
|
||||
|
|
|
@ -1197,6 +1197,10 @@ def getdata(
|
|||
return fp.data
|
||||
|
||||
|
||||
def _supported_modes() -> list[str]:
|
||||
return ["RGB", "RGBA", "P", "I", "F", "LA", "L", "1"]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Registry
|
||||
|
||||
|
|
|
@ -2553,6 +2553,14 @@ class Image:
|
|||
else:
|
||||
save_handler = SAVE[format.upper()]
|
||||
|
||||
if params.get("convert_mode"):
|
||||
plugin = sys.modules[save_handler.__module__]
|
||||
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)
|
||||
|
||||
created = False
|
||||
if open_fp:
|
||||
created = not os.path.exists(filename)
|
||||
|
@ -2586,6 +2594,66 @@ class Image:
|
|||
self.encoderinfo = {**im._default_encoderinfo, **encoderinfo}
|
||||
return encoderinfo
|
||||
|
||||
def _convert_mode(
|
||||
self, modes: list[str], params: dict[str, Any] = {}
|
||||
) -> Image | None:
|
||||
if not modes or self.mode in modes:
|
||||
return None
|
||||
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)
|
||||
# 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
|
||||
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 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"))
|
||||
return background
|
||||
|
||||
elif new_mode:
|
||||
return self.convert(new_mode)
|
||||
|
||||
return None
|
||||
|
||||
def seek(self, frame: int) -> None:
|
||||
"""
|
||||
Seeks to the given frame in this sequence file. If you seek
|
||||
|
|
|
@ -884,6 +884,10 @@ def jpeg_factory(
|
|||
return im
|
||||
|
||||
|
||||
def _supported_modes() -> list[str]:
|
||||
return ["RGB", "CMYK", "YCbCr", "RGBX", "L", "1"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Registry stuff
|
||||
|
||||
|
|
|
@ -1539,6 +1539,10 @@ def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]
|
|||
return chunks
|
||||
|
||||
|
||||
def _supported_modes() -> list[str]:
|
||||
return ["RGB", "RGBA", "P", "I", "LA", "L", "1"]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Registry
|
||||
|
||||
|
|
|
@ -312,6 +312,25 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
fp.write(data)
|
||||
|
||||
|
||||
def _supported_modes() -> list[str]:
|
||||
return [
|
||||
"RGB",
|
||||
"RGBA",
|
||||
"RGBa",
|
||||
"RGBX",
|
||||
"CMYK",
|
||||
"YCbCr",
|
||||
"HSV",
|
||||
"I",
|
||||
"F",
|
||||
"P",
|
||||
"LA",
|
||||
"LAB",
|
||||
"L",
|
||||
"1",
|
||||
]
|
||||
|
||||
|
||||
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
|
||||
if SUPPORTED:
|
||||
Image.register_save(WebPImageFile.format, _save)
|
||||
|
|
Loading…
Reference in New Issue
Block a user