[英]C# out of memory exception when converting a tiff file to pdf
[英]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擴展進行了分析,在這里我可以看到:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.