MS RFC 54:呈现接口API

日期

2009/03/01

作者

托马斯堡

联系

在camptocamp.com的thomas.bonfort

状态

规划

版本

MAPServer 6

目的

这个RFC建议重构mapserver的渲染后端,其方法类似于输入驱动程序使用的vtable体系结构。

这种重构的动机是,在当前代码中,每个渲染器都要负责低级绘图功能(例如,笔画、填充、符号学)和高级解释mapfile关键字组合(例如,取决于比例的大小调整、符号的行跟随方向…)。这会导致大量代码重复和维护困难。

在第一阶段中,将按照此体系结构添加cairo(https://www.cairographics.org/)渲染器,并支持传统的栅格格式(png、jpeg)和矢量格式(pdf、svg)。同样,还将添加对OpenGL和KML渲染后端的支持。在此阶段中,新的渲染器将与旧的渲染器共存,并且不应影响现有渲染器的功能。

在第二阶段,现有的渲染器将移植到新的架构中。Idealy所有渲染器都应该移植到新的架构中,尽管这项任务可能需要更多的时间和/或资金。目前的PDF和SVG渲染器可以逐步淘汰,取而代之的是开罗提供的本地渲染器。

备注

虽然当前的gd渲染器可以移植到API体系结构,但这可能不是我们想要做的事情,因为从gd的内部像素表示转换为此API使用的通用像素表示会导致性能下降。

低级渲染API

一个新的 renderObj 根据配置的驱动程序,结构被定义并附加到每个输出格式。renderobj包含指向每个渲染器应该实现的低级功能的函数指针,以及可以告诉高级调用代码渲染器支持或实现的内容的配置选项。

因此,向MapServer添加新的输出驱动程序包括两个步骤:

  • 实现低级渲染功能(取决于渲染器的功能和配置选项,并非所有功能都需要实现)

  • 通过使renderobj函数指针指向低级函数,创建并注册新的outputformat驱动程序

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)
}

配置参数

每个渲染器都可以通知更高级别的调用代码它支持或实现的功能。这将主要允许我们支持向量和栅格渲染器。在本文档的其余部分中,适当时将提供有关每个配置参数的更多详细信息。

图像创建功能

每个渲染器必须提供一个函数来创建新图像。mapserver的imageobj结构将有一个额外的void*成员,渲染器将使用该成员来存储包含它可能需要的任何有用信息的结构。

imageObj* (*createImage)(int width, int height, outputFormatObj *format,
            colorObj* bg);

备注

每个渲染器都将从传递给它的outputformatobj中提取特定于渲染器的创建选项。(这将被PDF渲染器用于页面布局选项)。

栅格处理功能

栅格处理将遵循不同的代码路径,这取决于底层渲染器是否已经是栅格渲染器(如png、jpeg),或者是否是矢量渲染器(如pdf、svg)。

栅格缓冲结构

定义了在内存中定义栅格对象的新结构,并用于替换当前使用gdimage作为公共格式的代码。

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 member is actually private, it should probably be
    // a void* so that any internal representation can be used
    unsigned char *pixelbuffer;

} rasterBufferObj;

渲染器必须提供一个函数来初始化RasterBuffer,其中像素对齐和波段顺序最适合其栅格阵列的内部表示。

void (*initializeRasterBuffer)(rasterBufferObj *buffer, int width, int height);

处理栅格图层

根据渲染器的功能,这里有两种可能性,由 supports_pixel_buffer 参数:

  • 如果渲染器支持像素缓冲区,则将为栅格层代码提供渲染器使用的内存缓冲区的句柄,并且direclty在需要时设置单个像素。这样的渲染器将实现一个函数来导出指向其内部像素缓冲区的RasterBufferObj:

    void (*getRasterBuffer)(imageObj *img,rasterBufferObj *rb);
    
  • 如果渲染器不使用可由栅格层代码直接填充的内部表示,则它必须提供两个功能,以允许更高级别的代码合并创建的栅格:

    void (*mergeRasterBuffer)(imageObj *dest, rasterBufferObj *overlay,
                double opacity, int dstX, int dstY)
    

图像保存功能

与栅格图层类似,有两种代码路径用于将创建的图像保存到流或缓冲区,这也取决于 supports_pixel_buffer 配置参数:

  • 如果渲染器支持像素缓冲,导出的RasterBufferObj将被一些新的图像写入代码(依赖于libpng/libjpeg/libgif)处理,这将允许每个渲染器不必实现图像保存,并允许我们在一个地方处理所有的格式选项。

  • 如果渲染器不支持扩展到RasterBuffer,则它应提供一个函数,用于将自身写入给定的流:

    int (*saveImage)(imageObj *img, FILE *fp, outputFormatObj *format);
    

TODO:导出到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);

标签和文本

  • 标签大小计算:如果传递一个“前进指针”,也会计算每个字形的单独前进(对于anlge foloow文本)

    int (*getTruetypeTextBBox)(imageObj *img,char *font, double size, char *string,
                rectObj *rect, double **advances);
    
  • 字形渲染:无实际更改

    void (*renderGlyphs)(imageObj *img, double x, double y, labelStyleObj
            *style, char *text);
    
    void (*renderGlyphsLine)(imageObj *img,labelPathObj *labelpath,
            labelStyleObj *style, char *text);
    

瓷砖和缓存

对于某些符号和渲染器组合,使用缓存可能很有用,这样就不必反复渲染复杂的符号。当使用属性绑定(关于大小、角度、颜色…)时,可能必须停用此行为,因为会有太多的缓存未命中和没有实际增益。

tbd:对一个图块使用特定于渲染器的格式(作为一个空*返回),或者渲染器通常创建的简单imageobj是否足够?第二个解决方案要简单得多,因为我们将使用相同的函数来呈现一个符号,而不是呈现一个图块。第一种解决方案更灵活,因为渲染器可以缓存任何内容。

其他功能

void (*transformShape)(shapeObj *shape, rectObj extend, double cellsize);

一些渲染器可能需要这些函数。待定

void (*startNewLayer)(imageObj *img, double opacity);
void (*closeNewLayer)(imageObj *img, double opacity);

清理功能

void (*freeImage)(imageObj *image);
void (*freeSymbol)(symbolObj *symbol);
void (*freeShape)(shapeObj *shape);

备注

这里添加了freesymbol和freeshape函数,因为我们将向symbolobj和shapeobj对象添加一个void*指针,渲染器可以在其中存储包含特定于实现的对象表示形式的私有缓存。

渲染API的高级使用

形象塑造

没什么特别的。只需传递给渲染器。

图像保存

取决于渲染器是否可以导出栅格缓冲区:

  • 支持:获取栅格缓冲区,最终应用格式选项(量化),并将缓冲区传递给MapServer的保存函数

  • 不支持:调用渲染器的保存函数

栅格层

MapServer的栅格层处理将被修改,以便它可以写入RasterBufferObj,而不仅仅写入gdImage。

备注

todo(frankw)在此处添加有关此处所引发的更改的一些详细信息

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);
}

符号缓存和标记呈现

类似于实际mapgd.c MapCache 的符号缓存将在mapimagecache.c中实现。

备注

正如前面指出的,还没有决定缓存是否将存储普通的ImageOJB,或者我们是否允许渲染器存储特定的结构。这也可以是一个配置选项,每个渲染器指定它是否可以将自己创建的imageobj作为缓存,或者是否需要一个void*缓存。这将使大多数渲染器的代码保持简单,同时为其他渲染器提供更多的灵活性(OpenGL渲染器可能需要这样做)。

每个渲染器都可以通过支持图像缓存配置选项来配置图像缓存的使用,因为根据格式,它的使用不一定有用。

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:
            /* ... */
    }
}

线条渲染

  • 简单笔画:直接调用渲染器

  • 标记符号:

    • 沿几何体计算位置和方向

    • 为每个位置使用渲染器的标记函数渲染标记符号。如果符号必须遵循行方向,那么使用图块缓存可能不是一个好主意,因为旋转缓存的图块会产生糟糕的结果。

  • 模式:

    • 创建图案的平铺(使用渲染器的标记函数)

    • 将图块传递给渲染器

多边形渲染

  • 简单填充

  • 模式填充:

    • 创建图案平铺

    • 最终缓存该图块以供将来在其他几何图形上使用

    • 将图块传递给渲染器

  • 填充图案填充:计算与图案填充对应的线条-使用渲染器的简单笔划功能

文本渲染

除了基本的初步测试(如果定义了字体,或者要呈现的字符串为空)和TrueType字体文件的系统路径的查找将由高级代码在调用呈现器绘图函数之前完成一次之外,这里没有特别要添加的内容。

备注

此呈现API将不支持栅格字体(至少在第一阶段)。仅实现TrueType字体。

图像输入输出

目前所有的图像I/O都是通过gd库完成的。为了提高这种依赖性,RFC建议让MapServer通过直接连接libpng/libjpeg/giflib库来实现图像加载和保存。相应的代码将添加到新文件“mapimageio.c”,并生成或读取rasterbufferobj。

这些函数将实现当前支持的格式选项。添加新的格式选项将通过向这些I/O函数中添加代码来完成,从而添加对使用此呈现API架构的所有呈现器的支持:

  • JPEG:

    • 质量:0-100

  • PNG:

    • 交错:默认情况下将设置为关闭,因为创建成本更高,生成的图像更重,并且与TileCache的元平铺功能不兼容。

    • 压缩:可以添加以选择zlib压缩的质量

    • 量化:从RGB或RGB图像模式生成基于调色板的PNG。

    • 调色板:根据给定的预计算调色板生成基于调色板的PNG。进一步的增强可能包括通过循环输入映射文件自动生成调色板,以提取颜色并确保它们最终出现在最终图像中(这只支持RGB,而不是RGBA)。

  • TBD

备注

此RFC的初始实现将不包括对写入GIF图像的支持。

备注

此RFC的初始实现不包括通过直接使用libjpeg/libpng/giflib库来加载图像的功能。相反,它将回退到gd加载,并将创建的gdimage转换为rasterbuffer。这可以在第二阶段中添加,因为它的优先级较低,并且不会引起重大的体系结构更改。

其他

在矢量渲染器中将一些图层绘制为栅格

当前的PDF(和SVG?)渲染器可以选择切换到某些图层的gd,然后将其作为栅格图像合并到最终地图中,而不是按常规方式添加每个特征。

此功能可能与此API和mergerasterbuffer函数一起保留。

备注

使用此功能的渲染器必须小心,因为将要传递的RasterBuffer将采用委托渲染器的本机格式,并且可能与调用initializerasterBuffer创建的格式不同。

传说

图例呈现代码将必须重构,因为它当前生成一个gdimage。图例渲染将像普通的ImageObj一样处理。

备注

TODO:“嵌入”模式有问题。

如果启用了PostLabelCache,则可以使用renderTile函数(对于支持它的渲染器)将创建的ImageObj轻松添加到主图像中。其他的可能没有嵌入的图例支持)

如果postlabelcache关闭,则会出现问题,因为创建的图例将作为pixmap符号传递给主地图渲染。这与矢量渲染器不兼容,因为它们创建的ImageObj不是像素数组。

标尺

TBD

参考图

TBD。我们还应该支持这个吗?

受影响的文件

  • maprendering.c,mapimageio.c,mapcairo.c(已添加)

  • mapfile.c(默认值,关键字从symbolobj移动到styleobj)

  • mapoutput.c(renderobj创建)

  • mapdraw.c(msdrawxxx函数移到maprendering.c)

  • mapserver.h

  • …更多

文档

除了迁移指南外,不需要任何最终文档。

MapScript

TBD

向后不兼容

符号学应该在渲染器之间表现得更好,但确实意味着在某些符号的渲染方式上会有一些向后不兼容的变化(尽管这可能被视为修复错误而不是向后不兼容)

  • 矢量符号的反向旋转方向。当前,矢量符号以与其他符号相反的方向旋转。

  • 对于线条几何图形,给定的图案(虚线…)将按线条的实际宽度缩放。因此,无论线的宽度如何,虚线都将被定义为图案1 1的末端。

  • 还会来吗?

评审期意见

投票历史