Improved handling of RGBA palettes when saving GIF images

This commit is contained in:
Andrew Murray 2024-09-10 18:50:06 +10:00
parent 22c333289e
commit d522e0a5c0
3 changed files with 36 additions and 4 deletions

View File

@ -1429,3 +1429,21 @@ def test_saving_rgba(tmp_path: Path) -> None:
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
reloaded_rgba = reloaded.convert("RGBA") reloaded_rgba = reloaded.convert("RGBA")
assert reloaded_rgba.load()[0, 0][3] == 0 assert reloaded_rgba.load()[0, 0][3] == 0
def test_optimizing_p_rgba(tmp_path: Path) -> None:
out = str(tmp_path / "temp.gif")
im1 = Image.new("P", (100, 100))
d = ImageDraw.Draw(im1)
d.ellipse([(40, 40), (60, 60)], fill=1)
data = [0, 0, 0, 0, 0, 0, 0, 255] + [0, 0, 0, 0] * 254
im1.putpalette(data, "RGBA")
im2 = Image.new("P", (100, 100))
im2.putpalette(data, "RGBA")
im1.save(out, save_all=True, append_images=[im2])
with Image.open(out) as reloaded:
assert reloaded.n_frames == 2

View File

@ -553,7 +553,9 @@ def _normalize_palette(
if im.mode == "P": if im.mode == "P":
if not source_palette: if not source_palette:
source_palette = im.im.getpalette("RGB")[:768] im_palette = im.getpalette(None)
assert im_palette is not None
source_palette = bytearray(im_palette)
else: # L-mode else: # L-mode
if not source_palette: if not source_palette:
source_palette = bytearray(i // 3 for i in range(768)) source_palette = bytearray(i // 3 for i in range(768))
@ -629,7 +631,10 @@ def _write_single_frame(
def _getbbox( def _getbbox(
base_im: Image.Image, im_frame: Image.Image base_im: Image.Image, im_frame: Image.Image
) -> tuple[Image.Image, tuple[int, int, int, int] | None]: ) -> tuple[Image.Image, tuple[int, int, int, int] | None]:
if _get_palette_bytes(im_frame) != _get_palette_bytes(base_im): palette_bytes = [
bytes(im.palette.palette) if im.palette else b"" for im in (base_im, im_frame)
]
if palette_bytes[0] != palette_bytes[1]:
im_frame = im_frame.convert("RGBA") im_frame = im_frame.convert("RGBA")
base_im = base_im.convert("RGBA") base_im = base_im.convert("RGBA")
delta = ImageChops.subtract_modulo(im_frame, base_im) delta = ImageChops.subtract_modulo(im_frame, base_im)
@ -984,7 +989,13 @@ def _get_palette_bytes(im: Image.Image) -> bytes:
:param im: Image object :param im: Image object
:returns: Bytes, len<=768 suitable for inclusion in gif header :returns: Bytes, len<=768 suitable for inclusion in gif header
""" """
return bytes(im.palette.palette) if im.palette else b"" if not im.palette:
return b""
palette = bytes(im.palette.palette)
if im.palette.mode == "RGBA":
palette = b"".join(palette[i * 4 : i * 4 + 3] for i in range(len(palette) // 3))
return palette
def _get_background( def _get_background(

View File

@ -1066,7 +1066,7 @@ class Image:
trns_im = new(self.mode, (1, 1)) trns_im = new(self.mode, (1, 1))
if self.mode == "P": if self.mode == "P":
assert self.palette is not None assert self.palette is not None
trns_im.putpalette(self.palette) trns_im.putpalette(self.palette, self.palette.mode)
if isinstance(t, tuple): if isinstance(t, tuple):
err = "Couldn't allocate a palette color for transparency" err = "Couldn't allocate a palette color for transparency"
assert trns_im.palette is not None assert trns_im.palette is not None
@ -2182,6 +2182,9 @@ class Image:
source_palette = self.im.getpalette(palette_mode, palette_mode) source_palette = self.im.getpalette(palette_mode, palette_mode)
else: # L-mode else: # L-mode
source_palette = bytearray(i // 3 for i in range(768)) source_palette = bytearray(i // 3 for i in range(768))
elif len(source_palette) > 768:
bands = 4
palette_mode = "RGBA"
palette_bytes = b"" palette_bytes = b""
new_positions = [0] * 256 new_positions = [0] * 256