Cairo 图形指南 (6) —— 透明

转载 2012年03月29日 17:48:24
这一篇讲述有关透明的一些基本知识,并提供两个有趣的透明效果。

透明是透过某种材质的可见度。理解透明最简单的方式就是想像一下玻璃或者水。从技术上讲,光线可以穿过玻璃,因此我们可以看到玻璃之后的物体。

在计算机图形学中,可以使用alpha 混合方式来实现透明效果。Alpha 混合,是通过将图像与背景组合实现部分透明的视觉效果。混合过程中使用了一种叫做 alpha 通道的东西。Alpha 通道在图形文件格式中是一个 8 位的层,用于表示图片的透明性。每个像素所包含的这个各额外的 8 位数字提供了一个蒙板,可以表达 256 个层次的透明度。

透明的矩形
第一个例子,绘制了 10 个不同透明程度的矩形。

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

static gboolean
on_expose_event (GtkWidget * widget,
                 GdkEventExpose * event, gpointer data)
{
        cairo_t *cr;

        cr = gdk_cairo_create (widget->window);

        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_destroy (cr);

        return FALSE;
}

int
main (int argc, char *argv[])
{
        GtkWidget *window;

        gtk_init (&argc, &argv);

        window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

        g_signal_connect (G_OBJECT (window), "expose-event",
                          G_CALLBACK (on_expose_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), 590, 90);
        gtk_window_set_title (GTK_WINDOW (window), "transparency");

        gtk_widget_set_app_paintable (window, TRUE);
        gtk_widget_show_all (window);

        gtk_main ();

        return 0;
}

cairo_set_source_rgba () 函数有一个可选的 alpha 参数,用于提供透明色支持。

--------------------------------------------------------------------------------

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

这段代码创建 10 个矩形,其 alpha 值从 0.1 递增到 1。


--------------------------------------------------------------------------------

淡出的效果

在下一个示例中,实现对一幅图片的淡出处理。这幅图片会逐渐变得越来越透明直至看不见。

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

cairo_surface_t *image;

static gboolean
on_expose_event (GtkWidget * widget,
                 GdkEventExpose * event, gpointer data)
{
        cairo_t *cr;

        cr = gdk_cairo_create (widget->window);

        static double alpha = 1;
        double const delta = 0.01;

        cairo_set_source_surface (cr, image, 10, 10);
        cairo_paint_with_alpha (cr, alpha);

        alpha -= delta;

        if (alpha <= 0)
                timer = FALSE;

        cairo_destroy (cr);

        return FALSE;
}

static gboolean
time_handler (GtkWidget * widget)
{
        if (widget->window == NULL)
                return FALSE;

        gtk_widget_queue_draw (widget);
        return TRUE;
}

int
main (int argc, char *argv[])
{
        GtkWidget *window;
        GtkWidget *darea;

        gint width, height;

        image = cairo_image_surface_create_from_png ("tuz.png");
        width = cairo_image_surface_get_width (image);
        height = cairo_image_surface_get_height (image);

        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 (darea, "expose-event",
                          G_CALLBACK (on_expose_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), width + 20,
                                     height + 20);

        gtk_window_set_title (GTK_WINDOW (window), "fade out");
        g_timeout_add (50, (GSourceFunc) time_handler,
                       (gpointer) window);
        gtk_widget_show_all (window);

        gtk_main ();

        cairo_surface_destroy (image);

        return 0;
}

在这一示例中,显示了一幅渐渐淡出的图片。所使用的图片是 Linux kernel 的新 Logo,详见 LinuxToy 网站的介绍

-------------------------------------------------------------------------------

        image = cairo_image_surface_create_from_png ("tuz.png");

出于对程序运行效率的考虑,图像外观的创建在主函数中进行。

-------------------------------------------------------------------------------

        cairo_set_source_surface (cr, image, 10, 10);

将载入的图片设置为源 (source)。

-------------------------------------------------------------------------------

        cairo_paint_with_alpha (cr, alpha);

调用函数 cairo_paint_with_alpha () 来实现图片淡出效果,该函数使用透明度作为蒙板。

-------------------------------------------------------------------------------

        alpha -= delta;

在每次响应 expose 事件的处理过程中,进行透明度的值递减。

--------------------------------------------------------------------------------

        if (alpha <= 0)
                alpha = 1;

当透明度为 0 时,意味着图片是完全透明的。这时,再将透明度置为 1,即将图片复原为不透明状态。这样做的目的是让图片周而复始、痛不欲生的淡出着。

-------------------------------------------------------------------------------

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

创建一个计时器,其回调函数为 time_handler (),每隔 50ms 便会被调用一次。

--------------------------------------------------------------------------------

static gboolean
time_handler (GtkWidget * widget)
{
        if (widget->window == NULL)
                return FALSE;

        gtk_widget_queue_draw (widget);
        return TRUE;
}

time_handler () 函数的实现,它的主要作用是调用 gtk_widget_queue_draw () 函数,实现窗口的重绘 (发出 expose 事件)。当该函数返回值为 FALSE 时,计时器便停止工作。

--------------------------------------------------------------------------------

        if (widget->window == NULL)
                return FALSE;

这两行代码看上去有点多余。但是考虑在计时器的时间间隔很小的情况下 (譬如 5ms),如果关闭窗口,这时进程尚未完全销毁,不过窗口已经被 destroy 了。这时,这两行代码可以防止计时器回调函数继续工作而引起的程序崩溃。


--------------------------------------------------------------------------------

“等待”的演示

下面的示例,使用透明效果制作了一个“等待”状态的演示,是通过绘制 8 条依次消隐淡出的线,模拟一条线的运动过程。像这样的效果通常被用于通知用户耐心地等待一个隐藏在屏幕之后漫长的任务的完成。在网上看视频时,经常可以看到类似的“等待”。

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

static gushort count = 0;

static gboolean
on_expose_event (GtkWidget * widget,
                 GdkEventExpose * event, gpointer data)
{
        cairo_t *cr;

        cr = gdk_cairo_create (widget->window);

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


        gint width, height;
        gtk_window_get_size (GTK_WINDOW (widget), &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[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);
        }

        cairo_destroy (cr);

        return FALSE;
}

static gboolean
time_handler (GtkWidget * widget)
{
        count += 1;
        gtk_widget_queue_draw (widget);
        return TRUE;
}

int
main (int argc, char *argv[])
{
        GtkWidget *window;
        GtkWidget *darea;

        gtk_init (&argc, &argv);

        window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

        g_signal_connect (G_OBJECT (window), "expose-event",
                          G_CALLBACK (on_expose_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");

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

        gtk_main ();

        return 0;
}

方法比较笨,就是绘制 8 条不同透明度的线。

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 条线的透明度就是这样被活生生地定义出来的,每一行表示一条线的透明度候选集。

--------------------------------------------------------------------------------

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

设置线宽与线帽。

--------------------------------------------------------------------------------

                cairo_set_source_rgba (cr, 0, 0, 0, trs[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 的旋转变换。每次都在前一次旋转的基础上继续逆时针旋转 角度。这样,8 次循环之后,恰好绕了一圈。

--------------------------------------------------------------------------------

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

又是计时器。


相关文章推荐

Cairo 图形指南 (5) —— 图像

在这一篇里,要讲述图像的处理。先是演示如何在 GTK+ 窗口中显示一幅图像,然后再制造一些特效。 图像的显示 在第一个例子里,显示了一幅图像。 [html] view...

Cairo 图形指南 (7) —— 合成

合成 (Compositing) 可以将一些单独的源 (source) 所生成可视元素组合成到单幅图像中去,主要用于创建所有的可视元素作为同一场景的一部分这样一种假象。合成在电影工业中被广为使用,用于...

Cairo 图形指南 (3) —— 变换

这一篇讲述变换(Transformation) 仿射变换是由一些线性变换与平移构成的。线性变换可以写为单个矩阵的形式。旋转是让一个刚体绕一点运动的变换。缩放变换是让物体的形状扩大与减小,并且在各个方向...

Cairo 图形指南 (5) —— 形状与填充

这一部分,讲述一些基本的以及较为高级的形状绘制及其纯色 (solid color)、图案 (pattern) 与渐变 (gradient) 填充方法。 基本形状 Cairo 提供了几个用于...

Cairo 图形指南(1) —— 基本绘图

这一部分讲述如何绘制一些简单的图元,包括直线、填充与笔画操作、虚线、线端(Cap)与线的交合等图形的绘制方法。 直线段 直线段是非常基础的矢量图形对象。画一条直线段,需要调用两个函数:cairo_...
  • haiwil
  • haiwil
  • 2011年09月13日 16:50
  • 4632

Cairo 图形指南 (5) —— 图像

在这一篇里,要讲述图像的处理。先是演示如何在 GTK+ 窗口中显示一幅图像,然后再制造一些特效。 图像的显示 在第一个例子里,显示了一幅图像。 #include #include...
  • haiwil
  • haiwil
  • 2011年09月13日 17:04
  • 1726

用 cairo 实现跨平台图形

cairo 的优点和用途 cairo 是一个免费的矢量绘图软件库,它可以绘制多种输出格式。cairo 支持许多平台,包括 Linux、BSD、Microsoft® Windows® 和 OSX(Be...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Cairo 图形指南 (6) —— 透明
举报原因:
原因补充:

(最多只允许输入30个字)