次の方法で共有


Xamarin.iOS の Core Graphics

この記事では、Core Graphics iOS フレームワークについて説明します。 Core Graphics を使用してジオメトリ、イメージ、PDF を描画する方法を示します。

iOS には、低レベルの描画のサポートを提供する Core Graphics フレームワークが含まれています。 これらのフレームワークにより、UIKit 内の豊富なグラフィカル機能が有効になります。

Core Graphics は、デバイスに依存しないグラフィックスを描画できる低レベルの 2D グラフィックス フレームワークです。 UIKit のすべての 2D 描画では、Core Graphics が内部的に使用されます。

Core Graphics では、次のようなさまざまなシナリオでの描画がサポートされています。

幾何学的空間

シナリオに関係なく、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 実装内のコードでは、描画する必要がある内容を記述する必要があります。 描画コードは、描画状態を設定し、描画を要求するメソッドを呼び出すパターンに従います。 このパターンは、次のように一般化できます。

  1. グラフィックス コンテキストを取得します。

  2. 描画属性を設定します。

  3. 描画プリミティブからジオメトリを作成します。

  4. 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 ();

パスが作成されたら、このパスがグラフィックス コンテキストに追加され、AddPathDrawPath をそれぞれ呼び出すことでグラフィックス コンテキストで描画できるようにします。

この結果のビューを次に示します。

サンプル出力の三角形

グラデーションの塗りつぶしの作成

より豊富な形式の描画も利用できます。 たとえば、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 つの方法のいずれかでコンテキストを作成します。

  1. UIGraphics.BeginImageContext (または BeginImageContextWithOptions) を呼び出す

  2. 新しい 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 を描画する方法を確認しました。