Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved GIF optimize condition #6378

Merged
merged 5 commits into from Jun 26, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 21 additions & 10 deletions Tests/test_file_gif.py
Expand Up @@ -158,6 +158,9 @@ def check(colors, size, expected_palette_length):
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))

# These do optimize the palette
check(256, 511, 256)
check(255, 511, 255)
check(129, 511, 129)
check(128, 511, 128)
check(64, 511, 64)
check(4, 511, 4)
Expand All @@ -167,11 +170,6 @@ def check(colors, size, expected_palette_length):
check(64, 513, 256)
check(4, 513, 256)

# Other limits that don't optimize the palette
check(129, 511, 256)
check(255, 511, 256)
check(256, 511, 256)


def test_optimize_full_l():
im = Image.frombytes("L", (16, 16), bytes(range(256)))
Expand All @@ -180,6 +178,19 @@ def test_optimize_full_l():
assert im.mode == "L"


def test_optimize_if_palette_can_be_reduced_by_half():
with Image.open("Tests/images/test.colors.gif") as im:
# Reduce dimensions because original is too big for _get_optimize()
im = im.resize((591, 443))
im_rgb = im.convert("RGB")

for (optimize, colors) in ((False, 256), (True, 8)):
out = BytesIO()
im_rgb.save(out, "GIF", optimize=optimize)
with Image.open(out) as reloaded:
assert len(reloaded.palette.palette) // 3 == colors


def test_roundtrip(tmp_path):
out = str(tmp_path / "temp.gif")
im = hopper()
Expand Down Expand Up @@ -982,8 +993,8 @@ def im_generator(ims):
def test_transparent_optimize(tmp_path):
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
# transparency.
# Need a palette that isn't using the 0 color, and one that's > 128 items where the
# transparent color is actually the top palette entry to trigger the bug.
# Need a palette that isn't using the 0 color,
# where the transparent color is actually the top palette entry to trigger the bug.

data = bytes(range(1, 254))
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
Expand All @@ -993,10 +1004,10 @@ def test_transparent_optimize(tmp_path):
im.putpalette(palette)

out = str(tmp_path / "temp.gif")
im.save(out, transparency=253)
with Image.open(out) as reloaded:
im.save(out, transparency=im.getpixel((252, 0)))

assert reloaded.info["transparency"] == 253
with Image.open(out) as reloaded:
assert reloaded.info["transparency"] == reloaded.getpixel((252, 0))


def test_rgb_transparency(tmp_path):
Expand Down
15 changes: 12 additions & 3 deletions src/PIL/GifImagePlugin.py
Expand Up @@ -824,9 +824,18 @@ def _get_optimize(im, info):
if count:
used_palette_colors.append(i)

if optimise or (
len(used_palette_colors) <= 128
and max(used_palette_colors) > len(used_palette_colors)
if optimise or max(used_palette_colors) >= len(used_palette_colors):
return used_palette_colors

num_palette_colors = len(im.palette.palette) // Image.getmodebands(
im.palette.mode
)
current_palette_size = 1 << (num_palette_colors - 1).bit_length()
if (
# check that the palette would become smaller when saved
len(used_palette_colors) <= current_palette_size // 2
# check that the palette is not already the smallest possible size
and current_palette_size > 2
):
return used_palette_colors

Expand Down