Coordinate Systems

As with most graphics systems, the position of items on a Graphics surface is controlled by X and Y coordinates. A single coordinate is a pair of numbers that represent the distance across the surface from left to right and the distance down the surface from top to bottom. For example, a line may be drawn from place to place using the code shown in listing 1.

g.DrawLine(Pens.Black,0,0,100,200);
g.DrawLine(Pens.Black,0,0,100,200)

The coordinates are given in the last four parameters of the line drawing command as 0,0,100,200 and represent a line drawn from position 0,0 to position 100,200 as shown in figure 1.

Figure 1.

In the default mode, the coordinates used in GDI+ refer to pixel positions but GDI+ is a Resolution Independent drawing system. This means that you can represent abstract drawing units such as pixels or real-world drawing units such as inches and millimeters in GDI+.

Goodbye to integers

Many graphics systems and particularly old Windows GDI used integer values for the coordinates. This meant that you could not represent an inch and a half by the number 1.5. In GDI+ this is a thing of the past because the coordinates used to place colour on the drawing surface are floating point numbers. This means that if you wish to create a CAD drawing program and show designs in accurate inch or millimeter sizes then you can with GDI+.

Coordinate spaces

There are three distinct coordinate spaces in GDI+. These are;

  1. World coordinate space. This is where you put the coordinates that define lines, shapes and points in the 2 dimensional space of the graphics system. World coordinates are abstract values expressed as floating point numbers. Essentially, whenever you draw something it goes into this coordinate space.

  2. Page Coordinate Space. The Page space is where the world coordinates are transformed into some real-world value. You can make the Page Space represent pixels, inches millimeters and so-on. This is what makes GDI+ a resolution independent system. You control how the page space interprets the world space by telling the Graphics object what PageUnit is being used and adjusting the PageScale.

  3. Device Coordinate Space. This space is controlled by the system and enables the real-world values in the Page Space to be translated to your screen or printer. Device space ensures that a 1 inch long line looks an inch long on the screen and on the printer even though the two devices may have very different pixel resolutions. You have no direct control over this space.

The correct term for this type of system, where coordinates are transformed from one system to another over several steps, is a Graphics Pipeline. The GDI+ graphics pipeline takes the values you use and transforms them from an abstract floating point number to a real-world value and then to the hardware world of the monitor or printer.

Real-world values.

The actual real-world standards available to you for the Page Space are;

  • Pixel. Each unit in world space represents one pixel on the screen or printer. This is the default value for the page units. Artifacts drawn with this setting will be of different sizes on different devices such as screens and printers.

  • Millimeter. Each unit in world space represents one millimeter. This page unit setting will look the same on the printer as it does on the screen.

  • Inch. Each unit in world space represents one inch. This page unit setting will look the same on the printer as it does on the screen.

  • Point. Each unit in world space represents one printers point which is 1/72nd of an inch. Points are the preferred unit of measure for type-intensive applications. This page unit setting will look the same on the printer as it does on the screen.

  • Display. A world space unit will represent 1/75th of an inch. This is a holdover from the days when common CRT dot-pitch was 75 dots-per-inch (DPI). This page unit setting will look the same on the printer as it does on the screen.

  • Document. World space units will represent 1/300th of an inch. This value is a holdover from when laser-printers commonly had 300 DPI resolutions

  • World. Supposedly, this should work the same way a the Pixel system but in practice it often causes an error. Generally, you don't need to worry about this setting.

The following demonstration application shows how the real-world values can be used in your code. The application draws several rectangles that are created to inch, millimeter and pixel sizes. The application will print too so that you can check the sizes against a printed copy and on screen. If you have a plug and play monitor you'll see that the printed sizes and the screen sizes of the real-world values are identical but the pixel values aren't. Figure 1 shows the application at work.

Figure 1. Real-world drawing systems.

The bit that does the drawing of the rectangles is shown in the following listing.

    private void DrawRectangles(Graphics g)

    {

      g.PageUnit=GraphicsUnit.Pixel;

      Pen p=new Pen(Color.Black, 3); //this pen will be 3 pixels wide

      g.DrawRectangle(p,10,10,200,100); //draw a rectangle in Pixel mode (the default)

      p.Dispose();

 

      g.PageUnit=GraphicsUnit.Inch;

      p=new Pen(Color.Blue,0.05f); //this pen will be 1/20th of an inch wide

      g.DrawRectangle(p,0.1f,1.5f,4f,1f); // draw a rectangle 4" by 1"

      p.Dispose();

 

      g.PageUnit=GraphicsUnit.Millimeter;

      p=new Pen(Color.Green,1f); //this pen will be 1 millimeter wide

      g.DrawRectangle(p,4f,80f,80f,60f); // draw a rectangle 80 by 60 mm

      p.Dispose();

    } 

    Private Sub DrawRectangles(ByVal g As Graphics)

      g.PageUnit = GraphicsUnit.Pixel

      Dim p As New Pen(Color.Black, 3) 'this pen will be 3 pixels wide

      g.DrawRectangle(p, 10, 10, 200, 100) 'draw a rectangle in Pixel mode (the default)

      p.Dispose()

 

      g.PageUnit = GraphicsUnit.Inch

      p = New Pen(Color.Blue, 0.05F) 'this pen will be 1/20th of an inch wide

      g.DrawRectangle(p, 0.1F, 1.5F, 4.0F, 1.0F) ' draw a rectangle 4" by 1"

      p.Dispose()

 

      g.PageUnit = GraphicsUnit.Millimeter

      p = New Pen(Color.Green, 1.0F) 'this pen will be 1 millimeter wide

      g.DrawRectangle(p, 4.0F, 80.0F, 80.0F, 60.0F) ' draw a rectangle 80 by 60 mm

      p.Dispose()

    End Sub 'DrawRectangles 

The same code, in DrawRectangles, is used for both printing and screen drawing. Note how, especially for the inch based drawing, floating point values with fractional numbers are used.

The full listing of the code, shown in listing 2, includes the button click handler to print a single page with the rectangles drawn on it. Unless by some strange quirk of fate your printer resolution is identical to that of your screen you'll see two very different versions of the same code.

using System;

using System.Drawing;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

using System.Drawing.Printing;

 

namespace RealWorld

{

  /// <summary>

  /// Summary description for Form1.

  /// </summary>

  public class Form1 : System.Windows.Forms.Form

  {

    private System.Windows.Forms.Panel panel1;

    private System.Windows.Forms.Button button1;

    /// <summary>

    /// Required designer variable.

    /// </summary>

    private System.ComponentModel.Container components = null;

 

    public Form1()

    {

      //

      // Required for Windows Form Designer support

      //

      InitializeComponent();

 

      //

      // TODO: Add any constructor code after InitializeComponent call

      //

    }

 

    /// <summary>

    /// Clean up any resources being used.

    /// </summary>

    protected override void Dispose( bool disposing )

    {

      if( disposing )

      {

        if (components != null)

        {

          components.Dispose();

        }

      }

      base.Dispose( disposing );

    }

 

    #region Windows Form Designer generated code

    /// <summary>

    /// Required method for Designer support - do not modify

    /// the contents of this method with the code editor.

    /// </summary>

    private void InitializeComponent()

    {

      this.panel1 = new System.Windows.Forms.Panel();

      this.button1 = new System.Windows.Forms.Button();

      this.SuspendLayout();

      //

      // panel1

      //

      this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)

        | System.Windows.Forms.AnchorStyles.Left)

        | System.Windows.Forms.AnchorStyles.Right)));

      this.panel1.BackColor = System.Drawing.Color.White;

      this.panel1.Location = new System.Drawing.Point(8, 8);

      this.panel1.Name = "panel1";

      this.panel1.Size = new System.Drawing.Size(312, 320);

      this.panel1.TabIndex = 0;

      this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1_Paint);

      //

      // button1

      //

      this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));

      this.button1.Location = new System.Drawing.Point(336, 40);

      this.button1.Name = "button1";

      this.button1.TabIndex = 1;

      this.button1.Text = "Print";

      this.button1.Click += new System.EventHandler(this.button1_Click);

      //

      // Form1

      //

      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);

      this.ClientSize = new System.Drawing.Size(416, 334);

      this.Controls.Add(this.button1);

      this.Controls.Add(this.panel1);

      this.Name = "Form1";

      this.Text = "Form1";

      this.ResumeLayout(false);

 

    }

    #endregion

 

    /// <summary>

    /// The main entry point for the application.

    /// </summary>

    [STAThread]

    static void Main()

    {

      Application.Run(new Form1());

    }

 

    private void DrawRectangles(Graphics g)

    {

      g.PageUnit=GraphicsUnit.Pixel;

      Pen p=new Pen(Color.Black, 3); //this pen will be 3 pixels wide

      g.DrawRectangle(p,10,10,200,100); //draw a rectangle in Pixel mode (the default)

      p.Dispose();

 

      g.PageUnit=GraphicsUnit.Inch;

      p=new Pen(Color.Blue,0.05f); //this pen will be 1/20th of an inch wide

      g.DrawRectangle(p,0.1f,1.5f,4f,1f); // draw a rectangle 4" by 1"

      p.Dispose();

 

      g.PageUnit=GraphicsUnit.Millimeter;

      p=new Pen(Color.Green,1f); //this pen will be 1 millimeter wide

      g.DrawRectangle(p,4f,80f,80f,60f); // draw a rectangle 80 by 60 mm

      p.Dispose();

    }

 

    private void panel1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)

    {

      DrawRectangles(e.Graphics);

    }

 

    private void button1_Click(object sender, System.EventArgs e)

    {

      PrintDocument pd=new PrintDocument();

      pd.PrintPage+=new PrintPageEventHandler(pd_PrintPage);

      pd.Print();

    }

 

    private void pd_PrintPage(object sender, PrintPageEventArgs e)

    {

      DrawRectangles(e.Graphics);

      e.HasMorePages=false;

    }

  }

}

 

 

Imports System

Imports System.Drawing

Imports System.Collections

Imports System.ComponentModel

Imports System.Windows.Forms

Imports System.Data

Imports System.Drawing.Printing

 

 

Namespace RealWorld

   '/ <summary>

   '/ Summary description for Form1.

   '/ </summary>

  

   Public Class Form1

    Inherits System.Windows.Forms.Form

    Private WithEvents panel1 As System.Windows.Forms.Panel

    Private WithEvents button1 As System.Windows.Forms.Button

    '/ <summary>

    '/ Required designer variable.

    '/ </summary>

    Private components As System.ComponentModel.Container = Nothing

    

    

    Public Sub New()

     '

     ' Required for Windows Form Designer support

     '

     InitializeComponent()

    End Sub 'New

     

    '

    ' TODO: Add any constructor code after InitializeComponent call

    '

    

    '/ <summary>

    '/ Clean up any resources being used.

    '/ </summary>

    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)

      If disposing Then

        If Not (components Is Nothing) Then

          components.Dispose()

        End If

      End If

      MyBase.Dispose(disposing)

    End Sub 'Dispose

 

#Region "Windows Form Designer generated code"

 

    '/ <summary>

    '/ Required method for Designer support - do not modify

    '/ the contents of this method with the code editor.

    '/ </summary>

    Private Sub InitializeComponent()

      Me.panel1 = New System.Windows.Forms.Panel

      Me.button1 = New System.Windows.Forms.Button

      Me.SuspendLayout()

      '

      ' panel1

      '

      Me.panel1.Anchor = CType(System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Left Or System.Windows.Forms.AnchorStyles.Right, System.Windows.Forms.AnchorStyles)

      Me.panel1.BackColor = System.Drawing.Color.White

      Me.panel1.Location = New System.Drawing.Point(8, 8)

      Me.panel1.Name = "panel1"

      Me.panel1.Size = New System.Drawing.Size(312, 320)

      Me.panel1.TabIndex = 0

      '

      ' button1

      '

      Me.button1.Anchor = CType(System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Right, System.Windows.Forms.AnchorStyles)

      Me.button1.Location = New System.Drawing.Point(336, 40)

      Me.button1.Name = "button1"

      Me.button1.TabIndex = 1

      Me.button1.Text = "Print"

      '

      ' Form1

      '

      Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)

      Me.ClientSize = New System.Drawing.Size(416, 334)

      Me.Controls.Add(button1)

      Me.Controls.Add(panel1)

      Me.Name = "Form1"

      Me.Text = "Form1"

      Me.ResumeLayout(False)

    End Sub 'InitializeComponent

#End Region

 

 

    '/ <summary>

    '/ The main entry point for the application.

    '/ </summary>

    <STAThread()> _

    Shared Sub Main()

      Application.Run(New Form1)

    End Sub 'Main

 

 

    Private Sub DrawRectangles(ByVal g As Graphics)

      g.PageUnit = GraphicsUnit.Pixel

      Dim p As New Pen(Color.Black, 3) 'this pen will be 3 pixels wide

      g.DrawRectangle(p, 10, 10, 200, 100) 'draw a rectangle in Pixel mode (the default)

      p.Dispose()

 

      g.PageUnit = GraphicsUnit.Inch

      p = New Pen(Color.Blue, 0.05F) 'this pen will be 1/20th of an inch wide

      g.DrawRectangle(p, 0.1F, 1.5F, 4.0F, 1.0F) ' draw a rectangle 4" by 1"

      p.Dispose()

 

      g.PageUnit = GraphicsUnit.Millimeter

      p = New Pen(Color.Green, 1.0F) 'this pen will be 1 millimeter wide

      g.DrawRectangle(p, 4.0F, 80.0F, 80.0F, 60.0F) ' draw a rectangle 80 by 60 mm

      p.Dispose()

    End Sub 'DrawRectangles

 

 

    Private Sub panel1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles panel1.Paint

      DrawRectangles(e.Graphics)

    End Sub 'panel1_Paint

 

 

    Private Sub button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles button1.Click

      Dim pd As New PrintDocument

      AddHandler pd.PrintPage, AddressOf pd_PrintPage

      pd.Print()

    End Sub 'button1_Click

 

 

    Private Sub pd_PrintPage(ByVal sender As Object, ByVal e As PrintPageEventArgs)

      DrawRectangles(e.Graphics)

      e.HasMorePages = False

    End Sub 'pd_PrintPage

  End Class 'Form1

End Namespace 'RealWorld

Return to the Beginners Guide to GDI+