Home | Docs | Issue Tracker | FAQ | Download | |
Date: | 2009/03/01 |
---|---|
Authors: | Thomas Bonfort |
Contact: | Thomas.Bonfort at camptocamp.com |
Status: | Planning |
Version: | MapServer 6.0 |
Id: | $Id$ |
This RFC proposes the refactoring of MapServer’s rendering backends, following an approach similar to the vtable architecture used by the input drivers.
The motivation for this refactoring is that in the current code, each renderer is responsible both for low-level drawing functionality (eg. strokes, fills, symbology) and for higher level interpretation of the combination of mapfile keywords (eg. scale-dependant size adjustments, line-folowing orientation for symbols ...). This leads to alot of code duplication and difficult maintainance.
In a first phase, a cairo (http://cairograhpics.org) renderer will be added following this architecture,, and will support traditional raster formats (png, jpeg) as well as vector formats (pdf, svg). Support for openGL and KML rendering backends will also be added similarly. During this phase, the new renderers will live alongside the older ones, and the functionality of the existing renderers should not be affected.
In a second phase, the existing renderers will be ported to the new architecture. Idealy all renderers should be ported to the new architecture, although this task might take more time and/or financing. The current PDF and SVG renderers could be phased out and replaced by the ones natively provided by cairo.
Note
While the current GD renderer could be ported to the API architecture, it probably isn’t something we want to do as we would be losing some performance induced by the switching from GD’s internal pixel representation to the generic one used by this API.
A new renderObj structure is defined and is attached to each outputformat depending on which driver is configured. A renderObj contains function pointers to the low-level functionality that each renderer should implement, and configuration options which can tell the higher level calling code what is supported or implemented by the renderer.
Adding a new output driver to mapserver therefore consists in two steps:
struct renderer{
// configuration parameters
// image creation functions
// raster handling functions (input and output)
// image saving functions (only for the renderers that cannot export a
// raster format)
// low level rendering functions lines and polygons symbology vector
// ellipse pixmap truetype text handling label size calculation label
// rendering
// support for using an image cache for symbology creation of a tile
// representing a cached symbol vector ellipse ... placement of a
// cached tile at a point using a tile as a fill pattern on lines and
// polygons
// freeing functions (main imageObj and caches)
}
Each renderer can inform the higher level calling code of what functionality it supports or implements. This will primarily allow us to support vector and raster renderers. More details on each configuration parameter will be given when appropriate in the rest of this document.
Each renderer must provide a function to create a new image. MapServer’s imageObj structure will have an additional void* member that will be used by the renderer to store a structure containing any usefull information it may need.
imageObj* (*createImage)(int width, int height, outputFormatObj *format,
colorObj* bg);
Note
Renderer specific creation options would be extracted by each renderer from the outputFormatObj passed to it. (This would be used eg by the PDF renderer for page layout options).
Raster handling will follow different code paths depending on wether the underlying renderer is already natively a raster renderer (eg. PNG, JPEG) , or if it is a vector one (eg. PDF, SVG).
A new structure defining a raster object in memory is defined, and is used to replace the current code that uses a gdImage as a common format.
typedef struct {
unsigned int width,height;
// pointers to the start of each band in the pixel buffer
unsigned char *a,*r,*g,*b;
// step between one component and its successor (inside the same band)
unsigned int pixel_step
//step between the beginning of each row in the image
unsigned int row_step;
// pointer to the actual pixels (should not be used by the high-level
// code)
// TODO: as this memeber is actually private, it should probably be
// a void* so that any internal representation can be used
unsigned char *pixelbuffer;
} rasterBufferObj;
A renderer must provide a function to initialize a rasterBuffer where the pixel alignment and the order of the bands best fits its internal representation of a raster array.
void (*initializeRasterBuffer)(rasterBufferObj *buffer, int width, int height);
Depending on the renderer’s capabilities, there are two possibilities here, that are determined by the supports_pixel_buffer parameter:
if the renderer supports a pixel buffer, then the raster layer code is given a handle to the memory buffer used by the renderer, and direclty sets individual pixels where needed. Such a renderer will thus implement a function to export a rasterBufferObj pointing to its internal pixel buffer:
void (*getRasterBuffer)(imageObj *img,rasterBufferObj *rb);
if the renderer does not use an internal representation that can be directly filled by the raster layer code, it must provide two functions that will allow the higher level code to merge the created raster:
void (*mergeRasterBuffer)(imageObj *dest, rasterBufferObj *overlay,
double opacity, int dstX, int dstY)
Similarly to raster layers, there are two code paths for saving a created image to a stream or to a buffer, depending also on the supports_pixel_buffer configuration paramter:
if the renderer supports a pixel buffer, the exported rasterBufferObj will be treated by some new image writing code (relying on libpng/libjpeg/libgif) This will allow each renderer to not have to implement image saving and will allow us to treat all the formatoptions in a single place.
if the renderer does not support exproting to a rasterBuffer, it should provide a function for writing itself to a given stream:
int (*saveImage)(imageObj *img, FILE *fp, outputFormatObj *format);
TODO: exporting to a buffer for mapscript getBytes()
void (*renderPolygon)(imageObj *img, shapeObj *p, colorObj *color);
void (*renderPolygonTiled)(imageObj *img, shapeObj *p, void *tile);
void (*renderLine)(imageObj *img, shapeObj *p, strokeStyleObj *style);
void (*renderLineTiled)(imageObj *img, shapeObj *p, void *tile);
void (*renderVectorSymbol)(imageObj *img, double x, double y,
symbolObj *symbol, symbolStyleObj *style);
void (*renderPixmapSymbol)(imageObj *img, double x, double y,
symbolObj *symbol, symbolStyleObj *style);
void (*renderEllipseSymbol)(imageObj *image, double x, double y,
symbolObj *symbol, symbolStyleObj *style);
void (*renderTruetypeSymbol)(imageObj *img, double x, double y,
symbolObj *symbol, symbolStyleObj *style);
void (*renderTile)(imageObj *img, void *tile, double x, double y, double angle);
label size calculation: If passed an “advances pointer”, also calculates individual advances for each glyphs (for anlge foloow text)
int (*getTruetypeTextBBox)(imageObj *img,char *font, double size, char *string,
rectObj *rect, double **advances);
glyph rendering: no real change
void (*renderGlyphs)(imageObj *img, double x, double y, labelStyleObj
*style, char *text);
void (*renderGlyphsLine)(imageObj *img,labelPathObj *labelpath,
labelStyleObj *style, char *text);
For some symbols and renderer combinations, it might be benficial to use a cache so as not to have to render a complicated symbol over and over again. This behavior would probably have to be deactivated when using attribute binding (on size, angle, color...) as there would be too many cache misses and no real gain.
TBD: use a renderer specific format for a tile (passed back as a void*), or would a simple imageObj created by normally by the renderer suffice ? Second solution is much simpler as we’d use the same functions for rendering a symbol than for rendering a tile. First solution is more flexible, as the renderer can cache anything.
void (*transformShape)(shapeObj *shape, rectObj extend, double cellsize);
These functions might be needed by some renderers. TBD
void (*startNewLayer)(imageObj *img, double opacity);
void (*closeNewLayer)(imageObj *img, double opacity);
void (*freeImage)(imageObj *image);
void (*freeSymbol)(symbolObj *symbol);
void (*freeShape)(shapeObj *shape);
Note
The freeSymbol and freeShape functions are added here as we will be adding a void* pointer to the symbolObj and shapeObj objects where a renderer can store a private cache containing an implementation specific representation of the object.
nothing special. just pass on to the renderer.
Depending on whether the renderer can export a raster buffer or not:
MapServer’s Raster layer handling will be modified so that it can write to a rasterBufferObj and not only to a gdImage.
Note
TODO (FrankW) add some detail here about the changes induced here
if(renderer->supports_pixel_buffer) {
rasterBufferObj buffer;
renderer->getRasterBuffer(img,&buffer);
msDrawRasterLayer(map,layer,&buffer);
}
else {
rasterBufferObj buffer;
renderer->initializeRasterBuffer(&buffer,width,height);
msDrawRasterLayer(map,layer,&buffer);
renderer->mergeRasterBuffer(img,&buffer,0,0);
renderer->freeRasterBuffer(&buffer);
}
A symbol cache similar to the actual mapgd.c imagecache will be implemented in mapimagecache.c .
Note
As pointed out earlier, it is yet to be decided if the cache will store plain imageOjbs or if we allow renderers to store a specific structure. This could also be a configuration option, with each renderer specifying if it can treat it’s own created imageObj as a cache, or if it needs a void* cache. This would keep the code simple for most renderers, while allowing more flexibility for others (the openGL renderer will probably be needing this)
The use of an image cache would be configurable by each renderer via the supports_imagecache configuration option, as its use isn’t necessarily useful depending on the format.
if(renderer->supports_imagecache) {
// this function looks in the global image cache if it finds a tile
// corresponding to the current symbol and styling and returns it. If
// it wasn't found, it calls the renderer functions to create a new
// cache tile and adds it to the global cache.
imageObj *tile = getTile(img, symbol, symbolstyle);
renderer->renderTile(img, tile, x, y);
}
else {
switch(symbol->type) {
case VECTOR:
renderer->renderVectorSymbol(img, params...);
break;
case ELLIPSE:
/* ... */
}
}
simple stroke: call the renderer directly
marker symbols:
- compute positions and orientations along the geometry
- render a marker symbol with the renderer’s marker functions for each of these positions. It probably isn’t a good idea to use a tile cache if the symbols have to follow line orientation, as rotating a cached tile will produce poor results.
pattern:
- create the pattern’s tile (with the renderer’s marker functions)
- pass the tile to the renderer
Nothing special to add here, except that the basic preliminary tests (if the font is defined, or if the string to render is NULL) and the lookup of the system path of the truetype font file will be done once by the high level code before calling the renderer drawing function.
Note
Raster fonts will not be supported (at least in a first phase) by this rendering API. Only truetype fonts are implemented.
Currently all image I/O is done through the GD library. To raise this dependency, the RFC proposes to have mapserver implement image loading and saving by directly interfacing the libpng / libjpeg / giflib libraries. The corresponding code will be added to a new file “mapimageio.c”, and will produce or read rasterBufferObj’s.
These functions will implement the formatoptions that are currently supported. Adding new formatoptions would be done by adding the code to these i/o functions, and would thus add the support for all the renderers that use this rendering API architecture:
Note
The initial implementation of this RFC will not include support for writing GIF images.
Note
The initial implementation of this RFC will not include image loading functions by direct usage of the libjpeg/libpng/giflib libraries. It will instead fallback to GD loading and convert the created gdImage to a rasterBuffer. This can be added in a second phase as it is lower priority, and does not induce a major architectural change.
The current pdf (and SVG?) renderers have the option of switching over to GD for some layers, that are then incorporated as a raster image in the final map rather than adding each feature in the usual way.
This functionality could probably be kept with this API with the mergeRasterBuffer function.
Note
A renderer using this functionality will have to take care as the rasterBuffer that it will be passed will be in the native format of the delegated renderer, and may not be in the exact same format it would have created with a call to initializeRasterBuffer.
The legend rendering code will have to be refactored, as it currently produces a gdImage. Legend rendering will be handled like a normal imageObj.
Note
TODO: “embed” mode is problematic.
If postlabelcache is on, then the created imageObj can easily be added to the main image using the renderTile function (for the renderers that support it. the others will probably have noe embedded legend support)
If postlabelcache is off we have a problem, as the created legend is passed as a pixmap symbol to the main map rendering. This is incompatible with the vector renderers, as the imageObj they have created isn’t an array of pixels.
TBD
TBD. Should we even still be supporting this ?
No end documentation needed aside from the migration guide.
TBD
Symbology should be better behaved between renderers, but does imply some backwards incompatible changes in how some symbols can be rendered (although this can probably seen as fixing bugs rather than backwards incompatibilities)