14.2.4 创建和运行效果

14.2.4 创建和运行效果

在 14.2.2节中,我们实现了几个颜色滤镜,但是,我们的应用程序包含更一般的图形效果的列表。看来我们还有很多工作要做,但实际上,从简单的颜色滤镜创建图形的效果所需要的一切,我们已经都有了。在下一节,每一个有意义的事情都会开始,在那儿,你将学习如何从颜色滤镜创建一个效果。

在 C# 中从颜色滤镜创建效果

要基于颜色滤镜创建通常的图形的效果,可以将滤镜应用于图像的所有像素。我们已经实现了它的序列形式,作为 RunFilter方法。让我们开始写一个简单的函数,使用 RunFilter来创建一个效果,按顺序的方式运行给定的滤镜,很快,我们会讨论并行版本。在清单 14.14 中你可以看到,我们使用 lambda函数构造一个效果,并从这个方法中返回一个委托。

Listing 14.14 Creating graphical effect from a color filter(C#)

Func<SimpleColor[,],SimpleColor[,]>MakeEffect
(Func<SimpleColor, SimpleColor>filter) {
return arr =>Filters.RunFilter(arr, filter);
}

这个方法有只有一个参数,是一个颜色滤镜,我们用来转换成效果的。效果应将此滤镜应用于图像的每个像素,但是,当我们还没有图像时,该怎样做呢?答案是,在后面,从表示影响的函数的一个参数中,我们会得到这个图像,所以,这个方法体通过lambda 函数返回效果。

这个 lambda函数只有一个参数:需要处理的图像。一次我们有了这个信息,就可以调用 RunFilter 方法。如果你还记得,在第 8章中,我们讨论过有关闭包(closure),你知道,方法的参数 filter 将由闭包捕获,它与返回的函数相关联。

务必要注意,返回的类型与保存在 EffectInfo中的类型是完全相同的,所以,当我们为工具栏构建效果的下拉列表时,可以立即使用它。当我们随后讨论图像的滤镜并行化时,会实现这个处理方法的并行版本RunFilterParallel,我们也需要为 MakeEffect方法添加并行版本,但这只是另一个三行的方法,只适应这个方法签名。

下面是如何从两个现有颜色滤镜创建效果,并将它们添加到 listFilter 控件的示例。当我们完成 MakeEffect的并行版本时,也能够添加的并行版本:

var effects =
newList<EffectInfo> {
newEffectInfo {
Name = "Grayscale(sequential)",
Effect = MakeEffect(Filters.Grayscale) },
newEffectInfo {
Name = "Lighten(sequential)",
Effect = MakeEffect(Filters.Lighten) }
};
listFilters.ComboBox.DataSource = effects;
listFilters.ComboBox.DisplayMember ="Name";

我们将使用 C#3.0 集合初始值设定项(collection initializer)来创建List<EffectInfo>,包含我们已经创建的两个颜色滤镜的有关信息。当我们调用MakeEffect 时,给它一个来自 Filters 类的方法组作为参数。这个方法组自动由 C# 编译器转换为 Func委托。最后两行把这个列表设置成下拉控件的数据源,并使用 DisplayMember 属性来指定效果名应该显示的文本。

这个应用程序的F# 版本的对应代码是很有趣的,虽然我们还没有看到完整的代码,不过会在这一部分讨论。

在 F# 中使用偏函数(PARTIAL FUNCTION APPLICATION)应用

这一问题的 F#解决将变得相当容易。我们刚才已经实现的方法,在 C# 中只改变了我们提供给 RunFilter 方法的参数值的方式。它称为MakeEffect,以更好地反映它除了名字以外,还会返回什么,但这并无太大的不同。要更好地理解这个,让我们看看类型。这里是MakeEffect 方法的类型签名,使用 F# 符号:

(SimpleColor-> SimpleColor)->(SimpleColor [,]-> SimpleColor[,])

让我们回顾一下,我们已经实现的 F# 的 runFilter 函数,来替代在 C# 中的 RunFilter方法。我们已经看到,它是处理二维数组的泛型函数。如果我们用实际类型,它将用来表示像素,来替换类型参数,我们就会得到以下的签名:

(SimpleColor-> SimpleColor)->SimpleColor [,]-> SimpleColor [,]

这两个签名之间的唯一区别,是第一个函数结果返回的一个函数,而第二个函数取两个参数值,并返回已处理的数组。我们在第 5 章中学习过,F#的编译器会把这两个函数好像是一样的看待。这意味着,如果我们调用 F# 的函数runFilter,使用偏应用,并只指定第一个参数值(颜色滤镜函数),我们就能获得一个函数,把效果表示成结果:

> let effect = runFilterColorFilters.Grayscale;;
val effect : (SimpleColor[,] -> SimpleColor[,])

如你所见,由于有了偏函数应用,我们不需要任何 F# 函数,相当于 MakeEffect 方法,我们自由地从滤镜转换到效果。我们可以写C# 的 RunFilter 方法,在某种程度上,其行为与 MakeEffect 相同,但这不是写 C#代码的自然方式,所以,我们选择添加一个简单的方法,作为简单的界面。像这个描述所暗示的,这种形式的转换并不以任何方式,不同于界面的设计模式。

现在,让我们返回到用户界面,再看看应用按钮的事件处理程序还必须做什么。

执行图形效果

当我们应用这个效果时,需要测量它所花的时间。我们应该记住运行效果之前的时间,然后,运行这个影响,从当前时间减去原始时间。然而,它混淆了时间方面与调用的方面。如果我们想要测量在不同的地方的时间,需要复制和粘贴代码,这并不是一个好的做法。函数式编程使我们有更好的方法,来解决这个问题。

我们可以用高阶函数实现时间测量,它取另一个函数作为参数值,并测量运行所花的时间。返回值是一个元组,包含该函数的结果和花费的时间,以毫秒计。清单14.15 显示了 F# 和 C# 的实现。

Listing 14.15 Measuring the time in C# and F#

C#

F#

using System.Diagnostics;

Tuple<T, long>MeasureTime<T>
(Func<T> f) {
var st = Stopwatch.StartNew();
var res = f();
var t = st.ElapsedMilliseconds;
return Tuple.Create(res, t);
}

open System.Diagnostics

let measureTime(f) =
let st = Stopwatch.StartNew()
let res = f()
let t = st.ElapsedMilliseconds
(res, t)

这个函数首先初始化测量时间的 Stopwatch类,然后,运行指定的函数。我们不想扔掉结果,因此,将它存储在本地,计算花费的时间。因为我们需要从函数返回多个值,因此,使用一个元组值。元组的第一个元素是传递进去的函数的结果,它可以是任何类型,取决于该函数。第二个元素将包含所花费的时间,以毫秒计。

清单 14.16使用了这个新方法,在运行按钮的 Click 事件的事件处理程序中。

Listing 14.16 Applying the selected effect to a bitmap (C#)

var info = (EffectInfo)listFilters.SelectedItem;
var effect = info.Effect;
var arr = loadedBitmap.ToArray2D();
var res = MeasureTime(() => filter(arr));

pictProcessed.Image = res.Item1.ToBitmap();
lblTime.Text = string.Format("Time: {0}ms", res.Item2);

在下拉列表中可用的效果,保存为 EffectInfo的实例,因此,我们首先访问列表中的选择项。一旦我们有了这个效果,就可以执行位图图像的处理。我们首先将位图转换为二维数组,然后,再应用这个滤镜。这个操作被打包到对MeasureTime 方法的调用,所以,res 的类型是 Tuple<SimpleColor[,],long>。我们首先将返回的数组转换成位图,显示位图,并显示应用这个效果所花的时间。

我们目前只关注这个效果的性能,但在位图和数组之间的并行化转换也是可能的。如果你有兴趣,我们留给你作为一个练习,但此刻,我们将继续并行化这个效果。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值