Chapter 5. Creating Images

In This Chapter

  • Understanding the System.Drawing namespace

  • Finding out how the drawing classes fit into the .NET Framework

  • Using System.Drawing to create a simple game application

No one is going to write the next edition of Bioshock using C#. It just isn't the kind of language you use to write graphics-intensive applications like shoot-'em-up games.

Still, C# packs a fair amount of power into the System.Drawing classes. Though these classes are somewhat primitive in some areas, and using them might cause you to have to write a few more lines of code than you should, there isn't much that these classes can't do with sufficient work.

The drawing capability provided by the .NET Framework is divided into four logical areas by the namespace design provided by Microsoft. All the general drawing capability is in the System.Drawing namespace. Then there are some specialized namespaces:

  • System.Drawing.2D has advanced vector drawing functionality.

  • System.Drawing.Imaging is mostly about using bitmap graphic formats, like .bmp and .jpg files.

  • System.Drawing.Text deals with advanced typography.

In this chapter, I focus on the base namespace and cover only the basics of drawing in C#. (Discussing every aspect of drawing could easily fill an entire book.)

Getting to Know System.Drawing

Even at the highest level, graphics programming consists of drawing polygons, filling them with color, and labeling them with text — all on a canvas of some sort. Unsurprisingly, this leaves you with four objects that form the core of the graphics code you write: graphics, pens, brushes, and text.

Graphics

Generally speaking, the Graphics class creates an object that is your palette. It's the canvas. All the methods and properties of the Graphics object are designed to make the area you draw upon more appropriate for your needs.

Also, most of the graphics- and image-related methods of other classes in the framework provide the Graphics object as output. For instance, you can call the System.Web.Forms.Control.CreateGraphics method from a Windows Forms application and get a Graphics object back that enables you to draw in a form control in your project. You can also handle the Paint event of a form, and check out the Graphics property of the event.

Graphics objects use pens and brushes (discussed later in this chapter, in the "Pens" and "Brushes" sections) to draw and fill. Graphics objects have methods such as these:

  • DrawRectangle

  • Fi HRectangle

  • DrawCircle

  • FillCircle

  • DrawBezier

  • DrawLine

These methods accept pens and brushes as parameters. You might think, "How can a circle help me?" but you must remember that even complex graphic objects such as the Covenant in Halo 3 are made up of circles and rectangles — thousands of them. The trick to useful art is using math to put together lots of circles and squares until you have a complete image. The sample application described later in this chapter is a simple example of just that.

Pens

You use pens to draw lines and curves. Complex graphics are made up of polygons, and those polygons are made of lines, and those lines are generated by pens. Pens have properties such as

  • Color

  • DashStyle

  • EndCap

  • Width

You get the idea: You use pens to draw things. These properties are used by the pens to determine how things are drawn.

Brushes

Brushes paint the insides of polygons. Though you use the pens to draw the shapes, you use brushes to fill in the shapes with gradients, patterns, or colors. Brushes are usually passed in as parameters to a DrawWhatever method of the pen objects. When the pen draws the shape it was asked to draw, it uses the brush to fill in the shape — just as you did in kindergarten with crayons and coloring books. (The brush object always stays inside the lines, though.)

Don't look for the Brush class, however. It's a holding area for the real brushes, which have kind of strange names. Brushes are made to be customized, but you can do a lot with the brushes that come with the framework as is. Some of the brushes include

  • SolidBrush

  • TextureBrush

  • HatchBrush

  • PathGradientBrush

Although the pens are used to pass into the Draw methods of the Graphics object, brushes are used to pass into the Fill methods that form polygons.

Text

Text is painted with a combination of fonts and brushes. Just like pens, the Font class uses brushes to fill in the lines of a text operation.

System.Drawing.Text has collections of all the fonts installed in the system running your program, or installed as part of your application. System.Drawing.Font has all the properties of the typography, such as

  • Bold

  • Size

  • Style

  • Underline

The Graphics object, again, provides the writing of the text on the palette.

How the Drawing Classes Fit into the Framework

The System.Drawing namespace breaks drawing into two steps:

  1. Create a System.Drawing.Graphics object.

  2. Use the tools in the System.Drawing namespace to draw on it.

It seems straightforward, and it is. The first step is to get a Graphics object. Graphics objects come from two main places — existing images and Windows Forms.

To get a Graphics object from an existing image, look at the Bitmap object. The Bitmap object is a great tool that enables you to create an object using an existing image file. This gives you a new palette that is based on a bitmap image (a JPEG file, for example) that is already on your hard drive. It's a convenient tool, especially for Web images.

Bitmap currentBitmap = new Bitmap(@"c:imagesmyImage.jpg");
Graphics palette = Graphics.FromImage(currentBitmap);

Now the object myPalette is a Graphics object whose height and width are based on the image in myBitmap. What's more, the base of the myPalette image looks exactly like the image referenced in the myBitmap object.

You can use the pens, brushes, and fonts in the Graphics class to draw directly on that image, as though it were a blank canvas. I use it to put text on images before I show them on Web pages and to modify the format of images on the fly, too.

Another way to get a Graphics object is to get it from Windows Forms. The method you want is System.Windows.Forms.Control.CreateGraphics. This method gives you a new palette that is based on the drawing surface of the control being referenced. If it's a form, it inherits the height and width of the form and has the form background color. You can use pens and brushes to draw right on the form.

When you have a Graphics object, the options are endless. Sophisticated drawing isn't out of the question, though you would have to do a ton of work to create graphics like you see in Halo 2 using Visual Studio. (There isn't a Master Chief class that you can just generate automatically.)

Nonetheless, even the most complex 3D graphics are just colored polygons, and you can make those with the System.Drawing class. In the following sections, I build a cribbage board with a Graphics object, pens, brushes, and fonts.

Using the System.Drawing Namespace

Good applications come from strange places. Gabrielle (my wife) and I enjoy games, and one of our favorites is the card game cribbage. We were on vacation in Disney World when she had the urge to play, but we didn't have a cribbage board. We had cards, but not the board.

However, I did have my laptop, Visual Studio, and the System.Drawing namespace. After just an hour or two of work, I built an application that serves as a working cribbage board!

It's shocking, I know, but somehow she still wanted to play me after watching me program for two hours!

Tip

This application is fairly complete, and I don't have enough pages to walk you through it step by step. Load the application from this book's Web site, and follow along with the rest of this chapter. This application isn't complex, but it's long.

Getting started

Cribbage is a card game where hands are counted up into points, and the first player to score 121 points wins. It's up to the players to count up the points, and the score is kept on a board.

Cribbage boards are made up of two lines of holes for pegs, usually totaling 120, but sometimes 60 holes are used and you play through twice. Figure 5-1 shows a typical cribbage board. Cribbage boards come in a bunch of styles — check out www.cribbage.org if you're curious; it has a great gallery of almost 100 boards, from basic to whimsical.

A traditional cribbage board.

Figure 5-1. A traditional cribbage board.

For this example, I create the board image for an application that keeps score of a cribbage game — but it wouldn't be beyond C# to write the cards into the game too!

So the board for this application has 40 holes on each of three pairs of lines, which is the standard board setup for two players playing to 120, as shown in Figure 5-2. The first task is to draw the board, and then to draw the pegs as the players' scores — entered in text boxes — change.

The premise is this: The players play a hand and enter the resulting scores in the text box below their respective names (refer to Figure 5-2). When the score for each hand is entered, the score next to the player's name is updated, and the peg is moved on the board. The next time that same player scores a hand, the peg is moved forward, and the back peg is moved into its place. Did I mention the back peg? Oh, yes, the inventor of cribbage was paranoid of cheating — if you're unfamiliar with cribbage, you may want to check out the rules at www.cribbage.org.

The digital cribbage board.

Figure 5-2. The digital cribbage board.

Setting up the project

To begin, create a playing surface. I set up the board shown in Figure 5-2 without drawing the board itself — I show you how to paint it on later with System.Drawing. My board looked a lot like Figure 5-3 when I was ready to start with the business rules.

The basic board.

Figure 5-3. The basic board.

I used a little subroutine to handle score changes by calling it from the two text boxes' OnChange events. Here's the code that calls the subroutine:

private void HandleScore(TextBox scoreBox, Label points, Label otherPlayer)
{
    try {
        if (0 > (int)scoreBox.Text | (int)scoreBox.Text > 27) {
            ScoreCheck.SetError(scoreBox, "Score must be between 0 and 27");
            scoreBox.Focus();
        }
else {
                ScoreCheck.SetError(scoreBox, "");
                //Add the score written to the points
                points.Text = (int)points.Text + (int)scoreBox.Text;
            }
        }
        catch (System.InvalidCastException ext) {
            //Something other than a number
            if (scoreBox.Text.Length > 0) {
                ScoreCheck.SetError(scoreBox, "Score must be a number");
            }
        }
        catch (Exception ex) {
            //Eek!
            MessageBox.Show("Something went wrong! " + ex.Message);
        }
        //Check the score
        if ((int)points.Text > 120) {
            if ((int)points.Text / (int)otherPlayer.Text > 1.5) {
                WinMessage.Text = scoreBox.Name.Substring(0, scoreBox.Name.Length −
        6) + " Skunked 'em!!!";
           }
           else {
              WinMessage.Text = scoreBox.Name.Substring(0, scoreBox.Name.Length −
        6) + " Won!!";
           }
           WinMessage.Visible = true;
        }
}

All this changing of screen values causes the Paint event of the form to fire — every time C# needs to change the look of a form for any reason, this event fires — so I just tossed a little code in that event handler that would draw my board for me:

private void CribbageBoard_Paint(object sender, PaintEventArgs e)
{
    PaintBoard(BillsPoints, GabriellesPoints);
}

From that point on, my largest concern is drawing the board itself.

Drawing the board

I need to paint right on a form to create the image of the board for my crib-bage application, so I use the CreateGraphics method of the form control. From there, I need to complete these tasks:

  • Paint the board brown using a brush.

  • Draw six rows of little circles using a pen.

  • Fill in the hole if that is the right score.

  • Clean up my supplies.

To that end, I came up with the PaintBoard method, which accepts the labels that contain the standing scores for both players. It's shown in Listing 5-1.

Example 5-1. The PaintBoard Method

private void PaintBoard(ref Label Bill, ref Label Gabrielle)
{
    Graphics palette = this.CreateGraphics;
    SolidBrush brownBrush = new SolidBrush(Color.Brown);
    palette.FillRectangle(brownBrush, new Rectangle(20, 20, 820, 180));
    //OK, now I need to paint the little holes.
    //There are 244 little holes in the board.
    //Three rows of 40 times two, with the little starts and stops on either end.
    //Let's start with the 240.
    int rows = 0;
    int columns = 0;
    int scoreBeingDrawn = 0;
    Pen blackPen = new Pen(System.Drawing.Color.Black, 1);
    SolidBrush blackBrush = new SolidBrush(Color.Black);
    SolidBrush redBrush = new SolidBrush(Color.Red);

    //There are 6 rows, then, at 24 and 40, 80 and 100, then 140 and 160.
    for (rows = 40; rows <= 160; rows += 60) {
        //There are 40 columns. They are every 20
        for (columns = 40; columns <= 820; columns += 20) {
            //Calculate score being drawn
            scoreBeingDrawn = ((columns - 20) / 20) + ((((rows + 20) / 60) - 1) *
    40);
            //Draw Bill
            //If score being drawn = bill fill, otherwise draw
            if (scoreBeingDrawn == (int)Bill.Text) {
                palette.FillEllipse(blackBrush, columns - 2, rows - 2, 6, 6);
            }
            else if (scoreBeingDrawn == BillsLastTotal) {
                palette.FillEllipse(redBrush, columns - 2, rows - 2, 6, 6);
            {
            else {
               palette.DrawEllipse(blackPen, columns - 2, rows - 2, 4, 4);
            }
            //Draw Gabrielle
            //If score being drawn = Gabrielle fill, otherwise draw
            if (scoreBeingDrawn == (int)Gabrielle.Text) {
                palette.FillEllipse(blackBrush, columns - 2, rows + 16, 6, 6);
            }
            else if (scoreBeingDrawn == GabriellesLastTotal) {
               palette.FillEllipse(redBrush, columns - 2, rows + 16, 6, 6); }
            else {
                palette.DrawEllipse(blackPen, columns - 2, rows + 16, 4, 4);
            }
        }
    }
    palette.Dispose();
    brownBrush.Dispose();
    blackPen.Dispose();
}

Aside from the math, note the decision making. If the score being drawn is the score in the label, fill in the hole with a red peg. If it's the last score drawn, fill in the hole with a black peg. Otherwise, well, just draw a circle.

It's tough to fathom, but this is exactly how large-scale games are written. Admittedly, big graphics engines make many more If-Then decisions, but the premise is the same.

Also, large games use bitmap images sometimes, rather than drawing all the time. For the cribbage scoring application, for example, you could use a bitmap image of a peg rather than fill an ellipse with a black or red brush!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.174.183