Core Graphics のレイヤー描画
CGLayer オブジェクト(CGLayerRef データ型) では、アプリケーションでレイヤーを使用して描画することができます。
レイヤーは以下のものに適しています:
- 再利用する計画の描画の高品質なオフスクリーンレンダリング。たとえば、シーンを構築して、同じ背景を再利用する計画があるとします。背景シーンをレイヤーに描画し、必要に応じてレイヤーを描画します。加わった 1 つの利点は、レイヤーに描画するための色空間やデバイス依存の情報を知る必要がないことです。
- 繰り返し描画。たとえば、同じアイテムを何度も何度も繰り返して描いたパターンを作成することができます。図 12-1 に示したように、アイテムをレイヤーに描画し、レイヤーを繰り返し描画します。 CGPath、CGShading、および CGPDFPage オブジェクトを含む、繰り返し描画する全ての Quartz オブジェクトは、CGLayer に描画するとパフォーマンスが向上します。レイヤーはスクリーン上の描画用だけではないことに注意してください。PDF グラフィックスコンテキストのように、スクリーン指向でないグラフィックスコンテキストにも使用できます。
- バッファリング。この目的のためにレイヤーを使用することはできますが、Quartz Compositor がバッファリングを不要にするため、レイヤーを使用する必要はありません。バッファに描画しなければならない場合は、ビットマップグラフィックスコンテキストの代わりにレイヤーを使用して下さい。
図 12-1 同じ蝶のイメージを繰り返しペイント
CGLayer オブジェクトと透過レイヤーは、CGContext 関数によって作成された CGPath オブジェクトおよびパスと平行です。CGLayer または CGPath オブジェクトの場合は、抽象的な宛先にペイントし、後で完全なペイントをディスプレイや PDF などの別の宛先に描画できます。透過レイヤーにペイントするか、パスを描画する CGContext 関数を使用すると、グラフィックスコンテキストによって表される宛先に直接描画されます。描画を組み立てるための中間的な抽象的な宛先はありません。
レイヤー描画のしくみ
CGLayerRef データ型で表されるレイヤーは、最適なパフォーマンスを実現するように設計されています。可能であれば、Quartz は関連する Quartz グラフィックスコンテキスト型に適したメカニズムを使用して CGLayer オブジェクトをキャッシュします。たとえば、ビデオカードに関連するグラフィックスコンテキストは、ビデオカード上のレイヤーをキャッシュし、ビットマップグラフィックスコンテキストで構築された同様のイメージをレンダリングするよりも、レイヤ内にあるコンテンツを非常に高速に描画できます。このため、通常、オフスクリーン描画ではビットマップグラフィックスコンテキストよりもレイヤーが優れています。
すべての Quartz 描画関数はグラフィックスコンテキストに描画されます。 グラフィックスコンテキストは、宛先の抽象化を提供し、解像度などの宛先の詳細から解放します。ユーザー空間で作業すると、Quartz は必要な変換を実行して、描画を宛先に正しくレンダリングします。描画用に CGLayer オブジェクトを使用する場合は、グラフィックスコンテキストに描画することもできます。図 12-2 に、レイヤー描画に必要な手順を示します。
図 12-2 レイヤー描画
すべてのレイヤー描画は、CGLayerCreateWithContext 関数を使用してそこから CGLayer オブジェクトを作成するグラフィックスコンテキストから始まります。CGLayer オブジェクトの作成に使用されるグラフィックスコンテキストは通常、ウィンドウグラフィックスコンテキストです。Quartz はグラフィックスコンテキストのすべての特性(解像度、色空間、及びグラフィックスの状態設定) を持つようにレイヤーを作成します。グラフィックスコンテキストのサイズを使用したくない場合は、レイヤーのサイズを指定できます。図 12-2 では、左側にレイヤーの作成に使用されたグラフィックスコンテキストを示します。右側のボックスの灰色部分(CGLayer オブジェクト) は、新しく作成されたレイヤーを表します。
レイヤーに描画する前に、CGLayerGetContext 関数を呼び出してレイヤーに関連するグラフィックスコンテキストを取得しなければなりません。このグラフィックスコンテキストは、レイヤーの作成に使用されたグラフィックスコンテキストと同じです。レイヤーを作成するために使用されたグラフィックスコンテキストがウィンドウグラフィックスコンテキストである限り、可能であれば CGLayer グラフィックスコンテキストは GPU にキャッシュされます。図 12-2 の右側のボックスの白い部分は、新たに作成されたレイヤーグラフィックスコンテキストを表します。
任意のグラフィックスコンテキストに描画するのと同じように、レイヤーのグラフィックコンテキストに描画し、そのレイヤーのグラフィックコンテキストを描画関数に渡します。図 12-2 は、レイヤーコンテキストに描画された葉っぱの形を示しています。
レイヤーのコンテンツを使用する準備ができたら、CGContextDrawLayerInRect 関数または CGContextDrawLayerAtPoint 関数を呼び出して、グラフィックスコンテキストにレイヤーを描画します。通常は、レイヤーオブジェクトの作成に使用したのと同じグラフィックスコンテキストに描画しますが、これは必須ではありません。どのようなグラフィックスコンテキストにもレイヤーを描画できます。レイヤー描画にはレイヤーオブジェクトの作成に使用されるグラフィックスコンテキストの特性があり、一定の制約(例えばパフォーマンスや解像度など) が発生する可能性があります。たとえば、スクリーンに関連したレイヤーがビデオハードウェアにキャッシュされます。宛先コンテキストが印刷または PDF コンテキストの場合は、グラフィックハードウェアからメモリにフェッチする必要があり、パフォーマンスが低下する可能性があります。
図 12-2 に、レイヤーの内容、葉っぱを示します。レイヤーオブジェクトの作成に使用されたグラフィックスコンテキストへの描画の繰り返しです。CGLayer オブジェクトを解放する前に、必要な回数だけレイヤー内の描画を再利用できます。
レイヤーを使った描画
CGLayer オブジェクトを使用して描画するには、以下の節で説明するタスクを実行する必要があります。
- 既存のグラフィックスコンテキストで初期化された CGLayer オブジェクトを作成
- レイヤー用のグラフィックコンテキストを取得
- CGLayer グラフィックスコンテキストに描画
- 宛先グラフィックスコンテキストへのレイヤーの描画
詳細なコード例については、複数の CGLayer オブジェクトを使用してフラグを描画する例 を参照してください。
既存のグラフィックスコンテキストで初期化された CGLayer オブジェクトを作成
CGLayerCreateWithContext 関数は、既存のグラフィックスコンテキストで初期化されたレイヤーを返します。このレイヤーは、色空間、サイズ、解像度、ピクセル形式などを含む、グラフィックスコンテキストのすべての特性を継承します。後で、レイヤーを宛先に描画すると、Quartz はレイヤーを自動的に宛先のコンテキストにマッチさせます。
CGLayerCreateWithContext 関数は 3 つのパラメータをとります:
- レイヤーをそこから作成するグラフィックスコンテキスト。通常、後でオンスクリーンにレイヤーを描画できるように、ウィンドウグラフィックスコンテキストを渡します。
- グラフィックスコンテキストに対するレイヤーのサイズ。レイヤーはグラフィックスコンテキストと同じサイズか小さくすることもできます。後でレイヤーサイズを取得する必要がある場合は、CGLayerGetSize 関数を呼び出すことができます。
- 補助の辞書。このパラメータは現在使用されていないため、NULL を渡します。
レイヤー用のグラフィックコンテキストを取得
Quartz は、常にグラフィックスコンテキストに描画します。レイヤーを作成したら、レイヤーに関連したグラフィックスコンテキストを作成しなければなりません。レイヤーグラフィックスコンテキストに描画した全てのものは、レイヤの一部です。
CGLayerGetContext 関数は、レイヤーをパラメーターとして取り、レイヤーに関連したグラフィックスコンテキストを返します。
CGLayer グラフィックスコンテキストに描画
レイヤーに関連したグラフィックスコンテキストを取得したら、レイヤーグラフィックスコンテキストへの任意の描画を実行できます。PDF ファイルまたはイメージファイルを開き、レイヤーにファイルの内容を描画できます。任意の Quartz 2D 関数を使用して、長方形、線、およびその他の描画の原型を描画できます。図 12-3 に、レイヤーへの長方形や線の描画の例を示します。
図 12-3 2 つの長方形と一連の線を含むレイヤー
たとえば、塗りつぶしの長方形を CGLayer グラフィックスコンテキストに描画するには、CGLayerGetContext 関数から取得したグラフィックスコンテキストを提供する CGContextFillRect 関数を呼び出します。グラフィックスコンテキストの名前が myLayerContext の場合、関数呼び出しは以下のようになります。
CGContextFillRect (myLayerContext, myRect)
宛先グラフィックスコンテキストへのレイヤーの描画
宛先グラフィックスコンテキストにレイヤーを描画する準備ができたら、以下のいずれかの関数を使用できます。
- CGContextDrawLayerInRect:指定された長方形内のグラフィックスコンテキストにレイヤーを描画します。
- CGContextDrawLayerAtPoint:指定した点のグラフィックスコンテキストにレイヤーを描画します。
通常、指定した宛先グラフィックスコンテキストは、ウィンドウグラフィックスコンテキストであり、レイヤーの作成に使用するのと同じグラフィックスコンテキストです。図 12-4 は、図 12-3 に示したレイヤー図面を繰り返し描画した結果を示しています。パターン化された効果を実現するには、CGContextDrawLayerAtPoint または CGContextDrawLayerInRect のいずれかのレイヤー描画関数を繰り返し呼び出して、毎回オフセットを変更します。例えば、CGContextTranslateCTM 関数を呼び出して、レイヤーを描画するたびに座標空間の原点を変更できます。
図 12-4 レイヤーを繰り返し描画
複数の CGLayer オブジェクトを使用して旗を描画する例
この節では、2 つの CGLayer オブジェクトを使用して、図 12-5 に示した旗をオンスクリーンに描画する方法を示します。最初に、単純な描画の基本に旗を分解す方法を見てから、描画を行うために必要なコードを見ていきます。
図 12-5 レイヤーを使用してアメリカの旗を描いた結果
それを画面上に描くという観点から、この旗には 3 つの部分があります。
- 赤と白の縞模様のパターン。オンスクリーン描画の場合は、白い背景を想定できるため、パターンを 1 つの赤いストライプに縮小できます。1 つの赤い長方形を作成し、さまざまなオフセットで長方形を繰り返し描き、アメリカの旗に必要な 7 つの赤い縞を作成します。繰り返し描画にはレイヤーが理想的です。赤い長方形をレイヤーに描画し、オンスクリーンにレイヤーを 7 回描画します。
- 青い長方形。一度だけ青い矩形が必要なので、レイヤーを使用しても効果はありません。青い長方形を描画するときには、それを直接オンスクリーンに描画します。
- 50 個の白い星のパターン。赤いストライプのように、レイヤーは星を描くのに理想的です。星型の輪郭を描くパスを作成し、パスを白く塗りつぶします。1 つのレイヤーに 1 つの星を描画し、そのレイヤーを 50 回描画し、毎回オフセットを調整して適切な間隔を得ます。
リスト 12-1 のコードは、図 12-5 に示した出力を生成します。番号付きコード行の詳細な説明は、リストの後に表示します。リストはかなり長いので、説明を印刷して、コードを見ながら読んだ方がいいかもしれません。myDrawFlag ルーチンは、Cocoa アプリケーション内から呼び出されます。アプリケーションは、ウィンドウグラフィックスコンテキストと、ウィンドウグラフィックスコンテキストに関連したビューのサイズを指定する長方形を渡します。
リスト 12-1 レイヤーを使用してフラグを描画するコード
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 つの星をトレースする線を指定する点の配列を宣言します。
- 一つのストライプの形である長方形を作成します。
- 星のフィールドの形である長方形を作成します。
- myDrawFlag ルーチンに渡されるウィンドウグラフィックスコンテキストと同じサイズの境界ボックスを作成します。
- myDrawFlag ルーチンに渡されるウィンドウグラフィックスコンテキストで初期化されるレイヤーを作成します。
- そのレイヤーに関連しているグラフィックスコンテキストを取得します。このレイヤーをストライプの描画に使用します。
- ストライプのレイヤーに関連したグラフィックスコンテキストの塗りつぶし色を不透明な赤色に設定します。
- 1 つの赤いストライプを表す長方形を塗りつぶします。
- myDrawFlag ルーチンに渡されるウィンドウグラフィックスコンテキストで初期化される別のレイヤーを作成します。
- そのレイヤーに関連しているグラフィックスコンテキストを取得します。このレイヤーを星の描画に使用します。
- 星のレイヤーに関連したグラフィックスコンテキストの塗りつぶし色を不透明な白に設定します。
- myStarPoints 配列で定義された 10 行を、星のレイヤーに関連したコンテキストに追加します。
- 追加したばかりの 10 行からなるパスを塗りつぶします。
- ウィンドウグラフィックスコンテキストのグラフィックス状態を保存します。同じストライプを繰り返し別の場所に配置して描くので、これを行う必要があります。
- フラグの赤いストライプごとに 1 回、7 回反復するループを設定します。
- ストライプのレイヤーを描画します(単一の赤いストライプから構成されています)。
- 次の赤いストライプを描画する位置に原点が配置されるように、現在の変換配列を変換します。
- グラフィックス状態を復元し、ストライプを描画する前の状態にします。
- 星のフィールドの塗りつぶし色を青の適切なシェードに設定します。注意:この色の不透明度は 1.0 です。この例のすべての色は不透明ですが、そうである必要はありません。部分的に透明な色を使用して、レイヤー描画で素敵な効果を作成できます。0.0 のアルファ値は透明な色を指定することを思い出してください。
- 星のフィールドの長方形を青で塗りつぶします。この長方形を直接ウィンドウグラフィックスコンテキストに描画します。何かを一度だけ描いている場合は、レイヤーを使用しないでください。
- CTM を変換して星を適切に配置するので、ウィンドウグラフィックスコンテキストのグラフィックス状態を保存します。
- CTM を変換して、最初の(最も下の) 行の最初の星(左側) に位置する星のフィールドに原点があるようにします。
- このループと次の for ループは、フラグ上の 5 つの奇数の行にそれぞれ 6 つの星が含まれるように、星のレイヤーを繰り返し描画するコードを設定します。
- ウィンドウグラフィックスコンテキストに星のレイヤーを描画します。星のレイヤーには 1 つの白い星が含まれていることを思い出してください。
- 次の星を描くために原点が右に移動するように CTM を配置します。
- 次の星の行を描くため、原点が上に移動するように CTM を配置します。
- CTM を変換して、原点が星のフィールドにあり、下から 2 番目の行の最初の星(左側) に配置されます。偶数行は奇数行に対してオフセットされていることに注意してください。
- このループと次の for ループは、フラグの 4 つの偶数行にそれぞれ 5 つの星が含まれるように、星のレイヤーを繰り返し描画するコードを設定します。
- ウィンドウグラフィックスコンテキストに星のレイヤーを描画します。
- 次の星を描くために原点が右に移動するように CTM を配置します。
- 次の星の行を描くために、原点が左下になるように CTM を配置します。
- ストライプのレイヤーを解放します。
- 星のレイヤーを解放します。
前の章 次の章