Cairo 中的基本绘图
最后修改于 2023 年 7 月 17 日
在 Cairo 图形教程的这一部分,我们绘制了一些基本图元。我们绘制简单的线条,使用填充和描边操作,讨论虚线、线帽和线连接。
线条
线条是非常基础的矢量对象。要绘制一条线,我们使用两个函数调用。起始点由 `cairo_move_to` 调用指定。线的终点由 `cairo_line_to` 调用指定。
#include <cairo.h> #include <gtk/gtk.h> static void do_drawing(cairo_t *); struct { int count; double coordx[100]; double coordy[100]; } glob; static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { do_drawing(cr); return FALSE; } static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width(cr, 0.5); int i, j; for (i = 0; i <= glob.count - 1; i++ ) { for (j = 0; j <= glob.count - 1; j++ ) { cairo_move_to(cr, glob.coordx[i], glob.coordy[i]); cairo_line_to(cr, glob.coordx[j], glob.coordy[j]); } } glob.count = 0; cairo_stroke(cr); } static gboolean clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data) { if (event->button == 1) { glob.coordx[glob.count] = event->x; glob.coordy[glob.count++] = event->y; } if (event->button == 3) { gtk_widget_queue_draw(widget); } return TRUE; } int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; glob.count = 0; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER(window), darea); gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); g_signal_connect(window, "button-press-event", G_CALLBACK(clicked), NULL); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_window_set_default_size(GTK_WINDOW(window), 400, 300); gtk_window_set_title(GTK_WINDOW(window), "Lines"); gtk_widget_show_all(window); gtk_main(); return 0; }
在我们的示例中,我们用鼠标左键随机点击一个窗口。每个点击都存储在一个数组中。当我们右键点击窗口时,所有点都与数组中的每个点连接。这样,我们可以创建一些有趣的图形。右键点击绘制的图形会清除窗口,我们可以点击绘制另一个图形。
cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width (cr, 0.5);
线条将以黑色墨水绘制,宽度为 0.5 磅。
int i, j; for (i = 0; i <= glob.count - 1; i++ ) { for (j = 0; j <= glob.count - 1; j++ ) { cairo_move_to(cr, glob.coordx[i], glob.coordy[i]); cairo_line_to(cr, glob.coordx[j], glob.coordy[j]); } }
我们将数组中的每个点与其他所有点连接。
cairo_stroke(cr);
`cairo_stroke` 调用绘制线条。
g_signal_connect(window, "button-press-event", G_CALLBACK(clicked), NULL);
我们将 `button-press-event` 连接到 clicked 回调函数。
if (event->button == 1) { glob.coordx[glob.count] = event->x; glob.coordy[glob.count++] = event->y; }
在 clicked 回调函数中,我们判断是左键点击还是右键点击。如果使用鼠标左键点击,我们将 x, y 坐标存储到数组中。
if (event->button == 3) { gtk_widget_queue_draw(widget); }
通过右键点击,我们重绘窗口。

填充与描边
描边操作绘制图形的轮廓,填充操作填充图形的内部。
#include <cairo.h> #include <gtk/gtk.h> #include <math.h> static void do_drawing(cairo_t *, GtkWidget *); static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { do_drawing(cr, widget); return FALSE; } static void do_drawing(cairo_t *cr, GtkWidget *widget) { GtkWidget *win = gtk_widget_get_toplevel(widget); int width, height; gtk_window_get_size(GTK_WINDOW(win), &width, &height); cairo_set_line_width(cr, 9); cairo_set_source_rgb(cr, 0.69, 0.19, 0); cairo_translate(cr, width/2, height/2); cairo_arc(cr, 0, 0, 50, 0, 2 * M_PI); cairo_stroke_preserve(cr); cairo_set_source_rgb(cr, 0.3, 0.4, 0.6); cairo_fill(cr); } int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER(window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_window_set_default_size(GTK_WINDOW(window), 300, 200); gtk_window_set_title(GTK_WINDOW(window), "Fill & stroke"); gtk_widget_show_all(window); gtk_main(); return 0; }
在我们的示例中,我们绘制一个圆并用实色填充它。
#include <math.h>
这个头文件对于 `M_PI` 常量是必需的。
GtkWidget *win = gtk_widget_get_toplevel(widget); int width, height; gtk_window_get_size(GTK_WINDOW(win), &width, &height);
在这里,我们获取窗口的宽度和高度。在绘制圆时需要这些值。当窗口大小改变时,圆也会随之调整大小。
cairo_set_line_width(cr, 9); cairo_set_source_rgb(cr, 0.69, 0.19, 0);
我们使用 `set_line_width` 方法设置线宽。我们使用 `set_source_rgb` 方法将源设置为深红色。
cairo_translate(cr, width/2, height/2); cairo_arc(cr, 0, 0, 50, 0, 2 * M_PI); cairo_stroke_preserve(cr);
使用 `cairo_translate` 方法,我们将绘图原点移动到窗口中心。我们希望我们的圆居中。 `arc` 方法向 Cairo 绘图上下文添加一个新的圆形路径。最后,`stroke_preserve` 方法绘制圆的轮廓。与 `stroke` 方法不同,它还保留了形状以便后续绘制。
cairo_set_source_rgb(cr, 0.3, 0.4, 0.6); cairo_fill(cr);
在这里,我们用蓝色填充圆。

笔触虚线
每条线都可以用不同的笔触虚线绘制。它定义了线的样式。虚线由 `cairo_stroke` 函数调用使用。虚线模式由 `cairo_set_dash` 函数指定。模式由虚线数组设置,它是一个正浮点数值的数组。它们设置了虚线模式的开启和关闭部分。我们还指定了数组的长度和偏移值。如果长度为 0,则禁用虚线。如果长度为 1,则假定一个对称模式,其中开启和关闭部分的大小由虚线中的单个值指定。
static void do_drawing(cairo_t *cr) { cairo_set_source_rgba(cr, 0, 0, 0, 1); static const double dashed1[] = {4.0, 21.0, 2.0}; static int len1 = sizeof(dashed1) / sizeof(dashed1[0]); static const double dashed2[] = {14.0, 6.0}; static int len2 = sizeof(dashed2) / sizeof(dashed2[0]); static const double dashed3[] = {1.0}; cairo_set_line_width(cr, 1.5); cairo_set_dash(cr, dashed1, len1, 0); cairo_move_to(cr, 40, 30); cairo_line_to(cr, 200, 30); cairo_stroke(cr); cairo_set_dash(cr, dashed2, len2, 1); cairo_move_to(cr, 40, 50); cairo_line_to(cr, 200, 50); cairo_stroke(cr); cairo_set_dash(cr, dashed3, 1, 0); cairo_move_to(cr, 40, 70); cairo_line_to(cr, 200, 70); cairo_stroke(cr); }
在这个示例中,我们绘制了三条具有不同虚线模式的线。
static const double dashed1[] = {4.0, 21.0, 2.0};
我们有一个包含三个数字的模式。我们绘制了 4 个点,21 个点未绘制,2 个点绘制。然后 4 个点未绘制,21 个点绘制,2 个点未绘制。这个模式会持续到线的末尾。
static int len1 = sizeof(dashed1) / sizeof(dashed1[0]);
我们获取数组的大小。
cairo_set_dash(cr, dashed1, len1, 0);
我们设置虚线。
static const double dashed3[] = {1.0}; ... cairo_set_dash(cr, dashed3, 1, 0); cairo_move_to(cr, 40, 70); cairo_line_to(cr, 200, 70); cairo_stroke(cr);
这些线条创建了一条具有对称模式的笔触虚线,其中开启和关闭点交替出现。

线帽
线帽是线条的端点。
- CAIRO_LINE_CAP_SQUARE
- CAIRO_LINE_CAP_ROUND
- CAIRO_LINE_CAP_BUTT
Cairo 中有三种不同的线帽样式。

带有 `CAIRO_LINE_CAP_SQUARE` 线帽的线,其大小会与带有 `CAIRO_LINE_CAP_BUTT` 线帽的线不同。如果一条线的宽度为 px,那么带有 `CAIRO_LINE_CAP_SQUARE` 线帽的线的大小将比它大 px。开头有 width/2 px,结尾有 width/2 px。
static void do_drawing(cairo_t *cr) { cairo_set_line_width(cr, 10); cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT); cairo_move_to(cr, 30, 50); cairo_line_to(cr, 150, 50); cairo_stroke(cr); cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); cairo_move_to(cr, 30, 90); cairo_line_to(cr, 150, 90); cairo_stroke(cr); cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE); cairo_move_to(cr, 30, 130); cairo_line_to(cr, 150, 130); cairo_stroke(cr); cairo_set_line_width(cr, 1.5); cairo_move_to(cr, 30, 40); cairo_line_to(cr, 30, 140); cairo_stroke(cr); cairo_move_to(cr, 150, 40); cairo_line_to(cr, 150, 140); cairo_stroke(cr); cairo_move_to(cr, 155, 40); cairo_line_to(cr, 155, 140); cairo_stroke(cr); }
该示例绘制了三条具有三种不同线帽的线。它还将直观地展示线条大小的差异。
cairo_set_line_width(cr, 10);
我们的线条将是 10 像素宽。
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); cairo_move_to(cr, 30, 90); cairo_line_to(cr, 150, 90); cairo_stroke(cr);
这里我们绘制了一条带有 `CAIRO_LINE_CAP_ROUND` 线帽的水平线。
cairo_set_line_width(cr, 1.5); cairo_move_to(cr, 30, 40); cairo_line_to(cr, 30, 140); cairo_stroke(cr);
这是用于演示尺寸差异的三条垂直线之一。

线条连接
线条可以使用三种不同的连接样式进行连接
- CAIRO_LINE_JOIN_BEVEL
- CAIRO_LINE_JOIN_ROUND
- CAIRO_LINE_JOIN_MITER

`CAIRO_LINE_JOIN_BEVEL` 使用一个截断的连接,连接在距离连接点线宽一半处被截断。`CAIRO_LINE_JOIN_ROUND` 使用一个圆角连接,圆心是连接点。`CAIRO_LINE_JOIN_MITER` 使用一个尖锐的角。
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0.1, 0, 0); cairo_rectangle(cr, 30, 30, 100, 100); cairo_set_line_width(cr, 14); cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER); cairo_stroke(cr); cairo_rectangle(cr, 160, 30, 100, 100); cairo_set_line_width(cr, 14); cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL); cairo_stroke(cr); cairo_rectangle(cr, 100, 160, 100, 100); cairo_set_line_width(cr, 14); cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); cairo_stroke(cr); }
在这个示例中,我们绘制了三个具有各种线连接样式的粗矩形。
cairo_rectangle(cr, 30, 30, 100, 100); cairo_set_line_width(cr, 14); cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER); cairo_stroke(cr);
在此代码示例中,我们绘制了一个使用 `CAIRO_LINE_JOIN_MITER` 连接样式的矩形。线条宽度为 14 像素。

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