AGG入門
一、配置開發環境
AGG入門(一) - 配置開發環境
AGG是一個高效的、高質量的、開源的矢量圖形庫,類似的有:GTK+的Cairo,Microsoft的GDI+。在三者中,AGG的性能是最高的(不討論Skia和Direct2D,他們有OGL和DX的硬件加速,繪圖速度根本不是一個檔次的)。讓我們細數一下他的優缺點:
- 優點:效率高,質量高(有反鋸齒),功能強大,跨平臺和平臺GUI支持,……
- 缺點:沒有硬件加速,文檔少,門檻有那麼點高,……
一、準備
- 到 http://www.antigrain.com/download/index.html下載源碼包。
- 今天用Visual C++ 6.0來做IDE和編譯器啦!要多經典有多經典。
二、工作
- 在VC6裏新建一個工程“AGG”,類型爲靜態庫Win32 Static Library
- 將下列源文件都加入工程:
- ./src/ 目錄下所有cpp文件【核心庫】*
- ./src/ctrl/ 目錄下所有cpp文件【控件庫】*
- ./src/platform/ 下對應平臺目錄(win32)下的所有cpp文件【平臺支持庫】*
- ./font_freetype/ 下所有的cpp文件【Freetype字體支持庫】
- ./font_win32_tt/ 下所有的cpp文件【Truetype字體支持庫】*
- ./gpc/ 下所有的cpp文件【Generic Polygon Clipper裁切庫】
- 將下列目錄加入Tools(工具)菜單– Options(選項)菜單 – Directory(目錄)選項卡中的Include Files目錄列表中:
- ./include/ *
- ./font_freetype/
- ./font_win32_tt/ *
- ./gpc/
- 選擇配置爲Win32 Release,編譯
- 編譯成功後,Tools(工具)菜單– Options(選項)菜單 – Directory(目錄)選項卡中的Library Files目錄列表中加入【工程所在目錄】\Release\
- 沒有成功編譯的童鞋,獎勵已經編譯好的AGG.lib一枚…… AGG.zip
三、測試
- 新建一個Win32 Application 空工程,新建一源文件,加入下面的代碼:
#include <agg_pixfmt_rgb.h>
#include <agg_renderer_base.h>
#include <platform/agg_platform_support.h>
class the_application : public agg::platform_support
{
public:
the_application(agg::pix_format_e format, bool flip_y) :
agg::platform_support(format, flip_y),
pix_fmt(rbuf_window()),
ren_bas(pix_fmt) //初始化渲染器
{ }
virtual void on_draw()
{
ren_bas.reset_clipping(true);
ren_bas.clear(agg::rgba8(204, 204, 204));
}
private:
agg::pixfmt_rgb24 pix_fmt;
agg::renderer_base<agg::pixfmt_rgb24> ren_bas;
};
int agg_main(int argc, char* argv[])
{
the_application app(agg::pix_format_rgb24, true);
app.caption("AGG Test");
if(app.init(500, 500, agg::window_resize)) {
return app.run();
}
return -1;
}
- 在【工程】菜單 - 【設置】菜單 - 【連接】選項卡 - 【對象/庫模塊】文本框中加入AGG.lib
- 編譯運行,不出意外,可以得到下面的結果:
二、平臺支持
AGG入門(二) - 平臺支持
一、先看看下面的代碼,並試着編譯下:
#include <platform/agg_platform_support.h>
#include <agg_pixfmt_rgb.h>
#include <agg_renderer_base.h>
#include <stdio.h>
#include <agg_path_storage.h>
class the_application : public agg::platform_support
{
public:
the_application(agg::pix_format_e format, bool flip_y) :
agg::platform_support(format, flip_y),
pix_fmt(rbuf_window()),
ren_bas(pix_fmt) //初始化渲染器
{
}
virtual void on_draw()
{
ren_bas.reset_clipping(true);
ren_bas.clear(agg::rgba8(255, 255, 255));
}
virtual void on_mouse_button_down(int x, int y, unsigned flags)
{
if(flags == agg::mouse_left) {
char str[50];
sprintf(str, "Mouse location:(%d, %d)", x, y);
message(str);
}
}
virtual void on_key(int x, int y, unsigned key, unsigned flags)
{
if(key == agg::key_return && flags == agg::kbd_shift) {
unsigned img = 0, states;
states = create_img(0, 500, 500);
states = load_img(img, "Steve-and-Bill.bmp");
copy_img_to_window(img);
update_window();
}
}
private:
agg::pixfmt_rgb24 pix_fmt;
agg::renderer_base<agg::pixfmt_rgb24> ren_bas;
};
int agg_main(int argc, char* argv[])
{
the_application app(agg::pix_format_rgb24, true);
app.caption("AGG Test");
if(app.init(500, 500, agg::window_resize)) {
return app.run();
}
return -1;
}
如果不出意外,在窗口中點擊鼠標左鍵將會出現對話框提示當前鼠標的位置,而按下Shift+Enter將會在窗口中顯示在工作目錄下的位圖“Steve-and-Bill.bmp”;
二、解釋
先看看頭文件:platform/agg_platform_support.h,它裏邊定義了一個platform_support類——它允許你建立一個窗口來測試你的圖形,並用鼠標鍵盤去控制它。
類型
- class platform_support
主要成員函數
- platform_support(pix_format_e, bool) : 構造函數。設置窗口風格和y軸是否上下翻轉;
- width() :返回窗口的寬;
- height() :返回窗口的高;
- caption([ const char* ]) :設置標題或返回標題字符串;
- format() :返回窗口風格;
- message(const char*) :彈出對話框(沒有風格可選);
- run() :運行窗口;
- force_redraw() :重繪窗口,調用on_draw();
- update_window() :更新窗口,既是把渲染緩存中已有的內容寫入窗口,不調用on_draw()。
- platform_support爲我們提供了一個很好地繪圖平臺,它有一系列的函數可用於操作位圖(BMP或PPM):
- create_img():創建一個編號爲idx的位圖;
- save_img():將位圖idx保存到文件中;
- load_img():從文件中加載位圖到idx中;
- copy_img_to_window():把idx拷貝到渲染緩存裏;
- copy_img_to_img():把idx拷貝到另一幅位圖裏;
- copy_window_to_img() :將渲染緩存裏的內容拷貝到位圖。
虛函數(一般都是些消息,要覆蓋它以讓消息循環調用)
- on_init() :窗口初始化時調用;
- on_resize(int, int) :改變大小時調用;
- on_idle() :空閒時調用;
- on_mouse_move(int, int, unsigned) :鼠標移動時調用;
- on_mouse_button_down(int, int, unsigned) :鼠標按下時調用;
- on_mouse_button_up(int, int, unsigned) :鼠標彈起時調用;
- on_key(int, int, unsigned, unsigned) :鍵盤打字時調用;
- on_draw() :窗口重繪時調用。
三、結語
platform_support 的功能不僅僅是這麼多,除此之外,他還能使用控件,等等。但很多時候,成熟的應用是不會使用它的,因爲它封裝了太多,雖然保證了跨平臺性,卻缺乏了自由性。platform_support 的主要作用是測試圖像和修改圖像,方便工作和移植……還有,方便初學者入門……
三、渲染器介紹
AGG入門(三) - 渲染器介紹
一、看回AGG入門(二)時on_draw()虛函數裏的代碼:
agg::rendering_buffer &rbuf = rbuf_window();
agg::pixfmt_rgb24 pixf(rbuf);
agg::renderer_base<agg::pixfmt_rgb24> renb(pixf);
renb.clear(agg::rgba8(255, 255, 255));
pixf.copy_pixel(20, 20, agg::rgba8(0, 0, 255));
二、渲染器
什麼是渲染?
渲染是把內存中的繪圖指令真正執行的過程。比如說,繪製一條線段,在內存裏只會保存着兩個端點的座標和線段的寬度,而渲染就把這兩個端點轉換爲位圖、緩存甚至顯示屏上的一個個像素的數據。又比如說,紙飛機下面肯定是要有投影的了,但這個投影的質量,就由渲染器決定;線段是走樣的(A),還是反走樣的(B),靠的就是渲染器的指令了。
AGG裏的渲染器
AGG分有多種渲染器。在AGG中,渲染器負責表現掃描線中的每個線段。在渲染器之前,AGG圖形中的線段是沒有顏色值的,只是位置、長度和覆蓋率(透明度)。渲染器賦於線段色彩,最終成爲一幅完整的圖像。其中最常用的是:
- 像素格式渲染器
- 基礎渲染器
- 掃描線(反鋸齒)渲染器
三、三種渲染器間的關係
- 像素格式渲染器(PixelFormat Renderer)是最基礎的渲染器,不需要任何其他渲染器的支持,所以可以直接聲明;
- 基礎渲染器(Base Renderer)是中級渲染器,需要像素格式作爲模版和像素格式渲染器的支持;
- 掃描線渲染器(Scanline Renderer)是高級渲染器,需要基礎渲染器作爲模版和支持;
(注:模版主要是爲了獲取像素格式的信息)
所以,除像素格式渲染器聲明爲:
agg::class object(agg::rendering_buffer &);
之外其他的渲染器都聲明爲:
agg::class<template> object(template &);
四、渲染緩存和混合器
AGG入門(四) - 渲染緩存和混合器
一、上一節的代碼
agg::rendering_buffer &rbuf = rbuf_window();
agg::pixfmt_rgb24 pixf(rbuf);
agg::renderer_base<agg::pixfmt_rgb24> renb(pixf);
renb.clear(agg::rgba8(255, 255, 255));
pixf.copy_pixel(20, 20, agg::rgba8(0, 0, 255));
二、渲染緩存
渲染緩存保存着一個個像素,作爲AGG的畫布。它僅僅是一個內存塊,用來儲存像素信息,不提供任何繪圖功能,只允許你讀取和修改裏面的數據。它也不告訴你裏面的像素是灰度的、RGB的還是RGBA的,不告訴你從哪裏到哪裏是一個像素——它只是用來管理內存數據的。
頭文件
#include "platform/agg_platform_support.h"
類型定義
typedef row_accessor<int8u> rendering_buffer //int8u是8 bit無符號整形
基本成員函數
- rendering_buffer(int8u* buf, unsigned width, unsigned height, int stride)
構造函數,指定事先分配好的內存塊(到時就畫到上面)首地址、寬高、一行的字節數(默認全部都是0); - row_ptr(int y)
返回第y行的首地址; - copy_from(void *buf)
從buf中拷貝像素; - clear(int8u value)
用value清空緩存 - buf(),
height(), weight(), stride()
返回緩存首地址、寬高、一行的字節數;
注:代碼中的rbuf_window()是platform_support的一個成員函數,用於返回platform_support一開始幫你申請的緩存引用。
三、混合器
混合器的存在是爲了適應不同平臺、不同需求下的不同像素格式。混合器有三種:agg::rgba,agg::rgba8和agg::rgba16,都是用來指定顏色的,rgba每個通道儲存爲double,rgba8爲unsigned char,rgba16爲int或long int;混合器起到的作用就像Win32API裏的RGB和COLORREF宏。
頭文件
#include "agg_pixfmt_rgba.h"
類型定義
struct rgba8; //對,你沒有看錯,是結構,不是類……
基本成員函數
- rgba8(unsigned r, unsigned g, unsigned b, unsigned a)
無須解釋了吧,最大255; - clear(),
no_color()
四個通道全部清零,也就是變沒色咯; - transparent()
alpha清零,變透明; - opacity()
返回透明度,用double表示; - gradient(agg::rgba8 &c, double k)
顏色梯度,就是顏色變爲從原先的顏色漸變爲c,變化率爲k; - add(agg::rgba8 &c, unsinged cover)
顏色疊加,疊加一個透明度爲cover/255的顏色c;
成員變量
- r, g, b, a都是無符號整型;
四、像素格式混合器
像素格式混合器的作用是直接操作像素(也就是緩存裏保存的數據,但起碼有個像素的樣子),起到Win32API裏的SetPixel()和GetPixel()的作用。像素格式由兩個屬性決定:混合器類型【agg::rgba8/agg::rgba16】、bgr/rgb/rgba/abgr順序【agg::order_bgr/agg::order_rgb/agg::order_rgba/agg::order_abgr】——這樣,共8種像素格式,它們起名字的規則就是:
agg::pixfmt_[order][bits*3];
下面用最常用的agg::pixfmt_rgb24來解釋:
頭文件
#include "agg_pixfmt_rgb.h"
類型定義
typedef pixfmt_alpha_blend_rgb<blender_rgb<rgba8, order_rgb>, rendering_buffer> pixfmt_rgb24;
基本成員函數
- pixfmt_rgb24(agg::rendering_buffer &)
構造函數,指定緩存就好; - blend_pixel(agg::rgba8& c, int x, int y, int8u cover)
用顏色c以cover(覆蓋率=透明度)的透明度混合像素(x, y); - copy_pixel(agg::rgba8& c, int x, int y),pixel(int
x, int y)
這個就是相當於SetPixel()和GetPixel()了; - copy_hline(int x, int y, unsigned len, agg::rgba8& c)
copy_vline(int x, int y, unsigned len, agg::rgba8& c)
從(x, y)開始打橫(豎)順序設置len長度的像素; - blend_hline(int x, int y,
unsigned len, agg::rgba8& c, int8u cover)
blend_vline(int x, int y, unsigned len, agg::rgba8& c, int8u cover)
從(x, y)開始打橫(豎)順序混合len長度的像素; - copy_solid_hspan(int x, int y,
unsigned len, agg::rgba8* colors)
copy_solid_vspan(int x, int y, unsigned len, agg::rgba8* colors)
blend_solid_hspan(int x, int y, unsigned len, agg::rgba8* colors, int8u* cover, int8u cover)
blend_solid_vspan(int x, int y, unsigned len, agg::rgba8* colors, int8u* cover, int8u cover)
同上兩個,不過不是一個顏色,是一系列的顏色; - for_each_pixel(void (*f)(agg::rgba8* color))
每一像素執行一遍f; - copy_from(agg::rendering_buffer & from, int xdst, int ydst, int xsrc, int ysrc, unsigned
len)
blend_from(agg::rendering_buffer & from, int xdst, int ydst, int xsrc, int ysrc, unsigned len[, unsigned cover])
從緩存form中(xsrc, ysrc)順序複製(混合)到當前緩存的(xdst, ydst)中;
【其他函數和像素格式就要靠大家的舉一反三,觸類旁通了……】
五、結語
上面說的三者關係是:混合器混合RGBA四個通道,像素格式混合器混合像素,像素格式混合器操作的結果是使渲染緩存裏的數據發生變化,而混合器則不會,因爲它的作用僅僅是表示顏色。
五、基礎渲染器
AGG入門(五) - 基礎渲染器
基礎渲染器(Base Renderer)是掃描線渲染器的基礎,可以說,正常情況下,你繪畫任何圖形、做任何事,都需要通過它。而基礎渲染器需要你以模版的形式提供像素格式的信息,他將會通過像素格式混合器來實現渲染。其實,基礎渲染器比像素格式混合器多了剪裁盒的功能,其他混合、拷貝什麼的和像素格式混合器是相似的,這裏就不列出來了。
矩形類
AGG封裝了一個專門表示矩形的模板類rect_base,方便矩形的操作。下面用rect_i說明一下。
頭文件
#include "agg_basics.h"
類型定義
typedef rect_base<int> rect_i;
基本成員函數
- rect_i(x1, y1, x2, y2)
構造函數,給出最小和最大座標; - normalize()
修正x1>x2或y1>y2的不合法矩形; - clip(rect_i& r)
取當前矩形與r相交的區域矩形作爲當前矩形; - is_valid()
檢查矩形是否合法; - hit_test(int x, int y)
檢查(x, y)是否在矩形內;
基礎渲染器
頭文件
#include "agg_renderer_base"
類型定義
template<class PixelFormat> class renderer_base
基本成員函數
- renderer_base(PixelFormat)
構造函數,提供像素格式; - ren()
返回像素格式對象; - clip_box(x1,y1,x2,y2)
clip_box_naked(x1,y1,x2,y2)
設置當前剪裁盒爲x1,y1,x2,y2圍成的剪裁盒,前者檢查剪裁盒是否合法,後者不檢查。 - reset_clipping(bool visibility)
重置剪裁盒,visibility決定剪裁盒是鋪滿窗口(可視)還是0(不可視); - clip_box()
xmin()
ymin()
xmax()
ymax()
返回當前剪裁盒矩形、以及縱橫座標; - copy_from()
blend_from()
可以比較方便地、以矩形方式拷貝和混合緩存裏的圖像了;
六、練習和細節
AGG入門(六) - 練習和細節
學到目前爲止,已經認識了六個類型:
- platform_support
- rendering_buffer
- rgba8
- pixfmt_rgb24
- rect_i
- renderer_base
現在來做些練習,看看有沒有掌握學過的東西,並且靈活運用吧。
一、基本框架
這一節的程序都以這個框架爲基礎,都是在on_draw中稍微改動的:
#include <agg_pixfmt_rgb.h>
#include <agg_renderer_base.h>
#include <platform/agg_platform_support.h>
class the_application : public agg::platform_support
{
public:
the_application(agg::pix_format_e format, bool flip_y) :
agg::platform_support(format, flip_y),
pix_fmt(rbuf_window()),
ren_bas(pix_fmt) //初始化渲染器
{ }
virtual void on_draw()
{
ren_bas.reset_clipping(true);
ren_bas.clear(agg::rgba8(255, 255, 255));
}
private:
agg::pixfmt_rgb24 pix_fmt;
agg::renderer_base<agg::pixfmt_rgb24> ren_bas;
};
int agg_main(int argc, char* argv[])
{
the_application app(agg::pix_format_bgr24, true);
app.caption("AGG Test");
if(app.init(500, 500, agg::window_resize)) {
return app.run();
}
return -1;
}
二、畫線函數
編寫如下函數,實現在渲染緩存中畫線的功能(無需反鋸齒):
inline void stroke_line(int x1,
int y1, int x2,
int y2, agg::rgba8& color);
參數:
- x1, y1, x2, y2分別是兩個端點的座標;
- color是顏色;
三、畫圓函數
編寫如下函數,實現在渲染緩存中畫圓的功能(無需反鋸齒):
void stroke_round(int r,
int C_x, int C_y, agg::rgba8& color,
float step = 0.01)
參數:
- C_x, C_y 是圓心的座標;
- color是顏色;
- step是步長,也就是吧圓細分成1/step邊形;
四、答案
- 畫線函數
inline void stroke_line(int x1, int y1, int x2, int y2, agg::rgba8& color)
{
double precision = max(abs(x1 - x2), abs(y1 - y2));
//精度,也就是畫多少個點
for(int i=0; i <= precision; i++)
ren_bas.copy_pixel( x1 + ( x2 - x1 ) / precision * i, //x
y1 + ( y2 - y1 ) / precision * i, //y
color);
}
- 畫圓函數
void stroke_round(int r, int C_x, int C_y, agg::rgba8& color, float step = 0.01)
{
int prev_x = int(r * cos(-0.01)) + C_x,
prev_y = int(r * sin(-0.01)) + C_y; //保存上一個點
int x, y; //保存當前的點
for(double rad = 0; rad < 2 * PI + step; rad+= step) {
x = int(r * cos(rad)) + C_x;
y = int(r * sin(rad)) + C_y; //計算弧度爲rad時的座標
stroke_line(x, y, prev_x, prev_y, color);
prev_x = x; prev_y = y;
}
}
可能有的人會覺得奇怪的是,爲什麼在畫線函數中,不用pix_fmt.copy_pixel()而用ren_bas.copy_pixel()呢?因爲,在pix_fmt中,混合器不進行檢查,像素拷貝的時候會拷貝到剪裁區域以外,這樣會造成很奇怪的情況,以至於如果寫到了緩存以外,還會出現異常。注意,剪裁盒功能是基礎渲染器級別才提供的,更加底層的操作,比如像素格式混合和直接操作緩存,高層次的渲染器是無從管理的。爲了安全起見,建議少碰基礎渲染器以下的工具……
七、頂點源
AGG入門(七) - 頂點源
一、修改模板
現在終於進入了真正的矢量繪圖階段,我們的模版也需要有所改變;至於爲什麼,有什麼作用,以後會說到;
包含下面的頭文件,並且在the_application類中添加兩個成員。
//掃描線和掃描線光柵器
#include <agg_scanline_u.h>
#include <agg_rasterizer_scanline_aa.h>
private:
//掃描線和掃描線光柵器
agg::scanline32_u8 scanline;
agg::rasterizer_scanline_aa<> rasterizer;
二、頂點源
頂點源(Vertex Source)不是一個類,而是一種類的模式。這種類裏面有rewind()函數和vertex()函數給AGG內部調用(沒錯,這就是它的定義)。類如其名,頂點源就是爲繪圖系統提供頂點信息的,大家能想象得出這兩個函數的作用了嗎?
rewind():回到最開始個步驟;
vertex(double* x, double* y):每調用一次,跳一個步驟(點),每一個步驟都輸出頂點的x,y座標(灰色字),以及這個座標的繪圖命令(紫色字);
三、內置頂點源
AGG內置了大量的頂點源,我們可以直接調用,他們包括:
agg::path_storage
agg::arc
agg::rounded_rect
agg::ellipse
agg::curve3
agg::curve4 ......
等等,爲什麼沒有線、點頂點源?其實,path_storage已經內置了畫線函數、畫弧函數、畫貝塞爾曲線函數,你可以用path_storage創造幾乎任何的圖形。至於畫點,copy_pixel()或者用橢圓吧……
四、路徑儲存器
Path storage 是用來管理路徑、畫複雜圖形的。在上面可以任意添加直線、曲線、其他路徑。
頭文件
#include <agg_path_storage.h>
類型定義
typedef path_base<vertex_block_storage<double> > path_storage;
基本成員函數
- move_to()
添加命令爲 path_cmd_move_to 的頂點,意爲下一條線從這個點開始畫; - line_to()
添加命令爲 path_cmd_line_to 的頂點,意爲畫線到這個點; - arc_to(double rx, double ry, double angle, bool large_arc_flag, bool sweep_flag, double x, double y)
添加一條弧路徑,畫軸長爲rx, ry,角度爲angle,優/劣弧,順逆時針,終點在(x,y)。 - curve3_to()
添加貝塞爾曲線,參數爲一個控制點和終點的座標 - curve4_to()
添加貝塞爾曲線,參數爲兩個控制點和終點的座標 - join_path()
添加一個頂點源,即組合 - vertex(unsigned idx, double* x, double* y)
last_vertex(double* x, double* y)
vertex(double* x, double* y)
取頂點位置,前者爲已知步驟,後兩者爲順序或倒序獲取 - modify_vertex()
modify_command()
修改步驟爲idx的頂點座標和命令
五、其他頂點源
其他頂點源就不一一介紹了,只列出其頭文件和構造函數:
- #include <agg_ellipse.h>
ellipse(double x, double y, double rx, double ry, unsigned num_steps=0, bool cw=false)
圓心(x, y)和長短半軸分別爲rx, ry,步驟數位num_steps(無用),cw決定相交地方是否空出 - #include <agg_arc.h>
arc(double x, double y, double rx, double ry, double a1, double a2, bool ccw=true)
圓心爲(x, y)和長短半軸分別爲rx, ry,初始角度和終結角度爲a1, a2 - #include <agg_curves.h>
curve3(double x1, double y1, double x2, double y2, double x3, double y3)
三個點,分別爲:初始點,控制點一,終結點 - #include <agg_curves.h>
curve4(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
四個點,分別爲:初始點,控制點一,控制點二,終結點 - #include <agg_rounded_rect.h>
rounded_rect(double x1, double y1, double x2, double r)
對角點的座標和圓角半徑
至於怎樣把他們畫在渲染內存上呢,我們下一篇會講到。
頭文件也漸漸地多了起來,包含頭文件時的工作量有點大;我特地列了一個頭文件,裏面已經包含了所有的AGG頭文件,以後大家只需要包含它就好了。
下載處:http://www.cppblog.com/Files/Shihira/agg.h.zip