|
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 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".)
|
|