由新位图(filePath)锁定的免费文件
Posted
技术标签:
【中文标题】由新位图(filePath)锁定的免费文件【英文标题】:Free file locked by new Bitmap(filePath) 【发布时间】:2011-06-15 19:01:16 【问题描述】:我有一个指向某个文件“A”的 PictureBox 的图像。在执行时,我想将 PictureBox 的图像更改为不同的“B”,但出现以下错误:
“在 mscorlib.dll 中发生了“System.IO.IOException”类型的第一次机会异常 附加信息:该进程无法访问文件“A”,因为它正被另一个进程使用。”
我将图像设置如下:
pbAvatar.Image = new Bitmap(filePath);
如何解锁第一个文件?
【问题讨论】:
【参考方案1】:我建议使用 PixelMap(在 NuGet 上可用) 或Github
非常易于使用,比 .NET 的标准位图快得多
PixelMap pixelMap = new PixelMap(bild);
pictureBox1.Image = pixelMap.GetBitmap();
【讨论】:
【参考方案2】:据我所知,这是 100% 安全的,因为生成的图像 100% 是在内存中创建的,没有任何链接资源,也没有在内存中留下任何打开的流。它的行为与任何其他 Bitmap
一样,它是从未指定任何输入源的构造函数创建的,并且与此处的其他一些答案不同,它保留了原始像素格式,这意味着它可以用于索引格式。
基于this answer,但有额外的修复并且没有外部库导入。
/// <summary>
/// Clones an image object to free it from any backing resources.
/// Code taken from http://***.com/a/3661892/ with some extra fixes.
/// </summary>
/// <param name="sourceImage">The image to clone</param>
/// <returns>The cloned image</returns>
public static Bitmap CloneImage(Bitmap sourceImage)
Rectangle rect = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height);
Bitmap targetImage = new Bitmap(rect.Width, rect.Height, sourceImage.PixelFormat);
targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
BitmapData sourceData = sourceImage.LockBits(rect, ImageLockMode.ReadOnly, sourceImage.PixelFormat);
BitmapData targetData = targetImage.LockBits(rect, ImageLockMode.WriteOnly, targetImage.PixelFormat);
Int32 actualDataWidth = ((Image.GetPixelFormatSize(sourceImage.PixelFormat) * rect.Width) + 7) / 8;
Int32 h = sourceImage.Height;
Int32 origStride = sourceData.Stride;
Boolean isFlipped = origStride < 0;
origStride = Math.Abs(origStride); // Fix for negative stride in BMP format.
Int32 targetStride = targetData.Stride;
Byte[] imageData = new Byte[actualDataWidth];
IntPtr sourcePos = sourceData.Scan0;
IntPtr destPos = targetData.Scan0;
// Copy line by line, skipping by stride but copying actual data width
for (Int32 y = 0; y < h; y++)
Marshal.Copy(sourcePos, imageData, 0, actualDataWidth);
Marshal.Copy(imageData, 0, destPos, actualDataWidth);
sourcePos = new IntPtr(sourcePos.ToInt64() + origStride);
destPos = new IntPtr(destPos.ToInt64() + targetStride);
targetImage.UnlockBits(targetData);
sourceImage.UnlockBits(sourceData);
// Fix for negative stride on BMP format.
if (isFlipped)
targetImage.RotateFlip(RotateFlipType.Rotate180FlipX);
// For indexed images, restore the palette. This is not linking to a referenced
// object in the original image; the getter of Palette creates a new object when called.
if ((sourceImage.PixelFormat & PixelFormat.Indexed) != 0)
targetImage.Palette = sourceImage.Palette;
// Restore DPI settings
targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
return targetImage;
要调用,只需使用:
/// <summary>Loads an image without locking the underlying file.</summary>
/// <param name="path">Path of the image to load</param>
/// <returns>The image</returns>
public static Bitmap LoadImageSafe(String path)
using (Bitmap sourceImage = new Bitmap(path))
return CloneImage(sourceImage);
或者,从字节:
/// <summary>Loads an image from bytes without leaving open a MemoryStream.</summary>
/// <param name="fileData">Byte array containing the image to load.</param>
/// <returns>The image</returns>
public static Bitmap LoadImageSafe(Byte[] fileData)
using (MemoryStream stream = new MemoryStream(fileData))
using (Bitmap sourceImage = new Bitmap(stream))
return CloneImage(sourceImage);
【讨论】:
请注意,如果是动画 gif,我不知道该怎么做... 这对我来说在非索引图像上被破坏了。当试图 Marshal.Copy 最后一行时,得到一个 AccessViolationException。我猜它正在读取源数组的末尾。 计算是正确的,我已经使用了很长时间没有任何问题,无论是索引图像还是非索引图像。你确定你没有做错什么,比如在从流中加载图像后关闭流? @KaseySpeakman 仅供参考:从 will need the stream to remain open for the entire life cycle of the image object 流创建的Image
对象。与文件不同,没有什么主动强制这样做,但是在流关闭后,图像在保存、克隆或以其他方式操作时会出错。
啊,我以为目的是通过创建新图像来断开图像与流等支持资源的连接。所以我在克隆到目标图像后关闭源图像及其流。【参考方案3】:
这是我在不锁定文件的情况下打开图像的方法...
public static Image FromFile(string path)
var bytes = File.ReadAllBytes(path);
var ms = new MemoryStream(bytes);
var img = Image.FromStream(ms);
return img;
更新:我做了一些性能测试,看看哪种方法最快。我将它与@net_progs“从位图复制”答案进行了比较(这似乎是最接近正确的,但确实存在一些问题)。我为每种方法加载了 10000 次图像,并计算了每张图像的平均时间。结果如下:
Loading from bytes: ~0.26 ms per image.
Copying from bitmap: ~0.50 ms per image.
结果似乎很有意义,因为您必须使用从位图复制方法创建两次图像。
更新: 如果你需要一个位图,你可以这样做:
return (Bitmap)Image.FromStream(ms);
【讨论】:
您仍然必须在图像的生命周期内保持流打开,这意味着数据在内存中两次。这对于较大的图像是不切实际的。 在您的代码示例中,尚不清楚您是否/何时应该(和/或确实)在 MemoryStream 和/或 Image 上调用 Dispose。 Peraps 我不应该抱怨(即调用 Dispose)是这个问题本质的一部分。 图像在创建后使用流(对不起,我不记得它使用的场景)。如果你处理它,它会抛出一个异常。在 MemoryStream 上调用 Dispose 实际上并不重要,因为它不使用任何系统资源(例如使用 FileStream 时的文件锁)。时间到了,GC 会正确清理。 谢谢,这对我帮助很大。 @asgerhallas 你肯定想在图像上调用 Dispose。它包含必须销毁的本机 GDI 对象。【参考方案4】:一旦文件被读取和处理,使用文件流将解锁文件:
using (var fs = new System.IO.FileStream("c:\\path to file.bmp", System.IO.FileMode.Open))
var bmp = new Bitmap(fs);
pct.Image = (Bitmap) bmp.Clone();
编辑:更新为允许释放原始位图,并允许关闭 FileStream。
此答案不安全 - 请参阅 cmets,并参阅 net_prog's answer 中的讨论。使用 Clone
的编辑不会使其更安全 - 克隆会克隆所有字段,包括文件流引用,这在某些情况下会导致问题。
【讨论】:
不!坏的! “您必须在位图的整个生命周期内保持流打开。” --MSDN。正如@Computer Linguist 在下面发布的那样。 @BrainSlugs83 您的评论是正确的,但是我们无法控制用户的行为,如果您认为错误的答案是错误的或有害的,您应该否决他们 这个答案仍然失败。 这不起作用,它改变了 PixelFormat 可能还有其他的东西 可疑。正如BrainSlugs answer 中提到的,并由Anlo 验证,“克隆”对这种情况没有帮助。其他答案更安全。【参考方案5】:(The accepted answer 是错误的。当你在克隆的位图上尝试LockBits(...)
时,最终你会遇到 GDI+ 错误。)
我只看到 3 种方法可以摆脱这种情况: 将您的文件复制到一个临时文件并以简单的方式打开它
new Bitmap(temp_filename)
打开您的文件,读取图像,创建一个像素大小的像素格式副本(不要Clone()
)并处理第一个位图
(接受锁定文件功能)
【讨论】:
将文件复制到临时文件(您愿意锁定)的好主意!对于第二种选择(“创建像素..复制”),请注意两个答案,它们提供了安全复制方法的代码:Brian's 和 Rennie's。 ... net prog's answer 也是另一种安全的复制方式。请注意,与从文件中打开相比,所有这些内存中的方法都会消耗双倍的内存(位图本身使用内存,并且这些技术还将数据保存在充当后备存储的内存流或数组中,以防需要位图重新创建);对于非常大的文件,复制到临时文件的技术更胜一筹,因为该文件充当持久的后备存储。【参考方案6】:这是我目前正在使用的技术,似乎效果最好。它的优点是可以生成与源文件具有相同像素格式(24 位或 32 位)和分辨率(72 dpi、96 dpi 等)的位图对象。
// ImageConverter object used to convert JPEG byte arrays into Image objects. This is static
// and only gets instantiated once.
private static readonly ImageConverter _imageConverter = new ImageConverter();
这可以根据需要经常使用,如下所示:
Bitmap newBitmap = (Bitmap)_imageConverter.ConvertFrom(File.ReadAllBytes(fileName));
编辑: 以下是上述技术的更新:https://***.com/a/16576471/253938
【讨论】:
@Nyerguds 我会这么认为,但我不确定。你为什么不试试呢? 它工作...但是,我检查了这背后的代码,似乎internally, all it does is make a byte stream and callImage.FromStream()
on it,在参考 MSDN 代码注释中的一个不太令人放心的注释“希望 GDI+ 知道什么与此有关!”因此,与其他解决方案一样,这会在内存中的某处留下未封闭的流。
@Nyerguds 你说的可能是真的,但它是一个未关闭(或未丢弃)的 memory 流,而不是打开的文件流和锁定的文件。而且我认为当您的程序中的任何位置不再引用 Image 对象时,垃圾收集将处理它并恢复内存。
哦,它有效。我只是觉得它不是一个非常干净的解决方案。【参考方案7】:
这是一个在网络上广泛讨论的常见锁定问题。
建议的使用流的技巧将不起作用,实际上它最初有效,但后来会导致问题。例如,它将加载图像并且文件将保持解锁状态,但如果您尝试通过 Save() 方法保存加载的图像,则会抛出通用 GDI+ 异常。
接下来,每像素复制的方式似乎并不可靠,至少它是嘈杂的。
这里描述了我发现的工作:http://www.eggheadcafe.com/microsoft/Csharp/35017279/imagefromfile--locks-file.aspx
这是应该如何加载图像的:
Image img;
using (var bmpTemp = new Bitmap("image_file_path"))
img = new Bitmap(bmpTemp);
我一直在寻找解决这个问题的方法,到目前为止,这种方法对我来说效果很好,所以我决定描述它,因为我发现很多人在这里和网络上建议不正确的流方法。
【讨论】:
这里有个小问题,像素格式总是 32 位 ARGB,分辨率总是 96 dpi。这对于大多数应用程序来说可能没问题,但对于某些应用程序来说,尝试保持源文件的像素格式和分辨率很重要。 DrawImage into a Bitmap with matching characteristics 似乎是传输位图最简单的方法,不会丢失像素格式。要保留 dpi,请参阅我对该答案的评论。 @ToolmakerSteve 不过,这不适用于索引格式。唯一的方法是使用LockBits
复制支持数组,然后恢复调色板。
仅供参考,另请参阅Brian's answer,它避免了两次创建位图,因此开销更少;相反,它使用字节数组作为中间来“打破”对文件的依赖。
@Nyerguds:谢谢,这为我节省了一些时间。结合@RenniePet 的提示,这是一个很好的解决方案:1)从文件加载位图#1。 2) 创建具有相同大小和像素格式的新位图#2,设置分辨率,设置调色板。 3) 两个位图上的 LockBits,int copySize = bitmapData1.Stride * bitmapData1.Height
Buffer.BlockCopy(bitmapData1.ToPointer(), bitmapData2.ToPointer(), copySize, copySize)
从第一个位图到第二个位图(这将需要方法上的unsafe
,但您可以使用 2x Marshal.Copy()
解决它。【参考方案8】:
当位图对象仍在使用流时,您不能释放/关闭流。 (位图对象是否需要再次访问它只有在您知道您正在使用的文件类型以及您将执行哪些操作时才具有确定性。 - 例如对于某些 .gif 格式的图像,流之前关闭构造函数返回。)
克隆创建位图的“精确副本”(根据文档;ILSpy 显示它调用本机方法,因此现在追踪太多)很可能,它也复制该 Stream 数据 - 否则它不会不是精确的副本。
最好的办法是创建图像的像素完美副本——尽管 YMMV(对于某些类型的图像可能不止一帧,或者您可能还必须复制调色板数据。)但对于大多数图像,这行得通:
static Bitmap LoadImage(Stream stream)
Bitmap retval = null;
using (Bitmap b = new Bitmap(stream))
retval = new Bitmap(b.Width, b.Height, b.PixelFormat);
using (Graphics g = Graphics.FromImage(retval))
g.DrawImage(b, Point.Empty);
g.Flush();
return retval;
然后你可以像这样调用它:
using (Stream s = ...)
Bitmap x = LoadImage(s);
【讨论】:
要保留dpi,在retval = ...;
之后添加:retval.SetResolution(b.HorizontalResolution, b.VerticalResolution);
这将在索引图像上崩溃,因为Graphics
类无法处理它们。【参考方案9】:
将其读入流中,创建位图,关闭流。
【讨论】:
您必须克隆位图 - 位图使流保持打开状态,否则会引发异常。见msdn.microsoft.com/en-us/library/z7ha67kw.aspx @NathanaelJones - 准确地说,必须Copy
位图(请参阅其他答案以了解各种方法),而不是Clone
;一个克隆包含所有相同的字段,包括导致异常的流引用。以上是关于由新位图(filePath)锁定的免费文件的主要内容,如果未能解决你的问题,请参考以下文章