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

在 Cairo 图形教程的这一部分中,我们将绘制一些基本图元。 我们将绘制简单的线条,使用填充和描边操作,我们将讨论笔划线,线帽和线连接。

直线

线是非常基本的矢量对象。 要画一条线,我们使用两个函数调用。 通过cairo_move_to()调用指定起点。 线的终点通过cairo_line_to()调用指定。

  1. #include <cairo.h>
  2. #include <gtk/gtk.h>
  3. static void do_drawing(cairo_t *);
  4. struct {
  5. int count;
  6. double coordx[100];
  7. double coordy[100];
  8. } glob;
  9. static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
  10. gpointer user_data)
  11. {
  12. do_drawing(cr);
  13. return FALSE;
  14. }
  15. static void do_drawing(cairo_t *cr)
  16. {
  17. cairo_set_source_rgb(cr, 0, 0, 0);
  18. cairo_set_line_width(cr, 0.5);
  19. int i, j;
  20. for (i = 0; i <= glob.count - 1; i++ ) {
  21. for (j = 0; j <= glob.count - 1; j++ ) {
  22. cairo_move_to(cr, glob.coordx[i], glob.coordy[i]);
  23. cairo_line_to(cr, glob.coordx[j], glob.coordy[j]);
  24. }
  25. }
  26. glob.count = 0;
  27. cairo_stroke(cr);
  28. }
  29. static gboolean clicked(GtkWidget *widget, GdkEventButton *event,
  30. gpointer user_data)
  31. {
  32. if (event->button == 1) {
  33. glob.coordx[glob.count] = event->x;
  34. glob.coordy[glob.count++] = event->y;
  35. }
  36. if (event->button == 3) {
  37. gtk_widget_queue_draw(widget);
  38. }
  39. return TRUE;
  40. }
  41. int main(int argc, char *argv[])
  42. {
  43. GtkWidget *window;
  44. GtkWidget *darea;
  45. glob.count = 0;
  46. gtk_init(&argc, &argv);
  47. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  48. darea = gtk_drawing_area_new();
  49. gtk_container_add(GTK_CONTAINER(window), darea);
  50. gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
  51. g_signal_connect(G_OBJECT(darea), "draw",
  52. G_CALLBACK(on_draw_event), NULL);
  53. g_signal_connect(window, "destroy",
  54. G_CALLBACK(gtk_main_quit), NULL);
  55. g_signal_connect(window, "button-press-event",
  56. G_CALLBACK(clicked), NULL);
  57. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  58. gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
  59. gtk_window_set_title(GTK_WINDOW(window), "Lines");
  60. gtk_widget_show_all(window);
  61. gtk_main();
  62. return 0;
  63. }

在我们的示例中,我们用鼠标左键随机单击一个窗口。 每次点击都存储在一个数组中。 当我们右键单击窗口时,所有点都与数组中的每个点相连。 这样,我们可以创建一些有趣的对象。 右键单击绘制的对象将清除窗口,我们可以单击另一个对象。

  1. cairo_set_source_rgb(cr, 0, 0, 0);
  2. cairo_set_line_width (cr, 0.5);

线条将用黑色墨水绘制,宽度为 0.5 点。

  1. int i, j;
  2. for (i = 0; i <= glob.count - 1; i++ ) {
  3. for (j = 0; j <= glob.count - 1; j++ ) {
  4. cairo_move_to(cr, glob.coordx[i], glob.coordy[i]);
  5. cairo_line_to(cr, glob.coordx[j], glob.coordy[j]);
  6. }
  7. }

我们将数组中的每个点连接到其他每个点。

  1. cairo_stroke(cr);

cairo_stroke()调用画线。

  1. g_signal_connect(window, "button-press-event",
  2. G_CALLBACK(clicked), NULL);

我们将button-press-event连接到单击的回调。

  1. if (event->button == 1) {
  2. glob.coordx[glob.count] = event->x;
  3. glob.coordy[glob.count++] = event->y;
  4. }

在单击的回调中,我们确定是单击鼠标左键还是单击鼠标右键。 如果单击鼠标左键,则将 x,y 坐标存储到数组中。

  1. if (event->button == 3) {
  2. gtk_widget_queue_draw(widget);
  3. }

通过右键单击,我们重新绘制窗口。

Cairo 基本图形 - 图1

图:直线

填充和描边

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

  1. #include <cairo.h>
  2. #include <gtk/gtk.h>
  3. #include <math.h>
  4. static void do_drawing(cairo_t *, GtkWidget *);
  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. int width, height;
  15. gtk_window_get_size(GTK_WINDOW(win), &width, &height);
  16. cairo_set_line_width(cr, 9);
  17. cairo_set_source_rgb(cr, 0.69, 0.19, 0);
  18. cairo_translate(cr, width/2, height/2);
  19. cairo_arc(cr, 0, 0, 50, 0, 2 * M_PI);
  20. cairo_stroke_preserve(cr);
  21. cairo_set_source_rgb(cr, 0.3, 0.4, 0.6);
  22. cairo_fill(cr);
  23. }
  24. int main (int argc, char *argv[])
  25. {
  26. GtkWidget *window;
  27. GtkWidget *darea;
  28. gtk_init(&argc, &argv);
  29. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  30. darea = gtk_drawing_area_new();
  31. gtk_container_add(GTK_CONTAINER(window), darea);
  32. g_signal_connect(G_OBJECT(darea), "draw",
  33. G_CALLBACK(on_draw_event), NULL);
  34. g_signal_connect(G_OBJECT(window), "destroy",
  35. G_CALLBACK(gtk_main_quit), NULL);
  36. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  37. gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  38. gtk_window_set_title(GTK_WINDOW(window), "Fill & stroke");
  39. gtk_widget_show_all(window);
  40. gtk_main();
  41. return 0;
  42. }

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

  1. #include <math.h>

M_PI常量需要此头文件。

  1. GtkWidget *win = gtk_widget_get_toplevel(widget);
  2. int width, height;
  3. gtk_window_get_size(GTK_WINDOW(win), &width, &height);

在这里,我们获得了窗口的宽度和高度。 绘制圆时,将需要这些值。 当我们调整窗口大小时,圆圈将被调整大小。

  1. cairo_set_line_width(cr, 9);
  2. cairo_set_source_rgb(cr, 0.69, 0.19, 0);

我们使用set_line_width()方法设置线宽。 我们使用set_source_rgb()方法将光源设置为深红色。

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

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

  1. cairo_set_source_rgb(cr, 0.3, 0.4, 0.6);
  2. cairo_fill(cr);

在这里,我们用蓝色填充线圈。

Cairo 基本图形 - 图2

图:填充和描边

笔划线

每条线可以用不同的笔划线绘制。 它定义了线条的样式。 笔划线由cairo_stroke()函数调用使用。 笔划线由cairo_set_dash()函数指定。 该模式由虚线数组设置,该数组是一个正浮点值的数组。 他们设置笔划线图案的开和关部分。 我们还指定了数组的长度和偏移值。 如果长度为 0,则禁用虚线。 如果为 1,则假定对称图案,并交替显示由虚线表示的单个值指定的大小的打开和关闭部分。

  1. static void do_drawing(cairo_t *cr)
  2. {
  3. cairo_set_source_rgba(cr, 0, 0, 0, 1);
  4. static const double dashed1[] = {4.0, 21.0, 2.0};
  5. static int len1 = sizeof(dashed1) / sizeof(dashed1[0]);
  6. static const double dashed2[] = {14.0, 6.0};
  7. static int len2 = sizeof(dashed2) / sizeof(dashed2[0]);
  8. static const double dashed3[] = {1.0};
  9. cairo_set_line_width(cr, 1.5);
  10. cairo_set_dash(cr, dashed1, len1, 0);
  11. cairo_move_to(cr, 40, 30);
  12. cairo_line_to(cr, 200, 30);
  13. cairo_stroke(cr);
  14. cairo_set_dash(cr, dashed2, len2, 1);
  15. cairo_move_to(cr, 40, 50);
  16. cairo_line_to(cr, 200, 50);
  17. cairo_stroke(cr);
  18. cairo_set_dash(cr, dashed3, 1, 0);
  19. cairo_move_to(cr, 40, 70);
  20. cairo_line_to(cr, 200, 70);
  21. cairo_stroke(cr);
  22. }

在此示例中,我们将绘制三条具有不同笔划线图案的线。

  1. static const double dashed1[] = {4.0, 21.0, 2.0};

我们有三个数字的模式。 我们得出 4 分,未得出 21 分,得出 2 分。 然后,未绘制 4 点,未绘制 21 点和未绘制 2 点。 该模式轮流直到行尾。

  1. static int len1 = sizeof(dashed1) / sizeof(dashed1[0]);

我们得到数组的大小。

  1. cairo_set_dash(cr, dashed1, len1, 0);

我们设置笔划线。

  1. static const double dashed3[] = {1.0};
  2. ...
  3. cairo_set_dash(cr, dashed3, 1, 0);
  4. cairo_move_to(cr, 40, 70);
  5. cairo_line_to(cr, 200, 70);
  6. cairo_stroke(cr);

这些线创建了一条带有描边的对称划线交替交替的单个点和断开点的线。

Cairo 基本图形 - 图3

图:虚线

线帽

线帽是线的端点。

  • CAIRO_LINE_CAP_SQUARE
  • CAIRO_LINE_CAP_ROUND
  • CAIRO_LINE_CAP_BUTT

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

Cairo 基本图形 - 图4

图:正方形,圆和端帽

带有CAIRO_LINE_CAP_SQUARE上限的行的大小将不同于带有CAIRO_LINE_CAP_BUTT上限的行。 如果一条线的宽度为 px 宽,则带有CAIRO_LINE_CAP_SQUARE上限的线的宽度将恰好为 px 宽度。 开头为width / 2像素,结尾为width / 2像素。

  1. static void do_drawing(cairo_t *cr)
  2. {
  3. cairo_set_line_width(cr, 10);
  4. cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
  5. cairo_move_to(cr, 30, 50);
  6. cairo_line_to(cr, 150, 50);
  7. cairo_stroke(cr);
  8. cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
  9. cairo_move_to(cr, 30, 90);
  10. cairo_line_to(cr, 150, 90);
  11. cairo_stroke(cr);
  12. cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
  13. cairo_move_to(cr, 30, 130);
  14. cairo_line_to(cr, 150, 130);
  15. cairo_stroke(cr);
  16. cairo_set_line_width(cr, 1.5);
  17. cairo_move_to(cr, 30, 40);
  18. cairo_line_to(cr, 30, 140);
  19. cairo_stroke(cr);
  20. cairo_move_to(cr, 150, 40);
  21. cairo_line_to(cr, 150, 140);
  22. cairo_stroke(cr);
  23. cairo_move_to(cr, 155, 40);
  24. cairo_line_to(cr, 155, 140);
  25. cairo_stroke(cr);
  26. }

该示例绘制了具有三个不同上限的三条线。 它还将以图形方式显示行大小的差异。

  1. cairo_set_line_width(cr, 10);

我们的线将是 10 像素宽。

  1. cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
  2. cairo_move_to(cr, 30, 90);
  3. cairo_line_to(cr, 150, 90);
  4. cairo_stroke(cr);

在这里,我们用CAIRO_LINE_CAP_ROUND帽画一条水平线。

  1. cairo_set_line_width(cr, 1.5);
  2. cairo_move_to(cr, 30, 40);
  3. cairo_line_to(cr, 30, 140);
  4. cairo_stroke(cr);

这是用来说明大小差异的三条垂直线之一。

Cairo 基本图形 - 图5

图:线帽

线连接

可以使用三种不同的连接样式来连接线:

  • CAIRO_LINE_JOIN_BEVEL
  • CAIRO_LINE_JOIN_ROUND
  • CAIRO_LINE_JOIN_MITER

Cairo 基本图形 - 图6

图:斜角,圆角,斜接线连接

CAIRO_LINE_JOIN_BEVEL使用切除连接,其中切除距离接合点的线宽一半。 CAIRO_LINE_JOIN_ROUND使用圆形连接,其中圆心是连接点。 CAIRO_LINE_JOIN_MITER使用了一个尖角。

  1. static void do_drawing(cairo_t *cr)
  2. {
  3. cairo_set_source_rgb(cr, 0.1, 0, 0);
  4. cairo_rectangle(cr, 30, 30, 100, 100);
  5. cairo_set_line_width(cr, 14);
  6. cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER);
  7. cairo_stroke(cr);
  8. cairo_rectangle(cr, 160, 30, 100, 100);
  9. cairo_set_line_width(cr, 14);
  10. cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL);
  11. cairo_stroke(cr);
  12. cairo_rectangle(cr, 100, 160, 100, 100);
  13. cairo_set_line_width(cr, 14);
  14. cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
  15. cairo_stroke(cr);
  16. }

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

  1. cairo_rectangle(cr, 30, 30, 100, 100);
  2. cairo_set_line_width(cr, 14);
  3. cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER);
  4. cairo_stroke(cr);

在此代码示例中,我们绘制具有CAIRO_LINE_JOIN_MITER连接样式的矩形。 线宽为 14px。

Cairo 基本图形 - 图7

图:直线连接

在本章中,我们做了一些基本绘图。