Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

PictureBox Zoom

0.00/5 (No votes)
30 Oct 2007 2  
This article will show you how to create two pictureboxes, one of them acting as a magnifier for the other one.

Screenshot - ScreenShot.png

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here