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上画东西。 一个比较有代表性的例子如下: 存成t1.c,然后使用下面的命令编译 gcc -o t1 t1.c -lcairo -I/usr/include/cairo 运行./t1,就生成了一个png图片文件,自己用工具打开看就可以了。 TIPS: cairo_stoke(cr); 神来一笔,描线函数,也就是将一个path用线把轮廓描出来。 cairo_fill(cr); 就是填充函数,也就是将一个path用某种颜色填充起来。 比如: 这时候就画了一个方块。 画了一条斜线,从0,0到100,100 设置线宽。
设置线条两端的显示方式
CAIRO_LINE_CAP_ROUND,圆形,圆形的中心就是你定义的两个端点 CAIRO_LINE_CAP_BUTT,方形,就从你定义的点开始。 CAIRO_LINE_CAP_SQUARE,方形,方形的中心就是你定义的两个端点。 也就是如果用square或者round,画出的线条比两个端点的距离要长一点,一面长半个LINE_WIDTH。 注意,cairo_set_line_cap好像只对线条有效,如果你画一个rectangle,角永远是尖的。
通过三点定义一道弧线。
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,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中是无效的。 在这里我据个例子:
还好,用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 Path: path的具体翻译不太好作,理解为路径又不能表达意思,就知道这个东西是path吧。 上面提到了:在画半圆的时候,因为前面进行了ove_to,所以,他会使用move_to到的坐标作为起点。是不是我们必须cairo_save和cairo_restore呢? 不用,有path就可以了。 比如 自己执行一下,看看结果,还是一条直线加大半圆。 我们修改代码如下: 另外,还有一个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 程序吧 Cool 代码: |