Apple - Quartz 2D Programming Guide

本文翻译自:Quartz 2D Programming Guide(更新时间:2017-03-21
https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/Introduction/Introduction.html#//apple_ref/doc/uid/TP30001066


文章目录


一、介绍

1、关于 Quartz 2D

Core Graphics 又称为 Quartz 2D,是一款先进的二维绘图引擎,可用于 iOS、tvOS 和 macOS 应用程序开发。

Quartz 2D 提供低级、轻量级的 2D 渲染,无论使用哪种显示或打印设备,都能提供无与伦比的输出保真度。

Quartz 2D 独立于分辨率和设备。

Quartz 2D API 易于使用,并提供强大的功能,如透明层、基于路径的绘制、离屏渲染、高级色彩管理、抗锯齿渲染以及 PDF 文档创建、显示和解析。


2、谁应该阅读本文档?

本文档适用于需要执行以下任何任务的开发人员:

  • 绘制图形
  • 在应用程序中提供图形编辑功能
  • 创建或显示位图图像
  • 处理 PDF 文档

3、本文档的组织

本文档分为以下章节:

  • Quartz 2D 概述描述了页面、绘图目标、Quartz 不透明数据类型、图形状态、坐标和内存管理,并介绍了 Quartz 的"内部"工作原理。
  • 图形上下文描述了绘图目标的种类并提供了创建各种图形上下文的分步说明。
  • 路径讨论了构成路径的基本元素,展示了如何创建和绘制路径,展示了如何设置剪切区域,并解释了混合模式如何影响绘画。
  • 颜色和颜色空间讨论了颜色值和使用 alpha 值表示透明度,并描述了如何创建颜色空间、设置颜色、创建颜色对象和设置渲染意图。
  • Transforms描述了当前的变换矩阵并解释了如何修改它,展示了如何设置仿射变换,展示了如何在用户空间和设备空间之间进行转换,并提供了 Quartz 执行的数学运算的背景信息。
  • 模式定义了什么是模式及其各个部分,说明了 Quartz 如何呈现它们,并展示了如何创建彩色和模板图案。
  • 阴影描述了什么是阴影,解释了阴影的工作原理,并展示了如何使用它们进行绘画。
  • 渐变讨论了轴向和径向渐变,并展示了如何创建和使用 CGShading 和 CGGradient 对象。
  • 透明层给出了透明层的示例,讨论了它们的工作原理,并提供了实现它们的分步说明。
  • Quartz 2D 中的数据管理讨论了如何将数据移入和移出 Quartz。
  • 位图图像和图像蒙版介绍了位图图像定义的组成,并展示了如何使用位图图像作为 Quartz 绘图原语。
    它还介绍了可以在图像上使用的蒙版技术,并展示了在绘制图像时使用混合模式可以实现的各种效果。
  • 核心图形层绘制描述了如何创建和使用绘制层来实现高性能图案绘制或屏幕外绘制。
  • PDF 文档创建、查看和转换展示如何打开和查看 PDF 文档、对其应用转换、创建 PDF 文件、访问 PDF 元数据、添加链接以及添加安全功能(如密码保护)。
  • PDF 文档解析描述了如何使用 CGPDFScanner 和 CGPDFContentStream 对象来解析和检查 PDF 文档。
  • PostScript 转换概述了在 Mac OS X 中可以使用哪些功能将 PostScript 文件转换为 PDF 文档。
    这些功能在 iOS 中不可用。
  • 文本描述了 Quartz 2D 对文本和字形的低级支持,以及提供高级和 Unicode 文本支持的替代方案。
    它还讨论了如何复制字体变体。
  • 词汇表定义了本指南中使用的术语。

4、也可以看看

对于任何使用 Quartz 2D 的人来说,这些内容都是必读的:

二、Quartz 2D 概述

Quartz 2D 是一个二维绘图引擎,可在 iOS 环境 以及内核之外的所有 Mac OS X 应用程序环境中 访问。

您可以使用 Quartz 2D 应用程序编程接口 (API) 来访问基于路径的绘图、使用透明度进行绘画、着色、绘制阴影、透明度图层、颜色管理、抗锯齿渲染、PDF 文档生成和 PDF 元数据访问等功能。

只要有可能,Quartz 2D 就会充分利用图形硬件的强大功能。

在 Mac OS X 中,Quartz 2D 可以与所有其他图形和图像技术(Core Image、Core Video、OpenGL 和 QuickTime)配合使用。

可以使用 QuickTime 的 GraphicsImportCreateCGImage 函数从 QuickTime 图形导入器在 Quartz 中创建图像。

有关详细信息,请参阅 QuickTime 框架参考
在 Mac OS X 中在 Quartz 2D 和 Core Image 之间移动数据 描述了如何向 Core Image 提供图像,Core Image 是一个支持图像处理的框架。

类似地,在 iOS 中,Quartz 2D 可与 所有可用的图形 和动画技术 配合使用,例如 Core Animation、OpenGL ES 和 UIKit 类。


1、本节内容

Quartz 2D 使用*画家模型(painter's model)*进行成像。

在画家模型中,每个连续的绘图操作 都会将一层"颜料(paint)"应用到输出"画布(canvas)"(通常称为页面)上。

可以通过额外的绘图操作 覆盖更多颜料来修改页面上的颜料。

除了覆盖更多颜料外,无法修改页面上绘制的对象。

此模型允许您从少量强大的图元构建极其复杂的图像。

图 1-1显示了画家模型的工作原理。

为了获得图中上部的图像,首先绘制左侧的形状,然后再绘制实心形状。

实心形状覆盖第一个形状,遮挡了第一个形状的整个边缘。

图下部的形状以相反的顺序绘制,首先绘制实心形状。

如您所见,在画家模型中,绘制顺序很重要。


图 1-1 画家的模型


页面可能是一张真实的纸张(如果输出设备是打印机);它可能是一张虚拟的纸张(如果输出设备是 PDF 文件);它甚至可能是一张位图图像。

页面的确切性质取决于您使用的特定图形上下文。


2、绘制目标:图形上下文

图形上下文 是一种不透明的数据类型 ( CGContextRef),它封装了 Quartz 用于将图像绘制到输出设备(例如 PDF 文件、位图或显示器上的窗口)的信息。

图形上下文中的信息包括图形绘制参数和页面上绘制的设备特定表示。

Quartz 中的所有对象都绘制到图形上下文中或包含在图形上下文中。

您可以将图形上下文视为绘制目标,如图1-2所示。

使用 Quartz 进行绘制时,所有设备特定特性 都包含在 您使用的 特定类型的图形上下文中。

换句话说,您只需为同一 Quartz 绘制例程序列提供不同的图形上下文,即可将同一图像绘制到不同的设备上。

您无需执行任何设备特定计算;Quartz 会为您完成这些计算。


图 1-2 Quartz 绘图目标


您的应用程序可以使用以下图形上下文:

  • 位图图形上下文 允许您将 RGB 颜色、CMYK 颜色或灰度绘制到位图中。
    位图 是像素的矩形阵列(或光栅),每个像素代表图像中的一个点。
    位图图像也称为采样图像
    请参阅 创建位图图形上下文
  • PDF 图形上下文 允许您创建 PDF 文件。
    在 PDF 文件中,您的绘图将保存为一系列命令。
    PDF 文件和位图之间存在一些显著差异:
    • PDF 文件与位图不同,可能包含多页。
    • 当您在其他设备上从 PDF 文件绘制页面时,生成的图像会针对该设备的显示特性进行优化。
    • PDF 文件本质上与分辨率无关 --- 绘制时的尺寸可以无限增大或减小,而不会牺牲图像细节。
      用户感知的位图图像质量与预期查看位图的分辨率相关。
      请参阅 创建 PDF 图形上下文
  • 窗口图形上下文 是可用于在窗口中进行绘制的图形上下文。
    请注意,由于 Quartz 2D 是图形引擎而不是窗口管理系统,因此您可以使用其中一个应用程序框架来获取窗口的图形上下文。
    有关详细信息,请参阅 在 Mac OS X 中创建窗口图形上下文
  • 图层上下文 ( CGLayerRef) 是与另一个图形上下文关联的屏幕外绘制目标。
    它旨在在将图层绘制到创建它的图形上下文时实现最佳性能。
    与位图图形上下文相比,图层上下文是屏幕外绘制的更好选择。
    请参阅 核心图形图层绘制
  • 当您要在 Mac OS X 中打印时,您会将内容发送到由打印框架管理的 PostScript 图形上下文
    有关更多信息,请参阅 获取用于打印的图形上下文。

3、Quartz 2D 不透明数据类型

Quartz 2D API 除了图形上下文之外还定义了多种不透明数据类型。

由于该 API 是 Core Graphics 框架的一部分,因此这些数据类型及其操作例程均使用 CG 前缀。

Quartz 2D 从不透明数据类型创建对象,您的应用程序可对这些对象进行操作以实现特定的绘图输出。

图 1-3显示了当您将绘图操作应用于 Quartz 2D 提供的三个对象时可以实现的结果。

例如:

  • 您可以通过创建 PDF 页面对象、对图形上下文应用旋转操作以及要求 Quartz 2D 将页面绘制到图形上下文来旋转和显示 PDF 页面。
  • 您可以通过创建图案对象、定义组成图案的形状以及设置 Quartz 2D 在绘制到图形上下文时使用该图案作为绘画来绘制图案。
  • 您可以通过创建阴影对象、提供确定阴影中每个点的颜色的函数,然后要求 Quartz 2D 使用阴影作为填充颜色,用轴向或径向阴影填充区域。

图 1-3 不透明数据类型是 Quartz 2D 中绘图图元的基础


Quartz 2D 中可用的不透明数据类型包括:

  • CGPathRef,用于矢量图形以创建您填充或描边的路径。
    请参阅 Paths
  • CGImageRef,用于根据您提供的示例数据表示位图图像和位图图像蒙版。
    请参阅 位图图像和图像蒙版
  • CGLayerRef,用于表示可用于重复绘制(例如背景或图案)和屏幕外绘制的绘图层。
    请参阅 核心图形层绘制
  • CGPatternRef,用于重复绘制。
    请参阅 Patterns
  • CGShadingRefCGGradientRef,用于绘制渐变。
    请参阅 渐变
  • CGFunctionRef,用于定义采用任意数量的浮点参数的回调函数。
    创建着色渐变时会使用此数据类型。
    请参阅 渐变
  • CGColorRefCGColorSpaceRef,用于告知 Quartz 如何解释颜色。
    请参阅 颜色和颜色空间
  • CGImageSourceRefCGImageDestinationRef,用于将数据移入和移出 Quartz。
    请参阅《 Quartz 2D和 *图像 I/O 编程指南》 *中的数据管理
  • CGFontRef,用于绘制文本。
    请参阅 文本
  • CGPDFDictionaryRefCGPDFStringRef``CGPDFArrayRefCGPDFObjectRef,用于访问 PDF 元数据。
    请参阅PDF 文档创建、查看CGPDFStreamCGPDFPageRef 转换
  • CGPDFScannerRefCGPDFContentStreamRef,用于解析 PDF 元数据。
    请参阅 PDF 文档解析
  • CGPSConverterRef,用于将 PostScript 转换为 PDF。
    它在 iOS 中不可用。
    请参阅 PostScript 转换

4、图形状态

Quartz 根据当前图形状态 中的参数修改绘制操作的结果。

图形状态包含否则将作为绘制例程的参数的参数。

绘制到图形上下文的例程会参考图形状态来确定如何呈现其结果。

例如,当您调用函数来设置填充颜色时,您正在修改存储在当前图形状态中的值。

当前图形状态的其他常用元素包括线宽、当前位置和文本字体大小。

图形上下文包含图形状态堆栈。

当 Quartz 创建图形上下文时,堆栈为空。

当您保存图形状态时,Quartz 会将当前图形状态的副本推送到堆栈上。

当您恢复图形状态时,Quartz 会将图形状态从堆栈顶部弹出。

弹出的状态将成为当前图形状态。

要保存当前图形状态,请使用CGContextSaveGState函数将当前图形状态的副本推送到堆栈上。

要恢复之前保存的图形状态,请使用CGContextRestoreGState函数将当前图形状态替换为堆栈顶部的图形状态。

请注意,当前绘图环境的并非所有方面都是图形状态的元素。

例如,当前路径不被视为图形状态的一部分,因此在调用CGContextSaveGState函数时不会被保存。

调用此函数时保存的图形状态参数列于表 1-1中。

参数 本章讨论
电流变换矩阵(CTM) 变换
裁剪区域 路径
线:宽度、连接、端点、虚线、斜接限制 路径
曲线估计精度(平整度) 路径
抗锯齿设置 图形上下文
颜色:填充和描边设置 颜色和颜色空间
Alpha 值(透明度) 颜色和颜色空间
渲染意图 颜色和颜色空间
色彩空间:填充和描边设置 颜色和颜色空间
文字:字体、字体大小、字符间距、文字绘制模式 文本
混合模式 路径位图图像和图像蒙版

5、Quartz 2D 坐标系

坐标系统(如图 1-4所示)定义了用于表示要在页面上绘制的对象的位置和大小的位置范围。

您可以在用户空间坐标系统(或更简单地说,用户空间)中指定图形的位置和大小*。

*坐标定义为浮点值。


图 1-4 Quartz 坐标系


由于不同设备具有不同的底层成像功能,因此必须以与设备无关的方式定义图形的位置和大小。

例如,屏幕显示设备可能只能显示每英寸不超过 96 个像素,而打印机可能只能显示每英寸 300 个像素。

如果您在设备级别定义坐标系(在此示例中为 96 个像素或 300 个像素),则在该空间中绘制的对象无法在其他设备上再现,而不会产生可见的失真。

它们会显得太大或太小。

Quartz 使用单独的坐标系(用户空间)实现设备独立性,并使用当前变换矩阵 (或 CTM)将其映射到输出设备(设备空间)的坐标系。
矩阵 是一种数学结构,用于有效描述一组相关方程式。

当前变换矩阵是一种特殊类型的矩阵,称为仿射变换,它通过应用**平移旋转缩放操作(移动、旋转和调整坐标系大小的计算)将点从一个坐标空间映射到另一个坐标空间。

当前变换矩阵还有第二个用途:它允许您变换对象的绘制方式。

例如,要绘制一个旋转 45 度的框,您需要在绘制框之前旋转页面的坐标系 (CTM)。

Quartz 使用旋转后的坐标系绘制到输出设备。

用户空间中的一个点由一个坐标对 ( x , y )表示,其中 x 表示沿水平轴(左和右)的位置,y 表示沿垂直轴(上和下)的位置。

用户坐标空间的 原点 是点 (0,0)。

原点位于页面的左下角,如图1-4所示。

在 Quartz 的默认坐标系中,x 轴从页面左侧向右侧移动时会增加。

y 轴从页面底部向顶部移动时会增加值。

某些技术使用与 Quartz 不同的默认坐标系来设置其图形上下文。

相对于 Quartz,这样的坐标系是一种修改后的坐标系 ,在执行某些 Quartz 绘制操作时必须进行补偿。

最常见的修改后的坐标系将原点置于上下文的左上角,并将 y 轴更改为指向页面底部。

您可能会看到使用此特定坐标系的几个地方如下:

  • 在 Mac OS X 中,NSView 的子类重写了 isFlipped方法以返回YES
  • 在 iOS 中,由 UIView 返回的绘图上下文。
  • 在 iOS 中,通过调用 UIGraphicsBeginImageContextWithOptions 函数创建的绘图上下文。

UIKit 返回具有修改后的坐标系的 Quartz 绘图上下文的原因是 UIKit 使用不同的默认坐标约定;它将变换应用于它创建的 Quartz 上下文,以便它们与其约定相匹配。

如果您的应用程序想要使用相同的绘图例程 来绘制UIView对象和 PDF 图形上下文(由 Quartz 创建并使用默认坐标系),则需要应用变换,以便 PDF 图形上下文接收相同的修改后的坐标系。

为此,应用变换,将原点平移到 PDF 上下文的左上角,并将 y 坐标缩放-1

使用缩放变换来否定 y 坐标会改变 Quartz 绘图中的某些约定。

例如,如果调用 CGContextDrawImage 将图像绘制到上下文中,则在将图像绘制到目标中时,变换会修改该图像。

类似地,路径绘制例程接受指定在默认 坐标系中是顺时针还是逆时针绘制圆弧的参数。

如果修改了坐标系,结果也会被修改,就像图像在镜子中反射一样。

在图 1-5中,将相同的参数传递到 Quartz 中会在默认坐标系中产生顺时针圆弧,而在 y 坐标被变换否定后,将产生逆时针圆弧。


图1-5 修改坐标系创建镜像。


您的应用程序可以自行调整对已应用变换的上下文进行的任何 Quartz 调用。

例如,如果您希望将图像或 PDF 正确绘制到图形上下文中,则您的应用程序可能需要临时调整图形上下文的 CTM。

在 iOS 中,如果您使用 UIImage 对象包装您创建的CGImage对象,则无需修改 CTM。
UIImage 对象会自动补偿 UIKit 应用的已修改坐标系。

重要提示: 如果您计划编写直接针对 iOS 上的 Quartz 的应用程序,则理解上述讨论至关重要,但这还不够。

在 iOS 3.2 及更高版本中,当 UIKit 为您的应用程序创建绘图上下文时,它还会对上下文进行其他更改以匹配默认的 UIKIt 约定。

特别是,不受 CTM 影响的图案和阴影会单独进行调整,以便它们的约定与 UIKit 的坐标系相匹配。

在这种情况下,没有与 CTM 等效的机制可供您的应用程序用来更改 Quartz 创建的上下文以匹配 UIKit 提供的上下文的行为;您的应用程序必须识别它正在绘制到哪种上下文中,并调整其行为以匹配上下文的期望。


6、内存管理:对象所有权

Quartz 使用 Core Foundation 内存管理模型,其中对象是引用计数的。

创建时,Core Foundation 对象的引用计数为 1。

您可以通过调用函数来保留对象来增加引用计数,并通过调用函数来释放对象来减少引用计数。

当引用计数减少到 0 时,对象将被释放。

此模型允许对象安全地共享对其他对象的引用。

需要牢记一些简单的规则:

  • 如果您创建或复制一个对象,您就拥有它,因此您必须释放它。
    也就是说,一般来说,如果您从名称中带有"Create"或"Copy"字样的函数获取对象,则必须在使用完后释放该对象。
    否则,会导致内存泄漏。
  • 如果您从名称中不包含单词"Create"或"Copy"的函数获取对象,则您不拥有对该对象的引用,并且您不得释放它。
    该对象将在将来的某个时间点由其所有者释放。
  • 如果您不拥有某个对象,但需要保留它,则必须保留它并在使用完后释放它。
    您可以使用特定于对象的 Quartz 2D 函数来保留和释放该对象。
    例如,如果您收到对 CGColorspace 对象的引用,则可以使用 CGColorSpaceRetain 函数和CGColorSpaceRelease 根据需要保留和释放该对象。
    您还可以使用 Core Foundation 的 CFRetainCFRelease函数,但必须小心不要传递NULL给这些函数。

三、图形上下文

图形上下文表示绘图目标。

它包含绘图参数和绘图系统执行任何后续绘图命令所需的所有设备特定信息。

图形上下文定义基本绘图属性,例如绘图时使用的颜色、裁剪区域、线宽和样式信息、字体信息、合成选项等。

您可以使用 Quartz 上下文创建函数或使用 Mac OS X 框架之一或 iOS 中的 UIKit 框架提供的高级函数来获取图形上下文。

Quartz 为各种类型的 Quartz 图形上下文(包括位图和 PDF)提供了函数,您可以使用它们来创建自定义内容。

本章向您介绍如何为各种绘图目标创建图形上下文。

图形上下文在代码中由 CGContextRef 数据类型表示,这是一种不透明的数据类型。

获取图形上下文后,您可以使用 Quartz 2D 函数在上下文中绘图、对上下文执行操作(例如平移)以及更改图形状态参数(例如线宽和填充颜色)。


1、在 iOS 中绘制到视图图形上下文

要在 iOS 应用程序中绘制到屏幕,您需要设置一个UIView对象并实现 drawRect:方法来执行绘制。

当视图在屏幕上可见且其内容需要更新时,将调用视图的 drawRect:方法。

在调用您的自定义drawRect:方法之前,视图对象会自动配置其绘制环境,以便您的代码可以立即开始绘制。

作为此配置的一部分, UIView对象为当前绘制环境创建一个图形上下文( CGContextRef 不透明类型)。

您可以通过调用 UIKit UIGraphicsGetCurrentContext 函数 在您的 drawRect:方法中获取此图形上下文。

UIKit 中使用的默认坐标系与 Quartz 使用的坐标系不同。

在 UIKit 中,原点位于左上角,正 y 值指向下方。
UIView 对象通过将原点平移到视图的左上角并将 y 轴乘以 来反转,从而修改 Quartz 图形上下文的 CTM 以匹配 UIKit 约定-1

有关修改后的坐标系及其在您自己的绘图代码中的含义的更多信息,请参阅 Quartz 2D 坐标系

UIView 对象在iOS 的视图编程指南*中有详细描述。


2、在 Mac OS X 中创建窗口图形上下文

在 Mac OS X 中绘图时,您需要创建适合您所用框架的窗口图形上下文。

Quartz 2D API 本身不提供获取窗口图形上下文的函数。

相反,您可以使用 Cocoa 框架 来获取在 Cocoa 中创建的窗口的上下文。

您可以使用以下代码行 从 Cocoa 应用程序例程 drawRect: 中获取 Quartz 图形上下文:

c 复制代码
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];

currentContext 方法返回当前线程的 NSGraphicsContext 实例。
graphicsPort 方法返回接收器所表示的低级、特定于平台的图形上下文,即 Quartz 图形上下文。

(不要对方法名称感到困惑;它们是历史性的。)有关更多信息,请参阅 NSGraphicsContext 类参考

获取图形上下文后,就可以在 Cocoa 应用程序中调用任何 Quartz 2D 绘图函数。

还可以将 Quartz 2D 调用与 Cocoa 绘图调用混合使用。

图 2-1显示了 Quartz 2D 在 Cocoa 视图上绘图的示例。

该绘图由两个重叠的矩形组成,一个是不透明的红色矩形和一个部分透明的蓝色矩形。

您将在颜色和颜色空间中了解有关透明度的更多信息。

能够控制"看透"颜色的程度是 Quartz 2D 的标志性功能之一。


图 2-1 Cocoa 框架中包含 Quartz 绘图的视图


要创建图 2-1中的绘图,首先要创建一个 Cocoa 应用程序 Xcode 项目。

在 Interface Builder 中,将自定义视图拖到窗口并对其进行子类化。

然后为子类视图编写一个实现,类似于例 2-1所示的内容。

在本例中,子类视图名为MyQuartzView

视图的 drawRect: 方法包含所有 Quartz 绘图代码。

示例后面显示了每行带编号代码的详细说明。

注意: 每次需要绘制视图时 都会自动调用该类的drawRect:方法 。

要了解有关重写 drawRect: 方法的更多信息,请参阅 NSView 类参考


例 2-1 绘制到窗口图形上下文

c 复制代码
@implementation MyQuartzView
 
- (id)initWithFrame:(NSRect)frameRect
{
    self = [super initWithFrame:frameRect];
    return self;
}
 
- (void)drawRect:(NSRect)rect
{
    CGContextRef myContext = [[NSGraphicsContext // 1
                                currentContext] graphicsPort];
   // ********** Your drawing code here ********** // 2
    CGContextSetRGBFillColor (myContext, 1, 0, 0, 1);// 3
    CGContextFillRect (myContext, CGRectMake (0, 0, 200, 100 ));// 4
    CGContextSetRGBFillColor (myContext, 0, 0, 1, .5);// 5
    CGContextFillRect (myContext, CGRectMake (0, 0, 100, 200));// 6
  }
 
@end

代码的作用如下:

  1. 获取视图的图形上下文。
  2. 这是您插入绘图代码的地方。
    以下四行代码是使用 Quartz 2D 函数的示例。
  3. 设置完全不透明的红色填充颜色。
    有关颜色和 alpha(设置不透明度)的信息,请参阅 颜色和颜色空间
  4. 填充一个矩形,其原点为 ( 0,0),宽度为200,高度为100
    有关绘制矩形的信息,请参阅 路径
  5. 设置部分透明的蓝色填充颜色。
  6. 填充一个矩形,其原点为 ( 0,0),宽度为 100,高度为200

3、创建 PDF 图形上下文

当您创建 PDF 图形上下文并在该上下文中绘图时,Quartz 会将您的绘图记录为写入文件的一系列 PDF 绘图命令。

您提供 PDF 输出的位置和默认媒体框 --- 指定页面边界的矩形。

图 2-2显示了在 PDF 图形上下文中绘图然后在预览中打开生成的 PDF 的结果。


图 2-2 使用 CGPDFContextCreateWithURL 创建的 PDF


Quartz 2D API 提供了两个创建 PDF 图形上下文的函数:

  • CGPDFContextCreateWithURL,当您想要将 PDF 输出的位置指定为 Core Foundation URL 时,可以使用它。
    例 2-2显示了如何使用此函数创建 PDF 图形上下文。
  • CGPDFContextCreate,当您想要将 PDF 输出发送给数据消费者时,可以使用它。
    (有关更多信息,请参阅 Quartz 2D 中的数据管理。)例 2-3显示了如何使用此函数创建 PDF 图形上下文。

每个列表后面都有对每行编号代码的详细解释。


iOS 注意: iOS 中的 PDF 图形上下文使用 Quartz 提供的默认坐标系,而不应用变换以匹配 UIKit 坐标系。

如果您的应用程序计划在 PDF 图形上下文和UIView对象提供的图形上下文之间共享绘图代码,则您的应用程序应修改 PDF 图形上下文的 CTM 以修改坐标系。

请参阅 Quartz 2D 坐标系


例 2-2 调用CGPDFContextCreateWithURL创建 PDF 图形上下文

c 复制代码
CGContextRef MyPDFContextCreate (const CGRect *inMediaBox,
                                    CFStringRef path)
{
    CGContextRef myOutContext = NULL;
    CFURLRef url;
 
    url = CFURLCreateWithFileSystemPath (NULL, // 1
                                path,
                                kCFURLPOSIXPathStyle,
                                false);
    if (url != NULL) {
        myOutContext = CGPDFContextCreateWithURL (url,// 2
                                        inMediaBox,
                                        NULL);
        CFRelease(url);// 3
    }
    return myOutContext;// 4
}

代码的作用如下:

  1. 调用 Core Foundation 函数,从提供给 MyPDFContextCreate函数的 CFString 对象创建 CFURL 对象。
    您将 NULL作为第一个参数 传递以使用默认分配器。
    您还需要指定路径样式,在本例中为 POSIX 样式路径名。
  2. 调用 Quartz 2D 函数,使用刚刚创建的 PDF 位置(作为 CFURL 对象)和指定 PDF 边界的矩形来创建 PDF 图形上下文。
    矩形 ( CGRect) 已传递给 MyPDFContextCreate函数,是 PDF 的默认页面媒体边界框。
  3. 释放 CFURL 对象。
  4. 返回 PDF 图形上下文。
    调用者在不再需要图形上下文时必须释放它。

例 2-3 调用CGPDFContextCreate创建 PDF 图形上下文

c 复制代码
CGContextRef MyPDFContextCreate (const CGRect *inMediaBox,
                                    CFStringRef path)
{
    CGContextRef        myOutContext = NULL;
    CFURLRef            url;
    CGDataConsumerRef   dataConsumer;
 
    url = CFURLCreateWithFileSystemPath (NULL, // 1
                                        path,
                                        kCFURLPOSIXPathStyle,
                                        false);
 
    if (url != NULL)
    {
        dataConsumer = CGDataConsumerCreateWithURL (url);// 2
        if (dataConsumer != NULL)
        {
            myOutContext = CGPDFContextCreate (dataConsumer, // 3
                                        inMediaBox,
                                        NULL);
            CGDataConsumerRelease (dataConsumer);// 4
        }
        CFRelease(url);// 5
    }
    return myOutContext;// 6
}

代码的作用如下:

  1. 调用 Core Foundation 函数,从提供给 MyPDFContextCreate 函数的 CFString 对象创建 CFURL 对象。
    您将 NULL作为第一个参数传递以使用默认分配器。
    您还需要指定路径样式,在本例中为 POSIX 样式路径名。
  2. 使用 CFURL 对象创建 Quartz 数据使用者对象。
    如果您不想使用 CFURL 对象(例如,您想将 PDF 数据放置在 CFURL 对象无法指定的位置),则可以从您在应用程序中实现的一组回调函数创建数据使用者。
    有关更多信息,请参阅 Quartz 2D 中的数据管理
  3. 调用 Quartz 2D 函数来创建 PDF 图形上下文,将数据使用者和传递给该函数的矩形(类型CGRect)作为参数传递MyPDFContextCreate
    此矩形是 PDF 的默认页面媒体边界框。
  4. 释放数据消费者。
  5. 释放 CFURL 对象。
  6. 返回 PDF 图形上下文。
    调用者在不再需要图形上下文时必须释放它。

例 2-4显示了如何调用MyPDFContextCreate例程并绘制。

示例后面列出了每行代码的详细说明。


例 2-4 绘制到 PDF 图形上下文

c 复制代码
CGRect mediaBox;// 1
 
mediaBox = CGRectMake (0, 0, myPageWidth, myPageHeight);// 2
myPDFContext = MyPDFContextCreate (&mediaBox, CFSTR("test.pdf"));// 3

CFStringRef myKeys[1];// 4
CFTypeRef myValues[1];
myKeys[0] = kCGPDFContextMediaBox;
myValues[0] = (CFTypeRef) CFDataCreate(NULL,(const UInt8 *)&mediaBox, sizeof (CGRect));
CFDictionaryRef pageDictionary = CFDictionaryCreate(NULL, (const void **) myKeys,
        (const void **) myValues, 1,
        &kCFTypeDictionaryKeyCallBacks,
        & kCFTypeDictionaryValueCallBacks);

CGPDFContextBeginPage(myPDFContext, &pageDictionary);// 5
    // ********** Your drawing code here **********// 6
    CGContextSetRGBFillColor (myPDFContext, 1, 0, 0, 1);
    CGContextFillRect (myPDFContext, CGRectMake (0, 0, 200, 100 ));
    CGContextSetRGBFillColor (myPDFContext, 0, 0, 1, .5);
    CGContextFillRect (myPDFContext, CGRectMake (0, 0, 100, 200 ));

CGPDFContextEndPage(myPDFContext);// 7
CFRelease(pageDictionary);// 8
CFRelease(myValues[0]);
CGContextRelease(myPDFContext);

代码的作用如下:

  1. 为用于定义 PDF 媒体框的矩形声明一个变量。
  2. 将媒体框的原点设置为,(0,0)并将宽度和高度设置为应用程序提供的变量。
  3. 调用 MyPDFContextCreate 函数(参见例 2-3)获取 PDF 图形上下文,提供媒体框和路径名。
    CFSTR 宏将字符串转换为CFStringRef数据类型。
  4. 使用页面选项设置字典。
    在此示例中,仅指定了媒体框。
    您不必传递用于设置 PDF 图形上下文的相同矩形。
    您在此处添加的媒体框将取代您传递的用于设置 PDF 图形上下文的矩形。
  5. 表示页面的开始。
    此函数用于面向页面的图形,即 PDF 绘图。
  6. 调用 Quartz 2D 绘图函数。
    你可以用适合你的应用程序的绘图代码替换此代码和以下四行代码。
  7. 表示 PDF 页面的结束。
  8. 当不再需要字典和 PDF 图形上下文时释放它们。

您可以将任何适合您的应用程序的内容写入 PDF(图像、文本、路径绘制),还可以添加链接和加密。

有关更多信息,请参阅 PDF 文档创建、查看和转换


4、创建位图图形上下文

位图图形上下文接受指向包含位图存储空间的内存缓冲区的指针。

当您在位图图形上下文中绘制时,缓冲区会更新。

释放图形上下文后,您将获得一个完全更新的位图,其像素格式与您指定的一致。

注意: 位图图形上下文有时用于屏幕外绘图。

在决定使用位图图形上下文进行此目的之前,请参阅 核心图形层绘图

CGLayer 对象 ( CGLayerRef) 针对屏幕外绘图进行了优化,因为只要有可能,Quartz 就会将图层缓存在视频卡上。


iOS 注意: iOS 应用程序应使用 UIGraphicsBeginImageContextWithOptions 函数,而不是使用此处描述的低级 Quartz 函数。

如果您的应用程序使用 Quartz 创建屏幕外位图,则位图图形上下文使用的坐标系是默认的 Quartz 坐标系。

相反,如果您的应用程序通过调用 UIGraphicsBeginImageContextWithOptions 函数 创建图像上下文,则 UIKit 会将相同的变换应用于 UIView 的上下文的坐标系,就像应用于对象的图形上下文一样。

这允许您的应用程序对两者使用相同的绘制代码,而不必担心不同的坐标系。

虽然您的应用程序可以手动调整坐标变换矩阵以获得正确的结果,但实际上,这样做对性能没有任何好处。

您可以使用 CGBitmapContextCreate 函数创建位图图形上下文。

此函数采用以下参数:

  • data. 提供一个指向内存中要渲染绘图的目标的指针。
    此内存块的大小应至少为 ( bytesPerRow* height) 个字节。
  • width. 指定位图的宽度(以像素为单位)。
  • height. 指定位图的高度(以像素为单位)。
  • bitsPerComponent. 指定内存中像素每个组件使用的位数。
    例如,对于 32 位像素格式和 RGB 颜色空间,您需要指定每个组件 8 位的值。
    请参阅 支持的像素格式
  • bytesPerRow. 指定位图每行使用的内存字节数。
    提示: 创建位图图形上下文时,如果确保数据和bytesPerRow是16 字节对齐的,您将获得最佳性能。
  • colorspace,位图上下文使用的颜色空间。
    创建位图图形上下文时,可以提供灰色、RGB、CMYK 或 NULL 颜色空间。
    有关颜色空间和颜色管理原则的详细信息,请参阅 颜色管理概述
    有关在 Quartz 中创建和使用颜色空间的信息,请参阅 颜色和颜色空间
    有关支持的颜色空间的信息,请参阅 位图图像和图像蒙版一章中的 颜色空间和位图布局
  • bitmapInfo. 位图布局信息,以CGBitmapInfo常量表示,指定位图是否应包含 alpha 分量、alpha 分量(如果有)在像素中的相对位置、alpha 分量是否预乘以及颜色分量是整数还是浮点值。
    有关这些常量是什么、何时使用以及 Quartz 支持的位图图形上下文和图像像素格式的详细信息,请参阅 位图图像和图像蒙版一章中的颜色空间和位图布局

例 2-5显示了如何创建位图图形上下文。

当您在生成的位图图形上下文中绘图时,Quartz 会将您的绘图作为位图数据记录在指定的内存块中。

示例后面是每行带编号代码的详细说明。


例 2-5 创建位图图形上下文

c 复制代码
CGContextRef MyCreateBitmapContext (int pixelsWide,
                            int pixelsHigh)
{
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;
 
    bitmapBytesPerRow   = (pixelsWide * 4);// 1
    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);
 
    colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);// 2
    bitmapData = calloc( bitmapByteCount, sizeof(uint8_t) );// 3
    if (bitmapData == NULL)
    {
        fprintf (stderr, "Memory not allocated!");
        return NULL;
    }
    context = CGBitmapContextCreate (bitmapData,// 4
                                    pixelsWide,
                                    pixelsHigh,
                                    8,      // bits per component
                                    bitmapBytesPerRow,
                                    colorSpace,
                                    kCGImageAlphaPremultipliedLast);
    if (context== NULL)
    {
        free (bitmapData);// 5
        fprintf (stderr, "Context not created!");
        return NULL;
    }
    CGColorSpaceRelease( colorSpace );// 6
 
    return context;// 7
}

代码的作用如下:

  1. 声明一个变量来表示每行的字节数。
    本例中位图中的每个像素由 4 个字节表示;红色、绿色、蓝色和 alpha 各占 8 位。
  2. 创建通用 RGB 颜色空间。
    您还可以创建 CMYK 颜色空间。
    有关更多信息以及通用颜色空间与设备相关颜色空间的讨论,请参阅 颜色和颜色空间。
  3. 调用calloc函数创建并清除用于存储位图数据的内存块。
    此示例创建一个 32 位 RGBA 位图(即每个像素 32 位的数组,每个像素包含 8 位红、绿、蓝和 alpha 信息)。
    位图中的每个像素占用 4 个字节的内存。
    在 Mac OS X 10.6 和 iOS 4 中,可以省略此步骤 --- 如果您NULL以位图数据形式传递,Quartz 会自动为位图分配空间。
  4. 创建位图图形上下文,提供位图数据、位图的宽度和高度、每个组件的位数、每行的字节数、颜色空间以及一个常量,该常量指定位图是否应包含 alpha 通道及其在像素中的相对位置。
    kCGImageAlphaPremultipliedLast 常量表示 alpha 组件存储在每个像素的最后一个字节中,并且颜色组件已乘以此 alpha 值。
    有关预乘 alpha 的更多信息,请参阅 Alpha 值。
  5. 如果由于某种原因未创建上下文,则释放为位图数据分配的内存。
  6. 释放色彩空间。
  7. 返回位图图形上下文。
    调用者在不再需要图形上下文时必须释放它。

例 2-6显示了调用 MyCreateBitmapContext 创建 位图图形上下文的代码,使用位图图形上下文创建 CGImage 对象,然后将生成的图像绘制到窗口图形上下文。
图 2-3显示了绘制到窗口的图像。

示例后面是每行带编号代码的详细说明。


例 2-6 绘制位图图形上下文

c 复制代码
CGRect myBoundingBox;// 1
 
myBoundingBox = CGRectMake (0, 0, myWidth, myHeight);// 2
myBitmapContext = MyCreateBitmapContext (400, 300);// 3
// ********** Your drawing code here ********** // 4
CGContextSetRGBFillColor (myBitmapContext, 1, 0, 0, 1);
CGContextFillRect (myBitmapContext, CGRectMake (0, 0, 200, 100 ));
CGContextSetRGBFillColor (myBitmapContext, 0, 0, 1, .5);
CGContextFillRect (myBitmapContext, CGRectMake (0, 0, 100, 200 ));
myImage = CGBitmapContextCreateImage (myBitmapContext);// 5
CGContextDrawImage(myContext, myBoundingBox, myImage);// 6
char *bitmapData = CGBitmapContextGetData(myBitmapContext); // 7
CGContextRelease (myBitmapContext);// 8
if (bitmapData) free(bitmapData); // 9
CGImageRelease(myImage);// 10

代码的作用如下:

  1. 声明一个变量来存储边界框的原点和尺寸,Quartz 将在其中绘制从位图图形上下文创建的图像。
  2. 将边界框的原点设置为(0,0),并将宽度和高度设置为先前声明的变量,但其声明未显示在本代码中。
  3. 调用应用程序提供的函数MyCreateBitmapContext(参见例 2-5)来创建宽 400 像素、高 300 像素的位图上下文。
    您可以使用适合您的应用程序的任何尺寸来创建位图图形上下文。
  4. 调用 Quartz 2D 函数在位图图形上下文中绘制。
    你可以用适合你的应用程序的绘制代码替换此行和接下来的四行代码。
  5. 从位图图形上下文创建一个 Quartz 2D 图像(CGImageRef )。
  6. 将图像绘制到由边界框指定的窗口图形上下文中的位置。
    边界框指定在用户空间中绘制图像的位置和尺寸。
    此示例未显示窗口图形上下文的创建。
    有关如何创建窗口图形上下文的信息,请参阅 在 Mac OS X 中创建窗口图形上下文
  7. 获取与位图图形上下文关联的位图数据。
  8. 当不再需要时释放位图图形上下文。
  9. 如果存在,则释放位图数据。
  10. 当不再需要图像时释放该图像。

图 2-3 从位图图形上下文创建并绘制到窗口图形上下文的图像


支持的像素格式

表 2-1总结了位图图形上下文支持的像素格式、相关颜色空间 ( cs) 以及首次提供该格式的 Mac OS X 版本。

像素格式指定为每像素位数 (bpp) 和每组件位数 (bpc)。

该表还包括与该像素格式关联的位图信息常量。

有关每个位图信息格式常量代表的内容的详细信息,请参阅 CGImage 参考。

CS 像素格式和位图信息常量 可用性
Null 8 bpp, 8 bpc, kCGImageAlphaOnly Mac OS X, iOS
Gray 8 bpp, 8 bpc,kCGImageAlphaNone Mac OS X, iOS
Gray 8 bpp, 8 bpc,kCGImageAlphaOnly Mac OS X, iOS
Gray 16 bpp, 16 bpc, kCGImageAlphaNone Mac OS X
Gray 32 bpp, 32 bpc, kCGImageAlphaNone kCGBitmapFloatComponents
RGB 16 bpp, 5 bpc, kCGImageAlphaNoneSkipFirst Mac OS X, iOS
RGB 32 bpp, 8 bpc, kCGImageAlphaNoneSkipFirst Mac OS X, iOS
RGB 32 bpp, 8 bpc, kCGImageAlphaNoneSkipLast Mac OS X, iOS
RGB 32 bpp, 8 bpc, kCGImageAlphaPremultipliedFirst Mac OS X, iOS
RGB 32 bpp, 8 bpc, kCGImageAlphaPremultipliedLast Mac OS X, iOS
RGB 64 bpp, 16 bpc, kCGImageAlphaPremultipliedLast Mac OS X
RGB 64 bpp, 16 bpc, kCGImageAlphaNoneSkipLast Mac OS X
RGB 128 bpp, 32 bpc, kCGImageAlphaNoneSkipLast kCGBitmapFloatComponents
RGB 128 bpp, 32 bpc, kCGImageAlphaPremultipliedLast kCGBitmapFloatComponents
CMYK 32 bpp, 8 bpc, kCGImageAlphaNone Mac OS X
CMYK 64 bpp, 16 bpc, kCGImageAlphaNone Mac OS X
CMYK 128 bpp, 32 bpc, kCGImageAlphaNone kCGBitmapFloatComponents

抗锯齿

位图图形上下文支持抗锯齿功能 ,即在绘制文本或形状时,人工校正位图图像中有时会出现的锯齿状(或锯齿状 )边缘的过程。

当位图的分辨率明显低于人眼的分辨率时,就会出现这些锯齿状边缘。

为了使对象在位图中显得平滑,Quartz 对形状轮廓周围的像素使用了不同的颜色。

通过以这种方式混合颜色,形状看起来很平滑。

您可以在图 2-4中看到使用抗锯齿的效果。

您可以通过调用 CGContextSetShouldAntialias 函数来关闭特定位图图形上下文的抗锯齿功能。

抗锯齿设置是图形状态的一部分。

您可以使用 CGContextSetAllowsAntialiasing 函数 来控制是否允许特定图形上下文使用抗锯齿。

传递true给此函数以 允许抗锯齿;false不允许。

此设置不是图形状态的一部分。

当上下文和图形状态设置设置为true 时,Quartz 执行抗锯齿。


图 2-4 锯齿绘制与抗锯齿绘制的比较


5、获取用于打印的图形上下文

Mac OS X 中的 Cocoa 应用程序通过自定义NSView子类实现打印。

通过调用视图的print:方法,视图被告知要打印。

然后,视图创建一个以打印机为目标的图形上下文并调用 drawRect:方法。

您的应用程序使用相同的绘图代码来绘制到打印机,就像它用于绘制到屏幕一样。

它还可以自定义对drawRect:打印机图像的调用,该调用与发送到屏幕的图像不同。

有关 Cocoa 中打印的详细讨论,请参阅 Mac 打印编程指南


四、路径

路径定义一个或多个形状或子路径。

子路径可以由直线、曲线或两者组成。

它可以是开放的,也可以是封闭的。

子路径可以是简单的形状,例如线条、圆形、矩形或星形,也可以是更复杂的形状,例如山脉的轮廓或抽象涂鸦。
3-1显示了一些可以创建的路径。

直线(在图的左上角)是虚线;线也可以是实线。

波浪路径(在中间顶部)由几条曲线组成,是一条开放路径。

同心圆是填充的,但没有描边。

加利福尼亚州是一条封闭的路径,由许多曲线和线条组成,路径既有描边也有填充。

星号说明了填充路径的两种选项,您将在本章后面读到。


图3-1 Quartz支持基于路径的绘图


在本章中,您将了解构成路径的构建块、如何描边和绘制路径以及影响路径外观的参数。


1、路径创建和路径绘制

路径创建和路径绘制是两个独立的任务。

首先,创建一条路径。

当您想要渲染一条路径时,您需要请求 Quartz 绘制它。

如图3-1所示,您可以选择描边路径、填充路径,或者同时描边和填充路径。

您还可以使用路径将其他对象的绘制限制在路径的边界内,从而创建剪贴区域

图 3-2显示了一条已绘制的路径,其中包含两条子路径。

左侧的子路径是一个矩形,右侧的子路径是由直线和曲线组成的抽象形状。

每条子路径都已填充,轮廓已描边。


图 3-2 包含两个形状或子路径的路径


图 3-3显示了独立绘制的多条路径。

每条路径包含一条随机生成的曲线,其中一些是填充的,另一些是描边的。

绘制被剪裁区域限制在一个圆形区域内。


图 3-3 剪切区域限制绘图


2、构建模块

子路径由直线、圆弧和曲线构成。

Quartz 还提供了便捷函数,只需调用一次函数即可添加矩形和椭圆。

点也是路径的基本构建块,因为点定义了形状的起始和终止位置。


积分

点是指定用户空间中位置的 x 和 y 坐标。

您可以调用 CGContextMoveToPoint 函数来指定新子路径的起始位置。

Quartz 会跟踪当前点 ,即用于路径构建的最后一个位置。

例如,如果您调用 CGContextMoveToPoint 函数将位置设置为 (10,10),则会将当前点移动到 (10,10)。

如果您随后绘制一条 50 个单位长的水平线,则该线上的最后一个点(即 (60,10))将成为当前点。

直线、圆弧和曲线始终从当前点开始绘制。

大多数情况下,您可以通过向 Quartz 函数传递两个浮点值来指定 x 和 y 坐标来指定一个点。

有些函数要求您传递一个CGPoint包含两个浮点值的数据结构。


线条

线由其端点定义。

其起点始终假定为当前点,因此当您创建线时,只需指定其端点。

您可以使用CGContextAddLineToPoint函数将单条线附加到子路径。

您可以通过调用 CGContextAddLines 函数向路径添加一系列连接的线。

您将一个点数组传递给此函数。

第一个点必须是第一条线的起点;其余的点是端点。

Quartz 从第一个点开始一条新的子路径,并将直线段连接到每个端点。


弧线

弧线是圆弧段。

Quartz 提供了两个创建弧线的函数。
CGContextAddArc 函数从圆创建曲线段。

您可以指定圆心、半径和径向角(以弧度为单位)。

您可以通过指定 2 pi 的径向角来创建完整的圆。

图 3-4显示了独立绘制的多个路径。

每个路径都包含一个随机生成的圆;一些是填充的,另一些是描边的。


图 3-4 多条路径;每条路径包含一个随机生成的圆


当您想将矩形的角弄圆时,CGContextAddArcToPoint 函数是理想的选择。

Quartz 使用您提供的端点来创建两条切线。

您还需要提供圆的半径,Quartz 将从该半径上切出圆弧。

圆弧的中心点是两个半径的交点,每个半径都垂直于两条切线中的一条。

圆弧的每个端点都是其中一条切线上的切点,如图3-5所示。

圆的红色部分是实际绘制的部分。


图 3-5 用两条切线和一条半径定义圆弧


如果当前路径已经包含子路径,Quartz 会从当前点向圆弧的起点追加一条直线段。

如果当前路径为空,Quartz 会在圆弧的起点处创建新的子路径,并且不会添加初始直线段。


曲线

二次和三次贝塞尔曲线是代数曲线,可以指定任意数量的有趣曲线形状。

这些曲线上的点是通过将多项式公式应用于起点和终点以及一个或多个控制点来计算的。

以这种方式定义的形状是矢量图形的基础。

公式比位数组更易于存储,并且具有可以在任何分辨率下重新创建曲线的优势。

图 3-6展示了通过独立绘制多条路径而产生的各种曲线。

每条路径都包含一条随机生成的曲线,有些是填充的,有些是描边的。


图 3-6 多条路径;每条路径包含一条随机生成的曲线


生成二次和三次贝塞尔曲线的多项式公式以及如何从公式生成曲线的细节在许多数学教材和描述计算机图形学的在线资源中都有讨论。

本文不再讨论这些细节。

您可以使用CGContextAddCurveToPoint函数从当前点附加三次贝塞尔曲线,使用控制点和您指定的端点。

图 3-7显示了由图中所示的当前点、控制点和端点生成的三次贝塞尔曲线。

两个控制点的位置决定了曲线的几何形状。

如果控制点都在起点和终点上方,则曲线向上拱起。

如果控制点都在起点和终点下方,则曲线向下拱起。


图 3-7 三次贝塞尔曲线使用两个控制点


您可以通过调用 CGContextAddQuadCurveToPoint 函数 并指定控制点和端点,从当前点附加二次贝塞尔曲线。

图 3-8显示了使用相同端点但不同控制点产生的两条曲线。

控制点决定曲线拱起的方向。

二次贝塞尔曲线无法像三次贝塞尔曲线那样创建许多有趣的形状,因为二次曲线仅使用一个控制点。

例如,无法使用单个控制点创建交叉。


图 3-8 二次贝塞尔曲线使用一个控制点


关闭子路径

要关闭当前子路径,您的应用程序应调用CGContextClosePath

此函数从当前点到子路径的起点添加一条线段并关闭子路径。

以子路径起点为终点的直线、圆弧和曲线实际上不会关闭子路径。

您必须明确调用CGContextClosePath来关闭子路径。

一些 Quartz 函数将路径的子路径视为已被应用程序关闭。

这些命令将每个子路径视为已由应用程序调用CGContextClosePath来关闭它,并隐式地将线段添加到子路径的起点。

关闭子路径后,如果您的应用程序进行额外的调用来向路径添加直线,圆弧或曲线,Quartz 将从您刚刚关闭的子路径的起点开始新的子路径。


省略号

椭圆本质上是一个压扁的圆。

创建椭圆的方法是定义两个焦点,然后绘制所有位于一定距离的点,使得椭圆上任意一点到一个焦点的距离与从同一点到另一个焦点的距离之和始终是相同的值。

图 3-9显示了独立绘制的多条路径。

每条路径都包含一个随机生成的椭圆;一些是填充的,另一些是描边的。


图 3-9 多条路径;每条路径包含一个随机生成的椭圆


您可以通过调用CGContextAddEllipseInRect函数将椭圆添加到当前路径。

您提供一个定义椭圆边界的矩形。

Quartz 使用一系列贝塞尔曲线来近似椭圆。

椭圆的中心是矩形的中心。

如果矩形的宽度和高度相等(即正方形),则椭圆为圆形,半径等于矩形宽度(或高度)的一半。

如果矩形的宽度和高度不相等,则它们定义椭圆的长轴和短轴。

添加到路径中的椭圆以移动到操作开始,以关闭子路径操作结束,所有移动均沿顺时针方向进行。


矩形

您可以通过调用CGContextAddRect函数将矩形添加到当前路径。

您提供一个CGRect 包含矩形原点及其宽度和高度的结构。

添加到路径中的矩形以移动到操作开始并以关闭子路径操作结束,所有移动均沿逆时针方向进行。

您可以通过调用 CGContextAddRects 函数并提供一个 CGRect 结构数组来向当前路径添加许多矩形。

图 3-10显示了独立绘制的多条路径。

每条路径都包含一个随机生成的矩形;一些矩形是填充的,另一些矩形是描边的。


图 3-10 多条路径,每条路径包含一个随机生成的矩形


3、创建路径

当你想在图形上下文中构建路径时,可以通过调用 CGContextBeginPath 函数 向 Quartz 发出信号。

接下来,通过调用 CGContextMoveToPoint 函数 设置路径中第一个形状或子路径的起点。

建立第一个点后,你可以向路径添加直线、圆弧和曲线,但要记住以下几点:

  • 在开始新路径之前,请调用 CGContextBeginPath 函数。
  • 直线、圆弧和曲线都是从当前点开始绘制的。
    空路径没有当前点;您必须调用CGContextMoveToPoint来设置第一个子路径的起点,或者调用一个便利函数来为您隐式地执行此操作。
  • 当您想要关闭路径中的当前子路径时,请调用 CGContextClosePath 函数 将一个段连接到子路径的起点。
    即使您没有明确设置新的起点,后续路径调用也会开始新的子路径。
  • 当你画圆弧时,Quartz 会在当前点和圆弧的起点之间画一条线。
  • 添加椭圆和矩形的 Quartz 例程会向路径添加新的封闭子路径。
  • 您必须调用绘画函数来填充或描边路径,因为创建路径不会绘制路径。
    有关详细信息,请参阅 绘画路径

绘制路径后,它会从图形上下文中清除。

你可能不想轻易丢失路径,特别是如果它描绘了一个你想反复使用的复杂场景。

因此,Quartz 提供了两种用于创建可重用路径的数据类型 ------ CGPathRefCGMutablePathRef

你可以调用CGPathCreateMutable函数来创建一个可变的 CGPath 对象,你可以向其中添加直线、圆弧、曲线和矩形。

Quartz 提供了一组 CGPath 函数,与构建块中讨论的函数类似。

路径函数对 CGPath 对象而不是图形上下文进行操作。

这些函数是:

  • CGPathCreateMutable,取代CGContextBeginPath
  • CGPathMoveToPoint,取代CGContextMoveToPoint
  • CGPathAddLineToPoint,取代CGContextAddLineToPoint
  • CGPathAddCurveToPoint,取代CGContextAddCurveToPoint
  • CGPathAddEllipseInRect,取代CGContextAddEllipseInRect
  • CGPathAddArc,取代CGContextAddArc
  • CGPathAddRect,取代CGContextAddRect
  • CGPathCloseSubpath,取代CGContextClosePath

请参阅 Quartz 2D 参考集合以获取路径函数的完整列表。

当您想要将路径附加到图形上下文时,可以调用 CGContextAddPath 函数。

路径将保留在图形上下文中,直到 Quartz 绘制它。

您可以通过调用 CGContextAddPath 再次添加路径。

注意: 您可以通过调用 CGContextReplacePathWithStrokedPath 函数将图形上下文中的路径替换为路径的描边版本。


4、绘制路径

您可以通过描边或填充或两者来绘制当前路径。
描边 绘制一条横跨路径的线。
填充 绘制路径内的区域。

Quartz 具有允许您描边路径、填充路径或同时描边和填充路径的函数。

描边线的特性(宽度、颜色等)、填充颜色以及 Quartz 用于计算填充区域的方法都是图形状态的一部分(请参阅 图形状态)。


影响抚摸的参数

您可以通过修改表 3-1中列出的参数来影响路径的描边方式。

这些参数是图形状态的一部分,这意味着您为参数设置的值会影响所有后续描边,直到您将参数设置为另一个值。

范围 设置参数值的函数
行宽 CGContextSetLineWidth
线连接 CGContextSetLineJoin
线帽 CGContextSetLineCap
斜接限制 CGContextSetMiterLimit
线划线图案 CGContextSetLineDash
描边颜色空间 CGContextSetStrokeColorSpace
描边颜色 CGContextSetStrokeColor``CGContextSetStrokeColorWithColor
描边图案 CGContextSetStrokePattern

线宽是线的总宽度,以用户空间的单位 表示。

线横跨路径,两边各占总宽度的一半。

线连接 指定了 Quartz 如何绘制相连的线段之间的连接点。

Quartz 支持表 3-2中描述的线连接样式。

默认样式为斜接连接。

风格 外貌 描述
斜接连接 Quartz 将两个线段的笔画外边缘延伸,直到它们以一定角度相交,就像相框一样。 如果线段以太尖锐的角度相交,则改用斜角连接。 如果斜角长度除以线宽大于斜角限制,则线段太尖锐。
圆形连接 Quartz 在端点周围绘制一个直径等于线宽的半圆弧。 封闭的区域被填充。
斜角连接 石英用对接帽对两个段进行精加工。 段末端的凹槽用三角形填充。

线端点 指定了绘制线端点的方法。

Quartz 支持表 3-3CGContextStrokePath中描述的线端点样式。

默认样式为 butt cap。

风格 外貌 描述
屁股帽 石英在路径的端点处将笔划弄成方形。 路径端点以外没有投影。
圆头帽 Quartz 在两条线段相交处绘制一个直径等于线宽的圆,形成一个圆角。 封闭的区域被填充。
凸出方形盖 Quartz 将笔划延伸到路径端点之外,延伸距离等于线宽的一半。 延伸部分呈方形。

封闭子路径将起点视为相连线段之间的连接点;使用选定的线连接方法渲染起点。

相反,如果通过添加连接到起点的线段来封闭路径,则路径的两端将使用选定的线帽方法绘制。

线虚线图案 允许您沿描边路径绘制分段线。

您可以通过将 虚线数组和虚线相位指定为 CGContextSetLineDash 参数 来控制沿线虚线段的大小和位置 :

c 复制代码
void CGContextSetLineDash (
    CGContextRef ctx,
    CGFloat phase,
    const CGFloat lengths[],
    size_t count
);

lengths 参数的元素指定虚线的宽度,在线条的绘制部分和未绘制部分之间交替。
phase 参数指定虚线图案的起点。

图 3-11显示了一些线条虚线图案。


图 3-11 线虚线图案示例


笔触颜色空间 决定了Quartz 如何解释 笔触颜色 值。

您还可以指定一个 Quartz 颜色(CGColorRef数据类型),它封装了颜色和颜色空间。

有关设置颜色空间和颜色的更多信息,请参阅 颜色和颜色空间


描边路径函数

Quartz 提供了表 3-4中所示的用于描边当前路径的函数。

其中一些是用于描边矩形或椭圆的便捷函数。

功能 描述
CGContextStrokePath 描边当前路径。
CGContextStrokeRect 描边指定的矩形。
CGContextStrokeRectWithWidth 使用指定的线宽描边指定的矩形。
CGContextStrokeEllipseInRect 描边一个适合指定矩形内部的椭圆。
CGContextStrokeLineSegments 描画一系列线条。
CGContextDrawPath 如果传递常量kCGPathStroke,则描边当前路径。 如果要同时填充和描边路径,请参阅 填充路径。

CGContextStrokeLineSegments 函数等同于下面的代码:

c 复制代码
CGContextBeginPath (context);
for (k = 0; k < count; k += 2) {
    CGContextMoveToPoint(context, s[k].x, s[k].y);
    CGContextAddLineToPoint(context, s[k+1].x, s[k+1].y);
}
CGContextStrokePath(context);

调用 CGContextStrokeLineSegments 时,将线段指定为点数组,这些点按对排列。

每对由线段的起点和线段的终点组成。

例如,数组中的第一个点指定第一条线的起始位置,第二个点指定第一条线的终止位置,第三个点指定第二条线的起始位置,依此类推。


填充路径

当你填充当前路径时,Quartz 会认为路径中包含的每个子路径都是封闭的。

然后,它会使用这些封闭的子路径并计算要填充的像素。

Quartz 可以使用两种方法来计算填充区域。

椭圆和矩形等简单路径具有明确定义的区域。

但是,如果你的路径由重叠的线段组成,或者路径包含多个子路径(如图3-12所示的同心圆),则可以使用两种规则来确定填充区域。

默认的填充规则称为非零绕数规则

要确定是否应该绘制特定点,请从该点开始并在绘图边界之外绘制一条线。

从 0 开始,每当路径段从左到右穿过线时,将计数加 1,每当路径段从右到左穿过线时,将计数减 1。

如果结果为 0,则不绘制该点。

否则,绘制该点。

绘制路径段的方向会影响结果。

图 3-12 显示了使用非零绕数规则填充的两组内圆和外圆。

当每个圆都以相同的方向绘制时,两个圆都会被填充。

当以相反的方向绘制圆时,内圆不会被填充。

您可以选择使用奇偶规则

要确定是否应绘制特定点,请从该点开始,并在绘图边界之外绘制一条线。

计算该线穿过的路径段的数量。

如果结果为奇数,则绘制该点。

如果结果为偶数,则不绘制该点。

绘制路径段的方向不会影响结果。

如图3-12所示,每个圆的绘制方向并不重要,填充将始终如图所示。


图 3-12 使用不同填充规则填充的同心圆


Quartz 提供了表 3-5中所示的函数用于填充当前路径。

其中一些是描边矩形或椭圆的便利函数。

功能 描述
CGContextEOFillPath 使用奇偶规则填充当前路径。
CGContextFillPath 使用非零绕数规则填充当前路径。
CGContextFillRect 填充适合指定矩形内的区域。
CGContextFillRects 填充适合指定矩形内的区域。
CGContextFillEllipseInRect 填充适合指定矩形内的椭圆。
CGContextDrawPath 如果通过kCGPathFill(非零绕数规则) 或kCGPathEOFill(奇偶规则),则填充当前路径。 如果通过kCGPathFillStrokekCGPathEOFillStroke,则填充并描边当前路径。

设置混合模式

混合模式 指定 Quartz 如何在背景上应用绘画。

Quartz 默认使用正常混合模式,该模式使用以下公式将前景绘画与背景绘画相结合:

c 复制代码
result = (alpha * foreground) + (1 - alpha) * background

颜色和颜色空间详细讨论了颜色的 alpha 分量,该分量指定颜色的不透明度。

对于本节中的示例,您可以假设颜色完全不透明(alpha 值 = 1.0)。

对于不透明颜色,当您使用正常混合模式进行绘制时,在背景上绘制的任何东西都会完全遮挡背景。

您可以通过调用CGContextSetBlendMode 函数 并传递适当的混合模式常量 来设置混合模式以实现各种效果。

请记住,混合模式是图形状态的一部分。

如果您在更改混合模式之前使用 CGContextSaveGState 函数,则调用 CGContextRestoreGState 函数会将混合模式重置为正常。

本节的其余部分展示了将图 3-13所示的矩形绘制在图 3-14所示的矩形上的结果。

在每种情况下(图 3-15至图 3-30),背景矩形都使用正常混合模式绘制。

然后通过使用适当的常量调用CGContextSetBlendMode函数来更改混合模式。

最后,绘制前景矩形。


图 3-13 前景中绘制的矩形


图 3-14 背景中绘制的矩形


注意: 您还可以使用混合模式合成两幅图像,或者将一张图片合成到已绘制到图形上下文的任何内容上。

混合模式与图像结合使用提供了有关如何使用混合模式合成图像的信息,并展示了将混合模式应用于两幅图像的结果。


正常混合模式

因为普通混合模式是默认混合模式,所以在使用其他混合模式常量之一后,只需使用常量调用 CGContextSetBlendMode 函数即可将混合模式重置为默认模式。

图 3-15显示了使用普通混合模式将图 3-13绘制在图 3-14上的结果。
kCGBlendModeNormal


图 3-15 使用普通混合模式绘制的矩形


正片叠底混合模式

乘法混合模式指定将前景图像样本与背景图像样本相乘。

生成的颜色至少与两个样本颜色一样暗。

图 3-16显示了使用乘法混合模式将图 3-13 绘制在 图 3-14 上的结果。

要使用此混合模式,请使用 kCGBlendModeMultiply常量调用 CGContextSetBlendMode 函数。


图 3-16 使用正片叠底混合模式绘制的矩形


屏幕混合模式

屏幕混合模式指定将前景图像样本的倒数与背景图像样本的倒数相乘。

生成的颜色至少与两个贡献样本颜色一样浅。

图 3-17显示了使用屏幕混合模式将图 3-13绘制在图 3-14上的结果。

要使用此混合模式,请使用 kCGBlendModeScreen 常量调用 CGContextSetBlendMode 函数。


图 3-17 使用屏幕混合模式绘制的矩形


叠加混合模式

叠加混合模式指定根据背景颜色将前景图像样本与背景图像样本相乘或屏蔽。

背景颜色与前景色混合以反映背景的亮度或暗度。

图 3-18显示了使用叠加混合模式将 图 3-13绘制在 图 3-14上的结果。

要使用此混合模式,请使用kCGBlendModeOverlay常量调用CGContextSetBlendMode函数。


图 3-18 使用叠加混合模式绘制的矩形


变暗混合模式

指定通过选择较暗的样本(来自前景图像或背景)来创建合成图像样本。

背景图像样本将被任何较暗的前景图像样本替换。

否则,背景图像样本保持不变。

图 3-19显示了使用变暗混合模式将图 3-13绘制在图 3-14上的结果。

要使用此混合模式,请使用kCGBlendModeDarken常量调用CGContextSetBlendMode函数。


图 3-19 使用变暗混合模式绘制的矩形


变亮混合模式

指定通过选择较亮的样本(来自前景或背景)来创建合成图像样本。

结果是背景图像样本被任何较亮的前景图像样本替换。

否则,背景图像样本保持不变。

图 3-20显示了使用变亮混合模式将图 3-13绘制在图 3-14上的结果。

要使用此混合模式,请使用kCGBlendModeLighten常量调用CGContextSetBlendMode函数。


图 3-20 使用变亮混合模式绘制的矩形


颜色减淡混合模式

指定使背景图像样本变亮以反映前景图像样本。

指定黑色的前景图像样本值不会产生变化。

图 3-21显示了使用颜色减淡混合模式将图 3-13绘制在图 3-14上的结果。

要使用此混合模式,请使用kCGBlendModeColorDodge常量调用CGContextSetBlendMode函数。


图 3-21 使用颜色减淡混合模式绘制的矩形


颜色加深混合模式

指定使背景图像样本变暗以反映前景图像样本。

指定白色的前景图像样本值不会产生变化。

图 3-22显示了使用颜色加深混合模式将图 3-13绘制在图 3-14上的结果。

要使用此混合模式,请使用kCGBlendModeColorBurn常量调用CGContextSetBlendMode函数。


图 3-22 使用颜色加深混合模式绘制的矩形


柔光混合模式

指定根据前景图像样本颜色使颜色变暗或变亮。

如果前景图像样本颜色比 50% 灰色浅,则背景变亮,类似于减淡。

如果前景图像样本颜色比 50% 灰色深,则背景变暗,类似于加深。

如果前景图像样本颜色等于 50% 灰色,则背景不变。

等于纯黑色或纯白色的图像样本会产生更暗或更亮的区域,但不会产生纯黑色或纯白色。

整体效果类似于通过在前景图像上照射漫射聚光灯所获得的效果。

使用此功能可以为场景添加高光。

图 3-23展示了使用柔光混合模式在图 3-14上绘制图 3-13的结果。

要使用此混合模式,请使用kCGBlendModeSoftLight常量调用CGContextSetBlendMode函数。


图 3-23 使用柔光混合模式绘制的矩形


强光混合模式

指定根据前景图像样本颜色对颜色进行乘法或筛选。

如果前景图像样本颜色比 50% 灰色浅,则背景会变亮,类似于筛选。

如果前景图像样本颜色比 50% 灰色深,则背景会变暗,类似于乘法。

如果前景图像样本颜色等于 50% 灰色,则前景图像不会改变。

等于纯黑或纯白的图像样本将产生纯黑或纯白。

整体效果类似于将强光聚光灯照射在前景图像上所获得的效果。

使用此功能可以为场景添加高光。

图 3-24展示了使用硬光混合模式在图 3-14上绘制图 3-13的结果。

要使用此混合模式,请使用kCGBlendModeHardLight常量调用CGContextSetBlendMode函数。


图 3-24 使用硬光混合模式绘制的矩形


差异混合模式

指定从背景图像样本颜色中减去前景图像样本颜色,或反之,具体取决于哪个样本具有更大的亮度值。

黑色的前景图像样本值不会产生任何变化;白色会反转背景颜色值。

图 3-25显示了使用差异混合模式将图 3-13绘制在图 3-14上的结果。

要使用此混合模式,请使用 kCGBlendModeDifference常量调用 CGContextSetBlendMode 函数。


图 3-25 使用差值混合模式绘制的矩形


排除混合模式

指定与kCGBlendModeDifference 产生的效果类似的效果,但对比度较低。

黑色的前景图像样本值不会产生变化;白色会反转背景颜色值。

图 3-26显示了使用排除混合模式将图 3-13绘制在图 3-14上的结果。

要使用此混合模式,请使用 kCGBlendModeExclusion 常量调用CGContextSetBlendMode函数。


图 3-26 使用排除混合模式绘制的矩形


色相混合模式

指定使用背景的亮度和饱和度值以及前景图像的色调。

图 3-27显示了使用色调混合模式将图 3-13绘制在图 3-14上的结果。

要使用此混合模式,请使用kCGBlendModeHue常量调用CGContextSetBlendMode函数。


图 3-27 使用色相混合模式绘制的矩形


饱和度混合模式

指定使用背景的亮度和色调值以及前景图像的饱和度。

背景中没有饱和度的区域(即纯灰色区域)不会产生变化。

图 3-28显示了使用饱和度混合模式将图 3-13绘制在图 3-14上的结果。

要使用此混合模式,请使用kCGBlendModeSaturation常量调用CGContextSetBlendMode函数。


图 3-28 使用饱和度混合模式绘制的矩形


颜色混合模式

指定使用背景的亮度值和前景图像的色调和饱和度值。

此模式保留图像中的灰度级。

您可以使用此模式为单色图像着色或为彩色图像着色。

图 3-29显示了使用颜色混合模式将图 3-13绘制在图 3-14上的结果。

要使用此混合模式,请使用kCGBlendModeColor常量调用CGContextSetBlendMode函数。


图 3-29 使用颜色混合模式绘制的矩形


亮度混合模式

指定使用背景的色调和饱和度以及前景图像的亮度。

此模式创建的效果与 创建的效果相反kCGBlendModeColor

图 3-30显示了使用亮度混合模式将图 3-13绘制在图 3-14上的结果。

要使用此混合模式,请使用kCGBlendModeLuminosity常量调用CGContextSetBlendMode函数。


图 3-30 使用亮度混合模式绘制的矩形


5、剪切到路径

当前剪切区域 是根据用作遮罩的路径创建的,允许您遮挡页面中您不想绘制的部分。

例如,如果您有一个非常大的位图图像并且只想显示其中的一小部分,则可以将剪切区域设置为仅显示您想要显示的部分。

当你绘画时,Quartz 只会在剪切区域内渲染绘画。

剪切区域封闭子路径内的绘画是可见的;剪切区域封闭子路径外的绘画则不可见。

最初创建图形上下文时,剪切区域包括上下文的所有可绘制区域(例如,PDF 上下文的媒体框)。

您可以通过设置当前路径,然后使用剪切函数(而不是绘制函数)来更改剪切区域。

剪切函数将当前路径的填充区域与现有剪切区域相交。

因此,您可以使剪切区域相交,从而缩小图片的可见区域,但不能增加剪切区域的面积。

裁剪区域是图形状态的一部分。

要将裁剪区域恢复到之前的状态,您可以在裁剪之前保存图形状态,并在裁剪完成后恢复图形状态。

例 3-1显示了一段代码,它设置了一个圆形的剪切区域。

此代码导致绘图被剪切,类似于图 3-3中所示的内容。

(有关另一个示例,请参阅 渐变一章中的剪切上下文。)


例 3-1 设置圆形裁剪区域

c 复制代码
CGContextBeginPath (context);
CGContextAddArc (context, w/2, h/2, ((w>h) ? h : w)/2, 0, 2*PI, 0);
CGContextClosePath (context);
CGContextClip (context);

功能 描述
CGContextClip 使用非零绕数规则计算当前路径与当前剪切路径的交点。
CGContextEOClip 使用奇偶规则计算当前路径与当前剪切路径的交点。
CGContextClipToRect 将剪切区域设置为当前剪切路径和指定矩形相交的区域。
CGContextClipToRects 将剪切区域设置为当前剪切路径和指定矩形内区域相交的区域。
CGContextClipToMask 将蒙版映射到指定的矩形中,并将其与图形上下文的当前剪切区域相交。 您对图形上下文执行的任何后续路径绘制都将被剪切。 (请参阅 通过剪切上下文来遮罩图像。)

五、颜色和颜色空间

设备(显示器、打印机、扫描仪、相机)处理颜色的方式各不相同;每种设备都有其可以忠实呈现的颜色范围。

一台设备上呈现的颜色可能无法在另一台设备上呈现。

为了有效地处理颜色并理解 Quartz 2D 使用颜色空间和颜色的函数,您应该熟悉 颜色管理概述 中讨论的术语。

该文档讨论了颜色感知、颜色值、设备无关和设备颜色空间、颜色匹配问题、渲染意图、颜色管理模块和 ColorSync。

在本章中,您将了解 Quartz 如何表示颜色和颜色空间,以及 alpha 分量是什么。

本章还讨论如何:

  • 创建色彩空间
  • 创建和设置颜色
  • 设置渲染意图

1、关于颜色和颜色空间

Quartz 中的颜色由一组值表示。

如果没有颜色空间来指示如何解释颜色信息,这些值就没有意义。

例如,表 4-1中的值都表示全强度的蓝色。

但如果不知道颜色空间或每个颜色空间允许的值范围,您就无法知道每组值代表哪种颜色。

价值观 色彩空间 成分
240 度,100%,100% HSB 色调、饱和度、亮度
0,0,1 RGB 红、绿、蓝
1,1,0,0 CMYK 青色、洋红色、黄色、黑色
1,0,0 BGR 蓝色、绿色、红色

如果提供了错误的色彩空间,就会出现非常明显的差异,如图4-1所示。

虽然 BGR 和 RGB 色彩空间对绿色的解释相同,但红色和蓝色的值却颠倒了。


图 4-1 将 BGR 和 RGB 颜色配置文件应用于同一图像


颜色空间可以有不同数量的组件。

表中的三个颜色空间有三个组件,而 CMYK 颜色空间有四个。

值范围与该颜色空间有关。

对于大多数颜色空间,Quartz 中的颜色值范围从 0.0 到 1.0,其中 1.0 表示全强度。

例如,在 Quartz 中的 RGB 颜色空间中指定的全强度蓝色的值为 (0, 0, 1.0)。

在 Quartz 中,颜色还有一个 alpha 值,用于指定颜色的透明度。

表 4-1 中的颜色值不显示 alpha 值。


2、Alpha 值

alpha 值 是Quartz 用来决定如何将新绘制的对象合成到现有页面的图形状态参数。

在最大强度下,新绘制的对象是不透明的。

在零强度下,新绘制的对象是不可见的。

图 4-2显示了五个大矩形,使用 alpha 值 1.0、0.75、0.5、0.1 和 0.0 绘制。

当大矩形变得透明时,它会露出下面绘制的一个较小的不透明红色矩形。


图 4-2 使用不同 alpha 值绘制的大矩形的比较


通过在绘制之前在图形上下文中全局设置 alpha 值,可以使页面上的对象和页面本身都透明。

图 4-3将全局 alpha 设置 0.5 与默认值 1.0 进行了比较。


图 4-3 全局 alpha 值的比较


在正常混合模式(图形状态的默认模式)下,Quartz 通过使用以下公式将源颜色的成分与目标颜色的成分组合来执行 alpha 混合:

c 复制代码
destination = (alpha * source) + (1 - alpha) * destination

其中 source是新油漆颜色的一个分量,destination是背景颜色的一个分量。

此公式针对每个新绘制的形状或图像执行。

对于对象透明度,将 alpha 值设置为1.0以指定您绘制的对象应完全不透明;将其设置为0.0以指定新绘制的对象完全透明。

介于 和 之间的 alpha 值0.0指定1.0部分透明的对象。

您可以将 alpha 值作为最后一个颜色组件提供给所有接受颜色的例程。

您还可以使用CGContextSetAlpha函数设置全局 alpha 值。

请记住,如果您同时设置两者,Quartz 会将 alpha 颜色组件乘以全局 alpha 值。

要使页面本身完全透明,只要图形上下文是窗口或位图图形上下文,您就可以使用CGContextClearRect函数明确清除图形上下文的 alpha 通道。

例如,您可能希望在为图标创建透明蒙版时执行此操作,或者使窗口的背景透明。


3、创建色彩空间

Quartz 支持色彩管理系统用于设备无关色彩空间的标准色彩空间,还支持通用、索引和图案色彩空间。
设备无关色彩空间 以可在设备之间移植的方式表示颜色。

它们用于将颜色数据从一个设备的本机色彩空间交换到另一个设备的本机色彩空间。

在设备功能允许的范围内,设备无关色彩空间中的颜色在不同设备上显示时看起来相同。

因此,设备无关色彩空间是您表示颜色的最佳选择。

具有精确颜色要求的应用程序应始终使用与设备无关的颜色空间。

常见的与设备无关的颜色空间是通用颜色空间。

通用颜色空间可让操作系统为您的应用程序提供最佳颜色空间。

在显示器上绘图的效果与在打印机上打印相同内容的效果一样好。

重要提示: iOS 不支持独立于设备或通用的色彩空间。

iOS 应用程序必须使用设备色彩空间。


创建与设备无关的色彩空间

要创建独立于设备的色彩空间,您需要为 Quartz 提供特定设备的参考白点、参考黑点和伽马值。

Quartz 使用此信息将源色彩空间中的颜色转换为输出设备的色彩空间。

Quartz 支持的与设备无关的颜色空间以及创建它们的函数是:

  • La b* 是孟塞尔颜色符号系统(一种通过色调、明度和饱和度(或色度)值指定颜色的系统)的非线性变换。
    此颜色空间将感知的颜色差异与颜色空间中的定量距离相匹配。
    L* 分量表示亮度值,a* 分量表示从绿色到红色的值,b* 分量表示从蓝色到黄色的值。
    此颜色空间旨在模仿人类大脑解码颜色的方式。
    使用CGColorSpaceCreateLab函数。
  • ICC 是国际色彩联盟定义的 ICC 颜色配置文件中的一种颜色空间。
    ICC 配置文件定义了设备支持的色域以及其他设备特性,因此可以使用这些信息准确地将一个设备的色彩空间转换为另一个设备的色彩空间。
    设备制造商通常会提供 ICC 配置文件。
    某些彩色显示器和打印机包含嵌入的 ICC 配置文件信息,某些位图格式(如 TIFF)也是如此。
    请使用CGColorSpaceCreateICCBased函数。
  • 校准的 RGB 是一种与设备无关的 RGB 颜色空间,它表示相对于参考白点的颜色,该参考白点基于输出设备可以生成的最白光。
    使用函数CGColorSpaceCreateCalibratedRGB
  • 校准灰色是一种与设备无关的灰度颜色空间,它表示相对于参考白点的颜色,该参考白点基于输出设备可以生成的最白光。
    使用函数CGColorSpaceCreateCalibratedGray

创建通用色彩空间

通用颜色空间将颜色匹配留给系统。

在大多数情况下,结果是可以接受的。

虽然名称可能暗示其他含义,但每个"通用"颜色空间(通用灰色、通用 RGB 和通用 CMYK)都是与设备无关的特定颜色空间。

通用颜色空间易于使用;您无需提供任何参考点信息。

您可以使用该函数CGColorSpaceCreateWithName以及以下常量之一来创建通用颜色空间:

  • kCGColorSpaceGenericGray,指定通用灰色,即一种单色空间,允许指定从绝对黑色(值 0.0)到绝对白色(值 1.0)的单一值。
  • kCGColorSpaceGenericRGB,它指定通用 RGB,即一个三分量颜色空间(红色、绿色和蓝色),用于模拟彩色显示器上单个像素的组成方式。
    RGB 颜色空间的每个分量的值范围从 0.0(零强度)到 1.0(全强度)。
  • kCGColorSpaceGenericCMYK,它指定通用 CMYK,即四分量颜色空间(青色、洋红色、黄色和黑色),用于模拟打印过程中墨水的累积方式。
    CMYK 颜色空间的每个分量的值范围从 0.0(不吸收颜色)到 1.0(完全吸收颜色)。

创建设备色彩空间

设备颜色空间主要由 iOS 应用程序使用,因为没有其他选项可用。

在大多数情况下,Mac OS X 应用程序应使用通用颜色空间,而不是创建设备颜色空间。

但是,某些 Quartz 例程需要具有设备颜色空间的图像。

例如,如果您调用CGImageCreateWithMask并指定图像作为掩码,则必须使用设备灰度颜色空间定义该图像。

您可以使用以下函数之一创建设备颜色空间:

  • CGColorSpaceCreateDeviceGray 用于设备相关的灰度颜色空间。
  • CGColorSpaceCreateDeviceRGB 用于设备相关的 RGB 颜色空间。
  • CGColorSpaceCreateDeviceCMYK 用于设备相关的 CMYK 颜色空间。

创建索引和模式颜色空间

索引颜色空间包含一个最多有 256 个条目的颜色表,以及颜色表条目映射到的基本颜色空间。

颜色表中的每个条目指定基本颜色空间中的一种颜色。

使用函数CGColorSpaceCreateIndexed

图案颜色空间(在图案中讨论)用于使用图案进行绘画。

使用CGColorSpaceCreatePattern函数。


4、设置和创建颜色

Quartz 提供了一套用于设置填充颜色、描边颜色、颜色空间和 alpha 的函数。

这些颜色参数中的每一个都适用于图形状态,这意味着一旦设置,该设置将一直有效,直到设置为另一个值。

颜色必须有关联的色彩空间。

否则,Quartz 将不知道如何解释颜色值。

此外,您需要为绘图目标提供适当的色彩空间。

比较图 4-4左侧的蓝色填充颜色(CMYK 填充颜色)与右侧显示的蓝色(RGB 填充颜色)。

如果您查看本文档的屏幕版本,您将看到填充颜色之间存在很大差异。

这些颜色在理论上是相同的,但仅当 RGB 颜色用于 RGB 设备并且 CMYK 颜色用于 CMYK 设备时才会看起来相同。


图 4-4 CMYK 填充颜色和 RGB 填充颜色


您可以使用函数CGContextSetFillColorSpaceCGContextSetStrokeColorSpace来设置填充和描边颜色空间,也可以使用其中一个便捷函数(列于表 4-2中)来设置设备颜色空间的颜色。

功能 用于设置颜色
CGContextSetRGBStrokeColor``CGContextSetRGBFillColor 设备 RGB。 在 PDF 生成时,Quartz 会将颜色写入,就好像它们位于相应的通用颜色空间中一样。
CGContextSetCMYKStrokeColor``CGContextSetCMYKFillColor 设备 CMYK。 (在 PDF 生成时保留设备 CMYK。)
CGContextSetGrayStrokeColor``CGContextSetGrayFillColor 设备灰色。 在 PDF 生成时,Quartz 会将颜色写入,就好像它们位于相应的通用颜色空间中一样。
CGContextSetStrokeColorWithColor``CGContextSetFillColorWithColor 任何颜色空间;您提供一个指定颜色空间的 CGColor 对象。 使用这些函数可重复获取您需要的颜色。
CGContextSetStrokeColor``CGContextSetFillColor 当前颜色空间。不推荐。 相反,使用 CGColor 对象和函数CGContextSetStrokeColorWithColor和来设置颜色CGContextSetFillColorWithColor

您可以将填充和描边颜色指定为位于填充和描边颜色空间内的值。

例如,RGB 颜色空间中的完全饱和红色被指定为四个数字的数组:(1.0、0.0、0.0、1.0)。

前三个数字指定全红色强度,不指定绿色或蓝色强度。

第四个数字是 alpha 值,用于指定颜色的不透明度。

如果您在应用程序中重复使用颜色,设置填充和描边颜色的最有效方法是创建一个 CGColor 对象,然后将其作为参数传递给函数CGContextSetFillColorWithColorCGContextSetStrokeColorWithColor

您可以根据需要保留 CGColor 对象。

您可以通过直接使用 CGColor 对象来提高应用程序的性能。

您可以通过调用CGColorCreate函数来创建 CGColor 对象,传递一个 CGColorspace 对象和一个指定颜色强度值的浮点值数组。

数组中的最后一个组件指定 alpha 值。


5、设置渲染意图

渲染意图指定 Quartz 如何将源颜色空间中的颜色映射到图形上下文的目标颜色空间色域内的颜色。

如果您未明确设置渲染意图,Quartz 将对所有绘图(位图(采样)图像除外)使用相对比色渲染意图。

Quartz 将对这些图像使用感知渲染意图。

要设置渲染意图,请调用 CGContextSetRenderingIntent 函数,传递图形上下文和以下常量之一:

  • kCGRenderingIntentDefault. 使用上下文的默认渲染意图。
  • kCGRenderingIntentAbsoluteColorimetric
    将输出设备色域之外的颜色映射到输出设备色域内最接近的颜色。
    这会产生剪切效果,其中图形上下文色域中的两个不同颜色值被映射到输出设备色域中的相同颜色值。
    当图形中使用的颜色在源和目标的色域内时,这是最佳选择,徽标或使用专色时通常如此。
  • kCGRenderingIntentRelativeColorimetric
    相对比色度会偏移所有颜色(包括色域内的颜色),以解释图形上下文的白点和输出设备的白点之间的差异。
  • kCGRenderingIntentPerceptual. 通过压缩图形上下文的色域以适应输出设备的色域来保留颜色之间的视觉关系。
    感知意图适用于照片和其他复杂、详细的图像。
  • kCGRenderingIntentSaturation
    在转换为输出设备的色域时保留颜色的相对饱和度值。
    结果是图像具有明亮、饱和的色彩。
    饱和度意图适合再现细节较少的图像,例如演示图表和图形。

六、变换

Quartz 2D 绘图模型定义了两个完全独立的坐标空间:用户空间(表示文档页面)和设备空间(表示设备的原始分辨率)。

用户空间坐标是浮点数,与设备空间像素的分辨率无关。

当您想要打印或显示文档时,Quartz 会将用户空间坐标映射到设备空间坐标。

因此,您无需重写应用程序或编写额外的代码来调整应用程序的输出,以便在不同的设备上获得最佳显示效果。

您可以通过操作当前变换矩阵 (或 CTM )来修改默认用户空间。

创建图形上下文后,CTM 为单位矩阵。

您可以使用 Quartz 变换函数来修改 CTM,从而修改用户空间中的绘图。

本章:

  • 概述了可用于执行转换的函数
  • 演示如何修改 CTM
  • 描述如何创建仿射变换
  • 展示如何确定两个变换是否等价
  • 描述如何获取用户到设备空间的变换
  • 讨论仿射变换背后的数学

1、关于 Quartz 转换函数

您可以使用 Quartz 2D 内置的转换函数轻松地平移、缩放和旋转您的绘图。

只需几行代码,您就可以以任何顺序和任何组合应用这些转换。

图 5-1说明了缩放和旋转图像的效果。

您应用的每个转换都会更新 CTM。

CTM 始终代表用户空间和设备空间之间的当前映射。

此映射可确保您的应用程序的输出在任何显示屏或打印机上看起来都很棒。


图 5-1 应用缩放和旋转


Quartz 2D API 提供了五个函数,允许您获取和修改 CTM。

您可以旋转、平移和缩放 CTM,还可以将仿射变换矩阵与 CTM 连接起来。

请参阅 修改当前变换矩阵

Quartz 还允许您创建仿射变换,这些变换不会在用户空间上运行,除非您决定将变换应用于 CTM。

您可以使用另一组函数来创建仿射变换,然后可以将其与 CTM 连接起来。

请参阅 创建仿射变换

您可以使用任一函数集,而无需了解任何矩阵数学知识。

但是,如果您想了解调用其中一个转换函数时 Quartz 会做什么,请阅读矩阵背后的数学


2、修改当前变换矩阵

在绘制图像之前,您可以操纵 CTM 来旋转、缩放或平移页面,从而变换您要绘制的对象。

在变换 CTM 之前,您需要保存图形状态,以便在绘制后恢复它。

您还可以将 CTM 与仿射变换连接起来(请参阅 创建仿射变换)。

本节将介绍这四种操作(平移、旋转、缩放和连接)以及执行每种操作的 CTM 函数。

下面这行代码绘制了一幅图像,假设你提供了一个有效的图形上下文、一个指向要绘制图像的矩形的指针和一个有效的 CGImage 对象。

该代码绘制了一幅图像,如图5-2所示的示例公鸡图像。

当你阅读本节的其余部分时,你会看到图像在你应用变换时是如何变化的。

c 复制代码
CGContextDrawImage(myContext,rect,myImage);

图 5-2 未变换的图像


平移 会将坐标空间的原点移动您为 x 轴和 y 轴指定的量。

您可以调用CGContextTranslateCTM函数将每个点的 x 和 y 坐标修改指定的量。

图 5-3显示了一幅图像在 x 轴上平移了 100 个单位,在 y 轴上平移了 50 个单位,使用以下代码行:

c 复制代码
CGContextTranslateCTM(myContext,100,50);

图 5-3 平移后的图像


旋转 将坐标空间移动您指定的角度。

您可以调用CGContextRotateCTM函数 来指定旋转角度(以弧度为单位)。

图 5-4显示了一幅图像,该图像绕原点(即窗口的左下角)旋转了 -45 度,使用以下代码行:

c 复制代码
CGContextRotateCTM(myContext,弧度(-45.));

由于旋转将图像的一部分移到了上下文之外,因此图像被裁剪。

您需要以弧度为单位指定旋转角度。

如果您计划执行多次旋转,编写弧度例程会很有用。

c 复制代码
#include <math.h>
static inline double radians (double degrees) {return degrees * M_PI/180;}

图 5-4 旋转后的图像


缩放 会根据您指定的 x 和 y 因子改变坐标空间的比例,从而有效地拉伸或收缩图像。

x 和 y 因子的大小决定新坐标是大于还是小于原始坐标。

此外,通过使 x 因子为负,您可以沿 x 轴翻转坐标;同样,通过使 y 因子为负,您可以沿 y 轴水平翻转坐标。

您可以调用CGContextScaleCTM函数 来指定 x 和 y 缩放因子。

图 5-5显示了一幅图像,其 x 值缩放了 .5,y 值缩放了 .75,使用以下代码行:

c 复制代码
CGContextScaleCTM(myContext,.5,.75);

图 5-5 缩放后的图像


连接 通过将两个矩阵相乘来组合它们。

您可以连接多个矩阵以形成一个包含矩阵累积效应的矩阵。

您可以调用 CGContextConcatCTM 函数将 CTM 与仿射变换组合起来。

仿射变换以及创建它们的函数在创建仿射变换中讨论。

实现累积效果的另一种方法是执行两次或多次转换,而不在转换调用之间恢复图形状态。

图 5-6显示了平移图像然后旋转图像的结果,使用以下代码行:

c 复制代码
CGContextTranslateCTM (myContext, w,h);
CGContextRotateCTM (myContext, radians(-180.));

图 5-6 平移和旋转后的图像


图5-7展示了平移、缩放和旋转的图像,使用以下代码行:

c 复制代码
CGContextTranslateCTM (myContext, w/4, 0);
CGContextScaleCTM (myContext, .25,  .5);
CGContextRotateCTM (myContext, radians ( 22.));

图 5-7 经过平移、缩放和旋转的图像


执行多个转换的顺序很重要;如果反转顺序,则会得到不同的结果。

反转用于创建图 5-7 的转换顺序,您将得到如图 5-8所示的结果,该结果由以下代码生成:

c 复制代码
CGContextRotateCTM (myContext, radians ( 22.));
CGContextScaleCTM (myContext, .25,  .5);
CGContextTranslateCTM (myContext, w/4, 0);

图 5-8 经过旋转、缩放和平移的图像


3、创建仿射变换

Quartz 中提供的仿射变换函数作用于矩阵,而不是 CTM。

您可以使用这些函数构造一个矩阵,然后通过调用函数将其应用于 CTM CGContextConcatCTM

仿射变换函数可以作用于数据结构,也可以返回CGAffineTransform数据结构。

您可以构造可重复使用的简单或复杂仿射变换。

仿射变换函数执行与 CTM 函数相同的操作 - 平移、旋转、缩放和连接。

表 5-1列出了执行这些操作的函数及其使用信息。

请注意,平移、旋转和缩放操作各有两个函数。

功能 使用
CGAffineTransformMakeTranslation 根据指定移动原点的量的 x 和 y 值构建一个新的平移矩阵。
CGAffineTransformTranslate 将平移操作应用于现有的仿射变换。
CGAffineTransformMakeRotation 根据以弧度为单位指定坐标系旋转多少的值构建新的旋转矩阵。
CGAffineTransformRotate 对现有的仿射变换应用旋转操作。
CGAffineTransformMakeScale 根据 x 和 y 值构建一个新的缩放矩阵,指定拉伸或收缩坐标的量。
CGAffineTransformScale 对现有的仿射变换应用缩放操作。

Quartz 还提供了一个仿射变换函数CGAffineTransformInvert,可以反转矩阵。

反转通常用于提供变换对象内点的反向变换。

当您需要恢复已由矩阵变换的值时,反转非常有用:反转矩阵,并将该值乘以反转矩阵,结果就是原始值。

您通常不需要反转变换,因为您可以通过保存和恢复图形状态来反转变换 CTM 的效果。

在某些情况下,您可能不想变换整个空间,而只想变换一个点或一个大小。

您可以通过调用CGPointApplyAffineTransform函数来对CGPoint结构进行操作。

您可以通过调用CGSizeApplyAffineTransform 函数来对CGSize结构进行操作。

您可以通过调用CGRectApplyAffineTransform 函数来对CGRect结构进行操作。

此函数返回包含传递给它的矩形的变换后角点的最小矩形。

如果对矩形进行操作的仿射变换仅执行缩放和平移操作,则返回的矩形与由四个变换后的角构成的矩形重合。

您可以通过调用CGAffineTransformMake函数 来创建新的仿射变换,但与其他创建新仿射变换的函数不同,此函数要求您提供矩阵条目。

要有效使用此函数,您需要了解矩阵数学。

请参阅 矩阵背后的数学


4、评估仿射变换

您可以通过调用函数来确定一个仿射变换是否等于另一个。

如果传递给它的两个变换相等,则CGAffineTransformEqualToTransform函数返回true,否则返回false

CGAffineTransformIsIdentity 函数是一个有用的函数,用于检查变换是否为恒等变换

恒等变换不执行平移、缩放或旋转。

将此变换应用于输入坐标始终会返回输入坐标。

Quartz 常量 CGAffineTransformIdentity表示恒等变换。


5、让用户进入设备空间转换

通常,当您使用 Quartz 2D 进行绘制时,您只在用户空间中工作。

Quartz 会为您处理用户空间和设备空间之间的转换。

如果您的应用程序需要获取 Quartz 用于在用户空间和设备空间之间进行转换的仿射变换,您可以调用CGContextGetUserSpaceToDeviceSpaceTransform函数。

Quartz 提供了许多便捷函数 来在用户空间 和设备空间 之间转换以下几何图形。

您可能会发现这些函数比应用从 CGContextGetUserSpaceToDeviceSpaceTransform 函数返回的仿射变换更容易使用。

  • Points。
    CGContextConvertPointToDeviceSpaceCGContextConvertPointToUserSpace 函数 将 CGPoint 数据类型 从一个空间转换到另一个空间。
  • Sizes。
    函数CGContextConvertSizeToDeviceSpaceCGContextConvertSizeToUserSpace 可将 CGSize 数据类型 从一个空间转换到另一个空间。
  • 矩形。
    函数CGContextConvertRectToDeviceSpaceCGContextConvertRectToUserSpace 可将CGRect数据类型 从一个空间转换到另一个空间。

6、矩阵背后的数学

唯一需要您了解矩阵数学的 Quartz 2D 函数是 CGAffineTransformMake 函数,它对 3 x 3 矩阵中的六个关键条目进行仿射变换。

即使您从未打算从头开始构建仿射变换矩阵,您也可能会发现变换函数背后的数学很有趣。

如果没有,您可以跳过本章的其余部分。

3 x 3 变换矩阵的六个临界值(abcdtxty)如下面的矩阵所示:


注意: 矩阵最右边的列始终包含常数值 0、0、1。

从数学上讲,第三列是连接所必需的,本节后面将对此进行解释。

它出现在本节中只是为了数学正确性。

给定上面描述的 3 x 3 变换矩阵,Quartz 使用以下方程将点 (x, y) 变换为结果点 (x', y'):


结果位于不同的坐标系中,即由变换矩阵中的变量值变换的坐标系。

以下等式是前一个矩阵变换的定义:


以下矩阵是单位矩阵。

它不执行平移、缩放或旋转。

将此矩阵乘以输入坐标始终返回输入坐标。


利用前面讨论过的公式,可以看到这个矩阵会生成一个与旧点 ( x , y ) 相同的新点 (x', y'):


该矩阵描述了翻译操作:


这些是 Quartz 用于应用翻译的结果方程:


该矩阵描述了对点 (x, y) 的**缩放*操作:


这些是 Quartz 用于缩放坐标的结果方程:


该矩阵描述了旋转操作,将点 (x, y) 逆时针旋转角度 a:


这些是 Quartz 用于应用旋转的结果方程:


此等式将旋转运算和平移运算 连接起来:


这些是 Quartz 用于应用变换的结果方程:


请注意,连接矩阵的顺序很重要------矩阵乘法不具有交换性。

也就是说,矩阵 A 乘以矩阵 B 的结果不一定等于矩阵 B 乘以矩阵 A 的结果。

如前所述,连接是仿射变换矩阵包含第三列(常数值为 0、0、1)的原因。

要将一个矩阵与另一个矩阵相乘,一个矩阵的列数必须与另一个矩阵的行数匹配。

这意味着 2 x 3 矩阵不能与 2 x 3 矩阵相乘。

因此,我们需要包含常数值的额外列。

运算会从变换后的坐标生成原始坐标。

给定坐标 (x, y),该坐标已由给定矩阵 A 变换为新坐标 (x', y'),将坐标 (x', y') 变换为矩阵 A 的逆会生成原始坐标 (x, y)。

将矩阵与其逆相乘,结果为单位矩阵。


七、模式

图案是一系列重复绘制到图形上下文的绘图操作。

您可以像使用颜色一样使用图案。

当您使用图案进行绘图时,Quartz 会将页面划分为一组图案单元,每个单元的大小与图案图像的大小相同,并使用您提供的回调绘制每个单元。
6-1显示了绘制到窗口图形上下文的图案。


图 6-1 绘制在窗口上的图案


1、模式的剖析

图案单元是图案的基本组成部分。

图 6-2显示了 图 6-1 中图案的图案单元。

黑色矩形不是图案的一部分;它是为了显示图案单元的结束位置而绘制的。


图 6-2 模式单元


这个特定图案单元的尺寸包括四个彩色矩形的面积以及矩形上方和右侧的空间,如图6-3所示。

图中围绕每个图案单元的黑色矩形不是单元的一部分;它是为了指示单元的边界 而绘制的。

创建图案单元时,您可以定义单元的边界并在边界内进行绘制。


图 6-3 图案单元格,其中绘制了黑色矩形以显示每个单元格的边界


您可以指定 Quartz 在水平和垂直方向上绘制每个图案单元的起点与下一个图案单元的起点之间的距离。

图 6-3中的图案单元的绘制方式是,一个图案单元的起点与下一个图案单元的距离正好是一个图案宽度,从而导致每个图案单元与下一个图案单元相邻。

图 6-4中的图案单元在水平和垂直两个方向上都添加了空间。

您可以为每个方向指定不同的间距值

如果使间距小于图案单元的宽度或高度,则图案单元会重叠。


图 6-4 图案 单元之间的间距


当你绘制一个模式单元格时,Quartz 使用模式空间 作为坐标系。

模式空间是一个抽象空间,它通过你在创建模式时指定的转换矩阵(即模式矩阵)映射到默认的用户空间。

注意: 模式空间与用户空间是分开的。

未转换的模式空间映射到基本(未转换的)用户空间,而不管当前转换矩阵的状态如何。

当您将转换应用于模式空间时,Quartz 只会将转换应用于模式空间。

模式坐标系的默认约定是底层图形上下文的约定。

默认情况下,Quartz 使用的坐标系中,正 x 值表示向右位移,正 y 值表示向上位移。

但是,UIKit 创建的图形上下文使用不同的约定,其中正 y 值表示向下位移。

虽然此约定通常通过将变换连接到坐标系来应用于图形上下文,但在这种情况下,Quartz还会修改模式空间的默认约定以匹配。

如果您不想让 Quartz 变换图案单元,您可以指定单位矩阵。

但是,您可以通过提供变换矩阵来实现有趣的效果。

图 6-5显示了缩放图 6-2中所示的图案单元的效果。

图 6-6演示了旋转图案单元。

平移图案单元有点微妙。

图 6-7显示了图案的原点,图案单元在水平和垂直两个方向上平移,因此图案不再像图 6-1中那样与窗口相邻。


图 6-5 缩放的图案单元


图 6-6 旋转后的图案单元


图 6-7 平移后的模式单元


2、彩色图案和模板(无色)图案

彩色图案 具有与其相关的固有颜色。

更改用于创建图案单元的颜色,图案就会失去其含义。

苏格兰格子呢(如图6-8中所示的样品)就是彩色图案的一个例子。

彩色图案中的颜色是在图案单元创建过程中指定的,而不是在图案绘制过程中指定的。


图 6-8 彩色图案具有固有颜色


其他图案仅根据其形状进行定义,因此可以将其视为模板图案 、无色图案,甚至是图像蒙版。

图 6-9中所示的红色和黑色星星分别是同一图案单元的再现。

该单元本身由一种形状组成 --- 实心星星。

定义图案单元时,没有与其关联的颜色。

颜色是作为图案绘制过程的一部分指定的,而不是作为图案单元创建的一部分指定的。


图 6-9 模板图案没有固有颜色


您可以在 Quartz 2D 中创建任意类型的图案(彩色或模板)。


3、平铺

平铺 是将图案单元渲染到页面的一部分的过程。

当 Quartz 将图案渲染到设备时,Quartz 可能需要调整图案以适应设备空间。

也就是说,由于用户空间单位和设备像素之间的差异,在用户空间中定义的图案单元在渲染到设备时可能并不完全适合。

Quartz 有三种平铺选项,可用于在必要时调整图案。

Quartz 可以保留:

  • 图案,以稍微调整图案单元之间的间距为代价,但调整幅度不超过一个设备像素。
    这被称为无失真
  • 单元格之间的间距,以稍微扭曲图案单元格为代价,但扭曲程度不超过一个设备像素。
    这被称为具有最小扭曲的恒定间距
  • 单元格之间的间距(对于最小扭曲选项而言)以尽可能扭曲图案单元格为代价,以实现快速平铺。
    这称为恒定间距

4、模式如何工作

图案的操作与颜色类似,你可以设置填充或描边图案,然后调用绘画函数。

Quartz 使用你设置的图案作为"绘画"。

例如,如果你想用纯色绘制一个填充的矩形,你首先调用一个函数,例如CGContextSetFillColor,来设置填充颜色。

然后调用CGContextFillRect函数用你指定的颜色绘制填充的矩形。

要用图案绘画,你首先调用CGContextSetFillPattern函数来设置图案。

然后调用 CGContextFillRect函数用你指定的图案实际绘制填充的矩形。

用颜色绘画和用图案绘画的区别在于你必须定义图案。

你将图案和颜色信息提供给 CGContextSetFillPattern 函数。

你将在绘制彩色图案绘制模板图案中 看到如何创建、设置和绘制图案。

下面是 Quartz 如何在后台使用您提供的图案进行绘制的示例。

当您使用图案进行填充或描边时,Quartz 在概念上会执行以下任务来绘制每个图案单元:

  1. 保存图形状态。
  2. 将当前变换矩阵转换为模式单元的原点。
  3. 将 CTM 与模式矩阵连接起来。
  4. 剪辑到图案单元的边界矩形。
  5. 调用绘图回调来绘制图案单元。
  6. 恢复图形状态。

Quartz 会为您处理所有平铺,反复将图案单元渲染到绘图空间,直到整个空间都被绘制完成。

您可以用图案填充或描边。

图案单元可以是您指定的任何大小。

如果您想看到图案,您应该确保图案单元适合绘图空间。

例如,如果您的图案单元是 8 个单位乘以 10 个单位,并且您使用图案描边宽度为 2 个单位的线,则图案单元将被剪裁,因为它的宽度为 10 个单位。

在这种情况下,您可能无法识别图案。


5、绘制彩色图案

以下部分介绍了绘制彩色图案所需执行的五个步骤:

  1. 编写绘制彩色图案单元的回调函数
  2. 设置彩色图案色彩空间
  3. 设置彩色图案的结构
  4. 将彩色图案指定为填充或描边图案
  5. 用彩色图案绘画

这些步骤与绘制模板图案的步骤相同。

两者的区别在于设置颜色信息的方式。

您可以在完整的彩色图案绘制功能中看到所有步骤是如何组合在一起的。


编写绘制彩色图案单元的回调函数

图案单元的外观完全由您决定。

对于此示例,例 6-1中的代码绘制了图 6-2中所示的图案单元。

回想一下,图案单元周围的黑线不是单元的一部分;它是为了显示图案单元的边界大于代码绘制的矩形而绘制的。

您稍后将图案大小指定给 Quartz。

您的图案单元格绘制函数是遵循以下形式的回调:

c 复制代码
typedef void (*CGPatternDrawPatternCallback) (
                void *info,
                CGContextRef context
    );

您可以随意命名回调。

例 6-1中的回调名为MyDrawColoredPattern

回调采用两个参数:

  • info,指向与模式关联的私有数据的通用指针。
    此参数是可选的;您可以传递NULL
    传递给回调的数据与您稍后创建模式时提供的数据相同。
  • context,用于绘制模式单元的图形上下文。

例 6-1中的代码绘制的图案单元是任意的。

您的代码将绘制适合您创建的图案的任何内容。

有关代码的以下细节很重要:

  • 图案大小已声明。
    编写绘图代码时,您需要牢记图案大小。
    此处,大小声明为全局大小。
    绘图函数不会特别引用大小,除非在注释中。
    稍后,您将图案大小指定给 Quartz 2D。
    请参阅 设置彩色图案的结构
  • 绘图函数遵循回调类型定义所定义的原型CGPatternDrawPatternCallback
  • 代码中执行的绘图设置了颜色,这使其成为彩色图案。

例 6-1 绘制彩色图案单元的绘图回调

c 复制代码
#define H_PATTERN_SIZE 16
#define V_PATTERN_SIZE 18
 
void MyDrawColoredPattern (void *info, CGContextRef myContext)
{
    CGFloat subunit = 5; // the pattern cell itself is 16 by 18
 
    CGRect  myRect1 = {{0,0}, {subunit, subunit}},
            myRect2 = {{subunit, subunit}, {subunit, subunit}},
            myRect3 = {{0,subunit}, {subunit, subunit}},
            myRect4 = {{subunit,0}, {subunit, subunit}};
 
    CGContextSetRGBFillColor (myContext, 0, 0, 1, 0.5);
    CGContextFillRect (myContext, myRect1);
    CGContextSetRGBFillColor (myContext, 1, 0, 0, 0.5);
    CGContextFillRect (myContext, myRect2);
    CGContextSetRGBFillColor (myContext, 0, 1, 0, 0.5);
    CGContextFillRect (myContext, myRect3);
    CGContextSetRGBFillColor (myContext, .5, 0, .5, 0.5);
    CGContextFillRect (myContext, myRect4);
}

设置彩色图案色彩空间

例 6-1中的代码使用颜色绘制图案单元。

您必须通过将基本图案颜色空间设置为 来确保 Quartz 使用您在绘图例程中使用的颜色进行绘制,如例 6-2NULL所示。

示例后面是每行代码的详细说明。


例 6-2 创建基本图案颜色空间

c 复制代码
CGColorSpaceRef patternSpace;
 
patternSpace = CGColorSpaceCreatePattern (NULL);// 1
CGContextSetFillColorSpace (myContext, patternSpace);// 2
CGColorSpaceRelease (patternSpace);// 3

代码的作用如下:

  1. 通过调用CGColorSpaceCreatePattern函数创建适合彩色图案的图案颜色空间,并将 NULL作为基础颜色空间传递。
  2. 将填充颜色空间设置为图案颜色空间。
    如果您正在描边图案,请调用CGContextSetStrokeColorSpace
  3. 释放图案色彩空间。

设置彩色图案的结构

关于模式结构的信息保存在 CGPattern 对象中。

通过调用CGPatternCreate函数创建 CGPattern 对象,其原型如例 6-3 所示。


例 6-3 CGPatternCreate 函数原型

c 复制代码
CGPatternRef CGPatternCreate (  void *info,
                                CGRect bounds,
                                CGAffineTransform matrix,
                                CGFloat xStep,
                                CGFloat yStep,
                                CGPatternTiling tiling,
                                bool isColored,
                                const CGPatternCallbacks *callbacks );

info参数是指向要传递给绘图回调的数据的指针。

这与编写绘制彩色图案单元格的回调函数中讨论的指针相同。

您可以在bounds参数中指定图案单元的大小。
matrix参数是您指定图案矩阵的地方,该矩阵将图案坐标系映射到图形上下文的默认坐标系。

如果您想使用与图形上下文相同的坐标系绘制图案,请使用单位矩阵。
xStepyStep参数 指定图案坐标系中单元之间的水平和垂直间距。

请参阅 图案的解剖结构以查看有关边界、图案矩阵和间距的信息。

tiling参数可以是以下三个值之一:

  • kCGPatternTilingNoDistortion
  • kCGPatternTilingConstantSpacingMinimalDistortion
  • kCGPatternTilingConstantSpacing

请参阅 平铺来查看有关平铺的信息。

参数isColored指定图案单元是彩色图案(true)还是模板图案(false)。

如果您true在此处传递,则您的绘图图案回调将指定图案颜色,并且您必须将图案颜色空间设置为彩色图案颜色空间(请参阅 设置彩色图案颜色空间)。

传递给CGPatternCreate函数的最后一个参数是指向CGPatternCallbacks数据结构的指针。

此结构有三个字段:

c 复制代码
struct CGPatternCallbacks
{
    unsigned int version;
    CGPatternDrawPatternCallback drawPattern;
    CGPatternReleaseInfoCallback releaseInfo;
};

您将version字段设置为0
drawPattern字段是指向您的绘图回调的指针。
releaseInfo字段是指向在释放 CGPattern 对象时调用的回调的指针,以释放info您传递给绘图回调的参数的存储空间。

如果您没有在此参数中传递任何数据,则将此字段设置为NULL


将彩色图案指定为填充或描边图案

你可以通过调用适当的函数来使用你的图案进行填充或描边---CGContextSetFillPatternCGContextSetStrokePattern

Quartz 使用你的图案进行任何后续的填充或描边。

这些函数各自采用三个参数:

  • 图形上下文
  • 您先前创建的 CGPattern 对象
  • 颜色分量数组

尽管彩色图案提供自己的颜色,但您必须传递一个 alpha 值来告知 Quartz 图案绘制时的整体不透明度。

Alpha 值可以从 1(完全不透明)到 0(完全透明)。

这些代码行显示了如何设置用于填充的彩色图案的不透明度的示例。

c 复制代码
CGFloat alpha = 1;
 
CGContextSetFillPattern (myContext, myPattern, &alpha);

用彩色图案绘画

完成上述步骤后,您可以调用任何用于绘制的 Quartz 2D 函数。

您的图案将用作"绘制"。

例如,您可以调用CGContextStrokePathCGContextFillPathCGContextFillRect或任何其他用于绘制的函数。


完整的彩色图案绘画功能

例 6-4中的代码包含一个绘制彩色图案的函数。

该函数包含前面讨论的所有步骤。

示例后面是每行代码的详细说明。


例 6-4 绘制彩色图案的函数

c 复制代码
void MyColoredPatternPainting (CGContextRef myContext,
                 CGRect rect)
{
    CGPatternRef    pattern;// 1
    CGColorSpaceRef patternSpace;// 2
    CGFloat         alpha = 1,// 3
                    width, height;// 4
    static const    CGPatternCallbacks callbacks = {0, // 5
                                        &MyDrawPattern,
                                        NULL};
 
    CGContextSaveGState (myContext);
    patternSpace = CGColorSpaceCreatePattern (NULL);// 6
    CGContextSetFillColorSpace (myContext, patternSpace);// 7
    CGColorSpaceRelease (patternSpace);// 8
 
    pattern = CGPatternCreate (NULL, // 9
                    CGRectMake (0, 0, H_PSIZE, V_PSIZE),// 10
                    CGAffineTransformMake (1, 0, 0, 1, 0, 0),// 11
                    H_PATTERN_SIZE, // 12
                    V_PATTERN_SIZE, // 13
                    kCGPatternTilingConstantSpacing,// 14
                    true, // 15
                    &callbacks);// 16
 
    CGContextSetFillPattern (myContext, pattern, &alpha);// 17
    CGPatternRelease (pattern);// 18
    CGContextFillRect (myContext, rect);// 19
    CGContextRestoreGState (myContext);
}

代码的作用如下:

  1. 声明稍后创建的 CGPattern 对象的存储。
  2. 声明稍后创建的图案颜色空间的存储。
  3. 声明一个 alpha 变量并将其设置为1,指定图案的不透明度为完全不透明。
  4. 声明变量来保存窗口的高度和宽度。
    在此示例中,图案被绘制在窗口区域上。
  5. 声明并填充回调结构,0作为版本和指向绘制回调函数的指针传递。
    此示例不提供发布信息回调,因此该字段设置为NULL
  6. 创建一个图案颜色空间对象,将图案的基本颜色空间设置为NULL
    当您绘制彩色图案时,图案会在绘图回调中提供自己的颜色,这就是您将颜色空间设置为NULL的原因。
  7. 将填充颜色空间设置为刚刚创建的图案颜色空间对象。
  8. 释放图案颜色空间对象。
  9. 通过,NULL因为模式不需要向绘图回调传递任何附加信息。
  10. 传递一个指定模式单元边界的 CGRect 对象。
  11. 传递一个 CGAffineTransform 矩阵,该矩阵指定如何将模式空间转换为使用该模式的上下文的默认用户空间。
    此示例传递了单位矩阵。
  12. 将水平图案大小作为每个单元格起点之间的水平位移传递。
    在此示例中,一个单元格与下一个单元格相邻绘制。
  13. 将垂直图案尺寸作为每个单元格起点之间的垂直位移传递。
  14. 传递常量kCGPatternTilingConstantSpacing以指定 Quartz 应如何呈现图案。
    有关更多信息,请参阅 Tiling
  15. 传递true 给参数isColored,指定该图案是彩色图案。
  16. 将一个指针传递给包含版本信息的回调结构,以及一个指向绘图回调函数的指针。
  17. 设置填充模式,传递上下文、刚刚创建的 CGPattern 对象以及指向指定 Quartz 应用于模式的不透明度的 alpha 值的指针。
  18. 释放 CGPattern 对象。
  19. 填充一个矩形,该矩形的大小与传递给MyColoredPatternPainting例程 的窗口的大小相同。
    Quartz 使用您刚刚设置的模式填充矩形。

6、绘制模板图案

以下部分介绍了绘制模板图案所需执行的五个步骤:

  1. 编写一个绘制模板图案单元格的回调函数
  2. 设置模板图案颜色空间
  3. 设置模板图案的结构
  4. 将模板图案指定为填充或描边图案
  5. 使用模板图案进行绘图

这些步骤实际上与您绘制彩色图案的步骤相同。

两者之间的区别在于您如何设置颜色信息。

您可以在完整的模板图案绘画功能中看到所有步骤是如何组合在一起的。


编写一个绘制模板图案单元格的回调函数

您为绘制模板图案而编写的回调遵循与为彩色图案单元描述的相同形式。

请参阅 编写绘制彩色图案单元的回调函数

唯一的区别是您的绘制回调不指定任何颜色。

图 6-10中显示的图案单元未从绘制回调中获取其颜色。

颜色设置在图案颜色空间中的绘制颜色之外。


图 6-10 模板图案单元


看一下例 6-5中的代码,它绘制了图 6-10所示的图案单元。

请注意,代码只是创建了一条路径并填充了该路径。

代码没有设置颜色。


例 6-5 绘制模板图案单元的绘图回调

c 复制代码
#define PSIZE 16    // size of the pattern cell
 
static void MyDrawStencilStar (void *info, CGContextRef myContext)
{
    int k;
    double r, theta;
 
    r = 0.8 * PSIZE / 2;
    theta = 2 * M_PI * (2.0 / 5.0); // 144 degrees
 
    CGContextTranslateCTM (myContext, PSIZE/2, PSIZE/2);
 
    CGContextMoveToPoint(myContext, 0, r);
    for (k = 1; k < 5; k++) {
        CGContextAddLineToPoint (myContext,
                    r * sin(k * theta),
                    r * cos(k * theta));
    }
    CGContextClosePath(myContext);
    CGContextFillPath(myContext);
}

设置模板图案颜色空间

模板图案要求您设置一个图案颜色空间,以便 Quartz 进行绘制,如例 6-6所示。

示例后面是每行代码的详细说明。


例 6-6 为模板图案创建图案颜色空间的代码

c 复制代码
CGPatternRef pattern;
CGColorSpaceRef baseSpace;
CGColorSpaceRef patternSpace;
 
baseSpace = CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB);// 1
patternSpace = CGColorSpaceCreatePattern (baseSpace);// 2
CGContextSetFillColorSpace (myContext, patternSpace);// 3
CGColorSpaceRelease(patternSpace);// 4
CGColorSpaceRelease(baseSpace);// 5

代码的作用如下:

  1. 此函数创建通用 RGB 空间。
    通用颜色空间将颜色匹配留给系统。
    有关更多信息,请参阅 创建通用颜色空间
  2. 创建图案颜色空间。
    您提供的颜色空间指定图案的颜色表现方式。
    稍后,当您为图案设置颜色时,必须使用图案颜色空间进行设置。
    对于此示例,您需要使用 RGB 值指定颜色。
  3. 设置填充图案时使用的颜色空间。
    您可以通过调用CGContextSetStrokeColorSpace函数来设置描边颜色空间。
  4. 释放图案颜色空间对象。
  5. 释放基础色彩空间对象。

设置模板图案的结构

您可以通过调用CGPatternCreate函数 来指定有关图案结构的信息,就像指定彩色图案结构一样。

唯一的区别是您传递了falseisColored 参数。

有关您提供给CGPatternCreate函数的参数的更多信息,请参阅 设置彩色图案的结构


将模板图案指定为填充或描边图案

你可以通过调用适当的函数,CGContextSetFillPattern或者CGContextSetStrokePattern,来使用你的图案进行填充或描边。

Quartz 使用你的图案进行任何后续的填充或描边。

这些函数各自采用三个参数:

  • 图形上下文
  • 您先前创建的 CGPattern 对象
  • 颜色分量数组

模板图案在绘图回调中不提供颜色,因此您必须将颜色传递给填充或描边函数,以告知 Quartz 使用哪种颜色。

例 6-7显示了如何为模板图案设置颜色的示例。

颜色数组中的值由 Quartz 在您之前设置的颜色空间中解释。

由于此示例使用设备 RGB,因此颜色数组包含红色、绿色和蓝色分量的值。

第四个值指定颜色的不透明度。


例 6-7 设置彩色图案不透明度的代码

c 复制代码
static const CGFloat color[4] = { 0, 1, 1, 0.5 }; //cyan, 50% transparent
 
CGContextSetFillPattern (myContext, myPattern, color);

使用模板图案进行绘图

完成上述步骤后,您可以调用任何用于绘制的 Quartz 2D 函数。

您的图案将用作"绘制"。

例如,您可以调用CGContextStrokePathCGContextFillPathCGContextFillRect或任何其他用于绘制的函数。


完整的模板图案绘制功能

例 6-8中的代码包含一个绘制模板图案的函数。

该函数包含前面讨论的所有步骤。

示例后面是每行代码的详细说明。


例 6-8 绘制模板图案的函数

c 复制代码
#define PSIZE 16
 
void MyStencilPatternPainting (CGContextRef myContext,
                                const Rect *windowRect)
{
    CGPatternRef pattern;
    CGColorSpaceRef baseSpace;
    CGColorSpaceRef patternSpace;
    static const CGFloat color[4] = { 0, 1, 0, 1 };// 1
    static const CGPatternCallbacks callbacks = {0, &drawStar, NULL};// 2
 
    baseSpace = CGColorSpaceCreateDeviceRGB ();// 3
    patternSpace = CGColorSpaceCreatePattern (baseSpace);// 4
    CGContextSetFillColorSpace (myContext, patternSpace);// 5
    CGColorSpaceRelease (patternSpace);
    CGColorSpaceRelease (baseSpace);
    pattern = CGPatternCreate(NULL, CGRectMake(0, 0, PSIZE, PSIZE),// 6
                  CGAffineTransformIdentity, PSIZE, PSIZE,
                  kCGPatternTilingConstantSpacing,
                  false, &callbacks);
    CGContextSetFillPattern (myContext, pattern, color);// 7
    CGPatternRelease (pattern);// 8
    CGContextFillRect (myContext,CGRectMake (0,0,PSIZE*20,PSIZE*20));// 9
}

代码的作用如下:

  1. 声明一个数组来保存颜色值并将该值(在 RGB 颜色空间中)设置为不透明绿色。
  2. 声明并填充回调结构,0作为版本和指向绘制回调函数的指针传递。
    此示例不提供发布信息回调,因此该字段设置为NULL
  3. 创建 RGB 设备颜色空间。
    如果图案绘制到显示器上,则需要提供这种颜色空间。
  4. 从 RGB 设备颜色空间创建一个图案颜色空间对象。
  5. 将填充颜色空间设置为刚刚创建的图案颜色空间对象。
  6. 创建图案对象。
    请注意,倒数第二个参数(isColored参数)是false
    模板图案不提供颜色,因此您必须传递false此参数。
    所有其他参数与为彩色图案示例传递的参数类似。
    请参阅 完整的彩色图案绘画函数
  7. 设置填充图案,传递先前声明的颜色数组。
  8. 释放 CGPattern 对象。
  9. 填充一个矩形。
    Quartz 使用你刚刚设置的图案填充矩形。

八、阴影

阴影是在图形对象下方绘制的图像,并与图形对象偏移,以便模拟光源投射在图形对象上的效果,如图7-1所示。

文本也可以被阴影化。

阴影可以使图像呈现三维效果或看起来像是漂浮的。


图 7-1 阴影


阴影有三个特点:

  • x 偏移量,指定阴影在水平方向上与图像的偏移量。
  • y 偏移,指定阴影在垂直方向上与图像的偏移距离。
  • 模糊值,指定图像是否具有硬边缘(如图7-2左侧所示)或漫反射边缘(如图 7-2 右侧所示)。

本章介绍阴影的工作原理并展示如何使用 Quartz 2D API 来创建它们。


图 7-2 没有模糊的阴影和有柔和边缘的阴影


1、阴影如何工作

Quartz 中的阴影是图形状态的一部分。

您可以调用CGContextSetShadow函数,传递图形上下文、偏移值和模糊值。

设置阴影后,您绘制的任何对象都会有一个用黑色绘制的阴影,该黑色在设备 RGB 颜色空间中具有 1/3 的 alpha 值。

换句话说,阴影是使用设置为的 RGBA 值绘制的{0, 0, 0, 1.0/3.0}

您可以通过调用CGContextSetShadowWithColor函数、传递图形上下文、偏移值、模糊值和 CGColor 对象来绘制彩色阴影。

为颜色提供的值取决于您要在其中绘制的颜色空间。

如果在调用CGContextSetShadowCGContextSetShadowWithColor之前保存图形状态,则可以通过恢复图形状态来关闭阴影。

您还可以通过将阴影颜色设置为 NULL 来禁用阴影。


2、阴影绘制惯例根据上下文而变化

前面描述的偏移指定了阴影相对于投射阴影的图像的位置。

这些偏移由上下文解释并用于计算阴影的位置:

  • 正的 x 偏移表示阴影位于图形对象的右侧。
  • 在 Mac OS X 中,正 y 偏移表示向上位移。
    这与 Quartz 2D 的默认坐标系一致。
  • 在 iOS 中,如果您的应用程序使用 Quartz 2D API 创建 PDF 或位图上下文,则正 y 偏移表示向上位移。
  • 在 iOS 中,如果图形上下文是由 UIKit 创建的,比如通过UIView对象创建的图形上下文或者通过调用UIGraphicsBeginImageContextWithOptions函数创建的上下文,则正的 y 偏移表示向下的位移。
    这符合 UIKit 坐标系的绘制约定。
  • 阴影绘制约定不受当前变换矩阵的影响。

3、用阴影绘画

请按照以下步骤使用阴影绘画:

  1. 保存图形状态。
  2. 调用CGContextSetShadow函数,传递适当的值。
  3. 完成所有想要应用阴影的绘图。
  4. 恢复图形状态。

请按照以下步骤使用彩色阴影进行绘画:

  1. 保存图形状态。
  2. 创建一个 CGColorSpace 对象以确保 Quartz 正确解释阴影颜色值。
  3. 创建一个 CGColor 对象,指定要使用的阴影颜色。
  4. 调用CGContextSetShadowWithColor函数,传递适当的值。
  5. 完成所有想要应用阴影的绘图。
  6. 恢复图形状态。

图 7-3中的两个矩形都带有阴影,其中一个带有彩色阴影。


图 7-3 彩色阴影和灰色阴影


例 7-1中的函数展示了如何设置阴影来绘制图 7-3所示的矩形。

示例后面列出了每行代码的详细说明。


例 7-1 设置阴影的函数

c 复制代码
void MyDrawWithShadows (CGContextRef myContext, // 1
                         CGFloat wd, CGFloat ht);
{
    CGSize          myShadowOffset = CGSizeMake (-15,  20);// 2
    CGFloat           myColorValues[] = {1, 0, 0, .6};// 3
    CGColorRef      myColor;// 4
    CGColorSpaceRef myColorSpace;// 5
 
    CGContextSaveGState(myContext);// 6
 
    CGContextSetShadow (myContext, myShadowOffset, 5); // 7
    // Your drawing code here// 8
    CGContextSetRGBFillColor (myContext, 0, 1, 0, 1);
    CGContextFillRect (myContext, CGRectMake (wd/3 + 75, ht/2 , wd/4, ht/4));
 
    myColorSpace = CGColorSpaceCreateDeviceRGB ();// 9
    myColor = CGColorCreate (myColorSpace, myColorValues);// 10
    CGContextSetShadowWithColor (myContext, myShadowOffset, 5, myColor);// 11
    // Your drawing code here// 12
    CGContextSetRGBFillColor (myContext, 0, 0, 1, 1);
    CGContextFillRect (myContext, CGRectMake (wd/3-75,ht/2-100,wd/4,ht/4));
 
    CGColorRelease (myColor);// 13
    CGColorSpaceRelease (myColorSpace); // 14
 
    CGContextRestoreGState(myContext);// 15
}

代码的作用如下:

  1. 采用三个参数------图形上下文以及构建矩形时使用的宽度和高度。
  2. 声明并创建一个包含阴影偏移值的 CGSize 对象。
    这些值指定阴影偏移量为对象左侧 15 个单位和对象上方 20 个单位。
  3. 声明颜色值数组。
    此示例使用 RGBA,但这些值只有在与颜色空间一起传递给 Quartz 时才有意义,而颜色空间是 Quartz 正确解释这些值所必需的。
  4. 声明颜色参考的存储。
  5. 声明颜色空间参考的存储。
  6. 保存当前图形状态,以便您稍后恢复。
  7. 将阴影设置为具有先前声明的偏移值和模糊值 5,这表示阴影边缘较软。
    阴影将显示为灰色,RGBA 值为 {0, 0, 0, 1/3}。
  8. 接下来的两行代码绘制了图 7-3右侧的矩形。
    你可以用自己的绘图代码替换这两行。
  9. 创建设备 RGB 颜色空间。
    创建 CGColor 对象时需要提供颜色空间。
  10. 创建一个 CGColor 对象,提供设备 RGB 颜色空间和先前声明的 RGBA 值。
    此对象指定阴影颜色,在本例中为红色,alpha 值为 0.6。
  11. 设置彩色阴影,提供刚刚创建的红色。
    阴影使用之前创建的偏移量和模糊值 5,这表示软阴影边缘。
  12. 接下来的两行代码绘制了图 7-3左侧的矩形。
    你可以用自己的绘图代码替换这两行。
  13. 释放颜色对象,因为不再需要它。
  14. 释放颜色空间对象,因为不再需要它。
  15. 将图形状态恢复到设置阴影之前的状态。

九、渐变

Quartz 提供两种不透明数据类型用于创建渐变 ------ CGShadingRefCGGradientRef

您可以使用其中任何一种来创建轴向或径向渐变。
渐变 是一种从一种颜色变为另一种颜色的填充。

轴向 渐变 (也称为线性渐变 )沿两个定义的端点之间的轴变化。

垂直于轴的线上的所有点都具有相同的颜色值。

径向渐变 是一种沿轴在两个定义的端点(通常都是圆)之间径向变化的填充。

如果点位于圆心落在轴上的圆的圆周上,则这些点共享相同的颜色值。

渐变圆形部分的半径由端点圆的半径定义;每个中间圆的半径从一端到另一端呈线性变化。

本章提供了使用 Quartz 可以创建的线性和径向渐变类型的示例,比较了绘制渐变的两种方法,然后展示了如何使用每种不透明数据类型来创建渐变。


1、轴向和径向渐变示例

Quartz 函数提供了丰富的词汇来创建渐变效果。

本节展示了您可以实现的一些结果。

图 8-1中的轴向渐变在一个端点(橙色阴影)和另一个端点(黄色阴影)之间变化。

在这种情况下,轴相对于原点成 45 度角。


图 8-1 沿 45 度轴的轴向渐变


Quartz 还允许您指定沿轴的颜色和位置,以创建更复杂的轴向渐变,如图8-2所示。

起点处的颜色是红色,终点处的颜色是紫色。

但是,轴上还有五个位置,其颜色分别设置为橙色、黄色、绿色、蓝色和靛蓝色。

您可以将结果视为沿同一轴的六个连续线性渐变。

虽然此处使用的轴与图 8-1中使用的轴相同(45 度角),但不必如此。

轴的角度由您提供的起点和终点定义。


图 8-2 用七种位置和颜色创建的轴向渐变


图 8-3显示了在小的鲜红色圆圈和较大的黑色圆圈之间变化的径向渐变。


图 8-3 两个圆之间变化的径向渐变


使用 Quartz,您不仅限于根据颜色变化创建渐变;您可以只改变 alpha,也可以改变 alpha 和其他颜色分量。

图 8-4显示了渐变,其红色、绿色和蓝色分量在 alpha 值从 1.0 变化到 0.1 时保持不变。

注意: 如果您使用 alpha 改变渐变,则在绘制 PDF 内容时将无法捕获该渐变。

因此,无法打印此类渐变。

如果您需要绘制渐变到 PDF,请使用 alpha 1.0。


图 8-4 仅通过改变 alpha 分量创建的径向渐变


您可以将圆圈置于径向渐变中以创建各种形状。

如果一个圆圈部分或全部位于另一个圆圈之外,Quartz 会为周长不等的圆圈创建圆锥面,为周长相等的圆圈创建圆柱面。

径向渐变的常见用途是创建阴影球体,如图8-5所示。

在这种情况下,单个点(半径为 的圆0)位于较大的圆内。


图 8-5 在点和圆之间变化的径向渐变


您可以通过嵌套几个类似于图 8-6所示形状的径向渐变来创建更复杂的效果。

形状的环形部分是使用同心圆创建的。


图片 8-6 嵌套径向渐变


2、CGShading 和 CGGradient 对象的比较

有两种类型的对象可用于创建渐变,您可能想知道哪种类型最适合使用。

本节将帮助您回答这个问题。

不透明数据类型CGShadingRef 使您可以控制渐变中每个点的颜色计算方式。

在创建 CGShading 对象之前,您必须创建一个 CGFunction 对象 ( CGFunctionRef),该对象定义用于计算渐变中颜色的函数。

编写自定义函数使您可以自由地创建平滑渐变,如图8-1图 8-3图 8-5所示,或更多非常规效果,如图8-12所示。

创建 CGShading 对象时,指定它是轴向(线性)还是径向。

除了渐变计算函数(封装为 CGFunction 对象)外,您还需提供颜色空间以及起点和终点或半径,具体取决于您绘制的是轴向渐变还是径向渐变。

在绘制时,您只需将 CGShading 对象连同绘制上下文一起传递给CGContextDrawShading 函数 。

Quartz 会为渐变中的每个点调用您的渐变计算函数。

CGGradient 对象是 CGShading 对象的子集,设计时充分考虑了易用性。

不透明数据类型 CGGradientRef 使用起来很简单,因为 Quartz 会为您计算渐变中每个点的颜色------您无需提供渐变计算函数。

创建渐变对象时,您需要提供一个位置和颜色数组。

Quartz 为每组连续位置计算渐变,并使用您为每个位置指定的颜色作为渐变的终点。

您可以将渐变对象设置为使用单个起始和结束位置,如图8-1所示,也可以提供多个点来创建类似于图 8-2所示的效果。

与使用仅限于两个位置的 CGShading 对象相比,能够提供两个以上位置是一个优势。

创建 CGGradient 对象时,只需设置颜色空间、位置以及每个位置的颜色。

使用渐变对象绘制到上下文时,可以指定 Quartz 是应该绘制轴向渐变还是径向渐变。

在绘制时,可以根据绘制轴向渐变还是径向渐变指定起点和终点或半径,这与 CGShading 对象不同,CGShading 对象的几何形状是在创建时定义的,而不是在绘制时定义的。

表 8-1总结了两种不透明数据类型之间的区别。

CGGradient CGShading
可以使用同一个对象绘制轴向和径向渐变。 需要为轴向和径向渐变创建单独的对象。
在绘图时设置渐变的几何形状。 在创建对象时设置渐变的几何形状。
Quartz 计算渐变中每个点的颜色。 您必须提供一个回调函数来计算渐变中每个点的颜色。
轻松定义两个以上的位置和颜色。 需要设计您的回调以使用两个以上的位置和颜色,因此您需要做更多的工作。

3、将颜色延伸到渐变末端之外

创建渐变时,您可以选择用纯色填充渐变末端以外的空间。

Quartz 使用渐变边界上定义的颜色作为填充颜色。

您可以扩展渐变的起点、终点或两者。

您可以将该选项应用于使用 CGShading 对象或 CGGradient 对象创建的轴向或径向渐变。

每种类型的对象都提供了可用于设置扩展选项的常量,如您将在使用 CGGradient 对象使用 CGShading 对象中看到的那样。

图 8-7显示了在起始和终止位置延伸的轴向渐变。

图中的线表示渐变的轴。

如您所见,填充颜色与起始点和终止点的颜色相对应。


图片 8-7 延伸轴向渐变


图 8-8比较了不使用扩展选项的径向渐变和在起始和终止位置都使用扩展选项的径向渐变。

Quartz 获取起始和终止颜色值,并使用这些纯色来扩展表面,如图所示。

该图显示了起始和终止圆以及渐变的轴。


图 8-8 延伸径向渐变


4、使用 CGGradient 对象

CGGradient 对象是渐变的抽象定义 - 它仅指定颜色和位置,但不指定几何形状。

您可以将同一对象用于轴向和径向几何形状。

作为抽象定义,CGGradient 对象可能比其对应物 CGShading 对象更容易重用。

不将几何形状锁定在 CGGradient 对象中,可以基于相同的配色方案迭代绘制渐变,而无需在多个 CGGradient 对象中占用内存资源。

因为 Quartz 会为你计算渐变,所以使用 CGGradient 对象创建和绘制渐变相当简单,只需以下步骤:

  1. 创建一个 CGGradient 对象,提供一个颜色空间、一个包含两个或多个颜色成分的数组、一个包含两个或多个位置的数组以及两个数组中每个数组中的项目数。
  2. 通过调用CGContextDrawLinearGradientCGContextDrawRadialGradient并提供上下文、CGGradient 对象、绘图选项以及起始和结束几何图形(轴向渐变的点或圆心和径向渐变的半径)来绘制渐变。
  3. 当不再需要 CGGradient 对象时,释放它。

位置是0.0 到 1.0 范围内的一个CGFloat值(含 0.0 和 1.0),用于指定沿渐变轴的标准化距离。

0.0 值指定轴的起点,而 1.0 指定轴的终点。

其他值指定距离的比例,例如 0.25 表示距离起点的四分之一,0.5 表示轴的中点。

Quartz 至少使用两个位置。

如果您传递NULL 给位置数组,Quartz 将使用 0 作为第一个位置,使用 1 作为第二个位置。

每种颜色的颜色分量数量取决于颜色空间。

对于屏幕绘图,您将使用 RGB 颜色空间。

由于 Quartz 使用 alpha 进行绘图,因此每种屏幕颜色都有四个分量 - 红色、绿色、蓝色和 alpha。

因此,对于屏幕绘图,您提供的颜色分量数组中的元素数量必须包含位置数量的四倍。

Quartz RGBA 颜色分量的值可以从 0.0 到 1.0(含)不等。

例 8-1是创建 CGGradient 对象的代码片段。

声明必要的变量后,代码设置位置和所需颜色分量的数量(在本例中为 2 X 4 = 8)。

它创建一个通用 RGB 颜色空间。

(在 iOS 中,通用 RGB 颜色空间不可用,您的代码应 改为调用CGColorSpaceCreateDeviceRGB。)然后,它将必要的参数传递给函数CGGradientCreateWithColorComponents

您还可以使用CGGradientCreateWithColors函数,如果您的应用程序设置了 CGColor 对象,这将非常方便。


例 8-1 创建 CGGradient 对象

c 复制代码
CGGradientRef myGradient;
CGColorSpaceRef myColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 0.5, 0.4, 1.0,  // Start color
                          0.8, 0.8, 0.3, 1.0 }; // End color
 
myColorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
myGradient = CGGradientCreateWithColorComponents (myColorspace, components,
                          locations, num_locations);

创建 CGGradient 对象后,可以使用它来绘制轴向或线性渐变。

例 8-2是一个代码片段,它声明并设置线性渐变的起点和终点,然后绘制渐变。
图 8-1显示了结果。

代码没有显示如何获取 CGContext 对象(myContext)。


例 8-2 使用 CGGradient 对象绘制轴向渐变

c 复制代码
CGPoint myStartPoint, myEndPoint;
myStartPoint.x = 0.0;
myStartPoint.y = 0.0;
myEndPoint.x = 1.0;
myEndPoint.y = 1.0;
CGContextDrawLinearGradient (myContext, myGradient, myStartPoint, myEndPoint, 0);

例 8-3是使用例 8-1中创建的 CGGradient 对象绘制如图 8-9所示的径向渐变的代码片段。

此示例说明了通过使用纯色填充来扩展渐变区域的结果。


例 8-3 使用 CGGradient 对象绘制径向渐变

c 复制代码
CGPoint myStartPoint, myEndPoint;
CGFloat myStartRadius, myEndRadius;
myStartPoint.x = 0.15;
myStartPoint.y = 0.15;
myEndPoint.x = 0.5;
myEndPoint.y = 0.5;
myStartRadius = 0.1;
myEndRadius = 0.25;
CGContextDrawRadialGradient (myContext, myGradient, myStartPoint,
                         myStartRadius, myEndPoint, myEndRadius,
                         kCGGradientDrawsAfterEndLocation);

图 8-9 使用 CGGradient 对象绘制的径向渐变


图 8-4所示的径向渐变是使用例 8-4所示的变量创建的。


例 8-4 通过改变 alpha 值来创建径向渐变所使用的变量

c 复制代码
CGPoint myStartPoint, myEndPoint;
CGFloat myStartRadius, myEndRadius;
myStartPoint.x = 0.2;
myStartPoint.y = 0.5;
myEndPoint.x = 0.65;
myEndPoint.y = 0.5;
myStartRadius = 0.1;
myEndRadius = 0.25;
size_t num_locations = 2;
CGFloat locations[2] = { 0, 1.0 };
CGFloat components[8] = { 0.95, 0.3, 0.4, 1.0,
                          0.95, 0.3, 0.4, 0.1 };

例 8-5显示了用于创建图 8-10所示的灰度渐变的变量,它有三个位置。


例 8-5 用于创建灰色渐变的变量

c 复制代码
size_t num_locations = 3;
CGFloat locations[3] = { 0.0, 0.5, 1.0};
CGFloat components[12] = {  1.0, 1.0, 1.0, 1.0,
                            0.5, 0.5, 0.5, 1.0,
                            1.0, 1.0, 1.0, 1.0 };

图片 8-10 具有三个位置的轴向渐变


5、使用 CGShading 对象

您可以通过创建 CGShading 对象调用 CGShadingCreateAxial 函数或CGShadingCreateRadial来设置渐变,并提供以下参数:

  • CGColorSpace 对象描述 Quartz 在解释回调提供的颜色组件值时使用的颜色空间。
  • 起点和终点。
    对于轴向渐变,这些是轴的起始和终止坐标(在用户空间中)。
    对于径向渐变,这些是起始和终止圆心的坐标。
  • 用于定义渐变区域的圆的起始和结束半径(仅适用于径向渐变)。
  • CGFunction 对象,您可以通过调用本节后面讨论的CGFunctionCreate函数来获取。
    此回调例程必须返回在特定点绘制的颜色。
  • 布尔值,指定是否用纯色填充起点或终点以外的区域。

您提供给 CGShading 创建函数的 CGFunction 对象包含一个回调结构以及 Quartz 实现回调所需的所有信息。

设置 CGShading 对象最棘手的部分可能是创建 CGFunction 对象。

调用CGFunctionCreate函数时,您需要提供以下内容:

  • 指向回调所需的任何数据的指针。
  • 回调的输入值数量。
    Quartz 要求回调接受一个输入值。
  • 浮点值数组。
    Quartz 只为您的回调提供此数组中的一个元素。
    输入值的范围可以从 0(渐变开始处的颜色)到 1(渐变结束处的颜色)。
  • 回调提供的输出值数量。
    对于每个输入值,回调必须为每个颜色组件提供一个值,并提供一个 alpha 值来指定不透明度。
    颜色组件值由 Quartz 在您创建的颜色空间中解释,并提供给 CGShading 创建函数。
    例如,如果您使用的是 RGB 颜色空间,则提供值 4 作为输出值的数量(R、G、B 和 A)。
  • 指定每个颜色分量和 alpha 值的浮点值数组。
  • 回调数据结构包含结构的版本(将此字段设置为0)、用于生成颜色分量值的回调,以及用于释放参数中提供给回调的数据的可选回调info
    如果您将回调命名为myCalculateShadingValues,它将如下所示:
c 复制代码
void myCalculateShadingValues (void *info, const CGFloat *in, CGFloat *out)

创建 CGShading 对象后,您可以根据需要设置额外的裁剪。

然后,调用CGContextDrawShading函数使用渐变绘制上下文的裁剪区域。

调用此函数时,Quartz 会调用您的回调来获取从起点到终点范围内的颜色值。

当您不再需要 CGShading 对象时,可以通过调用CGShadingRelease函数来释放它。

使用 CGShading 对象绘制轴向渐变使用 CGShading 对象绘制径向渐变 提供了有关编写使用 CGShading 对象绘制渐变的代码的分步说明。


使用 CGShading 对象绘制轴向渐变

轴向渐变和径向渐变需要您执行类似的步骤。

此示例展示了如何使用 CGShading 对象绘制轴向渐变,在图形上下文中创建半圆形剪切路径,然后将渐变绘制到剪切的上下文以实现如图8-11所示的输出。


图 8-11 经过裁剪和绘制的轴向渐变


要绘制图中所示的轴向渐变,请按照以下部分中说明的步骤操作:

  1. 设置 CGFunction 对象来计算颜色值
  2. 为轴向渐变创建 CGShading 对象
  3. 剪辑上下文
  4. 使用 CGShading 对象绘制轴向渐变
  5. 释放对象

设置 CGFunction 对象来计算颜色值

您可以按照自己喜欢的任何方式计算颜色值,只要颜色计算函数采用三个参数即可:

  • void *info,这是NULL或指向您传递给 CGShading 创建函数的数据的指针。
  • const CGFloat *in : Quartz 将 in 数组传递给您的回调。
    数组中的值必须在为 CGFunction 对象定义的输入值范围内。
    对于此示例,输入范围为 0 到 1;参见例 8-7
  • CGFloat *out : 回调将out数组传递给 Quartz。
    它包含颜色空间中每个颜色分量的一个元素和一个 alpha 值。
    输出值应在为 CGFunction 对象定义的输出值范围内。
    对于此示例,输出范围为 0 到 1;参见例 8-7

有关这些参数的更多信息,请参阅 CGFunctionEvaluateCallback

例 8-6展示了一个函数,它通过将常量数组中定义的值乘以输入值来计算颜色分量值。

由于输入值的范围是 0 到 1,因此输出值的范围是从黑色(对于 RGB,值为 0、0、0)到(1、0、.5)(紫色色调)。

请注意,最后一个分量始终设置为1,因此颜色始终完全不透明。


例 8-6 计算颜色分量值

c 复制代码
static void myCalculateShadingValues (void *info,
                            const CGFloat *in,
                            CGFloat *out)
{
    CGFloat v;
    size_t k, components;
    static const CGFloat c[] = {1, 0, .5, 0 };
 
    components = (size_t)info;
 
    v = *in;
    for (k = 0; k < components -1; k++)
        *out++ = c[k] * v;
     *out++ = 1;
}

编写回调函数来计算颜色值后,将其打包为 CGFunction 对象的一部分。

它是您在创建 CGShading 对象时提供给 Quartz 的 CGFunction 对象。

例 8-7显示了创建 CGFunction 对象的函数,该对象包含例 8-6中的回调函数。

示例后面列出了每行代码的详细说明。


例 8-7 创建 CGFunction 对象

c 复制代码
static CGFunctionRef myGetFunction (CGColorSpaceRef colorspace)// 1
{
    size_t numComponents;
    static const CGFloat input_value_range [2] = { 0, 1 };
    static const CGFloat output_value_ranges [8] = { 0, 1, 0, 1, 0, 1, 0, 1 };
    static const CGFunctionCallbacks callbacks = { 0,// 2
                                &myCalculateShadingValues,
                                NULL };
 
    numComponents = 1 + CGColorSpaceGetNumberOfComponents (colorspace);// 3
    return CGFunctionCreate ((void *) numComponents, // 4
                                1, // 5
                                input_value_range, // 6
                                numComponents, // 7
                                output_value_ranges, // 8
                                &callbacks);// 9
}

代码的作用如下:

  1. 采用颜色空间作为参数。
  2. 声明一个回调结构并用结构的版本(0)、指向颜色组件计算回调的指针以及NULL可选的释放函数填充它。
  3. 计算颜色空间中颜色成分的数量,并将值增加 1 以考虑 alpha 值。
  4. 传递指向numComponents值的指针。
    myCalculateShadingValues 回调将使用此值来确定要计算的组件数量。
  5. 指定 1 是回调的输入值的数量。
  6. 提供一个数组,指定输入的有效间隔。
    该数组包含 0 和 1。
  7. 传递输出值的数量,即颜色分量的数量加上 alpha 的数量。
  8. 提供一个数组,用于指定每个输出值的有效间隔。
    此数组为每个组件指定间隔 0 和 1。
    由于有四个组件,因此此数组中有八个元素。
  9. 将指针传递给先前声明和填充的回调结构。

为轴向渐变创建 CGShading 对象

要创建 CGShading 对象,请调用CGShadingCreateAxial函数,如例 8-8所示,传递颜色空间、起点和终点、CGFunction 对象以及指定是否填充渐变起点和终点之外的区域的布尔值。


例 8-8 为轴向渐变创建 CGShading 对象

c 复制代码
CGPoint     startPoint,
            endPoint;
CGFunctionRef myFunctionObject;
CGShadingRef myShading;
 
startPoint = CGPointMake(0,0.5);
endPoint = CGPointMake(1,0.5);
colorspace = CGColorSpaceCreateDeviceRGB();
myFunctionObject = myGetFunction (colorspace);
 
myShading = CGShadingCreateAxial (colorspace,
                        startPoint, endPoint,
                        myFunctionObject,
                        false, false);

剪辑上下文

当你绘制渐变时,Quartz 会填充当前上下文。

绘制渐变不同于使用颜色和图案,后者用于描边和填充路径对象。

因此,如果你想让渐变以特定形状出现,你需要相应地剪切上下文。

例 8-9中的代码向当前上下文添加了一个半圆,以便将渐变绘制到该剪切区域中,如图8-11所示。

如果仔细观察,您会注意到代码应该生成一个半圆,而图中显示的是半椭圆。

为什么?当您查看使用 CGShading 对象实现轴向渐变的完整例程中的整个例程时,您会看到上下文也被缩放了。

稍后会详细介绍。

虽然您可能不需要在应用程序中应用缩放或剪辑,但 Quartz 2D 中存在这些和许多其他选项来帮助您实现有趣的效果。


例 8-9 向图形上下文添加半圆剪辑

c 复制代码
CGContextBeginPath (myContext);
CGContextAddArc (myContext, .5, .5, .3, 0,
            my_convert_to_radians (180), 0);
CGContextClosePath (myContext);
CGContextClip (myContext);

使用 CGShading 对象绘制轴向渐变

调用CGContextDrawShading函数使用 CGShading 对象中指定的颜色渐变填充当前上下文:

c 复制代码
CGContextDrawShading(myContext,myShading);

释放对象

CGShadingRelease当你不再需要 CGShading 对象时,可以调用此函数。

你还需要释放 CGColorSpace 对象和 CGFunction 对象,如例 8-10所示。


例 8-10 释放对象

c 复制代码
CGShadingRelease (myShading);
CGColorSpaceRelease (colorspace);
CGFunctionRelease (myFunctionObject);

使用 CGShading 对象实现轴向渐变的完整例程

例 8-11中的代码展示了绘制轴向渐变的完整例程,使用了例 8-7中设置的 CGFunction 对象和例 8-6中显示的回调。

示例后面列出了每行代码的详细说明。


例 8-11 使用 CGShading 对象绘制轴向渐变

c 复制代码
void myPaintAxialShading (CGContextRef myContext,// 1
                            CGRect bounds)
{
    CGPoint     startPoint,
                endPoint;
    CGAffineTransform myTransform;
    CGFloat width = bounds.size.width;
    CGFloat height = bounds.size.height;
 
 
    startPoint = CGPointMake(0,0.5); // 2
    endPoint = CGPointMake(1,0.5);// 3
 
    colorspace = CGColorSpaceCreateDeviceRGB();// 4
    myShadingFunction = myGetFunction(colorspace);// 5
 
    shading = CGShadingCreateAxial (colorspace, // 6
                                 startPoint, endPoint,
                                 myShadingFunction,
                                 false, false);
 
    myTransform = CGAffineTransformMakeScale (width, height);// 7
    CGContextConcatCTM (myContext, myTransform);// 8
    CGContextSaveGState (myContext);// 9
 
    CGContextClipToRect (myContext, CGRectMake(0, 0, 1, 1));// 10
    CGContextSetRGBFillColor (myContext, 1, 1, 1, 1);
    CGContextFillRect (myContext, CGRectMake(0, 0, 1, 1));
 
    CGContextBeginPath (myContext);// 11
    CGContextAddArc (myContext, .5, .5, .3, 0,
                        my_convert_to_radians (180), 0);
    CGContextClosePath (myContext);
    CGContextClip (myContext);
 
    CGContextDrawShading (myContext, shading);// 12
    CGColorSpaceRelease (colorspace);// 13
    CGShadingRelease (shading);
    CGFunctionRelease (myShadingFunction);
 
    CGContextRestoreGState (myContext); // 14
}

代码的作用如下:

  1. 采用图形上下文和要绘制的矩形作为参数。
  2. 为起点分配一个值。
    该例程根据从 0 到 1 的用户空间计算值。
    稍后您将缩放 Quartz 绘制到的窗口的空间。
    您可以将此坐标位置视为最左侧的 x 和底部 50% 处的 y。
  3. 为终点分配一个值。
    您可以将此坐标位置视为最右侧的 x 和底部 50% 处的 y。
    如您所见,渐变的轴是一条水平线。
  4. 为设备 RGB 创建一个颜色空间,因为此例程会绘制到显示器。
  5. 通过调用例 8-7中所示的例程并传递刚刚创建的颜色空间来创建一个 CGFunction 对象。
  6. 为轴向渐变创建一个 CGShading 对象。
    最后两个参数是false,表示 Quartz 不应填充超出起点和终点的区域。
  7. 设置仿射变换,该变换会缩放到用于绘制的窗口的高度和宽度。
    请注意,高度不一定等于宽度。
    在此示例中,由于两者不相等,因此最终结果是椭圆形而不是圆形。
  8. 将您刚刚设置的转换与传递给例程的图形上下文连接起来。
  9. 保存图形状态以便您稍后恢复该状态。
  10. 设置剪切区域。
    此行和接下来的两行将上下文剪切到填充白色的矩形中。
    效果是将渐变绘制到具有白色背景的窗口。
  11. 创建路径。
    此行和接下来的三行设置一个半圆弧并将其作为剪切区域添加到图形上下文中。
    效果是将渐变绘制到半圆区域。
    但是,圆将根据窗口的高度和宽度进行变换(参见步骤 8),最终效果是将渐变绘制到半椭圆。
    当用户调整窗口大小时,剪切区域的大小也会调整。
  12. 将渐变绘制到图形上下文,并按前面描述的方式转换和剪裁渐变。
  13. 释放对象。
    此行和接下来的两行释放您创建的所有对象。
  14. 将图形状态恢复到设置填充背景并剪裁为半圆之前的状态。
    恢复的状态仍会根据窗口的宽度和高度进行变换。

使用 CGShading 对象绘制径向渐变

此示例显示如何使用 CGShading 对象产生如图 8-12所示的输出。


图 8-12 使用 CGShading 对象创建的径向渐变


要绘制径向渐变,请按照以下部分中说明的步骤操作:

  1. 设置一个 CGFunction 对象来计算颜色值
  2. 为径向渐变创建 CGShading 对象
  3. 使用 CGShading 对象绘制径向渐变
  4. 释放对象

设置 CGFunction 对象来计算颜色值

编写函数来计算径向和轴向渐变的颜色值没有区别。

事实上,你可以按照设置 CGFunction 对象来计算颜色值中针对轴向渐变概述的说明进行操作。

例 8-12计算颜色,使得颜色分量呈正弦变化,周期基于函数中声明的频率值。
图 8-12中看到的结果与图 8-11中显示的颜色有很大不同。

尽管颜色输出有所不同,但例 8-12中的代码与例 8-6类似,因为每个函数都遵循相同的原型。

每个函数都接受一个输入值并计算 N 个值,每个值代表颜色空间的每个颜色分量加上一个 alpha 值。


例 8-12 计算颜色分量值

c 复制代码
static void  myCalculateShadingValues (void *info,
                                const CGFloat *in,
                                CGFloat *out)
{
    size_t k, components;
    double frequency[4] = { 55, 220, 110, 0 };
    components = (size_t)info;
    for (k = 0; k < components - 1; k++)
        *out++ = (1 + sin(*in * frequency[k]))/2;
     *out++ = 1; // alpha
}

回想一下,编写颜色计算函数后,您需要创建一个 CGFunction 对象,如设置 CGFunction 对象来计算颜色值中针对轴值所述。


为径向渐变创建 CGShading 对象

要创建 CGShading 对象或径向渐变,请调用函数,如例 8-13CGShadingCreateRadial所示,传递颜色空间、起点和终点、起点和终点半径、CGFunction 对象和布尔值以指定是否填充渐变起点和终点以外的区域。


例 8-13 为径向渐变创建 CGShading 对象

c 复制代码
CGPoint startPoint, endPoint;
    CGFloat startRadius, endRadius;
 
    startPoint = CGPointMake(0.25,0.3);
    startRadius = .1;
    endPoint = CGPointMake(.7,0.7);
    endRadius = .25;
    colorspace = CGColorSpaceCreateDeviceRGB();
    myShadingFunction = myGetFunction (colorspace);
    CGShadingCreateRadial (colorspace,
                    startPoint,
                    startRadius,
                    endPoint,
                    endRadius,
                    myShadingFunction,
                    false,
                    false);

使用 CGShading 对象绘制径向渐变

调用该函数CGContextDrawShading使用 CGShading 对象中指定的指定颜色渐变填充当前上下文。

c 复制代码
CGContextDrawShading(myContext,阴影);

请注意,无论渐变是轴向的还是径向的,都可以使用相同的函数来绘制渐变。


释放对象

CGShadingRelease当你不再需要 CGShading 对象时,可以调用此函数。

你还需要释放 CGColorSpace 对象和 CGFunction 对象,如例 8-14所示。


例 8-14 释放对象的代码

c 复制代码
CGShadingRelease (myShading);
CGColorSpaceRelease (colorspace);
CGFunctionRelease (myFunctionObject);

使用 CGShading 对象绘制径向渐变的完整例程

例 8-15中的代码展示了使用例 8-7中设置的 CGFunction 对象和例 8-12中显示的回调绘制径向渐变的完整例程。

示例后面列出了每行代码的详细说明。


例 8-15 使用 CGShading 对象绘制径向渐变的例程

c 复制代码
void myPaintRadialShading (CGContextRef myContext,// 1
                            CGRect bounds);
{
    CGPoint startPoint,
            endPoint;
    CGFloat startRadius,
            endRadius;
    CGAffineTransform myTransform;
    CGFloat width = bounds.size.width;
    CGFloat height = bounds.size.height;
 
    startPoint = CGPointMake(0.25,0.3); // 2
    startRadius = .1;  // 3
    endPoint = CGPointMake(.7,0.7); // 4
    endRadius = .25; // 5
 
    colorspace = CGColorSpaceCreateDeviceRGB(); // 6
    myShadingFunction = myGetFunction (colorspace); // 7
 
    shading = CGShadingCreateRadial (colorspace, // 8
                            startPoint, startRadius,
                            endPoint, endRadius,
                            myShadingFunction,
                            false, false);
 
    myTransform = CGAffineTransformMakeScale (width, height); // 9
    CGContextConcatCTM (myContext, myTransform); // 10
    CGContextSaveGState (myContext); // 11
 
    CGContextClipToRect (myContext, CGRectMake(0, 0, 1, 1)); // 12
    CGContextSetRGBFillColor (myContext, 1, 1, 1, 1);
    CGContextFillRect (myContext, CGRectMake(0, 0, 1, 1));
 
    CGContextDrawShading (myContext, shading); // 13
    CGColorSpaceRelease (colorspace); // 14
    CGShadingRelease (shading);
    CGFunctionRelease (myShadingFunction);
 
    CGContextRestoreGState (myContext); // 15
}

代码的作用如下:

  1. 采用图形上下文和要绘制的矩形作为参数。
  2. 为起始圆的中心分配一个值。
    该例程根据从 0 到 1 变化的用户空间计算值。
    稍后您将缩放 Quartz 绘制到的窗口的空间。
    您可以将此坐标位置视为 x 距离左侧 25% 和 y 距离底部 30%。
  3. 指定起始圆的半径。
    您可以将其视为用户空间宽度的 10%。
  4. 为结束圆的中心分配一个值。
    您可以将此坐标位置视为 x 距离左侧 70% 的位置和 y 距离底部 70% 的位置。
  5. 指定结束圆的半径。
    您可以将其视为用户空间宽度的 25%。
    结束圆将大于起始圆。
    圆锥形状将从左到右,向上倾斜。
  6. 为设备 RGB 创建一个颜色空间,因为此例程会绘制到显示器。
  7. 通过调用例 8-7中所示的例程并传递刚刚创建的颜色空间来创建 CGFunctionObject 。
    不过,请记住,您将使用例 8-12中所示的颜色计算函数。
  8. 为径向渐变创建一个 CGShading 对象。
    最后两个参数是false,表示 Quartz 不应填充渐变起点和终点以外的区域。
  9. 设置仿射变换,该变换会缩放到用于绘制的窗口的高度和宽度。
    请注意,高度不一定等于宽度。
    事实上,只要用户调整窗口大小,变换就会发生变化。
  10. 将您刚刚设置的转换与传递给例程的图形上下文连接起来。
  11. 保存图形状态以便您稍后恢复该状态。
  12. 设置剪切区域。
    此行和接下来的两行将上下文剪切到填充白色的矩形中。
    效果是将渐变绘制到具有白色背景的窗口。
  13. 将渐变绘制到图形上下文中,按照前面描述的方式转换渐变。
  14. 释放对象。
    此行和接下来的两行释放您创建的所有对象。
  15. 将图形状态恢复到设置填充背景之前的状态。
    恢复的状态仍然根据窗口的宽度和高度进行变换。

6、也可以看看


十、透明层

透明 由两个或多个对象组成,这些对象组合在一起形成一个复合图形。

合成的复合图形将被视为单个对象。

当您想要将效果应用于一组对象时,透明层非常有用,例如,将阴影应用于图 9-1中的三个圆圈。


图 9-1 透明层中的三个圆圈组合


如果对图 9-1中的三个圆圈应用阴影而不先将它们渲染到透明层,则会得到如图 9-2所示的结果。


图 9-2 三个圆圈被画成独立的实体


1、透明层的工作原理

Quartz 透明层与许多流行图形应用程序中的层类似。

层是独立的实体。

Quartz 为每个上下文维护一个透明层堆栈,透明层可以嵌套。

但由于层始终是堆栈的一部分,因此您无法单独操作它们。

您可以通过调用CGContextBeginTransparencyLayer函数来表示透明层的开始,该函数以图形上下文和 CFDictionary 对象作为参数。

字典允许您提供选项来指定有关图层的其他信息,但由于 Quartz 2D API 尚未使用该字典,因此您传递了NULL

在此调用之后,图形状态参数保持不变,但 alpha(设置为1)、阴影(关闭)、混合模式(设置为正常)以及影响最终合成的其他参数除外。

开始透明层后,您可以执行任何想要在该层中显示的绘图。

指定上下文中的绘图操作将作为合成图像绘制到完全透明的背景中。

此背景被视为与上下文不同的目标缓冲区。

绘制完成后,调用CGContextEndTransparencyLayer函数。

Quartz 使用上下文的全局 alpha 值和阴影状态并尊重上下文的剪切区域将结果合成到上下文中。


2、绘画至透明层

绘制透明层需要三个步骤:

  1. 调用CGContextBeginTransparencyLayer函数。
  2. 在透明层中绘制想要合成的项目。
  3. 调用CGContextEndTransparencyLayer函数。

图 9-3中的三个矩形被绘制到透明层上。

Quartz 渲染阴影时,就好像这些矩形是一个整体一样。


图 9-3 绘制到透明层的三个矩形


例 9-1中的函数展示了如何使用透明层生成图 9-3中的矩形。

示例后面是每行代码的详细说明。


例 9-1 绘制透明层

c 复制代码
void MyDrawTransparencyLayer (CGContext myContext, // 1
                                CGFloat wd,
                                CGFloat ht)
{
    CGSize          myShadowOffset = CGSizeMake (10, -20);// 2
 
    CGContextSetShadow (myContext, myShadowOffset, 10);   // 3
    CGContextBeginTransparencyLayer (myContext, NULL);// 4
    // Your drawing code here// 5
    CGContextSetRGBFillColor (myContext, 0, 1, 0, 1);
    CGContextFillRect (myContext, CGRectMake (wd/3+ 50,ht/2 ,wd/4,ht/4));
    CGContextSetRGBFillColor (myContext, 0, 0, 1, 1);
    CGContextFillRect (myContext, CGRectMake (wd/3-50,ht/2-100,wd/4,ht/4));
    CGContextSetRGBFillColor (myContext, 1, 0, 0, 1);
    CGContextFillRect (myContext, CGRectMake (wd/3,ht/2-50,wd/4,ht/4));
    CGContextEndTransparencyLayer (myContext);// 6
}

代码的作用如下:

  1. 采用三个参数------图形上下文以及构建矩形时使用的宽度和高度。
  2. 设置一个包含阴影 x 和 y 偏移值的 CGSize数据结构。
    此阴影在水平方向上偏移 10 个单位,在垂直方向上偏移 -20 个单位。
  3. 设置阴影,指定一个值 10作为模糊值。
    (一个值0指定一个没有模糊的硬边阴影。)
  4. 发出透明层开始的信号。
    从此时起,将在此层上进行绘制。
  5. 接下来的六行代码设置填充颜色并填充图 9-3所示的三个矩形。
    你可以用自己的绘图代码替换这些行。
  6. 发出透明层结束的信号并发出信号,表明 Quartz 应该将结果合成到上下文中。

十一、Quartz 2D 中的数据管理

管理数据是每个图形应用程序都需要执行的任务。

对于 Quartz,数据管理是指向 Quartz 2D 例程提供数据或从其接收数据。

一些 Quartz 2D 例程会将数据移入 Quartz,例如从文件或应用程序的其他部分获取图像或 PDF 数据的例程。

其他例程会接受 Quartz 数据,例如将图像或 PDF 数据写入文件或将数据提供给应用程序的其他部分的例程。

Quartz 提供了多种管理数据的功能。

通过阅读本章,您应该能够确定哪些功能最适合您的应用程序。

注意: 读取和写入图像数据的首选方法是使用图像 I/O 框架,该框架在 iOS 4 和 Mac OS X 10.4 及更高版本中可用。

有关CGImageSourceRefCGImageDestinationRef不透明数据类型的更多信息,请参阅 图像 I/O 编程指南

图像源和目标不仅提供对图像数据的访问,还提供对访问图像元数据的更好支持。

Quartz 可识别三类数据源和目标:

  • URL。
    位置可以指定为 URL 的数据可以充当数据的提供者或接收者。
    您可以使用 Core Foundation 数据类型将 URL 传递给 Quartz 函数CFURLRef
  • CFData。
    Core Foundation 的数据类型CFDataRefCFMutableDataRef 是数据对象,它们使简单分配的缓冲区 具有 Core Foundation 对象的行为。
    CFData 与其 Cocoa Foundation 对应类"免费桥接" NSData;如果您将 Quartz 2D 与 Cocoa 框架一起使用,则可以将NSData对象传递给任何采用 CFData 对象的 Quartz 函数。
  • 原始数据。
    您可以提供指向任意类型数据的指针以及一组负责数据基本内存管理的回调。

数据本身(无论是通过 URL、CFData 对象还是数据缓冲区表示)都可以是图像数据或 PDF 数据。

图像数据可以使用任何类型的文件格式。

Quartz 了解大多数常见的图像文件格式。

一些 Quartz 数据管理函数专门用于图像数据,一些函数仅适用于 PDF 数据,而其他函数则更为通用,可用于 PDF 或图像数据。

URL、CFData 和原始数据源和目标引用 Mac OS X 或 iOS 图形技术范围之外的数据,如图10-1所示。

Mac OS X 或 iOS 中的其他图形技术通常提供自己的例程来与 Quartz 通信。

例如,Mac OS X 应用程序可以将 Quartz 图像发送到 Core Image,并使用它来更改图像以产生复杂的效果。


图 10-1 在 Mac OS X 中将数据移入或移出 Quartz 2D


1、将数据移入 Quartz 2D

表 10-1列出了从数据源获取数据的函数。

除 之外的所有这些函数CGPDFDocumentCreateWithURL要么返回图像源 ( CGImageSourceRef),要么返回数据提供者 ( CGDataProviderRef)。

图像源和数据提供者抽象了数据访问任务,并消除了应用程序通过原始内存缓冲区管理数据的需要。

图像源是将图像数据移入 Quartz 的首选方式。

图像源代表各种各样的图像数据。

图像源可以包含多幅图像、缩略图以及每幅图像和图像文件的属性。

有了 CGImageSourceRef后,您可以完成以下任务:

  • CGImageRef使用函数CGImageSourceCreateImageAtIndexCGImageSourceCreateThumbnailAtIndexCGImageSourceCreateIncremental
    CGImageRef 数据类型代表单个 Quartz 图像。
  • 使用函数CGImageSourceUpdateDataCGImageSourceUpdateDataProvider 向图像源添加内容。
  • 使用函数CGImageSourceGetCountCGImageSourceCopyPropertiesCGImageSourceCopyTypeIdentifiers 从图像源获取 信息。

函数CGPDFDocumentCreateWithURL是一个便捷函数,可以从位于指定 URL 的文件创建 PDF 文档。

数据提供者是一种功能较有限的较旧机制。

它们可用于获取图像或 PDF 数据。

您可以提供数据提供者来:

  • 图像创建函数,例如CGImageCreateCGImageCreateWithPNGDataProviderCGImageCreateWithJPEGDataProvider
  • PDF文档创建功能CGPDFDocumentCreateWithProvider
  • CGImageSourceUpdateDataProvider 使用新数据更新现有图像源。

有关图像的更多信息,请参阅 位图图像和图像蒙版


Table 10-1 Functions that move data into Quartz 2D

功能 使用此功能
CGImageSourceCreateWithDataProvider 从数据提供者创建图像源。
CGImageSourceCreateWithData 从 CFData 对象创建图像源。
CGImageSourceCreateWithURL 从指定图像数据位置的 URL 创建图像源。
CGPDFDocumentCreateWithURL 使用位于指定 URL 的数据创建 PDF 文档。
CGDataProviderCreateSequential 读取流中的图像或 PDF 数据。 提供回调来处理数据。
CGDataProviderCreateDirectAccess 在块中读取图像或 PDF 数据。 您提供回调来处理数据。
CGDataProviderCreateWithData 读取应用程序提供的图像或 PDF 数据缓冲区。 提供回调以释放为数据分配的内存。
CGDataProviderCreateWithURL 只要您能提供一个 URL,来指定对图像或 PDF 数据进行数据访问的目标。
CGDataProviderCreateWithCFData 从 CFData 对象读取图像或 PDF 数据。

2、将数据移出 Quartz 2D

表 10-2列出的函数将数据移出 Quartz 2D。

CGPDFContextCreateWithURL之外的所有这些函数要么返回图像目标(CGImageDestinationRef),要么返回数据消费者(CGDataConsumerRef)。

图像目标和数据消费者抽象了数据写入任务,让 Quartz 为您处理细节。

图像目标是将图像数据移出 Quartz 的首选方式。

与图像源类似,图像目标可以表示各种图像数据,从单个图像到包含多个图像、缩略图以及每个图像或图像文件的属性的目标。

有了CGImageDestinationRef 后,您可以完成以下任务:

  • 使用函数CGImageDestinationAddImageCGImageDestinationAddImageFromSource将图像(CGImageRef)添加到目标。
    CGImageRef数据类型代表单个 Quartz 图像。
  • 使用CGImageDestinationSetProperties函数设置属性。
  • 使用函数CGImageDestinationCopyTypeIdentifiersCGImageDestinationGetTypeID从图像目标获取信息。

函数CGPDFContextCreateWithURL是一个便捷函数,可将 PDF 数据写入 URL 指定的位置。

数据消费者是一种功能较有限的较旧机制。

它们用于写入图像或 PDF 数据。

您可以为以下对象提供数据消费者:

  • PDF 上下文创建函数:CGPDFContextCreate
    此函数返回一个图形上下文,将您的绘图记录为传递给数据消费者对象的 PDF 绘图命令序列。
  • CGImageDestinationCreateWithDataConsumer 从数据消费者创建图像目的地的功能。

注意: 为了在处理原始图像数据时获得最佳性能,请使用 vImage 框架。

您可以使用vImageBuffer_InitWithCGImage 函数从CGImageRef 参考中将图像数据导入 vImage 。

有关详细信息,请参阅 Accelerate 发行说明

有关图像的更多信息,请参阅 位图图像和图像蒙版


Table 10-2 Functions that move data out of Quartz 2D

功能 使用此功能
CGImageDestinationCreateWithDataConsumer 将图像数据写入数据消费者。
CGImageDestinationCreateWithData 将图像数据写入 CFData 对象。
CGImageDestinationCreateWithURL 只要您能提供一个 URL 来指定将图像数据写入何处。
CGPDFContextCreateWithURL 只要您能提供一个 URL 来指定在何处写入 PDF 数据。
CGDataConsumerCreateWithURL 只要您能提供一个 URL 来指定在何处写入图像或 PDF 数据。
CGDataConsumerCreateWithCFData 将图像或 PDF 数据写入 CFData 对象。
CGDataConsumerCreate 使用您提供的回调写入图像或 PDF 数据。

3、在 Mac OS X 中在 Quartz 2D 和 Core Image 之间移动数据

Core Image 框架是 Mac OS X 中提供的 Objective-C API,支持图像处理。

Core Image 允许您访问内置的视频和静态图像滤镜,并提供自定义滤镜和近乎实时的处理支持。

您可以将 Core Image 滤镜应用于 Quartz 2D 图像。

例如,您可以使用 Core Image 校正颜色、扭曲图像的几何形状、模糊或锐化图像以及在图像之间创建过渡。

Core Image 还允许您对图像应用迭代过程 - 将滤镜操作的输出反馈给输入。

要更全面地了解 Core Image 的功能,请参阅 Core Image 编程指南

Core Image 方法对打包为 Core Image 图像或 CIImage 对象的图像进行操作。

Core Image 不能直接对 Quartz 图像(CGImageRef数据类型)进行操作。

在将 Core Image 滤镜应用于图像之前,必须将 Quartz 图像转换为 Core Image 图像。

Quartz 2D API 不提供将 Quartz 图像打包为 Core Image 图像的任何函数,但 Core Image 提供了。

以下 Core Image 方法从 Quartz 图像或 Quartz 层 ( CGLayerRef) 创建 Core Image 图像。

您可以使用它们将 Quartz 2D 数据移动到 Core Image。

  • imageWithCGImage:
  • imageWithCGImage:options:
  • imageWithCGLayer:
  • imageWithCGLayer:options:

以下 Core Image 方法从 Core Image 图像返回 Quartz 图像。

您可以使用它们将处理后的图像移回 Quartz 2D:

  • createCGImage:fromRect:
  • createCGLayerWithSize:info:

有关 Core Image 方法的完整描述,请参阅 Core Image 参考集合


十二、位图图像和图像蒙版

位图图像和图像掩码与 Quartz 中的任何绘图基元类似。

Quartz 中的图像和图像掩码都由CGImageRef数据类型表示。

正如您将在本章后面看到的,有多种函数可用于创建图像。

其中一些需要数据提供者或图像源来提供位图数据。

其他函数通过复制图像或对图像应用操作从现有图像创建图像。

无论您如何在 Quartz 中创建位图图像,您都可以将图像绘制到任何类型的图形上下文中。

请记住,位图图像是具有特定分辨率的位数组。

如果将位图图像绘制到与分辨率无关的图形上下文(如 PDF 图形上下文),则位图将受到创建它的分辨率的限制。

有一种方法可以创建 Quartz 图像蒙版 --- 通过调用CGImageMaskCreate函数。

您将在创建图像蒙版中看到如何创建图像蒙版。

应用图像蒙版并不是蒙版绘制的唯一方法。
使用颜色蒙版图像使用图像蒙版蒙版图像通过剪切上下文蒙版图像部分讨论了 Quartz 中可用的所有蒙版方法。


1、关于位图图像和图像蒙版

位图图像 (或采样图像)是像素(或样本)的数组。

每个像素代表图像中的一个点。

JPEG、TIFF 和 PNG 图形文件是位图图像的示例。

应用程序图标是位图图像。

位图图像仅限于矩形。

但使用 alpha 分量后,它们可以呈现各种形状,并且可以旋转和剪切,如图11-1所示。


图 11-1 位 图图像


位图中的每个样本都包含指定颜色空间中的一个或多个颜色组件,以及一个指定 alpha 值以指示透明度的附加组件。

每个组件可以是 1 到 32 位。

在 Mac OS X 中,Quartz 还提供了对浮点组件的支持。

Mac OS X 和 iOS 中支持的格式在 中进行了描述 "Pixel formats supported for bitmap graphics contexts"

ColorSync 为位图图像提供颜色空间支持。

Quartz 还支持图像蒙版

图像蒙版是一个指定要绘制的区域但不指定颜色的位图。

实际上,图像蒙版充当模板,用于指定在页面上放置颜色的位置。

Quartz 使用当前填充颜色来绘制图像蒙版。

图像蒙版的深度可以是 1 到 8 位。


2、位图图像信息

Quartz 支持多种图像格式,并且内置了几种流行格式的知识。

在 iOS 中,格式包括 JPEG、GIF、PNG、TIF、ICO、GMP、XBM 和 CUR。

其他位图图像格式或专有格式要求您向 Quartz 指定有关图像格式的详细信息,以确保正确解释图像。

您提供给CGImageCreate函数的图像数据 必须按像素而不是按扫描线进行交错。

Quartz 不支持平面数据。

本节介绍与位图图像相关的信息。

当您创建和使用 Quartz 图像(使用CGImageRef数据类型)时,您会发现某些 Quartz 图像创建函数要求您指定所有这些信息,而其他函数则需要这些信息的子集。

您提供的信息取决于位图数据使用的编码,以及位图是代表图像还是图像蒙版。

注意: 为了在处理原始图像数据时获得最佳性能,请使用 vImage 框架。

您可以使用 vImageBuffer_InitWithCGImage函数从CGImageRef参考中将图像数据导入 vImage 。

有关详细信息,请参阅 Accelerate 发行说明

Quartz 在创建位图图像时使用以下信息(CGImageRef):

  • 位图数据源,可以是 Quartz 数据提供程序或 Quartz 图像源。
    Quartz 2D 中的数据管理描述了这两者,并讨论了提供位图数据源的功能。
  • 可选的解码数组(解码数组)。
  • 插值设置,它是一个布尔值,指定 Quartz 是否应该在调整图像大小时应用插值算法。
  • 渲染意图指定如何映射位于图形上下文的目标颜色空间内的颜色。
    图像蒙版不需要此信息。
    有关更多信息,请参阅 设置渲染意图。
  • 图像尺寸。
  • 像素格式,包括每组件的位数、每像素的位数和每行的字节数(像素格式)。
  • 对于图像,颜色空间和位图布局(颜色空间和位图布局)信息用于描述 alpha 的位置以及位图是否使用浮点值。
    图像蒙版不需要此信息。

解码数组

解码数组将图像颜色值映射到其他颜色值,这对于降低图像饱和度或反转颜色等任务非常有用。

该数组包含每个颜色分量的一对数字。

当 Quartz 渲染图像时,它会应用线性变换将原始分量值映射到适合目标颜色空间的指定范围内的相对数字。

例如,RGB 颜色空间中图像的解码数组包含六个条目,每个红色、绿色和蓝色颜色分量一对。


像素格式

像素格式由以下信息组成:

  • 每分量位数,即像素中每个单独颜色分量的位数。
    对于图像蒙版,此值是源像素中有效掩码位数。
    例如,如果源图像是 8 位蒙版,则指定每分量 8 位。
  • 每像素位数,即源像素中的总位数。
    此值必须至少为每组件位数乘以每像素组件数。
  • 每行字节数。图像中水平每行的字节数。

颜色空间和位图布局

为了确保 Quartz 正确解释每个像素的位,您必须指定:

  • 位图是否包含 alpha 通道。
    Quartz 支持 RGB、CMYK 和灰色颜色空间。
    它还支持 alpha 或透明度,尽管并非所有位图图像格式都提供 alpha 信息。
    当它可用时,alpha 分量可以位于像素的最高有效位或最低有效位中。
  • 对于具有 alpha 分量的位图,颜色分量是否已与 alpha 值相乘。
    预乘 alpha 描述其分量已与 alpha 值相乘的源颜色。
    预乘通过消除每个颜色分量的额外乘法运算来加快图像的渲染速度。
    例如,在 RGB 颜色空间中,使用预乘 alpha 渲染图像可消除图像中每个像素的三次乘法运算(红色乘以 alpha、绿色乘以 alpha 和蓝色乘以 alpha)。
  • 样本的数据格式------整数或浮点值。

使用CGImageCreate函数创建图像时,您需要提供一个CGImageBitmapInfo类型为bitmapInfo 的参数来指定位图布局信息。

以下常量指定 alpha 分量的位置以及颜色分量是否预乘:

  • kCGImageAlphaLast--- alpha 分量存储在每个像素的最低有效位中,例如 RGBA。
  • kCGImageAlphaFirst---alpha 分量存储在每个像素的最高有效位中,例如 ARGB。
  • kCGImageAlphaPremultipliedLast---alpha 分量存储在每个像素的最低有效位中,并且颜色分量已与该 alpha 值相乘。
  • kCGImageAlphaPremultipliedFirst---alpha 分量存储在每个像素的最高有效位中,并且颜色分量已与该 alpha 值相乘。
  • kCGImageAlphaNoneSkipLast--- 没有 alpha 分量。
    如果像素的总大小大于颜色空间中颜色分量数量所需的空间,则忽略最低有效位。
  • kCGImageAlphaNoneSkipFirst--- 没有 alpha 分量。
    如果像素的总大小大于颜色空间中颜色分量数量所需的空间,则最高有效位将被忽略。
  • kCGImageAlphaNone-相当于kCGImageAlphaNoneSkipLast

您可以使用常量kCGBitmapFloatComponents来指示使用浮点值的位图格式。

对于浮点格式,您可以OR将此常量与上一个列表中的相应常量逻辑地结合起来。

例如,对于使用预乘 alpha 的 128 位/像素浮点格式,alpha 位于每个像素的最低有效位,您可以向 Quartz 提供以下信息:

c 复制代码
kCGImageAlphaPremultipliedLast|kCGBitmapFloatComponents

图 11-2直观地描述了像素在使用 16 位或 32 位整数格式的 CMYK 和 RGB 颜色空间中是如何表示的。

32 位整数像素格式每个分量使用 8 位。

16 位整数格式每个分量使用 5 位。

Quartz 2D 还支持每个分量使用 32 位的 128 位浮点像素格式。

图中未显示 128 位格式。


图 11-2 Quartz 2D 中 CMYK 和 RGB 颜色空间的 32 位和 16 位像素格式


3、创建图像

表 11-1列出了 Quartz 提供的用于创建 CGImage 对象的函数。

图像创建函数的选择取决于图像数据的来源。

最灵活的函数是
它从任何类型的位图数据创建图像。
但是,它是最复杂的函数,因为您必须指定所有位图信息。
要使用此函数,您需要熟悉位图图像信息
CGImageCreate中讨论的主题。

如果要从使用标准图像格式(例如 PNG 或 JPEG)的图像文件创建 CGImage 对象,最简单的解决方案是调用CGImageSourceCreateWithURL函数创建图像源,然后调用 CGImageSourceCreateImageAtIndex 函数从图像源中特定索引处的图像数据创建图像。

如果原始图像文件仅包含一个图像,则提供0索引。

如果图像文件格式支持包含多个图像的文件,则需要提供相应图像的索引,请记住索引值从 0开始。

如果您已将内容绘制到位图图形上下文并希望将该绘制捕获到 CGImage 对象,请调用CGBitmapContextCreateImage函数。

有几个函数是操作现有图像的实用程序,用于复制、创建缩略图或从较大图像的一部分创建图像。

无论您如何创建 CGImage 对象,都可以使用CGContextDrawImage函数将图像绘制到图形上下文中。

请记住,CGImage 对象是不可变的。

当您不再需要 CGImage 对象时,请通过调用CGImageRelease函数将其释放。


able 11-1 Functions for creating images

Function Description
CGImageCreate A flexible function for creating an image. You must specify all the bitmap information that is discussed in Bitmap Image Information.
CGImageSourceCreateImageAtIndex Creates an image from an image source. Image sources can contain more than one image. See Data Management in Quartz 2D for information on creating an image source.
CGImageSourceCreateThumbnailAtIndex Creates a thumbnail image of an image that is associated with an image source. See Data Management in Quartz 2D for information on creating an image source.
CGBitmapContextCreateImage Creates an image by copying the bits from a bitmap graphics context.
CGImageCreateWithImageInRect Creates an image from the data contained within a sub-rectangle of an image.
CGImageCreateCopy A utility function that creates a copy of an image.
CGImageCreateCopyWithColorSpace A utility function that creates a copy of an image and replaces its color space.

以下部分讨论如何创建:

  • 来自现有图像的子图像
  • 来自位图图形上下文的图像

您可以查阅以下来源以获取更多信息:


从较大图像的一部分创建图像

CGImageCreateWithImageInRect 函数可让您从现有的 Quartz 图像创建子图像。

图 11-3说明了如何通过提供指定字母"A"位置的矩形,从较大的图像中提取包含字母"A"的图像。


图 11-3 从大图像创建的子图像


CGImageCreateWithImageInRect 函数返回的图像 保留了对原始图像的引用,这意味着调用此函数后就可以释放原始图像。

图 11-4显示了另一个提取图像的一部分来创建另一幅图像的示例。

在本例中,公鸡的头部从较大的图像中提取出来,然后绘制到比子图像更大的矩形上,从而有效地放大了图像。

例 11-1显示了创建并绘制子图像的代码。

函数CGContextDrawImage绘制公鸡头部的矩形的尺寸是提取的子图像尺寸的两倍。

示例是一个代码片段。

您需要声明适当的变量,创建公鸡图像,并处理公鸡图像和公鸡头部子图像。

由于代码是一个片段,它没有显示如何创建绘制图像的图形上下文。

您可以使用任何您喜欢的图形上下文。

有关如何创建图形上下文的示例,请参阅 图形上下文


图 11-4 一幅图像及其子图像的放大图


例 11-1 创建子图像并将其放大绘制的代码

c 复制代码
myImageArea = CGRectMake (rooster_head_x_origin, rooster_head_y_origin,
                            myWidth, myHeight);
mySubimage = CGImageCreateWithImageInRect (myRoosterImage, myImageArea);
myRect = CGRectMake(0, 0, myWidth*2, myHeight*2);
CGContextDrawImage(context, myRect, mySubimage);

从位图图形上下文创建图像

要从现有的位图图形上下文创建图像,请按如下方式调用CGBitmapContextCreateImage函数:

c 复制代码
CGImageRef myImage;
myImage = CGBitmapContextCreateImage (myBitmapContext);

函数返回的 CGImage 对象是通过复制操作创建的。

因此,您对位图图形上下文所做的任何后续更改都不会影响返回的 CGImage 对象的内容。

在某些情况下,复制操作实际上遵循写时复制语义,因此只有当位图图形上下文中的底层数据被修改时才会发生位的实际物理复制。

您可能希望使用生成的图像并在对位图图形上下文执行其他绘制之前释放它,这样您就可以避免实际的物理数据复制。

有关如何创建位图图形上下文的示例,请参阅 创建位图图形上下文


4、创建图像蒙版

Quartz 位图图像蒙版的使用方式与艺术家使用丝网印刷的方式相同。

位图图像蒙版决定颜色的传输方式,而不是使用哪种颜色。

图像蒙版中的每个样本值指定当前填充颜色在特定位置被遮盖的量。

样本值指定蒙版的不透明度。

值越大表示不透明度越大,并指定 Quartz 绘制颜色较少的位置。

您可以将样本值视为逆 alpha 值。

1为透明,0值为不透明。

图像掩码每个组件有 1、2、4 或 8 位。

对于 1 位掩码,样本值1指定掩码中阻挡当前填充颜色的部分。

样本值0指定掩码中显示绘制掩码时图形状态的当前填充颜色的部分。

您可以将 1 位掩码视为黑色和白色;样本要么完全阻挡绘制,要么完全允许绘制。

每个分量有 2、4 或 8 位的图像蒙版表示灰度值。

每个分量使用以下公式映射01范围:


例如,4 位掩码的值范围为 到 ,01 增量为1/15

或代表极端值 01 的组件值 ------------ 完全阻止绘制和完全允许绘制。
01之间的值允许使用公式 1 -- MaskSampleValue 进行部分绘制。

例如,如果 8 位掩码的样本值缩放到0.7,则颜色的绘制方式就好像它的 alpha 值(1 -- 0.7) 一样,即0.3

CGImageMaskCreate函数根据您提供的位图图像信息创建 Quartz 图像掩码,该信息在位图图像信息中进行了讨论。

您提供用于创建图像掩码的信息与创建图像提供的信息相同,只是您不提供颜色空间信息、位图信息常量或渲染意图,如例 11-2中的函数原型所示。


例 11-2 CGImageMaskCreate 函数的原型

c 复制代码
CGImageRef CGImageMaskCreate (
        size_t width,
        size_t height,
        size_t bitsPerComponent,
        size_t bitsPerPixel,
        size_t bytesPerRow,
        CGDataProviderRef provider,
        const CGFloat decode[],
        bool shouldInterpolate
);

5、遮罩图像

通过控制绘制图像的哪些部分,蒙版技术可以产生许多有趣的效果。

您可以:

  • 将图像蒙版应用于图像。
    您还可以使用图像作为蒙版来实现与应用图像蒙版相反的效果。
  • 使用颜色来掩盖图像的各个部分,其中包括称为色度键掩盖的技术。
  • 将图形上下文剪辑到图像或图像蒙版,当 Quartz 将内容绘制到剪辑的上下文时,这会有效地掩盖图像(或任何类型的绘图)。

使用图像蒙版遮罩图像

CGImageCreateWithMask函数返回通过将图像蒙版应用于图像而创建的图像。

此函数采用两个参数:

  • 要应用蒙版的图像。
    此图像不能是图像蒙版,也不能具有与之关联的蒙版颜色(请参阅 使用颜色蒙版图像)。
  • 通过调用CGImageMaskCreate函数创建的图像蒙版。
    可以提供图像来代替图像蒙版,但这会产生截然不同的结果。
    请参阅 使用图像蒙版图像

图像蒙版的源样本充当逆 alpha 值。

图像蒙版样本值 ( S):

  • 等于1块绘制相应的图像样本。
  • 等于0允许以完全覆盖的方式绘制相应的图像样本。
  • 大于0和小于1允许使用 alpha 值绘制相应的图像样本(1 -- S).

图 11-5显示了使用 Quartz 图像创建函数之一创建的图像,图 11-6显示了使用CGImageMaskCreate函数创建的图像蒙版。
图 11-7显示了调用CGImageCreateWithMask函数将图像蒙版应用于图像后得到的图像。


图 11-5 原始图像


图 11-6 图像蒙版


请注意,原始图像中与蒙版黑色区域相对应的区域在结果图像中显示出来(图 11-7)。

与蒙版白色区域相对应的区域未被绘制。

与蒙版灰色区域相对应的区域使用中间 alpha 值进行绘制,该值等于1图像蒙版样本值的减去。


图 11-7 将图像蒙版应用到原始图像后得到的图像


用图像掩盖图像

您可以使用CGImageCreateWithMask函数用另一幅图像来遮盖一幅图像,而不是用图像遮罩。

这样做可以实现与用图像遮罩遮盖图像时获得的效果相反的效果。

您无需传递使用CGImageMaskCreate函数 创建的图像遮罩,而是提供由 Quartz 图像创建函数之一创建的图像。

用作蒙版(但不是 Quartz 图像蒙版)的图像的源样本以 alpha 值的形式运行。

图像样本值(S):

  • 等于1允许以完全覆盖的方式绘制相应的图像样本。
  • 等于0块绘制相应的图像样本。
  • 大于0和小于1允许使用 alpha 值绘制相应的图像样本S

图 11-8显示了调用函数将图 11-6CGImageCreateWithMask所示的图像应用到图 11-5所示的图像后得到的图像。

在本例中,假设图 11-6所示的图像是使用 Quartz 图像创建函数之一创建的,例如CGImageCreate

将图 11-8与图 11-7进行比较,可以看到当使用相同的样本值作为图像样本而不是图像掩码样本时,会实现相反的效果。

原始图像中与图像黑色区域相对应的区域在结果图像中不会被绘制(图 11-8)。

与白色区域相对应的区域会被绘制。

与蒙版中的灰色区域相对应的区域使用与蒙版图像样本值相等的中间 alpha 值进行绘制。


图 11-8 使用图像遮罩原始图像后得到的图像


用颜色遮盖图像

CGImageCreateWithMaskingColors 函数通过屏蔽提供给该函数的图像中的一种颜色或一系列颜色来创建图像。

使用此函数,您可以执行类似于图 11-9所示的色度键屏蔽,也可以屏蔽一系列颜色,类似于图 11-11图 11-12图 11-13所示的颜色。

CGImageCreateWithMaskingColors函数接受两个参数:

  • 不是图像蒙版且不是将图像蒙版或蒙版颜色应用到另一幅图像的结果的图像。
  • 颜色分量数组,用于指定函数在图像中要屏蔽的颜色或颜色范围。

图 11-9 色度键遮罩


颜色组件数组中的元素数量必须等于图像颜色空间中颜色组件数量的两倍。

对于颜色空间中的每个颜色组件,提供一个最小值和一个最大值,以指定要屏蔽的颜色范围。

要仅屏蔽一种颜色,请将最小值设置为等于最大值。

颜色组件数组中的值按以下顺序提供:

{min[1], max[1], ... min[N], max[N]},其中N是组件的数量。

如果图像使用整数像素分量,则颜色分量数组中的每个值必须在 [0 .. 2^bitsPerComponent - 1] 范围内。

如果图像使用浮点像素分量,则每个值可以是任何有效的颜色分量浮点数。

如果图像样本的颜色值属于以下范围,则不会绘制该图像样本:

c 复制代码
{c[1], ... c[N]}

min[i] <= c[i] <= max[i] for 1 <= i <= N

未绘制的样本下面的任何内容(例如当前填充颜色或其他图形)都会显示出来。

图 11-10中显示的两只老虎的图像使用每个分量为 8 位的 RGB 颜色空间。

要屏蔽此图像中的一系列颜色,您需要提供0到范围内的最小和最大颜色分量值255


图 11-10 原始图像


例 11-3显示了一个代码片段,它设置了一个颜色分量数组,并将该数组提供给CGImageCreateWithMaskingColors 函数以实现如图 11-11 所示的结果。


例 11-3 将图像中的浅色遮罩至中等棕色

c 复制代码
CGImageRef myColorMaskedImage;
const CGFloat myMaskingColors[6] = {124, 255,  68, 222, 0, 165};
myColorMaskedImage = CGImageCreateWithMaskingColors (image,
                                        myMaskingColors);
CGContextDrawImage (context, myContextRect, myColorMaskedImage);

图 11-11 一张用浅棕色到中棕色遮罩的图像


例 11-4展示了另一个代码片段,它对图 11-10所示的图像进行操作,得到图 11-12所示的结果。

此示例掩盖了较暗的颜色范围。


例 11-4 将棕色阴影遮盖为黑色

c 复制代码
CGImageRef myMaskedImage;
const CGFloat myMaskingColors[6] = { 0, 124, 0, 68, 0, 0 };
myColorMaskedImage = CGImageCreateWithMaskingColors (image,
                                        myMaskingColors);
CGContextDrawImage (context, myContextRect, myColorMaskedImage);

图 11-12 将颜色从深棕色屏蔽为黑色后的图像


您可以屏蔽图像中的颜色,也可以设置填充颜色,以实现图 11-13所示的效果,其中屏蔽区域被填充颜色替换。

例 11-5显示了生成图 11-13所示图像的代码片段。


例 11-5 屏蔽一系列颜色并设置填充颜色

c 复制代码
CGImageRef myMaskedImage;
const CGFloat myMaskingColors[6] = { 0, 124, 0, 68, 0, 0 };
myColorMaskedImage = CGImageCreateWithMaskingColors (image,
                                        myMaskingColors);
CGContextSetRGBFillColor (myContext, 0.6373,0.6373, 0, 1);
CGContextFillRect(context, rect);
CGContextDrawImage(context, rect, myColorMaskedImage);

图 11-13 屏蔽颜色范围并设置填充颜色后绘制的图像


通过剪切上下文来掩盖图像

CGContextClipToMask函数将遮罩映射到矩形中,并将其与图形上下文的当前裁剪区域相交。

您提供以下参数:

  • 您想要剪辑的图形上下文。
  • 要应用蒙版的矩形。
  • 通过调用CGImageMaskCreate函数创建的图像蒙版。
    您可以提供图像而不是图像蒙版,以实现与提供图像蒙版相反的效果。
    图像必须使用 Quartz 图像创建函数创建,但不能是将蒙版或蒙版颜色应用于另一个图像的结果。

最终的裁剪区域取决于您为CGContextClipToMask函数提供的是图像蒙版还是图像。

如果您提供图像蒙版,则会得到类似于使用图像蒙版遮罩图像 中所述的结果,只是图形上下文会被裁剪。

如果您提供图像,则会像使用图像遮罩图像 中所述的一样裁剪图形上下文。

请看图 11-14。

假设它是通过调用CGImageMaskCreate函数创建的图像蒙版,然后将蒙版作为参数提供给CGContextClipToMask函数。

生成的上下文允许绘制黑色区域,不允许绘制白色区域,并允许绘制具有 alpha 值的灰色区域1--S,其中S是图像蒙版的样本值。

如果使用CGContextDrawImage函数将图像绘制到裁剪的上下文中,您将获得类似于图 11-15 所示的结果。


图 11-14 遮罩图像


图 11-15 使用图像蒙版裁剪内容后绘制到上下文中的图像


当将遮罩图像作为图像处理时,会得到相反的结果,如图11-16所示。


图 11-16 使用图片裁剪内容后绘制到上下文中的图片


6、对图像使用混合模式

您可以使用 Quartz 2D 混合模式(请参阅 设置混合模式)来合成两幅图像,或者在已绘制到图形上下文的任何内容上合成一张图片。

本节讨论如何在背景绘制上合成一张图片。

在背景上合成图像的一般过程如下:

  1. 绘制背景。
  2. 通过使用混合模式常量之一调用 CGContextSetBlendMode 函数来设置混合模式。
    (混合模式基于PDF 参考第四版1.5 版(Adobe Systems, Inc.)中定义的模式。)
  3. 通过调用CGContextDrawImage函数在背景上绘制想要合成的图像。

此代码片段使用"变暗"混合模式在背景上合成一张图片:

c 复制代码
CGContextSetBlendMode (myContext, kCGBlendModeDarken);
CGContextDrawImage (myContext, myRect, myImage2);

本节的其余部分使用 Quartz 中可用的每种混合模式,在由图左侧所示的绘制矩形组成的背景上绘制图 11-17右侧所示的图像。

在所有情况下,首先将矩形绘制到图形上下文中。

然后,通过调用CGContextSetBlendMode函数 并传递适当的混合模式常量来设置混合模式。

最后,将跳投的图像绘制到图形上下文中。


图 11-17 背景图(左)和前景图像(右)


正常混合模式

正常混合模式将源图像样本绘制在背景图像样本上。

正常混合模式是 Quartz 中的默认混合模式。

如果您当前正在使用其他混合模式并希望切换到正常混合模式,则只需明确设置正常混合模式。

您可以通过将kCGBlendModeNormal常量传递给CGContextSetBlendMode函数,或通过使用 CGContextRestoreGState 函数恢复图形状态(假设先前的图形状态使用正常混合模式)来设置正常混合模式。

图 11-18显示了使用普通混合模式将图 11-17中的图像绘制在同一图中的矩形上的结果。

在此示例中,使用 alpha 值 1.0 绘制图像,因此背景完全被图像遮挡。


图 11-18 使用普通混合模式在背景上绘制图像


正片叠底混合模式

乘法混合模式将源图像样本与背景图像样本相乘。

结果图像中的颜色至少与两个样本颜色中的任一个一样暗。

通过将kCGBlendModeMultiply常量传递 给CGContextSetBlendMode函数来指定乘法混合模式。

图 11-19显示了使用乘法混合模式将图 11-17所示的图像绘制在同一张图中所示的矩形上的结果。


图 11-19 使用多重混合模式在背景上绘制图像


屏幕混合模式

屏幕混合模式将源图像样本的倒数与背景图像样本的倒数相乘,以获得至少与两个贡献样本颜色一样亮的颜色。

通过将kCGBlendModeScreen常数传递给 CGContextSetBlendMode 函数来指定屏幕混合模式。

图 11-20显示了使用屏幕混合模式将图 11-17所示的图像绘制在同一张图中所示的矩形上的结果。


图 11-20 使用屏幕混合模式在背景上绘制图像


叠加混合模式

叠加混合模式会根据背景样本的颜色,将源图像样本与背景图像样本相乘或筛选。

结果是叠加现有图像样本,同时保留背景的高光和阴影。

背景颜色与源图像混合以反映背景的亮度或暗度。

通过将kCGBlendModeOverlay常量 传递给 CGContextSetBlendMode函数来指定叠加混合模式。

图 11-21显示了使用叠加混合模式将图 11-17所示的图像绘制在同一张图中所示的矩形上的结果。


图 11-21 使用叠加混合模式在背景上绘制图像


变暗混合模式

变暗混合模式通过从源图像或背景中选择较暗的样本来创建合成图像样本。

比背景图像样本更暗的源图像样本将替换相应的背景样本。

通过将 kCGBlendModeDarken 常量传递给 CGContextSetBlendMode 函数来指定变暗混合模式。

图 11-22显示了使用变暗混合模式将图 11-17所示的图像绘制在同一张图中所示的矩形上的结果。


图 11-22 使用变暗混合模式在背景上绘制图像


变亮混合模式

变亮混合模式通过从源图像或背景中选择较亮的样本来创建合成图像样本。

比背景图像样本更亮的源图像样本将替换相应的背景样本。

通过将kCGBlendModeLighten常量传递给CGContextSetBlendMode函数来指定变亮混合模式。

图 11-23显示了使用变亮混合模式将图 11-17所示的图像绘制在同一张图中所示的矩形上的结果。


图 11-23 使用变亮混合模式在背景上绘制图像


颜色减淡混合模式

颜色减淡混合模式使背景图像样本变亮以反映源图像样本。

指定黑色的源图像样本值保持不变。

通过将kCGBlendModeColorDodge常量传递给 CGContextSetBlendMode函数来指定颜色减淡混合模式。

图 11-24显示了使用颜色减淡混合模式将图 11-17所示的图像绘制在同一图中所示的矩形上的结果。


图 11-24 使用颜色减淡混合模式在背景上绘制图像


颜色加深混合模式

颜色加深混合模式使背景图像样本变暗以反映源图像样本。

指定白色的源图像样本值保持不变。

通过将kCGBlendModeColorBurn常量传递 给 CGContextSetBlendMode 函数来指定颜色加深混合模式。

图 11-25显示了使用颜色加深混合模式将图 11-17所示的图像绘制在同一张图中所示的矩形上的结果。


图 11-25 使用颜色加深混合模式在背景上绘制图像


柔光混合模式

柔光混合模式会根据源图像样本颜色使颜色变暗或变亮。

如果源图像样本颜色比 50% 灰色浅,则背景会变亮,类似于减淡。

如果源图像样本颜色比 50% 灰色深,则背景会变暗,类似于加深。

如果源图像样本颜色等于 50% 灰色,则背景不会发生变化。

纯黑色或纯白色的图像样本会产生较暗或较亮的区域,但不会产生纯黑色或纯白色。

整体效果类似于通过在源图像上照射漫射聚光灯所实现的效果。

您可以通过将kCGBlendModeSoftLight常量传递 给 CGContextSetBlendMode函数来指定柔光混合模式。

图 11-26显示了使用柔光混合模式将图 11-17所示的图像绘制在同一张图中所示的矩形上的结果。


图 11-26 使用柔光混合模式在背景上绘制图像


强光混合模式

强光混合模式会根据源图像样本颜色对颜色进行乘法或筛选。

如果源图像样本颜色比 50% 灰色浅,则背景会变亮,类似于筛选。

如果源图像样本颜色比 50% 灰色深,则背景会变暗,类似于乘法。

如果源图像样本颜色等于 50% 灰色,则源图像不会改变。

等于纯黑或纯白的图像样本会变成纯黑或纯白。

整体效果类似于将刺眼的聚光灯照射在源图像上所获得的效果。

通过将kCGBlendModeHardLight常量传递 给CGContextSetBlendMode函数来指定硬光混合模式。

图 11-27显示了使用硬光混合模式将图 11-17所示的图像绘制在同一张图中所示的矩形上的结果。


图 11-27 使用强光混合模式在背景上绘制图像


差异混合模式

差异混合模式会从背景图像样本颜色中减去源图像样本颜色,或从背景图像样本颜色中减去源图像样本颜色,具体取决于哪个样本的亮度值更大。

黑色的源图像样本值不会产生任何变化;白色会反转背景颜色值。

通过将kCGBlendModeDifference常量 传递给CGContextSetBlendMode函数来指定差异混合模式。

图 11-28显示了使用差异混合模式将图 11-17所示的图像绘制在同一张图中所示的矩形上的结果。


图 11-28 使用差异混合模式在背景上绘制图像


排除混合模式

排除混合模式产生低对比度版本的差异混合模式。

黑色的源图像样本值不会产生变化;白色会反转背景颜色值。

通过将kCGBlendModeExclusion常量传递给CGContextSetBlendMode函数来指定排除混合模式。

图 11-29显示了使用排除混合模式将图 11-17所示的图像绘制在同一张图中所示的矩形上的结果。


图 11-29 使用排除混合模式在背景上绘制图像


色相混合模式

色相混合模式将背景的亮度和饱和度值与源图像的色相结合。

通过将kCGBlendModeHue常量 传递给CGContextSetBlendMode函数来指定色相混合模式。

图 11-30显示了使用色相混合模式将图 11-17所示的图像绘制在同一图中的矩形上的结果。


图 11-30 使用色相混合模式在背景上绘制图像


饱和度混合模式

饱和度混合模式使用背景的亮度和色调值以及源图像的饱和度。

纯灰色区域不会产生变化。

通过将kCGBlendModeSaturation常数 传递给CGContextSetBlendMode函数来指定饱和度混合模式。

图 11-31显示了使用饱和度混合模式将图 11-17所示的图像绘制在同一图中所示的矩形上的结果。


图 11-31 使用饱和度混合模式在背景上绘制图像


颜色混合模式

颜色混合模式使用背景的亮度值和源图像的色调和饱和度值。

此模式保留图像中的灰度级。

通过将kCGBlendModeColor常量 传递给CGContextSetBlendMode函数来指定颜色混合模式。

图 11-32显示了使用颜色混合模式将图 11-17所示的图像绘制在同一图中的矩形上的结果。


图 11-32 使用颜色混合模式在背景上绘制图像


亮度混合模式

亮度混合模式使用背景的色调和饱和度以及源图像的亮度来创建与颜色混合模式创建的效果相反的效果。

通过将kCGBlendModeLuminosity常量 传递给CGContextSetBlendMode函数 来指定亮度混合模式。

图 11-33显示了使用亮度混合模式将图 11-17所示的图像绘制在同一张图中所示的矩形上的结果。


图 11-33 使用亮度混合模式在背景上绘制图像


十三核心图形层绘制

CGLayer 对象(CGLayerRef数据类型)允许您的应用程序使用图层进行绘图。

图层适用于以下情况:

  • 对您计划重复使用的绘图进行高质量的屏幕外渲染。
    例如,您可能正在构建一个场景并计划重复使用相同的背景。
    将背景场景绘制到图层上,然后在需要时绘制该图层。
    一个额外的好处是,您不需要知道颜色空间或设备相关信息即可绘制到图层上。
  • 重复绘制。
    例如,您可能想要创建一个由重复绘制的相同项目组成的图案。
    将项目绘制到图层上,然后重复绘制该图层,如图12-1所示。
    如果您将任何重复绘制的 Quartz 对象(包括 CGPath、CGShading 和 CGPDFPage 对象)绘制到 CGLayer 上,性能都会得到提升。
    请注意,图层不仅仅用于屏幕绘制;您还可以将其用于非面向屏幕的图形上下文,例如 PDF 图形上下文。
  • 缓冲。
    虽然您可以使用图层来实现此目的,但您不需要这样做,因为 Quartz Compositor 让您无需进行缓冲。
    如果您必须绘制到缓冲区,请使用图层而不是位图图形上下文。

图 12-1 重复绘制同一只蝴蝶图像


CGLayer 对象和透明层与 CGPath 对象和由 CGContext 函数创建的路径类似。

对于 CGLayer 或 CGPath 对象,您可以绘制到抽象目标,然后稍后将完整的绘画绘制到另一个目标,例如显示器或 PDF。

当您绘制到透明层或使用绘制路径的 CGContext 函数时,您可以直接绘制到图形上下文所表示的目标。

没有用于组装绘画的中间抽象目标。


1、图层绘制的工作原理

图层(由CGLayerRef数据类型表示)是为实现最佳性能而设计的。

如果可能,Quartz 会使用适合其所关联的 Quartz 图形上下文类型的机制来缓存 CGLayer 对象。

例如,与视频卡关联的图形上下文可能会将图层缓存在视频卡上,这使得绘制图层中的内容比渲染由位图图形上下文构建的类似图像要快得多。

因此,与位图图形上下文相比,图层通常是屏幕外绘制的更好选择。

所有 Quartz 绘图函数都绘制到图形上下文。

图形上下文提供了目标的抽象,使您无需考虑目标的细节(例如其分辨率)。

您在用户空间中工作,Quartz 执行必要的转换以将绘图正确呈现到目标。

当您使用 CGLayer 对象进行绘图时,您也会绘制到图形上下文。

图 12-1说明了图层绘制的必要步骤。


图 12-2 图层绘制


所有的图层绘制都从一个图形上下文开始,你可以使用CGLayerCreateWithContext函数从中创建一个 CGLayer 对象。

用于创建 CGLayer 对象的图形上下文通常是窗口图形上下文。

Quartz 创建一个图层,使其具有图形上下文的所有特征------分辨率、颜色空间和图形状态设置。

如果你不想使用图形上下文的大小,你可以为图层提供一个大小。

在图 12-2中,左侧显示了用于创建图层的图形上下文。

右侧框的灰色部分(标记为 CGLayer 对象)表示新创建的图层。

在绘制图层之前,必须通过调用函数获取与图层关联的图形上下文。

此图形上下文与用于创建图层的图形上下文相同。

只要用于创建图层的图形上下文是窗口图形上下文,则 CGLayer 图形上下文就会尽可能缓存到 GPU。

图 12-2CGLayerGetContext右侧框的白色部分表示新创建的图层图形上下文。

绘制到图层的图形上下文就像绘制到任何图形上下文一样,将图层的图形上下文传递给绘制函数。

图 12-2显示了绘制到图层上下文的叶子形状。

当您准备好使用图层的内容时,可以调用函数CGContextDrawLayerInRectCGContextDrawLayerAtPoint 将图层绘制到图形上下文中。

通常,您会绘制到用于创建图层对象的相同图形上下文,但这不是必须的。

您可以将图层绘制到任何图形上下文,但请记住,图层绘制具有用于创建图层对象的图形上下文的特征,这可能会施加某些限制(例如性能或分辨率)。

例如,与屏幕关联的图层可能会缓存在视频硬件中。

如果目标上下文是打印或 PDF 上下文,则可能需要将其从图形硬件提取到内存,从而导致性能不佳。

图 12-2显示了图层的内容(叶子)被重复绘制到用于创建图层对象的图形上下文中。

在释放 CGLayer 对象之前,您可以根据需要多次重用图层中的绘图。

提示: 当您想要合成绘图的各个部分以实现诸如阴影一组对象之类的效果时,请使用透明层。

(请参阅 透明层。)当您想要在屏幕外绘图或需要重复绘制相同内容时,请使用 CGLayer 对象。


2、使用图层绘图

您需要执行以下部分中描述的任务才能使用 CGLayer 对象进行绘制:

  1. 创建使用现有图形上下文初始化的 CGLayer 对象
  2. 获取图层的图形上下文
  3. 绘制到 CGLayer 图形上下文
  4. 将图层绘制到目标图形上下文

请参阅 示例:使用多个 CGLayer 对象绘制旗帜以获取详细的代码示例。


创建使用现有图形上下文初始化的 CGLayer 对象

CGLayerCreateWithContext 函数返回一个使用现有图形上下文初始化的图层。

该图层继承了图形上下文的所有特性,包括颜色空间、大小、分辨率和像素格式。

稍后,当您将图层绘制到目标时,Quartz 会自动将图层的颜色与目标上下文进行匹配。

CGLayerCreateWithContext 函数有三个参数:

  • 创建图层的图形上下文。
    通常,您会传递一个窗口图形上下文,以便稍后可以在屏幕上绘制图层。
  • 图层相对于图形上下文的大小。
    图层可以与图形上下文大小相同或更小。
    如果您稍后需要检索图层大小,可以调用CGLayerGetSize函数。
  • 辅助词典。
    此参数目前未使用,因此传递NULL

获取图层的图形上下文

Quartz 总是绘制到图形上下文。

现在您有了一个图层,您必须创建与该图层关联的图形上下文。

您在图层图形上下文中绘制的任何东西都是图层的一部分。

CGLayerGetContext函数以图层作为参数,并返回与该图层关联的图形上下文。


绘制到 CGLayer 图形上下文

获取与图层关联的图形上下文后,您可以对图层图形上下文执行任何您想要的绘制。

您可以打开 PDF 文件或图像文件并将文件内容绘制到图层。

您可以使用任何 Quartz 2D 函数来绘制矩形、线条和其他绘图基元。

图 12-3显示了将矩形和线条绘制到图层的示例。


图 12-3 包含两个矩形和一系列线条的图层


例如,要将一个填充的矩形绘制到 CGLayer 图形上下文,请调用函数CGContextFillRect,并提供从CGLayerGetContext函数 获得的图形上下文。

如果图形上下文名为myLayerContext,则函数调用如下所示:

c 复制代码
CGContextFillRect (myLayerContext, myRect)

将图层绘制到目标图形上下文

当您准备将图层绘制到其目标图形上下文时,可以使用以下任一函数:

  • CGContextDrawLayerInRect,在指定的矩形内的图形上下文中绘制一个图层。
  • CGContextDrawLayerAtPoint,将图层绘制到指定点的图形上下文中。

通常,您提供的目标图形上下文是窗口图形上下文,它与您用于创建图层的图形上下文相同。

图 12-4显示了重复绘制图 12-3中所示的图层的结果。

要实现图案效果,您可以重复调用任一图层绘制函数(CGContextDrawLayerAtPointCGContextDrawLayerInRect)每次更改偏移量。

例如,您可以调用函数CGContextTranslateCTM来更改坐标空间的原点,每次绘制图层时都更改原点。


图 12-4 重复绘制图层


注意: 您无需将图层绘制到用于初始化图层的同一图形上下文中。

但是,如果您将图层绘制到另一个图形上下文中,则原始图形上下文的任何限制都会强加到您的绘制中。


3、示例:使用多个 CGLayer 对象绘制旗帜

本节介绍如何使用两个 CGLayer 对象在屏幕上绘制如图 12-5所示的旗帜。

首先,您将了解如何将旗帜简化为简单的绘图基元,然后您将查看完成绘制所需的代码。


图12-5 使用图层绘制美国国旗的结果


从在屏幕上绘制的角度来看,该标志由三部分组成:

  • 红白条纹图案。
    您可以将图案缩小为单个红色条纹,因为对于屏幕绘图,您可以假设背景为白色。
    您可以创建一个红色矩形,然后以不同的偏移量重复绘制该矩形,以创建美国国旗所需的七个红色条纹。
    图层是重复绘图的理想选择。
    您将红色矩形绘制到图层上,然后在屏幕上绘制该图层七次。
  • 一个蓝色矩形。
    您只需要蓝色矩形一次,因此使用图层没有任何好处。
    当需要绘制蓝色矩形时,直接在屏幕上绘制即可。
  • 50 颗白色星星的图案。
    与红色条纹一样,图层是绘制星星的理想选择。
    您可以创建一条勾勒出星星形状的路径,然后用白色填充该路径。
    将一颗星星绘制到图层上,然后绘制该图层 50 次,每次调整偏移量以获得适当的间距。

图 12-2中的代码生成了图 12-5所示的输出。

示例后面是每行代码的详细说明。

示例相当长,因此您可能希望打印出说明,以便在查看代码时可以阅读它。
myDrawFlag例程是从 Cocoa 应用程序内部调用的。

应用程序传递一个窗口图形上下文和一个矩形,该矩形指定与窗口图形上下文关联的视图的大小。

注意: 在调用此函数或任何使用 CGLayer 对象的例程之前,您必须检查以确保系统正在运行 Mac OS X v10.4 或更高版本,并且具有支持使用 CGLayer 对象的显卡。


例 12-1 使用图层绘制旗帜的代码

c 复制代码
void myDrawFlag (CGContextRef context, CGRect* contextRect)
{
    int          i, j,
                 num_six_star_rows = 5,
                 num_five_star_rows = 4;
    CGFloat      start_x = 5.0,// 1
                 start_y = 108.0,// 2
                 red_stripe_spacing = 34.0,// 3
                 h_spacing = 26.0,// 4
                 v_spacing = 22.0;// 5
    CGContextRef myLayerContext1,
                 myLayerContext2;
    CGLayerRef   stripeLayer,
                 starLayer;
    CGRect       myBoundingBox,// 6
                 stripeRect,
                 starField;
 // ***** Setting up the primitives *****
    const CGPoint myStarPoints[] = {{ 5, 5},   {10, 15},// 7
                                    {10, 15},  {15, 5},
                                    {15, 5},   {2.5, 11},
                                    {2.5, 11}, {16.5, 11},
                                    {16.5, 11},{5, 5}};
 
    stripeRect  = CGRectMake (0, 0, 400, 17); // stripe// 8
    starField  =  CGRectMake (0, 102, 160, 119); // star field// 9
 
    myBoundingBox = CGRectMake (0, 0, contextRect->size.width, // 10
                                      contextRect->size.height);
 
     // ***** Creating layers and drawing to them *****
    stripeLayer = CGLayerCreateWithContext (context, // 11
                            stripeRect.size, NULL);
    myLayerContext1 = CGLayerGetContext (stripeLayer);// 12
 
    CGContextSetRGBFillColor (myLayerContext1, 1, 0 , 0, 1);// 13
    CGContextFillRect (myLayerContext1, stripeRect);// 14
 
    starLayer = CGLayerCreateWithContext (context,
                            starField.size, NULL);// 15
    myLayerContext2 = CGLayerGetContext (starLayer);// 16
    CGContextSetRGBFillColor (myLayerContext2, 1.0, 1.0, 1.0, 1);// 17
    CGContextAddLines (myLayerContext2, myStarPoints, 10);// 18
    CGContextFillPath (myLayerContext2);    // 19
 
     // ***** Drawing to the window graphics context *****
    CGContextSaveGState(context);    // 20
    for (i=0; i< 7;  i++)   // 21
    {
        CGContextDrawLayerAtPoint (context, CGPointZero, stripeLayer);// 22
        CGContextTranslateCTM (context, 0.0, red_stripe_spacing);// 23
    }
    CGContextRestoreGState(context);// 24
 
    CGContextSetRGBFillColor (context, 0, 0, 0.329, 1.0);// 25
    CGContextFillRect (context, starField);// 26
 
    CGContextSaveGState (context);              // 27
    CGContextTranslateCTM (context, start_x, start_y);      // 28
    for (j=0; j< num_six_star_rows;  j++)   // 29
    {
        for (i=0; i< 6;  i++)
        {
            CGContextDrawLayerAtPoint (context,CGPointZero,
                                            starLayer);// 30
            CGContextTranslateCTM (context, h_spacing, 0);// 31
        }
        CGContextTranslateCTM (context, (-i*h_spacing), v_spacing); // 32
    }
    CGContextRestoreGState(context);
 
    CGContextSaveGState(context);
    CGContextTranslateCTM (context, start_x + h_spacing/2, // 33
                                 start_y + v_spacing/2);
    for (j=0; j< num_five_star_rows;  j++)  // 34
    {
        for (i=0; i< 5;  i++)
        {
        CGContextDrawLayerAtPoint (context, CGPointZero,
                            starLayer);// 35
            CGContextTranslateCTM (context, h_spacing, 0);// 36
        }
        CGContextTranslateCTM (context, (-i*h_spacing), v_spacing);// 37
    }
    CGContextRestoreGState(context);
 
    CGLayerRelease(stripeLayer);// 38
    CGLayerRelease(starLayer);        // 39
}

代码的作用如下:

  1. 声明一个变量来表示第一颗星的水平位置。
  2. 声明一个变量来表示第一颗星的垂直位置。
  3. 声明一个变量来表示旗帜上红色条纹之间的间距。
  4. 声明一个变量来表示国旗上星星之间的水平间距。
  5. 声明一个变量来表示国旗上星星之间的垂直间距。
  6. 声明指定在何处绘制旗帜的矩形(边界框)、条纹层和星场。
  7. 声明一个点数组,这些点指定描绘出一颗星的线。
  8. 创建一个单一条纹形状的矩形。
  9. 创建一个与星场形状相同的矩形。
  10. 创建一个与传递给 myDrawFlag 例程的窗口图形上下文大小相同的边界框。
  11. 创建一个使用传递给myDrawFlag例程的窗口图形上下文初始化的层 。
  12. 获取与该图层关联的图形上下文。
    您将使用该图层进行条纹绘制。
  13. 将与条纹层关联的图形上下文的填充颜色设置为不透明红色。
  14. 填充代表一条红色条纹的矩形。
  15. 创建另一个层,并用传递给 myDrawFlag 例程的 窗口图形上下文对其进行初始化。
  16. 获取与该图层关联的图形上下文。
    您将使用此图层来绘制星形。
  17. 将与星形层关联的图形上下文的填充颜色设置为不透明的白色。
  18. myStarPoints数组定义的10条线 添加到与星形层关联的上下文中。
  19. 填充路径,由刚刚添加的 10 条线组成。
  20. 保存 Windows 图形上下文的图形状态。
    您需要这样做是因为您将在不同位置重复绘制相同的条纹。
  21. 设置一个迭代 7 次的循环,旗帜上每个红色条纹迭代一次。
  22. 绘制条纹层(由一条红色条纹组成)。
  23. 平移当前变换矩阵,使得原点位于下一个必须绘制红色条纹的位置。
  24. 将图形状态恢复到绘制条纹之前的状态。
  25. 将填充颜色设置为适合星空的蓝色阴影。
    请注意,此颜色的不透明度为 1.0。
    虽然本例中的所有颜色都是不透明的,但它们并不需要如此。
    您可以使用部分透明的颜色通过分层绘制创建漂亮的效果。
    回想一下,alpha 值为 0.0 表示透明颜色。
  26. 用蓝色填充星空矩形。
    您可以直接将此矩形绘制到窗口图形上下文中。
    如果您只绘制一次,请不要使用图层。
  27. 保存窗口图形上下文的图形状态,因为您将转换 CTM 来正确定位星星。
  28. 平移 CTM,使原点位于星场中,定位于第一行(底部)的第一颗星(左侧)。
  29. 这个和下一个 for 循环设置代码重复绘制星层,以便旗帜上的五个奇数行每行包含六颗星。
  30. 将星形层绘制到窗口图形上下文中。
    回想一下,星形层包含一颗白色星星。
  31. 定位 CTM,使原点向右移动,准备绘制下一个星形。
  32. 定位 CTM,使原点向上移动,准备绘制下一行星星。
  33. 平移 CTM,使原点位于星场中,定位在倒数第二行的第一个星号(左侧)。
    请注意,偶数行相对于奇数行有偏移。
  34. 这个和下一个 for 循环设置代码重复绘制星层,以便旗帜上的四个偶数行每行包含五颗星。
  35. 将星形层绘制到窗口图形上下文中。
  36. 定位 CTM,使原点向右移动,准备绘制下一个星形。
  37. 将 CTM 定位到原点位于左下方,准备绘制下一行星星。
  38. 释放条纹层。
  39. 释放星层。

十四、PDF 文档创建、查看和转换

PDF 文档将与分辨率无关的矢量图形、文本和图像存储为一系列用紧凑编程语言编写的命令。

PDF 文档可以包含多页图像和文本。

PDF 可用于创建跨平台的只读文档以及绘制与分辨率无关的图形。

Quartz 为所有应用程序创建高保真 PDF 文档,这些文档保留了应用程序的绘图操作,如图13-1所示。

系统的其他部分或第三方产品可能会针对特定用途(例如特定打印机或 Web)优化生成的 PDF。

Quartz 生成的 PDF 文档可以在 Preview 和 Acrobat 中正确查看。


图13-1 Quartz创建高质量PDF文档


Quartz 不仅使用 PDF 作为其"数字论文",而且还在其 API 中包含了许多函数,您可以使用这些函数显示和生成 PDF 文件并完成许多其他与 PDF 相关的任务。

有关 PDF 的详细信息,包括 PDF 语言和语法,请参阅*《PDF 参考》*,第四版,版本 1.5。


1、打开并查看 PDF

Quartz 提供了表示 PDF 文档的 CGPDFDocumentRef数据类型。

您可以使用CGPDFDocumentCreateWithProvider函数 或CGPDFDocumentCreateWithURL函数创建 CGPDFDocument 对象。

创建 CGPDFDocument 对象后,您可以将其绘制到图形上下文中。

图 13-2显示了窗口内显示的 PDF 文档。


图 13-2 PDF 文档


例 13-1显示了如何创建 CGPDFDocument 对象并获取文档中的页数。

示例后面列出了每行代码的详细说明。


例 13-1 从 PDF 文件创建 CGPDFDocument 对象

c 复制代码
CGPDFDocumentRef MyGetPDFDocumentRef (const char *filename)
{
    CFStringRef path;
    CFURLRef url;
    CGPDFDocumentRef document;
    size_t count;
 
    path = CFStringCreateWithCString (NULL, filename,
                         kCFStringEncodingUTF8);
    url = CFURLCreateWithFileSystemPath (NULL, path, // 1
                        kCFURLPOSIXPathStyle, 0);
    CFRelease (path);
    document = CGPDFDocumentCreateWithURL (url);// 2
    CFRelease(url);
    count = CGPDFDocumentGetNumberOfPages (document);// 3
    if (count == 0) {
        printf("`%s' needs at least one page!", filename);
        return NULL;
    }
    return document;
}

代码的作用如下:

  1. 调用 Core Foundation 函数从代表要显示的 PDF 文件的文件名的 CFString 对象创建 CFURL 对象。
  2. 从 CFURL 对象创建 CGPDFDocument 对象。
  3. 获取 PDF 中的页数,以便代码中的下一个语句可以确保文档至少有一页。

通过查看例 13-2中的代码,您可以了解如何将 PDF 页面绘制到图形上下文中。

示例后面列出了每行编号代码的详细说明。


例 13-2 绘制 PDF 页面

c 复制代码
void MyDisplayPDFPage (CGContextRef myContext,
                    size_t pageNumber,
                    const char *filename)
{
    CGPDFDocumentRef document;
    CGPDFPageRef page;
 
    document = MyGetPDFDocumentRef (filename);// 1
    page = CGPDFDocumentGetPage (document, pageNumber);// 2
    CGContextDrawPDFPage (myContext, page);// 3
    CGPDFDocumentRelease (document);// 4
}

代码的作用如下:

  1. 调用您的函数(参见例 13-1)根据您提供的文件名创建一个 CGPDFDocument 对象。
  2. 从 PDF 文档中获取指定页码的页面。
  3. 通过调用CGContextDrawPDFPage函数从 PDF 文件中绘制指定页面。
    您需要提供图形上下文和要绘制的页面。
  4. 释放CGPDFDocument对象。

2、为 PDF 页面创建转换

Quartz 提供了一个函数CGPDFPageGetDrawingTransform ------通过将 PDF 页面中的框映射到您指定的矩形来创建仿射变换。

此函数的原型是:

c 复制代码
CGAffineTransform CGPDFPageGetDrawingTransform (
        CGPPageRef page,
        CGPDFBox box,
        CGRect rect,
        int rotate,
        bool preserveAspectRatio
);

该函数使用以下算法返回仿射变换:

  • 将与您在参数中指定的 PDF 框类型box(媒体、裁剪、出血、修剪或艺术)相关联的矩形与/MediaBox指定 PDF 页面的条目相交。
    相交结果为有效矩形
  • /Rotate根据PDF 页面条目指定的量来旋转有效矩形。
  • 将生成的矩形置于您在rect参数中提供的矩形的中心。
  • 如果您提供的参数值rotate非零且为 90 的倍数,则该函数会将有效矩形旋转您提供的度数。
    正值将矩形向右旋转;负值将矩形向左旋转。
    请注意,您传递的是度数,而不是弧度。
    请记住,PDF 页面的 /Rotate条目也包含旋转,您提供的rotate参数与/Rotate条目相结合。
  • 如果必要的话,缩放有效矩形,以便它与您提供的矩形的边缘重合。
  • 如果通过 在preserveAspectRatio参数传入 true 指定保留纵横比,则最终的矩形将与您在rect参数中提供的矩形的更严格尺寸的边缘重合。

例如,如果您正在编写类似于图 13-3所示的 PDF 查看应用程序,则可以使用此函数。

如果您要提供向左旋转/向右旋转功能,则可以调用CGPDFPageGetDrawingTransform来计算当前窗口大小和旋转设置的适当变换。


图 13-3 向右旋转 90 度的 PDF 页面


例 13-3显示了使用传递给函数的参数为 PDF 页面创建仿射变换、应用变换然后绘制 PDF 页面的函数。

示例后面列出了每行带编号的代码的详细说明。


例 13-3 为 PDF 页面创建仿射变换

c 复制代码
void MyDrawPDFPageInRect (CGContextRef context,
                    CGPDFPageRef page,
                    CGPDFBox box,
                    CGRect rect,
                    int rotation,
                    bool preserveAspectRatio)
{
    CGAffineTransform m;
 
    m = CGPDFPageGetDrawingTransform (page, box, rect, rotation,// 1
                                    preserveAspectRato);
    CGContextSaveGState (context);// 2
    CGContextConcatCTM (context, m);// 3
    CGContextClipToRect (context,CGPDFPageGetBoxRect (page, box));// 4
    CGContextDrawPDFPage (context, page);// 5
    CGContextRestoreGState (context);// 6
}

代码的作用如下:

  1. 根据提供给函数的参数创建仿射变换。
  2. 保存图形状态。
  3. 将 CTM 与仿射变换连接起来。
  4. 将图形上下文剪贴到参数指定的矩形box
    CGPDFPageGetBoxRect函数获取与您提供的常量(kCGPDFMediaBoxkCGPDFCropBoxkCGPDFBleedBoxkCGPDFTrimBoxkCGPDFArtBox)关联的页面边界框(媒体、裁剪、出血、修剪和艺术框)。
  5. 将 PDF 页面绘制到转换和剪辑的上下文中。
  6. 恢复图形状态。

3、创建 PDF 文件

使用 Quartz 2D 创建 PDF 文件与绘制任何图形上下文一样简单。

您可以指定 PDF 文件的位置,设置 PDF 图形上下文,并使用与任何图形上下文相同的绘图例程。

例 13-4 中显示的MyCreatePDFFile函数显示了您的代码执行的创建 PDF 文件的所有任务。

示例后面显示了每行编号代码的详细说明。

请注意,代码通过调用CGPDFContextBeginPage函数和CGPDFContextEndPage来描绘 PDF 页面。

您可以传递 CFDictionary 对象来指定页面属性,包括媒体、裁剪、出血、修剪和艺术框。

有关字典键常量的列表以及每个键常量的更详细描述,请参阅 CGPDFContext 参考


例 13-4 创建 PDF 文件

c 复制代码
void MyCreatePDFFile (CGRect pageRect, const char *filename)// 1
{
    CGContextRef pdfContext;
    CFStringRef path;
    CFURLRef url;
    CFDataRef boxData = NULL;
    CFMutableDictionaryRef myDictionary = NULL;
    CFMutableDictionaryRef pageDictionary = NULL;
 
    path = CFStringCreateWithCString (NULL, filename, // 2
                                kCFStringEncodingUTF8);
    url = CFURLCreateWithFileSystemPath (NULL, path, // 3
                     kCFURLPOSIXPathStyle, 0);
    CFRelease (path);
    myDictionary = CFDictionaryCreateMutable(NULL, 0,
                        &kCFTypeDictionaryKeyCallBacks,
                        &kCFTypeDictionaryValueCallBacks); // 4
    CFDictionarySetValue(myDictionary, kCGPDFContextTitle, CFSTR("My PDF File"));
    CFDictionarySetValue(myDictionary, kCGPDFContextCreator, CFSTR("My Name"));
    pdfContext = CGPDFContextCreateWithURL (url, &pageRect, myDictionary); // 5
    CFRelease(myDictionary);
    CFRelease(url);
    pageDictionary = CFDictionaryCreateMutable(NULL, 0,
                        &kCFTypeDictionaryKeyCallBacks,
                        &kCFTypeDictionaryValueCallBacks); // 6
    boxData = CFDataCreate(NULL,(const UInt8 *)&pageRect, sizeof (CGRect));
    CFDictionarySetValue(pageDictionary, kCGPDFContextMediaBox, boxData);
    CGPDFContextBeginPage (pdfContext, pageDictionary); // 7
    myDrawContent (pdfContext);// 8
    CGPDFContextEndPage (pdfContext);// 9
    CGContextRelease (pdfContext);// 10
    CFRelease(pageDictionary); // 11
    CFRelease(boxData);
}

代码的作用如下:

  1. 将指定 PDF 页面大小的矩形和指定文件名的字符串作为参数。
  2. 根据传递给MyCreatePDFFile函数的文件名创建 CFString 对象。
  3. 从 CFString 对象创建一个 CFURL 对象。
  4. 创建一个空的 CFDictionary 对象来保存元数据。
    接下来的两行添加了标题和创建者。
    您可以使用CFDictionarySetValue函数添加任意数量的键值对。
    有关创建字典的更多信息,请参阅 CFDictionary 参考
  5. 创建 PDF 图形上下文,传递三个参数:
  • 指定 PDF 数据位置的 CFURL 对象。
  • 指向定义 PDF 页面默认大小和位置的矩形的指针。
    矩形的原点通常为 (0, 0)。
    Quartz 使用此矩形作为页面媒体框的默认边界。
    如果传递NULL,Quartz 将使用默认页面大小 8.5 x 11 英寸(612 x 792 点)。
  • CFDictionary 对象,包含 PDF 元数据。
    如果没有元数据可添加,传递NULL
    您可以使用 CFDictionary 对象指定输出意图选项 - 意图子类型、条件、条件标识符、注册表名称、目标输出配置文件以及包含有关预期目标设备或生产条件的其他信息或注释的人性化文本字符串。
    有关输出意图选项的更多信息,请参阅 CGPDFContext 参考
  1. 创建一个 CFDictionary 对象来保存 PDF 页面的页面框。此示例设置了媒体框。
  2. 表示页面的开始。
    当您使用支持多页的图形上下文(例如 PDF)时,您可以同时调用 CGPDFContextBeginPage函数和CGPDFContextEndPage来在输出中划定页面边界。
    每个页面必须通过调用CGPDFContextBeginPage和括起来CGPDFContextEndPage
    Quartz 会忽略在基于页面的上下文中在页面边界之外执行的所有绘制操作。
  3. 调用应用程序定义的函数将内容绘制到 PDF 上下文中。
    您可在此提供绘图例程。
  4. 表示基于页面的图形上下文中的页面的结束。
  5. 释放 PDF 上下文。
  6. 释放页面字典。

4、添加链接

您可以向您创建的 PDF 上下文添加链接和锚点。

Quartz 提供了三个函数,每个函数都以 PDF 图形上下文作为参数,以及有关链接的信息:

  • CGPDFContextSetURLForRect让您指定当用户单击当前 PDF 页面中的矩形时打开的 URL。
  • CGPDFContextSetDestinationForRect让您设置当用户单击当前 PDF 页面中的矩形时跳转到的目标。
    您必须提供目标名称。
  • CGPDFContextAddDestinationAtPoint让您设置当用户单击当前 PDF 页面中的某个点时跳转到的目标。
    您必须提供目标名称。

5、保护 PDF 内容

为了保护 PDF 内容,您可以在传递给CGPDFContextCreate函数的辅助字典中指定许多安全选项。

您可以通过在辅助字典中包含以下键来设置所有者密码、用户密码以及是否可以打印或复制 PDF:

  • kCGPDFContextOwnerPassword,定义 PDF 文档的所有者密码。
    如果指定了此键,则使用值作为所有者密码对文档进行加密;否则,不加密文档。
    此键的值必须是可以用 ASCII 编码表示的 CFString 对象。
    只有前 32 个字节用于密码。
    此键没有默认值。
    如果此键的值不能用 ASCII 表示,则不会创建文档,创建函数返回NULL
    Quartz 使用 40 位加密。
  • kCGPDFContextUserPassword,定义 PDF 文档的用户密码。
    如果文档已加密,则此键的值是文档的用户密码。
    如果未指定,则用户密码为空字符串。
    此键的值必须是可以 ASCII 编码表示的 CFString 对象;密码仅使用前 32 个字节。
    如果此键的值无法用 ASCII 表示,则不会创建文档,创建函数返回NULL
  • kCGPDFContextAllowsPrinting 指定当使用用户密码解锁时是否可以打印文档。
    该键的值必须是 CFBoolean 对象。
    该键的默认值为kCFBooleanTrue
  • kCGPDFContextAllowsCopying 指定当使用用户密码解锁时是否可以复制文档。
    该键的值必须是 CFBoolean 对象。
    该键的默认值为kCFBooleanTrue

例 14-4(在下一章中)显示检查 PDF 文档是否被锁定的代码,如果被锁定,则尝试使用密码打开该文档。


十五、PDF 文档解析

Quartz 提供一些函数,让您可以检查 PDF 文档结构和内容流。

检查文档结构可以让您读取文档目录中的条目以及与每个条目相关的内容。

通过递归遍历目录,您可以检查整个文档。

PDF 内容流正如其名称所示 --- 一个顺序数据流,例如'BT 12 /F71 Tf (draw this text) Tj . . . ',PDF 运算符及其描述符与实际 PDF 内容混合在一起。

检查内容流需要您按顺序访问它。

本章介绍如何检查 PDF 文档的结构以及解析 PDF 文档的内容。


1、检查 PDF 文档结构

PDF 文件可能包含多页图像和文本。

您可以使用 Quartz 访问文档和页面级别的元数据以及 PDF 页面上的对象。

本节简要介绍您可以访问的元数据。

PDF 文档对象 ( CGPDFDocument) 包含与 PDF 文档相关的所有信息,包括其目录和内容。

目录中的条目以递归方式描述 PDF 文档的内容。

您可以通过调用CGPDFDocumentGetCatalog函数 来访问 PDF 文档目录的内容。

PDF 页面对象 ( CGPDFPage) 表示 PDF 文档中的页面,包含与特定页面相关的信息,包括页面词典和页面内容。

您可以通过调用CGPDFPageGetDictionary函数来获取页面词典。

图 14-1显示了描述组成图 13-2中显示的 PDF 文件的两幅图像(文本和公鸡图像)的一些元数据。


图 14-1 PDF 文件中两个图像的元数据


通过访问 PDF 元数据,您可以获取更多有用的信息。

图 14-1中的项目只是示例。

例如,您可以使用例 14-1所示的代码检查 PDF 是否具有缩略图(如图 14-2所示) 。


例 14-1 获取 PDF 的缩略图视图

c 复制代码
CGPDFDictionaryRef d;
CGPDFStreamRef stream; // represents a sequence of bytes
d = CGPDFPageGetDictionary(page);
// check for thumbnail data
if (CGPDFDictionaryGetStream (d, "Thumb", &stream)){
    // get the data if it exists
    data = CGPDFStreamCopyData (stream, &format);

Quartz 为您执行数据流的所有解密和解码。


图 14-2 缩略图


Quartz 提供了许多函数,您可以使用它们来获取 PDF 元数据中项目的单个值。

您可以使用函数CGPDFObjectGetValue,传递一个CGPDFObjectRef、PDF 对象类型(kCGPDFObjectTypeBooleankCGPDFObjectTypeInteger等等)和值的存储空间。

返回时,存储空间将填充该值。

您可以使用许多其他函数来遍历 PDF 文件的层次结构以访问各个节点及其子节点。

例如,函数CGPDFArrayCGPDFArrayGetBooleanCGPDFArrayGetDictionaryCGPDFArrayGetInteger等等)允许您访问值数组以检索特定类型的值。

您可以通过阅读 PDF 规范了解有关如何使用这些函数的更多信息。


2、解析 PDF 内容

PDF 内容流包含运算符,这些运算符表示 PDF 内容流中可能对您的应用程序感兴趣的部分。

运算符可以标记单个点或序列。

运算符被指定为具有属性列表或与之关联的对象的标签。

标签指定点或内容序列代表什么。

属性列表是一个字典,其中包含 PDF 内容创建者指定的键值对。

解析 PDF 内容流时,您的应用程序会查找任何感兴趣的标记,检查与标记关联的标签、属性列表或对象,然后执行任何适当的进一步处理。

查阅 PDF 参考获取完整的 PDF 运算符列表。

您使用 CGPDFScanner 对象(CGPDFScannerRef数据类型)来解析 PDF 内容流。

CGPDFScanner 对象会为您已注册回调的流中的任何运算符调用回调。

执行以下部分中描述的任务来解析内容流:

  1. 为操作符编写回调
    你只需要为想要处理的操作符编写回调。
  2. 创建并设置操作员表
  3. 打开 PDF 文档
  4. 扫描每个页面的内容流

当适合这样做时,您需要确保释放扫描仪、内容流和操作员表。

以下部分介绍如何解析内容流以查找标记内容运算符 (参见表14-1)。

标记内容运算符仅代表 PDF 内容中使用的部分 PDF 运算符。

编写自己的代码时,您需要查找适合您的应用程序的 PDF 运算符。

操作员 描述
MP 具有关联标签的标记点。
DP 具有标签以及与之关联的属性列表或对象的标记点。
BMC 表示标记内容序列的开始(开始标记内容),并与EMC表示序列结束的标记配对。 具有与之关联的标签。
BDC 表示标记内容序列的开始,并与EMC表示序列结束的标记配对。 具有与之关联的标签和属性列表或对象。
EMC 表示以BMCBDC标记开头的标记内容序列(结束标记内容)的结束。 此运算符没有与之关联的标签。

为操作员编写回调

当 Quartz 调用 PDF 操作符的回调时,它会传递一个 CGPDFScanner 对象和一个指向回调所需的任何信息的指针。

通常,回调会检索与操作符关联的任何项目。

例如,例 14-2MP中所示的操作符的回调会调用CGPDFScannerPopName函数从堆栈中检索与操作符关联的字符串。

如果示例中的代码成功从扫描仪堆栈中检索名称,则会打印该名称。

Quartz 有多种CGPDFScannerPop函数用于检索对象、布尔值、名称、数字、字符串、数组、字典和流。

每个函数都会返回一个布尔值来指示该项目是否已成功检索。


例 14-2 MP 运算符的回调

c 复制代码
static void
op_MP (CGPDFScannerRef s, void *info)
{
    const char *name;
 
    if (!CGPDFScannerPopName(s, &name))
        return;
 
    printf("MP /%s\n", name);
}

创建并设置操作员表

CGPDFOperatorTable 对象存储您编写的 PDF 运算符回调函数。
CGPDFOperatorTableCreate 函数创建一个运算符表,如例 14-3所示。

创建运算符表后,您可以为要添加到表中的每个回调调用CGPDFOperatorTableSetCallback函数。

您传递表、指定 PDF 运算符的字符串以及指向您编写的用于处理该运算符的回调函数的指针。

您可以随意命名回调。

只需确保您传递给CGPDFOperatorTableSetCallback函数的回调名称没有拼写错误。

例 14-3中的代码为表 14-1中列出的每个标记内容运算符设置了一个回调。

您的应用程序只需为感兴趣的运算符设置回调。

PDF 运算符字符串在 Adobe 的PDF 参考中定义。


例 14-3 为操作符表设置回调

c 复制代码
CGPDFOperatorTableRef myTable;
 
myTable = CGPDFOperatorTableCreate();
 
CGPDFOperatorTableSetCallback (myTable, "MP", &op_MP);
CGPDFOperatorTableSetCallback (myTable, "DP", &op_DP);
CGPDFOperatorTableSetCallback (myTable, "BMC", &op_BMC);
CGPDFOperatorTableSetCallback (myTable, "BDC", &op_BDC);
CGPDFOperatorTableSetCallback (myTable, "EMC", &op_EMC);

打开 PDF 文档

在扫描 PDF 文档的内容之前,您需要打开它。

例 14-4显示了从提供给代码的 URL 创建 CGPDFDocument 对象的代码片段。

请注意,示例是一个代码片段,因此并非所有变量都已声明。

示例后面显示了每行带编号代码的详细说明。


例 14-4 从 URL 打开 PDF 文档

c 复制代码
CGPDFDocumentRef myDocument;
myDocument = CGPDFDocumentCreateWithURL(url);// 1
if (myDocument == NULL) {// 2
        error ("can't open `%s'.", filename);
        CFRelease (url);
        return EXIT_FAILURE;
}
CFRelease (url);
if (CGPDFDocumentIsEncrypted (myDocument)) {// 3
    if (!CGPDFDocumentUnlockWithPassword (myDocument, "")) {
        printf ("Enter password: ");
        fflush (stdout);
        password = fgets(buffer, sizeof(buffer), stdin);
        if (password != NULL) {
            buffer[strlen(buffer) - 1] = '\0';
            if (!CGPDFDocumentUnlockWithPassword (myDocument, password))
                error("invalid password.");
        }
    }
}
if (!CGPDFDocumentIsUnlocked (myDocument)) {// 4
        error("can't unlock `%s'.", filename);
        CGPDFDocumentRelease(myDocument);
        return EXIT_FAILURE;
    }
}
 if (CGPDFDocumentGetNumberOfPages(myDocument) == 0) {// 5
        CGPDFDocumentRelease(myDocument);
        return EXIT_FAILURE;
}

代码的作用如下:

  1. 从提供给代码的 URL 创建 CGPDFDocument 对象。
  2. 检查以确保已创建 CGPDFDocument 对象。
    如果没有,则代码退出,因为没有文档则继续执行毫无意义。
  3. 检查文档是否已加密。
    如果文档已加密,代码将尝试使用空白密码打开文档。
    如果失败,代码将要求用户输入密码并尝试使用密码解锁文档。
  4. 检查文档是否已解锁。如果没有,则代码退出。
  5. 检查以确保文档至少有一页。否则,代码退出。

扫描每个页面的内容流

例 14-5中的代码片段扫描文档中的每一页。

当扫描仪遇到您为其注册了回调的 PDF 操作符之一时,Quartz 会调用您的回调。

示例后面是每行代码的详细说明。


例 14-5 扫描文档的每一页

c 复制代码
int k;
CGPDFPageRef myPage;
CGPDFScannerRef myScanner;
CGPDFContentStreamRef myContentStream;
 
numOfPages = CGPDFDocumentGetNumberOfPages (myDocument);// 1
for (k = 0; k < numOfPages; k++) {
    myPage = CGPDFDocumentGetPage (myDocument, k + 1 );// 2
    myContentStream = CGPDFContentStreamCreateWithPage (myPage);// 3
    myScanner = CGPDFScannerCreate (myContentStream, myTable, NULL);// 4
    CGPDFScannerScan (myScanner);// 5
    CGPDFPageRelease (myPage);// 6
    CGPDFScannerRelease (myScanner);// 7
    CGPDFContentStreamRelease (myContentStream);// 8
 }
 CGPDFOperatorTableRelease(myTable);// 9

代码的作用如下:

  1. 获取之前打开的文档的页数。
    请参阅 打开 PDF 文档
  2. 检索要扫描的页面。页码从 开始1
  3. 为页面创建内容流。
  4. 为内容流创建扫描仪。
    您必须传递之前使用回调创建和设置的内容流和运算符表。
    请参阅 创建和设置运算符表
    您还可以传递回调所需的任何数据。
  5. 解析与扫描仪相关的内容流。
    每次 Quartz 遇到您提供回调的操作符时,它都会调用您的回调。
  6. 释放页面。
  7. 释放扫描仪。
  8. 释放内容流。
  9. 扫描 PDF 中的所有页面后释放操作员表。

十六、PostScript 转换

Preview 应用程序会自动将 PostScript 文件转换为 PDF。

Quartz 2D API 提供了可用于在应用程序中执行 PostScript 转换的函数。

Quartz 2D PostScript 转换函数在 iOS 中不可用。

按照以下步骤将 PostScript 文档转换为 PDF 文档:

  1. 编写回调。Quartz 通过回调来传达每个页面进程的状态。
  2. 填充回调结构。
  3. 创建一个 PostScript 转换器对象。
  4. 为想要转换的 PostScript 文件创建数据提供程序对象。
  5. 为转换后的 PDF 创建数据消费者对象。
  6. 执行转换。

以下各节将讨论上述每个步骤。


1、编写回调

回调为 Quartz 提供了一种将转换状态告知应用程序的方法。

如果您的应用程序有用户界面,您可以使用状态信息向用户提供反馈,如图15-1所示。


图 15-1 PostScript 转换应用程序的状态消息


您可以提供回调来通知您的应用程序 Quartz 2D 是:

  • 开始转换(CGPSConverterBeginDocumentCallback)。
    Quartz 2D 将一个通用指针传递给你的回调函数,该指针指向你提供的数据。
  • 结束转换(CGPSConverterEndDocumentCallback)。
    Quartz 2D 将一个指向您提供的数据的通用指针和一个表示成功(true)或失败(false)的布尔值传递给您的回调。
  • 开始一个页面(CGPSConverterBeginPageCallback)。
    Quartz 2D 向你的回调传递一个指向你提供的数据的通用指针、页码和一个 CFDictionary 对象,该对象目前尚未使用。
  • 结束页面(CGPSConverterEndPageCallback)。
    Quartz 2D 向回调传递一个指向您提供的数据的通用指针和一个指示成功(true)或失败(false)的布尔值
  • 转换进展(CGPSConverterProgressCallback)。
    转换过程中会定期调用此回调。
    Quartz 2D 将向回调传递一个指向您提供的数据的通用指针。
  • 发送有关该过程的消息 ( CGPSConverterMessageCallback)。
    转换过程中可以发送多种类型的消息。
    最有可能的是字体替换消息,以及 PostScript 代码本身生成的任何消息。
    写入的任何 PostScript 消息stdout都通过此回调路由 - 通常这些是调试或状态消息。
    此外,如果文档格式不正确,可能会有错误消息。
    Quartz 2D 将一个指向您提供的数据的通用指针和一个包含有关转换消息的 CFString 对象传递给您的回调。
  • 释放 PostScript 转换器对象 ( CGPSConverterReleaseInfoCallback)。
    如果您提供了数据,则可以使用此回调释放通用指针并执行任何其他后处理任务。
    Quartz 2D 将通用指针传递给您的回调,该指针指向您提供的数据。

请参阅CGPSConverter 参考以了解每个回调所遵循的原型。


2、填写回调结构

您需要为CGPSConverterCallbacks数据结构的相应字段分配一个版本号和您创建的回调(如例 15-1 所示)。

版本为0。分配NULL给您未提供回调的字段。


例 15-1 PostScript 转换器回调数据结构

c 复制代码
struct CGPSConverterCallbacks {
   unsigned int version;
   CGPSConverterBeginDocumentCallback beginDocument;
   CGPSConverterEndDocumentCallback endDocument;
   CGPSConverterBeginPageCallback beginPage;
   CGPSConverterEndPageCallback endPage;
   CGPSConverterProgressCallback noteProgress;
   CGPSConverterMessageCallback noteMessage;
   CGPSConverterReleaseInfoCallback releaseInfo;
};

3、创建 PostScript 转换器对象

调用CGPSConverterCreate函数 来创建 PostScript 转换器对象。

此函数有三个参数:

  • NULL指向要传递给回调的通用数据的指针。
    如果不需要提供任何数据,则可以提供。
  • 指向已填充CGPSConverterCallbacks数据结构的指针。
  • NULL. 此字段保留供将来使用。

重要提示: 尽管 CGPSConverterConvert函数是线程安全的(它使用锁来防止在同一进程中同时进行多个转换),但它对于资源管理器而言并不是线程安全的。

如果您的应用程序在单独的线程上使用资源管理器,则您应该使用锁来防止CGPSConverterConvert 在使用资源管理器期间执行该操作,或者您应该在单独的进程中使用 PostScript 转换器执行转换。


4、创建数据提供者和数据消费者对象

您可以通过调用CGDataProviderCreateWithURL函数来创建数据提供程序对象,并提供一个指定要转换的 PostScript 文件地址的 CFURL 对象。

类似地,您可以通过调用CGDataConsumerCreateWithURL函数 来创建数据消费者对象,并提供一个指定转换后 PDF 文档地址的 CFURL 对象。


5、执行转换

您可以调用CGPSConverterConvert函数来执行从 PostScript 到 PDF 的实际转换。

此函数采用以下参数:

  • PostScript 转换器对象。
  • 提供 PostScript 数据的数据提供者对象。
  • 转换后数据的数据消费者对象。
  • NULL. 此参数保留供将来使用。

如果转换成功则函数返回true

您可以调用CGPSConverterIsConverting函数来检查转换是否仍在进行中。


十七、文本

本章之前介绍了 Quartz 提供的基本文本支持。

但是,Quartz 提供的低级支持已被弃用,并被 Core Text 取代,Core Text 是一种用于布局文本和处理字体的高级低级技术。

Core Text 旨在实现高性能和易用性,并允许您将 Unicode 文本直接绘制到图形上下文中。

如果您正在编写需要精确控制文本显示方式的应用程序,请参阅 Core Text 编程指南

如果您正在开发适用于 iOS 的文本应用程序,请首先查看 iOS 文本编程指南 ,其中介绍了 iOS 中的文本支持。

具体来说,UIKit 提供了实现常见任务的类,让您可以轻松地将文本添加到应用程序:

如果您正在为 Mac OS X 开发文本应用程序,请先查看《 Cocoa 文本架构指南》 ,其中介绍了 Cocoa 文本系统。

Cocoa 提供完整的 Unicode 支持、文本输入和编辑、精确的文本布局和排版、字体管理以及许多其他高级文本处理功能。


十八、词汇表

  • alpha 值
    Quartz 用来确定如何将新绘制的对象合成到现有页面的图形状态参数。
    在最大强度(alpha = 1.0)下,新绘制的对象是不透明的。
    在零强度下,新绘制的对象是不可见的(alpha = )0.0
  • axial gradient | 轴向梯度
    沿两个定义的端点之间的轴变化的填充。
    垂直于轴的线上的所有点都具有相同的颜色值。
    也称为线性渐变
  • bitmap
    像素的矩形阵列(或光栅),每个像素代表图像中的一个点。
    位图图像也称为采样图像
  • blend mode | 混合模式
    指定 Quartz 如何将前景绘画与背景绘画结合起来。
  • clipping area | 裁剪区域
    用于限制其范围内其他对象的绘制的路径。
  • color space | 色彩空间
    一维、二维、三维或四维环境,其组件(或通道)表示强度值。
    例如,RGB 空间是一个三维颜色空间,其刺激物是构成给定颜色的红色、绿色和蓝色强度;红色、绿色和蓝色是颜色通道。
  • concatenation | 级联
    通过将两个矩阵相乘来合并它们的运算。
  • current graphics state | 当前图形状态
    决定 Quartz 在绘制时如何呈现结果的参数值。
  • current point | 当前点
    Quartz 绘制路径时使用的最后一个位置。
  • current transformation matrix | 当前变换矩阵
    Quartz 使用仿射变换将点从一个坐标空间映射到另一个坐标空间。
  • device color space | 设备色彩空间
    与特定设备的颜色表示系统相关的颜色空间。
    这种颜色空间不适合在不同设备之间交换颜色数据。
  • device-independent color space | 设备无关的色彩空间
    一种可在设备之间移植的颜色表示,用于将颜色数据从一个设备的原生颜色空间交换到另一个设备的原生颜色空间。
    在设备功能允许的范围内,独立于设备的颜色空间中的颜色在不同设备上显示时看起来相同。
  • even-odd rule | 奇偶规则
    确定何时绘制像素的填充规则。
    结果不取决于绘制路径段的方向。
    非零绕数规则进行比较。
  • fill | 填充满
    绘制路径内区域的操作。
  • generic color space | 通用颜色空间
    Mac OS X 自动选择与设备无关的色彩空间,为绘图目标产生最佳颜色。
  • gradient | 梯度
    从一种颜色到另一种颜色变化的填充。
    另请参阅 轴向渐变径向渐变
  • graphics context | 图形上下文
    一种不透明数据类型 ( CGContextRef),封装了 Quartz 用于将图像绘制到输出设备(例如 PDF 文件、位图或显示器上的窗口)的信息。
    图形上下文中的信息包括图形绘制参数和页面上绘制的设备特定表示。
  • identity transform | 恒等变换
    仿射变换,当应用于输入坐标时,始终返回输入坐标。
  • image mask | 图像蒙版
    指定要绘制的区域但不指定颜色的位图。
    图像遮罩的作用类似于模板,用于指定在页面上放置颜色的位置。
  • inversion | 倒置
    从变换后的坐标生成原始坐标的操作。
  • layer context | 层上下文
    为实现最佳性能而设计的屏幕外绘图目标 ( CGLayerRef)。
    与位图图形上下文相比,图层上下文是屏幕外绘图的更好选择。
  • line cap | 线帽
    Quartz 用来绘制线的端点的样式 - 平端、圆形或投影正方形。
  • line dash pattern | 线划线图案
    用于绘制虚线的重复的一系列线段和空格。
  • line join | 线连接
    Quartz 用来绘制连接线段之间的连接点的样式 - 斜接、圆形或斜面。
  • line width | 行宽
    线的总宽度,以用户空间单位表示。
  • linear gradient | 线性渐变
    参见轴向梯度
  • nonzero winding number rule | 非零缠绕数规则
    确定何时绘制像素的填充规则。
    结果取决于绘制路径段的方向。
    奇偶规则进行比较。
  • page | 页
    Quartz 绘画的虚拟画布。
  • painter's model | 画家模型
    一种绘图模型,其中每个连续的绘图操作都会在页面上应用一层油漆。
  • path
    Quartz 作为一个单元绘制的一个或多个形状(称为子路径)。
    子路径可以由直线、曲线或两者组成。
    它可以是开放的,也可以是封闭的。
  • pattern
    Quartz 可以重复绘制到图形上下文的一系列绘图操作。
  • pattern space | 模式空间
    通过创建模式时指定的转换矩阵(模式矩阵)映射到默认用户空间的抽象空间。
    模式空间与用户空间是分开的。
    未转换的模式空间映射到基本(未转换)用户空间,而不管当前转换矩阵的状态如何。
  • premultiplied alpha
    源颜色的成分已与 alpha 值相乘。
    预乘可消除每个颜色成分的额外乘法运算,从而加快图像渲染速度。
    另请参阅 alpha 值
  • radial gradient
    沿两个定义端点(通常都是圆)之间的轴呈放射状变化的填充。
    如果点位于圆心落在轴上的圆的圆周上,则这些点共享相同的颜色值。
    渐变圆形部分的半径由端点圆的半径定义;每个中间圆的半径从一端到另一端呈线性变化。
  • rendering intent
    指定 Quartz 如何将源颜色空间中的颜色映射到图形上下文的目标颜色空间色域内的颜色。
  • rotation
    将坐标空间移动指定角度的操作。
  • scaling
    通过指定的 x 和 y 因子更改坐标空间比例的操作,有效地拉伸或收缩坐标。
    x 和 y 因子的大小决定新坐标是大于还是小于原始坐标。
    负因子会翻转相应的轴。
  • shadow | 阴影
    在图形对象下方绘制并偏移的图像,使得阴影模仿投射在图形对象上的光源的效果。
  • stroke
    绘制一条横跨路径的线的操作。
  • tiling
    将图案单元渲染到页面的一部分的过程。
    Quartz 有三种平铺选项 - 无失真、恒定间距且失真最小、恒定间距。
  • translation
    将坐标空间的原点移动 x 轴和 y 轴指定的单位数的操作。
  • transparency layer | 透明层
    两个或多个对象的组合,Quartz 在应用效果(例如阴影)时将其视为单个对象。
  • user space | 用户空间
    使用 Quartz 2D 绘图时使用的与设备无关的坐标系。

2024-06-03(一)

相关推荐
lzhdim34 分钟前
iPhone 17 Air看点汇总:薄至6mm 刷新苹果轻薄纪录
ios·iphone
安和昂38 分钟前
【iOS】知乎日报第四周总结
ios
麦田里的守望者江3 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
_黎明5 小时前
【Swift】字符串和字符
开发语言·ios·swift
ZVAyIVqt0UFji6 小时前
iOS屏幕共享技术实践
macos·ios·objective-c·cocoa
hfxns_8 小时前
iOS 18.2 Beta 4开发者预览版发布,相机新增辅助功能
ios
AirDroid_cn18 小时前
如何控制自己玩手机的时间?两台苹果手机帮助自律
ios·智能手机·ipad·手机使用技巧·苹果手机使用技巧
郝晨妤20 小时前
鸿蒙原生应用开发元服务 元服务是什么?和App的关系?(保姆级步骤)
android·ios·华为od·华为·华为云·harmonyos·鸿蒙
tealcwu21 小时前
【Unity踩坑】在Mac上安装Cocoapods失败
unity·ios·游戏引擎
名字不要太长 像我这样就好1 天前
【iOS】iOS的轻量级数据库——FMDB
数据库·ios·sqlite·objective-c