Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Tests/test_file_gif.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,17 @@ def test_rgb_transparency(tmp_path):
assert "transparency" not in reloaded.info


def test_rgba_transparency(tmp_path):
out = str(tmp_path / "temp.gif")

im = hopper("P")
im.save(out, save_all=True, append_images=[Image.new("RGBA", im.size)])

with Image.open(out) as reloaded:
reloaded.seek(1)
assert_image_equal(hopper("P").convert("RGB"), reloaded)


def test_bbox(tmp_path):
out = str(tmp_path / "temp.gif")

Expand Down
38 changes: 15 additions & 23 deletions src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,39 +424,28 @@ def _close__fp(self):
RAWMODE = {"1": "L", "L": "L", "P": "P"}


def _normalize_mode(im, initial_call=False):
def _normalize_mode(im):
"""
Takes an image (or frame), returns an image in a mode that is appropriate
for saving in a Gif.

It may return the original image, or it may return an image converted to
palette or 'L' mode.

UNDONE: What is the point of mucking with the initial call palette, for
an image that shouldn't have a palette, or it would be a mode 'P' and
get returned in the RAWMODE clause.

:param im: Image object
:param initial_call: Default false, set to true for a single frame.
:returns: Image object
"""
if im.mode in RAWMODE:
im.load()
return im
if Image.getmodebase(im.mode) == "RGB":
if initial_call:
palette_size = 256
if im.palette:
palette_size = len(im.palette.getdata()[1]) // 3
im = im.convert("P", palette=Image.Palette.ADAPTIVE, colors=palette_size)
if im.palette.mode == "RGBA":
for rgba in im.palette.colors.keys():
if rgba[3] == 0:
im.info["transparency"] = im.palette.colors[rgba]
break
return im
else:
return im.convert("P")
im = im.convert("P", palette=Image.Palette.ADAPTIVE)
if im.palette.mode == "RGBA":
for rgba in im.palette.colors.keys():
if rgba[3] == 0:
im.info["transparency"] = im.palette.colors[rgba]
break
return im
return im.convert("L")


Expand Down Expand Up @@ -514,7 +503,7 @@ def _normalize_palette(im, palette, info):


def _write_single_frame(im, fp, palette):
im_out = _normalize_mode(im, True)
im_out = _normalize_mode(im)
for k, v in im_out.info.items():
im.encoderinfo.setdefault(k, v)
im_out = _normalize_palette(im_out, palette, im.encoderinfo)
Expand Down Expand Up @@ -646,11 +635,14 @@ def get_interlace(im):
def _write_local_header(fp, im, offset, flags):
transparent_color_exists = False
try:
transparency = im.encoderinfo["transparency"]
except KeyError:
if "transparency" in im.encoderinfo:
transparency = im.encoderinfo["transparency"]
else:
transparency = im.info["transparency"]
transparency = int(transparency)
except (KeyError, ValueError):
pass
else:
transparency = int(transparency)
# optimize the block away if transparent color is not used
transparent_color_exists = True

Expand Down