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

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

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

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

平移

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

  1. static void do_drawing(cairo_t *cr)
  2. {
  3. cairo_set_source_rgb(cr, 0.2, 0.3, 0.8);
  4. cairo_rectangle(cr, 10, 10, 30, 30);
  5. cairo_fill(cr);
  6. cairo_translate(cr, 20, 20);
  7. cairo_set_source_rgb(cr, 0.8, 0.3, 0.2);
  8. cairo_rectangle(cr, 0, 0, 30, 30);
  9. cairo_fill(cr);
  10. cairo_translate(cr, 30, 30);
  11. cairo_set_source_rgb(cr, 0.8, 0.8, 0.2);
  12. cairo_rectangle(cr, 0, 0, 30, 30);
  13. cairo_fill(cr);
  14. cairo_translate(cr, 40, 40);
  15. cairo_set_source_rgb(cr, 0.3, 0.8, 0.8);
  16. cairo_rectangle(cr, 0, 0, 30, 30);
  17. cairo_fill(cr);
  18. }

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

  1. cairo_translate(cr, 20, 20);

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

变换 - 图1

图:平移

剪切

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

  1. static void do_drawing(cairo_t *cr)
  2. {
  3. cairo_matrix_t matrix;
  4. cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
  5. cairo_rectangle(cr, 20, 30, 80, 50);
  6. cairo_fill(cr);
  7. cairo_matrix_init(&matrix,
  8. 1.0, 0.5,
  9. 0.0, 1.0,
  10. 0.0, 0.0);
  11. cairo_transform(cr, &matrix);
  12. cairo_rectangle(cr, 130, 30, 80, 50);
  13. cairo_fill(cr);
  14. }

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

  1. cairo_matrix_t matrix;

cairo_matrix_t是具有仿射变换的结构。

  1. cairo_matrix_init(&matrix,
  2. 1.0, 0.5,
  3. 0.0, 1.0,
  4. 0.0, 0.0);

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

  1. cairo_transform(cr, &matrix);

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

变换 - 图2

图:抖动

缩放

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

  1. static void do_drawing(cairo_t *cr)
  2. {
  3. cairo_set_source_rgb(cr, 0.2, 0.3, 0.8);
  4. cairo_rectangle(cr, 10, 10, 90, 90);
  5. cairo_fill(cr);
  6. cairo_scale(cr, 0.6, 0.6);
  7. cairo_set_source_rgb(cr, 0.8, 0.3, 0.2);
  8. cairo_rectangle(cr, 30, 30, 90, 90);
  9. cairo_fill(cr);
  10. cairo_scale(cr, 0.8, 0.8);
  11. cairo_set_source_rgb(cr, 0.8, 0.8, 0.2);
  12. cairo_rectangle(cr, 50, 50, 90, 90);
  13. cairo_fill(cr);
  14. }

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

  1. cairo_scale(cr, 0.6, 0.6);
  2. cairo_set_source_rgb(cr, 0.8, 0.3, 0.2);
  3. cairo_rectangle(cr, 30, 30, 90, 90);
  4. cairo_fill(cr);

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

  1. cairo_scale(cr, 0.8, 0.8);
  2. cairo_set_source_rgb(cr, 0.8, 0.8, 0.2);
  3. cairo_rectangle(cr, 50, 50, 90, 90);
  4. cairo_fill(cr);

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

变换 - 图3

图:缩放

隔离变换

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

  1. static void do_drawing(cairo_t *cr)
  2. {
  3. cairo_set_source_rgb(cr, 0.2, 0.3, 0.8);
  4. cairo_rectangle(cr, 10, 10, 90, 90);
  5. cairo_fill(cr);
  6. cairo_save(cr);
  7. cairo_scale(cr, 0.6, 0.6);
  8. cairo_set_source_rgb(cr, 0.8, 0.3, 0.2);
  9. cairo_rectangle(cr, 30, 30, 90, 90);
  10. cairo_fill(cr);
  11. cairo_restore(cr);
  12. cairo_save(cr);
  13. cairo_scale(cr, 0.8, 0.8);
  14. cairo_set_source_rgb(cr, 0.8, 0.8, 0.2);
  15. cairo_rectangle(cr, 50, 50, 90, 90);
  16. cairo_fill(cr);
  17. cairo_restore(cr);
  18. }

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

  1. cairo_save(cr);
  2. cairo_scale(cr, 0.6, 0.6);
  3. cairo_set_source_rgb(cr, 0.8, 0.3, 0.2);
  4. cairo_rectangle(cr, 30, 30, 90, 90);
  5. cairo_fill(cr);
  6. cairo_restore(cr);

我们通过将cairo_save()cairo_restore()函数之间的cairo_scale()函数隔离缩放操作。

变换 - 图4

图:隔离转换

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

甜甜圈

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

  1. #include <cairo.h>
  2. #include <gtk/gtk.h>
  3. #include <math.h>
  4. static void do_drawing(cairo_t *, GtkWidget *widget);
  5. static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
  6. gpointer user_data)
  7. {
  8. do_drawing(cr, widget);
  9. return FALSE;
  10. }
  11. static void do_drawing(cairo_t *cr, GtkWidget *widget)
  12. {
  13. GtkWidget *win = gtk_widget_get_toplevel(widget);
  14. gint width, height;
  15. gtk_window_get_size(GTK_WINDOW(win), &width, &height);
  16. cairo_set_line_width(cr, 0.5);
  17. cairo_translate(cr, width/2, height/2);
  18. cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
  19. cairo_stroke(cr);
  20. gint i;
  21. for (i = 0; i < 36; i++) {
  22. cairo_save(cr);
  23. cairo_rotate(cr, i*M_PI/36);
  24. cairo_scale(cr, 0.3, 1);
  25. cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
  26. cairo_restore(cr);
  27. cairo_stroke(cr);
  28. }
  29. }
  30. int main(int argc, char *argv[])
  31. {
  32. GtkWidget *window;
  33. GtkWidget *darea;
  34. gtk_init(&argc, &argv);
  35. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  36. darea = gtk_drawing_area_new();
  37. gtk_container_add(GTK_CONTAINER (window), darea);
  38. g_signal_connect(G_OBJECT(darea), "draw",
  39. G_CALLBACK(on_draw_event), NULL);
  40. g_signal_connect(G_OBJECT(window), "destroy",
  41. G_CALLBACK(gtk_main_quit), NULL);
  42. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  43. gtk_window_set_default_size(GTK_WINDOW(window), 350, 250);
  44. gtk_window_set_title(GTK_WINDOW(window), "Donut");
  45. gtk_widget_show_all(window);
  46. gtk_main();
  47. return 0;
  48. }

我们将进行旋转和缩放操作。 我们还将保存和恢复 Cairo 上下文。

  1. cairo_translate(cr, width/2, height/2);
  2. cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
  3. cairo_stroke(cr);

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

  1. gint i;
  2. for (i = 0; i < 36; i++) {
  3. cairo_save(cr);
  4. cairo_rotate(cr, i*M_PI/36);
  5. cairo_scale(cr, 0.3, 1);
  6. cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
  7. cairo_restore(cr);
  8. cairo_stroke(cr);
  9. }

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

星星

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

  1. #include <cairo.h>
  2. #include <gtk/gtk.h>
  3. static void do_drawing(cairo_t *, GtkWidget *widget);
  4. int points[11][2] = {
  5. { 0, 85 },
  6. { 75, 75 },
  7. { 100, 10 },
  8. { 125, 75 },
  9. { 200, 85 },
  10. { 150, 125 },
  11. { 160, 190 },
  12. { 100, 150 },
  13. { 40, 190 },
  14. { 50, 125 },
  15. { 0, 85 }
  16. };
  17. static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
  18. gpointer user_data)
  19. {
  20. do_drawing(cr, widget);
  21. return FALSE;
  22. }
  23. static void do_drawing(cairo_t *cr, GtkWidget *widget)
  24. {
  25. static gdouble angle = 0;
  26. static gdouble scale = 1;
  27. static gdouble delta = 0.01;
  28. GtkWidget *win = gtk_widget_get_toplevel(widget);
  29. gint width, height;
  30. gtk_window_get_size(GTK_WINDOW(win), &width, &height);
  31. cairo_set_source_rgb(cr, 0, 0.44, 0.7);
  32. cairo_set_line_width(cr, 1);
  33. cairo_translate(cr, width/2, height/2 );
  34. cairo_rotate(cr, angle);
  35. cairo_scale(cr, scale, scale);
  36. gint i;
  37. for ( i = 0; i < 10; i++ ) {
  38. cairo_line_to(cr, points[i][0], points[i][1]);
  39. }
  40. cairo_close_path(cr);
  41. cairo_fill(cr);
  42. cairo_stroke(cr);
  43. if ( scale < 0.01 ) {
  44. delta = -delta;
  45. } else if (scale > 0.99) {
  46. delta = -delta;
  47. }
  48. scale += delta;
  49. angle += 0.01;
  50. }
  51. static gboolean time_handler(GtkWidget *widget)
  52. {
  53. gtk_widget_queue_draw(widget);
  54. return TRUE;
  55. }
  56. int main(int argc, char *argv[])
  57. {
  58. GtkWidget *window;
  59. GtkWidget *darea;
  60. gtk_init(&argc, &argv);
  61. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  62. darea = gtk_drawing_area_new();
  63. gtk_container_add(GTK_CONTAINER (window), darea);
  64. g_signal_connect(G_OBJECT(darea), "draw",
  65. G_CALLBACK(on_draw_event), NULL);
  66. g_signal_connect(window, "destroy",
  67. G_CALLBACK(gtk_main_quit), NULL);
  68. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  69. gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
  70. gtk_window_set_title(GTK_WINDOW(window), "Star");
  71. g_timeout_add(10, (GSourceFunc) time_handler, (gpointer) window);
  72. gtk_widget_show_all(window);
  73. gtk_main();
  74. return 0;
  75. }

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

  1. int points[11][2] = {
  2. { 0, 85 },
  3. { 75, 75 },
  4. { 100, 10 },
  5. ...

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

  1. static gdouble angle = 0;
  2. static gdouble scale = 1;
  3. static gdouble delta = 0.01;

我们初始化三个重要变量。 角度用于旋转,比例用于缩放星形对象。 delta变量控制星星何时生长以及何时收缩。

  1. cairo_translate(cr, width/2, height/2);
  2. cairo_rotate(cr, angle);
  3. cairo_scale(cr, scale, scale);

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

  1. gint i;
  2. for ( i = 0; i < 10; i++ ) {
  3. cairo_line_to(cr, points[i][0], points[i][1]);
  4. }
  5. cairo_close_path(cr);
  6. cairo_fill(cr);
  7. cairo_stroke(cr);

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

  1. if ( scale < 0.01 ) {
  2. delta = -delta;
  3. } else if (scale > 0.99) {
  4. delta = -delta;
  5. }

这些线控制星形对象的生长或收缩。

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