【问题标题】:Loading an image from a stream without keeping the stream open从流中加载图像而不保持流打开
【发布时间】:2011-04-20 05:10:33
【问题描述】:

是否可以使用 System.Drawing.Image 的 FromStream 方法而不必在图像的生命周期内保持流打开?

我有一个应用程序,它使用Image.FromStreamAssembly.GetManifestResourceStream 的组合从资源文件中加载一堆工具栏图形。

我遇到的问题是,虽然这在 Windows 7 上运行良好,但在 Windows XP 上,如果链接到这些图像之一的用户界面元素被禁用,应用程序会崩溃。在 Windows 7 上,图像以灰度呈现。在 XP 上,它会因内存不足异常而崩溃。

经过大量的拉扯后,我终于将其追溯到图像的初始加载。当然,如果我创建了任何实现 IDisposable 的对象,并且在同一方法中也被销毁,我将其包装在 using 语句中,例如

using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
{
   image = Image.FromStream(resourceStream);
}

如果我删除 using 语句以便不释放流,则应用程序不再在 XP 上崩溃。但是我现在有一堆“孤儿”流挂着 - 图像存储在命令类中,当它们自己被处理时,它们会正确处理图像,但原始流不是。

我检查了FromStream 的文档,它确认流需要保持打开状态。然而,为什么它没有在 Windows 7 开发系统上崩溃和烧毁是一个谜!

我真的不希望这个流一直存在,我当然也不想存储对这个流的引用以及图像,以便我以后可以处理它。我只需要那个流一次,所以我想摆脱它:)

是否可以创建图像然后在那里杀死流?

【问题讨论】:

  • 顺便说一下,System.Drawing 中的 OutOfMemoryException 通常意味着 GDI+ 中的一般错误。造成这种情况的原因是historic 并且是由于非托管错误代码的映射。
  • 如果您传递给 Image.FromStream 的流是不可搜索的,您可以安全地关闭流。但这是未记录的行为,因此使用风险自负。

标签: c# image stream out-of-memory


【解决方案1】:

需要打开流的原因是following

GDI+,因此 System.Drawing 命名空间,可能会推迟原始图像位的解码,直到图像需要这些位。此外,即使图像已被解码,GDI+ 也可能确定为大位图丢弃内存并稍后重新解码更有效。因此,GDI+ 必须在 BitmapImage 对象的生命周期内访问图像的源位。

记录的解决方法是使用Graphics.DrawImage 创建非索引图像或从原始图像创建索引Bitmap,如下所述:

Bitmap and Image constructor dependencies

【讨论】:

  • 感谢您提供非常有用的答案!我知道 FromFile 锁定文件,但直到现在我才知道原因。我已经使用 Create a Non-Indexed Image 方法进行了测试,该方法运行良好,并且应用程序不再在我的 XP VM 上崩溃。我将使用 Indexed 方法再次测试,看看会发生什么 - 我不清楚这两种类型之间的区别。
  • Fwiw:他们修复了 Vista 版本的 gdiplus.dll 中索引像素格式的几个问题。他们这样做很好,但是当您的代码需要在 XP 上运行时会很头疼。一般来说,远离他们以避免这种麻烦。
【解决方案2】:

根据Image.FromStream 的文档,在使用图像时,流必须保持打开状态。因此,即使关闭有效(没有什么可以说您不能在处理流之前关闭流,就流对象本身而言)它可能不是一个非常可靠的方法。

您可以将图像复制到另一个图像对象,然后使用它。但是,这可能比保持流打开更占用内存。

【讨论】:

  • 感谢您的回答。这就是我所做的,使用上面链接的知识库文章提供了原因。
  • 它也很详细,但在某些情况下保持流可能更有效,这一点值得牢记。
【解决方案3】:

您可以将流保存到临时文件并使用Image.FromFile 方法。或者干脆不嵌入图像,将其保存为文件并在运行时从该文件加载。

【讨论】:

  • 感谢您的回答,虽然这是我已经放弃的东西,因为我从过去的经验中知道这会锁定文件,因此在应用程序结束之前无法删除它。跨度>
【解决方案4】:

我相信这会对某人有所帮助:)

我将它用于我的 dataGridView_SelectionChanged:

private void dataGridViewAnzeige_SelectionChanged(object sender, EventArgs e)
{
    var imageAsByteArray = File.ReadAllBytes(path);
    pictureBox1.Image = byteArrayToImage(imageAsByteArray);
}

public Image byteArrayToImage(byte[] byteArrayIn)
{
    MemoryStream ms = new MemoryStream(byteArrayIn);
    Image returnImage = Image.FromStream(ms);
    return returnImage;
}

【讨论】:

    猜你喜欢
    • 2019-04-14
    • 1970-01-01
    • 1970-01-01
    • 2016-08-21
    • 1970-01-01
    • 2010-12-18
    • 1970-01-01
    • 2020-01-02
    • 2012-12-01
    相关资源
    最近更新 更多