Creating an offscreen snapshot of a GtkWidget



Hi all,

We are stuck in a problem where we want to create an offscreen
snapshot/pixbuf of a GtkWidget without showing the widget (realizing
is allowed). We want the same pixels which are displayed on the screen
when the widget is shown. Gdk does not do any drawing operations upon
realize. It does so only when the widget is shown/exposed. We tried to
send expose events to the realized widget but that also did not work.
We have read that GtkWidgets are double-buffered and that all drawing
operations go into the backend buffer, but we do not know if that can
be used for solving our problem.

Pasted below are two programs.   They can be compiled using 'g++
`pkg-config --cflags --libs gtk+-2.0` snapshot.cpp -lXpm' command
line.

Following is the program (this is a working one) which tries to get
the offscreen snapshot of a shown widget:

////////////////////////////////////////////////////////////////////////////////////////
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#include </usr/include/X11/xpm.h>

GtkWidget *window;
GtkWidget *button;
GtkWidget *vbox;
GtkWidget *drawing;

static void hello( GtkWidget *widget, gpointer data )
{
        g_print ("Hello World\n");

        gint width, height;

        GtkWidget *wid = widget;
        gtk_widget_show(wid);

        width = height = 200;
        GdkPixmap *gdkPixmap = gdk_pixmap_new(wid->window, width, height, -1);

        GdkGC *gc = gdk_gc_new(wid->window);
        gdk_draw_drawable(gdkPixmap, gc, wid->window, 0, 0, 0, 0, width, height);
        
        Pixmap xPixmap = GDK_PIXMAP_XID(gdkPixmap);
        
        XImage *mXimg = XGetImage(GDK_DISPLAY(), xPixmap, 0, 0, width,
height, AllPlanes, ZPixmap);
        
        XpmWriteFileFromImage(GDK_DISPLAY(), "/tmp/snapshot.xpm",  mXimg, 0, NULL);

        const gchar *l = gtk_button_get_label(GTK_BUTTON(wid));
        g_print("%s\n", l);
}

static gboolean delete_event( GtkWidget *widget, GdkEvent *event,
gpointer data )
{
        g_print ("delete event occurred\n");

        return FALSE;
}

static void destroy(GtkWidget *widget, gpointer data)
{
        gtk_main_quit();
}

int main(int argc, char *argv[])
{
        gtk_init (&argc, &argv);

        window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
        
        drawing = gtk_drawing_area_new();
        vbox = gtk_vbox_new(TRUE, 0);

        g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK
(delete_event), NULL);

        g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);

        button = gtk_button_new_with_label ("Hello World is good");
        gtk_widget_set_size_request(button, 200, 200);

        g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (hello), NULL);

        gtk_box_pack_start_defaults(GTK_BOX(vbox), button);
        gtk_box_pack_start_defaults(GTK_BOX(vbox), drawing);

        gtk_container_set_border_width(GTK_CONTAINER(window), 0);
        gtk_container_add (GTK_CONTAINER (window), vbox);
        
        gtk_widget_show_all(window);

        gtk_main ();

        return 0;
}
////////////////////////////////////////////////////////////////////////////////////////

The above program creates a snapshot of 'button' in /tmp/snapshot.xpm
upon clicking the button.  But we want the same without showing the
widget (say 'button' in the above program).

Here's the program we have tried (it does not work):
////////////////////////////////////////////////////////////////////////////////////////
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
#include </usr/include/X11/xpm.h>

GtkWidget *window;
GtkWidget *button;
GtkWidget *button1;
GtkWidget *button2;
GtkWidget *vbox;
GtkWidget *drawing;

static void hello( GtkWidget *widget, gpointer data )
{
        g_print ("Hello World\n");

        gint width, height;

        GtkWidget *wid = widget;
        gtk_widget_show(wid);

        width = height = 200;
        GdkPixmap *gdkPixmap = gdk_pixmap_new(wid->window, width, height, -1);

        GdkGC *gc = gdk_gc_new(wid->window);
        gdk_draw_drawable(gdkPixmap, gc, wid->window, 0, 0, 0, 0, width, height);
        
        Pixmap xPixmap = GDK_PIXMAP_XID(gdkPixmap);
        
        XImage *mXimg = XGetImage(GDK_DISPLAY(), xPixmap, 0, 0, width,
height, AllPlanes, ZPixmap);
        
        XpmWriteFileFromImage(GDK_DISPLAY(), "/tmp/snapshot.xpm",  mXimg, 0, NULL);

        const gchar *l = gtk_button_get_label(GTK_BUTTON(wid));
        g_print("%s\n", l);
}

static gboolean delete_event( GtkWidget *widget, GdkEvent *event,
gpointer data )
{
        g_print ("delete event occurred\n");

        return FALSE;
}

static void destroy(GtkWidget *widget, gpointer data)
{
        gtk_main_quit();
}

int main(int argc, char *argv[])
{
        gtk_init (&argc, &argv);

        window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
        
        drawing = gtk_drawing_area_new();
        vbox = gtk_vbox_new(TRUE, 0);

        g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK
(delete_event), NULL);

        g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);

        button = gtk_button_new_with_label ("Hello World is good");
        button1 = gtk_button_new_with_label ("Hello World 1");
        button2 = gtk_button_new_with_label ("Hello World 2");
        gtk_widget_set_size_request(button, 200, 200);
        gtk_widget_set_size_request(button1, 200, 200);
        gtk_widget_set_size_request(button2, 200, 200);

        g_signal_connect (G_OBJECT (button2), "clicked", G_CALLBACK (hello), NULL);

        gtk_box_pack_start_defaults(GTK_BOX(vbox), button);
        gtk_box_pack_start_defaults(GTK_BOX(vbox), button1);
        gtk_box_pack_start_defaults(GTK_BOX(vbox), button2);
        gtk_box_pack_start_defaults(GTK_BOX(vbox), drawing);

        gtk_container_set_border_width(GTK_CONTAINER(window), 0);
        gtk_container_add (GTK_CONTAINER (window), vbox);
        
        gtk_widget_realize(window);
        gtk_widget_realize(vbox);
        gtk_widget_realize(button);
        gtk_widget_show(button);
        gtk_widget_show(vbox);
        
        //all this is just to send expose events to the GdkWindow
programmatically. Since we want an offscreen snapshot of the widget
without showing it, so we are trying to pass expose events so that it
may draw itself correctly - but seems its not working

        //The hacks to send expose events start here
        GdkEvent eventExpose;
        eventExpose.expose.type = GDK_EXPOSE;
        eventExpose.expose.window = button->window;
        eventExpose.expose.send_event = TRUE;
        eventExpose.expose.area.x = 0;
        eventExpose.expose.area.y = 0;
        eventExpose.expose.area.width = 200;
        eventExpose.expose.area.height = 200;
        eventExpose.expose.count = 0;

        //gtk_widget_queue_draw(button);
        GdkRectangle rect;
        rect.x = 0;
        rect.y = 0;
        rect.width = 200;
        rect.height = 200;
        gdk_window_invalidate_rect(button->window, &rect, TRUE);

        gtk_widget_send_expose(button, &eventExpose);
        gtk_widget_send_expose(vbox, &eventExpose);

        //gdk_window_process_updates(button->window, TRUE);

        gdk_window_process_all_updates();
        //The hacks to send expose events end here

        //Code to get the contents of the GdkWindow in a offscreen pixmap
which we ultimately write to a file
        gint width, height;
        width = height = 200;
        GdkPixmap *gdkPixmap = gdk_pixmap_new(button->window, width, height, -1);

        GdkGC *gc = gdk_gc_new(button->window);
        gdk_draw_drawable(gdkPixmap, gc, button->window, 0, 0, 0, 0, width, height);
        
        Pixmap xPixmap = GDK_PIXMAP_XID(gdkPixmap);
        
        XImage *mXimg = XGetImage(GDK_DISPLAY(), xPixmap, 0, 0, width,
height, AllPlanes, ZPixmap);
        
        XpmWriteFileFromImage(GDK_DISPLAY(), "/tmp/snapshot_new.xpm",  mXimg, 0, NULL);
        //

        gtk_widget_realize(button);
        gtk_widget_realize(vbox);
        gtk_widget_realize(window);

        gtk_widget_show(button1);
        gtk_widget_show(button2);
        gtk_widget_show(drawing);
        gtk_widget_show(vbox);
        gtk_widget_show_all(window);
        
        //gtk_widget_hide(button);

        gtk_main ();

        return 0;
}
////////////////////////////////////////////////////////////////////////////////////////

Here we have hidden 'button' and try to send expose events and then
get its contents offscreen but that always returns a garbage xpm in
/tmp/snapshot_new.xpm

Even the xpm which is created in the 'hello' callback
(/tmp/snapshot.xpm) is not of the correct button in this case.


Can someone provide any pointers for creating an offscreen snapshot of
a GtkWidget.

Thanks,
narinder



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]