月別アーカイブ: 2014年12月

Xamarin.Formsでカメラを使う – Windows Phone 8編

| | 未分類

XAML Advent Calender 2014 12日目の記事です。

Advent Calenderで連載物なんて載せちゃって怒られそうな気がしなくもなくなくないですが、今回は前回「Xamarin.Formsでカメラを使う – Android編」に引き続きXamarin.FormsでWindows Phone 8のカメラを使う方法をご紹介します。
恐縮ですが、この記事を読む前に前回の記事をお読みください。

なおサンプルプログラムをGithubで公開しています。
完全なコードは、こちらをご覧ください。

準備

まずCustomeRendererとCameraPreviewクラスを前回の記事に従い、PCLプロジェクトに作成します。

WP8用プロジェクトでの実装

WP8用のプロジェクトでは、WP8に特化したViewRendererのサブクラスを作成します。

Xamarin.Forms.dllの更新

前回の記事にも書いたように、Generic版のViewRendererを利用するため、WP8のプロジェクトで参照しているXamarin.Forms.dllをVersion 1.2.2.*以降にアップデートする必要があります。
NuGetパッケージマネージャから更新しましょう。
xamarin.forms_update

カスタムレンダラの実装

Xamarin.Forms.Platform.WinPhone.ViewRendererクラスを継承した、CameraPreviewRendererクラスを作成します。

TViewにはPCL内で定義したCameraPreviewクラス、TNativeViewにはカスタムレンダラで実際に表示させるプラットフォーム固有のViewを指定します。
今回はContentControlをこのNativeViewとし、その中にViewBoxコントロール,Canvasコントロールを配置し、Canvasコントロールに映し出したカメラのプレビュー画像を自動でリサイズできるように実装します。
XAMLで書くとこんな感じです。

<ContentControl>
	<ViewBox>
		<Canvas />
	</ViewBox>
</ContentContro>

ViewBoxの外側にContentControlを置いておかないとViewBoxのリサイズがうまく働かないので注意してください。

XAML Advent CalenderらしくこのコントロールをXAMLで書くのもいいかとは思うのですが、今回は簡略化のためcsのコードのみで実装します。

public class CameraPreviewRenderer : ViewRenderer<CameraPreview, ContentControl>
{
	Canvas canvas_ = null;
	CameraPreview cameraPreview_ = null;
	PhotoCamera camera_ = null;

	public CameraPreviewRenderer()
	{
		// コントロールのイベントハンドラを設定
		// コントロールがロードされたとき・アンロードされたときのイベント
		Loaded += CameraPreviewRenderer_Loaded;
		Unloaded += CameraPreviewRenderer_Unloaded;
	}

	protected override void OnElementChanged(ElementChangedEventArgs<CameraPreview> e)
	{
		base.OnElementChanged(e);

		// 最初に生成された時だけここを通る
		if (e.OldElement == null) {
			cameraPreview_ = e.NewElement; // CameraPreviewオブジェクトを取得				
			// コントロールを作成
			var contentControl = new ContentControl();
			
			// ViewBoxを作成
			var viewBox = new Viewbox() {
				HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
				VerticalAlignment = System.Windows.VerticalAlignment.Stretch,
				Stretch = System.Windows.Media.Stretch.Uniform
			};

			// カメラプレビューをを写すためのCanvasを作成
			canvas_ = new Canvas();

			// ContentControl - ViewBox - Canvas となるよう親子関係を構築
			viewBox.Child = canvas_;
			contentControl.Content = viewBox;

			// ContentControlをNativeViewとして設定
			SetNativeControl(contentControl);

			// CameraPreviewクラスの写真要求イベントを登録
			cameraPreview_.PictureRequired += cameraPreview_PictureRequired;
		}
	}

	/// <summary>
	/// 写真要求イベントのコールバック
	/// </summary>
	void cameraPreview_PictureRequired(object sender, EventArgs e)
	{
		if (camera_ != null) {
			// 写真撮影命令発行
			camera_.CaptureImage();
		}
	}

	/// <summary>
	/// コントロールがロードされた時のコールバック
	/// </summary>
	void CameraPreviewRenderer_Loaded(object sender, System.Windows.RoutedEventArgs e)
	{
		// カメラを初期化
		try {
			camera_ = new PhotoCamera(CameraType.Primary); // 失敗すると例外が飛ぶ
		}
		catch {
			camera_ = null;
		}

		if (camera_ != null) {
			// canvasにPreviewBrushを設定し、canvasの大きさをプレビュー画像に合わせる
			var previewResolution = camera_.PreviewResolution;
			canvas_.Width = previewResolution.Width;
			canvas_.Height = previewResolution.Height;

			var videoBrush = new System.Windows.Media.VideoBrush();
			videoBrush.SetSource(camera_);
			canvas_.Background = videoBrush;

			// 写真撮影に成功したときのコールバックを設定
			camera_.CaptureImageAvailable += camera__CaptureImageAvailable;
		}
	}

	/// <summary>
	/// コントロールがアンロードされたときのコールバック
	/// </summary>
	void CameraPreviewRenderer_Unloaded(object sender, System.Windows.RoutedEventArgs e)
	{
		// カメラを破棄
		if (camera_ != null) {
			var camera = camera_;
			camera_ = null;
			camera.Dispose();
		}
	}

	/// <summary>
	/// 写真が撮影された時のコールバック
	/// </summary>
	void camera__CaptureImageAvailable(object sender, ContentReadyEventArgs e)
	{
		// CameraPreviewクラスに撮影された写真を渡す
		var image = new Models.WPImage {
			ImageSource = ImageSource.FromStream(() => e.ImageStream)
		};
		cameraPreview_.OnPictureTaken(image);
	}
}

コントロールが初期化されるタイミングでCameraを初期化し、CameraPreviewクラスより写真撮影要求が発行されたときに写真撮影命令を発行、写真撮影が完了したらCameraPreviewのOnPictureTaken()を呼び出しその旨を通知しています。
ViewRenderer.LoadedイベントとViewRenderer.Unloadedイベントがポイントで、このタイミングでCameraの初期化・破棄を行うことで、Xamarin.FormsのViewのライフサイクルに合わせたリソースの取得・確保ができます。

カスタムレンダラのExport

カスタムレンダラを実装したら、Xamarin.Forms.ExportRendererAttribute 属性を用いて作成したカスタムレンダラをExportします。
前回と同じく、以下の一行をコードの先頭部分などに記述するだけです。

[assembly: ExportRenderer(typeof(CameraPreview), typeof(XamarinFormsCameraPreview.WinPhone.Renderers.CameraPreviewRenderer))]

これで、カスタムレンダラの実装は完了です。

カスタムレンダラを利用する。

作成したカスタムレンダラを利用してカメラのプレビューを表示させるのですが、実はこの時点で前回作成したPLCプロジェクトを全く変更せず、WP8においてもカメラプレビューを表示できます。

動作確認

実際にアプリケーションを動かして動作を確認してみましょう。

WP8のエミュレータ上でアプリケーションを起動すると、次のようにプレビューが表示されます。
WP_capture

TakePictureボタンをタップするとその場で写真が撮影され、以下の撮影写真プレビュー画面へ遷移します。
WP_capture_taken

正常に写真が撮影され、画像として取得できていることが確認できました。


前回に引き続きXamarin.Formsを利用しWindows Phone 8でカメラプレビュー画像を表示しました。
本記事ではWindows Phone 8用プロジェクトにWindows Phone 8用のCameraPreviewRendererを実装したのみで、PLCプロジェクトは前回と全く同じ物を利用しています。
このあたりはCustom Rendererの高い抽象度とその利便性がわかります。Xamarin.Forms万歳です。直接WinRT叩けよという異論は認めます。