使用 F# 和 GDI+ 进行二维角色设计





5.00/5 (4投票s)
在本技巧中,我们将设计两个有趣的角色。第一个是 Code Project Bob 贴纸,第二个是喜鹊,它是孟加拉国的国鸟。
引言
在本技巧中,我们将看到 GDI+ 库是多么有用。我们甚至可以使用它的各种绘图函数设计整个 2D 角色。GDI+ 的 Curve
绘图函数是一个非常强大的功能,我们可以使用它轻松绘制复杂和平滑的矢量图像。虽然,通过编程设计整个 2D 角色会非常困难。它需要适当的注意力和耐心。但是,我认为这个技巧对于我们以不同的方式学习 GDI+ 和 F# 语言来说是一个很好的练习。
在本技巧中,我们将设计两个有趣的角色。第一个是 Code Project Bob 贴纸,第二个是喜鹊,它是孟加拉国的国鸟。
Using the Code
为了设计我们的角色,我们将使用离屏绘图技术,就像我以前在“F# 和 GDI+”文章中所做的那样。
我们总是从实现主窗口类开始编码,因为我们的项目需要图形用户界面。虽然,我们可以创建窗口而不实现用户定义的类,因为 F# 是一种函数式编程语言。但我认为面向对象的代码更容易理解。
type MainWindow() as this =
inherit Form()
在我之前的 F# 编程语言文章中,我在主窗口类中使用了一个 init
函数来初始化窗口的一些常用属性并进行一些其他初始化。我们从程序的入口点调用了这个函数。但这次,我不会再次做同样的事情来让你失望。相反,我们将使用 'do' 绑定从类构造函数调用该函数 -
// Here we initialize our main window through calling this member function.
do this.Init()
请注意,'do' 和 'let' 绑定必须在类型中的任何成员定义之前出现。
我们的初始化函数如下所示
member this.Init() =
this.BackColor <- System.Drawing.Color.FromArgb(255, 225, 235)
this.Text <- "Characters With F# And GDI+"
this.Size <- new System.Drawing.Size(450, 400)
this.StartPosition <- FormStartPosition.CenterScreen
this.FormBorderStyle <- FormBorderStyle.Fixed3D
this.MaximizeBox <- false
this.Paint.AddHandler(new Windows.Forms.PaintEventHandler(fun s pe -> this.Event_Paint(s, pe)))
然后我们实现窗口的 paint
事件处理函数,我们将在 paint 事件处理函数中绘制我们的角色。
// Our paint event
member this.Event_Paint(sender : System.Object, e : PaintEventArgs) =
现在,我们创建离屏位图对象。我们使用 F# 语言的 'let' 绑定将它声明为 MainWindow
类中的私有字段。
let mutable offscr_bitmap_bob = null
let mutable offscr_bitmap_magpie = null
我们在 init
成员函数中使用主窗口客户端大小初始化 Bitmap
对象。
offscr_bitmap_bob <- new Bitmap(this.ClientSize.Width, this.ClientSize.Height)
为了将某些东西绘制到离屏位图中,我们还需要从 Bitmap
对象创建一个 Graphics
对象。我们使用 Graphics
类的 FromImage
静态方法来完成它。
let mutable ofg = Graphics.FromImage(offscr_bitmap_bob)
ofg.SmoothingMode <- SmoothingMode.HighQuality
ofg.CompositingQuality <- CompositingQuality.HighQuality
我们创建两个成员函数来绘制我们的两个角色。Draw_Bob
和 Draw_Magpie
。
this.Draw_Bob(ofg)
offscr_bitmap_magpie <- new Bitmap(this.ClientSize.Width, this.ClientSize.Height)
ofg <- Graphics.FromImage(offscr_bitmap_magpie)
ofg.SmoothingMode <- SmoothingMode.HighQuality
ofg.CompositingQuality <- CompositingQuality.HighQuality
this.Draw_Magpie(ofg)
让我们实现 Bob 角色绘制函数 Draw_Bob
。
member this.Draw_Bob(g : System.Drawing.Graphics) =
let brushGreen = new SolidBrush(Color.FromArgb(152, 202, 71))
let backBrush = new SolidBrush(this.BackColor)
以下点表示 Bob 角色的身体形状。
let ptsBodyShape =
[|
PointF(124.0f, 108.0f)
PointF(78.0f, 187.0f)
PointF(76.0f, 218.0f)
PointF(78.0f, 240.0f)
PointF(87.0f, 258.0f)
PointF(105.0f, 272.0f)
PointF(112.0f, 295.0f)
// Left shoe
PointF(98.0f, 296.0f)
PointF(76.0f, 293.0f)
PointF(60.0f, 298.0f)
PointF(50.0f, 310.0f)
PointF(55.0f, 323.0f)
PointF(78.0f, 326.0f)
PointF(108.0f, 325.0f)
PointF(120.0f, 318.0f)
// Right shoe
PointF(143.0f, 328.0f)
PointF(184.0f, 325.0f)
PointF(195.0f, 310.0f)
PointF(184.0f, 297.0f)
PointF(164.0f, 292.0f)
// ...
PointF(135.0f, 296.0f)
PointF(139.0f, 272.0f)
PointF(152.0f, 256.0f)
PointF(162.0f, 239.0f)
PointF(163.0f, 220.0f)
PointF(154.0f, 188.0f)
|]
我们使用 GDI+ Graphics
类的 FillClosedCurve
方法来绘制填充和闭合的曲线。
g.FillClosedCurve(Brushes.Black, ptsBodyShape)
然后我们编写代码来绘制角色的所有其他部分。以下点数组表示 Bob 角色头部外形。
let ptsMouthOuterShape =
[|
PointF(120.0f, 5.0f)
PointF(99.0f, 25.0f)
PointF(73.0f, 55.0f)
PointF(55.0f, 80.0f)
PointF(46.0f, 95.0f)
PointF(40.0f, 110.0f)
PointF(38.0f, 130.0f)
PointF(47.0f, 160.0f)
//PointF(48.0f, 160.0f)
//PointF(55.0f, 169.0f)
PointF(73.0f, 185.0f)
//PointF(65.0f, 178.0f)
PointF(120.0f, 200.0f)
PointF(160.0f, 187.0f)
PointF(188.0f, 157.0f)
PointF(197.0f, 125.0f)
PointF(194.0f, 105.0f)
PointF(189.0f, 90.0f)
PointF(178.0f, 68.0f)
PointF(153.0f, 30.0f)
PointF(131.0f, 5.0f)
PointF(120.0f, 5.0f)
|]
g.FillClosedCurve(Brushes.Black, ptsMouthOuterShape)
以下点数组表示 Bob 角色头部内部形状。
let ptsMouthInnerShape =
[|
PointF(122.0f, 29.0f)
PointF(110.0f, 45.0f)
PointF(96.0f, 65.0f)
PointF(85.0f, 82.0f)
PointF(78.0f, 95.0f)
PointF(72.0f, 110.0f)
PointF(70.0f, 134.0f)
PointF(80.0f, 158.0f)
PointF(96.0f, 176.0f)
PointF(124.0f, 188.0f)
PointF(158.0f, 180.0f)
PointF(180.0f, 157.0f)
PointF(190.0f, 131.0f)
PointF(190.0f, 110.0f)
PointF(184.0f, 90.0f)
PointF(172.0f, 68.0f)
PointF(147.0f, 35.0f)
PointF(131.0f, 20.0f)
PointF(122.0f, 29.0f)
|]
g.FillClosedCurve(brushGreen, ptsMouthInnerShape)
// Left Shoe Green
let ptsLeftShoeGShape =
[|
PointF(72.0f, 314.0f)
PointF(78.0f, 309.0f)
PointF(100.0f, 306.0f)
PointF(110.0f, 314.0f)
PointF(82.0f, 320.0f)
PointF(72.0f, 314.0f)
|]
g.FillClosedCurve(brushGreen, ptsLeftShoeGShape)
// Right Shoe Green
let ptsRightShoeGShape =
[|
PointF(136.0f, 312.0f)
PointF(146.0f, 304.0f)
PointF(176.0f, 312.0f)
PointF(170.0f, 320.0f)
PointF(146.0f, 316.0f)
PointF(136.0f, 312.0f)
|]
g.FillClosedCurve(brushGreen, ptsRightShoeGShape)
// Gap between two legs
let ptsGapShape =
[|
PointF(121.0f, 296.0f)
PointF(117.0f, 277.0f)
PointF(122.0f, 273.0f)
PointF(127.0f, 277.0f)
PointF(125.0f, 296.0f)
|]
g.FillClosedCurve(backBrush, ptsGapShape)
// Left Hand
let ptsLeftHandShape =
[|
PointF(78.0f, 187.0f)
PointF(70.0f, 205.0f)
PointF(44.0f, 234.0f)
PointF(78.0f, 216.0f)
|]
g.FillClosedCurve(Brushes.Black, ptsLeftHandShape)
// Left Palm
let ptsLeftPalmShape =
[|
PointF(37.0f, 237.0f)
PointF(4.0f, 242.0f)
PointF(19.0f, 249.0f)
PointF(12.0f, 268.0f)
PointF(26.0f, 262.0f)
PointF(34.0f, 276.0f)
// PointF(37.0f, 237.0f)
|]
g.FillClosedCurve(Brushes.Black, ptsLeftPalmShape)
// Right Hand
let ptsRightHandShape =
[|
PointF(154.0f, 188.0f)
PointF(160.0f, 200.0f)
PointF(192.0f, 234.0f)
PointF(162.0f, 218.0f)
|]
g.FillClosedCurve(Brushes.Black, ptsRightHandShape)
// Right Palm
let ptsRightPalmShape =
[|
PointF(199.0f, 238.0f)
PointF(235.0f, 242.0f)
PointF(216.0f, 250.0f)
PointF(226.0f, 268.0f)
PointF(210.0f, 260.0f)
PointF(204.0f, 275.0f)
//PointF(199.0f, 238.0f)
|]
g.FillClosedCurve(Brushes.Black, ptsRightPalmShape)
我们使用填充的椭圆绘制腹部和眼睛
// Belly
// Outer green
g.FillEllipse(brushGreen, 97.0f, 200.0f, 60.0f, 60.0f)
// Inner black
g.FillEllipse(Brushes.Black, 122.0f, 232.0f, 12.0f, 12.0f)
// Left eye
// Outer black
g.FillEllipse(Brushes.Black, 85.0f, 79.0f, 56.0f, 85.0f)
// Inner white
g.FillEllipse(Brushes.White, 93.0f, 91.0f, 40.0f, 60.0f)
// Inner black
g.FillEllipse(Brushes.Black, 106.0f, 104.0f, 16.0f, 31.0f)
// Right eye
// Outer black
g.FillEllipse(Brushes.Black, 138.0f, 83.0f, 44.0f, 71.0f)
// Inner white
g.FillEllipse(Brushes.White, 144.0f, 94.0f, 31.0f, 50.0f)
// Inner black
g.FillEllipse(Brushes.Black, 154.0f, 107.0f, 13.0f, 24.0f)
这是我们的喜鹊绘制函数 Draw_Magpie
。
member this.Draw_Magpie(g : System.Drawing.Graphics) =
let brushGreen = new SolidBrush(Color.FromArgb(152, 202, 71))
let backBrush = new SolidBrush(this.BackColor)
以下点创建喜鹊鸟的主体形状。
let ptsBodyShape =
[|
PointF(246.0f, 83.0f)
PointF(266.0f, 68.0f)
PointF(240.0f, 57.0f)
PointF(230.0f, 57.0f)
PointF(216.0f, 67.0f)
PointF(207.0f, 77.0f)
PointF(196.0f, 85.0f)
PointF(156.0f, 93.0f)
PointF(118.0f, 105.0f)
PointF(85.0f, 126.0f)
PointF(56.0f, 78.0f)
PointF(21.0f, 6.0f)
PointF(12.0f, 4.0f)
PointF(2.0f, 10.0f)
PointF(3.0f, 18.0f)
PointF(38.0f, 78.0f)
PointF(64.0f, 120.0f)
PointF(68.0f, 138.0f)
PointF(76.0f, 154.0f)
PointF(48.0f, 197.0f)
PointF(62.0f, 197.0f)
PointF(168.0f, 158.0f)
PointF(196.0f, 142.0f)
PointF(214.0f, 169.0f)
PointF(235.0f, 140.0f)
PointF(249.0f, 105.0f)
PointF(268.0f, 80.0f)
|]
以下点构建身体形状的另外三个部分。
let ptsBodyShape2 =
[|
PointF(106.0f, 182.0f)
PointF(120.0f, 186.0f)
PointF(140.0f, 190.0f)
PointF(160.0f, 188.0f)
PointF(180.0f, 186.0f)
PointF(214.0f, 170.0f)
PointF(214.0f, 120.0f)
PointF(104.0f, 140.0f)
|]
let ptsBodyShape3 =
[|
PointF(266.0f, 68.0f)
PointF(246.0f, 83.0f)
PointF(294.0f, 74.0f)
|]
let ptsBodyShape4 =
[|
PointF(68.0f, 178.0f)
PointF(128.0f, 134.0f)
PointF(148.0f, 136.0f)
PointF(180.0f, 106.0f)
PointF(194.0f, 104.0f)
PointF(198.0f, 114.0f)
PointF(188.0f, 124.0f)
PointF(170.0f, 132.0f)
PointF(156.0f, 150.0f)
PointF(134.0f, 146.0f)
PointF(126.0f, 158.0f)
|]
我们使用 Graphics
类的 DrawLine
方法绘制腿。
let pen0 = new Pen(Color.FromArgb(250, 250, 250), 3.6f)
let pen1 = new Pen(Color.FromArgb(172, 189, 183), 2.6f)
let pen2 = new Pen(Color.FromArgb(72, 89, 83), 2.6f)
g.DrawLine(pen2, 147.0f, 223.0f, 140.0f, 238.0f)
g.DrawLine(pen1, 148.0f, 220.0f, 154.0f, 238.0f)
g.DrawLine(pen0, 148.0f, 220.0f, 162.0f, 228.0f)
// The leg line
g.DrawLine(pen0, 117.0f, 180.0f, 151.0f, 223.0f)
g.DrawLine(pen2, 113.0f, 180.0f, 147.0f, 223.0f)
//
g.DrawLine(pen2, 193.0f, 212.0f, 186.0f, 227.0f)
g.DrawLine(pen1, 194.0f, 209.0f, 200.0f, 227.0f)
g.DrawLine(pen0, 194.0f, 209.0f, 208.0f, 217.0f)
// The leg line
g.DrawLine(pen0, 167.0f, 180.0f, 197.0f, 213.0f)
g.DrawLine(pen2, 163.0f, 180.0f, 193.0f, 212.0f)
g.FillClosedCurve(Brushes.White, ptsBodyShape3, FillMode.Winding, 0.2f)
g.FillClosedCurve(Brushes.White, ptsBodyShape2, FillMode.Winding, 0.3f)
g.FillClosedCurve(Brushes.Black, ptsBodyShape, FillMode.Winding, 0.3f)
g.DrawCurve(Pens.Black, ptsBodyShape3, 0.3f)
g.DrawCurve(Pens.Black, ptsBodyShape2, 0.3f)
g.FillClosedCurve(Brushes.White, ptsBodyShape4, FillMode.Winding, 0.2f)
以下代码在鸟的嘴唇上绘制一条灰色线
let pen1 = new Pen(Color.FromArgb(180, 180, 180), 2.0f)
let ptsLips2 =
[|
PointF(292.0f, 73.0f)
PointF(276.0f, 72.0f)
PointF(266.0f, 73.0f)
PointF(250.0f, 82.0f)
|]
g.DrawLines(pen1, ptsLips2)
然后,我们使用 Graphics
类的 FillEllipse
方法绘制眼睛。
g.FillEllipse(Brushes.White, 232.0f, 66.0f, 12.0f, 12.0f)
g.FillEllipse(Brushes.Black, 234.5f, 69.0f, 8.5f, 8.5f)
现在,在 paint 事件处理函数中,我们绘制我们的两个位图对象。我们拉伸两个位图以适应主窗口。我们使用 Graphics
类的 DrawImage
方法绘制位图。我们还使用 Graphics
类的 DrawString
方法绘制一些文本。
// Draw Bob
let dest = new Rectangle(10, 20, 250, 230)
e.Graphics.DrawImage(offscr_bitmap_bob, dest, 0, 0, offscr_bitmap_bob.Width,
offscr_bitmap_bob.Height, GraphicsUnit.Pixel)
e.Graphics.DrawString("Code Project Bob Sticker", font, clrText, new PointF(14.0f, 250.0f))
// Draw Magpie
let dest = new Rectangle(180, 40, 280, 280)
e.Graphics.DrawImage(offscr_bitmap_magpie, dest, 0, 0, offscr_bitmap_magpie.Width,
offscr_bitmap_magpie.Height, GraphicsUnit.Pixel)
e.Graphics.DrawString("Magpie The National Bird Of Bangladesh", font,
clrText, new PointF(180.0f, 240.0f))
最后,我们实现我们的入口点函数,然后我们创建主窗口并通过使用 Application.Run
方法运行它。
[<STAThread>]
let START =
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
let mainWindow = new MainWindow()
// Lets run our application
Application.Run(mainWindow)
结论
使用 GDI+ 库和 F# 语言,我们可以通过编程进行许多图形设计。所以我希望这个技巧能帮助程序员学习 GDI+ 和 F# 编程语言。
历史
- 版本 1.0