(转)用AGG实现高质量图形输出(四)
2011-04-28 17:40 CoolJie 阅读(1663) 评论(1) 编辑 收藏 举报AGG的字符输出
字符输出,对于AGG来说,这个功能可以处于显示流程的 不同位置。比如字体引擎可直接处于“Scanline Rasterizer”层向渲染器提供已处理完毕的扫描线,也可以处于“Vertex Source顶点源”层提供字体的顶点数据。
下面,我们开始学习AGG不同的字符输出方式。如没有特殊说明,所以示例代码都基于此处代码
方式一、使用gsv_text对象
gsv_text属于顶点源层的对象,它的用法也很简单,直接看下例:
引用头文件:#include <agg_gsv_text.h>
在on_draw()方法的最后加入下面的代码
// gsv_text类 agg::gsv_text txt; agg::conv_stroke<agg::gsv_text> cstxt(txt); // 设置大小及是否反转 txt.flip(true); txt.size(18); // 设置位置和文字 txt.start_point(20,100); txt.text("cpp"); // 以红色输出上面的文字 ras.add_path(cstxt); agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba(1,0,0)); // 设置新的位置和文字 txt.start_point(20+txt.text_width(),100); txt.text("prog.com"); // 以蓝色输出上面的文字 ras.add_path(cstxt); agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba(0,0,1));
显示效果
注:gsv_text的flip ()方法指出是否上下反转输出,这里设置了flip是因为在Windows下agg::platform_support的rbuf_window()其 实是一个DIB缓存,它的方向是从下到上的。
gsv_text必须用conv_stroke转 换才能正确输出文字,否则会被当作多边形处理。为了使用方便,AGG提供了gsv_text_outline包 装,它实现了conv_stroke和坐标转换,代码很短:
template<class Transformer = trans_affine> class gsv_text_outline { public: gsv_text_outline(gsv_text& text, const Transformer& trans) : m_polyline(text), m_trans(m_polyline, trans){} void width(double w){ m_polyline.width(w); } void transformer(const Transformer* trans){ m_trans->transformer(trans); } void rewind(unsigned path_id) { m_trans.rewind(path_id); m_polyline.line_join(round_join); m_polyline.line_cap(round_cap); } unsigned vertex(double* x, double* y) { return m_trans.vertex(x, y); } private: conv_stroke<gsv_text> m_polyline; conv_transform<conv_stroke<gsv_text>, Transformer> m_trans; };
我们使用gsv_text_outline重写上面的代码:
#include <agg_gsv_text.h> #include <agg_trans_single_path.h> ... ///////////////////////////////////////////////// // gsv_text及gsv_text_outline agg::gsv_text txt; agg::trans_single_path tran_path; //使用trans_single_path作为变换器 agg::gsv_text_outline<agg::trans_single_path> txtol(txt,tran_path); // 设置变换器 tran_path.add_path(ell); ///////////////////////////////////////////////// // 设置大小及是否反转 txt.flip(true); txt.size(24); // 设置位置和文字 txt.start_point(0,0); txt.text("cpp"); // 以红色输出上面的文字 ras.add_path(txtol); agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba(1,0,0)); // 设置新的位置和文字 txt.start_point(0+txt.text_width(),0); txt.text("prog.com"); // 以蓝色输出上面的文字 ras.add_path(txtol); agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba(0,0,1));
显示效果
gsv_text的使用很简单,不过要了解的一点是:它只能输出ASCII可显示字符,对于汉字是无能为力的(你可以试试输出"C++编程")。如果要输出汉字,我 们得继续寻找其它字符输出方式。
方式二、使用字体引擎(Font Engine)
AGG的字体引擎利用WinAPI:GetGlyphOutline或FreeType库得到字体数据(字模),它可以处于 “Scanline Rasterizer”层或“顶点源”层。要使用字体引擎,要把相应的字体引擎源码(agg_font_win32_tt.cpp或 agg_font_freetype.cpp)加入项目一起编译。
头文件
#include <agg_font_win32_tt.h>
#include <agg_font_freetype.h>
注意,它们都有自己的文件夹,不是在agg的include文件夹里。
类型
agg::font_engine_win32_tt_int16 agg::font_engine_win32_tt_int32 agg::font_engine_freetype_int16 agg::font_engine_freetype_int32
显然,前两个利用WinAPI实现,后两个利用FreeType库实现。类型后面的_int16或_int32后缀用于指定坐标单位, 一般int16已经可以满足要求。
成员类型定义:
typedef path_adaptor_type | 把字体数据包装成顶点源的类 |
typedef gray8_adaptor_type | 把字体数据包装成Scanline Rasterizer的类 |
typedef mono_adaptor_type | 把字体数据包装成Scanline Rasterizer的类,但无AA效果 |
成员属性:
double: height | 字体高度,单位为Point(和Word里的单位一样) |
double: width | 字体宽度,单位为Point*2.4。0表示规则大小(height/2.4) |
bool: italic | 斜体 |
bool: flip_y | 上下翻转 |
bool: hinting | 字体修正 |
unsigned: resolution | 字体解析度,单位为dpi |
成员方法:
void transform(const trans_affine& affine); | 按矩阵变换 |
bool create_font(const char* typeface_, glyph_rendering ren_type); |
font_engine_win32_tt_*专有方法
|
bool load_font(const char* font_name,
|
font_engine_freetype_*专有方法
|
bool prepare_glyph(unsigned glyph_code)
|
得到字体数据(字模)所需方法 |
字体引擎的create_font()方法和load_font()方法需要一个glyph_rendering类型的ren_type参数,它决定了字 体数据的形式。三个成员类型定义:path_adaptor_type、gray8_adaptor_type和mono_adaptor_type所包 装的字体数据是 不一样的,只有与ren_type参数对应才能生成正确的AGG显示节点。
glyph_rendering是一个枚举类型,定义是:
enum agg::glyph_rendering{ glyph_ren_native_mono, //对应mono_adaptor_type glyph_ren_native_gray8, //对应gray8_adaptor_type glyph_ren_outline, //对应path_adaptor_type glyph_ren_agg_mono, //对应mono_adaptor_type glyph_ren_agg_gray8 //对应gray8_adaptor_type };
示例代码1 - 从顶点源层输出文字
typedef agg::font_engine_win32_tt_int16 fe_type; typedef fe_type::path_adaptor_type vs_type; // 字体引擎 fe_type fe( ::GetDC(::GetActiveWindow()) ); //注意,实际应用时要释放HDC fe.height(36.0); fe.flip_y(true); fe.hinting(true); // 注意后面的glyph_rendering ren_type参数 fe.create_font("黑体",agg::glyph_ren_outline); // 字体串 wchar_t *s = L"C++编程"; // 存放字体数据 std::vector<agg::int8u> data; // 顶点源 vs_type vs; // 注意这里,使用conv_curve转换 agg::conv_curve<vs_type> ccvs(vs); // 字符输出的位置 int x=20,y=100; for(;*s;s++) { // 让字体引擎准备好字体数据 if(!fe.prepare_glyph(*s)) continue; // 把字体数据放到容器里 data.resize( fe.data_size() ); fe.write_glyph_to( &data[0] ); // 从字体数据中得到顶点源 vs.init(&data[0], data.size(), x, y); // 移动输出位置 x += fe.advance_x(); y += fe.advance_y(); // 输出 ras.add_path(ccvs); agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba(0,0,1)); }
由于字体顶点源可能会包含带Curve命令的顶点,所以要用conv_curve来 转换。你可以试试去掉这层转换,字符'C' 就不会那么平滑了。
示例代码2 - 从Scanline Rasterizer层输出文字
// 字体引擎类型定义 typedef agg::font_engine_win32_tt_int16 fe_type; typedef fe_type::gray8_adaptor_type ras_type; typedef ras_type::embedded_scanline sl_type; // 字体引擎 fe_type fe( ::GetDC(::GetActiveWindow()) ); //注意,实际应用时要释放HDC fe.height(36.0); fe.flip_y(true); fe.hinting(true); // 注意后面的glyph_rendering ren_type参数 fe.create_font("黑体",agg::glyph_ren_agg_gray8); // 字体串 wchar_t *s = L"C++编程"; // 存放字体数据 std::vector<agg::int8u> data; // Rasterizer和Scanline ras_type ras_font; sl_type sl_font; // 字符输出的位置 int x=20,y=100; for(;*s;s++) { // 让字体引擎准备好字体数据 if(!fe.prepare_glyph(*s)) continue; // 把字体数据放到容器里 data.resize( fe.data_size() ); fe.write_glyph_to( &data[0] ); // 从字体数据中得到Rasterizer ras_font.init(&data[0], data.size(), x, y); // 移动输出位置 x += fe.advance_x(); y += fe.advance_y(); // 输出 agg::render_scanlines_aa_solid(ras_font,sl_font,renb,agg::rgba(0,0,1)); }
显示效果
Linux、 FreeBSD等开源操作系统里一般使用FreeType来显示文字,Windows下的一些软件如Foxit也有FreeType的身影。
AGG的font_engine_freetype_int16字 体引擎就使用FreeType来取得字模,在Windows里,在使用font_engine_freetype_int16之前,我们得先编译好 FreeType:
- 从www.freetype.org下 载FreeType2,偶下载的是目前最新的freetype-2.3.9,解压。
- 以VC2005 Express为例,直接打开\builds\win32\vc2005里的freetype.sln编译即可。(不过这个版本的 freetype.sln好 像好点问题,要用文本编辑器打开,把第一行“Microsoft Visual Studio Solution File, Format Version 10.00”后面的“10.00”改成“9.00”才行)
- 对于其它编译器,如C++Builder,直接把\docs\INSTALL.ANY里列出的文件加入项目即可(不过加入这些东 西还真是比较麻烦,可以点这里下载C++Builder的FreeType2库编译工程)。
- 把编译后的库文件(在\objs\win32里,注意编译版本)加入项目,并设置include路径为freetype- 2.3.9\include就可 以了
AGG使用FreeType的源代码:
typedef agg::font_engine_freetype_int16 fe_type; typedef fe_type::path_adaptor_type vs_type; fe_type fe; if(!fe.load_font("c:\\windows\\fonts\\simhei.ttf",0,agg::glyph_ren_outline)) return; fe.height(36.0); fe.flip_y(true); fe.hinting(true); wchar_t *s = L"C++编程"; std::vector<agg::int8u> data; vs_type vs; agg::conv_curve<vs_type> ccvs(vs); int x=20,y=100; for(;*s;s++) { if(!fe.prepare_glyph(*s)) continue; data.resize( fe.data_size() ); fe.write_glyph_to( &data[0] ); vs.init(&data[0], data.size(), x, y); x += fe.advance_x(); y += fe.advance_y(); ras.add_path(ccvs); agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba(0,0,1)); }
方式三、使用字体缓存管理器
每次都重新读字模是很费时的,比如前面的例子,"C++" 里的两个'+' 就读两次字模,效率可以想象。
一个好的办法是把已读出来的字模缓存起来,下次再遇到这个字时就不用从字体引擎里读取了,AGG提供的font_cache_manager类就是 负责这项工作的。
头文件
#include "agg_font_cache_manager.h"
类型
template<class FontEngine> class font_cache_manager;
模板参数FontEngine指定管理器所用的字体引擎。另外构造参数也是FontEngine。
成员方法
const glyph_cache* glyph(unsigned glyph_code); |
获得字模并缓存,glyph_cache类的定义是: struct glyph_cache { unsigned glyph_index; int8u* data; unsigned data_size; glyph_data_type data_type; rect_i bounds; double advance_x; double advance_y; }; |
path_adaptor_type& path_adaptor(); |
字体引擎的path_adaptor_type实例 |
gray8_adaptor_type& gray8_adaptor(); gray8_scanline_type& gray8_scanline(); |
字体引擎的gray8_adaptor_type实例以及对应的Scanline |
mono_adaptor_type& mono_adaptor(); mono_scanline_type& mono_scanline(); |
字体引擎的mono_adaptor_type实例以及对应的Scanline |
void init_embedded_adaptors(const glyph_cache* gl, double x, double y, double scale=1.0); |
初始化上面的adaptor成员实例(与字体引擎的ren_type设置相关) |
bool add_kerning(double* x, double* y); |
调整坐标 |
agg::font_engine_win32_tt_int16 font(dc); agg::font_cache_manager< agg::font_engine_win32_tt_int16 > font_manager(font); font.height(72.0); font.width(0); font.italic(true); font.flip_y(true); font.hinting(true); font.transform(agg::trans_affine_rotation(agg::deg2rad(4.0))); font.create_font("宋体",agg::glyph_ren_agg_gray8); double x=10, y=72; //起始位置 wchar_t *text = L"C++编程网"; // 画所有字符 for(;*text;text++) { //取字模 const agg::glyph_cache* glyph = font_manager.glyph(*text); if(glyph) { // 初始化gray8_adaptor实例 font_manager.init_embedded_adaptors(glyph, x, y); agg::render_scanlines_aa_solid(font_manager.gray8_adaptor(), font_manager.gray8_scanline(), renb, agg::rgba8(0, 0, 0)); // 前进 x += glyph->advance_x; y += glyph->advance_y; } }
显示效果
示例代码2-作为顶点源渲染:
typedef agg::font_engine_win32_tt_int16 fe_type; fe_type font(GetDC(0)); typedef agg::font_cache_manager<fe_type> fcman_type; fcman_type font_manager(font); font.height(72.0); font.width(0); font.italic(true); font.flip_y(true); font.hinting(true); font.transform(agg::trans_affine_rotation(agg::deg2rad(4.0))); font.create_font("宋体",agg::glyph_ren_outline); double x=10, y=72; //起始位置 wchar_t *text = L"C++编程网"; // 画所有字符 for(;*text;text++) { const agg::glyph_cache* glyph = font_manager.glyph(*text); if(glyph) { // 准备*_adaptor font_manager.init_embedded_adaptors(glyph, x, y); // 先用conv_curve typedef agg::conv_curve< fcman_type::path_adaptor_type > cc_pa_type; cc_pa_type ccpath(font_manager.path_adaptor()); // 画轮廓 typedef agg::conv_stroke<cc_pa_type> cs_cc_pa_type; cs_cc_pa_type csccpath(ccpath); agg::rasterizer_scanline_aa<> ras; agg::scanline_u8 sl; ras.add_path(csccpath); agg::render_scanlines_aa_solid(ras, sl, renb, agg::rgba8(0, 0, 0)); // 前进 x += glyph->advance_x; y += glyph->advance_y; } }
显示效果
作为本文的结尾,这里放上一个用AGG生成不规则文字窗体的代码。它综合了我们之前学到的AGG字体引擎、坐标转换、颜色渐变等几大模 块。由于AGG的抗锯齿特性,使用生 成的窗体看上去边缘过渡非常自然,几乎看不到“毛边”。
先放上最终生成的窗体的效果:
#define _WIN32_WINNT 0x0501 #include <windows.h> #include <agg_array.h> #include <agg_pixfmt_rgba.h> #include <agg_scanline_u.h> #include <agg_renderer_scanline.h> #include <../font_win32_tt/agg_font_win32_tt.h> #include <agg_font_cache_manager.h> #include <agg_span_solid.h> #include <agg_span_interpolator_linear.h> #include <agg_span_gradient.h> #include <agg_span_allocator.h> #include <agg_conv_transform.h> #include <agg_ellipse.h> #include <agg_trans_single_path.h> typedef agg::font_engine_win32_tt_int16 fe_type; typedef agg::font_cache_manager<fe_type> fcman_type; typedef agg::renderer_base<agg::pixfmt_bgra32> renb_type; // 使用指定的顶点源和线段生成器输出文字 template<class VS, class SpanGenerator> void AggDrawText(renb_type &renb, fcman_type &font_manager, VS &vs, SpanGenerator &span_gen, const wchar_t *txt) { using namespace agg; span_allocator<rgba8> span_alloc; rasterizer_scanline_aa<> ras; scanline_u8 sl; double x=0, y=0; for(const wchar_t *p = txt; *p; p++) { const glyph_cache* gc = font_manager.glyph(*p); if(gc) { font_manager.init_embedded_adaptors(gc, x, y); ras.add_path(vs); agg::render_scanlines_aa(ras, sl, renb, span_alloc, span_gen); x += gc->advance_x; y += gc->advance_y; } } } // 向renb的指定位置和半径输出http://www.cppprog.com ,有环绕效果 void DrawUrl(HDC dc, renb_type &renb, double ox, double oy, double rx, double ry) { using namespace agg; //字体引擎 fe_type font(dc); fcman_type font_manager(font); font.height(18.0); font.flip_y(true); font.hinting(true); if(!font.create_font("Comic Sans MS",agg::glyph_ren_outline)) return; //坐标转换管道 typedef conv_curve< fcman_type::path_adaptor_type > cc_pa_type; cc_pa_type ccpath(font_manager.path_adaptor()); typedef conv_transform<cc_pa_type, trans_single_path> ct_cc_pa_type; trans_single_path trans_path; ct_cc_pa_type ctpath(ccpath, trans_path); ellipse ell(0,0,rx,ry); trans_affine ellmtx; conv_transform<ellipse> ctell(ell, ellmtx); ellmtx.rotate(agg::pi); ellmtx.translate(ox,oy); trans_path.add_path(ctell); // 线段生成器 span_solid<rgba8> ss; ss.color(rgba(1,0,0)); AggDrawText(renb, font_manager, ctpath, ss, L"http://www.cppprog.com"); } // 向renb的指定位置输出“C++编程”几个字,有镜象效果 void DrawName(HDC dc, renb_type &renb, double x, double y) { using namespace agg; // 字体引擎 fe_type font(dc); fcman_type font_manager(font); font.height(30.0); font.flip_y(true); font.hinting(true); if(!font.create_font("黑体",agg::glyph_ren_outline)) return; // 坐标转换管道 typedef conv_curve< fcman_type::path_adaptor_type > cc_pa_type; cc_pa_type ccpath(font_manager.path_adaptor()); typedef conv_transform<cc_pa_type> ct_cc_pa_type; trans_affine mtx; ct_cc_pa_type ctpath( ccpath, mtx ); mtx.translate(50,50); //线段生成器 span_solid<rgba8> ss; ss.color(rgba(0,0,0)); AggDrawText(renb, font_manager, ctpath, ss, L"C++编程"); // 改变坐标转换矩阵(镜像) mtx.reset(); mtx.flip_y(); mtx.translate(50,60); // 渐变线段生成器 typedef span_interpolator_linear<> interpolator_type; trans_affine img_mtx; interpolator_type ip(img_mtx); typedef gradient_y gradientF_type; gradientF_type grF; typedef gradient_linear_color<rgba8> colorF_type; colorF_type colorF(rgba(0,0,0), rgba(0,0,0,0)); typedef span_gradient<rgba8, interpolator_type, gradientF_type, colorF_type> span_gen_type; span_gen_type span_gen(ip,grF,colorF,30,80); AggDrawText(renb, font_manager, ctpath, span_gen, L"C++编程"); } // 调用DrawUrl和DrawName向renb输出文字 void DrawIt(HDC dc, renb_type &renb) { // 以透明色填充 renb.clear(rgba(0,0,0,0)); // 输出文字 DrawUrl(dc, renb, 100, 50, 80, 40); DrawName(dc, renb, 50, 50); } // 使用AGG处理图片后与hwnd关联 void SetLayoutWin(HWND hwnd) { // 起始位置和窗体大小 POINT ptWinPos = {500,200}; SIZE sizeWindow = {200, 100}; // 建立DIB BITMAPINFO bmp_info; ::ZeroMemory(&bmp_info, sizeof(bmp_info)); bmp_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmp_info.bmiHeader.biWidth = sizeWindow.cx; bmp_info.bmiHeader.biHeight = sizeWindow.cy; bmp_info.bmiHeader.biPlanes = 1; bmp_info.bmiHeader.biBitCount = 32; bmp_info.bmiHeader.biCompression = BI_RGB; HDC hdcTemp = GetDC(0); HDC mem_dc = ::CreateCompatibleDC(hdcTemp); ReleaseDC(0, hdcTemp); void* buf = NULL; HBITMAP bmp = ::CreateDIBSection( mem_dc, &bmp_info, DIB_RGB_COLORS, &buf, 0, 0 ); // 把bmp与mem_dc关联,这样AGG就可以和原生GDI一起工作了 HBITMAP temp = (HBITMAP)::SelectObject(mem_dc, bmp); { // AGG处理 agg::rendering_buffer rbuf( (unsigned char*)buf, sizeWindow.cx, sizeWindow.cy, -sizeWindow.cx*4); agg::pixfmt_bgra32 pixf(rbuf); renb_type renb(pixf); DrawIt(mem_dc,renb); } // 把画好的mem_dc与hwnd关联到一起 BLENDFUNCTION m_Blend={AC_SRC_OVER,0,255,AC_SRC_ALPHA}; POINT ptSrc = {0, 0}; BOOL bRet = UpdateLayeredWindow(hwnd, 0, &ptWinPos, &sizeWindow, mem_dc, &ptSrc, 0, &m_Blend, ULW_ALPHA); // 回收 ::DeleteObject(bmp); ::DeleteDC(mem_dc); } // Windows消息处理 LRESULT CALLBACK WndProc (HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) { switch (umsg) { case WM_CLOSE: DestroyWindow (hwnd); return 0; case WM_DESTROY: PostQuitMessage (0); return 0; case WM_NCHITTEST: return HTCAPTION; } return DefWindowProc (hwnd, umsg, wParam, lParam); } int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { WNDCLASS wc={ 0,WndProc, 0,0, hInstance, NULL,LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW+1), 0,"AGGWIN" }; ::RegisterClass(&wc); HWND hWnd = ::CreateWindowEx(WS_EX_LAYERED,"AGGWIN", NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) return -1; SetLayoutWin(hWnd); ::ShowWindow(hWnd, nCmdShow); // 主消息循环: MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; }