Very Simple Smooth Animation with GTK+2 and Cairo - c

I'm using gtk+2.0 and cairo. I wrote a simple program that open a window and
move a point. A simple biliard, only horizontal motion. It's just a test.
The problem is that it seems to be not so smooth, and I would ask if there
are some gtk or cairo expert here that could check for errors in the code.
Thanks.
#include <gtk/gtk.h>
#include <math.h>
gboolean timeout(gpointer data)
{
GtkWidget *widget = GTK_WIDGET(data);
if (!widget->window) return TRUE;
gtk_widget_queue_draw(widget);
}
gboolean configure(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
{
return TRUE;
}
double px = 10;
double vx = **2**;
gboolean expose(GtkWidget *widget, GdkEventExpose *event, gpointer data)
{
cairo_t *cr = gdk_cairo_create(widget->window);
cairo_rectangle(cr, event->area.x, event->area.y, event->area.width, event->area.height);
cairo_clip(cr);
cairo_set_source_rgb(cr,1,0,0);
cairo_arc(cr, px, 100, 6, 0, 2*M_PI);
cairo_fill(cr);
cairo_set_source_rgb(cr,0,0,0);
cairo_destroy(cr);
if (px <= 3 || px >= 200-3) vx = -vx;
px += vx;
return FALSE;
}
int main(int argc, char *argv[])
{
char *title = "Test";
int sx = 200;
int sy = 200;
gtk_init(NULL,NULL);
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window),title);
gtk_container_set_border_width(GTK_CONTAINER (window), 2);
g_signal_connect(window, "destroy",G_CALLBACK(gtk_main_quit),&window);
GtkWidget *drawing_area = gtk_drawing_area_new();
//g_signal_connect(window,"key-press-event",G_CALLBACK(on_key_press),NULL);
const GdkColor bianco = { 0, 0xffff, 0xffff, 0xffff };
const GdkColor nero = { 0, 0x0, 0x0, 0x0 };
gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &bianco);
gtk_widget_set_size_request(drawing_area, sx, sy);
g_signal_connect(drawing_area,"configure_event",G_CALLBACK(configure), NULL);
g_signal_connect(drawing_area,"expose_event",G_CALLBACK(expose),NULL);
gtk_container_add(GTK_CONTAINER(window), drawing_area);
gtk_widget_show(drawing_area);
g_timeout_add(**10**, timeout, window);
if (!GTK_WIDGET_VISIBLE (window))
gtk_widget_show_all(window);
else {
gtk_widget_destroy (window);
window = NULL;
}
gtk_main();
return 0;
}

Not so smooth ? Well, with a period of 100ms, you're drawing at best 10 frames per second, no wonder it's not smooth... You should aim for 60 fps. Furthermore, you're invalidating the whole widget by calling gtk_widget_queue_draw, so your call to cairo_clip is mostly useless, as the clipping region is the whole widget. You should call gtk_widget_queue_draw_area instead so your clipping region is useful, and determining the area by keeping a record of the animation at frame n and n-1, so you redraw both areas to avoid the previous frame not being deleted.
There are lots of interesting stuff on animation perception on Owen Tailor's blog, starting with this post and more recent:
http://blog.fishsoup.net/2009/05/28/frames-not-idles/
Give a look at all the posts with figures, it's a gold mine.

Related

How to accelerate the Cairo painting speed of Gtk2+ on Arm without a GPU

I'm running the Gtk2+(configured with DirectFB and no X system) on a arm device with A53(1.5GHz) CPU but no GPU supported on Linux. It's got a frame buffer of size:3840*2160. Now, I need to draw some line, circle...on the screen, just like the windows painting board. So, I tried to do it with Cairo.But I found the speed of Cairo painting is too slow! Drawing a line from the upper left to lower right takes 400ms! Because the mouse is moved so quickly, and at the every motion event, I need to update the whole screen and redraw the line,but the drawing takes too long, so the response of motion event is too slow. I don't know whether there is some way to accelerate the speed to make drawing task smoothly just like MS paint. I've tried the gdk_draw_line too, but the update also takes too long...Here is some code below:
#include <gtk/gtk.h>
#include <time.h>
static gint prex, prey, nowx, nowy;
static GtkWidget *window = NULL;
static gboolean on_button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
if (event->button == 1) {
prex = event->x;
prey = event->y;
}
return TRUE;
}
static gboolean on_button_release_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
if (event->button == 1) {
prex = 0;
prey = 0;
}
return TRUE;
}
static gboolean on_motion_notify_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)
{
GdkModifierType state;
int minx, miny, maxx, maxy;
GdkRectangle update_rect;
gdk_window_get_pointer(event->window, &nowx, &nowy, &state);
if (state & GDK_BUTTON1_MASK) {
if (prex != 0 && prey != 0) {
minx = prex < nowx ? prex : nowx;
miny = prey < nowy ? prey : nowy;
maxx = prex < nowx ? nowx : prex;
maxy = prey < nowy ? nowy : prey;
update_rect.x = minx;
update_rect.y = miny;
update_rect.width = maxx - minx;
update_rect.height = maxy - miny;
gdk_window_invalidate_rect(widget->window, &update_rect, FALSE);
}
}
return TRUE;
}
void on_expose_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)
{
struct timeval tv_start, tv_end;
cairo_t *cr;
cr = gdk_cairo_create(widget->window);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_set_line_width(cr, 1);
cairo_move_to(cr, prex, prey);
cairo_line_to(cr, nowx, nowy);
gettimeofday(&tv_start, NULL);
cairo_stroke(cr);
gettimeofday(&tv_end, NULL);
printf("draw use time: %dus\n", (tv_end.tv_sec-tv_start.tv_sec)*1000000 + (tv_end.tv_usec-tv_start.tv_usec));
//this takes almost 400ms when I draw a line from the upper left to lower right.
cairo_destroy(cr);
return FALSE;
}
int main (int argc,char* argv[])
{
GtkWidget *canvas;
gtk_init(&argc,&argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size(GTK_WINDOW(window), 3840, 2160);
g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
canvas = gtk_drawing_area_new();
gtk_widget_set_size_request(canvas, 3840, 2160);
gtk_container_add(GTK_CONTAINER(window), canvas);
g_signal_connect(canvas, "expose_event", G_CALLBACK(on_expose_event), NULL);
g_signal_connect(canvas, "motion_notify_event", G_CALLBACK(on_motion_notify_event), NULL);
g_signal_connect(canvas, "button_press_event", G_CALLBACK(on_button_press_event), NULL);
g_signal_connect(canvas, "button_release_event", G_CALLBACK(on_button_release_event), NULL);
gtk_widget_set_events(canvas, gtk_widget_get_events(canvas)
| GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
gtk_widget_show_all(window);
gtk_main();
return FALSE;
}
Try re-drawing the line only if the last mouse-move event was more than KL milliseconds ago, but no more than MN milliseconds (KL < MN, of course). In that way, you decrease the computational needs, while still providing responsiveness.

GTK3+ - How to update graphics based on mouse click?

I am trying to modify the cairo graphics rendering I found in http://zetcode.com/gfx/cairo/basicdrawing/ so that it updates the graphics when I click the drawing area with the left mouse button. Currently the code in the website uses the right mouse button click to update the graphics.
I have tried adding gtk_widget_queue_draw(widget); to gboolean clicked(), when event->button == 1:
#include <cairo.h>
#include <gtk/gtk.h>
static void do_drawing(cairo_t *);
struct {
int count;
double coordx[100];
double coordy[100];
} glob;
static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
gpointer user_data)
{
do_drawing(cr);
return FALSE;
}
static void do_drawing(cairo_t *cr)
{
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_set_line_width(cr, 0.5);
int i, j;
for (i = 0; i <= glob.count - 1; i++ ) {
for (j = 0; j <= glob.count - 1; j++ ) {
cairo_move_to(cr, glob.coordx[i], glob.coordy[i]);
cairo_line_to(cr, glob.coordx[j], glob.coordy[j]);
}
}
glob.count = 0;
cairo_stroke(cr);
}
static gboolean clicked(GtkWidget *widget, GdkEventButton *event,
gpointer user_data)
{
if (event->button == 1) {
glob.coordx[glob.count] = event->x;
glob.coordy[glob.count++] = event->y;
gtk_widget_queue_draw(widget);
}
if (event->button == 3) {
gtk_widget_queue_draw(widget);
}
return TRUE;
}
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *darea;
glob.count = 0;
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), "draw",
G_CALLBACK(on_draw_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), 400, 300);
gtk_window_set_title(GTK_WINDOW(window), "Lines");
gtk_widget_show_all(window);
gtk_main();
return 0;
}
However, this does not work since now the graphics aren't updated at all.
Is there a way of updating the graphics by pressing the left mouse button (button 1) and simultaneously carry out inserting the coordinates to struct glob as before?
Thanks!
You are setting glob.count = 0 after drawing the lines, however glob.count is only updated by one in the clicked() callback, so it will never draw more than a single line. Also the first line is from point (glob.coordx[0], glob.coordy[0]) to (glob.coordx[0], glob.coordy[0]) (that is: it starts and ends at the same point) so it has no length, and will not be visible.
Solution: remove the line glob.count = 0; from do_drawing().
g_signal_connect(window, "button-press-event", G_CALLBACK(clicked), NULL);
When your callback is called, the window (which emits "button-press-event") is passed as first parameter. The queue_draw call queues redraw of window. However, redraw of DrawingArea is not requested and will not happen.
You can:
Queue redraw of DrawingArea
Connect to "button-press-event" of DrawingArea
Without any changes of code: press a mouse button and resize the window. Gtk will see, that DrawingArea has changed (it's resized too) and queue redraw of this widget.

Size of the GDK double buffer

When (re)drawing CAIRO rectangles after an expose_event, I see that the double buffering prevents the screen from flickering. Unfortunately when resizing the window frame the double buffer seems to "hang" on the screen as a shadow. Setting double buffering to FALSE, removes the shadow but makes a complex drawing flicker.
If I would "invalidate" the complete newly resized window area (in the expose event), the problem is solved, but the expose event fires constantly new request to redraw.
How can I resize the GDK clip region in a double buffered context ?
thanks a lot
The program code I used detect the cause of problems :
#include <stdio.h>
#include <cairo.h>
#include <gtk/gtk.h>
static gboolean on_expose_event (GtkWidget *widget,
GdkEventExpose *event ,
gpointer data)
{
cairo_t *cr;
int width, height, kwadrant;
cr = gdk_cairo_create (widget->window);
gtk_window_get_size(GTK_WINDOW(widget), &width, &height);
cairo_rectangle(cr, 0.0, 0.0, width, height);
cairo_set_source_rgb(cr, 0.0, 0.0, 0.5);
cairo_fill(cr);
if (width < height)
kwadrant = 0.8 * width;
else
kwadrant = 0.8 * height;
cairo_set_line_width(cr, 1);
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
cairo_rectangle(cr, (width - kwadrant)/2, (height - kwadrant)/2, kwadrant, kwadrant);
cairo_stroke(cr);
cairo_destroy(cr);
return TRUE;
}
void frame_callback(GtkWindow *window,
GdkEvent *event,
gpointer data)
{
char buff [50];
sprintf (buff, "%i X %i", event->configure.width,
event->configure.height);
gtk_window_set_title(window, buff);
return;
}
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);
gtk_widget_set_app_paintable(window, TRUE);
gtk_widget_set_double_buffered(window, TRUE);
g_signal_connect(G_OBJECT(window), "expose-event",
G_CALLBACK(on_expose_event), NULL);
g_signal_connect(G_OBJECT(window), "configure-event",
G_CALLBACK(frame_callback), NULL);
g_signal_connect_swapped(G_OBJECT(window), "destroy",
G_CALLBACK(gtk_main_quit), G_OBJECT(window));
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);
gtk_widget_show (window);
gtk_main();
return 0;
}
========= added later :
As it often happens :
After searching for days for a valid solution, I posted this message. A few moments afterwards I tried to isolate the "firing the clipping" outside the EXPOSE event and replace all
gtk_widget_queue_draw (widget);
calls with
void request_redraw (GtkWidget *widget)
{
GdkRectangle rect;
int width, height;
gtk_window_get_size (GTK_WINDOW(window), &width, &height);
rect.x = 0;
rect.y = 0;
rect.width = width;
rect.height = height;
gdk_window_invalidate_rect (gtk_widget_get_window(widget), &rect, TRUE);
gtk_widget_queue_draw (widget);
return;
}
This allows me to keep the double buffering, avoid the flickering of the screen and to redraw the full area that corresponds with the resized frame/window.
Is this a correct solution or is there a better way ?
Sorry for having asked an unnecessary question

Drawing on the GtkDrawingArea with Xlib func

I have a problem.
I need to draw on the widget type GtkDrawingArea using functions Xlib (XDrawLine etc).
Why?
I use the library, which draws with Xlib. And I need to pass any arguments (Display, Window, GC) in the rendering function drawSome (...). All is well. I obtain these arguments (via gdk_x11_... (), GdkDrawable, GdkGC) and call drawSome (...) with obtained parameters.
But there are problems - drawing is not always done. The image is not displayed when maximizing windows, dragging, resizing DrawingArea etc.. The image is displayed only under unusual manipulation of the top window .
Then I tested the function XDrawPoint/Line/Rectangle - the same problem. If we use gdk_draw_rectangle (...) - all is normal.
Here's the code below:
...
GtkDrawingArea* area;
...
int main (int argc, char *argv[])
{
...
area=GTK_DRAWING_AREA(gtk_builder_get_object(builder,"area"));
gtk_widget_realize (GTK_WIDGET(area));
...
g_signal_connect (G_OBJECT(area), "expose_event", G_CALLBACK(expose_event_callback), NULL);
...
}
...
gboolean expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
{
Display *dpy = gdk_x11_drawable_get_xdisplay(widget->window);
Window win =gdk_x11_drawable_get_xid(widget->window);
GC gc = DefaultGC(dpy, DefaultScreen(dpy));
//draw image on (0,0) in widget DrawingArea and a small black rectangle over image
drawSome(dpy, win, gc, ...);
XFillRectangle(dpy, win, gc, 0, 0, 10, 10);
return FALSE;
}
...
Image and a small black rectangle displayed only in one case: if the window move beyond the desktop and return back to the desktop - the image appears. In other cases, it is not displayed.
The impression is that another function erases DrawingArea.
Who can tell me what's the problem?
I would be grateful!
And... sorry to so bad English!
I think all you need to do is add XFlush(dpy); after the XFillRectangle command. I wrote a short routine and it seems to work.
#include <X11/Xlib.h> //-lX11
#include <gtk/gtk.h> //$$(pkg-config --cflags --libs gtk+-3.0)
#include <gdk/gdkx.h>
void DrawOnWidget(GtkWidget *widget)
{
GdkDisplay *gdk_dis = gdk_display_get_default();
Display *dis = gdk_x11_display_get_xdisplay (gdk_dis);
GC gc = DefaultGC(dis, DefaultScreen(dis));
GdkWindow *gdk_window = gtk_widget_get_window(widget);
Window win = gdk_x11_window_get_xid(gdk_window);
unsigned long valuemask = GCForeground;
XGCValues vColor;
vColor.foreground = 0x000000FF;
XChangeGC(dis, gc, valuemask, &vColor);
XFillRectangle(dis, win, gc, 0, 0, 100, 100);
XFlush(dis);
}
You'll need to use these functions X Window System Interaction but be warned but there may be other pitfalls. I think you'll also need to disable double-buffering for your GtkDrawingArea using gtk_widget_set_double_buffered
This is my second answer to this question... My first answer probably addresses the original question so I left it as unchanged. Although, the more general solution which is what I was looking for when I first found this thread was not addressed therefore, I decided to post a more general solution as well.
In short, I created a gtk image widget which can be displayed independently or attached to other widgets like buttons. Then I sent the image widget to the drawing function. In the drawing function all of the xlib parameters are queried in order to create a Pixmap as the xlib drawable which is where all xlib drawing is rendered. Then the GdkPixbuf is created and the Pixmap pixels are copied to the GdkPixbuf which is then set to the image widget...
There are a few commented lines which can be used to change the behavior the explanation will be left to the reader to determine.
It should be noted that while using xlib is possible; cairo appears to be a bit less cumbersome to implement.
#include <gtk/gtk.h> //$$(pkg-config --cflags --libs gtk+-3.0)
#include <gdk/gdkx.h>
#include <X11/Xlib.h> //-lX11
void DrawOnWidget(GtkWidget *widget, int width, int height)
{
GdkDisplay *gdk_dis = gdk_display_get_default();
Display *dis = gdk_x11_display_get_xdisplay (gdk_dis);
GC gc = DefaultGC(dis, DefaultScreen(dis));
GdkWindow *gdk_window = gtk_widget_get_window(widget);
Window win = gdk_x11_window_get_xid(gdk_window);
GdkPixbuf *pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, width, height);
//GdkPixbuf *pb = gtk_image_get_pixbuf((GtkImage *) widget);
char *data = (char *) gdk_pixbuf_read_pixels((const GdkPixbuf *) pb); //RGB(A)
//int width = gdk_pixbuf_get_width(pb);
//int height = gdk_pixbuf_get_height(pb);
int pb_depth = gdk_pixbuf_get_n_channels(pb);
int depth = DefaultDepth(dis, DefaultScreen(dis)) / 8;
Pixmap pm = XCreatePixmap(dis, win, width, height, depth * 8);
unsigned long valuemask = GCForeground;
XGCValues vColor;
vColor.foreground = 0x00FF0000;
XChangeGC(dis, gc, valuemask, &vColor);
XFillRectangle(dis, pm, gc, 0, 0, width, height);
XFlush(dis);
XImage *ximage = XGetImage(dis, pm, 0, 0, width, height, AllPlanes, ZPixmap); //BGRX
for(int i=0, j=0; i<width*height*pb_depth; i+=pb_depth, j+=4)
{
data[i+0] = ximage->data[j+2];
data[i+1] = ximage->data[j+1];
data[i+2] = ximage->data[j+0];
if(pb_depth == 4) data[i+3] = 255;
}
gtk_image_set_from_pixbuf((GtkImage *) widget, pb);
XFreePixmap(dis, pm);
g_object_unref(pb);
return;
}
static void destroy( GtkWidget *widget, gpointer data )
{
gtk_main_quit();
}
static void test( GtkWidget *widget, gpointer data )
{
DrawOnWidget((GtkWidget *) data, 200, 200);
}
void GTK_Win()
{
GtkWidget *window, *grid;
GtkWidget *button_Exit, *button_Test;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (window, "destroy", G_CALLBACK (destroy), NULL);
grid = gtk_grid_new ();
gtk_container_add (GTK_CONTAINER (window), grid);
button_Exit = gtk_button_new_with_label ("x");
g_signal_connect (button_Exit, "clicked", G_CALLBACK (destroy), NULL);
//GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 200, 200);
//GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
GtkWidget *image = gtk_image_new();
button_Test = gtk_button_new_with_label ("Test");
g_signal_connect (button_Test, "clicked", G_CALLBACK (test), image);
//gtk_button_set_image((GtkButton *) button_Test, image);
gtk_grid_attach (GTK_GRID (grid), button_Exit, 0, 0, 1, 1);
gtk_grid_attach (GTK_GRID (grid), button_Test, 0, 1, 1, 1);
gtk_grid_attach (GTK_GRID (grid), image, 0, 2, 1, 1);
gtk_widget_show_all (window);
gtk_main();
}
int main(int argc, char** argv)
{
gtk_init (&argc, &argv);
GTK_Win();
return 0;
}

How to rotate image using GTK+ / Cairo

I've got a simple application that is supposed to rotate a decorated wheel so many degrees every x number of milliseconds using GTK+ and Cairo. I've got some code below that calls cairo_rotate() from a timer. However, the image doesn't change. Do I have to invalidate the image to cause the expose-event to fire? I'm so new to Cairo that a simple example demonstrating how to rotate an image using Cairo in GTK+ would be highly appreciated.
#include <cairo.h>
#include <gtk/gtk.h>
cairo_surface_t *image;
cairo_t *cr;
gboolean rotate_cb( void )
{
cairo_rotate (cr, 1);
//cairo_paint(cr);
printf("rotating\n");
return( TRUE );
}
static gboolean
on_expose_event(GtkWidget *widget,
GdkEventExpose *event,
gpointer data)
{
cr = gdk_cairo_create (widget->window);
cairo_set_source_surface(cr, image, 0, 0);
cairo_paint(cr);
printf("Paint\n");
//cairo_destroy(cr);
return FALSE;
}
int main(int argc, char *argv[])
{
GtkWidget *window;
image = cairo_image_surface_create_from_png("wheel.png");
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), 500, 500);
gtk_widget_set_app_paintable(window, TRUE);
gtk_widget_show_all(window);
g_timeout_add(500, (GSourceFunc) rotate_cb, NULL);
gtk_main();
cairo_destroy(cr);
cairo_surface_destroy(image);
return 0;
}
You need to store the rotation in a variable and put the cairo_rotate(cr, rotation_amt); call into the on_expose_event method, before paint.
Also translate to the center of the window, rotate, and translate back, to make the wheel rotate around it's center, if the image is centered.
cairo_translate(cr, width / 2.0, height / 2.0);
cairo_rotate(cr, rotation_amt);
cairo_translate(cr, - image_w / 2.0, - image_h / 2.0);
cairo_set_source_surface(cr, image, 0, 0);
cairo_paint(cr);
I hope that's right.
And as ptomato said, you need to invalidate your drawing surface by calling gtk_widget_queue_draw from rotate_cb. And keeping a global variable for the Cairo context is redundant. The image doesn't rotate because a newly created context is loaded with an identity matrix and all your previous transformations are reset.
With the help of the cairo mailing list, this is a working example. I'm sharing this for those who might find this useful.
#include <cairo.h>
#include <gtk/gtk.h>
#include <math.h>
#include <stdlib.h>
cairo_surface_t *image;
cairo_t *cr;
gdouble rotation = 0;
GtkWidget *window;
gint image_w, image_h;
double DegreesToRadians( int degrees );
double DegreesToRadians( int degrees )
{
return((double)((double)degrees * ( M_PI/180 )));
}
gboolean rotate_cb( void *degrees )
{
// Any rotation applied to cr here will be lost, as we create
// a new cairo context on every expose event
//cairo_rotate (cr, 4);
rotation += DegreesToRadians((*(int*)(degrees)));
//cairo_paint(cr);
// printf("rotating\n");
// Tell our window that it should repaint itself (ie. emit an expose event)
gtk_widget_queue_draw(window);
return( TRUE );
}
static gboolean on_expose_event(GtkWidget *widget, GdkEventExpose *event,gpointer data)
{
// Make sure our window wasn't destroyed yet
// (to silence a warning)
g_return_if_fail(GTK_IS_WIDGET(widget));
cr = gdk_cairo_create (widget->window);
// We need to apply transformation before setting the source surface
// We translate (0, 0) to the center of the screen,
// so we can rotate the image around its center point,
// not its upper left corner
cairo_translate(cr, image_w/2, image_h/2);
cairo_rotate(cr, rotation);
cairo_set_source_surface(cr, image, -image_w/2, -image_h/2);
// We need to clip around the image, or cairo will paint garbage data
//cairo_rectangle(cr, -image_w/2, -image_h/2, image_w, image_h);
//cairo_clip(cr);
cairo_paint(cr);
//printf("Paint\n");
cairo_destroy(cr);
return FALSE;
}
int main(int argc, char *argv[])
{
int degrees = 10, speed = 125;
image = cairo_image_surface_create_from_png("wheel.png");
image_w = cairo_image_surface_get_width(image);
image_h = cairo_image_surface_get_height(image);
gtk_init(&argc, &argv);
if( argc == 3 )
{
degrees = atoi(argv[1]);
speed = atoi(argv[2]);
}
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), image_w, image_h);
gtk_widget_set_app_paintable(window, TRUE);
gtk_widget_show_all(window);
g_timeout_add(speed, (GSourceFunc) rotate_cb, (void *)&degrees);
gtk_main();
cairo_surface_destroy(image);
return 0;
}

Resources