Alpha-Mask Adaptor
Alpha-Mask 是一个分离出来的缓存区,通常用于在底层实现任意形状的裁剪。有一个特制的适配器类,可以将所有对像素格式渲染器(pixel format renderes)的调用先丢给 alpha-mask 过滤器。alpha-mask 一般是一个灰度缓存区(每像素一个字节),大小与主渲染缓存区(main rendering buffer)是一样的。在 alpha-mask 中的每个像素就是对应着主渲染缓存区相应像素的一个额外的覆盖值(coverage value)。copy_hline()之类没有酸辣值作为参数的函数会将调用转向相应的有覆盖值的函数。比如,copy_hline()会从 alpha mask 缓存区中取出水平 span ,再用它来调用 blend_solid_hspan() 函数。
包含文件:
#include "agg_pixfmt_amask_adaptor.h" #include "agg_alpha_mask_u8.h"
下面是一个例子,它展示了如何声明一个带有 alpha-mask 适配器的像素格式渲染器。
#include "agg_pixfmt_rgb24.h" #include "agg_pixfmt_amask_adaptor.h" #include "agg_alpha_mask_u8.h" //. . . // Allocate the alpha-mask buffer, create the rendering buffer object // and create the alpha-mask object. //-------------------------------- agg::int8u* amask_buf = new agg::int8u[frame_width * frame_height]; agg::rendering_buffer amask_rbuf(amask_buf, frame_width, frame_height, frame_width); agg::amask_no_clip_gray8 amask(amask_rbuf); // Create the alpha-mask adaptor attached to the alpha-mask object // and the pixel format renderer. Here pixf is a previously // created pixel format renderer of type agg::pixfmt_rgb24. agg::pixfmt_amask_adaptor<agg::pixfmt_rgb24, agg::amask_no_clip_gray8> pixf_amask(pixf, amask); //. . .
注意在这里我们用的是 amask_no_clip_gray8 ,它不带区域裁剪功能。因为我们使用的 alpha-mask 缓存区和主缓存区的大小是一样的,所以只要主缓存区没有非法访问的话,alpha-mask 缓存区也不会存在非法访问。裁剪在高层中实现,如果你使用的 alpha-mask 缓存区比主缓存区小的话,你必须使用 alpha_mask_gray8 这个类来代替。
下面是一个完整的例子:
#include <stdio.h> #include <string.h> #include "agg_pixfmt_rgb24.h" #include "agg_pixfmt_amask_adaptor.h" #include "agg_alpha_mask_u8.h" enum { frame_width = 320, frame_height = 200 }; // [...write_ppm is skipped...] int main() { // Allocate the main rendering buffer and clear it, for now "manually", // and create the rendering_buffer object and the pixel format renderer //-------------------------------- agg::int8u* buffer = new agg::int8u[frame_width * frame_height * 3]; memset(buffer, 255, frame_width * frame_height * 3); agg::rendering_buffer rbuf(buffer, frame_width, frame_height, frame_width * 3); agg::pixfmt_rgb24 pixf(rbuf); // Allocate the alpha-mask buffer, create the rendering buffer object // and create the alpha-mask object. //-------------------------------- agg::int8u* amask_buf = new agg::int8u[frame_width * frame_height]; agg::rendering_buffer amask_rbuf(amask_buf, frame_width, frame_height, frame_width); agg::amask_no_clip_gray8 amask(amask_rbuf); // Create the alpha-mask adaptor attached to the alpha-mask object // and the pixel format renderer agg::pixfmt_amask_adaptor<agg::pixfmt_rgb24, agg::amask_no_clip_gray8> pixf_amask(pixf, amask); // Draw something in the alpha-mask buffer. // In this case we fill the buffer with a simple verical gradient unsigned i; for(i = 0; i < frame_height; ++i) { unsigned val = 255 * i / frame_height; memset(amask_rbuf.row_ptr(i), val, frame_width); } // Draw the spectrum, write a .ppm and free memory //---------------------- agg::rgba8 span[frame_width]; for(i = 0; i < frame_width; ++i) { agg::rgba c(380.0 + 400.0 * i / frame_width, 0.8); span[i] = agg::rgba8(c); } for(i = 0; i < frame_height; ++i) { pixf_amask.blend_color_hspan(0, i, frame_width, span, 0); } write_ppm(buffer, frame_width, frame_height, "agg_test.ppm"); delete [] amask_buf; delete [] buffer; return 0; }
这是运行结果:
你看到了,我们是用白色来清空主缓存的,如果我们将:
memset(buffer, 255, frame_width * frame_height * 3);
修改成:
memset(buffer, 0, frame_width * frame_height * 3);
那么结果就像下面这样:
换句话说,alpha-mask 是这样工作的:它是一个分离出来的 alpha 通道,用于混合渲染基本的描画物。因为它包含的是 8-bit 的值,所以你可以将描画裁剪成任意的形状,而且这种裁剪可以有很非常好的去锯齿效果。
Basic Renderers
有两种基础渲染器,renderer_base 和 renderer_mclip 它们的功能几乎是一样的。主要使用的是前者(renderer_base),它进行低层次的裁剪处理。通用的裁剪处理(clipping)是一个复杂的任务。在 AGG 中,至少可以有两层的裁剪,底层(像素级)的,和高层的(向量级)。这两个类可以进行像素级别的裁剪,用以防止对缓存区的越界访问。 renderer_mclip 类可以支持多个矩形区域的裁剪区域,但它的性能和裁剪区域的数量有关。
renderer_base 和 renderer_mclip 都是模板类,它们的模板参数就是像素格式渲染器(pixel format renderer)。
template<class PixelFormat> class renderer_base { public: typedef PixelFormat pixfmt_type; typedef typename pixfmt_type::color_type color_type; . . . };
Creation
renderer_base(pixfmt_type& ren); renderer_mclip(pixfmt_type& ren);
两个类的构造函数都可以接受像素格式渲染器(pixel format renderer)对象作为参数。 renderer_mclip 在内部使用 renderer_base<PixelFormat> ,用来进行单矩形区域的裁剪。注意,你也可以使用 pixfmt_amask_adaptor 作为参数 PixelFormat。
构造的开销是非常小的,它只是初始化各个成员变量。不过,如果你添加新的裁剪区域,那么 renderer_mclip 需要申请新的内存,在析构时也会有相应的释放动作。它会用 pod_deque 类来完成内存块的申请,并且它不会(提前)释放不需要的内存,当你重新设置裁剪区域的时候,它会重用原来申请的内存区。 AGG 中广泛使用了这种技术,这可以避免产生过多的内存碎片。
Member Functions
const pixfmt_type& ren() const; pixfmt_type& ren();
返回指向像素渲染器(pixel format renderer)的引用。
unsigned width() const; unsigned height() const;
返回渲染缓存区(rendering buffer)的高和度。
void reset_clipping(bool visibility);
这个函数会重设裁剪区域,如果 visibility 值设置为 true ,那么裁剪区域会被设置为 (0, 0, width()-1, height()-1),如果设置为 false,会设置一个不可见的区域,比如(1,1,0,0)。对于 renderer_mclip 的这个函数来说,它会移除之前添加的所有剪裁区域。
重要!
如果你将另一块内存区附着到 rendering_buffer 上的话(rendering_buffer是连接着这个(basic_renderer),你必须调用一次 reset_clipping() ,否则的话,原来的裁剪区域会变得无效。 因为这个内存改变动作做完之后并不会,rendering buffer 不会给 renderers 任何反馈。也就是说,renderer_base 和 renderer_mclip 对于 rendering buffer 的变动一无所知(译注:renderer_base是用 pixel format render来构造的,而 pixel format render 又是由 rendering buffer 来支撑,所以它们之间就有这样的关系,具体可见前文)。如果有这样的机制,可以在它们之间传递消息或是使用委托,那么这种机制可能成为 overkill (译注:不知道怎么理解这个overkill)。
bool clip_box(int x1, int y1, int x2, int y2);
这个函数用于设置新的裁剪区域(clipping box)。只有 renderer_base 才有这个函数。裁剪区域包含了边界位置,所以最大的裁剪区域是 (0, 0, width()-1, height()-1) 。裁剪区域在(重新)设置之前,会被设置为最大值。所以,就算你设置新区域的比 (0, 0, width()-1, height()-1) 还要大,也是安全的(译注:因为不会被采用)
void add_clip_box(int x1, int y1, int x2, int y2);
添加一个新的裁剪区域。只有 renderer_mclip 才有这个函数。你可以添加任何个新的矩形区域,但他们之间不能有重叠的部分。如果有重叠的区域的话,有些元素就会被描画两次或两次以上。被添加的新区域会在加入之时被 (0, 0, width()-1, height()-1) 这个区域预先裁剪掉,这主要是有效率上的考量。这也意味着调用 reset_clipping(false)没有任何意义,因为所有被添加的新区域都会用一个不可见区域内先裁剪掉,因此也就不会被添加。(译注:原句如下:t also means that calling reset_clipping(false) for the renderer_mclip doesn't make any sense because all adding regions will be clipped by an invisible area and will not be actually added.对这个我不能理解它的意思,后面看了代码再来修正吧)。可见区域包含裁剪区域的边界,也就是说,调用 add_clip_box(100,100,100,100) 会添加一个 1 像素的裁剪区域。
void clip_box_naked(int x1, int y1, int x2, int y2);
只有 renderer_base 有这个函数,它用来给 rendering buffer 大小设置一个新的裁剪区域。这个函数不安全,主要是 renderer_mclip 类在使用它,用来在不同的子区域之间快速的切换。这个函数的目的就是为了避免额外的开销。
bool inbox(int x, int y) const;
检查 (x,y) 这个点是不是在裁剪区域内。只有 renderer_base 才有这个函数。
void first_clip_box(); bool next_clip_box();
这两个函数用于枚举渲染器所有的裁剪区域。对于 renderer_base 类来说,它是返回的是空的, next_clip_box() 始终返回 false。
const rect& clip_box() const; int xmin() const; int ymin() const; int xmax() const; int ymax() const;
以一个矩形的形式可是以四个独立的整数值形式返回裁剪区域。 renderer_mclip 的这个函数始终返回 (0, 0, width()-1, height()-1)。
const rect& bounding_clip_box() const; int bounding_xmin() const; int bounding_ymin() const; int bounding_xmax() const; int bounding_ymax() const;
以一个矩形的形式可是以四个独立的整数值形式返回裁剪区域的边界。对于 renderer_base ,这个函数返回的值于上面那组函数是一样的。对于 renderer_mclip 来说,它们返回的边界是由所有被添加的矩形合计得出的。
void clear(const color_type& c);
用 c 这个颜色来清除缓存区内的所有区域(不考虑裁剪区域)。
void copy_pixel(int x, int y, const color_type& c);
设置一个像素的颜色(考虑裁剪区域clipping)。
void blend_pixel(int x, int y, const color_type& c, cover_type cover);
混合描画一个像素。它的行为与 pixel format renderer 的对应函数是一样的。
color_type pixel(int x, int y) const;
获取指定坐标(x,y)的颜色值,如果这个点在裁剪区域外,这个函数返回 color_type::no_color(). 对于 rgba8 来说,就是 (0,0,0,0).
void copy_hline(int x1, int y, int x2, const color_type& c); void copy_vline(int x, int y1, int y2, const color_type& c); void blend_hline(int x1, int y, int x2, const color_type& c, cover_type cover); void blend_vline(int x, int y1, int y2, const color_type& c, cover_type cover);
描画(拷贝)或是混合渲染水平或垂直的像素线。行为与 pixel format renders 的相应函数一样。但在这里,使用的是线的起始点和终点坐标,而不是 pixfmt 中的(x, y, length)。
void copy_bar(int x1, int y1, int x2, int y2, const color_type& c); void blend_bar(int x1, int y1, int x2, int y2, const color_type& c, cover_type cover);
描画(拷贝)或是混合渲染一个矩形区。
void blend_solid_hspan(int x, int y, int len, const color_type& c, const cover_type* covers); void blend_solid_vspan(int x, int y, int len, const color_type& c, const cover_type* covers);
混合渲染一个水平或是垂直的纯色(solid-color)的span。这些在渲染实多边形时会用到。
void blend_color_hspan(int x, int y, int len, const color_type* colors, const cover_type* covers); void blend_color_vspan(int x, int y, int len, const color_type* colors, const cover_type* covers);
混合渲染一个水平或是垂直的(? vertical -color)的span。这个函数与不同的 span 生成器一起使用,比如 gradients, images, patterns, Gouraud interpolation 等等。函数接受一个颜色数组,颜色的类型必须与正在使用 pixel format 兼容。
void blend_color_hspan_no_clip(int x, int y, int len, const color_type* colors, const cover_type* covers); void blend_color_vspan_no_clip(int x, int y, int len, const color_type* colors, const cover_type* covers);
与上面的函数是一样的,但不考虑裁剪区域。这两个函数用到 scanline renderers 中。分离出这两个函数的原因也是为了效率,scanline 由很多的 spans 组合而成,在进行区域裁剪时,拥有整个 scanline 的信息会比逐个的裁剪每个 span 来得更有效率一些,对于 renderer_mclip 这个类尤其如此。
void copy_from(const rendering_buffer& from, const rect* rc=0, int x_to=0, int y_to=0);
将源缓存区的内容拷入本缓存区中(考虑区域裁剪)。它假设两块缓存区的像素格式是一样的。rc是一个可选项,它指示的是源缓存区内的一个矩形,x_to 、 y_to ———— rc->x1, rc->y1 坐标值映射到目标缓存区。
A common example
在使用 rendering buffer 和底层 renderers 时,下面的代码是非常常用的。
// Typedefs of the low level renderers to simplify the declarations. // Here you can use any other pixel format renderer and // agg::renderer_mclip if necessary. //-------------------------- typedef agg::pixfmt_rgb24 pixfmt_type; typedef agg::renderer_base<agg::pixfmt_rgb24> renbase_type; enum { bytes_per_pixel = 3 }; unsigned char* buffer = new unsigned char[frame_width * frame_height * bytes_per_pixel]; agg::rendering_buffer rbuf(buffer, frame_width, frame_height, frame_width * bytes_per_pixel); pixfmt_type pixf(rbuf); renbase_type rbase(pixf); rbase.clear(clear_color); //. . .
在最后,我们使用clear()函数,以某种颜色清除了缓存区中,而没有“手动地”使用 memset() :-)。同时要注意,与例子所不同的是,stride 值并不是必须要和 frame_width * bytes_per_pixel 值一致。很多时候应用时会有一些对齐上的要求,比如,对于 windows中的位图(bitmap)来说,这个值必须是4的倍数。
Primitives and Markers Renderers
AGG 中也加入了一些基础描画对象(primitives)和标记(marker)的渲染器。这个机制可以让你很快地描画一些常见的、带锯齿的对象,比如直线,矩形,椭圆等。标记(marker)可以画出一些在散点图(scatter plots)中常见的形状。如果你不打算用它们,那么你可以跳过这一节的内容。
Primitives Renderer
头文件: agg_renderer_primitives.h
Declaration
template<class BaseRenderer> class renderer_primitives { public: typedef BaseRenderer base_ren_type; typedef typename base_ren_type::color_type color_type; //. . . };
这里的 BaseRenderer 可以用 renderer_base 或是 renderer_mclip.
Creation
renderer_primitives(base_ren_type& ren) : m_ren(&ren), m_fill_color(), m_line_color(), m_curr_x(0), m_curr_y(0) {}
创建的开销非常小,只是初始化指向 base renderer 对象的指针、两个颜色信息、以及初始化坐标值,这些坐标值会在 move_to 和 line_to 函数中用到。
Member functions
static int coord(double c);
将一个 double 型的坐标值转换成亚像素精度的 int 型。它做的事就是将 double 值乘以256 ,然后返回它的整型部分。
void fill_color(const color_type& c); void line_color(const color_type& c); const color_type& fill_color() const; const color_type& line_color() const;
获取或是设置 fill 或是直线的颜色。颜色可以带有 alpha 值并且这个值会生效,这样的话,基础画面对象(primitives)就可以进行 alpha 混合渲染。
void rectangle(int x1, int y1, int x2, int y2);
描画一个矩形,但不填充线的颜色。使用的是正规的像素坐标,边界的宽度始终是 1 像素,不能修改。
void solid_rectangle(int x1, int y1, int x2, int y2);
描画一个不带边界的实心矩形,填充线的颜色。使用的是像素坐标。
void outlined_rectangle(int x1, int y1, int x2, int y2);
描画一个带边界的实心矩形。使用了颜色线(colors line)和填充(fill?)。
void ellipse(int x, int y, int rx, int ry); void solid_ellipse(int x, int y, int rx, int ry); void outlined_ellipse(int x, int y, int rx, int ry);
描画椭圆,空心的、实心的或是实心带边界的。坐标是整数值,以像素数来衡量。rx 和 ry 是像素数衡量的椭圆半径。
void line(int x1, int y1, int x2, int y2, bool last=false);
使用亚像素精度描画一条 bresenham 光栅线。坐标的格式是 24.8,即,1/256的像素精度。 last 决定是否描画最后一个像素,这对于使用 alpha混合渲染来描画 consecutive 线段会非常重要。不应该有像素被多次描画。
void move_to(int x, int y); void line_to(int x, int y, bool last=false);
line()版本。
const base_ren_type& ren() const { return *m_ren; } base_ren_type& ren() { return *m_ren; }
返回 base renderer 对象的引用。
const rendering_buffer& rbuf() const { return m_ren->rbuf(); } rendering_buffer& rbuf() { return m_ren->rbuf(); }
返回附着于 base renderer 的 rendering buffer 的引用。
Marker Renderer
头文件: agg_renderer_markers.h
marker renderer 可以描画或是 alpha混合沉浸下面的这些图形:
enum marker_e { marker_square, marker_diamond, marker_circle, marker_crossed_circle, marker_semiellipse_left, marker_semiellipse_right, marker_semiellipse_up, marker_semiellipse_down, marker_triangle_left, marker_triangle_right, marker_triangle_up, marker_triangle_down, marker_four_rays, marker_cross, marker_x, marker_dash, marker_dot, marker_pixel };
Declaration
template<class BaseRenderer> class renderer_markers : public renderer_primitives<BaseRenderer> { public: typedef renderer_primitives<BaseRenderer> base_type; typedef BaseRenderer base_ren_type; typedef typename base_ren_type::color_type color_type; // . . . };
Creation
renderer_markers(base_ren_type& rbuf) : base_type(rbuf) {}
正如你所见,构造的过程和 renderer_primitives 是一样简单的。
Member Functions
marker 的所有函数都接受 x 、y 和半径作为参数,这些值以像素数来衡量。在pixel() 中,半径值没有任何作用。填充(fill)和边线(line)颜色都来自 renderer_primitives 基类。
void square(int x, int y, int r); void diamond(int x, int y, int r); void circle(int x, int y, int r); void crossed_circle(int x, int y, int r); void semiellipse_left(int x, int y, int r); void semiellipse_right(int x, int y, int r); void semiellipse_up(int x, int y, int r); void semiellipse_down(int x, int y, int r); void triangle_left(int x, int y, int r); void triangle_right(int x, int y, int r); void triangle_up(int x, int y, int r); void triangle_down(int x, int y, int r); void four_rays(int x, int y, int r); void cross(int x, int y, int r); void xing(int x, int y, int r); void dash(int x, int y, int r); void dot(int x, int y, int r); void pixel(int x, int y, int);
当然,有一些为图方便的函数存在,这些函数基本上就是使用 switch/case 来构造:
void marker(int x, int y, int r, marker_e type);
根据给定的类型来描画一个 marker。
template<class T> void markers(int n, const T* x, const T* y, T r, marker_e type);
根据给定的类型描画一系列的 marker,坐标存储在 x 和 y 两个数组中。
template<class T> void markers(int n, const T* x, const T* y, const T* r, marker_e type);
根据给定的类型描画一系列的 marker,坐标和半径值分别存储在 x、 y 和 r 三个数组中。
template<class T> void markers(int n, const T* x, const T* y, const T* r, const color_type* fill_colors, marker_e type);
根据给定的类型描画一系列的 marker,坐标、半径值以及颜色分别存储在 x、 y 、 r 和 fill_colors 四个数组中。
template<class T> void markers(int n, const T* x, const T* y, const T* r, const color_type* fc, const color_type* lc, marker_e type);
根据给定的类型描画一系列的 marker,坐标、半径值以及颜色分别存储在 x、 y 、 r 、fc 和 lc 等数组中。