Coordinate system conversion

The resolution independent nature of GDI+ can cause problems when trying to convert from mouse coordinates to the current GraphicsUnit setting or from world units to pixels. When the page units are set to GraphicsUnit.Pixel, everything is fine but as soon as you begin using a real-world measurement such as points, or inches the conversion between the mouse position in pixels and the page position in inches is not immediately clear. Add to this the fact that the view of the page may have been scrolled and you have a potential head-scratcher that can eat up hours of development time.

What you need is a simple formula that will enable you to translate from screen-pixel units to page units and back again whatever the page-unit setting and whatever the current scroll position settings.

The basic formula to convert from mouse position to page-units is:

page unit position = (mousepos-scrollpos) * scaling factor (where scaling factor is 1/resolution)

The formula for converting page position to screen position is:

worldpos-(scrollpos*scalingfactor)

These formulae are good for both the X and the Y values but you must remember to calculate the X and Y resolutions independently because they are not guaranteed to be the same.

The following listing defines an application that manages scaling, scrolling, mouse input and paint output to any one of the three Page Unit settings Pixel, Millimeter and Inch.

using System;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

 

namespace ScaleDraw

{

    /// <summary>

    /// Summary description for Form1.

    /// </summary>

    public class Form1 : System.Windows.Forms.Form

    {

        /// <summary>

        /// Required designer variable.

        /// </summary>

        private System.ComponentModel.Container components = null;

 

        public Form1()

        {

            //

            // Required for Windows Form Designer support

            //

            InitializeComponent();

 

            this.AutoScrollMinSize=new Size(640,480);

            this.MaximumSize=new Size(640,480);

 

            // initial size values.

        }

 

        /// <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.mainMenu1 = new System.Windows.Forms.MainMenu();

            this.menuItem1 = new System.Windows.Forms.MenuItem();

            this.menuItem2 = new System.Windows.Forms.MenuItem();

            this.menuItem3 = new System.Windows.Forms.MenuItem();

            this.menuItem4 = new System.Windows.Forms.MenuItem();

            this.menuItem5 = new System.Windows.Forms.MenuItem();

            this.menuItem6 = new System.Windows.Forms.MenuItem();

            //

            // mainMenu1

            //

            this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {

                                                                                      this.menuItem1,

                                                                                      this.menuItem3});

            //

            // menuItem1

            //

            this.menuItem1.Index = 0;

            this.menuItem1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {

                                                                                      this.menuItem2});

            this.menuItem1.Text = "File";

            //

            // menuItem2

            //

            this.menuItem2.Index = 0;

            this.menuItem2.Text = "Exit";

            //

            // menuItem3

            //

            this.menuItem3.Index = 1;

            this.menuItem3.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {

                                                                                      this.menuItem4,

                                                                                      this.menuItem5,

                                                                                      this.menuItem6});

            this.menuItem3.Text = "PageUnit";

            //

            // menuItem4

            //

            this.menuItem4.Index = 0;

            this.menuItem4.Text = "Pixel";

            this.menuItem4.Popup += new System.EventHandler(this.menuItem4_Popup);

            this.menuItem4.Click += new System.EventHandler(this.menuItem4_Click);

            //

            // menuItem5

            //

            this.menuItem5.Index = 1;

            this.menuItem5.Text = "Millimeter";

            this.menuItem5.Popup += new System.EventHandler(this.menuItem5_Popup);

            this.menuItem5.Click += new System.EventHandler(this.menuItem5_Click);

            //

            // menuItem6

            //

            this.menuItem6.Index = 2;

            this.menuItem6.Text = "Inch";

            this.menuItem6.Popup += new System.EventHandler(this.menuItem6_Popup);

            this.menuItem6.Click += new System.EventHandler(this.menuItem6_Click);

            //

            // Form1

            //

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

            this.AutoScroll = true;

            this.AutoScrollMinSize = new System.Drawing.Size(150, 150);

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

            this.ClientSize = new System.Drawing.Size(536, 249);

            this.MaximumSize = new System.Drawing.Size(1024, 768);

            this.Menu = this.mainMenu1;

            this.Name = "Form1";

            this.Text = "Form1";

            this.SizeChanged += new EventHandler(this.Form1_SizeChanged);

            this.Paint += new PaintEventHandler(this.Form1_Paint);

            this.MouseMove += new MouseEventHandler(this.Form1_MouseMove);

 

        }

        #endregion

 

        /// <summary>

        /// The main entry point for the application.

        /// </summary>

        [STAThread]

        static void Main()

        {

            Application.Run(new Form1());

        }

 

        PointF CanvasPoint;

        private System.Windows.Forms.MainMenu mainMenu1;

        private System.Windows.Forms.MenuItem menuItem1;

        private System.Windows.Forms.MenuItem menuItem2;

        private System.Windows.Forms.MenuItem menuItem3;

        private System.Windows.Forms.MenuItem menuItem4;

        private System.Windows.Forms.MenuItem menuItem5;

        private System.Windows.Forms.MenuItem menuItem6;

 

        //This is used to hold points drawn by the user

         ArrayList blobs = new ArrayList();

 

        private void Form1_MouseMove(object sender, MouseEventArgs e)

        {

            Graphics g = CreateGraphics();

 

            //calculate scaling factor

 

            PointF ScalingFactor=new PointF(1,1);

 

            switch(PageUnit)

            {

                case GraphicsUnit.Millimeter:

                    ScalingFactor = new PointF(1.0f / (g.DpiX/25.4f),1.0f / (g.DpiY/25.4f));

                    break;

                case GraphicsUnit.Inch:

                    ScalingFactor = new PointF(1.0f / g.DpiX,1.0f / g.DpiY);

                    break;

            }

 

            //get world position.

 

            CanvasPoint = new PointF(

                    (float)(e.X-this.AutoScrollPosition.X)*ScalingFactor.X,

                    (float)(e.Y-this.AutoScrollPosition.Y)*ScalingFactor.Y

                );

 

            // store blob if mouse is pressed

 

            if(Control.MouseButtons == MouseButtons.Left)

            {

                blobs.Add(CanvasPoint);

                Invalidate();

            }

        }

 

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

        {

            SolidBrush b=new SolidBrush(Color.Black);

            PointF ScalingFactor=new PointF(1,1);

 

            //calculate scaling factor (duplicated because I'm lazy);

 

            switch(PageUnit)

            {

                case GraphicsUnit.Millimeter:

                    ScalingFactor = new PointF(1.0f / (e.Graphics.DpiX/25.4f),1.0f / (e.Graphics.DpiY/25.4f));

                    break;

                case GraphicsUnit.Inch:

                    ScalingFactor = new PointF(1.0f / e.Graphics.DpiX,1.0f / e.Graphics.DpiY);

                    break;

            }

 

            // store the graphics

            GraphicsState gs = e.Graphics.Save();

 

            e.Graphics.PageUnit=this.PageUnit;

 

            // adjust the scroll position according to the page unit scale

            Matrix m = new Matrix();

            m.Translate(this.AutoScrollPosition.X*ScalingFactor.X,this.AutoScrollPosition.Y*ScalingFactor.Y,MatrixOrder.Append);

            e.Graphics.Transform=m;

 

            // draw all the blobs in world coordinates (regardless of page scaling or units)

 

            foreach(PointF pnt in blobs)

            {

                e.Graphics.FillEllipse(b,pnt.X-2,pnt.Y-2,4,4);

            }

            b.Dispose();

 

            // get the original clean graphics back

 

            e.Graphics.Restore(gs);

        }

 

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

        {

            Invalidate();

        }

 

        GraphicsUnit PageUnit=GraphicsUnit.Pixel;

 

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

        {

            // set page unit

            PageUnit = GraphicsUnit.Pixel;

            //adjust scroll sizes

            this.AutoScrollMinSize=new Size(640,480);

            Invalidate();

        }

 

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

        {

            Graphics g = CreateGraphics();

            // set page units

           PageUnit = GraphicsUnit.Millimeter;

            // adjust scroll sizes

            this.AutoScrollMinSize=new Size((int)(640*(g.DpiX/25.4)),(int)(480*(g.DpiY/25.4)));

            Invalidate();

 

            g.Dispose();

        }

 

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

        {

            Graphics g = CreateGraphics();

            //set page units

           PageUnit = GraphicsUnit.Inch;

 

            // you know the dril...

            this.AutoScrollMinSize=new Size((int)(640*g.DpiX),(int)(480*g.DpiY));

            Invalidate();

            g.Dispose();

        }

 

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

        {

            if(PageUnit == 0)

                this.menuItem4.Checked=true;

            else

                this.menuItem4.Checked=false;

        }

 

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

        {

            if(PageUnit == 0)

                this.menuItem5.Checked=true;

            else

                this.menuItem5.Checked=false;

        

        }

 

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

        {

            if(PageUnit == 0)

                this.menuItem6.Checked=true;

            else

                this.menuItem6.Checked=false;

        

        }

   }

}

 

Back to the GDI+ FAQ

 

copyright Robert W. Powell 2003. All rights reserved.