Core Graphics 之 Core Graphics Layer Drawing (十三)

Core Graphics Layer Drawing

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

层适用于以下情况:

您计划重用的绘图的高质量屏幕外呈现。例如,您可能正在构建场景并计划重用相同的背景。绘制背景场景到一个层,然后绘制层,无论何时你需要它。另外一个好处是,您不需要知道颜色空间或与设备相关的信息就可以绘制到一个层。
重复的画。例如,您可能希望创建一个由反复绘制的相同项组成的模式。将项目绘制到一个层,然后重复绘制该层,如图12-1所示。您重复绘制的任何Quartz对象——包括CGPath、CGShading和CGPDFPage对象——如果您将其绘制到CGLayer,那么它的性能将得到改善。注意,一个图层不只是用于屏幕绘图;您可以将其用于不面向屏幕的图形上下文,例如PDF图形上下文。
缓冲。尽管您可以为此使用图层,但您不需要这样做,因为Quartz合成体使您的部分不需要进行缓冲。如果必须绘制到缓冲区,则使用一个层而不是位图图形上下文。


屏幕快照 2018-08-28 上午10.56.04.png

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

How Layer Drawing Works

以CGLayerRef数据类型为代表的层被设计为最佳性能。如果可能的话,Quartz会使用一种适合于它所关联的石英图形上下文类型的机制来缓存CGLayer对象。例如,与显卡关联的图形上下文可能会将该层缓存到显卡上,这使得绘制层中的内容比绘制由位图图形上下文构造的类似图像要快得多。由于这个原因,对于屏幕外绘图,层通常是比位图图形上下文更好的选择。

所有石英绘图功能绘制到一个图形上下文。图形上下文提供目标的抽象,将您从目标的细节(如其分辨率)中解放出来。您在用户空间中工作,Quartz会执行必要的转换,以将绘图正确呈现到目的地。当您使用CGLayer对象进行绘图时,您也可以绘制到图形上下文。图12-1说明了图层绘制的必要步骤。


屏幕快照 2018-08-28 上午10.57.10.png

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

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

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

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

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

提示:当你想要合成一个图形的部分时,使用透明层来达到像隐藏一组对象这样的效果。(参见透明层)。使用CGLayer对象当你想画出屏幕外或当你需要重复画相同的东西。

Drawing with a Layer

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

使用现有的图形上下文创建CGLayer对象初始化
获取该层的图形上下文
绘制到CGLayer图形上下文
将层绘制到目标图形上下文
请参阅示例:使用多个CGLayer对象为详细的代码示例绘制标记。

Create a CGLayer Object Initialized with an Existing Graphics Context

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

CGLayerCreateWithContext函数需要三个参数:

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

Get a Graphics Context for the Layer

Quartz总是绘制到图形上下文。既然已经有了一个层,就必须创建一个与该层相关联的图形上下文。你在图层中绘制的任何图形都是图层的一部分。

函数CGLayerGetContext接受一个层作为参数,并返回一个与该层相关联的图形上下文。

Draw to the CGLayer Graphics Context

获得与层相关联的图形上下文后,可以对层图形上下文执行任何绘图操作。您可以打开一个PDF文件或图像文件,并将文件内容绘制到该层。您可以使用任何一个Quartz 2D函数来绘制矩形、直线和其他绘图原语。图12-3显示了将矩形和直线绘制到一个层的示例。


屏幕快照 2018-08-28 上午11.00.39.png

例如,要将填充矩形绘制到CGLayer图形上下文,需要调用CGContextFillRect函数,提供从CGLayerGetContext函数中获得的图形上下文。如果图形上下文命名为myLayerContext,那么函数调用如下所示

CGContextFillRect (myLayerContext, myRect)
Draw the Layer to the Destination Graphics Context

当你准备绘制层到它的目标图形上下文,你可以使用以下任一功能:

CGContextDrawLayerInRect,它将一个层绘制到指定矩形中的图形上下文。
CGContextDrawLayerAtPoint,它将层绘制到指定点的图形上下文。
通常,您提供的目标图形上下文是窗口图形上下文,它与创建层所用的图形上下文相同。图12-4显示了重复绘制图12-3所示的层图的结果。为了实现图形效果,您可以反复调用层绘制函数—cgcontextdrawlayeratpoint或cgcontextdrawlayerinrect—每次更改偏移量。例如,您可以在每次绘制层时调用函数CGContextTranslateCTM,以更改坐标空间的原点。


屏幕快照 2018-08-28 下午12.52.11.png

注意:您不需要将一个层绘制到用于初始化该层的相同图形上下文。然而,如果你把这个层绘制到另一个图形上下文,原始图形上下文的任何限制都会强加到你的绘图上。

Example: Using Multiple CGLayer Objects to Draw a Flag

本节展示如何使用两个CGLayer对象在屏幕上绘制如图12-5所示的标志。首先,您将看到如何将标记简化为简单的绘图原语,然后您将看到完成绘图所需的代码。


屏幕快照 2018-08-28 下午12.53.40.png

从屏幕绘制的角度来看,国旗分为三个部分:

红白条纹的图案。您可以将模式减少为单个红色条纹,因为对于屏幕绘图,您可以假定为白色背景。创建一个红色矩形,然后在不同的偏移位置重复绘制矩形,创建美国国旗所需的7条红色条纹。一层是理想的重复绘图层。将红色矩形绘制到一个层,然后在屏幕上绘制该层7次。
一个蓝色的矩形。你需要蓝色的矩形一次,所以使用一个图层是没有好处的。当需要绘制蓝色矩形时,直接在屏幕上绘制。
50颗白色星星的图案。像红色条纹一样,一层是画星星的理想选择。创建一个轮廓星形的路径,然后用白色填充路径。在一个图层上画一个星号,然后画50次,每次调整偏移以得到合适的间距。
图12-2中的代码产生了图12-5所示的输出。清单后面显示了每一行代码的详细说明。清单相当长,所以您可能希望打印说明,以便在查看代码时能够阅读。myDrawFlag例程是从Cocoa应用程序中调用的。应用程序传递一个窗口图形上下文和一个指定与窗口图形上下文关联的视图大小的矩形。

注意:在调用这个或任何使用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循环将设置代码来重复绘制星号层,以便标记上的5个奇数行包含6个星号。
30、将星形层绘制到窗口图形上下文。回想一下,星层包含一颗白星。
31、定位CTM,使原点移动到右边,为绘制下一个恒星做准备。
32、定位CTM,使原点向上移动,为绘制下一行恒星做准备。
33、翻译CTM,使原点位于星场,定位在从底部第二排的第一颗星(左边)。注意,偶数行相对于奇数行是偏移的。
34、这个循环和下一个for循环将设置代码来重复绘制星号层,以便标记上的4行偶数都包含5颗星。
35、将星形层绘制到窗口图形上下文。
36、定位CTM,使原点移动到右边,为绘制下一个恒星做准备。
37、放置CTM,使原点向下和向左,为绘制下一行恒星做准备。
38、释放条纹层。
39、发布明星层。

你可能感兴趣的:(Core Graphics 之 Core Graphics Layer Drawing (十三))