Tom 发表于 2011-2-3 02:19:23

[转]Cairo 图形指南 (9) —— 变换

   

这一篇讲述变换(Transformation)仿射变换是由一些线性变换与平移构成的。线性变换可以写为单个矩阵的形式。旋转是让一个刚体绕一点运动的变换。缩放变换是让物体的形状扩大与减小,并且在各个方向上的缩放因子都相同。平移变换将每个点沿着指定的方向移动常量距离。错切对于给定轴线,沿垂直于它的方向对物体进行移动的变换,并且在轴线的一侧的移动距离大于另一侧。
——上述内容来自维基百科全书
平移
下面这个例子演示了一个简单的平移变换。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#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.6, 0.6, 0.6);
cairo_rectangle(cr, 20, 20, 80, 50);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);

cairo_translate(cr, 100, 100);

cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
cairo_rectangle(cr, 20, 20, 80, 50);
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;

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

gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 300, 230);
gtk_widget_set_app_paintable(window, TRUE);

gtk_widget_show_all(window);

gtk_main();

return 0;
}



这个例子先是画了个矩形,然后将它平移并绘制出平移结果。
?
1
cairo_translate(cr, 100, 100);



cairo_translate() 函数可通过平移用于空间的原点来修改当前的变换矩阵。在这个示例中,是将原点沿水平和竖直方向平移了 100 个单位长度。
http://zetcode.com/tutorials/cairographicstutorial/images/translate.png
旋转
下面这个例子演示了一个简单的旋转变换。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#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.6, 0.6, 0.6);
cairo_rectangle(cr, 20, 20, 80, 50);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);

cairo_translate(cr, 150, 100);
cairo_rotate(cr, M_PI/2);

cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
cairo_rectangle(cr, 20, 20, 80, 50);
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;

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

gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 300, 230);
gtk_widget_set_app_paintable(window, TRUE);

gtk_widget_show_all(window);

gtk_main();

return 0;
}



这个例子先是画了个矩形,然后对它进行了平移和旋转变换,并绘制出变换结果。
?
1
2
cairo_translate(cr, 150, 100);
cairo_rotate(cr, M_PI/2);



首先对用户空间的原点进行平移,然后再围绕它旋转 180°。注意:旋转角度是弧度,而非角度。
http://zetcode.com/tutorials/cairographicstutorial/images/rotate.png
缩放
下面这个例子演示了一个对象的缩放变换。(作者还真是沉闷阿,相同的句式连用了 n 次,这个可怜的矩形被折腾的痛苦不堪!)
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#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_save(cr);
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
cairo_rectangle(cr, 20, 30, 80, 50);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);
cairo_restore(cr);

cairo_save(cr);
cairo_translate(cr, 130, 30);
cairo_scale(cr, 0.7, 0.7);

cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
cairo_rectangle(cr, 0, 0, 80, 50);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);
cairo_restore(cr);

cairo_save(cr);
cairo_translate(cr, 220, 30);
cairo_scale(cr, 1.5, 1.5);

cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
cairo_rectangle(cr, 0, 0, 80, 50);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);
cairo_restore(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(window, "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), 360, 140);
gtk_widget_set_app_paintable(window, TRUE);

gtk_widget_show_all(window);

gtk_main();

return 0;
}



这次的例子是用指定的缩放因子,把初始的矩形变的小了点,然后又把它变的大了点。
?
1
2
3
cairo_save(cr);
...
cairo_restore(cr);



若对初始的矩形完成两次缩放操作,需要将初始的变换矩阵保存一下,这个可通过 cairo_save() 和 cairo_restore() 函数来实现。
?
1
2
cairo_translate(cr, 130, 30);
cairo_scale(cr, 0.7, 0.7);



这里首先将用户空间的原点平移了一下,然后又开始用 0.7 作为因子进行缩放变换。
http://zetcode.com/tutorials/cairographicstutorial/images/scale.png

错切
在下面的示例中,我们来实现错切变换。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <cairo.h>
#include <gtk/gtk.h>


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

cr = gdk_cairo_create (widget->window);

cairo_save(cr);
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
cairo_rectangle(cr, 20, 30, 80, 50);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);
cairo_restore(cr);

cairo_save(cr);
cairo_translate(cr, 130, 30);
cairo_matrix_init(&matrix,
      1.0, 0.5,
      0.0, 1.0,
      0.0, 0.0);

cairo_transform (cr, &matrix);

cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
cairo_rectangle(cr, 0, 0, 80, 50);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);
cairo_restore(cr);

cairo_save(cr);
cairo_translate(cr, 220, 30);
cairo_matrix_init(&matrix,
      1.0, 0.0,
      0.7, 1.0,
      0.0, 0.0);

cairo_transform(cr, &matrix);

cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
cairo_rectangle(cr, 0, 0, 80, 50);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);
cairo_restore(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(window, "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), 360, 140);
gtk_widget_set_app_paintable(window, TRUE);

gtk_widget_show_all(window);

gtk_main();

return 0;
}



这份示例代码实现了两次错切变换。对于错切变换,没有特定的函数,必须使用矩阵来实现。
?
1
cairo_matrix_t matrix;



这个 cairo_matrix 是存储仿射变换的数据结构。
?
1
2
3
4
5
6
cairo_matrix_init(&matrix,
   1.0, 0.5,
   0.0, 1.0,
   0.0, 0.0);

cairo_transform (cr, &matrix);



这一变换的数学形式可表示为:

http://latex.codecogs.com/gif.latex?%5Cbegin%7Bbmatrix%7Dx_%7Bnew%7D%5C%5C%20y_%7Bnew%7D%5C%5C%201.0%5Cend%7Bbmatrix%7D%3D%5Cbegin%7Bbmatrix%7D%7B1.0%20&%200.0%20&%200.0%7D%5C%5C%7B0.5%20&%201.0%20&%200.0%7D%5C%5C%7B0.0%20&%200.0%20&%201.0%7D%5Cend%7Bbmatrix%7D%5Cbegin%7Bbmatrix%7Dx%5C%5C%20y%5C%5C%201.0%5Cend%7Bbmatrix%7D
?
1
2
3
4
5
6
cairo_matrix_init(&matrix,
    1.0, 0.0,
    0.7, 1.0,
    0.0, 0.0);

cairo_transform(cr, &matrix);




这一变换的数学形式可表示为:

http://latex.codecogs.com/gif.latex?%5Cbegin%7Bbmatrix%7Dx_%7Bnew%7D%5C%5C%20y_%7Bnew%7D%5C%5C%201.0%5Cend%7Bbmatrix%7D%3D%5Cbegin%7Bbmatrix%7D%7B1.0%20&%200.7%20&%200.0%7D%5C%5C%7B0.0%20&%201.0%20&%200.0%7D%5C%5C%7B0.0%20&%200.0%20&%201.0%7D%5Cend%7Bbmatrix%7D%5Cbegin%7Bbmatrix%7Dx%5C%5C%20y%5C%5C%201.0%5Cend%7Bbmatrix%7D
http://zetcode.com/tutorials/cairographicstutorial/images/shear.png

椭圆
下面的这个例子,画了一个灰常复杂的形状,它由一串旋转的椭圆形成。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#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 width, height;
gtk_window_get_size(GTK_WINDOW(widget), &width, &height);

cairo_set_line_width(cr, 0.5);
cairo_translate(cr, width/2, height/2);
cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
cairo_stroke(cr);

gint i;

cairo_save(cr);
for ( i = 0; i < 36; i++) {
      cairo_rotate(cr, i*M_PI/36);
      cairo_scale(cr, 0.3, 1);
      cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
      cairo_restore(cr);
      cairo_stroke(cr);
      cairo_save(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), 350, 250);

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

gtk_main();

return 0;
}



?
1
2
3
cairo_translate(cr, width/2, height/2);
cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
cairo_stroke(cr);



在 GTK+ 的窗口中间,绘制了一个圆,它是那些椭圆的边界圆。

?
1
2
3
4
5
6
7
8
9
cairo_save(cr);
for ( i = 0; i < 36; i++) {
    cairo_rotate(cr, i*M_PI/36);
    cairo_scale(cr, 0.3, 1);
    cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
    cairo_restore(cr);
    cairo_stroke(cr);
    cairo_save(cr);
}



沿着边界圆画 36 个椭圆。椭圆可用圆的缩放变换而获得。旋转这个椭圆,这样就创建了一个有趣的形状。
http://zetcode.com/tutorials/cairographicstutorial/images/ellipserotate.png

星星
下面的示例绘制了一个又旋转又缩放的星星,可惜不会发光呃。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#include <cairo.h>
#include <gtk/gtk.h>
#include <math.h>

int points = {
    { 0, 85 },
    { 75, 75 },
    { 100, 10 },
    { 125, 75 },
    { 200, 85 },
    { 150, 125 },
    { 160, 190 },
    { 100, 150 },
    { 40, 190 },
    { 50, 125 },
    { 0, 85 }
};


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

static gdouble angle = 0;
static gdouble scale = 1;
static gdouble delta = 0.01;

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

cr = gdk_cairo_create(widget->window);

cairo_set_source_rgb(cr, 0, 0.44, 0.7);
cairo_set_line_width(cr, 1);

cairo_translate(cr, width / 2, height / 2 );
cairo_rotate(cr, angle);
cairo_scale(cr, scale, scale);

gint i;

for ( i = 0; i < 10; i++ ) {
      cairo_line_to(cr, points, points);
}

cairo_close_path(cr);
cairo_fill(cr);
cairo_stroke(cr);

if ( scale < 0.01 ) {
      delta = -delta;
} else if (scale > 0.99) {
      delta = -delta;
}

scale += delta;
angle += 0.01;

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;

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


gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_title(GTK_WINDOW(window), "star");
gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
gtk_widget_set_app_paintable(window, TRUE);

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

gtk_widget_show_all(window);

gtk_main();

return 0;
}



在这个示例中,画了一颗星星,然后平移它,旋转它,缩放它。
?
1
2
3
cairo_translate(cr, width / 2, height / 2 );
cairo_rotate(cr, angle);
cairo_scale(cr, scale, scale



先将星星平移到窗口中间,旋转它,缩放它。(作者还真不是一般的罗嗦)
?
1
2
3
4
5
6
7
for ( i = 0; i < 10; i++ ) {
    cairo_line_to(cr, points, points);
}

cairo_close_path(cr);
cairo_fill(cr);
cairo_stroke(cr);



画它!
?
1
2
3
4
5
if ( scale < 0.01 ) {
    delta = -delta;
} else if (scale > 0.99) {
    delta = -delta;
}



这几行代码控制星星的缩放过程。
http://zetcode.com/tutorials/cairographicstutorial/images/star.gif
原文地址:http://liyanrui.is-programmer.com/2010/4/6/cairo-transformation.16627.html


页: [1]
查看完整版本: [转]Cairo 图形指南 (9) —— 变换