当您使用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
重要提示
在这里,我使用 Using 语句创建 FileStream 或 MemoryStream 以确保释放流。它在我的系统上运行良好,而且它似乎也适用于你,虽然 MSDN 提到了Image.FromStream(stream) 方法:
您必须在图像的生命周期内保持流打开。
这句话的原因在这里解释一下:KB814675 Bitmap and Image constructor dependencies
GDI+,因此 System.Drawing 命名空间,可能会推迟
解码原始图像位,直到图像需要这些位。
此外,即使图像已被解码,GDI+ 可能
确定丢弃大量内存更有效
位图并稍后重新解码。因此,GDI+ 必须有权访问
位图或图像生命周期中图像的源位
对象。
为了保留对源位的访问,GDI+ 锁定任何源文件,并且
强制应用程序维持任何源流的生命周期,对于
位图或图像对象的生命周期。
所以知道上面的代码可能会生成GDIexceptions,因为使用Using 释放流。当您从文件中保存图像或在图像创建过程中可能会发生这种情况。从这个线程 Loading an image from a stream without keeping the stream open 和 Hans Passant 的评论 他们修复了 Vista 版本的 gdiplus.dll 中索引像素格式的几个问题。,这只会在 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 losing scope。
知识库文章提供了一些解决方法:
创建非索引图像
此方法要求新图像位于非索引像素中
格式(每像素超过 8 位),即使原始图像在
一种索引格式。此解决方法使用 Graphics.DrawImage()
将图像复制到新的 Bitmap 对象的方法:
- 从流、内存或文件中构造原始位图。
- 创建一个相同大小的新位图,像素格式超过每像素 8 位 (BPP)。
- 使用 Graphics.FromImage() 方法获取第二个位图的 Graphics 对象。
- 使用 Graphics.DrawImage() 将第一个位图绘制到第二个位图上。
- 使用 Graphics.Dispose() 来处理 Graphics。
- 使用 Bitmap.Dispose() 处理第一个 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 的其他模式打开文件,则使用FileAccess.Read 模式打开流不会阻止拥有IO.Exception。
如果您希望能够显示图像并同时能够将数据保存到文件中,由于您不使用这些方法锁定文件,因此您应该能够使用Image.Save 方法保存图像(删除/覆盖以前的文件)。