ZetCode

PyCairo 基础绘图

最后修改于 2023 年 7 月 17 日

在 PyCairo 教程的这一部分,我们绘制一些基本图元。我们使用填充和描边操作、虚线、线帽和线连接。

线条

线是非常基础的矢量对象。要绘制一条线,我们使用两个方法调用。起始点由 `move_to` 调用指定。线的终点由 `line_to` 调用指定。

lines.py
#!/usr/bin/python

'''
ZetCode PyCairo tutorial 

In this program, we connect all mouse
clicks with a line.

Author: Jan Bodnar
Website: zetcode.com
'''


from gi.repository import Gtk, Gdk
import cairo


class MouseButtons:
    
    LEFT_BUTTON = 1
    RIGHT_BUTTON = 3
    
    
class Example(Gtk.Window):

    def __init__(self):
        super(Example, self).__init__()
        
        self.init_ui()
        
        
    def init_ui(self):    

        self.darea = Gtk.DrawingArea()
        self.darea.connect("draw", self.on_draw)
        self.darea.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)        
        self.add(self.darea)
        
        self.coords = []
                     
        self.darea.connect("button-press-event", self.on_button_press)

        self.set_title("Lines")
        self.resize(300, 200)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.connect("delete-event", Gtk.main_quit)
        self.show_all()
        
    
    def on_draw(self, wid, cr):

        cr.set_source_rgb(0, 0, 0)
        cr.set_line_width(0.5)
        
        for i in self.coords:
            for j in self.coords:
                
                cr.move_to(i[0], i[1])
                cr.line_to(j[0], j[1]) 
                cr.stroke()

        del self.coords[:]            
                         
                         
    def on_button_press(self, w, e):
        
        if e.type == Gdk.EventType.BUTTON_PRESS \
            and e.button == MouseButtons.LEFT_BUTTON:
            
            self.coords.append([e.x, e.y])
            
        if e.type == Gdk.EventType.BUTTON_PRESS \
            and e.button == MouseButtons.RIGHT_BUTTON:
            
            self.darea.queue_draw()           
                                                        
    
def main():
    
    app = Example()
    Gtk.main()
        
        
if __name__ == "__main__":    
    main()

在我们的示例中,我们用鼠标左键随机点击窗口。每次点击都存储在一个列表中。当我们在窗口上右键单击时,所有点都与列表中的其他所有点连接起来。再次右键单击会清除窗口。

class MouseButtons:
    
    LEFT_BUTTON = 1
    RIGHT_BUTTON = 3

GTK 文档只是说明鼠标左键的编号是 1,鼠标右键的编号是 3。我们创建一个自定义类来为鼠标按钮提供一些标识符。

self.darea.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)   

有些事件默认未启用;鼠标按下事件就是其中之一。因此,我们需要使用 `set_event` 方法启用鼠标按下事件。

self.darea.connect("button-press-event", self.on_button_press)

在此代码示例中,我们响应鼠标按下事件。

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

线条以黑色墨水绘制,宽度为 0.5 磅。

for i in self.coords:
    for j in self.coords:
        
        cr.move_to(i[0], i[1])
        cr.line_to(j[0], j[1]) 
        cr.stroke()

我们将列表中的每个点都连接到其他每个点。`stroke` 调用绘制线条。

del self.coords[:]    

最后,所有坐标都被删除。我们现在可以创建另一个对象了。

def on_button_press(self, w, e):
    
    if e.type == Gdk.EventType.BUTTON_PRESS \
        and e.button == MouseButtons.LEFT_BUTTON:
        
        self.coords.append([e.x, e.y])
...

如果我们按下鼠标左键,我们将它的 x 和 y 坐标添加到 `self.coords` 列表中。

if e.type == Gdk.EventType.BUTTON_PRESS \
    and e.button == MouseButtons.RIGHT_BUTTON:
    
    self.darea.queue_draw()

在按下鼠标右键的情况下,我们调用 `queue_draw` 方法,该方法会重绘绘图区域。所有点都用线条连接。

Lines
图:线条

填充和描边

描边操作绘制形状的轮廓,填充操作填充形状的内部。

fillstroke.py
#!/usr/bin/python

'''
ZetCode PyCairo tutorial 

This code example draws a circle
using the PyCairo library.

Author: Jan Bodnar
Website: zetcode.com
'''

from gi.repository import Gtk
import cairo
import math


class Example(Gtk.Window):

    def __init__(self):
        super(Example, self).__init__()
        
        self.init_ui()
        
        
    def init_ui(self):    

        darea = Gtk.DrawingArea()
        darea.connect("draw", self.on_draw)
        self.add(darea)

        self.set_title("Fill & stroke")
        self.resize(230, 150)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.connect("delete-event", Gtk.main_quit)
        self.show_all()
        
    
    def on_draw(self, wid, cr):

        cr.set_line_width(9)
        cr.set_source_rgb(0.7, 0.2, 0.0)
        
        w, h = self.get_size()      

        cr.translate(w/2, h/2)
        cr.arc(0, 0, 50, 0, 2*math.pi)
        cr.stroke_preserve()
        
        cr.set_source_rgb(0.3, 0.4, 0.6)
        cr.fill()
        
    
def main():
    
    app = Example()
    Gtk.main()
        
        
if __name__ == "__main__":    
    main()

在示例中,我们绘制一个圆并用纯色填充它。

import math

需要此模块来使用 `pi` 常量,该常量用于绘制圆。

cr.set_line_width(9)
cr.set_source_rgb(0.7, 0.2, 0.0)

我们使用 `set_line_width` 方法设置线条宽度。我们使用 `set_source_rgb` 方法将源设置为深红色。

w, h = self.get_size()     

在这里,我们获取窗口的宽度和高度。我们需要这些值来将圆居中显示在窗口上。

cr.translate(w/2, h/2)
cr.arc(0, 0, 50, 0, 2*math.pi)
cr.stroke_preserve()

使用 `translate` 方法,我们将绘图原点移动到窗口中心。我们希望我们的圆是居中的。`arc` 方法向 Cairo 绘图上下文添加一个新的圆形路径。最后,`stroke_preserve` 方法绘制圆的轮廓。与 `stroke` 方法不同,它还保留了形状以便稍后绘制。

cr.set_source_rgb(0.3, 0.4, 0.6)
cr.fill()

我们更改绘图颜色,并使用 `fill` 方法用新颜色填充圆。

Fill & stroke
图:填充与描边

画笔虚线

每条线都可以用不同的画笔虚线绘制。画笔虚线定义了线的样式。虚线样式由 `set_dash` 方法指定。样式由虚线列表设置,该列表是浮点值的列表。它们设置了虚线样式的开启和关闭部分。虚线由 `stroke` 方法用于创建线条。如果虚线数量为 0,则禁用虚线。如果虚线数量为 1,则假定为对称模式,其中开启和关闭部分的大小由虚线中单个值指定。

def on_draw(self, wid, cr):

    cr.set_source_rgba(0, 0, 0, 1)
    cr.set_line_width(2)

    cr.set_dash([4.0, 21.0, 2.0])

    cr.move_to(40, 30)  
    cr.line_to(250, 30)
    cr.stroke()

    cr.set_dash([14.0, 6.0])

    cr.move_to(40, 50)
    cr.line_to(250, 50)
    cr.stroke()

    cr.set_dash([1.0])

    cr.move_to(40, 70)
    cr.line_to(250, 70)
    cr.stroke()                

我们绘制了三条不同画笔虚线的线。

cr.set_dash([4.0, 21.0, 2.0])

我们有一个由三个数字组成的模式。我们绘制了 4 个点,跳过了 21 个点,然后绘制了 2 个点,接着跳过了 4 个点,绘制了 21 个点,然后跳过了 2 个点。这个模式一直持续到线的末尾。

cr.set_dash([14.0, 6.0])

在此模式中,我们始终绘制 14 个点,跳过 6 个点。

cr.set_dash([1.0])

这里我们创建了一个画笔虚线,它是一个对称模式,交替绘制单个开启和关闭的点。

Pen dashes
图:画笔虚线

线帽

线帽是线的端点。

Cairo 中有三种不同的线帽样式。

Line caps
图:方形、圆形和圆头线帽

带有 `cairo.LINE_CAP_SQUARE` 线帽的线与带有 `cairo.LINE_CAP_BUTT` 线帽的线大小不同。如果一条线宽为 x 单位,那么带有 `cairo.LINE_CAP_SQUARE` 线帽的线将正好比它大 x 单位;开始处大 x/2 单位,结束处大 x/2 单位。

def on_draw(self, wid, cr):

    cr.set_source_rgba(0, 0, 0, 1)
    cr.set_line_width(12)

    cr.set_line_cap(cairo.LINE_CAP_BUTT)
    cr.move_to(30, 50)
    cr.line_to(150, 50)
    cr.stroke()

    cr.set_line_cap(cairo.LINE_CAP_ROUND)
    cr.move_to(30, 90)
    cr.line_to(150, 90)
    cr.stroke()

    cr.set_line_cap(cairo.LINE_CAP_SQUARE)
    cr.move_to(30, 130)
    cr.line_to(150, 130)
    cr.stroke()

    cr.set_line_width(1.5)

    cr.move_to(30, 35)
    cr.line_to(30, 145)
    cr.stroke()

    cr.move_to(150, 35)
    cr.line_to(150, 145)
    cr.stroke()

    cr.move_to(155, 35)
    cr.line_to(155, 145)
    cr.stroke()

该示例绘制了三条具有三种不同线帽的线。它还将通过绘制三条额外的细垂直线来图形化地展示尺寸差异。

cr.set_line_width(12)

我们的线宽将为 12 单位。默认线宽为 2。

cr.set_line_cap(cairo.LINE_CAP_ROUND)
cr.move_to(30, 90)
cr.line_to(150, 90)
cr.stroke()

这里我们绘制了一条带有 `cairo.LINE_CAP_ROUND` 线帽的水平线。

cr.set_line_width(1.5)

cr.move_to(30, 35)
cr.line_to(30, 145)
cr.stroke()

这是用于演示尺寸差异的三条垂直线之一。

Line caps
图:线帽

线条连接

可以使用三种不同的连接样式连接线条。

Bevel, Round, Miter line joins
图:斜接、圆角、斜角线连接
def on_draw(self, wid, cr):

    cr.set_line_width(14)
    
    cr.rectangle(30, 30, 100, 100)        
    cr.set_line_join(cairo.LINE_JOIN_MITER)
    cr.stroke()

    cr.rectangle(160, 30, 100, 100)
    cr.set_line_join(cairo.LINE_JOIN_BEVEL)
    cr.stroke()

    cr.rectangle(100, 160, 100, 100)
    cr.set_line_join(cairo.LINE_JOIN_ROUND)
    cr.stroke()

在此示例中,我们绘制了三个具有各种连接样式的粗矩形。

cr.set_line_width(14)

线条宽度为 14 单位。

cr.rectangle(30, 30, 100, 100)        
cr.set_line_join(cairo.LINE_JOIN_MITER)
cr.stroke()

这里我们绘制了一个带有 `cairo.LINE_JOIN_MITER` 连接样式的矩形。

Line joins
图:线条连接

贝塞尔曲线

贝塞尔曲线是根据数学公式定义的曲线。绘制曲线的数学方法是由 Pierre Bézier 在 20 世纪 60 年代末为雷诺汽车制造而创建的。

curve_to(x1, y1, x2, y2, x3, y3)

`curve_to` 方法向路径添加一个三次贝塞尔样条。参数是第一个控制点的 x 和 y 坐标、第二个控制点的 x 和 y 坐标以及曲线终点的 x 和 y 坐标。

def on_draw(self, wid, cr):

    cr.move_to(20, 40)
    cr.curve_to(320, 200, 330, 110, 450, 40)        
    cr.stroke()

在示例中,使用 `curve_to` 方法绘制了一条贝塞尔曲线。

Bézier curve
图:贝塞尔曲线

在 PyCairo 教程的本章中,我们进行了一些基础绘图。