Optimise palettes with more than 128 colors

This commit is contained in:
Andrew Murray 2022-06-19 16:47:50 +10:00
parent f656711c80
commit 709744432a
2 changed files with 20 additions and 27 deletions

View File

@ -158,6 +158,9 @@ def test_optimize_correctness():
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
# These do optimize the palette # These do optimize the palette
check(256, 511, 256)
check(255, 511, 255)
check(129, 511, 129)
check(128, 511, 128) check(128, 511, 128)
check(64, 511, 64) check(64, 511, 64)
check(4, 511, 4) check(4, 511, 4)
@ -167,11 +170,6 @@ def test_optimize_correctness():
check(64, 513, 256) check(64, 513, 256)
check(4, 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(): def test_optimize_full_l():
im = Image.frombytes("L", (16, 16), bytes(range(256))) 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(): def test_optimize_if_palette_can_be_reduced_by_half():
with Image.open("Tests/images/test.colors.gif") as im: 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)) im = im.resize((591, 443))
imrgb = im.convert("RGB") im_rgb = im.convert("RGB")
for (optimize, colors) in ((False, 256), (True, 8)):
out = BytesIO() out = BytesIO()
imrgb.save(out, "GIF", optimize=False) im_rgb.save(out, "GIF", optimize=optimize)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert len(reloaded.palette.palette) // 3 == 256 assert len(reloaded.palette.palette) // 3 == colors
out = BytesIO()
imrgb.save(out, "GIF", optimize=True)
with Image.open(out) as reloaded:
assert len(reloaded.palette.palette) // 3 == 8
def test_roundtrip(tmp_path): def test_roundtrip(tmp_path):
@ -997,8 +993,8 @@ def test_append_images(tmp_path):
def test_transparent_optimize(tmp_path): def test_transparent_optimize(tmp_path):
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses # From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
# transparency. # transparency.
# Need a palette that isn't using the 0 color, and one that's > 128 items where the # Need a palette that isn't using the 0 color,
# transparent color is actually the top palette entry to trigger the bug. # where the transparent color is actually the top palette entry to trigger the bug.
data = bytes(range(1, 254)) data = bytes(range(1, 254))
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
@ -1008,10 +1004,10 @@ def test_transparent_optimize(tmp_path):
im.putpalette(palette) im.putpalette(palette)
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
im.save(out, transparency=253) im.save(out, transparency=im.getpixel((252, 0)))
with Image.open(out) as reloaded:
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): def test_rgb_transparency(tmp_path):

View File

@ -824,18 +824,15 @@ def _get_optimize(im, info):
if count: if count:
used_palette_colors.append(i) used_palette_colors.append(i)
num_palette_colors = ( if optimise or max(used_palette_colors) >= len(used_palette_colors):
len(im.palette.palette) // 4 return used_palette_colors
if im.palette.mode == "RGBA"
else len(im.palette.palette) // 3 num_palette_colors = len(im.palette.palette) // Image.getmodebands(
im.palette.mode
) )
# Round up to power of 2 but at least 4 # Round up to power of 2 but at least 4
num_palette_colors = max(4, 1 << (num_palette_colors - 1).bit_length()) num_palette_colors = max(4, 1 << (num_palette_colors - 1).bit_length())
if optimise or ( if len(used_palette_colors) <= num_palette_colors // 2:
len(used_palette_colors) <= 128
and max(used_palette_colors) >= len(used_palette_colors)
or len(used_palette_colors) <= num_palette_colors // 2
):
return used_palette_colors return used_palette_colors