mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-10 16:22:22 +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"))
|
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:
|
def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
# Test getheader/getdata against legacy values.
|
# Test getheader/getdata against legacy values.
|
||||||
# Create a 'P' image with holes in the palette.
|
# Create a 'P' image with holes in the palette.
|
||||||
|
|
|
@ -778,13 +778,24 @@ class TestFileJpeg:
|
||||||
img = Image.new(mode, (20, 20))
|
img = Image.new(mode, (20, 20))
|
||||||
img.save(out, "JPEG")
|
img.save(out, "JPEG")
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("LA", "La", "RGBA", "RGBa", "P"))
|
def test_save_wrong_modes(self, tmp_path: Path) -> None:
|
||||||
def test_save_wrong_modes(self, mode: str) -> None:
|
|
||||||
# ref https://github.com/python-pillow/Pillow/issues/2005
|
# ref https://github.com/python-pillow/Pillow/issues/2005
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
img = Image.new(mode, (20, 20))
|
for mode in ["LA", "La", "RGBA", "RGBa", "P", "I"]:
|
||||||
with pytest.raises(OSError):
|
img = Image.new(mode, (20, 20))
|
||||||
img.save(out, "JPEG")
|
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:
|
def test_save_tiff_with_dpi(self, tmp_path: Path) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -243,6 +243,14 @@ class TestFilePng:
|
||||||
# image has 876 transparent pixels
|
# image has 876 transparent pixels
|
||||||
assert im.getchannel("A").getcolors()[0][0] == 876
|
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:
|
def test_save_p_transparent_palette(self, tmp_path: Path) -> None:
|
||||||
in_file = "Tests/images/pil123p.png"
|
in_file = "Tests/images/pil123p.png"
|
||||||
with Image.open(in_file) as im:
|
with Image.open(in_file) as im:
|
||||||
|
|
|
@ -94,6 +94,12 @@ class TestFileWebp:
|
||||||
target = target.convert(self.rgb_mode)
|
target = target.convert(self.rgb_mode)
|
||||||
assert_image_similar(image, target, epsilon)
|
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:
|
def test_write_rgb(self, tmp_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Can we write a RGB mode file to webp without error?
|
Can we write a RGB mode file to webp without error?
|
||||||
|
|
|
@ -19,6 +19,7 @@ from PIL import (
|
||||||
ImageDraw,
|
ImageDraw,
|
||||||
ImageFile,
|
ImageFile,
|
||||||
ImagePalette,
|
ImagePalette,
|
||||||
|
TiffImagePlugin,
|
||||||
UnidentifiedImageError,
|
UnidentifiedImageError,
|
||||||
features,
|
features,
|
||||||
)
|
)
|
||||||
|
@ -475,6 +476,77 @@ class TestImage:
|
||||||
for ext in [".cur", ".icns", ".tif", ".tiff"]:
|
for ext in [".cur", ".icns", ".tif", ".tiff"]:
|
||||||
assert ext in extensions
|
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:
|
def test_effect_mandelbrot(self) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
size = (512, 512)
|
size = (512, 512)
|
||||||
|
|
|
@ -1197,6 +1197,10 @@ def getdata(
|
||||||
return fp.data
|
return fp.data
|
||||||
|
|
||||||
|
|
||||||
|
def _supported_modes() -> list[str]:
|
||||||
|
return ["RGB", "RGBA", "P", "I", "F", "LA", "L", "1"]
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Registry
|
# Registry
|
||||||
|
|
||||||
|
|
|
@ -2553,6 +2553,14 @@ class Image:
|
||||||
else:
|
else:
|
||||||
save_handler = SAVE[format.upper()]
|
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
|
created = False
|
||||||
if open_fp:
|
if open_fp:
|
||||||
created = not os.path.exists(filename)
|
created = not os.path.exists(filename)
|
||||||
|
@ -2586,6 +2594,66 @@ class Image:
|
||||||
self.encoderinfo = {**im._default_encoderinfo, **encoderinfo}
|
self.encoderinfo = {**im._default_encoderinfo, **encoderinfo}
|
||||||
return 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:
|
def seek(self, frame: int) -> None:
|
||||||
"""
|
"""
|
||||||
Seeks to the given frame in this sequence file. If you seek
|
Seeks to the given frame in this sequence file. If you seek
|
||||||
|
|
|
@ -884,6 +884,10 @@ def jpeg_factory(
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
|
def _supported_modes() -> list[str]:
|
||||||
|
return ["RGB", "CMYK", "YCbCr", "RGBX", "L", "1"]
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# Registry stuff
|
# Registry stuff
|
||||||
|
|
||||||
|
|
|
@ -1539,6 +1539,10 @@ def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]
|
||||||
return chunks
|
return chunks
|
||||||
|
|
||||||
|
|
||||||
|
def _supported_modes() -> list[str]:
|
||||||
|
return ["RGB", "RGBA", "P", "I", "LA", "L", "1"]
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Registry
|
# Registry
|
||||||
|
|
||||||
|
|
|
@ -312,6 +312,25 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
fp.write(data)
|
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)
|
Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
|
||||||
if SUPPORTED:
|
if SUPPORTED:
|
||||||
Image.register_save(WebPImageFile.format, _save)
|
Image.register_save(WebPImageFile.format, _save)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user