变换
最后修改于 2023 年 7 月 17 日
在 Cairo 图形编程教程的这一部分,我们讨论变换。
仿射变换 由零个或多个线性变换(旋转、缩放或剪切)和翻译(平移)组成。多个线性变换可以合并为单个矩阵。旋转 是围绕固定点移动刚体的变换。缩放 是放大或缩小对象的变换。缩放因子在所有方向上都是相同的。
平移 是将每个点沿指定方向移动恒定距离的变换。剪切 是将物体垂直于给定轴移动的变换,在轴的一侧比另一侧具有更大的值。
平移
以下示例描述了一个简单的平移。
static void do_drawing(cairo_t *cr)
{
cairo_set_source_rgb(cr, 0.2, 0.3, 0.8);
cairo_rectangle(cr, 10, 10, 30, 30);
cairo_fill(cr);
cairo_translate(cr, 20, 20);
cairo_set_source_rgb(cr, 0.8, 0.3, 0.2);
cairo_rectangle(cr, 0, 0, 30, 30);
cairo_fill(cr);
cairo_translate(cr, 30, 30);
cairo_set_source_rgb(cr, 0.8, 0.8, 0.2);
cairo_rectangle(cr, 0, 0, 30, 30);
cairo_fill(cr);
cairo_translate(cr, 40, 40);
cairo_set_source_rgb(cr, 0.3, 0.8, 0.8);
cairo_rectangle(cr, 0, 0, 30, 30);
cairo_fill(cr);
}
示例绘制一个矩形。然后我们进行平移,再次绘制相同的矩形。
cairo_translate(cr, 20, 20);
cairo_translate 函数通过平移用户空间原点来修改当前变换矩阵。在我们的例子中,我们将原点在两个方向上都偏移 20 个单位。
剪切
在下面的示例中,我们执行剪切操作。剪切是物体沿特定轴的失真。此操作没有专门的剪切函数。我们需要创建自己的变换矩阵。请注意,每个仿射变换都可以通过创建变换矩阵来执行。
static void do_drawing(cairo_t *cr)
{
cairo_matrix_t matrix;
cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
cairo_rectangle(cr, 20, 30, 80, 50);
cairo_fill(cr);
cairo_matrix_init(&matrix,
1.0, 0.5,
0.0, 1.0,
0.0, 0.0);
cairo_transform(cr, &matrix);
cairo_rectangle(cr, 130, 30, 80, 50);
cairo_fill(cr);
}
在此代码示例中,我们执行一个简单的剪切操作。
cairo_matrix_t matrix;
cairo_matrix_t 是一个保存仿射变换的结构。
cairo_matrix_init(&matrix,
1.0, 0.5,
0.0, 1.0,
0.0, 0.0);
此变换将 y 值按 x 值的 0.5 进行剪切。
cairo_transform(cr, &matrix);
我们通过 `transform()` 方法执行变换。
缩放
下一个示例演示了缩放操作。缩放是一种对象被放大或缩小的变换操作。
static void do_drawing(cairo_t *cr)
{
cairo_set_source_rgb(cr, 0.2, 0.3, 0.8);
cairo_rectangle(cr, 10, 10, 90, 90);
cairo_fill(cr);
cairo_scale(cr, 0.6, 0.6);
cairo_set_source_rgb(cr, 0.8, 0.3, 0.2);
cairo_rectangle(cr, 30, 30, 90, 90);
cairo_fill(cr);
cairo_scale(cr, 0.8, 0.8);
cairo_set_source_rgb(cr, 0.8, 0.8, 0.2);
cairo_rectangle(cr, 50, 50, 90, 90);
cairo_fill(cr);
}
我们绘制三个 90x90 像素大小的矩形。在其中两个上,我们执行缩放操作。
cairo_scale(cr, 0.6, 0.6); cairo_set_source_rgb(cr, 0.8, 0.3, 0.2); cairo_rectangle(cr, 30, 30, 90, 90); cairo_fill(cr);
我们将矩形均匀地缩放了 0.6 倍。
cairo_scale(cr, 0.8, 0.8); cairo_set_source_rgb(cr, 0.8, 0.8, 0.2); cairo_rectangle(cr, 50, 50, 90, 90); cairo_fill(cr);
这里我们执行了另一个缩放操作,因子为 0.8。如果我们看图片,我们会发现第三个黄色矩形是最小的。即使我们使用了较小的缩放因子。这是因为变换操作是累加的。实际上,第三个矩形被缩放了 0.528 倍(0.6x0.8)。
隔离变换
变换操作是累加的。为了将一个操作与其他操作隔离,我们可以使用 `cairo_save` 和 `cairo_restore` 函数。`cairo_save` 函数会复制绘图上下文的当前状态,并将其保存在一个内部堆栈中。`cairo_restore` 函数会将上下文恢复到保存的状态。
static void do_drawing(cairo_t *cr)
{
cairo_set_source_rgb(cr, 0.2, 0.3, 0.8);
cairo_rectangle(cr, 10, 10, 90, 90);
cairo_fill(cr);
cairo_save(cr);
cairo_scale(cr, 0.6, 0.6);
cairo_set_source_rgb(cr, 0.8, 0.3, 0.2);
cairo_rectangle(cr, 30, 30, 90, 90);
cairo_fill(cr);
cairo_restore(cr);
cairo_save(cr);
cairo_scale(cr, 0.8, 0.8);
cairo_set_source_rgb(cr, 0.8, 0.8, 0.2);
cairo_rectangle(cr, 50, 50, 90, 90);
cairo_fill(cr);
cairo_restore(cr);
}
在示例中,我们缩放了两个矩形。这次我们将缩放操作相互隔离。
cairo_save(cr); cairo_scale(cr, 0.6, 0.6); cairo_set_source_rgb(cr, 0.8, 0.3, 0.2); cairo_rectangle(cr, 30, 30, 90, 90); cairo_fill(cr); cairo_restore(cr);
我们将 `cairo_scale` 函数放在 `cairo_save` 和 `cairo_restore` 函数之间,从而隔离了缩放操作。
现在第三个黄色矩形比第二个红色矩形要大。
甜甜圈
在下面的示例中,我们通过旋转一组椭圆来创建一个复杂的形状。
#include <cairo.h>
#include <gtk/gtk.h>
#include <math.h>
static void do_drawing(cairo_t *, GtkWidget *widget);
static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
gpointer user_data)
{
do_drawing(cr, widget);
return FALSE;
}
static void do_drawing(cairo_t *cr, GtkWidget *widget)
{
GtkWidget *win = gtk_widget_get_toplevel(widget);
gint width, height;
gtk_window_get_size(GTK_WINDOW(win), &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;
for (i = 0; i < 36; i++) {
cairo_save(cr);
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);
}
}
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *darea;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
darea = gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER (window), darea);
g_signal_connect(G_OBJECT(darea), "draw",
G_CALLBACK(on_draw_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_window_set_title(GTK_WINDOW(window), "Donut");
gtk_widget_show_all(window);
gtk_main();
return 0;
}
我们进行旋转和缩放操作。我们还保存和恢复 Cairo 上下文。
cairo_translate(cr, width/2, height/2); cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI); cairo_stroke(cr);
在 GTK+ 窗口的中间,我们创建一个圆。这将是我们椭圆的边界圆。
gint i;
for (i = 0; i < 36; i++) {
cairo_save(cr);
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);
}
我们在边界圆的路径上创建了 36 个椭圆。我们使用 `cairo_save` 和 `cairo_restore` 方法将每个旋转和缩放操作相互隔离。
星形
下一个示例显示了一个旋转和缩放的星星。
#include <cairo.h>
#include <gtk/gtk.h>
static void do_drawing(cairo_t *, GtkWidget *widget);
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_draw_event(GtkWidget *widget, cairo_t *cr,
gpointer user_data)
{
do_drawing(cr, widget);
return FALSE;
}
static void do_drawing(cairo_t *cr, GtkWidget *widget)
{
static gdouble angle = 0;
static gdouble scale = 1;
static gdouble delta = 0.01;
GtkWidget *win = gtk_widget_get_toplevel(widget);
gint width, height;
gtk_window_get_size(GTK_WINDOW(win), &width, &height);
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;
}
static gboolean time_handler(GtkWidget *widget)
{
gtk_widget_queue_draw(widget);
return TRUE;
}
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *darea;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
darea = gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER (window), darea);
g_signal_connect(G_OBJECT(darea), "draw",
G_CALLBACK(on_draw_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), 400, 300);
gtk_window_set_title(GTK_WINDOW(window), "Star");
g_timeout_add(10, (GSourceFunc) time_handler, (gpointer) window);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
在此示例中,我们创建了一个星形对象。我们对其进行平移、旋转和缩放。
int points[11][2] = {
{ 0, 85 },
{ 75, 75 },
{ 100, 10 },
...
星形对象将由这些点构建。
static gdouble angle = 0; static gdouble scale = 1; static gdouble delta = 0.01;
我们初始化了三个重要变量。`angle` 用于旋转,`scale` 用于缩放星形对象。`delta` 变量控制星形何时变大和何时变小。
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;
}
这些行控制星形对象的增长或收缩。
在 Cairo 图形教程的这一部分,我们讨论了变换。