在UWP应用里,如果我们需要调用设备的摄像头拍照并保存到文件,这曾经是比较复杂的。开发者需要了解许多知识,也要写一定量代码才能完成(就像你可以从微软样例代码(https://github.com/Microsoft/Windows-universal-samples)中找到的Camera相关例子那样)。实际上,在许多情况下,我们仅需要用最简单的默认摄像头选项来拍照。这种场合里我们的关注点在应用逻辑本身,而不需要花费数个小时在如何调通摄像头代码上。
现在,有了Windows Community Toolkit,这件事情得十分简单。
CameraPreview控件提供了一个非常直接的方式让我们操作摄像头,而不用了解其中的细节原理。
1
使用NuGet安装
PM> Install-Package Microsoft.Toolkit.Uwp.UI.Controls
2
在你的UWP应用中启用WebCam权限
你可以直接从Visual Studio的界面中打开Package.appxmanifest文件来操作
也可以手工编辑这个文件的代码,加入DeviceCapability节点
<Capabilities>
<Capability Name="internetClient" />
<uap:Capability Name="picturesLibrary" />
<DeviceCapability Name="webcam" />
</Capabilities>
3
创建应用界面
首先,在你的Page或者UserControl里加入下面的命名空间
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
然后加入CameraPreview控件,这仅需要一行XAML代码
<controls:CameraPreview x:Name="CameraPreviewControl" PreviewFailed="CameraPreviewControl_OnPreviewFailed" />
PreviewFailed事件不是必须的,但建议加上,这样可以提高用户体验也便于debug
为了演示,我添加了3个按钮
<StackPanel Grid.Column="1" VerticalAlignment="Center" Margin="20,0,0,0">
<Button Content="Start" x:Name="BtnStart" Margin="0,0,0,10" Click="BtnStart_Click" />
<Button Content="Stop" x:Name="Stop" Margin="0,0,0,10" Click="Stop_Click"/>
<Button Content="Capture" x:Name="BtnCapture" Click="BtnCapture_Click" />
</StackPanel>
4
代码
首先,我们完成PreviewFailed事件处理。我想要在摄像头启动失败的情况下给用户弹出一个错误消息的对话框:
private async void CameraPreviewControl_OnPreviewFailed(object sender, PreviewFailedEventArgs e)
{
await new MessageDialog(e.Error, "ERROR").ShowAsync();
}
注意,async void仅仅用于像这样的事件处理函数,其他地方请用async Task
现在让我们启动摄像头
private async void BtnStart_Click(object sender, RoutedEventArgs e)
{
await CameraPreviewControl.StartAsync();
}
到这部为止,你已经能够看到正常工作的界面了,摄像头的画面也会出现在你的应用里。
要停止摄像头,仅需调用Stop()方法
private void Stop_Click(object sender, RoutedEventArgs e)
{
CameraPreviewControl.Stop();
}
那么我们如何拍照并保存到文件系统呢?我们需要另一个事件处理函数:
CameraPreviewControl.CameraHelper.FrameArrived
这个例子里,我把事件处理代码加在Start按钮里:
private async void BtnStart_Click(object sender, RoutedEventArgs e)
{
await CameraPreviewControl.StartAsync();
CameraPreviewControl.CameraHelper.FrameArrived += CameraPreviewControl_FrameArrived;
}
在事件处理函数中,我想要把当前的摄像头画面保存到一个私有字段里:
private VideoFrame _currentVideoFrame;
private void CameraPreviewControl_FrameArrived(object sender, FrameEventArgs e)
{
_currentVideoFrame = e.VideoFrame;
}
现在当我们点击Capture按钮,我们就把当前的这帧画面保存到本地文件系统。摄像头的图形是用SoftwareBitmap对象来表示的。我们能够通过_currentVideoFrame?.SoftwareBitmap来访问到。
让用户选择文件保存的位置,我们需要用FileSavePicker,它会返回一个StorageFile对象,表示我们要保存的照片文件。你也可以选择文件格式,我这里选择照片最常用的JPG格式。
private async void BtnCapture_Click(object sender, RoutedEventArgs e)
{
var softwareBitmap = _currentVideoFrame?.SoftwareBitmap;
if (softwareBitmap != null)
{
if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 || softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
{
softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
var savePicker = new FileSavePicker
{
SuggestedStartLocation = PickerLocationId.Desktop
};
savePicker.FileTypeChoices.Add("Jpg Image", new[] { ".jpg" });
savePicker.SuggestedFileName = "example.jpg";
StorageFile sFile = await savePicker.PickSaveFileAsync();
await WriteToStorageFile(softwareBitmap, sFile);
}
}
最后一步,把SoftwareBitmap保存到StorageFile的代码如下
private static async Task<FileUpdateStatus> WriteToStorageFile(SoftwareBitmap bitmap, StorageFile file)
{
StorageFile sFile = file;
if (sFile != null)
{
CachedFileManager.DeferUpdates(sFile);
using (var fileStream = await sFile.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, fileStream);
encoder.SetSoftwareBitmap(bitmap);
await encoder.FlushAsync();
}
FileUpdateStatus status = await CachedFileManager.CompleteUpdatesAsync(sFile);
return status;
}
return FileUpdateStatus.Failed;
}
Tips
如果你想要了解CameraPreivew控件,可以点击官网GitHub了解一下:
https://github.com/Microsoft/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Controls/CameraPreview