将 Tiff 的帧组合成单个 png 会导致内存不足异常 C#

Dyl*_*est 0 c# asp.net tiff image-processing out-of-memory

首先,我将 tiff 的帧保存到位图列表中:

public Bitmap SaveTiffAsTallPng(string filePathAndName)
{
    byte[] tiffFile = System.IO.File.ReadAllBytes(filePathAndName);
    string fileNameOnly = Path.GetFileNameWithoutExtension(filePathAndName);
    string filePathWithoutFile = Path.GetDirectoryName(filePathAndName);
    string filename = filePathWithoutFile + "\\" + fileNameOnly + ".png";

    if (System.IO.File.Exists(filename))
    {
        return new Bitmap(filename);
    }
    else
    {
        List<Bitmap> bitmaps = new List<Bitmap>();
        int pageCount;

        using (Stream msTemp = new MemoryStream(tiffFile))
        {
            TiffBitmapDecoder decoder = new TiffBitmapDecoder(msTemp, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
            pageCount = decoder.Frames.Count;

            for (int i = 0; i < pageCount; i++)
            {
                System.Drawing.Bitmap bmpSingleFrame = Worker.BitmapFromSource(decoder.Frames[i]);
                bitmaps.Add(bmpSingleFrame);
            }

            Bitmap bmp = ImgHelper.MergeImagesTopToBottom(bitmaps);

            EncoderParameters eps = new EncoderParameters(1);
            eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 16L);
            ImageCodecInfo ici = Worker.GetEncoderInfo("image/png");

            bmp.Save(filename, ici, eps);

            return bmp;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我将该位图列表传递给一个单独的函数来进行实际的组合:

public static Bitmap MergeImagesTopToBottom(IEnumerable<Bitmap> images)
{
    var enumerable = images as IList<Bitmap> ?? images.ToList();
    var width = 0;
    var height = 0;

    foreach (var image in enumerable)
    {
        width = image.Width > width
            ? image.Width
            : width;
        height += image.Height;
    }

    Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format16bppGrayScale);                                      

    Graphics g = Graphics.FromImage(bitmap);//gives Out of Memory Exception

    var localHeight = 0;
    foreach (var image in enumerable)
    {
        g.DrawImage(image, 0, localHeight);
        localHeight += image.Height;
    }

    return bitmap;                     
}
Run Code Online (Sandbox Code Playgroud)

但我通常会收到内存不足异常,具体取决于 tiff 中的帧数。即使只有 5 张大约 2550 像素 x 3300 像素的图像也足以导致错误。这只有大约 42MB,最终保存为 png,总共 2,550 像素乘 16,500 像素,磁盘上只有 1.5MB。我什至在我的 web.config 中使用这个设置:<gcAllowVeryLargeObjects enabled=" true" />其他详细信息:我正在使用 16GB RAM 的 64 位 Windows 7(我通常以大约 65% 的 ram 使用率运行),并且我的代码在 Visual Studio 2013 中的 asp.net MVC 项目中运行。我正在以 32 位运行该项目,因为我使用的是仅在 32 位中可用的 Tessnet2。尽管如此,我认为我应该有足够的内存来一次处理 5 张以上的图像。有没有更好的方法来解决这个问题?如果可以的话,我宁愿不必求助于付费框架。我觉得这是我应该能够免费使用开箱即用的 .net 代码完成的事情。谢谢!

Art*_*yan 5

TL; 博士

Graphics.FromImage 的文档说:如果图像具有以下任何像素格式,此方法也会引发异常: Undefined, DontCare, Format16bppArgb1555, Format16bppGrayScale。所以,使用另一种格式或尝试另一种图像库(它不是建立在 GDI+ 之上,如Emgu CV

详细的

您收到 OutOfMemoryException,但这与内存不足无关。这就是Windows GDI+ 的工作方式以及如何封装在 .NET 中。我们可以看到Graphics.FromImage 调用 GDI+

int status = SafeNativeMethods.Gdip.GdipGetImageGraphicsContext(new HandleRef(image, image.nativeImage), out gdipNativeGraphics);
Run Code Online (Sandbox Code Playgroud)

如果状态不正常,它会抛出一个异常

internal static Exception StatusException(int status)
{
    Debug.Assert(status != Ok, "Throwing an exception for an 'Ok' return code");

    switch (status)
    {
        case GenericError: return new ExternalException(SR.GetString(SR.GdiplusGenericError), E_FAIL);
        case InvalidParameter: return new ArgumentException(SR.GetString(SR.GdiplusInvalidParameter));
        case OutOfMemory: return new OutOfMemoryException(SR.GetString(SR.GdiplusOutOfMemory));
        case ObjectBusy: return new InvalidOperationException(SR.GetString(SR.GdiplusObjectBusy));
        .....
     }
}
Run Code Online (Sandbox Code Playgroud)

你的场景中的 status 等于3,所以为什么它会抛出 OutOfMemoryException 。

为了重现它,我只是尝试从 16x16 位图创建图像:

int w = 16;
int h = 16;
Bitmap bitmap = new Bitmap(w, h, PixelFormat.Format16bppGrayScale);
Graphics g = Graphics.FromImage(bitmap); // OutOfMemoryException here
Run Code Online (Sandbox Code Playgroud)

我使用windbg + SOSEX 进行 .NET 扩展进行分析,在这里我可以看到: 数据库