http://www.linuxfans.org/nuke/modules.php?name=Forums&file=viewtopic&t=111396&postdays=0&postorder=asc&highlight=cairo&start=0
Cairo 将成為Linux 2D 繪圖的未來,相信我,沒錯的。這是一個筆記,另外還有一個cairo 粗斜體顯示中文的更新檔,這個更新檔我永遠也不會向外放,因為,這麼作,就跟firefly和akito的做法一樣,用一個錯誤的方法解決錯誤的問題。
粗體實作,應該在freetype的GetBitmap之前就要完成,這樣,任何基于freetype的東西都不再需要更新檔了。
這個文檔會不斷完善,也會跟着cairo 的版本更新作修改,我希望最後這個文檔能夠涵蓋cairo 程式設計所有的東西,同時也希望有興趣的能夠一起來寫這個文檔。
這個文檔還沒有清楚的解釋什麼是surface,什麼是path,什麼是pattern,慢慢完善吧,不過如果你用一段時間後,你會慢慢的悟出來。
Cairo 真是個好東西,你可以用它畫出所有的東西。
Cairo 程式設計入門
1,什麼是Cairo 。
按照官方的說法:Cairo is a vector graphics library with cross-device output support.
翻譯過來,就是cairo 是一個支援多種輸出的向量圖形庫。
具體解釋一下,也就是說,cairo 是種畫圖的工具庫,他可以向多種裝置上畫圖,比如:
cairo 可以輸出到png,可以輸出到pdf,可以輸出到ps,可以輸出到xlib,可以輸出到XCB,可以輸出到win32,以後還要輸出到svg
我們可以展望一下,如果cairo 能夠統一linux下所有的畫圖接口,那麼,所見所得可能就會成為顯示。畢竟現在顯示和列印是兩套代碼
2,編譯安裝cairo :
目前的linux作業系統應該都沒有問題,隻要注意提供freetype/fontconfig就可以了。
然後,下載下傳:
libpixman
glitz
就可以編譯安裝cairo 了。
如果需要svg-cairo 就要下載下傳libsvg, libsvg-cairo 編譯安裝。
如果需要python綁定,下載下傳pycairo
如果需要gtk綁定,下載下傳gtkcairo
如果需要qt綁定,自己去研究一下吧。不過你要自己寫也行,使用doublebuffer,然後用bitblt就可以了。
3,快速入門:
如果單純的從代碼上了解cairo ,可能容易讓人迷惑,這種類似于Postscript編碼風格的東西确實讓人有點混亂,是以,我們需要這麼去了解cairo :
cairo 是一個畫筆,你可以為這個畫筆設定顔色、設定字型、設定alpha,也可以用這個畫筆去畫出任何圖形。
就像畫家作畫一樣,你可以用畫布,也可以用宣紙,也可以用其他的材料。cairo 這支畫筆,可以在png,ps,pdf或者Xlib上畫東西。
一個比較有代表性的例子如下:
#include <cairo .h>
#include <cairo -png.h>
#include
#include
int main()
{
FILE *file;
file=fopen("a.png","w");//打開一個檔案,寫入,檔案名為a.png
cairo_t *cr; //聲明一支畫筆
cr=cairo_create();//建立畫筆
cairo_set_target_png(cr,file,CAIRO_FORMAT_ARGB32,400,400);//設定畫布,這裡就是剛剛打開的那個檔案,a.png
cairo_set_rgb_color(cr,0,1,0);//設定畫筆顔色,也就是紅,綠,藍,這裡設定成綠色。
cairo_rectangle(cr,10,10,200,200);//畫一個方塊,位置從坐标(10,10)開始,寬200,高200
cairo_fill(cr);//填充,使用的顔色當然是上面設定的顔色。
cairo_move_to(cr,250,200);//将畫筆移動到(250,200)
cairo_select_font (cr, "DongWen--Song",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_NORMAL);//為cairo 設定一個字型,字型名DongWen--Song,非斜體,非粗體。
cairo_scale_font(cr,60);//縮放字型到60倍
cairo_show_text(cr,"hello world");//畫出一個串
cairo_destroy(cr);//銷毀畫筆
fclose(file);//關閉檔案
}
存成t1.c,然後使用下面的指令編譯
gcc -o t1 t1.c -lcairo -I/usr/include/cairo
運作./t1,就生成了一個png圖檔檔案,自己用工具打開看就可以了。
TIPS:
cairo_stoke(cr);
神來一筆,描線函數,也就是将一個path用線把輪廓描出來。
cairo_fill(cr);
就是填充函數,也就是将一個path用某種顔色填充起來。
比如:
cairo_rectangle(cr,10,10,100,100);
cairo_set_line_width(cr,10)
cairo_set_rgb_color(cr,0,0,1);
cairo_stroke(cr);
這時候就畫了一個方塊。
cairo_move_to(cr,0,0);
cairo_line_to(cr,100,100);
cairo_set_rgb_color(cr,1,0,0);
cairo_stroke(cr);
畫了一條斜線,從0,0到100,100
cairo_set_line_width(cr,LINE_WIDTH);
設定線寬。
cairo_set_line_cap(cr, LINE_CAP);
設定線條兩端的顯示方式
CAIRO_LINE_CAP_ROUND,圓形,圓形的中心就是你定義的兩個端點
CAIRO_LINE_CAP_BUTT,方形,就從你定義的點開始。
CAIRO_LINE_CAP_SQUARE,方形,方形的中心就是你定義的兩個端點。
也就是如果用square或者round,畫出的線條比兩個端點的距離要長一點,一面長半個LINE_WIDTH。
注意,cairo_set_line_cap好像隻對線條有效,如果你畫一個rectangle,角永遠是尖的。
cairo_curve_to(cr,x1,y1,x2,y2,x3,y3)
通過三點定義一道弧線。
cairo_arc(cr,x,y,radius,from,to);
畫一個圓,圓心x,y,半徑,然後注意:指定弧度,比如從0,M_PI(在math.h裡)。就可以是一個半圓。
從0到2*M_PI就是一個整圓。其他弧度,自己用M_PI去除就OK了。
注意,這裡畫線的方向是順時針的。
cairo_arc_negative(cr,x,y,radius,from,to);
這個函數功能跟上面的函數一樣,隻是逆時針畫。
cairo_rotate(cr,angle);
接受弧度做參數,自己用M_PI去除吧。
旋轉函數,注意,使用時,它是影響全局的,也就是旋轉了之後,後面畫的一切都是旋轉的,是以,在旋轉之前,做一下cairo_save,完成需要的旋轉之後
做一下cairo_restore能友善一點。
cairo_fill(cr);
填充函數,将你前面畫的東西用指定顔色填充起來。
比如:
cairo_arc(cr,100,100,40,0,2*M_PI);//以100,100坐标為圓心畫一個半徑為40的圓(整圓)。
cairo_set_rgb_color(cr,1,0,0);//紅色
cairo_fill(cr);//這時候得到的就是一個紅色的實心圓。
如果是
cairo_arc(cr,100,100,40,0,2*M_PI);
cairo_set_line_width(cr,3);
cairo_set_rgb_color(cr,1,0,0);
cairo_stroke(cr);
這時候就畫了一個空心圓圈。
如果是
cairo_arc(cr,100,100,40,0,M_PI);
然後再作cairo_stroke(cr);
這時候得到的是一個開口的半圓,cairo 并不會給你畫上直線。
cairo_scale(cr,x,y);
縮放圖形,謹慎使用,注意使用cairo_save和cairo_restore,這個函數可以在不同的surface上使用。
x指寬度縮放倍數,y指高度縮放的倍數。
字型操作:
cairo_font_scale(cr,num);
将字型縮放多少倍,僅影響字型。
cairo_select_font(cr, char* fontname, cairo_font_slant_t, cairo_font_weight_t );
更改字型函數, fontname就是字型名,比如SimSun, DongWen--Song等。
其中cairo_font_slant_t有
CAIRO_FONT_SLANT_NORMAL//正常
CAIRO_FONT_SLANT_ITALIC//斜體
CAIRO_FONT_SLANT_OBLIQUE//更斜體
cairo_font_weight_t有
CAIRO_FONT_WEIGHT_NORMAL//正常
CAIRO_FONT_WEIGHT_BOLD//粗體
比如我要設定一個粗斜體就是:
cairo_select_font(cr, "Nimbus Sans L", CAIRO_FONT_SLANT_ITALIC, CAIRO_FONT_WEIGHT_BOLD);
注意:中文字型全部沒有粗體,斜體變化,有興趣的可以看看
cairo_ft_font.c中的
cairo_ft_font_create_glyph函數,在FT_Outline_Get_Bitmap拿到bitmap之後
作一下偏移就可以拿到一個fake 的bold,不過,這個是治标不是治本,真正要搞定,還是要修改
freetype,有興趣的自己去看freetype的代碼吧。
也可以通過
cairo_ft_font_create();傳回cairo_font_t *
cairo_set_font();接受參數cairo_t *和cairo_font_t *
之類的函數來操作字型,很遺憾,在0.4.0中是無效的。
在這裡我據個例子:
#include
int main()
{
......
FcPattern *pattern;
FcPattern *matched;
FcResult error;
pattern=FcPatternCreate();
FcPatternAddString(pattern,FC_FAMILY,"Nimbus Sans L");
FcPatternAddInteger(pattern,FC_SLANT,FC_SLANT_ITALIC);
FcPatternAddInteger(pattern,FC_WEIGHT,FC_WEIGHT_BOLD);
matched = FcFontMatch(NULL,pattern, &error);
cairo_matrix_t *matrix = cairo_matrix_create();
cairo_font_t * cft = cairo_ft_font_create(matched,matrix);
cairo_set_font(cr,cft);
//可惜,以上代碼無效

......
}
還好,用select_font加上scale_font還能夠滿足要求,是以不這麼寫反而省事了。
Cairo 畫筆拷貝操作:
cairo 提供了以下函數操作畫筆:
cairo_create();建立畫筆。
cairo_reference();增加引用計數1。
cairo_destroy();減少引用計數1,如果傳回0,所有的資源均被釋放。
cairo_save();儲存目前cairo 畫筆。
cairo_restore();恢複上一次儲存的cairo 畫筆,也就是說必須跟cairo_save()配對。
如果僅僅做了一次cairo_save();卻使用了兩次cairo_restore(),那麼就會發生不可預料的效果。
看下面的例子:
cairo_t * cr;
cr = cairo_create(); //聲明畫筆并建立
FILE *file;
file=fopen("c.png","w");
cairo_set_target_png(cr,file,CAIRO_FORMAT_ARGB32,400,400);//設定目标為一個png檔案,大小為400x400,色彩32位。
cairo_save(cr);//SAVE_1 儲存畫筆,這時候,畫筆是幹幹淨淨的,沒有任何操作。
cairo_rectangle(cr,10,10,200,200);
cairo_set_rgb_color(cr,0,0,1);
cairo_set_line_width(cr,4);
cairo_stroke(cr); //畫一個矩形框,藍色
cairo_move_to(cr,60,60);//将畫筆移動到 60x60坐标,注意:畫文字的時候,這個坐标是文字左下角坐标,如果你這時候移動到0,0坐标,
其實你的文字将被畫到400x400的畫布之外。切記。
cairo_select_font(cr,"Nimbus Sans L", 0,0); //選擇正體Nimbus Sans L,請根據你系統的fc-list自己選擇一個字型
cairo_scale_font(cr,60);//設定為60,雖然是scale,但是cairo 内部實際上用的是字号
cairo_show_text(cr,"hehe");//顯示hehe,藍色,60号,Nimbus Sans L字型
cairo_restore(cr);//恢複到 SAVE_1,這時候的畫筆是幹幹淨淨的。
cairo_save(cr);//SAVE_2 儲存一下,沒準以後還會用到這個幹淨的畫筆。
cairo_move_to(cr,70,70);//移動到70,70
cairo_set_rgb_color(cr,1,0,0);
cairo_scale_font(cr,60);
cairo_show_text(cr,"hehe");//顯示文本hehe,紅色,60号,預設字型,如果上面不作restore,而你有天真的進行了scale_font,這時候,字型就是60x60倍了
//有可能根本就看不到了
cairo_restore(cr); //恢複到SAVE_2,這時候畫筆仍然是初始狀态。
cairo_set_rgb_color(cr,0,1,0);
cairo_arc(cr,100,100,60,0, 1.5*M_PI);
cairo_set_line_width(cr,3);
cairo_set_line_cap(cr,CAIRO_LINE_CAP_ROUND);
cairo_stroke(cr);
cairo_destroy(cr);
cairo_destroy(cr);
fclose(file);
Cairo Path:
path的具體翻譯不太好作,了解為路徑又不能表達意思,就知道這個東西是path吧。
上面提到了:在畫半圓的時候,因為前面進行了move_to,是以,他會使用move_to到的坐标作為起點。是不是我們必須cairo_save和cairo_restore呢?
不用,有path就可以了。
比如
cairo_t *cr;
cr = cairo_create();
cairo_set_target_....;
cairo_move_to(70,70);
cairo_set_rgb_color(cr,0,1,0);
cairo_arc(cr,100,100,60,0, 1.5*M_PI);
cairo_set_line_width(cr,3);
cairo_set_line_cap(cr,CAIRO_LINE_CAP_ROUND);
cairo_stroke(cr);
自己執行一下,看看結果,還是一條直線加大半圓。
我們修改代碼如下:
cairo_t *cr;
cr = cairo_create();
cairo_set_target_....;
cairo_move_to(70,70);
cairo_new_path(cr);
cairo_set_rgb_color(cr,0,1,0);
cairo_arc(cr,100,100,60,0, 1.5*M_PI);
cairo_set_line_width(cr,3);
cairo_set_line_cap(cr,CAIRO_LINE_CAP_ROUND);
cairo_stroke(cr);
cairo_close_path(cr);
另外,還有一個cairo_text_path(char * utf8);
這個函數可以用來顯示字型,這個功能跟cairo_show_text(cairo_t *, char *)類似。
另外一個很重要的功能就是,他可以把字型當成path處理,是以,你可以作出任何特效和變換。
比如,作一個空心字:
cairo_text_path("Hello");
cairo_set_line_width(cr,1);
cairo_stroke(cr);
這是通過cairo_show_text()做不到的。
Cairo 後端:
1,png 後端:
#include <cairo -png.h>
FILE * file;
file = fopen("cairo_out.png","w");
cairo_set_target_png(cr,file,CAIRO_FORMAT_ARGB32,400,400);
解釋一下這個函數,這個函數接受5個參數,第一個參數就是cairo 畫筆,第二個參數是要輸出的檔案,第三個參數是色彩深度。
色彩深度的類型是cairo_format_t *,有:
CAIRO_FORMAT_ARGB32:32位
CAIRO_FORMAT_RGB24:32位
CAIRO_FORMAT_A8:8位
CAIRO_FORMAT_A1:1位
後兩個參數,分别是PNG圖像的寬度和高度(如果沒有例外,cairo 中所有的函數都是寬度在前,高度在後);
2,PDF後端:
#include <cairo -pdf.h>
FILE *file;
file = fopen("cairo .pdf","w");
double width = 10;//英寸
double height = 10;//英寸
double xPPI=10;//每英寸象素
double yPPI=10;//每英寸象素
cairo_set_target_pdf(cr,file,width,height,xPPI,yPPI);
3,PS後端:
函數原型跟pdf基本一樣。
#include <cairo -ps.h>
FILE *file;
file = fopen("cairo .ps","w");
double width = 10;//英寸
double height = 10;//英寸
double xPPI=10;//每英寸象素
double yPPI=10;//每英寸象素
cairo_set_target_ps(cr,file,width,height,xPPI,yPPI);
4,X後端:
也就是用來直接在X應用上畫圖,是以,所有基于xlib的應用全部都可以使用這種方式,比如qt和gtk等等
對Xlib應用來說,舉例:
Display *dpy;
Window win;
cairo_set_target_drawable (cr, dpy, win);
第一個參數必須是Display *,第二個參數可以是任何Drawable的内容,比如一個Pixmap.
Pixmap pix;
pix = XCreatePixmap(.....);
然後,再把pix draw到視窗上等等,總之怎麼作都行了。
對于qt應用,可以直接在控件上畫,也可以直接在QPixmap上畫,總之,凡是從
QPaintDevice類派生來的都可以。
比如:
QPixmap pixmap;
pixmap.resize(size());
Display *dpy = pixmap.x11AppDisplay();
Drawable drw = pixmap.handle();
cairo_set_target_drawable(cr,dpy,drw);
然後再把這個pixmap bitBlt到控件上。
同樣,也可以直接在控件上畫,也就是this.x11AppDisplay()和this.handle()
5,通用target,cairo_set_target_image()
使用方法;
char * bitmap;
int width=1024;
int height=768;
int stride = width*4
bimap = malloc(stride*height);
cairo_set_target_image(cr,bitmap,CAIRO_FORMAT_ARGB32,width,height,stride);
最後一個參數表示步進。
如何draw到framebuffer?很簡單了:
打開device,将device mmap到記憶體,然後畫就可以了。
幾個rel函數:
cairo_rel_move_to();
cairo_rel_line_to();
cairo_rel_curve_to();
什麼意思?相對坐标的意思。
其實很簡單,就是以你目前的點為坐标的原點,然後在移動或者畫到制定的位置。
比如:
cairo_move_to(cr,10,10);
cairo_rel_move_to(cr,10,10);
這時候點就在20,20上。
cairo_move_to(cr,20,10);
cairo_line_to(cr,100,100);
這時候畫了一跟線,起點坐标是(20,10),終點坐标是(100,100);
如果是
cairo_move_to(cr,20,10);
cairo_rel_line_to(cr,100,100);
這時候的線其實是從(20,10)畫到了(120,110);
也就是cairo_rel_line_to(cr,100,100);
等于
cairo_line_to(cr,120,110);
同樣cairo_curve_to也是一樣的算法。
取點函數:
cairo_current_point(cr,double *, double *);
double x;
double y;
cairo_move_to(cr,10,10);
cairo_rel_line_to(cr,70,100);
cairo_current_point(cr,&x,&y);
printf("x: %lf, y: %lf/n",x,y);
輸出結果就是:
80.000000
110.000000
scale和rotate
看下面一個例子:
cairo_t *cr;
cr = cairo_create();
cairo_set_target_.....;
cairo_rotate(cr,M_PI/6);//順時針旋轉30度。如果是-M_PI/6,也就是逆時針旋轉30度了。
cairo_rectangle(cr,10,10,100,100);//正方形
cairo_set_rgb_color(cr,1,0,0);
cairo_set_line_width(cr,10);
cairo_stroke(cr);
這時候,發現正方形确實順時針旋轉了三十度,但是還是正方形。
看這個:
cairo_t *cr;
cr = cairo_create();
cairo_set_target_.....;
cairo_scale(cr,1,0.5);也就是水準方向不變,垂直方向縮小為一半。
cairo_rotate(cr,M_PI/6);//順時針旋轉30度。如果是-M_PI/6,也就是逆時針旋轉30度了。
cairo_rectangle(cr,10,10,100,100);//正方形,經過了scale之後,變成了長方形。
cairo_set_rgb_color(cr,1,0,0);
cairo_set_line_width(cr,10);
cairo_stroke(cr);
注意,首先,上下兩條邊的粗細變成了原來的一半,同時,這個圖形不是一個直角矩形,而成了一個平行四邊形。
為什麼?
因為:
進行了scale之後,影響垂直方向的所有變換,都變成了原來的一半。是以,本來應該是每條邊都旋轉30度,現在
上下兩條邊都變成了旋轉一半的度數,也就是15度。
當然,如果你要旋轉一個長方形,隻要直接用cairo_rectangle畫一個長方形就OK了
注意,cairo_rotate隻影響其後的内容,是以,不能在畫完了在rotate。
目前,gtk-cairo 已經可用,可以draw出任意樣式的控件。
qt cairo 沒有明确定義,但是qt4應該已經明确要使用cairo ,目前qt4還沒有定型,太多的技術特征還沒有明确,是以,還不清楚到底怎麼樣,我跟過qt4preview版本,canvas的實作還是不錯的,性能有待提升。
cairo 可以用來寫theme,寫控件,或者就是作一個類似于畫闆一樣的畫圖工具,都可以。
當然cairo 可以用來畫特效,但是,cairo 的目标不在于特效,而且,cairo 能夠提供的隻是豐富的繪圖功能以及平滑效果,比如gtk color select,如果使用cairo ,那個圓圈要平滑的多。
如果用cairo 來作特效,其實還是要依賴算法,比如sproing這個例子,其實就是算法的例子,跟cairo 關系反而不大了,cairo 僅僅做了表示層的一個實作。
個人認為,cairo 真正的目的,不是在gtk/qt之上或者直接面向程式設計人員,他很有可能會成為基本庫,比如,沒有cairo ,構件一個豎寫的文本控件也不困難,但是,你還需要建構一個列印的實作來作豎寫的列印。
cairo 的特長就在于cross device 的rendering。也就是說使用同一套api函數來實作向任何裝置上的輸出。
windows是這麼作的,畫和打是一套函數。
是以,他是一個基礎2D繪圖庫。
有可能他會動搖X的根基或者成為xorg的一部分,走的更底層一點。
也有可能有朝一日跳出X這一層,而直接作為2D繪圖引擎。
總之,這是一個很好的方向。
目前,cairo 也有不完善的地方:
1,API命名有缺陷,比如cairo_current_point之類,不夠規範。
2,還沒有完全實作各種backend的統一,對一個text作rotate,輸出到ps/png和輸出到pdf的結果還是有差别的。
等等。
但是,呵呵,用cairo 畫出來的東西真的很漂亮。
下面的 Hello World 代碼可以在 2005.4.06 的 cairo 和 pixman 下編譯通過,Fedora Core 3,gcc version 3.4.2 20041017 (Red Hat 3.4.2-6.fc3)。反正可以跑,有沒有 memory leak 不知道。
開源就是好啊,否則這種一點文檔都沒有的東西,根本不可能用起來。
大家都來動手寫 Cairo 程式吧
代碼: |
#include <stdio.h> #include <cairo .h> #include <cairo -png.h> int main() { FILE *fp = fopen("hw.png", "w"); cairo_t *cr = cairo_create(); cairo_set_target_image_no_data(cr, CAIRO_FORMAT_ARGB32, 200, 100); cairo_select_font(cr, "Serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_scale_font(cr, 32.0); cairo_set_rgb_color(cr, 0.0, 0.3, 1.0); cairo_move_to(cr, 10.0, 50.0); cairo_show_text(cr, "Hello World"); cairo_surface_write_png (cairo_get_target_surface (cr), fp); cairo_destroy(cr); fclose(fp); } |