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