The GNOME Canvas Federico Mena Quintero
federico@nuclecu.unam.mx
1999 The Free Software Foundation The GNOME canvas is an engine for structured graphics that offers a rich imaging model, high performance rendering, and a powerful, high-level API. It offers a choice of two rendering back-ends, one based on Xlib for extremely fast display, and another based on Libart, a sophisticated, antialiased, alpha-compositing engine. Applications have a choice between the Xlib imaging model or a superset of the PostScript imaging model, depending on the level of graphic sophistication required. This white paper presents the architecture of the GNOME canvas and Libart, and describes the situations in which it is useful to use these technologies.
Introduction The GNOME canvas is a high-level engine for creating structured graphics. This means the programmer can insert graphical items like lines, rectangles, and text into the canvas, and refer to them later for further manipulation. The programmer does not need to worry about repainting these items or generating events for them; the canvas automatically takes care of these operations. The canvas provides several predefined item types, including lines, rectangles, and text. However, the canvas is designed to serve as a general-purpose display engine. Applications can define their own custom item types that integrate with the rest of the canvas framework. This lets them have a flicker-free display engine with proper event management and propagation. The canvas supports two rendering models. One is based directly on Xlib (used via GDK), which provides an extremely fast and lean display that runs well over networks. The second rendering model is based on Libart, a sophisticated library for manipulating and rendering vector paths using antialiasing and alpha compositing. Libart provides a superset of the PostScript imaging model, allowing for extremely high-quality displays. Simple applications can use the predefined canvas item types to create interactive graphics displays. For example, the GNOME Calendar program uses the canvas to display and manipulate monthly calendars. It only needs simple graphical items like rectangles and text to display its information. Please look at for an example of the use of the canvas in the GNOME Calendar.
Use of the canvas in the <application>GNOME Calendar</application> The whole month view is a single big canvas. Stock canvas items like rectangles and text are used to display an easily-customizable calendar. The little monthly calendars are custom canvas groups based on the stock item types. The calendar program implements different behavior for each place in which the monthly calendar item is used.
Applications with more sophisticated requirements can define their custom canvas item types. The Gnumeric spreadsheet defines a large ‘Sheet’ item that takes care of painting the sheet's grid and the cell contents. It also defines an item to handle the complex cursor object in the spreadsheet, which must handle the current selection, the current cell, and the border items to drag and extend selections. Please look at for an example of the use of the canvas in the Gnumeric spreadsheet.
Use of the canvas in the <application>Gnumeric</application> spreadsheet Gnumeric uses a custom "Sheet" item to display the spreadsheet contents. This item is responsible for drawing grid lines and cell contents. The cursor is another custom item. In it is shown as a 4×2-cell selection with the current cell being G13. The cursor item handles all aspects of a spreadsheet's cursor, including the current cell, the selection range, and the clickable areas that let the user drag and fill cells automatically. The column and row headers are two modes of a single custom item. This item draws the button-like headers and highlights them as appropriate when cells are selected. Gnumeric uses the stock canvas items (like lines, rectangles, and ellipses) for the graphic elements that the user may want to overlay on the spreadsheet. Here the user has inserted an arrow, and the canvas takes care of repainting everything automatically.
This white paper describes the architecture of the GNOME canvas and Libart. It gives examples on why application writers may want to use these technologies.
The Canvas Architecture The original version of the GNOME canvas was based on the canvas widget from the Tk toolkit. From the standpoint of the application programmer, the canvas presents the following characteristics: A canvas appears as a normal GTK+ widget with its own GDK window (or X window). The programmer can insert graphical items into the canvas. The canvas provides several predefined item types, including lines, rectangles, ellipses, polygons, and text. Canvas items can be manipulated after they are created and inserted into the canvas. Common operations include changing the color of an item or moving it to a different position. Canvas items receive events just as if they were normal X windows or other widgets, and the programmer can bind these events to actions. Common actions include moving an item when the user drags it with the mouse, or changing an item's color when the mouse enters its visible area. Canvas items are normal GTK+ objects. Custom item types can be created by simply deriving a new object class from the base GnomeCanvasItemClass. Items emit events in the form of GTK+ signals, just like other widgets. The canvas takes care of all drawing operations so that it never flickers, and so that the user does not have to worry about repainting the items he or she wants to display. The canvas allows for hierarchical drawings by using nested groups of items. It uses recursive bounding boxes that allow culling of items for efficient repainting. Operations such as moving or deleting a group apply to all the items inside the group. This makes it easy to create and manipulate hierarchical graphics displays. Canvas Items Canvas items are normal GTK+ objects. All canvas items are ultimately derived from an abstract GnomeCanvasItemClass that defines the basic operations all canvas items must provide, like painting the item or deciding whether a point is inside it. Using the GTK+ object system provides several advantages: No extra work is involved in wrapping canvas items for different language bindings, so the canvas is usable from languages like Scheme, Perl, Python, and C++. Canvas items use the GTK+ signal/slot mechanism to emit events, making it easy to define behavior based on the events that items receive. One can associate arbitrary data items to canvas items by using the GTK+ dataset mechanism. All the attributes of items (line style, color, position) are configured using the GTK+ object argument mechanism. Since items may have many configurable attributes, using the object argument mechanism allows us to minimize the number of API entry points, and also makes it easier to create language bindings for the canvas. Items can be hidden and shown at any time, as well as modified using affine transformations. An affine transformation is a mapping of the plane onto itself, specified as a 3×3 matrix that can be multiplied by a vector that specifies a 2D point to transform it to a different coordinate system. The user can specify an arbitrary affine transformation matrix to be applied to an item. Convenience functions are provided for common operations like translating, scaling, and rotating and item. Grouping of Items Items can be organized in the canvas using a tree hierarchy. Items can be groups (nodes in the tree), or terminal items (leaves in the tree). Groups can contain any number of children, which can be leaf items or other groups. A GnomeCanvasGroupClass is provided to manage groups of items. Items can be nested to an arbitrary depth inside the canvas. A canvas has a single root group. Simple drawings or diagrams can be created by inserting all leaf items directly under the root. Complex schematics and hierarchical drawings can be created by nesting groups together. For example, a circuit editor may use small groups of basic items to create logic gates. More complex components could be created by combining the groups that represent logic gates, and complete circuits could, in turn, be composed of these components. Operations on a group apply to all of its children; for example, moving a group produces the same visual effect as moving each child individually. A canvas item must know how to compute the distance between a point and itself, so that the canvas can tell whether the mouse is inside an item or not. For efficiency, items keep a rectangular bounding box that lets the canvas ignore an item if a point is tested to be outside the item's bounds, which can be done very quickly. In turn, a canvas group will make its bounding box big enough to encompass all of its children's bounding boxes, allowing for efficient recursive culling of items. Items inside a group are stacked on top of each other, and items that are higher up in the stack obscure the items below them. An item can be raised or lowered in its parent group's stack. Behavior of Items The canvas does not define any default behavior for items. Instead, the programmer can create signal connections to the event signal in items and define the appropriate behavior. Items get the normal user-initiated events, such as mouse button press/release events, mouse motion events, mouse enter/leave events, key press/release events, and focus in/out events. When an event signal is emitted for an item, it is propagated upwards in the item hierarchy until it is marked as handled by one of the event handlers. This works in the same way as event propagation in the GTK+ widget system. The Updating and Rendering Process The canvas uses an update/render process when something requires a change in appearance. This process goes as follows: A state change happens in a canvas item, usually from direct manipulation through the user interface. The canvas item requests an update from the canvas. The item is thus marked as “requiring an update”. The canvas installs an idle handler on the GTK+ main loop. The application keeps running, possibly requesting updates for other items, until it gets back to the GTK+ main loop. This is where idle handlers are run. The idle handler for the canvas is run. Here, the canvas calls the update method of each item that requested an update. The update method may then queue a redraw of a certain area of the item. This area is represented as a microtile array, to be described later. The canvas decomposes the final microtile array into a list of rectangles that need repainting. The canvas calls the draw or render method of each item that intersects one of these rectangles, depending on whether the canvas is in Xlib or Libart modes, respectively. The canvas is now fully updated and redrawn, and the application continues running. This method has some important characteristics. First, all updates and redraw requests are delayed until the idle loop. This ensures that the canvas will not try to repaint itself until the last state change to an item has happened, and may also reduce the number of update-related operations that need to be performed. For example, changing the coordinates of a polygon's vertices several times is equivalent to changing them just once to the final position. Also, items are asked to draw themselves onto a temporary buffer (a GDK pixmap in the case of the Xlib renderer, or an RGB buffer in the case of the Libart renderer). This completely eliminates flicker. The Libart Library Libart is a high-performance rendering library that provides a rich imaging model. Libart's imaging model is a superset of PostScript, and it adds support for antialiasing and alpha compositing (transparency). It is similar to ‘next-generation’ imaging models such as Adobe's Bravo, the Java 2D API, Adobe's Precision Graphics Markup Language (PGML), and the World Wide Web Consortium's Scalable Vector Graphics (SVG). These are some of the data structures that Libart provides to applications. Point This is a simple 2D point specified as an ordered pair of coordinates. Rectangle A pair of points that define the opposite corners of a rectangle. Vector path A PostScript-like ordered list of operations and points that are used to define an open or closed path. Operations include moveto, lineto, and curveto. Affine transformation matrix An array of six numbers that define the values for a 3×3 transformation matrix. Depending on these values, the matrix can be used to scale, rotate, translate, or shear a point or a vector path. Bézier path Similar to a vector path, but each segment can be a Béezier curve specified by its control points. Sorted vector path (SVP) A vector path that has been processed so that its segments are stored with monotonically-increasing Y coordinates. This allows for very efficient rasterization, since the segments are in top-to-bottom order. Microtile array A simple data structure to represent 2D regions, particularly the region of a window that needs repainting. RGB and RGBA images Color images with optional opacity information. Some imaging operations provided by Libart include: Antialiased vector path filling. Vector path stroking, with a variety of line join and cap options. Vector path operations including intersection, union, and symmetric difference. Computation of microtile arrays. This can be done directly from rectangles or vector paths. Decomposing microtile arrays into a list of rectangles that are arranged in a way that is efficient for repainting. RGB and RGBA image compositing and transformation. Performance Libart uses several techniques to maximize performance. Microtile arrays allow the client application to efficiently compute and store the region that needs repainting. Sorted vector paths are an optimization for the vector rendering stage.