Quartz2D 编程指南(四)位图与图像遮罩、CoreGraphics 绘制 Layer

  1. 概览
  2. 图形上下文
  3. 路径
  4. 颜色与颜色空间
  5. 变换
  6. 图案
  7. 阴影
  8. 渐变
  9. 透明层
  10. Quartz 2D 中的数据管理
  11. 位图与图像遮罩
  12. CoreGraphics 绘制 Layer

位图与图像遮罩

简介

  • 位图与图像遮罩和 Quartz 中的其它绘制元素一样。两者在 Quartz 中都是用 CGImageRef 数据类型来表示。

位图和图像遮罩

  • 一个位图是一个像素数组。每一个像素表示图像中的一个点。JPEG, TIFF 和 PNG 图像文件都是位图。应用程序的 icon 也是位图。

  • 位图中的每一个采样包含特定颜色空间下的一个或更多颜色分量,以及一个额外的用于指定 alpha 值以表示透明度的分量。

  • Quartz同样支持图像遮罩。一个图像遮罩也是一个位图,它指定了一个绘制区域,而不是颜色。一个颜色遮罩可以有 1 - 8 位的深度。

位图信息

  • 创建 CGImageRef 时,Quartz 需要使用以下信息。
CGImageRef __nullable CGImageCreate(size_t width, size_t height,
    size_t bitsPerComponent, size_t bitsPerPixel, size_t bytesPerRow,
    CGColorSpaceRef __nullable space, CGBitmapInfo bitmapInfo,
    CGDataProviderRef __nullable provider,
    const CGFloat * __nullable decode, bool shouldInterpolate,
    CGColorRenderingIntent intent);
  1. 位图数据源:可以是一个 Quartz 数据提供者(provider)或者是一个 Quartz 图像源(source)。
  2. 可选的解码数组。
  3. 插值设置:这是一个布尔值,指定 Quartz 在重置图像大小时是否使用插值算法。
  4. 渲染意图:指定如何映射位于图形上下文中的目标颜色空间中的颜色。该值在图像遮罩中不需要。
  5. 图像尺寸
  6. 像素格式:包括每个分量中的位数,每个像素的位数和每行中的字节数。
  7. 颜色空间和位图布局信息:描述了 alpha 的位置和位置是否使用浮点值。图像遮罩不需要这个信息。
  • 一些 Quartz 图像创建函数需要我们指定所有的信息,而其它函数只需要部分信息。我们所需要提供的信息依赖于位图数据的编码,以及位图是表示一个图像还是图像遮罩。

  • Quartz提供了很多图像格式并内建了多种常用的格式。在 iOS 中,这些格式包括 JPEG,GIF,PNG,TIF,ICO,GMP,XBM 和 CUR。其它的位图格式或专有格式需要我们指定图像格式的详细信息,以便 Quartz 能正确地解析图像。

  • 我们提供给 CGImageCreate 函数的图像数据必须是位图。Quartz不支持矢量图。

解码数组

  • 解码数组包含每个颜色分量的一对数值用来将图像颜色值线性映射到其它颜色值。对于诸如对一个图像做去饱和或者反转颜色值非常有用。

  • 在 RGB 颜色空间中的一个图像的解码数组包含 6 个数值,分别用于红、绿、蓝颜色分量。

像素格式

  • 像素格式包含以下信息。
  1. 每个分量的位数:即在一个像素中每个独立颜色分量的位数。对于一个图像遮罩,这个值是源像素中遮罩 bit 的数目。例如,如果源图片是 8-bit 的遮罩,则指定每个分量是 8 位。
  2. 每个像素的位数:即一个源像素所占的总的位数。这个值必须至少是每个分量的位数乘以每个像素中分量的数目。
  3. 每行的字节数:即图像中水平行的字节数。

颜色空间和位图布局

  • 为了确保 Quartz 能正确的解析每个像素,我们必须指定:
  1. 位图是否包含 alpha 通道。Quartz 支持 RGB,CMYK 和灰度颜色空间。同时支持 alpha,或 transparency,虽然并不是所有位图图像格式都支持 alpha 通道。alpha 通道可用时,alpha 分量可以位于像素的高位或地位。
  2. 对于有 alpha 分量的位图,指定颜色分量是否已经乘以了 alpha 值。预乘 alpha(Premultiplied alpha)表示一个已将颜色分量乘以了 alpha 值的源颜色。这种预处理通过消除每个颜色分量的额外的乘法运算来加速图片的渲染。
  3. 采样的数据格式是整型还是浮点型。
  • 当我们使用 CGImageCreate 函数来创建图像时,我们提供一个类型为 CGBitmapInfo(包含 CGImageAlphaInfo)的 bitmapInfo 参数来指定位置布局信息。CGImageAlphaInfo 的常量指定了 alpha 分量的位置及颜色分量是否做了预处理。
  1. kCGImageAlphaLast:alpha 分量存储在每个像素中的低位,如RGBA。
  2. kCGImageAlphaFirst:alpha 分量存储在每个像素中的高位,如ARGB。
  3. kCGImageAlphaPremultipliedLast:alpha 分量存储在每个像素中的低位,同时颜色分量已经乘以了 alpha 值。
  4. kCGImageAlphaPremultipliedFirst:alpha 分量存储在每个像素中的高位,同时颜色分量已经乘以了 alpha 值。
  5. kCGImageAlphaNoneSkipLast:没有 alpha 分量。如果像素的总大小大于颜色空间中颜色分量数目所需要的空间,则低位将被忽略。
  6. kCGImageAlphaNoneSkipFirst:没有 alpha 分量。如果像素的总大小大于颜色空间中颜色分量数目所需要的空间,则高位将被忽略。
  7. kCGImageAlphaNone:等于kCGImageAlphaNoneSkipLast。
  • CGBitmapInfo 的常量则指定了采样的数据格式是整型还是浮点型。
typedef CF_OPTIONS(uint32_t, CGBitmapInfo) {
  kCGBitmapAlphaInfoMask = 0x1F,
  
  kCGBitmapFloatInfoMask = 0xF00,
  kCGBitmapFloatComponents = (1 << 8),
  
  kCGBitmapByteOrderMask = 0x7000,
  kCGBitmapByteOrderDefault = (0 << 12),
  kCGBitmapByteOrder16Little = (1 << 12),
  kCGBitmapByteOrder32Little = (2 << 12),
  kCGBitmapByteOrder16Big = (3 << 12),
  kCGBitmapByteOrder32Big = (4 << 12)
};
  • 我们使用常量 kCGBitmapFloatComponents 来标识一个位图格式使用浮点值。对于浮点格式,我们将这个常量与上述描述的合适的常量进行按位与操作来组成 bitmapInfo 的参数。
kCGImageAlphaPremultipliedLast | kCGBitmapFloatComponents
  • 下图演示了一个像素在使用 16 或 32bit 整型像素格式的 CMYK 和 RGB 颜色空间中如何表示。Quartz 同样支持 128bit 浮点像素格式,每个分量占 32 位。128bit 格式没有显示在下图中。

创建图像

  • 我们可以使用如下函数来创建 CGImageRef。
CGImageRef imageRef = [UIImage imageNamed:@"image"].CGImage;
CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
CGImageRef image = CGImageCreate(80, 80, 8, 32, 320,
                                 CGColorSpaceCreateDeviceRGB(),
                                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big,
                                 dataProvider,
                                 NULL, true,
                                 kCGRenderingIntentPerceptual);
//A flexible function for creating an image. You must specify all the bitmap information that is discussed in Bitmap Image Information.

NSString *path = [[NSBundle mainBundle] pathForResource:@"image" ofType:@"png"];
NSURL *url = [NSURL fileURLWithPath:path];
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL);

image = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
// 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.

CFStringRef string[] = {kCGImageSourceShouldCache};
CFBooleanRef boolean[] = {kCFBooleanTrue};
CFDictionaryRef dic = CFDictionaryCreate(kCFAllocatorDefault, (void *)string, (void *)boolean, 1, NULL, NULL);
image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, dic);
// 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.

image = CGBitmapContextCreateImage(context);
// Creates an image by copying the bits from a bitmap graphics context.

image = CGImageCreateWithImageInRect(imageRef, CGRectMake(0, 0, 80, 80));
// Creates an image from the data contained within a sub-rectangle of an image.

image = CGImageCreateCopy(imageRef);
// A utility function that creates a copy of an image.

image = CGImageCreateCopyWithColorSpace(imageRef, CGColorSpaceCreateDeviceRGB());
// A utility function that creates a copy of an image and replaces its color space.

CGContextDrawImage(context, CGRectMake(0, 0, 80, 80), image);

image = CGBitmapContextCreateImage(context);
  • 最常用的函数是 CGImageCreate。它可以从任何类型的位图数据来创建图像。然而,它是最复杂的函数,因为需要提供所有的位图信息。为了使用这个函数,我们需要熟悉上面讨论的所有内容。

  • 在使用 CGImageCreate 创建图像时必须提供正确的参数,否则图像将无法正确绘制。我们在测试时可以使用如下函数来获取正确的图像参数。

CGImageRef imageRef = [UIImage imageNamed:@"image"].CGImage;
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
size_t bitsPerRow = CGImageGetBytesPerRow(imageRef);
CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
CGFloat *decode = CGImageGetDecode(imageRef);
bool shouldInterpolate = CGImageGetShouldInterpolate(imageRef);
CGColorRenderingIntent renderingIntent = CGImageGetRenderingIntent(imageRef);
  • 如果我们想从标准的图像格式(PNG、JPEG 等)来创建图片,则最简单的方法是调用函数 CGImageSourceCreateWithURL 来创建一个图像源,然后调用CGImageSourceCreateImageAtIndex 以使用从图像源中索引 index 指定的图像数据来创建图像。如果图像文件格式支持包含多个图像的文件,则需要提供所需要图像的索引值。index 起始值为 0。

  • 其中部分函数可以操作已有的图像,如拷贝、创建缩略图,或从大图像中创建裁剪的图像。

  • CGImage 是不可变的。当不再需要 CGImage 对象时,调用 CGImageRelease 函数来释放它。

从大图像中创建裁剪的图像

  • 调用 CGImageCreateWithImageInRect(imageRef, CGRectMake(0, 0, 80, 80)) 从大图像中创建裁剪的图像。

  • 函数 CGImageCreateWithImageInRect 返回的图像保留了源图像的引用,所以我们可以在调用完这个函数后可以释放源图像。

从上下文中获取图片

  • 如果我们已经将内容渲染到图图形上下文,可以调用 CGBitmapContextCreateImage(context)(或 UIKit 提供的便捷函数UIGraphicsGetImageFromCurrentImageContext)函数从上下文中获取图片。

  • 这个函数返回的 CGImage 对象是通过一个拷贝操作创建的。因此我们对位图图形上下文所做的修改都不会影响到已返回的CGImage对象。

  • 在某些情况下,这个拷贝操作实际上沿用了 copy-on-write 语义,即只有当位图图形上下文中的数据被修改时才会去实际拷贝这些数据。我们可能需要在绘制额外数据到位图图形上下文之前使用结果数据或者释放它们,以避免实际去拷贝这些数据。

创建图像遮罩

  • 位图图像遮罩定义了如何转换颜色,而不是使用哪些颜色。图像遮罩中的每个采样值指定了在特定位置中,当前填充颜色值被遮罩的数量。采样值指定了遮罩的不透明度。值越大,表示越不透明,Quartz 在指定位置绘制的颜色越少。

  • 图像遮罩的每个分量可能是 1/2/4/8 位。1bit 的遮罩,要么完全遮挡,要么完全显示。2/4/8bit 的遮罩代表灰度值,每个分量使用以下的公式值映射到 [0, 1] 之间。

  • 调用函数 CGImageMaskCreate 创建 Quartz 图像遮罩。我们提供的信息与创建图像所提供的信息相同,只是不需要提供颜色空间信息,位图信息常量或渲染意图。
CG_EXTERN CGImageRef __nullable CGImageMaskCreate(
    size_t width, 
    size_t height,
    size_t bitsPerComponent, 
    size_t bitsPerPixel, 
    size_t bytesPerRow,
    CGDataProviderRef __nullable provider, 
    const CGFloat * __nullable decode,
    bool shouldInterpolate
);

遮罩图像

遮罩技术可以让我们通过控制图片的哪一部分被绘制,以生成很多有趣的效果,我们可以:

  1. 使用图像遮罩来遮罩图像。我们也可以把图像作为遮罩图,以获取同使用图像遮罩相反的效果。
  2. 使用颜色来遮罩图像。其中包含被称为颜色遮罩的技术。
  3. 使用图像或图像遮罩来裁剪上下文。实际是裁剪 Quartz 绘制内容到裁剪的图形上下文时的图片。

使用图像遮罩来遮罩图像

  • 函数 CGImageCreateWithMask(CGImageRef image, CGImageRef mask) 通过将图像遮罩使用到一个图像上的方式来创建一个图像。这个函数带有两个参数:
  1. 原始图像,遮罩将用于其上。这个图像不能是图像遮罩,也不能有与之相关的遮罩颜色。
  2. 图像遮罩,通过调用 CGImageMaskCreate 函数创建。也可以提供一个图像来替代图像遮罩,但这将获得同使用图像遮罩相反的效果。
  • 图像遮罩采样值 S = 1 时,则不会绘制对应的图像样本。S = 0 时,则允许完全绘制对应的图像样本。S = 0 ~ 1 时,则让对应的图像样本的 alpha 的值为(1-S)。
  1. 原图


  2. 使用 CGImageMaskCreate 创建的图像遮罩


  3. 原图被遮罩后的图像


使用图像来遮罩图像

  • 假设上图图 2 是使用 CGImageCreate 创建的,而不是使用 CGImageMaskCreate 创建的。当其传入 CGImageCreateWithMask(CGImageRef image, CGImageRef mask) 后则会获得相反的效果。

使用颜色来遮罩图像

  • 使用 CGImageCreateWithMaskingColors(CGImageRef image, const CGFloat * components) 通过遮罩一种颜色或一个颜色范围内的颜色来创建一个图像。函数 CGImageCreateWithMaskingColors 有两个参数:
  1. 一个图像,它不能是遮罩图像,也不能是使用过图像遮罩或颜色遮罩的图像。
  2. 一个颜色分量数组,指定了一个颜色或一组颜色值,以用于遮罩图像。
  • 颜色分量数组中元素的个数必须等于图像所在颜色空间的颜色分量数目的两倍。如果图像使用整型像素分量,则颜色分量数组中的每个值必须在 [0 ~ 2bitsPerComponent – 1] 范围之内。如果图像使用浮点像素分量,则值可以是表示任何有效的颜色分量值的浮点数。

  • 通过如下代码使用颜色范围来遮罩图像可以获得如下图片的效果。

const CGFloat myMaskingColors[6] = {124, 255, 68, 222, 0, 165};
CGImageRef myColorMaskedImage = CGImageCreateWithMaskingColors(image, myMaskingColors);
CGContextDrawImage(context, myContextRect, myColorMaskedImage);
  • 我们同样可以设置填充颜色来作为图像的遮罩颜色,来得到如下图片的效果,其中被遮罩区域使用了填充颜色。
const CGFloat myMaskingColors[6] = {0, 124, 0, 68, 0, 0};
CGImageRef myColorMaskedImage = CGImageCreateWithMaskingColors(image, myMaskingColors);
CGContextSetRGBFillColor (myContext, 0.6373,0.6373, 0, 1);
CGContextFillRect(context, rect);
CGContextDrawImage(context, rect, myColorMaskedImage);

通过裁减上下文来遮罩图片

  • 调用 CGContextClipToMask(CGContextRef c, CGRect rect, CGImageRef mask) 裁减上下文来遮罩图片。
  1. 需要裁减的图形上下文
  2. 要使用遮罩的矩形区域
  3. 一个图像遮罩,通过 CGImageMaskCreate 函数创建。我们可以使用图像来替代图像遮罩以达到相反的效果。图像必须使用 Quartz 图像创建函数来创建,但不能是使用过图像遮罩或颜色遮罩的图像。
  • 裁剪的效果如下图所示。
  1. 通过 CGImageMaskCreate 创建的图像遮罩。


  2. 被裁剪后的绘制到图形上下文的图片。


  3. 当遮罩图像是使用 CGImageCreate 创建,而不是使用 CGImageMaskCreate 创建的时,可以得到相反的效果。


在图像中使用混合模式

  • 使用 CGContextSetBlendMode 来设置混合模式在“路径”章节中的“混合模式”小节中已经讲过,不再重复讲述。

CoreGraphics 绘制 Layer

简介

  • CGLayer 对象(CGLayerRef 数据类型)允许程序使用层来进行绘制。层适合于以下几种情况:
  1. 高质量离屏渲染,以绘制我们想重用的图形。例如,我们可能要建立一个场景并重用相同的背景。将背景场景绘制于一个层上,然后在需要的时候再绘制层。绘制层的一个额外的好处是我们不需要知道颜色空间或其它设备依赖的信息。
  2. 重复绘制。例如,我们可能想创建一个由相同元素反复绘制而组成的图案。将元素绘制到一个层中,然后重复绘制这个层。任何我们重复绘制的 Quartz 对象,包括 CGPath, CGShading 和 CGPDFPage,都可以通过将其绘制到 CGLayer 来优化性能。需要注意的是层不仅仅是用于在屏幕上绘制;我们也可以将其用于那些不是面向屏幕的图形上下文,如 PDF 图形上下文。
  3. 缓存。虽然我们可以将层用于此目的,但通常不需要这样做,因为 Quartz Compositor 已经做了此事。如果我们必须绘制一个缓存,则使用层来代替位图图形上下文。
  • CGLayer 对象和透明层与 CGPath 和 CGContext 函数创建的 paths 是对等的。对于 CGLayer 或 CGPath 对象,我们可以将其绘制到一个抽象目标,之后可以将其完整地绘制到另一个目标,如显示器或 PDF 中。当我们在透明层上绘制或使用绘制路径的 CGContext 函数时,可以直接绘制到图形上下文表示的目标上,而不需要负责组装绘制的中间抽象目标。

Layer 绘制原理

  • 层由 CGLayerRef 数据类型表示,是为优化性能而设计的。在可能的时候,Quartz 使用合适的机制将 CGLayer 对象缓存到与之相关的 Quartz 图形上下文中。例如,与显卡相关的图形上下文可能将层缓存到显卡中,这样绘制在层中的内容时,就比渲染从位图图形上下文中构造的类似图像要快得多。基于这个原因,层比位图图形上下文更适用于离屏绘制。

  • 所有的 Quartz 绘制函数都是绘制到图形上下文中。图形上下文提供了一个抽象的渲染目标,而将我们从目标的细节中解放出来。我们使用用户空间,Quartz 执行必要的转换来将绘图正确地渲染到目标。当我们使用 CGLayer 对象来绘制时,我们也是绘制到图形上下文中。

  • 使用 CGLayer 对象进行绘制的原理如下所示。

  1. 调用 CGLayerCreateWithContext 从图形上下文中创建 CGLayer。这个 CGLayer 具有图形上下文的所有特性(包括分辨率,颜色空间和图形状态设置)。如果我们不想使用图形上下文的大小,也可以指定 CGLayer 的小大。

  2. 绘制层之前需要调用 CGLayerGetContext 来获取与层相关的图形上下文。这个图形上下文与用于创建 CGLayer 的图形上下文差不多。只要用于创建层的图形上下文是 Window 图形上下文,则 CGLayer 图形上下文会尽可能地被缓存到 GPU 中。

  3. 调用 CGContextDrawLayerInRect 或 CGContextDrawLayerAtPoint 将层绘制到图形上下文。通常情况下,我们会将层绘制到创建层对象的图形上下文中,但这不是必须的。我们可以将层绘制到任意的图形上下文,但层带有创建层对象的图形上下文的所有特性,这会使原始图形上下文的所有限制都会反映到我们的绘图中(性能、分辨率)。例如,与屏幕关联的层可能会被缓存到显卡中。如果目标上下文是打印机或 PDF 上下文,则可能需要将层对象从显卡中取出并放到内存中,从而导致性能很差。

  • 我们可以在释放 CGLayer 对象之前,任意地重复使用层中的绘图。

使用层来绘制

  • 使用 CGLayer 对象进行绘制需要如下几个步骤。
  1. 创建从已存在的图形上下文初始化的 CGLayer 对象
  2. 为 CGLayer 获取图形上下文
  3. 绘制到 CGLayer 图形上下文
  4. 将 CGLayer 绘制到目标图形上下文

创建从已存在的图形上下文初始化的 CGLayer 对象

  • 函数 CGLayerCreateWithContext 带有三个参数:
CGLayerRef CGLayerCreateWithContext(
    CGContextRef context,
    CGSize size, 
    CFDictionaryRef auxiliaryInfo)
  1. 用于创建 CGLayer 的图形上下文。通常我们传递一个 Window 图形上下文以便后面可以离屏绘制层。
  2. 层相对于图形上下文的大小。层的大小可以和图形上下文一样,或者更小。如果想要获得层的大小,我们可以调用函数 CGLayerGetSize(CGLayerRef layer)。
  3. 辅助字典。这个参数现在已经不用了,所以传递 NULL 即可。

为 CGLayer 获取图形上下文

  • Quartz 总是在图形上下文中进行绘制。现在我们有了 CGLayer 对象,还必须创建一个与 CGLayer 相关的图形上下文。所有绘制到 CGLayer 图形上下文的内容都是 CGLayer 的一部分。

  • 使用 CGContextRef CGLayerGetContext(CGLayerRef layer) 获取与 CGLayer 相关的图形上下文。

绘制到 CGLayer 图形上下文

  • 在获取到与 CGLayer 相关的图形上下文之后,我们可以在 CGLayer 图形上下文中绘制任何东西。

  • 为了在 CGLayer 图形上下文中绘制一个填充矩形,我们调用函数 CGContextFillRect,并提供从 CGLayerGetContext 函数中获取到的图形上下文作为参数。

CGContextFillRect(myLayerContext, myRect)

将层绘制到目标图形上下文

  • 当我们已经准备好将 CGLayer 绘制到目标图形上下文时,我们可以使用以下函数来进行绘制。
  1. CGContextDrawLayerInRect:将层绘制到图形上下文中指定的矩形内。
  2. CGContextDrawLayerAtPoint:将层绘制到图形上下文中指定的点。
  • 通常情况下,我们提供的目标图形上下文是 Window 图形上下文,这也是我们用于创建 CGLayer 对象所使用的图形上下文。为了达到图案的效果,我们可以使用上述方法,只是每次改变偏移量而已。例如,我们每次绘制层时,可以调用函数 CGContextTranslateCTM 来改变坐标系统的原点。

使用多个 CGLayer 对象来绘制星条旗

  • 从上图可以看出,旗子主要分三部分。
  1. 红色条纹和白色条纹的图案。我们可以将这个图案分解为一个单一的红色条纹,因为对于屏幕绘制来说,我们可以设置其背景颜色为白色。我们创建一个红色矩形,然后以变化的偏移量来重复绘制这个矩形,以创建美国国旗上的七条红色条纹。我们将红色矩形绘制到一个层,然后将其绘制到屏幕上七次。
  2. 一个蓝色矩形。我们只需要一个蓝色矩形,所以没有必要使用层。当绘制蓝色矩形时,直接将其绘制到屏幕上。
  3. 50 个白色星星的图案。与红色条纹一样,可以使用层来绘制星星。我们创建星星边框的一个路径,然后使用白条来填充。将一个星星绘制到层,然后重复 50 次绘制这个层,每次绘制时适当调整偏移量。
void myDrawFlag (CGContextRef context, CGRect* contextRect) {
    int i, j, num_six_star_rows = 5, num_five_star_rows = 4;
    CGFloat start_x = 5.0, start_y = 108.0, red_stripe_spacing = 34.0, h_spacing = 26.0, v_spacing = 22.0;
    CGContextRef myLayerContext1, myLayerContext2;
    CGLayerRef stripeLayer, starLayer;
    CGRect myBoundingBox, stripeRect, starField;
    
    // ***** Setting up the primitives *****
    CGPoint point1 = {5, 5}, point2 = {10, 15}, point3 = {10, 15}, point4 = {15, 5};
    CGPoint point5 = {15, 5}, point6 = {2.5, 11}, point7 = {2.5, 11}, point8 = {16.5, 11};
    CGPoint point9 = {16.5, 11}, point10 = {5, 5};
    const CGPoint myStarPoints[] = {point1, point2,
                                    point3, point4,
                                    point5, point6,
                                    point7, point8,
                                    point9, point10};
    
    stripeRect = CGRectMake(0, 0, 400, 17); // stripe
    starField = CGRectMake(0, 102, 160, 119); // star field
    
    myBoundingBox = CGRectMake (0, 0, contextRect->size.width, contextRect->size.height);
    
    // ***** Creating layers and drawing to them *****
    stripeLayer = CGLayerCreateWithContext(context, stripeRect.size, NULL);
    myLayerContext1 = CGLayerGetContext(stripeLayer);
    
    CGContextSetRGBFillColor(myLayerContext1, 1, 0 , 0, 1);
    CGContextFillRect(myLayerContext1, stripeRect);
    
    starLayer = CGLayerCreateWithContext(context, starField.size, NULL);
    myLayerContext2 = CGLayerGetContext(starLayer);
    CGContextSetRGBFillColor(myLayerContext2, 1.0, 1.0, 1.0, 1);
    CGContextAddLines(myLayerContext2, myStarPoints, 10);
    CGContextFillPath(myLayerContext2);
    
    // ***** Drawing to the window graphics context *****
    CGContextSaveGState(context);
    for (i = 0; i < 7; i++) {
        CGContextDrawLayerAtPoint(context, CGPointZero, stripeLayer);
        CGContextTranslateCTM(context, 0.0, red_stripe_spacing);
    }
    CGContextRestoreGState(context);
    
    CGContextSetRGBFillColor(context, 0, 0, 0.329, 1.0);
    CGContextFillRect(context, starField);
    
    CGContextSaveGState (context);
    CGContextTranslateCTM (context, start_x, start_y);
    for (j = 0; j < num_six_star_rows; j++) {
        for (i = 0; i < 6; i++) {
            CGContextDrawLayerAtPoint(context,CGPointZero, starLayer);
            CGContextTranslateCTM(context, h_spacing, 0);
        }
        CGContextTranslateCTM(context, (-i * h_spacing), v_spacing);
    }
    CGContextRestoreGState(context);
    
    CGContextSaveGState(context);
    CGContextTranslateCTM(context, start_x + h_spacing/2, start_y + v_spacing/2);
    for (j = 0; j < num_five_star_rows; j++) {
        for (i = 0; i < 5; i++) {
            CGContextDrawLayerAtPoint(context, CGPointZero, tarLayer);
            CGContextTranslateCTM(context, h_spacing, 0);
        }
        CGContextTranslateCTM(context, (-i * h_spacing), v_spacing);
    }
    CGContextRestoreGState(context);
    
    CGLayerRelease(stripeLayer);
    CGLayerRelease(starLayer);
}

博客:xuyafei.cn
简书:jianshu.com/users/2555924d8c6e
微博:weibo.com/xuyafei86
Github:github.com/xiaofei86

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容