PyCairo 图形学编程指南的这个部分,我们将讨论变换。
一个仿射变换由0个或者多个线性变换(旋转,放缩或切变)和平移(移位)组成。一些线性变换可以被结合起来放进一个单一的矩阵中。一个旋转是将一个精确的对象沿着一个固定的点做移动的变换。一个放缩是将对象进行放大或缩小的变换。放缩系数在所有方向上都是一致的。一个平移,是在一个特定的方向上,将每一个点都移动固定的距离的变换。一个切变是一个将一个对象正交的移动向给定的轴,同时保持轴某一侧的值比另一侧更大的变换。
来源: (wikipedia.org, freedictionary.com)
下面的例子描述了一个简单的平移。
def on_draw(self, wdith, cr): cr.set_source_rgb(0.2, 0.3, 0.8) cr.rectangle(10, 10, 60, 60) cr.fill() cr.translate(30, 30) cr.set_source_rgb(0.8, 0.3, 0.2) cr.rectangle(0, 0, 60, 60) cr.fill() cr.translate(60, 60) cr.set_source_rgb(0.8, 0.8, 0.2) cr.rectangle(0, 0, 60, 60) cr.fill() cr.translate(70, 70) cr.set_source_rgb(0.3, 0.8, 0.8) cr.rectangle(0, 0, 60, 60) cr.fill()
这个例子先画了一个矩形,然后我们做一个平移,并多次画了相同的矩形。
cr.translate(30, 30)
translate()函数通过平移用户空间原点来修改当前的平移矩阵。在我们的例子中,我们在两个方向上将原点移动20个单位。
Figure: Translation operation
在下面的例子中,我们执行一个切变操作。切变是沿着一个特定的轴来扭曲对象的操作。并没有一个方法来完成这个操作。我们需要创建我们自己的变换矩阵。注意,每一个仿射变换都可以通过创建一个变换矩阵来执行。
def on_draw(self, wdith, cr): cr.set_source_rgb(0.6, 0.6, 0.6) cr.rectangle(20, 30, 80, 50) cr.fill() mtx = cairo.Matrix(1.0, 0.5, 0.0, 1.0, 0.0, 0.0) cr.transform(mtx) cr.rectangle(130, 30, 80, 50) cr.fill()
这段code中,我们执行一个简单的切变操作。
mtx = cairo.Matrix(1.0, 0.5, 0.0, 1.0, 0.0, 0.0)
这个变换将y值切变为x值的0.5倍。
cr.transform(mtx)
我们用 transform()方法来执行变换。
Figure: Shearing operation
下一个例子演示了一个放缩操作。放缩是一个将对象放大或缩小的变换操作。
def on_draw(self, wdith, cr): cr.set_source_rgb(0.2, 0.3, 0.8) cr.rectangle(10, 10, 150, 150) cr.fill() cr.scale(0.6, 0.6) cr.set_source_rgb(0.8, 0.3, 0.2) cr.rectangle(30, 30, 150, 150) cr.fill() cr.scale(0.8, 0.8) cr.set_source_rgb(0.8, 0.8, 0.2) cr.rectangle(50, 50, 150, 150) cr.fill()
我们画了三个90×90 px大小的矩形。对于它们中的2个,我们执行了放缩操作。
cr.scale(0.6, 0.6) cr.set_source_rgb(0.8, 0.3, 0.2) cr.rectangle(30, 30, 150, 150) cr.fill()
我们一致地用一个因子0.6来缩小一个矩形。
cr.scale(0.8, 0.8) cr.set_source_rgb(0.8, 0.8, 0.2) cr.rectangle(50, 50, 150, 150) cr.fill()
这里我们用因子0.8执行了另外一个放缩操作。如果我们看图片,我们将看到,第三个黄色矩形是最小的。即使我们已经使用了另一个更小的放缩因子。这是由于变换操作是累积的。事实上,第三个矩形是用一个放缩因子0.528(0.6 ×0.8)来进行放缩的。
Figure: Scaling operation
变换是累积的。为了隔离各个变换操作,我们可以使用save()和restore()操作。save()方法创建一份当前的绘制上下文状态的拷贝,并将它存进一个保存状态的内部栈中。restore()方法将会重建上下文到保存的状态。
def on_draw(self, wdith, cr): cr.set_source_rgb(0.2, 0.3, 0.8) cr.rectangle(10, 10, 120, 120) cr.fill() cr.save() cr.scale(0.6, 0.6) cr.set_source_rgb(0.8, 0.3, 0.2) cr.rectangle(30, 30, 120, 120) cr.fill() cr.restore() cr.save() cr.scale(0.8, 0.8) cr.set_source_rgb(0.8, 0.8, 0.2) cr.rectangle(50, 50, 120, 120) cr.fill() cr.restore()
这个例子中我们放缩了两个矩形。这一次我们隔离了各个放缩操作。
cr.save() cr.scale(0.6, 0.6) cr.set_source_rgb(0.8, 0.3, 0.2) cr.rectangle(30, 30, 120, 120) cr.fill() cr.restore()
我们通过将scale()方法放到save()和restore()方法之间来隔离放缩操作。
Figure: Isolating transformations
注意,第三个黄色矩形比第二个红色的要大。
在下面的例子中,我们通过旋转一束椭圆形来创建一个复杂的形状。
#!/usr/bin/python ''' ZetCode PyCairo tutorial This program creates a 'donut' shape in PyCairo. author: Jan Bodnar website: zetcode.com last edited: August 2012 ''' import gtk import cairo import math class MainWindow(gtk.Window): def __init__(self): super(self.__class__, self).__init__() self.init_ui() def init_ui(self): self.darea = gtk.DrawingArea() self.darea.connect("expose_event", self.expose) self.add(self.darea) self.set_title("Donut") self.resize(350, 250) self.set_position(gtk.WIN_POS_CENTER) self.connect("delete-event", gtk.main_quit) self.show_all() def expose(self, widget, event): self.context = widget.window.cairo_create() self.on_draw(350, self.context) def on_draw(self, wdith, cr): cr.set_line_width(0.5) w, h = self.get_size() cr.translate(w/2, h/2) cr.arc(0, 0, 120, 0, 2 *math.pi) cr.stroke() for i in range(36): cr.save() cr.rotate(i * math.pi / 36) cr.scale(0.3, 1) cr.arc(0, 0, 120, 0, 2 *math.pi) cr.restore() cr.stroke() def main(): window = MainWindow() gtk.main() if __name__ == "__main__": main()
我们将执行旋转和放缩操作。我们也将保存和恢复PyCairo上下文。
cr.translate(w/2, h/2) cr.arc(0, 0, 120, 0, 2 *math.pi) cr.stroke()
在GTK窗口的中间,我们创建了一个圆形。这将是我们的椭圆形的边界圆形。
for i in range(36): cr.save() cr.rotate(i * math.pi / 36) cr.scale(0.3, 1) cr.arc(0, 0, 120, 0, 2 *math.pi) cr.restore() cr.stroke()
我们沿着我们的边界圆形的路径创建了36个椭圆形。我们通过save()和restore()方法,将各个旋转和放缩操作隔离开。
Figure: Donut
下一个例子中,显示了一个旋转和放缩的星星。
#!/usr/bin/python ''' ZetCode PyCairo tutorial This is a star example which demostrates scaling, translation and rotation operations in PyCairo. author: Jan Bodnar website: zetcode.com last edited: August 2012 ''' import gtk, glib import cairo class cv(object): points = ( (0, 85), (75, 75), (100, 10), (125, 75), (200, 85), (150, 125), (160, 190), (100, 150), (40, 190), (50, 125), (0, 85) ) SPEED = 20 TIMER_ID = 1 class MainWindow(gtk.Window): def __init__(self): super(self.__class__, self).__init__() self.init_ui() self.init_vars() def init_ui(self): self.darea = gtk.DrawingArea() self.darea.connect("expose_event", self.expose) self.add(self.darea) self.set_title("Star") self.resize(400, 300) self.set_position(gtk.WIN_POS_CENTER) self.connect("delete-event", gtk.main_quit) self.show_all() def init_vars(self): self.angle = 0 self.scale = 1 self.delta = 0.01 glib.timeout_add(cv.SPEED, self.on_timer) def on_timer(self): if self.scale < 0.01 or self.scale > 0.99: self.delta = - self.delta self.scale += self.delta self.angle += 0.01 self.darea.queue_draw() return True def expose(self, widget, event): self.context = widget.window.cairo_create() self.on_draw(350, self.context) def on_draw(self, wdith, cr): w, h = self.get_size() cr.set_source_rgb(0, 0.44, 0.7) cr.set_line_width(1) cr.translate(w/2, h/2) cr.rotate(self.angle) cr.scale(self.scale, self.scale) for i in range(10): cr.line_to(cv.points[i][0], cv.points[i][1]) cr.fill() def main(): window = MainWindow() gtk.main() if __name__ == "__main__": main()
这个例子中,我们创建了一个星星对象。我们将平移它,旋转它并放缩它。
points = ( (0, 85), (75, 75), (100, 10), (125, 75), (200, 85), ...
星星对象将从这些点来创建。
def init_vars(self): self.angle = 0 self.scale = 1 self.delta = 0.01 ...
在init_vars()方法中,我么初始化三个变量。self.angle被用于旋转,self.scale被用于放缩星星对象。self.delta变量控制何时星星不断放大而何时又不断缩小。
glib.timeout_add(cv.SPEED, self.on_timer)每一个 cv.SPEED ms,on_timer()方法都会被调到。
if self.scale < 0.01 or self.scale > 0.99: self.delta = - self.delta
这几行控制星星是逐渐变大还是变小。
cr.translate(w/2, h/2) cr.rotate(self.angle) cr.scale(self.scale, self.scale)
我们将星星移动到窗口的中心。旋转并放缩它。
for i in range(10): cr.line_to(cv.points[i][0], cv.points[i][1]) cr.fill()
我们在这里绘制星星对象。
Figure: Star
在PyCairo指南的这个部分,我们讨论了变换。