天天看點

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