AGG 中文手册
1 渲染内存 Rendering Buffer
我们先从这里开始:在内存中开辟一块存储区,然后将它的内容以最简单的光栅格式写到文件中,也就
是 PPM(Portable Pixel Map)格式。虽然 Windows 对这种格式并没有原生的支持,但很多图像浏览器和
转换器都能使用这种格式,比如 IrfanView(www.irfanview.com)。所有 AGG 的控制台例子都使用了 P6
256 格式,也就是 RGB,每个字节代码一个颜色。现在假设我们将在下图所示的 RGB-buffer 内存区中工
作:
1.1 第一个简单例子
#include
#include
#include "agg_rendering_buffer.h"
enum
{
frame_width = 320,
frame_height = 200
};
// Writing the buffer to a .PPM file, assuming it has
// RGB-structure, one byte per color component
//--------------------------------------------------
bool write_ppm(const unsigned char* buf,
unsigned width,
unsigned height,
const char* file_name)
{
FILE* fd = fopen(file_name, "wb");
if(fd)
{
fprintf(fd, "P6 %d %d 255 ", width, height);
fwrite(buf, 1, width * height * 3, fd);
fclose(fd);
return true;
}
return false;
}
// Draw a black frame around the rendering buffer, assuming it has
// RGB-structure, one byte per color component
//--------------------------------------------------
void draw_black_frame(agg::rendering_buffer& rbuf)
{
unsigned i;
for(i = 0; i < rbuf.height(); ++i)
{
unsigned char* p = rbuf.row_ptr(i);
*p++ = 0; *p++ = 0; *p++ = 0;
p += (rbuf.width() - 2) * 3;
*p++ = 0; *p++ = 0; *p++ = 0;
}
memset(rbuf.row_ptr(0), 0, rbuf.width() * 3);
memset(rbuf.row_ptr(rbuf.height() - 1), 0, rbuf.width() * 3);
}
int main()
{
// In the first example we do the following:
//--------------------
// Allocate the buffer.
// Clear the buffer, for now "manually"
// Create the rendering buffer object
// Do something simple, draw a diagonal line
// Write the buffer to agg_test.ppm
// Free memory
unsigned char* buffer = new unsigned char[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);
unsigned i;
for(i = 0; i < rbuf.height()/2; ++i)
{
// Get the pointer to the beginning of the i-th row (Y-coordinate)
// and shift it to the i-th position, that is, X-coordinate.
//---------------
unsigned char* ptr = rbuf.row_ptr(i) + i * 3;
// PutPixel, very sophisticated, huh? :)
//-------------
*ptr++ = 127; // R
*ptr++ = 200; // G
*ptr++ = 98; // B
}
draw_black_frame(rbuf);
write_ppm(buffer, frame_width, frame_height, "agg_test.ppm");
delete [] buffer;
return 0;
}
在这个例子中,你甚至不需要链接任何的 AGG 的代码文件,你只需要在你的编译器命令行中设置好
AGG 的包含路径就行了。编译并运行它,你会看到现图所示的结果:
这个例子中几乎所有东西都“手工打制”的,使用的唯一一个现成的类是 rendering_buffer。这个类
本身并不知道关于内存中像素格式的任何信息,它只是保存了一个数组,数组中的元素分别指向每行(像
素的开头)。为申请和释放这块存储区是使用者的责任,你可以使用任何可行的方式来申请和释放内存,
比如使用系统提供的 API 函数,或是简单的用内存分配器(译注:应该是 new、delete、malloc、free 等),
甚至是直接使用一个静态数组。在上面这个例子中,因为每个像素要占用 3 个字节,所以我们申请了
width*height*3 字节的内存,在实际内存中是不存在“行”这种布局方式的,但这样做可以提高程序的性
能,而且有时候在使用 API 的时候需要。
1.2 Class rendering_buffer
包含文件: agg_rendering_buffer.h
rendering_buffer 这个类保存了指向每一行像素的指针,基本上这个类做的事就是这些了。看起来
好像不是什么了不起的事,不过我们还是继续分析下去。这个类的接口和功能都很简单,它只是模板类
row_ptr_cache 的一个 typedef 而已:
typedef row_ptr_cache rendering_buffer;
row_prt_cache 这个类的接口的功能如下:
template class row_ptr_cache
{
public:
row_ptr_cache();
row_ptr_cache(T* buf, unsigned width, unsigned height, int stride);
void attach(T* buf, unsigned width, unsigned height, int stride);
T* buf();
const T* buf() const;
unsigned width() const;
unsigned height() const;
int stride() const;
unsigned stride_abs() const;
T* row_ptr(int, int y, unsigned);
T* row_ptr(int y);
const T* row_ptr(int y) const;
row_data row (int y) const;
T const* const* rows() const;
template void copy_from(const RenBuf& src);
void clear(T value)
};
这个类的实现里没有使用断言或是验证的措施,所以,使用者有责任在用这个类对象时正确地将它初
始化到实际的内存块中,这可以在构造函数中完成,也可以使用 attach() 函数。它们的参数解释如下:
buf — 指向内存块的指针。
width — 以像素为单位表示的图像宽度。rendering buffer(渲染内存区)并不知道像
素格式和每个像素在内存中大小等信息。这个值会直接存储在 m_width 这个成员变量中,使用
width() 函数就可以获取它的值。
height — 以像素为单位表示的图像高度(同样也是行数)
stride — Stride(大步幅,-_-; 不知道怎么翻了„„),也就是用类型 T 来度量的一
行的长度。 rendering_buffer 是一个 typedef,也就是 row_prt_cache,所以这个值是
以字节数来算的。Stride 决定内存中每一行的实现长度。如果这个值是负的,那么 Y 轴的方向就
是反过来的。也就是说 Y 等 0 的点是是内在块的最后一行的。Y == height - 1 则是第一行。
stride 的绝对值也很重要,因为它让你可以方便地操作内存中的“行”(就像 windows 中的
BMP)。另外,这个参数允许使用者可以像操作整个内存区块一样,操作其中任意一个“矩形”内
存区。
attach()函数会改变缓冲区或是它的参数,它自己会为“行指针”数组重新分配内存,所以你可以在任何
时候调用它。当(且仅当)新的 height 值比之前使用过的最大的 height 值还要大时,它才会重新申请内
存。
构造的这个对象的开销仅仅是初始化它的成员变量(设置为 0),attach()的开销则是分配
sizeof(ptr)*height 个字节的内存,然后将这些指针指向对应的“行”。
最常使用的函数是 row_prt(y),这个函数只是简单地返回指向第 y 函数指针,这个指针指向的位置已
经考虑到了 Y 轴的方向了。
注意:
渲染内存区(rendering buffer)并不管任何裁减或是边界检查的事,这是更高层的类的责任。
buf(), width(), height(), stride(), stride_abs() 这些函数的意义显而易见,就不解释了。
copy_from()函数会将其它内存的内容拷贝至“本”内存中。这个函数是安全的,如果(两者的)width
和 height 值不相同,那它会尽可能拷贝大一些的区域。一般来讲都用于拷贝相同大小区域。
1.3 Two Modifications of the Example
首先,在创建 rendering buffer 的对象时将 stride 取负值:
agg::rendering_buffer rbuf(buffer,
frame_width,
frame_height,
-frame_width * 3);
那么结果将变成这样:
然后,我们试下将 rendering buffer 附着(attach)到被分配的内存区的某部分。这个修改会使得
rendering buffer 两次附着在同一块内存区上,第一次是整个被分配的内存区域,第二次是其中的一部
分:
int main()
{
unsigned char* buffer = new unsigned char[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);
// Draw the outer black frame
//------------------------
draw_black_frame(rbuf);
// Attach to the part of the buffer,
// with 20 pixel margins at each side.
rbuf.attach(buffer +
frame_width * 3 * 20 + // initial Y-offset
3 * 20, // initial X-offset
frame_width - 40,
frame_height - 40,
frame_width * 3 // Note that the stride
// remains the same
);
// Draw a diagonal line
//------------------------
unsigned i;
for(i = 0; i < rbuf.height()/2; ++i)
{
// Get the pointer to the beginning of the i-th row (Y-coordinate)
// and shift it to the i-th position, that is, X-coordinate.
//---------------
unsigned char* ptr = rbuf.row_ptr(i) + i * 3;
// PutPixel, very sophisticated, huh? :)
//-------------
*ptr++ = 127; // R
*ptr++ = 200; // G
*ptr++ = 98; // B
}
// Draw the inner black frame
//------------------------
draw_black_frame(rbuf);
// Write to a file
//------------------------
write_ppm(buffer, frame_width, frame_height, "agg_test.ppm");
delete [] buffer;