Cairo FAQ Transformations
Материал из Wiki.crossplatform.ru
In this part of the Cairo graphics programming tutorial, we will talk about transformations.
An affine transform is composed of zero or more linear transformations (rotation, scaling or shear) and translation (shift). Several linear transformations can be combined into a single matrix. A rotation is a transformation that moves a rigid body around a fixed point. A scaling is a transformation that enlarges or diminishes objects. The scale factor is the same in all directions. A translation is a transformation that moves every point a constant distance in a specified direction. A shear is a transformation that moves an object perpendicular to a given axis, with greater value on one side of the axis than the other.
sources: (wikipedia.org, freedictionary.com)
Содержание |
Translation
The following example describes a simple translation.
#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; }
The examle draws a rectangle. Then we do a translation and draw the same rectangle again.
cairo_translate(cr, 100, 100);
The cairo_translate() function modifies the current transormation matrix by tranlating the user space origin. In our case we shift the origin by 100 units in both directions.
Rotation
The next example demonstrates a rotation.
#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; }
The example draws a rectangle, performs a translation and a rotation and draws the same rectangle again.
cairo_translate(cr, 150, 100); cairo_rotate(cr, M_PI/2);
First we shift the user space origin. Then we rotate it by 180°. Notice that we specify the angle in radians, not degrees. 2M_PI = 360°.
Scale
The next example demonstrates a scaling of an object.
#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; }
This time the example makes the initial rectangle smaller and then bigger by a specific scale factor.
cairo_save(cr); ... cairo_restore(cr);
We want to perform two scaling operations on the initial rectangle. Therefore, we need to save the initial transformation matrix. This is done by a pair of cairo_restore() functions.
cairo_translate(cr, 130, 30); cairo_scale(cr, 0.7, 0.7);
Here we shift the user space origin an scale it by a factor of 0.7.
Shear
In the following example we perform shearing.
#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; }
In this code example, we perform two shear transformations. For a shear transformation, we do not have a special function. We must use matrices.
cairo_matrix_t matrix;
The cairo_matrix_t is a structure that holds an affine transformation.
cairo_matrix_init(&matrix, 1.0, 0.5, 0.0, 1.0, 0.0, 0.0); cairo_transform (cr, &matrix);
This transformation shears y values by 0.5 of the x values.
cairo_matrix_init(&matrix, 1.0, 0.0, 0.7, 1.0, 0.0, 0.0); cairo_transform(cr, &matrix);
And this transformation multiplies the x value of each coordinate by 0.7 of the y.
Ellipses
In the following example we create an complex shape by rotating a bunch of ellipses.
#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);
In the middle of the GTK+ window, we create a circle. This will be a bounding circle for our ellipses.
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); }
We create 36 ellipses along the path of our bounding circle. An ellipse is a scaled circle. We rotate the ellipses. Thus we create an interesting shape.
Star
The next example shows a rotating and scaling star.
#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; }
In this example, we create a star object. We will translate it, rotate it and scale it.
cairo_translate(cr, width / 2, height / 2 ); cairo_rotate(cr, angle); cairo_scale(cr, scale, scale
We shift the star into the middle of the window. Rotate it and scale it.
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);
Here we draw the star object.
if ( scale < 0.01 ) { delta = -delta; } else if (scale > 0.99) { delta = -delta; }
These line control the growing or shrinking of the star object.