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

在 Cairo 教程的这一部分中,我们将讨论剪切和遮罩。

剪裁

剪裁将图形限制在某个区域。 这样做是出于效率方面的考虑,并会产生有趣的效果。

在下面的示例中,我们将裁剪图像。

  1. #include <cairo.h>
  2. #include <gtk/gtk.h>
  3. #include <math.h>
  4. static void do_drawing(cairo_t *, GtkWidget *);
  5. struct {
  6. cairo_surface_t *image;
  7. } glob;
  8. static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
  9. gpointer user_data)
  10. {
  11. do_drawing(cr, widget);
  12. return FALSE;
  13. }
  14. static void do_drawing(cairo_t *cr, GtkWidget *widget)
  15. {
  16. static gint pos_x = 128;
  17. static gint pos_y = 128;
  18. static gint radius = 40;
  19. static gint delta[] = { 3, 3 };
  20. GtkWidget *win = gtk_widget_get_toplevel(widget);
  21. gint width, height;
  22. gtk_window_get_size(GTK_WINDOW(win), &width, &height);
  23. if (pos_x < 0 + radius) {
  24. delta[0] = rand() % 4 + 5;
  25. } else if (pos_x > width - radius) {
  26. delta[0] = -(rand() % 4 + 5);
  27. }
  28. if (pos_y < 0 + radius) {
  29. delta[1] = rand() % 4 + 5;
  30. } else if (pos_y > height - radius) {
  31. delta[1] = -(rand() % 4 + 5);
  32. }
  33. pos_x += delta[0];
  34. pos_y += delta[1];
  35. cairo_set_source_surface(cr, glob.image, 1, 1);
  36. cairo_arc(cr, pos_x, pos_y, radius, 0, 2*M_PI);
  37. cairo_clip(cr);
  38. cairo_paint(cr);
  39. }
  40. static gboolean time_handler(GtkWidget *widget)
  41. {
  42. gtk_widget_queue_draw(widget);
  43. return TRUE;
  44. }
  45. int main(int argc, char *argv[])
  46. {
  47. GtkWidget *window;
  48. GtkWidget *darea;
  49. gint width, height;
  50. glob.image = cairo_image_surface_create_from_png("turnacastle.png");
  51. width = cairo_image_surface_get_width(glob.image);
  52. height = cairo_image_surface_get_height(glob.image);
  53. gtk_init(&argc, &argv);
  54. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  55. darea = gtk_drawing_area_new();
  56. gtk_container_add(GTK_CONTAINER (window), darea);
  57. g_signal_connect(G_OBJECT(darea), "draw",
  58. G_CALLBACK(on_draw_event), NULL);
  59. g_signal_connect(G_OBJECT(window), "destroy",
  60. G_CALLBACK(gtk_main_quit), NULL);
  61. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  62. gtk_window_set_default_size(GTK_WINDOW(window), width+2, height+2);
  63. gtk_window_set_title(GTK_WINDOW(window), "Clip image");
  64. gtk_widget_show_all(window);
  65. g_timeout_add(100, (GSourceFunc) time_handler, (gpointer) window);
  66. gtk_main();
  67. cairo_surface_destroy(glob.image);
  68. return 0;
  69. }

在此示例中,我们将裁剪图像。 屏幕上正在移动一个圆圈,并显示了一部分基础图像。 这就像我们从孔中看一样。

  1. if (pos_x < 0 + radius) {
  2. delta[0] = rand() % 4 + 5;
  3. } else if (pos_x > width - radius) {
  4. delta[0] = -(rand() % 4 + 5);
  5. }

如果圆碰到窗口的左侧或右侧,则圆的移动方向会随机变化。 顶部和底部也一样。

  1. cairo_set_source_surface(cr, glob.image, 1, 1);
  2. cairo_arc(cr, pos_x, pos_y, radius, 0, 2*M_PI);

在这里,我们绘制图像和一个圆。 请注意,我们目前不在窗口上绘制,而仅在内存中绘制。

  1. cairo_clip(cr);

cairo_clip()设置剪切区域。 裁剪区域是当前使用的路径。 当前路径是通过cairo_arc()函数调用创建的。

  1. cairo_paint(cr);

cairo_paint()在当前剪裁区域内的任何地方绘制当前源。

  1. glob.image = cairo_image_surface_create_from_png("turnacastle.png");

使用cairo_image_surface_create_from_png()函数从 PNG 图像创建图像表面。

剪裁和遮罩 - 图1

图:剪裁图像

遮罩

在将源应用于表面之前,先对其进行过滤。 遮罩用作过滤器。 遮罩确定在哪里应用源,在哪里不应用。 遮罩的不透明部分允许复制源。 透明零件不允许将源复制到表面。

  1. #include <cairo.h>
  2. #include <gtk/gtk.h>
  3. static void do_drawing(cairo_t *);
  4. struct {
  5. cairo_surface_t *surface;
  6. } glob;
  7. static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
  8. gpointer user_data)
  9. {
  10. do_drawing(cr);
  11. return FALSE;
  12. }
  13. static void do_drawing(cairo_t *cr)
  14. {
  15. cairo_set_source_rgb(cr, 0, 0, 0);
  16. cairo_mask_surface(cr, glob.surface, 0, 0);
  17. cairo_fill(cr);
  18. }
  19. static void create_surface()
  20. {
  21. glob.surface = cairo_image_surface_create_from_png("omen.png");
  22. }
  23. static void destroy_surface()
  24. {
  25. cairo_surface_destroy(glob.surface);
  26. }
  27. int main(int argc, char *argv[])
  28. {
  29. GtkWidget *window;
  30. GtkWidget *darea;
  31. gtk_init(&argc, &argv);
  32. create_surface();
  33. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  34. darea = gtk_drawing_area_new();
  35. gtk_container_add(GTK_CONTAINER(window), darea);
  36. g_signal_connect(G_OBJECT(darea), "draw",
  37. G_CALLBACK(on_draw_event), NULL);
  38. g_signal_connect(G_OBJECT(window), "destroy",
  39. G_CALLBACK(gtk_main_quit), NULL);
  40. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  41. gtk_window_set_default_size(GTK_WINDOW(window), 305, 100);
  42. gtk_window_set_title(GTK_WINDOW(window), "Mask");
  43. gtk_widget_show_all(window);
  44. gtk_main();
  45. destroy_surface();
  46. return 0;
  47. }

这个小例子清楚地说明了遮罩背后的基本思想。 遮罩可确定在何处绘画和不在何处绘画。

  1. static void do_drawing(cairo_t *cr)
  2. {
  3. cairo_set_source_rgb(cr, 0, 0, 0);
  4. cairo_mask_surface(cr, glob.surface, 0, 0);
  5. cairo_fill(cr);
  6. }

do_drawing()函数中,我们使用图像作为遮罩。 因此,它显示在窗口上。

剪裁和遮罩 - 图2

图:应用遮罩

蒙蔽效果

在此代码示例中,我们将忽略图像。 这类似于我们使用卷帘所做的。

  1. #include <cairo.h>
  2. #include <gtk/gtk.h>
  3. static void do_drawing(cairo_t *);
  4. struct {
  5. cairo_surface_t *image;
  6. cairo_surface_t *surface;
  7. gboolean timer;
  8. gint img_width;
  9. gint img_height;
  10. } glob;
  11. static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
  12. gpointer user_data)
  13. {
  14. do_drawing(cr);
  15. return FALSE;
  16. }
  17. static gboolean time_handler(GtkWidget *widget)
  18. {
  19. if (!glob.timer) return FALSE;
  20. gtk_widget_queue_draw(widget);
  21. return TRUE;
  22. }
  23. static void do_drawing(cairo_t *cr)
  24. {
  25. cairo_t *ic;
  26. static gint h = 0;
  27. ic = cairo_create(glob.surface);
  28. cairo_rectangle(ic, 0, 0, glob.img_width, h);
  29. cairo_fill(ic);
  30. h += 1;
  31. if ( h == glob.img_height) glob.timer = FALSE;
  32. cairo_set_source_surface(cr, glob.image, 10, 10);
  33. cairo_mask_surface(cr, glob.surface, 10, 10);
  34. cairo_destroy(ic);
  35. }
  36. static void init_vars()
  37. {
  38. glob.timer = TRUE;
  39. glob.image = cairo_image_surface_create_from_png("beckov.png");
  40. glob.img_width = cairo_image_surface_get_width(glob.image);
  41. glob.img_height = cairo_image_surface_get_height(glob.image);
  42. glob.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
  43. glob.img_width, glob.img_height);
  44. }
  45. static void cleanup()
  46. {
  47. cairo_surface_destroy(glob.image);
  48. cairo_surface_destroy(glob.surface);
  49. }
  50. int main(int argc, char *argv[])
  51. {
  52. GtkWidget *window;
  53. GtkWidget *darea;
  54. gtk_init(&argc, &argv);
  55. init_vars();
  56. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  57. darea = gtk_drawing_area_new();
  58. gtk_container_add(GTK_CONTAINER(window), darea);
  59. g_signal_connect(G_OBJECT(darea), "draw",
  60. G_CALLBACK(on_draw_event), NULL);
  61. g_signal_connect(G_OBJECT(window), "destroy",
  62. G_CALLBACK(gtk_main_quit), NULL);
  63. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  64. gtk_window_set_default_size(GTK_WINDOW(window), 325, 250);
  65. gtk_window_set_title(GTK_WINDOW(window), "Blind down");
  66. g_timeout_add(15, (GSourceFunc) time_handler, (gpointer) window);
  67. gtk_widget_show_all(window);
  68. gtk_main();
  69. cleanup();
  70. return 0;
  71. }

盲目效应背后的想法很简单。 图像高度为h像素。 我们绘制高度为 1px 的 0、1、2 … 线。 每个周期,图像的一部分高 1px,直到整个图像可见为止。

  1. struct {
  2. cairo_surface_t *image;
  3. cairo_surface_t *surface;
  4. gboolean timer;
  5. gint img_width;
  6. gint img_height;
  7. } glob;

在全局结构中,我们将存储两个表面,一个计时器以及图像的宽度和高度变量。

  1. static void init_vars()
  2. {
  3. glob.timer = TRUE;
  4. glob.image = cairo_image_surface_create_from_png("beckov.png");
  5. glob.img_width = cairo_image_surface_get_width(glob.image);
  6. glob.img_height = cairo_image_surface_get_height(glob.image);
  7. glob.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
  8. glob.img_width, glob.img_height);
  9. }

init_vars()函数中,我们初始化先前声明的变量。 最后一行创建一个空的图像表面。 它将用我们之前创建的图像表面的像素线填充。

  1. ic = cairo_create(glob.surface);

我们从空图像源创建一个 cairo 上下文。

  1. cairo_rectangle(ic, 0, 0, glob.img_width, h);
  2. cairo_fill(ic);

我们在最初为空的图像中绘制一个矩形。 矩形每个周期将高出 1 像素。 以这种方式创建的图像稍后将用作遮罩。

  1. h += 1;

要显示的图像高度增加一个单位。

  1. if ( h == glob.img_height) glob.timer = FALSE;

当我们在 GTK 窗口上绘制整个图像时,我们将停止计时器函数。

  1. cairo_set_source_surface(cr, glob.image, 10, 10);
  2. cairo_mask_surface(cr, glob.surface, 10, 10);

城堡的图像被设置为绘画的来源。 cairo_mask_surface()使用表面的 Alpha 通道作为遮罩来绘制电流源。

  1. static void cleanup()
  2. {
  3. cairo_surface_destroy(glob.image);
  4. cairo_surface_destroy(glob.surface);
  5. }

cleanup()函数中,我们销毁了创建的曲面。

本章涉及在 Cairo 的剪切和遮罩。