python-pillow / Pillow

Python Imaging Library (Fork)
https://python-pillow.org
Other
12.25k stars 2.23k forks source link

GIF file size #6863

Closed lxccc812 closed 1 year ago

lxccc812 commented 1 year ago

When I use the save method to save the picture list, the generated gif memory is too large. I have used 14 images within 10kb, but the generated gif accounts for about 400k. When I use the same image to generate gif repeatedly, the size of the generated gif is different each time. What is this situation? Can you answer it.

radarhere commented 1 year ago

Hi. You start out talking about memory, but then you switch to talking about file size. Memory is used temporarily during the execution of the script and then released afterwards. File size refers to the amount of space that the created GIF takes up when the script is done. I'm going to presume that you are just talking about file size.

Could you provide a self-contained example to demonstrate what is happening? That is, a copy of the script that you run, with any input images attached?

lxccc812 commented 1 year ago

image

from PIL import ImageSequence, Image

def t1():
    image_list = []
    im = Image.open(r'C:\Users\LXC\Desktop\t1\666.gif')
    for index, frame in enumerate(ImageSequence.Iterator(im)):
        rate = 1
        width = frame.size[0]
        height = frame.size[1]
        if width / height >= rate:
            target_height = height
            target_width = height * rate
            point_1x = width / 2 - target_width / 2
            point_1y = 0
            point_2x = width / 2 + target_width / 2
            point_2y = target_height
        else:
            target_width = width
            target_height = width / rate
            point_1x = 0
            point_1y = height / 2 - target_height / 2
            point_2x = target_width
            point_2y = height / 2 + target_height / 2
        cut_frame = frame.crop((point_1x, point_1y, point_2x, point_2y))
        resize_frame = cut_frame.resize((240, 240), Image.ANTIALIAS)
        image_list.append(resize_frame)

    duration = (im.info).get('duration', 30)
    transparency = (im.info).get('transparency', 255)
    loop = (im.info).get('loop', 0)
    comment = (im.info).get('comment', '')
    image_list[0].save(
        r'C:\Users\LXC\Desktop\t1\new666.gif',
        save_all=True,
        include_color_table=True,
        disposal=2,
        append_images=image_list[1:],

        duration=duration,
        transparency=transparency,
        loop=loop,
        comment=comment
    )

After I cut and resize each image in the gif, I synthesize a new gif image. Its size is larger than the original gif. On the left is the file size between modifications, and on the right is the file size after modifications.

radarhere commented 1 year ago

Could you attach a copy of the original GIF?

lxccc812 commented 1 year ago

666

radarhere commented 1 year ago

For this specific example, I find that if you change

resize_frame = cut_frame.resize((240, 240), Image.ANTIALIAS)

to

resize_frame = cut_frame.resize((240, 240))

then the created GIF is actually smaller than your original GIF.

lxccc812 commented 1 year ago

According to the method you said, the file is really smaller after modification. But it is still not the effect I want. When I use 14 10 kb or so images to compose a gif, the file size becomes about 500 kb, which is unreasonable and incorrect. I'm not sure why this happened. The folder on the left is the picture I used, and the folder on the right is my composite gif. Its size is too large. I think this is wrong.

image

radarhere commented 1 year ago

Could you attach the 14 images?

lxccc812 commented 1 year ago

666-0 666-1 666-2 666-3 666-4 666-5 666-6 666-7 666-8 666-9 666-10 666-11 666-12 666-13

from PIL import Image, ImageSequence

def t2():
    image_list = []
    for i in range(14):
        im = Image.open(fr'C:\Users\LXC\Desktop\t2\666-{i}.jpg')
        print(im.mode)
        image_list.append(im)
    image_list[0].save(
        r'C:\Users\LXC\Desktop\t2\new666.gif',
        save_all=True,
        include_color_table=True,
        disposal=2,
        append_images=image_list[1:],

        duration=10,
        loop=0
    )

When I use this code to synthesize images, the gif is too large and not very reasonable image

radarhere commented 1 year ago

If I just open a single image and save it as a GIF,

Image.open("0.jpg").save("zero.gif")

then the 10kb JPG becomes a 44kb GIF. I tested converting the image with ImageMagick instead of Pillow, and it was 42kb. So I don't think Pillow is doing anything wrong explicitly. JPEGs and GIFs are different, and sometimes a GIF image might be bigger. Over at http://users.wfu.edu/matthews/misc/jpg_vs_gif/JpgVsGif-compact.html, they mention a particular GIF being 2.4 times bigger than a JPEG.

44kb * 14 = 616kb, which is greater than your final file size, so I don't think the problem has anything to do with the animation.

Experimenting, I found that for this specific example, if I insert this new line above image_list.append(im),

im = im.quantize(36, method=Image.Quantize.FASTOCTREE)

then your output image is now 180kb.

lxccc812 commented 1 year ago

I watched the comparison between jpg and gif. It roughly means that the more colors in the image, the bigger the gif. But I still don't understand. I split the original gif to get a lot of pictures, then cut and limit the size of these pictures, and then synthesize the gif. The result is even larger than the original gif, which I can't understand. I observed every step in the process and strictly followed the document. Still can not achieve the effect I want.If I can make this phenomenon clear, I think I can avoid it.

radarhere commented 1 year ago

Opening the image and resaving it

from PIL import Image, ImageSequence
im = Image.open("in.gif")
frames = []
for i, frame in enumerate(ImageSequence.Iterator(im)):
    frames.append(frame.copy())
frames[0].save("out.gif", save_all=True, append_images=frames[1:])

gives a 493kb GIF.

If we crop it,

from PIL import Image, ImageSequence
im = Image.open("in.gif")
frames = []
for i, frame in enumerate(ImageSequence.Iterator(im)):
    cropped_frame = frame.crop((0, 0, 247, 247))
    frames.append(cropped_frame)
frames[0].save("out.gif", save_all=True, append_images=frames[1:])

then it gives a 307kb GIF. Fair enough, it is smaller.

If we crop and resize it with ANTIALIAS,

from PIL import Image, ImageSequence
im = Image.open("in.gif")
frames = []
for i, frame in enumerate(ImageSequence.Iterator(im)):
    cropped_frame = frame.crop((0, 0, 247, 247))
    resized_frame = cropped_frame.resize((240, 240), Image.ANTIALIAS)
    frames.append(resized_frame)
frames[0].save("out.gif", save_all=True, append_images=frames[1:])

then it gives a 513kb GIF. Why is this bigger? Here is some code to count the number of colours in the image before and after it is resized.

from PIL import Image, ImageSequence

def get_number_of_colors(frame):
    colors = []
    for x in range(frame.width):
        for y in range(frame.height):
            color = frame.getpixel((x, y))
            if color not in colors:
                colors.append(color)
    return len(colors)

im = Image.open("in.gif")
frames = []
for i, frame in enumerate(ImageSequence.Iterator(im)):
    cropped_frame = frame.crop((0, 0, 247, 247))
    print("Before resizing, there are "+str(get_number_of_colors(cropped_frame))+" colors")
    resized_frame = cropped_frame.resize((240, 240), Image.ANTIALIAS)
    print("After resizing, there are "+str(get_number_of_colors(resized_frame))+" colors")
    frames.append(resized_frame)
frames[0].save("out.gif", save_all=True, append_images=frames[1:])

prints

Before resizing, there are 91 colors
After resizing, there are 91 colors
Before resizing, there are 93 colors
After resizing, there are 17891 colors
Before resizing, there are 93 colors
After resizing, there are 18622 colors
Before resizing, there are 90 colors
After resizing, there are 18082 colors
Before resizing, there are 93 colors
After resizing, there are 19052 colors
Before resizing, there are 89 colors
After resizing, there are 18140 colors
Before resizing, there are 92 colors
After resizing, there are 18444 colors
Before resizing, there are 90 colors
After resizing, there are 18013 colors
Before resizing, there are 90 colors
After resizing, there are 18097 colors
Before resizing, there are 91 colors
After resizing, there are 17609 colors
Before resizing, there are 91 colors
After resizing, there are 17429 colors
Before resizing, there are 93 colors
After resizing, there are 15807 colors
Before resizing, there are 93 colors
After resizing, there are 16406 colors
Before resizing, there are 93 colors
After resizing, there are 17394 colors

The resize operation has introduced more colours. The image has become less simple. It therefore seems reasonable that the GIF is larger.

Yay295 commented 1 year ago

Image.ANTIALIAS is also the old way to refer to Image.Resampling.LANCZOS. https://pillow.readthedocs.io/en/stable/reference/Image.html#resampling-filters I think Image.Resampling.NEAREST may give the smallest size.

radarhere commented 1 year ago

@lxccc812 has that explained things?

lxccc812 commented 1 year ago

The problem has been solved. Thank you for your answer.