SkiaSharp 入門

# 導入
これは [Xamarin Advent Calendar 2016](http://qiita.com/advent-calendar/2016/xamarin) の10日目の記事です。
本当はもうちょっとそれぞれサンプルコードを添えたかったのですが、主に GitHub の草を途切れらせない為のアラートアプリ keep.grass の開発上の経験で書いてますので必要に応じて [keep.grass](https://github.com/wraith13/keep.grass) のソースコードでも見てください。( 主に [/source/AlphaCircleGraph.cs](https://github.com/wraith13/keep.grass/blob/master/source/AlphaCircleGraph.cs) のあたり )

# 使用例
先にも触れましたが keep.grass では SkiaSharp を使って次のような画面(画面上半分。ナビゲーションバーおよび画面下部は Xamarin.Forms によるもの)を実現しています。 ( このスクリーンショット v2 に向けての改修中のもので、円グラフとアイコンがアニメーションを伴って描画されます。現在ストアで公開されている v1 はアニメーションしません。 )

<img width="320" alt="Simulator Screen Shot 2016.12.11 0.45.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/146335/662fa105-1f8e-b5d5-58af-97aeb104b4e1.png">

- [iOS版](https://itunes.apple.com/us/app/keep.grass/id1170833136?l=ja&ls=1&mt=8)
- [Android版](https://play.google.com/store/apps/details?id=net.trickpalace.keep_grass)
- [UWP版](https://www.microsoft.com/store/apps/9nblggh51p1m)

# SkiaSharp とは
SkiaSharp は [Skia Graphics Library](https://skia.org) という Google のオープンソースな2Dグラフィックライブラリ(より正確には元々 Skia Inc. が開発していたモノで、 Google による買収後、修正BSDライセンスでオープンソースとなった。)を C# 向けにラップしているモノで Xamarin が公式に推しているライブラリでもあります。

# 構成
構成としては以下のようになっています。利用される場合は必要に応じてそれぞれ nuget で取得してください。

- `SkiaSharp`
	- 本体
- `SkiaSharp.Views`
	- `SkiaSharp` 本体は画面への描画をサポートしていないので画面への描画を行う際はこちらを利用します。
	- 各種プラットフォーム用のコンポーネントの為、PCL版は存在しませんし、またPCLには必要ありません。
- `SkiaSharp.Views.Forms`
	- `Xamarin.Forms` 用

なお、 `SkiaSharp.Views` および `SkiaSharp.Views.Forms` が無くとも `SkiaSharp` 本体だけでイメージを作成し、ユーザーコードでそのイメージを画面に貼り付けて使っても構いません。実際、ちょっと前まで `SkiaSharp` 本体しか存在しなかったので、 keep.grass では最初そのようにしていました。

# hallo, SkiaSharp
https://developer.xamarin.com/guides/cross-platform/drawing/introduction/
コードとしてはこのあたりにあるヤツでも参考にしてください。

`SkiaSharp` 本体だけで使う場合は次のような流れになります。

- `SKSurface.Create()` で Surface を作成( 画面に貼り付ける前提の場合、 `ColorType` は通常 `SKColorType.Rgba8888` で良いですが UWP アプリの場合は `SKColorType.Bgra8888` します。  )
- Surface の `Canvas` メンバーに対して `SKPaint` などを使い描画
- 絵が完成したら Surface から `.Snapshot().Encode()` で `SKData` を取得
- 後は用途に応じて `SKData` の `.AsStream()` が返す Stream や `ToArray()` が返すバイト列を使って `Xamarin.Forms` の `ImageSource.FromStream()` を作成して画面に貼り付けてもヨシ、そのままファイル等に保存するもヨシ

`SkiaSharp.Views.Forms` の場合は次のような流れになります。

- `SKCanvasView` を継承したクラスを作成し `OnPaintSurface()` 関数のオーバーライドを実装する。
- そのクラスを画面を構成する View の一つとして組み込む
- `SKCanvasView` の `OnPaintSurface()` 関数が呼び出されたらその引数の `SKPaintSurfaceEventArgs` のメンバーに `Surface` が存在してるので、さらにその `Surface` の `Canvas` メンバーに対して描画を行えば、画面に反映されます。
- 描画内容を更新したい場合は `SKCanvasView` の `InvalidateSurface()` 関数を呼び出せば再度 `OnPaintSurface()` 関数が呼び出されるのでそこで新しい内容で描画を行います。

描画の際の塗る色( `.Color` )だの線の太さ( `.StrokeWidth` )だの枠線を引くだけなのか塗りつぶすのか( `.IsStroke` )だのアンチエイリアスの有効無効( `.IsAntialias` )だのと言った多くの指定は主に `SKPaint` のプロパティで指定します。

# 物理ピクセル
`Xamarin.Forms` の場合、画面の座標系は論理ピクセルベースとなりますが、 `SkiaSharp.Views.Forms` を使うと自動的に View の大きさに合わせた物理ピクセルベースのサイズの Canvas が作成されます。この場合、 Canvas の `GetClipBounds()` 関数を呼び出すことで物理ピクセルの Rect が手に入るのでその枠内で描画します。

# 日本語文字
テキスト描画は `SKPaint` のプロパティで `TextSize` だの `TextAlign` だのを指定して `SKCanvas` の `DrawText()` を呼び出せばいいだけなんですが普通にやると日本語文字がお豆腐になります。

ということで no more 豆腐 —— noto フォントさんでも利用しましょう。(別にnotoフォントである必要はありません。)

- https://www.google.com/get/noto/
- https://www.google.com/get/noto/help/cjk/

## フォントの埋め込み
フォントは `EmbeddedResource` として埋め込んでおきます。埋め込む先は別に PCL だろうがプラットフォーム別のプロジェクトだろうが構いません。

埋め込んだリソースが `EmbeddedResource` になっているかどうかは Xamarin Studio 上のプロパティの `ビルド アクション` の項目で確認できます。 `ビルド アクション` が `EmbeddedResource` になっていなければ `ソリューション` 内から目的のファイルを右クリックしてコンテキストメニューから `ビルド アクション` → `EmbeddedResource` を選択します。

![スクリーンショット 2016-12-15 0.34.07-2.png](https://qiita-image-store.s3.amazonaws.com/0/146335/5f25e6c4-147d-e8e3-ec60-cbb6cb2be973.png)


UI上は上記の通りなのですが、たまに `*.csproj` 上で正常な形で登録できておらず上手く動作しないことがあります。この場合、 `*.csproj` ファイルをテキストエディタで開き次ぎのような形(これは keep.grass の [keep.grass.csproj](https://github.com/wraith13/keep.grass/blob/master/source/keep.grass.csproj) の抜粋です )で登録されているか確認し、そうなっていなければ適切な形に整形して保存してください。

```*.csproj
  <ItemGroup>
    <EmbeddedResource Include="Images\keep.grass.120.png" />
    <EmbeddedResource Include="Images\wraith13.120.png" />
    <EmbeddedResource Include="Images\GitHub-Mark.120.png" />
    <EmbeddedResource Include="Images\right.120.png" />
    <EmbeddedResource Include="Images\refresh.120.png" />
    <EmbeddedResource Include="Images\export.120.png" />
    <EmbeddedResource Include="Fonts\NotoSansCJKjp-Regular.otf" />
  </ItemGroup>
```

## 埋め込みフォントの取得
埋め込んだフォントは `typeof(`フォントを埋め込んだプロジェクトで定義されてる適当な型`).GetTypeInfo().Assembly.GetManifestResourceStream(`Xamarin Studioで埋め込んだフォントのプロパティでリソースIDとして表示されている文字列`);` で得られるストリームを `SKManagedStream` コンストラクタに渡し、さらにそれを `SKTypeface.FromStream()` に喰わせれば `SkiaSharp` で使える形式になるので `SKPaint` の `Typeface` プロパティにセットしてからテキスト描画を行います。

![スクリーンショット 2016-12-15 0.34.07-3.png](https://qiita-image-store.s3.amazonaws.com/0/146335/9b92ea0a-0026-4a41-a1ac-054d84ae1b49.png)

# 線画
`SKPath` クラスのオブジェクトを作成し、一筆書きの要領で `MoveTo()`, `LineTo()`, `ArcTo()` など使い目的の線(パス)を描き、必要に応じて `Close()` を呼び出してパスを閉じます。パスで囲んだ領域を塗り潰すなら必ず `Close()` を呼び出してパスを閉じましょう。 

`ArcTo()` だけちょっと引数の指定が分かり難いので次の図を参考にしてください。
![SkiaSharp.ArcTo.png](https://qiita-image-store.s3.amazonaws.com/0/146335/d81856e5-0e90-9eb1-8d31-276bb8c1fdb6.png)
赤線の弧を描画したい場合、円を囲む青い四角が `oval` で、緑の角度が `startAngle` で、オレンジの角度が `sweepAngle` となり、指定角度単位は degree になります。

# 画像
`SKData` のコンストラクタに画像のバイト列を喰わせたものをさらに `SKBitmap.Decode()` に与えれば `SkiaSharp` で利用できる形式になるので後は Canvas の `DrawBitmap()` で座標を指定して貼り付けるだけです。

ソースとなる画像は最終的に `SKBitmap.Decode()` で扱えるバイト列やストリームが手に入ればいいののでネットから取得したものでも埋め込みリソースでも構いません。埋め込みリソースを使う場合は上の[フォントの埋め込み]の所を参考にしてください。埋め込みリソースの扱いは中身がフォントだろうが画像だろうが違いはありません。

# アンチエイリアシングでの重ね塗りによる滲み
これはまぁ別に `SkiaSharp` 云々に関係なくアンチエイリアスを有効にしてる状態での描画全般に関わる話ですが、 `SkiaSharp.Views.Forms` で描画処理の高速化を狙った描画の最小化の為に Canvas の `Clear()` 関数の呼び出しだのその他の背景色での塗りつぶしだのを行わずにアンチエイリアスを有効にした状態での描画を繰り返すと下の画像のような滲みが発生してしまうので必要に応じて背景を塗り潰すなり描画の緩衝領域を設けるなどしましょう。

左が重ね塗りによる滲みが出てる画像、右が適宜背景を塗りつぶしてる画像
![スクリーンショット 2016-12-10 23.52.39.png](https://qiita-image-store.s3.amazonaws.com/0/146335/f7a2be6a-69c3-d273-41bf-dac5c6fa9270.png)

# アニメーション
`SkiaSharp` 自体は直接的にはアニメーションをサポートしません。ですが、自前でタイミングをコントロールしつつ描画したり、 `Xamarin.Forms` の View が備える `Animate()` 関数( +都度 `SKCanvasView` の `InvalidateSurface()` 関数を呼び出す )などを使えばできないことはないです。実際にやってみたところかなり環境依存＆アニメーションの内容に依りそうですが、それなりには動作してくれそうです。本格的にアニメーションしたいなら CocosSharp などを使ったほうがよいかと。