Merge pull request #3 from radarhere/fix_get_optimize

Optimise palettes with more than 128 colors
This commit is contained in:
Ray Gardner 2022-06-26 13:55:49 -06:00 committed by GitHub
commit 7c839d8d81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 25 additions and 28 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,17 +824,18 @@ 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 current_palette_size = 1 << (num_palette_colors - 1).bit_length()
num_palette_colors = max(4, 1 << (num_palette_colors - 1).bit_length()) if (
if optimise or ( # check that the palette would become smaller when saved
len(used_palette_colors) <= 128 len(used_palette_colors) <= current_palette_size // 2
and max(used_palette_colors) >= len(used_palette_colors) # check that the palette is not already the smallest possible size
or len(used_palette_colors) <= num_palette_colors // 2 and current_palette_size > 2
): ):
return used_palette_colors return used_palette_colors