当前位置: 首页 > 文档资料 > PyGTK 教程 >

13用 Cairo 库绘图 II

优质
小牛编辑
120浏览
2023-12-01

在教程的这个部分,我们将继续用Cairo图形库进行绘图。

Donut

在下面的例子中,我们通过旋转一串椭圆来创建一个复杂的形状。

Code:donut.py

#!/usr/bin/python
# ZetCode PyGTK tutorial 
#
# This program creates a donut
# with cairo library
#
# author: jan bodnar
# website: zetcode.com 
# last edited: February 2009
import gtk
import math
class PyApp(gtk.Window):
    def __init__(self):
        super(PyApp, self).__init__()
        
        self.set_title("Donut")
        self.set_size_request(350, 250)
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect("destroy", gtk.main_quit)
        darea = gtk.DrawingArea()
        darea.connect("expose-event", self.expose)
        self.add(darea)
        
        self.show_all()
    def expose(self, widget, event):
        cr = widget.window.cairo_create()
        cr.set_line_width(0.5)
        w = self.allocation.width
        h = self.allocation.height
       
        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()
            
PyApp()
gtk.main()

在这个例子中,我们创建了一个环状线圈。它的形状有点像一种甜点,因此名字叫做donut。

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

几个旋转之后,就有了一个环状线圈。我们使每个旋转孤立,并且通过save()restore()方法一个接一个的进行缩放操作。

Figure:Donut

Gradients

在计算机图形中,梯度(Gradient)是一种光滑的渐变混合,从亮到暗或者从一种颜色到另一种。在2D绘图程序和喷绘程序中,梯度常被用来创建色彩斑斓的背景和类似模仿的光和影一样的效果。(引用answers.com)

Code:gradients.py

#!/usr/bin/python
# ZetCode PyGTK tutorial 
#
# This program works with
# gradients in cairo
#
# author: jan bodnar
# website: zetcode.com 
# last edited: February 2009
import gtk
import cairo
class PyApp(gtk.Window):
    def __init__(self):
        super(PyApp, self).__init__()
        
        self.set_title("Gradients")
        self.set_size_request(340, 390)
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect("destroy", gtk.main_quit)
        darea = gtk.DrawingArea()
        darea.connect("expose-event", self.expose)
        self.add(darea)
        self.show_all()
    def expose(self, widget, event):
        cr = widget.window.cairo_create()
        lg1 = cairo.LinearGradient(0.0, 0.0, 350.0, 350.0)
       
        count = 1
        i = 0.1
        while i < 1.0: 
            if count % 2:
                lg1.add_color_stop_rgba(i, 0, 0, 0, 1)
            else:
                lg1.add_color_stop_rgba(i, 1, 0, 0, 1)
            i = i + 0.1
            count = count + 1      
        cr.rectangle(20, 20, 300, 100)
        cr.set_source(lg1)
        cr.fill()
        lg2 = cairo.LinearGradient(0.0, 0.0, 350.0, 0)
       
        count = 1
         
        i = 0.05    
        while i < 0.95: 
            if count % 2:
                lg2.add_color_stop_rgba(i, 0, 0, 0, 1)
            else:
                lg2.add_color_stop_rgba(i, 0, 0, 1, 1)
            i = i + 0.025
            count = count + 1        
        cr.rectangle(20, 140, 300, 100)
        cr.set_source(lg2)
        cr.fill()
        lg3 = cairo.LinearGradient(20.0, 260.0,  20.0, 360.0)
        lg3.add_color_stop_rgba(0.1, 0, 0, 0, 1) 
        lg3.add_color_stop_rgba(0.5, 1, 1, 0, 1) 
        lg3.add_color_stop_rgba(0.9, 0, 0, 0, 1) 
        cr.rectangle(20, 260, 300, 100)
        cr.set_source(lg3)
        cr.fill()
PyApp()
gtk.main()

在我们的例子中,我们用三种不同的梯度来绘制了三个矩形。

lg1 = cairo.LinearGradient(0.0, 0.0, 350.0, 350.0)

这里我们创建了一个线形的梯度图案。参数指定了线沿着我们想画的梯度的方向。在这里它是垂直方向的线。

lg3 = cairo.LinearGradient(20.0, 260.0,  20.0, 360.0)
lg3.add_color_stop_rgba(0.1, 0, 0, 0, 1) 
lg3.add_color_stop_rgba(0.5, 1, 1, 0, 1) 
lg3.add_color_stop_rgba(0.9, 0, 0, 0, 1) 

为了产生我们的梯度图案,我们定义了颜色停顿。在这里,梯度是黑色和黄色的混合。通过给一个黄色停顿增加两个黑色,我们创建了一个水平的梯度图案。这些停顿实际上是什么意思呢?在这里,我们以黑色开始,它将在1/10大小的位置上停顿,然后我们逐渐用黄色进行喷绘,它将在到达图形的中间达到最亮。黄色在9/10大小的位置停止。这里我们重新开始用黑色进行喷绘,知道结尾。

Figure:Gradients

Puff

在下面的例子中,我们将创建膨胀(puff)效果。例子显示正在变大的居中文本。最后从一些端点逐渐淡出。这是非常通用的效果,你可以在flash动画中经常看到这种效果。

Code:puff.py

#!/usr/bin/python
# ZetCode PyGTK tutorial 
#
# This program creates a puff
# effect 
#
# author: jan bodnar
# website: zetcode.com 
# last edited: February 2009
import gtk
import glib
import cairo
class PyApp(gtk.Window):
    def __init__(self):
        super(PyApp, self).__init__()
        
        self.set_title("Puff")
        self.resize(350, 200)
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect("destroy", gtk.main_quit)
        self.darea = gtk.DrawingArea()
        self.darea.connect("expose-event", self.expose)
        self.add(self.darea)
        
        self.timer = True
        self.alpha = 1.0
        self.size = 1.0
        glib.timeout_add(14, self.on_timer)
        
        self.show_all()
        
    def on_timer(self):
        if not self.timer: return False
        self.darea.queue_draw()
        return True
    def expose(self, widget, event):
        cr = widget.window.cairo_create()
        w = self.allocation.width
        h = self.allocation.height
        cr.set_source_rgb(0.5, 0, 0)
        cr.paint()
        cr.select_font_face("Courier", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
        self.size = self.size + 0.8
        if self.size > 20:
            self.alpha = self.alpha - 0.01
        
        cr.set_font_size(self.size)
        cr.set_source_rgb(1, 1, 1)
        
        (x, y, width, height, dx, dy) = cr.text_extents("ZetCode")
        cr.move_to(w/2 - width/2, h/2)
        cr.text_path("ZetCode")
        cr.clip()
        cr.stroke()
        cr.paint_with_alpha(self.alpha)
        if self.alpha <= 0:
            self.timer = False
PyApp()
gtk.main()

这个例子在窗口创建了一个增大和淡出的文本。

glib.timeout_add(14, self.on_timer)

每隔14ms on_timer()方法会被调用。

def on_timer(self):
    if not self.timer: return False
    self.darea.queue_draw()
    return True

on_timer()方法中,我们在绘图区上调用了queue_draw()方法,该方法将触发expose信号。

cr.set_source_rgb(0.5, 0, 0)
cr.paint()

我们设置背景颜色为暗红色。

self.size = self.size + 0.8

每个循环,字体的大小将会增加0.8个单位。

if self.size > 20:
    self.alpha = self.alpha - 0.01

当字体的尺寸大于20的之后,淡出过程就开始了。

(x, y, width, height, dx, dy) = cr.text_extents("ZetCode")

我们获得了文本的公制尺度。

cr.move_to(w/2 - width/2, h/2)

我们用文本的公制尺度来使文本居于窗口的中心。

cr.text_path("ZetCode")
cr.clip()

我们获得了文本的路径,并且将当前的修剪部位设置为这个路径。

cr.stroke()
cr.paint_with_alpha(self.alpha)

我们绘出当前的路径,并且将alpha值加入到这个过程中去。

Figure:Puff

Reflection

在下面的例子中,我们将显示一个反射的图像。这个漂亮的效果会使人产生一种幻觉,就像一幅图像在水里被反射一样。

Code:reflection.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
# ZetCode PyGTK tutorial 
#
# This program creates an
# image reflection
#
# author: Jan Bodnar
# website: zetcode.com 
# last edited: April 2011
import gtk
import cairo
import sys
class PyApp(gtk.Window):
    def __init__(self):
        super(PyApp, self).__init__()
        
        self.set_title("Reflection")
        self.resize(300, 350)
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect("destroy", gtk.main_quit)
        darea = gtk.DrawingArea()
        darea.connect("expose-event", self.expose)
        self.add(darea)
        
        try:
            self.surface = cairo.ImageSurface.create_from_png("slanec.png")
        except Exception, e:
            print e.message
            sys.exit(1)
        
        
        self.imageWidth = self.surface.get_width()
        self.imageHeight = self.surface.get_height()
        self.gap = 40
        self.border = 20
        self.show_all()
    def expose(self, widget, event):
        cr = widget.window.cairo_create()
                   
        w = self.allocation.width
        h = self.allocation.height
          
        lg = cairo.LinearGradient(w/2, 0, w/2, h*3)
        lg.add_color_stop_rgba(0, 0, 0, 0, 1)
        lg.add_color_stop_rgba(h, 0.2, 0.2, 0.2, 1)
        cr.set_source(lg)
        cr.paint()
        
        cr.set_source_surface(self.surface, self.border, self.border)
        cr.paint()
        alpha = 0.7
        step = 1.0 / self.imageHeight
      
        cr.translate(0, 2 * self.imageHeight + self.gap)
        cr.scale(1, -1)
        
        i = 0
        
       
        while(i < self.imageHeight):
            cr.rectangle(self.border, self.imageHeight-i, self.imageWidth, 1)
            i = i + 1
            
            cr.save()
            cr.clip()
            cr.set_source_surface(self.surface, self.border, self.border)
            alpha = alpha - step
            cr.paint_with_alpha(alpha)
            cr.restore()
        
PyApp()
gtk.main()

这个例子展示了一个反射的城堡。

lg = cairo.LinearGradient(w/2, 0, w/2, h*3)
lg.add_color_stop_rgba(0, 0, 0, 0, 1)
lg.add_color_stop_rgba(h, 0.2, 0.2, 0.2, 1)

cr.set_source(lg)
cr.paint()

背景是用一种梯度的喷绘填充的。这个喷绘是一种从黑色到暗灰色的平滑的组合。

cr.translate(0, 2 * self.imageHeight + self.gap)
cr.scale(1, -1)

这行代码将图像翻转,使其转变为在原始图像的下面。Translation操作是很必要的,因为scaling操作使这个图像上部下来,然后再translate这个图像。要理解这里发生了什么,请找一张照片,将它放在桌子上,现在翻转(flip)它。

cr.rectangle(self.border, self.imageHeight-i, self.imageWidth, 1)
i = i + 1
cr.save()
cr.clip()
cr.set_source_surface(self.surface, self.border, self.border)
alpha = alpha - step
cr.paint_with_alpha(alpha)
cr.restore()

这是代码的关键部分。我们使第二幅图像变得透明,但是这个透明度并不是恒定的。图像逐渐淡出,这就是用梯度来完成的。

Figure:Reflection

Waiting

在这个例子中,我们使用透明度效果来创建一个等待的演示实例。我们将画8条线,它们将逐渐淡出,这样就创建了一个幻觉——一条线在运动。这个效果通常用来通知用户,一个冗长的任务正在后台运行。一个典型的例子是从因特网上打开一个视频。

Code:waiting.py

#!/usr/bin/python
# ZetCode PyGTK tutorial 
#
# This program creates an
# waiting effect
#
# author: jan bodnar
# website: zetcode.com 
# last edited: February 2009
import gtk
import glib
import math
import cairo
trs = (
    ( 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 ),
    ( 1.0, 0.0,  0.15, 0.30, 0.5, 0.65, 0.8, 0.9 ),
    ( 0.9, 1.0,  0.0,  0.15, 0.3, 0.5, 0.65, 0.8 ),
    ( 0.8, 0.9,  1.0,  0.0,  0.15, 0.3, 0.5, 0.65 ),
    ( 0.65, 0.8, 0.9,  1.0,  0.0,  0.15, 0.3, 0.5 ),
    ( 0.5, 0.65, 0.8, 0.9, 1.0,  0.0,  0.15, 0.3 ),
    ( 0.3, 0.5, 0.65, 0.8, 0.9, 1.0,  0.0,  0.15 ),
    ( 0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0,  0.0, )
)
class PyApp(gtk.Window):
    def __init__(self):
        super(PyApp, self).__init__()
        
        self.set_title("Waiting")
        self.set_size_request(250, 150)
        self.set_position(gtk.WIN_POS_CENTER)
        self.connect("destroy", gtk.main_quit)
        self.darea = gtk.DrawingArea()
        self.darea.connect("expose-event", self.expose)
        self.add(self.darea)
        
        self.count = 0
        
        glib.timeout_add(100, self.on_timer)
        
        self.show_all()
        
    def on_timer(self):
        self.count = self.count + 1
        self.darea.queue_draw()
        return True
    def expose(self, widget, event):
        cr = widget.window.cairo_create()
        cr.set_line_width(3)
        cr.set_line_cap(cairo.LINE_CAP_ROUND)
        w = self.allocation.width
        h = self.allocation.height
       
        cr.translate(w/2, h/2)
        for i in range(8):
            cr.set_source_rgba(0, 0, 0, trs[self.count%8][i])
            cr.move_to(0.0, -10.0)
            cr.line_to(0.0, -40.0)
            cr.rotate(math.pi/4)
            cr.stroke()
PyApp()
gtk.main()

我们用8个不同的alpha值画了8条线。

glib.timeout_add(100, self.on_timer)

我们使用了一个timer函数来创建动画。

trs = (
    ( 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 ),
    ...
)

这是一个二维的元组,其值为用于该例子的透明度值。这里有八行,每行是一种状态。8条线每条将会陆续地使用这些值。

cr.set_line_width(3)
cr.set_line_cap(cairo.LINE_CAP_ROUND)

我们是线宽稍微大点,这样才会有更好的视觉效果。我们用环形的cap来画这些线。他们看起来更好。

cr.set_source_rgba(0, 0, 0, trs[self.count%8][i]

这里我们定义了一条线的透明度值。

cr.move_to(0.0, -10.0)
cr.line_to(0.0, -40.0)
cr.rotate(math.pi/4)
cr.stroke()

这些代码行将绘制出八条线中的每条。

Figure:Waiting

在教程的这章中,我们用Cairo图形库做了一些更高级的绘画工作。