Skip to content

Commit

Permalink
Merge pull request #3 from radarhere/fix_get_optimize
Browse files Browse the repository at this point in the history
Optimise palettes with more than 128 colors
  • Loading branch information
raygard committed Jun 26, 2022
2 parents f656711 + 847ad8c commit 7c839d8
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 28 deletions.
32 changes: 14 additions & 18 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 @@ -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")
out = BytesIO()
imrgb.save(out, "GIF", optimize=False)
with Image.open(out) as reloaded:
assert len(reloaded.palette.palette) // 3 == 256
im_rgb = im.convert("RGB")

for (optimize, colors) in ((False, 256), (True, 8)):
out = BytesIO()
imrgb.save(out, "GIF", optimize=True)
im_rgb.save(out, "GIF", optimize=optimize)
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):
Expand Down Expand Up @@ -997,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 @@ -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):
Expand Down
21 changes: 11 additions & 10 deletions src/PIL/GifImagePlugin.py
Expand Up @@ -824,17 +824,18 @@ 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
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

0 comments on commit 7c839d8

Please sign in to comment.