Xamarin.iOS の Core Graphics
この記事では、Core Graphics iOS フレームワークについて説明します。 Core Graphics を使用してジオメトリ、イメージ、PDF を描画する方法を示します。
iOS には、低レベルの描画のサポートを提供する Core Graphics フレームワークが含まれています。 これらのフレームワークにより、UIKit 内の豊富なグラフィカル機能が有効になります。
Core Graphics は、デバイスに依存しないグラフィックスを描画できる低レベルの 2D グラフィックス フレームワークです。 UIKit のすべての 2D 描画では、Core Graphics が内部的に使用されます。
Core Graphics では、次のようなさまざまなシナリオでの描画がサポートされています。
-
UIView
を使用して画面に描画する。 - メモリ内または画面上でイメージを描画する。
- PDF を作成して描画する。
- 既存の PDF を読み取って描画する。
幾何学的空間
シナリオに関係なく、Core Graphics を使用して行われるすべての描画が幾何学的空間で行われます。つまり、ピクセルではなく抽象点として動作します。 ユーザーは、ジオメトリや描画状態 (色、線のスタイルなど) の観点から描画する内容を記述します。Core Graphics は、すべてをピクセルに変換します。 このような状態がグラフィックス コンテキストに追加されます。これは、画家のキャンバスのようなものと見なすことができます。
このアプローチにはいくつかの利点があります。
- 描画コードは動的になるため、実行時にグラフィックスを変更できるようになります。
- アプリケーション バンドル内の静的イメージの必要性が減るため、アプリケーション のサイズを小さくすることができます。
- デバイス間の解像度の変更に対するグラフィックスの回復性が高まります。
UIView サブクラスでの描画
すべての UIView
に、描画する必要があるときにシステムによって呼び出される Draw
メソッドがあります。 ビューに描画コードを追加するには、UIView
をサブクラス化し、Draw
をオーバーライドします。
public class TriangleView : UIView
{
public override void Draw (CGRect rect)
{
base.Draw (rect);
}
}
Draw を直接呼び出すべきではありません。 これは、実行ループの処理中にシステムによって呼び出されます。 実行ループを介してビューがビュー階層に初めて追加されると、その Draw
メソッドが呼び出されます。 後続の Draw
に対する呼び出しは、ビューで SetNeedsDisplay
または SetNeedsDisplayInRect
を呼び出すことでビューが描画が必要としてマークされたときに発生します。
グラフィックス コードのパターン
Draw
実装内のコードでは、描画する必要がある内容を記述する必要があります。 描画コードは、描画状態を設定し、描画を要求するメソッドを呼び出すパターンに従います。 このパターンは、次のように一般化できます。
グラフィックス コンテキストを取得します。
描画属性を設定します。
描画プリミティブからジオメトリを作成します。
Draw メソッドまたは Stroke メソッドを呼び出します。
基本的な描画の例
たとえば、次のコード スニペットを考えてみます。
//get graphics context
using (CGContext g = UIGraphics.GetCurrentContext ()) {
//set up drawing attributes
g.SetLineWidth (10);
UIColor.Blue.SetFill ();
UIColor.Red.SetStroke ();
//create geometry
var path = new CGPath ();
path.AddLines (new CGPoint[]{
new CGPoint (100, 200),
new CGPoint (160, 100),
new CGPoint (220, 200)});
path.CloseSubpath ();
//add geometry to graphics context and draw it
g.AddPath (path);
g.DrawPath (CGPathDrawingMode.FillStroke);
}
このコードを分解してみましょう。
using (CGContext g = UIGraphics.GetCurrentContext ()) {
...
}
この行では、描画に使用する現在のグラフィックス コンテキストを最初に取得します。 グラフィックス コンテキストは、描画が行われるキャンバスである見なすことができます。これには、描画するジオメトリだけでなく、ストロークや塗りつぶしの色など、描画に関するすべての状態が含まれます。
g.SetLineWidth (10);
UIColor.Blue.SetFill ();
UIColor.Red.SetStroke ();
グラフィックス コンテキストを取得した後、上に示すように、描画時に使用するいくつかの属性が設定されます。 この場合、線の幅、ストローク、塗りつぶしの色が設定されます。 その後の描画では、これらの属性が使用されます。なぜなら、これらはグラフィックス コンテキストの状態内に保持されるからです。
ジオメトリを作成するために、線と曲線からグラフィックス パスを記述することを可能にする CGPath
が使用されます。 この場合、このパスが、点の配列をつなげて三角形を構成する線を追加します。 次に示すように、Core Graphics はビューの描画に座標系を使用します。この場合、原点は左上にあり、正の x 方向は右方向、正の y 方向は下方向にあります。
var path = new CGPath ();
path.AddLines (new CGPoint[]{
new CGPoint (100, 200),
new CGPoint (160, 100),
new CGPoint (220, 200)});
path.CloseSubpath ();
パスが作成されたら、このパスがグラフィックス コンテキストに追加され、AddPath
と DrawPath
をそれぞれ呼び出すことでグラフィックス コンテキストで描画できるようにします。
この結果のビューを次に示します。
グラデーションの塗りつぶしの作成
より豊富な形式の描画も利用できます。 たとえば、Core Graphics では、グラデーションの塗りつぶしを作成し、クリッピング パスを適用できます。 前の例のパス内にグラデーションの塗りつぶしを描画するには、最初にパスをクリッピング パスとして設定する必要があります。
// add the path back to the graphics context so that it is the current path
g.AddPath (path);
// set the current path to be the clipping path
g.Clip ();
現在のパスをクリッピング パスとして設定すると、線形グラデーションを描画する次のコードのように、パスのジオメトリ内の後続の描画がすべて制約されます。
// the color space determines how Core Graphics interprets color information
using (CGColorSpace rgb = CGColorSpace.CreateDeviceRGB()) {
CGGradient gradient = new CGGradient (rgb, new CGColor[] {
UIColor.Blue.CGColor,
UIColor.Yellow.CGColor
});
// draw a linear gradient
g.DrawLinearGradient (
gradient,
new CGPoint (path.BoundingBox.Left, path.BoundingBox.Top),
new CGPoint (path.BoundingBox.Right, path.BoundingBox.Bottom),
CGGradientDrawingOptions.DrawsBeforeStartLocation);
}
これらの変更により、次に示すようにグラデーションの塗りつぶしが生成されます。
線パターンの変更
線の描画属性も、Core Graphics を使用して変更できます。 これには、次のコードに示すように、線の幅、ストロークの色、線パターン自体の変更が含まれます。
//use a dashed line
g.SetLineDash (0, new nfloat[] { 10, 4 * (nfloat)Math.PI });
次に示すように、任意の描画操作の前にこのコードを追加すると、長さが 10 単位で間隔が 4 単位の破線のストロークが作成されます。
Xamarin.iOS で Unified API を使用する場合、配列型は nfloat
である必要があり、Math.PI に明示的にキャストされることも必要であることに注意してください。
イメージとテキストの描画
Core Graphics では、ビューのグラフィックス コンテキストでのパスの描画に加えて、画像とテキストの描画もサポートされています。 イメージを描画するには、単純に CGImage
を作成して DrawImage
呼び出しに渡します。
public override void Draw (CGRect rect)
{
base.Draw (rect);
using(CGContext g = UIGraphics.GetCurrentContext ()){
g.DrawImage (rect, UIImage.FromFile ("MyImage.png").CGImage);
}
}
ただし、この場合、次に示すように、逆さまで描画されたイメージが生成されます。
その理由は、イメージ描画の Core Graphics の原点が左下にある一方、ビューの原点は左上にあるからです。 そのため、画像を正しく表示するには、原点を変更する必要があります。これは、 現在の変換マトリックス (CTM)を変更することで実現できます。 CTM は、点が存在する場所 (ユーザー空間とも呼ばれます) を定義します。 CTM を y 方向に反転し、負の y 方向に境界の高さ分シフトすると、イメージを反転させることができます。
グラフィックス コンテキストには、CTM を変換するためのヘルパー メソッドがあります。 この場合、次に示すように、ScaleCTM
が描画を "反転" し、TranslateCTM
が描画を左上にシフトします。
public override void Draw (CGRect rect)
{
base.Draw (rect);
using (CGContext g = UIGraphics.GetCurrentContext ()) {
// scale and translate the CTM so the image appears upright
g.ScaleCTM (1, -1);
g.TranslateCTM (0, -Bounds.Height);
g.DrawImage (rect, UIImage.FromFile ("MyImage.png").CGImage);
}
その後、その結果のイメージが直立して表示されます。
重要
グラフィックス コンテキストへの変更は、後続のすべての描画操作に適用されます。 したがって、CTM を変換すると、追加の任意の描画に影響します。 たとえば、CTM 変換の後に三角形を描画した場合、これは逆さまに表示されます。
イメージへのテキストの追加
パスやイメージの場合と同様に、Core Graphics を使用したテキストの描画には、グラフィックスの状態を設定し、描画するメソッドを呼び出す、同じ基本的なパターンが伴います。 テキストの場合、テキストを表示するメソッドは ShowText
です。 イメージの描画の例に次のコードを追加すると、Core Graphics を使用してテキストが描画されます。
public override void Draw (RectangleF rect)
{
base.Draw (rect);
// image drawing code omitted for brevity ...
// translate the CTM by the font size so it displays on screen
float fontSize = 35f;
g.TranslateCTM (0, fontSize);
// set general-purpose graphics state
g.SetLineWidth (1.0f);
g.SetStrokeColor (UIColor.Yellow.CGColor);
g.SetFillColor (UIColor.Red.CGColor);
g.SetShadow (new CGSize (5, 5), 0, UIColor.Blue.CGColor);
// set text specific graphics state
g.SetTextDrawingMode (CGTextDrawingMode.FillStroke);
g.SelectFont ("Helvetica", fontSize, CGTextEncoding.MacRoman);
// show the text
g.ShowText ("Hello Core Graphics");
}
ご覧のように、テキスト描画に対するグラフィックス状態の設定は、ジオメトリ描画と似ています。 ただし、テキスト描画の場合は、テキスト描画モードとフォントも適用されます。 この場合、影も適用されますが、影の適用はパス描画と同じように動作します。
結果として生成されるテキストは、次に示すようにイメージと共に表示されます。
メモリに基づくイメージ
Core Graphics では、ビューのグラフィックス コンテキストへの描画に加えて、メモリに基づくイメージの描画 (オフスクリーン描画とも呼ばれます) がサポートされています。 そのためには、次の作業を行う必要があります。
- メモリ内ビットマップに基づくグラフィックス コンテキストの作成
- 描画状態の設定と描画コマンドの発行
- コンテキストからのイメージの取得
- コンテキストの削除
ビューによってコンテキストが提供される Draw
メソッドとは異なり、この場合は、次の 2 つの方法のいずれかでコンテキストを作成します。
UIGraphics.BeginImageContext
(またはBeginImageContextWithOptions
) を呼び出す新しい
CGBitmapContextInstance
を作成する
CGBitmapContextInstance
は、カスタム イメージ操作アルゴリズムを使用する場合など、イメージ ビットを直接操作する場合に便利です。 それ以外の場合はすべて、BeginImageContext
または BeginImageContextWithOptions
を使用する必要があります。
イメージ コンテキストが作成されたら、描画コードの追加は UIView
サブクラス内の場合と同じです。 たとえば、三角形を描画するために前に使用したコード例を使用して次に示すように、UIView
内の場合の代わりに、メモリ内のイメージに描画するために使用できます。
UIImage DrawTriangle ()
{
UIImage triangleImage;
//push a memory backed bitmap context on the context stack
UIGraphics.BeginImageContext (new CGSize (200.0f, 200.0f));
//get graphics context
using(CGContext g = UIGraphics.GetCurrentContext ()){
//set up drawing attributes
g.SetLineWidth(4);
UIColor.Purple.SetFill ();
UIColor.Black.SetStroke ();
//create geometry
path = new CGPath ();
path.AddLines(new CGPoint[]{
new CGPoint(100,200),
new CGPoint(160,100),
new CGPoint(220,200)});
path.CloseSubpath();
//add geometry to graphics context and draw it
g.AddPath(path);
g.DrawPath(CGPathDrawingMode.FillStroke);
//get a UIImage from the context
triangleImage = UIGraphics.GetImageFromCurrentImageContext ();
}
return triangleImage;
}
メモリに基づくビットマップへの描画の一般的な用途は、任意の UIView
からイメージをキャプチャすることです。 たとえば、次のコードでは、ビューのレイヤーをビットマップ コンテキストにレンダリングし、そこから UIImage
を作成します。
UIGraphics.BeginImageContext (cellView.Frame.Size);
//render the view's layer in the current context
anyView.Layer.RenderInContext (UIGraphics.GetCurrentContext ());
//get a UIImage from the context
UIImage anyViewImage = UIGraphics.GetImageFromCurrentImageContext ();
UIGraphics.EndImageContext ();
PDF の描画
Core Graphics では、イメージに加えて PDF 描画もサポートされています。 イメージと同様に、PDF をメモリにレンダリングしたり、UIView
でレンダリングするために PDF を読み取ったりできます。
UIView の PDF
Core Graphics では、ファイルから PDF を読み取り、CGPDFDocument
クラスを使用してこれをビューでレンダリングすることもサポートしています。 CGPDFDocument
クラスは、コード内の PDF を表し、ページの読み取りと描画に使用できます。
たとえば、UIView
サブクラス内の次のコードは、PDF をファイルから CGPDFDocument
に読み込みます。
public class PDFView : UIView
{
CGPDFDocument pdfDoc;
public PDFView ()
{
//create a CGPDFDocument from file.pdf included in the main bundle
pdfDoc = CGPDFDocument.FromFile ("file.pdf");
}
public override void Draw (Rectangle rect)
{
...
}
}
その後、次に示すように、Draw
メソッドは CGPDFDocument
を使用してページを CGPDFPage
に読み込み、DrawPDFPage
を呼び出してこれをレンダリングできます。
public override void Draw (CGRect rect)
{
base.Draw (rect);
//flip the CTM so the PDF will be drawn upright
using (CGContext g = UIGraphics.GetCurrentContext ()) {
g.TranslateCTM (0, Bounds.Height);
g.ScaleCTM (1, -1);
// render the first page of the PDF
using (CGPDFPage pdfPage = pdfDoc.GetPage (1)) {
//get the affine transform that defines where the PDF is drawn
CGAffineTransform t = pdfPage.GetDrawingTransform (CGPDFBox.Crop, rect, 0, true);
//concatenate the pdf transform with the CTM for display in the view
g.ConcatCTM (t);
//draw the pdf page
g.DrawPDFPage (pdfPage);
}
}
}
メモリに基づく PDF
メモリ内 PDF の場合、BeginPDFContext
を呼び出して PDF コンテキストを作成する必要があります。 PDF への描画は、ページごとに細分化されます。 各ページは、BeginPDFPage
を呼び出すことによって開始され、EndPDFContent
を呼び出すことによって完了し、その間にグラフィックス コードがあります。 また、イメージ描画の場合と同様に、メモリに基づく PDF 描画では左下にある原点が使用されます。これには、イメージの場合と同様に CTM を変更することで対応できます。
次のコードは、PDF にテキストを描画する方法を示しています。
//data buffer to hold the PDF
NSMutableData data = new NSMutableData ();
//create a PDF with empty rectangle, which will configure it for 8.5x11 inches
UIGraphics.BeginPDFContext (data, CGRect.Empty, null);
//start a PDF page
UIGraphics.BeginPDFPage ();
using (CGContext g = UIGraphics.GetCurrentContext ()) {
g.ScaleCTM (1, -1);
g.TranslateCTM (0, -25);
g.SelectFont ("Helvetica", 25, CGTextEncoding.MacRoman);
g.ShowText ("Hello Core Graphics");
}
//complete a PDF page
UIGraphics.EndPDFContent ();
その結果生成されるテキストは PDF に描画されます。その後、この PDF を NSData
内に収容し、保存、アップロード、メール送信などを行うことができます。
まとめ
この記事では、Core Graphics フレームワークを介して提供されるグラフィックス機能について説明しました。 また、Core Graphics を使用して、UIView,
のコンテキスト内で、およびメモリーに基づくグラフィックス コンテキストにジオメトリ、イメージ、PDF を描画する方法を確認しました。