Karma Home Page
Next: Image Editing Up: Karma Programming Manual Previous: Event generation and dispatch

# Device-independent data-oriented graphics library

# Purpose

The image display support is intended to ease the display of computed images by modules (applications). In most situations, the X Window system is used as the basic graphics system. However, there is more to image display than just writing a 2-D array to the graphics system. Issues such as handling window resize events, colourmap resizes, fast movie animation, controlled zooming, cursor events, etc. complicate applications requiring image display. Besides, you may want to display a 2-D array as a contour map, rather than as a bitmapped image. The image display support provided in the Karma library can save thousands of lines of messy, intricate code.

# Related documents

The documentation on ``Intelligent Arrays'', communications support, colourmap support and image editing support are highly recommended. The documentation on Karma widgets explains how to build a complete graphical application using the Xt Graphical User Interface toolkit.

# Architecture

The image display support in Karma is built on a layered approach, in a similar way to the communications support. The image display support requires linking of the core Karma library and the Karma graphics library.

The various packages in the Karma library which provide image display support are listed below.

## ``ds'' package

The starting point for image display support in Karma is based on direct conversion from 2-D array to image display. Any simple, 2-D array using the Karma data format may be easily displayed. The data structure support is provided by the ds package. You can also construct your own C arrays and use a function like ds_wrap_preallocated_n_element_array to create the Karma data structure wrappers for the data, and then pass these to the graphics library.

## ``xi'' package

X11 XImage structure manipulation support is provided by the xi package. This package is not meant to be used by the application programmer: it provides support for higher levels in the Karma library. It is mentioned here only for reference.

## ``imw'' package

Image display support is provided by the imw package. This code is not meant to be used by the application programmer: it provides support for higher levels in the Karma library. It is mentioned here only for reference.

## ``kwin'' package

The lowest level display canvas is provided by the kwin package. It provides a pixel canvas class. A pixel canvas is graphics system independent. The graphics system is the layer of software that provides basic access to display hardware. There is support for the following graphics systems:

- The X Window System (using Xlib)
- The XGL rendering library
- The OpenGL rendering library
- A VX board (used to be hot stuff in it's day:-)

### Purpose

Routines are provided to create a pixel canvas from an X window. Many pixel canvases may be created per X window (though typically only one) and mosaiced. The routines to create pixel canvases are naturally graphics system dependent.

A number of routines allow drawing onto a pixel canvas. These routines have generic interfaces (i.e. no reference to the graphics system such as X). This allows an application to be written that can easily be ported to a non-X environment (provided the Karma graphics library supports the new graphics system). Note that these drawing routines generally do NOT perform any "clipping". If an object is drawn outside the pixel canvas boundaries, it is not necessarily clipped. It is the responsibility of the underlying graphics system (i.e. X) to handle this. The reason for this is that it is not my purpose to reinvent X, but rather to ease the porting of image display applications to specialised display hardware and different windowing systems, as well as providing some higher-level graphics primitives.

### Graphics Primitives

Drawing operations on a pixel canvas are given in terms of pixels. The origin of a pixel canvas (0, 0) is the top-left corner of the canvas. Some of the pixel canvas drawing routines allows one to draw an image. However, these routines are not intended for general use: they merely provide the interface for higher library packages. Events (ie. refresh/ resize/ cursor) on the underlying window (i.e. X window) may be ``pushed'' up into the pixel canvas, where they will be redistributed to application or library code which has registered an interest in these events.

### Refresh and Resize events

When the underlying window (i.e. X window) needs to be redisplayed or has resized, the kwin_resize routine should be called. This will send refresh/ resize events to higher levels in the image display system. If you are using the ``Canvas'' widget (or a derivative) provided by the Karma widgets library, you don't have to worry about explicitely calling kwin_resize, the widget does this automatically whenever the window is refreshed/resized. You can use the kwin_register_refresh_func function to register a function which will be called whenever a pixel canvas should be refreshed (redrawn).

The following code example shows the structure of a refresh function. All it does is draw a green line diagonally across the canvas.

    void refresh_func (KPixCanvas canvas, int width, int height,
                       void **info, PostScriptPage pspage,
                       unsigned int num_areas,
                       KPixCanvasRefreshArea *areas,
                       flag *honoured_areas)
    /*  [SUMMARY] Process a refresh event for a pixel canvas.
        <canvas> The pixel canvas.
        <width> The width of the canvas in pixels.
        <height> The height of the canvas in pixels.
        <info> A pointer to the arbitrary canvas information pointer.
        <pspage> If not NULL, the PostScriptPage object the refresh is
        redirected to.
        <num_areas> The number of areas that need to be refreshed. If this is
        0 then the entire pixel canvas needs to be refreshed.
        <areas> The list of areas that need to be refreshed.
        <honoured_areas> If the value TRUE is written here it is assumed the
        routine honoured the list of refresh areas and did not write outside
        these areas and hence the list of areas will be passed to subsequent
        registered refresh routines. If FALSE is written here (or nothing is
        written here), implying the routine refreshed the entire pixel canvas,
        subsequent refresh routines will be told to refresh the entire canvas.
        [RETURNS] Nothing.
    */
    {
        unsigned int pixel_value;

        if ( !kwin_get_colour (canvas, "green", &pixel_value, NULL, NULL, NULL) )
        {
            fprintf (stderr, "Error getting colour: \"green\"\n");
            return;
        }
        kwin_draw_line (canvas, 0, 0, width - 1, height - 1, pixel_value);
    }   /*  End Function refresh_func  */

### Input events

Other events, such as cursor events on the X window are inserted using the kwin_process_position_event function. The pixel canvas will automatically forward these events to all routines previously registered to handle these events. The process stops when the event is consumed by a routine. In many applications, the higher levels of the library process these events, so the application programmer need not write any event processing code. This can save thousands of lines of code even in a modest application. If you are using the ``Canvas'' widget (or a derivative) provided by the Karma widgets library, you don't have to worry about explicitely calling kwin_process_position_event, the widget does this automatically whenever an event occurs on a window.

You simply call kwin_register_position_event_func if you want to register a function that receives input (position) events.

The follow code example shows a simple event handling function (termed a callback) for a pixel canvas. This function simply prints the current mouse pointer position when the left mouse button is pressed down.

    flag position_func (KPixCanvas canvas, int x, int y,
                        unsigned int event_code, void *e_info, void **f_info)
        [SUMMARY] Process a position event on a pixel canvas.
        <canvas> The pixel canvas on which the event occurred.
        <x> The horizontal position of the event, relative to the canvas origin
        <y> The vertical position of the event, relative to the canvas origin.
        <event_code> The arbitrary event code.
        <e_info> A pointer to arbitrary event information.
        <f_info> A pointer to an arbitrary function information pointer.
        [RETURNS] TRUE if the event was consumed, else FALSE indicating that
        the event is still to be processed.
    */
    {
        if (event_code != K_CANVAS_EVENT_LEFT_MOUSE_CLICK) return (FALSE);
        fprintf (stderr, "left click at: %d %d\n", x, y);
        return (TRUE);
    }   /*  End Function position_func  */

### 24bit Support

There are special attributes associated with each pixel canvas which must be used when computing pixel values for drawing on canvases with visual type TrueColour or DirectColour. This paragraph is only of relevance for TrueColour or DirectColour pixel canvasses. Those familiar with Xlib programming will recall that there is a defined bitmask for red, green and blue for each visual of type TrueColour or DirectColour. The bits in a pixel value required for full intensity for a primary colour requires using all the bits of that colour's bitmask. The colour white is a pixel value that is the OR of all three bitmasks. The Xlib model allows you to construct pixel values this way and draw them as required. However, for efficiency reasons, the model used in the Karma Graphics library is a little more complete. In Karma the distinction is made between pixel values used in most drawing operations and those used when drawing images of pixels. Hence a pixel canvas has the following attributes:

red|green|blue pixel masks which should be used except when drawing images

red|green|blue image masks which should be used only when drawing images

## ``kcmap'' package

The support for dynamic colourmaps is provided by the kcmap package. This is another generic package, portable to non-X environments. It allows colourmaps to be resized, changed, dynamically shared across a network as well as reading and writing to disc. See the chapter on colourmaps for more details.

## ``event'' package

The event package provides a mechanism for graphics events to be sent to other programmes. This should not be confused with the event dispatching mechanism in the kwin package, which descibes events within a single programme. The event package distributes events to other programmes over network connections, and depends on the interprocess communications support in the Karma library. This package is useful if you want to capture events from an existing visualisation tool such as <kvis> in another programme.

The package defines the ``generic_event'' protocol with the conn package. A server is the programme which dispatches events to ``generic_event'' clients. Usually, you don't have to worry about writing server support, as that has already been done inside the standard visualisation tools. Most programmers will be interested in how to write a client.

Clients specify which events they wish to receive from the server by specifying a bitmask. Table event_MASKS defines the mask values. Clients will only receive the type of events specified in the the bitmask.

There are two basic modes in which a client can communicate with the server. One is blocking and the other is asynchronous. These are described below.

### Blocking Mode

This involves the client waiting (blocking) for a single event from a specified server. Any events in the server will not be sent to the client unless the client is waiting. The client waits for an event by calling event_wait. Once the client has received the event, the server will no longer send events. This mode of operation is useful for simple mainline programmes (i.e. non event-driven) which only occasionally want to look for an event. A disadvantage of this mode is if the server is generating events very quickly, the client can miss some.

The following example code shows a client connecting to the <kvis> programme on the local machine, capturing a number of keypress events. The client is operating in blocking mode.

    #include <stdio.h>
    #include <karma.h>
    #include <karma_conn.h>
    #include <karma_wcs.h>
    #include <karma_dm.h>
    #include <karma_r.h>
    #include <karma_event.h>

    #define MAX_EVENTS 10

    main ()
    {
        KEvent event;
        Connection conn;
        unsigned int count;
        char ra_str[STRING_LENGTH], dec_str[STRING_LENGTH];

        dm_native_setup ();
        conn_initialise ( ( void (*) () ) NULL );
        event_initialise ();
        conn = conn_attempt_connection ("localhost",
                                        r_get_def_port ("kview",
                                                        r_getenv ("DISPLAY")),
                                        "generic_event");
        for (count = 0; count < MAX_EVENTS; ++count)
        {
            event_wait (K_EVENT_MASK_KEYPRESS, conn, &event);
            wcs_astro_format_hms (ra_str,
                                  event.data.keypress.position.world.x / 15.0);
            wcs_astro_format_dms (dec_str, event.data.keypress.position.world.y);
            fprintf (stderr,
                     "Key: \"%s\" at impix: %g %g  world: Ra %s  Dec %s\n",
                     event.data.keypress.string,
                     event.data.keypress.position.image_pixel.x,
                     event.data.keypress.position.image_pixel.y,
                     ra_str, dec_str);
        }
    }   /*  End Function main  */

### Asynchronous Mode

This is used for event-driven clients (such as visualisation tools). Here the client calls event_register_event_func to register an application function that is called whenever a new event is received from any server. The client calls event_set_mask to set the event mask for a specific server or all servers. This mode of operation is more efficient and does not suffer from the event loss problem. The disadvantage is that the client has to relinquish control of its main loop to the Karma event management system (provided by the dm package) or some other co-operating event management system (such as that provided by the Xt library).

The following example code shows a client connecting to the <kvis> programme on the local machine, capturing a number of keypress events. The client is operating in asynchronous mode.

    #include <stdio.h>
    #include <karma.h>
    #include <karma_conn.h>
    #include <karma_wcs.h>
    #include <karma_dm.h>
    #include <karma_r.h>
    #include <karma_event.h>

    #define MAX_EVENTS 10

    STATIC_FUNCTION (flag event_func,
                     (KEvent event, Connection connection, void **f_info) );

    main ()
    {
        unsigned int count = 0;

        dm_native_setup ();
        conn_initialise ( ( void (*) () ) NULL );
        event_initialise ();
        conn_attempt_connection ("localhost",
                                 r_get_def_port ("kview", r_getenv ("DISPLAY")),
                                 "generic_event");
        event_register_event_func (event_func, &count);
        event_set_mask (K_EVENT_MASK_KEYPRESS, MAX_EVENTS, NULL);
        while (TRUE) dm_native_poll (-1);
    }   /*  End Function main  */

    static flag event_func (KEvent event, Connection connection, void **f_info)
    /*  [SUMMARY] Generic event callback.
        <event> The event data. This becomes invalid when the callback returns.
        <connection> The connection over which the event came.
        <f_info> The arbitrary function information pointer.
        [RETURNS] TRUE if the event was consumed, else FALSE indicating that
        the event is still to be processed.
    */
    {
        unsigned int *count = (unsigned int *) *f_info;
        char ra_str[STRING_LENGTH], dec_str[STRING_LENGTH];

        wcs_astro_format_hms (ra_str, event.data.keypress.position.world.x / 15.0);
        wcs_astro_format_dms (dec_str, event.data.keypress.position.world.y);
        fprintf (stderr,
                 "Key: \"%s\" at impix: %g %g  world: Ra %s  Dec %s\n",
                 event.data.keypress.string,
                 event.data.keypress.position.image_pixel.x,
                 event.data.keypress.position.image_pixel.y,
                 ra_str, dec_str);
        if (++*count >= MAX_EVENTS) exit (RV_OK);
        return (TRUE);
    }   /*  End Function event_func  */

## ``iedit'' package

Image editing support is based on the iedit package. This allows 2-D image edit instructions to be processed and shared among modules. See the image-edit guide for more details.

## ``iarray'' package

The simplified Karma data structure interface is provided by the iarray package, which defines ``Intelligent Arrays''. Using this package, it is trivial to create and fill 2-D arrays, prior to display. See the iarray chapter for more details.

## ``canvas'' package

The next level up in the image display system is the canvas package. It defines a world canvas class. A world canvas is similar to a pixel canvas, however, drawing operations are specified in world co-ordinates, rather than pixel co-ordinates. For example, a pixel canvas might be 512*512 pixels. A world canvas may map this to a simple unit canvas from (0.0, 0.0) to (1.0, 1.0). A world canvas is created from a pixel canvas.

The origin of a world canvas is the bottom-left corner of the canvas. Note how this differs with the pixel canvas. The pixel canvas is a nearly direct mapping onto the underlying graphics system. The world canvas is modelled on a standard cartesian co-ordinate system. A linear transformation is applied when converting between pixel and world co-ordinates. If a non-linear world co-ordinate system is required, the canvas_register_transforms_func routine may be used to register co-ordinate conversion routines. Note, however, that when drawing into a world canvas, the co-ordinates (or offsets: radii) specified in the function call are transformed into pixel co-ordinates and a similarly shaped object is drawn into the underlying pixel canvas. Thus, an ellipse draw in a world canvas appears as an ellipse, irrespective of the co-ordinate transformation specified.

The world canvas package also provides drawing routines allowing one to draw an image. However, these routines are not intended for general use: it merely provides the interface for higher library packages. The canvas package allows 2-D image edit instructions to be drawn onto a window, and interfaces directly onto the iedit package. This makes the process of drawing objects (i.e. filled ellipses, polygons) in world co-ordinates a trivial task.

## ``viewimg'' package

The most important package (as far as the application programmer is concerned) is the viewimg package. This defines a viewable image class. A viewable image may be created from a plane of an n-D Karma array, or from a 2-D Intelligent Array. A viewable image is associated with one world canvas. Each world canvas may have many viewable images associated with it. A viewable image contains a reference to the 2-D data array. It is this data array which is easily filled by the application programmer. All the mechanics of determining how to refresh, resize, zoom the image display are handled by the viewimg package. This greatly simplifies the implementation of panning and zooming. Window refresh and resize events for viewable images are handled entirely inside the library, leaving the programmer free to concentrate on the data to be displayed, rather than having to cope with a cumbersome event driven system. This does not preclude the registration of other routines to process events on the underlying world and pixel canvases.

An example of where a world canvas would have a refresh/ resize routine registered is when graphic overlays on an image are desired (however, there is also library support for maintaining overlay lists to make this simpler too).

The application programmer may update the data array at will, and, using the viewimg_register_data_change function, update the window display with the new image. Just as simply, the viewimg_make_active function may be used to select which viewable image should be displayed in a world canvas: this is termed making the viewable image "active". This makes the implementation of a movie loop trivial. Nor is there any penalty for using the layers of image display support in Karma: frame rates of over 10 frames per second (512*512*8bit) on conventional SUN IPCs, without graphics accelerators, have been attained.

The viewimg package allows 2-D image edit instructions to be drawn onto a viewable image. This means that the original 2-D data array is modified and the viewable image is redisplayed. Thus writing a painting and image editing application becomes far simpler. These features interface directly onto the iedit package. Because of this integration, the networking support built into the iedit package allows one to easily implement a ``shared whiteboard'' application, a research topic in collaboration technology.

## ``overlay'' package

Another important package is the overlay package. This manages overlay lists (lists of geometric objects). Each overlay object is defined in (possibly mixed) co-ordinates, and may be selectively moved or removed. Overlay lists may be shared between modules (using the ``2D_overlay'' connection protocol). Each overlay list may be associated with multiple world canvases. When the canvas is refreshed, any associated overlay lists will be automatically redrawn. By using the restriction specification mechanisms, a single overlay list can have objects which apply to all frames of a movie (when using the viewimg package) or to a single frame. Hence, from frame to frame, a different set of overlay objects is automatically drawn.

Mixing of co-ordinate types can be very powerful. For example, you may place a line with one end specified by relative co-ordinates and the other in world co-ordinates. This would allow you to have a line which was fixed to some convenient position in the canvas at one end, and the other end would be placed near some feature of interest.

## ``contour'' package

The contour package provides a similar interface as the viewimg package, except that 2-D array data is displayed as contours rather than a bitmapped image. This package is designed to have an almost identical interface to the viewimg package so that little distinction is made between the two different methods of displaying 2-D array data.

# Tutorial

A quite powerful image display tool is <kvis>. The power user is urged to use this module and then read the source code to see how much functionality may be packed into so few lines of code, using the image display support in the Karma library. An entire chapter is devoted to a simple image display tool (this uses Karma widgets and is less then 500 lines long), so you are urged to read this.

Karma Home Page
Next: Image Editing Up: Karma Programming Manual Previous: Event generation and dispatch