Cairo 图形指南 (9) —— 变换

这 一篇讲述变换(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. }

这几行代码控制星星的缩放过程。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值