WindowsPhone8拍照功能实现简介

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

1、使用CameraCaptureTask;

2、使用PhotoCamera类;

3、使用PhotoCaptureDevice类。

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

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

 //以下代码均来自MSDN
//引用命名空间
using Microsoft.Phone.Tasks; //定义变量
CameraCaptureTask cameraCaptureTask; //页面构造函数中实例化
cameraCaptureTask = new CameraCaptureTask();
cameraCaptureTask.Completed += new EventHandler<PhotoResult>(cameraCaptureTask_Completed); //定义事件响应函数
void cameraCaptureTask_Completed(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK)
{
MessageBox.Show(e.ChosenPhoto.Length.ToString()); System.Windows.Media.Imaging.BitmapImage bmp = new System.Windows.Media.Imaging.BitmapImage();
bmp.SetSource(e.ChosenPhoto);
myImage.Source = bmp;
}
} //在需要使用任务的地方调用
cameraCaptureTask.Show();

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

WindowsPhone8拍照功能实现简介重要说明:

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

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

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

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

     <Canvas.Background>
<VideoBrush x:Name="viewfinderBrush" >
<VideoBrush.RelativeTransform>
<CompositeTransform x:Name="previewTransform"
CenterX=".5"
CenterY=".5" />
</VideoBrush.RelativeTransform>
</VideoBrush>
</Canvas.Background>

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

      //Code for initialization, capture completed, image availability events; also setting the source for the viewfinder.
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{ // Check to see if the camera is available on the device.
if ((PhotoCamera.IsCameraTypeSupported(CameraType.Primary) == true) ||
(PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing) == true))
{
// Initialize the camera, when available.
if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
{
// Use front-facing camera if available.
cam = new Microsoft.Devices.PhotoCamera(CameraType.FrontFacing);
}
else
{
// Otherwise, use standard camera on back of device.
cam = new Microsoft.Devices.PhotoCamera(CameraType.Primary);
} // Event is fired when the PhotoCamera object has been initialized.
cam.Initialized += new EventHandler<Microsoft.Devices.CameraOperationCompletedEventArgs>(cam_Initialized); // Event is fired when the capture sequence is complete.
cam.CaptureCompleted += new EventHandler<CameraOperationCompletedEventArgs>(cam_CaptureCompleted); // Event is fired when the capture sequence is complete and an image is available.
cam.CaptureImageAvailable += new EventHandler<Microsoft.Devices.ContentReadyEventArgs>(cam_CaptureImageAvailable); // Event is fired when the capture sequence is complete and a thumbnail image is available.
cam.CaptureThumbnailAvailable += new EventHandler<ContentReadyEventArgs>(cam_CaptureThumbnailAvailable); // The event is fired when auto-focus is complete.
cam.AutoFocusCompleted += new EventHandler<CameraOperationCompletedEventArgs>(cam_AutoFocusCompleted); // The event is fired when the viewfinder is tapped (for focus).
viewfinderCanvas.Tap += new EventHandler<GestureEventArgs>(focus_Tapped); // The event is fired when the shutter button receives a half press.
CameraButtons.ShutterKeyHalfPressed += OnButtonHalfPress; // The event is fired when the shutter button receives a full press.
CameraButtons.ShutterKeyPressed += OnButtonFullPress; // The event is fired when the shutter button is released.
CameraButtons.ShutterKeyReleased += OnButtonRelease; //Set the VideoBrush source to the camera.
viewfinderBrush.SetSource(cam);
}
else
{
// The camera is not supported on the device.
this.Dispatcher.BeginInvoke(delegate()
{
// Write message.
txtDebug.Text = "A Camera is not available on this device.";
}); // Disable UI.
ShutterButton.IsEnabled = false;
FlashButton.IsEnabled = false;
AFButton.IsEnabled = false;
ResButton.IsEnabled = false;
}
}

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

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

   void _camera_Initialized(object sender, CameraOperationCompletedEventArgs e)
{
if (e.Succeeded)
{
this.Dispatcher.BeginInvoke(delegate()
{
// 初始化闪光灯模式
_camera.FlashMode = FlashMode.Off;
btnFlash.Content = "闪光灯:Off"; // 初始化分辨率设置
_camera.Resolution = _camera.AvailableResolutions.ElementAt<Size>(_currentResolutionIndex);
btnResolution.Content = "分辨率:" + _camera.AvailableResolutions.ElementAt<Size>(_currentResolutionIndex); lblMsg.Text = "主摄像头初始化成功";
});
}
}

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

 void cam_CaptureImageAvailable(object sender, Microsoft.Devices.ContentReadyEventArgs e)
{
string fileName = savedCounter + ".jpg"; try
{ // Write message to the UI thread.
Deployment.Current.Dispatcher.BeginInvoke(delegate()
{
txtDebug.Text = "Captured image available, saving picture.";
}); // Save picture to the library camera roll.
library.SavePictureToCameraRoll(fileName, e.ImageStream); // Write message to the UI thread.
Deployment.Current.Dispatcher.BeginInvoke(delegate()
{
txtDebug.Text = "Picture has been saved to camera roll."; }); // Set the position of the stream back to start
e.ImageStream.Seek(, SeekOrigin.Begin); // Save picture as JPEG to isolated storage.
using (IsolatedStorageFile isStore = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream targetStream = isStore.OpenFile(fileName, FileMode.Create, FileAccess.Write))
{
// Initialize the buffer for 4KB disk pages.
byte[] readBuffer = new byte[];
int bytesRead = -; // Copy the image to isolated storage.
while ((bytesRead = e.ImageStream.Read(readBuffer, , readBuffer.Length)) > )
{
targetStream.Write(readBuffer, , bytesRead);
}
}
} // Write message to the UI thread.
Deployment.Current.Dispatcher.BeginInvoke(delegate()
{
txtDebug.Text = "Picture has been saved to isolated storage."; });
}
finally
{
// Close image stream
e.ImageStream.Close();
} }

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

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

  private void OnButtonFullPress(object sender, EventArgs e)
{
if (_camera != null)
{
_camera.CaptureImage();
}
}

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

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

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

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

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

先来设置必要的变量:

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

先看下下面的代码:

       //检查手机是否支持摄像头
if (PhotoCaptureDevice.AvailableSensorLocations.Contains(CameraSensorLocation.Back) ||
PhotoCaptureDevice.AvailableSensorLocations.Contains(CameraSensorLocation.Front))
{
// 初始化摄像头信息
if (PhotoCaptureDevice.AvailableSensorLocations.Contains(CameraSensorLocation.Back))
{
//使用后置摄像头
BackSupportedResolutions = PhotoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Back);
this.cameraSensorLocation = CameraSensorLocation.Back;
this.captureResolution = BackSupportedResolutions[];
}
else
{
//使用前置摄像头
FrontSupportedResolutions = PhotoCaptureDevice.GetAvailableCaptureResolutions(CameraSensorLocation.Front);
this.cameraSensorLocation = CameraSensorLocation.Front;
this.captureResolution = FrontSupportedResolutions[];
}
20         }

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

接下来初始化相机:

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

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

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

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

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

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

   MemoryStream thumbnailStream = new MemoryStream();
MemoryStream imageStream = new MemoryStream(); his.cameraCaptureSequence.Frames[].ThumbnailStream = thumbnailStream.AsOutputStream();
this.cameraCaptureSequence.Frames[].CaptureStream = imageStream.AsOutputStream();
//最终获得图片的位置
await this.cameraCaptureSequence.StartCaptureAsync();

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

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

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

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

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

上一篇:docker使用web界面管理Registry


下一篇:Gym - 101002D:Programming Team (01分数规划+树上依赖背包)