我有一个gif
我想调整大小,pillow
以便它的大小减少.目前的大小gif
是2MB.
我在尝试着
调整大小,使其高度/宽度更小
降低其质量.
使用JPEG,以下代码通常就足以使大图像的大小显着减小.
from PIL import Image im = Image.open("my_picture.jpg") im = im.resize((im.size[0] // 2, im.size[1] // 2), Image.ANTIALIAS) # decreases width and height of the image im.save("out.jpg", optimize=True, quality=85) # decreases its quality
但是,使用GIF,它似乎不起作用.以下代码甚至out.gif
比最初的gif更大:
im = Image.open("my_gif.gif") im.seek(im.tell() + 1) # loads all frames im.save("out.gif", save_all=True, optimize=True, quality=10) # should decrease its quality print(os.stat("my_gif.gif").st_size) # 2096558 bytes / roughly 2MB print(os.stat("out.gif").st_size) # 7536404 bytes / roughly 7.5MB
如果我添加以下行,则仅保存GIF的第一帧,而不是其全部帧.
im = im.resize((im.size[0] // 2, im.size[1] // 2), Image.ANTIALIAS) # should decrease its size
我一直在想呼吁resize()
对im.seek()
或者im.tell()
不过这些方法都没有返回一个Image对象,因此,我不能把resize()
自己的输出.
你知道如何在保留所有镜架的同时使用Pillow减小GIF的尺寸吗?
[编辑]部分解决方案:
在Old Bear的回复之后,我做了以下更改:
我正在使用BigglesZX的脚本来提取所有帧.值得注意的是,这是一个Python 2脚本,我的项目是用Python 3编写的(我最初提到过这个细节,但它是由Stack Overflow社区编辑出来的).Running 2to3 -w gifextract.py
使该脚本与Python 3兼容.
我一直在单独修改每个框架: frame.resize((frame.size[0] // 2, frame.size[1] // 2), Image.ANTIALIAS)
我一直在把所有的框架保存在一起:img.save("out.gif", save_all=True, optimize=True)
.
新的gif现在已保存并可以正常工作,但有两个主要问题:
我不确定resize方法是否正常工作,out.gif
仍然是7.5MB.最初的gif是2MB.
gif速度增加,gif不循环.它在第一次运行后停止.
例:
原gif my_gif.gif
:
处理后的Gif(out.gif
)https://i.imgur.com/zDO4cE4.mp4(我无法将其添加到Stack Overflow).Imgur使它变慢(并将其转换为mp4).当我从计算机打开gif文件时,整个gif持续约1.5秒.
使用BigglesZX的脚本,我创建了一个新的脚本,使用Pillow调整GIF的大小.
原始GIF(2.1 MB):
调整大小后输出GIF(1.7 MB):
我在这里保存了脚本.它使用thumbnail
Pillow 的方法而不是resize
方法,因为我发现该resize
方法不起作用.
这不是完美的,所以随意分叉并改进它.以下是一些未解决的问题:
虽然GIF在imgur托管时显示得很好,但是当我从计算机打开它时会出现速度问题,整个GIF只需要1.5秒.
同样,虽然imgur似乎弥补了速度问题,但当我尝试将其上传到GIF时,GIF无法正确显示stack.imgur
.仅显示第一帧(您可以在此处看到).
完整代码(如果上面的要点被删除):
def resize_gif(path, save_as=None, resize_to=None): """ Resizes the GIF to a given length: Args: path: the path to the GIF file save_as (optional): Path of the resized gif. If not set, the original gif will be overwritten. resize_to (optional): new size of the gif. Format: (int, int). If not set, the original GIF will be resized to half of its size. """ all_frames = extract_and_resize_frames(path, resize_to) if not save_as: save_as = path if len(all_frames) == 1: print("Warning: only 1 frame found") all_frames[0].save(save_as, optimize=True) else: all_frames[0].save(save_as, optimize=True, save_all=True, append_images=all_frames[1:], loop=1000) def analyseImage(path): """ Pre-process pass over the image to determine the mode (full or additive). Necessary as assessing single frames isn't reliable. Need to know the mode before processing all frames. """ im = Image.open(path) results = { 'size': im.size, 'mode': 'full', } try: while True: if im.tile: tile = im.tile[0] update_region = tile[1] update_region_dimensions = update_region[2:] if update_region_dimensions != im.size: results['mode'] = 'partial' break im.seek(im.tell() + 1) except EOFError: pass return results def extract_and_resize_frames(path, resize_to=None): """ Iterate the GIF, extracting each frame and resizing them Returns: An array of all frames """ mode = analyseImage(path)['mode'] im = Image.open(path) if not resize_to: resize_to = (im.size[0] // 2, im.size[1] // 2) i = 0 p = im.getpalette() last_frame = im.convert('RGBA') all_frames = [] try: while True: # print("saving %s (%s) frame %d, %s %s" % (path, mode, i, im.size, im.tile)) ''' If the GIF uses local colour tables, each frame will have its own palette. If not, we need to apply the global palette to the new frame. ''' if not im.getpalette(): im.putpalette(p) new_frame = Image.new('RGBA', im.size) ''' Is this file a "partial"-mode GIF where frames update a region of a different size to the entire image? If so, we need to construct the new frame by pasting it on top of the preceding frames. ''' if mode == 'partial': new_frame.paste(last_frame) new_frame.paste(im, (0, 0), im.convert('RGBA')) new_frame.thumbnail(resize_to, Image.ANTIALIAS) all_frames.append(new_frame) i += 1 last_frame = new_frame im.seek(im.tell() + 1) except EOFError: pass return all_frames
根据Pillow 4.0x,Image.resize函数仅适用于单个图像/帧.
为了达到你想要的效果,我相信你必须首先从.gif文件中提取每一帧,每次调整一帧,然后重新组装它们.
要做第一步,似乎需要注意一些细节.例如,是否每个gif帧使用局部调色板或全局调色板应用于所有帧,以及gif是否使用完整或部分帧替换每个图像.BigglesZX开发了一个脚本来解决这些问题,同时从gif文件中提取每个帧,以便利用它.
接下来,您必须编写脚本来调整每个提取的帧的大小,并使用PIL.Image.resize()和PIL.Image.save()将它们全部组装为新的.gif文件.
我注意到你写了" im.seek(im.tell() + 1) # load all frames
".我认为这是不正确的.而是用于在.gif文件的帧之间递增.我注意到你在.gif文件的保存功能中使用了quality = 10.我没有在PIL文档中找到这个.您可以通过阅读此链接了解有关BiggleZX脚本中提到的tile属性的更多信息