Cairo 圖形指南 (3) —— 變換

這一篇講述變換(Transformation) 仿射變換是由一些線性變換與平移構成的。線性變換可以寫爲單個矩陣的形式。旋轉是讓一個剛體繞一點運動的變換。縮放變換是讓物體的形狀擴大與減小,並且在各個方向上的縮放因子都相同。平移變換將每個點沿着指定的方向移動常量距離。錯切對於給定軸線,沿垂直於它的方向對物體進行移動的變換,並且在軸線的一側的移動距離大於另一側。

——上述內容來自維基百科全書

平移

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

#include <cairo.h>
#include <gtk/gtk.h>
 
 
staticgboolean
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);
 
  returnFALSE;
}
 
 
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();
 
  return0;
}



這個例子先是畫了個矩形,然後將它平移並繪製出平移結果。


cairo_translate(cr, 100, 100);

cairo_translate() 函數可通過平移用於空間的原點來修改當前的變換矩陣。在這個示例中,是將原點沿水平和豎直方向平移了 100 個單位長度。

旋轉

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


#include <cairo.h>
#include <gtk/gtk.h>
#include <math.h>
 
 
staticgboolean
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);
 
  returnFALSE;
}
 
 
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();
 
  return0;
}


這個例子先是畫了個矩形,然後對它進行了平移和旋轉變換,並繪製出變換結果。


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

首先對用戶空間的原點進行平移,然後再圍繞它旋轉 180°。注意:旋轉角度是弧度,而非角度。

縮放

下面這個例子演示了一個對象的縮放變換。(作者還真是沉悶阿,相同的句式連用了 n 次,這個可憐的矩形被折騰的痛苦不堪!)


#include <cairo.h>
#include <gtk/gtk.h>
 
 
staticgboolean
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);
 
  returnFALSE;
}
 
 
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();
 
  return0;
}


這次的例子是用指定的縮放因子,把初始的矩形變的小了點,然後又把它變的大了點。


cairo_save(cr);
...
cairo_restore(cr);

若對初始的矩形完成兩次縮放操作,需要將初始的變換矩陣保存一下,這個可通過 cairo_save() 和 cairo_restore() 函數來實現。


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

這裏首先將用戶空間的原點平移了一下,然後又開始用 0.7 作爲因子進行縮放變換。

 

錯切

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


#include <cairo.h>
#include <gtk/gtk.h>
 
 
staticgboolean
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);
 
  returnFALSE;
}
 
 
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();
 
  return0;
}


這份示例代碼實現了兩次錯切變換。對於錯切變換,沒有特定的函數,必須使用矩陣來實現。


cairo_matrix_t matrix;

這個 cairo_matrix 是存儲仿射變換的數據結構。 


cairo_matrix_init(&matrix,
   1.0, 0.5,
   0.0, 1.0,
   0.0, 0.0);
 
cairo_transform (cr, &matrix);

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


cairo_matrix_init(&matrix,
    1.0, 0.0,
    0.7, 1.0,
    0.0, 0.0);
 
cairo_transform(cr, &matrix);

 

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

 

橢圓

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


#include <cairo.h>
#include <gtk/gtk.h>
 
staticgboolean
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);
 
  returnFALSE;
}
 
 
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();
 
  return0;
}



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

在 GTK+ 的窗口中間,繪製了一個圓,它是那些橢圓的邊界圓。

 

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

 

星星

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


#include <cairo.h>
#include <gtk/gtk.h>
#include <math.h>
 
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 }
};
 
 
staticgboolean
on_expose_event(GtkWidget *widget,
    GdkEventExpose *event,
    gpointer data)
{
  cairo_t *cr;
 
  staticgdouble angle = 0;
  staticgdouble scale = 1;
  staticgdouble 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[i][0], points[i][1]);
  }
 
  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);
 
  returnFALSE;
}
 
staticgboolean
time_handler (GtkWidget *widget)
{
  if(widget->window == NULL) returnFALSE;
  gtk_widget_queue_draw(widget);
  returnTRUE;
}
 
 
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();
 
  return0;
}


在這個示例中,畫了一顆星星,然後平移它,旋轉它,縮放它。


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

先將星星平移到窗口中間,旋轉它,縮放它。(作者還真不是一般的羅嗦)


for ( i = 0; i < 10; i++ ) {
    cairo_line_to(cr, points[i][0], points[i][1]);
}
 
cairo_close_path(cr);
cairo_fill(cr);
cairo_stroke(cr);

畫它!


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

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章