Click here to Skip to main content
14,333,933 members

2D Character Design With F# And GDI+

Rate this:
5.00 (4 votes)
Please Sign up or sign in to vote.
5.00 (4 votes)
2 Nov 2015CPOL
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.

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

Share

About the Author

Farhad Reza
Software Developer
Bangladesh Bangladesh
আমি একজন লেখক। নিয়মিত বিজ্ঞান বিষয়ে লিখছি। আমি সবসময় এই আশঙ্কায় থাকি যে কখন একজন লেখক ও লেখকের পরিবারের সদস্যদের ওপর বিনাদোষে অনাকাঙ্ক্ষিত দুঃখজনক পরিস্থিতি এসে পড়ে।

I'm a Bangladeshi citizen. Currently, I live in the capital, Dhaka.

Followings are my current computer skills:

- 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 researches on compiler and programming language development. I've made various kind of software and now I want to share my experiences with people.

I love my country very much and I love all the peoples of my country. In fact, I love all the peoples live in this world. I love my country's independence. Since I'm becoming a writer, I feel insecure due to the fact that there are a lot of fake blames that are given to writers when they are totally innocent. It will happen as well as with their family.

Despite this bad situation, I'm proud of being Bengali and a Bangladeshi writer. I'm trying to take steps towards working on Computer Programming and also on Scientific subjects for my country. But it is too far from being a success and will require a lot of time due to my family's economic position.

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
memberFarhad 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.

Tip/Trick
Posted 2 Nov 2015

Stats

6.7K views
64 downloads
1 bookmarked