ZetCode

透明度

最后修改于 2023 年 7 月 17 日

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

透明度是能够透过物质的品质。理解透明度最简单的方法就是想象一块玻璃或水。技术上来说,光线可以穿过玻璃,这样我们就能看到玻璃后面的物体。

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

透明矩形

第一个例子将绘制十个具有不同透明度级别的矩形。

static void do_drawing(cairo_t *cr)
{
  gint i;
  for (i = 1; i <= 10; i++) {
      cairo_set_source_rgba(cr, 0, 0, 1, i*0.1);
      cairo_rectangle(cr, 50*i, 20, 40, 40);
      cairo_fill(cr);  
  }      
}

cairo_set_source_rgba 有一个可选的 alpha 参数用于提供透明度。此代码创建十个 alpha 值从 0.1 到 1 的矩形。

Transparency
图:透明度

烟雾效果

在下面的例子中,我们创建了一个烟雾效果。该示例将显示一个不断增长的居中文本,该文本将从某个点开始逐渐淡出。这是一种非常常见的效果,我们经常在 Flash 动画中看到。cairo_paint_with_alpha 方法对于创建此效果至关重要。

#include <cairo.h>
#include <gtk/gtk.h>

void do_drawing(cairo_t *, GtkWidget *);

struct {
  gboolean timer; 
  gdouble alpha;
  gdouble size;
} glob;

static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, 
    gpointer user_data)
{      
  do_drawing(cr, widget);

  return FALSE;
}

void do_drawing(cairo_t *cr, GtkWidget *widget)
{
  cairo_text_extents_t extents;

  GtkWidget *win = gtk_widget_get_toplevel(widget);
  
  gint width, height;
  gtk_window_get_size(GTK_WINDOW(win), &width, &height);  
  
  gint x = width/2;
  gint y = height/2;
  
  cairo_set_source_rgb(cr, 0.5, 0, 0); 
  cairo_paint(cr);   

  cairo_select_font_face(cr, "Courier",
      CAIRO_FONT_SLANT_NORMAL,
      CAIRO_FONT_WEIGHT_BOLD);
 
  glob.size += 0.8;

  if (glob.size > 20) {
      glob.alpha -= 0.01;
  }

  cairo_set_font_size(cr, glob.size);
  cairo_set_source_rgb(cr, 1, 1, 1); 

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

  cairo_paint_with_alpha(cr, glob.alpha);
  
  if (glob.alpha <= 0) {
      glob.timer = FALSE;
  }     
}

static gboolean time_handler(GtkWidget *widget)
{ 
  if (!glob.timer) return FALSE;
  gtk_widget_queue_draw(widget);

  return TRUE;
}


int main(int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *darea;  
  
  glob.timer = TRUE;
  glob.alpha = 1.0;
  glob.size = 1.0;

  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(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), 350, 200); 
  gtk_window_set_title(GTK_WINDOW(window), "Puff");

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

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例在窗口上创建一个不断增长和淡出的文本。

struct {
  gboolean timer; 
  gdouble alpha;
  gdouble size;
} glob; 

在这里,我们在结构体中定义了一些变量。这是为了避免使用全局变量。

draw_text(cr, widget); 

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

GtkWidget *win = gtk_widget_get_toplevel(widget);

gint width, height;
gtk_window_get_size(GTK_WINDOW(win), &width, &height);  

gint x = width/2;
gint y = height/2;

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

cairo_set_source_rgb(cr, 0.5, 0, 0); 
cairo_paint(cr); 

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

cairo_select_font_face(cr, "Courier",
    CAIRO_FONT_SLANT_NORMAL,
    CAIRO_FONT_WEIGHT_BOLD);

文本将使用 Courier Bold 字体。

glob.size += 0.8;

if (glob.size > 20) {
    glob.alpha -= 0.01;
}

文本的大小增加了 0.8 个单位。当它达到 20 个单位后,alpha 值开始减小。文本逐渐淡出。

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

我们获取文本度量。我们只使用文本宽度。我们将移动到一个文本居中显示在窗口上的位置。

cairo_text_path(cr, "ZetCode");
cairo_clip(cr);

cairo_paint_with_alpha(cr, glob.alpha);

我们使用 cairo_text_path 方法获取文本路径。我们使用 cairo_clip 方法将绘制限制在当前路径内。cairo_paint_with_alpha 方法使用 alpha 值的蒙版在当前剪辑区域内的任何位置绘制当前源。

glob.timer = TRUE;
glob.alpha = 1.0;
glob.size = 1.0;

我们初始化三个变量。

static gboolean time_handler(GtkWidget *widget)
{
  if (!glob.timer) return FALSE;

  gtk_widget_queue_draw(widget);

  return TRUE;
}

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

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

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

Puff effect
图:烟雾效果

等待演示

在这个例子中,我们使用透明度效果来创建一个等待演示。我们绘制了八条线,它们将逐渐淡出,产生一种线条正在移动的错觉。这种效果通常用于通知用户后台正在进行耗时的任务。例如,通过 Internet 流式传输视频。

#include <cairo.h>
#include <gtk/gtk.h>
#include <math.h>

static void do_drawing(cairo_t *, GtkWidget *);

struct {
  gushort count;
} glob;

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)
{  
  static gdouble const trs[8][8] = {
      { 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 },
      { 1.0, 0.0,  0.15, 0.30, 0.5, 0.65, 0.8, 0.9 },
      { 0.9, 1.0,  0.0,  0.15, 0.3, 0.5, 0.65, 0.8 },
      { 0.8, 0.9,  1.0,  0.0,  0.15, 0.3, 0.5, 0.65},
      { 0.65, 0.8, 0.9,  1.0,  0.0,  0.15, 0.3, 0.5 },
      { 0.5, 0.65, 0.8, 0.9, 1.0,  0.0,  0.15, 0.3 },
      { 0.3, 0.5, 0.65, 0.8, 0.9, 1.0,  0.0,  0.15 },
      { 0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0,  0.0, }
  };

  GtkWidget *win = gtk_widget_get_toplevel(widget);
  
  gint width, height;
  gtk_window_get_size(GTK_WINDOW(win), &width, &height);

  cairo_translate(cr, width/2, height/2);

  gint i = 0;
  for (i = 0; i < 8; i++) {
      cairo_set_line_width(cr, 3);
      cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
      cairo_set_source_rgba(cr, 0, 0, 0, trs[glob.count%8][i]);

      cairo_move_to(cr, 0.0, -10.0);
      cairo_line_to(cr, 0.0, -40.0);
      cairo_rotate(cr, M_PI/4);

      cairo_stroke(cr);
  }   
}

static gboolean time_handler(GtkWidget *widget)
{
  glob.count += 1;
  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);

  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), 250, 150); 
  gtk_window_set_title(GTK_WINDOW(window), "Waiting demo");

  g_timeout_add(100, (GSourceFunc) time_handler, (gpointer) window);
  gtk_widget_show_all(window);  

  gtk_main();

  return 0;
}

我们绘制八条线,每条线有八个不同的 alpha 值。

 static gdouble const trs[8][8] = {
     { 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 },
     { 1.0, 0.0,  0.15, 0.30, 0.5, 0.65, 0.8, 0.9 },
     { 0.9, 1.0,  0.0,  0.15, 0.3, 0.5, 0.65, 0.8 },
     { 0.8, 0.9,  1.0,  0.0,  0.15, 0.3, 0.5, 0.65},
     { 0.65, 0.8, 0.9,  1.0,  0.0,  0.15, 0.3, 0.5 },
     { 0.5, 0.65, 0.8, 0.9, 1.0,  0.0,  0.15, 0.3 },
     { 0.3, 0.5, 0.65, 0.8, 0.9, 1.0,  0.0,  0.15 },
     { 0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0,  0.0, }
 };

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

cairo_set_line_width(cr, 3);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);

我们使线条稍粗,以便它们更可见。 我们用圆角绘制线条。

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

这里我们定义了线的透明度值。

cairo_move_to(cr, 0.0, -10.0);
cairo_line_to(cr, 0.0, -40.0);
cairo_rotate(cr, M_PI/4);

这些代码将绘制这八条线。

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

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

Waiting demo
图:等待演示

在本篇 Cairo 教程中,我们涵盖了透明度。