Introduction
There are several articles about panning and zooming, but to do both of these at the same time can be more useful to the user of your program. But, this does take a little getting used to since you can pan your image too much if you are not used to it.
Background
This is not a new idea, CAD programs use it all the time. But, I don't usually see this feature in imaging software, and since it can be useful, I decided to write one.
Playing With the Code
Try a click drag, to pan the image, and use the mouse wheel to zoom in and out.
Then, try zooming in to a point of interest like an eye or something, and see how the pan-zoom works. It will take some getting used to because you can easily over shoot your target.
Using the Code
Since it is not too long or complicated, I just posted the important parts below. Some things to note are I use buffered graphics for this demo, and I didn't go through extensive error checking situations. I did test this and tried to block some common bugs.
Think of an image on a really big piece of paper, this is our world coordinates. Then, take a window that looks at the world. You can move the window left or right to see different parts of the world. You can move the window forwards or backwards (zooming) to adjust how much of the world you can see. This is the basic concept behind the world coordinates and the viewport coordinates below.
public partial class Form1 : Form
{
public class overRidePanel : Panel
{
protected override void OnPaintBackground(PaintEventArgs pevent) { }
}
Bitmap bitmap;
BufferedGraphicsContext currentContext;
BufferedGraphics myBuffer;
PointF viewPortCenter;
float Zoom = 1.0f;
bool draging = false;
Point lastMouse;
public Form1()
{
InitializeComponent();
currentContext = BufferedGraphicsManager.Current;
setup(false);
}
private void setup(bool resetViewport)
{
if (myBuffer != null)
myBuffer.Dispose();
myBuffer = currentContext.Allocate(this.panel1.CreateGraphics(),
this.panel1.DisplayRectangle);
if (bitmap != null)
{
if (resetViewport)
SetViewPort(new RectangleF(0, 0, bitmap.Width, bitmap.Height));
}
this.panel1.Focus();
this.panel1.Invalidate();
}
private void SetViewPort(RectangleF worldCords)
{
if (worldCords.Height > worldCords.Width)
{
this.Zoom = worldCords.Width / bitmap.Width;
}
else
this.Zoom = worldCords.Height / bitmap.Height;
viewPortCenter = new PointF(worldCords.X +(worldCords.Width / 2.0f),
worldCords.Y + (worldCords.Height / 2.0f));
this.toolStripStatusLabel1.Text = "Zoom: " +
((int)(this.Zoom*100)).ToString()+"%";
}
private void SetViewPort(Rectangle screenCords)
{
}
private void PaintImage()
{
if (bitmap != null)
{
float widthZoomed = panel1.Width / Zoom;
float heigthZoomed = panel1.Height / Zoom;
if (widthZoomed > 30000.0f)
{
Zoom = panel1.Width / 30000.0f;
widthZoomed = 30000.0f;
}
if (heigthZoomed > 30000.0f)
{
Zoom = panel1.Height / 30000.0f;
heigthZoomed = 30000.0f;
}
if (widthZoomed < 2.0f)
{
Zoom = panel1.Width / 2.0f;
widthZoomed = 2.0f;
}
if (heigthZoomed < 2.0f)
{
Zoom = panel1.Height / 2.0f;
heigthZoomed = 2.0f;
}
float wz2 = widthZoomed / 2.0f;
float hz2 = heigthZoomed / 2.0f;
Rectangle drawRect = new Rectangle(
(int)(viewPortCenter.X - wz2),
(int)(viewPortCenter.Y - hz2),
(int)(widthZoomed),
(int)(heigthZoomed));
myBuffer.Graphics.Clear(Color.White);
myBuffer.Graphics.DrawImage(bitmap,
this.panel1.DisplayRectangle, drawRect, GraphicsUnit.Pixel);
myBuffer.Render(this.panel1.CreateGraphics());
this.toolStripStatusLabel1.Text = "Zoom: " +
((int)(this.Zoom * 100)).ToString() + "%";
}
}
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
if (this.openFileDialog1.ShowDialog() == DialogResult.OK)
{
bitmap = (Bitmap)Bitmap.FromFile(openFileDialog1.FileName);
setup(true);
}
}
private void Form1_Resize(object sender, EventArgs e)
{
setup(false);
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
PaintImage();
}
private void panel1_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
{
Zoom += Zoom * (e.Delta / 1200.0f);
if (e.Delta > 0)
viewPortCenter = new PointF(viewPortCenter.X +
((e.X - (panel1.Width / 2)) /(2* Zoom)), viewPortCenter.Y +
((e.Y - (panel1.Height/2)) / (2*Zoom)));
this.panel1.Invalidate();
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
draging = true;
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (draging)
{
viewPortCenter = new PointF(viewPortCenter.X +
((lastMouse.X - e.X)/Zoom), viewPortCenter.Y + ((lastMouse.Y- e.Y)/Zoom));
panel1.Invalidate();
}
lastMouse = e.Location;
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
draging = false;
}
}
Points of Interest
A couple of things. First, you will notice that zooming in can be blocky or clunky at times; this is because the pan is moving large amounts. I reduced this by dividing by 2, but it is noticeable. To fix this and make it more smooth, you can either make the maximum pan amount a constant or some proportion of the zoom level. The method posted above accomplished the goal, so I did not pursue the finer details. Note that I create a class whose entire existence and purpose is to override the OnPaintBackground
of the standard Panel
object. This is done to remove the flicker created by a normal Panel
doing the background drawing.
History
This is the second program like this. Since this version performs better, I posted it.