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

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

基本形状

Cairo 提供了几个用于绘制基本形状的函数。

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

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

        cr = gdk_cairo_create  (widget->window );

        cairo_set_source_rgb  (cr,  000 );
        cairo_set_line_width  (cr,  1 );

        cairo_rectangle  (cr,  202012080 );
        cairo_rectangle  (cr,  180208080 );
        cairo_stroke_preserve  (cr );
        cairo_set_source_rgb  (cr,  111 );
        cairo_fill  (cr );

        cairo_set_source_rgb  (cr,  000 );
        cairo_arc  (cr,  330604002 * M_PI );
        cairo_stroke_preserve  (cr );
        cairo_set_source_rgb  (cr,  111 );
        cairo_fill  (cr );

        cairo_set_source_rgb  (cr,  000 );
        cairo_arc  (cr,  9016040, M_PI /  4, M_PI );
        cairo_close_path  (cr );
        cairo_stroke_preserve  (cr );
        cairo_set_source_rgb  (cr,  111 );
        cairo_fill  (cr );

        cairo_set_source_rgb  (cr,  000 );
        cairo_translate  (cr,  220180 );
        cairo_scale  (cr,  10.7 );
        cairo_arc  (cr,  005002 * M_PI );
        cairo_stroke_preserve  (cr );
        cairo_set_source_rgb  (cr,  111 );
        cairo_fill  (cr );

        cairo_destroy  (cr );

         return  FALSE;
}


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  (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 )390240 );

        gtk_widget_show_all  (window );

        gtk_main  ( );

         return  0;
}

这个示例,绘制了矩形、正方形、圆、圆弧和椭圆。

下面对关键代码简单分析:


        cairo_rectangle  (cr,  202012080 );
        cairo_rectangle  (cr,  180208080 );

绘制矩形与正方形。正方形在 cairo 中是矩形的一种特例。


        cairo_arc  (cr,  330604002 * M_PI );

 画了一个圆,圆心为 (330, 60)px,半径为 40px。Cairo 所谓的圆,其实是起始角为 0 度,终止角为 360 度的弧线。


        cairo_scale  (cr,  10.7 );
        cairo_arc  (cr,  005002 * M_PI );

画椭圆的方法也与画圆类似,只是需要先设定长轴与短轴的比例,在本例中为 1:0.7。



复杂的图形

复杂的图形是由简单的图形拼凑出来的,譬如下面这个绘制圆角矩形的程序。

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

static  void
draw_round_rectangle  (cairo_t * cr,
                       double x,  double y,
                       double width,  double height,  double r )
{
        cairo_move_to  (cr, x + r, y );
        cairo_line_to  (cr, x + width - r, y );

        cairo_move_to  (cr, x + width, y + r );
        cairo_line_to  (cr, x + width, y + height - r );

        cairo_move_to  (cr, x + width - r, y + height );
        cairo_line_to  (cr, x + r, y + height );

        cairo_move_to  (cr, x, y + height - r );
        cairo_line_to  (cr, x, y + r );

        cairo_arc  (cr, x + r, y + r, r, M_PI,  3 * M_PI /  2.0 );
        cairo_arc  (cr, x + width - r, y + r, r,  3 * M_PI /  22 * M_PI );
        cairo_arc  (cr, x + width - r, y + height - r, r,  0, M_PI /  2 );
        cairo_arc  (cr, x + r, y + height - r, r, M_PI /  2, M_PI );
}

static gboolean
on_expose_event  (GtkWidget * widget,
                 GdkEventExpose * event, gpointer data )
{
        cairo_t *cr;
         int width, height;
        
         double w, h, x, y, r;
        
        gtk_window_get_size  (GTK_WINDOW  (widget ), &width, &height );
        
        x = width /  5.0;
        y = height /  5.0;
        w =  3 * width /  5.0;
        h =  3 * height /  5.0;
        r = h /  4.0;
        
        cr = gdk_cairo_create  (widget->window );

        cairo_set_source_rgb  (cr,  0.80.40 );
        cairo_set_line_width  (cr,  6 );

        draw_round_rectangle  (cr, x, y, w, h, r );
        cairo_stroke_preserve  (cr );
        cairo_set_source_rgb  (cr,  0.80.80.2 );
        cairo_fill  (cr );

        cairo_destroy  (cr );

        g_print  ( "test\n" );
        
         return  FALSE;
}

static gboolean
on_configure_event  (GtkWidget * widget,
              GdkEventConfigure * event, gpointer data )
{
        gdk_window_invalidate_rect  (widget->window,
                                    &widget->allocation,
                                     FALSE );
         return  FALSE;
}

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

        gtk_init  (&argc, &argv );

        window = gtk_window_new  (GTK_WINDOW_TOPLEVEL );

        g_signal_connect  (window,  "expose-event",
                          G_CALLBACK  (on_expose_event )NULL );
        g_signal_connect  (window,  "destroy",
                          G_CALLBACK  (gtk_main_quit )NULL );
        g_signal_connect (G_OBJECT (window )"configure-event",
                         G_CALLBACK (on_configure_event )NULL );

        
        gtk_window_set_position  (GTK_WINDOW  (window ),
                                 GTK_WIN_POS_CENTER );
        gtk_window_set_default_size  (GTK_WINDOW  (window )400300 );
        gtk_widget_set_app_paintable  (window,  TRUE );
        gtk_widget_show_all  (window );

        gtk_main  ( );

         return  0;
}

注:因为 "The cairo graphics tutorial" 在这一部分所提供的示例程序不具代表性,因此写了这个程序。

该示例程序绘制了一个可跟随窗口尺寸进行缩放变化的圆角矩形。

自定义的 draw_round_rectangle () 函数利用 Cairo 提供的基本图元函数,利用直线段与圆弧拼凑出圆角矩形。on_configure_event () 函数用于响应窗口尺寸变化事件,在其中调用 gdk_window_invalidate_rect () 函数让窗口绘图区域失效,并产生窗口重绘制事件(即 expose 事件)。



填充 (Fill)

虽然上一篇已经讲述了一些有关填充的知识,但这里所讲述的内容是与形状相关的。填充可分为三种类型:纯色、图案、渐变。

纯色 (Solid color)

对象的颜色是采用红 (R)、绿 (G)、蓝 (B) 三原色描述的,Cairo 的 RGB 取值是从 0 到 1 的双精浮点数。

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

         int width, height;
        gtk_window_get_size  (GTK_WINDOW  (widget ), &width, &height );


        cairo_set_source_rgb  (cr,  0.50.51 );
        cairo_rectangle  (cr,  2020100100 );
        cairo_fill  (cr );

        cairo_set_source_rgb  (cr,  0.60.60.6 );
        cairo_rectangle  (cr,  15020100100 );
        cairo_fill  (cr );

        cairo_set_source_rgb  (cr,  00.30 );
        cairo_rectangle  (cr,  20140100100 );
        cairo_fill  (cr );

        cairo_set_source_rgb  (cr,  100.5 );
        cairo_rectangle  (cr,  150140100100 );
        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 )270260 );
        gtk_window_set_title  (GTK_WINDOW  (window )"colors" );

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

        gtk_main  ( );

         return  0;
}

该示例绘制了 4 个正方形,分别采用四种不同颜色进行填充。这个例子,由于很简单,就不再像原作者那样自作多情的分析了。



图案 (Pattern)

所谓图案填充,就是将图片填充到形状内部。

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

cairo_surface_t *surface1;
cairo_surface_t *surface2;
cairo_surface_t *surface3;
cairo_surface_t *surface4;

static  void
create_surfaces  ( )
{
        surface1 = cairo_image_surface_create_from_png  ( "blueweb.png" );
        surface2 = cairo_image_surface_create_from_png  ( "maple.png" );
        surface3 = cairo_image_surface_create_from_png  ( "crack.png" );
        surface4 =
            cairo_image_surface_create_from_png  ( "chocolate.png" );
}

static  void
destroy_surfaces  ( )
{
        g_print  ( "destroying surfaces" );
        cairo_surface_destroy  (surface1 );
        cairo_surface_destroy  (surface2 );
        cairo_surface_destroy  (surface3 );
        cairo_surface_destroy  (surface4 );
}

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

        cairo_pattern_t *pattern1;
        cairo_pattern_t *pattern2;
        cairo_pattern_t *pattern3;
        cairo_pattern_t *pattern4;

        cr = gdk_cairo_create  (widget->window );

         int width, height;
        gtk_window_get_size  (GTK_WINDOW  (widget ), &width, &height );


        pattern1 = cairo_pattern_create_for_surface  (surface1 );
        pattern2 = cairo_pattern_create_for_surface  (surface2 );
        pattern3 = cairo_pattern_create_for_surface  (surface3 );
        pattern4 = cairo_pattern_create_for_surface  (surface4 );


        cairo_set_source  (cr, pattern1 );
        cairo_pattern_set_extend  (cairo_get_source  (cr ),
                                  CAIRO_EXTEND_REPEAT );
        cairo_rectangle  (cr,  2020100100 );
        cairo_fill  (cr );

        cairo_set_source  (cr, pattern2 );
        cairo_pattern_set_extend  (cairo_get_source  (cr ),
                                  CAIRO_EXTEND_REPEAT );
        cairo_arc  (cr,  200705002 * M_PI );
        cairo_fill  (cr );

        cairo_set_source  (cr, pattern3 );
        cairo_pattern_set_extend  (cairo_get_source  (cr ),
                                  CAIRO_EXTEND_REPEAT );
        cairo_rectangle  (cr,  20140100100 );
        cairo_fill  (cr );

        cairo_set_source  (cr, pattern4 );
        cairo_pattern_set_extend  (cairo_get_source  (cr ),
                                  CAIRO_EXTEND_REPEAT );
        cairo_rectangle  (cr,  150140100100 );
        cairo_fill  (cr );

        cairo_pattern_destroy  (pattern1 );
        cairo_pattern_destroy  (pattern2 );
        cairo_pattern_destroy  (pattern3 );
        cairo_pattern_destroy  (pattern4 );

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

        create_surfaces  ( );

        gtk_window_set_position  (GTK_WINDOW  (window ),
                                 GTK_WIN_POS_CENTER );
        gtk_window_set_default_size  (GTK_WINDOW  (window )270260 );
        gtk_window_set_title  (GTK_WINDOW  (window )"patterns" );

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

        gtk_main  ( );

        destroy_surfaces  ( );

         return  0;
}

该示例,载入 4 张图片,分别填充至三个矩形与一个圆形内部区域。所使用的 4 幅图,均采用 GIMP 制作。程序中,图片的外观 (surface) 实在 on_expose_event () 函数中创建的,这并不是很妥当,因为窗口每次被重绘时,都需要从硬盘中读取图片。


        pattern1 = cairo_pattern_create_for_surface  (surface1 );

由图片外观创建一个图案。


        cairo_set_source  (cr, pattern1 );
        cairo_pattern_set_extend  (cairo_get_source  (cr ),
                                  CAIRO_EXTEND_REPEAT );
        cairo_rectangle  (cr,  2020100100 );
        cairo_fill  (cr );

这里,绘制第一个矩形。cairo_set_source () 函数通知 Cairo 环境,让它使用一份图案作为源 (source)。图片所形成的图案或许并不适合于形状,当使用 cairo_pattern_set_extend () 函数讲图案填充模式设为 CAIRO_EXTEND_REPEAT 时,可以让图案像瓦片那样填充于形状内部。cairo_rectangle () 函数创建一个矩形路径,cairo_fill () 函数将已经准备好的图案填充到矩形路径所构成的封闭区域中。



渐变 (Gradient)

在计算机图形学中,渐变是形状由明到暗或者从一种颜色向另一种颜色的平滑过度。在 2D 绘图与渲染程序中,渐变通常被用于创造多彩的背景与一些特效,比如光影的仿真。

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

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

        cr = gdk_cairo_create  (widget->window );

        pat1 = cairo_pattern_create_linear  ( 0.00.0350.0350.0 );

        gdouble j;
        gint count =  1;
         for  (j =  0.1; j <  1; j +=  0.1 )  {
                 if  ( (count %  2 ) )  {
                        cairo_pattern_add_color_stop_rgb  (pat1, j,  00,
                                                           0 );
                 }  else  {
                        cairo_pattern_add_color_stop_rgb  (pat1, j,  10,
                                                           0 );
                 }
                count++;
         }

        cairo_rectangle  (cr,  2020300100 );
        cairo_set_source  (cr, pat1 );
        cairo_fill  (cr );


        pat2 = cairo_pattern_create_linear  ( 0.00.0350.00.0 );

        gdouble i;
        count =  1;
         for  (i =  0.05; i <  0.95; i +=  0.025 )  {
                 if  ( (count %  2 ) )  {
                        cairo_pattern_add_color_stop_rgb  (pat2, i,  00,
                                                           0 );
                 }  else  {
                        cairo_pattern_add_color_stop_rgb  (pat2, i,  00,
                                                           1 );
                 }
                count++;
         }

        cairo_rectangle  (cr,  20140300100 );
        cairo_set_source  (cr, pat2 );
        cairo_fill  (cr );


        pat3 = cairo_pattern_create_linear  ( 20.0260.020.0360.0 );

        cairo_pattern_add_color_stop_rgb  (pat3,  0.1000 );
        cairo_pattern_add_color_stop_rgb  (pat3,  0.5110 );
        cairo_pattern_add_color_stop_rgb  (pat3,  0.9000 );

        cairo_rectangle  (cr,  20260300100 );
        cairo_set_source  (cr, pat3 );
        cairo_fill  (cr );

        cairo_pattern_destroy  (pat1 );
        cairo_pattern_destroy  (pat2 );
        cairo_pattern_destroy  (pat3 );

        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 )340390 );
        gtk_window_set_title  (GTK_WINDOW  (window )"gradients" );

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

        gtk_main  ( );

         return  0;
}
 

在这一示例程序中,我们绘制了三个具有不同渐变风格的矩形。


        pat3 = cairo_pattern_create_linear  ( 20.0260.020.0360.0 );

这里,创建了一个线性渐变图案。参数设定了绘制渐变方向的直线,在示例中,它是一条竖线。


        cairo_pattern_add_color_stop_rgb  (pat3,  0.1000 );
        cairo_pattern_add_color_stop_rgb  (pat3,  0.5110 );
        cairo_pattern_add_color_stop_rgb  (pat3,  0.9000 );

定义了渐变图案的断点。在示例中,渐变图案表现为黑色与黄色的过渡。通过添加两个黑色断点和一个黄色断点,就可以构成一个水平方向的渐变图案,颜色 的变化方向则是沿竖直方向。渐变图案从矩形的上端至下端,开始是黑色,到 1/10 宽度时,黑色便停止了,然后就是由黑色向黄色的渐变渲染;到达矩形中部时,黄色达到饱和状态。黄色断点会在 9/10 宽度处终止,最后的 1/10 又是黑色。



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值