Skip to content

Commit

Permalink
Improved handling of RGBA palettes when saving GIF images
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed Sep 10, 2024
1 parent 22c3332 commit d522e0a
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 4 deletions.
18 changes: 18 additions & 0 deletions Tests/test_file_gif.py
Original file line number Diff line number Diff line change
Expand Up @@ -1429,3 +1429,21 @@ def test_saving_rgba(tmp_path: Path) -> None:
with Image.open(out) as reloaded:
reloaded_rgba = reloaded.convert("RGBA")
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
17 changes: 14 additions & 3 deletions src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,9 @@ def _normalize_palette(

if im.mode == "P":
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
if not source_palette:
source_palette = bytearray(i // 3 for i in range(768))
Expand Down Expand Up @@ -629,7 +631,10 @@ def _write_single_frame(
def _getbbox(
base_im: Image.Image, im_frame: Image.Image
) -> 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")
base_im = base_im.convert("RGBA")
delta = ImageChops.subtract_modulo(im_frame, base_im)
Expand Down Expand Up @@ -984,7 +989,13 @@ def _get_palette_bytes(im: Image.Image) -> bytes:
:param im: Image object
: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(
Expand Down
5 changes: 4 additions & 1 deletion src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1066,7 +1066,7 @@ def convert_transparency(
trns_im = new(self.mode, (1, 1))
if self.mode == "P":
assert self.palette is not None
trns_im.putpalette(self.palette)
trns_im.putpalette(self.palette, self.palette.mode)
if isinstance(t, tuple):
err = "Couldn't allocate a palette color for transparency"
assert trns_im.palette is not None
Expand Down Expand Up @@ -2182,6 +2182,9 @@ def remap_palette(
source_palette = self.im.getpalette(palette_mode, palette_mode)
else: # L-mode
source_palette = bytearray(i // 3 for i in range(768))
elif len(source_palette) > 768:
bands = 4
palette_mode = "RGBA"

palette_bytes = b""
new_positions = [0] * 256
Expand Down

0 comments on commit d522e0a

Please sign in to comment.