15.4.2 创建和组合绘图

本文介绍了一个简单的绘图系统,可以通过组合基本图形元素如圆来创建复杂的图像。文章详细讲解了如何使用函数式编程方法实现图形的创建、平移和组合,并展示了如何利用这些基本概念制作动画。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

15.4.2 创建和组合绘图

<wbr></wbr>

<wbr><wbr><wbr>我们要保持事情,简单地画一个圆。我们可以用类似的方式,实现很多其他类型,但是,我们来看一个例子,可以添加更多的绘图。精确的形状并不是特别重要的,在我们可以讨论的更有趣的组合的话题之前,需要一些具体的东西。</wbr></wbr></wbr>

<wbr></wbr>

创建和移动圆

<wbr></wbr>

<wbr><wbr><wbr>创建绘图,大约就是在图形对象上提供绘制的函数,图形对象是这个函数参数值。图形类型具有 FillEllipse 方法,所以,基实现根本不应该棘手。值得注意的是,清单包含了少量的额外的噪音:添加另一个绘图只需要几行代码。清单 15.13 显示了 C# 和 F# 的实现。</wbr></wbr></wbr>

<wbr></wbr>

Listing 15.13 Creating circle in F# and C#

<wbr></wbr>

// C# version
public static class Drawings {
<wbr>public static IDrawing Circle(Brush brush, float size) {<br><wbr><wbr><wbr>return new Drawing(gr =&gt;<br><wbr><wbr><wbr><wbr><wbr>gr.FillEllipse(brush, -size/2.0f, -size/2.0f, size, size)<br><wbr><wbr><wbr>);<br><wbr>}<br> }</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

// F# version
module Drawings =
<wbr>let circle brush size =<br><wbr><wbr><wbr>drawing(fun g –&gt;<br><wbr><wbr><wbr><wbr><wbr>g.FillEllipse(brush, -size/2.0f, -size/2.0f, size, size))</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr></wbr>

<wbr><wbr><wbr>为了更好地组织代码,我们把函数放在名叫 Drawings 的组织单元内。在 C# 中,它被实现为静态类,而在 F# 中,使用模块。C# 代码实现 Circle 方法,创建一个新的 Drawing对象,把绘图函数给它作为参数。Lambda 函数调用 FillEllipse,指定画笔和大小。在 F# 中,我们把 circle 实现为一个简单的函数,取画笔和大小作为两个参数。</wbr></wbr></wbr>

<wbr><wbr><wbr>在传统的函数式设计中,这是写函数的首选方法,除非有一些逻辑原因,需要使用元组(例如,一个元组表示有两个坐标的点)。在 F# 中,我们一直在更经常使用元组化(tupled)的参数,以符合通常的 .NET 编码风格,但在这里,我们将使用函数的方法。开发组合库时,使用函数风格,通常是一个好主意,我们将很快看到这种设计如何可以方便地创建动画圆。</wbr></wbr></wbr>

<wbr><wbr><wbr>该函数使用我们早些时候实现的高阶函数 drawing,给它 lambda 函数画圆。现在,我们将使用 circle 作为唯一的绘图基元,并看到我们能用它来做什么。</wbr></wbr></wbr>

<wbr><wbr><wbr>如果我们创建了两个圆,它们两个的中心都是点 (0, 0)。这意味着,如果我们组合两个圆,就不会得到非常有趣的结果。清单 15.13 中的代码,可以指定一个圆的大小,但看来,我们忘了指定位置!其实这是经过故意的,因为我们要使用不同的方法指定位置。我们将创建一个圆,以 (0,0) 为中心,并将其移动到任何我们需要的点。</wbr></wbr></wbr>

<wbr><wbr><wbr>我们会把绘图的运动实现为函数(或方法),它取绘图和一对坐标作为参数。然后,返回新的绘图,绘制原来的图形,经过给定偏移量的平移。我们如何能够实现这个函数呢?我们可以把原始绘图绘制为位图,然后,绘制位图到指定的坐标,但有一个更简单的解决方案。我们将使用绘图的图形类型,直接支持平移转换。清单 15.14 显示了这两种语言的实现。</wbr></wbr></wbr>

<wbr></wbr>

Listing 15.14 Translating drawings in F# and C#

<wbr></wbr>

// F# version
let translate x y (img:Drawing) =
<wbr>drawing(fun g –&gt;<br><wbr><wbr><wbr>g.TranslateTransform(x, y)<br><wbr><wbr><wbr>img.Draw(g)<br><wbr><wbr><wbr>g.TranslateTransform(-x, -y) )</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

// C# version
public static IDrawing Translate(this IDrawing img, float x, float y) {
<wbr>return new Drawing(g =&gt; {<br><wbr><wbr><wbr>g.TranslateTransform(x, y);<br><wbr><wbr><wbr>img.Draw(g);<br><wbr><wbr><wbr>g.TranslateTransform(-x, -y); }<br><wbr>);<br> }</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr></wbr>

<wbr><wbr><wbr>这个组合返回一个新的平移后的绘图值。这个实现使用的创建模式,绘制圆的代码相同。 F# 函数使用 drawing 基元,指定如何绘制平移的图形,而在 C# 中,我们直接创建一个新的 Drawing 对象,并指定绘图函数。C# 版本把 Translate 实现为扩展方法,这样,可以使用点表示法来调用。注意,我们为接口 IDrawing 加了一个方法。没有扩展方法,这是不可能的,因为,接口只能包含抽象成员。</wbr></wbr></wbr>

<wbr><wbr><wbr>这一次,这个实现稍微有趣一点了,它改变了使用的坐标系的原点,当在图形上使用 TranslateTransform 绘图时。这意味着,如果我们运行原始的绘图代码(例如,绘制一个圆),它仍将绘制在 (0,0),但是这个点其实会在图形表面的其它地方。一旦我们配置平移,再运行原始绘图,并重置变换。严格地说,我们可以运行代码,在 finally 块中恢复原始设置,但我们想保持代码的简单性。</wbr></wbr></wbr>

<wbr><wbr><wbr>现在,我们可以到处移动绘图,终于可以创建其他的东西,而不是绘制叠在一起的圆。然而,我们不想在所有的时候者使用绘图的集合。那么,我们如何可以从两个绘图创建一个绘图呢?</wbr></wbr></wbr>

<wbr></wbr>

组合绘图

<wbr></wbr>

<wbr><wbr><wbr>如果把所有的绘图保存在集合中,组合绘图,将不得不重复许多函数。我们可能要在集合中移动所有绘图,但平移只适用于单个绘图。相反,我们想要创建一个绘图,将绘制所有的组合值,我们将创建一个组合函数实现这个目标。要理解这个函数的工作方式,我们可以看看它的类型签名:</wbr></wbr></wbr>

<wbr></wbr>

val compose : Drawing -> Drawing –> Drawing

<wbr></wbr>

<wbr><wbr><wbr>这个函数取两个绘图值作为参数,并返回一个绘图。我们不需要指定任何偏移量,定义绘图的位置,因为我们可以在调用 compose 前,平移参数,使用上一节中的 translate 函数。这个实现是很简单,下面你可以看到。</wbr></wbr></wbr>

<wbr></wbr>

Listing 15.15 Creating composed drawing in F# and C#

<wbr></wbr>

// F# version
let compose (img1:Drawing) (img2:Drawing) =
<wbr>drawing(fun g –&gt;<br><wbr><wbr><wbr>img1.Draw(g)<br><wbr><wbr><wbr>img2.Draw(g) )</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

// C# version
public static IDrawing Compose(this IDrawing img1, IDrawing img2) {
<wbr>return new Drawing(g =&gt; {<br><wbr><wbr><wbr>img1.Draw(g);<br><wbr><wbr><wbr>img2.Draw(g); }<br><wbr>);<br> }</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr></wbr>

<wbr><wbr><wbr>清单 15.15 再一次重复了我们一直在使用的创建绘图的模式。这一次,使用的新绘图的 lambda 函数,调用 Draw 方法,把两个原始绘图组合到一起。</wbr></wbr></wbr>

<wbr><wbr><wbr>使用刚刚已经实现的三个函数,可以在不同的位置,创建几个包含多种色彩的圆的绘图。实现其他基元绘图会很简单,但不会教给我们新的东西,因此,在这一章我们坚持用圆。</wbr></wbr></wbr>

<wbr><wbr><wbr>到目前为止,我们已经看到用于实现绘图的代码,但还没有使用。我们探索动画之前,让我们看一些代码,来创建一个简单的绘图。我们还不会创建完整的应用程序,因为,这样可以更容易演示绘图什么时候开始动起来,现在,我们只看一下代码。图 15.4 显示了我们要创建的简单绘图。</wbr></wbr></wbr>

15.4.2_创建和组合绘图

图 15.4 两个圆,用 translate 移动,compose 组合。

<wbr></wbr>

<wbr><wbr><wbr>我们只看 F# 版本的代码。一旦把所有的一切都转换成动画,C# 示例会更有趣。</wbr></wbr></wbr>

<wbr></wbr>

open Drawings

let greenCircle = circle Brushes.OliveDrab 100.0f
let blueCircle = circle Brushes.SteelBlue 100.0f

let greenAndBlue =
<wbr>compose (translate -35.0f 35.0f greenCircle)<br><wbr><wbr><wbr>(translate 35.0f -35.0f blueCircle)</wbr></wbr></wbr></wbr>

<wbr></wbr>

<wbr><wbr><wbr>代码首先通过引用 Drawings 模块,包含了用于处理绘图的所有函数。接下来,创建一个绿色、一个蓝色的圆,大小为 100 个像素。我们在不同的方向上移动这些圆,大约 50 个像素,然后,组合这两个平移过的绘图,创建一个新的绘图值。</wbr></wbr></wbr>

<wbr></wbr>

注意

<wbr></wbr>

<wbr><wbr><wbr>在这一节中,我们只实现了几个基本的绘图函数,但还有许多其他可能需要尝试的概念。可以创建新的基元,比如,正方形或位图,还有一些新的转换,比如,旋转和缩放。图形类型使得实现这些很容易,使用 RotateTransform 和 ScaleTransform。你首先可能想完成这一章,因此,可以看到每一个如何运行成动画。</wbr></wbr></wbr>

<wbr></wbr>

<wbr><wbr><wbr>现在,我们已经分别实现了绘图和时变值的概念,把这两者结合起来实现动画很简单。</wbr></wbr></wbr>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值