Project: Kaleidoscope

Standard Scheme does not provide any facilities for graphics programming, but for particular implementations of Scheme it is often possible to obtain libraries that create windows, draw lines and curves onto them, add text in various fonts and colors, provide text-entry forms and labelled buttons and sliders and other ``widgets,'' and generally integrate Scheme programs with the multimedia resources of the machines on which they run.

As a small example of graphics programming in Scheme, we'll use Elk, an implementation of Scheme created by Oliver Laumann, to develop a kaleidoscope program. The program will open up a window on your workstation and create colorful designs of a random, symmetric nature in it.

The file kaleidoscope-project.scm contains a working, but primitive, version of the kaleidoscope program. Your project will be to refine it, adding symmetry and variety to its graphical displays.

To run the program, open an hpterm window and type the command elk -i at the prompt to start Elk Scheme. At the Scheme prompt, type (load "kaleidoscope-project.scm"). A square window will appear, presenting a black background with one colored line drawn onto it. Pressing any key on the keyboard will have some effect on the window: The Q key will close the window and exit from the program, the C key will erase the current pattern and start over with just one colored line, and any other key will add a new colored line on top of the existing pattern.

On MathLAN workstations, windows are normally created and displayed with the assistance of a ``window manager'' program, vuewm, by invoking procedures in a library known as ``X Windows.'' Although this library is intended primarily for programmers using the C and C++ languages, Laumann provided us with an interface that allows us to invoke X Windows procedures from inside Elk, using Scheme syntax, and thus to integrate X Windows programming with Scheme programming. A full list of the procedures that make up Elk's X Windows library can be found in the Elk/Xlib reference manual. Here, however, we shall cover only the twenty or so that are used in the kaleidoscope program.

One of X Windows data structures is the display, which contains information about the output device on which any window we create will appear. Open-display is a procedure of zero arguments that allocates and returns an appropriately initialized display record describing the workstation's monitor; it returns #f if the monitor is not suitable for displaying windows (which might happen if, for instance, one tried to run an X Windows program from a VAX terminal). Display? is a type predicate that tests whether its argument is a display record.

One of the fields of a display is its color map, a vector containing all of the various colors that appear simultaneously in the display. Our workstations are capable of displaying 16777216 different colors, but no more than 256 of them can be on screen at the same time, and the elements of the color map are the ones that have been selected by the various programs that are running as ones that they wish to use. The display-colormap procedure is a selector that extracts the color map from a given display.

In order to add a new color to the color map, making it eligible for use on screen, one invokes a procedure of two arguments named alloc-color. The first argument to alloc-color is the color map to which the new color is to be added; the second is a record indicating the intensities of the red, green, and blue components of the new color, as real numbers in the range from 0.0 to 1.0. The make-color procedure is the constructor for color records; it takes three arguments -- the red, green, and blue intensities -- and returns the color characterized by those intensities. Here's a typical use of these procedures:

(alloc-color current-color-map (make-color 1.0 0.0 0.0))

This expression builds a bright red color and adds it to the current color map. Alloc-color returns the index of the position within the color map at which the color was installed. Often this index is given a mnemonic name that suggests the color:

(define sky-blue
  (alloc-color current-color-map (make-color 0.4 0.6 1.0)))

Another field of the display is its root window, which is the window that typically occupies the entire display and serves as a backdrop against which other windows are placed. The display-root-window procedure is a selector for this field.

The internal representation of one specific window, such as the one in which the kaleidoscope program will draw its graphics, is also a record. The create-window procedure allocates and returns such a record; it takes a variable number of parameters -- always, however, an even number -- which are alternately symbols naming the fields that need to be initialized and values to store in those fields. Some typical fields of a window record are parent (another window within which the new one is to be placed), width (the number of pixels from the left edge of the window to the right edge), height (the number of pixels from the top edge to the bottom), and background-pixel (the color map's index for the color to be used as the background within the new window).

In order to perform any drawing operations within a window, we'll also need a record called a graphics context, which keeps track of various quantities that control the drawing style -- the foreground color in which new elements are drawn, the width of lines, the font in which text is to be displayed, and so on. The root window of a display already possesses such a graphics context, and one common way to get started in a new window is to make a fresh copy of the root window's graphics context and associate it with the new window. (This makes it possible to change some of the fields of the graphics context without affecting the appearance of any other window.) A procedure named copy-gcontext constructs and returns a copy of a given graphics context record.

Elk provides mutator procedures for several fields of a graphics context, and the kaleidoscope program uses two of these:

The kaleidoscope program invokes both of these procedures before each drawing operation, to randomly change the line width and color.

The vuewm window manager keeps records containing information about the windows it helps to put up, and there are procedures for mutating various fields of those records, with such names as set-wm-hints!, set-wm-normal-hints!, set-wm-class!, set-window-event-mask!, set-wm-name!, and set-wm-icon-name!. Perhaps the most interesting of these is set-window-event-mask!, which determines which of the various kinds of ``input events'' -- moving the mouse, pressing and releasing mouse buttons, pressing and releasing keyboard keys, and so on -- the window will pay attention to.

Once the record for a window has been created and the window manager has been modified to register its existence, one can ask for the window to be drawn onto the display by invoking the map-window procedure, which takes the window record as its only argument.

X Windows includes several procedures for drawing shapes into a window. Here are some of the ones that you're most likely to use, with sample invocations: