From 709744432a2a290170d2b01e4c39fe2789f505f8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 19 Jun 2022 16:47:50 +1000 Subject: [PATCH] Optimise palettes with more than 128 colors --- Tests/test_file_gif.py | 32 ++++++++++++++------------------ src/PIL/GifImagePlugin.py | 15 ++++++--------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 2bfaef4ee..dbbd3bf9d 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -158,6 +158,9 @@ def test_optimize_correctness(): 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) @@ -167,11 +170,6 @@ def test_optimize_correctness(): 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))) @@ -182,17 +180,15 @@ def test_optimize_full_l(): def test_optimize_if_palette_can_be_reduced_by_half(): with Image.open("Tests/images/test.colors.gif") as im: - # Reduce because original is too big for _get_optimize() + # Reduce dimensions because original is too big for _get_optimize() im = im.resize((591, 443)) - imrgb = im.convert("RGB") + im_rgb = im.convert("RGB") + + for (optimize, colors) in ((False, 256), (True, 8)): out = BytesIO() - imrgb.save(out, "GIF", optimize=False) + im_rgb.save(out, "GIF", optimize=optimize) with Image.open(out) as reloaded: - assert len(reloaded.palette.palette) // 3 == 256 - out = BytesIO() - imrgb.save(out, "GIF", optimize=True) - with Image.open(out) as reloaded: - assert len(reloaded.palette.palette) // 3 == 8 + assert len(reloaded.palette.palette) // 3 == colors def test_roundtrip(tmp_path): @@ -997,8 +993,8 @@ def test_append_images(tmp_path): 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) @@ -1008,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): diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index b30ed1728..dd659c959 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -824,18 +824,15 @@ def _get_optimize(im, info): if count: used_palette_colors.append(i) - num_palette_colors = ( - len(im.palette.palette) // 4 - if im.palette.mode == "RGBA" - else len(im.palette.palette) // 3 + 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 ) # Round up to power of 2 but at least 4 num_palette_colors = max(4, 1 << (num_palette_colors - 1).bit_length()) - if optimise or ( - len(used_palette_colors) <= 128 - and max(used_palette_colors) >= len(used_palette_colors) - or len(used_palette_colors) <= num_palette_colors // 2 - ): + if len(used_palette_colors) <= num_palette_colors // 2: return used_palette_colors