這一篇講述變換(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; } |
這幾行代碼控制星星的縮放過程。
沒有留言:
張貼留言