![]() |
Docs: Graphics Command Library
The graphics command library allows a program to install and process a set of standard graphics commands in conjunction with the ERPSS graphics environment and command-processing system. By installing these commands, a graphics program can provide a user with access to a set of basic plotting utilities that are present in a variety of programs. In addition, a standardized set of options is available for controlling graphics parameters such as line width and color, and these options can be conjoined with the native commands of a particular graphics program. By using this library, graphics programs can share both specific commands and general command formats, thus reducing both programming overhead and user effort.
This document assumes that the reader is familiar with the ERPSS plotting environment, the usage of the standard graphics commands, and the ERPSS command interface. These issues are described in the following documents:
the ERPSS plotting environment is
described in plot(3p)
a functional description of the standard
graphics commands can be found in the Multiplot
Tutorial and User's Guide
the general principles underlying
the command interface can be found in command(3u)
this document
The standard graphics commands are implemented in a library named "/usr/erpss/lib/libp.a", and a variety of useful constants and data structures are defined in /usr/erpss/include/plot/grafcom.h. The routines in libp.a must be linked with several other ERPSS libraries: libpf.a, libd.a, libu.a, and libesys.a (in that order). All of the graphics commands can be installed into a program's command list by calling a single routine, gc_install(). This routine is passed a handle to the program's command list and a handle to the plot buffer structure. Once this routine has been called, the program need only process a user's input with the standard do_command() and do_command_line() routines to provide the user with access to all of the standard graphics commands. At the end of the program, the gc_free() routine should be called, which frees the memory associated with the graphics options, followed by the free_commandlist() routine, which will free the memory associated with the command list, including any program-specific commands.
The following is an example of a program, similar to multiplot, that installs the graphics commands and then reads user commands from the standard input and executes them (note that this is a bare-bones version that lacks many desirable features, such as error checking).
#include#include "/usr/erpss/include/plot/plot.h" #include "/usr/erpss/include/command.h" #include "/usr/erpss/include/grafcom.h" #include "/usr/erpss/include/getargs.h" COMMANDPTR commandlist; /* Stores the commands */ PLOTBUF *pb; /* Plot buffer */ ARGBUF ab; /* Argument buffer */ main(argc, argv) int argc; char *argv[]; { pb = pdopen(argv[1]); /* Get plot buffer */ commandlist = NULL; /* Command list is empty */ set_verbose(COM_LOQUACIOUS); /* Enable error messages */ gc_install(&commandlist, &pb); /* Install graphics commands */ /* Any program-specific commands can be installed here with install_command(). */ while (getargs(&ab, stdin) != EOF) { /* Process commands */ do_command_line(commandlist, ab.lac, ab.lap); pdflush(pb); /* Flush plot filter pipeline */ } gc_free(); /* Free memory for options */ free_commandlist(&commandlist); /* Free memory for commands */ pdclose(pb); /* Release plot device */ }
In addition to installing the standard graphics commands, most programs will also combine program-specific commands with the standard library options that manipulate line style, text style, translation, etc. These options can be accessed through a small set of global variables and functions, which can be declared as externals in the main program. The names of all of the global variables and functions in the graphics command library begin with "gc_", and to prevent accidental overlap, a program's native variables and functions should not begin with this prefix.
One major set of standard options is used to control the appearance of lines, text, and polygon-based shapes. These types of graphical objects have different, but overlapping sets of options associated with them. The line options control line width, line style, and line color. The shape options include these parameters, but add a fill pattern option. The text options also include the line options, and add textsize, font, and header variable options. When gc_install() is called, the option lists are created and the pointers to these lists are stored in the global variables gc_line_opts, gc_grid_opts, gc_text_opts, and gc_shape_opts. These option lists can be used in program-specific commands directly, or the append_options() routine can be used to join these options with other, program-specific options. An example of this approach can be found later in this document.
These options use a common data structure for passing information between the option-processing routines and the command-processing routine. This data structure is named gc_style, and is defined in grafcom.h. It includes a variety of members, some of which are irrelevant for a particular type of option (e.g., the font member is irrelevant for rectangles), but this is necessary since the same functions are used to process identical options that occur in different option lists (e.g., the "color=" option is processed by the same routine for both line color and text color). And since few of these structures are typically defined at a given time, the memory necessary for the unused members is relatively inconsequential.
The gc_style structure is defined as:
struct gc_style {
u_short color; /* color */
u_short linewidth; /* line width */
u_short linetype; /* line type */
short fill; /* fill pattern (shape only) */
u_short font; /* font style (text only) */
u_short laborigin; /* label origin (text only) */
double tsize; /* text size (text only) */
int numvar; /* number of header variables (text only) */
char *varname[MAXVAR]; /* header variable names (text only) */
int varindex[MAXVAR]; /* header variables indices (text only) */
char *special; /* for program-specific use */
};
A structure of this type will be passed to the processing routine for any command that uses the standard line, shape, or text options (see example below). Note that one of the members of this structure, named "special", is designed to be used by any program-specific options that are merged with the standard style options. It is defined as a character pointer, but appropriate type casting can be used to force any type of data into the memory space pointed to by this variable (see example below).
The option storage structure for a command must be initialized when the command is invoked. The standard commands initialize their style structures to default values, but these default values can be changed with the linestyle, textstyle, and gridstyle commands. The linestyle command controls the default values for both the line options and the shape options, and the gridstyle command controls the default values for any future commands that draw grids or axes (no grid commands currently exist in the library; this command anticipates the use of program-specific grid commands). When a line or shape command is given at runtime, the command's option storage structure is initialized to the default value, which is stored in a global variable named gc_curlinestyle. Then any options specified by the user are used to update the option storage structure, which is finally passed to the command-processing routine. A similar sequence of events occurs for text commands, which are initialized to the value of gc_curtextstyle, and grid commands, which are initialized to the value of gc_curgridstyle.
When gc_install() is called to install the commands and options, the default style variables are initialized to the values stored in a global variable named gc_defaultstyle. By manipulating the elements of this variable before calling gc_install(), it is possible to control the initial status of all of the style variables.
The following example adds a "grid" command to the program in the previous example. This command draws a grid of lines on the screen: 2 mandatory arguments specify the number of lines in the x and y dimensions, respectively, and a set of four optional arguments can be used to specify a rectangle that bounds the grid (default is the entire screen). The standard line style options are also available, and their values are initialized to gc_curgridstyle.
The four optional arguments for specifying the boundary rectangle are "left=", "top=", "right=", and "bottom=", and the values for these parameters are stored in an array pointed to by the special member of the gc_style structure. The memory for this array is allocated at the beginning of the program in the main() routine (described below), and special is assigned the address of this memory space.
The first step in implementing the boundary rectangle options is to write an initialization routine for the gc_style structure, an example of which can be found below. The routine first copies the value of gc_curgridstyle into the style structure, thereby setting the members of the style structure to the default grid style values. The gc_curgridstyle structure's special member is a pointer, so the value of this pointer is copied to the new style structure rather than the contents of the area pointed to. Therefore, the new style structure's special member's value must be retained, and the values pointed to by gc_curgridstyle.special are copied rather than the pointer itself. The constants LEFT, TOP, RIGHT, and BOTTOM are defined to keep track of the position of the coordinates within this space.
This is the initialization routine:
#define LEFT 0 /* Index/ID for left edge of boundary rect */
#define TOP 1 /* Index/ID for top edge of boundary rect */
#define RIGHT 2 /* Index/ID for right edge of boundary rect */
#define BOTTOM 3 /* Index/ID for bottom edge of boundary rect */
int init_grid(st)
struct gc_style *st; /* Pointer to struct being initialized */
{
extern struct gc_style gc_curgridstyle;
double *tmp1, *tmp2;
tmp1 = (double *) st->special; /* This pointer must be retained */
tmp2 = (double *) gc_curgridstyle.special;
*st = gc_curgridstyle; /* Initialize style structure */
st->special = (char *) tmp1; /* Restore the pointer */
*(tmp1 + LEFT) = *(tmp2 + LEFT); /* Copy the boundary values */
*(tmp1 + TOP) = *(tmp2 + TOP);
*(tmp1 + RIGHT) = *(tmp2 + RIGHT);
*(tmp1 + BOTTOM) = *(tmp2 + BOTTOM);
return(0);
}
The next step after creating an initialization routine is to write a routine for processing the options. When an option-processing routine is called, it is passed an ID value that can be used to indicate which of several options has called the routine to life. By using LEFT, TOP, RIGHT, and BOTTOM, as ID values for the left=, top=, right=, and bottom= options, respectively, the ID value can also serve as an index into the array pointed to by special (e.g., the ID for "left=" is LEFT). The option-processing routine is also passed a pointer to the string that follows the "=" sign, and the atof() function can be used to convert this string into the double-precision floating point value that must be stored in the array. This, then, is what the option-processing routine would look like for the boundary rectangle options:
int do_boundary(id, str, st)
int id; /* ID of option */
char *str; /* Value of option */
struct gc_style *st; /* Style structure pointer */
{
extern double atof();
double *tmp;
/* Note: ID of option = offset into array */
tmp = (double *) st->special;
*(tmp + id) = atof(str);
return(0);
}
Before considering the actual command-processing routine for the grid command, let's look at how the grid command and its options are installed. Because the standard "gridstyle" command should allow the boundary rectangle options to be specified, we'll want to add the options onto the gc_grid_opts option list rather than creating a new option list. This is done by simply calling install_option() and using gc_grid_opts as the option list (note that gc_install() must be called before this point or the standard options will not yet have been defined). The global variable gc_curgridstyle must have space allocated for the boundary rectangle coordinates, and the initial values for these coordinates must be set. After the options have been installed, the "grid" command itself is installed. The command-processing routine is named draw_grid(), and it has two mandatory arguments, xticks and yticks. The gc_grid_opts option list is specified for the options, using init_grid() as the initialization routine. A gc_style structure named "tmpstyle" is used to store the values of the options, and so this variable is declared in the main routine (as a static variable) and a pointer to this structure is passed to the installation routine. When the "grid" command is invoked at runtime, this pointer is passed to the initialization routine, the option-processing routines, and the command-processing routine. The special member of this structure must point to a memory space that can contain the coordinates for the grid's boundary rectangle, and this allocation is also done within the main() routine.
Here is an updated version of the main() routine that was described in the first example, complete with the installation commands for the "grid" command and its options (the new portions are printed in boldface):
main(argc, argv)
int argc;
char *argv[];
{
static struct gc_style tmpstyle;/* Option storage structure */
extern OPTIONPTR gc_grid_opts; /* The standard line options */
double *tmp; /* Temporary pointer to double */
pb = pdopen(argv[1]); /* Get plot buffer */
commandlist = NULL; /* Command list is empty */
set_verbose(COM_LOQUACIOUS); /* Enable error messages */
gc_install(&commandlist, &pb); /* Install commands */
/* Initialize storage space and values for boundary rectangle */
tmpstyle.special = malloc(4*sizeof(double));
gc_curgridstyle.special = malloc(4*sizeof(double));
tmp = (double *) gc_gridstyle.special;
*(tmp + LEFT ) = 0.0; /* Left */
*(tmp + TOP ) = 1.0; /* Top */
*(tmp + RIGHT ) = 1.0; /* Right */
*(tmp + BOTTOM ) = 0.0; /* Bottom */
/* Install grid options */
install_option(&gc_grid_opts, "left=", do_boundary, LEFT);
install_option(&gc_grid_opts, "top=", do_boundary, TOP);
install_option(&gc_grid_opts, "right=", do_boundary, RIGHT);
install_option(&gc_grid_opts, "bottom=", do_boundary, BOTTOM);
/* Install grid command with grid options */
install_command(&commandlist, "grid", draw_grid, 3, "xticks xticks",
init_grid, gc_grid_opts, &tmpstyle);
while (getargs(&ab, stdin) != EOF) { /* Process commands */
do_command_line(commandlist, ab.lac, ab.lap);
pdflush(pb); /* Flush plot buffer pipeline */
}
free(tmpstyle.special); /* Free boundary rect memory */
free(gc_curgridstyle.special);
gc_free(); /* Free memory for options */
free_commandlist(&commandlist); /* Free memory for commands */
pdclose(pb); /* Release plot device */
}
The final stage in creating the grid command is the routine that actually draws the grid, draw_grid(). This routine is passed three parameters, the argument count (argc) and strings (argv) from the actual command invocation and the gc_style structure that holds the values of the optional parameters. Since the do_command() routine that calls draw_grid() checks for the existence of the proper number of mandatory arguments, the argument count parameter is not used. However, the array of argument strings is necessary because the command-processing routine itself must process the mandatory arguments contained in these strings (in this case, the number of x and y ticks).
The draw_grid() routine is also responsible for utilizing the information passed in the gc_style structure, and must set the plot buffer to the appropriate line color, line width, and line style. There are some useful library routines that set all of the relevant plot buffer parameters for line, text, or shape options: gc_setlineinfo(), gc_settextinfo(), and gc_setshapeinfo(). These routines are passed a gc_style structure and set the plot buffer to the values relevant for the relevant graphical object (e.g., text size is updated only by gc_settextinfo()). For efficiency, these routines only issue a command to the plot filter if the new value differs from the old value.
Before calling gc_setlineinfo(), the draw_grid() routine saves the current plot buffer values so that they can be restored after the grid has been drawn. Saving and restoring the plotting attributes is not really necessary in this program, because each command explicitly sets these attributes rather than assuming default values. However, because future program-specific commands may assume a constant plotting environment, all of the standard graphics commands restore the plotting attributes upon completion, and the "grid" command has been implemented with this constraint. Two pairs of saving and restoring routines are available for this purpose: gc_save_style() and gc_restore_style() simply use a static gc_style variable for storage; gc_push() and gc_pop() maintain a dynamically allocated stack, thus allowing nesting. These routines also differ in the amount of information that is stored: the save and restore routines maintain only the major style parameters (i.e., the parameters contained in the gc_style structure), whereas the stack-oriented routines also save the default line, text, and grid styles and the translation, rotation, and scaling values (described below). The stack-oriented routines also require more memory and are slower, so they should only be used when nesting is possible (e.g., for commands that read files of commands).
The following is an implementation of the draw_grid() routine. For the sake of brevity, the actual drawing steps have been omitted, but all of the overhead associated with the mandatory and optional arguments has been included.
int draw_grid(argc, argv, st)
int argc; /* Argument count */
char *argv[]; /* Argument values */
struct gc_style *st; /* Style structure pointer */
{
int xticks, yticks;
short left, top, right, bottom;
double *tmp;
gc_save_style(); /* Save old style values */
gc_setlineinfo(*st); /* Set new style values */
xticks = atoi(argv[1]); /* # of x ticks */
yticks = atoi(argv[2]); /* # of y ticks */
/* Translate from 0.0-1.0 coordinates into virtual pixels */
tmp = (double *) st->special; /* Use a (double *) */
left = *(tmp + LEFT) * XVIRPIX;
top = *(tmp + TOP) * pb->pfinfo.vymax;
right = *(tmp + RIGHT) * XVIRPIX;
bottom = *(tmp + BOTTOM) * pb->pfinfo.vymax;
/*
Main grid-drawing statements would be placed here...
*/
gc_restore_style(); /* Restore old style values */
return(0);
}
In addition to the style options discussed above, the library supports a set of options for controlling the translation, rotation, and scaling of graphical images. These options are usually used for commands that affect a large number of graphical objects, rather than individual objects. For example, most programs will want to support a "readfile" command that allows a set of commands to be read from a file, and this is an appropriate command for the translation options.
The standard options are used to specify the translation offset ("offset=x,y"), the scale ("scale=x,y"), the rotation ("rotation=theta"), and the location around which the scaling and rotation are centered ("center=x,y" -- the need for this centering option is discussed in the Multiplot User's Guide). The centering is implemented by using the two stages of translation supported by the ERPSS plot filter system. Images are processed by translating once, doing the scaling and rotation, and then translating again. The first translation moves the center of the object to (0,0) and the second translation moves the object back to its original center and adds any additional translation that is desired. The offset and center values specified by the user are used to define the first translation coordinate as (-centerX, -centerY) and the second translation coordinate as (centerX+offsetX, centerY+offsetY).
When the scale and rotation are specified by the user, they are processed absolutely rather than relatively; in other words, the new values are not added to the previous values. However, as the name implies, the offset values are offsets from the previous translation, and are additive (e.g., an offset of (.1,.2) followed by an offset of (-.3, .5) would result in an offset of (-.2,.7)).
The translation options are implemented by interposing a set of routines and global variables between the option-processing routines and the plot filter routines, and any program that manipulates the translation values must use these routines to avoid conflicting with the standard graphics commands. At the heart of these routines is a structure called gc_TRSinfo ("TRS" is an abbreviation for translation, rotation, and scaling). It is defined in grafcom.h as:
struct gc_TRSinfo {
short xscale; /* X scaling factor */
short yscale; /* Y scaling factor */
short xcenter; /* Center point */
short ycenter; /* Center point */
short xoffset; /* X offset */
short yoffset; /* Y offset */
short rotation; /* Rotation (in degrees) */
char *special; /* For program-specific options */
};
Note that, like gc_style, one of the members of this structure is named special and is a character pointer that is designed for any program-specific options that are merged with the standard translation options.
There is a gc_TRSinfo structure named gc_TRS that is used to store the current translation values, and it is available as a global variable. If gc_TRS is modified, the modifications must also be passed to the plot filter so that gc_TRS is always an accurate index of its status. The preferred way to modify the value of gc_TRS is to call the routine gc_update_TRS(), which is passed a gc_TRSinfo structure that contains the new values for gc_TRS. When this routine is called, it sets gc_TRS to the specified values and calls a routine called gc_do_TRS(), which sends the new values to the plot filter (for the sake of efficiency, values that are identical to those in the plot buffer are not sent to the plot filter).
The option list for the standard translation options is pointed to by a global variable named gc_trs_opts. These options can be installed with a new command very easily. As an example, we will add a "readfile" command to our previous multiplot-like example. This command will cause the program to read commands from a file that is specified as a mandatory parameter, using the standard translation options.
As it was for the "grid" command, the first here step is to create an initialization routine that sets the default values for the gc_TRSinfo structure. There is a routine in the library called gc_init_transform() that sets a gc_TRSinfo structure to the value of gc_TRS, which should be the default value, and this routine can be used as the initialization routine for the "readfile" command. Since we are not adding any additional options to the standard set, we don't need to create any option-processing routines, so the next step is to install the "readfile" command. This can be done in the main() routine, right after the installation of the "grid" command, in the following manner:
install_command(&commandlist, "readfile", read_file, 2, "filename", gc_init_transform, gc_trs_opts, &trs);
In this installation procedure, the first set of parameters indicate that the name of the command is "readfile", that it has two mandatory arguments (the command itself and the "filename"), and that read_file() is the command-processing routine. The last three parameters indicate that gc_init_transform() is the initialization routine, that gc_trs_opts is the list of options for this command, and that a structure named trs should be used to store the data from the options. The trs structure and gc_trs_opts must have been declared previously as:
static struct gc_TRSinfo trs; /* TRS storage structure */ extern static OPTIONPTR gc_trs_opts; /* From library */
The final step is to create the command-processing routine, read_file(). First, the file named in argv[1] is opened. Next, the current plotting environment is saved; since "readfile" commands may be nested, the stack-oriented routines are used. Next, the translation values passed to the read_file() routine in the gc_TRSinfo structure are used to update the plot buffer with the gc_update_TRS() routine. After these preliminary operations, lines of input are read with the getargs() routine and processed with the do_command_line() routine. When the end of file is reached or an error occurs, the routine stops reading from the input file, closes the file, flushes the plot filter pipeline, and restores the plotting environment to its previous state.
Here is the read_file() routine:
int read_file(argc, argv, info)
int argc;
char *argv[];
struct gc_TRSinfo *info;
{
ARGBUF ab; /* Stores user command lines */
FILE *inputfile; /* Points to file containing commands */
int err = 0; /* Stores return values */
inputfile = fopen(argv[1], "r");
if (gc_push() != 0) return(-1); /* Save plotting environment */
gc_update_TRS(*info); /* Set new TRS values */
/* Read and execute commands from inputfile */
while (!err && getargs(&ab, inputfile) != EOF)
err = do_command_line(commandlist, ab.lac, ab.lap);
fclose(inputfile); /* Close command file */
pdflush(pb); /* Flush plot filter */
return(gc_pop()); /* Restore plotting environment */
} /* read_file() */