Cairo2D图形库

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 窗口上进行绘制。尽管有像 QtGTK 这样非常强大的库,但它们对于简单应用程序来说通常过于复杂。
下面我将解释如何使用 Xlib 打开一个 X11 窗口,并展示如何使用 Cairo 图形创建图形输出。

打开窗口

X11 可能是最灵活的图形界面,这让它看起来至少在初次接触时有些复杂。要打开一个窗口,您需要执行以下步骤:

  1. 连接到 X 服务器:.XOpenDisplay(3)
  2. 选择输出屏幕:.DefaultScreen(3)
  3. 创建一个窗口:.XCreateSimpleWindow(3)
  4. 选择输入事件:. 请注意,这对于打开窗口不是必须的,但通常您会希望接收诸如鼠标点击和键盘输入之类的事件:.XSelectInput(3)
  5. 显示窗口:.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) 不是首选方法。如果是这种情况,你应该开始学习 OpenGLSDL

让窗口全屏在 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 的工作原理以及您将使用许多函数来创建您期望的图形体验。

为了在您的计算机上跟随本教程,您需要以下几样东西:

  1. Cairo 本身。您需要库和开发文件。如果您还没有,请参见下载部分。

  2. 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 像素,我可以以三种方式之一设置它:

  1. cairo_translate (cr, 10, 10); cairo_scale (cr, 100, 100);
  2. cairo_scale (cr, 100, 100); cairo_translate (cr, 0.1, 0.1);
  3. 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 API

适用于 Cairo 1.18.0 的信息。

Drawing(绘图)

Fonts(字体)

Surfaces(表面)

Utilities(实用工具)

Index(索引)

Index of new symbols in 1.0

Index of new symbols in 1.2

Index of new symbols in 1.4

Index of new symbols in 1.6

Index of new symbols in 1.8

Index of new symbols in 1.10

Index of new symbols in 1.12

Index of new symbols in 1.14

Index of new symbols in 1.16

Index of new symbols in 1.18

A. Creating a language binding for cairo(A.为 cairo 创建语言绑定)

General considerations(一般考虑因素)

Memory management(内存管理)

Multiple return values()多个返回值

Overloading and optional arguments(重载和可选参数)

Streams and File I/O(流和文件 I/O)

Error handling(错误处理)

Patterns(图案)

Surfaces(表面)

Fonts(字体)

cairo_path_t

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

childish_tree

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值