问题:
为什么用于 ImageA 的 MemoryStream 对象会影响名为 ImageB 的克隆 Image 对象?我知道 Microsoft 表示,当您从内存流创建 Image 对象时,您必须在图像的整个生命周期内保持该流打开。但我尝试从 MemoryStream 创建 ImageA,将 ImageA 克隆到 ImageB,然后处理 ImageA 和用于创建 ImageA 的 MemoryStream。之后我在处理 ImageB 时出现错误。
来源示例:
以下所有代码均用于从 3 个 tiff 文件(单页)创建新的多页 tiff 图像。最终得到一个 3 页的单个 tiff 图像。由此您获得...
- 图片 newImg
- 内存流毫秒
注意:对于一个小测试来说,这似乎有很多代码,但我想给出一个很好的例子来说明我在哪里获取我正在使用的对象。这个示例代码的很多部分取自几个不同的方法/函数,我只是做了一些改动,以便真正描绘出我从导入文件本身开始所做的一切。
Image img = Image.FromFile(@"c:\test\page001.tif");
MemoryStream ms = new MemoryStream();
List<Image> imagesToAdd = new List<Image>()
{
Image.FromFile(@"c:\test\page002.tif"),
Image.FromFile(@"c:\test\page003.tif")
};
// Create compression encoder parameter
EncoderParameter compressionParam = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionCCITT4);
// Create first page frame parameter
EncoderParameter firstFrameParam = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.MultiFrame);
// Create additional pages frame parameter
EncoderParameter additionalFramesParam = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.FrameDimensionPage);
// Create color depth parameter
EncoderParameter colorDepthParam = new EncoderParameter(Encoder.ColorDepth, (long)1);
// Create flush parameter
EncoderParameter flushParam = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.Flush);
// Create last frame parameter
EncoderParameter lastPageParam = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.LastFrame);
// Create first page encoder parameters
EncoderParameters firstFrameParams = new EncoderParameters(2)
{
Param = new EncoderParameter[]
{
compressionParam,
firstFrameParam,
}
};
// Create additional pages encoder parameters
EncoderParameters additionalFrameParams = new EncoderParameters(2)
{
Param = new EncoderParameter[]
{
compressionParam,
additionalFramesParam,
}
};
// Create save to file encoder parameters
EncoderParameters saveToFileParams = new EncoderParameters(2)
{
Param = new EncoderParameter[]
{
compressionParam,
colorDepthParam,
}
};
// Create flush encoder parameters
EncoderParameters flushParams = new EncoderParameters(1)
{
Param = new EncoderParameter[]
{
flushParam,
lastPageParam,
}
};
// Get the tiff image codec
ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders().Where(c => c.MimeType == "image/tiff").First();
// Save the first page to memory stream
img.Save(ms, codec, firstFrameParams);
// Save aditional pages to memory stream
foreach (Image image in imagesToAdd)
img.SaveAdd(image, additionalFrameParams);
// Finalize the new multi-page image
img.SaveAdd(flushParams);
//Image newImg = pageOne.SaveMultiPageImage(imgCol, out MemoryStream ms);
Image newImg = Image.FromStream(ms);
// Cleanup
firstFrameParams.Dispose();
additionalFrameParams.Dispose();
saveToFileParams.Dispose();
flushParam.Dispose();
compressionParam.Dispose();
firstFrameParam.Dispose();
additionalFramesParam.Dispose();
colorDepthParam.Dispose();
flushParam.Dispose();
lastPageParam.Dispose();
下面的代码实际上是在其他地方使用的。我已将所有这些功能都放在一个函数中进行测试,以便制作一个很好的示例。下面代码的思路是使用上面的代码生成的图像,然后对其进行克隆,删除原始图像和用于创建它的内存流,然后尝试使用新创建的 Image 对象。
问题:在处理用于创建 newImg 图像对象的内存流之后,从 newImg 克隆的新图像(newImg2)会损坏。
// Try to work with the newImg Image object (no issues)
int pageCount = newImg.GetFrameCount(FrameDimension.Page);
for (int i = 0; i < pageCount; i++)
{
newImg.SelectActiveFrame(FrameDimension.Page, i);
Debug.WriteLine($"BEFORE - Page:{i + 1} W/H: {newImg.Width}/{newImg.Height}");
}
// Create a new Image (newImg2) based on the original Image (newImg)
// then dispose of the original Image object as it's no longer needed
Image newImg2 = (Image)newImg.Clone();
newImg.Dispose();
// This causes issues when dealing with newImg2 going forward
ms.Dispose();
// Try to work with the newImg2 Image object (issues)
pageCount = newImg2.GetFrameCount(FrameDimension.Page);
for (int i = 0; i < pageCount; i++)
{
newImg2.SelectActiveFrame(FrameDimension.Page, i);
Debug.WriteLine($"AFTER - Page:{i + 1} W/H: {newImg2.Width}/{newImg2.Height}");
}
使用 Clone() 就像复制指针或引用。两个图像(原始图像和克隆图像)共享原始 MemoryStream 中的相同底层内存/图像数据。
因此,释放 ms 意味着保存图像数据的内存将被释放,不再有效。引用此 MemoryStream 的所有图像在尝试访问其内容时都会失败。
如果您打算在此处关闭 memoryStream,请确保 newImg2 是一个完全独立的副本。
这必须在调用之前完成
或者,(见评论,谢谢)
所以问题的答案很简单,ImageA 和 ImageB 都在底层使用相同的 MemoryStream。
如果有人想要像我这样的解决方案来真正克隆 Image 对象,我已经创建了一个扩展方法来处理这个问题。
我唯一担心的是,新的 MemoryStream 对象被创建后从未被释放。通常,人们总是会尝试使用类似块的东西来清理支持 IDisposable 接口的对象
using () { }
。但是,每当您从 MemoryStream 创建 Image 对象时,您都无法释放 MemoryStream。它必须在 Image 对象的整个生命周期内存在。否则,您将在运行时开始看到 GDI+ 通用错误。另请参考本页上的引文,
看起来,如果没有对 Image 对象的引用,底层的 MemoryStream 将被垃圾收集器清理。
例子:
此外,我开始思考这个问题,当你调用时,
Image img = Image.FromFile()
你永远无法访问 MemoryStream。然而,当你 Dispose 一个 Image 对象时,并没有内存泄漏。所以我认为 GC 会负责清理以后没有使用的东西。