Click here to Skip to main content
15,881,559 members
Articles / Multimedia / GDI+
Tip/Trick

2D Character Design With F# And GDI+

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
2 Nov 2015CPOL3 min read 10.4K   76   1   2
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.

Image 1

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.

F#
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 -

F#
// 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:

F#
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.

F#
// 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.

F#
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.

F#
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.

F#
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.

F#
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.

F#
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.

F#
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.

F#
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.

F#
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.

F#
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:

F#
// 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.

F#
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.

F#
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.

F#
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.

F#
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:

F#
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.

F#
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.

F#
// 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.

F#
[<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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Bangladesh Bangladesh
Hi, I'm Shah Farhad Reza. I'm a desktop and web software developer.

Recently I've developed an web based ERP (Enterprise Resource Planning) Software for a manufacturing company. The software is in use and working effectively fulfilling its goal (Alhamdulillah) - [February 10, 2023]

The areas of my expertise are the followings:

- OS Kernel developing.
- Programming language's compiler design and implement.
- Expert in C, C++ and Visual Basic and have basic knowledge on C#, D, Java.
- A few times used the Microsoft's new language F#.
- SQL Database programming.
- I've basic knowledge on lowest level programming language like assembly.
- Learning Mozilla’s Rust & Google’s GO programming language for software development.
- Code optimization for performance.
- Multi-threaded programming in C/C++ and Java.
- Know various advanced computer algorithm and have used them to develop graphics and simulation programs. Also working with Linear Algebra and keen to learn Quadratic Algebra in future.
- Graphics and Game programming (Both 2D and 3D).

Currently, I'm doing research on programming language and its compiler development. I've made various kind of software and now I want to share my experiences with people.


Comments and Discussions

 
PraiseMy vote of 5! Pin
jediYL3-Nov-15 16:18
professionaljediYL3-Nov-15 16:18 
GeneralRe: My vote of 5! Pin
Farhad Reza3-Nov-15 18:14
Farhad Reza3-Nov-15 18:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.