WindowsPhone8拍照功能实现简介

WindowsPhone作为一款智能手机操作系统,支持APP中拍照是必不可少的,目前在WP8上的拍照主要有以下三种途径:

1、使用CameraCaptureTask;

2、使用PhotoCamera类;

3、使用PhotoCaptureDevice类。

下面简单介绍下这三种方法:

第一个就是CameraCaptureTask了。做WP开发的都知道微软为了让开发者能够使用一些手机功能在framework中提供了大量的选择器和启动器,本文不谈论这个话题,但是要知道CameraCaptureTask就是属于选择器的一种。CameraCaptureTask用起来非常的简单,使用如下代码即可:

 1 //以下代码均来自MSDN
 2 //引用命名空间
 3 using Microsoft.Phone.Tasks;
 4 
 5 //定义变量
 6 CameraCaptureTask cameraCaptureTask;
 7 
 8 //页面构造函数中实例化
 9 cameraCaptureTask = new CameraCaptureTask();
10 cameraCaptureTask.Completed += new EventHandler<PhotoResult>(cameraCaptureTask_Completed);
11 
12 //定义事件响应函数
13 void cameraCaptureTask_Completed(object sender, PhotoResult e)
14 {
15     if (e.TaskResult == TaskResult.OK)
16     {
17         MessageBox.Show(e.ChosenPhoto.Length.ToString());
18 
19         System.Windows.Media.Imaging.BitmapImage bmp = new System.Windows.Media.Imaging.BitmapImage();
20         bmp.SetSource(e.ChosenPhoto);
21         myImage.Source = bmp;
22     }
23 }
24 
25 //在需要使用任务的地方调用
26 cameraCaptureTask.Show();

当cameraCaptureTask.Show执行时你会看到一个近似系统原生相机应用的页面,在这个页面中可以对拍摄进行一些个性的选择。当拍摄完成,点击了接受后cameraCaptureTask_Completed函数会执行,其中参数e的ChosenPhoto属性就是包含了相片信息的流,对这个流进行处理就可以显示或者保持照片了。CameraCaptureTask应该说是这三种方法中使用起来最简单的一个,它使用的拍摄UI以及一些功能(比如闪光灯切换、摄像头前后切换等)都是系统实现的,但是这个方法也是有弊端的,先看一段MSDN上的说明:

重要说明重要说明:

使用 CameraCaptureTask API 拍摄的照片始终会复制到手机的本机拍照中。如果客户已将其手机设置为自动上载,则会将这些照片复制到 SkyDrive,并且可能会不按照应用的设定而与更广泛的受众共享。出于此原因,如果您并不希望共享或上载应用拍摄的照片,例如临时图像或包含隐私信息的图像,请勿使用 CameraCaptureTask API。而是应该使用 PhotoCamera API 实现您自己的相机 UI。

这段话中提到了隐私的问题,而除了隐私问题,CameraCaptureTask还有一个可能是所有选择器(似乎是所有吧,我自己也没有全部用过)都存在的问题,就是它会使得你的APP墓碑化。也许你不在乎这个问题,但是有些时候墓碑化会使得APP发生一些我们不希望发生的问题。出于不想墓碑化的考虑,我们可以使用PhotoCamera或者PhotoCaptureDevice来实现拍照。

 

使用PhotoCamera来拍照。主要有一些几个部分:

UI上核心的是VideoBrush,是用来显示摄像头内容的。而CompositeTransform则是用来随着手机转动而旋转VideoBrush的。

 

1     <Canvas.Background>
2                 <VideoBrush x:Name="viewfinderBrush" >
3                     <VideoBrush.RelativeTransform>
4                         <CompositeTransform x:Name="previewTransform"
5                             CenterX=".5"
6                             CenterY=".5" />
7                     </VideoBrush.RelativeTransform>
8                 </VideoBrush>
9             </Canvas.Background>

 

后台代码中需要定义相机的各种事件:

 1      //Code for initialization, capture completed, image availability events; also setting the source for the viewfinder.
 2         protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
 3         {
 4 
 5             // Check to see if the camera is available on the device.
 6             if ((PhotoCamera.IsCameraTypeSupported(CameraType.Primary) == true) ||
 7                  (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing) == true))
 8             {
 9                 // Initialize the camera, when available.
10                 if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
11                 {
12                     // Use front-facing camera if available.
13                     cam = new Microsoft.Devices.PhotoCamera(CameraType.FrontFacing);
14                 }
15                 else
16                 {
17                     // Otherwise, use standard camera on back of device.
18                     cam = new Microsoft.Devices.PhotoCamera(CameraType.Primary);
19                 }
20 
21                 // Event is fired when the PhotoCamera object has been initialized.
22                 cam.Initialized += new EventHandler<Microsoft.Devices.CameraOperationCompletedEventArgs>(cam_Initialized);
23 
24                 // Event is fired when the capture sequence is complete.
25                 cam.CaptureCompleted += new EventHandler<CameraOperationCompletedEventArgs>(cam_CaptureCompleted);
26 
27                 // Event is fired when the capture sequence is complete and an image is available.
28                 cam.CaptureImageAvailable += new EventHandler<Microsoft.Devices.ContentReadyEventArgs>(cam_CaptureImageAvailable);
29 
30                 // Event is fired when the capture sequence is complete and a thumbnail image is available.
31                 cam.CaptureThumbnailAvailable += new EventHandler<ContentReadyEventArgs>(cam_CaptureThumbnailAvailable);
32 
33                 // The event is fired when auto-focus is complete.
34                 cam.AutoFocusCompleted += new EventHandler<CameraOperationCompletedEventArgs>(cam_AutoFocusCompleted);
35 
36                 // The event is fired when the viewfinder is tapped (for focus).
37                 viewfinderCanvas.Tap += new EventHandler<GestureEventArgs>(focus_Tapped);
38 
39                 // The event is fired when the shutter button receives a half press.
40                 CameraButtons.ShutterKeyHalfPressed += OnButtonHalfPress;
41 
42                 // The event is fired when the shutter button receives a full press.
43                 CameraButtons.ShutterKeyPressed += OnButtonFullPress;
44 
45                 // The event is fired when the shutter button is released.
46                 CameraButtons.ShutterKeyReleased += OnButtonRelease;
47 
48                 //Set the VideoBrush source to the camera.
49                 viewfinderBrush.SetSource(cam);
50             }
51             else
52             {
53                 // The camera is not supported on the device.
54                 this.Dispatcher.BeginInvoke(delegate()
55                 {
56                     // Write message.
57                     txtDebug.Text = "A Camera is not available on this device.";
58                 });
59 
60                 // Disable UI.
61                 ShutterButton.IsEnabled = false;
62                 FlashButton.IsEnabled = false;
63                 AFButton.IsEnabled = false;
64                 ResButton.IsEnabled = false;
65             }
66         }

在这些事件中,最为重要的是Initialized、CaptureImageAvailable和CaptureThumbnailAvailable这三个事件。而CameraButtons.ShutterKeyHalfPressed、CameraButtons.ShutterKeyPressed和CameraButtons.ShutterKeyReleased三个事件就是手机物理拍照按键的事件了。

Initialized意味着相机初始化完毕,此时可以设置闪光灯模式、相机分辨率等。

 

 1   void _camera_Initialized(object sender, CameraOperationCompletedEventArgs e)
 2         {
 3             if (e.Succeeded)
 4             {
 5                 this.Dispatcher.BeginInvoke(delegate()
 6                 {
 7                     // 初始化闪光灯模式
 8                     _camera.FlashMode = FlashMode.Off;
 9                     btnFlash.Content = "闪光灯:Off";
10 
11                     // 初始化分辨率设置
12                     _camera.Resolution = _camera.AvailableResolutions.ElementAt<Size>(_currentResolutionIndex);
13                     btnResolution.Content = "分辨率:" + _camera.AvailableResolutions.ElementAt<Size>(_currentResolutionIndex);
14 
15                     lblMsg.Text = "主摄像头初始化成功";
16                 });
17             }
18         }

CaptureImageAvailable是当相片流得到后触发的。此时参数e的ImageStream就是图片流,可以按照需要保存。

 1 void cam_CaptureImageAvailable(object sender, Microsoft.Devices.ContentReadyEventArgs e)
 2         {
 3             string fileName = savedCounter + ".jpg";
 4 
 5             try
 6             {   // Write message to the UI thread.
 7                 Deployment.Current.Dispatcher.BeginInvoke(delegate()
 8                 {
 9                     txtDebug.Text = "Captured image available, saving picture.";
10                 });
11 
12                 // Save picture to the library camera roll.
13                 library.SavePictureToCameraRoll(fileName, e.ImageStream);
14 
15                 // Write message to the UI thread.
16                     Deployment.Current.Dispatcher.BeginInvoke(delegate()
17                 {
18                     txtDebug.Text = "Picture has been saved to camera roll.";
19 
20                 });
21 
22                 // Set the position of the stream back to start
23                 e.ImageStream.Seek(0, SeekOrigin.Begin);
24 
25                 // Save picture as JPEG to isolated storage.
26                 using (IsolatedStorageFile isStore = IsolatedStorageFile.GetUserStoreForApplication())
27                 {
28                     using (IsolatedStorageFileStream targetStream = isStore.OpenFile(fileName, FileMode.Create, FileAccess.Write))
29                     {
30                         // Initialize the buffer for 4KB disk pages.
31                         byte[] readBuffer = new byte[4096];
32                         int bytesRead = -1;
33 
34                         // Copy the image to isolated storage. 
35                         while ((bytesRead = e.ImageStream.Read(readBuffer, 0, readBuffer.Length)) > 0)
36                         {
37                             targetStream.Write(readBuffer, 0, bytesRead);
38                         }
39                     }
40                 }
41 
42                 // Write message to the UI thread.
43                 Deployment.Current.Dispatcher.BeginInvoke(delegate()
44                 {
45                     txtDebug.Text = "Picture has been saved to isolated storage.";
46 
47                 });
48             }
49             finally
50             {
51                 // Close image stream
52                 e.ImageStream.Close();
53             }
54 
55         }

CaptureThumbnailAvailable的代码和上面的几乎一样,只不过这个事件中参数e的ImageStream是拍照缩略图的流文件。

到此为止相机的准备工作就差不多了,那么当我真正要拍照时怎么做呢?以使用相机的物理拍照按键为例:

1  private void OnButtonFullPress(object sender, EventArgs e)
2         {
3             if (_camera != null)
4             {
5                 _camera.CaptureImage();
6             }
7         }

就是简单的一个CaptureImage方法即可。它会触发我们之前设置好的PhotoCamera的各种事件,最终得到图片流。

在使用CaptureThumbnailAvailable时随着手机的转动,如果不在代码中调整CompositeTransform的角度,会使得VideoBrush中显示的内容发生偏转。一般都是在页面的OnOrientationChanged事件中处理,具体的就不写出来了,请参考PhotoCamera的例子:

http://code.msdn.microsoft.com/Basic-Camera-Sample-52dae359

总体来说PhotoCamera是可以在APP中执行的,不需要墓碑化。而且使用起来也比较简单,它也可以设置是否对焦等相机的基本功能。但是似乎它存在一个致命的问题,说似乎是因为我找了很多办法都无法解决,在stack overflow上看到有人说这个确实解决不了。这个问题就是当你旋转手机时,即使你用代码的方式让VideoBrush显示正确了,但是拍摄出来的照片仍旧旋转角度不对,也就是说VideoBrush中看到的不是真正拍摄出来的。如果哪位看官有办法解决这个问题,还望不吝赐教。

 

最后说下PhotoCaptureDevice,其实我个人认为,搞定这个其它两个都可以不使用了。习惯看MSDN的同志应该都有看到这个类的使用,构建一个功能完全的拍照应用就应该也必须使用它。

先来设置必要的变量:

1    //设置摄像头是正面还是背面
2    public CameraSensorLocation cameraSensorLocation { get; set; }
3    //摄像头
4    public PhotoCaptureDevice curPhotoCaptureDevice { get; private set; }
5    //捕获序列
6    private CameraCaptureSequence cameraCaptureSequence;
7    //当前摄像头像素,目前采用的是最大像素
8    private Windows.Foundation.Size captureResolution;

 

先看下下面的代码:

 1       //检查手机是否支持摄像头
 2             if (PhotoCaptureDevice.AvailableSensorLocations.Contains(CameraSensorLocation.Back) ||
 3                 PhotoCaptureDevice.AvailableSensorLocations.Contains(CameraSensorLocation.Front))
 4             {
 5                 // 初始化摄像头信息
 6                 if (PhotoCaptureDevice.AvailableSensorLocations.Contains(CameraSensorLocation.Back))
 7                 {
 8                     //使用后置摄像头
 9                     BackSupportedResolutions = PhotoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Back);
10                     this.cameraSensorLocation = CameraSensorLocation.Back;
11                     this.captureResolution = BackSupportedResolutions[0];
12                 }
13                 else
14                 {
15                     //使用前置摄像头
16                     FrontSupportedResolutions = PhotoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Front);
17                     this.cameraSensorLocation = CameraSensorLocation.Front;
18                     this.captureResolution = FrontSupportedResolutions[0];
19                 }
20         }

      首先用PhotoCaptureDevice的静态方法来判定手机是否支持前置和后置摄像头。然后如果支持后置一般默认使用后置摄像头,当然这个是可以切换的。你需要得到一个相机的分辨率captureResolution,这个是在初始化相机时使用的,我使用是相机所支持的最大像素,只要拍摄出来的图片会比较大。以及一个相机位置cameraSensorLocation(前置或者后置)。

     接下来初始化相机:

1    //如果摄像头已经加载,则不重复加载
2    if (this.curPhotoCaptureDevice != null)
3      {
4          return false;
5      }
6    //创建PhotoCaptureDevice
7    this.cameraSensorLocation = cameraSensorLocation;
8    this.curPhotoCaptureDevice = await PhotoCaptureDevice.OpenAsync(this.cameraSensorLocation, captureResolution);

 当相机初始化完成后,可以指定相机的属性,比如下面的。这些属性是比较多的,具体需要请参靠MSDN。

1  curPhotoCaptureDevice.SetProperty(KnownCameraGeneralProperties.PlayShutterSoundOnCapture, true);
2  curPhotoCaptureDevice.SetProperty(KnownCameraGeneralProperties.AutoFocusRange, AutoFocusRange.Infinity);

设置完属性就需要设置捕获序列。捕获序列是很重要的一个东西,在创建完捕获序列后还可以设置帧的属性,比如可以设置相机场景模式:例如人物啊、风景啊,用过数码相机的都应该知道是怎么回事。这个就是PhotoCaptureDevice的强大之处,你可以创建一个非常不错的拍摄应用。最后在拍照前需要准备好捕获序列。

1 //捕获序列是发送给手机 CPU 的工作单位。当发起捕获时,使用它定义希望发生的内容。
2 //使用照片捕获设备上的方法创建捕获序列。需要指定的唯一参数是希望包括在序列中的帧的数量。
3 //在此版本中,该值将始终为 1。当发起捕获时,将会立即捕获帧。
4 this.cameraCaptureSequence = this.curPhotoCaptureDevice.CreateCaptureSequence(1);
5 //设置照相机场景模式
6 this.cameraCaptureSequence.Frames[0].DesiredProperties[KnownCameraPhotoProperties.SceneMode]
7                               = CameraSceneMode.Portrait;
8 await this.curPhotoCaptureDevice.PrepareCaptureSequenceAsync(this.cameraCaptureSequence);

最后看一下拍照的核心部分:

1   MemoryStream thumbnailStream = new MemoryStream();
2   MemoryStream imageStream = new MemoryStream();
3 
4   his.cameraCaptureSequence.Frames[0].ThumbnailStream =  thumbnailStream.AsOutputStream();
5   this.cameraCaptureSequence.Frames[0].CaptureStream = imageStream.AsOutputStream();
6   //最终获得图片的位置
7   await this.cameraCaptureSequence.StartCaptureAsync();

捕获序列的ThumbnailStream是拍照的缩略图流,而CaptureStream则是正常图片流。它们保存在了我们定义的thumbnailStream和imageStream中。这样你就可以自由的处理图片流了。

说了这么多,基本上PhotoCaptureDevice的拍照就完成了。等等,我好像忘记了什么。上面说到PhotoCamera在最终得到图片后旋转角度可能有问题,那么PhotoCaptureDevice中呢?其实在PhotoCaptureDevice中如果不专门设置也会有问题的,但是就因为PhotoCaptureDevice多了下面的属性设置可以使得拍照出来的流内容在编码前得到旋转。具体角度也是需要计算的,这个和PhotoCamera差不多。

1  this.curPhotoCaptureDevice.SetProperty(KnownCameraGeneralProperties.EncodeWithOrientation, 90);

下面给大家提供个MSDN上PhotoCaptureDevice的例子,这个例子很好的演示了如何使用PhotoCaptureDevice。

http://code.msdn.microsoft.com/Basic-Lens-sample-359fda1b

posted @ 2013-11-24 17:26  鱼丸粗面  阅读(620)  评论(0编辑  收藏  举报