这篇文章是Mango开发31日谈系列文章的第七天。
今天我们将要谈谈Windows Phone手机当中的内置摄像头,以及我们如何在自己的程序中使用它。这并不是一篇对启动器(Launchers)和选择器(Choosers)讨论的文章,因为关于它们已经在之前Windows Phone开发31日谈#7文章中讲过了。他们可以允许你提示用户在手机中去拍摄或选择一张照片。而这篇文章将会包含在屏幕中显示摄像头原始数据,捕获图片,使用硬件按钮,和保存图片到用户手机中。
本程序可以在微软Marketplace中下载到:
当我们谈论摄像头原始数据时,其实我们已经在谈论如何在程序中直接使用摄像头来获取实时画面。我想向你展示该过程的最好方法就是使用一个简短视频来帮你理解:
现在你应该明白我们到底要做什么了吧(不过我们所要完成的还不止这些),现在让我们开始代码的编写!
显示摄像头原始数据(Camera Feed)
要完成一个吸引人的摄像头程序时,我们首先要获取屏幕上的摄像头原始数据。这一步非常简单。我们将在XAML页面创建一个Rectangle 控件,然后在C#文件中将它的source设置为一个PhotoCamera对象。下面是我们完成XAML文件:
<phone:PhoneApplicationPage
x:Class="Day7_RawCamera.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="31 DAYS OF MANGO" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="raw camera" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Rectangle x:Name="ViewBox" Height="460" Margin="-22,-1,-131,148">
<Rectangle.Fill>
<VideoBrush x:Name="CameraSource" />
</Rectangle.Fill>
<Rectangle.RenderTransform>
<RotateTransform Angle="90" CenterX="240" CenterY="240" />
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
正如你所看到的,我们添加了一个Rectangle到默认页面模板中,然后定义了它的Fill属性为一个VideoBrush,名称为“CameraSource”。你应该会注意到我对Rectangle使用了RenderTransform属性。通过旋转90度,我们手机可以自适应横竖屏切换。在我们的C#后台代码中,我们需要将照相机数据赋值给VideoBrush。我是通过创建一个PhotoCamera对象来完成这些的,其名称为“camera”。然后设置VideoBrush 的源为PhotoCamera 对象。
using System;
using System.Windows;
using Microsoft.Phone.Controls;
using Microsoft.Devices;
using System.Windows.Media.Imaging;
using System.Windows.Media;
namespace Day7_RawCamera
{
public partial class MainPage : PhoneApplicationPage
{
PhotoCamera camera;
// Constructor
public MainPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
camera = new PhotoCamera(CameraType.FrontFacing);
else
camera = new PhotoCamera(CameraType.Primary);
CameraSource.SetSource(camera);
}
protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
if (camera != null)
{
camera.Dispose();
}
}
}
}
正如我之前所说的,这个示例非常简单,但是有两点比较重要的细节需要注意。你可以在上面的代码中注意到,当我创建一个新的PhotoCamera对象时,我使用了参数CameraType.Primary 和 CameraType.FrontFacing。这表明我想使用前置还是后置摄像头。最新的Mango版本的手机可能会有前置摄像头这个选项。在这个示例中,我将使用标准的摄像头,除非你有可用的前置摄像头。
你还会发现我使用了OnNavigatedTo 和OnNavigatingFrom事件,每一此用户导航到该界面(在程序内部,或从其他程序导航回来),我想确保摄像头被初始化。我使用OnNavigatingFrom方法来在离开该页面时对对其销毁。这将可以延长设备上的电池和内存寿命,而且对摄像头的正常工作工作也是必要的。
来看看这个应用程序。如果你将其部署在一个Windows Phone设备上,你会看到照相机所拍摄的一切,就像视频。如果你使用的是模拟器,你会看到如下图所示:
模拟器并不能利用网络摄像头或是其他视频源,所以只能显示一个白框来表示其是一张图片。一个黑色的小矩形在屏幕周围旋转,当我们捕获一张图片时,一张相似的图片会返回给我们。下面让我们看看他是如何工作的:
照相
下一步我想利用摄像头程序使得用户可以照一张相片。先让我们添加一个按钮到用户界面当中。在之前创建Rectangle下面添加一个按钮,其代码如下:
<Button Foreground="Green" BorderBrush="Green" Content="Capture" Height="72" HorizontalAlignment="Left" Margin="6,535,0,0" Name="CaptureButton" VerticalAlignment="Top" Width="160" Click="CaptureButton_Click" />
我将其设置为绿色,他将显示在视频源的上方,如果你使用的是模拟器(90%大小),你不会看到它。我们可以创建一个Click事件处理程序来触发手机拍照。
打开你的后台代码文件,我们需要写一些方法。首先是用于按钮的事件处理方法。他只需要一行代码就可以完成,因为捕获图片是一个过程,我们要确保每次捕获发生在下一次开始之前。对于这个示例,我添加了try/catch来防止出现异常。
private void CaptureButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
try { camera.CaptureImage(); }
catch (Exception ex) { Dispatcher.BeginInvoke(() => MessageBox.Show(ex.Message)); }
}
你可以看到最主要的一行代码是camera.CaptureImage()的调用。只需要使用这一行代码,然后快速点击Capture按钮多次即可。你会发现出现异常提示,注意你的操作需要确保线程安全。每一次你调用设备的传感器时,你都需要确保你在编写一个线程清晰的代码。接着我将向你展示何时我们会获得来自照相机返回的图片。
CaptureImage()方法的调用是为了通知摄像头我们需要捕获一张图片。如果我们不创建一个用于接收它完成事件句柄,我们不会获得任何结果。要完成这些,我们需要通过PhotoCamera 对象调用CaptureImageAvailable 。
你可以拷贝粘贴这里所有的代码到你的项目当中。在OnNavigatedTo方法中我们创建了 camera_CaptureImageAvailable事件句柄 (它将其结果传送至一个独立线程),其指定的ThreadSafeImageCapture 方法用于获取到捕获到的图片结果。注意,在OnNavigatingFrom()方法中我们也对该事件处理进行了注销。这能延长你手机内存使用和电池的寿命。
using System;
using System.Windows;
using Microsoft.Phone.Controls;
using Microsoft.Devices;
using System.Windows.Media.Imaging;
using System.Windows.Media;
namespace Day7_RawCamera
{
public partial class MainPage : PhoneApplicationPage
{
PhotoCamera camera;
// Constructor
public MainPage()
{
InitializeComponent();
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
camera = new PhotoCamera(CameraType.FrontFacing);
else
camera = new PhotoCamera(CameraType.Primary);
camera.CaptureImageAvailable += new System.EventHandler<ContentReadyEventArgs>(camera_CaptureImageAvailable);
CameraSource.SetSource(camera);
}
protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
if (camera != null)
{
camera.Dispose();
camera.CaptureImageAvailable -= camera_CaptureImageAvailable;
}
}
private void CaptureButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
try { camera.CaptureImage(); }
catch (Exception ex) { Dispatcher.BeginInvoke(() => MessageBox.Show(ex.Message)); }
}
void camera_CaptureImageAvailable(object sender, ContentReadyEventArgs e)
{
Dispatcher.BeginInvoke(() => ThreadSafeImageCapture(e));
}
void ThreadSafeImageCapture(ContentReadyEventArgs e)
{
BitmapImage image = new BitmapImage();
image.SetSource(e.ImageStream);
ImageBrush still = new ImageBrush();
still.ImageSource = image;
ViewBox.Fill = still;
}
}
}
在ThreadSafeImageCapture方法中,我们需要创建一个新的BitmapImage 对象(它需要使用System.Windows.Media.Imaging声明),我将BitmapImage 的source设置为所捕获的图片。
接下来一步是用来将我们捕获的图片显示在屏幕中。
这是一个有关摄像头程序的很好开始,然而我们仍有很多问题需要解决,我们还要讨论如何将所捕获的图片存入手机相册中。
使用摄像头硬件按钮
为了使用摄像头硬件按钮,有三个事件需要触发:ShutterKeyHalfPress, ShutterKeyPressed, 和 ShutterKeyReleased。当我们建立自己的摄像头程序时,该按钮不会有其默认的行为,因此我们需要自己去实现它。
- ShutterKeyHalfPress – 将摄像头作为焦点
- ShutterKeyPress –拍照
- ShutterKeyReleased – 取消摄像头焦点。
首先我们需要实现每一个事件,他们的事件句柄在OnNavigatedTo 方法中进行了定义,我们要在之后建立对每一个事件处理方法。
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
camera = new PhotoCamera(CameraType.FrontFacing);
else
camera = new PhotoCamera(CameraType.Primary);
camera.CaptureImageAvailable += new System.EventHandler<ContentReadyEventArgs>(camera_CaptureImageAvailable);
CameraSource.SetSource(camera);
CameraButtons.ShutterKeyHalfPressed += new EventHandler(CameraButtons_ShutterKeyHalfPressed);
CameraButtons.ShutterKeyPressed += new EventHandler(CameraButtons_ShutterKeyPressed);
CameraButtons.ShutterKeyReleased += new EventHandler(CameraButtons_ShutterKeyReleased);
}
我们先为ShutterKeyHalfPressed事件创建一个事件处理方法,这主要是因为它在三个事件中最为重要(尽管很简单)。接下来,我们需要检测是否存在PhotoCamera 对象,然后通过它来调用focus方法,此处我采用了try/catch方法进行处理,但并没有对捕获的异常进行处理,在每次捕获图片时,都会引发该异常,因为按钮在按下的过程中会有两次处于“一半按下”的状态。你可以将此记录在日志中,我在这里并没有对其进行处理。
void CameraButtons_ShutterKeyHalfPressed(object sender, EventArgs e) { if (camera != null) { try { camera.Focus(); } catch (Exception ex) { } } }
对于ShutterKeyPressed事件,当我创建一个按钮用来捕获图片时,我将使用和之前相同的代码。
void CameraButtons_ShutterKeyPressed(object sender, EventArgs e)
{
if (camera != null)
{
try { camera.CaptureImage(); }
catch (Exception ex) { Dispatcher.BeginInvoke(() => MessageBox.Show(ex.Message)); }
}
}
最后,我们还有ShutterKeyReleased 事件。在这里我们只想注销之前ShutterHalfKeyPressed 事件。CancelFocus并不会取消图片焦点,而是停止摄像头工作。
void CameraButtons_ShutterKeyReleased(object sender, EventArgs e)
{
if (camera != null)
camera.CancelFocus();
}
最后一步是通过事件来确保当我们离开页面时,我们之前创建的事件全部被注销。
protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
if (camera != null)
{
camera.Dispose();
camera.CaptureImageAvailable -= camera_CaptureImageAvailable;
CameraButtons.ShutterKeyHalfPressed -= CameraButtons_ShutterKeyHalfPressed;
CameraButtons.ShutterKeyPressed -= CameraButtons_ShutterKeyPressed;
CameraButtons.ShutterKeyReleased -= CameraButtons_ShutterKeyReleased;
}
}
此时,你应该可以在你程序中使用摄像头硬件按钮了,图像会显示在屏幕中。下一步,我们将对其进行保存。
保存图片到相册
微软为我们提供了MediaLibrary 类可以很好的帮助我们完成此操作。通过该类你可以在相册中找到之前所保存的图片,并且可以同步到电脑上。
这整个过程的实现其实只有两行代码,首先在页面顶部创建PhotoCamera 对象的下方实例化MediaLibrary。
MediaLibrary library = new MediaLibrary();
然后将之前的ThreadSafeImageCapture 方法替换成如下代码:
void ThreadSafeImageCapture(ContentReadyEventArgs e)
{
library.SavePictureToCameraRoll(DateTime.Now.ToString() + ".jpg", e.ImageStream);}
现在你的程序应该可以在手机上运行了,你可以拍一张照片然后看它是否存在相册中。下面是我的一个简短视频。
这个程序只是一个简短的示例,其实你可以利用所得的数据进行其他许多操作,比如拍摄视频,对图片进行处理,甚至改变闪光设置,在MSDN上有个一个代码示例系列教程,你可以找到有关摄像头使用的示例。
总结
这篇文章貌似有点长,不过在这里你学习到了如果向用户展示原始照片数据,如何创建一个用户界面来捕获图片,如何使用手机中的照相机硬件按钮,最后是如何保存图片到相册。希望你能理解它。
下载完整代码示例可以点击下面图标。
明天我们将有一次飞跃,我会带领你去看看手机中的可用的用户数据,教你如何访问联系人列表。明天见!