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