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

Things to do in Graphics Land

0.00/5 (No votes)
2 Jun 2010 1  
Basic and advanced graphics via C#.

Basic and Advanced Graphics via C#

One of the most underrated aspects of C# and the .NET Framework is the advanced graphic capabilities. This article will focus on those capabilities, but will first begin with some of the basics in an effort to present the content sequentially. The advanced student in .NET graphics should overlook this article. But, in my limited knowledge, I will try and explain (by example) how to gain a working knowledge of these graphics capabilities.

For starters, it is common knowledge that to draw on a form, we first create a Graphics object by calling the System.Windows.Forms.Control.CreateGraphics method. We then create a Pen object and call a member of the Graphics class to draw on the control using the Pen. By default, pens draw solid lines. To draw a dotted line, set the Pen.DashStyle property to one of these values:

  • DashStyle.Dash
  • DashStyle.DashDot
  • DashStyle.DashDotDot
  • DashStyle.Dot
  • DashStyle.Solid

Here is a code example of these set properties:

namespace PenApp
{
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

public class MainForm : System.Windows.Forms.Form
{
    private System.ComponentModel.Container components;
    public MainForm()
    {
        InitializeComponent();
        CenterToScreen();
        SetStyle(ControlStyles.ResizeRedraw, true);    
    }
    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }
    private void InitializeComponent()
    {
       this.AutoScaleBaseSize = new System.Drawing.Size(4, 13);
       this.ClientSize = new System.Drawing.Size(320, 273);
       this.Text = "Pens...";
       this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
    }
        
    [STAThread]
    static void Main() 
    {
        Application.Run(new MainForm());
    }

    private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
       Graphics g = e.Graphics;
       Pen p = new Pen(Color.Red, 7);
       p.DashStyle = DashStyle.Dot;
       g.DrawLine(p, 50, 25, 400, 25);
       p.DashStyle = DashStyle.Dash;
       g.DrawLine(p, 50, 50, 400, 50);
       p.DashStyle = DashStyle.DashDot;
       g.DrawLine(p, 50, 75, 400, 75);
       p.DashStyle = DashStyle.DashDotDot;
       g.DrawLine(p, 50, 100, 400, 100);
       p.DashStyle = DashStyle.Solid;
       g.DrawLine(p, 50, 125, 400, 125);
    }
}
}

Result:

1.JPG

For most of the Draw methods, the Graphics class also has Fill methods that draw a shape and fill the contents. These methods work exactly like the Draw methods, except they require an instance of the Brush class instead of the Pen class. The Brush class is abstract, so you must instantiate one of the child classes:

  • System.Drawing.Drawing2D.HatchBrush
  • System.Drawing.Drawing2D.LinearGradientBrush
  • System.Drawing.Drawing2D.PathGradientBrush.
  • System.Drawing.SolidBrush
  • System.Drawing.TextureBrush

This very basic code example below draws a solid, maroon, five-sided polygon:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
    private System.ComponentModel.Container components;
    public MainForm()
    {
        InitializeComponent();
        CenterToScreen();
        SetStyle(ControlStyles.ResizeRedraw, true); 
    }

    protected override void Dispose( bool disposing )
    {
        if( disposing )
        {
            if (components != null) 
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }

    private void InitializeComponent()
    {
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
        this.ClientSize = new System.Drawing.Size(292, 273);
        this.Text = "Fun with graphics";
        this.Paint += 
          new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
    }

    [STAThread]
    static void Main() 
    {
        Application.Run(new MainForm());
    }

    private void MainForm_Paint(object sender, 
                 System.Windows.Forms.PaintEventArgs e)
    {
        Graphics g = this.CreateGraphics();
        Brush b = new SolidBrush(Color.Maroon);
        Point [] points = new Point[]
          {new Point(10, 10),
           new Point(10, 100),
           new Point(50, 65),
           new Point(100, 100),
           new Point(85, 40)};
        g.FillPolygon(b, points);
     }
}

Result:

2.JPG

General Paths

Our next example demonstrates the use of a general path. A general path is a shape constructed from straight lines and complex curves. An object of class GraphicsPath (namespace System.Drawing.Drawing2D) represents a general path. The GraphicsPath class provides functionality that enables the creation of complex shapes from vector-based primitive graphics objects. A GraphicsPath object consists of figures defined by simple shapes. The start point of each vector-graphics object (such as a line or arc) that is added to the path is connected by a straight line to the end point of the previous object. When called, the CloseFigure method attaches the final vector-graphic object end point to the initial starting point for the current figure by a straight line, then starts a new figure. The method StartFigure begins a new figure within the path without closing the previous figure.

So the program below draws general paths in the shape of five-pointed stars. We define two int arrays, representing the x- and y-coordinates of the points in the star, to then define a GraphicsPath object star. A loop then creates lines to connect the points of the star and adds these lines to the star. We use the GraphicsPath method AddLine to append a line to the shape. The arguments of AddLine specify the coordinates for the line's endpoints; each new call to AddLine adds a line from the previous point to the current point. We use the GraphicsPath method CloseFigure to complete the shape.

Afterwards, we set the origin of the Graphics object. The arguments to the method TranslateTransform indicate that the origin should be translated to the coordinates (150, 150). The loop in lines draws the star 18 times, rotating it around the origin. The Graphics method RotateTransform is called to move to the next position on the form; the argument specifies the rotation angle in degrees. The Graphics method FillPath then draws a filled version of the star with the Brush created. The application determines the SolidBrush's color randomly, using the Random method Next.

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;


public class DrawStarsForm : Form
{
   public DrawStarsForm()
   {
      InitializeComponent();
   } // end constructor


   private System.ComponentModel.IContainer components = null;

     
   protected override void Dispose(bool disposing)
   {
       if (disposing && (components != null))
       {
           components.Dispose();
       }
       base.Dispose(disposing);
   }


   private void InitializeComponent()
   {
     this.SuspendLayout();
     // 
     // DrawStarsForm
     // 
     this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
     this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
     this.ClientSize = new System.Drawing.Size(313, 302);
     this.Name = "DrawStarsForm";
     this.Text = "Drawing Stars";
     this.Paint += new System.Windows.Forms.PaintEventHandler(
                       this.DrawStarsForm_Paint);
     this.ResumeLayout(false);
   }

   [STAThread]
   static void Main()
   {
     Application.EnableVisualStyles();
     Application.Run(new DrawStarsForm());
   }

   private void DrawStarsForm_Paint(object sender, PaintEventArgs e)
   {
        Graphics graphicsObject = e.Graphics;
        Random random = new Random();
        SolidBrush brush = new SolidBrush( Color.DarkMagenta );

        int[] xPoints = { 55, 67, 109, 73, 83, 55, 27, 37, 1, 43 };
        int[] yPoints = { 0, 36, 36, 54, 96, 72, 96, 54, 36, 36 };

        GraphicsPath star = new GraphicsPath();

        for ( int i = 0; i <= 8; i += 2 )
            star.AddLine( xPoints[ i ], yPoints[ i ],
                          xPoints[ i + 1 ], yPoints[ i + 1 ] );
        star.CloseFigure();

        // translate the origin to (150, 150)
        graphicsObject.TranslateTransform( 150, 150 );

        // rotate the origin and draw stars in random colors
        for ( int i = 1; i <= 18; i++ )
        {
            graphicsObject.RotateTransform( 20 );

            brush.Color = Color.FromArgb( 
            random.Next( 200, 255 ), random.Next( 255 ), 
            random.Next( 255 ), random.Next( 255 ) );

            graphicsObject.FillPath( brush, star );
        } 
    } 
}

Result:

3.JPG

The next example sort of integrates some of the concepts discussed thus far. In C#, all linear gradients are defined along a line that determines the gradient endpoints. This line can be specified either by the starting and ending points, or by the diagonal of a rectangle. The first argument, Rectangle drawArea1, represents the endpoints of the linear gradient: the upper-left corner is the starting point and the bottom-right corner is the ending point. The second and third arguments specify the colors that the gradient will use. In this case, the color of the ellipse will gradually change from Color.Blue to Color.Yellow. The last argument, a type from the enumeration LinearGradientMode, specifies the linear gradient's direction. In our case, we use LinearGradientMode.ForwardDiagonal, which creates a gradient from the upper-left to the lower-right corner. We then use the Graphics method FillEllipse to draw an ellipse with linearBrush; the color gradually changes from blue to yellow, as described above. We create a Pen object thickRedPen. We pass to thickRedPen's constructor Color.Red an int argument 10, indicating that we want thickRedPen to draw red lines that are 10 pixels wide.

We then create a new Bitmap image, which initially is empty. The class Bitmap can produce images in color and gray scale; this particular Bitmap is 10 pixels wide and 10 pixels tall. The method FromImage is a static member of the Graphics class, and retrieves the Graphics object associated with an Image, which may be used to draw on an image. Lines 52-65 draw on the Bitmap a pattern consisting of black, blue, red, and yellow rectangles and lines. A TextureBrush is a brush that fills the interior of a shape with an image, rather than a solid color. In line 71, the TextureBrush object, textureBrush, fills a rectangle with our Bitmap. The TextureBrush constructor used takes as an argument an image that defines its texture.

Next, we draw a pie-shaped arc with a thick white line. We then set coloredPen's color to White and modify its width to be 6 pixels. We then draw the pie on the form by specifying the Pen, the x-coordinate, y-coordinate, width and height of the bounding rectangle, and the start and sweep angles.

Going further, we draw a five-pixel-wide green line. Finally, we use the enumerations DashCap and DashStyle (namespace System.Drawing.Drawing2D) to specify settings for a dashed line. We then follow to set the DashCap property of coloredPen (not to be confused with the DashCap enumeration) to a member of the DashCap enumeration. The DashCap enumeration specifies the styles for the start and end of a dashed line. In this case, we want both ends of the dashed line to be rounded, so we use DashCap.Round. We then set the DashStyle property of coloredPen (not to be confused with the DashStyle enumeration) to DashStyle.Dash, indicating that we want our line to consist entirely of dashes:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

public partial class DrawShapesForm : Form
{
  
   public DrawShapesForm()
   {
      InitializeComponent();
   } 
   private System.ComponentModel.IContainer components = null;

      
   protected override void Dispose(bool disposing)
   {
       if (disposing && (components != null))
       {
           components.Dispose();
       }
       base.Dispose(disposing);
   }
  
   private void InitializeComponent()
   {
     this.SuspendLayout();
     this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
     this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
     this.ClientSize = new System.Drawing.Size(449, 188);
     this.Name = "DrawShapesForm";
     this.Text = "Drawing Shapes";
     this.Paint += new System.Windows.Forms.PaintEventHandler(
                              this.DrawShapesForm_Paint);
     this.ResumeLayout(false);
   }

    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.Run(new DrawShapesForm());
    }

    private void DrawShapesForm_Paint( object sender, PaintEventArgs e )
    {
        Graphics graphicsObject = e.Graphics;
        Rectangle drawArea1 = new Rectangle( 5, 35, 30, 100 );
        LinearGradientBrush linearBrush =
        new LinearGradientBrush( drawArea1, Color.Blue,
        Color.Yellow, LinearGradientMode.ForwardDiagonal );
        graphicsObject.FillEllipse( linearBrush, 5, 30, 65, 100 );
        Pen thickRedPen = new Pen( Color.Red, 10 );
        Rectangle drawArea2 = new Rectangle( 80, 30, 65, 100 );
        graphicsObject.DrawRectangle( thickRedPen, drawArea2 );

        Bitmap textureBitmap = new Bitmap( 10, 10 );
        Graphics graphicsObject2 =
        Graphics.FromImage( textureBitmap );
        SolidBrush solidColorBrush =
        new SolidBrush( Color.Red );
        Pen coloredPen = new Pen( solidColorBrush );
        solidColorBrush.Color = Color.Yellow;
        graphicsObject2.FillRectangle( solidColorBrush, 0, 0, 10, 10 );
        coloredPen.Color = Color.Black;
        graphicsObject2.DrawRectangle( coloredPen, 1, 1, 6, 6 );
        solidColorBrush.Color = Color.Blue;
        graphicsObject2.FillRectangle( solidColorBrush, 1, 1, 3, 3 );
        solidColorBrush.Color = Color.Red;
        graphicsObject2.FillRectangle( solidColorBrush, 4, 4, 3, 3 );
        TextureBrush texturedBrush =
        new TextureBrush( textureBitmap );
        graphicsObject.FillRectangle( texturedBrush, 155, 30, 75, 100 );
        coloredPen.Color = Color.White;
        coloredPen.Width = 6;
        graphicsObject.DrawPie( coloredPen, 240, 30, 75, 100, 0, 270 );
        coloredPen.Color = Color.Green;
        coloredPen.Width = 5;
        graphicsObject.DrawLine( coloredPen, 395, 30, 320, 150 );
        coloredPen.Color = Color.Yellow;
        coloredPen.DashCap = DashCap.Round;
        coloredPen.DashStyle = DashStyle.Dash;
        graphicsObject.DrawLine( coloredPen, 320, 30, 395, 150 );
    } 
}

Result:

4.JPG

Image Processing

The processing of an image consists of modifying its pixels based on certain mathematical operations. Each pixel is encoded using three integer values, one for red, one for green, and one for blue. The range of values depends on the number of bits per pixel. As of the writing of this paper, the best quality possible is obtained with 24 bits per pixel, a value between 0 and 255 for each component, which means a little over 16 million colors. The example below show image processing by involving the inversion of colors. For example, suppose that the number of bits per pixel is 24. The inversion of a color consists of assigning the 255-complement to each of the three components, for each pixel of the image. Run the code, notice the image, and then the colors will be inverted:

using System;
using System.Threading;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Data;
public class MainForm : Form
{
    private System.ComponentModel.Container components;
    public MainForm()
    {
        InitializeComponent();
        CenterToScreen();
    } 
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (components != null)
            {
                components.Dispose();
            }
        }
        base.Dispose( disposing );
    }
    private void InitializeComponent()
    {
        this.AutoScaleBaseSize= new System.Drawing.Size(5, 13);
        this.ClientSize = new System.Drawing.Size(292, 272);
        this.Text = "Picture.jpg";
        this.Paint += new System.Windows.Forms.PaintEventHandler(
                          this.MainForm_Paint);
    }

    [STAThread]
    static void Main() {
        Application.Run(new MainForm());
    }

    private void MainForm_Paint(object sender, PaintEventArgs e)
    {
        using ( Graphics g = CreateGraphics() ) {
            Bitmap m_Bmp = new Bitmap(@"C:\garden.jpg");
            g.DrawImage( m_Bmp, new Point(5,5) );
            Thread.Sleep(1000);
            int width = m_Bmp.Width;
            int height = m_Bmp.Height;
            Color cSrc, cDest;
            for (int y = 0; y < height; y++)
            for (int x = 0; x < width; x++) {
                cSrc = m_Bmp.GetPixel(x, y);
                cDest = Color.FromArgb( 255-cSrc.R, 255-cSrc.G, 255-cSrc.B);
                m_Bmp.SetPixel(x, y, cDest);
            }
            g.DrawImage(m_Bmp, new Point (5, 5));
        }
    }
}

Here is the initial image:

6.JPG

Here is the image after the processing. Just let the image be and the colors will invert:

7.JPG

Buffered Graphics

The BufferedGraphics class allows you to implement custom double buffering for your graphics. It provides a wrapper for a graphics buffer, along with methods that you can use to write to the buffer and render its contents to an output device. Graphics that use double buffering can reduce or eliminate flicker that is caused by redrawing a display surface. When you use double buffering, updated graphics are first drawn to a buffer in memory, and the contents of this buffer are then quickly written to some or the entire displayed surface. This relatively brief overwrite of the displayed graphics typically reduces or eliminates the flicker that sometimes occurs when graphics are updated. The BufferedGraphics class has no public constructor, and must be created by the BufferedGraphicsContext for an application domain, using its Allocate method. You can retrieve the BufferedGraphicsContext for the current application domain from the static BufferedGraphicsManager.Current property:

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace BufferingExample 
{
    public class BufferingExample : Form
    {
        private BufferedGraphicsContext context;
        private BufferedGraphics grafx;

        private byte bufferingMode;
        private string[] bufferingModeStrings = 
        {
            "Draw to Form without OptimizedDoubleBufferring control style",
            "Draw to Form using OptimizedDoubleBuffering control style",
            "Draw to HDC for form" };

            private System.Windows.Forms.Timer timer1;
            private byte count;

            public BufferingExample() : base()
            {
                // Configure the Form for this example.
                this.Text = "User double buffering";
                this.MouseDown += new MouseEventHandler(this.MouseDownHandler);
                this.Resize += new EventHandler(this.OnResize);
                this.SetStyle( ControlStyles.AllPaintingInWmPaint | 
                               ControlStyles.UserPaint, true );

                // Configure a timer to draw graphics updates.
                timer1 = new System.Windows.Forms.Timer();
                timer1.Interval = 200;
                timer1.Tick += new EventHandler(this.OnTimer);

                bufferingMode = 2;
                count = 0;

                context = BufferedGraphicsManager.Current;
                context.MaximumBuffer = new Size(this.Width+1, this.Height+1);
                grafx = context.Allocate(this.CreateGraphics(), 
                new Rectangle( 0, 0, this.Width, this.Height ));
                DrawToBuffer(grafx.Graphics);
            }

            private void MouseDownHandler(object sender, MouseEventArgs e)
            {
                if( e.Button == MouseButtons.Right )
                {
                    if( ++bufferingMode > 2 )
                        bufferingMode = 0;

                     if( bufferingMode == 1 )
                         this.SetStyle( ControlStyles.OptimizedDoubleBuffer, true );

                     // If the current buffering mode uses
                     // the OptimizedDoubleBuffering ControlStyle,
                     // enabke the control style.
                     if( bufferingMode == 2 )
                         this.SetStyle( ControlStyles.OptimizedDoubleBuffer, false );

                     // Cause the background to be cleared and redraw.
                     count = 6;
                     DrawToBuffer(grafx.Graphics);
                     this.Refresh();
                }
                else
                {
                    // Toggle whether the redraw timer is active.
                    if( timer1.Enabled )
                        timer1.Stop();
                    else
                        timer1.Start(); 
                }
            }

            private void OnTimer(object sender, EventArgs e)
            {
                // Draw randomly positioned ellipses to the buffer.
                DrawToBuffer(grafx.Graphics);

                // If in bufferingMode 2, draw to the form's HDC.
                if( bufferingMode == 2 )            
                    // Render the graphics buffer to the form's HDC.
                    grafx.Render(Graphics.FromHwnd(this.Handle)); 
                    // If in bufferingMode 0 or 1, draw in the paint method.
                else
                    this.Refresh();
            }

            private void OnResize(object sender, EventArgs e)
            {
               // Re-create the graphics buffer for a new window size.
               context.MaximumBuffer = new Size(this.Width+1, this.Height+1);
               if( grafx != null )
               {
                       grafx.Dispose();
                       grafx = null;
               }
               grafx = context.Allocate(this.CreateGraphics(), 
                   new Rectangle( 0, 0, this.Width, this.Height ));

               // Cause the background to be cleared and redraw.
               count = 6;
               DrawToBuffer(grafx.Graphics);
               this.Refresh();
            }    

            private void DrawToBuffer(Graphics g)
            {
                // Clear the graphics buffer every five updates.
                if( ++count > 5 )
                {
                    count = 0;
                    grafx.Graphics.FillRectangle(Brushes.Black, 0, 0, 
                                   this.Width, this.Height);
                }

                // Draw randomly positioned and colored ellipses.
                Random rnd = new Random();
                for( int i=0; i<20; i++ )
                {
                    int px = rnd.Next(20,this.Width-40);
                    int py = rnd.Next(20,this.Height-40);
                    g.DrawEllipse(new Pen(Color.FromArgb(rnd.Next(0, 255), rnd.Next(0,255), 
                                  rnd.Next(0,255)), 1), px, py, 
                                  px+rnd.Next(0, this.Width-px-20), 
                                  py+rnd.Next(0, this.Height-py-20));
                }

                // Draw information strings.
                g.DrawString("Buffering Mode: "+bufferingModeStrings[bufferingMode], 
                             new Font("Arial", 8), Brushes.White, 10, 10);
                g.DrawString("Right-click to cycle buffering mode", 
                             new Font("Arial", 8), Brushes.White, 10, 22);
                g.DrawString("Left-click to toggle timed display refresh", 
                             new Font("Arial", 8), Brushes.White, 10, 34);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            grafx.Render(e.Graphics);
        }

        [STAThread]
        public static void Main(string[] args)
        {
            Application.Run(new BufferingExample());
        }
    }
}

Result:

8.JPG

The Graphics property can be used for drawing to the graphics buffer. This property provides access to the Graphics object that draws to the graphics buffer allocated for this BufferedGraphics object. The Render method with no arguments draws the contents of the graphics buffer to the surface specified when the buffer was allocated. Other overloads of the Render method allow you to specify a Graphics object or an IntPtr object that points, or rather makes reference to, a device context to which to draw the contents of the graphics buffer.

References

  • The MSDN Library - Graphics section.

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