Projective transformation demo program

Bryce Harrington <[email protected]>
Newsgroups gmane.comp.lib.cairo
Message-ID <[email protected]>
Attached is transquare.c, a demo of various types of transformations
including projective transformations (using the patches posted today).
A screenshot of the demo is attached as well.

You can compile this example against stock cairo using the following
commandline:

    gcc transquare.c -o transquare \
        $(pkg-config --cflags --libs gtk+-2.0 cairo)
    ./transquare

Notice that with this, the rotational example works fine.

After patching Cairo with the projective transformation patchset and
installing (e.g. to /usr/local), you can build the demo like this:

    gcc transquare.c -DWITH_PROJECTIVE -o transquare  \
    	$(pkg-config --cflags --libs gtk+-2.0 cairo)
    ./transquare

(I'd recommend doing a 'make uninstall' on cairo after finishing the
build.)

As you can see from the screenshot, the rotation transformation is buggy
here.  I'm not even sure how to best describe it, but it's rotating the
points in a heavily distorted way, as though there are stray elements in
the matrix.  I'm sure I'm missing something simple but I've
doublechecked the math and am a bit stymied, so I'm posting on list in
hopes a second pair of eyes will spot where the error is.

Bryce

-- 
cairo mailing list
[email protected]
http://lists.cairographics.org/mailman/listinfo/cairo
projective-transform.png (image/png, 49.4 KB) - not displayed
transquare.c (text/x-csrc, 13.1 KB)
/*
 * Compile with:
 *   gcc transquare.c -DWITH_PROJECTIVE -o transquare  $(pkg-config --cflags --libs gtk+-2.0 cairo)
 *
 * Or to build against cairo without projective transformation functionality:
 *   gcc transquare.c -o transquare  $(pkg-config --cflags --libs gtk+-2.0 cairo)
 */

#include <math.h>
#include <cairo.h>
#include <cairo-xlib.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>

static void do_drawing(cairo_t *);

struct {
    gboolean timer;
    int count;
    int cycle;
    int velocity;
    double coordx[100];
    double coordy[100];
} glob;

static cairo_t *
begin_paint (GdkDrawable *window)
{
    Display *dpy;
    Drawable xid;
    Visual *visual;
    GdkDrawable *drawable;
    gint x_offset, y_offset;
    gint width, height;
    cairo_surface_t *surface;
    cairo_t *cr;

    if (GDK_IS_WINDOW (window))
	gdk_window_get_internal_paint_info (window, &drawable,
					&x_offset, &y_offset);
    else
	drawable = window;

    dpy = gdk_x11_drawable_get_xdisplay (drawable);
    xid = gdk_x11_drawable_get_xid (drawable);
    gdk_drawable_get_size (drawable, &width, &height);
    visual = GDK_VISUAL_XVISUAL(gdk_drawable_get_visual (drawable));
    surface = cairo_xlib_surface_create (dpy, xid, visual,
					 width, height);
    cr = cairo_create (surface);
    cairo_surface_destroy (surface);

    if (GDK_IS_WINDOW (window))
	cairo_translate (cr, -x_offset, -y_offset);

    return cr;
}

static void
end_paint (cairo_t *cr)
{
    cairo_destroy (cr);
}


static gboolean
on_expose_event (GtkWidget	*widget,
		 GdkEventExpose *event,
		 gpointer	 data)
{
    cairo_t *cr;

    cr = begin_paint (widget->window);
    cairo_save (cr);
    {
	do_drawing (cr);
    }
    cairo_restore (cr);
    end_paint (cr);

    return TRUE;
}

static void draw_green_square(cairo_t *cr, int s)
{
    cairo_set_source_rgb(cr, 0, 0.5, 0);
    cairo_set_line_width(cr, 2);

    cairo_rel_line_to(cr, 0, s/2);
    cairo_rel_line_to(cr, s/2, s/2);
    cairo_rel_line_to(cr, s/2, -s/2);
    cairo_rel_line_to(cr, 0, -s/2);
    cairo_close_path(cr);

    cairo_stroke(cr);
}

static void draw_green_square_abs(cairo_t *cr, int s, int x, int y)
{
    cairo_set_source_rgb(cr, 0, 0.5, 0);
    cairo_set_line_width(cr, 2);

    cairo_move_to(cr, x, y);
    cairo_line_to(cr, x+0,   y+s/2);
    cairo_line_to(cr, x+s/2, y+s);
    cairo_line_to(cr, x+s,   y+s/2);
    cairo_line_to(cr, x+s,   y);
    cairo_close_path(cr);

    cairo_stroke(cr);
}

static void draw_group_box(cairo_t *cr, int x, int y, const char *text, int height)
{
    // Group box
    cairo_set_source_rgb (cr, 1, 1, 1);
    cairo_set_line_width (cr, 1);
    cairo_rectangle (cr, x-5, y-5, 385, height);
    cairo_stroke (cr);

    cairo_select_font_face(cr, "Courier",
			   CAIRO_FONT_SLANT_NORMAL,
			   CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size (cr, 12);
    cairo_set_source_rgb(cr, 1, 1, 1);
    cairo_move_to(cr, x, y+10);
    cairo_text_path(cr, text);
    cairo_fill(cr);
}

static void draw_label(cairo_t *cr, int x, int y, const char *text)
{
    cairo_select_font_face(cr, "Courier",
			   CAIRO_FONT_SLANT_NORMAL,
			   CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size (cr, 12);
    cairo_set_source_rgb(cr, 0, 0, 1);
    cairo_move_to(cr, x, y);
    cairo_text_path(cr, text);
    cairo_fill(cr);
}

static void do_drawing(cairo_t *cr)
{
    int x = 10;
    int y = 10;
    int s = 30;
    double param;
    cairo_matrix_t matrix;

    glob.cycle += glob.velocity;
    if (glob.cycle <= -100 || glob.cycle >= 100) {
	glob.velocity *= -1;
    }

    // Background
    cairo_paint (cr);

    /* ------------------------------------------------------------ */
    draw_group_box (cr, x, y, "BASIC TRANSFORMATIONS", 190);
    y += 30;

    // Untransformed
    cairo_move_to(cr, x, y);
    cairo_save (cr);
    draw_green_square (cr, s);
    cairo_restore (cr);
    draw_label (cr, x+3*s, y+s/2, "Untransformed");
    y += s + 10;

    // Scaled
    param = 1.0 + glob.cycle/300.0;
    cairo_move_to(cr, x,   y);
    cairo_save (cr);
    cairo_scale (cr, param, param);
    draw_green_square (cr, s);
    cairo_restore (cr);
    draw_label (cr, x+3*s, y+s/2, "Scaled");
    y += s + 10;

    // Rotated half a radian
    param = glob.cycle / 200.0;
    cairo_save (cr);
    cairo_move_to(cr, x, y);
    cairo_rotate (cr, param);
    draw_green_square (cr, s);
    cairo_restore (cr);
    draw_label (cr, x+3*s, y+s/2, "Rotated 1/2 Radian");
    y += s + 10;

    // Translated
    param = s * (0.5 + glob.cycle / 200.0);
    cairo_save (cr);
    cairo_translate (cr, param, 0);
    cairo_move_to(cr, x,   y);
    draw_green_square (cr, s);
    cairo_restore (cr);
    draw_label (cr, x+3*s, y+s/2, "Translated");
    y += s + 10;

    y += s + 10;

    /* ------------------------------------------------------------ */
    draw_group_box (cr, x, y, "2D MATRIX TRANSFORMATIONS", 360);
    y += 30;

    // Identity matrix - Untransformed
    cairo_matrix_init_identity (&matrix);
    cairo_transform (cr, &matrix);
    cairo_move_to(cr, x,   y);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Identity matrix");
    y += s + 10;

    // Scaling matrix
    param = 1.0 + glob.cycle/300.0;
    cairo_move_to(cr, x,   y);
    cairo_matrix_init (&matrix,
		       param, 0.0,
		       0.0, param,
		       0.0, 0.0);
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Scaling matrix");
    y += s + 10;

    // Rotation matrix
    param = glob.cycle / 200.0;
    cairo_move_to(cr, x,   y);
    cairo_matrix_init (&matrix,
		       cos(param), -sin(param),
		       sin(param), cos(param),
		       0.0, 0.0);
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Rotational matrix");
    y += s + 10;

    // Translation matrix
    param = s * (1 + glob.cycle / 100.0);
    cairo_matrix_init_translate (&matrix, param, 0);
    cairo_set_matrix (cr, &matrix);
    cairo_move_to(cr, x,   y);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Translation matrix");
    y += s + 10;

    // Inversion matrix
    if (glob.cycle < 0)
	param = -1;
    else
	param = 1;
    cairo_move_to(cr, x,   y + 0.5 * (1 - param) * s );  // Correction for drawing upside down
    cairo_matrix_init (&matrix,
		       1.0, 0.0,
		       0.0, param,
		       0.0, 0.0);
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Inversion matrix");
    y += s + 10;

    // Y Shear matrix
    param = 0.5 + glob.cycle / 300.0;
    cairo_move_to(cr, x,   y);
    cairo_matrix_init (&matrix,
		       1.0, 0.0,
		       param, 1.0,
		       0.0, 0.0);
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Vertical shear matrix");
    y += s + 10;

    // X Shear matrix
    param = 0.5 + glob.cycle / 300.0;
    cairo_matrix_init (&matrix,
		       1.0, param,
		       0.0, 1.0,
		       0.0, 0.0);
    cairo_transform (cr, &matrix);
    cairo_move_to(cr, x,   y);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Horizontal shear matrix");
    y += s + 10;

#ifdef WITH_PROJECTIVE
    /* ------------------------------------------------------------ */
    x += 420;
    y = 10;
    draw_group_box (cr, x, y, "PROJECTIVE TRANSFORMATIONS", 500);
    y += 30;

    // Identity matrix - Untransformed
    cairo_matrix_init_identity (&matrix);
    cairo_transform (cr, &matrix);
    cairo_move_to(cr, x,   y);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Identity matrix");
    y += s + 10;

    // X projection
    param = 0.5 + glob.cycle / 200.0;
    cairo_move_to(cr, x + 10,   y);
    cairo_matrix_init_identity (&matrix);
    matrix.px = 0.001 * param;
    matrix.py = 0;
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Horizontal identity projection");
    y += s + 10;

    // Y projection
    param = 0.5 + glob.cycle / 200.0;
    cairo_move_to(cr, x + 10,   y);
    cairo_matrix_init_identity (&matrix);
    matrix.px = 0;
    matrix.py = 0.00125 * param;
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Vertical identity projection");
    y += s + 10;

    // Scaling matrix with X projection
    param = 0.5 + glob.cycle / 300.0;
    cairo_move_to(cr, x,   y);
    cairo_matrix_init (&matrix,
		       param, 0.0,
		       0.0, param,
		       0.0, 0.0);
    matrix.px = 0.00001 * param;
    matrix.py = 0.0;
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Scaling X projection");
    y += s + 10;

    // Scaling matrix with Y projection
    param = 0.5 + glob.cycle / 300.0;
    cairo_move_to(cr, x,   y);
    cairo_matrix_init (&matrix,
		       param, 0.0,
		       0.0, param,
		       0.0, 0.0);
    matrix.px = 0;
    matrix.py = -0.00001 * param;
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Scaling Y projection");
    y += s + 10;

    // Rotation matrix with X projection
    param = 0.5 + glob.cycle / 200.0;
    cairo_move_to(cr, x,   y);
    cairo_matrix_init (&matrix,
		       cos(param), -sin(param),
		       sin(param), cos(param),
		       0.0, 0.0);
    matrix.px = 0;
    matrix.py = -0.00001 * param;
    cairo_transform (cr, &matrix);

/*
    printf("Matrix:\n %f, %f,\n %f, %f\n %f, %f\n %f, %f\n",
	   matrix.xx, matrix.xy,
	   matrix.yx, matrix.yy,
	   matrix.x0, matrix.y0,
	   matrix.px, matrix.py);
*/
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Rotational matrix");
    y += s + 10;

    // Y Shear matrix w/ X projection
    param = 0.5 + glob.cycle / 300.0;
    cairo_move_to(cr, x,   y);
    cairo_matrix_init (&matrix,
		       1.0, 0.0,
		       param, 1.0,
		       0.0, 0.0);
    matrix.px = 0.001 * param;
    matrix.py = 0.0;
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Vertical shear with X projection");
    y += s + 10;

    // Y Shear matrix w/ Y projection
    param = 0.5 + glob.cycle / 300.0;
    cairo_move_to(cr, x,   y);
    cairo_matrix_init (&matrix,
		       1.0, 0.0,
		       param, 1.0,
		       0.0, 0.0);
    matrix.px = 0.0;
    matrix.py = 0.001 * param;
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Vertical shear with Y projection");
    y += s + 10;

    // X Shear matrix w/ X projection
    param = 0.5 + glob.cycle / 300.0;
    cairo_move_to(cr, x,   y);
    cairo_matrix_init (&matrix,
		       1.0, param,
		       0.0, 1.0,
		       0.0, 0.0);
    matrix.px = 0.00005 * param;
    matrix.py = 0.0;
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Horizontal shear matrix with X projection");
    y += s + 10;

    // X Shear matrix w/ Y projection
    param = 0.5 + glob.cycle / 300.0;
    cairo_move_to(cr, x,   y);
    cairo_matrix_init (&matrix,
		       1.0, param,
		       0.0, 1.0,
		       0.0, 0.0);
    matrix.px = 0.0;
    matrix.py = 0.0001 * param;
    cairo_transform (cr, &matrix);
    draw_green_square (cr, s);
    cairo_identity_matrix (cr);
    draw_label (cr, x+3*s, y+s/2, "Horizontal shear matrix with Y projection");
    y += s + 10;
#endif

}

static gboolean clicked(GtkWidget *widget, GdkEventButton *event,
			gpointer user_data)
{
    printf("Clicked %f %f on button %d\n", event->x, event->y, event->button);
    if (event->button == 1) {
	glob.coordx[glob.count] = event->x;
	glob.coordy[glob.count++] = event->y;
	glob.timer = !glob.timer;
    }

    if (event->button == 3) {
	gtk_widget_queue_draw(widget);
    }

    return TRUE;
}

static gboolean time_handler(GtkWidget *widget)
{
    if (!glob.timer) return FALSE;
    gtk_widget_queue_draw(widget);

    return TRUE;
}


int main(int argc, char *argv[])
{
    GtkWidget *window;
    GtkWidget *darea;

    glob.timer = TRUE;
    glob.count = 0;
    glob.cycle = -100;
    glob.velocity = 5;

    gtk_init(&argc, &argv);

    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    darea = gtk_drawing_area_new();
    gtk_container_add(GTK_CONTAINER(window), darea);

    gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);

    g_signal_connect(G_OBJECT(darea), "expose_event",
		     G_CALLBACK(on_expose_event), NULL);
    g_signal_connect(window, "destroy",
		     G_CALLBACK(gtk_main_quit), NULL);

    g_signal_connect(window, "button-press-event",
		     G_CALLBACK(clicked), NULL);

    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(window), 816, 600);
    gtk_window_set_title(GTK_WINDOW(window), "Transformations");

    g_timeout_add(50, (GSourceFunc) time_handler, (gpointer) window);

    gtk_widget_show_all(window);

    gtk_main();

    return 0;
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.