PictureBox 缩放





4.00/5 (9投票s)
本文将向您展示如何创建两个 PictureBox 控件,其中一个控件充当另一个控件的放大镜。
引言
不久前,我需要在同一个窗体上放置两个 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();
}
该方法首先根据当前缩放因子计算我们要捕获的图像部分的尺寸。然后创建一个临时位图,并将图像的该部分复制到其中。较小的 PictureBox
的 SizeMode
属性设置为 SizeMode.StretchImage
以模拟缩放。缩放因子越大,复制的部分就越小,因此拉伸后的图像看起来会越放大。
最后,在放大的图像上绘制一个十字准星,以复制原始图像中鼠标光标的位置。
备注
为了获得更好的功能,我需要微调一些设置和属性。为了消除缩放图像中的一些闪烁,我将窗体的 DoubleBuffered
属性设置为 true
。
这两个 PictureBox
控件都是正方形的,即它们的宽度和高度相同。如果您想更改这一点,则需要调整部分代码,特别是上面显示的方法。此外,我选择 120 作为较小 PictureBox
控件的宽度和高度是有原因的:120 可以被 2、3、4、5 和 6 整除。这正好是我演示项目中缩放因子的可能值。这确保了十字准星光标的正确放置,该光标基于使用除法的计算。带有舍入误差的除法可能会导致十字准星的定位略有偏差。