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

平移和缩放非常大的图像

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (144投票s)

2006 年 9 月 28 日

Ms-PL

5分钟阅读

viewsIcon

817535

downloadIcon

16234

平滑地平移和缩放非常大的图像可能是一项挑战。这里有一个控件,附带源代码,演示了一种克服此挑战的方法,以及一些“额外”的图像处理功能。

注意:此代码最初是针对 .NET 2.0 编写的,然后手动转换为 .NET 1.1。

Sample image

引言

我最近写了一篇关于平移图像的简单方法的文章。对于中小尺寸的图像,代码效果很好。但是,在使用非常大的图像时,性能会显著下降。

那篇文章在一个面板中使用了 picture box,并利用面板的自动滚动功能来实现滚动。我收到了很多反馈,表明需要一个版本能够处理非常大的图像,并且仍然能够平滑地平移。我还收到了关于如何放大和缩小图像的想法。所以,我开始着手了。

我开发了一个控件,可以平滑地平移超大尺寸的图像,并提供缩放功能。我的测试对象是一个 49MB 的 GIF(7000 x 7000)。性能非常流畅。当然,该控件同样适用于小图像。控件在包含的示例项目中进行了演示。

这个自定义控件使用 picture box,也不继承自 picture box。也没有面板或任何“自动滚动”。这是一种非常不同且(在我看来)更好处理图像平移的方式。此示例的附加好处是能够缩放图像而无需调整 picture box 的大小(这可能会占用大量内存)。

工作原理

  1. 仅绘制当前可见的图像部分。
  2. 双缓冲可实现无闪烁的平移。
  3. GDI+ 会自动为我们缩放图像。

公共属性

  • 公共属性 PanButton() As System.Windows.Forms.MouseButtons
  • 公共属性 ZoomOnMouseWheel() As Boolean
  • 公共属性 ZoomFactor() As Double
  • 公共属性 Origin() As System.Drawing.Point

公共阴影

  • 公共阴影属性 Image() As System.Drawing.Image
  • 公共阴影属性 initialimage() As System.Drawing.Image

公共方法

  • 公共子 ShowActualSize()
  • 公共子 ResetImage()

使用该控件就像使用标准的 PictureBox 一样简单。首先,将控件拖放到窗体上,然后当您需要显示图像时,可以这样做:

Dim bmp As New Bitmap("Image.jpg")
Me.ImageViewer1.Image = bmp

别忘了更改文件名!

需要注意的是:如果您正在处理非常大的图像,则不应在设计器中预加载它们。这会严重导致项目臃肿,并可能导致“内存不足”问题。取而代之的是,应在运行时加载图像。

默认行为

  • 平移图像:将鼠标光标置于图像上,然后单击并按住左鼠标按钮。然后,在仍然按下按钮的情况下,只需移动鼠标。
  • 缩放:确保控件具有焦点(单击图像)。然后,使用鼠标滚轮进行放大和缩小。

自定义使用

您可以通过“PanButton”属性告诉控件使用哪个按钮进行平移。您可以将 ZoomOnMouseWheel 属性设置为 False 来关闭默认的缩放功能。

您可以手动设置缩放因子,以便实现自己的缩放功能(例如,使用滑块或按钮)。

您可以通过设置原点来以编程方式移动图像。原点属性获取或设置视图窗口左上角相对于原始图像的坐标。例如,如果您想查看大小为 5000 x 5000 的图像的右下角,而您的视图控件大小为 500 像素 x 500 像素,则可以将原点设置为 4500, 4500。当然,这假设您的缩放因子为 1(未放大或缩小)。

您可以捕获控件的绘制事件并覆盖您自己的图形。如果您需要相对于原始图像以精确的坐标进行绘制,只需注意考虑缩放因子。

滚动条

应广大用户要求,现已实现滚动条。

双缓冲

双缓冲是通过在构造函数中设置控件样式来实现的,如下所示:

Public Sub New()
     MyBase.New()
     'This call is required by the Windows Form Designer.
     InitializeComponent()
     'Add any initialization after the InitializeComponent() call
     Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
     Me.SetStyle(ControlStyles.DoubleBuffer, True)
End Sub

即时绘制?

嗯,算是吧。虽然我们在内存中有一个图像的副本,但我们只绘制当前可见的区域。

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
     e.Graphics.Clear(Me.BackColor)
     DrawImage(e.Graphics)
     MyBase.OnPaint(e)
End Sub

Protected Overrides Sub OnSizeChanged(ByVal e As EventArgs)
     DestRect = New System.Drawing.Rectangle(0, 0, _
                    ClientSize.Width, ClientSize.Height)
     MyBase.OnSizeChanged(e)
End Sub

Private Sub DrawImage(ByRef g As Graphics)
     If m_OriginalImage Is Nothing Then Exit Sub
     SrcRect = New System.Drawing.Rectangle(m_Origin.X, m_Origin.Y, _
                          ClientSize.Width / m_ZoomFactor, _
                          ClientSize.Height / m_ZoomFactor)
     g.DrawImage(m_OriginalImage, DestRect, SrcRect, GraphicsUnit.Pixel)
End Sub

请注意,我们在绘制时考虑了当前的缩放因子。通过使用 Graphics 对象的 DrawImage 方法,GDI 会将图像从源区域缩放到适合目标区域。

平移图像

平移图像并考虑缩放因子的代码如下:

Private Sub ImageViewer_MouseMove(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.MouseEventArgs) _
        Handles MyBase.MouseMove

     'Make sure we are panning on the correct mouse button
     If e.Button = m_MouseButtons Then
          Dim DeltaX As Integer = m_PanStartPoint.X - e.X
          Dim DeltaY As Integer = m_PanStartPoint.Y - e.Y

          'Set the origin of the new image
          m_Origin.X = m_Origin.X + (DeltaX / m_ZoomFactor)
          m_Origin.Y = m_Origin.Y + (DeltaY / m_ZoomFactor)

          'Make sure we don't go out of bounds
          If m_Origin.X < 0 Then m_Origin.X = 0
          If m_Origin.Y < 0 Then m_Origin.Y = 0

          If m_Origin.X > m_OriginalImage.Width - _
                         (ClientSize.Width / m_ZoomFactor) Then
               m_Origin.X = _m_OriginalImage.Width - _
                            (ClientSize.Width / m_ZoomFactor)
          End If
          If m_Origin.Y > m_OriginalImage.Height - _
                           (ClientSize.Height / m_ZoomFactor) Then
               m_Origin.Y = m_OriginalImage.Height - _
                           (ClientSize.Height / m_ZoomFactor)
          End If

          If m_Origin.X < 0 Then m_Origin.X = 0
          If m_Origin.Y < 0 Then m_Origin.Y = 0

          'reset the startpoints
          m_PanStartPoint.X = e.X
          m_PanStartPoint.Y = e.Y

          'Force a paint
          Me.Invalidate()
     End If
End Sub

结论

此示例项目中的许多概念都值得单独写文章介绍。因此,我没有详细介绍双缓冲是什么,也没有深入探讨 .NET 中 GDI+ 的复杂性。但是,我希望我已经充分涵盖了该控件如何工作以及如何使用它的基本知识。

请注意...

这绝非旨在提供一个完整的解决方案,也不是“生产就绪”的代码。此外,解决问题通常有多种方法;这只是一种。然而,希望这个示例能有所裨益。也许,这篇文章能为您提供一个更好的实现方法,或者一个扩展现有功能的想法。太好了!这正是我写它的目的。请随时留下反馈。告诉我您的情况。如果您有改进此示例或本文的想法,也请告诉我。

附注:别忘了投票!如果您还没有账号,请注册一个!

修订和错误修复...

  • 02/04/2007
    • 添加了滚动条功能
    • 修复了空图像 bug
    • 修复了内存泄漏
    • 实施了多项性能改进建议
    • 增加了反色功能
    • 增加了拉伸图像或设置为实际像素的功能
    • 删除了硬编码的图像文件,并在测试工具中添加了对话框
  • 02/06/2007
    • 添加了 .NET 1.1 版本
  • 30/10/2009
    • 删除了 .NET 1.1 的 zip 文件

待办事项

  1. Point 更改为 PointF,将 Rectangle 更改为 RectangleF,以便在放大到非常精细时进行更精细的平移和滚动
  2. 更新文章以剖析应用程序并解释其工作原理
  3. 更新文章中提供的代码

感谢您的耐心等待!

© . All rights reserved.