簡體   English   中英

將Tiff的幀合並為單個png會產生內存不足異常C#

[英]Combining Frames of a Tiff into a single png gives Out of Memory Exception C#

首先,我將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;
        }
    }
}

然后,我將該位圖列表傳遞到一個單獨的函數中進行實際組合:

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;                     
}

但是我通常會收到“內存不足”異常,具體取決於tiff中的幀數。 即使只有5張約2550px x 3300px的圖像也足以引起錯誤。 這只有42MB左右,最終以png格式保存,總共為2,550px x 16,500px,並且在磁盤上只有1.5MB。 我什至在我的web.config中使用此設置: <gcAllowVeryLargeObjects enabled=" true" />其他詳細信息:我在具有16GB RAM的64位Windows 7上工作(並且我通常以ram使用率的65%運行) ),並且我的代碼在asp.net MVC項目的Visual Studio 2013中運行。 我將項目運行為32位,因為我使用的Tessnet2僅支持32位。 不過,我認為我應該有足夠的內存來一次處理5張以上的圖像。 有沒有更好的方法來解決這個問題? 如果可以的話,我寧願不必求助於付費框架。 我覺得這是我應該可以免費使用現成的.net代碼進行的操作。 謝謝!

我沒有要測試的數據,因此無法測試您的代碼。 但是,顯然,您至少可以在這里以一種以上明顯的方式節省內存。

進行以下更改:

首先,不要讀取所有字節並使用MemoryStream :將整個文件復制到內存,然后使用BitmapCacheOption.Default創建Decoder ,這應該將整個內存流再次加載到解碼器中...

消除tiffFile數組。 using語句中,打開文件上的FileStream 並使用BitmapCacheOption.None創建解碼器-無解碼器的內存中存儲。

然后,為每幀創建一個完整的BitMaps列表! 相反,只需迭代解碼器上的幀即可獲得目標大小( BitmapFrame具有PixelWidth )。 用它創建目標BitMap ; 然后迭代這些幀並在那里繪制每個幀:將每個幀的BitMap放入using塊中,並在將其繪制到目標之后立即處理每個幀。

將您的本地Bitmap bmp變量移到using塊之外,以便在創建該BitMap之后可以釋放所有先前版本。 然后在using塊之外將其寫入文件。

顯然,您要制作整個圖像的兩個完整副本,再加上為每個幀制作每個BitMap,再制作最終的BitMap,這是很多蠻力的副本。 它全部在一個using塊內,因此在完成新文件的寫入之前不會留下任何內存。

可能還有更好的方法將Streams傳遞給解碼器並使用Streams制作BitMap,這將不需要將所有字節都轉儲到內存中。

public Bitmap SaveTiffAsTallPng(string filePathAndName) {
    string pngFilename = Path.ChangeExtension(filePathAndName), "png");
    if (System.IO.File.Exists(pngFilename))
        return new Bitmap(pngFilename);
    else {
        Bitmap pngBitmap;
        using (FileStream tiffFileStream = File.OpenRead(filePathAndName)) {
            TiffBitmapDecoder decoder
                    = new TiffBitmapDecoder(
                            tiffFileStream,
                            BitmapCreateOptions.PreservePixelFormat,
                            BitmapCacheOption.None);
            int pngWidth = 0;
            int pngHeight = 0;
            for (int i = 0; i < decoder.Frames.Count; ++i) {
                pngWidth = Math.Max(pngWidth, decoder.Frames[i].PixelWidth);
                pngHeight += decoder.Frames[i].PixelHeight;
            }
            bitmap = new Bitmap(pngWidth, pngHeight, PixelFormat.Format16bppGrayScale);
            using (Graphics g = Graphics.FromImage(pngBitmap)) {
                int y = 0;
                for (int i = 0; i < decoder.Frames.Count; ++i) {
                    using (Bitmap frameBitMap = Worker.BitmapFromSource(decoder.Frames[i])) {
                        g.DrawImage(frameBitMap, 0, y);
                        y += frameBitMap.Height;
                    }
                }
            }
        }
        EncoderParameters eps = new EncoderParameters(1);
        eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 16L);
        pngBitmap.Save(
                pngFilename,
                Worker.GetEncoderInfo("image/png"),
                eps);
        return pngBitmap;
    }
}

TL; DR

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);

如果狀態不正常 ,它將引發異常

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));
        .....
     }
}

您的方案中的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

我將windbg + SOSEX用於.NET擴展進行了分析,在這里我可以看到: WinDbg

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM