枕头 - 调整GIF大小

时间:2017-01-18 12:00:54

标签: python image-processing gif pillow

我有// Exception to distinguish abort due to newer jobs in queue class NewerJobsException extends hudson.AbortException { public NewerJobsException(String message) { super(message); } } // Find jenkins job name from url name (which is the most consistently named // field in the task object) // Known forms: // job/NAME/ // job/NAME/98/ @NonCPS def name_from_url(url) { url = url.substring(url.indexOf("/") + 1); url = url.substring(0, url.indexOf("/")); return url } // Depending on installed plugins multiple jobs may be queued. If that is the // case skip this one. // http://stackoverflow.com/questions/26845003/how-to-execute-only-the-most-recent-queued-job-in-jenkins // http://stackoverflow.com/questions/8974170/jenkins-parameterized-job-that-only-queues-one-build @NonCPS def check_queue() { def name = env.JOB_NAME def queue = jenkins.model.Jenkins.getInstance().getQueue().getItems() if (queue.any{ name_from_url(it.task.getUrl()) == name }) { print "Newer ${name} job(s) in queue, aborting" throw new NewerJobsException("Newer ${name} job(s) in queue, aborting") } else { print "No newer ${name} job(s) in queue, proceeding" } } 我希望使用gif调整大小,以便缩小其大小。 pillow的当前大小为2MB。

我正在尝试

  1. 调整大小以使其高度/宽度更小

  2. 降低质量。

  3. 使用JPEG,以下代码通常足以使大图像的大小显着减小。

    gif

    但是,使用GIF,它似乎不起作用。以下代码甚至使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
    

    我一直在考虑在im = im.resize((im.size[0] // 2, im.size[1] // 2), Image.ANTIALIAS) # should decrease its size resize()上调用im.seek(),但这些方法都没有返回Image对象,因此我无法在其输出上调用im.tell()

    你知道如何在保留所有镜架的同时使用Pillow减小GIF的尺寸吗?

    [edit]部分解决方案:

    关注Old Bear's response后,我做了以下更改:

    • 我正在使用BigglesZX's script来提取所有帧。值得注意的是,这是一个Python 2脚本,我的项目是用Python 3编写的(我最初提到过这个细节,但它是由Stack Overflow社区编辑出来的)。运行resize()使该脚本与Python 3兼容。

    • 我一直在单独修改每个框架:2to3 -w gifextract.py

    • 我一直在保存所有相框:frame.resize((frame.size[0] // 2, frame.size[1] // 2), Image.ANTIALIAS)

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

    • 我不确定resize方法是否有效,因为img.save("out.gif", save_all=True, optimize=True)仍然是7.5MB。最初的gif是2MB。

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

    示例:

    原创gif out.gif

    Original gif

    处理后的Gif(my_gif.gifhttps://i.imgur.com/zDO4cE4.mp4(我无法将其添加到Stack Overflow)。 Imgur使它变慢(并将其转换为mp4)。当我从计算机打开gif文件时,整个gif持续约1.5秒。

3 个答案:

答案 0 :(得分:5)

根据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 documentation中提供的内容。您可以通过阅读此link

了解有关BiggleZX脚本中提到的tile属性的更多信息

答案 1 :(得分:5)

使用BigglesZX's script,我创建了一个新脚本,使用Pillow调整GIF大小。

原始GIF(2.1 MB):

Original gif

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

Output gif

我已保存脚本here。我使用Pillow的thumbnail方法而不是resize方法,因为我发现resize方法不起作用。

不完美,所以随意分叉并改进它。以下是一些未解决的问题:

  • 虽然GIF在imgur托管时显示得很好,但是当我从我的电脑打开它时会出现速度问题,整个GIF只需要1.5秒。
  • 同样,虽然imgur似乎弥补了速度问题,但当我尝试将其上传到stack.imgur时,GIF无法正确显示。仅显示第一帧(您可以看到它here)。

完整代码(如果上面的要点被删除):

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

答案 2 :(得分:0)

我正在使用下面的功能来调整大小和裁剪包括动画(GIF,WEBP)在内的图像。简单来说,我们需要迭代gif或webp中的每一帧。

var x = 0;
var y = 0;
var prevx = 0;
var prevy = 0;
var zoom = 25;

function plot(func) {

    this.func = func;
    this.x = x;
    this.y = y;
    this.prevx = prevx;
    this.prevy = prevy;

    function draw() {

        c.beginPath();
        c.moveTo(this.prevx * zoom, this.prevy * zoom);
        c.lineTo(this.x * zoom, this.y * zoom);
        c.strokeStyle = '#fff';
        c.stroke();


        this.prevx = this.x;
        this.prevy = this.y;

        this.x += 0.1;
        this.y = func(this.x);

    }

    function animate() {
        requestAnimationFrame(animate);
        draw();

    }

    animate();
}

plot(Math.sin);
plot(Math.cos);
// plot(Math.tan);