有关 Nokia Imaging SDK 的初级使用,可以参考:Nokia Imaging SDK滤镜使用入门
本文的主题:
1、如何 PhotoCaptureDevice 类使用,以及如何在 MediaElement 控件上显示摄像头预览。
2、如何通过 Nokia Imaging SDK 提供的滤镜,运用到摄像头的预览缩略图图片流上。相片拍照完成后
,再为高像素图片运用用户选择的滤镜效果。
3、通过本工程,开发者可以快速预览 Nokia Imaging SDK 所提供 53 种滤镜 的大致效果,可以在
实际的开发过程中,快速选择相应的滤镜进行使用。
本文的内容:
1、首先提供一个基本的代码架构(basic文件夹下)。接下来的步骤是基于这个基本代码工程进行操作的。“基本代码工程”
实现了照片拍摄(使用 WP8 中的高级拍照 PhotoCaptureDevice 类)的基本功能,拍照完成后跳转到照片预览页面。
基本代码的运行如图:
注意:本文不会对讲述 PhotoCaptureDevice 类的使用。参考 MSDN。
2、打开 basic 文件夹下的基本工程。在基本工程里面,已经添加好 Nokia Imaging SDK的相关类库。
工程目录及相关类和页面功能的说明:
3、因为工程完成后的代码量较大,为了更好的完成本工程,先介绍一下下面步骤将实现的功能。
1)单击屏幕,显示滤镜选择列表。包括 Nokia Imaging SDK 提供的 53个滤镜。
2)从滤镜列表选择滤镜后,摄像头取景框实时显示:
3)单击拍摄按钮,从 MainPage.xaml 页面跳转到ImagePreviewPage.xaml ,显示经过滤镜处理的图片(截图选择的是“魔术笔滤镜”):
4、在工程的根目录下新建一个文件夹“FilterComponent”,用来存储接下来的代码文件。
5、因为 MediaElement 有两个方法:
public void SetSource(MediaStreamSource mediaStreamSource); public void SetSource(Stream stream);
这里我们使用第一个方法。
在 FilterComponent 文件夹下新建一个 CameraStreamSource类,该类继承自 MediaStreamSource类。把这个CameraStreamSource
类用来作为 MediaElement 控件的数据源。
注:这里只列出重点方法的解释,并未贴出全部代码及注释。具体代码请查看Code_Snippets文件夹下的 CameraStreamSource.cs 文件。
6、重写上面定义的CameraStreamSource类中的两个重要方法:
protected override void OpenMediaAsync() protected override void GetSampleAsync(MediaStreamType mediaStreamType)
1)OpenMediaAsync 方法:收集实例化 MediaStreamDescription 对象的集合所需的元数据,然后对其进行实例化。可以把这个方法理解成,
当初始化为 MediaElement 视频流时会调用 OpenMediaAsync(),在这个方法里面,设置流数据信息,包括了流数据,视频宽高,视频时间,
当初始化完成后,调用 ReportOpenMediaCompleted(mediaSourceAttributes, mediaStreamDescriptions) 方法,来通知 MediaElement
控件,MediaStreamSource已经打开,并提供相关视频流信息。该方法仅会在视频信息初始化时调用一次。
具体的解释可以参考工程的代码注释。
2)GetSampleAsync 方法:导致 MediaStreamSource 准备一个 MediaStreamSample,它描述要由媒体管线呈现的下一个媒体示例。可以通过 ReportGetSampleCompleted 和 ReportGetSampleProgress 响应此方法。当为 MediaElement 控件提供视频流的每一帧时调用,此时我们能够
获得图像的帧数据,此时我们为该帧添加用户选择的滤镜效果。
7、在 FilterComponent 文件夹下,新建一个 FilterItem 类,该类有5个成员:
public class FilterItem { public int Index { get; set; } public string Name { get; set; } /// <summary> /// 引用当前用户选择的滤镜效果 /// </summary> public static FilterItem CurrentFilterItem { get; set; } /// <summary> /// 创建滤镜效果 /// </summary> /// <param name="index">当 index 不为空时,设置 index 指定的滤镜,如果为空,则指定 /// CurrentFilterItem.Index 指定的滤镜</param> /// <returns></returns> public static List<IFilter> CreateInstance(int? index = null) { … } /// <summary> /// 滤镜列表数据源 /// </summary> public static readonly List<FilterItem> FilterSource = new List<FilterItem> { … } }
该类的作用是为 MainPage 页面的滤镜列表提供数据源。其中CreateInstance() 方法通过Switch 语句来创建相应的滤镜实体。
注:这里只列出重点方法的解释,并未贴出全部代码及注释。具体代码请查看Code_Snippets文件夹下的 FilterItem.cs 文件。
8、在 FilterComponet 文件夹下,新建一个 NokiaImagingSDKEffects 类,在这个类中,我们主要添加一个
public async Task GetNewFrameAndApplyEffect(IBuffer frameBuffer, Size frameSize)
该方法的作用是为预览视频流的帧添加滤镜。该方法的全部代码:
/// <summary> /// 为预览视频流的帧添加滤镜 /// </summary> /// <param name="frameBuffer">帧数据</param> /// <param name="frameSize">帧尺寸</param> /// <returns></returns> public async Task GetNewFrameAndApplyEffect(IBuffer frameBuffer, Size frameSize) { var scanlineByteSize = (uint)frameSize.Width * 4; // 4 bytes per pixel in BGRA888 mode var bitmap = new Bitmap(frameSize, ColorMode.Bgra8888, scanlineByteSize, frameBuffer); // 通过 _photoCaptureDevice 创建一个新的 image source _cameraPreviewImageSource = new CameraPreviewImageSource(_photoCaptureDevice); if (FilterItem.CurrentFilterItem != null) { _filterEffect = new FilterEffect(_cameraPreviewImageSource) { Filters = FilterItem.CreateInstance() }; var renderer = new BitmapRenderer(_filterEffect, bitmap); await renderer.RenderAsync(); } else { var renderer = new BitmapRenderer(_cameraPreviewImageSource, bitmap); await renderer.RenderAsync(); } }
注:这里只列出重点方法的解释,并未贴出全部代码及注释。具体代码请查看Code_Snippets文件夹下的 NokiaImagingSDKEffects.cs 文件。
9、在 FilterComponent 文件夹下创建一个 MainPage_Realtime.cs 文件,该文件存放的类是 MainPage 类的分部类,使用关键字 partial
关键字把新代码和基础代码工程的 MainPage 类进行区分:
public partial class MainPage : PhoneApplicationPage { … }
在该文件中添加三个全局变量:
private MediaElement _mediaElement = null; private NokiaImagingSDKEffects _cameraEffect = null; private CameraStreamSource _cameraStreamSource = null;
并添加一个 InitializeEffect() 方法来初始这些变量。
10、修改 MainPage 类。
1)为 MainPage.xaml 页面中,DataTemplate 中为 Image 控件添加一个 Loaded 事件,当该控件加载完成后,添加预览滤镜图片:
<Image Loaded="Image_Loaded"/>
MainPage_Realtime.cs 文件中添加该 Image_Loaded 方法:
/// <summary> /// 当滤镜列表模版中的 Image 加载完成后,添加相应的滤镜预览图 /// </summary> private async void Image_Loaded(object sender, RoutedEventArgs e) { Image image = sender as Image; // 读取根目录下的示例图片 Stream _imageSource = App.GetResourceStream(new Uri("Sample.jpg", UriKind.Relative)).Stream; // 获取当前 Item 的 DataContext FilterItem item = image.DataContext as FilterItem; WriteableBitmap writeableBitmap = new WriteableBitmap(100, 100); // 为示例图片添加相应的滤镜效果 using (var source = new StreamImageSource(_imageSource)) using (var filterEffect = new FilterEffect(source) { Filters = FilterItem.CreateInstance(item.Index).ToArray() }) using (var renderer = new WriteableBitmapRenderer(filterEffect, writeableBitmap)) { await renderer.RenderAsync(); } // 显示到页面中 image.Source = writeableBitmap; }
2)在 MainPage.xaml 页面,为 ListBox 中 DataTemplate 模版中的 Border 控件添加 Tap="Border_Tap" 事件,来把用户选择的滤镜效
果设置为全局共享变量,该事件在 MainPage 的分部类中实现:
/// <summary> /// 用户单击滤镜列表中的滤镜项 /// </summary> private void Border_Tap(object sender, System.Windows.Input.GestureEventArgs e) { //e.Handled = true; FilterItem.CurrentFilterItem = (sender as Border).DataContext as FilterItem; }
2)在 MainPage.xaml 给名为 LayoutRoot 的 grid 添加一个 Tap="LayoutRoot_Tap" 事件,来动态切换滤镜列表的显示与隐藏,
该事件在 MainPage 的分部类中实现:
/// <summary> /// 单击屏幕时,切换滤镜列表 /// </summary> private void LayoutRoot_Tap(object sender, System.Windows.Input.GestureEventArgs e) { e.Handled = true; if (listbox_preview.Visibility == System.Windows.Visibility.Collapsed) { listbox_preview.Visibility = System.Windows.Visibility.Visible; } else { listbox_preview.Visibility = System.Windows.Visibility.Collapsed; } }
3)添加完上面代码,在 MainPage.xaml.cs 文件中,修改 OnNavigatedTo() 方法,把 BackgroundVideoBrush.SetSource(_photoCaptureDevice);
替换为 MainPage 分部类种添加的 InitializeEffect(); 当页面导航到 MainPage 页面后,初始化相应的控件和滤镜。
同样,在 OnNavigatingFrom() 中,添加 MainPage分部类 中的 Uninitialize_Realtime();方法,以当页面导航离开时,释放资源。
注:这里只列出重点方法的解释,并未贴出全部代码及注释。具体代码请查看Code_Snippets文件夹下的 MainPage_Realtime.cs 文件。
11、ImagePreviewPage.xaml.cs 页面中,为 PhotoCaptureDevice 类捕获的高分辨率图片添加用户选择的滤镜效果。因为高分辨率图片保存在了 ImageDataContext.Singleton.ImageStream 属性中,所以直接对它添加滤镜效果,然后显示在页面上:
protected override async void OnNavigatedTo(NavigationEventArgs e) { ImageDataContext dataContext = ImageDataContext.Singleton; if (dataContext.ImageStream != null) { // 显示默认没有滤镜效果的图片 //BitmapImage bi = new BitmapImage(); //bi.SetSource(dataContext.ImageStream); //image.Source = bi; dataContext.ImageStream.Seek(0, SeekOrigin.Begin);//严重注意!!!!!!!! using (var source = new StreamImageSource(dataContext.ImageStream)) using (var filterEffect = new FilterEffect(source) { Filters = FilterItem.CreateInstance() }) using (var renderer = new JpegRenderer(filterEffect)) { Windows.Storage.Streams.IBuffer buf = await renderer.RenderAsync(); Stream stream = System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBufferExtensions.AsStream(buf); BitmapImage bi = new BitmapImage(); bi.SetSource(stream); image.Source = bi; } } base.OnNavigatedTo(e); }
代码工程完成后,运行效果:
上面的 gif 图片快速演示了 Nokia Imging SDK 中的 53 种滤镜,并且给取景器快速运用其中对比比较明显的几个滤镜,拍照
后,即可得到经过滤镜处理的全像素照片。
总结:
本实验代码量比较多,没有全部粘贴工程代码,而是先在 basic 文件夹下提供一个用 PhotoCaptureDeVice类实现的拍
照功能的基本代码工程,然后在该文件夹下的 Code_Snippets 文件夹中,提供相应的代码片段。动手实验文档中主要讲解了重点代码的作用,
并且 Nokia Imaging SDK 提供的 全部 53种滤镜,都只是设置了默认值,具体参数的调整已经添加到工程的代码注释上。
有关 Nokia Imaging SDK 滤镜使用流程,请参考相关文档
代码工程下载:http://pan.baidu.com/s/1sj4RKo1
提示:
1、因为本实验会使用到手机摄像头,所以建议通过真机调试
2、在运行源代码时,会出现一个编译错误: Nokia Imaging SDK does not support the AnyCPU target platform.
因为 Nokia Imaging SDK 支持托管代码和本地代码,所以在编译前需要进行设置:
1)在模拟器上运行时:菜单 -> 生成 -> 配置管理器 -> 活动解决方案平台 -> x86 2)在真机上运行时: 菜单 -> 生成 -> 配置管理器 -> 活动解决方案平台 -> ARM