A design goal is to have Display Library applications portable to another graphics system by augmenting the Library to provide an interface to that graphics system.
The Display library is a set of tools for the AIPS++ programmer which make it easier to create drawing windows and use those drawing windows as output devices for graphical primitives, including images. To some extent, the Display Library consists of two parts. One part is a set of device indepentent classes that give the basic functionality for data display. The classes of the first group are quite generic and can basically be used for any application that needs an interactive display window and these classes not tied to AIPS++ data sets or AIPS++ coordinate systems. They make only very few assumptions for what they will be used. The second part is more concerned with transforming data sets into objects that are understood by the first layer, as well as classes that should make it easier to build a full application that displays data. The classes of the second group can be quite data specific.
The main classes from the first group are the PixelCanvas, the WorldCanvas and the ColorMap classes. The PixelCanvas conceptually corresponds to the window on the screen. It works only in 'screen' units, ie. screen pixel coordiantes and screen data values. The PixelCanvas is, to some extent, the interface between Aips++ and the display hardware and graphics environment (e.g. X11). The WorldCanvas is mostly an interface layer between the 'real world' and the PixelCanvas. Its main responsibility is to transform drawing instruction that are specified in world coordinates and world values to the corresponding instructions of the PixelCanvas specified in pixel coordinates and pixel values. The PixelCanvas and the WorldCanvas are intended to be relatively 'raw' devices. With this we mean that both classes have basically no knowledge of the classes that use these Canvases to display data. In effect, The PixelCanvas does not know what a WorldCanvas is. The reason to make the canvases relatively ignorant, is that by putting in as few assumptions as possible in the canvases, they (hopefully) can be used in a very wide range of applications. For example, it is quite easy to make a 2D colormap editor (an application that is quite different from e.g. displaying a contour map) based on the PixelCanvas or on the WorldCanvas. To achieve that only a few assumption have to be built in in the canvases, the canvases communicate to other classes via event handlers that these other classes have to register with the canvases. Also the PixelCanvas communicates with the WorldCanvas thourg such event handlers. For example, if the window corresponding to the Pixelcanvas is resized by the user, this generates a refresh event and the Pixelcanvas calls the refresh event handlers registered with the PixelCanvas. When a WorldCanvas is created, the first thing the WorldCanvas does is to install a number of event handlers on the PixelCanvas. This means that if e.g. the refresh event occurs on the PixelCanvas, the WorldCanvas is notified and takes the necessary action. This scheme of communication through event handlers is used throughout the Display Library. The classes that generate events do not have to know what classes consume these events, the only thing that is defined is the interface (and content) of the event. This means that one could build a class on top of the PixelCanvas that is quite different from the WorldCanvas.
To keep the WorldCanvas as generic as possible, the coordinate system the WorldCanvas uses to transform from screen pixels to world coordinates and back is not stored in the WorldCnvas but is also defined through an event handler (a CoordinateHandler). If the WorldCanvas has to do a coordinate transformation, it asks the CoordinateHandler to do this. In many cases, this CoordinateHandler will use a standard Aips++ CoordinateSystem to do the transformation, but this is not a requirement, and the programmer can implement any tranformation required, as long as it satisfies the interface of the CoordinateHandler.
The features of the first group include
The classes of the second group have a quite different purpose. They are much more concerned with building an application that needs to display one or more aips++ datasets in a number of ways.
To make the link between the relatively generic display classes (PixelCanvas, WorldCanvas etc) and classes quite specific for displaying Aips++ datasets, an intermediate class is required. This class is called the WorldCanvasHolder. The main role of the WorldCanvasHolder is to catch the events that occur on a WorldCanvas and distribute these events to the data that is being displayed on the Worldcanvas. Another important role of the WorldCanvasHolder is that it is the class that is used to control what is actually displayed on the WorldCanvas. One important design requirement for the Display Library was that, from the programmer point of view, it should be easy to display more than one dataset in a window at the same time. A standard example is a contourmap on top of a greyscale image. Consequently, one can register more than one display object with the WorldCanvasHolder so that they are displayed at the same time.
One important aspect that the WorldCanvasHolder takes care of is the sizeControl of the WorldCanvas. If a refresh event happens on the WorldCanvas, before invoking the refresh event handlers installed on the WorldCanvas, the WorldCanvas invokes a sizeControl event. The meaning of this event is to ask the object that draws on the WorldCanvas (e.g. the WorldCanvasHolder) to check if the state of the WorldCanvas is ok so that it can be drawn on, and if it is not ok, that the state of the WorldCanvas be modified. For example, if an image is drwan as a pseudeocolor image and the window (or WorldCanvas) is much larger that the data array, one wants to expand the data array so that it fills more or less the window. One way of doing this is to do this by pixelreplication. But this means that there are requirements on the size of the area that the WorldCanvas uses for drawing the image (it has to be a integer multiple of the size of the data array. This explains the name sizeControl). It is the responsibility of the objects that draw on the WorldCanvas, to get these size right. What in practice happens is that the WorldCanvasHolder asks the display objects to sort this out and set the correct size on the WorldCanvas. Once this is sorted out, the WorldCanvas is in the coreect state and things can be drawn. The name sizeControl is not entirely correct. The obvious use of this mechanism is as in the example described above, but the problem is more general: before display objects can draw on the WorldCanvas they have to be sure that the WorldCanvas is in the correct state and possibly they have to modify the attributes of the WorldCanvas. Because more than one display object can draw on the WorldCanvas, this has to be done before the actual drawing occurs (because the display objects do not know if there are more than one display objects drawing on the WorldCanvas).
Another requirement for the Display Library was that no distinction should be made based on how data is displayed. For example, displaying an image as a pseudocolor image on the screen should, from the Display Library point of view, be the same operation as displaying that image as a contourmap. This means that the display object is quite an 'abstract' class. Display Objects in the Display Library are called DisplayData (or better: are derived from DisplayData). The role of the DisplayData is, using a certain algorithm, to transform data into one or more draw instructions for the WorldCanvas. An example is ImageDisplayData that takes an Aips++ dataset and display its contents as images, Another example would be ContourDisplayData that draws images as contourmaps. Also here, the communication to the DisplayData goes via calling event handlers. Depending on what happens (e.g. a refresh event, or a position event), the WorldCanvasHolder (who catches these events from the WorldCanvas), invokes member functions of the DisplayData registered with the WorldCanvasHolder (so to a large extent, a DisplayData is an event handler on the WorldCanvasHolder). For example, if a refresh event happens, the WorldCanvasHolder will ask the DisplayData to draw what they should draw. If a programmer wants to implement a new way of displaying data, what this programmer has to do is to implement a new class derived from DisplayData that computes whatever it has to compute (say a volume rendering of a datacube) and transform this into draw instructions for the WorldCanvas (in this example, just call WorldCanvas.drawImage() on the result of the volume rendering). A class derived from DisplayData does not need to have an Aips++ dataset, but one could for example image a DisplayData class that reads positions from a catalog and plots these positions on the WorldCanvas.
Because everything in the Display Library is event driven, it is also easy to make applications that require more than one display window. The programmer only has to decide how to link the various windows by installng the appropriate event handlers on the various classes. An example of how this can be done is the Zoomer class. This class is a high-level class that catches position events on the WorldCanvases (possibly more than one) that are registered with the Zoomer. If the right event happens on one of the WorldCanvases (say, the user is rubberbanding a rectangle), the Zoomer sets the correct attributes on all the WorldCanvases registered with the Zoomer (meaning: it defines the area to which should be zoomed in) and invokes a refresh event on all these WorldCanvases. This means that all the WorldCanvases will zoom in synch.
Another design requirement of the Display Library was that it should be easy to display movies, and the DisplayData are build with sequences in mind (as is clear from their interface). The programmer is free to define what a sequence really means, but it is probably best to keep the structure of the sequence in a DisplayData logical to at least some extent. But there is no real requirement on the structure of the sequence. Movies can be controlled using the Animator class. The easy way is to use indices, but there is a generic way of defining movies using restrictions (see later what these mean). So a sequence does not have to correspond to a 'physical' sequence in one datastructure (channels in a cube for example), but can be made of representations of different datasets (e.g. blinking), or more exotic combinations of different datasets displayed in different form (e.g. blink between a greyscale and a contourmap, if such a thing would be useful). This system is very flexible and there are no real limits to what a movie really means.
An important concept in the Display Library is that of Attributes (and their use as restrictions). Attributes are name-value pairs. Many classes of the Display Library have a buffer where they can store these Attributes. They can be used for various things (see AttributeBuffer for a number of examples). One applications is that a uniform userinterface can be defined for changing attributes of a class (meaning internal members), but since Attributes can have an arbitrary name, they can be used to place (almost) any kind of information on classes at run time. This provides a mechanism of distributing information in a display aplication, while at compile time it is not yet defined what that information is (name, type, etc).
An important application of Attributes is their use as restrictions: they are used to select which data is actually displayed. DisplayData classes, like ImageDisplayData, are supposed to have defined, for each distinct representation that they can draw, one or more Attributes, specifically defined to select what data is displayed. These Attributes are in a separate (or several separate) AttributeBuffer, called the restriction buffer. Also the WorldCanvasHolder has such a restriction buffer whose content should be controlled by the application programmer. If a refresh happens, the WorldCanvasHolder, after the sizeControl step, asks each DisplayData that is registered with the WorldCanvasHolder to draw itself, by calling the refreshEH() member of each DisplayData. The first thing that a DisplayData should do it to see if the restrictions that are placed on the WorldCanvasHolder are compatible with this DisplayData and/or check with which element of the DisplayData the restriction buffer WorldCanvasHolder is compatible. If the DisplayData has compatible data, it should draw this data. An example may make this process clearer. An ImageDisplayData is a class derived from DisplayData that displays the 2D slices from a n-dimensional dataset on a canvas (for example the channels from a datacube). An ImageDisplayData has two general restrictions (meaning they apply to all channels): the names of the axes ("xAxisName" = "Ra" and "yAxisName" = "Dec"). Since an ImageDisplayData (possibly) consists of a number of channels, additional restrictions exist, but these have a different value for each element (ie. channel). In the case of an ImageDisplayData, each 2D subset has defined "zIndex" and "zValue", the first has the value of the pixelcoordinate of the 2D image (in our example channel number), the second has the value of the WorldCoordinate of centre of the 2D image (in our example the velocity of the channel). So to display a channel, a programmer has to set 3 restrictions on the WorldCanvasHolder: "xAxisName", "yAxisName" to select channels from a datacube and set these to "Ra" and "Dec" (or whatever is in the header of the data), plus a restriction to select the actual channel, e.g. set "zIndex" to 20 to select channel 20, or "zValue" to 1200.0, to select the channel corresponding to velocity 1200.0. To display a position velocity image, one would have to specify e.g. "xAxisName" = "Dec" and "yAxisName" = "Velocity" and set "zValue" to 5.23443311 (the Ra of the slice you want to look at).
To determine if restrictionbuffers match, one can simply use the member matches() of an AttributeBuffer. The logic of matching restrictions is perhaps a bit distorted: restrictions (or Attributes in general) of different names always match. So if a DisplayData has a restriction called "A" and the WorldCanvasHolder specifies "B", the DisplayData should draw itself. Restrictions (and Attributes) can have some tolerance. In our example we could have specified "zValue" to be 1200.0, plus or minus 5.0. This obviously can be used e.g. to match the channels of two data cubes that are on a different velocity grid. The Animator class, who can be used to make movies, completely relies on this restriction mechanism.
The PixelCanvas display library defines an interface to an underlying 2D graphics library.
The design of the PixelCanvas emphasizes the following features:
PixelCanvas drawing commands accept simple AIPS++ objects. Presently the
drawing commands accept points as Vector
There are 3 kinds of events the PixelCanvas reports
Applications handle
PixelCanvas events by creating
PixelCanvas event handlers
that must be derived from the appropriate event class, either
PCRefreshEH,
PCPositionEH, or
PCMotionEH. The () operator
must be overridden and implemented by responding to the
information contained in the
PCRefreshEvent,
PCPositionEvent, or
PCMotionEvent, as appropriate.
There is a system for creating sequences of commands. It works
be turning on caching, performing drawing commands, then shutting
it off. An id is returned to the user to recall the stored
sequence. This gives the user control over what is to be cached
while still abstracting the business of caching.
The cache system improves drawing speed by storing drawing data
in native library formats.
Data stored in native formats means that, for the X11PixelCanvas,
It is the responsibility of the user to rebuild display lists
when necessary. Normally this means when the canvas changes
size or colormap distribution (if colormaps are in use).
The PixelCanvas colormap system design was one of the most difficult
design problems we faced in building this system. The design goals
were as follows:
The constraints above have driven us to the following scheme:
The color image can be reused as long as the distribution of colors
on the
PixelCanvasColorTable
doesn't change. Any such change will trigger a refresh event with the
reason Display::ColorTableChange on the affected
PixelCanvas.
This process can also be used to draw other primitives that each have
a value associated with it.
Several colormaps can be switched between by setting the active
colormap and querying the range, and using the mapToColor function.
The WorldCanvas is intended to
serve as a world-coordinate plotting canvas
The WorldCanvas drawing commands
expect to be given world coordinate values for position information.
The WorldCanvas event handling
is similar to the PixelCanvas
event handling, but the WorldCanvas
events contain extra information which includes the world coordinate
and linear coordinate position of the mouse pointer. The same
basic three events are available:
Applications handle
WorldCanvas events by creating
WorldCanvas event handlers
that must be derived from the appropriate event class, either
WCRefreshEH,
WCPositionEH, or
WCMotionEH. The () operator
must be overridden and implemented by responding to the
information contained in the
WCRefreshEvent,
WCPositionEvent, or
WCMotionEvent, as appropriate.
Other handlers can be registered with the WorldCanvas to customize:
The WorldCanvasHolder is a user of a WorldCanvas. It installs handlers on
the WorldCanvas for each event a WorldCanvas can generate. The main role
of the WorldCanvasHolder is to allow to have more than one DisplayData
object draw on a WorldCanvas (e.g contours on top of an image). A number
of DisplayDatas can be registered with a WorldCanvasHolder, and the
WorldCanvasHolder passes WorldCanvas events to these DisplayData.
To control what is displayed on a WorldCanvas, the programmer can put
restrictions on a WorldCanvasHolder. Only those DisplayData do actually
draw whose restrictions match those of the WorldCanvasHolder. See the
example given above, or have a look at the doc of the Animator.
A DisplayData is the class that transforms data (in whatever form: an
image, a cube, a catalog or whatever) into drawing instructions for the
WorldCanvas. A DisplayData is the 'workhorse' of the display libary: here
is defined in what way on can represent data. If one want to add a new way
of displaying data to aips++, they only thing that has to be done is to
write a new class, derived from DisplayData, that computes this new
representation and draws it. For example, if one would want to add volume
rendering to aips++, one would have to write a class that computes this
volume rendering (e.g. using some hot-gas algorithm), and draw the result
of this on a WorldCanvas (ie. simply WorldCanvas.drawImage() on the
result). DisplayDatas are registered witha WorldCanvasHolder and the
restriction mechanism is used to select what is being
displayed. DisplayDatas are also responsible for defining the
WorldCoordinate system of the WorldCanvas (they have an interface that can
be called (indirectly) by the WorldCanvas to do the transformations), as
wel las they have to insure that the state of the WorldCanvas is ok (the
sizeControl, see above). An example of a DisplayData is the
ImageDisplayData class, that draws pseudocolor images from a data set.
Zooming is done by setying the linear coordinates of the WorldCanvas to
define the zoom area (using a set function of the WorldCanvas) and force a
refresh of the WorldCanvas. The Zoomer class defines the userinterface for
zooming. One can register one (or more) WorldCanvas with a Zoomer. This
Zoomer installs a position event handler on the WorldCanvas that listens
to certain key- and mouse events. The Zoomer defines a default
userinterface, but this can be re-defined. A Zoomer can also handle more
than one WorldCanvas, so it is easy to let different WorldCanvases zoom in synch.
The role of the Animator is to give an easy way of controling what is
displayed on one or more WorldCanvases. By specifying how an Animator
controls what is displayed (by using indices, world coordinates or
restrictions), the Animator sets the necessary restrictions on the
WorldCanvasHolders that are registered with the Animator, and forces a
refresh on all of them. All WorldCanvasHolder registered on an Animator
move in synch.
The following list illustrates work remaining at the PixelCanvas level:
The following is the todo list for the WorldCanvas and related classes
The following is the todo list for the application level classes:
The following section is intended for a programmer who wants to
improve the Display Library
An advanced optimization to the
PixelCanvas
caching system would be to compact sequences
of drawing commands. This would mean that a sequence received
by the PixelCanvas would be transformed into a more efficient sequence
of drawing commands that produces the same result. Obviously, sequences
of the same commands can often be combined into a single command.
But consider for example a drawn raster image I followed by a set of
vectors V on top of the image. This sequence can be represented by
{ I V }.
We can partition V into 2 sets: Vin (contains vectors drawn inside the
image) and Vout (vectors that have portions drawn outside the image).
A new image I' can be created from I by drawing I onto a pixmap, then
painting the vectors Vin on top, and storing the resultant image I'.
The cached sequence is then { I' Vout }, and I and V can be discarded
This will result in faster drawing times and reduced memory usage if
Vin has significant size because the vectors in Vin are not drawn explicitly.
Similar results can be achieved with drawn text and points, so that some
sequence { I V P T } can be replaced with { I' Vout Pout Tout }. Also
overlapping images can be stored to avoid drawing the overlapping region
more than once.
It is not clear whether this would be worthwhile to pursue, so it has not
been implemented.
PixelCanvas Event Handling
PixelCanvas Caching Mechanism
PixelCanvas Colormap System
The WorldCanvas
WorldCanvas DrawingCommands
WorldCanvas Event Handling
Other WorldCanvas Handlers
The WorldCanvasHolder
The DisplayData Interface
The Zoomer
The Animator
Motivation
Need to provide some usefull tools to assist in developing graphical
C++ applications, addressing the problems encountered in trying to
use other graphical libraries.
This section is intended as information about what tasks remain to be
accomplished for the Display library.
To Do
To Do
To Do
Caching System Optimization
Classes