当您使用该Image.FromFile(strFileName)
方法创建 时Image
,该方法会锁定文件,直到您释放Image
. 确切的原因在下面解释。这就是为什么您不能使用这种方法多次访问同一个图像文件的原因。
你可以改为:
SafeImageFromFile
以下是不锁定图像文件的自定义方法的可能实现:
Public Shared Function SafeImageFromFile(path As String) As Image
Using fs As New FileStream(path, FileMode.Open, FileAccess.Read)
Dim img = Image.FromStream(fs)
Return img
End using
End Function
或者
Public Shared Function SafeImageFromFile(path As String) As Image
Dim bytes = File.ReadAllBytes(path)
Using ms As New MemoryStream(bytes)
Dim img = Image.FromStream(ms)
Return img
End Using
End Function
用法
If strFileName.ToLower.EndsWith(".jpg") Then
Dim inImg As Image = SafeImageFromFile(strFileName)
Dim index as integer = DataGridView4.Rows.Add()
DataGridView4.Rows(index).Cells(0).Value = inImg
End If
重要的提示
在这里,我创建了FileStream
一个MemoryStream
usingUsing
语句来确保流被释放。它在我的系统上运行良好,而且似乎也适用于你,尽管MSDN 提到Image.FromStream(stream)方法:
您必须在图像的生命周期内保持流打开。
这句话的原因在这里解释一下:KB814675 Bitmap and Image constructor dependencies
GDI+,因此 System.Drawing 命名空间,可能会推迟原始图像位的解码,直到图像需要这些位。
此外,即使图像已被解码,GDI+ 也可能确定为大位图丢弃内存并稍后重新解码更有效。因此,GDI+ 必须在 Bitmap 或 Image 对象的生命周期内访问图像的源位。
为了保留对源位的访问,GDI+ 锁定任何源文件,并强制应用程序在位图或图像对象的生命周期内维护任何源流的生命周期。
所以知道上面的代码可能会GDIexceptions
因为使用Using
. 当您从文件中保存图像或在图像创建过程中可能会发生这种情况。从这个线程加载图像而不保持流打开和汉斯帕桑特的评论他们修复了gdiplus.dll的Vista版本中索引像素格式的几个问题。,它只会发生在 XP 上。
为避免这种情况,您需要保持流打开。方法是:
Public Shared Function SafeImageFromFile(path As String) As Image
Dim fs As New FileStream(path, FileMode.Open, FileAccess.Read)
Dim img = Image.FromStream(fs)
Return img
End Function
或者
Public Shared Function SafeImageFromFile(path As String) As Image
Dim bytes = File.ReadAllBytes(path)
Dim ms = New MemoryStream(bytes)
Dim img = Image.FromStream(ms)
Return img
End Function
但是最后这些方法有一些缺点,例如不释放流(内存问题),并且它们违反了规则CA2000 Dispose objects before lost scope。
知识库文章提供了一些解决方法:
创建非索引图像
这种方法要求新图像采用非索引像素格式(每像素超过 8 位),即使原始图像采用索引格式。此解决方法使用 Graphics.DrawImage() 方法将图像复制到新的 Bitmap 对象:
- 从流、内存或文件构造原始位图。
- 创建一个相同大小的新位图,像素格式超过 8 位/像素 (BPP)。
- 使用 Graphics.FromImage() 方法获取第二个 Bitmap 的 Graphics 对象。
- 使用 Graphics.DrawImage() 将第一个位图绘制到第二个位图上。
- 使用 Graphics.Dispose() 处理 Graphics。
- 使用 Bitmap.Dispose() 处理第一个 Bitmap。
创建索引图像
此解决方法以索引格式创建 Bitmap 对象:
- 从流、内存或文件构造原始位图。
- 创建一个与第一个位图具有相同大小和像素格式的新位图。
- 使用 Bitmap.LockBits() 方法以原始像素格式锁定两个 Bitmap 对象的整个图像。
- 使用 Marshal.Copy 函数或其他内存复制函数将图像位从第一个 Bitmap 复制到第二个 Bitmap。
- 使用 Bitmap.UnlockBits() 方法解锁两个 Bitmap 对象。使用 Bitmap.Dispose() 处理第一个 Bitmap。
这是基于知识库文章和此答案https://stackoverflow.com/a/7972963/2387010的非索引图像创建的实现 您最好的选择是创建图像的像素完美副本-尽管 YMMV(带有某些类型的图像可能不止一帧,或者您可能还必须复制调色板数据。)但对于大多数图像,这有效:
Private Shared Function SafeImageFromFile(path As String) As Bitmap
Dim img As Bitmap = Nothing
Using fs As New FileStream(path, FileMode.Open, FileAccess.Read)
Using b As New Bitmap(fs)
img = New Bitmap(b.Width, b.Height, b.PixelFormat)
Using g As Graphics = Graphics.FromImage(img)
g.DrawImage(b, Point.Empty)
g.Flush()
End Using
End Using
End Using
Return img
End Function
有人指出,重要的是以FileStream
读取模式打开(FileAccess.Read
)。
是的,但是如果您不使用Using
语句并且不释放流,或者在多线程上下文中,它更有意义:FileAccess.Write
不合适,并且FileAccess.ReadWrite
不是必需的,但是使用FileAccess.Read
模式打开流不会阻止拥有如果另一个程序(或您在多线程上下文中的IO.Exception
程序)以另一种模式打开文件,而不是FileAccess.Read
.
如果您希望能够显示图像并同时能够将数据保存到文件中,由于您不使用这些方法锁定文件,因此您应该可以保存图像(删除/覆盖以前的文件)使用该Image.Save
方法。