|
|
Строка 1: |
Строка 1: |
- | In this part of the Cairo graphics programming tutorial, we will talk about transformations.
| |
| | | |
- | <b>An affine transform</b> 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 <b>rotation</b> is a transformation that moves a rigid body around a fixed point.
| |
- | A <b>scaling</b> is a transformation that enlarges or diminishes objects. The scale factor is the same in all directions.
| |
- | A <b>translation</b> is a transformation that moves every point a constant distance in a specified direction.
| |
- | A <b>shear</b> 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.
| |
- |
| |
- | <source lang="cpp">
| |
- | #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;
| |
- | }
| |
- | </source>
| |
- |
| |
- | The examle draws a rectangle. Then we do a translation and draw the same rectangle again.
| |
- |
| |
- | <source lang="cpp">
| |
- | cairo_translate(cr, 100, 100);
| |
- |
| |
- | </source>
| |
- |
| |
- | The <b>cairo_translate()</b> 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.
| |
- |
| |
- | [[image: cairo_faq_translate.png | center]]
| |
- |
| |
- | == Rotation ==
| |
- |
| |
- | The next example demonstrates a rotation.
| |
- |
| |
- | <source lang="cpp">
| |
- | #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;
| |
- | }
| |
- | </source>
| |
- |
| |
- | The example draws a rectangle, performs a translation and a rotation and draws the same rectangle again.
| |
- |
| |
- | <source lang="cpp">
| |
- | cairo_translate(cr, 150, 100);
| |
- | cairo_rotate(cr, M_PI/2);
| |
- | </source>
| |
- |
| |
- | 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°.
| |
- |
| |
- | [[image: cairo_faq_rotate.png | center]]
| |
- |
| |
- | == Scale ==
| |
- |
| |
- | The next example demonstrates a scaling of an object.
| |
- |
| |
- | <source lang="cpp">
| |
- | #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;
| |
- | }
| |
- | </source>
| |
- |
| |
- | This time the example makes the initial rectangle smaller and then bigger by a specific scale factor.
| |
- |
| |
- | <source lang="cpp">
| |
- | cairo_save(cr);
| |
- | ...
| |
- | cairo_restore(cr);
| |
- | </source>
| |
- |
| |
- | 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 <b>cairo_restore()</b> functions.
| |
- |
| |
- | <source lang="cpp">
| |
- | cairo_translate(cr, 130, 30);
| |
- | cairo_scale(cr, 0.7, 0.7);
| |
- |
| |
- | </source>
| |
- |
| |
- | Here we shift the user space origin an scale it by a factor of 0.7.
| |
- |
| |
- | [[image: cairo_faq_scale.png | center]]
| |
- |
| |
- | == Shear ==
| |
- |
| |
- | In the following example we perform shearing.
| |
- |
| |
- | <source lang="cpp">
| |
- | #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;
| |
- | }
| |
- |
| |
- | </source>
| |
- |
| |
- | In this code example, we perform two shear transformations. For a shear transformation, we do not have a special function. We must use matrices.
| |
- |
| |
- | <source lang="cpp">
| |
- | cairo_matrix_t matrix;
| |
- | </source>
| |
- |
| |
- | The <b>cairo_matrix_t</b> is a structure that holds an affine transformation.
| |
- |
| |
- | <source lang="cpp">
| |
- | cairo_matrix_init(&matrix,
| |
- | 1.0, 0.5,
| |
- | 0.0, 1.0,
| |
- | 0.0, 0.0);
| |
- |
| |
- | cairo_transform (cr, &matrix);
| |
- | </source>
| |
- |
| |
- | This transformation shears y values by 0.5 of the x values.
| |
- |
| |
- | <source lang="cpp">
| |
- | cairo_matrix_init(&matrix,
| |
- | 1.0, 0.0,
| |
- | 0.7, 1.0,
| |
- | 0.0, 0.0);
| |
- |
| |
- | cairo_transform(cr, &matrix);
| |
- |
| |
- | </source>
| |
- |
| |
- | And this transformation multiplies the x value of each coordinate by 0.7 of the y.
| |
- |
| |
- | [[image: cairo_faq_shear.png | center]]
| |
- |
| |
- | == Ellipses ==
| |
- |
| |
- | In the following example we create an complex shape by rotating a bunch of ellipses.
| |
- |
| |
- | <source lang="cpp">
| |
- | #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;
| |
- | }
| |
- |
| |
- | </source>
| |
- |
| |
- | <source lang="cpp">
| |
- | cairo_translate(cr, width/2, height/2);
| |
- | cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI);
| |
- | cairo_stroke(cr);
| |
- | </source>
| |
- |
| |
- | In the middle of the GTK+ window, we create a circle. This will be a bounding circle for our ellipses.
| |
- |
| |
- | <source lang="cpp">
| |
- | 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);
| |
- | }
| |
- | </source>
| |
- |
| |
- | 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.
| |
- |
| |
- | [[image: cairo_faq_ellipserotate.png | center]]
| |
- |
| |
- | == Star ==
| |
- |
| |
- | The next example shows a rotating and scaling star.
| |
- |
| |
- | <source lang="cpp">
| |
- | #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;
| |
- | }
| |
- |
| |
- | </source>
| |
- |
| |
- | In this example, we create a star object. We will translate it, rotate it and scale it.
| |
- |
| |
- | <source lang="cpp">
| |
- | cairo_translate(cr, width / 2, height / 2 );
| |
- | cairo_rotate(cr, angle);
| |
- | cairo_scale(cr, scale, scale
| |
- | </source>
| |
- |
| |
- | We shift the star into the middle of the window. Rotate it and scale it.
| |
- |
| |
- | <source lang="cpp">
| |
- | 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);
| |
- |
| |
- | </source>
| |
- |
| |
- | Here we draw the star object.
| |
- |
| |
- | <source lang="cpp">
| |
- | if ( scale < 0.01 ) {
| |
- | delta = -delta;
| |
- | } else if (scale > 0.99) {
| |
- | delta = -delta;
| |
- | }
| |
- | </source>
| |
- |
| |
- | These line control the growing or shrinking of the star object.
| |
- |
| |
- | [[image: cairo_faq_star.gif | center]]
| |
- |
| |
- |
| |
- | [[Категория:GTK+]]
| |
- | [[Категория:Cairo]]
| |