首页 > 代码库 > (转)用AGG实现高质量图形输出(二)

(转)用AGG实现高质量图形输出(二)

本文上接《用AGG实现高质量图形输出(一)》,分别介绍了AGG显示流程中的各个环节。

上次讲了AGG的显示原理并举了一个简单的例子,这一篇文章开始讲AGG工作流程里的每个环节。为了方便对照,再放一次AGG显示流程 图

20090816015959443

另外,上一篇文章里的例程也很重要,后面的例子都将基于这个代码。

下面,我们来考察AGG显示流程中的每个环节。理解每个环节最好的方法是编写实验代码,建议先参照这里建 立一个可以运行的AGG实验环境。

顶点源(Vertex Source)

顶点源是一种可以产生多边形所需要的“带命令的顶点”的对象。比如三角形顶点源,就应该会产生一个带“MoveTo”命令的点,另外二 个带"LineTo"命令的点和最终闭合的“ClosePoly”命令。

头文件
1
2
3
4
5
6
7
#include <agg_path_storage.h> //path_storage
#include <agg_ellipse.h>  // ellipse
#include <agg_arc.h> // arc
#include <agg_arrowhead.h> // arrowhead
#include <agg_curves.h> // curve3, curve4
#include <agg_gsv_text.h> // gsv_text, gsv_text_outline
#include <agg_rounded_rect.h> // rounded_rect</agg_rounded_rect.h></agg_gsv_text.h></agg_curves.h></agg_arrowhead.h></agg_arc.h></agg_ellipse.h></agg_path_storage.h>
类型
自定义类所有实现了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
arrowhead ah;
ah.head(d1,d2,d3,d4); //定义箭头
ah.tail(d1,d2,d3,d4); //定义箭尾
VertexSource VS; //其它顶点源
// 使用顶点转换器,并指定Markers类型为vcgen_markers_term
// 顶点转换器可以是conv_dash、conv_stroke或conv_marker_adaptor,见后文《坐标转换管道》
// vcgen_markers_term:以端点作为标记点
conv_stroke<vertexsource> csVS(VS);
...draw_term
// 用conv_marker指定ah作为线段marker点的标记
conv_marker<vcgen_markers_term> arrow(csVS.markers(), ah);
ras.add_path(csVS);
ras.add_path(arrow); //marker要紧随其后加入
...render</vcgen_markers_term></vertexsource>

ah.head()和ah.tail()方法中的d1,d2,d3,d4参数的意义见下图

20090821044820908

例,画一条简单的箭头直线(基于此处代码)

在on_draw()方法最后加上下列代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
agg::arrowhead ah;
     ah.head(0,10,5,5);
     ah.tail(10,10,5,5);
     // 用path_storage生成一条直线
     agg::path_storage ps;
     ps.move_to(160,60);
     ps.line_to(100,100);
     // 转换
     agg::conv_stroke<?xml:namespace prefix = agg /><agg:agg:agg:agg:agg:agg::path_storage> csps(ps);
     agg::conv_marker<agg:agg:agg:agg:agg:agg::vcgen_markers_term>
         arrow(csps.markers(), ah);
     // 画线
     ras.add_path(csps);
     agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba8(0,0,0));
     // 画箭头
     ras.add_path(arrow);
     agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba8(255,0,0));</agg:agg:agg:agg:agg:agg::vcgen_markers_term></agg:agg:agg:agg:agg:agg::path_storage>
得到的图形是:

20090821044820318

注意要加头文件:

1
2
3
4
#include "agg_conv_marker.h"
#include "agg_arrowhead.h"
#include "agg_path_storage.h"
#include "agg_vcgen_markers_term.h"
试验代码,自定义一个顶点源(基于此处代码)

为了对顶点源有更深入的了解,我们要自己实现一个顶点源,这个顶点源只是很简单的一个三角形。

前面说过,只要实现了 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
enum path_commands_e
    {
        path_cmd_stop     = 0,        //----path_cmd_stop  
        path_cmd_move_to  = 1,        //----path_cmd_move_to
        path_cmd_line_to  = 2,        //----path_cmd_line_to
        path_cmd_curve3   = 3,        //----path_cmd_curve3
        path_cmd_curve4   = 4,        //----path_cmd_curve4
        path_cmd_curveN   = 5,        //----path_cmd_curveN
        path_cmd_catrom   = 6,        //----path_cmd_catrom
        path_cmd_ubspline = 7,        //----path_cmd_ubspline
        path_cmd_end_poly = 0x0F,     //----path_cmd_end_poly
        path_cmd_mask     = 0x0F      //----path_cmd_mask  
    };

path_commands_e还能和path_flags_e组合:

1
2
3
4
5
6
7
8
enum path_flags_e
{
    path_flags_none  = 0,         //----path_flags_none
    path_flags_ccw   = 0x10,      //----path_flags_ccw
    path_flags_cw    = 0x20,      //----path_flags_cw 
    path_flags_close = 0x40,      //----path_flags_close
    path_flags_mask  = 0xF0       //----path_flags_mask
};

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
// 等边三角形
    class triangle{
    public:
        triangle(double cx, double cy, double r)//中心点,r为中心点到边的长度
        {
            // 直接准备好三个点
            m_step = 0;
            m_pt[0].x = cx; m_pt[0].y = cy-r;
            m_pt[1].x = cx+r*0.866; m_pt[1].y = cy+r*0.5;
            m_pt[2].x = cx-r*0.866; m_pt[2].y = cy+r*0.5;
            //AGG把方向作为区分多边形内部和外部的依据,可以试试m_pt[1]和m_pt[2]对调
        }
        void rewind(unsigned)
        {
            m_step = 0;
        }
        unsigned vertex(double* x, double* y)
        {
            switch(m_step++)
            {
            case 0:
                //第一步,move_to
                *x = m_pt[0].x;
                *y = m_pt[0].y;
                return agg::path_cmd_move_to;
            case 1:
            case 2:
                //第二、三步,line_to
                *x = m_pt[m_step-1].x;
                *y = m_pt[m_step-1].y;
                return agg::path_cmd_line_to;
            case 3:
                // 第四步,闭合多边形
                return agg::path_cmd_end_poly|agg::path_flags_close;
            default:
                // 第五步,结束
                return agg::path_cmd_stop;
            }
        }
    private:
        agg::point_d m_pt[3];
        unsigned m_step;
    };

在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;

得到的图形是:

20090821044820532

除了文字输出功能(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
// Vertex Source
agg::ellipse ell(0,0,50,50); //圆心在中间
  
// Coordinate conversion pipeline
agg::trans_affine mtx;
mtx.scale(0.5,1); // x轴缩小到原来的一半
mtx.rotate(agg::deg2rad(30)); // 旋转30度
mtx.translate(100,100); // 平移100,100
typedef agg::conv_transform<agg:agg:agg:agg:agg:agg::ellipse> ell_ct_type;
ell_ct_type ctell(ell, mtx); // 矩阵变换
  
typedef agg::conv_contour<ell_ct_type> ell_cc_type;
ell_cc_type ccell(ctell); // 轮廓变换
  
typedef agg::conv_stroke<ell_cc_type> ell_cc_cs_type;
ell_cc_cs_type csccell(ccell); // 转换成多义线</ell_cc_type></ell_ct_type></agg:agg:agg:agg:agg:agg::ellipse>
得到的图形是:

20090821044820601

注:trans_affine不 仅仅用于源顶点的变换,在AGG库中有不少地方都能看到它。比如后面会讲到的线段(span)生成器,通过变换矩阵,就能够 自由变换填充于多边形之内的图案。

坐标转换管道
头文件
1
2
3
4
5
6
7
8
#include <agg_conv_stroke.h> // conv_stroke
#include <agg_conv_dash.h> // conv_dash
#include <agg_conv_marker.h> // conv_marker
#include <agg_conv_curve.h> // conv_curve
#include <agg_conv_contour.h> // conv_contour
#include <agg_conv_smooth_poly1.h> // conv_smooth_poly1.h
#include <agg_conv_bspline.h> // conv_bspline
#include <agg_conv_transform.h> // conv_transform</agg_conv_transform.h></agg_conv_bspline.h></agg_conv_smooth_poly1.h></agg_conv_contour.h></agg_conv_curve.h></agg_conv_marker.h></agg_conv_dash.h></agg_conv_stroke.h>
类型(演示程序基于基于此处代码)

template<class VertexSource,
class Markers = null_markers>
struct conv_stroke;

变成连续线
构造参数为VertexSource
width属性决定线宽。

在例程的ell_cc_cs_type csccell(ccell);
后面加上csccell.width(3);线宽就会变成3。

20090821044820576

template<class VertexSource,
class Markers = null_markers>
struct conv_dash;

虚线
构造参数为VertexSource
用add_dash设置虚线长度和间隔
与conv_stroke套用

// Coordinate conversion pipelinetypedef 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);...
20090821044820246

template<class MarkerLocator,
class MarkerShapes>
class conv_marker;

建立标记

请参考arrowhead示例代码

20090821044820318

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

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

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和变换矩阵

见变换矩阵一节的例子
20090821044820601

Scanline Rasterizer

Scanline Rasterizer能够把顶点数据转换成一组水平扫描线,扫描线由一组线段(Span)组成,线段(Span)包含了起始位置、长度和覆盖率(可以理解 为透明度)信息。AGG的抗锯齿(Anti-Aliasing)功能也是在这时引入的。

扫描线Scanline

扫描线是一种保存span的容器,span用于表示一小条(水平方向)细线。图像中同一行的span组成一个Scanline。

头文件
1
2
3
#include <agg_scanline_u.h> // scanline_u8,scanline32_u8
#include <agg_scanline_p.h> // scanline_p8,scanline32_p8
#include <agg_scanline_bin.h> // scanline_bin,scanline32_bin</agg_scanline_bin.h></agg_scanline_p.h></agg_scanline_u.h>
类型
scanline_bin,scanline32_bin不携带AA信息的span容器。scanline32_bin中的32代表坐标位数,一般16位已经足够了,所以前一版 本用得更多些(下同)
scanline_u8,scanline32_u8unpacked版的span容器,用每个span来保存各自的线段信息
scanline_p8,scanline32_p8packed版的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 clip="rasterizer_sl_clip_int">
class rasterizer_scanline_aa;</class>
成员方法

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
template<class> class renderer_scanline_aa_solid; //实色AA渲染
template<class> class renderer_scanline_bin_solid; //实色原始渲染
template<class>
 class renderer_scanline_aa; // 自定义AA渲染
template<class>
 class renderer_scanline_bin; // 自定义原始渲染</class></class></class></class>

以及自己写的实现了void prepare() 和 template<class Scanline> void render(const Scanline& sl) 方法的类

另外,头文件agg_renderer_scanline.h中 的render_scanlines函 数很重要,它是AGG显示流程的实现。

1
void render_scanlines(Rasterizer& ras, Scanline& sl, Renderer& ren);

从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
void render_scanlines_aa_solid(Rasterizer& ras, Scanline& sl,
 BaseRenderer& ren, const ColorT& color)
template<class>
void render_scanlines_aa(Rasterizer& ras, Scanline& sl, BaseRenderer& ren,
 SpanAllocator& alloc, SpanGenerator& span_gen);</class>
实验代码(基于此 处代码)

把on_draw()方法里原

1
typedef agg::renderer_scanline_aa_solid<renderer_base_type> renderer_scanline_type;</renderer_base_type>

改成

1
typedef agg::renderer_scanline_bin_solid<renderer_base_type> renderer_scanline_type;</renderer_base_type>
得到的图形是:

20090821044820595

去掉renderer_scanline_type以及所有的rensl相关语句,把

1
agg::render_scanlines(ras,sl,rensl);

改成

1
agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba8(0,0,i*50));

同样可以得到我们想要的图形

Basic Renderers
头文件
#include <agg_renderer_base.h>#include <agg_renderer_mclip.h>
类型
1
2
3
template<class> class renderer_base;
 
template<class> class renderer_mclip;</class></class>
构造函数
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
renb.clear(agg::rgba8(255,255,255));
    renb.clip_box(30,30,160,160); // 设置可写区域
得到的图形是:

20090821044820502

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_gray8
pixfmt_rgb24
pixfmt_bgr24
pixfmt_rgba32
pixfmt_bgr24_gamma
...
构造函数

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
//从50,20开始,画20条长度为100的坚线,颜色从黑渐变到红,覆盖率为128(半透明)
    for(int i=0; i<20; i++)
        pixf.blend_vline(50+i,20,100,agg::rgba(i/20.0,0,0),128);
得到的图形是:

20090821044820392

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::int8u* p = rbuf.row_ptr(20);//得到第20行指针
memset(p,0,rbuf.stride_abs());//整行以0填充
得到的图形是:

20090821044820853

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
#include <agg_rendering_buffer.h>
    #include <agg_pixfmt_rgba.h>
    #include <agg_renderer_base.h>
    #include <agg_rasterizer_scanline_aa.h>
    #include <agg_scanline_p.h>
    ...
    // 首先让系统生成一个32位的bmp缓存
    BITMAPINFO bmp_info;
    bmp_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmp_info.bmiHeader.biWidth = width;
    bmp_info.bmiHeader.biHeight = height;
    bmp_info.bmiHeader.biPlanes = 1;
    bmp_info.bmiHeader.biBitCount = 32;
    bmp_info.bmiHeader.biCompression = BI_RGB;
    bmp_info.bmiHeader.biSizeImage = 0;
    bmp_info.bmiHeader.biXPelsPerMeter = 0;
    bmp_info.bmiHeader.biYPelsPerMeter = 0;
    bmp_info.bmiHeader.biClrUsed = 0;
    bmp_info.bmiHeader.biClrImportant = 0;
      
    HDC mem_dc = ::CreateCompatibleDC(hdc);
      
    void* buf = 0;
      
    HBITMAP bmp = ::CreateDIBSection(
        mem_dc,
        &bmp_info,
        DIB_RGB_COLORS,
        &buf,
        0,
        0
        );
      
    // 把bmp与mem_dc关联,这样AGG就可以和原生GDI一起工作了
    HBITMAP temp = (HBITMAP)::SelectObject(mem_dc, bmp);
      
    //============================================================
    // 以下是AGG代码
    agg::rendering_buffer rbuf;
    // 32位位图,每行字节数为width*4。
    // BMP是上下倒置的,为了和GDI习惯相同,最后一个参数是负值。
    rbuf.attach((unsigned char*)buf, width, height, -width*4);
      
    // 像素格式和renderer_base
    agg::pixfmt_bgra32 pixf(rbuf);
    agg::renderer_base<agg:agg:agg:agg:agg:agg::pixfmt_bgra32> renb(pixf);
      
    renb.clear(agg::rgba8(255, 255, 255, 255));
      
    // Scanline renderer
    agg::renderer_scanline_aa_solid<agg:agg:agg:agg:agg:agg::renderer_base><agg:agg:agg:agg:agg:agg::pixfmt_bgra32> > ren(renb);
      
    // Rasterizer & scanline
    agg::rasterizer_scanline_aa<> ras;
    agg::scanline_p8 sl;
      
    // 多义线(三角形)
    ras.move_to_d(20.7, 34.15);
    ras.line_to_d(398.23, 123.43);
    ras.line_to_d(165.45, 401.87);
      
    // 设置颜色后渲染
    ren.color(agg::rgba8(80, 90, 60));
    agg::render_scanlines(ras, sl, ren);
    //============================================================
      
    // 把bmp显示到hdc上,如果图片中有Alpha通道,可以使用AlphaBlend代替BitBlt。
    ::BitBlt(
        hdc,
        rt.left,
        rt.top,
        width,
        height,
        mem_dc,
        0,
        0,
        SRCCOPY
        );
      
    // 释放资源
    ::SelectObject(mem_dc, temp);
    ::DeleteObject(bmp);
    ::DeleteObject(mem_dc);</agg:agg:agg:agg:agg:agg::pixfmt_bgra32></agg:agg:agg:agg:agg:agg::renderer_base></agg:agg:agg:agg:agg:agg::pixfmt_bgra32></agg_scanline_p.h></agg_rasterizer_scanline_aa.h></agg_renderer_base.h></agg_pixfmt_rgba.h></agg_rendering_buffer.h>
得到的图形是:

20090821044820710

使用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
#include <agg_rendering_buffer.h>
    #include <agg_pixfmt_rgba.h>
    #include <agg_renderer_base.h>
    #include <agg_rasterizer_scanline_aa.h>
    #include <agg_scanline_p.h>
    #include <platform>
    ...
    CRect rc;
    GetClientRect(&rc);
      
    agg::pixel_map pm;
    pm.create(rc.right,rc.bottom,agg::org_color32);
      
    //============================================================
    // 以下是AGG代码
    agg::rendering_buffer rbuf;
    rbuf.attach(pm.buf(), pm.width(), pm.height(), -pm.stride());
      
    // 像素格式和renderer_base
    agg::pixfmt_bgra32 pixf(rbuf);
    agg::renderer_base<agg:agg:agg:agg:agg:agg::pixfmt_bgra32> renb(pixf);
      
    renb.clear(agg::rgba8(255, 255, 255, 255));
      
    // Scanline renderer
    agg::renderer_scanline_aa_solid<agg:agg:agg:agg:agg:agg::renderer_base><agg:agg:agg:agg:agg:agg::pixfmt_bgra32> > ren(renb);
      
    // Rasterizer & scanline
    agg::rasterizer_scanline_aa<> ras;
    agg::scanline_p8 sl;
      
    // 多义线(三角形)
    ras.move_to_d(20.7, 34.15);
    ras.line_to_d(398.23, 123.43);
    ras.line_to_d(165.45, 401.87);
      
    // 设置颜色后渲染
    ren.color(agg::rgba8(80, 90, 60));
    agg::render_scanlines(ras, sl, ren);
    //============================================================
    pm.draw(hdc);</agg:agg:agg:agg:agg:agg::pixfmt_bgra32></agg:agg:agg:agg:agg:agg::renderer_base></agg:agg:agg:agg:agg:agg::pixfmt_bgra32></platform></agg_scanline_p.h></agg_rasterizer_scanline_aa.h></agg_renderer_base.h></agg_pixfmt_rgba.h></agg_rendering_buffer.h>