Department of Statistics
The University of Auckland
The basic idea of these changes is to separate the graphics into three components: graphics devices, graphics engine, and graphics systems. R's main graphics are an example of a graphics system -- I refer to these as "base graphics".
This separation means that R's base graphics do not have any particularly special status (apart from the fact that they get loaded by default). Other graphics systems can be written and can get equal access to R's graphics engine and graphics devices (prior to these changes, other graphics systems had to go via R's base graphics, which was not convenient for some things. For example, controlling the clipping region on devices was very awkward.)
The interface to the graphics device component is given in
This describes the data and functions that every device must store -- the
graphics engine assumes this information about every device.
Graphics calls to the device must specify all locations and dimensions
in device units.
The interface to the graphics engine component is given in
This describes the functions that the graphics engine can perform and is
used by graphics systems to produce graphical output.
Calls to the graphics engine must use device coordinates -- the graphics
engine provides some helper functions for converting between device
coordinates, normalised device coordinates, and inches.
The interface to R's base graphics is still given by
until further changes are made (see below).
Both the graphics engine and the graphics devices must contain no information
about a particular graphics system. Graphics systems must register with the
graphics engine using
GEregisterSystem. The system must provide
a callback function when it registers -- the graphics engine will use
this callback to signal the graphics system when important events occur
(e.g., when a new device has been created).
integer that the graphics system must record -- the graphics engine records
system-specific information with every device amd this integer
is used within the
callback function to get system-specific information back out of a device.
Calls to the graphics engine (and devices) must specify any relevant
graphical parameters. For example, a call to
specify a line colour, a line type, and a line width. (It used to be the
case that the graphics engine and even graphics devices would look back
up to the graphics system to see what the current settings were for
graphcal parameters. This reflected the assumption that there would only
ever be one graphics system to look back up to.)
This reduces the allowable set of graphics
parameters to those understood by the graphics engine; these are:
col (for lines and borders), fill (for filling rects, polygons, ...),
lwd (line width), lty (line type), font (font face), ps (point size of
text), cex (character scaling factor). Other
parameters are restricted to base graphics (as they should be).
The creation, destruction, copying, ... of devices is handled by the graphics engine.
There are three types of information stored about each R device: device-level information (including device-specific information) in the NewDevDesc structure; graphics engine information in the GEDevDesc structure; and graphics system-specific information in the GESystemDesc structure. The graphics engine is the master both in relation to the devices and in relation to graphics systems. The GEDevDesc structure (per device) contains information about the device in a NewDevDesc and information about every registered graphics system (e.g., current graphics system state) in an array of GESystemDesc's.
GE_InitStateevent. This code can rely on the system knowing its register index, but the
dd->gesd[systemRegisterIndex]structure is empty. It is the callback code's responsibility to place a system state object within the
dd->gesd[systemRegisterIndex]structure before trying to call code that will access that structure.
GEDevDesc. (stored in the global
R_Devicesarray). This structure represent the graphics engine's record of the device. There is information about the device itself, in a
NewDevDescstructure, and graphics-state information for the device for each registered graphics system in an array of
GEDevDesc NewDevDesc *dev GESystemDesc gesd
NewDevDescstructure contains basic generic information about the device, PLUS device-specific information in a device-specific structure, PLUS pointers to device-specific implementations of the required set of graphical functions.
NewDevDesc < generic info > < graphics function pointers > void *deviceSpecific
GESystemDescstructure contains system-specific information in a system-specific structure, PLUS a callback function pointer.
GESystemDesc void *systemSpecific GEcallback callback
GESystemDescstructure (stored in the global
registeredDevicesarray). This is just used to retain a record of which systems are registered and the respective callback hooks.
device creates NewDevDesc device sets basic information and graphics function pointers device creates deviceSpecific device puts deviceSpecific in NewDevDesc engine creates GEDevDesc engine puts NewDevDesc in GEDevDesc engine creates GESystemDesc for each registered system each system creates systemSpecific system puts systemSpecific in GESystemDesc engine puts GESystemDescs in GEDevDesc
device frees deviceSpecific each system frees systemSpecific engine frees GESystemDesc for each registered system engine frees NewDevDesc engine frees GEDevDesc
engine creates GESystemDesc for each existing device system creates systemSpecific for each GESystemDesc system puts systemSpecific in GESystemDesc engine puts GESystemDescs in GEDevDesc engine creates GESystemDesc for global array
system frees systemSpecific for each existing device engine frees GESystemDesc for each existing device engine frees GESystemDesc in global array
grid, that would need to be considered when writing another graphics system.
replayPlot()(R-level functions) are only base graphics functions at the moment -- they could be made graphics engine functions.
replayPlot()are compatible with "snapshots" (used in Windows plot history), but only by good fortune at this stage; they need thinking about to ensure compatibility for any future graphics system, in particular saving such objects and loading them into a new session. An important consideration when making the change will be how to handle incompatibility with old snapshots (pre device changes), which is also only fortuitously achieved at this point.
graphics.cstill consists of a mixture of base graphics code, code to support the Graphics.h interface, and graphics engine code (e.g., display list code). This needs separating so that graphics engine code ends up in
engine.cand base graphics code ends up in
base.cand only Graphics.h interface support is left in graphics.c. Here's a list of the various code blocks in graphics.c and where they should end up:
A particular example of this is the overlap between the base GPar structure and the NewDevDesc structure. The NewDevDesc structure respresents information about a device and this can be removed from the base GPar structure, which should contain only information about base graphics graphical parameters. This change will require modifications of the base graphics code (e.g., in graphics.c and plot.c) to access device information from a NewDevDesc rather than a GPar.
Another important issue here is that there currently exists two copies of
some code, where there is a G* function and a GE* equivalent. Need to
at least modify the G* functions so that they call the GE* equivalent.
This has now been done (in development version post 1.8.0).
Most G* functions in graphics.c
(basically the ones that are to do with drawing things)
are now simply wrappers that call the corresponding GE* function in
engine.c. This allows
allows code in places like plot.c to remain unchanged. Modifying
plot.c to go directly to the GE* functions will require a bit of care.
(NOTE: the graphicsQC package has proved invaluable for checking that
changes to the C code do NOT produce changes in output and would be
an important tool in further changes.)
The current state of the code is represented in the following diagram
(most of the high-level base C code goes through Graphics.h to
graphics.c which goes through GraphicsEngine.h to engine.c):
Rf_gpptret al; lots of stuff in
graphics.c; all pointer coercions from/to