PythonでアニメGIF作成 その1

[pukiwiki]
今さらアニメGIFなんてー と思われるかもしれませんが。

作成したアニメGIFのサンプル
http://boxheadroom.com/wp/wp-content/uploads/2009/05/yukin.gif

[[手書き文字風GIF:http://boxheadroom.com/2009/05/19/ntoro_gif]]を作るためのプログラムは、また今度。
[/pukiwiki]

[pukiwiki]
—-
仕様言語 Python2.5
依存ライブラリ PIL
[[こちらのコードを元にしています:http://svn.effbot.python-hosting.com/pil/Scripts/gifmaker.py]]

参考ページ
-[[GIFファイル形式(nekopps):http://uketama.nekopps.com/article/gif_format]]
-[[GIF89a仕様書(抜粋):http://homepage3.nifty.com/g-dragon/gif/gif_spec.html]]
—-
以下のコードを gifmaker2.py として保存
[/pukiwiki]

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"gifmaker2.py"

import Image,ImageChops
from GifImagePlugin import getheader, getdata
def deco(f):
    return f()
class GifMaker(object):
    cls_none=0
    cls_overwrite=cls_left=1
    cls_bg=2
    cls_resume=3
    Debug=False
    
    def __init__(self,trans=False,loop=False,duration=100,
                 cls=2,mode="P"):
        """trans= False -> No transparent
                    0..255   ->  set transparent color
                    'auto' -> color(0,0) as transcolor in each frame

                    (x,y) -> pick color (x,y) as transcolor
                    (r,g,b) -> color code ==(r,g,b) as tramscolor
            loop=    0 ->loop infinit

                     False -> No Loop
            duration= 100  -> 1 sec
            cls=    0 GifMaker.cls_none  : none
                    1 GifMaker.cls_overwrite 'left'
                    2 GifMaker.cls_bg  : clear background'
                    3 GifMaker.cls_resume : resume

         """
        self.mode=mode
        self.idx=0
        self.dat=""
        if cls in range(4):
            self.cls=cls
        else :
            self.cls=self_cls_bg
        self.loop=loop
        self.tmode=self._gettransmode(trans)
        self.trans=trans
        self.duration=duration
    def _logopen(self,fname="gifmaker.log",mode="a"):
        self.Debug=True
        self.log=open(fname,mode)
    def _logclose(self):
        self.log.close()


    def _gettransmode(self,trans):

        t=type(trans)
        if t==str and trans.lower()=="auto":
            tmode="auto"
        elif t==int :
            tmode="col"
        elif t==tuple or t==list :
            l=len(trans)
            if l==2:
                tmode=="xy"
            elif l==3 :
                tmode="rgb"
        else :
            tmode=""
        return tmode

    @deco
    def _gettrans():

        def case_auto(self):
            return self.im.getpixel((0,0))

        def case_col(self):
            return self.trans
        def case_xy(self):
            return self.im.getpixel(self.trans)
        def case_rgb(self):
            im=self.im.convert("RGB")
            pix=im.load()
            w,h=im.size
            try:
                for y in xrange(h):
                    for x in xrange(w):
                        if  pix[x,y]==self.trans:
                            raise "match rgb"
                raise "rgb No match"
            except "match rgb" :
                return self.im.getpixel((x,y))
        sw=dict(
            auto=case_auto,
            col=case_col,
            xy=case_xy,
            rgb=case_rgb
            )

        def _gettrans(self):
            "_gettrans"
            if self.tmode :
                return sw[self.tmode](self)
            else :
                return 0
        return _gettrans

    def _getdelta(self):
        if self.Debug:
            print >>self.log , "_getdelta"
        
        delta = ImageChops.subtract_modulo(self.im, self.previous)
        assert(self.im!=self.previous)
        bbox = delta.getbbox()
        
        if bbox:
            dat=getdata(self.im.crop(bbox), offset = bbox[:2])
            if self.Debug:
                print >>self.log , "_getdelta"
                print >>self.log , dat 
            return "".join(dat)
        if self.Debug:
            print >>self.log , "_getdelta_exit"
            
    def writeframe(self,im,duration=None):
        if self.Debug:
            print >>self.log , "wrtiteframe"

        if not duration:
            duration=self.duration
        if self.mode != im.mode :
            im=im.convert(self.mode)
        self.im=im
        if not self.idx :
            h=getheader(im)
            
            h[0]="GIF89a"+h[0][6:]
            if self.Debug:
                print >>self.log , "header"
                print >>self.log , h
            self.dat="".join(h)
            self.previous=im.copy()
        if type(self.trans)==str and self.trans.lower()=="auto":
            self.trans=im.load()[0,0]
        if not self.idx :
            #gce=self._getgce(duration=duration)
            dat=getdata(im)
            if self.Debug:
                print >>self.log , "getdata"
                print >>self.log , dat
            #self.dat+=gce+"".join(dat)
            self.dat+="".join(dat)
        else:
            gce=self._getgce(duration=duration)
            delta=self._getdelta()
            if delta:
                
                self.dat+=gce+delta
        
        if False and self.idx:
            im.save("im%03d.png"%self.idx)
            self.previous.save("prev%03d.png"%self.idx)

        if self.idx:
            self.previous.paste(im)
        self.idx+=1
        
    def _getgce(self,duration=None):
        if self.Debug:
            print >>self.log , "_getgce"
        
        if not duration :
            duration=self.duration
        trans=self._gettrans()
        cls=(self.cls<<2)& 0x0c
        dh=(duration>>8)&0x0ff
        dl=duration&0x0ff

        dat=[ "!\xf9\x04"+chr(cls | 1 if self.tmode else 0 ),
            chr(dl)+chr(dh),chr(self._gettrans()),"\0"]
        if self.Debug:
            print >>self.log , dat
        return "".join(dat)

    def save(self,fname,wait=300):
        if self.Debug:
            print >>self.log , "save"

        dat=self.dat[:]
        fp=open(fname,"wb")
        fp.write(dat)
        if wait :
            gce=self._getgce(duration=wait)
            dummy=getdata(self.im.crop((0,0,1,1)))
            if self.Debug:

                print >>self.log , "save_wait_dummy"
                print >>self.log , dummy
                
            fp.write(gce)
            fp.write("".join(dummy))
     
        if type(self.loop)==int :
            looph=(self.loop>>8)&0x0ff
            loopl=self.loop&0xff
            dat=["!\xff\x0BNETSCAPE2.0","\x03\x01",
                     chr(loopl),chr(looph),"\0"]
            if self.Debug:
                print >>self.log , "save_netscape"
                print >>self.log , [dat]
            fp.write("".join(dat))
        fp.write(";")
        if self.Debug:
            print >>self.log , [";"]

        fp.close()
        
if __name__=="__main__":
    import ImageFont,ImageDraw
    sz=18
    prompt="YUKI.N>"
    txt=u"みえてる? "
    font = ImageFont.truetype("meiryo.ttc", sz)
    W,H=font.getsize(prompt+txt)
    W+=sz

    im=Image.new("1",(W,H),1)
    draw=ImageDraw.ImageDraw(im)

    gm=GifMaker(loop=0,duration=33,cls=GifMaker.cls_bg,mode="1")

    x=sz//2
    gm.writeframe(im.convert("1"),duration=100)
    draw.text((x,0),prompt,fill=0,font=font)
    gm.writeframe(im.convert("1"),duration=200)
    x=font.getsize(prompt)[0]+sz//2
    for i in range(1,len(txt)) :
        draw.text((x,0),txt[:i],fill=0,font=font)
        gm.writeframe(im.convert("1"))

    gm.save("yukin.gif",wait=300)


<<修正>>
関数save内、最後のフレームに、画像データを丸々一枚書き込んでいたのを、左上1ドット分のみに変更(画像サイズが小さくなるはず)
2009-06-30
getgce内、clsのマスク処理のバグ修正
ちょっとした実験用にデバッグ用コード追加

サンプルのGIFを作成するためのプログラム
ムダに長いですけど。
動作テスト環境 Vista (フォントはメイリオを使用)

# -*- coding: utf-8 -*-
from gifmaker2 import GifMaker
import Image,ImageDraw,ImageFont

sz=18
prompt="YUKI.N>"
txt=u"みえてる? "
font = ImageFont.truetype("meiryo.ttc", sz)
W,H=font.getsize(prompt+txt)
W+=sz

im=Image.new("1",(W,H),1)
draw=ImageDraw.ImageDraw(im)

gm=GifMaker(loop=0,duration=33,cls=GifMaker.cls_bg,mode="1")

x=sz//2
gm.writeframe(im.convert("1"),duration=100)
draw.text((x,0),prompt,fill=0,font=font)
gm.writeframe(im.convert("1"),duration=200)
x=font.getsize(prompt)[0]+sz//2
for i in range(1,len(txt)) :
    draw.text((x,0),txt[:i],fill=0,font=font)
    gm.writeframe(im.convert("1"))

gm.save("yukin.gif",wait=1000)

コメントを残す

メールアドレスが公開されることはありません。