Quartz 2D编程指南 (十五) —— Core Graphics图层绘制(一)

版本记录

版本号 时间
V1.0 2018.09.08

前言

Quartz 2D框架相信大家都知道,也都一直在使用。Quartz 2D的API是纯C语言的,它是一个二维绘图引擎,同时支持iOS和Mac系统。Quartz 2D的API来自于Core Graphics框架,数据类型和函数基本都以CG作为前缀,接下来几篇我们就一起来看一下这个框架。感兴趣可以看上面几篇文章。
1. Quartz 2D编程指南 (一) —— 简介(一)
2. Quartz 2D编程指南 (二) —— Quartz 2D概览(二)
3. Quartz 2D编程指南 (三) —— 图形上下文(三)
4. Quartz 2D编程指南 (四) —— Paths路径(一)
5. Quartz 2D编程指南 (五) —— Paths路径(二)
6. Quartz 2D编程指南 (六) —— 颜色和颜色空间(一)
7. Quartz 2D编程指南 (七) —— 变换(一)
8. Quartz 2D编程指南 (八) —— Patterns图案样式(一)
9. Quartz 2D编程指南 (九) —— 阴影(一)
10. Quartz 2D编程指南 (十) —— 渐变(一)
11. Quartz 2D编程指南 (十一) —— 透明层(一)
12. Quartz 2D编程指南 (十二) —— Quartz 2D中的数据管理(一)
13. Quartz 2D编程指南 (十三) —— 位图图像和图像蒙版(一)
14. Quartz 2D编程指南 (十四) —— 位图图像和图像蒙版(二)

Core Graphics Layer Drawing - Core Graphics图层绘制

CGLayer对象(CGLayerRef数据类型)允许您的应用程序使用图层进行绘制。

图层适用于以下内容:

  • 您计划重复使用的高质量的绘图离线渲染。例如,您可能正在构建场景并计划重用相同的背景。将背景场景绘制到图层,然后在需要时绘制图层。一个额外的好处是您不需要知道绘制到图层的颜色空间或设备相关信息。
  • 重复绘图。例如,您可能希望创建一个由反复绘制的相同项组成的图案。将项目绘制到图层,然后重复绘制图层,如图12-1所示。您重复绘制的任何Quartz对象(包括CGPathCGShadingCGPDFPage对象) - 如果将其绘制到CGLayer,都可以从改进的性能中受益。请注意,图层不仅适​​用于屏幕绘图;您可以将它用于非面向屏幕的图形上下文,例如PDF图形上下文。
  • 缓冲。虽然您可以为此目的使用图层,但您不需要这样做,因为Quartz Compositor不需要您的缓冲。如果必须绘制到缓冲区,请使用图层而不是位图图形上下文。
Figure 12-1 Repeatedly painting the same butterfly image

CGLayer对象和透明层与CGContext函数创建的CGPath对象和路径并行。 对于CGLayerCGPath对象,您可以绘制到抽象目标,然后可以将完整的绘制绘制到另一个目标,例如显示或PDF。 当您绘制透明图层或使用绘制路径的CGContext函数时,您可以直接绘制到图形上下文所表示的目标。 没有用于组装绘画的中间抽象目的地。


How Layer Drawing Works - 图层绘制原理

CGLayerRef数据类型表示的层旨在实现最佳性能。如果可能,Quartz使用适合与其关联的Quartz图形上下文类型的机制来缓存CGLayer对象。例如,与视频卡相关联的图形上下文可以缓存视频卡上的图层,这使得绘制图层中的内容比渲染从位图图形上下文构造的类似图像快得多。因此,与位图图形上下文相比,图层通常是屏幕外绘制的更好选择。

所有Quartz绘图函数都绘制到图形上下文。图形上下文提供了目的地的抽象,使您可以从目的地详细信息中解放出来,例如其分辨率。您在用户空间中工作,Quartz执行必要的转换以将绘图正确呈现到目的地。使用CGLayer对象进行绘制时,还可以绘制到图形上下文。图12-1说明了图层绘制的必要步骤。

Figure 12-2 Layer drawing

所有图层绘制都以图形上下文开始,您可以使用CGLayerCreateWithContext函数从该图形上下文创建CGLayer对象。用于创建CGLayer对象的图形上下文通常是窗口图形上下文。 Quartz创建一个图层,使其具有图形上下文的所有特征 - 分辨率,颜色空间和图形状态设置。如果您不想使用图形上下文的大小,则可以为图层提供大小。在图12-2中,左侧显示了用于创建图层的图形上下文。右侧框中的灰色部分标记为CGLayer对象,表示新创建的图层。

在绘制到图层之前,必须通过调用函数CGLayerGetContext来获取与图层关联的图形上下文。此图形上下文与用于创建图层的图形上下文相同。只要用于创建图层的图形上下文是窗口图形上下文,那么尽可能将CGLayer图形上下文缓存到GPU。图12-2右侧框的白色部分表示新创建的图层图形上下文。

您可以绘制到图层的图形上下文,就像绘制到任何图形上下文一样,将图层的图形上下文传递给绘图函数。图12-2显示了绘制到图层上下文的叶子形状。

当您准备使用图层的内容时,可以调用函数CGContextDrawLayerInRectCGContextDrawLayerAtPoint,将图层绘制到图形上下文中。通常,您将绘制到用于创建图层对象的相同图形上下文,但您不需要。您可以将图层绘制到任何图形上下文,请记住图层绘制具有用于创建图层对象的图形上下文的特征,这可能会施加某些约束(例如,性能或分辨率)。例如,与屏幕相关联的层可以高速缓存在视频硬件中。如果目标上下文是打印或PDF上下文,则可能需要将其从图形硬件提取到内存,从而导致性能不佳。

图12-2显示了图层的内容 - 重复绘制的叶子到用于创建图层对象的图形上下文。在释放CGLayer对象之前,您可以多次重复使用图层中的图形。

提示:如果要合成图形的某些部分以实现遮蔽一组对象等效果,请使用透明度图层。 (请参阅Transparency Layers。)如果要在屏幕外绘制或需要重复绘制相同的内容时,请使用CGLayer对象。


Drawing with a Layer - 使用图层进行绘制

您需要执行以下部分中描述的任务以使用CGLayer对象进行绘制:

请参阅示例:Example: Using Multiple CGLayer Objects to Draw a Flag以获取详细的代码示例。

1. Create a CGLayer Object Initialized with an Existing Graphics Context - 创建使用现有图形上下文初始化的CGLayer对象

函数CGLayerCreateWithContext返回使用现有图形上下文初始化的图层。 该图层继承了图形上下文的所有特征,包括颜色空间,大小,分辨率和像素格式。 稍后,当您将图层绘制到目标时,Quartz会自动将图层颜色与目标上下文匹配。

函数CGLayerCreateWithContext有三个参数:

  • 从中创建图层的图形上下文。 通常,您传递窗口图形上下文,以便稍后可以在屏幕上绘制图层。
  • 图层相对于图形上下文的大小。 图层可以与图形上下文相同或更小。 如果以后需要检索图层大小,可以调用函数CGLayerGetSize
  • 辅助字典。 此参数当前未使用,因此传递NULL

2. Get a Graphics Context for the Layer - 获取图层的图形上下文

Quartz总是绘制到图形上下文。 现在您有了一个图层,您必须创建一个与图层关联的图形上下文。 您在图层图形上下文中绘制的任何内容都是图层的一部分。

函数CGLayerGetContext将图层作为参数,并返回与图层关联的图形上下文。

3. Draw to the CGLayer Graphics Context - 绘制CGLayer图形上下文

获取与图层关联的图形上下文后,可以执行图层图形上下文中的任何绘图。 您可以打开PDF文件或图像文件,并将文件内容绘制到图层。 您可以使用任何Quartz 2D函数来绘制矩形,线条和其他绘图基元。 图12-3显示了绘制矩形和线到图层的示例。

Figure 12-3 A layer that contains two rectangles and a series of lines

例如,要将填充的矩形绘制到CGLayer图形上下文,可以调用函数CGContextFillRect,提供从函数CGLayerGetContext获取的图形上下文。 如果图形上下文名为myLayerContext,则函数调用如下所示:

CGContextFillRect(myLayerContext,myRect)

4. Draw the Layer to the Destination Graphics Context - 将图层绘制到目标图形上下文

准备好将图层绘制到目标图形上下文时,可以使用以下任一函数:

通常,您提供的目标图形上下文是窗口图形上下文,它与您用于创建图层的图形上下文相同。 图12-4显示了重复绘制图12-3所示图层图的结果。 要实现图案效果,可以重复调用图层绘制函数 - CGContextDrawLayerAtPointCGContextDrawLayerInRect - 每次更改偏移量。 例如,您可以调用函数CGContextTranslateCTM以在每次绘制图层时更改坐标空间的原点。

Figure 12-4 Drawing a layer repeatedly

注意:您不需要将图层绘制到用于初始化图层的相同图形上下文。 但是,如果将图层绘制到另一个图形上下文,则会对绘图施加原始图形上下文的任何限制。


Example: Using Multiple CGLayer Objects to Draw a Flag - 示例:使用多个CGLayer对象绘制旗帜

本节介绍如何使用两个CGLayer对象绘制屏幕上图12-5所示的国旗。 首先,您将看到如何将国旗缩减为简单的绘图基元,然后您将查看完成绘图所需的代码。

Figure 12-5 The result of using layers to draw the United States flag

从在屏幕上绘制它的角度来看,旗帜有三个部分:

  • 红色和白色条纹的图案。您可以将图案缩小为单个红色条纹,因为对于屏幕绘图,您可以采用白色背景。您创建一个红色矩形,然后在各种偏移处重复绘制矩形,以创建美国国旗所需的七个红色条纹。层是重复绘图的理想选择。您将红色矩形绘制到图层,然后在屏幕上绘制七次图层。
  • 一个蓝色的矩形。你需要一次蓝色矩形,所以使用一个层是没有好处的。在绘制蓝色矩形时,请直接在屏幕上绘制。
  • 50颗白色星星的图案。像红色条纹一样,一层是绘制星星的理想选择。您创建一个概述星形的路径,然后用白色填充路径。将一个星形绘制到一个图层,然后将该图层绘制50次,每次调整偏移量以获得适当的间距。

图12-2中的代码产生如图12-5所示的输出。列表后面会显示每个编号行代码的详细说明。列表相当长,因此您可能需要打印说明,以便在查看代码时可以阅读。从Cocoa应用程序中调用myDrawFlag例程。应用程序传递窗口图形上下文和矩形,该矩形指定与窗口图形上下文关联的视图的大小。

注意:在调用此程序或使用CGLayer对象的任何例程之前,必须检查以确保系统运行的是Mac OS X v10.4或更高版本,并且具有支持使用CGLayer对象的图形卡。

// Listing 12-1  Code that uses layers to draw a flag

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) 释放星形图层。

后记

本篇主要讲述了Core Graphics图层绘制,感兴趣的给个赞或者关注~~~

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

推荐阅读更多精彩内容