Introduction
In this tip, we will see how useful the GDI+ library is. We can even design an entire 2D character using its various drawing functions. The Curve
drawing function of GDI+ is such a powerful feature that we can easily draw complex and smooth vector images by using it. Though, it will be very tough to design an entire 2D character via programming. It needs proper attention and patience. However, I think this tip will be a good practice for us to learn both the GDI+ and the F# language in a different way.
We will design two interesting characters in this tip. The first one is Code Project Bob Sticker and the second one is Magpie Bird which is the national bird of Bangladesh.
Using the Code
To design our characters, we will use the Off-Screen drawing technique as I have always done in my earlier 'F# and GDI+' articles.
We always start coding by implementing the main window class since our project needs a graphical user interface. Although, we can create the window without implementing a user-defined class, since F# is functional programming language. But I think object-oriented code is much easier to understand.
type MainWindow() as this =
inherit Form()
In my earlier F# programming language articles, I used a init
function in main window class to initialize some common properties of the window and to do some other initialization. We called this function from the Entry Point of the program. But this time, I will not disappoint you by doing the same thing again. Instead, we will call the function from class constructor using 'do' bindings -
do this.Init()
Note that 'do' and 'let' bindings must come before any member definition in type.
Our initializer function is as given below:
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)))
Then we implement the paint
event handler function for the window and we will draw our characters in the paint event handler.
member this.Event_Paint(sender : System.Object, e : PaintEventArgs) =
Now, we create the off-screen bitmap objects. We declare it inside the MainWindow
class as private fields of the class using the 'let' binding of F# language.
let mutable offscr_bitmap_bob = null
let mutable offscr_bitmap_magpie = null
We initialize the Bitmap
object with main window client size inside the init
member function.
offscr_bitmap_bob <- new Bitmap(this.ClientSize.Width, this.ClientSize.Height)
To draw something into the off-screen bitmap, we also need to create a Graphics
object from the Bitmap
object. We do it using FromImage
static method of Graphics
class.
let mutable ofg = Graphics.FromImage(offscr_bitmap_bob)
ofg.SmoothingMode <- SmoothingMode.HighQuality
ofg.CompositingQuality <- CompositingQuality.HighQuality
We create two member functions to draw our two characters. Draw_Bob
and 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)
Let's implement the Bob character drawing function 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)
The following points represent the Bob character body shape.
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)
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)
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)
|]
We use FillClosedCurve
method of GDI+ Graphics
class to draw filled and closed curve.
g.FillClosedCurve(Brushes.Black, ptsBodyShape)
Then we write code for drawing all the other parts of the character. The following point array represents the outer head shape of Bob character.
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(73.0f, 185.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)
And the following point array represents the inner head shape of Bob character.
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)
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)
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)
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)
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)
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)
|]
g.FillClosedCurve(Brushes.Black, ptsLeftPalmShape)
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)
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)
|]
g.FillClosedCurve(Brushes.Black, ptsRightPalmShape)
We use filled ellipse to draw belly and eyes:
g.FillEllipse(brushGreen, 97.0f, 200.0f, 60.0f, 60.0f)
g.FillEllipse(Brushes.Black, 122.0f, 232.0f, 12.0f, 12.0f)
g.FillEllipse(Brushes.Black, 85.0f, 79.0f, 56.0f, 85.0f)
g.FillEllipse(Brushes.White, 93.0f, 91.0f, 40.0f, 60.0f)
g.FillEllipse(Brushes.Black, 106.0f, 104.0f, 16.0f, 31.0f)
g.FillEllipse(Brushes.Black, 138.0f, 83.0f, 44.0f, 71.0f)
g.FillEllipse(Brushes.White, 144.0f, 94.0f, 31.0f, 50.0f)
g.FillEllipse(Brushes.Black, 154.0f, 107.0f, 13.0f, 24.0f)
Here is our Magpie drawing function 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)
The following points create the main body shape of the Magpie bird.
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)
|]
The following points build other three-part of the body shape.
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)
|]
We draw legs using DrawLine
method of Graphics
class.
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)
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)
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)
The following code draws a Gray color line on the bird lips:
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)
Then, we draw the eyes using FillEllipse
method of Graphics
class.
g.FillEllipse(Brushes.White, 232.0f, 66.0f, 12.0f, 12.0f)
g.FillEllipse(Brushes.Black, 234.5f, 69.0f, 8.5f, 8.5f)
Now, inside the paint event handler, we draw our two bitmap objects. We stretch the two bitmaps to fit in the main window. We draw the bitmaps using DrawImage
method of Graphics
class. We also draw some text using DrawString
method of Graphics
class.
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))
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))
Finally, we implement our Entry point function, then we create the main window and run it by using the Application.Run
method.
[<STAThread>]
let START =
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
let mainWindow = new MainWindow()
Application.Run(mainWindow)
Conclusion
Using the GDI+ library and F# language, we can make many graphics designs through programming. So I hope this tip has helped programmers to learn the GDI+ and the F# programming language.
History