次の方法で共有


ピクセル、およびデバイスに依存しない単位

SkiaSharp 座標と Xamarin.Forms 座標の違いを詳しく知る

この記事では、SkiaSharp で使用する座標系と Xamarin.Forms で使用する座標系との違いを詳しく説明します。 また、2 つの座標系間の変換や、次の例のように、特定の領域いっぱいにグラフィックを描く方法についても説明します。

画面を塗りつぶす楕円

Xamarin.Forms のプログラミングにある程度長く携わり、Xamarin.Forms の座標とサイズの感覚が身についている方には、 この前 2 本の記事で示した円の描画例は少し小さすぎると思われたかもしれません。

実際、それらは Xamarin.Forms のサイズから考えると小さい円でした。 SkiaSharp の場合、既定ではピクセル単位で描画が行われるのに対し、Xamarin.Forms においては、基礎にあるプラットフォームで採用されているデバイス非依存の単位に基づいた座標とサイズが使用されます (Xamarin.Forms 座標系の詳細については、「第 5 章: サイズの処理」(書籍『Xamarin.Forms でモバイルアプリを作成する』) を参照してください。

サンプル プログラムの Surface Size というページでは、以下 3 つの異なる情報源から表示サーフェイスのサイズを取得し、SkiaSharp のテキスト出力で表示しています。

  • SKCanvasView オブジェクトの通常の Xamarin.FormsWidth および Height プロパティ。
  • CanvasSize オブジェクトの SKCanvasView プロパティです。
  • SKImageInfo 値の Size プロパティ。これは、この前 2 つのページで使用した Width および Height プロパティの値に合致します。

SurfaceSizePage クラスは、これらの値を表示する方法を示しています。 次のコンストラクターでは、SKCanvasView オブジェクトをフィールドの 1 つに保存し、PaintSurface イベント ハンドラーからアクセスできるようにしています。

SKCanvasView canvasView;

public SurfaceSizePage()
{
    Title = "Surface Size";

    canvasView = new SKCanvasView();
    canvasView.PaintSurface += OnCanvasViewPaintSurface;
    Content = canvasView;
}

SKCanvas には 6 つの異なる DrawText メソッドが用意されていますが、この DrawText が最もシンプルなものです。

public void DrawText (String text, Single x, Single y, SKPaint paint)

テキスト文字列、描画開始位置の X 座標と Y 座標、および SKPaint オブジェクトを引数で指定します。 X 座標はテキストの左側の位置ですが、Y 座標は、テキストの "ベースライン" 位置を指定するものであることに注意が必要です。 ベースラインとは、英字の下端を揃える基準線です (文字 g、p、q、y などの下へ伸びる部分を除く)。罫線入りノートに手書きした英字を思い浮かべるとわかりやすいでしょう。

SKPaint オブジェクトでは、テキストのカラー、フォント ファミリ、サイズを指定できます。 TextSize プロパティの既定値は 12 ですが、このままだと、スマートフォンなどの高解像度デバイスではテキスト表示が非常に小さくなります。 アプリケーションがきわめてシンプルなものでない限り、表示するテキストのサイズに関する情報は必要です。 SKPaint クラスには FontMetrics プロパティといくつかの MeasureText メソッドを定義されていますが、ニーズがさほど複雑ではない場合、テキスト行間隔については FontSpacing プロパティに用意された推奨値を使用できます。

次の PaintSurface ハンドラーは、SKPaint オブジェクトを作成し、TextSize (上へ伸びる文字の上端から下へ伸びる文字の下端まで含めたテキスト垂直方向のサイズ) を 40 ピクセルに設定しています。 SKPaint オブジェクトから返される FontSpacing の値は、それよりも少し大きい 47 ピクセル程度になります。

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    SKPaint paint = new SKPaint
    {
        Color = SKColors.Black,
        TextSize = 40
    };

    float fontSpacing = paint.FontSpacing;
    float x = 20;               // left margin
    float y = fontSpacing;      // first baseline
    float indent = 100;

    canvas.DrawText("SKCanvasView Height and Width:", x, y, paint);
    y += fontSpacing;
    canvas.DrawText(String.Format("{0:F2} x {1:F2}",
                                  canvasView.Width, canvasView.Height),
                    x + indent, y, paint);
    y += fontSpacing * 2;
    canvas.DrawText("SKCanvasView CanvasSize:", x, y, paint);
    y += fontSpacing;
    canvas.DrawText(canvasView.CanvasSize.ToString(), x + indent, y, paint);
    y += fontSpacing * 2;
    canvas.DrawText("SKImageInfo Size:", x, y, paint);
    y += fontSpacing;
    canvas.DrawText(info.Size.ToString(), x + indent, y, paint);
}

このメソッドは、X 座標 20 (左側に少し余白を確保)、Y 座標 fontSpacing を開始位置としてテキストの最初の行を表示します。この Y 座標は、表示サーフェイスの最上部に 1 行目のテキストを表示できる必要最小限の高さよりも少し低い位置です。 DrawText を呼び出すたびに、Y 座標の値を fontSpacing の 1 つまたは 2 つ分ずつ増やしています。

実行中のプログラムを次に示します。

スクリーンショットには、2 台のモバイル端末で Surface Size アプリが動作している様子が示されています。

ご覧のとおり、SKCanvasViewCanvasSize プロパティと SKImageInfo 値の Size プロパティからは一貫したピクセル寸法が報告されています。 SKCanvasViewHeight および Width プロパティは Xamarin.Forms のプロパティであり、ビューのサイズは、プラットフォームによって定義されたデバイス非依存の単位で報告されます。

デバイス非依存単位が、左の iOS 7 シミュレーターでは 2 ピクセルに、中央の Android Nexus 5 では 3 ピクセルに相当しています。 前の記事で示した単純な円の描画サイズがプラットフォームごとに異なっていたのは、このためです。

完全にデバイス非依存の単位だけを使って処理できるようにするには、SKCanvasViewIgnorePixelScaling プロパティを true に設定します。 ただし、望ましい表示結果が得られるとは限りません。 デバイス サーフェイスが非常に小さい場合、SkiaSharp は、デバイス非依存の単位によるビューのサイズと同じピクセル サイズでグラフィックをレンダリングし (たとえば、Nexus 5 では 360 x 512 ピクセルの表示サーフェイスを使用します)、その後で画像のサイズを拡大します。このため、ビットマップのギザギザが目立つ結果になります。

同じ画像解像度を維持する良い方法としては、2 つの座標系間の変換を行うシンプルな関数を自分で記述することが考えられます。

SKCanvas には、DrawCircle メソッドのほか、楕円を描画する 2 種類の DrawOval メソッドも定義されています。 楕円の形は、1 つではなく 2 つの半径によって定義されます。 それらは "長半径"、"短半径" と呼ばれます。 DrawOval メソッドでは、X 軸と Y 軸に平行な 2 つの半径で表される楕円を描画します (X 軸と Y 軸に平行でない軸を持つ楕円を描画する必要がある場合は、記事「回転変換」で説明されている回転変換か、記事「円弧を描画する 3 つの方法」で説明されているグラフィック パスを使用して実現できます)。 DrawOval メソッドのこのオーバーロードでは、2 つの半径パラメーターに、X 軸と Y 軸に平行であることを示す rxry という名前が付けられています。

public void DrawOval (Single cx, Single cy, Single rx, Single ry, SKPaint paint)

表示サーフェイス全体を満たす大きさの楕円を描くことは可能でしょうか? その方法は、Ellipse Fill ページのデモで説明されています。 EllipseFillPage.xaml.cs クラスの PaintSurface イベント ハンドラーでは、楕円全体とその枠線が表示サーフェイスに収まるよう、xRadiusyRadius の値からストローク幅の半分を引き算しています。

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    float strokeWidth = 50;
    float xRadius = (info.Width - strokeWidth) / 2;
    float yRadius = (info.Height - strokeWidth) / 2;

    SKPaint paint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Blue,
        StrokeWidth = strokeWidth
    };
    canvas.DrawOval(info.Width / 2, info.Height / 2, xRadius, yRadius, paint);
}

実行結果は下のようになります。

スクリーンショットには、Ellipse Fill アプリが 2 つのモバイルデバイスで動作している様子が示されています。

もう 1 つの DrawOval メソッドは、四角形の左上隅の X、Y 座標、右下隅の X、Y 座標を示す SKRect 型の引数を取ります。 これは四角形を満たす大きさの楕円を描く指示であるため、次のように Ellipse Fill ページで使用できそうに思われます。

SKRect rect = new SKRect(0, 0, info.Width, info.Height);
canvas.DrawOval(rect, paint);

しかし実際には、楕円のアウトラインが 4 辺にかかり、裁ち落とされてしまいます。 期待どおりに機能させるには、SKRect コンストラクターのすべての引数を strokeWidth に基づいて調整する必要があります。

SKRect rect = new SKRect(strokeWidth / 2,
                         strokeWidth / 2,
                         info.Width - strokeWidth / 2,
                         info.Height - strokeWidth / 2);
canvas.DrawOval(rect, paint);