Python 枕头-调整GIF的大小

Python 枕头-调整GIF的大小,python,image-processing,gif,pillow,Python,Image Processing,Gif,Pillow,我有一个gif,我想用枕头调整它的大小,这样它的大小就会减小。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 he

我有一个
gif
,我想用
枕头调整它的大小,这样它的大小就会减小。
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
    
    我一直在考虑对
    im.seek()
    im.tell()
    调用
    resize()
    ,但这两种方法都不返回图像对象,因此我无法对其输出调用
    resize()

    你知道我如何使用枕头来减少GIF的大小,同时保留所有的框架吗

    [编辑]部分解决方案:

    接下来,我做了以下更改:

    • 我正在使用提取所有帧。值得注意的是,这是一个Python2脚本,我的项目是用Python3编写的(我最初确实提到了这个细节,但它被Stack Overflow社区编辑掉了)。运行
      2to3-wgifextract.py使该脚本与Python 3兼容

    • 我已经分别调整了每个帧的大小:
      frame.resize((frame.size[0]//2,frame.size[1]//2),Image.antialas)

    • 我一直在一起保存所有帧:
      img.save(“out.gif”,save\u all=True,optimize=True)

    新的gif现在已保存并工作,但存在两个主要问题:

    • 我不确定resize方法是否有效,因为
      out.gif
      仍然是7.5MB。最初的gif是2MB

    • gif速度增加,gif不会循环。它在第一次运行后停止

    例如:

    原始gif
    my_gif.gif


    处理后的Gif(
    out.Gif
    )(我无法将其添加到堆栈溢出)。Imgur使其速度变慢(并将其转换为mp4)。当我从电脑打开gif文件时,整个gif大约持续1.5秒。

    根据Pillow 4.0x,Image.resize功能仅在单个图像/帧上工作

    为了达到您想要的效果,我相信您必须首先从.gif文件中提取每一帧,一次调整每一帧的大小,然后重新组装它们

    要完成第一步,似乎需要注意一些细节。例如,每个gif帧是否使用本地调色板或全局调色板应用于所有帧,以及gif是否使用完整或部分帧替换每个图像。已经开发了一个脚本来解决这些问题,同时从gif文件中提取每一帧,所以要充分利用这一点

    接下来,您必须编写脚本来调整每个提取帧的大小,并使用PIL.Image.resize()和PIL.Image.save()将它们全部组装为一个新的.gif文件

    我注意到你写了“
    im.seek(im.tell()+1)#加载所有帧
    ”。我认为这是不正确的。相反,它用于在.gif文件的帧之间递增。我注意到您在.gif文件的保存函数中使用了quality=10。我没有发现该文件中提供的内容。您可以通过阅读此

    了解BiggleZX脚本中提到的tile属性的更多信息。使用,我创建了一个新脚本,它使用枕头调整GIF的大小

    原始GIF(2.1MB):

    调整大小后输出GIF(1.7 MB):

    我已经保存了脚本。它使用枕头的
    缩略图
    方法,而不是
    调整大小
    方法,因为我发现
    调整大小
    方法不起作用

    这是不完美的,所以请随意叉和改善它。以下是一些尚未解决的问题:

    • 当由imgur托管时,GIF显示正常,但当我从计算机上打开它时,整个GIF只需1.5秒,因此存在速度问题
    • 同样,虽然imgur似乎可以弥补速度问题,但当我尝试将GIF上传到
      stack.imgur
      时,它无法正确显示。仅显示了第一帧(您可以看到它)
    完整代码(如果删除上述要点):


    我正在使用下面的函数来调整和裁剪图像,包括动画图像(GIF、WEBP)。简单地说,我们需要在GIF或WEBP中迭代每个帧

    from math import floor, fabs
    from PIL import Image, ImageSequence
    
    def transform_image(original_img, crop_w, crop_h):
      """
      Resizes and crops the image to the specified crop_w and crop_h if necessary.
      Works with multi frame gif and webp images also.
    
      args:
      original_img is the image instance created by pillow ( Image.open(filepath) )
      crop_w is the width in pixels for the image that will be resized and cropped
      crop_h is the height in pixels for the image that will be resized and cropped
    
      returns:
      Instance of an Image or list of frames which they are instances of an Image individually
      """
      img_w, img_h = (original_img.size[0], original_img.size[1])
      n_frames = getattr(original_img, 'n_frames', 1)
    
      def transform_frame(frame):
        """
        Resizes and crops the individual frame in the image.
        """
        # resize the image to the specified height if crop_w is null in the recipe
        if crop_w is None:
          if crop_h == img_h:
            return frame
          new_w = floor(img_w * crop_h / img_h)
          new_h = crop_h
          return frame.resize((new_w, new_h))
    
        # return the original image if crop size is equal to img size
        if crop_w == img_w and crop_h == img_h:
          return frame
    
        # first resize to get most visible area of the image and then crop
        w_diff = fabs(crop_w - img_w)
        h_diff = fabs(crop_h - img_h)
        enlarge_image = True if crop_w > img_w or crop_h > img_h else False
        shrink_image = True if crop_w < img_w or crop_h < img_h else False
    
        if enlarge_image is True:
          new_w = floor(crop_h * img_w / img_h) if h_diff > w_diff else crop_w
          new_h = floor(crop_w * img_h / img_w) if h_diff < w_diff else crop_h
    
        if shrink_image is True:
          new_w = crop_w if h_diff > w_diff else floor(crop_h * img_w / img_h)
          new_h = crop_h if h_diff < w_diff else floor(crop_w * img_h / img_w)
    
        left = (new_w - crop_w) // 2
        right = left + crop_w
        top = (new_h - crop_h) // 2
        bottom = top + crop_h
    
        return frame.resize((new_w, new_h)).crop((left, top, right, bottom))
    
      # single frame image
      if n_frames == 1:
        return transform_frame(original_img)
      # in the case of a multiframe image
      else:
        frames = []
        for frame in ImageSequence.Iterator(original_img):
          frames.append( transform_frame(frame) )
        return frames
    
    来自math import floor,fabs
    从PIL导入图像,图像序列
    def变换图像(原始图像、裁剪w、裁剪h):
    """
    如有必要,将图像调整大小并裁剪为指定的裁剪宽度和裁剪高度。
    也适用于多帧gif和webp图像。
    args:
    original_img是由枕头创建的图像实例(image.open(filepath))
    crop_w是将被调整大小和裁剪的图像的宽度(以像素为单位)
    crop_h是要调整大小和裁剪的图像的高度(以像素为单位)
    返回:
    图像的实例或帧列表,它们分别是图像的实例
    """
    img\u w,img\u h=(原始img.size[0],原始img.size[1])
    n_frames=getattr(原始图像,'n_frames',1)
    def变换_帧(帧):
    """
    调整图像中单个帧的大小并进行裁剪。
    """
    #如果配方中的裁剪为空,则将图像调整为指定高度
    如果作物w为无:
    如果作物高度==img高度:
    回程架
    新高度=地板(高度/高度)
    新高度=作物高度
    返回帧。调整大小((新的w,新的h))
    #如果裁剪大小等于img大小,则返回原始图像
    如果作物w==img\u w和作物h==img\u h:
    回程架
    #首先调整大小以获得图像的最可见区域,然后进行裁剪
    w_diff=晶圆厂(作物w-img_w)
    h_diff=晶圆厂(作物h-img_h)
    放大图像=如果裁剪>图像,则为真;如果裁剪>图像,则为假
    收缩图像=如果剪切图像,则为真
    
    from math import floor, fabs
    from PIL import Image, ImageSequence
    
    def transform_image(original_img, crop_w, crop_h):
      """
      Resizes and crops the image to the specified crop_w and crop_h if necessary.
      Works with multi frame gif and webp images also.
    
      args:
      original_img is the image instance created by pillow ( Image.open(filepath) )
      crop_w is the width in pixels for the image that will be resized and cropped
      crop_h is the height in pixels for the image that will be resized and cropped
    
      returns:
      Instance of an Image or list of frames which they are instances of an Image individually
      """
      img_w, img_h = (original_img.size[0], original_img.size[1])
      n_frames = getattr(original_img, 'n_frames', 1)
    
      def transform_frame(frame):
        """
        Resizes and crops the individual frame in the image.
        """
        # resize the image to the specified height if crop_w is null in the recipe
        if crop_w is None:
          if crop_h == img_h:
            return frame
          new_w = floor(img_w * crop_h / img_h)
          new_h = crop_h
          return frame.resize((new_w, new_h))
    
        # return the original image if crop size is equal to img size
        if crop_w == img_w and crop_h == img_h:
          return frame
    
        # first resize to get most visible area of the image and then crop
        w_diff = fabs(crop_w - img_w)
        h_diff = fabs(crop_h - img_h)
        enlarge_image = True if crop_w > img_w or crop_h > img_h else False
        shrink_image = True if crop_w < img_w or crop_h < img_h else False
    
        if enlarge_image is True:
          new_w = floor(crop_h * img_w / img_h) if h_diff > w_diff else crop_w
          new_h = floor(crop_w * img_h / img_w) if h_diff < w_diff else crop_h
    
        if shrink_image is True:
          new_w = crop_w if h_diff > w_diff else floor(crop_h * img_w / img_h)
          new_h = crop_h if h_diff < w_diff else floor(crop_w * img_h / img_w)
    
        left = (new_w - crop_w) // 2
        right = left + crop_w
        top = (new_h - crop_h) // 2
        bottom = top + crop_h
    
        return frame.resize((new_w, new_h)).crop((left, top, right, bottom))
    
      # single frame image
      if n_frames == 1:
        return transform_frame(original_img)
      # in the case of a multiframe image
      else:
        frames = []
        for frame in ImageSequence.Iterator(original_img):
          frames.append( transform_frame(frame) )
        return frames