【Xamarin】SkiaSharpを使って2Dを描画する

# やりたいこと
1. 一つのコードで2Dを描画する。
2. １の画像をiOS,Android各々のViewに表示させる。

開発環境はMacのXamarin Studioです。
2D描画のフレームワークにはSkiaSharpを使うことにしました。使い勝手が良いかどうかは不明ですが、公式APIsに乗ってたので、きっと使えるはず。

## 準備
ソリューションを作成する。
今回は"DrawTest"という名前で作成しました。
ソリューションはAppの"Native(iOS,Android)を、"Shared Code:"は"Use Portable Class Library"を選択しました。

## まずは描画ライブラリを作る
PCLを作る方法は[こちら](https://www.xlsoft.com/jp/products/xamarin/portable_class_libraries.html)を参照。
お試しだけなら、初めに用意されている共有プロジェクトでテストしても問題ないかも？

1.PCLプロジェクトを追加します。名前は"Draw2dCore"にしました。
    	　　プロジェクトを右クリック→追加(A)→新しいプロジェクトを追加(A)
    	　　Multiplatform→ライブラリ→ポータブルライブラリ(C#)
    	　　プロジェクト名に"Draw2dCore"を記入→作成
2.Draw2dCoreプロジェクトに新しいファイルを追加します。
    	　　Draw2dCoreプロジェクトを右クリック→追加(A)→新しいファイル(F)
	　　General→空のクラス ファイル名"SkiaSharpTest"
3.Draw2dCoreプロジェクトにSkiaSharpパッケージを追加します。
	　　共有プロジェクトを右クリック→追加→NuGet パッケージの追加(P)
	　　SkiaSharpを検索して追加する
4.Xamarinの[ドキュメント](https://developer.xamarin.com/api/type/SkiaSharp.SKCanvas/)にあるサンプルコードを参考に、6角形を描画するコードを書きます。
5.各アプリで表示できるように、System.IO.Stream型で返すようにします。

```SkiaSharpTest.cs
using System;
using SkiaSharp;

namespace Draw2dCore
{
	public class SkiaSharpTest
	{
		public System.IO.Stream CreateCanvas(int width, int height)
		{
			System.IO.Stream imgStream;

			using (var surface = SKSurface.Create(width: width, height: height, colorType: SKImageInfo.PlatformColorType, alphaType: SKAlphaType.Premul))
			{
				SKCanvas canvas = surface.Canvas;

				canvas.Clear(SKColors.White);

				// set up drawing tools
				using (var paint = new SKPaint())
				{
					paint.IsAntialias = true;
					paint.Color = new SKColor(0x2c, 0x3e, 0x50);
					paint.StrokeCap = SKStrokeCap.Round;

					// create the Xamagon path
					using (var path = new SKPath())
					{
						path.MoveTo(71.4311121f, 56f);
						path.CubicTo(68.6763107f, 56.0058575f, 65.9796704f, 57.5737917f, 64.5928855f, 59.965729f);
						path.LineTo(43.0238921f, 97.5342563f);
						path.CubicTo(41.6587026f, 99.9325978f, 41.6587026f, 103.067402f, 43.0238921f, 105.465744f);
						path.LineTo(64.5928855f, 143.034271f);
						path.CubicTo(65.9798162f, 145.426228f, 68.6763107f, 146.994582f, 71.4311121f, 147f);
						path.LineTo(114.568946f, 147f);
						path.CubicTo(117.323748f, 146.994143f, 120.020241f, 145.426228f, 121.407172f, 143.034271f);
						path.LineTo(142.976161f, 105.465744f);
						path.CubicTo(144.34135f, 103.067402f, 144.341209f, 99.9325978f, 142.976161f, 97.5342563f);
						path.LineTo(121.407172f, 59.965729f);
						path.CubicTo(120.020241f, 57.5737917f, 117.323748f, 56.0054182f, 114.568946f, 56f);
						path.LineTo(71.4311121f, 56f);
						path.Close();


						// draw the Xamagon path
						canvas.DrawPath(path, paint);
					}
				}

				// Stream型に変換する
				SKData img = surface.Snapshot().Encode();
				imgStream = img.AsStream();
			}

			return imgStream;
		}
	}
}

```
キャンバスサイズを受け取った値にする以外はほとんどサンプルコードのままです。

## iOSの表示部分を作る
1.Main.storyboardでツールボックスから新しいUIViewを追加する。
2.Viewのプロパティ→Identity→Classに表示ビュークラスとして"CanvasView"と入力する。
3.CanvasView.csファイルが作成されるので、そちらを編集する。
4.iOSプロジェクトにDraw2dCoreプロジェクトの参照を追加します。
	　　iOSプロジェクトの参照ノードを右クリック→参照の編集(E)
	　　プロジェクトタブ→Draw2dCoreにチェック
5.iOSプロジェクトにSkiaSharpパッケージを追加します。
	　　iOSプロジェクトを右クリック→追加(A)→NuGet パッケージの追加(P)
	　　SkiaSharpを検索して追加する
6.Drawメソッドをオーバーライドして、画像を描画させる。

```CanvasView.cs
		public override void Draw(CoreGraphics.CGRect rect)
		{
			base.Draw(rect);

			// ２D描画クラス
			var skia = new Draw2dCore.SkiaSharpTest();
			var imgStream = skia.CreateCanvas((int)rect.Width, (int)rect.Height);

			// StreamデータからNSDataに変換
			NSData imgData = NSData.FromStream(imgStream);
			imgStream.Close();

			// 画像データをコンテキストに描画
			using (var context = UIGraphics.GetCurrentContext())
			{
				UIImage uiimg = new UIImage(imgData);
				uiimg.Draw(new CoreGraphics.CGPoint(0, 0));
			}

		}
```
こんな感じに表示されました！
<img width="320" alt="Simulator Screen Shot 2016.11.16 17.16.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/129520/d3ae47a2-09a7-6703-1643-049c2af23928.png">


## Androidの表示部分を作る
1.ビュークラスを追加する。
　　Androidプロジェクト右クリック→追加→新しいファイル(F)
　　Android→ビュー　ファイル名"CanvasView"
2.ここでAndroidをビルドする。(これでツールボックスにCanvasViewが追加される）
3.Main.axmlでツールボックスから"CanvasView.cs"を追加する。
4.iOSプロジェクトにDraw2dCoreプロジェクトの参照を追加します。
	　　iOSプロジェクトの参照ノードを右クリック→参照の編集(E)
	　　プロジェクトタブ→Draw2dCoreにチェック
5.AndroidプロジェクトにSkiaSharpパッケージを追加します。
	　　共有プロジェクトを右クリック→追加(A)→NuGet パッケージの追加(P)
	　　SkiaSharpを検索して追加する
6."CanvasView.cs"のDrawメソッドをオーバーライドして、画像を描画させる。

```CanvasView.cs
		public override void Draw(Android.Graphics.Canvas canvas)
		{
			base.Draw(canvas);

			// 2D描画クラス
			var skia = new Draw2dCore.SkiaSharpTest();

			var imgStream = skia.CreateCanvas(canvas.Width, canvas.Height);

			// Streamデータをメモリーにコピー
			using (var mem = new System.IO.MemoryStream())
			{
				imgStream.CopyTo(mem);
				imgStream.Close();

				// ビットマップに変換
				var data = mem.ToArray();
				var bmp = global::Android.Graphics.BitmapFactory.DecodeByteArray(data, 0, data.Length);
			
				// ビットマップをビューに表示
				canvas.DrawBitmap(bmp, 0, 0, null);
			}
		}
```
Androidは画面サイズが大きいのでちっちゃく表示されてます。
<img width="426" alt="スクリーンショット 2016-11-16 17.21.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/129520/026040f2-76c1-9ae0-87d2-6e62c5af1790.png">

Androidのビュー追加方法は手探りでやりました。これであっているのか不安。。。

## めでたしめでたし
後は、アプリに合わせて描画する内容を書き換えたり、タイミングを変えたりすればOK!
最終的に画像に変換するので、Draw内で表示する必要もない気がします。
ビューを追加するときに、初めに入っているボタンやサンプルコードなんかは邪魔なので削除しちゃってくださいね。

### 余談ですが。。。
はじめ、Androidエミュレータを使っていたのですが、あまりにも動作が不安定だったのでgenymotionに切り替えました。こちらの方が安定して動作している模様。