基本绘图
这一部分讲述如何绘制一些简单的图元,包括直线、填充与笔画操作、虚线、线端(Cap)与线的交合等图形的绘制方法。
直线段
直线段是非常基础的矢量图形对象。画一条直线段,需要调用两个函数:cairo_move_to()
函数,用于设置线段起点;cairo_line_to()
用于设定线段终点。
#include <cairo.h>
#include <gtk/gtk.h>
double coordx
[
100
] ;
double coordy
[
100
] ;
int count =
0 ;
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,
0.5
) ;
int i, j;
for
( i =
0 ; i <= count -
1 ; i++
)
{
for
( j =
0 ; j <= count
-1 ; j++
)
{
cairo_move_to
( cr, coordx
[ i
] , coordy
[ i
]
) ;
cairo_line_to
( cr, coordx
[ j
] , coordy
[ j
]
) ;
}
}
count =
0 ;
cairo_stroke
( cr
) ;
cairo_destroy
( cr
) ;
return
FALSE ;
}
gboolean clicked
( GtkWidget *widget, GdkEventButton *event,
gpointer user_data
)
{
if
( event->button ==
1
)
{
coordx
[ count
] = event->x;
coordy
[ count++
] = event->y;
}
if
( event->button ==
3
)
{
gtk_widget_queue_draw
( widget
) ;
}
return
TRUE ;
}
int
main
(
int argc,
char *argv
[
]
)
{
GtkWidget *window;
gtk_init
( &argc, &argv
) ;
window = gtk_window_new
( GTK_WINDOW_TOPLEVEL
) ;
gtk_widget_add_events
( window, GDK_BUTTON_PRESS_MASK
) ;
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
( window,
"button-press-event" ,
G_CALLBACK
( clicked
) ,
NULL
) ;
gtk_window_set_position
( GTK_WINDOW
( window
) , GTK_WIN_POS_CENTER
) ;
gtk_window_set_title
( GTK_WINDOW
( window
) ,
"lines"
) ;
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 ;
}
该示例会创建一个支持鼠标交互绘制直线段的 GTK+ 窗口。在窗口中使用鼠标左键随便点几下,每一次点击时,光标位置的坐标都会被记入长度为 100 的数组;然后点击鼠标右键,所有由鼠标左键点击所得到的点会被彼此连接形成直线段;在窗口中再次点击鼠标右键时,会对窗口绘图区域进行清除。
下面对该示例程序代码进行分析:
cairo_set_source_rgb
( cr,
0 ,
0 ,
0
) ;
cairo_set_line_width
( cr,
0.5
) ;
设置颜色为黑色,线宽为 0.5pt 为参数,绘制直线段。
int i, j;
for
( i =
0 ; i <= count -
1 ; i++
)
{
for
( j =
0 ; j <= count
-1 ; j++
)
{
cairo_move_to
( cr, coordx
[ i
] , coordy
[ i
]
) ;
cairo_line_to
( cr, coordx
[ j
] , coordy
[ j
]
) ;
}
}
用 cairo_move_to() 和 cairo_line_to() 函数在 cr 中定义绘图路径 (path),连接 coordx[] 和 coordy[] 所记录的每个点。
cairo_stroke() 函数会将 cr 中的路径绘制出来。
g_signal_connect
( window,
"button-press-event" ,
G_CALLBACK
( clicked
) ,
NULL
) ;
设定 button-press-event
事件的回调函数为 clicked ()
。
if
( event->button ==
1
)
{
coordx
[ count
] = event->x;
coordy
[ count++
] = event->y;
}
在 clicked ()
函数中,当鼠标左键点击事件发生时,讲光标所在位置的 x 和 y 坐标分别记入数组 coordx 和 coordy
。
if
( event->button ==
3
)
{
gtk_widget_queue_draw
( widget
) ;
}
在 clicked ()
函数中,当鼠标右键单击时,调用 gtk_widget_queue_draw () 函数重绘窗口区域。
描绘 (Stroke) 与填充 (Fill)
描绘 (Stroke) 可以绘制形状的轮廓,填充 (Fill) 则用于向形状内部灌注颜色。
#include <math.h>
#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_line_width
( cr,
9
) ;
cairo_set_source_rgb
( cr,
0.69 ,
0.19 ,
0
) ;
cairo_arc
( cr, width /
2 , height /
2 ,
( width < height ? width : height
) /
2 -
10 ,
0 ,
2 * M_PI
) ;
cairo_stroke_preserve
( cr
) ;
cairo_set_source_rgb
( cr,
0.3 ,
0.4 ,
0.6
) ;
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
) ,
200 ,
150
) ;
gtk_widget_set_app_paintable
( window,
TRUE
) ;
gtk_widget_show_all
( window
) ;
gtk_main
(
) ;
return
0 ;
}
这个示例绘制一个内部填充灰色的圆。
下面对代码进行解析:
之所以引入这个头文件,是因为程序中使用了圆周率常量 M_PI。
int width, height;
gtk_window_get_size
( GTK_WINDOW
( widget
) , &width, &height
) ;
获取窗口的宽度与高度尺寸。程序中将使用这些值作为绘制圆形的参考尺寸,以实现窗口尺寸变化时,所绘制的圆的尺寸也会相应变化。
cairo_set_source_rgb
( cr,
0.69 ,
0.19 ,
0
) ;
cairo_arc
( cr, width /
2 , height /
2 ,
( width < height ? width : height
) /
2 -
10 ,
0 ,
2 * M_PI
) ;
cairo_stroke_preserve
( cr
) ;
描绘圆的轮廓。这里要注意一下 cairo_stroke_preserve () 函数与 cairo_stroke () 函数的区别(最好的办法是用后者替换一下前者,看看程序执行效果)。cairo_stroke_preserve () 函数会将它绘制的路径依然保存在 cairo 环境中,而 cairo_stroke () 所绘制的路径,在绘制完成后,就从 cairo的环境中清除了。
cairo_set_source_rgb
( cr,
0.3 ,
0.4 ,
0.6
) ;
cairo_fill
( cr
) ;
对使用 cairo_stroke_preserve () 函数绘制的路径进行蓝色填充。
虚线 (Dash)
每条线都可以用不同的虚线笔 (dash pen) 来画。虚线模式是通过 cairo_set_dash () 函数来设定。模式类型通过一个数组来定义,数组中的值均为正数,它们用于设置虚线的虚部分与实部分。数组的长度与偏移量可以在程序中设定。如果数组的长度 为 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
) ;
cairo_set_source_rgba
( cr,
0 ,
0 ,
0 ,
1
) ;
static
const
double dashed1
[
] =
{
4.0 ,
1.0
} ;
static
int len1 =
sizeof
( dashed1
) /
sizeof
( dashed1
[
0
]
) ;
static
const
double dashed2
[
] =
{
4.0 ,
10.0 ,
4.0
} ;
static
int len2 =
sizeof
( dashed2
) /
sizeof
( dashed2
[
0
]
) ;
static
const
double dashed3
[
] =
{
1.0
} ;
cairo_set_line_width
( cr,
1.5
) ;
cairo_set_dash
( cr, dashed1, len1,
0
) ;
cairo_move_to
( cr,
40 ,
60
) ;
cairo_line_to
( cr,
360 ,
60
) ;
cairo_stroke
( cr
) ;
cairo_set_dash
( cr, dashed2, len2,
10
) ;
cairo_move_to
( cr,
40 ,
120
) ;
cairo_line_to
( cr,
360 ,
120
) ;
cairo_stroke
( cr
) ;
cairo_set_dash
( cr, dashed3,
1 ,
0
) ;
cairo_move_to
( cr,
40 ,
180
) ;
cairo_line_to
( cr,
360 ,
180
) ;
cairo_stroke
( 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
) ,
400 ,
300
) ;
gtk_widget_show_all
( window
) ;
gtk_main
(
) ;
return
0 ;
}
该示例演示了三种虚线模式的设置及绘制。
下面分析一下关键代码。
static
const
double dashed1
[
] =
{
4.0 ,
1.0
} ;
设定第一条虚线的模式,它的实部是 4 个像素,虚部是 1 个像素。
static
int len1 =
sizeof
( dashed1
) /
sizeof
( dashed1
[
0
]
) ;
计算数组 dashed1 的长度。
cairo_set_dash
( cr, dashed1, len1,
0
) ;
设置虚线模式。
darea = gtk_drawing_area_new
(
) ;
gtk_container_add
( GTK_CONTAINER
( window
) , darea
) ;
这次,我们是在 drawing_area 部件上绘图,不再是窗口区域了。
线帽 (Line caps)
线帽是针对直线段的端点形状而言的,分为三种:
- CAIRO_LINE_CAP_SQUARE
- CAIRO_LINE_CAP_ROUND
- CAIRO_LINE_CAP_BUTT
对应形状如下图所示:
同一条直线段,CAIRO_LINE_CAP_SQUARE 线帽与 CAIRO_LINE_CAP_BUTT 线帽会导致直线段长度有所差别,前者会比后者长一个线宽尺寸。
#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
) ;
cairo_set_source_rgba
( cr,
0 ,
0 ,
0 ,
1
) ;
cairo_set_line_width
( cr,
10
) ;
cairo_set_line_cap
( cr, CAIRO_LINE_CAP_BUTT
) ;
cairo_move_to
( cr,
40 ,
60
) ;
cairo_line_to
( cr,
360 ,
60
) ;
cairo_stroke
( cr
) ;
cairo_set_line_cap
( cr, CAIRO_LINE_CAP_ROUND
) ;
cairo_move_to
( cr,
40 ,
150
) ;
cairo_line_to
( cr,
360 ,
150
) ;
cairo_stroke
( cr
) ;
cairo_set_line_cap
( cr, CAIRO_LINE_CAP_SQUARE
) ;
cairo_move_to
( cr,
40 ,
240
) ;
cairo_line_to
( cr,
360 ,
240
) ;
cairo_stroke
( cr
) ;
cairo_set_line_width
( cr,
1.5
) ;
cairo_move_to
( cr,
40 ,
40
) ;
cairo_line_to
( cr,
40 ,
260
) ;
cairo_stroke
( cr
) ;
cairo_move_to
( cr,
360 ,
40
) ;
cairo_line_to
( cr,
360 ,
260
) ;
cairo_stroke
( cr
) ;
cairo_move_to
( cr,
365 ,
40
) ;
cairo_line_to
( cr,
365 ,
260
) ;
cairo_stroke
( cr
) ;
cairo_destroy
( cr
) ;
return
FALSE ;
}
该示例绘制三条具有不同线帽的直线段,同时也展示了不同线帽对线的长度的影响。
下面对关键代码进行简单分析:
cairo_set_line_width
( cr,
10
) ;
设置线的宽度为 10px。
cairo_set_line_cap
( cr, CAIRO_LINE_CAP_ROUND
) ;
cairo_move_to
( cr,
40 ,
150
) ;
cairo_line_to
( cr,
360 ,
150
) ;
cairo_stroke
( cr
) ;
画了一条线帽为 CAIRO_LINE_CAP_ROUND 的直线段。
cairo_move_to
( cr,
40 ,
40
) ;
cairo_line_to
( cr,
40 ,
260
) ;
cairo_stroke
( cr
) ;
这是三条竖线之一,用于表现线帽对线的长度的影响。
线的交合 (Line joins)
线的交合存在以下三种风格:
- CAIRO_LINE_JOIN_MITER
- CAIRO_LINE_JOIN_BEVEL
- CAIRO_LINE_JOIN_ROUND
对应形状如下图所示。
#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
) ;
cairo_set_source_rgb
( cr,
0.1 ,
0 ,
0
) ;
cairo_rectangle
( cr,
30 ,
30 ,
100 ,
100
) ;
cairo_set_line_width
( cr,
14
) ;
cairo_set_line_join
( cr, CAIRO_LINE_JOIN_MITER
) ;
cairo_stroke
( cr
) ;
cairo_rectangle
( cr,
160 ,
30 ,
100 ,
100
) ;
cairo_set_line_width
( cr,
14
) ;
cairo_set_line_join
( cr, CAIRO_LINE_JOIN_BEVEL
) ;
cairo_stroke
( cr
) ;
cairo_rectangle
( cr,
100 ,
160 ,
100 ,
100
) ;
cairo_set_line_width
( cr,
14
) ;
cairo_set_line_join
( cr, CAIRO_LINE_JOIN_ROUND
) ;
cairo_stroke
( 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
) ,
300 ,
280
) ;
gtk_widget_show_all
( window
) ;
gtk_main
(
) ;
return
0 ;
}
该示例采用不同的交合类型绘制了三个矩形。
下面对关键代码进行简单分析:
cairo_rectangle
( cr,
30 ,
30 ,
100 ,
100
) ;
cairo_set_line_width
( cr,
14
) ;
cairo_set_line_join
( cr, CAIRO_LINE_JOIN_MITER
) ;
cairo_stroke
( cr
) ;
绘制了一个线宽为 14px,交合类型为 CAIRO_LINE_JOIN_MITER 的矩形。