Cairo2D图形库
Window 编译最新版Cairo库 - 1.18.0
cairo[01] Windows环境学习笔记——配置cairo
【翻译】写给Python程序员的Cairo教程
介绍
Cairo 是一个支持多输出的 2D 图形库 设备。当前支持的输出目标
包括 X 窗口系统(通过 Xlib 和 XCB)、Quartz、Win32、图像缓冲区、 PostScript、PDF 和 SVG 文件输出。
Cairo 旨在在所有输出介质上产生一致的输出同时利用显示硬件加速(如果可用) (例如,通过 X 渲染扩展)。
cairo API 提供与绘图类似的操作 PostScript 和 PDF 的运算符。在开罗的业务包括 抚摸和填充立方贝塞尔样条曲线,变换和 合成半透明图像和抗锯齿文本渲染。都 绘图操作可以通过任何仿射变换进行变换 (刻度、旋转、剪切等)
Cairo 是作为用 C 编程编写的库实现的语言,但绑定
可用于几种不同的 编程语言。
Cairo 是[自由软件](什么是自由软件? - GNU 工程 - 自由软件基金会),可以重新分发 和/或根据 GNU Lesser General 的条款进行修改 公共许可证 (LGPL) 版本 2.1 或 Mozilla 公共许可证 (MPL) 版本 1.1 由您选择。
后端
Cairo有多个不同的后端,支持多种输出设备。
Cairo目前包含的后端有:
- image:目标是内存中的图像缓冲区。图像缓冲区可以保存到文件中,或者数据可以提供给没有原生后端的其它图形系统。
- cairo-gl:使用OpenGL绘制硬件加速的图形。取代了之前的基于glitz的后端。这个后端支持GLX、WGL和EGL。
- ps:生成适合高质量打印输出的PostScript文件。
- pdf:生成适合高质量打印输出的矢量PDF文件。
- xlib:使用X Window系统的Xlib接口。这个后端可以定位Windows或Pixmaps。如果可用,将使用Render扩展,但不是必需的。
- xcb:提供类似于xlib后端的支持,但使用XCB接口而不是Xlib。
- quartz:Mac OS/X后端
- win32:Windows GDI后端
- svg:生成SVG文件。
- beos:BeOS/Zeta后端
其他正在开发中或已被提议的后端包括: - directfb:正在开发DirectFB后端(http://lists.freedesktop.org/archives/cairo/2005-November/005625.html)
- OpenVG:2008年宣布了OpenVG后端(http://lists.cairographics.org/archives/cairo/2008-January/012833.html)
- os2:正在开发OS/2后端(http://lists.freedesktop.org/archives/cairo/2005-August/004957.html)
语言绑定
Cairo 的语言绑定,它们尽可能地遵循绑定指南
,可用于多种语言:
- Ada : CairoAda
- C++ : cairomm
- Common Lisp : cl-cairo2, cffi-cairo
- COM-Wrapper : 也称为 “ActiveX-dll” … 使用 VB5/6 或 VBA 最简单,随 cairo 一起作为 “卫星-dll”(使用 StdCall 调用约定编译)下载页面: (那里也有一个大的 VB-Democode-Tutorial)
- Harbour : hbcairo
- Haskell : hscairo
- Java : java-gnome 中的 org.freedesktop.cairo
- C# : https://github.com/zwcloud/CairoSharp
- Mono 和 .NET 绑定曾经捆绑在 Mono 分发中的 Mono.Cairo 库中,但现在不再维护。
在某个时候,NDesk 也提供了绑定。 - Nickle : cairo-nickle
- Objective Caml : cairo-ocaml
- Perl : cairo-perl
- PHP : cairo-php
- Prolog : PLcairo
- Python : pycairo, qahirah 高级 Pythonic 绑定,使用 CFFI 创建的 cairocffi 绑定,pygobject 包含 Cairo 绑定。
- Ruby : rcairo
- Scheme: guile-cairo
- Ypsilon Scheme 附带 Cairo 绑定
- Squeak : Rome
- Digitalmars D: cairoD - D 直接 C API 的简单包装器
- Lua : Lua-Cairo, Lua-OOCairo, LuaCairo
- LuaJIT: luapower/cairo
- Vala
Pixman 绑定
Python : python_pixman 是一个高级像素操作库(示例)
工具包绑定
由于 cairo 只是一个绘图库,将其与图形用户界面工具包集成可能非常有用。
FLTK 完全支持 cairo(通过 “–enable-cairo” 编译开关)。
GNUstep 绑定:http://cvs.savannah.gnu.org/viewvc/gnustep/gnustep/core/back/Source/cairo/
GTK+ 2.8+ 完全支持 cairo。
绑定指南
附录 A. 为 cairo 创建语言绑定
虽然 cairo 是用 C 实现的,并且有一个 C API,但预计许多 cairo 的用户将会使用除 C 以外的其他语言。连接核心 cairo 库到另一种语言的粘合剂被称为语言绑定。这个附录试图收集创建 cairo 语言绑定时出现的问题,并提出标准化的解决方案,以促进不同语言绑定之间的连贯性。
一般考虑因素
cairo_t 类型命名是一个特殊的例外。该对象是“一个 cairo 上下文”,而不是“一个 cairo”,并且像 cairo_t 而不是 cairo_context_t 以及 cairo_set_source() 而不是 cairo_context_set_source() 这样的名称,只是为了让 C API 更易于接受而进行的缩写。在具有面向对象语法的语言中,这种缩写 much less useful. 实际上,如果使用 ‘Cairo’ 作为命名空间,那么在许多语言中,您最终会得到一个像 ‘Cairo.Cairo’ 这样的荒谬类型名称。因此,为了在语言间保持一致性,所有面向对象的语言都应该将此类型命名为 cairo_context_t。
Cairo 的类型名称和方法名称的标点和大小写应该被更改,以匹配语言的通用约定。在 Java 中,类型名称用 StudlyCaps 写作,方法名称用 javaCaps,因此 cairo_font_extents_t 将变为 FontExtents,cairo_set_source() 将变为 setSource(),而 cairo_get_source() 将变为 getSource()。与更改标点和大小写相比,更改方法名称本身应更加谨慎。即使在你使用的语言中通常省略 getters 中的 get,你也不应该将 cairo_get_source() 绑定为 cr.source()。同样,cairo_set_source(cr, source) 应该绑定到 cr.setSource(source)。
Xlib
使用 Cairo 与 X11/Xlib
Cairo 是一个图形库,它提供了独立于实际后端的通用绘图原语。它附带了一组后端,比如 PNG 或 PDF。这些后端之一允许使用 libcairo 在 X11 窗口上进行绘制。尽管有像 Qt
或 GTK
这样非常强大的库,但它们对于简单应用程序来说通常过于复杂。
下面我将解释如何使用 Xlib 打开一个 X11 窗口,并展示如何使用 Cairo 图形创建图形输出。
打开窗口
X11 可能是最灵活的图形界面,这让它看起来至少在初次接触时有些复杂。要打开一个窗口,您需要执行以下步骤:
- 连接到 X 服务器:
.XOpenDisplay(3)
- 选择输出屏幕:
.DefaultScreen(3)
- 创建一个窗口:
.XCreateSimpleWindow(3)
- 选择输入事件:. 请注意,这对于打开窗口不是必须的,但通常您会希望接收诸如鼠标点击和键盘输入之类的事件:
.XSelectInput(3)
- 显示窗口:
.XMapWindow(3)
窗口准备好后,它会被映射到一个 cairo Xlib 表面。以下函数展示了如何进行操作。
cairo_surface_t *cairo_create_x11_surface0(int x, int y)
{
Display *dsp;
Drawable da;
int screen;
cairo_surface_t *sfc;
if ((dsp = XOpenDisplay(NULL)) == NULL)
exit(1);
screen = DefaultScreen(dsp);
da = XCreateSimpleWindow(dsp, DefaultRootWindow(dsp),
0, 0, x, y, 0, 0, 0);
XSelectInput(dsp, da, ButtonPressMask | KeyPressMask);
XMapWindow(dsp, da);
sfc = cairo_xlib_surface_create(dsp, da,
DefaultVisual(dsp, screen), x, y);
cairo_xlib_surface_set_size(sfc, x, y);
return sfc;
}`
接收事件
接下来的任务是接收事件。上面的函数配置了窗口以接收鼠标按钮和键盘事件。该函数返回 X 服务器事件队列中的下一个事件,如果队列空了它会阻塞。如果您的工具不能阻塞,因为您需要持续交互——例如在电脑游戏中——您需要在检索事件之前检查队列中是否有事件,以避免阻塞。这是通过 XNextEvent(3)``XNextEvent(3)``XPending(3)
完成的。该函数立即返回队列中的事件数。因此,如果没有事件,它将返回 0。
XNextEvent(3) 返回一个 XEvent,这实际上是一个联合体,包含了所有不同类型的 X 事件。类型字段用于区分它们。XKeyEvent 接收的键码需要使用 XLookupString(3) 转换为符号。所有符号都在 X11/keysymdef.h 中定义。以下函数展示了如何进行转换。
int cairo_check_event(cairo_surface_t *sfc, int block)
{
char keybuf[8];
KeySym key;
XEvent e;
for (;;)
{
if (block || XPending(cairo_xlib_surface_get_display(sfc)))
XNextEvent(cairo_xlib_surface_get_display(sfc), &e);
else
return 0;
switch (e.type)
{
case ButtonPress:
return -e.xbutton.button;
case KeyPress:
XLookupString(&e.xkey, keybuf, sizeof(keybuf), &key, NULL);
return key;
default:
fprintf(stderr, "Dropping unhandled XEevent.type = %d.\n", e.type);
}
}
}
将所有这些内容结合起来,就可以创建一个简单的窗口示例。下载 cairo_xlib_simple.c 源文件。
动画和全屏
要创建动画,你只需在循环中重新绘制图像。问题是,你基本上无法控制 X 服务器实际更新屏幕的时间。所有图形操作都被排队,在某个时间点由 X 服务器处理。这可能会导致动画闪烁。解决方案是将所有操作推送到一个组 (cairo_push_group()),而不是直接绘制到表面上。最后,将组弹出 (cairo_pop_group_to_source()) 到 Cairo 绘图源,并一次性绘制 (cairo_paint())。最后,我们强制 X 服务器刷新其队列 (cairo_surface_flush())。尽管这产生了好的结果,但如果你打算编写一个高速图形密集型的电脑游戏,你应该知道 (Cairo + Xlib) 不是首选方法。如果是这种情况,你应该开始学习 OpenGL
或 SDL
。
让窗口全屏在 X11 中似乎是一个很好的保护秘密。全屏是一个特定窗口的属性,比如“最大化”、“最小化”等等。这个属性是通过函数 XChangeProperty(3) 设置的,使用 _NET_WM_STATE_FULLSCREEN 属性。
将所有这些内容结合起来,就得到了第二个最终的示例 cairo_xlib.c。
最后注意:由于窗口的大小可以改变,你必须相应地做出反应。窗口变更事件作为一个 XConfigureEvent 发送到事件队列中。它包含了新的宽度和高度,这必须通过 cairo_xlib_surface_set_size() 传递给 Cairo 表面。
Cairo 教程
这个教程源自 Michael Urman 为 Python 程序员编写的 cairo 教程。原始的代码片段已经被翻译成 C,文本仅进行了必要的更改。
Cairo 是一个强大的二维图形库。本文档将向您介绍 cairo 的工作原理以及您将使用许多函数来创建您期望的图形体验。
为了在您的计算机上跟随本教程,您需要以下几样东西:
-
Cairo 本身。您需要库和开发文件。如果您还没有,请参见下载部分。
-
C 编译器。常见问题解答部分包含了一个最小示例,说明如何将代码转换成产生期望输出的程序。
如果您想看到本教程中包含的代码片段的实际效果,您可以尝试点击一些图像。您将得到一个包含描述的绘图代码的小型 C 程序。
或者,如果您愿意接受挑战,您可以将示例翻译成您偏好的语言和主机环境,并且只需要上述的 cairo。
注意:文本中提到了 cairo_push_group() 和 cairo_pop_group()。至少需要 cairo 1.2.0 版本才能使用这些函数。
Cairo 的绘图模型
为了解释 cairo 使用的操作,我们首先深入研究 cairo 如何建模绘图的模型。涉及的只有几个概念,然后通过不同的方法反复应用。首先我会描述名词:目标、源、遮罩、路径和上下文。之后我会描述动词,这些动词提供了一种方式来操作名词并绘制您希望创建的图形。
名词(Nouns)
Cairo 的名词有些抽象。为了使它们具体化,我包括了描述它们如何交互的图表。您在这部分看到的图表中的前三个名词是三个图层。第四个名词,路径,在相关时绘制在中间层。最后一个名词,上下文,没有显示。
目标(Destination)
目标是你绘制的表面。它可能是一个像素数组的数组,就像本教程中的那样,也可能是一个 SVG 或 PDF 文件,或者是其他东西。这个表面收集了你的图形元素,当你应用它们时,允许你像在画布上绘画一样构建一个复杂的作品。
源(Source)
源是您即将使用的“颜料(paint)”。我将其展示为它本来的样子——在几个示例中为纯黑色——但为了显示下层,它是半透明的。与真实的颜料不同,它不必是单一颜色;它可以是一种图案(pattern),甚至可以是之前创建的目标表面(参见如何从一个表面绘制到另一个表面?)。而且与真实的颜料不同,它可以包含透明度信息——Alpha 通道。
遮罩(Mask)
遮罩是最重要的部分:它控制您将源应用到目标的位置。我将它展示为带有允许源通过的孔的黄色层。当你应用一个绘图动词时,就像你在目标上盖上源的印章。遮罩允许的任何地方,源都会被复制。遮罩不允许的任何地方,什么都不会发生。
路径(Path)
路径介于遮罩和上下文之间。我将它展示为遮罩层上的细绿色线条。通过路径动词来操作它,然后由绘图动词使用。
上下文(Context)
上下文记录了所有受动词影响的事物。它跟踪一个源、一个目标和一个遮罩。它还跟踪一些辅助变量,比如你的线条宽度和样式、字体面和大小,以及更多。最重要的是,它跟踪路径,这是通过绘图动词转换成遮罩的。
在您开始使用 cairo 绘制东西之前,您需要创建上下文。上下文存储在 cairo 的中心数据类型中,称为 cairo_t。当您创建一个 cairo 上下文时,它必须与特定的表面绑定——例如,如果您想创建一个 PNG 文件,那么就是与一个图像表面绑定。也有一个表面数据类型,称为 cairo_surface_t。您可以像这样初始化您的 cairo 上下文:cairo_t 和 cairo_surface_t。
cairo_surface_t *surface;
cairo_t *cr;
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 120, 120);
cr = cairo_create(surface);
这个示例中的 cairo 上下文绑定到一个尺寸为 120 x 120 的图像表面,每个像素有 32 位来存储 RGB 和 Alpha 信息。表面可以针对大多数 cairo 后端创建,具体细节请参阅手册。
动词(Vers)
您在程序中使用 cairo 的原因是为了绘制。Cairo 内部使用一个基本的绘图操作来绘制:源和遮罩自由地放置在目标上的某个地方。然后将所有层压在一起,将源的颜料转移到遮罩允许的任何地方。因此,以下五种绘图动词或操作在这一点上是相似的。它们的不同之处在于它们如何构建遮罩。
绘制路径(Stroke)
cairo_stroke() 操作沿着路径绘制虚拟笔触。它允许源通过遮罩在路径周围以薄(或厚)的线条进行传递,根据笔触的线条宽度(line width)、虚线样式(dash sttyle)和线条末端(line caps)。
注意:要看到代码片段的实际效果,请使用与右侧的图标链接的 stroke.c 文件。直接将代码片段粘贴到 FAQ 中的 hello.c 文件中可能会得到意外的结果,因为不同的缩放。继续阅读;缩放将在下文的“与变换一起工作”部分中解释。
cairo_set_line_width(cr, 0.1);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_rectangle(cr, 0.25, 0.25, 0.5, 0.5);
cairo_stroke(cr);
填充(Fill)
cairo_fill() 操作将路径用于像彩色书的线条,并允许源通过遮罩,其边界是路径的孔。对于复杂的路径(具有多个封闭子路径的路径——比如甜甜圈——或者自相交的路径),这会受到填充规则(fill rule)的影响。请注意,虽然绘制路径会在路径的每侧转移源的半条线宽,但填充路径会直接填充到路径的边缘,而不会更远。
cairo_set_source_rgb (cr, 0, 0, 0);
cairo_rectangle (cr, 0.25, 0.25, 0.5, 0.5);
cairo_fill (cr);
显示文本 / 字形(Show Text / Glyphs)
cairo_show_text() 操作通过文本形成遮罩。您可以将 cairo_show_text() 视为创建路径的快捷方式,通过 cairo_text_path() 然后使用 cairo_fill() 进行传递。请注意,cairo_show_text() 会缓存字形,如果你处理大量文本,它会非常高效。
cairo_text_extents_t te;
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_select_font_face(cr, "Georgia",
CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 1.2);
cairo_text_extents(cr, "a", &te);
cairo_move_to(cr, 0.5 - te.width / 2 - te.x_bearing,
0.5 - te.height / 2 - te.y_bearing);
cairo_show_text(cr, "a");
绘制(Paint)
cairo_paint() 操作使用一个遮罩,将整个源转移到目标。有些人认为这是一个无限大的遮罩,而其他人则认为它没有遮罩;结果是相同的。相关的操作 cairo_paint_with_alpha() 同样允许将整个源转移到目标,但它只传输提供的颜色的百分比。
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
cairo_paint_with_alpha (cr, 0.5);`
遮罩(Mask)
cairo_mask() 和 cairo_mask_surface() 操作允许根据第二个源图案或表面的透明度/不透明度进行传输。当图案或表面不透明时,当前源被转移到目标。当图案或表面透明时,什么都不会被传输。
cairo_pattern_t *linpat, *radpat;
linpat = cairo_pattern_create_linear (0, 0, 1, 1);
cairo_pattern_add_color_stop_rgb (linpat, 0, 0, 0.3, 0.8);
cairo_pattern_add_color_stop_rgb (linpat, 1, 0, 0.8, 0.3);
radpat = cairo_pattern_create_radial (0.5, 0.5, 0.25, 0.5, 0.5, 0.75);
cairo_pattern_add_color_stop_rgba (radpat, 0, 0, 0, 0, 1);
cairo_pattern_add_color_stop_rgba (radpat, 0.5, 0, 0, 0, 0);
cairo_set_source (cr, linpat);
cairo_mask (cr, radpat);`
使用 Cairo 绘制(Drawing with Cairo)
为了创建您想要的图像,您需要为每个绘图动词准备上下文。要使用 cairo_stroke() 或 cairo_fill(),您首先需要一个路径。要使用 cairo_show_text(),您必须通过其插入点定位您的文本。要使用 cairo_mask(),您需要第二个源图案(pattern)或表面(syrface)。为了使用任何操作,包括 cairo_paint(),您都需要一个主要的源。
准备和选择源(Preparing and Selecting a Source)
在 cairo 中,有三种主要的源:颜色、渐变和图像。颜色是最简单的;它们使用整个源的均匀色调和不透明度。您可以使用 cairo_set_source_rgb() 和 cairo_set_source_rgba() 选择这些源,无需任何准备。使用 cairo_set_source_rgb (cr, r, g, b) 与使用 cairo_set_source_rgba (cr, r, g, b, 1.0) 相等,它将您的源颜色设置为使用全不透明度。
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_move_to(cr, 0, 0);
cairo_line_to(cr, 1, 1);
cairo_move_to(cr, 1, 0);
cairo_line_to(cr, 0, 1);
cairo_set_line_width(cr, 0.2);
cairo_stroke(cr);
cairo_rectangle(cr, 0, 0, 0.5, 0.5);
cairo_set_source_rgba(cr, 1, 0, 0, 0.80);
cairo_fill(cr);
cairo_rectangle(cr, 0, 0.5, 0.5, 0.5);
cairo_set_source_rgba(cr, 0, 1, 0, 0.60);
cairo_fill(cr);
cairo_rectangle(cr, 0.5, 0, 0.5, 0.5);
cairo_set_source_rgba(cr, 0, 0, 1, 0.40);
cairo_fill (cr);
渐变描述了颜色的过渡,通过设置开始和停止参考位置以及一系列“停止”来定义。线性渐变(Linear gradients)由两个点构建,这些点通过平行线通过,以定义开始和停止位置。放射性渐变(Radial gradients)也是由两个点构建,但每个点都有一个与圆的半径相关联的圆,以定义开始和停止位置。使用 cairo_add_color_stop_rgb() 和 cairo_add_color_stop_rgba() 可以添加颜色到渐变中,这些函数接受一个颜色,如 cairo_set_source_rgb*(),以及一个偏移量,以指示它位于参考位置之间。相邻停止之间的颜色在空间中平均分布,形成平滑的混合。最后,可以使用 cairo_set_extend() 来控制参考位置之外的行为。
int i, j;
cairo_pattern_t *radpat, *linpat;
radpat = cairo_pattern_create_radial(0.25, 0.25, 0.1, 0.5, 0.5, 0.5);
cairo_pattern_add_color_stop_rgb(radpat, 0, 1.0, 0.8, 0.8);
cairo_pattern_add_color_stop_rgb(radpat, 1, 0.9, 0.0, 0.0);
for (i=1; i<10; i++)
for (j=1; j<10; j++)
cairo_rectangle(cr, i/10.0 - 0.04, j/10.0 - 0.04, 0.08, 0.08);
cairo_set_source(cr, radpat);
cairo_fill(cr);
linpat = cairo_pattern_create_linear(0.25, 0.35, 0.75, 0.65);
cairo_pattern_add_color_stop_rgba(linpat, 0.00, 1, 1, 1, 0);
cairo_pattern_add_color_stop_rgba(linpat, 0.25, 0, 1, 0, 0.5);
cairo_pattern_add_color_stop_rgba(linpat, 0.50, 1, 1, 1, 0);
cairo_pattern_add_color_stop_rgba(linpat, 0.75, 0, 0, 1, 0.5);
cairo_pattern_add_color_stop_rgba(linpat, 1.00, 1, 1, 1, 0);
cairo_rectangle (cr, 0.0, 0.0, 1, 1);
cairo_set_source (cr, linpat);
cairo_fill (cr);
图像包括从现有文件加载的表面,如,以及作为早期目的地的表面,这些表面是在 cairo 内部创建的。从 cairo 1.2 开始,将早期目的地作为源的最简单方法是使用,然后是 或 cairo_pop_group_to_source()。使用 仅在选择新源时使用它,而使用 cairo_pop_group() 当你想要保存它以便你可以反复选择它时。
图像包括使用 cairo_image_surface_create_from_png() 从现有文件加载的表面以及从 cairo 内部作为早期目标创建的表面。 从 cairo 1.2 开始,创建和使用早期目标作为源的最简单方法是使用 cairo_push_group() 和 cairo_pop_group() ) 或cairo_pop_group_to_source()。 使用 cairo_pop_group_to_source() 在您选择新源之前使用它,并在您想要保存它时使用 cairo_pop_group() 以便您可以使用 cairo_set_source() 反复选择它。
创建路径(Creating a Path)
Cairo 始终有一个活动路径。如果你调用 cairo_stroke(),它会使用你的线条设置绘制路径。如果你调用 cairo_fill(),它会填充路径的内部。但是,很多时候路径是空的,两个调用都不会改变你的目的地。为什么路径这么频繁地是空的?一方面,它是空的开始;更重要的是,每次调用 cairo_stroke() 或 cairo_fill() 后,它都会被清空,以便你开始构建下一个路径。
如果你想在同一个路径上做多次操作呢?例如,要绘制一个带有黑色边框的红色矩形,你希望用红色源填充矩形路径,然后用黑色源绘制路径。矩形路径可以轻松地多次创建,但许多路径更加复杂。
Cairo 通过支持其操作的替代版本来轻松地重复使用路径。这些操作都绘制相同的内容,但替代版本不会重置路径。对于绘制,除了 cairo_stroke(),还有 cairo_stroke_preserve();对于填充,cairo_fill_preserve() 与 cairo_fill() 一起使用。即使是设置剪裁也有一个保留变体。除了选择何时保留您的路径之外,只有少数常见的操作。
移动(Moving)
Cairo 在创建路径时使用点连线的风格系统。从点 1 开始,绘制到点 2,然后是点 3,依此类推。当你开始一个路径,或者当你需要开始一个新的子路径时,你希望它就像点 1 一样:没有任何东西与它相连。为此,使用 cairo_move_to()。这设置了一个没有与前一个点相连的当前参考点。还有一个相对坐标的变体,cairo_rel_move_to(),它将新的参考点设置为指定距离外的当前参考点。在设置第一个参考点之后,使用其他路径操作,这些操作都会更新参考点并以某种方式与之连接。
cairo_move_to(cr, 0.25, 0.25);
直线(Straight Lines)
无论是使用绝对坐标 cairo_line_to()(从参考点到这个点扩展路径),还是相对坐标 cairo_rel_line_to()(从参考点这个方向延伸路径这么远),路径连接将是一条直线。新的参考点将位于线的另一端。
cairo_line_to(cr, 0.5, 0.375);
cairo_rel_line_to(cr, 0.25, -0.125);
弧(Arcs)
弧是圆的外部部分。与直线不同,您直接指定的点并不在路径上。相反,它是构成路径新增部分的圆的中心。必须指定圆上的起始和结束点,这些点要么顺时针由 cairo_arc() 连接,要么逆时针由 cairo_arc_negative() 连接。如果之前的参考点不在这个新曲线的新曲线上,它会从它直接添加一条直线到弧开始的地方。然后将参考点更新为弧结束的地方。只有绝对版本。
cairo_arc(cr, 0.5, 0.5, 0.25 * sqrt(2), -0.25 * M_PI, 0.25 * M_PI);
曲线(Curves)
Cairo 中的曲线是三次 Bézier 样条。它们从当前参考点开始,平滑地跟随两个其他点的方向(不经过它们)以到达第三个指定点。就像直线一样,也有绝对(cairo_curve_to())和相对(cairo_rel_curve_to())版本。请注意,相对变体指定所有点相对于之前的参考点,而不是每个点相对于曲线的前一个控制点。
cairo_rel_curve_to(cr, -0.25, -0.125, -0.25, 0.125, -0.5, 0);
闭合路径(Close the path)
Cairo 还可以通过绘制一条直线到当前子路径的开始来闭合路径。这条直线对于多边形的最后一条边很有用,但对于基于曲线的形状并不直接有用。闭合路径与开放式路径根本不同:它是一个连续的路径,没有开始或结束。闭合路径没有线条末端,因为无处放置它们。
cairo_close_path(cr);
文本(Text)
最后,文本可以通过 cairo_text_path() 转换为路径。从文本创建的路径就像任何其他路径,支持绘线或填充操作。这个路径放置在当前参考点上,所以在你将文本转换为路径之前,应该先使用 cairo_move_to() 将路径放置在您想要的位置。然而,如果你在处理大量文本时,这样做会有性能问题;如果可能,你应该优先使用 cairo_show_text() 而不是 cairo_text_path()和 cairo_fill()。
理解文本(Understanding Text)
为了有效地使用文本,你需要知道它将去哪里。方法 cairo_font_extents() 和 cairo_text_extents() 可以为你提供这些信息。由于这个图表太小,很难看清楚,我建议获取它的源(source)并将其大小增加到 600。它显示了参考点(红色点)、建议的下一个参考点(蓝色点)、边界框(虚线蓝色线)、引导位移(实线蓝色线)以及高度、上升线、基线和下降线(虚线绿色)之间的关系。
参考点总是在基线上。下降线在基线以下,反映了字体中所有字符的近似边界框。但它是一种艺术选择,旨在指示对齐而不是真正的边界框。同样,基线以上的是上升线。在上升线之上的是高度线,这是艺术家推荐的基线之间的间距。这三条线都是从基线报告的距离,尽管它们的方向不同,但都是正数。
引导是从参考点到边界框上左角的位移。对于 x 方向的位移,它通常是零或一个小的正数,但对于像 j 这样的字符,它可以是负的;对于 y 方向的位移,它几乎总是负值。然后,宽度和高度描述了边界框的大小。前进带你到下一个字母的建议参考点。请注意,如果引导是负的,或者前进比宽度所暗示的要小,后续文本块的边界框可能会重叠。
除了放置之外,您还需要指定一个面、样式和大小。使用 cairo_select_font_face() 一起设置面和样式,使用 cairo_set_font_size() 设置大小。如果您需要更精细的控制,尝试获取一个 cairo_font_options_t 对象,使用 cairo_get_font_options(),调整它,然后使用 cairo_set_font_options() 设置它。
与变换一起工作(Working with Transforms)
变换有三个主要用途。首先,它们允许您设置一个易于思考和工作的坐标系,同时输出可以是任何大小。其次,它们允许您创建在 (0, 0) 附近工作或应用的辅助函数,但可以在输出图像的任何位置使用。第三,它们让您变形图像,将圆形弧变成椭圆形弧等。变换是设置两个坐标系统之间关系的一种方式。设备空间坐标系与表面绑定,不能更改。用户空间坐标系默认匹配那个空间,但可以因上述原因而改变。辅助函数 cairo_user_to_device() 和 cairo_user_to_device_distance() 告诉您用户坐标位置或距离的设备坐标。相应地,cairo_device_to_user() 和 cairo_device_to_user_distance() 告诉您设备坐标位置或距离的用户坐标。请记住,将位置通过非距离变体发送,而相对移动或其他距离通过距离变体发送。
我利用所有这些原因来绘制本文档中的图表。无论我是在绘制 120 x 120 还是 600 x 600,我都使用 cairo_scale() 给我一个 1.0 x 1.0 的工作区。为了将结果放置在右侧列中,例如在讨论 cairo 的绘图模型时,我使用 cairo_translate()。为了为重叠的层添加透视视图,我在 cairo_matrix_t 上设置了一个任意的变形,使用 cairo_transform()。
为了理解您的变换,请从下到上阅读它们,并将它们应用于您正在绘制的点。为了确定要创建哪些变换,请逆向思考这个过程。例如,如果我想让我的 1.0 x 1.0 工作区在 120 x 120 像素表面的中间是 100 x 100 像素,我可以以三种方式之一设置它:
- cairo_translate (cr, 10, 10); cairo_scale (cr, 100, 100);
- cairo_scale (cr, 100, 100); cairo_translate (cr, 0.1, 0.1);
- cairo_matrix_t mat; cairo_matrix_init (&mat, 100, 0, 0, 100, 10, 10); cairo_transform (cr, &mat);
在相关的情况下使用第一种,因为它通常是最可读的;在必要时使用第三种,以访问主要函数不可用的额外控制。
在相关的情况下使用第一种,因为它通常是最可读的;在必要时使用第三种,以访问主要函数不可用的额外控制。
在变换下尝试绘制线时,要小心。即使你在缩放因子为 1 的情况下设置了线条宽度,线条宽度设置始终以用户坐标系为单位,并且不会被设置缩放修改。在操作缩放时,线条的宽度会乘以那个缩放因子。要指定以像素为单位的线条宽度,请使用 cairo_device_to_user_distance() 将一个 (1, 1) 设备空间距离转换为,例如,一个 (0.01, 0.01) 用户空间距离。请注意,如果你的变换变形了图像,可能没有一种方法可以指定具有均匀宽度的线条。
接下来去哪里
这结束了教程。它没有涵盖 cairo 中的所有函数,所以对于一些“高级”的较少使用的特性,您需要寻找其他资源。示例背后的代码(层图(layer diagrams),绘制插图(drawing illustrations))使用了一些技巧,这些技巧在文档中没有描述,所以分析它们可能是很好的第一步。cairographics.org 上的其他示例指向不同的方向。与所有事物一样,知道工具的规则和能够很好地使用它之间存在很大的差距。本文档的最后一部分提供了一些帮助您跨越这个差距的想法。
技巧和窍门
在之前的部分中,您应该已经对 cairo 使用来创建图像的操作有了坚实的掌握。在本节中,我收集了一些我特别有用或不显而易见的片段。我自己也是 cairo 的新手,所以可能还有其他更好的方法来做这些事情。如果您发现更好的方法,或者发现另一种酷炫的方法,请告诉我,也许我可以将这些技巧整合到其中。
线条宽度
当您在统一缩放变换下工作时,您不能直接使用像素作为线条的宽度。但是,使用 cairo_device_to_user_distance()(假设像素宽度为 1)的帮助,这很容易实现。
double ux=1, uy=1;
cairo_device_to_user_distance (cr, &ux, &uy);
if (ux < uy)
ux = uy;
cairo_set_line_width (cr, ux);
当您在变形缩放下工作时,您可能希望线条宽度在设备空间中保持统一。对于这种情况,您应该在绘制路径之前返回到统一缩放。在图像中,左侧的弧是在变形下绘制的,而右侧的弧是在统一缩放下绘制的。
cairo_set_line_width (cr, 0.1);
cairo_save (cr);
cairo_scale (cr, 0.5, 1);
cairo_arc (cr, 0.5, 0.5, 0.40, 0, 2 * M_PI);
cairo_stroke (cr);
cairo_translate (cr, 1, 0);
cairo_arc (cr, 0.5, 0.5, 0.40, 0, 2 * M_PI);
cairo_restore (cr);
cairo_stroke (cr);
文本对齐(Text Alignment)
当您尝试在不同的位置逐个字符地居中文本时,您必须决定您希望如何居中它。例如,以下代码实际上会逐个字符地居中字母,当您的字母大小不同时,结果会非常糟糕。(与大多数示例不同,在这里我假设一个 26 x 1 的工作区。)
cairo_text_extents_t te;
char alphabet[] = "AbCdEfGhIjKlMnOpQrStUvWxYz";
char letter[2];
for (i=0; i < strlen(alphabet); i++) {
*letter = '\0';
strncat (letter, alphabet + i, 1);
cairo_text_extents (cr, letter, &te);
cairo_move_to (cr, i + 0.5 - te.x_bearing - te.width / 2,
0.5 - te.y_bearing - te.height / 2);
cairo_show_text (cr, letter);
}
相反,垂直居中必须基于字体的一般大小,因此保持您的基线稳定。请注意,现在确切的定位取决于字体本身提供的度量,因此从字体到字体的结果不一定相同。
cairo_font_extents_t fe;
cairo_text_extents_t te;
char alphabet[] = "AbCdEfGhIjKlMnOpQrStUvWxYz";
char letter[2];
cairo_font_extents (cr, &fe);
for (i=0; i < strlen(alphabet); i++) {
*letter = '\0';
strncat (letter, alphabet + i, 1);
cairo_text_extents (cr, letter, &te);
cairo_move_to (cr, i + 0.5 - te.x_bearing - te.width / 2,
0.5 - fe.descent + fe.height / 2);
cairo_show_text (cr, letter);
}
Cairo API
适用于 Cairo 1.18.0 的信息。
-
cairo_t — 绘图上下文
-
Paths — 创建路径和操作路径数据
-
cairo_pattern_t — 绘图的资源
-
Regions — 表示像素对齐的区域
-
Transformations — 操作当前变换矩阵
-
text — 渲染文本和字形
-
Raster Sources — 提供任意的图像数据
-
Tags and Links — 超链接和文档结构
-
cairo_font_face_t — 字体面孔(font face)的基础类
-
cairo_scaled_font_t — 指定大小和选项的字体面孔
-
cairo_font_options_t — 字体应该如何被渲染
-
FreeType Fonts — 对 FreeType 的字体支持
-
Win32 GDI Fonts — 对 Microsoft Windows 的字体支持
-
DWrite Fonts — 对 Microsoft DirectWrite 的字体支持
-
Quartz (CGFont) Fonts — 通过 Apple 操作系统上的 Core Text 的字体支持
-
User Fonts — 使用用户提供字体数据的字体支持
-
cairo_device_t — 与底层渲染系统的接口
-
cairo_surface_t — 表面(surfaces)的基础类
-
Image Surfaces — 渲染到内存缓冲区
-
PDF Surfaces — 渲染 PDF 文档
-
PNG Support — 读取和写入 PNG 图像
-
PostScript Surfaces — 渲染 PostScript 文档
-
Recording Surfaces — 记录所有绘图操作
-
Win32 Surfaces — Microsoft Windows surface支持
-
SVG Surfaces — 渲染 SVG 文档
-
Quartz Surfaces — 渲染到 Quartz 表面
-
XCB Surfaces — 使用 XCB 库的 X Window System 渲染
-
XLib Surfaces — 使用 XLib 的 X Window System 渲染
-
XLib-XRender Backend — 使用 XLib 和 X Render 扩展的 X Window System 渲染
-
Script Surfaces — 渲染到可重播的脚本
-
Surface Observer — 观察其他表面
-
Tee surface — 重定向输入到多个表面
-
cairo_matrix_t — 通用矩阵操作
-
Error handling — 解码 cairo 的状态
-
Version Information — 编译时和运行时版本检查
-
Types — 通用数据类型
A. Creating a language binding for cairo(A.为 cairo 创建语言绑定)
General considerations(一般考虑因素)
Overloading and optional arguments(重载和可选参数)