原文: https://zetcode.com/gfx/pycairo/transformations/

在 PyCairo 图形编程教程的这一部分中,我们将讨论变换。

仿射变换由零个或多个线性变换(旋转,缩放或剪切)和平移(移位)组成。 几个线性变换可以组合成一个矩阵。 旋转是使刚体绕固定点移动的变换。 缩放比例是一种放大或缩小对象的变换。 比例因子在所有方向上都是相同的。 平移是一种变换,可以使每个点在指定方向上移动恒定的距离。 剪切是一种将对象垂直于给定轴移动的变换,该值在轴的一侧比另一侧更大。

数据来源:(wikipedia.org,freedictionary.com)

平移

以下示例描述了一个简单的平移。

  1. def on_draw(self, wid, cr):
  2. cr.set_source_rgb(0.2, 0.3, 0.8)
  3. cr.rectangle(10, 10, 30, 30)
  4. cr.fill()
  5. cr.translate(20, 20)
  6. cr.set_source_rgb(0.8, 0.3, 0.2)
  7. cr.rectangle(0, 0, 30, 30)
  8. cr.fill()
  9. cr.translate(30, 30)
  10. cr.set_source_rgb(0.8, 0.8, 0.2)
  11. cr.rectangle(0, 0, 30, 30)
  12. cr.fill()
  13. cr.translate(40, 40)
  14. cr.set_source_rgb(0.3, 0.8, 0.8)
  15. cr.rectangle(0, 0, 30, 30)
  16. cr.fill()

该示例绘制一个矩形。 然后,我们进行平移并再次绘制相同的矩形几次。

  1. cr.translate(20, 20)

translate()函数通过变换用户空间原点来修改当前变换矩阵。 在我们的例子中,我们在两个方向上将原点移动了 20 个单位。

PyCairo 中的变换 - 图1

图:平移 operation

剪切

在以下示例中,我们执行剪切操作。 剪切是沿特定轴的对象变形。 此操作没有剪切方法。 我们需要创建自己的变换矩阵。 注意,可以通过创建变换矩阵来执行每个仿射变换。

  1. def on_draw(self, wid, cr):
  2. cr.set_source_rgb(0.6, 0.6, 0.6)
  3. cr.rectangle(20, 30, 80, 50)
  4. cr.fill()
  5. mtx = cairo.Matrix(1.0, 0.5,
  6. 0.0, 1.0,
  7. 0.0, 0.0)
  8. cr.transform(mtx)
  9. cr.rectangle(130, 30, 80, 50)
  10. cr.fill()

在此代码示例中,我们执行一个简单的剪切操作。

  1. mtx = cairo.Matrix(1.0, 0.5,
  2. 0.0, 1.0,
  3. 0.0, 0.0)

此变换将 y 值剪切为 x 值的 0.5。

  1. cr.transform(mtx)

我们使用transform()方法执行变换。

PyCairo 中的变换 - 图2

图:抖动 operation

缩放

下一个示例演示了缩放操作。 缩放是一种变换操作,其中对象被放大或缩小。

  1. def on_draw(self, wid, cr):
  2. cr.set_source_rgb(0.2, 0.3, 0.8)
  3. cr.rectangle(10, 10, 90, 90)
  4. cr.fill()
  5. cr.scale(0.6, 0.6)
  6. cr.set_source_rgb(0.8, 0.3, 0.2)
  7. cr.rectangle(30, 30, 90, 90)
  8. cr.fill()
  9. cr.scale(0.8, 0.8)
  10. cr.set_source_rgb(0.8, 0.8, 0.2)
  11. cr.rectangle(50, 50, 90, 90)
  12. cr.fill()

我们绘制三个90x90px的矩形。 在其中两个上,我们执行缩放操作。

  1. cr.scale(0.6, 0.6)
  2. cr.set_source_rgb(0.8, 0.3, 0.2)
  3. cr.rectangle(30, 30, 90, 90)
  4. cr.fill()

我们将矩形均匀缩放 0.6 倍。

  1. cr.scale(0.8, 0.8)
  2. cr.set_source_rgb(0.8, 0.8, 0.2)
  3. cr.rectangle(50, 50, 90, 90)
  4. cr.fill()

在这里,我们以 0.8 的系数执行另一个缩放操作。 如果看图片,我们可以看到第三个黄色矩形是最小的一个。 即使我们使用了较小的比例因子。 这是因为变换操作是累加的。 实际上,第三个矩形的缩放比例为 0.528(0.6x0.8)。

PyCairo 中的变换 - 图3

图:缩放 operation

隔离变换

变换操作是累加的。 为了将一个操作与另一个操作隔离开,我们可以使用save()restore()方法。 save()方法复制图形上下文的当前状态,并将其保存在保存状态的内部栈中。 restore()方法将把上下文重新建立为保存状态。

  1. def on_draw(self, wid, cr):
  2. cr.set_source_rgb(0.2, 0.3, 0.8)
  3. cr.rectangle(10, 10, 90, 90)
  4. cr.fill()
  5. cr.save()
  6. cr.scale(0.6, 0.6)
  7. cr.set_source_rgb(0.8, 0.3, 0.2)
  8. cr.rectangle(30, 30, 90, 90)
  9. cr.fill()
  10. cr.restore()
  11. cr.save()
  12. cr.scale(0.8, 0.8)
  13. cr.set_source_rgb(0.8, 0.8, 0.2)
  14. cr.rectangle(50, 50, 90, 90)
  15. cr.fill()
  16. cr.restore()

在示例中,我们缩放了两个矩形。 这次我们将缩放操作相互隔离。

  1. cr.save()
  2. cr.scale(0.6, 0.6)
  3. cr.set_source_rgb(0.8, 0.3, 0.2)
  4. cr.rectangle(30, 30, 90, 90)
  5. cr.fill()
  6. cr.restore()

我们通过将scale()方法放在save()restore()方法之间来隔离缩放操作。

PyCairo 中的变换 - 图4

图:隔离转换

现在,第三个黄色矩形大于第二个红色矩形。

甜甜圈

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

  1. #!/usr/bin/python
  2. '''
  3. ZetCode PyCairo tutorial
  4. This program creates a 'donut' shape
  5. in PyCairo.
  6. author: Jan Bodnar
  7. website: zetcode.com
  8. last edited: August 2012
  9. '''
  10. from gi.repository import Gtk
  11. import cairo
  12. import math
  13. class Example(Gtk.Window):
  14. def __init__(self):
  15. super(Example, self).__init__()
  16. self.init_ui()
  17. def init_ui(self):
  18. darea = Gtk.DrawingArea()
  19. darea.connect("draw", self.on_draw)
  20. self.add(darea)
  21. self.set_title("Donut")
  22. self.resize(350, 250)
  23. self.set_position(Gtk.WindowPosition.CENTER)
  24. self.connect("delete-event", Gtk.main_quit)
  25. self.show_all()
  26. def on_draw(self, wid, cr):
  27. cr.set_line_width(0.5)
  28. w, h = self.get_size()
  29. cr.translate(w/2, h/2)
  30. cr.arc(0, 0, 120, 0, 2*math.pi)
  31. cr.stroke()
  32. for i in range(36):
  33. cr.save()
  34. cr.rotate(i*math.pi/36)
  35. cr.scale(0.3, 1)
  36. cr.arc(0, 0, 120, 0, 2*math.pi)
  37. cr.restore()
  38. cr.stroke()
  39. def main():
  40. app = Example()
  41. Gtk.main()
  42. if __name__ == "__main__":
  43. main()

我们将进行旋转和缩放操作。 我们还将保存和还原 PyCairo 上下文。

  1. cr.translate(w/2, h/2)
  2. cr.arc(0, 0, 120, 0, 2*math.pi)
  3. cr.stroke()

在 GTK 窗口的中间,我们创建了一个圆。 这将是我们椭圆的边界圆。

  1. for i in range(36):
  2. cr.save()
  3. cr.rotate(i*math.pi/36)
  4. cr.scale(0.3, 1)
  5. cr.arc(0, 0, 120, 0, 2*math.pi)
  6. cr.restore()
  7. cr.stroke()

我们沿着边界圆的路径创建了 36 个椭圆。 我们使用save()restore()方法将每个旋转和缩放操作相互隔离。

PyCairo 中的变换 - 图5

图:多纳圈

星星

下一个示例显示了一个旋转和缩放的星星。

  1. #!/usr/bin/python
  2. '''
  3. ZetCode PyCairo tutorial
  4. This is a star example which
  5. demonstrates scaling, translating and
  6. rotating operations in PyCairo.
  7. author: Jan Bodnar
  8. website: zetcode.com
  9. last edited: August 2012
  10. '''
  11. from gi.repository import Gtk, GLib
  12. import cairo
  13. class cv(object):
  14. points = (
  15. ( 0, 85 ),
  16. ( 75, 75 ),
  17. ( 100, 10 ),
  18. ( 125, 75 ),
  19. ( 200, 85 ),
  20. ( 150, 125 ),
  21. ( 160, 190 ),
  22. ( 100, 150 ),
  23. ( 40, 190 ),
  24. ( 50, 125 ),
  25. ( 0, 85 )
  26. )
  27. SPEED = 20
  28. TIMER_ID = 1
  29. class Example(Gtk.Window):
  30. def __init__(self):
  31. super(Example, self).__init__()
  32. self.init_ui()
  33. self.init_vars()
  34. def init_ui(self):
  35. self.darea = Gtk.DrawingArea()
  36. self.darea.connect("draw", self.on_draw)
  37. self.add(self.darea)
  38. self.set_title("Star")
  39. self.resize(400, 300)
  40. self.set_position(Gtk.WindowPosition.CENTER)
  41. self.connect("delete-event", Gtk.main_quit)
  42. self.show_all()
  43. def init_vars(self):
  44. self.angle = 0
  45. self.scale = 1
  46. self.delta = 0.01
  47. GLib.timeout_add(cv.SPEED, self.on_timer)
  48. def on_timer(self):
  49. if self.scale < 0.01:
  50. self.delta = -self.delta
  51. elif self.scale > 0.99:
  52. self.delta = -self.delta
  53. self.scale += self.delta
  54. self.angle += 0.01
  55. self.darea.queue_draw()
  56. return True
  57. def on_draw(self, wid, cr):
  58. w, h = self.get_size()
  59. cr.set_source_rgb(0, 0.44, 0.7)
  60. cr.set_line_width(1)
  61. cr.translate(w/2, h/2)
  62. cr.rotate(self.angle)
  63. cr.scale(self.scale, self.scale)
  64. for i in range(10):
  65. cr.line_to(cv.points[i][0], cv.points[i][1])
  66. cr.fill()
  67. def main():
  68. app = Example()
  69. Gtk.main()
  70. if __name__ == "__main__":
  71. main()

在此示例中,我们创建一个星形对象。 我们将对其进行平移,旋转和缩放。

  1. points = (
  2. ( 0, 85 ),
  3. ( 75, 75 ),
  4. ( 100, 10 ),
  5. ( 125, 75 ),
  6. ( 200, 85 ),
  7. ...

从这些点将构造星形对象。

  1. def init_vars(self):
  2. self.angle = 0
  3. self.scale = 1
  4. self.delta = 0.01
  5. ...

init_vars()方法中,我们初始化了三个变量。 self.angle用于旋转,self.scale用于缩放星形对象。 self.delta变量控制星星何时生长和何时收缩。

  1. glib.timeout_add(cv.SPEED, self.on_timer)

on_timer()方法的每个cv.SPEED ms 被调用。

  1. if self.scale < 0.01:
  2. self.delta = -self.delta
  3. elif self.scale > 0.99:
  4. self.delta = -self.delta

这些线控制星星是要增长还是要缩小。

  1. cr.translate(w/2, h/2)
  2. cr.rotate(self.angle)
  3. cr.scale(self.scale, self.scale)

我们将星星移到窗口中间。 旋转并缩放比例。

  1. for i in range(10):
  2. cr.line_to(cv.points[i][0], cv.points[i][1])
  3. cr.fill()

在这里,我们绘制星形对象。

在 PyCairo 教程的这一部分中,我们讨论了变换。