Coordinate Systems

As a side effect of its complex needs, the GNOME Canvas must use more than one coordinate system to express the locations of its objects at different times. Each coordinate system has a slightly different meaning, as described in Sections 11.2.1 through 11.2.4. The Canvas provides several helper functions to make it easy for you to transform from one system to another. Internally, the Canvas uses affine transforms (see Section 10.4.5) to perform these translations, but you don't need to know this to use the functions. You can, however, directly access these affine matrices if you need to.

World Coordinates

The Canvas uses a logical, abstracted coordinate space as its native, internal coordinate system. This coordinate system, called world coordinates, is used not for rendering operations (the Canvas must transform to a pixel-oriented coordinate space for that), but rather for exact, conceptual placement of objects. World coordinates are expressed as unbounded floating-point numbers, so 10.6221 and -335.0105 are equally valid, although depending on the visible area, they may or may not appear on the rendered Canvas.

Like most GUI-related coordinates, world coordinates have an inverted y-axis; that is, the positive numbers become larger as you move down your screen. Unlike pixel-based screen coordinates, the negative values of which are always off-screen, the negative world coordinates can be visible just like positive coordinates. It all depends on how you set up the viewing area. Thus the world coordinates origin at (0.0, 0.0) does not necessarily have to correspond to the upper left corner of the Canvas widget. It can be offset by an arbitrary amount.

The Canvas will convert world coordinates into screen coordinates when it's ready to render itself to the drawing buffer, so you can use whatever scale is most convenient for you. World coordinates need not have a one-to-one cor- relation to screen pixels. One thing you should keep in mind is that the aspect ratio between the x and y coordinates does not change between world and screen coordinate systems. You cannot stretch your coordinate system in one direction without stretching it equally in the other direction.

You can choose the range of world coordinates when you set up the Canvas, so you should settle on something that makes sense in your application. For example, don't use negative numbers unless something in your display model specifically calls for it. In most cases, positive-only world co- ordinates work fine.

Item Coordinates

Although world coordinates represent the native, global space of the Canvas, each individual Canvas item has its own private coordinate space, expressed in the same units as the world coordinate system. In fact, item coordinates are more or less world coordinates offset so that the origin falls at the upper left corner of the item in question. Thus a point at world coordinates (125.0, 150.0) on a Canvas item located at (100.0, 100.0) would have item coordinates of (25.0, 50.0) for that particular item. The same point might have different item coordinates for a second Canvas item if that Canvas item were offset from the first Canvas item.

The Canvas comes with a handful of easy-to-use functions to convert between coordinate systems. The following functions convert between world coordinates and item coordinates. All Canvas items carry a pointer to the Canvas that owns them, so it's not necessary to supply the Canvas as a separate parameter:

void gnome_canvas_item_w2i(GnomeCanvasItem *item, 
    double *x, double *y);
void gnome_canvas_item_i2w(GnomeCanvasItem *item,
    double *x, double *y);

To use these functions, pass in pointers to two gdouble variables for the x and y coordinates. You should set the x and y parameters to the values you want to convert; the function will return the remapped coordinates in the same variables. In terms of our earlier example, the code might look like this:

/* Find item coordinates at world coordinates (125.0, 150.0) */
/* Item is located at (100.0, 100.0) */
gdouble xcoord = 125.0;
gdouble ycoord = 150.0;

/* Convert to item coordinates */
gnome_canvas_item_w2i(item, &xcoord, &ycoord);
/* xcoord == 25.0, ycoord == 50.0 */

/* Convert back to world coordinates (round-trip) */
gnome_canvas_item_i2w(item, &xcoord, &ycoord);
/* xcoord == 125.0, ycoord == 150.0 */

Occasionally you'll want to access the raw affine transforms that the Canvas uses to perform the above translations. The following functions will do the trick:

void gnome_canvas_item_i2w_affine(GnomeCanvasItem *item, 
    double affine[6]);
void gnome_canvas_item_w2i_affine(GnomeCanvasItem *item, 
    double affine[6]);

Canvas Coordinates

Eventually the Canvas must translate its abstract internal coordinate system into concrete integer-based coordinates for rendering to the RGB buffer, and eventually onto the screen. Because pixel buffers tend to be zero-based arrays, they are usually expressed with positive-only integer coordinates, ranging from the origin in the upper left corner, downward and to the right. In the Canvas these coordinates are referred to as canvas coordinates.

Again, the Canvas uses affine transformations to convert from one system to the other. This makes it possible to handle offsets and Canvas scaling. When the time comes to render, the Canvas can quite easily convert a world coordi- nate area with a bounding box defined by (-500.0, -500.0, +500.0, +500.0) into pixel-based canvas coordinates of (0, 0, 150, 150), for example. The Canvas will take care of these transformations internally. As we'll see in Section 11.3.2, all you have to do is set the viewable area and the scaling factor, and the Canvas will handle the rest.

The Canvas has three conversion functions for translating between the world and canvas coordinate systems. The basic functions, gnome_canvas_w2c( ) and gnome_canvas_c2w( ), take two gdouble parameters for the world coordinates and two int parameters for the Canvas coordinates. If you want to use gdouble parameters for both systems, you can use the gnome_canvas_w2c_d( ) function instead. These functions work similarly to the item-based conversion functions in the previous section, except that because the two systems have different types, it's not possible to pass the input and output values through the same parameters. Instead, you pass each coordinate separately:

void gnome_canvas_w2c(GnomeCanvas *canvas,
    double wx, double wy, int *cx, int *cy);
void gnome_canvas_c2w(GnomeCanvas *canvas,
    int cx, int cy, double *wx, double *wy);
void gnome_canvas_w2c_d(GnomeCanvas *canvas,
    double wx, double wy, double *cx, double *cy);

You can also use the affine transform. If you need to reverse it to obtain the c2w transform, you can call art_affine_invert( ), as discussed in Section 10.4.5.

void gnome_canvas_w2c_affine(GnomeCanvas *canvas,
    double affine[6]);

Window Coordinates

Window coordinates, the final coordinate system of interest to Canvas users, are rarely used within the Canvas because most internal Canvas calculations use world coordinates. Instead window coordinates are used usually outside of the Canvas. Like canvas coordinates, window coordinates are pixel based, but they are relative to the upper left corner of the top-level window that owns the Canvas, rather than to the upper left corner of the Canvas widget.

The only time you should need to translate to or from window coordinates is to process GDK events not already handled by the Canvas. For example, if you wanted to support drag-and-drop from external applications into your Canvas, you would have to process those events yourself. The coordinates passed inside the GDK events would be in window coordinates; you would need to transform them into native world coordinates before using them inside the Canvas because all the Canvas items store their own positions as world coordinates. This is exactly what the Canvas does internally when it's processing mouse and keyboard events.

void gnome_canvas_window_to_world(GnomeCanvas *canvas,
    double winx, double winy, double *worldx, double *worldy);
void gnome_canvas_world_to_window(GnomeCanvas *canvas,
    double worldx, double worldy, double *winx, double *winy);