Pythonでベジェ曲線クラスを実装してwxPythonで動かす

中学生でもわかるベジェ曲線

を読んでなるほどなーと思い、理解の確認でPythonベジェ曲線のクラスを実装した。

#! /usr/bin/python2.7
# coding: utf-8
# bezier.py
# ref: http://ruiueyama.tumblr.com/post/11197882224

def pt(p, q, t):
    assert 0 <= t <= 1
    return [a + (b - a) * float(t) for a,b in zip(p, q)]
def mid(p, q):
    return pt(p, q, .5)

class Bezier:
    def __init__(self, ctrls, dt=3*1e-3):
        self.ctrls = ctrls
        self.dt = dt
    def __iter__(self):
        ctrl = self.ctrls
        dt = self.dt
        
        t = 0
        while t <= 1:
            x,y = self.walkdown(ctrl, t)
            yield x,y
            t += dt
    def walkdown(self, ctrl, t):
        dt = self.dt
        if len(ctrl) == 1:
            return ctrl[0]
        else:
            ps = [pt(p, q, t) for p,q in zip(ctrl, ctrl[1:])]
            return self.walkdown(ps, t)

これだけ。ついでにwxPythonで曲線を描画してみた。gnuplotでやったほうがよかったかなー。

#!/usr/bin/python2.7
# coding: utf-8
import wx
from Bezier import *

class Mapper(object):
    # coordinate mapper: bounds -> client
    def __init__(self, size, lowerleft, upperright):
        self.size = size 
        self.ll = lowerleft
        self.ur = upperright
    def toClient(self, x, y):
        w, h = self.size
        xs,ys = map(float, self.ll)
        xe,ye = map(float, self.ur)
        
        xx,yy = x - xs, y - ys
        xp,yp = (xe - xs) / w, (ye - ys) / h
        xn,yn = xx / xp, h - yy / yp
        #print x,y,'=>',xn,yn
        return int(xn), int(yn)

class MyFrame(wx.Frame):
    def __init__(self):
        # init controls
        super(MyFrame, self).__init__(
            None,
            title='Bezier Curve',
            style= wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX
            )
        self.panel = wx.Panel(self, size=(400, 400))
        self.panel.Bind(wx.EVT_PAINT, self.paint)
        self.Fit()
        # init bezier control points
        self.ctrls = [(2, 7), (6, 14), (10, 12), (13, 2)]
    def paint(self, e):
        dc = wx.PaintDC(self.panel)
        m = Mapper(self.panel.GetSize(), (-1, -1), (15, 15))
        
        self.clear(dc)
        self.drawgrid(m, dc)
        self.drawguide(m, dc)
        self.drawbezier(m, dc)

    def clear(self, dc):
        self.setColor(dc,'white')
        dc.DrawRectangle(*(0, 0) + tuple(self.panel.GetSize()))
        
    def drawbezier(self, m, dc):
        # draw bezier curve
        self.setColor(dc,'black')
        #dc.DrawPointList([m.toClient(x, y) for x, y in Bezier(self.ctrls)])
        lst = list(Bezier(self.ctrls))
        dc.DrawLineList([m.toClient(*p) + m.toClient(*q) for p,q in zip(lst, lst[1:])])

    def drawguide(self, m, dc):
        #
        self.setColor(dc,'yellow')
        for p1,p2 in zip(self.ctrls, self.ctrls[1:]):
            line = m.toClient(*p1) + m.toClient(*p2)
            dc.DrawLine(*line)
        
        self.setColor(dc,'green')
        lst = []
        for p1,p2,p3 in zip(self.ctrls, self.ctrls[1:], self.ctrls[2:]):
            p = m.toClient(*mid(p1, p2))
            q = m.toClient(*mid(p2, p3))
            dc.DrawCircle(*p + (2,))
            dc.DrawCircle(*q + (2,))
            dc.DrawLine(*p + q)
            lst.append(mid(p, q))
        
        self.setColor(dc,'blue')
        dc.DrawCircle(*lst[0] + [2,])
        dc.DrawCircle(*lst[1] + [2,])
        dc.DrawLine(*lst[0] + lst[1])
        
        # draw control points
        self.setColor(dc,'red')
        for x,y in self.ctrls:
            dc.DrawCircle(*m.toClient(x, y) + (3,))

    def drawgrid(self, m, dc):
        xs,ys = m.ll
        xe,ye = m.ur
        
        self.setColor(dc,'black')
        dc.DrawLine(*m.toClient(xs, 0) + m.toClient(xe, 0))
        dc.DrawLine(*m.toClient(0, ys) + m.toClient(0, ye))
        
        dc.SetFont(self.panel.GetFont())
        dc.DrawText('%+d' % xs, *m.toClient(xs , 0))
        dc.DrawText('%+d' % ye, *m.toClient(0, ye))
        #x,y = m.toClient(xe, 0)
        #dc.DrawText('%+d' % xe, *(x - dc.GetTextExtent('%+d' % xe)[0], y))
        #x,y = m.toClient(0, ys)
        #dc.DrawText('%+d' % ys, *(x, y dc.GetTextExtent('%+d' % ys)[1]))
        
        #...
    def setColor(self, dc, color):
        dc.SetPen(wx.Pen(color))
        dc.SetBrush(wx.Brush(color))
        
app = wx.PySimpleApp()
frame = MyFrame().Show()
app.MainLoop()