473,395 Members | 1,584 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes and contribute your articles to a community of 473,395 developers and data experts.

Object scaling for varying resolutions

GaryTexmo
1,501 Expert 1GB
Hi all,

I'm going to describe a method of scaling your objects so they fit correctly on your screen no matter what size of canvas you're drawing them on. This is going to be a fairly simple, starter approach that will hopefully spark your imagination and get you started. This is really a general graphics approach, but I'm going to do it in WinForms with GDI+ for simplicity sake. You can apply the same principles to XNA or DirectX if you like.

Ok, lets get started. We need something to draw first, so lets create a simple object hierarchy that we can use. I'll start with three objects... an interface class, IDrawableObject, then two other classes, DrawableRectangle and DrawableCircle that both inherit from this interface.

Expand|Select|Wrap|Line Numbers
  1.     public interface IDrawableObject
  2.     {
  3.         void Draw(Graphics g);
  4.     }
  5.  
  6.     public class DrawableRectangle : IDrawableObject
  7.     {
  8.         #region Private Members
  9.         private Point m_location = new Point();
  10.         private Size m_size = new Size();
  11.         private Color m_drawColor;
  12.         #endregion
  13.  
  14.         #region Public Properties
  15.         public Color Color
  16.         {
  17.             get { return m_drawColor; }
  18.             set { m_drawColor = value; }
  19.         }
  20.  
  21.         public Point Location
  22.         {
  23.             get { return m_location; }
  24.             set { m_location = value; }
  25.         }
  26.  
  27.         public Size Size
  28.         {
  29.             get { return m_size; }
  30.             set { m_size = value; }
  31.         }
  32.  
  33.         public int X
  34.         {
  35.             get { return m_location.X; }
  36.             set { m_location.X = value; }
  37.         }
  38.  
  39.         public int Y
  40.         {
  41.             get { return m_location.Y; }
  42.             set { m_location.Y = value; }
  43.         }
  44.  
  45.         public int Width
  46.         {
  47.             get { return m_size.Width; }
  48.             set { m_size.Height = value; }
  49.         }
  50.  
  51.         public int Height
  52.         {
  53.             get { return m_size.Height; }
  54.             set { m_size.Height = value; }
  55.         }
  56.         #endregion
  57.  
  58.         #region Constructors
  59.         public DrawableRectangle() { }
  60.  
  61.         public DrawableRectangle(Point location, Size size)
  62.             : this()
  63.         {
  64.             m_location = location;
  65.             m_size = size;
  66.         }
  67.  
  68.         public DrawableRectangle(int x, int y, int width, int height)
  69.             : this(new Point(x, y), new Size(width, height))
  70.         {
  71.         }
  72.  
  73.         public DrawableRectangle(Point location, Size size, Color color)
  74.             : this(location, size)
  75.         {
  76.             m_drawColor = color;
  77.         }
  78.  
  79.         public DrawableRectangle(int x, int y, int width, int height, Color color)
  80.             : this(new Point(x, y), new Size(width, height), color)
  81.         {
  82.         }
  83.         #endregion
  84.  
  85.         #region IDrawableObject
  86.         public void Draw(Graphics g)
  87.         {
  88.             if (g != null)
  89.             {
  90.                 g.DrawRectangle(new Pen(m_drawColor), m_location.X, m_location.Y, m_size.Width, m_size.Height);
  91.             }
  92.         }
  93.         #endregion
  94.     }
  95.  
  96.     public class DrawableCircle : IDrawableObject
  97.     {
  98.         #region Private Members
  99.         private Point m_location;
  100.         private int m_radius;
  101.         private Color m_drawColor;
  102.         #endregion
  103.  
  104.         #region Public Properties
  105.         public Color Color
  106.         {
  107.             get { return m_drawColor; }
  108.             set { m_drawColor = value; }
  109.         }
  110.  
  111.         public Point Location
  112.         {
  113.             get { return m_location; }
  114.             set { m_location = value; }
  115.         }
  116.  
  117.         public int X
  118.         {
  119.             get { return m_location.X; }
  120.             set { m_location.X = value; }
  121.         }
  122.  
  123.         public int Y
  124.         {
  125.             get { return m_location.Y; }
  126.             set { m_location.Y = value; }
  127.         }
  128.  
  129.         public int Radius
  130.         {
  131.             get { return m_radius; }
  132.             set { m_radius = value; }
  133.         }
  134.         #endregion
  135.  
  136.         #region Constructors
  137.         public DrawableCircle() { }
  138.  
  139.         public DrawableCircle(Point location, int radius)
  140.             : this()
  141.         {
  142.             m_location = location;
  143.             m_radius = radius;
  144.         }
  145.  
  146.         public DrawableCircle(int x, int y, int radius)
  147.             : this(new Point(x, y), radius)
  148.         {
  149.         }
  150.  
  151.         public DrawableCircle(Point location, int radius, Color color)
  152.             : this(location, radius)
  153.         {
  154.             m_drawColor = color;
  155.         }
  156.  
  157.         public DrawableCircle(int x, int y, int radius, Color color)
  158.             : this(new Point(x, y), radius, color)
  159.         {
  160.         }
  161.         #endregion
  162.  
  163.         #region IDrawableObject
  164.         public void Draw(Graphics g)
  165.         {
  166.             if (g != null)
  167.             {
  168.                 g.DrawEllipse(new Pen(m_drawColor), m_location.X - m_radius, m_location.Y - m_radius, m_radius * 2, m_radius * 2);
  169.             }
  170.         }
  171.         #endregion
  172.     }
Lets start with our window size as 320x200 ('cause I'm a QBasic nostalgic :D). We can also set up a circle to be drawn in the center of the form with a rectangle on top of it. We can create a list of IDrawableObjects on our form and we might as well go ahead and override the OnPaint event to draw them.

Expand|Select|Wrap|Line Numbers
  1.     public partial class Form1 : Form
  2.     {
  3.         private List<IDrawableObject> m_drawList = new List<IDrawableObject>();
  4.         private Size m_baseSize;
  5.  
  6.         public Form1()
  7.         {
  8.             InitializeComponent();
  9.  
  10.             m_baseSize = new Size(320, 200);
  11.             this.Size = new Size(m_baseSize.Width, m_baseSize.Height);
  12.  
  13.             DrawableCircle c = new DrawableCircle(m_baseSize.Width / 2, m_baseSize.Height / 2, 50, Color.Red);
  14.             DrawableRectangle r = new DrawableRectangle(c.X - 50, c.Y - 100, 100, 50, Color.Blue);
  15.  
  16.             m_drawList.Add(c);
  17.             m_drawList.Add(r);
  18.         }
  19.  
  20.         protected override void OnPaint(PaintEventArgs e)
  21.         {
  22.             base.OnPaint(e);
  23.  
  24.             foreach (IDrawableObject obj in m_drawList)
  25.             {
  26.                 obj.Draw(e.Graphics);
  27.             }
  28.         }
  29.     }
Ok, so how do we scale these objects so that if we switch to a different resolution (ie, different form size), things will show up correctly? Well, we're going to need to calculate a scale, then we're going to have to use that scale to change our drawing. Lets start by calculating that scale... we can do this with a point, but lets use a PointF object and round later to maintain accuracy. Lets add a private member to our form...

Expand|Select|Wrap|Line Numbers
  1. private PointF m_scale;
Getting the scale is as simple as generating the ratio of the current object canvas size to the base canvas size. We can create a method to do this and put it in the Form class.

Expand|Select|Wrap|Line Numbers
  1.         private PointF CalculateScale(float baseX, float baseY, float newX, float newY)
  2.         {
  3.             return new PointF(newX / baseX, newY / baseY);
  4.         }
Now in the constructor, we can calculate our scale...

Expand|Select|Wrap|Line Numbers
  1. m_scale = CalculateScale((float)m_baseSize.Width, (float)m_baseSize.Height, this.Size.Width, this.Size.Height);
Obviously, if we run this it's going to generate a scale of X=1 and Y=1, so lets change the code in our constructor that sets the size of the form to double it, just by multiplying the base size by 2.

Expand|Select|Wrap|Line Numbers
  1. this.Size = new Size(m_baseSize.Width * 2, m_baseSize.Height * 2);
Right, so now if we run the code we'll see our objects are no longer in the center, and that we're generating a scale of 2. We need to update our interface to provide a new drawing method, DrawScaled and we can implement those on our objects to take advantage of the scale.

Expand|Select|Wrap|Line Numbers
  1.     public interface IDrawableObject
  2.     {
  3.         void Draw(Graphics g);
  4.         void DrawScaled(Graphics g, PointF scale);
  5.     }
Lets do the circle first... we can update our DrawableCircle method with the following...

Expand|Select|Wrap|Line Numbers
  1.         public void DrawScaled(Graphics g, PointF scale)
  2.         {
  3.             if (g != null)
  4.             {
  5.                 g.DrawEllipse
  6.                 (
  7.                     new Pen(m_drawColor),
  8.                     (m_location.X - m_radius) * scale.X,
  9.                     (m_location.Y - m_radius) * scale.Y,
  10.                     (m_radius * 2) * scale.X,
  11.                     (m_radius * 2) * scale.Y
  12.                 );
  13.             }
  14.         }
All we need to do is multiply each coordinate by the scale to move it into the correct location. Now if we run it, we'll get a bigger circle that's back to being centered on the form. Of course, now our rectangle needs to be updated as well, but that's easy enough... we do the same thing.

Expand|Select|Wrap|Line Numbers
  1.         public void DrawScaled(Graphics g, PointF scale)
  2.         {
  3.             if (g != null)
  4.             {
  5.                 g.DrawRectangle
  6.                 (
  7.                     new Pen(m_drawColor),
  8.                     m_location.X * scale.X,
  9.                     m_location.Y * scale.Y,
  10.                     m_size.Width * scale.X,
  11.                     m_size.Height * scale.Y
  12.                 );
  13.             }
  14.         }
Now if we run the code, everything is back to where it should be. We can change the form's size to an arbitrary number now and we'll still get a circle drawing in the center of the form with a rectangle sitting on top of it.

To demonstrate this, lets add an event listener to the resize event on the form. All we'll need to do is recalculate the scale and invalidate the form to force a repaint.

In the constructor...
Expand|Select|Wrap|Line Numbers
  1. this.Resize += new EventHandler(Form1_Resize);
Then implement the event handler...
Expand|Select|Wrap|Line Numbers
  1.         void Form1_Resize(object sender, EventArgs e)
  2.         {
  3.             m_scale = CalculateScale(m_baseSize.Width, m_baseSize.Height, this.Width, this.Height);
  4.             this.Invalidate();
  5.         }
You can apply this scaling principle to anything you draw on your form. Note that what I've done here doesn't respect aspect ratios, it calculates the scale directly. If you wanted to maintain an aspect ratio you'd need to do a little more calculation to get the correct scaling factors, but the code wouldn't look too much differently. Really, it's a simple matter of finding which dimension, width or height, is larger and locking the scale to the smaller amount. This forces scale to be the same in both width and height and using the smaller scale factor ensures you're not going to scale off the screen.

Here is the complete code for this, I hope folks have found it useful :)

Expand|Select|Wrap|Line Numbers
  1.     public partial class Form1 : Form
  2.     {
  3.         private List<IDrawableObject> m_drawList = new List<IDrawableObject>();
  4.         private PointF m_scale;
  5.         private Size m_baseSize;
  6.  
  7.         public Form1()
  8.         {
  9.             InitializeComponent();
  10.  
  11.             m_baseSize = new Size(320, 200);
  12.             this.Size = new Size(640, 480);
  13.  
  14.             this.Resize += new EventHandler(Form1_Resize);
  15.  
  16.             m_scale = CalculateScale((float)m_baseSize.Width, (float)m_baseSize.Height, this.Size.Width, this.Size.Height);
  17.  
  18.             DrawableCircle c = new DrawableCircle(m_baseSize.Width / 2, m_baseSize.Height / 2, 50, Color.Red);
  19.             DrawableRectangle r = new DrawableRectangle(c.X - 50, c.Y - 100, 100, 50, Color.Blue);
  20.  
  21.             m_drawList.Add(c);
  22.             m_drawList.Add(r);
  23.         }
  24.  
  25.         void Form1_Resize(object sender, EventArgs e)
  26.         {
  27.             m_scale = CalculateScale(m_baseSize.Width, m_baseSize.Height, this.Width, this.Height);
  28.             this.Invalidate();
  29.         }
  30.  
  31.         private PointF CalculateScale(float baseX, float baseY, float newX, float newY)
  32.         {
  33.             return new PointF(newX / baseX, newY / baseY);
  34.         }
  35.  
  36.         protected override void OnPaint(PaintEventArgs e)
  37.         {
  38.             base.OnPaint(e);
  39.  
  40.             foreach (IDrawableObject obj in m_drawList)
  41.             {
  42.                 obj.DrawScaled(e.Graphics, m_scale);
  43.             }
  44.         }
  45.     }
  46.  
  47.     #region Drawable Objects
  48.     public interface IDrawableObject
  49.     {
  50.         void Draw(Graphics g);
  51.         void DrawScaled(Graphics g, PointF scale);
  52.     }
  53.  
  54.     public class DrawableRectangle : IDrawableObject
  55.     {
  56.         #region Private Members
  57.         private Point m_location = new Point();
  58.         private Size m_size = new Size();
  59.         private Color m_drawColor;
  60.         #endregion
  61.  
  62.         #region Public Properties
  63.         public Color Color
  64.         {
  65.             get { return m_drawColor; }
  66.             set { m_drawColor = value; }
  67.         }
  68.  
  69.         public Point Location
  70.         {
  71.             get { return m_location; }
  72.             set { m_location = value; }
  73.         }
  74.  
  75.         public Size Size
  76.         {
  77.             get { return m_size; }
  78.             set { m_size = value; }
  79.         }
  80.  
  81.         public int X
  82.         {
  83.             get { return m_location.X; }
  84.             set { m_location.X = value; }
  85.         }
  86.  
  87.         public int Y
  88.         {
  89.             get { return m_location.Y; }
  90.             set { m_location.Y = value; }
  91.         }
  92.  
  93.         public int Width
  94.         {
  95.             get { return m_size.Width; }
  96.             set { m_size.Height = value; }
  97.         }
  98.  
  99.         public int Height
  100.         {
  101.             get { return m_size.Height; }
  102.             set { m_size.Height = value; }
  103.         }
  104.         #endregion
  105.  
  106.         #region Constructors
  107.         public DrawableRectangle() { }
  108.  
  109.         public DrawableRectangle(Point location, Size size)
  110.             : this()
  111.         {
  112.             m_location = location;
  113.             m_size = size;
  114.         }
  115.  
  116.         public DrawableRectangle(int x, int y, int width, int height)
  117.             : this(new Point(x, y), new Size(width, height))
  118.         {
  119.         }
  120.  
  121.         public DrawableRectangle(Point location, Size size, Color color)
  122.             : this(location, size)
  123.         {
  124.             m_drawColor = color;
  125.         }
  126.  
  127.         public DrawableRectangle(int x, int y, int width, int height, Color color)
  128.             : this(new Point(x, y), new Size(width, height), color)
  129.         {
  130.         }
  131.         #endregion
  132.  
  133.         #region IDrawableObject
  134.         public void Draw(Graphics g)
  135.         {
  136.             if (g != null)
  137.             {
  138.                 g.DrawRectangle(new Pen(m_drawColor), m_location.X, m_location.Y, m_size.Width, m_size.Height);
  139.             }
  140.         }
  141.  
  142.         public void DrawScaled(Graphics g, PointF scale)
  143.         {
  144.             if (g != null)
  145.             {
  146.                 g.DrawRectangle
  147.                 (
  148.                     new Pen(m_drawColor),
  149.                     m_location.X * scale.X,
  150.                     m_location.Y * scale.Y,
  151.                     m_size.Width * scale.X,
  152.                     m_size.Height * scale.Y
  153.                 );
  154.             }
  155.         }
  156.         #endregion
  157.     }
  158.  
  159.     public class DrawableCircle : IDrawableObject
  160.     {
  161.         #region Private Members
  162.         private Point m_location;
  163.         private int m_radius;
  164.         private Color m_drawColor;
  165.         #endregion
  166.  
  167.         #region Public Properties
  168.         public Color Color
  169.         {
  170.             get { return m_drawColor; }
  171.             set { m_drawColor = value; }
  172.         }
  173.  
  174.         public Point Location
  175.         {
  176.             get { return m_location; }
  177.             set { m_location = value; }
  178.         }
  179.  
  180.         public int X
  181.         {
  182.             get { return m_location.X; }
  183.             set { m_location.X = value; }
  184.         }
  185.  
  186.         public int Y
  187.         {
  188.             get { return m_location.Y; }
  189.             set { m_location.Y = value; }
  190.         }
  191.  
  192.         public int Radius
  193.         {
  194.             get { return m_radius; }
  195.             set { m_radius = value; }
  196.         }
  197.         #endregion
  198.  
  199.         #region Constructors
  200.         public DrawableCircle() { }
  201.  
  202.         public DrawableCircle(Point location, int radius)
  203.             : this()
  204.         {
  205.             m_location = location;
  206.             m_radius = radius;
  207.         }
  208.  
  209.         public DrawableCircle(int x, int y, int radius)
  210.             : this(new Point(x, y), radius)
  211.         {
  212.         }
  213.  
  214.         public DrawableCircle(Point location, int radius, Color color)
  215.             : this(location, radius)
  216.         {
  217.             m_drawColor = color;
  218.         }
  219.  
  220.         public DrawableCircle(int x, int y, int radius, Color color)
  221.             : this(new Point(x, y), radius, color)
  222.         {
  223.         }
  224.         #endregion
  225.  
  226.         #region IDrawableObject
  227.         public void Draw(Graphics g)
  228.         {
  229.             if (g != null)
  230.             {
  231.                 g.DrawEllipse(new Pen(m_drawColor), m_location.X - m_radius, m_location.Y - m_radius, m_radius * 2, m_radius * 2);
  232.             }
  233.         }
  234.  
  235.         public void DrawScaled(Graphics g, PointF scale)
  236.         {
  237.             if (g != null)
  238.             {
  239.                 g.DrawEllipse
  240.                 (
  241.                     new Pen(m_drawColor),
  242.                     (m_location.X - m_radius) * scale.X,
  243.                     (m_location.Y - m_radius) * scale.Y,
  244.                     (m_radius * 2) * scale.X,
  245.                     (m_radius * 2) * scale.Y
  246.                 );
  247.             }
  248.         }
  249.         #endregion
  250.     }
  251.     #endregion
If you find any issues, please feel free to let me know. I don't pretend to be incapable of mucking things up from time to time ;)
Feb 28 '11 #1
0 6163

Sign in to post your reply or Sign up for a free account.

Similar topics

1
by: Ron Crump | last post by:
Hi, I have a small javascript (scalegr.js) that re-scales a sequence of images on a page to be less than the frame width, thus: function scalegr(a) { var imgs = document.images; var fw =...
9
by: M O J O | last post by:
Hi, I have my windows-display-font-size set to "Large fonts" and I've created a new project with two forms. Both of them are 600 width and 400 height. One have autoscale=true and the other is...
3
by: | last post by:
Hi! How to extand and access the internat Office object structure. Is there any Dev Kit for Microsoft Partners that in not available to "normal" users? Something like NOKIA back entrence!? ...
1
by: James Dean | last post by:
I have tried a few different methods for scaling an image down but all seem a bit on the slow side. Is there any way i can make this scaling faster....... Here is how i do it now. To display my...
5
by: Me | last post by:
I am about to roll out a vb.net 2003 app for screens using 1024x768. However some users will only have 800x600. Is there a quick method of scaling down the screens already written (e.g....
3
by: Larry Serflaten | last post by:
I am taking a 256 color bitmap from a file and scaling it up X 16 to a 32bppPARGB bitmap in memory. I copy that image to the screen. After scaling, the edges of all the lines and colors are...
17
by: IanIpp | last post by:
We have a 3 month old quad processor/dual core server running SQL Server 2005 and already it is getting close to hitting the CPU wall. An 8 way CPU box is prohibitively expensive and out of the...
3
by: illegal.prime | last post by:
Hi all, I'm looking for the right way to create a checkered background in a PictureBox. I will be applying various images to this PictureBox and I want to to see the checkered background based on...
1
by: Etayki | last post by:
Hi! I am building a website that has two frames with a vertical divide. Each frame displays a page from a different websites (such that the site visitor can view two websites on my one web...
11
by: Neil | last post by:
Does anyone know of an add-on or other that can be implemented into an MDB front end to allow its forms and reports to look the same at different screen resolutions?
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.