ピクセル、およびデバイスに依存しない単位
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 つ分ずつ増やしています。
実行中のプログラムを次に示します。
ご覧のとおり、SKCanvasView
の CanvasSize
プロパティと SKImageInfo
値の Size
プロパティからは一貫したピクセル寸法が報告されています。 SKCanvasView
の Height
および Width
プロパティは Xamarin.Forms のプロパティであり、ビューのサイズは、プラットフォームによって定義されたデバイス非依存の単位で報告されます。
デバイス非依存単位が、左の iOS 7 シミュレーターでは 2 ピクセルに、中央の Android Nexus 5 では 3 ピクセルに相当しています。 前の記事で示した単純な円の描画サイズがプラットフォームごとに異なっていたのは、このためです。
完全にデバイス非依存の単位だけを使って処理できるようにするには、SKCanvasView
の IgnorePixelScaling
プロパティを 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 軸に平行であることを示す rx
、ry
という名前が付けられています。
public void DrawOval (Single cx, Single cy, Single rx, Single ry, SKPaint paint)
表示サーフェイス全体を満たす大きさの楕円を描くことは可能でしょうか? その方法は、Ellipse Fill ページのデモで説明されています。 EllipseFillPage.xaml.cs クラスの PaintSurface
イベント ハンドラーでは、楕円全体とその枠線が表示サーフェイスに収まるよう、xRadius
と yRadius
の値からストローク幅の半分を引き算しています。
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);
}
実行結果は下のようになります。
もう 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);