About the Computer Graphics Exercices


TDB's Sun computers

How do I get access to the computers?

You will need an account on TDB's Sun system in order to do the exercises (or, more precisely, at least one in each group of one, two, or three students will need an account). If you don't have one, contact Jan Kärrman, room 2414 (jan@tdb.uu.se) and tell him why you need the account.

A note about linking programs

Newcomers to UNIX (and others) often experience difficulties understanding how compiling and linking programs work. While it is my sincere hope that the various Makefile stubs I have created for your convenience will be sufficient, I still think it is good idea to understand the various commands in the files.

Compiling

In order to compile a program, all files that are referenced by #include directives in the source code must be found. If the files are located in the current directory there's no problem. Many files in other directories are also found by the compiler without problems: this is true of all files in /usr/include and /usr/local/include. If you have placed an include file in some unknown (to the compiler) directory, you can direct the compiler to look in that directory with an -I-directive. For exemple, because the include files for X reside in /usr/openwin/include you need the directive -I/usr/openwin/include when you compile X programs; the files actually reside in /usr/openwin/include/X11 but by convention the X11 part is included in the include directive in the source code (you write #include <X11/Xlib.h>). Example:
cc -I/usr/openwin/include drawX.c -c
where -c instructs g++ to execute only the compiler step, not the linking.

Other include file directories - already defined in the Makefile stubs - used in the exercises include:

/usr/dt/include            // Include files for Motif
/home/grafik/SRGP          // Include files for SRGP 
/home/grafik/SPHIGS        // Include files for SPHIGS
/home/grafik/X/v/includex  // Include files for V
SRGP header files are references internally by SPHIGS header files.

Linking

There are two types of libraries: dynamic and static. Library files have the prefix lib, libX11.a and libX11.so, for instance. Code from static libraries that is referenced by your object modules is loaded with the executable at link-time and becomes part of the executable file. Static libraries have the extension ".a"

Code from dynamic libraries is connected to your program at run-time (or at load-time just before run-time, rather). Since this is done every time you run your program, the library files must exist when you run your program. Dynamic libraries have the extension ".so".

Some libraries, like the C (and C++) standard library, are automatically found by the linker. But most libraries must be mentioned explicitly on the command line: enter the name of the library after an -l-directive. If your program uses math functions (declared in math.h) you can link it with the following command

g++ myprogram.o -lm
The linker automatically searches a few directories (/usr/lib and /usr/local/lib), but if your library is placed somewhere else you have to tell the linker to search that directory. For this, you use an -L-directive:
cc drawX.o -L/usr/openwin/lib -o drawX -lX11 
where -o is used to change the default output name from a.out to drawX. The procedure as described so far works for static libraries. If you use dynamic libraries (as libX11.so really is) you also need to tell the linker where to look for them at run-time (or, more precisely, load-time). This you do with a -R-directive:
cc drawX.o -L/usr/openwin/lib -R/usr/openwin/lib -o drawX -lX11
If you use the first version, the one without -R, your program will link without problems, but when you try to run it you will get an error message:
ld.so.1: myapplication: fatal: libX11.so.4: can't open file: errno=2
This means that the loader did not find the library when attempting to load the program in order to run it. Unless some evil wrong-doer has removed the library from its designated place after you linked your program, the message indicates that you forgot the -R-directive.

Static libraries need no -R-directive, since relevant code is already part of the executable.

Often, of course, compiling and linking is merged into one single command with all the necessary options:

cc drawX.c -I/usr/openwin/include -L/usr/openwin/lib \
           -R/usr/openwin/lib -o drawX -lX11 
Important (for this course) library file directories and libraries:
/home/grafik/SRGP/lib:
     libsrgp.a     - The SRGP library
/home/grafik/SPHIGS/lib:
     libsphigs.a   - The SPHIGS library
/home/grafik/X/v/lib/sun4:
     libV.a        - The V library
/usr/openwin/lib:
     libX11.so     - Xlib functions and procedures
     libXt.so      - The Xt toolkit
     libXmu.so     - Miscellaneous X utility functions
     libXext.so	   - Extensions, supports non-rectangular windows, etc.
     libXaw.so     - The Athena widgets
     libxview.so   - The XView toolkit
/usr/dt/lib:
     libXm.so      - The Motif toolkit
/opt/tdb/lib:
     libtk.a       - The Tk toolkit
     libtcl.a      - The Tcl toolkit
Note that the dynamic libraries exist in static versions as well. This is necessary if one develops X applications on one machine but wants it to be executable on another one without the libraries installed.

Libary paths may also be defined by setting the environment variable LD_LIBRARY_PATH. Paths included there will be searched by the linker automatically. I think LD_LIBRARY_PATH only specifies -R-paths, at least that is what the name suggests to me. However, empirical studies (oh, these euphemisms for a single test) show that it seems to specify -L-paths as well.


E1: AVS

AVS is not yet (961118) available for several simultaneous users.

E2: Programming in SPHIGS

SPHIGS Documentation

Note that where the descriptions in the reference manual SPHIGS and in the textbook differ, the reference manual is correct (in the sense that it describes the actual package accurately).

Hints about compiling SPHIGS programs

If you use the Makefile in /home/grafik/HT96/E2 there should be no problem: just add your own source code (.cc files) with .o substituted for .cc on the line beginning OBJ. Warning: The Makefile here isn't very smart: it does not recognize changes in header-files, for example.

In complete disregard of good programming practice is seems the file "sphigs.h" does not support C++. The remedy here is to tell the compiler that functions declared here indeed are C functions. You do this with the extern directive:

extern "C" {
#include "sphigs.h"
}
Don't do this if you write your program in C!

Skip the section "Makefiles in ~grafik/ExamplesSPHIGS" if you plan to use /home/grafik/HT96/E2/Makefile.

Makefiles in ~grafik/ExamplesSPHIGS

The directory ~grafik/ExamplesSPHIGS contains makefiles that support C (Makefile) and C++ (MakeC++). These are delivered with the SPHIGS distribution and have a few idiosyncrasies that may need som clarification.

Here's how you compile and link your program: First you copy the appropriate Makefile (Makefile or MakeC++) to your directory (rename MakeC++ and give it the default name Makefile). Then, in order to compile your program (xxxx.c if you use c, xxxx.cc if you use C++) enter the command:

make PROG=xxxx
If everything is OK, the Makefile then executes
gcc -fpcc-struct-return -I/home/grafik/SRGP/include 
    -I/home/grafik/SPHIGS/include -I/usr/openwin/include 
      xxxx*.c -L/home/grafik/SRGP/lib -L/home/grafik/SPHIGS/lib 
   -lsphigs -lsrgp -lX11 -lm -o xxxx
or
g++ -fpcc-struct-return -I/home/grafik/SRGP/include 
    -I/home/grafik/SPHIGS/include -I/usr/openwin/include 
     xxxx*.cc -L/home/grafik/SRGP/lib -L/home/grafik/SPHIGS/lib 
   -lsphigs -lsrgp -lX11 -lm -o xxxx

Colours in SPHIGS

About colours in SPHIGS: page 7 of SPHIGS describes how you can load colours, but fails to tell you what the second parameter to the function SPH_loadCommonColor should look like. Here is how it works: the parameter colorname should be one of the color names in the file /opt/SUNWxt/v2.0/rgb.txt.

Note that you determine how many colours and how many shades of each colour you have available in SPH_begin().

The SRGP logfile

When you run SPHDEMO (PHIGS demo program) you may get the message "Unable to open the SRGP logfile!" and the program is terminated. This problem occurs only if you run the program from a directory where you don't have write access: the logfile is created by the program. Solution: run the program from you own account.

E3: User interface in V

For your information: it seems the home page for V (the link in the home page) is unavailable sometimes. Whether this is a momentary or lasting condition is not known, but for the moment you can use this link to the V reference guide instead.

The structure of a V application is quite simple. I will briefly describe the most important classes and how they work together. Parts of the text here is from the web page cited above ( (c) Bruce Wampler, 1995, 1996). Other sources of information include pages 24 and 25 of the "Introduction to the X Window System" and the tutorial program in ~grafik/X/v/tutor (aka "Tutor.Listing" in the list of X example programs that was handed out 14/11). In what follows, I assume that you are familiar with C++, including classes, inheritance, and polymorphism. Please note that descriptions of some methods, constructors, etc. are simplified: defaulted parameters removed, etc.

Overall structure

The application class, vApp -- vapp.h

Exactly one instance of vApp must exist in every V application (the static one, see above). The methods listed below are those that you are most likely to override.

int AppMain(int argc, char **argv) -- vapp.h

Your main receives the command line arguments.

vAppWinInfo -- vawinfo.h

You define most of the behaviour of this class yourself. A pointer to a window's vAppWinInfo instance is available via vApp::getAppWinInfo().

Command windows: vCmdWindow -- vcmdwin.h

The class vCmdWindow is the main control window. It contains menus, canvases, etc. You should derive your window class from vCmdWindow. The vPane class serves as a base class for various pane objects (menus, commands, and canvases). vMenuPane, vCommandPane,and vCanvasPane are derived from vPane.

Menu panes (pulldown menus), vMenu, vMenuPane -- vmenu.h

A vMenuPane consists of vMenu items. Since vMenu may contain lists of other vMenu items as submenus the structure can be nested - or recursive. The leftmost choice in any plldown menu is usually File (a vMenu item) with a list of operations such as Open, Save, and Exit (an array of vMenu items).

Static arrays of vMenu items declared in the implementation file of your derivation of vCmdWindow are usually used. The vMenu initializer:

vMenu xx = { char *name, ItemVal menuId, int sensitive, int checked,
             char *keyLabel, vKey accel, vMenu *SubMenu };
where name is the label of the button, menuId is a user-assigned unique id sent to the WindowCommand method, sensitive should be one of notSens or isSens, checked should be one of isChk, notChk, or notUsed (for the top-level menu bar), keyLabel is a label for an accelerator key, key is an accelerator key, and SubMenu is either noSub (for the leaves in the menu hierarchy or an address to a submenu. There are a number of predefined ItemVal values beginning with M_ you can use. vMenu initializations looks like:
const ItemVal m_Special= 100;
vMenu mysub = {
    { "Special", m_Special, isSens, notCkh, noKeyLbl, noKey, noSub},
    { "Exit", M_Exit, isSens, notCkh, noKeyLbl, noKey, noSub}
  }
vMenu StandardMenu[] = {
    { "File", M_File, isSens, notUsed, notUsed, noKey, &mysub[0]},
    { "Edit", M_Edit, isSens, notUsed, notUsed, noKey, noSub}
  }
The constructor for vMenuPane takes an address of a vMenu hierarchy as parameter:

Command panes, vCommandPane -- vcmdpane.h

A command pane is a horizontal bar in a command window that hold CommandObjectss, which will be described in the section about dialogs.

Constructor:

Dialogs, vDialog and vModalDialog -- vdialog.h, vmodald.h

Dialogs may be created in vCmdWindows's constructor with new or as they are needed - either dynamically with new or statically by simply declaring them.

The class vDialog for modeless diaglogs:

The class vModalDialog for modal (meaning that all other interaction is locked until the user interacts and dismisses the dialog window) dialogs (vModalDialog is derived from vDialog so the methods of that class are inherited (or overridden in some cases):

CommandObject (aka DialogCmd) -- v_defs.h

NOTE: CommandObject and DialogCmd are different names for the same thing. DialogCmd is the older name, available only for backwards compatibility.

Command objects are building blocks used in dialogs and command panes. Each dialog is made up of an array of command objects. The command object structure:

typedef struct CommandObject
  {
    CmdType cmdType;        // what kind of item is this
    ItemVal cmdId;          // unique id for the item
    ItemVal retVal;         // value returned when picked (might be < 0)
    char* title;            // string (for C_Label, C_Text, and C_TextIn)
    void* itemList;         // a list of stuff to use for the cmd (for lists)
    CmdAttribute attrs;     // bit map of attributes
    int Sensitive;          // if item is sensitive or not
    ItemVal cFrame;         // Frame used for an item (or NoFrame)
    ItemVal cRightOf;       // Item placed left of this id
    ItemVal cBelow;         // Item placed below this one
    int size;               // Used for sizes
  } CommandObject;
There are many different kines of command items (CmdType). The most important ones (for this exercise) are:
C_Button    - self-explanatory, I should hope
C_EndOfList - must be entered as the last item in a CommandObject array.
C_Frame     - a line around a group of related items.
C_Label     - a labelm usually a static text string
C_Text      - boxed (framed) text, for information that may change
C_TextIn    - user-editable text
CommandObjects are used in the two types of dialogs and in vCommandPanes.

File selection, vFileSelect -- vfilesel.h

vFileSelect is a specialization of vModalDialog for selecting filenames. It does not open or close a file, it merely constructs a legal filename for use in opening a file.

Yes/no dialog -- vynreply.h

The vYNReplyDialog is another specialization of a modal dialog that can be used for confirmation of user actions (such as exiting without saving data):

Canvases, vCanvasPane, vTextCanvasPane -- vcanvas.h, vtextcnv.h

I will describe only vTextCanvasPane since that is the variety you will be using in the exercise. There are many more methods (editing, scrolling text, changing fonts, etc.) but you won't need these in this exercise.

E4: Splines in Motif

About the XmNinputCallback in DrawingArea

About callbacks in X: If you use a DrawingArea to display your splines and an XmNinputCallback to recognize when the user wants to input something, your callback function will be called twice: when the mouse button is released as well as when it is pressed. You probably don't want that so here is how your callback function can recognize what happened:

void UpdateIt( Widget w, XtPointer client, XtPointer call) {
  Window win = XtWindow( w );
  XmDrawingAreaCallbackStruct *dacs = 
    (XmDrawingAreaCallbackStruct *) call;
  XButtonEvent *ev = (XButtonEvent *) dacs->event;
  if (ev->type != ButtonPress) return; /*ignore releases*/
/*  do the drawing */
}

Communication between widgets

Any toolkit faces the task of transferring data (or pointers to data, at the very least) between the various callback functions. In general terms, we may think of an application model (the application, really) that must be accessible from the functions that are called as a result of user actions.

One way of accomplishing this is through global variables - purists may raise objections, but a global pointer to an application model is not such a bad idea (that's my view, anyway). In C++, for example, the application may be modelled with a class, and the various callback functions may then send messages to a globally accessible instance of that class (call member functions, to put it bluntly) in response to user actions.

In Xt/Motif, there are two ways to communicate between callback functions and between the application model and callback functions

I will show how to use the two methods with examples. Note the use of typecasts, which is necessary for a generic data type like XtPointer (it does put the burden of typechecking on the programmer, though).

Callback function parameter

Assume we want to sent a pointer to a struct to a callback function. This is done in two steps. First, after having created the widget - a simple pushbutton in this case - we send the pointer as the fourth parameter to XtAddCallback. Then, in the callback function, we retrieve the pointer as the second parameter:
// Declared in a header file:
typedef struct {
  int a;
  char b[10];
} TestStruct;

// Created in main (or another function but we would need
// dynamic allocation in that case).

TestStruct myStruct = { 1, "kalle" };


// Widget creation and setup of callback

Widget testw =
  XtVaCreateManagedWidget("testw", xmPushButtonWidgetClass, bb,
                          XmNx, 10,
			  XmNy, 10,
			  XMNwidth, 50,
			  XmNheight, 30,
			  NULL
	                  );
XtAddCallback ( testw, XmNactivateCallback, TestIt,
               (XtPointer) &myStruct);

// The callback function

void TestIt( Widget w, XtPointer client, XtPointer call) {
  TestStruct *ts = (TestStruct *) client;
  printf("%d %s\n", ts -> a, ts -> b ); // will print ' 1 kalle'
  exit(0);
}

The resource userData

Primitive and Manager both define the resource XmNuserData of type Pointer. In effect, this means that all Motif widgets except MenuShell and DialogShell inherit this resource (but Xt widgets like Core and ApplicationShell do not include the resource).

In this example, I create a button and a text field and assume that, in order to process button events correctly, the button callback must be able to retrieve the text in the text field. I add that capability to the button by setting the userData resource to point at the text field widget.

Widget tf, pb;
tf =  XtVaCreateManagedWidget("text",
                             xmTextFieldWidgetClass,
                             parent,
			     XmNx, 10,
			     XmNy, 10,
			     XmNwidth, 180,
			     XmNheight, 20,	       
			     XmNvalue, "test text",
			     NULL);
pb = XtVaCreateManagedWidget("PushButtonText",
                             xmPushButtonWidgetClass,
			     parent,
		             XmNx, 200,
			     XmNy, 10,
			     XmNwidth, 80,
			     XmNheight, 20,	       
			     XmNuserData, tf;
			     NULL);
//
// Code with access to pb (callback functions, for example) 
// may access the text in tf in the following manner 
// (we assume a callback function for pb, so w is pb):
//

void TestCallback( Widget w, XtPointer client, XtPointer call) {
  Widget textf;
  char *txt;
  XtVaGetValues( w,     XmNuserData, &textf, NULL );
  XtVaGetValues( textf, XmNvalue,    &txt,   NULL );
  printf("Text field value is '%s'\n", txt);
  free ( txt ); // should be freed after use !!!
}

A note about colours and GC configurability

In the hand-out entitled "Introduction to the X Window System" the X graphics model including the GC is introduced. The paper fails to show you how to configure the GC, however. Here I will describe, in increasing order of "sophistication", several ways of defining the foreground and background colour.

In X, colours are always represented by the abstract data type Pixel.

Hardcoded colours

The following code shows you how to create a GC for drawing black lines on a white background. Note that this does not make the background of the DrawingArea white (this is a resource of the widget), the background is used in certain drawing operations. (dpy is a pointer to your Display struct, easily retrieved by calling XtDisplay(myWidget), win is the X window (from XtWindow(myWidget)).
XGCValues gcv; 
gcv.foreground = BlackPixel( dpy, DefaultScreen(dpy) ); 
gcv.background = WhitePixel( dpy, DefaultScreen(dpy) ); 
gc = XCreateGC( dpy, win, GCForeground | GCBackground , &gcv); 
BlackPixel and WhitePixel are macros that return the pixel value of black and white, respectively - the only two colours guaranteed to exist on every display.

An alternative to XCreateGC from Xlib is XtGetGC from Xt:

gc = XtGetGC( w, GCForeground | GCBackground , &gcv); 
where w is a widget. Because XtGetGC and XCreateGC are very similar, very little is gained by using the higher-level library in this case.

Hardcoded, allocated colours

Of course we may want other colours than black and white. This code shows how to allocate colours (in its simplest form and without checks for failures):
XColor cbgr, cfgr;
XGCValues gcv;
cbgr.flags = cfgr.flags = DoRed | DoGreen | DoBlue;
cbgr.red  = 65535; cbgr.green = cbgr.blue = 0;
cfgr.blue = 65535; cfgr.green = cfgr.red = 0;
XAllocColor( dpy, DefaultColormap(dpy, DefaultScreen(dpy)), &cbgr );
XAllocColor( dpy, DefaultColormap(dpy, DefaultScreen(dpy)), &cfgr );
gcv.background = cbgr.pixel;
gcv.foreground = cfgr.pixel;
gc = XCreateGC( dpy, win, GCForeground | GCBackground , &gcv); 
Note that 16 bits per primary (red, green, and blue) are used. As an alternative to specifying the colours by digits, there is also a function for allocation of named colours ("red", "green"). Example:
XAllocNamedColor( dpy, DefaultColormap(dpy, DefaultScreen(dpy)), 
                  "yellow", &cfgr, &exact );
The fourth and fifth parameters are both output parameters of type XColor, cfgr in the example contains the colour and pixel of the cell actually allocated while the last parameter contains the values (in the database) of the named colour. The two may differ if the function failed to allocate the exact colour you requested.

Using colours from the resource database

According to the generally accepted style guidelines for Motif, resources should be configurable if at all possible. So it seems natural to use the resources of the DrawingArea instead of hardcoding. This can be accomplished in the following manner:
Pixel fgr, bgr;
XGCValues gcv;
XtVaGetValues( w,  XmNforeground, &fgr,
		   XmNbackground, &bgr,
		   NULL);
gcv.background = bgr;
gcv.foreground = fgr; 
gc = XtGetGC( w, GCForeground | GCBackground , &gcv); 
where w once more is the widget we're drawing in. Or, simplifying and hiding the data type Pixel:
XGCValues gcv;
XtVaGetValues( w,  XmNforeground, &(gcv.foreground),
		   XmNbackground, &(gcv.background),
		   NULL);
gc = XtGetGC( w, GCForeground | GCBackground , &gcv); 
Here is more information about resource setting in the C code; it applies to all Xt functions that manipulate resources, not only XtVaGetValues. As you may have guessed, the tiny font used here indicates "only for those interested in Xt peculiarities".

There are two programmer interfaces (two sets of Xt functions) for resource manipulation: one older (the ArgList interface) and one newer (which uses the varargs feature of C, the XtVa routines). Here I will only describe the varargs interface: it is less error-prone, easier to program, and has some features that are missing from the ArgList variety (but using the varargs functions is slighlty less efficient).

Resources are name/value pairs. A resource name is a symbolic constant defined in <Xm/Xm.h> (or <X11/StringDefs.h> in some cases). The constants are strings; the rationale for using symbolic constants instead of strings is that the compiler will catch a misspelled constant but not a "misspelled" string. Resource values differ in type: some are single int's, others are complex structures. A rule of thumb is that if the value is no bigger than a longword, the value itself should be sent, if it is longer a pointer to the value should be used - the exact definition of each type can be found in Motif reference guides, for instance.

The above applies for setting resources, XtVaGetValues needs pointers for small values, and pointers to pointers for large values. Motif widgets return a copy to the resource value, not a pointer to the value itself, so the programmer may free structures after use (this is not entirely true: due to a bug in Motif some resources should not be freed). Athena widgets, on the other hand, return pointers to internal data, so strings, for example, queried from Athena widgets should not be manipulated or freed.

Note that there is no way for Motif to typecheck the resources. It is the responsibility of the programmer to make sure that correct types are used.

Lists of resources for each widget are available in Motif reference guides and on-line (man).

Using widget internals to speed things up (don't!)

The code here reads the resources, not from the server, but from the drawingArea's internal record.

Skip this section unless you have a great deal of interest in what's inside a widget (I would have used the tiny font here too, if only I could bring myself to "degrade" a whole section of text). This way of getting resources is intended only for widget programmers. The code here accesses widget records that are really private - in order to use them the file DrawingAP.h (the capital P means private) must be included.

But the code actually displays the object-oriented philosophy of X in the way the core part defines the background pixel (which is thus "inherited" by all other widgets if you recall the figure on page 13 of "Introduction to the X Window System"):

XGCValues gcv;
XmDrawingAreaRec *daw = (XmDrawingAreaRec*) w;
gcv.foreground = daw -> manager . foreground;
gcv.background = daw -> core    . background_pixel;
gc = XtGetGC( w, GCForeground | GCBackground , &gcv); 
Of course the claim that this is the most "sophisticated" way of setting resources is highly questionable: retrieving data that are public only because a program package isn't implemented in an OO language is well down in sophistication (but it may elevate me to hitherto unknown heights in the eyes of hackers, perhaps).
[kursens hemsida]