Cairo 图形指南 (9) —— 变换
这一篇讲述变换(Transformation) 仿射变换是由一些线性变换与平移构成的。线性变换可以写为单个矩阵的形式。旋转是让一个刚体绕一点运动的变换。缩放变换是让物体的形状扩大与减小,并且在各个方向上的缩放因子都相同。平移变换将每个点沿着指定的方向移动常量距离。错切对于给定轴线,沿垂直于它的方向对物体进行移动的变换,并且在轴线的一侧的移动距离大于另一侧。
——上述内容来自维基百科全书
平移
下面这个例子演示了一个简单的平移变换。
#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; }
这个例子先是画了个矩形,然后将它平移并绘制出平移结果。
cairo_translate(cr, 100, 100);
cairo_translate() 函数可通过平移用于空间的原点来修改当前的变换矩阵。在这个示例中,是将原点沿水平和竖直方向平移了 100 个单位长度。
旋转
下面这个例子演示了一个简单的旋转变换。
#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; }
这个例子先是画了个矩形,然后对它进行了平移和旋转变换,并绘制出变换结果。
cairo_translate(cr, 150, 100); cairo_rotate(cr, M_PI/2);
首先对用户空间的原点进行平移,然后再围绕它旋转 180°。注意:旋转角度是弧度,而非角度。
缩放
下面这个例子演示了一个对象的缩放变换。(作者还真是沉闷阿,相同的句式连用了 n 次,这个可怜的矩形被折腾的痛苦不堪!)
#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; }
这次的例子是用指定的缩放因子,把初始的矩形变的小了点,然后又把它变的大了点。
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> 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; }
这份示例代码实现了两次错切变换。对于错切变换,没有特定的函数,必须使用矩阵来实现。
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);
这一变换的数学形式可表示为:
[tex]
\begin{bmatrix}x_{new}\\ y_{new}\\ 1.0\end{bmatrix}=\begin{bmatrix}
{1.0 & 0.0 & 0.0}\\
{0.5 & 1.0 & 0.0}\\
{0.0 & 0.0 & 1.0}
\end{bmatrix}\begin{bmatrix}x\\ y\\ 1.0\end{bmatrix}
[/tex]
cairo_matrix_init(&matrix, 1.0, 0.0, 0.7, 1.0, 0.0, 0.0); cairo_transform(cr, &matrix);
这一变换的数学形式可表示为:
[tex]
\begin{bmatrix}x_{new}\\ y_{new}\\ 1.0\end{bmatrix}=\begin{bmatrix}
{1.0 & 0.7 & 0.0}\\
{0.0 & 1.0 & 0.0}\\
{0.0 & 0.0 & 1.0}
\end{bmatrix}\begin{bmatrix}x\\ y\\ 1.0\end{bmatrix}
[/tex]
椭圆
下面的这个例子,画了一个灰常复杂的形状,它由一串旋转的椭圆形成。
#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; }
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 } }; 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[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); 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; }
在这个示例中,画了一颗星星,然后平移它,旋转它,缩放它。
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; } else if (scale > 0.99) { delta = -delta; }
这几行代码控制星星的缩放过程。
2013年8月07日 15:02
Cairo变换操作都是都是针对当前绘制图形,可以读取一个图形文件,然后对它进行变换操作么