[翻译]PyCairo指南--变换

折月煮酒 提交于 2020-11-18 20:10:43

变换

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指南的这个部分,我们讨论了变换。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!