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