導入
これは Xamarin Advent Calendar 2016 の10日目の記事です。
本当はもうちょっとそれぞれサンプルコードを添えたかったのですが、主に GitHub の草を途切れらせない為のアラートアプリ keep.grass の開発上の経験で書いてますので必要に応じて keep.grass のソースコードでも見てください。( 主に /source/AlphaCircleGraph.cs のあたり )
使用例
先にも触れましたが keep.grass では SkiaSharp を使って次のような画面(画面上半分。ナビゲーションバーおよび画面下部は Xamarin.Forms によるもの)を実現しています。 ( このスクリーンショット v2 に向けての改修中のもので、円グラフとアイコンがアニメーションを伴って描画されます。現在ストアで公開されている v1 はアニメーションしません。 )
SkiaSharp とは
SkiaSharp は Skia Graphics Library という 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フォントである必要はありません。)
フォントの埋め込み
フォントは EmbeddedResource
として埋め込んでおきます。埋め込む先は別に PCL だろうがプラットフォーム別のプロジェクトだろうが構いません。
埋め込んだリソースが EmbeddedResource
になっているかどうかは Xamarin Studio 上のプロパティの ビルド アクション
の項目で確認できます。 ビルド アクション
が EmbeddedResource
になっていなければ ソリューション
内から目的のファイルを右クリックしてコンテキストメニューから ビルド アクション
→ EmbeddedResource
を選択します。
UI上は上記の通りなのですが、たまに *.csproj
上で正常な形で登録できておらず上手く動作しないことがあります。この場合、 *.csproj
ファイルをテキストエディタで開き次ぎのような形(これは keep.grass の keep.grass.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
プロパティにセットしてからテキスト描画を行います。
線画
SKPath
クラスのオブジェクトを作成し、一筆書きの要領で MoveTo()
, LineTo()
, ArcTo()
など使い目的の線(パス)を描き、必要に応じて Close()
を呼び出してパスを閉じます。パスで囲んだ領域を塗り潰すなら必ず Close()
を呼び出してパスを閉じましょう。
ArcTo()
だけちょっと引数の指定が分かり難いので次の図を参考にしてください。
赤線の弧を描画したい場合、円を囲む青い四角が oval
で、緑の角度が startAngle
で、オレンジの角度が sweepAngle
となり、指定角度単位は degree になります。
画像
SKData
のコンストラクタに画像のバイト列を喰わせたものをさらに SKBitmap.Decode()
に与えれば SkiaSharp
で利用できる形式になるので後は Canvas の DrawBitmap()
で座標を指定して貼り付けるだけです。
ソースとなる画像は最終的に SKBitmap.Decode()
で扱えるバイト列やストリームが手に入ればいいののでネットから取得したものでも埋め込みリソースでも構いません。埋め込みリソースを使う場合は上の[フォントの埋め込み]の所を参考にしてください。埋め込みリソースの扱いは中身がフォントだろうが画像だろうが違いはありません。
アンチエイリアシングでの重ね塗りによる滲み
これはまぁ別に SkiaSharp
云々に関係なくアンチエイリアスを有効にしてる状態での描画全般に関わる話ですが、 SkiaSharp.Views.Forms
で描画処理の高速化を狙った描画の最小化の為に Canvas の Clear()
関数の呼び出しだのその他の背景色での塗りつぶしだのを行わずにアンチエイリアスを有効にした状態での描画を繰り返すと下の画像のような滲みが発生してしまうので必要に応じて背景を塗り潰すなり描画の緩衝領域を設けるなどしましょう。
左が重ね塗りによる滲みが出てる画像、右が適宜背景を塗りつぶしてる画像
アニメーション
SkiaSharp
自体は直接的にはアニメーションをサポートしません。ですが、自前でタイミングをコントロールしつつ描画したり、 Xamarin.Forms
の View が備える Animate()
関数( +都度 SKCanvasView
の InvalidateSurface()
関数を呼び出す )などを使えばできないことはないです。実際にやってみたところかなり環境依存&アニメーションの内容に依りそうですが、それなりには動作してくれそうです。本格的にアニメーションしたいなら CocosSharp などを使ったほうがよいかと。