形状与填充
这一部分,讲述一些基本的以及较为高级的形状绘制及其纯色 (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,
0 ,
0 ,
0
) ;
cairo_set_line_width
( cr,
1
) ;
cairo_rectangle
( cr,
20 ,
20 ,
120 ,
80
) ;
cairo_rectangle
( cr,
180 ,
20 ,
80 ,
80
) ;
cairo_stroke_preserve
( cr
) ;
cairo_set_source_rgb
( cr,
1 ,
1 ,
1
) ;
cairo_fill
( cr
) ;
cairo_set_source_rgb
( cr,
0 ,
0 ,
0
) ;
cairo_arc
( cr,
330 ,
60 ,
40 ,
0 ,
2 * M_PI
) ;
cairo_stroke_preserve
( cr
) ;
cairo_set_source_rgb
( cr,
1 ,
1 ,
1
) ;
cairo_fill
( cr
) ;
cairo_set_source_rgb
( cr,
0 ,
0 ,
0
) ;
cairo_arc
( cr,
90 ,
160 ,
40 , M_PI /
4 , M_PI
) ;
cairo_close_path
( cr
) ;
cairo_stroke_preserve
( cr
) ;
cairo_set_source_rgb
( cr,
1 ,
1 ,
1
) ;
cairo_fill
( cr
) ;
cairo_set_source_rgb
( cr,
0 ,
0 ,
0
) ;
cairo_translate
( cr,
220 ,
180
) ;
cairo_scale
( cr,
1 ,
0.7
) ;
cairo_arc
( cr,
0 ,
0 ,
50 ,
0 ,
2 * M_PI
) ;
cairo_stroke_preserve
( cr
) ;
cairo_set_source_rgb
( cr,
1 ,
1 ,
1
) ;
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
) ,
390 ,
240
) ;
gtk_widget_show_all
( window
) ;
gtk_main
(
) ;
return
0 ;
}
这个示例,绘制了矩形、正方形、圆、圆弧和椭圆。
下面对关键代码简单分析:
cairo_rectangle
( cr,
20 ,
20 ,
120 ,
80
) ;
cairo_rectangle
( cr,
180 ,
20 ,
80 ,
80
) ;
绘制矩形与正方形。正方形在 cairo 中是矩形的一种特例。
cairo_arc
( cr,
330 ,
60 ,
40 ,
0 ,
2 * M_PI
) ;
画了一个圆,圆心为 (330, 60)px,半径为 40px。Cairo 所谓的圆,其实是起始角为 0 度,终止角为 360 度的弧线。
cairo_scale
( cr,
1 ,
0.7
) ;
cairo_arc
( cr,
0 ,
0 ,
50 ,
0 ,
2 * 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 /
2 ,
2 * 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.8 ,
0.4 ,
0
) ;
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.8 ,
0.8 ,
0.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
) ,
400 ,
300
) ;
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.5 ,
0.5 ,
1
) ;
cairo_rectangle
( cr,
20 ,
20 ,
100 ,
100
) ;
cairo_fill
( cr
) ;
cairo_set_source_rgb
( cr,
0.6 ,
0.6 ,
0.6
) ;
cairo_rectangle
( cr,
150 ,
20 ,
100 ,
100
) ;
cairo_fill
( cr
) ;
cairo_set_source_rgb
( cr,
0 ,
0.3 ,
0
) ;
cairo_rectangle
( cr,
20 ,
140 ,
100 ,
100
) ;
cairo_fill
( cr
) ;
cairo_set_source_rgb
( cr,
1 ,
0 ,
0.5
) ;
cairo_rectangle
( cr,
150 ,
140 ,
100 ,
100
) ;
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
) ,
270 ,
260
) ;
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,
20 ,
20 ,
100 ,
100
) ;
cairo_fill
( cr
) ;
cairo_set_source
( cr, pattern2
) ;
cairo_pattern_set_extend
( cairo_get_source
( cr
) ,
CAIRO_EXTEND_REPEAT
) ;
cairo_arc
( cr,
200 ,
70 ,
50 ,
0 ,
2 * M_PI
) ;
cairo_fill
( cr
) ;
cairo_set_source
( cr, pattern3
) ;
cairo_pattern_set_extend
( cairo_get_source
( cr
) ,
CAIRO_EXTEND_REPEAT
) ;
cairo_rectangle
( cr,
20 ,
140 ,
100 ,
100
) ;
cairo_fill
( cr
) ;
cairo_set_source
( cr, pattern4
) ;
cairo_pattern_set_extend
( cairo_get_source
( cr
) ,
CAIRO_EXTEND_REPEAT
) ;
cairo_rectangle
( cr,
150 ,
140 ,
100 ,
100
) ;
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
) ,
270 ,
260
) ;
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,
20 ,
20 ,
100 ,
100
) ;
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.0 ,
0.0 ,
350.0 ,
350.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,
0 ,
0 ,
0
) ;
}
else
{
cairo_pattern_add_color_stop_rgb
( pat1, j,
1 ,
0 ,
0
) ;
}
count++;
}
cairo_rectangle
( cr,
20 ,
20 ,
300 ,
100
) ;
cairo_set_source
( cr, pat1
) ;
cairo_fill
( cr
) ;
pat2 = cairo_pattern_create_linear
(
0.0 ,
0.0 ,
350.0 ,
0.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,
0 ,
0 ,
0
) ;
}
else
{
cairo_pattern_add_color_stop_rgb
( pat2, i,
0 ,
0 ,
1
) ;
}
count++;
}
cairo_rectangle
( cr,
20 ,
140 ,
300 ,
100
) ;
cairo_set_source
( cr, pat2
) ;
cairo_fill
( cr
) ;
pat3 = cairo_pattern_create_linear
(
20.0 ,
260.0 ,
20.0 ,
360.0
) ;
cairo_pattern_add_color_stop_rgb
( pat3,
0.1 ,
0 ,
0 ,
0
) ;
cairo_pattern_add_color_stop_rgb
( pat3,
0.5 ,
1 ,
1 ,
0
) ;
cairo_pattern_add_color_stop_rgb
( pat3,
0.9 ,
0 ,
0 ,
0
) ;
cairo_rectangle
( cr,
20 ,
260 ,
300 ,
100
) ;
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
) ,
340 ,
390
) ;
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.0 ,
260.0 ,
20.0 ,
360.0
) ;
这里,创建了一个线性渐变图案。参数设定了绘制渐变方向的直线,在示例中,它是一条竖线。
cairo_pattern_add_color_stop_rgb
( pat3,
0.1 ,
0 ,
0 ,
0
) ;
cairo_pattern_add_color_stop_rgb
( pat3,
0.5 ,
1 ,
1 ,
0
) ;
cairo_pattern_add_color_stop_rgb
( pat3,
0.9 ,
0 ,
0 ,
0
) ;
定义了渐变图案的断点。在示例中,渐变图案表现为黑色与黄色的过渡。通过添加两个黑色断点和一个黄色断点,就可以构成一个水平方向的渐变图案,颜色 的变化方向则是沿竖直方向。渐变图案从矩形的上端至下端,开始是黑色,到 1/10 宽度时,黑色便停止了,然后就是由黑色向黄色的渐变渲染;到达矩形中部时,黄色达到饱和状态。黄色断点会在 9/10 宽度处终止,最后的 1/10 又是黑色。