Introduction
I have found quite a few articles about zooming into a fixed point. I have also tried many solutions, but not all of them worked as they should. Many of them resized the PictureFrame so that it eventually would overlap other parts of the screen, others could not hold focus, and some others lost focus when panning the image. This solution does work. When panning, the image doesn't move when you grab hold of the image, and when zooming, it does keep the same zoom point at all zoom levels.
Background
This image viewer was a small part of a self-encrypting, self-contained image viewer, where I needed a simple image viewer that could zoom at the point I was pointing at.
Using the code
The code is quite simple. The big problem was to hold track of the offset when zooming so I could move the image to the correct position to hold the focus point.
A few event handlers to catch some mouse events:
this.pictureBox.MouseDown +=
new System.Windows.Forms.MouseEventHandler(this.imageBox_MouseDown);
this.pictureBox.MouseMove +=
new System.Windows.Forms.MouseEventHandler(this.pictureBox_MouseMove);
this.pictureBox.MouseUp +=
new System.Windows.Forms.MouseEventHandler(this.imageBox_MouseUp);
ImageZoomMainForm
initializes the windows, and sets the initial zoom factor. By default, it zooms the image so it is as wide as the view.
First, I declare some global variables to store the image and keep track of the zoom and the current offset of the image.
Image img;
Point mouseDown;
int startx = 0; int starty = 0;
int imgx = 0; int imgy = 0;
bool mousepressed = false; float zoom = 1;
ImageZoomMainForm
initializes the windows, opens the image, and sets the initial zoom factor. By default, it zooms the image so it is as wide as the view. I have to take into account that the screen and the image can have different resolutions. It did take me quite a long time to figure out why my fully zoomed windows wasn't zoomed, until I realised that the screen had a 96 resolution and the image's was 300.
public ImageZoomMainForm()
{
InitializeComponent();
string imagefilename = @"..\..\test.tif";
img = Image.FromFile(imagefilename);
Graphics g = this.CreateGraphics();
zoom = ((float)pictureBox.Width / (float)img.Width) *
(img.HorizontalResolution / g.DpiX);
pictureBox.Paint += new PaintEventHandler(imageBox_Paint);
}
MouseMove
, MouseDown
, and MouseUp
take care of the panning. MouseDown
records the starting position, and MouseMove
then moves the image box accordingly. MouseUp
just sets mousepressed
to false
so that I know that the button has been released.
private void pictureBox_MouseMove(object sender, EventArgs e)
{
MouseEventArgs mouse = e as MouseEventArgs;
if (mouse.Button == MouseButtons.Left)
{
Point mousePosNow = mouse.Location;
int deltaX = mousePosNow.X - mouseDown.X;
int deltaY = mousePosNow.Y - mouseDown.Y;
imgx = (int)(startx + (deltaX / zoom));
imgy = (int)(starty + (deltaY / zoom));
pictureBox.Refresh();
}
}
private void imageBox_MouseDown(object sender, EventArgs e)
{
MouseEventArgs mouse = e as MouseEventArgs;
if (mouse.Button == MouseButtons.Left)
{
if (!mousepressed)
{
mousepressed = true;
mouseDown = mouse.Location;
startx = imgx;
starty = imgy;
}
}
}
private void imageBox_MouseUp(object sender, EventArgs e)
{
mousepressed = false;
}
OnMouseWheel
does the actual zooming. It calculates where in the image the mouse is pointing, calculates the new zoomfactor, make sa new calculation to figure out where the mouse will be positioned after the zoom, and then calculates where to move the image to hold the focus point. Paint
does the transform. It resizes the image and moves it to the correct location.
protected override void OnMouseWheel(MouseEventArgs e)
{
float oldzoom = zoom;
if (e.Delta > 0)
{
zoom += 0.1F;
}
else if (e.Delta < 0)
{
zoom = Math.Max(zoom - 0.1F, 0.01F);
}
MouseEventArgs mouse = e as MouseEventArgs;
Point mousePosNow = mouse.Location;
int x = mousePosNow.X - pictureBox.Location.X;
int y = mousePosNow.Y - pictureBox.Location.Y;
int oldimagex = (int)(x / oldzoom);
int oldimagey = (int)(y / oldzoom);
int newimagex = (int)(x / zoom);
int newimagey = (int)(y / zoom);
imgx = newimagex - oldimagex + imgx;
imgy = newimagey - oldimagey + imgy;
pictureBox.Refresh(); }
private void imageBox_Paint(object sender, PaintEventArgs e)
{
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.ScaleTransform(zoom, zoom);
e.Graphics.DrawImage(img, imgx, imgy);
}
ProcessCmdKey
overrules the default method. It makes it possible to move around in the image using the arrow keys and PgUp and PgDown. Arrow keys move 10%, PgUp and PgDown move 90% of the screen size.
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
const int WM_KEYDOWN = 0x100;
const int WM_SYSKEYDOWN = 0x104;
if ((msg.Msg == WM_KEYDOWN) || (msg.Msg == WM_SYSKEYDOWN))
{
switch (keyData)
{
case Keys.Right:
imgx -= (int)(pictureBox.Width * 0.1F / zoom);
pictureBox.Refresh();
break;
case Keys.Left:
imgx += (int)(pictureBox.Width * 0.1F / zoom);
pictureBox.Refresh();
break;
case Keys.Down:
imgy -= (int)(pictureBox.Height * 0.1F / zoom);
pictureBox.Refresh();
break;
case Keys.Up:
imgy += (int)(pictureBox.Height * 0.1F / zoom);
pictureBox.Refresh();
break;
case Keys.PageDown:
imgy -= (int)(pictureBox.Height * 0.90F / zoom);
pictureBox.Refresh();
break;
case Keys.PageUp:
imgy += (int)(pictureBox.Height * 0.90F / zoom);
pictureBox.Refresh();
break;
}
}
return base.ProcessCmdKey(ref msg, keyData);
}
}
History