Thursday, January 17, 2013

Image Scaling in GTK 3 (with Cairo)

GTK 3 differs significantly to GTK 2 in the image rendering department. In GTK 2, image rendering is handled by GDK via GdkPixbuf. On the other hand, GTK 3 moves almost all of the burden to Cairo. This could be rather confusing to GTK newbies.

This post explains how to carry out image scaling with Cairo via a GTK 3 sample application. In GTK 2, this is a relatively simpler task via GDK. You should consult this, if you want to do image scaling in GTK 2.

Now, let me go straight to the sample code. The makefile for the code as follows:
all:
 gcc $(shell pkg-config --cflags --libs gtk+-3.0) cairo_test.c -o cairo_test

clean:
 rm -vf *~ cairo_test
The makefile assumes that the (single) source code file is named cairo_test.c. Now, let's proceed to cairo_test.c.
#include <cairo.h>
#include <gtk/gtk.h>


static void do_drawing (cairo_t *, GtkWidget * widget);

struct
{
  cairo_surface_t *image;
} glob;

static gboolean
on_draw_event (GtkWidget * widget, cairo_t * cr, gpointer user_data)
{
  cr = gdk_cairo_create (gtk_widget_get_window (widget));
  do_drawing (cr, widget);
  cairo_destroy (cr);

  return FALSE;
}

static void
do_drawing (cairo_t * cr, GtkWidget * widget)
{
  gfloat screen_width;
  gfloat screen_height;
  gfloat image_width;
  gfloat image_height;
  gfloat x_scaling;
  gfloat y_scaling;

  /* Display screen dimension in console  */
  screen_width = gdk_screen_get_width (gdk_screen_get_default ());
  screen_height = gdk_screen_get_height (gdk_screen_get_default ());
  g_message ("Screen width %f", screen_width);
  g_message ("Screen height %f", screen_height);

  /* Scale the loaded image to occupy the entire screen  */
  image_width = cairo_image_surface_get_width (glob.image);
  image_height = cairo_image_surface_get_height (glob.image);

  g_message ("Image width %f", image_width);
  g_message ("Image height %f", image_height);

  x_scaling = screen_width / image_width;
  y_scaling = screen_height / image_height;

  g_message ("x_scaling %f", x_scaling);
  g_message ("y_scaling %f", y_scaling);

  cairo_scale (cr, x_scaling, y_scaling);

  cairo_set_source_surface (cr, glob.image, 0, 0);
  cairo_paint (cr);
}

static void
load_image ()
{
  glob.image =
    cairo_image_surface_create_from_png ("resources/quake_3_arena.png");
}

static void
draw_mark ()
{
  cairo_t *ic;
  ic = cairo_create (glob.image);
  cairo_set_font_size (ic, 11);

  cairo_set_source_rgb (ic, 0.9, 0.9, 0.9);
  cairo_move_to (ic, 100, 100);
  cairo_show_text (ic, " 343 Industries 2013 , (c) Pinczakko ");
  cairo_stroke (ic);
}

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

  load_image ();
  draw_mark ();

  gtk_init (&argc, &argv);


  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

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

  g_signal_connect (G_OBJECT (darea), "draw",
      G_CALLBACK (on_draw_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), "Cairo Test");
  gtk_window_set_decorated (GTK_WINDOW (window), FALSE);
  gtk_window_fullscreen (GTK_WINDOW (window));


  gtk_widget_show_all (window);

  gtk_main ();

  cairo_surface_destroy (glob.image);

  return 0;
}
In the code above you can see I'm using a file in the resources directory, with name quake_3_arena.png as the image to be loaded and scaled to the dimension of the entire screen (full screen). The image scaling and drawing happens in the "draw" event handler function.

You should pay attention to call cairo_paint() right after setting the cairo surface to draw into. In this case, the code calls cairo_set_source_surface() to do that in do_drawing() function. The code above is based on a modified code from this tutorial. The code displayed above, reads a PNG input file and then scale the image to the dimension of the screen before displaying it. The code also superimpose a "watermark" on the image by using the draw_mark() function.

Testing on Raspberry Pi with 1366x768 pixel display shows that Raspberry Pi is not quite up to the task when the image on the screen is repeatedly redrawn (for example when juggling between windows with Alt+Tab). In this particular case, the lag is noticeable before the scaled image is shown, could be up to 1 second.
I'm still evaluating whether or not 2D acceleration is enabled or not. Because I think Raspberry Pi have enough horsepower for the task (without the 1 second delay) of 2D acceleration is active.
Post a Comment

No comments: