65.9K
CodeProject 正在变化。 阅读更多。
Home

PictureBox 缩放

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (9投票s)

2007年10月30日

CPOL

4分钟阅读

viewsIcon

225973

downloadIcon

18076

本文将向您展示如何创建两个 PictureBox 控件,其中一个控件充当另一个控件的放大镜。

Screenshot - ScreenShot.png

引言

不久前,我需要在同一个窗体上放置两个 PictureBox 控件。一个较大的 PictureBox 显示图像,而另一个则根据光标的位置放大同一图像的某个部分。我找不到现成的解决方案或示例源代码,因此我为此付出了一些努力并成功实现了功能,而且没有闪烁或延迟。我希望这能对您有所帮助。

演示项目

可下载的演示项目包含了所有必需的源代码,我建议您首先下载它。本文不是一个分步教程,而是侧重于实现目标的核心部分。

问题定义

为了找到解决方案,我需要解决几个问题。

  • 如何在固定大小的 PictureBox 控件中加载图像,而不改变原始图像的比例。
  • 如何将此调整大小的图像居中放置在 PictureBox 控件中。
  • 如何将图像的一部分复制到第二个 PictureBox 控件中使用可变缩放因子将其放大。

解决方案

我遇到的第一个问题是如何将任意大小的图像加载到 PictureBox 控件中,而不改变 PictureBox 控件的尺寸,也不改变图像的比例。

这乍看之下似乎很简单,只需将主 PictureBox 控件的 SizeMode 属性设置为 SizeMode.CenterImage 即可,但这会带来更复杂的新问题。图像将按照我想要的方式显示,但这也意味着我必须计算鼠标光标相对于图像的确切位置,每次鼠标移动到图像上方时都要计算。我希望鼠标位置能与图像位置匹配,即使鼠标在图像边缘之外。

为了解决这个问题,我决定将加载的图像复制到一个与 PictureBox 控件大小相同的临时位图,用指定颜色填充未被图像覆盖的区域。下面的 ResizeAndDisplayImage() 方法展示了我是如何实现的。

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;
}

该方法在用户从文件选择要加载的图像后被调用。它首先使用可以适应 PictureBox 控件的最大尺寸来调整图像大小,并用选定的背景色填充图像未覆盖的区域。

然后,该方法计算用于居中图像的顶部和左侧位置,并将其复制到一个临时位图,该位图用作 PictureBox 控件中的图像。图像现在已在 PictureBox 中显示,居中且已调整大小以适应。

下一个问题是在鼠标移动到图像上方时捕获图像的一部分,并将其放大显示在另一个较小的 PictureBox 中。

当鼠标移动到较大的 PictureBox 控件上方时调用的 UpdateZoomedImage(MouseEventArgs e) 方法处理此问题。

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();
}

该方法首先根据当前缩放因子计算我们要捕获的图像部分的尺寸。然后创建一个临时位图,并将图像的该部分复制到其中。较小的 PictureBoxSizeMode 属性设置为 SizeMode.StretchImage 以模拟缩放。缩放因子越大,复制的部分就越小,因此拉伸后的图像看起来会越放大。

最后,在放大的图像上绘制一个十字准星,以复制原始图像中鼠标光标的位置。

备注

为了获得更好的功能,我需要微调一些设置和属性。为了消除缩放图像中的一些闪烁,我将窗体的 DoubleBuffered 属性设置为 true

这两个 PictureBox 控件都是正方形的,即它们的宽度和高度相同。如果您想更改这一点,则需要调整部分代码,特别是上面显示的方法。此外,我选择 120 作为较小 PictureBox 控件的宽度和高度是有原因的:120 可以被 2、3、4、5 和 6 整除。这正好是我演示项目中缩放因子的可能值。这确保了十字准星光标的正确放置,该光标基于使用除法的计算。带有舍入误差的除法可能会导致十字准星的定位略有偏差。

© . All rights reserved.