C# 在WPF中处理图像时发生OutOfMemoryException

C# 在WPF中处理图像时发生OutOfMemoryException,c#,wpf,bitmapimage,imaging,C#,Wpf,Bitmapimage,Imaging,我必须从大图像的特定部分制作一些缩略图,并将它们保存为png/jpeg图像。以下是我正在做的: public static void Save(BitmapImage srcBitmap, Int32Rect srcRegion, Rect destRegion) { var cropped = new CroppedBitmap(srcBitmap, srcRegion); var drawingVisual = new DrawingVisual(); using

我必须从大图像的特定部分制作一些缩略图,并将它们保存为png/jpeg图像。以下是我正在做的:

public static void Save(BitmapImage srcBitmap, Int32Rect srcRegion, Rect destRegion)
{
    var cropped = new CroppedBitmap(srcBitmap, srcRegion);

    var drawingVisual = new DrawingVisual();

    using (DrawingContext drawingContext = drawingVisual.RenderOpen())
    {
        drawingContext.DrawImage(cropped, destRegion);
    }

    var bmp = new RenderTargetBitmap(256, 256, 96, 96, PixelFormats.Pbgra32);

    bmp.Render(drawingVisual);

    var bitmapEncoder = new PngBitmapEncoder();

    bitmapEncoder.Frames.Add(BitmapFrame.Create(bmp));

    using (var filestream = new FileStream(path, FileMode.Create))
    {
        bitmapEncoder.Save(filestream);
    }
}
我可能会使用一个大的
位图
和不同的
srcRegion
调用此方法数千次,应用程序越来越多地消耗更多的RAM,最后抛出
System.OutOfMemoryException
!这个函数似乎有内存泄漏,但我不知道它在哪里。有人能帮忙吗

编辑:此外,我不确定这是否是获取大图像的一部分并将该部分大小调整为较小图像(例如256*256)并保存它的最佳方法。还有更好的办法吗


RenderTargetBitmap
不是
IDisposable
,但它应该是(因为它使用本机资源),这是一个奇怪的设计实现,但事实就是如此。您可以在退出之前尝试调用
bmp.Clear()
,这将释放本机资源

RenderTargetBitmap
自己检查GC压力(使用)以释放非托管资源,但根据我的经验,这并不是很好(虽然是很久以前的事了,最近可能会更新)

另外,不是为了生产代码,而是为了测试目的,我要添加一个:

GC.Collect();
GC.WaitForPendingFinalizers();

在您的方法之上(或者在调用它之后,从调用方),只是为了确保问题不是托管资源,而GC没有释放这些资源的内存压力(系统上可用的内存可能比您可以容纳本机句柄的内存多,这将使所有这些都混淆).

好的,我通过使用
TransformedBitmap
改进了这段代码(实际上我是从@Clemens那里得到这个想法的),并且不再抛出异常

public static void Save(BitmapImage srcBitmap, Int32Rect srcRegion, Rect destRegion)
{
    var cropped = new CroppedBitmap(srcBitmap, srcRegion);

    var drawingVisual = new DrawingVisual();

    //Here is the changes:
    var scale = 256.0 / srcRegion.Width;
    var transform = new ScaleTransform(scale, scale);
    //

    using (DrawingContext drawingContext = drawingVisual.RenderOpen())
    {
        //Here is the changes:
        drawingContext.DrawImage(new TransformedBitmap(cropped, transform), destRegion);
        //
    }

    var bmp = new RenderTargetBitmap(256, 256, 96, 96, PixelFormats.Pbgra32);

    bmp.Render(drawingVisual);

    var bitmapEncoder = new PngBitmapEncoder();

    bitmapEncoder.Frames.Add(BitmapFrame.Create(bmp));

    using (var filestream = new FileStream(path, FileMode.Create))
    {
        bitmapEncoder.Save(filestream);
    }
}

该功能是静态的,这可能是问题的原因。尝试移除静电干扰。您可能希望将代码放在一个单独的类中,然后处置该类以确保对象被销毁以防止泄漏。请使用PerfView并执行内存-获取堆快照。你看到了什么。如果泄漏与GC的太懒性质和处理非托管资源的奇怪WPF方式有关,那么使用.NET4.6.2可能会更好。这可能很有趣:
desregion
是否与
(0,0,256,256)
不同,并且
srcRegion
的大小是否与
(256,256)
不同?如果没有,您可能会直接将裁剪后的位图传递给BitmapEncoder,而不使用DrawingVisual和RenderTargetBtmap。您实际上不需要/希望/应该使用RenderTargetBitmap、DrawingVisual和其他wpf来对图像区域进行调整,尤其是在这样做了数千次的情况下。这不是适合这项工作的工具,即使是标准位图也应该做得更好。@Evk
System.Drawing.Bitmap
有一个问题,它不能为非常大的图像(例如:300MB)创建,但我可以使用
BitmapImage
来处理如此大的图像。您可能需要强制收集GC(请参阅我添加的注释)。WPF确实有一种奇怪的处理资源的方式,它自己进行GC检查(而不是像它应该的那样使用
IDisposable
)。GC通常没有足够的压力来释放资源,非托管句柄很容易填满。。。如果这不起作用,我只能建议您做一些更严肃的内存分析:我看不出您的代码有任何错误