網誌存檔

2010年11月16日 星期二

「轉貼」Cairo 圖形指南 (14) —— 變換

這一篇講述變換(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
#include
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 個單位長度。

旋轉

下面這個例子演示了一個簡單的旋轉變換。

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
#include
#include
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°。注意:旋轉角度是弧度,而非角度。

縮放

下面這個例子演示了一個對象的縮放變換。(作者還真是沉悶阿,相同的句式連用了 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
#include
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 作為因子進行縮放變換。

錯切

在下面的示例中,我們來實現錯切變換。

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

這一變換的數學形式可表示為:

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

這一變換的數學形式可表示為:

橢圓

下面的這個例子,畫了一個灰常複雜的形狀,它由一串旋轉的橢圓形成。

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
#include
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 <>
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 <>
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 個橢圓。橢圓可用圓的縮放變換而獲得。旋轉這個橢圓,這樣就創建了一個有趣的形狀。

星星

下面的示例繪製了一個又旋轉又縮放的星星,可惜不會發光呃。

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
#include
#include
int points[11][2] = {
{ 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 <>
cairo_line_to(cr, points[i][0], points[i][1]);
}
cairo_close_path(cr);
cairo_fill(cr);
cairo_stroke(cr);
if ( scale <>
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 <>
cairo_line_to(cr, points[i][0], points[i][1]);
}
cairo_close_path(cr);
cairo_fill(cr);
cairo_stroke(cr);

畫它!

1
2
3
4
5
if ( scale <>
delta = -delta;
} else if (scale > 0.99) {
delta = -delta;
}

這幾行代碼控制星星的縮放過程。

沒有留言:

張貼留言