这 一篇讲述变换(Transformation) 仿射变换是由一些线性变换与平移构成的。线性变换可以写为单个矩阵的形式。旋转是让一个刚体绕一点运动的变换。缩放变换是让物体的形状扩大与减小,并且在 各个方向上的缩放因子都相同。平移变换将每个点沿着指定的方向移动常量距离。错切对于给定轴线,沿垂直于它的方向对物体进行移动的变换,并且在轴线的一侧 的移动距离大于另一侧。
下面这个例子演示了一个简单的平移变换。
01.
#include <cairo.h>
02.
#include <gtk/gtk.h>
03.
04.
05.
static
gboolean
06.
on_expose_event(GtkWidget *widget,
07.
GdkEventExpose *event,
08.
gpointer data)
09.
{
10.
cairo_t *cr;
11.
12.
cr = gdk_cairo_create (widget->window);
13.
14.
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
15.
cairo_rectangle(cr, 20, 20, 80, 50);
16.
cairo_stroke_preserve(cr);
17.
cairo_set_source_rgb(cr, 1, 1, 1);
18.
cairo_fill(cr);
19.
20.
cairo_translate(cr, 100, 100);
21.
22.
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
23.
cairo_rectangle(cr, 20, 20, 80, 50);
24.
cairo_stroke_preserve(cr);
25.
cairo_set_source_rgb(cr, 1, 1, 1);
26.
cairo_fill(cr);
27.
28.
cairo_destroy(cr);
29.
30.
return
FALSE;
31.
}
32.
33.
34.
int
main(
int
argc,
char
*argv[])
35.
{
36.
GtkWidget *window;
37.
38.
gtk_init(&argc, &argv);
39.
40.
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
41.
42.
g_signal_connect(window,
"expose-event"
,
43.
G_CALLBACK (on_expose_event), NULL);
44.
g_signal_connect(window,
"destroy"
,
45.
G_CALLBACK (gtk_main_quit), NULL);
46.
47.
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
48.
gtk_window_set_default_size(GTK_WINDOW(window), 300, 230);
49.
gtk_widget_set_app_paintable(window, TRUE);
50.
51.
gtk_widget_show_all(window);
52.
53.
gtk_main();
54.
55.
return
0;
56.
}
这个例子先是画了个矩形,然后将它平移并绘制出平移结果。
1.
cairo_translate(cr, 100, 100);
cairo_translate() 函数可通过平移用于空间的原点来修改当前的变换矩阵。在这个示例中,是将原点沿水平和竖直方向平移了 100 个单位长度。
旋转
下面这个例子演示了一个简单的旋转变换。
01.
#include <cairo.h>
02.
#include <gtk/gtk.h>
03.
#include <math.h>
04.
05.
06.
static
gboolean
07.
on_expose_event(GtkWidget *widget,
08.
GdkEventExpose *event,
09.
gpointer data)
10.
{
11.
cairo_t *cr;
12.
13.
cr = gdk_cairo_create (widget->window);
14.
15.
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
16.
cairo_rectangle(cr, 20, 20, 80, 50);
17.
cairo_stroke_preserve(cr);
18.
cairo_set_source_rgb(cr, 1, 1, 1);
19.
cairo_fill(cr);
20.
21.
cairo_translate(cr, 150, 100);
22.
cairo_rotate(cr, M_PI/2);
23.
24.
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
25.
cairo_rectangle(cr, 20, 20, 80, 50);
26.
cairo_stroke_preserve(cr);
27.
cairo_set_source_rgb(cr, 1, 1, 1);
28.
cairo_fill(cr);
29.
30.
cairo_destroy(cr);
31.
32.
return
FALSE;
33.
}
34.
35.
36.
int
main(
int
argc,
char
*argv[])
37.
{
38.
GtkWidget *window;
39.
40.
gtk_init(&argc, &argv);
41.
42.
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
43.
44.
g_signal_connect(window,
"expose-event"
,
45.
G_CALLBACK (on_expose_event), NULL);
46.
g_signal_connect(window,
"destroy"
,
47.
G_CALLBACK (gtk_main_quit), NULL);
48.
49.
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
50.
gtk_window_set_default_size(GTK_WINDOW(window), 300, 230);
51.
gtk_widget_set_app_paintable(window, TRUE);
52.
53.
gtk_widget_show_all(window);
54.
55.
gtk_main();
56.
57.
return
0;
58.
}
这个例子先是画了个矩形,然后对它进行了平移和旋转变换,并绘制出变换结果。
1.
cairo_translate(cr, 150, 100);
2.
cairo_rotate(cr, M_PI/2);
首先对用户空间的原点进行平移,然后再围绕它旋转 180°。注意:旋转角度是弧度,而非角度。
缩放
下面这个例子演示了一个对象的缩放变换。(作者还真是沉闷阿,相同的句式连用了 n 次,这个可怜的矩形被折腾的痛苦不堪!)
01.
#include <cairo.h>
02.
#include <gtk/gtk.h>
03.
04.
05.
static
gboolean
06.
on_expose_event(GtkWidget *widget,
07.
GdkEventExpose *event,
08.
gpointer data)
09.
{
10.
cairo_t *cr;
11.
12.
cr = gdk_cairo_create (widget->window);
13.
14.
cairo_save(cr);
15.
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
16.
cairo_rectangle(cr, 20, 30, 80, 50);
17.
cairo_stroke_preserve(cr);
18.
cairo_set_source_rgb(cr, 1, 1, 1);
19.
cairo_fill(cr);
20.
cairo_restore(cr);
21.
22.
cairo_save(cr);
23.
cairo_translate(cr, 130, 30);
24.
cairo_scale(cr, 0.7, 0.7);
25.
26.
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
27.
cairo_rectangle(cr, 0, 0, 80, 50);
28.
cairo_stroke_preserve(cr);
29.
cairo_set_source_rgb(cr, 1, 1, 1);
30.
cairo_fill(cr);
31.
cairo_restore(cr);
32.
33.
cairo_save(cr);
34.
cairo_translate(cr, 220, 30);
35.
cairo_scale(cr, 1.5, 1.5);
36.
37.
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
38.
cairo_rectangle(cr, 0, 0, 80, 50);
39.
cairo_stroke_preserve(cr);
40.
cairo_set_source_rgb(cr, 1, 1, 1);
41.
cairo_fill(cr);
42.
cairo_restore(cr);
43.
44.
cairo_destroy(cr);
45.
46.
return
FALSE;
47.
}
48.
49.
50.
int
main(
int
argc,
char
*argv[])
51.
{
52.
GtkWidget *window;
53.
54.
gtk_init(&argc, &argv);
55.
56.
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
57.
58.
g_signal_connect(window,
"expose-event"
,
59.
G_CALLBACK (on_expose_event), NULL);
60.
g_signal_connect(window,
"destroy"
,
61.
G_CALLBACK (gtk_main_quit), NULL);
62.
63.
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
64.
gtk_window_set_default_size(GTK_WINDOW(window), 360, 140);
65.
gtk_widget_set_app_paintable(window, TRUE);
66.
67.
gtk_widget_show_all(window);
68.
69.
gtk_main();
70.
71.
return
0;
72.
}
这次的例子是用指定的缩放因子,把初始的矩形变的小了点,然后又把它变的大了点。
1.
cairo_save(cr);
2.
...
3.
cairo_restore(cr);
若对初始的矩形完成两次缩放操作,需要将初始的变换矩阵保存一下,这个可通过 cairo_save() 和 cairo_restore() 函数来实现。
1.
cairo_translate(cr, 130, 30);
2.
cairo_scale(cr, 0.7, 0.7);
这里首先将用户空间的原点平移了一下,然后又开始用 0.7 作为因子进行缩放变换。
错切
在下面的示例中,我们来实现错切变换。
01.
#include <cairo.h>
02.
#include <gtk/gtk.h>
03.
04.
05.
static
gboolean
06.
on_expose_event(GtkWidget *widget,
07.
GdkEventExpose *event,
08.
gpointer data)
09.
{
10.
cairo_t *cr;
11.
cairo_matrix_t matrix;
12.
13.
cr = gdk_cairo_create (widget->window);
14.
15.
cairo_save(cr);
16.
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
17.
cairo_rectangle(cr, 20, 30, 80, 50);
18.
cairo_stroke_preserve(cr);
19.
cairo_set_source_rgb(cr, 1, 1, 1);
20.
cairo_fill(cr);
21.
cairo_restore(cr);
22.
23.
cairo_save(cr);
24.
cairo_translate(cr, 130, 30);
25.
cairo_matrix_init(&matrix,
26.
1.0, 0.5,
27.
0.0, 1.0,
28.
0.0, 0.0);
29.
30.
cairo_transform (cr, &matrix);
31.
32.
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
33.
cairo_rectangle(cr, 0, 0, 80, 50);
34.
cairo_stroke_preserve(cr);
35.
cairo_set_source_rgb(cr, 1, 1, 1);
36.
cairo_fill(cr);
37.
cairo_restore(cr);
38.
39.
cairo_save(cr);
40.
cairo_translate(cr, 220, 30);
41.
cairo_matrix_init(&matrix,
42.
1.0, 0.0,
43.
0.7, 1.0,
44.
0.0, 0.0);
45.
46.
cairo_transform(cr, &matrix);
47.
48.
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
49.
cairo_rectangle(cr, 0, 0, 80, 50);
50.
cairo_stroke_preserve(cr);
51.
cairo_set_source_rgb(cr, 1, 1, 1);
52.
cairo_fill(cr);
53.
cairo_restore(cr);
54.
55.
cairo_destroy(cr);
56.
57.
return
FALSE;
58.
}
59.
60.
61.
int
main(
int
argc,
char
*argv[])
62.
{
63.
GtkWidget *window;
64.
65.
gtk_init(&argc, &argv);
66.
67.
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
68.
69.
g_signal_connect(window,
"expose-event"
,
70.
G_CALLBACK(on_expose_event), NULL);
71.
g_signal_connect(window,
"destroy"
,
72.
G_CALLBACK(gtk_main_quit), NULL);
73.
74.
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
75.
gtk_window_set_default_size(GTK_WINDOW(window), 360, 140);
76.
gtk_widget_set_app_paintable(window, TRUE);
77.
78.
gtk_widget_show_all(window);
79.
80.
gtk_main();
81.
82.
return
0;
83.
}
这份示例代码实现了两次错切变换。对于错切变换,没有特定的函数,必须使用矩阵来实现。
1.
cairo_matrix_t matrix;
这个 cairo_matrix 是存储仿射变换的数据结构。
1.
cairo_matrix_init(&matrix,
2.
1.0, 0.5,
3.
0.0, 1.0,
4.
0.0, 0.0);
5.
6.
cairo_transform (cr, &matrix);
这一变换的数学形式可表示为:
1.
cairo_matrix_init(&matrix,
2.
1.0, 0.0,
3.
0.7, 1.0,
4.
0.0, 0.0);
5.
6.
cairo_transform(cr, &matrix);
这一变换的数学形式可表示为:
椭圆
下面的这个例子,画了一个灰常复杂的形状,它由一串旋转的椭圆形成。
01.
#include <cairo.h>
02.
#include <gtk/gtk.h>
03.
04.
static
gboolean
05.
on_expose_event(GtkWidget *widget,
06.
GdkEventExpose *event,
07.
gpointer data)
08.
{
09.
cairo_t *cr;
10.
11.
cr = gdk_cairo_create(widget->window);
12.
13.
gint width, height;
14.
gtk_window_get_size(GTK_WINDOW(widget), &width, &height);
15.
16.
cairo_set_line_width(cr, 0.5);
17.
cairo_translate(cr, width/2, height/2);
18.
cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
19.
cairo_stroke(cr);
20.
21.
gint i;
22.
23.
cairo_save(cr);
24.
for
( i = 0; i < 36; i++) {
25.
cairo_rotate(cr, i*M_PI/36);
26.
cairo_scale(cr, 0.3, 1);
27.
cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
28.
cairo_restore(cr);
29.
cairo_stroke(cr);
30.
cairo_save(cr);
31.
}
32.
33.
cairo_destroy(cr);
34.
35.
return
FALSE;
36.
}
37.
38.
39.
int
main(
int
argc,
char
*argv[])
40.
{
41.
42.
GtkWidget *window;
43.
44.
gtk_init(&argc, &argv);
45.
46.
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
47.
48.
g_signal_connect(G_OBJECT(window),
"expose-event"
,
49.
G_CALLBACK(on_expose_event), NULL);
50.
g_signal_connect(G_OBJECT(window),
"destroy"
,
51.
G_CALLBACK(gtk_main_quit), NULL);
52.
53.
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
54.
gtk_window_set_default_size(GTK_WINDOW(window), 350, 250);
55.
56.
gtk_widget_set_app_paintable(window, TRUE);
57.
gtk_widget_show_all(window);
58.
59.
gtk_main();
60.
61.
return
0;
62.
}
1.
cairo_translate(cr, width/2, height/2);
2.
cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
3.
cairo_stroke(cr);
在 GTK+ 的窗口中间,绘制了一个圆,它是那些椭圆的边界圆。
01.
cairo_save(cr);
02.
for
( i = 0; i < 36; i++) {
03.
cairo_rotate(cr, i*M_PI/36);
04.
cairo_scale(cr, 0.3, 1);
05.
cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
06.
cairo_restore(cr);
07.
cairo_stroke(cr);
08.
cairo_save(cr);
09.
}
沿着边界圆画 36 个椭圆。椭圆可用圆的缩放变换而获得。旋转这个椭圆,这样就创建了一个有趣的形状。
星星
下面的示例绘制了一个又旋转又缩放的星星,可惜不会发光呃。
001.
#include <cairo.h>
002.
#include <gtk/gtk.h>
003.
#include <math.h>
004.
005.
int
points[11][2] = {
006.
{ 0, 85 },
007.
{ 75, 75 },
008.
{ 100, 10 },
009.
{ 125, 75 },
010.
{ 200, 85 },
011.
{ 150, 125 },
012.
{ 160, 190 },
013.
{ 100, 150 },
014.
{ 40, 190 },
015.
{ 50, 125 },
016.
{ 0, 85 }
017.
};
018.
019.
020.
static
gboolean
021.
on_expose_event(GtkWidget *widget,
022.
GdkEventExpose *event,
023.
gpointer data)
024.
{
025.
cairo_t *cr;
026.
027.
static
gdouble angle = 0;
028.
static
gdouble scale = 1;
029.
static
gdouble delta = 0.01;
030.
031.
gint width, height;
032.
gtk_window_get_size(GTK_WINDOW(widget), &width, &height);
033.
034.
cr = gdk_cairo_create(widget->window);
035.
036.
cairo_set_source_rgb(cr, 0, 0.44, 0.7);
037.
cairo_set_line_width(cr, 1);
038.
039.
cairo_translate(cr, width / 2, height / 2 );
040.
cairo_rotate(cr, angle);
041.
cairo_scale(cr, scale, scale);
042.
043.
gint i;
044.
045.
for
( i = 0; i < 10; i++ ) {
046.
cairo_line_to(cr, points[i][0], points[i][1]);
047.
}
048.
049.
cairo_close_path(cr);
050.
cairo_fill(cr);
051.
cairo_stroke(cr);
052.
053.
if
( scale < 0.01 ) {
054.
delta = -delta;
055.
}
else
if
(scale > 0.99) {
056.
delta = -delta;
057.
}
058.
059.
scale += delta;
060.
angle += 0.01;
061.
062.
cairo_destroy(cr);
063.
064.
return
FALSE;
065.
}
066.
067.
static
gboolean
068.
time_handler (GtkWidget *widget)
069.
{
070.
if
(widget->window == NULL)
return
FALSE;
071.
gtk_widget_queue_draw(widget);
072.
return
TRUE;
073.
}
074.
075.
076.
int
main(
int
argc,
char
*argv[])
077.
{
078.
079.
GtkWidget *window;
080.
081.
gtk_init(&argc, &argv);
082.
083.
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
084.
085.
gtk_widget_add_events (window, GDK_BUTTON_PRESS_MASK);
086.
087.
g_signal_connect(window,
"expose-event"
,
088.
G_CALLBACK(on_expose_event), NULL);
089.
g_signal_connect(window,
"destroy"
,
090.
G_CALLBACK(gtk_main_quit), NULL);
091.
092.
093.
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
094.
gtk_window_set_title(GTK_WINDOW(window),
"star"
);
095.
gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
096.
gtk_widget_set_app_paintable(window, TRUE);
097.
098.
g_timeout_add(10, (GSourceFunc) time_handler, (gpointer) window);
099.
100.
gtk_widget_show_all(window);
101.
102.
gtk_main();
103.
104.
return
0;
105.
}
在这个示例中,画了一颗星星,然后平移它,旋转它,缩放它。
1.
cairo_translate(cr, width / 2, height / 2 );
2.
cairo_rotate(cr, angle);
3.
cairo_scale(cr, scale, scale
先将星星平移到窗口中间,旋转它,缩放它。(作者还真不是一般的罗嗦)
1.
for
( i = 0; i < 10; i++ ) {
2.
cairo_line_to(cr, points[i][0], points[i][1]);
3.
}
4.
5.
cairo_close_path(cr);
6.
cairo_fill(cr);
7.
cairo_stroke(cr);
画它!
1.
if
( scale < 0.01 ) {
2.
delta = -delta;
3.
}
else
if
(scale > 0.99) {
4.
delta = -delta;
5.
}
这几行代码控制星星的缩放过程。