ZetCode

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);
}

通过右键点击,我们重绘窗口。

Lines
图:线条

填充与描边

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

#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);

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

Fill and stroke
图:填充与描边

笔触虚线

每条线都可以用不同的笔触虚线绘制。它定义了线的样式。虚线由 `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);  

这些线条创建了一条具有对称模式的笔触虚线,其中开启和关闭点交替出现。

Dashes
图:虚线

线帽

线帽是线条的端点。

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

Line caps
图:方形、圆形和直角线帽

带有 `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);

这是用于演示尺寸差异的三条垂直线之一。

Line caps
图:线帽

线条连接

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

Bevel, Round, Miter line joins
图:斜角、圆角、尖角线连接

`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 像素。

Line joins
图:线条连接

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