PictureBox Zoom





4.00/5 (9 votes)
This article will show you how to create two pictureboxes, one of them acting as a magnifier for the other one.
Introduction
Some time ago, I needed to have two picturebox controls on one form. One larger picturebox shows an image, while the other zooms a portion of the same image, depending on the position of the cursor. I could not find a ready-made solution or example source code for it, so I struggled a bit and got it to work properly, without flickering or lagging. I hope some of you can use this or find it helpful.
The demo project
The downloadable demo project contains all the source code needed, and I suggest you download it first. This article is not a step-by-step instruction, but rather focuses on the core parts to achieve the goal.
Definition of the problems
There were a couple of problems I had to solve to get to the solution.
- How to load an image into a fixed size
PictureBox
control, without changing the proportions of the original image. - How to center this resized image in the
PictureBox
control. - How to copy a part of the image to the second
PictureBox
control and zoom it in using a variable zoom factor.
Solutions
The first problem I encountered was to load an image of any size into the PictureBox
control, without changing the dimensions of the PictureBox
control and without changing the proportions of the image.
This might seem straightforward at first sight, by setting the SizeMode
property of the main PictureBox
control to SizeMode.CenterImage
, but that creates a more complex new problem. The image will be displayed the way I would like it to, but that also means I have to calculate the exact position of the mouse cursor relative to the image, each time the mouse moves over the image. I wanted the mouse position to match the image position, even if the mouse is outside of the edge of the image.
To solve this, I decided to copy the loaded image to a temporary bitmap which is the same size as the PictureBox
control, filling any area that is not covered by the image with a given color. The ResizeAndDisplayImage()
method below shows you how I achieved it.
private void ResizeAndDisplayImage()
{
// Set the backcolor of the pictureboxes
picImage.BackColor = _BackColor;
picZoom.BackColor = _BackColor;
// If _OriginalImage is null, then return. This situation can occur
// when a new backcolor is selected without an image loaded.
if (_OriginalImage == null)
return;
// sourceWidth and sourceHeight store
// the original image's width and height
// targetWidth and targetHeight are calculated
// to fit into the picImage picturebox.
int sourceWidth = _OriginalImage.Width;
int sourceHeight = _OriginalImage.Height;
int targetWidth;
int targetHeight;
double ratio;
// Calculate targetWidth and targetHeight, so that the image will fit into
// the picImage picturebox without changing the proportions of the image.
if (sourceWidth > sourceHeight)
{
// Set the new width
targetWidth = picImage.Width;
// Calculate the ratio of the new width against the original width
ratio = (double)targetWidth / sourceWidth;
// Calculate a new height that is in proportion with the original image
targetHeight = (int)(ratio * sourceHeight);
}
else if (sourceWidth < sourceHeight)
{
// Set the new height
targetHeight = picImage.Height;
// Calculate the ratio of the new height against the original height
ratio = (double)targetHeight / sourceHeight;
// Calculate a new width that is in proportion with the original image
targetWidth = (int)(ratio * sourceWidth);
}
else
{
// In this case, the image is square and resizing is easy
targetHeight = picImage.Height;
targetWidth = picImage.Width;
}
// Calculate the targetTop and targetLeft values, to center the image
// horizontally or vertically if needed
int targetTop = (picImage.Height - targetHeight) / 2;
int targetLeft = (picImage.Width - targetWidth) / 2;
// Create a new temporary bitmap to resize the original image
// The size of this bitmap is the size of the picImage picturebox.
Bitmap tempBitmap = new Bitmap(picImage.Width, picImage.Height,
PixelFormat.Format24bppRgb);
// Set the resolution of the bitmap to match the original resolution.
tempBitmap.SetResolution(_OriginalImage.HorizontalResolution,
_OriginalImage.VerticalResolution);
// Create a Graphics object to further edit the temporary bitmap
Graphics bmGraphics = Graphics.FromImage(tempBitmap);
// First clear the image with the current backcolor
bmGraphics.Clear(_BackColor);
// Set the interpolationmode since we are resizing an image here
bmGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Draw the original image on the temporary bitmap, resizing it using
// the calculated values of targetWidth and targetHeight.
bmGraphics.DrawImage(_OriginalImage,
new Rectangle(targetLeft, targetTop, targetWidth, targetHeight),
new Rectangle(0, 0, sourceWidth, sourceHeight),
GraphicsUnit.Pixel);
// Dispose of the bmGraphics object
bmGraphics.Dispose();
// Set the image of the picImage picturebox to the temporary bitmap
picImage.Image = tempBitmap;
}
This method is called after the user selects an image to load from file. It first resizes the image using the maximum size that can be fit inside the PictureBox
control, and fills the areas that are not covered by the image with the selected background color.
The method then calculates a top and left position to center the image, and copies it to a temporary bitmap, which is used as the image in the PictureBox
control. The image is now shown in the PictureBox
, centered, and resized to fit.
The next problem was to capture a part of the image when the mouse moves over it, and zoom it to display it in the other smaller PictureBox
.
The method UpdateZoomedImage(MouseEventArgs e)
, which is called when the mouse moves over the larger PictureBox
control, handles this problem.
private void UpdateZoomedImage(MouseEventArgs e)
{
// Calculate the width and height of the portion of the image we want
// to show in the picZoom picturebox. This value changes when the zoom
// factor is changed.
int zoomWidth = picZoom.Width / _ZoomFactor;
int zoomHeight = picZoom.Height / _ZoomFactor;
// Calculate the horizontal and vertical midpoints for the crosshair
// cursor and correct centering of the new image
int halfWidth = zoomWidth / 2;
int halfHeight = zoomHeight / 2;
// Create a new temporary bitmap to fit inside the picZoom picturebox
Bitmap tempBitmap = new Bitmap(zoomWidth, zoomHeight,
PixelFormat.Format24bppRgb);
// Create a temporary Graphics object to work on the bitmap
Graphics bmGraphics = Graphics.FromImage(tempBitmap);
// Clear the bitmap with the selected backcolor
bmGraphics.Clear(_BackColor);
// Set the interpolation mode
bmGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Draw the portion of the main image onto the bitmap
// The target rectangle is already known now.
// Here the mouse position of the cursor on the main image is used to
// cut out a portion of the main image.
bmGraphics.DrawImage(picImage.Image,
new Rectangle(0, 0, zoomWidth, zoomHeight),
new Rectangle(e.X - halfWidth, e.Y - halfHeight,
zoomWidth, zoomHeight), GraphicsUnit.Pixel);
// Draw the bitmap on the picZoom picturebox
picZoom.Image = tempBitmap;
// Draw a crosshair on the bitmap to simulate the cursor position
bmGraphics.DrawLine(Pens.Black, halfWidth + 1,
halfHeight - 4, halfWidth + 1, halfHeight - 1);
bmGraphics.DrawLine(Pens.Black, halfWidth + 1, halfHeight + 6,
halfWidth + 1, halfHeight + 3);
bmGraphics.DrawLine(Pens.Black, halfWidth - 4, halfHeight + 1,
halfWidth - 1, halfHeight + 1);
bmGraphics.DrawLine(Pens.Black, halfWidth + 6, halfHeight + 1,
halfWidth + 3, halfHeight + 1);
// Dispose of the Graphics object
bmGraphics.Dispose();
// Refresh the picZoom picturebox to reflect the changes
picZoom.Refresh();
}
This method first calculates the size of the portion of the image we want to capture, depending on the current zoom factor. It then creates a temporary bitmap, and copies the portion of the image to it. The SizeMode
property of the smaller PictureBox
is set to SizeMode.StretchImage
to simulate zooming. The larger the zoom factor, the smaller the portion copied, and therefore, the more zoomed in a stretched image will look.
Finally, a crosshair is drawn on the zoomed image to copy the mouse cursor position in the original image.
Remarks
For better functionality, I had to fine tune some settings and properties. To remove some flickering in the zoomed image, I set the DoubleBuffered
property of the form to true
.
Both of the PictureBox
controls are square, i.e., they have the same width and height. If you want to change this, you will have to adjust some of the code, particularly the methods shown above. Also, I chose a width and height of 120 for the smaller PictureBox
control for a reason: 120 is divisible by 2, 3, 4, 5, and 6. This happens to be the possible values of the zoom factor in my demo project. This ensures correct positioning of the crosshair cursor, which is based on a calculation that uses division. A division with round-off errors might position the crosshair just off the correct position.