mysvglibを作り始めた
if __name__ == "__main__": cont = Context() cont.initialize() cont.render_header() cont = cont.trans(300, 300) cont.circle(r=100) for i in range(8): cont.rot(degrees(45 * i)).trans(200).circle(r=50) cont.render_footer()
仕様を網羅したライブラリを作る気ははなから存在しないので、このライブラリの進捗は僕が何を描きたいと思うかに依存します Dependencies: やる気、描きたいもの
cont.circle(r=100) cont.loop( 8, lambda cont: cont.rot(degrees(45)), lambda cont: cont.trans(200).circle(r=50))
これでさっきと同じ図が出るようにした。
cont.circle(**attrs(r=100, fill_opacity=0.5)) cont.loop( 8, lambda cont: cont.rot(degrees(45)), lambda cont: cont.trans(100).circle( **attrs(r=50, fill_opacity=0.5)))
んー、「**attrs」はさすがにダメかなぁ。
attrsをユーザに使わせるのはださいが、circleやsquareで毎回同じことをするのも実装としてださい。というわけで。
def attrs_decorator(f): def new_func(self, **kw): "replace key's hyphen: e.g. fill_rule -> fill-rule" new_kw = dict((key.replace("_", "-"), kw[key]) for key in kw) return f(self, **new_kw) return new_func class Context(dict): ... @attrs_decorator def circle(self, **kw): kw["transform"] = mat2str(self["matrix"]) attr_str = " ".join('%s="%s"' % p for p in kw.items()) self["out"].write('''<circle %s />\n''' % attr_str) return self
こんな感じ。自分の持っている行列の情報からtransformを作るところとか他のエレメントと共通かもしれないな。まあ、それは他のエレメントができてからくくりだせばいいや。僕は円だけで十分。
あとは複数の命令をまとめる機能が欲しいわけかな。
まずこうして
cont = cont.trans(300, 300).scale(100) cont.circle(fill_opacity=0.5) cont.loop( 8, lambda cont: cont.rot(degrees(45)), lambda cont: ( cont.trans(1) .scale(0.5) .circle(fill_opacity=0.5)))
この「cont.circle(fill_opacity=0.5)」を再利用可能な塊にしたい、と。
関数を取ってapplyして返すメソッドをcontextにつけてやればいいだけか。
class Context(dict): ... def apply(self, f): return f(self)
def circles(cont): return cont.circle(fill_opacity=0.5) cont = cont.trans(300, 300).scale(100) cont.apply(circles) cont.loop( 8, lambda cont: cont.rot(degrees(45)), lambda cont: ( cont.trans(1) .scale(0.5) .apply(circles)))
これでさっきと同じ絵が生成された。そして
def circles(cont): return ( cont .circle(fill_opacity=0.5) .scale(0.5) .circle(fill_opacity=0.5) .scale(0.5) .circle(fill_opacity=0.5))
うん、めでたしめでたし。
@recursion.max_depth(5) def circles(cont): return ( cont .circle(fill_opacity=0.5) .scale(0.8) .trans(0.7) .apply(circles)) cont = cont.trans(300, 300).scale(100) cont.apply(circles)
うん、ちゃんと5回で再起呼び出しが止まっている。
次は「だんだん薄くなる/濃くなる」をやろうかと思ったけど、それってfill-opacityの属性に干渉する必要があるんだな。Context Free Artではalphaの値がfill-opacityに加算で干渉するんだけども、この設計はいくらなんでもひどいと思うわけだ。で、乗算で干渉するのがただしいかって言うと、薄くなる方向はそれでいいのだけども、濃くなる方向もそれでいいのかというとちょっと疑問。
opacityって要するに前景色と背景色がx : 1-xなわけだから、xが0から1を動くときに比の値は0から無限大まで動くんだよね。前景色を「2倍に濃くする」と背景色が「2分の1に薄くなる」であってほしい。ということはどういうことか。opacityがxで、この「何倍濃くする」の値をmとすると、もともとx / (1-x) だったものをmx / (1-x)にするわけだ。比の値がそうなるように新しいxを決める。
でけた。
@recursion.max_depth(15) def circles(cont): return ( cont .circle(fill_opacity=0.5) .trans(0.7) .alpha(0.8) .apply(circles)) cont.apply(circles) cont.trans(y=2).alpha(10).apply(circles)
上は濃さ0.5から0.8倍ずつ薄くなって行って、下は10倍濃い状態から0.8倍ずつ薄くなって行っている。0を下回って表示されなくなったり、1を超えて振り切れてしまったりしていたContext Free Artに比べるとだいぶよくなったんじゃなかろうか。少なくともアート作品を作る上では。
今日の結論
を
if __name__ == "__main__": cont = Context() cont.initialize() cont.render_header() cont = cont.trans(300, 300).scale(100) @recursion.max_depth(7) def circles(cont): return ( cont .circle(fill_opacity=0.5) .loop(5, lambda cont: cont.rot(invr(5)), lambda cont: cont.trans(1) .alpha(1.2) .scale(0.5) .apply(circles))) cont.alpha(0.1).apply(circles) cont.render_footer()
これくらいの行数で出力できる。
ちなみにこのSVGは3メガバイトあって、20000個くらいの円がある。そろそろ画面プレビューを済ませた後、時間のかかるパラメータ設定で直接画像を出力、とかできるようにすべきかの。今日のところはこの辺で寝ることにする。続きは気がむいたとき。
TODO: 色!