Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

2D Character Design With F# And GDI+

0.00/5 (No votes)
2 Nov 2015 1  
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.

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 -

// Here we initialize our main window through calling this member function.
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.

// Our paint event
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)

        // 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)
        |]

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(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)

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)

// 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)

We use filled ellipse to draw belly and eyes:

// 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)

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)

// 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)

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.

// 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))

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()

    // Lets run our application
    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

  • Version 1.0

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here