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

GIF - use single colour table for all frames #3788

Closed
Gadgetoid opened this issue Apr 11, 2019 · 3 comments · Fixed by #5603
Closed

GIF - use single colour table for all frames #3788

Gadgetoid opened this issue Apr 11, 2019 · 3 comments · Fixed by #5603
Labels
Projects

Comments

@Gadgetoid
Copy link

What did you do?

Attempted to normalise a sequence of P images to use the same colour palette in an attempt to output a smaller GIF file that uses a common palette across all frames.

The complete code is here - https://github.com/pimoroni/mlx90640-library/blob/master/python/rgb-to-gif.py - but the relevant snippet can be found below:

    if len(frames) > 1:
        # Generate an image with all frames, and crunch it to ADAPTIVE to form a master colour palette
        master = Image.new('RGB', (32, 24 * len(frames)))
        for index, image in enumerate(frames):
            master.paste(image, (0, 24 * index))
        master = master.convert('P', dither=False, palette=Image.ADAPTIVE, colors=256)

        for index, image in enumerate(frames):
            image = image.convert('P', dither=False, palette=master.palette)
            # image = image.quantize(method=3, palette=master)
            image = image.transpose(Image.ROTATE_270).transpose(Image.FLIP_LEFT_RIGHT)
            image = image.resize(OUTPUT_SIZE, Image.NEAREST)
            frames[index] = image

        filename = 'mlx90640-{}.gif'.format(
            datetime.now().strftime("%Y-%m-%d-%H-%M-%S"))
print("Saving {} with {} frames.".format(filename, len(frames)))

        frames[0].save(
            filename,
            save_all=True,
            append_images=frames[1:],
            duration=1000 // fps,
            loop=0,
            include_color_table=True,
            optimize=True,
palette=master.palette.getdata())

What did you expect to happen?

For the GIF format save method to understand that I was sharing a colour palette across frames, and export accordingly. There doesn't seem to be a way - that I can identify - to communicate this.

What actually happened?

Result a ~1.19MiB GIF:

mlx90640-2019-04-11-16-34-25

Versus a ~263.4KiB GIF - This is a ~70-80% size saving just running the result through ezgif (https://ezgif.com/optimize) optimisation and picking "Use single color table for all frames":

ezgif-4-917f7c76321e

Unless I am very much misunderstanding what ezgif is doing in this case, no other optimisation was performed.

What are your OS, Python and Pillow versions?

  • OS: Linux, Raspbia
  • Python: 3.4.9
  • Pillow: PIL.PILLOW_VERSION == 5.4.1
@radarhere radarhere added the GIF label Apr 12, 2019
@aclark4life aclark4life added this to Backlog in Pillow May 11, 2019
@aclark4life aclark4life moved this from Backlog to In progress in Pillow May 11, 2019
@radarhere
Copy link
Member

Taking your 1.2mb image, and running

from PIL import Image
im = Image.open('input.gif')
im.save('output.gif', save_all=True)

Before #5291, the output is 1.3mb. After, it is 320kb.

So we're not using a single color table yet, but that is a step towards solving your underlying problem.

@radarhere
Copy link
Member

I've created #5603 to resolve this. With it, this code produces a 281kb file, down from the 320kb file from the code in my last post.

from PIL import Image, ImagePalette, ImageSequence

with Image.open("input.gif") as im:
	# Construct a palette with all colors from the image
	palette = ImagePalette.ImagePalette()
	for frame in ImageSequence.Iterator(im):
		for i, count in enumerate(frame.histogram()):
			if count:
				color = tuple(frame.palette.palette[i * 3 : i * 3 + 3])
				palette.getcolor(color)
	
	# Save, using that palette for all frames
	im.save("output.gif", save_all=True, palette=palette.palette)

@Gadgetoid
Copy link
Author

That's mighty close to the output I got above, and a considerable size reduction! Amazing. Thank you for taking the time to do this- it would have taken me forever just to figure out how to figure out how.

Pillow automation moved this from In progress to Closed Aug 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Pillow
  
Closed
Development

Successfully merging a pull request may close this issue.

2 participants