Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Gtk+/Gnome Application Development
Prev Home Next

Drawing Methods

The most important task of any canvas item is rendering itself onto the canvas. Rendering is a two-stage process for efficiency reasons. The first stage, implemented in a GnomeCanvasItem's update method, is guaranteed to happen only once per item per rendering cycle; the idea is to do any expensive affine transformations or other calculations in the update method. In the second stage, the canvas item renders itself to some region on the screen. The render method implements stage two for antialiased items, while the draw method implements stage two for GDK items. An item's render or draw method may be invoked multiple times during a canvas repaint.

Rendering occurs in a one-shot idle function. That is, whenever the canvas receives an expose event or otherwise determines that a redraw is needed, it adds an idle function which removes itself after a single invocation. (An idle function runs when no GTK+ events are pending and the flow of execution is in the GTK+ main loop---see the section called The Main Loop in the chapter called GTK+ Basics for details.) The canvas maintains a list of redraw regions and adds to it whenever a redraw request is received, so it knows which areas to repaint when the idle handler is finally invoked.

Canvas items carry a flag indicating whether they need to be updated. Whenever a canvas item "changes" (for example, if you set a new fill color for GnomeCanvasRect), it will call gnome_canvas_item_request_update() to set the "update needed" flag for itself and the groups that contain it, up to and including the root canvas group. (The GnomeCanvas widget is only aware of a single canvas item, the root group---all other items are handled recursively when methods are invoked on the root group.) In its one-shot idle function, the canvas invokes the update method of the root canvas item if its update flag is set, then clears the flag so the update method will not be run next time. The GnomeCanvasGroup update method does the same for each child item.

Once all canvas items have been updated, the rendering process begins. The canvas creates an RGB or GdkPixmap buffer, converts its list of redraw regions into a list of buffer-sized rectangles, then invokes the render or draw method of the root canvas group once per rectangle. After each rectangle is rendered, the buffer is copied to the screen.

The Update Method

The update method is primarily used by antialiased canvas items. libart_lgpl can prebuild a vector path to be rendered, performing clipping and affine transformation in advance. The render method stamps the pre-assembled path into the RGB buffer.

The update method is one of the two that GnomeCanvasRect and GnomeCanvasEllipse have to implement differently. Here is the GnomeCanvasRect implementation:


static void
gnome_canvas_rect_update (GnomeCanvasItem *item, double affine[6], 
                          ArtSVP *clip_path, gint flags)
{
  GnomeCanvasRE *re;
  ArtVpath vpath[11];
  ArtVpath *vpath2;
  double x0, y0, x1, y1;
  double dx, dy;
  double halfwidth;
  int i;

  gnome_canvas_re_update_shared (item, affine, clip_path, flags);

  re = GNOME_CANVAS_RE (item);

  if (item->canvas->aa) {
    x0 = re->x1;
    y0 = re->y1;
    x1 = re->x2;
    y1 = re->y2;

    gnome_canvas_item_reset_bounds (item);

    if (re->fill_set) {
      vpath[0].code = ART_MOVETO;
      vpath[0].x = x0;
      vpath[0].y = y0;
      vpath[1].code = ART_LINETO;
      vpath[1].x = x0;
      vpath[1].y = y1;
      vpath[2].code = ART_LINETO;
      vpath[2].x = x1;
      vpath[2].y = y1;
      vpath[3].code = ART_LINETO;
      vpath[3].x = x1;
      vpath[3].y = y0;
      vpath[4].code = ART_LINETO;
      vpath[4].x = x0;
      vpath[4].y = y0;
      vpath[5].code = ART_END;
      vpath[5].x = 0;
      vpath[5].y = 0;

      vpath2 = art_vpath_affine_transform (vpath, affine);

      gnome_canvas_item_update_svp_clip (item, &re->fill_svp, art_svp_from_vpath (vpath2), clip_path);
      art_free (vpath2);
    } else
      gnome_canvas_item_update_svp (item, &re->fill_svp, NULL);

    if (re->outline_set) {
      if (re->width_pixels)
        halfwidth = re->width * 0.5;
      else
        halfwidth = re->width * item->canvas->pixels_per_unit * 0.5;

      if (halfwidth < 0.25)
        halfwidth = 0.25;

      i = 0;
      vpath[i].code = ART_MOVETO;
      vpath[i].x = x0 - halfwidth;
      vpath[i].y = y0 - halfwidth;
      i++;
      vpath[i].code = ART_LINETO;
      vpath[i].x = x0 - halfwidth;
      vpath[i].y = y1 + halfwidth;
      i++;
      vpath[i].code = ART_LINETO;
      vpath[i].x = x1 + halfwidth;
      vpath[i].y = y1 + halfwidth;
      i++;
      vpath[i].code = ART_LINETO;
      vpath[i].x = x1 + halfwidth;
      vpath[i].y = y0 - halfwidth;
      i++;
      vpath[i].code = ART_LINETO;
      vpath[i].x = x0 - halfwidth;
      vpath[i].y = y0 - halfwidth;
      i++;

      if (x1 - halfwidth > x0 + halfwidth &&
          y1 - halfwidth > y0 + halfwidth) {
        vpath[i].code = ART_MOVETO;
        vpath[i].x = x0 + halfwidth;
        vpath[i].y = y0 + halfwidth;
        i++;
        vpath[i].code = ART_LINETO;
        vpath[i].x = x1 - halfwidth;
        vpath[i].y = y0 + halfwidth;
        i++;
        vpath[i].code = ART_LINETO;
        vpath[i].x = x1 - halfwidth;
        vpath[i].y = y1 - halfwidth;
        i++;
        vpath[i].code = ART_LINETO;
        vpath[i].x = x0 + halfwidth;
        vpath[i].y = y1 - halfwidth;
        i++;
        vpath[i].code = ART_LINETO;
        vpath[i].x = x0 + halfwidth;
        vpath[i].y = y0 + halfwidth;
        i++;
      }
      vpath[i].code = ART_END;
      vpath[i].x = 0;
      vpath[i].y = 0;

      vpath2 = art_vpath_affine_transform (vpath, affine);

      gnome_canvas_item_update_svp_clip (item, &re->outline_svp, art_svp_from_vpath (vpath2), clip_path);
      art_free (vpath2);
    } else
      gnome_canvas_item_update_svp (item, &re->outline_svp, NULL);
  } else {
    get_bounds (re, &x0, &y0, &x1, &y1);
    gnome_canvas_update_bbox (item, x0, y0, x1, y1);
  }
}
      

As you can see, the first thing this function does is invoke an update function shared by GnomeCanvasRect and GnomeCanvasEllipse; here is that function:


static void
gnome_canvas_re_update_shared (GnomeCanvasItem *item, double *affine, 
                               ArtSVP *clip_path, int flags)
{
  GnomeCanvasRE *re;

  re = GNOME_CANVAS_RE (item);

  if (re_parent_class->update)
    (* re_parent_class->update) (item, affine, clip_path, flags);

  if (!item->canvas->aa) {
    set_gc_foreground (re->fill_gc, re->fill_pixel);
    set_gc_foreground (re->outline_gc, re->outline_pixel);
    set_stipple (re->fill_gc, &re->fill_stipple, 
                 re->fill_stipple, TRUE);
    set_stipple (re->outline_gc, &re->outline_stipple, 
                 re->outline_stipple, TRUE);
    set_outline_gc_width (re);
  } 
}

      

There is a lot of code involved here; the update method is almost always the most complicated one, since it does all the work of preparing to render a canvas item. Also, the update method is different for GDK and antialiased mode; notice the code which depends on the item->canvas->aa flag.

The first thing GnomeCanvasRE does during an update is invoke the update method of its parent class. The GnomeCanvasItem default update method does nothing whatsoever in Gnome 1.0, but it is good practice to chain up for future robustness. Then, GnomeCanvasRE calls a series of utility routines to fill in its graphics contexts with their correct values. These are straightforward functions, so their implementations are omitted here.

Next gnome_canvas_rect_update() continues with GnomeCanvasRect-specific details. Several tasks are accomplished:

  • The bounding box of the canvas item is updated. Every canvas item has an associated bounding box; the GnomeCanvasGroup draw and render methods use this box to determine which items are in the redraw region. The bounding box must be updated in both GDK and antialiased mode.

  • In antialiased mode, a sorted vector path is created. A sorted vector path is simply a series of drawing instructions, similar to primitive PostScript operations, that libart_lgpl can render to an RGB buffer.

  • In antialiased mode, the affine and clip_path arguments to the update method are used to transform the sorted vector path; thus the affine and clip path are implicitly stored for use in the render method. If you do not use libart_lgpl's sorted vector paths in your own canvas items, you must arrange some other way to ensure the affine and clip are taken into account when you render.

  • In both modes, a redraw is requested for both the region the item used to occupy, and the region the item will now occupy.

Much of this work takes place behind the scenes in utility functions from libgnomeui/gnome-canvas-util.h. gnome_canvas_update_bbox() sets the item's new bounding box and requests a redraw on both the old and new bounding boxes; it is used in GDK mode. (gnome_canvas_update_bbox() expects canvas pixel coordinates; get_bounds() is a trivial function which computes the rectangle's bounds in canvas pixel coordinates.)

So you know what's happening behind the scenes, here is the implementation of gnome_canvas_update_bbox():


void
gnome_canvas_update_bbox (GnomeCanvasItem *item, 
                          int x1, int y1, 
                          int x2, int y2)
{
  gnome_canvas_request_redraw (item->canvas, 
                               item->x1, item->y1, 
                               item->x2, item->y2);
  item->x1 = x1;
  item->y1 = y1;
  item->x2 = x2;
  item->y2 = y2;
  gnome_canvas_request_redraw (item->canvas, 
                               item->x1, item->y1, 
                               item->x2, item->y2);
}
      

Of course you're free to do the equivalent yourself, this is merely a convenience function.

In GDK mode, that's about all that happens; we update the bounds and then return. Antialiased mode is a bit more complex, but essentially the same tasks are performed. First, gnome_canvas_item_reset_bounds() sets the item's bounds back to an empty rectangle. Then, two sorted vector paths are prepared; one for the solid part of the rectangle (if any), and one for the rectangle's outline (if any). The same procedure is followed each time. First, a vector path for libart_lgpl is prepared; next, the path is affine transformed; then gnome_canvas_item_update_svp_clip() is used to request a redraw on the old path, free the old path, clip the new path, request a redraw on the new one, and save the new one for use in rendering. If the rectangle's fill or outline has been turned off, a redraw is requested on the old vector path, but no new path is created.

To give you a clearer idea what is happening, here is the implementation of gnome_canvas_item_update_svp_clip():


void
gnome_canvas_item_update_svp_clip (GnomeCanvasItem *item, 
                                   ArtSVP **p_svp, ArtSVP *new_svp,
                                   ArtSVP *clip_svp)
{
  ArtSVP *clipped_svp;

  if (clip_svp != NULL) 
    {
      clipped_svp = art_svp_intersect (new_svp, clip_svp);
      art_svp_free (new_svp);
    } 
  else 
    {
      clipped_svp = new_svp;
    }

  gnome_canvas_item_update_svp (item, p_svp, clipped_svp);
}

      

and gnome_canvas_item_update_svp():


void
gnome_canvas_item_update_svp (GnomeCanvasItem *item, 
                              ArtSVP **p_svp, ArtSVP *new_svp)
{
  ArtDRect bbox;

  gnome_canvas_update_svp (item->canvas, p_svp, new_svp);
  if (new_svp) 
    {
      bbox.x0 = item->x1;
      bbox.y0 = item->y1;
      bbox.x1 = item->x2;
      bbox.y1 = item->y2;
      art_drect_svp_union (&bbox, new_svp);
      item->x1 = bbox.x0;
      item->y1 = bbox.y0;
      item->x2 = bbox.x1;
      item->y2 = bbox.y1;
    }
}
      

and then gnome_canvas_update_svp():


void
gnome_canvas_update_svp (GnomeCanvas *canvas, 
                         ArtSVP **p_svp, ArtSVP *new_svp)
{
  ArtSVP *old_svp;
  ArtSVP *diff;
  ArtUta *repaint_uta;

  old_svp = *p_svp;
  if (old_svp != NULL && new_svp != NULL) 
    {
      repaint_uta = art_uta_from_svp (old_svp);
      gnome_canvas_request_redraw_uta (canvas, repaint_uta);
      repaint_uta = art_uta_from_svp (new_svp);
      gnome_canvas_request_redraw_uta (canvas, repaint_uta);
    } 
  else if (old_svp != NULL) 
    {
      repaint_uta = art_uta_from_svp (old_svp);
      art_svp_free (old_svp);
      gnome_canvas_request_redraw_uta (canvas, repaint_uta);
    }
  *p_svp = new_svp;
}
      

Again, all of these are in libgnomeui/gnome-canvas-util.h for any canvas item to use. Ignore the implementation details; the idea is simply to see what work is being done. The code may be easier to understand if you know that an ArtDRect is a "rectangle defined with doubles," from libart_lgpl, and that an ArtUta is a "microtile array," basically a list of small regions. (The antialiased canvas tracks the redraw region in a fairly sophisticated way. Note that the "U" in "Uta" is supposed to suggest the greek letter symbolizing "micro," it does not stand for a word beginning with "U".)

Gtk+/Gnome Application Development
Prev Home Next

 
 
  Published under free license. Design by Interspire