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 像素。
在本章中,我们进行了一些基本绘图。