03、核心原则
为读者介绍 Imaging SDK 基本的知识,以及更高级特性。
这个类库
在使用 Nokia Imaging SDK 提供的功能前,必须在工程中添加对 SDK 类库的引用。可以使用 VS 的
NuGet 包管理器。关于更多介绍,可以参考:Download and add the libraries to the project.
这个 Nokia Imaging SDK 是一个 Windows Phone Runtime 库。有个关键的优势就是开发者可以使用
C#、Visual Basic、C++ 调用库中的方法。更多参考 Windows Phone Runtime Windows Phone API reference (MSDN).
基本架构模块
这个 SDK 允许你在不必解码整张 JPEG 图片的前提下,提供快速的预览、旋转、裁切高像素图片,
并且添加提供的 50多个滤镜中的一个或者多个效果。所有这些情景的混合实现需要满足三个要素:
一个 Image Source,一个 Render,和一个 Effect 来组合成一个操作序列,或一个图谱。
因为它们都实现了 “IImageProvider” 和 “IImageConsumer” 接口,所以它们以不同的方式
组合形成链接和分支,灵活地实现了一个图片处理流程的强大的“图谱”。
下面三种类型的元素用于执行图片的处理流程:
— Image source:通常放在处理管道的开始。原始图片的来源有多种方式,比如从 storage 中加载,
然后对它设置后,在处理管道中做进一步的处理。所有的 image sources 都实现了 IImageProvider 接口。
— Effect:获取一张或者多张图片源后,经过不同方式的处理,输出一张新图片。所有的 effects 都实现了
IImageConsumer 接口,从而使它接受一个初级原始图片。如果需要第二张图片源,也会提供相应的属性。
同样的,为了成为图片源,所有的 effects 同样实现了 IImageProvider 接口,从而输出经过处理的图片。
— Render:作为处理管道的结束。把最终图片输出成相应的格式来供程序使用。所有的 renders 都实现了
IImageConsumer 接口,因为它们需要接收图片源。
这个 SDK 包含了很多 image sources,effects 和 renderers 的具体实现,来满足各种各样的需求。
根据不同的图片源选择相应的 image source 类。示例:StreamImageSource,BufferImageSource,
BitmapImageSource。
根据不同类型的处理选择相应的 effect 类。一个示例是 FilterEffect,通过它可以添加 SDK 提供的 50+
的图片滤镜序列。
根据需要输出的不同类型,来选择选择相应的 renders 类。示例:JpegRender 和 BitmapRenderer。
一旦创建完成,应用程序可以保持这些对象的引用,根据需要重用或者组装成不同的处理管道。
一个处理管道的实际示例
例如用户已经通过 PhotoChooserTask 选择一张图片,获得一个 System.IO.Stream 对象。我们为
图片选择两个滤镜,一个 Antique filter,然后一个 Rotation filter。图片处理的结果被输出到一个
WriteableBitmap 对象中。
原图片 | 经过 “Antique” 滤镜处理 | 经过“Rotation”滤镜处理 |
处理管道会设置成:
1、使用一个 JPEG 图片的 System.IO.Stream 流创建一个 StreamImageSource 对象
2、创建一个 FilterEffect 对象,并且把上面的 StreamImageSource 对象作为源传递进去。
创建一个轻量级的滤镜列表,并且分配给 FilterEffect。
3、创建一个 WriteableBitampRender ,并把上面的 FilterEffect 作为源传递进去,同时传递一个
WriteableBitmap 对象作为输出。
4、调用 renderer 的 RenderAsync() 方法,从而输出一个包含处理结果的 WriteableBitmap 图片。
5、释放对象。
下面是 C# 示例代码:
var filters = new IFilter[] { new AntiqueFilter(), new RotationFilter(35.0) }; using (var source = new StreamImageSource(stream)) using (var filterEffect = new FilterEffect(source) { Filters = filters }) using (var renderer = new WriteableBitmapRenderer(filterEffect, writeableBitmap)) { await renderer.RenderAsync(); }
注意到创建的 IFilter 接口的 array,FilterEffect 使用的所有的轻量级的滤镜都实现了 IFilter 接口。
当 rendering 异步操作正在执行时,尝试调用参与对象的方法或者改变它们的属性都会导致抛出一个
exception。留意保护它们以防意外的结果。
当 rendering 操作完成后,应用程序有能自由的更改处理管道中对象的属性,例如 RotationFilter 对象
的角度。想看到新结果,仅仅需要再次调用 renderer 的 RenderAsync 方法。
最终的输出数据保存在 传递到 WriteableBitmapRender 中的 WriteableBitmap 对象。它同样可以
作为返回类型 IAsyncOperation<WriteableBitmap> 通过 RenderAsync 方法返回,因此应用程序
可以传递这个 IAsyncOperation 给程序的其它部分,来作为 “future result”,而不必必须跟踪原始的
WriteableBitmap 对象。
同样通过这个示例可以看出,参与处理的对象,通过 using 语句进行 created 和 disposed。当对图片进行
简单的处理时,很推荐这么做。当然,在不需要的时候对这些对象进行释放,同样可以保持对它们的引用,在
其它渲染操作中进行重用。
Image sources
一个图片源可能以各种格式存在。开发者可以根据需要选择相应的格式,既可以选择压缩的数据格式 比
如 JPEG(占用很少的内存)或者处理 raw bitmaps。下面是包含的数据源类:
1)BimapImageSource
当数据源是 Nokia.Graphics.Imaging.Bitmap 中的 raw pixels 时选择它。同样,可以通过 WriteableBitmap
的扩展方法 AsBuffer 获得:
new BitmapImageSource(writeableBitmap.AsBitmap)
2)BufferImageSource
当源图片文件的数据(JPEG/JFIF(包含 EXIF 元数据) ,PNG 等等)存储在一个 WinRT IBuffer 对象中
时选择这个它。
3)CameraPreviewImageSource
当源图片是一个 ICameraCaptureDevice 的预览数据时使用它。当每次在处理管道中
调用 RenderAsync 方法时,这个 image source 会加载最新的预览图片数据。除了预览
的数据,它不会影响 ICameraCaptureDevice,所以可以在拍摄的情形下以插入的方式使用。
4)ColorImageSource
当源图片是一个纯平面彩图时使用它。使用 ColorImageSource 可以避免创建占用内存的位图。
5)DelegatingImageSource
当源图片是实现了 ICustomImageSource 接口的用户自定义类产生时使用它。当用户自定义类创建
时就附加到了这个 DelegatingImageSource 上。在 C# 中,通过使用 CustomImageSourceBase
这个基类更加的容易。参考章节 “CustomImageSourceBase”。
6)GradientImageSource
当源图片是一种颜色的渐变时使用它。使用 GradientImageSource 可以避免创建占用内存的位图对象。
下面的示例演示了如何创建一张从红色到绿色渐变的图片:
var rad = new RadialGradient(new Windows.Foundation.Point(0.5, 0.5), new EllipseRadius(0.3, 0.3)); rad.Stops = new GradientStop[] { new GradientStop() { Color = Windows.UI.Color.FromArgb(255, 255, 0, 0), Offset = 0 }, new GradientStop() { Color = Windows.UI.Color.FromArgb(255, 0, 255, 0), Offset = 1 } }; using (var grad = new GradientImageSource( new Windows.Foundation.Size(Width, Height), rad)) { var buffer = await new JpegRenderer(grad).RenderAsync(); }
图片1 GradientImageSource 代码示例的结果
7)RandomAccessStreamImageSource
当源图片是一个 WinRT IRandomAccessStream 类型的文件数据(JPEG/JFIF(包含 EXIF 元数据),PNG 等等)时使用它。
8)StorageFileImageSource
当源图片是一个 WinRT IStorageFile 类型的文件数据(JPEG/JFIF(包含 EXIF 元数据),PNG 等等)时使用它。
9)StreamImageSource
当源图片是一个 .NET System.IO.Stream 类型的文件数据(JPEG/JFIF(包含 EXIF 元数据),PNG 等等)时使用它。
Effects
Effects 以一些方式处理图片,并且是处理管道中占据架构最大的部分。在这个 release 版本中,
提供了下面的 effects:
FilterEffect
当你想要运用一个或多个 SDK 中包含的轻量级滤镜时使用它。这个 effect 按顺序运用列表中的 filters
到图片上。一个形象的类比是堆叠光学滤镜的单反相机。这个 SDK 提供超过 50种滤镜:Sepia, MagicPen
, Antique 等等。
应用程序可能经常在处理管道中,选择使用一个单一的 FilterEffect 。可以把它组合成一个固定的模块
来被替换使用。
1)ReframingFilter
这个 reframing filter 允许用户指定一个新 “画布”来自由高效的重构图片。图片重构的区域可以在上面通过
指定一个 rectangle ,一个 rotation 和一个可选的轴心(默认是重构区域的中心)。这个 Rectangle 可以延伸
当前图片边界的外面,这些区域会被生成为透明的黑色。
下面的代码示例演示对一张摩天轮图片的 3个重构的操作:
1、这张图片通过设置一个 ReframingArea 来重构一张图片
2、这张图是接着步骤一,以 ReframingArea 的中心作为轴心旋转 25度
3、这种图是接着步骤一,以 ReframingArea 的左上角作为轴心旋转 25度
using (var imageSource = new StorageFileImageSource(storageFile)) using (var filterEffect = new FilterEffect(imageSource)) using (var renderer = new BitmapRenderer(filterEffect)) { var imageInfo = await imageSource.GetInfoAsync(); var filter = new ReframingFilter(); filter.ReframingArea = new Windows.Foundation.Rect(180, 10, 200, 340); filter.Angle = 0; filterEffect.Filters = new IFilter[] { filter }; var buffer1 = await renderer.RenderAsync(); filter.Angle = 25; var buffer2 = await renderer.RenderAsync(); filter.PivotPoint = new Windows.Foundation.Point(0, 0); buffer = await renderer.RenderAsync(); }
原图 | 第一次重构 | 第二次重构 | 第三次重构 |
对于简单的图片裁切操作,可以使用 CropFilter。当调整 “画布”尺寸 时旋转图片到任意角度,可以使用
RotationFilter
2)BlendFilter
这个 blend filter 接收一个 foreground 图片作为源图片输入,然后融合到这个 FilterEffect 滤镜处理
的图片上。
下面是把一张边框为黑色,内部为透明的图片融合到其它图片上:
using (var backgroundSource = new StorageFileImageSource(backgroundFile)) using (var foregroundSource = new StorageFileImageSource(foregroundFile)) using (var filterEffect = new FilterEffect(backgroundSource)) using (var blendFilter = new BlendFilter(foregroundSource)) using (var renderer = new BitmapRenderer(filterEffect)) { blendFilter.BlendFunction = BlendFunction.Normal; filterEffect.Filters = new IFilter[] { blendFilter }; var buffer = await renderer.RenderAsync(); }
背景图片 | 前景图片 | 融合结果 |
3)DelegatingEffect
当使用 实现了 ICustomEffect 接口的用户自定义类处理图片时 使用 DelegatingEffect。当用户自定义
类创建时就被附加到 DelegationgEffect。在 C# 中,使用 CustomEffectBase 基类更加简单。
更多参考章节 “CustomEffectBase”。
Renders
在输出阶段,开发者可以通过为渲染类提供一个 类型的对象来接收结果。
1)BitmapRenderer
当输出图片是一个在接下来的步骤中继续被使用的位图时,或者你需要高效的获取 pixels 时,
可以使用它。应用程序既可以创建一个事先指定尺寸的 Bitmap,然后把它传递进去,或者
让这个 BitmapRenderer 直接输出一个新的经过处理的 Bitmap 对象。
2)WriteableBitmapRenderer
当输出结果需要是一个 WriteableBitmap 来呈现图片时使用它。但是不建议把 WriteableBitmap
作为做进一步处理的位图格式。相反,在这种情形下应该使用一个 BitmapRender。
3)JpegRender
当输出图片可能是 JPEG/JFIF(包含可选的 EXIF 元数据) 时使用它。它的输出结果是一个 IBuffer 对象,
从而可以被保存到 stream,file 等等 Windows Phone 应用程序通常使用的格式。
CustomImageSourceBase
在图片处理的图谱中可以创建和使用用户自定义的 image sources。
在 C++/CX 中,必须使用这个 DelegatingImageSource,并且用户提供一个 ICustomImageSource 接口
的实现。本章节不会涉及更多。
像 C# 这些托管代码,一个更容易并且被强烈推荐的选项是去继承 Nokia.Graphics.Imaging.CustomImageSourceBase。
这个基类处理的大部分的管道,所以用户只需要在子类中提供实际的像素数据(pixel data)。
这个类可以被直接使用。
class SimpleImageSource : CustomImageSourceBase { private Windows.UI.Color[] m_Colors; public SimpleImageSource(Windows.Foundation.Size size, Windows.UI.Color[] colors) : base(size) { m_Colors = colors; } protected override void OnProcess(PixelRegion pixelRegion) { pixelRegion.ForEachRow((index, width, pos) => { for (int x = 0; x < width; ++x, ++index) { // Pick one of the configured colors based on the x coordinate. var c = m_Colors[x % m_Colors.Length]; pixelRegion.ImagePixels[index] = FromColor(c); } }); } }
示例中这个用户自定义图片源(custom image source) 展示了一个 custom image source 的基本需求:
— 继承 CustomImageSourceBase
— 给基类的构造函数传递一个 Windows.Foundation.Size 对象。这是生成的图片的 “固有”尺寸。
在这个示例中,它来让调用者指定。
— 重写 OnProcess 方法,并且写入数据到 PixelRegion.ImagePixels 数组。这是必须的。
它也显示了下面的原则:
— 使用 PixelRegion.ForEachRow 方法来轻松迭代写入 pixels。
— 使用 FromColor 方法来方便地 覆盖一个 Windows.UI.Color ,这个 colour 格式是
这个 PixelRegion.ImagePixels 的数组
然后,任何其它的 IImageProvider 可以使用这个 SimpleImageSource 了。
(注意示例代码只是用来解释说明,并没有任何优化)
CustomEffectBase
用户自定义的 effects 可以在图片处理的 “图谱” 上创建和使用。
在 C++/CX 中,必须使用 DelegationEffect,并且用户需要提供一个 ICustomEffect 接口的实现。
本章节不过涉及到。
像 C# 这种托管代码,可以更容易并且强烈推荐的选项是 去继承 Nokia.Graphics.Imaging.CustomEeffectBase。
它接管了绝大多数管道,所以用户自定义类只需要 读、写实际的像素数据(pixel data)。子类可以被直接使用。
public class DoubleEffect : CustomEffectBase { public DoubleEffect(IImageProvider source) : base(source) { } protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion) { sourcePixelRegion.ForEachRow((index, width, pos) => { for (int x = 0; x < width; ++x, ++index) { Color c = ToColor(sourcePixelRegion.ImagePixels[index]); c.R = (byte)Math.Min(255, c.R * 2); c.G = (byte)Math.Min(255, c.G * 2); c.B = (byte)Math.Min(255, c.B * 2); targetPixelRegion.ImagePixels[index] = FromColor(c); } }); } }
示例展示了一个 自定义效果的基本要求:
— 继承自 CustomEffectBase
— 向基类的构造函数传递一个 IImageProvider。像示例中演示的,通常直接通过一个构造函数
的参数传递。
— 重写 OnProcess 方法,从 “sourcePixelRegion.ImagePixels” 中读数据 pixel 数组,然后向
“targetPixelRegion” 像素数组中写数据。
同时也展示了相应的原则:
— 使用 PixelRegion.ForEachRow 来方便的迭代写入像素数组。
— 使用 ToColor 和 FromColor 方法来轻松覆盖一个 Windows.UI.Color 和在像素数组中预期
的 colour 格式。
这个最终的 DoubleEffect 现在可以被使用了。例如 作为一个 IIamgeProvider 或者一个 IImageConsumer。
(注意示例代码只是用来解释说明,并没有任何优化)