本文上接《用AGG实现高质量图形输出(一)》,分别介绍了AGG显示流程中的各个环节。
上次讲了AGG的显示原理并举了一个简单的例子,这一篇文章开始讲AGG工作流程里的每个环节。为了方便对照,再放一次AGG显示流程 图
另外,上一篇文章里的例程也很重要,后面的例子都将基于这个代码。
下面,我们来考察AGG显示流程中的每个环节。理解每个环节最好的方法是编写实验代码,建议先参照这里建 立一个可以运行的AGG实验环境。
顶点源(Vertex Source)
顶点源是一种可以产生多边形所需要的“带命令的顶点”的对象。比如三角形顶点源,就应该会产生一个带“MoveTo”命令的点,另外二 个带"LineTo"命令的点和最终闭合的“ClosePoly”命令。
头文件
1 2 3 4 5 6 7 |
|
类型
自定义类 | 所有实现了void rewind(unsigned path_id);和unsigned vertex(double* x, double* y);的类。 |
ellipse | 圆,输入为中心点坐标和XY轴半径,本文所用的例子就 使用了这个顶点源 |
arc | 弧线,输入为中心点坐标和XY轴半径,以及起始和终止角(rad),顺时针/逆时针方向 |
curve3 | 贝塞尔曲线,输入为起点坐标、第一控制点坐标、终点点坐标 |
curve4 | 贝塞尔曲线,输入为起点坐标、第一控制点坐标、第二控制点坐标、终点坐标 |
gsv_text | 使用AGG自带字模的文字输出(只支持ASCII码),使用start_point方法指定文字位置,text方法指定 文字,flip指定是否上下倒转,size指定文字大小,适合与conv_stroke或gsv_text_outline配合。 |
gsv_text_outline<> | 可变换文字,输入为gsv_text和变换矩阵(默认为trans_affine,后文会提到)。width方法设置文 字宽度 |
rounded_rect |
圆角方形,输入为左上角右下角坐标和圆角半径 |
path_storage |
路径存储器,可以用join_path方法加入多个顶点源。而且path_storage本身支持move_to, line_to,curve和arc_to等画线功能 |
arrowhead | 箭头,它是作为标记点来用的 |
其中的arrowhead颇为特殊,它一般作为线段的标记点,具体用法是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
ah.head()和ah.tail()方法中的d1,d2,d3,d4参数的意义见下图
例,画一条简单的箭头直线(基于此处代码)
在on_draw()方法最后加上下列代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
得到的图形是:
注意要加头文件:
1 2 3 4 |
|
试验代码,自定义一个顶点源(基于此处代码)
为了对顶点源有更深入的了解,我们要自己实现一个顶点源,这个顶点源只是很简单的一个三角形。
前面说过,只要实现了 void rewind(unsigned path_id); 和 unsigned vertex(double* x, double* y); 方法就可以作为顶点源。
rewind方 法指示顶点源回到第一个顶点;vertex方 法取出当前顶点然后把当前顶点下移,返回值是当前顶点所带的命令。所谓的命令是一个enum path_commands_e类 型,定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
path_commands_e还能和path_flags_e组合:
1 2 3 4 5 6 7 8 |
|
vertex()返回的命令中含有path_cmd_stop时 表示结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
在on_draw()方法里把
agg::ellipse ell(100,100,50,50); 改成triangle ell(100,100,50);
typedef agg::conv_contour<agg::ellipse> ell_cc_type;改成typedef agg::conv_contour<triangle> ell_cc_type;
得到的图形是:
除了文字输出功能(gsv_text只能输出ASCII文字),上面这些顶点源提供的图形丰富程度已经超过了系统API。文字输出功能 将以单独的篇幅讲述。
Coordinate conversion pipeline 坐标转换管道
Coordinate conversion pipeline 坐标转换管道
坐标转换管道用于改变顶点源产生的顶点,包括坐标、命令、产生新顶点等。如对顶点进行矩阵变换、插入顶点形成虚线之类的功能。
变换矩阵(trans_affine)
在认识转换管道之前,先了解一下AGG的变换矩阵。通过顶点坐标与矩阵的运行,我们可以得到新的坐标。关于图像的矩阵运算,MSDN里 有一篇关 于GDI+矩阵运算的文章,很值得一看
头文件
#include <agg_trans_affine.h>
类型
trans_affine
成员变量
double sx, shy, shx, sy, tx, ty; |
这六个变量组成一个2*3的矩阵,与坐标计算后得到一个新的坐标。比如对点(x,y)进行变换,则新的点(x‘,y‘) 为: x‘ = x*sx + y*shx + tx; y‘ = x*shy + y*sy + ty; |
成员方法
void transform(double* x, double* y) const; | 用上面的公式转换x,y坐标 |
const trans_affine& scale(double s); const trans_affine& scale(double x, double y); |
缩放 |
const trans_affine& rotate(double a); | 旋转,弧度单位(pi/180) |
const trans_affine& translate(double x, double y); | 平移 |
trans_affine operator * (const trans_affine& m); | 矩阵乘法 |
const trans_affine& invert(); | 取反矩阵 |
坐标转换管道中有个叫conv_transform的 转换器,它能利用矩阵对源顶点进行变换,我们先在这里玩玩吧^_^
实验代码(基于此处代码)
加入头文件 #include "agg_conv_transform.h"
把on_draw()方法的里从“// Vertex Source”到“// Scanline Rasterizer”之间的代码改写成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
得到的图形是:
注:trans_affine不 仅仅用于源顶点的变换,在AGG库中有不少地方都能看到它。比如后面会讲到的线段(span)生成器,通过变换矩阵,就能够 自由变换填充于多边形之内的图案。
坐标转换管道
头文件
1 2 3 4 5 6 7 8 |
|
类型(演示程序基于基于此处代码)
template<class VertexSource, class Markers = null_markers> struct conv_stroke; |
变成连续线 构造参数为VertexSource width属性决定线宽。 |
在例程的ell_cc_cs_type csccell(ccell); 后面加上csccell.width(3);线宽就会变成3。 |
template<class VertexSource, class Markers = null_markers> struct conv_dash; |
虚线 构造参数为VertexSource 用add_dash设置虚线长度和间隔 与conv_stroke套用 |
// Coordinate conversion pipeline typedef agg::conv_contour<agg::ellipse> ell_cc_type; ell_cc_type ccell(ell); typedef agg::conv_dash<ell_cc_type> ell_cd_type; ell_cd_type cdccell(ccell); cdccell.add_dash(5,5); typedef agg::conv_stroke<ell_cd_type> ell_cc_cs_type; ell_cc_cs_type csccell(cdccell); ... |
template<class MarkerLocator, class MarkerShapes> class conv_marker; |
建立标记 |
请参考arrowhead示例代码 |
template<class VertexSource> struct conv_contour; |
轮廓变换 构造参数为VertexSource width属性决定扩展或收缩轮廓。 |
见例程代码 |
template<class VertexSource> struct conv_smooth_poly1_curve; |
圆滑过渡多边形各顶点(贝塞尔) 构造参数为VertexSource smooth_value属性决定圆滑度(默认为1) |
在例 程on_draw()方法最后加入下面代码 triangle t(100,100,50);//自定义顶点源 agg::conv_smooth_poly1_curve<triangle> cspct(t); ras.add_path(cspct); agg::render_scanlines_aa_solid( ras,sl,renb,agg::rgba8(255,0,0)); |
template<class VertexSource> struct conv_bspline; |
圆滑过渡多义线各顶点(贝塞尔) 构造参数为VertexSource interpolation_step属性决定步长。 |
在例 程on_draw()方法最后加入下面代码 triangle t(100,100,50); agg::conv_bspline<triangle> cspct(t); ras.add_path(cspct); agg::render_scanlines_aa_solid( ras,sl,renb,agg::rgba8(255,0,0)); |
template<class VertexSource, class Curve3 = curve3, class Curve4 = curve4> class conv_curve; |
可识别VertexSource中的曲线信息 构造参数为VertexSource。conv_smooth_poly1_curve 就是基于它实现的。 |
例程里的顶点都没有曲线信息,算了, 到后面讲到文字输出时会用到它的。 |
template<class VertexSource, class Transformer = trans_affine> class conv_transform; |
矩阵变换 用变换矩阵重新计算顶点位置 构造参数为VertexSource和变换矩阵 |
见变换矩阵一节的例子 |
Scanline Rasterizer
Scanline Rasterizer能够把顶点数据转换成一组水平扫描线,扫描线由一组线段(Span)组成,线段(Span)包含了起始位置、长度和覆盖率(可以理解 为透明度)信息。AGG的抗锯齿(Anti-Aliasing)功能也是在这时引入的。
扫描线Scanline
扫描线是一种保存span的容器,span用于表示一小条(水平方向)细线。图像中同一行的span组成一个Scanline。
头文件
1 2 3 |
|
类型
scanline_bin,scanline32_bin | 不携带AA信息的span容器。scanline32_bin中的32代表坐标位数,一般16位已经足够了,所以前一版 本用得更多些(下同) |
scanline_u8,scanline32_u8 | unpacked版的span容器,用每个span来保存各自的线段信息 |
scanline_p8,scanline32_p8 | packed版的span容器,相同属性的span会合并成一个 |
成员类型
struct span; | 线段数据,其中的成员变量有:x起始位置,len长度,*covers覆盖率 |
typename iterator,const_iterator; | span迭代器 |
typename cover_type; | span中covers类型(覆盖率) |
成员方法
iterator begin(); unsigned num_spans(); |
用于遍历span,begin()取得指向第一个span的迭代器 num_spans()取得容器中span的数目 |
void reset(int min_x, int max_x); |
设置容器大小 |
void add_span(int x, unsigned len, unsigned cover) |
加入一条线段 |
void add_cell(int x, unsigned cover) | 加入一个点 |
void add_cells(int x, unsigned len, const cover_type* covers) | 加入一组点 |
void finalize(int y) int y(); |
Scanline容器对应的Y坐标 |
Rasterizer
怎么翻译呢?光栅化?光栅制造机?嗯~~算了,还是直接叫它Rasterizer(雷死特拉倒)吧-_-!!!
Rasterizer就是把相当于矢量数据的一堆顶点和命令转换成一行行的扫描线的设备,它就象粉刷工人对照着图纸把彩漆刷到墙上一 样。可以说是AGG里最重要的类型之一,套用建翔兄的话就是:
立功了!立功了!不要给GDI任何的机会!伟大的AGG的Rasterizer类!他了继承开源社区的光荣传统!达芬奇、Linus、 唐寅,在这一刻灵魂附体!
Rasterizer是关键对象!他代表了AGG伟大的设计理念!在这一刻!他不是一个人的战斗!他不是一个人!面对着全世界人民的目 光和期待,他深知责任的重大,0.001秒种之后将会是什么样的图像?
头文件
#include <agg_rasterizer_scanline_aa.h>
类型
1 2 |
|
成员方法
template<class GammaF> void gamma(const GammaF& gamma_function); |
设置gamma值。 GammaF为一种仿函数 AGG自带有gamma_power、gamma_none、gamma_threshold、 gamma_linear、gamma_multiply |
bool rewind_scanlines(); |
跳到第一个scanline位置,同时设置sorted为true。 这时再加入其它顶点会先清空现有顶点 |
bool navigate_scanline(int y); | 跳到y行 |
bool sweep_scanline(Scanline&); | 把当前行画入Scanline,当下移一行 |
void reset(); | 清空 |
void move_to(int x, int y); void line_to(int x, int y); |
简单的画线功能,单位为1/poly_subpixel_scale (poly_subpixel_scale一般为256) |
void move_to_d(double x, double y); void line_to_d(double x, double y); |
简单的画线功能,单位为像素 |
void add_path(VertexSource& vs, unsigned path_id=0) | 加入顶点 |
Renderers 渲染器
渲染器负责表现扫描线Scanline中的每个线段(span)。在渲染器之前,AGG图形中的线段是没有颜色值的,只是位置、长度和 覆盖率(透明度)。渲染器赋于线段色彩,最终成为一幅完整的图像。
渲 染器被分成底中高三层。其中底层负责像素包装,由PixelFormat Renderer实现;中层是基础层,在PixelFormat
Renderer的基础上提供更多方法,是所有高层渲染器依赖的基础,由Base
Renderer实现;高层负责渲染Scanline中的线段,由Scanline Renderer等实现。
Scanline Renderer
头文件
#include <agg_renderer_scanline.h>
类型
1 2 3 4 5 6 |
|
以及自己写的实现了void prepare() 和 template<class Scanline> void render(const Scanline& sl) 方法的类
另外,头文件agg_renderer_scanline.h中 的render_scanlines函 数很重要,它是AGG显示流程的实现。
1 |
|
从Rasterizer生成逐行的Scanline,然后交给Scanline Renderer渲染。
这 里还要提一下render_scanlines_aa_solid、render_scanlines_aa、 render_scanlines_bin_solid、render_scanlines_bin这 几个函数。它们的作用和 render_scanlines一 样,只是跳过了Scanline Renderer环节,直接向Base Renderer渲染。
1 2 3 4 5 |
|
实验代码(基于此 处代码)
把on_draw()方法里原
1 |
|
改成
1 |
|
得到的图形是:
去掉renderer_scanline_type以及所有的rensl相关语句,把
1 |
|
改成
1 |
|
同样可以得到我们想要的图形
Basic Renderers
头文件
#include <agg_renderer_base.h> #include <agg_renderer_mclip.h>
类型
1 2 3 |
|
构造函数
renderer_base(pixfmt_type& ren); |
参数ren指定底层的PixelFormat Renderer |
成员方法
pixfmt_type& ren(); | 返回底层的PixelFormat Renderer |
unsigned width() const; unsigned height() const; |
宽高 |
void reset_clipping(bool visibility); |
设置是否可见 clipping box=visibility?(0,0,width-1,height-1):(1,1,0,0) |
bool clip_box(int x1, int y1, int x2, int y2); | 设置clipping box,renderer_base专有 |
void add_clip_box(int x1, int y1, int x2, int y2); | 添加clipping box,renderer_mclip专有 |
bool inbox(int x, int y) const; | x,y点是否在clipping box内,renderer_base专有 |
void first_clip_box(); bool next_clip_box(); |
切换clipping box,renderer_mclip专用 |
const rect& clip_box() const; int xmin() const; int ymin() const; int xmax() const; int ymax() const; const rect& bounding_clip_box() const; int bounding_xmin() const; int bounding_ymin() const; int bounding_xmax() const; int bounding_ymax() const; |
返回clipping box大小 |
void clear(const color_type& c); | 以颜色c填充所有区域 |
void copy_pixel(int x, int y, const color_type& c); void blend_pixel(int x, int y, const color_type& c, cover_type cover); color_type pixel(int x, int y) const; void copy_h(v)line(int x1, int y, int x2, const color_type& c); void blend_h(v)line(int x1, int y, int x2, const color_type& c, cover_type cover); void blend_solid_h(v)span(int x, int y, int len, const color_type& c, const cover_type* covers); void blend_color_h(v)span(_no_slip)(int x, int y, int len, const color_type* colors, const cover_type* covers); |
见后文的PixelFormat Renderer |
void copy_from(const rendering_buffer& from, const rect* rc=0, int x_to=0, int y_to=0); |
从from复制一个矩形区域过来,rc指定源区域,x_to,y_to指定目标位置 |
实验代码(基于此处代码)
在on_draw()方法的renb.clear(agg::rgba8(255,255,255));语句后面加上:
1 2 |
|
得到的图形是:
PixelFormat Renderer
PixelFormat Renderer的作用是以指定的颜色空间来包装原始的Rendering Buffer(见后文),AGG把它归类于底层Renderer。
Rendering Buffer是以字节为单位的,而PixelFormat Renderer则是以像素为单位的。
头文件
#include "agg_pixfmt_rgb.h #include "agg_pixfmt_gray.h"
类型
1 2 3 4 5 6 |
|
构造函数
pixfmt_base(rbuf_type& rb);
rb参数为Rendering Buffer类型
类型定义
typedef color_type; |
像素类型 需要了解的是在AGG中像素也是一个功能完善的类,常用的有rgba、rgba8、gray8。 rgba里每个颜色分量用double表示,范围从0~1。其它像素类后面的数字代表每个颜色分量占用的位数。大部分像素类都可以从rgba构造。 同时, 像素类还有gradient等牛X的颜色计算方法。 |
typedef value_type; | 单个颜色分量的类型 |
typedef order_type; |
颜色排序方式,我们可以通过里面的枚举值R G B A得到各颜色分量所在位置,常用的有order_rgb,order_bgr,order_rgba。 这是order_rgb的定义: struct order_rgb { enum rgb_e { R=0, G=1, B=2, rgb_tag }; }; |
成员方法
unsigned width() unsigned height() |
宽高 |
color_type pixel(int x, int y); void copy_pixel(int x, int y, const color_type& c); |
取得、设置指定点的颜色 |
void blend_pixel(int x, int y, const color_type& c, int8u cover); | 设置指定点颜色,与原颜色有混合效果,强度由cover指定 |
void copy_hline(int x, int y, unsigned len, const color_type& c); void copy_vline(int x, int y, unsigned len, const color_type& c); |
从x,y开始画一条长度为len的线,颜色为c,同样有blend_版本 |
void blend_solid_h(v)span(int x, int y, unsigned len, const color_type& c, const int8u* covers); void blend_color_h(v)span(int x, int y, unsigned len, const color_type* colors, const int8u* covers); |
类似hline和vline版本,color版指定一组颜色,依次着色。covers指定覆盖率 |
实验代码(基于此处代码)
在on_draw()方法的最后加上:
1 2 3 |
|
得到的图形是:
Rendering Buffer
Rendering
Buffer是一个内存块,用于保存图像数据。这是AGG与显示器之间的桥梁,我们要显示AGG图形实际上就是识别这个内存块并使用系统的API显示出来
而已(实际上几乎不需要做转换工作,因为无论是Windows还是Linux,API所用的图像存储格式与Rendering
Buffer都是兼容的)。
头文件:
#include "agg_rendering_buffer.h"
类型:
rendering_buffer
构造函数:
rendering_buffer(int8u* buf, unsigned width, unsigned height, int stride);
参数分别表示内存块指针,宽、高、每行的步幅(当步幅<0时,表示上下颠倒)
成员方法:
void attach(int8u* buf, unsigned width, unsigned height, int stride); | 参数与构造函数相同 |
int8u* buf(); | 返回内存块指针 |
unsigned width() const; unsigned height() const; int stride() const; unsigned stride_abs() const; |
返回宽、高、每行步幅 |
int8u* row_ptr(int y) | 返回指向第y行起点的指针 |
void clear(int8u value) | 以value值填充整个内存块 |
template<class RenBuf> void copy_from(const RenBuf& src) | 从另一rendering_buffer中复制数据 |
实验代码(基于此处代码)
在on_draw()方法的最后加上:
1 2 |
|
得到的图形是:
AGG与GDI显示
Rendering Buffer的图像存储方式和Windows的BMP是一样的,所以让AGG处理BMP是很简单的事情,下面的代码演示了怎样在HDC上显示AGG
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
|
得到的图形是:
使用AGG提供的pixel_map类
如果你觉得上面的方法还是有点烦的话(这个要怪MS的API太麻烦),可以考虑用AGG友情提供的pixel_map类,用它操作 BMP方便多了。(要把[AGG]\src\platform\win32\agg_win32_bmp.cpp加入一起编译)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
(转)用AGG实现高质量图形输出(二)