[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)