河南洛阳 祝小鹰 2015-07-07
接触AGG一年多,积累了一些心得体会。现在贴出来与大家分享。
注1:学习笔记按记录时间的逆序排列。
注2:当初学习AGG的目的是为了绘制SVG图形文件,因此夹杂了一些SVG的内容。
注3:早期的笔记由于刚接触AGG,可能存在一些错误。
例程和库文件下载:http://pan.baidu.com/s/1o6OhqMQ
-----------------------------------------------------------------------
关于SVG中<linearGradinet>、<radialGradient>的属性xlink:href的补充说明:
- 只有在不包含<stop>子元素的情况下,才“继承”引用元素的<stop>子元素。
- 如果引用的url不存在,则忽略xlink:href属性。
- 允许间接引用,例如lg3引用lg2,而lg2引用lg1。
- <linearGradinet>可引用<radialGradient>,反之亦可。
-----------------------------------------------------------------------
AGG的marker locator(vcgen_markers_term)在处理分段线段时,每一个子线段都会生成一组起点和终点的定位信息。也就是说,每一个子线段的起点和终点都将绘制标记点。但是SVG仅在第一个起点和最后一个终点处绘制标记点。
因此为svg_viewer编写了一个类模板svg_markers_term,用于将AGG的marker locator转换为SVG规范。转换后,只保留第一个起点和最后一个终点的定位信息。
-----------------------------------------------------------------------
SVG中,图元的transform属性对渐变色绘制的影响:
- SVG先按照图元的坐标数据进行渐变色渲染,然后渐变色随同图元一起进行坐标变换(应用图元的transform属性)。这意味着在AGG中实现时,插值器矩阵需要叠加图元的transform属性。
- 对于objectBoundingBox模式,其参考坐标系是图元应用transform属性之前的包围矩形。
-----------------------------------------------------------------------
在SVG中:不论是绘制填充区域,还是绘制轮廓线,对于objectBoundingBox模式下的渐变色渲染,其参考坐标系均为填充区域的包围矩形。
-----------------------------------------------------------------------
使用AGG实现objectBoundingBox模式的渐变色绘制的步骤:
1 以单位矩形为基准,设计渐变方式和参考值区间。
2 对插值器矩阵应用gradientTransform中的变换(注意按逆序),此时得到的渐变区域仍然基于单位矩形。
3 通过缩放(w,h)和平移(x0,y0),将渐变区域变换至待填充图元的包围矩形。(x0,y0,w,h)是包围矩形在绝对坐标系中的几何参数。
上述方法还存在一个问题。首先分析一下像素颜色的计算过程:
1 待填充的像素通过插值器矩阵(上述变换的逆矩阵),变换至单位矩形。
2 计算参考值,并将参考值映射至颜色区间,从而得到像素颜色。在此过程中,已变换至单位矩形的点坐标和参考值区间[d1,d2]均换算成亚像素坐标(其实就是乘以16然后取整)参与计算。
由于单位矩形的边长换算成亚像素只有16,而参考值区间又是以单位矩形为基准选取的,因此参考值区间对应的亚像素数目有限,导致映射至颜色区间后,颜色看上去不连续。
解决办法:以边长w的正方形为基准,设计渐变方式和参考值区间,gradientTransform属性中的相对长度也乘以w,然后缩放系数改为(1,h/w)。
但是在绘制SVG图元前,gradientTransform属性已经被解析为一个矩阵,如何实现“相对长度乘以w”?参考《计算机图形学 第三版》P201 公式5.42,将矩阵的平移项(第3列前两个元素)乘以w即可。
-----------------------------------------------------------------------
SVG中,objectBoundingBox模式的渐变色绘制的实现原理:
在objectBoundingBox模式下,<linearGradient>和<radialGradient>的属性参数所设定的渐变区域,是以单位矩形(边长为1)为基准,然后通过缩放(w,h)和平移(x0,y0),变换至待填充图元的包围矩形。(x0,y0,w,h)是包围矩形在绝对坐标系中的几何参数。
-----------------------------------------------------------------------
SVG中,<linearGradient>使用向量标识渐变方向。用AGG实现时,通常使用水平渐变gradient_x,以原点为渐变起始点,参考值区间为[0,向量长度],然后通过插值器矩阵的旋转、平移,变换至渐变向量的位置。
要考虑向量长度近似为0的情况:如果向量长度 < 0.5,则按实色填充,填充色取第一个渐变色。
-----------------------------------------------------------------------
关于SVG中线性渐变元素<linearGradient>的描述:
属性:
id 元素标识
x1, y1 渐变矢量的起点,缺省值“0, 0”
x2, y2 渐变矢量的终点,缺省值“100%, 0”
gradientUnits x1 y1 x2 y2的坐标系,缺省值为“objectBoundingBox”
- userSpaceOnUse 用户坐标系(与SVG图元使用同一个坐标系)
- objectBoundingBox 以对象的包围矩形为参考坐标系
spreadMethod 重复方式,默认值为“pad”
- pad 不重复
- repeat 对应agg::gradient_repeat_adaptor
- reflect 对应agg::gradient_reflect_adaptor
xlink:href 引用另外一个已定义好的渐变元素
gradientTransform 变换矩阵,缺省值为单位阵
与图元的transform属性类似,也是按逆序应用变换(绝对坐标系)
用于AGG中的插值器时,矩阵还要求逆
gradientUnits对gradientTransform有影响
- userSpaceOnUse 坐标变换使用用户坐标系(与SVG图元使用同一个坐标系)
在此情况下,不需要对坐标值和变换矩阵进行额外处理
- objectBoundingBox 坐标变换以对象的包围矩形为参考坐标系
* translate(x,y)中,参数x,y是相对长度
* rotate(angle,cx,cy)中,cx,cy是相对长度
* 注意:这里的相对长度不支持百分比,只能用小数
如果x1 y1 x2 y2 gradientUnits均使用缺省值,填充效果如何?
经测试,等效于 (x1,y1) = (0,0) (x2,y2) = (1,0)
如果x1 y1 x2 y2使用缺省值,gradientUnits=userSpaceOnUse,填充效果如何?
经测试,等效于 (x1,y1) = (0,0) (x2,y2) = (100%,0)
注意:100%是相对于SVG绘图区域的长度
坐标x1 y1 x2 y2中的“%”与属性gradientUnits的关系:
- gradientUnits="userSpaceOnUse"
60% 和 0.6 不等价,前者是相对SVG绘图区域的长度,后者就是0.6px
- gradientUnits="objectBoundingBox"
60% 和 0.6 等价,二者是相对待填充图元的长度
在userSpaceOnUse方式下,相对长度的换算比较麻烦,有可能牵扯到SVG容器窗口。因此为了简化程序设计,不论哪种方式,svg_viewer均将百分比换算成小数,即60% = 0.6。设计SVG时,避免在userSpaceOnUse方式下使用“%”。
-----------------------------------------------------------------------
agg::platform_support内置了一组图形缓存区(16个,编号0-15),可通过下列函数访问:
load_img
save_img
create_img
rbuf_img
copy_img_to_window
copy_window_to_img
copy_img_to_img
注:load_img/save_img只支持.bmp(Windows系统)或.ppm(Linux系统),且文件名不能带后缀。
-----------------------------------------------------------------------
在SVG中,长度单位“%”表明该数值为相对长度:
- 如果“%”用于<svg>元素的width、height属性,其参考基准为SVG容器窗口
- 如果“%”用于图元,其参考基准为SVG的绘图区域(即svg>元素的width、height)
从上可以看出,如果<svg>元素的width/height和图元都使用了“%”,图元将间接依赖于容器窗口的实际尺寸。
解析SVG时,需要依据容器窗口的实际尺寸,将相对长度换算成像素坐标。一旦容器窗口发生变化,需要重新解析
换算。
由于实现起来比较复杂,因此svg_viewer暂不实现“%”的解析。
-----------------------------------------------------------------------
目前svg_viewer只实现了将长度单位pt in cm mm 换算至px,而且仅限于如下属性:
- <svg>元素的width、height
- 图元的stroke-width
-----------------------------------------------------------------------
《用AGG实现高质量图形输出》16.3章节中,关于图像滤镜(Image Filter)的例程存在bug,会导致程序崩溃。
存在bug的代码:
span_gen_type span_gen(accessor, ip, agg::image_filter_sinc36());
运行过程:
1) 构造一个image_filter_sinc36临时对象
2) span_gen_type构造函数中,参数3的类型为const image_filter_lut&,因此会用image_filter_sinc36临时对象,通过转换构造函数构造一个image_filter_lut临时对象
3) 构造对象span_gen,image_filter_lut临时对象的地址保存在基类span_image_filter的成员变量m_filter
4) 该行代码运行完毕后,销毁临时对象。也就是说,后续代码访问m_filter时,该指针指向的对象已销毁,从而导致程序出错。
正确的代码如下:
agg::image_filter_sinc36 filter_sinc36;
agg::image_filter_lut filter( filter_sinc36 );
span_generator_type span_gen( accessor, ip, filter );
或
agg::image_filter_lut filter;
filter.calculate( agg::image_filter_sinc36() );
span_generator_type span_gen( accessor, ip, filter );
-----------------------------------------------------------------------
通过查看span_converter的源代码,发现所有的线段生成器中,只有span_gradient_alpha可以和别的线段生成器进行组合,而且span_gradient_alpha必须作为span_converter的第二个模板参数。
原因在于span_gradient_alpha只修改线段(span)的alpha值,其余的线段生成器则修改线段的rgba值。
-----------------------------------------------------------------------
通过查看gradient_repeat_adaptor和gradient_reflect_adaptor的源代码,发现它们以区间[0,d2]为重复周期,不是[d1,d2]。设计渐变方式时要注意这一点。
例如下列代码:
// 渐变方式
typedef agg::gradient_repeat_adaptor<agg::gradient_x> gradientF_type;
agg::gradient_x grF_basic;
gradientF_type grF(grF_basic);
… …
// 线段生成器
typedef agg::span_gradient<… …> span_generator_type;
span_generator_type span_gen( ip, grF, colorF, 50, 150 );
其重复区间并不是[50,150],而是[0,150]。
如果希望以[50,150]为重复区间,应该令d1=0,d2=150-50,然后对插值器矩阵应用平移translate(50,0),将渐变区域变换至[50,150]。
该方法仅适用于线性渐变。对于放射状渐变,参考值的几何意义是距放射中心的距离,区间[d1,d2]对应环形渐变区域,区间[0,d2-d1]对应圆形渐变区域,二者的形状都不一致,显然无法通过常规的矩阵变换,将后者变换至前者。
终极解决办法是参照gradient_repeat_adaptor和gradient_reflect_adaptor,另外编写一组新类,以区间
[d1,d2]为重复周期计算参考值(d2已通过calculate函数的第3个参数传递给对象实例,d1可以通过构造函数传递)。
注:暂不编写新类,原因分析如下:
- SVG中的放射状渐变,其渐变起点总是从0开始,即d1=0。
- SVG中的线性渐变使用向量(两个点坐标)标识渐变方向。用AGG实现时,通常使用水平渐变gradient_x,以原点为渐变起始点,参考值区间为[0,向量长度],然后通过插值器矩阵的旋转、平移,变换至渐变向量的位置。
从以上分析可以看出,无论哪种渐变,用AGG实现时,d1均为0。因此暂时用不着编写新类。
-----------------------------------------------------------------------
在图案/渐变色填充过程中,插值器的矩阵负责将目标位置(待填充的区域)变换至源位置(源图像)。但是从源图像变换至填充区域更符合人们的思维习惯。因此在实际应用中,通常先按源图像变换至填充区域给出变换矩阵,然后对该矩阵求逆,得到插值器的变换矩阵。
-----------------------------------------------------------------------
AGG中渐变色填充的实现机理:
- 类agg::gradient_xxxx的calculate函数负责将顶点坐标换算成一个参考值,通常是某种长度。
- 类agg::span_gradient构造函数的参数d1、d2指定了一个参考值区间,通过线性变换,将calculate返回的参考值映射至colorF_type指定的颜色区间。从而得到顶点颜色。
- 参考值的计算方式决定了颜色的渐变方式。
注意事项:
- 调用calculate函数时,顶点坐标已经转换成亚像素,即乘以16。同样参考值区间[d1,d2]也已乘以16。
- 如果calculate计算的是某种长度,计算结果的亚像素系数和参考值区间的亚像素系数相互抵消,因此设计渐变方式时,可以忽略亚像素,直接用顶点坐标、d1、d2的原始值进行计算。
- 如果是类似这样的计算公式:pow(x*x + y*y, 0.25),亚像素系数无法抵消,计算时必须考虑该系数。
- 设计calculate函数时,不用考虑实际的渐变(绘制)区域,为了简化计算,通常以原点为基准进行设计。设计完成后,通过插值器的变换矩阵将设计好的渐变区域移至实际的渐变(填充)区域。
-----------------------------------------------------------------------
虽然AGG提供了标志位path_flags_ccw、path_flags_cw用于设置顶点源的旋转方向,但实际上只有顶点转换管线conv_contour用到了这些标志位。其余情况下,例如区域填充,AGG通过计算获得顶点源的实际旋转方向。
-----------------------------------------------------------------------
svg_viewer使用ANSI版Expat解析SVG文件,文件数据是分段读取的,默认每次读取512字节。如果恰好读取至如下代码中,元素<tspan>的文本内部,会触发两次content函数调用:
<tspan>你好世界</tspan>
Expat会处理好文字的编码问题,不会出现一个汉字的UTF-8编码被切分开的情况。
-----------------------------------------------------------------------
[bug3] 绘制光滑贝塞尔曲线的path_base::curve3、curve4(参数个数为2、4)存在bug:
在计算第一个控制点的坐标时,只判断了倒数第2个顶点是否为曲线顶点,没有判断倒数第1个顶点是否为曲线顶点。
如果倒数第1个顶点类型是move_to或line_to,倒数第2个顶点是一个贝塞尔曲线的终点,AGG就会计算该终点相对倒数第1个顶点的对称点,作为当前曲线的第一个控制点,导致错误。
正确的做法是只有在最后两个顶点都是曲线顶点的情况下,才通过对称点方式计算控制点坐标。
注:该bug已修正。
-----------------------------------------------------------------------
[bug2] 函数path_base::rel_to_abs将相对坐标转成绝对坐标存在bug,如果path_base::m_vertices最后一个顶点是close类型,坐标值将保持不变。这么处理是错误的,应该以上一个move_to顶点的坐标为基准进行计算。
注:该bug已修正。
-----------------------------------------------------------------------
svg_viewer使用ANSI版Expat解析SVG文件,字符串均被转成UTF-8格式。对于纯英文,可直接按ANSI字符串处理。如果包含中文,需要进行相应转换。
-----------------------------------------------------------------------
AGG官方例程examples\svg_viewer在解析SVG文件后,调用path_renderer::arrange_orientations,将所有图元的顶点顺序调整为逆时针(相关代码位于svg_test.cpp)。调整原因暂时还不清楚。
svg_viewer没有解析图元属性fill-rule,填充时均使用nonzero。对于由顶点顺序相反的两个多边形构成的环形区域,调整为同方向后,其内部空白将被填充。
解决办法:如果遇到这种情况,不要调用path_renderer::arrange_orientations,维持顶点顺序不变。同时注意观察是否会导致图元绘制出现问题。
-----------------------------------------------------------------------
SVG图元<path>的绘图指令如果出现多次连续重复的情况,可以只保留第一个命令字,后面的省略(参见《基于XML的SVG应用指南》P45)。
注意:如果第一个命令字是“M/m”,后面省略的将被认为是“L/l”。
-----------------------------------------------------------------------
[bug1] AGG中,用虚线(conv_dash)绘制封闭轮廓线存在一个bug:当起始部分和结束部分都是实线时,二者没有连接起来。而FireFox、Sketsa SVG Editor等软件在这种情况下都进行了连接处理。
例如:
<path style="stroke:black;stroke-width:10;fill:none;stroke-dasharray:60 20"
d="M 200,50 L 300,50 300,150 z" />
注:该bug已修正。
-----------------------------------------------------------------------
path_storage::join_path和concat_path的区别:
- join_path
添加过程中,将move_to类型的顶点改为line_to。也就是说,如果新加入的顶点源包含若干分离的部分,都会被连接起来
- concat_path
不修改顶点,原样加入
-----------------------------------------------------------------------
agg::ellipse的顶点顺序(窗口坐标系):
- 从右侧开始,按圆心角递增方向绘制,在窗口坐标系下为顺时针
- 最后的path_cmd = path_cmd_end_poly | path_flags_ccw | path_flags_close
agg::rounded_rect的顶点顺序(窗口坐标系):
- 左上角圆弧 -> 上边 -> 右上角圆弧 -> 右边 ->右下角圆弧 -> 下边 -> 左下角圆弧 -> 左边
- 每段圆弧均按圆心角递增方向绘制,其实可以将整个矩形看做按中心角递增方向绘制,在窗口坐标系下为顺时针
- 最后的path_cmd = path_cmd_end_poly | path_flags_ccw | path_flags_close
-----------------------------------------------------------------------
函数path_storage::arc_to对应SVG图元<path>的命令“A”,其参数sweep_flag表示圆弧的绘制方向:
- true 按圆心角递增的方向绘制
- false 按圆心角递减的方向绘制
《基于XML的SVG应用指南》P51对该参数的描述是“1代表从起点到终点的弧线绕椭圆中心是顺时针方向,0是逆时针方向”。该说法不准确,事实上,这种情况仅限于窗口坐标系(即Y轴正方向朝下)。
-----------------------------------------------------------------------
我现在明白了为什么AGG自带的SVG解析工具svg_viewer不解析<circle>、<ellipse>等元素:与<path>不同,这些元素在解析过程中直接生成模拟曲线的折线顶点数据,顶点数目已经固定,因此缩放后有可能变得不光滑。
解决办法:创建<circle>、<ellipse>、<rect>(圆角矩形)等图元时,不要使用agg::ellipse、rounded_rect,而是用agg::path_storage,调用其成员函数arc_to生成曲线。这样得到的将是保留了曲线信息的顶点,而不是模拟曲线的折线顶点。
-----------------------------------------------------------------------
下列代码中,设置approximation_scale没有起到预期的作用:
agg::curve3 cur3( 0, 0, 20, 30, 30, 0 );
cur3.approximation_scale(...);
cur3.angle_tolerance( 0.0 );
... ... // 对cur3进行缩放
原因在于:AGG在构造curve3对象的同时,已经完成了模拟曲线的折线顶点数据的生成(参见函数curve3_div::init)。而对于conv_curve<path_storage>,模拟曲线的折线顶点数据一直推迟到直接或间接调用vertex时才生成(最常见的间接调用就是rasterizer_scanline_aa::add_path),只要在这之前设置approximation_scale即可。
解决办法:使用缺省构造器构造curve3对象,然后先设置approximation_scale,再调用init生成顶点数据。代码如下:
agg::curve3 cur3;
cur3.approximation_scale(...);
cur3.angle_tolerance( 0.0 );
cur3.init( 0, 0, 20, 30, 30, 0 );
... ... // 对cur3进行缩放
-----------------------------------------------------------------------
SVG按尖朝右的方式定义终点的标记点,与AGG相反。因此使用AGG绘制时,还需要旋转180度。
-----------------------------------------------------------------------
定义标记点(marker)的注意事项:
- 标记点使用局部坐标系,坐标原点位于线段的端点
- 起点的标记点:按线段从左至右进行定义(左端点为起点)
终点的标记点:按线段从右至左进行定义(左端点为终点)
简单的说,就是起点和终点的标记点,均按尖朝左的方式进行定义
(参见例程agg_demo07、agg_demo08)
-----------------------------------------------------------------------
构造conv_marker对象时,参数2是一个顶点源,包含了所有标记点的形状。AGG调用该顶点源的rewind函数时,使用参数path_id区分标记点(目前已知的取值:0=起点、1=终点)。
-----------------------------------------------------------------------
SVG的标记点(marker)由填充区域和轮廓线组成,使用AGG绘制标记点的步骤:
- 定义标记点的顶点源(即填充区域)
- 使用conv_stroke生成轮廓线
- 使用conv_transform对填充区域和轮廓线进行坐标变换
变换矩阵 = 标记点的transform属性 -> scale(目标线段的宽度)
注:目标线段指的是需要绘制标记点的线段,标记点大小和目标线段的宽度成正比
- 使用conv_marker对填充区域进行变换,conv_marker将根据目标线段端点的位置和方向,对填充区域进行平移和旋转
- 绘制变换后的填充区域
- 使用conv_marker对轮廓线进行变换并绘制
上述方法先生成标记点的填充区域和轮廓线,然后用conv_marker分别转换并绘制,也可以不用conv_marker转换轮廓线,而是直接从最终的、已应用过conv_marker的填充区域生成轮廓线:
轮廓线宽度 = 标记点的初始轮廓线宽度 x 标记点transform属性的scale系数 x 目标线段的宽度
这种方法简捷一些,但是存在一个缺点:如果起点、终点采用不同的标记点,其轮廓线设置(线型、宽度、连接方式等)有可能不一致,但该方法起点、终点的轮廓线最终使用同一个conv_stroke对象生成,也就是说,只能使用相同的轮廓线设置。因此通常情况下还是使用第一种方法绘制标记点。
两种方法的绘制效果依次见例程agg_demo07的上、下图。
-----------------------------------------------------------------------
SVG图元的transform属性可以包含多个变换,这些变换的应用顺序是正序还是逆序?
如果使用绝对坐标系,按逆序应用变换(先应用后面的变换)
如果使用局部坐标系,按正序应用变换(先应用前面的变换)
AGG程序中,按调用顺序考虑坐标变换的效果时,使用的是绝对坐标系。为了与AGG一致,建议还是按绝对坐标系、逆序应用SVG图元的transform属性。
-----------------------------------------------------------------------
解析SVG图元的fill、stroke属性时要注意,两者的缺省处理方式是相反的:
- 如果没有设置fill属性,相当于fill="black", 即使用黑色填充
- 如果没有设置stroke属性,相当于stroke="none",即不显示轮廓线
-----------------------------------------------------------------------
conv_curve、conv_transform的先后关系对绘制效果的影响
conv_curve使用折线模拟曲线,折线的顶点数目和曲线大小成正比。如果先应用conv_curve,再应用conv_transform进行放大,即先生成折线再放大,由于顶点数目匹配的是放大前的尺寸,因此顶点数目偏少,曲线不光滑。解决办法有两个:
1 交换conv_curve、conv_transform的顺序,先应用conv_transform进行放大,那么在应用conv_curve时,将根据放大后的尺寸生成折线,绘制出的曲线更加光滑。
2 不改变顶点转换管线的应用顺序,但是在绘制前调用如下代码:
conv_curve对象.approximation_scale( 变换矩阵的scale系数 );
conv_curve对象.angle_tolerance( 0.0 );
conv_curve将依据scale系数决定折线的顶点数目,从而达到与方法1相同的效果。(svg_viewer在绘制图元时用到了方法2,参见函数path_renderer::render)
那么这两个方法应该选哪一个呢?如果只绘制填充区域,两个方法都可以。如果还要绘制轮廓线,以下是三种绘制方式的对比:
原始方法:顶点源 -> conv_curve -> conv_stroke -> 矩阵变换
绘制效果:不光滑,轮廓线的线宽按比例缩放
方法1: 顶点源 -> 矩阵变换 -> conv_curve -> conv_stroke
绘制效果:光滑,轮廓线的线宽没有按比例缩放
方法2: 顶点源 -> conv_curve -> conv_stroke -> 设置approximation_scale -> 矩阵变换
绘制效果:光滑,轮廓线的线宽按比例缩放
从对比中可以看出,此时应选择方法2。为了代码的一致性,建议无论是否绘制轮廓线,均使用方法2。
三种绘制方式的对比效果见例程agg_demo06。
-----------------------------------------------------------------------
AGG程序中,按translate、rotate等函数的调用顺序应用坐标变换时,使用的是绝对坐标系。假设调用顺序为T1 T2 T3,则最终的变换矩阵为T3*T2*T1。
如果使用局部坐标系,矩阵T3*T2*T1对应的变换顺序为T3 T2 T1,即按照translate、rotate等函数的调用顺序的逆序应用坐标变换。
-----------------------------------------------------------------------
agg::trans_affine各成员函数对应的矩阵计算公式:
multiply(m) ==> m·this
premultiply(m) ==> this·m
operator * (m) ==> m·this
translate(...) ==> T·this(T为平移矩阵)
rotate(...) ==> R·this(R为旋转矩阵)
scale(...) ==> S·this(S为缩放矩阵)
-----------------------------------------------------------------------
变换矩阵agg::trans_affine中,数据成员与矩阵元素的对应关系如下:
| sx shx tx |
| shy sy ty |
| 0 0 1 |
-----------------------------------------------------------------------
顶点转换管线agg::conv_xxxx本身也是顶点源,构造conv_xxxx对象只是建立顶点源的级联关系,实际的顶点转换工作在调用conv_xxxx::vertex(...)时完成。
-----------------------------------------------------------------------
函数 agg::pixel_map::bitmap_info() 返回指向 BITMAPINFO 结构的指针;agg::pixel_map::buf() 返回指向像素数据的指针。
-----------------------------------------------------------------------
经反复测试,AGG绘制图形时所使用的Alpha混合公式应该是:
Dst.Red = Src.Alpha * Src.Red + (1 - Src.Alpha) * Dst.Red
Dst.Green = Src.Alpha * Src.Green + (1 - Src.Alpha) * Dst.Green
Dst.Blue = Src.Alpha * Src.Blue + (1 - Src.Alpha) * Dst.Blue
Dst.Alpha = Src.Alpha + (1 - Src.Alpha) * Dst.Alpha
-----------------------------------------------------------------------
绘制封闭图形时,如何实现内部填充用一种颜色,边框用另一种颜色?
实现步骤:
1 使用填充色绘制顶点源
2 使用agg::conv_stroke将顶点源转换为轮廓线
3 使用另一种颜色绘制轮廓线
-----------------------------------------------------------------------
svg_viewer在解析SVG文件时,图元的公共属性,例如style、fill,由函数parser::parse_attr负责解析。图元的特有属性则在各图元的处理函数(parser::parse_line、parse_rect ...)中进行解析。
-----------------------------------------------------------------------
如果想让agg::pixel_map具备Alpha混合功能,编译agg_win32_bmp.cpp时要定义预处理器 AGG_BMP_ALPHA_BLEND。agg::pixel_map的Alpha混合功能是通过Win32函数AlphaBlend实现的。
-----------------------------------------------------------------------
AGG的顶点源以path_cmd_stop为结束命令,并不是说,其顶点序列中一定会存在一个path_cmd_stop顶点。以agg::path_storage为例,在其vertex函数中,判断如果已经遍历到顶点末尾,则返回path_cmd_stop。
-----------------------------------------------------------------------
agg::path_storage用于保存多个path,它是类模板agg::path_base的一个实例。在调用agg::path_storage::rewind(...)、agg::rasterizer_scanline_aa::add_path(...)时,会用到参数path_id,该参数来自agg::path_storage::start_new_path()的返回值,用于标识path。该返回值实际上就是已有path的顶点总数,因此第一个path的标识总是0。详情参见agg::path_base的程序注释。
-----------------------------------------------------------------------
AGG会自动封闭非conv_stroke对象的起点和终点,并填充对象。封闭代码好像位于函数agg::rasterizer_scanline_aa::close_polygon() 。
注:随着对AGG了解的加深,发现上述关于“非conv_stroke对象”的提法是有问题的。在AGG中,经过conv_stroke转换后,得到的仍然是顶点源,换句话说,是一个多边形。只不过这个多边形位于初始顶点源的边缘,而且比较细,看上去象一条线。也就是说,AGG中不存在conv_stroke对象与非conv_stroke对象的区别,AGG会自动封闭所有对象的起点和终点。
-----------------------------------------------------------------------
函数 agg::render_scanlines 只绘制新添加的顶点源。通过跟踪、分析源代码,其实现方式好像是这样的:
- 调用 agg::rasterizer_scanline_aa::add_path 添加顶点源
* 调用 m_outline.sorted(),判断是否已绘制过/已光栅化
$ 返回 m_sorded
* 如果已绘制过,则调用 reset() 复位
* 添加顶点
- 调用 agg::render_scanlines 绘制顶点源
* 调用 ras.rewind_scanlines()
$ 调用 m_outline.sort_cells()
# 设置 m_sorted = true
由上可以看出,函数 add_path 会清除已绘制过的顶点源,之后调用 render_scanlines 时,只绘制新增的顶点源。
-----------------------------------------------------------------------
通过分析源代码,函数 agg::conv_contour::width(w) 的作用好像是:将轮廓线扩展w像素,则轮廓线的中心线扩展w/2像素。