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閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容