I. An introduction to System.Drawing.Graphics
1. What is System.Drawing.Graphics?
For those of you not familiar with the Graphics class, there is a class in the System.Drawing namespace (System.Drawing.Graphics) that is used for basic or advanced drawing, text rendering, and general graphic manipulation of any control. Before you even read any further, i suggest you open up Visual Studio and browse through the Graphics class with intellisense. You will see there are many methods, most of them beginning with "Draw" or "Fill". With the Graphics class you can:
-- Draw on a control
-- Draw on an Image or Bitmap object
2. Creating an instance of the Graphics object
Notice if you try to create an instance of the graphics class this way
System.Drawing.Graphics GraphicsObject = new Graphics();that it will not work. The Graphics class has no constructors. The only way to create an instance of the graphics class is through static methods in the graphics class. These methods are:
That will create a graphics object and assign it to draw on the image you provide. A basic example of this would be
System.Drawing.Image exampleimage = Image.FromFile("C:\\MyImage.bmp"); System.Drawing.Graphics GraphicsObject = Graphics.FromImage(exampleimage);That will create a graphics object that will draw on the fictional image C:\MyImage.bmp.
Lets use this for real now. First, open up an image editor (it can be anything from Paint to Photoshop) and draw whatever you want on it. Then, save that image as "C:\MyImage.bmp". Open up Visual Studio and make a windows form application. In the load event, put this code:
System.Drawing.Image exampleimage = Image.FromFile("C:\\MyImage.bmp"); System.Drawing.Graphics GraphicsObject = Graphics.FromImage(exampleimage); GraphicsObject.FillEllipse(Brushes.Red, new Rectangle(0, 0, 100, 100)); exampleimage.Save("C:\\MyImage2.bmp", System.Drawing.Imaging.ImageFormat.Bmp); exampleimage.Dispose(); GraphicsObject.Dispose();(We will discuss the FillEllipse method later) That code will create a graphics object from the image you just made. It will then create a new image called C:\MyImage2.bmp. Open that image up in something. You will see a change. There will be a red circle in the upper-left corner of the image that is 100 pixels by 100 pixels. You just edited an image with the graphics class. Keep that instance of Visual Studio open, as we will use it later.
That will create a graphics object to paint on a window. Window is just another name for a control. A control can be anything from your form itself to a button to a text box. To use this method, you must pass it the handle to the control. To get the handle to any control, use a property called Handle. To get the handle to a button called "Button1", use Button1.Handle. To create a graphics object to draw on that same fictional button, use
System.Drawing.Graphics GraphicsObject = Graphics.FromHwnd(Button1.Handle)
Now, lets use this in a real situation. In Visual Studio, if you still have some code in your load event from the last example, clear it out. Make a Shown (not Load) event for your form. In that event, put:
System.Drawing.Graphics GraphicsObject = Graphics.FromHwnd(this.Handle); GraphicsObject.FillEllipse(Brushes.Red, new Rectangle(0, 0, 100, 100));That will draw a red circle in the upper-left corner of your form when is is shown. Notice we used the same FillEllipse method used in the image scenario. It will draw a red 100x100 circle. Now, run your application. You should see the circle. Now, move your form off the screen, so that part of the circle is hidden. When you move it back, you will see that the part of the circle you just moved away is gone! That is because that area of your form redrew itself, so anything you drew in that area vanishes. To correct that in this example, create a paint event for your form. A paint event will run through any time any part of your control (form in this case) redraws itself. Put the same code as you put in the shown event in that paint event. Then, run the app and do anything you want with the form. Resize it, move it off the screen, anything, the circle will always be there. That is because now, any time your form redraws any part of itself, it will also redraw the circle.
Another way to create a graphics object from a control is to use the CreateGraphics() method all controls have. Here is the code that will create a graphics object for a control called Button1 again:
System.Drawing.Graphics GraphicsObject = Graphics.FromHwnd(Button1.Handle);
The equivalent of that using the CreateGraphics() method would be to use:
System.Drawing.Graphics GraphicsObject = Button1.CreateGraphics();
The same can be done in the form scenario. Just use this.CreateGraphics() to create a graphics object for your form.
II. Stuff you need to know
1. AntiAlias & other Smoothing Modes
The graphics class has a host of methods which can let you draw anything from an open circle to a filled square. In advanced drawing application such as GIMP, Paint Shop Pro, and Photoshop, you may have heard the term "Anti Alias" (or just "AntiAlias"). For example, there may have been a check box that said "Use AntiAlias Rendering". If you don't already know what that means, when you draw a figure using AntiAlias rendering, it will smooth the edges to fit into the background.
AntiAlias'ing a Circle: *
(fig 1) Without (fig 2) With
*Pixels enlarged to show detail
*Image made with Paint Shop Pro, not with the graphics class
The Graphics class can also either draw with or without AntiAlias'ing. There is a property in the graphics class called SmoothingMode. That property can be set to one of the following:
Will specify that any object drawn with that graphics class will use AntiAlias smoothing.
Will specify that no smoothing of any kind is to be used. This will make figures that are drawn to resemble the figure in the Without AntiAliasing example above.
Will specify high quality, and low speed rendering and smoothing. I am not sure if this will produce a result any different than using the AntiAlias option, as i have used both and the results they produce look virtually identical.
This will, like the default, specify no smoothing of any kind. I also believe it will produce the same result as the default option.
Will throw an exception if used. Do not specify this option. (Exception will read "Parameter not valid")
Will produce the same result as if you used either the Default or HighSpeed option.
Just so were all clear, here is an example on how to specify AntiAlias rendering in a graphics object called "GraphicsObject":
GraphicsObject.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
And to specify Default rendering:
GraphicsObject.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default;
2. Rendering Origin
By default, when we create a new graphics class for a control or an image, the rendering origin is set at the top-left corner of the image or control (0, 0). The Rendering Origin is like the origin on a X, Y graph. Actually, it is exactly like the origin on an X, Y graph. Think of the image or control you are drawing on as a graph. The rendering origin is the origin of that graph. When you want to draw something, you have to input the location where you want to draw it at. That point you input is in relation to the Rendering Origin. If the rendering origin is set at the control's or image's top-left corner (0, 0) and you tell it to draw a circle at point (50, 50), then it will draw a circle 50 pixels away from the top and 50 pixels from the left. However, if you set the rendering origin to (100, 100), and you choose to draw the same circle at point (50, 50), then that circle will be draw 150 pixels from the top and 150 pixels from the left.
To change the rendering origin of a graphics object, there is a property called, well, RenderingOrigin. Like i said, by default this is set at (0,0). You can change it to anything you want. Just to be clear on this, to change the rendering origin of a graphics object called GraphicsObject to (100, 200), you would use:
GraphicsObject.RenderingOrigin = new Point(100, 200);
3. Interpolation Modes
to introduce (something additional or extraneous) between other things or parts; interject; interpose; intercalate.
There is another property in the graphics class called InterpolationMode. In addition to being able to draw circles and squares and lines and text with a graphics object, you can also draw images themselves onto graphics object. However, you might want to change the image's size before you draw it. For example, if you had an image that was 20 by 20, and you wanted to draw it in an area of 100 by 100, then you would have to scale up the image. This property specifies what algorithm will be used to do this. Like the definition said, "Interpolation" is introducing new things between others. When you scale up and image, you have to introduce new pixels between the already known ones to produce the final result. By changing this property, you can change the quality of how this is done. Here are the possible values this property can take:
Specifies that the Bicubic algorithm will be used when scaling the image. This will produce a bad result if you are shrinking the image below 25% of it's origninal size.
Specifies that the Bilinear algorithm will be used when scaling the image. This will produce a bad result if you are shrinking the image below 50% of it's origninal size.
Will use a low quality algorithm in resizing an image. Like the name suggests, this is the default value givin to the InterpolationMode property when you create a new graphics object.
Will use a higher quality interpolation than the default. It will also take a bit longer.
Will use a very high quality Bicubic algorithm in resizing the image. This will produce a high quality sized image, whether you are shrinking or enlarging it. This produces the highest quality transformed image. However, it also takes the longest.
Will use a very high quality Bilinear algorithm in resizing the image. This will produce a high quality sized image, whether you are shrinking or enlarging it, but not as good as the HighQualityBicubic.
Throws an exception.
Produces a lower quality image than using the default option.
Will use Nearest-Neighbor (also called Pixel Resize) interpolation. This turns off any smoothing that may be used on the image. This is not good for shrinking an image, and if you are resizing an image up it will produce a blocky image. This is very useful if you are creating an image editing application where you need to resize up the image so you can see the individual pixels that made up the original, as that is exactly what this does.
And again, to be clear, i will give an example on how to change this mode to HighQualityBicubic for a graphics object called "GraphicsObject":
GraphicsObject.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
4. Unit of measure
Think back to when we discusses the rendering origin. I said that if the rendering origin was set at (50, 50), then the origin would be 50 pixels off the left of the object you are drawing on and 50 pixels off the top of the object you are drawing on. However, there is a property in the graphics class called PageUnit. It determines the unit of measure when you draw something. So, if the graphics unit is set at pixels, and the rendering origin was at (50, 50), then the origin would be 50 pixels off the left and 50 pixels off the top. However, if the graphics unit was set to inch, then then the rendering origin would be 50 inches off the left and 50 inches off the top. Be default, it is set to GraphicsUnit.Pixel.
Here are the values it can take:
Specifies the unit of measure for the display device. If you are drawing on the screen, it will be the same as a pixel.
Specifies the document unit as the unit of measure. This is the same as 1/300 of an inch.
Specifies an inch as the unit of measure.
Specifies a millimeter as the unit of measure.
Specifies a pixel as the unit of measure. This is the default value.
Specifies a printers point as the unit of measure. This is equivalent to 1/72 of an inch.
Specifies the world coordinate system as the unit of measure. This will throw an exception if used.
Now, you should still have Visual Studio open. Clear all the code out of your class. Then, make a paint event for your form. Put this code in it:
System.Drawing.Graphics GraphicsObject = Graphics.FromHwnd(this.Handle); GraphicsObject.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; System.Drawing.Pen redpen = new System.Drawing.Pen(Color.Red, 1); GraphicsObject.PageUnit = GraphicsUnit.Pixel; GraphicsObject.DrawLine(redpen, new Point(0, 0), new Point(50, 50));Run your application. You will see there is a thin 1-pixel wide 50-pixels long red line in the top-left of your form. Close your form. In the code in your paint event, see the line that says GraphicsObject.PageUnit = GraphicsUnit.Pixel? Change that so it reads GraphicsObject.PageUnit = GraphicsUnit.Inch. Run your application again. You will see that the red line is not 1-pixel wide, but 1 inch wide. And it is not 50 pixels long, but 50 inches long. Unless you happen to have a 50'' monitor, you will not be able to see all of the 50 inch long line, but believe me, it's there. Now, change that line so it reads GraphicsObject.PageUnit = GraphicsUnit.Millimeter. Run your application again. Now, that line that used to be an inch wide is now 1 millimeter wide, and 50 millimeters long. Keep that code in your paint event, and again, keep Visual Studio open as we will use it later.
IV. Classes related to System.Drawing.Graphics
1. The System.Drawing.Pen
There are a few drawing methods in the graphics class i need to inform you of now:
We will discuss how to use the methods later. Before that, i need to teach you about a class called System.Drawing.Pen. All of these methods have something in common; they will all draw open figures. By open, i mean they are comprised of lines that can be curved and intersect one another at various points to form the final figure. Let's take the DrawLine method for example. That is probably the most basic drawing method in the whole graphics class. It will draw a straight line defined by a starting and ending point. It takes three arguments: the starting point, the ending point, and a Pen. The pen will determine what the line will look like (width, color, etc). Declare a new pen:
System.Drawing.Pen redpen = new System.Drawing.Pen(Color.Red);That will declare a new pen with a width of 1 and a color of red. By width of 1, i mean width of 1 unit of measure used by the graphics object that will use this pen. If the graphics object that is going to use this pen has a graphics unit of inch, then the pen will have a width of 1 inch. If the graphics object has a graphics unit of pixel, the the pen is one pixel wide. Browse through the pen with intellisense. There are many properties which can determine how the pen will look. Here are the main ones:
Determines the width of the pen. We have already discussed this.
Specifies the color of the pen.
Specifies the cap at the starting point of the line.
Specifies the cap at the ending point of the line.
This is used when you draw several lines one connecting to the end of the other. It specifies how the joint between the lines will look.
Specifies the dash style of the pen. It can make the line solid, dashed, dotted, or a combination of dashes and dots.
Specifies how the end and beginning of dashes and dots will look.
Pen Dash Caps:
(fig 3) Flat (Default):
(fig 4) Round:
(fig 5) Triangle:
Now, remember the example we did from the last section? You know, discussing graphics units. I instructed you to leave that code in your paint event, and here we are going to use it to help to explain how to use a pen. You should see i declared a pen called redpen, it is on the line that reads System.Drawing.Pen redpen = new System.Drawing.Pen(Color.Red);. I named it redpen, because it is red! (wow, that genius! ). Your graphics unit for that graphics object should still be set to millimeter. Add this line of code right after you declared the pen variable:
redpen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;Run your program. You will see there is a 1 millimeter wide dotted line. Close your form. Play around with the DashStyle property a bit to see what all the different line styles look like. Now, change it back to DashStyle.Dash. Add this line of code right after that one:
redpen.DashCap = System.Drawing.Drawing2D.DashCap.Triangle;Run your application again. You will be able to make out a triangle cap on the end of the dashes, resembling fig. 6. You may want to change your graphics unit to inch or increase the pen's width to 3 or 4 to see the cap a bit better. Play around with the DashCap property to see the different caps.
Change your pen's dash style back to solid. It's time to put some caps on your line. Clear all the code out of the paint event. Now, put this code in it:
System.Drawing.Graphics GraphicsObject = Graphics.FromHwnd(this.Handle); GraphicsObject.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; System.Drawing.Pen redpen = new System.Drawing.Pen(Color.Red, 5); //These are the lines that change the pen's starting and ending caps redpen.StartCap = System.Drawing.Drawing2D.LineCap.RoundAnchor; //round starting cap redpen.EndCap = System.Drawing.Drawing2D.LineCap.DiamondAnchor; //diamond ending cap /******************************************************************/ GraphicsObject.PageUnit = GraphicsUnit.Millimeter; GraphicsObject.DrawLine(redpen, new Point(20, 20), new Point(50, 50));Run your application again. You will see there is a line in the center of your form. It has a ball-like starting cap and a square like ending cap. Play around with those a bit until you feel confident you have a good understanding of what each of the caps looks like. Keep Visual Studio open, but clear the code out of the paint event. We are about to introduce you the pen's brother.
2. The System.Drawing.Brush
There are a few more drawing methods in the graphics class i need to inform you of now:
Again, i will discuss these later. These methods have something in common. They will draw shapes, not lines. For example, the FillEllipse method will draw a closed circle. This is similar to the DrawEllipse method, the only difference is that the DrawEllipse method draw an open circle comprised of a curved line, and the FillEllipse method draws a closed circular figure. The DrawEllipse method took an argument of a Pen which would define what the line would look like. The FillEllipse method takes a Brush object to define what the closed figure will look like. There are a lot more options when creating a filled shape, because then you can have thing like gradients and textures. Just as the pen determines what a line will look like, the Brush will determine how a filled region looks. Actually, the System.Drawing.Brush is nothing but an abstract class. There are other classes that inherit from it. These classes are:
A brush of a solid color. This class requires very little explaining. It only contains one property, the color. For an example on how to use the SolidBrush, put this code in your paint event:
System.Drawing.Graphics GraphicsObject = Graphics.FromHwnd(this.Handle); GraphicsObject.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; System.Drawing.SolidBrush RedSolidBrush = new SolidBrush(Color.Red); GraphicsObject.PageUnit = GraphicsUnit.Millimeter; GraphicsObject.FillEllipse(RedSolidBrush, new Rectangle(5, 5, 50, 50));
We declared the solidbrush on line 2 of that code. That's about it for the features of the solid brush, all it does is specify the color to fill the inside of a shape with.
This is my personal favorite of all the brushes. It takes an image, and uses that to fill the inside of a shape, not a color. Use this code in your paint event:
System.Drawing.Graphics GraphicsObject = Graphics.FromHwnd(this.Handle); GraphicsObject.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; System.Drawing.TextureBrush Texture = new TextureBrush(Image.FromFile( /*put the path to an image on your computer here*/ )); /******************************************************************/ GraphicsObject.PageUnit = GraphicsUnit.Millimeter; GraphicsObject.FillEllipse(Texture, new Rectangle(5, 5, 100, 100));Like it implies, replace the part that says "/*put the path to an image on your computer here*/" with a string path to an image on your computer. Any image. Run your program and see what happens. There is also a property in the texturebrush we need to discuss. It is called WrapMode. It specifies how the image will be tiled on the surface you are drawing on. It can take one of the following:
-- -- WrapMode.Clamp
The image will not be tiled, but stretched to fit the drawing canvas.
-- -- WrapMode.Tile
The image is tiled.
-- -- WrapMode.TileFlipX
The image is mirrored, then tiled.
-- -- WrapMode.TileFlipXY
The image is mirrored and flipped vertically, then tiled.
-- -- WrapMode.TileFlipY
The image is flipped vertically, then tiled.
This brush will produce a gradient from one color to another inside the region you are filling. Try this code:
System.Drawing.Graphics GraphicsObject = Graphics.FromHwnd(this.Handle); GraphicsObject.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; System.Drawing.Drawing2D.LinearGradientBrush gradient = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(5, 5, 100, 100), Color.Red, Color.Green, 0); /******************************************************************/ GraphicsObject.PageUnit = GraphicsUnit.Millimeter; GraphicsObject.FillEllipse(gradient, new Rectangle(5, 5, 100, 100));When you run that, you will see there is a circle that is red on the left, and fades to green on the right. Here is how we declared the gradient brush used to paint that:
System.Drawing.Drawing2D.LinearGradientBrush gradient = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(5, 5, 100, 100), Color.Red, Color.Green, 0);The first argument is the space the gradient will take up. If the space the gradient takes up is less than the size of the region to be filled, then the gradient is tiled like an image. The next argument is the starting color, red in this case. The next is the ending color, green in this case. The next is the angle at which the gradient will be shown. Zero produces a horizontal gradient, like the one in the example. Change the angle to 45, and it produces a top-left to bottom-right diagonal gradient. Change it to 90 degrees, and it produces a top to bottom vertical gradient.
Ah, i love this brush too. It will draw a pattern in the region to be filled. To see what i'm talking about, put this code in your paint event:
System.Drawing.Graphics GraphicsObject = Graphics.FromHwnd(this.Handle); GraphicsObject.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; System.Drawing.Drawing2D.HatchBrush pattern = new System.Drawing.Drawing2D.HatchBrush(System.Drawing.Drawing2D.HatchStyle.SolidDiamond, Color.Red, Color.Green); /******************************************************************/ GraphicsObject.PageUnit = GraphicsUnit.Millimeter; GraphicsObject.FillEllipse(pattern, new Rectangle(5, 5, 100, 100));That particular arrangement will produce a diamond-weave like red and green texture. Let's take a look at the constructor i used to make that brush:
System.Drawing.Drawing2D.HatchBrush(System.Drawing.Drawing2D.HatchStyle.SolidDiamond, Color.Red, Color.Green);The first argument is the most important. It is the pattern that will be used when drawing with the brush. In my example, i used the HatchStyle.SolidDiamond pattern, which produced the diamond like texture. There are over 50 different patterns that can be used, and there's no way i'm going to explain all of them in this tutorial. Simply play around, see what the different patterns look like. The next argument is called the Foreground Color. A pattern produced by this brush is comprised of two colors, the foreground color is simply the first of those colors. In this one, it is red. The next argument is called the background color. It is the second color in the pattern.
3. The System.Drawing.Point
There is one thing i want to go over really quick here. In the unlikely event you have never heard of a Point, is is a simple struct with only 2 properties. It specifies a point on a 2D plane, defined by X and Y coordinates. This is used quite frequently throughout the graphics class and in .NET programming in general.
3.5 The System.Drawing.PointF
This is very similar to the System.Drawing.Point, with only one difference. The System.Drawing.Point uses two ints call X and Y to specify a location in 2D space. The PointF struct uses two floats called X and Y to specify a location in 2D space. It's that simple. In fact, PointF stands for "PointFloat".
4 The System.Drawing.Rectangle
This is another quick struct i want to go over. The rectangle is similar to the point in the respect that is has an X and Y property for the upper-left corner of the rectangle, however it also has two properties called Width and Height. The rectangle is used to specify a, well, a rectangle. A 2D rectangle in space. There are quite a few more properties in the rectangle class, and i would like to go over them:
We have covered this.
We have covered this.
We have covered this.
We have covered this.
This property can be used to obtain a System.Drawing.Size of how big the rectangle is based on its width and its height. It can also be set, so instead of doing something like this:
//Example for a size of (100, 50) r.Width = 100; r.Height = 50;One could simply use
//Example for a size of (100, 50) r.Size = new Size(100, 50);
Gets a point representing where the upper-left corner of the rectangle is. This can also be set. So, instead of using:
//Example for a location of (100, 50) r.Left = 100; r.Top = 50;One could use
//Example for a location of (100, 50) r.Location = new Point(100, 50);
This is readonly. It will get the sum of the X coordinate and the Width of the rectangle, so basically it returns the X coordinate of the right side of the rectangle.
This is readonly. It will get the sum of the Y coordinate and the Height of the rectangle, so basically it returns the Y coordinate of the bottom side of the rectangle.
There are also a few methods in the rectangle struct i want to cover:
This will add the specified size to the rectangle's size. If the rectangle's size is (100, 50), and you inflate it by (10, 10), the rectangle's new size will be (110, 60).
Sees if the rectangle you provided as a parameter intersects with the rectangle you called the method from. This can be useful if you are creating a pong game, for example.
Will offset the rectangles location. The first parameter says how much to offset the X property, and the second says how much to offset Y.
4.5 The System.Drawing.RectangleF
Point is to PointF as Rectangle is to RectangleF. Makes sense, doesn't it? The only difference between the Rectangle and the RectangleF is that the X, Y, Width, and Height properties take floats, and not ints. Also, the Inflate method will take a SizeF, and not a Size. The Offset method will take two floats, and not two ints.
III. Drawing methods of the Graphics class
In the Graphics class, you will notice there are many methods that begin with "Draw" or "Fill". There are three basic types of drawing methods in the Graphics class:
1. Those that draw figures consisting of different lines. These methods usually begin with "Draw" and take Pens that define the style of the lines. These methods are:
2. Those that draw closed, filled shapes. These methods usually begin with "Fill" and take a Brush that defines how the interior of the shape will be filled with the color, texture, pattern, or gradient. These methods are:
3. Those that draw images or icons onto the drawing surface. These methods are:
There is, however, one method that doesn't fit into any of those categories. It is the DrawString method. It is used to draw text onto the drawing surface. There are several different overloads for this method, but basically it will take the string with the text you wish to draw, the font you want it drawn in, and the Brush you want to draw it with. Because it takes a brush and not a pen, this means you can draw textured, gradient, or patterned text.
V. Examples & Applications
I feel it necessary to provide a real-life example of how to use all these methods and classes i've been explaining. I have a very simple application:
Number of downloads: 8128
This is a very simple <200 line drawing application, however it demonstrates how to draw with both brushes and pens. It also demonstrates how to draw on both an image and a control. The code for the main form is probably ~50% comments, as i wanted to thoroughly explain what was happening.
The System.Drawing.Graphics class truly has some amazing abilities. Once you master them, you will be able to create painting programs with amazing features. You will learn more by experimenting with the System.Drawing.Graphics class and making paint programs by yourself than you ever will by reading this tutorial or any book. Reading how to do something will not teach you near as much unless you actually do it. So, go make something!