【Xamarin】SkiaSharpを使ってお絵かきツールを作る

# やりたいこと
画面をタッチした奇跡を線で結び、お絵かきツールのようなものを作る。

[前回](http://qiita.com/yoshieya/items/3da7b02c50e1c945cac4)はSkiaSharpで描画した画像をViewに表示するところをやりましたが、それだけだと、お絵かきツールのようにリアルタイムに描画を更新するような場合、画像がカクカクしてしまいます。
滑らかに描画するために、試行錯誤していたのですが、結局SkiaSharpが用意しているSKCanvasViewを使って、描画したキャンバスを直接表示することにしました。

##描画ライブラリ
まずは、描画のためのキャンバスを用意します。
前回作ったCanvasCreateメソッドを元にしていますが、今回は何も返さず、キャンバスを作るだけにとどめました。

```SkiaSharpTest.cs
		public void CreateCanvas(int width, int height)
		{

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

			canvas.Clear(SKColors.White);

			// set up drawing tools
			paint = new SKPaint();
			paint.IsAntialias = true;
			paint.Color = new SKColor(0x2c, 0x3e, 0x50);
			paint.StrokeCap = SKStrokeCap.Round;
			paint.StrokeWidth = 10f;
		}
```

##キャンバスを表示するビュー
### iOSの場合
1.SkiaSharp Views & Layersパッケージを入れる。
iOSプロジェクトを右クリック→追加(A)→NuGet パッケージの追加(P)
　　SkiaSharp Views & Layersを検索して追加する。
を検索して追加する
<img width="606" alt="スクリーンショット 2016-12-05 17.37.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/129520/e242bcb8-20f9-22d7-a8d2-8006f0bfd7ab.png">

2.描画させるビューをSKCanvasViewにする

```CanvasVie.cs
using CoreGraphics;
using SkiaSharp;
using SkiaSharp.Views.iOS;
...
public partial class CanvasView : SKCanvasView
```
CGPointを使うのでCoreGraphicsをusingしています。SKPointにすればこれはいらないことに後で気づきました。。。

### Androidの場合
1.SkiaSharp Views & Layersパッケージを入れる。
Androidプロジェクトを右クリック→追加(A)→NuGet パッケージの追加(P)
　　SkiaSharp Views & Layersを検索して追加する。
を検索して追加する

2.描画させるビューをSKCanvasViewにする。

```CanvasVie.cs
using Android.Graphics;
using SkiaSharp;
using SkiaSharp.Views.Android;
...
public class CanvasView : SKCanvasView
```
こちらも、Pointを使うのでGraphicsをusingしています。

## タッチポイントを取得する
### iOSの場合
方法は色々ありますが、今回は簡単にTouchesbegan(開始),TouchesMoved(移動),TouchesEnded(終了)を使いました。

```CanvasView.cs
		// タッチイベント
		public override void TouchesBegan(NSSet touches, UIEvent evt)
		{
			base.TouchesBegan(touches, evt);

			UITouch touch = touches.AnyObject as UITouch;
			if (touch != null)
			{
				tPoint = touch.LocationInView(this); // タッチしたポイントを取得
			}
		}

		public override void TouchesMoved(NSSet touches, UIEvent evt)
		{
			base.TouchesMoved(touches, evt);

			UITouch touch = touches.AnyObject as UITouch;
			if (touch != null)
			{
				CGPoint nowPoint = touch.LocationInView(this); // タッチしたポイントを取得
				drawImage(nowPoint); // 描画メソッド

				tPoint = nowPoint; // 次の開始点
			}
		}

		public override void TouchesEnded(NSSet touches, UIEvent evt)
		{
			base.TouchesEnded(touches, evt);

			UITouch touch = touches.AnyObject as UITouch;
			if (touch != null)
			{
				CGPoint nowPoint = touch.LocationInView(this); // タッチしたポイントを取得
				drawImage(nowPoint); // 描画メソッド
			}
		}
				
		// 描画メソッド
		public void drawImage(CGPoint nowPoint)
		{
			pCanvas.Paint((float)tPoint.X, (float)tPoint.Y, (float)nowPoint.X, (float)nowPoint.Y); // 描画ライブラリで線を引く

			this.SetNeedsDisplay(); // 再描画
		}

```

### Androidの場合
Viewでタッチイベントを取得するにはOnTouchEventを使います。

```CanvasView.cs
		// タッチイベント
		public override bool OnTouchEvent(MotionEvent e)
		{
			int posX = (int)e.GetX();	// タッチポイントX
			int posY = (int)e.GetY();	// タッチポイントY

			Point newPoint = new Point(posX, posY);// タッチしたポイントを取得

			switch (e.Action)
			{
				case MotionEventActions.Down:	// 開始
					tPoint = newPoint; // 線の開始点
					break; 
				case MotionEventActions.Move:	// 移動

					drawImage(newPoint); // 描画メソッド

					tPoint = newPoint;	// 次の開始点
					break;
				case MotionEventActions.Up:		// 終了

					drawImage(newPoint); // 描画メソッド
					break;
				default:
					break;
			}

			return true;
		}

		// 描画メソッド
		public void drawImage(Point nowPoint)
		{
			pCanvas.Paint((float)tPoint.X, (float)tPoint.Y, (float)nowPoint.X, (float)nowPoint.Y); // 描画ライブラリで線を引く

			Invalidate(); // 再描画
		}
```

### 描画ライブラリ
CanvasCreateで設定したpaintで線を引きます。

```SkiaSharpTest.cs
		// 線を描画
		public void Paint(float startX, float startY, float endX, float endY)
		{
			if (canvas == null)
			{
				System.Diagnostics.Debug.WriteLine("canvas is NULL");
				return;
			}

			canvas.DrawLine(startX, startY, endX, endY, paint);
		}
```

## 線を引いた画像を表示する
再描画のたびに、Drawメソッドで画面に画像を表示させます。
線を引くメソッドと、キャンバスのキャプチャを取得するメソッドを用意。今回はネイティブ側もSkiaSharpを入れているので、SKImageを渡します。
毎回Streamに変換する処理時間が勿体無いので。

### iOSの場合
SKCanvasViewのDrawInSurfaceを使うと、キャンバスに直接SKImageを描画できます。

```CanvasView.cs
		public override void DrawInSurface(SKSurface surface, SKImageInfo info)
		{
			base.DrawInSurface(surface, info);

			var localcanvas = surface.Canvas;
			using (var img = pCanvas.getImageImage())
			{
				localcanvas.DrawImage(img, localcanvas.ClipBounds, null);
			}
```

### Androidの場合
SKCanvasViewが用意しているOnDraw(SKSurface surface, SKImageInfo info)を使います。
引数が通常のOnDrawと違うのでご注意ください。

```CanvasView.cs
		protected override void OnDraw(SKSurface surface, SKImageInfo info)
		{
			base.OnDraw(surface, info);
		
			var canvas = surface.Canvas;
			using (var img = pCanvas.getImageImage())
			{
				canvas.DrawImage(img, canvas.ClipBounds, null);
			}
		}
```

### 描画ライブラリ
描画キャンバスのキャプチャ画像を返すメソッドを作ります。

```SkiaSharpTest.cs

		// 画像を取得
		public SKImage getImageImage()
		{
			if (surface == null)
			{
				System.Diagnostics.Debug.WriteLine("surface is NULL");
				return null;
			}

			SKImage img = surface.Snapshot(); // 画像取得

			return img;
		}

```

これで、お絵かきツールの元ができました。
あとは、線を変えたり、キャンバスサイズを変えたりと自由自在に使いましょう〜。

滑らかに動かすために、変更箇所だけを再描画したりするのですが、今回はそれをしなくてもかなり滑らかに描画できているので、そのままにしています。
ちょっとカクカクするなーって気になった方は、線を引いたところだけを再描画するように工夫すると良いかも。