python制作gif动图_PIL生成透明GIF动图

最近使用PIL来将多张图片合并为一张GIF动图,结果发现在生成背景透明的GIF时总是有问题。研究一番终于解决,遂记之……

首先,安装一下PIL:

pillow

ZSH

sudo pip pinstall pillow -i http://pypi.douban.com/simple

1sudopippinstallpillow-ihttp://pypi.douban.com/simple

基本用法

PIL的用法可以参考官方文档,对于其中具体的参数,也可查看PIL handbook。

使用PIL读取GIF图片,我们以下面的图片为例:

pipixia.gif

read_gif

Python

#!/usr/bin/env python

# -*- coding: utf-8 -*-

from PIL import Image

from PIL import ImageSequence

def read_gif(gif):

img = Image.open(gif)

i = 0

for frame in ImageSequence.Iterator(img):

frame.save("gif_%d.png" % i)

i += 1

img.close()

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15#!/usr/bin/env python

# -*- coding: utf-8 -*-

fromPILimportImage

fromPILimportImageSequence

defread_gif(gif):

img=Image.open(gif)

i=0

forframeinImageSequence.Iterator(img):

frame.save("gif_%d.png"%i)

i+=1

img.close()

将GIF动图的每一帧重新合并回来:

write_gif

Python

def write_gif(gif):

frames = []

img = Image.open(gif)

for frame in ImageSequence.Iterator(img):

frames.append(frame.copy().convert("RGBA"))

img = Image.new("RGBA", frame.size, (255, 255, 255, 0))

img.save("output.gif", save_all=True, append_images=frames, loop=0)

1

2

3

4

5

6

7

8defwrite_gif(gif):

frames=[]

img=Image.open(gif)

forframeinImageSequence.Iterator(img):

frames.append(frame.copy().convert("RGBA"))

img=Image.new("RGBA",frame.size,(255,255,255,0))

img.save("output.gif",save_all=True,append_images=frames,loop=0)

结果生成的动图(暂时先忽略第一帧的白屏):

pipixia2.gif

由于背景透明,我们需要特殊处理:

write_gif

Python

def write_gif2(gif):

frames = []

img = Image.open(gif)

mask = Image.new("RGBA", img.size, (255, 255, 255, 0))

for frame in ImageSequence.Iterator(img):

f = frame.copy().convert("RGBA")

frames.append(Image.alpha_composite(mask, f))

img = Image.new("RGBA", frame.size, (255, 255, 255, 0))

img.save("output.gif", save_all=True, append_images=frames, loop=0, transparency=0)

1

2

3

4

5

6

7

8

9

10defwrite_gif2(gif):

frames=[]

img=Image.open(gif)

mask=Image.new("RGBA",img.size,(255,255,255,0))

forframeinImageSequence.Iterator(img):

f=frame.copy().convert("RGBA")

frames.append(Image.alpha_composite(mask,f))

img=Image.new("RGBA",frame.size,(255,255,255,0))

img.save("output.gif",save_all=True,append_images=frames,loop=0,transparency=0)

结果生成的动图:

pipixia3.gif

虽然解决了背景透明的问题,然而生成的动图却仍然有问题,前一帧的内容不断叠加在一起。

ImageMagick

参考 ImageMagick 中关于动画的文档,其中 convert 命令有一个参数 dispose,具体的区别可以查看文档说明:Dispose None – each frame overlaid in sequence

Dispose Previous – preserve background canvas

Dispose Background – clear to background

PIL中 Image.save() 不同的图像类型有不同的存储参数,找到 GIF格式,其提供的参数:save_all:If present and true, all frames of the image will be saved. If not, then only the first frame of a multiframe image will be saved.

append_images:A list of images to append as additional frames. Each of the images in the list can be single or multiframe images.

duration:The display duration of each frame of the multiframe gif, in milliseconds. Pass a single integer for a constant duration, or a list or tuple to set the duration for each frame separately.

loop:Integer number of times the GIF should loop.

optimize:If present and true, attempt to compress the palette by eliminating unused colors. This is only useful if the palette can be compressed to the next smaller power of 2 elements.

palette:Use the specified palette for the saved image. The palette should be a bytes or bytearray object containing the palette entries in RGBRGB… form. It should be no more than 768 bytes. Alternately, the palette can be passed in as an PIL.ImagePalette.ImagePalette object.

发现其并未提供 dispose 相关的选项。

Whatsinagif

参考 这里 and 这里 的介绍,其中主要是 Graphics Control Extension几个字节进行控制。因此,我们只需要在 Disposal Method 的 3bit 写入正确的数据即可。The first byte is the extension introducer. All extension blocks begin with 21. Next is the graphic control label, F9, which is the value that says this is a graphic control extension. Third up is the totalblock size in bytes. Next is a packed field. Bits 1-3 are reserved for future use. Bits 4-6 indicate disposal method. The penult bit is the user input flag and the last is the transparent color flag. The delay time value follows in the next two bytes stored in the unsigned format. After that we have the transparent color index byte. Finally we have the block terminator which is always 00.

python制作gif动图_PIL生成透明GIF动图_第1张图片

GifImagePlugin

在 PIL 库中,针对每一种图像格式都有专门的Plugin,具体可以参考文档。其中 GifImagePlugin 专门用来处理gif图像,根据上面gif图像的介绍,我们可以按自己的需要添加 disposal 参数,修改PIL库下的GifImagePlugin.py:

GifImagePlugin.py

Python

# 修改写入 header 的内容

def _write_local_header(fp, im, offset, flags):

......

disposal = int(im.encoderinfo.get('disposal', 0))

if transparent_color_exists or duration != 0 or disposal:

packed_flag = 1 if transparent_color_exists else 0

packed_flag |= disposal << 2

if not transparent_color_exists:

transparency = 0

fp.write(b"!" +

o8(249) + # extension intro

o8(4) + # length

o8(packed_flag) + # packed fields

o16(duration) + # duration

o8(transparency) + # transparency index

o8(0))

......

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19# 修改写入 header 的内容

def_write_local_header(fp,im,offset,flags):

......

disposal=int(im.encoderinfo.get('disposal',0))

iftransparent_color_existsorduration!=0ordisposal:

packed_flag=1iftransparent_color_existselse0

packed_flag|=disposal<<2

ifnottransparent_color_exists:

transparency=0

fp.write(b"!"+

o8(249)+# extension intro

o8(4)+# length

o8(packed_flag)+# packed fields

o16(duration)+# duration

o8(transparency)+# transparency index

o8(0))

......

修改源码后重新生成动图:

write_gif

Python

def write_gif3(gif):

frames = []

img = Image.open(gif)

mask = Image.new("RGBA", img.size, (255, 255, 255, 0))

for frame in ImageSequence.Iterator(img):

f = frame.copy().convert("RGBA")

frames.append(Image.alpha_composite(mask, f))

img = Image.new("RGBA", frame.size, (255, 255, 255, 0))

img.save("pipixia4.gif", save_all=True, append_images=frames, loop=0, transparency=0, disposal=2)

1

2

3

4

5

6

7

8

9

10defwrite_gif3(gif):

frames=[]

img=Image.open(gif)

mask=Image.new("RGBA",img.size,(255,255,255,0))

forframeinImageSequence.Iterator(img):

f=frame.copy().convert("RGBA")

frames.append(Image.alpha_composite(mask,f))

img=Image.new("RGBA",frame.size,(255,255,255,0))

img.save("pipixia4.gif",save_all=True,append_images=frames,loop=0,transparency=0,disposal=2)

pipixia4.gif

效果展示

使用 PIL 可以很方便实现各种图片处理需求,具体可以关注下面的公众号体验一下。

python制作gif动图_PIL生成透明GIF动图_第2张图片

你可能感兴趣的:(python制作gif动图)