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

在 Cairo C API 教程的这一部分中,我们将讨论透明度。 我们将提供一些基本定义和两个有趣的透明效果。

透明性是指能够透视材料的质量。 了解透明度的最简单方法是想象一块玻璃或水。 从技术上讲,光线可以穿过玻璃,这样我们就可以看到玻璃后面的物体。

在计算机图形学中,我们可以使用 alpha 合成实现透明效果。 Alpha 合成是将图像与背景组合以创建部分透明外观的过程。 合成过程使用 alpha 通道。 Alpha 通道是图形文件格式的 8 位层,用于表达半透明性(透明度)。 每个像素的额外八位用作掩码,表示 256 级半透明。
(answers.com,wikipedia.org)

透明矩形

第一个示例将绘制十个透明度不同的矩形。

  1. static void do_drawing(cairo_t *cr)
  2. {
  3. gint i;
  4. for ( i = 1; i <= 10; i++) {
  5. cairo_set_source_rgba(cr, 0, 0, 1, i*0.1);
  6. cairo_rectangle(cr, 50*i, 20, 40, 40);
  7. cairo_fill(cr);
  8. }
  9. }

cairo_set_source_rgba()具有可选的 alpha 参数以提供透明度。 此代码创建十个矩形,其 alpha 值从 0.1 到 1。

透明度 - 图1

图:透明度

泡芙效果

在以下示例中,我们创建一个粉扑效果。 该示例将显示一个不断增长的居中文本,该文本将从某个点逐渐淡出。 这是一个非常常见的效果,我们经常可以在 Flash 动画中看到它。 cairo_paint_with_alpha()方法对于产生效果至关重要。

  1. #include <cairo.h>
  2. #include <gtk/gtk.h>
  3. void do_drawing(cairo_t *, GtkWidget *);
  4. struct {
  5. gboolean timer;
  6. gdouble alpha;
  7. gdouble size;
  8. } glob;
  9. static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
  10. gpointer user_data)
  11. {
  12. do_drawing(cr, widget);
  13. return FALSE;
  14. }
  15. void do_drawing(cairo_t *cr, GtkWidget *widget)
  16. {
  17. cairo_text_extents_t extents;
  18. GtkWidget *win = gtk_widget_get_toplevel(widget);
  19. gint width, height;
  20. gtk_window_get_size(GTK_WINDOW(win), &width, &height);
  21. gint x = width/2;
  22. gint y = height/2;
  23. cairo_set_source_rgb(cr, 0.5, 0, 0);
  24. cairo_paint(cr);
  25. cairo_select_font_face(cr, "Courier",
  26. CAIRO_FONT_SLANT_NORMAL,
  27. CAIRO_FONT_WEIGHT_BOLD);
  28. glob.size += 0.8;
  29. if (glob.size > 20) {
  30. glob.alpha -= 0.01;
  31. }
  32. cairo_set_font_size(cr, glob.size);
  33. cairo_set_source_rgb(cr, 1, 1, 1);
  34. cairo_text_extents(cr, "ZetCode", &extents);
  35. cairo_move_to(cr, x - extents.width/2, y);
  36. cairo_text_path(cr, "ZetCode");
  37. cairo_clip(cr);
  38. cairo_paint_with_alpha(cr, glob.alpha);
  39. if (glob.alpha <= 0) {
  40. glob.timer = FALSE;
  41. }
  42. }
  43. static gboolean time_handler(GtkWidget *widget)
  44. {
  45. if (!glob.timer) return FALSE;
  46. gtk_widget_queue_draw(widget);
  47. return TRUE;
  48. }
  49. int main(int argc, char *argv[])
  50. {
  51. GtkWidget *window;
  52. GtkWidget *darea;
  53. glob.timer = TRUE;
  54. glob.alpha = 1.0;
  55. glob.size = 1.0;
  56. gtk_init(&argc, &argv);
  57. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  58. darea = gtk_drawing_area_new();
  59. gtk_container_add(GTK_CONTAINER (window), darea);
  60. g_signal_connect(G_OBJECT(darea), "draw",
  61. G_CALLBACK(on_draw_event), NULL);
  62. g_signal_connect(window, "destroy",
  63. G_CALLBACK(gtk_main_quit), NULL);
  64. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  65. gtk_window_set_default_size(GTK_WINDOW(window), 350, 200);
  66. gtk_window_set_title(GTK_WINDOW(window), "Puff");
  67. g_timeout_add(14, (GSourceFunc) time_handler, (gpointer) window);
  68. gtk_widget_show_all(window);
  69. gtk_main();
  70. return 0;
  71. }

该示例在窗口上创建一个逐渐增长和褪色的文本。

  1. struct {
  2. gboolean timer;
  3. gdouble alpha;
  4. gdouble size;
  5. } glob;

在这里,我们在结构内部定义了一些变量。 这用于避免使用全局变量。

  1. draw_text(cr, widget);

文本的实际绘制委托给draw_text()函数。

  1. GtkWidget *win = gtk_widget_get_toplevel(widget);
  2. gint width, height;
  3. gtk_window_get_size(GTK_WINDOW(win), &width, &height);
  4. gint x = width/2;
  5. gint y = height/2;

文本将在窗口上居中。 因此,我们需要找出父窗口小部件的大小。

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

窗口的背景充满了一些深红色。

  1. cairo_select_font_face(cr, "Courier",
  2. CAIRO_FONT_SLANT_NORMAL,
  3. CAIRO_FONT_WEIGHT_BOLD);

文本将以 Courier 粗体显示。

  1. glob.size += 0.8;
  2. if (glob.size > 20) {
  3. glob.alpha -= 0.01;
  4. }

文本大小增加了 0.8 个单位。 达到 20 个单位后,alpha 值开始减小。 文本逐渐消失。

  1. cairo_text_extents(cr, "ZetCode", &extents);
  2. cairo_move_to(cr, x - extents.width/2, y);

我们得到了文本指标。 我们将仅使用文本宽度。 我们移动到文本将在窗口上居中的位置。

  1. cairo_text_path(cr, "ZetCode");
  2. cairo_clip(cr);
  3. cairo_paint_with_alpha(cr, glob.alpha);

我们使用cairo_text_path()方法获得文本的路径。 我们使用cairo_clip()方法将绘画限制为当前路径。 cairo_paint_with_alpha()方法使用 alpha 值的掩码在当前剪裁区域内的任何地方绘制当前源。

  1. glob.timer = TRUE;
  2. glob.alpha = 1.0;
  3. glob.size = 1.0;

我们初始化三个变量。

  1. static gboolean time_handler(GtkWidget *widget)
  2. {
  3. if (!glob.timer) return FALSE;
  4. gtk_widget_queue_draw(widget);
  5. return TRUE;
  6. }

time_handler调用的主要功能是定期重绘窗口。 当函数返回FALSE时,超时功能将停止工作。

  1. g_timeout_add(14, (GSourceFunc) time_handler, (gpointer) window);

我们创建一个计时器函数。 该函数每 14 毫秒调用一次time_handler

透明度 - 图2

图:粉扑效果

等待演示

在此示例中,我们使用透明效果创建一个等待演示。 我们将绘制 8 条线,这些线将逐渐消失,从而产生一种错觉,即一条线在移动。 此类效果通常用于通知用户幕后正在进行繁重的任务。 一个示例是通过互联网流式传输视频。

  1. #include <cairo.h>
  2. #include <gtk/gtk.h>
  3. #include <math.h>
  4. static void do_drawing(cairo_t *, GtkWidget *);
  5. struct {
  6. gushort count;
  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 gdouble const trs[8][8] = {
  17. { 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 },
  18. { 1.0, 0.0, 0.15, 0.30, 0.5, 0.65, 0.8, 0.9 },
  19. { 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65, 0.8 },
  20. { 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65},
  21. { 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5 },
  22. { 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3 },
  23. { 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15 },
  24. { 0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, }
  25. };
  26. GtkWidget *win = gtk_widget_get_toplevel(widget);
  27. gint width, height;
  28. gtk_window_get_size(GTK_WINDOW(win), &width, &height);
  29. cairo_translate(cr, width/2, height/2);
  30. gint i = 0;
  31. for (i = 0; i < 8; i++) {
  32. cairo_set_line_width(cr, 3);
  33. cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
  34. cairo_set_source_rgba(cr, 0, 0, 0, trs[glob.count%8][i]);
  35. cairo_move_to(cr, 0.0, -10.0);
  36. cairo_line_to(cr, 0.0, -40.0);
  37. cairo_rotate(cr, M_PI/4);
  38. cairo_stroke(cr);
  39. }
  40. }
  41. static gboolean time_handler(GtkWidget *widget)
  42. {
  43. glob.count += 1;
  44. gtk_widget_queue_draw(widget);
  45. return TRUE;
  46. }
  47. int main(int argc, char *argv[])
  48. {
  49. GtkWidget *window;
  50. GtkWidget *darea;
  51. glob.count = 0;
  52. gtk_init(&argc, &argv);
  53. window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  54. darea = gtk_drawing_area_new();
  55. gtk_container_add(GTK_CONTAINER (window), darea);
  56. g_signal_connect(G_OBJECT(darea), "draw",
  57. G_CALLBACK(on_draw_event), NULL);
  58. g_signal_connect(G_OBJECT(window), "destroy",
  59. G_CALLBACK(gtk_main_quit), NULL);
  60. gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  61. gtk_window_set_default_size(GTK_WINDOW(window), 250, 150);
  62. gtk_window_set_title(GTK_WINDOW(window), "Waiting demo");
  63. g_timeout_add(100, (GSourceFunc) time_handler, (gpointer) window);
  64. gtk_widget_show_all(window);
  65. gtk_main();
  66. return 0;
  67. }

我们用八个不同的 alpha 值绘制八条线。

  1. static gdouble const trs[8][8] = {
  2. { 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 },
  3. { 1.0, 0.0, 0.15, 0.30, 0.5, 0.65, 0.8, 0.9 },
  4. { 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65, 0.8 },
  5. { 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65},
  6. { 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5 },
  7. { 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3 },
  8. { 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15 },
  9. { 0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, }
  10. };

这是此演示中使用的透明度值的二维数组。 有 8 行,每行一种状态。 8 行中的每行将连续使用这些值。

  1. cairo_set_line_width(cr, 3);
  2. cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);

我们使线条更粗一些,以便更好地显示它们。 我们用带帽的线画线。

  1. cairo_set_source_rgba(cr, 0, 0, 0, trs[glob.count%8][i]);

在这里,我们定义了一条线的透明度值。

  1. cairo_move_to(cr, 0.0, -10.0);
  2. cairo_line_to(cr, 0.0, -40.0);
  3. cairo_rotate(cr, M_PI/4);

这些代码将绘制八行中的每一行。

  1. g_timeout_add(100, (GSourceFunc) time_handler, (gpointer) window);

我们使用计时器函数来创建动画。

透明度 - 图3

图:等待 demo

在 Cairo 教程的这一部分中,我们介绍了透明度。