• [翻译] AGG Reference 之 Basic Renderers(基础渲染器)


    翻译:唐风

    Rendering Buffer

    我们先从这里开始:在内存中开辟一块存储区,然后将它的内容以最简单的光栅格式写到文件中,也就是 PPM(Portable Pixel Map)格式。虽然 Windows 对这种格式并没有原生的支持,但很多图像浏览器和转换器都能使用这种格式,比如 IrfanView(www.irfanview.com)。所有 AGG 的控制台例子都使用了 P6 256 格式,也就是 RGB,每个字节代码一个颜色。现在假设我们将在下图所示的 RGB-buffer 内存区中工作:

    pixfmt_rgb24

    The first and the Simplest Example

    这是第一个例子:

    #include <stdio.h>
    #include <string.h>
    #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 的包含路径就行了。

    编译并运行它,你会看到现图所示的结果:

    tmp66E

    这个例子中几乎所有东西都“手工打制”的,使用的唯一一个现成的类是 rendering_buffer。这个类本身并不知道关于内存中像素格式的任何信息,它只是保存了一个数组,数组中的元素分别指向每行(像素的开头)。为申请和释放这块存储区是使用者的责任,你可以使用任何可行的方式来申请和释放内存,比如使用系统提供的 API 函数,或是简单的用内存分配器(译注:应该是new、delete、malloc、free等),甚至是直接使用一个静态数组。在上面这个例子中,因为每个像素要占用3个字节,所以我们申请了 width*height*3 字节的内存,在实际内存中是不存在“行”这种布局方式的,但这样做可以提高程序的性能,而且有时候在使用 API 的时候需要。

    Class rendering_buffer

    包含文件: agg_rendering_buffer.h

    rendering_buffer 这个类保存了指向每一行像素的指针,基本上这个类做的事就是这些了。看起来好像不是什么了不起的事,不过我们还是继续分析下去。这个类的接口和功能都很简单,它只是模板类 row_ptr_cache 的一个 typedef 而已:

    typedef row_ptr_cache<int8u> rendering_buffer;

    row_prt_cache 这个类的接口的功能如下:

    template<class T> 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<class RenBuf> 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<int8u>,所以这个值是以字节数来算的。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值不相同,那它会尽可能拷贝大一些的区域。一般来讲都用于拷贝相同大小区域。

    Two Modifications of the Example

    首先,在创建 rendering buffer 的对象时将 stride 取负值:

    agg::rendering_buffer rbuf(buffer, 
                               frame_width, 
                               frame_height, 
                               -frame_width * 3);

    那么结果将变成这样:

    tmp66F

    然后,我们试下将 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;
        return 0;
    }

    最后描画出来的结果是这样:

    tmp672

    最后一处修改是:

    // Attach to the part of the buffer, 
    // with 20 pixel margins at each side and negative 'stride'
    rbuf.attach(buffer + 
                frame_width * 3 * 20 +      // initial Y-offset
                3 * 20,                     // initial X-offset
                frame_width - 40,
                frame_height - 40,
                -frame_width * 3              // Negate the stride
                );   

    运行结果如下:

    tmp673

    在最后的一个例子里,我们只是使 stride 取了负值,而指针则和上个例子一样,仍然指向内存区的起启处。

    注意:Function write_ppm() writes the pixel map to a file. Hereafter it will be omited in this text, but duplicated when necessary in source code in the agg2/tutorial directory.。

    Pixel Format Renderers

    首先,我们创建一个更“文明”(译注:显得更高级一点)的例子:

    #include <stdio.h>
    #include <string.h>
    #include "agg_pixfmt_rgb24.h"
    
    enum
    {
        frame_width = 320,
        frame_height = 200
    };
    
    // [...write_ppm is skipped...]
    
    // Draw a black frame around the rendering buffer
    //--------------------------------------------------
    template<class Ren>
    void draw_black_frame(Ren& ren)
    {
        unsigned i;
        agg::rgba8 c(0,0,0);
        for(i = 0; i < ren.height(); ++i)
        {
            ren.copy_pixel(0,               i, c);
            ren.copy_pixel(ren.width() - 1, i, c);
        }
        for(i = 0; i < ren.width(); ++i)
        {
            ren.copy_pixel(i, 0,                c);
            ren.copy_pixel(i, ren.height() - 1, c);
        }
    }
    
    
    int main()
    {
        //--------------------
        // Allocate the buffer.
        // Clear the buffer, for now "manually"
        // Create the rendering buffer object
        // Create the Pixel Format renderer
        // 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);
    
        agg::pixfmt_rgb24 pixf(rbuf);
    
        unsigned i;
        for(i = 0; i < pixf.height()/2; ++i)
        {
            pixf.copy_pixel(i, i, agg::rgba8(127, 200, 98));
        }
    
        draw_black_frame(pixf);
        write_ppm(buffer, frame_width, frame_height, "agg_test.ppm");
    
        delete [] buffer;
        return 0;
    }

    这个例子看起来和前面的没什么不一样的,但其实他们差别很大,看看这个声明:

    agg::pixfmt_rgb24 pixf(rbuf);

    这里我们创建了一个底层的像素渲染对象(pixel rendering object)并将它附着到渲染内存区(rendering buffer)上,它是这样定义的:

    typedef pixel_formats_rgb24<order_rgb24> pixfmt_rgb24;

    类模板 pixel_formats_rgb24 掌握了内存中具体的像素格式信息。唯一的模板参数可以是 order_rgb24 或是 order_rgb23,它们定义了颜色字节(color channels)的顺序。

    与 rendering buffer 不同的是,这些类使用整型的像素坐标进行操作,因为它们知道怎么计算对于特定点 X 的偏移。你可能会说,如果在 rendering buffer 中保存像素的宽度值的话会更容易,但是在实践中会有很多限制。别忘了,像素宽度可能比一个字节还小,比如在打印机渲染高解析度的 B&W 图像的时候就是这样。因此,我们需要将这个功能分离出来,rendering_buffer 这个类就用于加速对“行”的访问,而 pixel format renderers 就负责如何解析“行”是什么。

    现在,AGG 里下面这些文件实现了各种不同的像素格式:

    • agg_pixfmt_gray8.h:每个字节表示一个像素灰度。这种像素格式允许你将它和 rgb24 或是 rgb23 中的某个颜色成分放在一起工作。它有两个模板参数:Step 和 Offset,为方便起见,还有下面这些 typedef :
      • typedef pixfmt_gray8_base<1, 0> pixfmt_gray8;
      • typedef pixfmt_gray8_base<3, 0> pixfmt_gray8_rgb24r;
      • typedef pixfmt_gray8_base<3, 1> pixfmt_gray8_rgb24g;
      • typedef pixfmt_gray8_base<3, 2> pixfmt_gray8_rgb24b;
      • typedef pixfmt_gray8_base<3, 2> pixfmt_gray8_bgr24r;
      • typedef pixfmt_gray8_base<3, 1> pixfmt_gray8_bgr24g;
      • typedef pixfmt_gray8_base<3, 0> pixfmt_gray8_bgr24b;
      • typedef pixfmt_gray8_base<4, 0> pixfmt_gray8_rgba32r;
      • typedef pixfmt_gray8_base<4, 1> pixfmt_gray8_rgba32g;
      • typedef pixfmt_gray8_base<4, 2> pixfmt_gray8_rgba32b;
      • typedef pixfmt_gray8_base<4, 3> pixfmt_gray8_rgba32a;
      • typedef pixfmt_gray8_base<4, 1> pixfmt_gray8_argb32r;
      • typedef pixfmt_gray8_base<4, 2> pixfmt_gray8_argb32g;
      • typedef pixfmt_gray8_base<4, 3> pixfmt_gray8_argb32b;
      • typedef pixfmt_gray8_base<4, 0> pixfmt_gray8_argb32a;
      • typedef pixfmt_gray8_base<4, 2> pixfmt_gray8_bgra32r;
      • typedef pixfmt_gray8_base<4, 1> pixfmt_gray8_bgra32g;
      • typedef pixfmt_gray8_base<4, 0> pixfmt_gray8_bgra32b;
      • typedef pixfmt_gray8_base<4, 3> pixfmt_gray8_bgra32a;
      • typedef pixfmt_gray8_base<4, 3> pixfmt_gray8_abgr32r;
      • typedef pixfmt_gray8_base<4, 2> pixfmt_gray8_abgr32g;
      • typedef pixfmt_gray8_base<4, 1> pixfmt_gray8_abgr32b;
      • typedef pixfmt_gray8_base<4, 0> pixfmt_gray8_abgr32a;
    • agg_pixfmt_rgb24.h, 每个像素占 3 个字节,有 RGB 或是 BGR 两种颜色顺序。定义了下面这些像素类型:
      • typedef pixel_formats_rgb24<order_rgb24> pixfmt_rgb24;
      • typedef pixel_formats_rgb24<order_bgr24> pixfmt_bgr24;
    • agg_pixfmt_rgb555.h, 每个像素占 15 bits。 每 5 个 bits 代表一种颜色成分,最低位没用到。
    • agg_pixfmt_rgb565.h, 每个像素占 16 bits。红色占 5 bits, 绿色占 6 bits, 蓝色 5 bits。
    • agg_pixfmt_rgba32.h, 每个像素占 4 个字节, RGB 再加 Alpha 通道值。下面有不同的颜色顺序类型:
      • typedef pixel_formats_rgba32<order_rgba32> pixfmt_rgba32;
      • typedef pixel_formats_rgba32<order_argb32> pixfmt_argb32;
      • typedef pixel_formats_rgba32<order_abgr32> pixfmt_abgr32;
      • typedef pixel_formats_rgba32<order_bgra32> pixfmt_bgra32;

    像素格式的类定义了它们原始的颜色空间和颜色类型,像下面这样:

    typedef rgba8 color_type;

    对于 pixfmt_gray8_nnn,这些都是 gray8,这种机制允许你写自己的像素和颜色格式,举个例子,HSV、CMYK 等等,AGG 的其它部分可以完全无误地和你自己定义的新的像素格式一起工作。

    注意

    区分清楚 color type 和 buffer 代表的原始的颜色空间是非常重要的。比如说,你可以假设你正在使用 CMYK 进行工作,但使用的是 RGB 的 buffer (你只需要写一个简单的转换函数,就可以从 CMYK结构 中创建 rgba8 的对象)。但这种转换只是一个近似,可能会因此出现颜色上的失真,因为有些 CMYK 中的颜色无法用 RGB 来表示,反之亦然。如果想完全使用某种颜色空间的表现能力,你可能要写一个为这种颜色空间写一个可以避免中间转换的 pixel format renderer 。

    Creation

    重要!

    像素格式相关的类并不进行任何的裁剪操作,也就是说直接使用这些类进行工作一般来说不太安全。裁剪是上层类的功能。采用这样设计的理由很简单:要让用户设计自定义的像素格式类越简单越好。像素格式可能会五花八门,但裁剪操作的代码一般都没有什么区别。

    pixel_formats_rgb24(rendering_buffer& rb);

    像素格式渲染器(pixecl format renderers)的构造函数需要一已经创建并良好初始化的rendering_buffer对象的引用。这个构建工作的开销很小,基本上只是初始化一个指针。

    Member Functions

    像素格式渲染器(pixecl format renderers)必须要实现以下这些接口:

    unsigned width()  const { return m_rbuf->width();  }
    unsigned height() const { return m_rbuf->height(); }

    返回内存区的宽和高(以像素数来衡量)

    color_type pixel(int x, int y);

    返回(x,y)坐标处的像素的颜色

    void copy_pixel(int x, int y, const color_type& c); 

    将带颜色的像素拷入缓存区中。如果是本身 RGB 像素格式,那么它就不考虑 rgba8 拷贝源中的存在的 alpha 通道。如果本身是 RGBA,那么它就简单地把所有值都拷贝过来,包括 R、G、B,以及 alpha 通道值。

    void blend_pixel(int x, int y, const color_type& c, int8u cover);

    这个函数将带颜色信息的像素 c 与缓存区(x,y)处的像素进行混合(blending)。现在我们来解释一下“混合”的概念。混合(blending)是抗锯齿(anti-aliasing)的关键特性。在 RGBA 的颜色空间中,我们使用 rgba8 结构体来代表颜色。这个结构体有一个数据成员 int8u a ,它就是 alpha 通道。不过,在这个函数里,我们还看到一个参数 cover ,表示像素的覆盖值大小,比如,这个像素被多边形所“覆盖”的部分的大小(译注:这涉及到亚像素精度,因为一个像素可以分为 256*256 份,所以这个像素并不一定全部被“覆盖”,详细可参考 AGG 对于亚像素的说明)。其实你可以把它看成是另一个 alpha(或者应该叫它Beta?:))。这么做有两个原因,首先,颜色类型(color type)不一定非要包含 alpha 值)。就算颜色类型带有 alpha 值,它的类型也不一定非要与抗锯齿算法中使用的颜色类型一致。假设你现在使用的是 "Hi-End" RGBA 颜色空间,这种颜色空间使用4个取值范围是[0,1]浮点型来表示,alpha 通道值也使用浮点数————对于这种情况来说,混合时使用一个byte实在太少了,但在去锯齿时却非常够用。所以,cover 值就是为去锯齿而使用的一个统一的备用 alpha 值。在大部分情况来说,用 cover_type 来定义它,但在光栅化处理器(rasterizers)中是直接显示地使用 int8u 类型的。这是故意这么定义的,因为如果需要加强 cover_type 的能力时,会使得所有已经存在的像素格式光栅化处理器(pixel format rasterizres)变得与 cover_type 不兼容。它们确实是不兼容的,在进行颜色混合时,如果中间值使用 32-bit 值来暂存的话,那么最大只能使用 8-bit 的覆盖值(coverage value)和 8-bit 的 alpha 值(alpha) 。如果使用 16-bit 的值的话,就要用 64-bit 的中间值暂存,这对于 32-bit 的平台来说会有非常昂贵的开销。

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

    使用某种颜色描画一条水平或是垂直的线。

    void blend_hline(int x, int y, unsigned len, const color_type& c, int8u cover); 
    void blend_vline(int x, int y, unsigned len, const color_type& c, int8u cover);

    采用混合颜色的模式描画一带某种颜色的水平(或垂直线)线。之所以要分开 copy 和 blend 两个版本,是因为考虑到效率问题。虽然可以使用一个 if/else (其实在 blend 版的描画函数中就有)来区分,但对于某些场合,比如要描画很多小型标识(markers)时,这会很影响效率,这种场景在不同的散点图描画程序(scatter plot applicatioin)中常常遇到。

    void blend_solid_hspan(int x, int y, unsigned len, 
                           const color_type& c, const int8u* covers);
    void blend_solid_vspan(int x, int y, unsigned len, 
                           const color_type& c, const int8u* covers);

    混合描画一条水平或是垂直的 solid-color 的 span, Span与 hline 和 vline 几乎是一样的,但它拥有一个存有 coverage value 的数组。这两个函数在渲染实心的去锯齿多边形时会用到。

    void blend_color_hspan(int x, int y, unsigned len, 
                           const color_type* colors, const int8u* covers);
    void blend_color_vspan(int x, int y, unsigned len, 
                           const color_type* colors, const int8u* covers);
    

    混合描画水平或是垂直的颜色 span ,这两个函数用于不同的 span 产生器中,比如说 gradient,image,patterns,Gouraud interpolation 等等。函数接受一个颜色数组参数,这个颜色数组必须与所使用的像素格式兼容。比如说,所有 AGG 中已经有的 RGB 像素格式都与 rgb8 类型是兼容的。 covers 参数是一个 coverage value 的数组,这与 blend_solid_hspan 中的是一样的。这是参数可选,可以设置为 0 。

    下面这个例子是描画阳光的光谱。rgba 类包含有 4 个浮点数的颜色部分(包括alpha),这个类有一个静态函数 from_wavelength ,以及相应的构造函数。rgba8 可以用 rgba 来构造(在 AGG 中这是一个常见的策略,也就是任何的颜色类型都可以用 rgba 来构造)。

    #include <stdio.h>
    #include <string.h>
    #include "agg_pixfmt_rgb24.h" 
    
    enum
    {
        frame_width = 320,
        frame_height = 200
    }; 
    
    // [...write_ppm is skipped...] 
    int main()
    {
        //--------------------
        // Allocate the buffer.
        // Clear the buffer, for now "manually"
        // Create the rendering buffer object
        // Create the Pixel Format renderer
        // Create one line (span) of type rgba8.
        // Fill the buffer using blend_color_span
        // 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); 
        agg::pixfmt_rgb24 pixf(rbuf); 
        agg::rgba8 span[frame_width]; 
        unsigned i;
        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.blend_color_hspan(0, i, frame_width, span, 0);
        } 
        write_ppm(buffer, frame_width, frame_height, "agg_test.ppm"); 
        delete [] buffer;
        return 0;
    } 

    运行结果如下:

    tmp6D1

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

    这是运行结果:

    tmp6D2

    你看到了,我们是用白色来清空主缓存的,如果我们将:

    memset(buffer, 255, frame_width * frame_height * 3); 

    修改成:

    memset(buffer, 0, frame_width * frame_height * 3);

    那么结果就像下面这样:

    tmp6D3

    换句话说,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 等数组中。

  • 相关阅读:
    javascript基础全等号运算符
    javascript 使用ScriptX实现打印
    跨服务器与本地服务器不同数据库的SQL操作语句
    ASP.NET网络上实现单点登录
    FGMap API 帮助文档
    基于ArcEngine写的GoogleMap地图切割程序
    基于SuperMap Objects写的GoogleMap地图切割程序(三)
    使用SuperSocket开发联网斗地主(四):出牌
    JAVA创建对象方法
    Mysql 外键约束
  • 原文地址:https://www.cnblogs.com/muxue/p/1751094.html
Copyright © 2020-2023  润新知