关闭

AGG-SVG学习笔记 [2015-07-07]

标签: AGGsvgc++
1002人阅读 评论(0) 收藏 举报

河南洛阳 祝小鹰 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像素。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:10321次
    • 积分:272
    • 等级:
    • 排名:千里之外
    • 原创:17篇
    • 转载:0篇
    • 译文:0篇
    • 评论:4条
    最新评论