代码改变世界

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

2011-04-27 12:56  CoolJie  阅读(2272)  评论(0编辑  收藏  举报

线段生成器(Span Generator)

我们前面举的例子使用的都是简单的单一实色,如蓝色的圆、黑色的线等。这是因为在例子里我们一直使用renderer_scanline_aa_solid或render_scanlines_aa_solid。

上篇文章的渲染器一节中除了renderer_scanline_aa_solid外,还提到有一个renderer_scanline_aa,这里再写一遍它的声明:

template <class BaseRenderer, class SpanAllocator, class SpanGenerator>
class renderer_scanline_aa;

另外,还有一个函数版本:

template<class spanallocator, spangenerator baserenderer, scanline, rasterizer,>
void render_scanlines_aa(Rasterizer& ras, Scanline& sl, BaseRenderer& ren,
SpanAllocator& alloc, SpanGenerator& span_gen);

renderer_scanline_aa (还有一个兄弟版本renderer_scanline_bin)可以按指定的图案或不同的颜色(如渐变)填充顶点源里的多边形。其中的模板参数 SpanAllocator用于准备span,我们直接使用agg::span_allocator就行。这里的SpanGenerator就是本节要说的线段生成器,它决定了最终用什么东西填到rendering_buffer里。

线段生成器品种很多,常用的在致可以分成图案类和色彩类两大部分:图案类线段生成器使用已有图像作为span来源;色彩类线段生成器使用指定的颜色作为span来源。

图案类线段生成器

头文件
    #include <agg_span_image_filter_gray.h>
    #include <agg_span_image_filter_rgb.h>
    #include <agg_span_image_filter_rgba.h>
    #include <agg_span_pattern_gray.h>
    #include <agg_span_pattern_rgb.h>
    #include <agg_span_pattern_rgba.h>
类型
template<class source, interpolator&>
        span_image_filter_[gray|rgb|rgba]
    template<class source, interpolator>
        span_image_filter_[gray|rgb|rgba]_2x2
    template<class source, interpolator>
        span_image_filter_[gray|rgb|rgba]_bilinear
    template<class source, interpolator>
        span_image_filter_[gray|rgb|rgba]_bilinear_clip
    template<class source, interpolator>
        span_image_filter_[gray|rgb|rgba]_nn
    template<class source, interpolator>
        span_image_resample_[gray|rgb|rgba]
    template<class source>
        span_image_resample_[gray|rgb|rgba]_affine
    template<class source>
        class agg::span_pattern_[gray|rgb|rgba]

上面这些线段生成器类的模板参数都比较相似:Source用于指定图像来源,可以是PixelFormat renderer或agg::image_accessor_clip(由不同的线段生成器类决定);Interpolator是一种插值器,用于填充图像间隙。我们先写一段示例代码,先看一下线段生成器的作用,也为后面的各种实验做准备。

示例代码,使用span_image_filter_rgb_bilinear_clip

还是基于这个代码(http://www.cppprog.com/2009/0816/146.html),加入下面的头文件

#include "platform agg_win32_bmp.h win32"
#include "agg_span_allocator.h"
#include "agg_span_image_filter_rgb.h"

在on_draw()方法的最后加上下面这些代码

...
    // 以图像填充
    agg::pixel_map pm_img;
    if(pm_img.load_from_bmp("d:\\spheres.bmp"))
    {
        // pm_img里的图案作为填充来源
        agg::rendering_buffer rbuf_img(
            pm_img.buf(),
            pm_img.width(), pm_img.height(),
            -pm_img.stride());
        agg::pixfmt_bgr24 pixf_img(rbuf_img);// 我用的bmp是24位的
        // 线段分配器
        typedef agg::span_allocator span_allocator_type;//分配器类型
        span_allocator_type span_alloc; // span_allocator
        // 插值器
        typedef agg::span_interpolator_linear<> interpolator_type; //插值器类型
        agg::trans_affine img_mtx; // 变换矩阵
        interpolator_type ip(img_mtx); // 插值器
        // 线段生成器
        typedef agg::span_image_filter_rgb_bilinear_clip span_gen_type; // 这个就是Span Generator
            
        span_gen_type span_gen(pixf_img, agg::rgba(0,1,0), ip);
        // 组合成渲染器
        agg::renderer_scanline_aa<
            renderer_base_type,
            span_allocator_type,
            span_gen_type
        > my_renderer(renb, span_alloc, span_gen);
        // 插值器的矩阵变换
        img_mtx.scale(0.5);
        img_mtx.translate(40,40);
        img_mtx.invert(); //注意这里
        // 用我们的渲染器画圆
        ras.add_path(ell);
        agg::render_scanlines(ras,sl,my_renderer);
    }

其中的d:\\spheres.bmp是我预先放在D盘里的24位bmp图像,作为填充的来源。

显示效果:

20090829103651165

  1. 在第19行的span_gen_type之前,所有的事情都在为定义这个线段生成器做准备。
  2. 首先是用pixel_map读取bmp文件,然后生成rendering_buffer和pixfmt_bgr24作为这个线段生成器的"Source"。
  3. 然后是线段分配器,这个没什么特殊要求的话用span_allocator就可以了。
  4. 接着是插值器类型,插值器也有几个类型(后面会介绍),它的构造函数需要一个变换矩阵对象,于是我们得为它装备一个。
  5. 现在,终于可以组合成一个我们的线段生成器了。这里使用的是span_image_filter_rgb_bilinear_clip,它的Source是PixelFormat Renderer,如本例的pixfmt_bgr24。
  6. span_image_filter_rgb_bilinear_clip的构造函数有三个参数,分别是Source对象,填充来源范围之外的颜色和插值器对象。
  7. 我们可以改变插值器的矩阵来变换填充图像,象这里的img_mtx.scale(0.5)和img_mtx.translate(40,40)。要注意的是,插值器的矩阵运算是从目标位置向源位置计算的(即根据目标位置变换得到对应的填充源位置),所以想对源图像变换的话,要记得最后调用矩阵的invert()方法取反。
  8. 最后,画圆。由于ell是ellipse对象,没有被conv_stroke转换的ellipse对象是实心的(多边形而不是多义线),于是填充之。

插值器Interpolator

插值器的作用是连接目标位置和源位置,比如要填充一个8*8的图形,对应的填充源是一个4*4的图像,一种简单的线性插值器就要根据目标的位置线性计算得到源对应的位置,如目标点(4,4)、(4,5)、(5,4)、(5,5)这几个位置点对应到源的(2,2)点上。

头文件
#include <agg_span_interpolator_linear.h>
#include <agg_span_interpolator_persp.h>
#include <agg_span_interpolator_trans.h>
类型
template<class Transformer = trans_affine, unsigned SubpixelShift = 8>
      class agg::span_interpolator_linear
    template<class Transformer = trans_affine, unsigned SubpixelShift = 8>
      class agg::span_interpolator_linear_subdiv
    template<unsigned SubpixelShift = 8>
      class agg::span_interpolator_persp_exact
    template<unsigned SubpixelShift = 8>
      class agg::span_interpolator_persp_lerp
    template<class Transformer, unsigned SubpixelShift = 8>
      class agg::span_interpolator_trans

不同的插值器对于不同的变换有各自的优势,对于大部分应用来说,span_interpolator_linear是比较简单高效的。

实验代码,使用span_interpolator_persp_lerp

上面的演示代码里的interpolator_type改成span_interpolator_persp_lerp,这是一个透视变换的插值器,输入为源四个角的坐标和目标的四个角上的坐标。

...
    // 插值器
    //typedef agg::span_interpolator_linear<> interpolator_type; //插值器类型
    //agg::trans_affine img_mtx; // 变换矩阵不需要了
    //interpolator_type ip(img_mtx); // 插值器
    typedef agg::span_interpolator_persp_lerp<> interpolator_type; //插值器类型
    const double offset = 50; // 偏移
    const double scale = 0.5; // 缩放倍数
    double src[8]={offset+0,offset+0,
        offset+pm_img.width()*scale,offset+0,
        offset+pm_img.width()*scale,offset+pm_img.height()*scale,
        offset+0,offset+pm_img.height()*scale
    }; //源四角坐标,按偏移和缩放倍数改变一哈
    double dst[8]={0,0,
        pm_img.width(),0,
        pm_img.width()+100,pm_img.height(),
        100,pm_img.height()-100
    }; //目标四角坐标,左右乱扯一哈
    interpolator_type ip(src,dst);
    ...

最后别忘了把img_mtx相关代码注释掉:

//img_mtx.scale(0.5);
//img_mtx.translate(40,40);
//img_mtx.invert(); //注意这里
显示效果:

20090829103651734

变换器Transformer

注意一下我们前面用的span_interpolator_linear,以及曾经使用过的conv_transform,默认的模板参数Transformer为trans_affine。我们已经知道trans_affine是一个2*3的变换矩阵。在AGG中,变换器不仅仅只有矩阵,这里将要介绍的就是其它的一些变换器。

头文件
#include <agg_trans_affine.h>
#include <agg_trans_bilinear.h>
#include <agg_trans_single_path.h>
#include <agg_trans_double_path.h>
#include <agg_trans_perspective.h>
#include <agg_trans_viewport.h>
#include <agg_trans_warp_magnifier.h>
类型
agg::trans_affine
agg::trans_bilinear
agg::trans_single_path
agg::trans_double_path
agg::trans_perspective
agg::trans_viewport
agg::trans_warp_magnifier
实验代码,使用trans_warp_magnifier

同样把示例代码中的插值器部分改成下列代码

...
    // 插值器
    //typedef agg::span_interpolator_linear<> interpolator_type; //插值器类型
    //agg::trans_affine img_mtx; // 变换矩阵
    //interpolator_type ip(img_mtx); // 插值器
    typedef agg::span_interpolator_trans<
        agg::trans_warp_magnifier // 使用trans_warp_magnifier
    > interpolator_type; //插值器类型
    agg::trans_warp_magnifier mag;
    interpolator_type ip(mag);
    mag.magnification(0.5);
    mag.center(100,100);
    mag.radius(50);
    ...

建议把后面的ras.add_path(ell)改成ras.add_path(ccell),画得大一点好看清效果,呵呵

显示效果

20090925023817592

图像访问器Image Accessor

也许有不少同学看到开头的线段生成器一节时,已经尝试修改示例代码中的span_image_filter_rgb_bilinear_clip了(比如改成span_image_filter_rgb_bilinear)。不过编译时会出错,这是因为大部分的线段生成器类接受的Source模板不是 PixelFormat Renderer,而是Image Accessor即图像存取器。

头文件
#include <agg_image_accessors.h>
类型
template<class PixFmt>
      class agg::image_accessor_clip // 图像以外的地方用指定颜色填充
    template<class PixFmt>
      class agg::image_accessor_clone // 图像以外的地方以图像边缘填充
    template<class PixFmt>
      class agg::image_accessor_no_clip // 图像以外不可读取,否则引发异常
    template<class PixFmt, class WrapX, class WrapY>
      class agg::image_accessor_wrap // 平铺图像,平铺方式由WrapX和WrapY指定
实验代码

示例代码中的span_image_filter_rgb_bilinear_clip部分改成下面的代码

...
    // 线段生成器
    //typedef agg::span_image_filter_rgb_bilinear_clip span_gen_type; // 这个就是Span Generator
     
    //span_gen_type span_gen(pixf_img, agg::rgba(0,1,0), ip);
    // 图像访问器
    typedef agg::image_accessor_clone image_accessor_type;
    image_accessor_type    accessor(pixf_img);
    // 使用span_image_filter_rgb_bilinear
    typedef agg::span_image_filter_rgb_bilinear<
        image_accessor_type,
        interpolator_type > span_gen_type;
    span_gen_type span_gen(accessor, ip);
    ...

建议把后面的ras.add_path(ell)改成ras.add_path(ccell)

显示效果

20090829103651359

image_accessor_wrap类要指定WrapX和WrapY,可选的有:

wrap_mode_reflect
wrap_mode_reflect_auto_pow2
wrap_mode_pow2
wrap_mode_repeat
wrap_mode_repeat_auto_pow2
wrap_mode_repeat_pow2

比如我们把本例中的image_accessor_type定义改成

//typedef agg::image_accessor_clone>agg::pixfmt_bgr24> image_accessor_type;
    typedef agg::image_accessor_wrap>agg::pixfmt_bgr24,
        agg::wrap_mode_reflect,agg::wrap_mode_repeat> image_accessor_type;
显示效果是

20090829103651927

(为了突出效果,用矩阵img_mtx把源缩小了)

图像过滤器(Image Filter)

在一些线段生成器里,比如span_image_filter_[gray|rgb|rgba],span_image_resample_[gray|rgb|rgba]等类,它们的构造函数还有一个“const image_filter_lut &filter”参数,这个参数用于变换图像的像素值。它们的名称都以image_filter作为前缀,AGG中称为Image Filter(图像过滤器)。

头文件
#include <agg_image_filters.h>
类型
image_filter_bilinear;
    image_filter_blackman;
    image_filter_blackman[36|64|100|144|196|256];
    image_filter_kaiser;
    image_filter_lanczos;
    image_filter_lanczos[36|64|100|144|196|256];
    image_filter_mitchell;
    ...还有很多呢...
实验代码

把上面的span_image_filter_rgb_bilinear改成span_image_resample_rgb_affine

...
    //typedef agg::image_accessor_clone<agg::pixfmt_bgr24> image_accessor_type;
    typedef agg::image_accessor_wrap<agg::pixfmt_bgr24,
        agg::wrap_mode_reflect,agg::wrap_mode_repeat> image_accessor_type;
    image_accessor_type    accessor(pixf_img);
                
    //typedef agg::span_image_filter_rgb_bilinear<
    //    image_accessor_type,
    //    interpolator_type > span_gen_type;
    //span_gen_type span_gen(accessor, ip);
    typedef agg::span_image_resample_rgb_affine<image_accessor_type> span_gen_type;
    span_gen_type span_gen(accessor, ip, agg::image_filter_sinc36());
    ...
显示效果

20090829103651860

色彩类线段生成器

头文件
#include <agg_span_solid.h>
    #include <agg_span_gradient.h>
    #include <agg_span_gradient_alpha.h>
    #include <agg_span_gouraud_gray.h>
    #include <agg_span_gouraud_rgba.h>
类型
template<class ColorT>
        class agg::span_solid;
    template<class ColorT, class Interpolator, class GradientF, class ColorF>
        class agg::span_gradient;
    template<class ColorT, class Interpolator, class GradientF, class AlphaF>
        class agg::span_gradient_alpha;
    template<class ColorT>
        class agg::span_gouraud_[gray|rgba];

如果你是从上面的图案类线段生成器看到这里的话,那么色彩类的就相对简单得多了。同样,我们先写一个示例代码,也方便以后做实验。

示例代码

同样基于这个代码(http://www.cppprog.com/2009/0816/146.html),加入下面的头文件

#include "agg_span_allocator.h"
    #include "agg_span_gradient.h"

在on_draw()方法的最后加上下面这些代码

// 色彩类线段生成器demo
    // 线段分配器
    typedef agg::span_allocator<agg::rgba8> span_allocator_type;//分配器类型
    span_allocator_type span_alloc; // span_allocator
    // 插值器
    typedef agg::span_interpolator_linear<> interpolator_type; //插值器类型
    agg::trans_affine img_mtx; // 变换矩阵
    interpolator_type ip(img_mtx); // 插值器
    // 渐变方式
    typedef agg::gradient_radial_focus gradientF_type;
    gradientF_type grF(1, 0.1, 0.5);
    // 渐变颜色
    typedef agg::gradient_linear_color<agg::rgba8> colorF_type;
    colorF_type colorF(agg::rgba(1,1,1), agg::rgba(0,0,1));//白色到蓝色
    // 线段生成器
    typedef agg::span_gradient<agg::rgba8,
        interpolator_type,
        gradientF_type,
        colorF_type> span_gen_type;
    span_gen_type span_gen(ip,grF,colorF,0,50);
    // 组合成渲染器
    agg::renderer_scanline_aa<
        renderer_base_type,
        span_allocator_type,
        span_gen_type
    > my_renderer(renb, span_alloc, span_gen);
    // 矩阵变换
    img_mtx.translate(100,100);
    img_mtx.invert(); //注意这里
    // 使用我们的渲染器画圆
    ras.add_path(ell);
    agg::render_scanlines(ras,sl,my_renderer);
显示效果

20090829103651519

  1. span_gradient是一个模板类(费话,AGG的大部分类都是),前两个模板参数ColorT和Interpolator一个是颜色类型一个是插值器没什么好说的了。关键是后面两个:GradientF用于指定渐变的方式,如水平渐变、垂直渐变、圆形渐变等;ColorF指定渐变的颜色。
  2. 渐变方式选择了agg::gradient_radial_focus,这是一个可指定焦点的圆形渐变方式。
  3. 渐变色使用agg::gradient_linear_color设置起始颜色和终止颜色。
  4. span_gradient的构造函数前三个分别是插值器、渐变方式、渐变颜色,后面两个数字表示渐变的起始和终止位置。不同的渐变方式起始和终止位置的意义是不同的,如在圆形填充里起始和终止表示中心和边缘;水平渐变则表示从左到右。
  5. 插值器的矩阵变换把这个渐变中心移到(100,100)点上,同样要记得调用invert()方法反转。

渐变颜色

前面说到span_gradient的模板参数ColorF指定渐变的颜色,我们使用的是gradient_linear_color,那么有哪些类可以作为ColorF呢?

AGG文档里说只要实现了“operator []()”和“size()”的类就可以作为ColorF,嗯,std::vector<rgba8>也行哈。

实验代码,使用std::vector<rgba8>实现多颜色渐变

示例代码的渐变颜色部分改成这样:

...
    // 渐变颜色
    //typedef agg::gradient_linear_color<agg::rgba8> colorF_type;
    //colorF_type colorF(agg::rgba(1,1,1), agg::rgba(0,0,1));//白色到蓝色
    typedef std::vector<agg::rgba8> colorF_type;
    colorF_type colorF(256);
    agg::rgba begin_color(1,1,1), mid_color(1,0,0), end_color(0,0,1);
    for(int i=0; i<128; i++) //前128从白到红
        colorF[i] = begin_color.gradient(mid_color,i/128.0);
    for(int i=0; i<128; i++) //后128从红到蓝
        colorF[i+128] = mid_color.gradient(end_color,i/128.0);
显示效果 

20090829103651306

这里指定的vector容量256指的是用于的颜色,想要更平滑过渡的话可以使用更多的颜色数。

除了用vector实现多种颜色的渐变外,我们还可以用AGG提供的一个gradient_lut类,用它可以方便很多。

gradient_lut的头文件是#include <agg_gradient_lut.h>

类声明为
template<class ColorInterpolator, unsigned ColorLutSize = 256>
        class agg::gradient_lut

其中的ColorInterpolator负责生成两种颜色的中间色,直接使用AGG自带的agg::color_interpolator就行。

通过gradient_lut的add_color(double offset, color_type color)方法添加多种颜色,其中的offset表示添加的颜色所处的偏移位置,取值为0~1之间。

添加完所有颜色后调用build_lut()方法让gradient_lut内部生成颜色数组。

实验代码,使用gradient_lut实现多颜色渐变

示例代码的渐变颜色部分改成这样:

...
    // 渐变颜色
    //typedef agg::gradient_linear_color<agg::rgba8> colorF_type;
    //colorF_type colorF(agg::rgba(1,1,1), agg::rgba(0,0,1));//白色到蓝色
    typedef agg::gradient_lut<
        agg::color_interpolator<agg::rgba8>
    > colorF_type;
    colorF_type colorF;
    colorF.add_color(0, agg::rgba(1,1,1));
    colorF.add_color(0.2, agg::rgba(1,0,0));
    colorF.add_color(0.4, agg::rgba(0,1,0));
    colorF.add_color(0.8, agg::rgba(0,0,1));
    colorF.build_lut();
    ...
显示效果

20090829103651527

渐变方式

除本例中的gradient_radial_focus以外,AGG还提供了很多渐变方式,它们都定义在#include <agg_span_gradient.h>头文件之中。

修改演示代码的渐变方式是很简单的,如:

...
    // 渐变方式
    //typedef agg::gradient_radial_focus gradientF_type;
    //gradientF_type grF(1, 0.1, 0.5);
    typedef agg::gradient_x gradientF_type;
    gradientF_type grF;
    ...
这里是其中的一部分AGG自带渐变方式以及显示效果
  gradient_x  gradient_y  gradient_diamond
20090829103651338  20090829103651808  20090829103651545
  gradient_xy  gradient_conic  gradient_radial
  20090829103651625  20090829103651939  20090829103651230
本节的最后,再介绍一下其它几个色彩类的线段生成器
  • span_solid没什么好说的,实色填充而已
  • span_gradient_alpha是透明度渐变,参数和span_gradient差不多,区别是ColorF改成了AlphaF,“operator []()”返回值也由颜色结构变为的透明度数值。
  • span_gouraud_rgba 高氏三角着色,需指定三角形的三个顶点和三种颜色,用法见下例
// 色彩类线段生成器demo
    // 线段分配器
    typedef agg::span_allocator span_allocator_type;//分配器类型
    span_allocator_type span_alloc; // span_allocator
     
    typedef agg::span_gouraud_rgba span_gen_type;
    span_gen_type span_gen;
    //三种颜色
    span_gen.colors(
        agg::rgba(1,0,0),
        agg::rgba(0,1,0),
        agg::rgba(0,0,1)
        );
    //三角形三个顶点
    span_gen.triangle(
        100,50,
        130,125,
        70,125,0
        );
    agg::renderer_scanline_aa<
        renderer_base_type,
        span_allocator_type,
        span_gen_type
    > my_renderer(renb, span_alloc, span_gen);
     
    ras.add_path(ell);
    agg::render_scanlines(ras,sl,my_renderer);
显示效果

20090829103651826

组合类线段生成器

头文件
#include <agg_span_converter.h>
类型
template<class SpanGenerator, class SpanConverter>
    class agg::span_converter;

span_converter的作用是组合两种生成器,比如先由图案类线段生成器产生图案,然后由色彩类线段生成器产生半透明色叠加在图案上。

下面的演示代码演示了怎样组合span_image_filter_rgb_bilinear_clip和span_gradient_alpha两种生成器

演示代码,同样基于这个代码,加入下面的头文件
#include "agg_span_allocator.h"
#include "agg_span_gradient_alpha.h"
#include "agg_span_converter.h"
#include "span_image_filter_rgb_bilinear_clip.h"

在on_draw()方法的最后加上下面这些代码

agg::pixel_map pm_img;
    if(pm_img.load_from_bmp("d:\\spheres.bmp"))
    {
        // pm_img里的图案作为填充来源
        agg::rendering_buffer rbuf_img(
            pm_img.buf(),
            pm_img.width(), pm_img.height(),
            -pm_img.stride());
        agg::pixfmt_bgr24 pixf_img(rbuf_img);// 我用的bmp是24位的
        // 线段分配器
        typedef agg::span_allocator span_allocator_type;//分配器类型
        span_allocator_type span_alloc; // span_allocator
        // 插值器
        typedef agg::span_interpolator_linear<> interpolator_type; //插值器类型
        agg::trans_affine img_mtx; // 变换矩阵
        interpolator_type ip_img(img_mtx); // 插值器
        
        agg::trans_affine alpha_mtx; // 变换矩阵
        interpolator_type ip_alpha(alpha_mtx); // 插值器
     
        // 渐变方式
        typedef agg::gradient_x gradientF_type;
        gradientF_type grF;
     
        typedef std::vector alphaF_type;
        alphaF_type alphaF(256);
        for(int i=0; i<256; i++) alphaF[i] = i;
     
        // Alpha线段生成器
        typedef agg::span_gradient_alpha alpha_span_gen_type;
        alpha_span_gen_type alpha_span_gen(ip_alpha,grF,alphaF,0,150);
     
        // 图案线段生成器
        typedef agg::span_image_filter_rgb_bilinear_clip pic_span_gen_type;
        pic_span_gen_type pic_span_gen(pixf_img, agg::rgba(0,1,0), ip_img);
     
        // 使用span_converter组合成新的线段生成器
        typedef agg::span_converter span_gen_type;
        span_gen_type span_gen(pic_span_gen,alpha_span_gen);
     
        // 组合成渲染器
        agg::renderer_scanline_aa<
            renderer_base_type,
            span_allocator_type,
            span_gen_type
        > my_renderer(renb, span_alloc, span_gen);
        // 插值器的矩阵变换
        img_mtx.scale(0.5);
        img_mtx.translate(40,40);
        img_mtx.invert(); //注意这里
     
        // 用我们的渲染器画圆
        ras.add_path(ccell);
        agg::render_scanlines(ras,sl,my_renderer);
    }
显示效果
20090829103651630