GtkImageViewer version 0.9.1 tutorial
=====================================
Dov  Grobgeld <dov.grobgeld@gmail.com>
v0.1, Tuesday 2008-10-07 23:48 

Background
----------
This tutorial shows in a few examples how to use the GtkImageViewer.
All the example codes may be found in the examples directory in the
source distribution.

A trivial image viewer
----------------------
The following is a trivial image viewer using the GtkImageViewer widget.
It takes as input a filename and displays it in a window with the
size of the image. The widget responds to keyboard and mouse input that 
has been modeled on Gimp and others.

.test_file.c
[c]
^^^^^^^^^^^^^^^
/* 
 * A trivial GtkImageViewer example shows the file given on the
 * command line.
 */
#include <stdlib.h>
#include "gtk-image-viewer.h"

int 
main (int argc, char *argv[])
{
  GtkWidget *window, *image_viewer;
  char *filename;
  
  gtk_init (&argc, &argv);

  if (argc < 2)
    {
      printf("Need name of image!\n");
      exit(0);
    }
  else
    filename = argv[1];
    
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
  gtk_window_set_title (GTK_WINDOW (window), filename);
  g_signal_connect (window, "destroy", G_CALLBACK (gtk_exit), NULL);

  image_viewer = gtk_image_viewer_new_from_file(filename);
  gtk_container_add (GTK_CONTAINER (window), image_viewer);

  gtk_widget_show_all (window);
  
  gtk_main ();
  
  return 0;
}
^^^^^^^^^^^^^^^

Note that the call to +gtk_window_set_policy()+ is needed in order to allow 
shrinking as well as expanding the maind window.

User interaction
----------------

The following interactions are honered:

ScrollWheel:: Zoom in and out by 10%
Mouse button 1:: Zoom in by a factor of 2 and center coordinate under mouse
Mouse button 2:: Pan
Mouse button 3:: Zoom out by a factor of 2
*F*:: Fill image in window
*H*:: Horizontal flip
*V*:: Vertical flip
*1* or *N*:: Show native size
*>* or *+* or *=*:: Zoom in
*<* or *-*:: Zoom out

Adding scrolling
----------------
To add scrolling, just wrap the image viewer inside a scrolled window. 
Note that you have to manually set the size of the scrolled window as 
it does not inherit it from the size of the widgets it manages. To
set the size of the widget it is more convinient to use the 
+gtk_image_viewer_new()+ constructor, that takes a pointed to a +GdkPixbuf+
as a parameter.

The example also adds a motion handler that shows the pixel coordinate
when the mouse moves over the image. Note that even if you zoom and
flip the image the correct pixel coordinate is shown.

.test_scroll.c
[c]
^^^^^^^^^^^^^^^
/* 
 * A basic image viewer using the GtkImageViewer widget that shows
 * the pixel coordinate when moving over the image.
 */
#include <stdlib.h>
#include <gtk/gtk.h>
#include <gtk-image-viewer.h>
#include <math.h>

static gint
cb_key_press_event(GtkWidget *widget, GdkEventKey *event)
{
  gint k = event->keyval;
  
  if (k == 'q')
      exit(0);

  return FALSE;
}

static gint
cb_motion_event(GtkWidget *widget,
                GdkEventButton *event,
                gpointer user_data)
{
    gchar pixel_info[64];
    GtkWidget *label = GTK_WIDGET(user_data);
    double img_x = 0, img_y = 0;

    gtk_image_viewer_canv_coord_to_img_coord(GTK_IMAGE_VIEWER(widget),
                                             event->x, event->y,
                                             &img_x, &img_y);
    
    g_snprintf(pixel_info, sizeof(pixel_info), "(%d,%d)", 
               (int)floor(img_x),
               (int)floor(img_y));
    gtk_label_set_text(GTK_LABEL(label), pixel_info);

    return 0;
}

int 
main (int argc, char *argv[])
{
  GtkWidget *window, *vbox, *image_viewer, *scrolled_win, *label;
  GdkPixbuf *img;
  int width, height;
  
  gtk_init (&argc, &argv);

  if (argc < 2)
    {
      printf("Need name of image!\n");
      exit(0);
    }
  else
    {
      GError *error = NULL;
      img = gdk_pixbuf_new_from_file (argv[1], &error);
    }
    
  width = gdk_pixbuf_get_width (img);
  height = gdk_pixbuf_get_height (img);
  
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
  gtk_window_set_title (GTK_WINDOW (window), argv[1]);
  g_signal_connect (window, "destroy", G_CALLBACK (gtk_exit), NULL);

  vbox = gtk_vbox_new(0,0);
  gtk_container_add (GTK_CONTAINER (window), vbox);
  
  scrolled_win = gtk_scrolled_window_new(NULL,NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_win),
                                 GTK_POLICY_AUTOMATIC,
                                 GTK_POLICY_AUTOMATIC);

  gtk_box_pack_start (GTK_CONTAINER (vbox), scrolled_win,
                      TRUE, TRUE, 0);
  gtk_widget_show(scrolled_win);

  image_viewer = gtk_image_viewer_new(img); 
  gtk_widget_set_size_request (image_viewer, width<500?width:500,
                               height<500?height:500); 
  g_signal_connect (window, "key_press_event",
                    G_CALLBACK(cb_key_press_event), NULL);
  gtk_container_add (GTK_CONTAINER (scrolled_win), image_viewer);
  gtk_widget_show (image_viewer);

  label = gtk_label_new("");
  gtk_box_pack_start (GTK_CONTAINER (vbox),
                      label, TRUE, TRUE, 0);
  gtk_widget_show(label);
  
  g_signal_connect (image_viewer, "motion_notify_event",
                    G_CALLBACK(cb_motion_event), label);

  gtk_widget_show_all (window);
  
  gtk_main ();
  
  return 0;
}
^^^^^^^^^^^^^^^

Drawing in the image annotate callback
--------------------------------------

The following example does not provide an image to the gtk image
viewer, but instead uses the image annotate callback to generate image
data dynamically.  The image annotate callback receives a GdkPixbuf from
the image being displayed and allows the user to annotate it before it
is being shipped to the display. This allows for flicker free drawing
of image overlays. In case no image has been provided to the
GtkImageViewer then image annotate is simply provided with a white
image.

.circle.c
[c]
^^^^^^^^^^^^^^^
//======================================================================
//  circle.c - A trivial example of how to generate pixel data on
//             demand. 
//
//  Dov Grobgeld <dov.grobgeld@gmail.com>
//  Fri Oct  3 15:51:01 2008
//----------------------------------------------------------------------
#include <math.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "gtk-image-viewer.h"


static void
cb_image_annotate(GtkImageViewer *imgv,
                  GdkPixbuf *pixbuf,
                  gint shift_x,
                  gint shift_y,
                  gdouble scale_x,
                  gdouble scale_y,
                  gpointer user_data
                  )
{
  int img_width = gdk_pixbuf_get_width(pixbuf);
  int img_height = gdk_pixbuf_get_height(pixbuf);
  int row_stride = gdk_pixbuf_get_rowstride(pixbuf);
  int pix_stride = 4;
  guint8 *buf = gdk_pixbuf_get_pixels(pixbuf);
  int col_idx, row_idx;

  for (row_idx=0; row_idx<img_height; row_idx++)
    {
      guint8 *row = buf + row_idx * row_stride;
      for (col_idx=0; col_idx<img_width; col_idx++)
        {
          gint gl = 255;
          double x=(col_idx+shift_x) / scale_x;
          double y=(row_idx+shift_y) / scale_y;
          
          // Draw a circle of size 4
          if (fabs(x*x + y*y) < 16)
            gl = 0;
          *(row+col_idx*pix_stride) = gl;   // r
          *(row+col_idx*pix_stride+1) = gl; // g

          // Play inner part of circle blue
          if (fabs(x*x + y*y) < 3.5*3.5)
            gl = 255;
          
          *(row+col_idx*pix_stride+2) = gl; // b
        }
    }
}

static gint
cb_key_press_event(GtkWidget *widget, GdkEventKey *event)
{
  gint k = event->keyval;
  
  if (k == 'q')
    exit(0);

  return FALSE;
}

int main(int argc, char **argv)
{
  GtkWidget *window, *image_viewer, *scrolled_win;
  GdkPixbuf *img;
  int width, height;

  gtk_init (&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
    
  gtk_window_set_title (GTK_WINDOW (window), "A circle");
  g_signal_connect (window, "destroy", G_CALLBACK (gtk_exit), NULL);

  scrolled_win = gtk_scrolled_window_new(NULL,NULL);
  gtk_container_add (GTK_CONTAINER (window), scrolled_win);
  gtk_widget_show(scrolled_win);
    
  image_viewer = gtk_image_viewer_new(NULL);
  g_signal_connect(image_viewer,
                   "image-annotate",
                   G_CALLBACK(cb_image_annotate), NULL);

  gtk_widget_set_size_request (window, 500,500);

  g_signal_connect (window, "key_press_event",
                    GTK_SIGNAL_FUNC(cb_key_press_event), NULL);
    
  gtk_container_add (GTK_CONTAINER (scrolled_win), image_viewer);
    
  gtk_widget_show (image_viewer);
  gtk_widget_show (window);

  // Set the scroll region and zoom range
  gtk_image_viewer_set_scroll_region(GTK_IMAGE_VIEWER(image_viewer),
                                     -5,-5,5,5);
  gtk_image_viewer_set_zoom_range(GTK_IMAGE_VIEWER(image_viewer),
                                  -HUGE, HUGE);

  // Need to do a manual zoom fit at creation because a bug when
  // not using an image.
  gtk_image_viewer_zoom_fit(GTK_IMAGE_VIEWER(image_viewer));
  gtk_main ();

  exit(0);
  return(0);
}
^^^^^^^^^^^^^^^

Complex dynamic image generation
--------------------------------

Here is a more interesting example using the same concept to 
generate a mandelbrot image. It shows the power of the image
generation on demand, even during panning. When panning, the
image viewer generates image annotate events only or the newly
exposed rectangles, and the rest of the image are just copied.

The same idea could be used for extracting data from a database,
e.g. in a GIS system.

.mandelbrot.c
[c]
^^^^^^^^^^^^^^^
//======================================================================
//  mandelbrot.c - An example of how to use the gtk image viewer
//  infinite scrolling with image generation on the fly.
//
//  Dov Grobgeld <dov.grobgeld@gmail.com>
//  Fri Oct  3 15:51:01 2008
//----------------------------------------------------------------------
#include <math.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "gtk-image-viewer.h"


#define MAX_IT 200
static int color_table[MAX_IT][3];

// Build a nice color table
static void build_color_table()
{
  int i;
  for (i=0; i<MAX_IT; i++)
    {
      // Three arbitrary functions that start at 0 and end at 1
      double r = pow(1.0*i/(MAX_IT-1), 0.1);
      double g = pow(1.0*i/(MAX_IT-1), 0.5);
      double b = pow(1.0*i/(MAX_IT-1), 1);
      color_table[i][0] = (int)(255*r);
      color_table[i][1] = (int)(255*g);
      color_table[i][2] = (int)(255*b);
    }
}

// Callback for exposure
static void
cb_image_annotate(GtkImageViewer *imgv,
                  GdkPixbuf *pixbuf,
                  gint shift_x,
                  gint shift_y,
                  gdouble scale_x,
                  gdouble scale_y,
                  gpointer user_data
                  )
{
  int img_width = gdk_pixbuf_get_width(pixbuf);
  int img_height = gdk_pixbuf_get_height(pixbuf);
  int row_stride = gdk_pixbuf_get_rowstride(pixbuf);
  int pix_stride = 4; // Fixed for GdkPixbuf's in cb_image_annotate
  guint8 *buf = gdk_pixbuf_get_pixels(pixbuf);
  int col_idx, row_idx;

  for (row_idx=0; row_idx<img_height; row_idx++) {
    guint8 *row = buf + row_idx * row_stride;
    for (col_idx=0; col_idx<img_width; col_idx++) {
      gint gl = 255;
      double x=(col_idx+shift_x) / scale_x;
      double y=(row_idx+shift_y) / scale_y;
      double x0 = x;
      double y0 = y;
      int iteration = 0;
      
      // Run mandelbrot test for (x,y) with code from wikipedia
      while(x*x + y*y < (2*2) && iteration < MAX_IT) {
        double xtemp = x*x - y*y + x0;
        y = 2 * x * y + y0;
        x = xtemp;
        iteration++;
      }
      
      *(row+col_idx*pix_stride) = color_table[iteration][0];
      *(row+col_idx*pix_stride+1) = color_table[iteration][1];
      *(row+col_idx*pix_stride+2) = color_table[iteration][2];
    }
  }
}

// main excluded as it is identical to the previous example
^^^^^^^^^^^^^^^

Result before and after zoom-in:

image:images/mandelbrot.png[Output of the mandelbrot program]

Drawing with cairo
------------------

The following example uses cairo to draw in the image annotate callback.
Note that because of incompatibilities between the GdkPixbuf and Cairo, it 
is necessary to swap the red and the blue color in the cairo context.

.cairo-circles.c
[c]
^^^^^^^^^^^^^^^
//======================================================================
//  cairo-circles.c - An example of how to use cairo to draw on demand.
//
//  This program is released in the public domain. 
//
//  Dov Grobgeld <dov.grobgeld@gmail.com>
//  Fri Oct  3 15:51:01 2008
//----------------------------------------------------------------------
#include <math.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <cairo.h>
#include "gtk-image-viewer.h"


static void
cb_image_annotate(GtkImageViewer *imgv,
                  GdkPixbuf *pixbuf,
                  gint shift_x,
                  gint shift_y,
                  gdouble scale_x,
                  gdouble scale_y,
                  gpointer user_data
                  )
{
  int img_width = gdk_pixbuf_get_width(pixbuf);
  int img_height = gdk_pixbuf_get_height(pixbuf);

  cairo_surface_t *surface
    = cairo_image_surface_create_for_data(gdk_pixbuf_get_pixels(pixbuf),
                                          CAIRO_FORMAT_RGB24,
                                          img_width,
                                          img_height,
                                          gdk_pixbuf_get_rowstride(pixbuf));
  cairo_t *cr = cairo_create (surface);
  cairo_translate(cr, -shift_x, -shift_y);
  cairo_scale(cr, scale_x, scale_y);

  // Now do any cairo commands you want, but you have to swap
  // R and B in set_source_rgba() commands because of cairo and
  // pixbuf incompabilitities.
  cairo_set_source_rgba (cr, 0,0,1.0,0.5);
  cairo_arc(cr,
            -1, 0,
            3, 0.0, 2*G_PI);
  cairo_fill(cr);

  cairo_set_source_rgba (cr, 1.0,0,0,0.5);
  cairo_arc(cr,
            1, 0,
            3, 0.0, 2*G_PI);
  cairo_fill(cr);

  cairo_set_source_rgba (cr, 0,0.9,0,0.9);
  cairo_arc(cr,
            0, 0,
            1, 0.0, 2*G_PI);
  cairo_fill(cr);

  // Display some tiny text
  cairo_set_font_size(cr, 0.08);
  cairo_set_source_rgba (cr, 0,0,0,1);
  cairo_move_to(cr, -0.2, 0);
  cairo_show_text(cr, "Cairo Rules!!!");
    
  // cleanup
  cairo_surface_destroy(surface);
  cairo_destroy(cr);
}

// main excluded as it is identical to the previous example
^^^^^^^^^^^^^^^

Result before and after zoom-in:

image:images/cairo-circles.png[Output of the cairo-circles program]

Drawing with agg
----------------

Anti-Grain graphics may be faster than cairo in certain situation, and 
it may also be used for drawing on the GdkPixbuf.

.agg-circles.c
[c]
^^^^^^^^^^^^^^^
//======================================================================
//  agg-example.c - Drawing with agg on the gtk image viewer.
//
//  This program is in the public domain
//  Dov Grobgeld <dov.grobgeld@gmail.com>
//  Sun Oct  5 21:34:03 2008
//----------------------------------------------------------------------

#include <math.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "agg2/agg_rasterizer_scanline_aa.h"
#include "agg2/agg_ellipse.h"
#include "agg2/agg_scanline_p.h"
#include "agg2/agg_renderer_scanline.h"
#include "agg2/agg_path_storage.h"
#include "agg2/agg_pixfmt_rgba.h"
#include "gtk-image-viewer.h"

static void
cb_image_annotate(GtkImageViewer *imgv,
                  GdkPixbuf *pixbuf,
                  gint shift_x,
                  gint shift_y,
                  gdouble scale_x,
                  gdouble scale_y,
                  gpointer user_data
                  )
{
  int width = gdk_pixbuf_get_width(pixbuf);
  int height = gdk_pixbuf_get_height(pixbuf);

  // initialize agg
  agg::rendering_buffer rbuf(gdk_pixbuf_get_pixels(pixbuf), 
                             width, 
                             height,
                             gdk_pixbuf_get_rowstride(pixbuf));
  agg::pixfmt_rgba32 pixf(rbuf);
  agg::renderer_base<agg::pixfmt_rgba32> rbase(pixf);
  agg::rasterizer_scanline_aa<> pf;
  agg::scanline_p8 sl;
  agg::ellipse e1;

  // Setup the affine transform
  agg::trans_affine mtx;
  mtx *= agg::trans_affine_scaling(scale_x, scale_y);
  mtx *= agg::trans_affine_translation(-shift_x, -shift_y);

  // Apply it for each element being drawn.
  agg::conv_transform<agg::ellipse, agg::trans_affine> trans(e1, mtx);

  e1.init(1,0,3,3,128);
  pf.add_path(trans);
  agg::render_scanlines_aa_solid(pf, sl, rbase,
                                 agg::rgba(1,0,0,0.5));

  e1.init(-1,0,3,3,128);
  pf.add_path(trans);
  agg::render_scanlines_aa_solid(pf, sl, rbase,
                                 agg::rgba(0,0,1,0.5));

  e1.init(0,0,1,1,128);
  pf.add_path(trans);
  agg::render_scanlines_aa_solid(pf, sl, rbase,
                                 agg::rgba(0,0.9,0,0.9));

}

// main excluded as it is identical to the previous example
^^^^^^^^^^^^^^^

The result is the same as with cairo:

image:images/agg-circles.png[Circles drawn with agg]

Drawing on top of images
------------------------

Drawing on top of images is really more of the same. The
example draws two circles around Lena's eyes. Notice the nice
cairo blending effect.

.lena-circles.c
[c]
^^^^^^^^^^^^^^^
static void
cb_image_annotate(GtkImageViewer *imgv,
                  GdkPixbuf *pixbuf,
                  gint shift_x,
                  gint shift_y,
                  gdouble scale_x,
                  gdouble scale_y,
                  gpointer user_data
                  )
{
  int img_width = gdk_pixbuf_get_width(pixbuf);
  int img_height = gdk_pixbuf_get_height(pixbuf);

  cairo_surface_t *surface
    = cairo_image_surface_create_for_data(gdk_pixbuf_get_pixels(pixbuf),
                                          CAIRO_FORMAT_RGB24,
                                          img_width,
                                          img_height,
                                          gdk_pixbuf_get_rowstride(pixbuf));
  cairo_t *cr = cairo_create (surface);
  cairo_translate(cr, -shift_x, -shift_y);
  cairo_scale(cr, scale_x, scale_y);

  // Now do any cairo commands you want, but you have to swap
  // R and B in set_source_rgba() commands because of cairo and
  // pixbuf incompabilitities.
  cairo_set_source_rgba (cr, 0,0,1.0,0.6);
  cairo_set_line_width(cr, 3);
  cairo_arc(cr,
            266.0, 267.0,
            12, 0.0, 2*G_PI);
  cairo_stroke(cr);

  cairo_set_source_rgba (cr, 1.0,0,0.0,0.6);
  cairo_arc(cr,
            327.0, 267.0,
            12, 0.0, 2*G_PI);
  cairo_stroke(cr);


  // cleanup
  cairo_surface_destroy(surface);
  cairo_destroy(cr);
}

// in main
  :
  image_viewer = gtk_image_viewer_new_from_file("lena.pgm"); 
  g_signal_connect(image_viewer,
                   "image-annotate",
                   G_CALLBACK(cb_image_annotate), NULL);
  :
^^^^^^^^^^^^^^^


The result is:

image:images/lena-circle.png[Circles drawn with cairo on top of an image]
