使用PDFiumSharp转大PDF为单图时Image.Save到MemoryStream失败如何解决
错误原因分析
- 首先是JPEG格式标准硬限制:JPEG单帧图像的最大边长不得超过65535像素。按你当前200DPI的设置,单张A4 PDF(高度为842点)转换后高度约为2339像素,28页左右总高度就会超出65535的限制,保存时必然触发编码错误。
- 其次是GDI+ Bitmap的限制:GDI+的Bitmap内存分配在系统GDI共享内存池,而非.NET托管堆,对单张图像的总像素、最大高度都有环境相关的隐性限制,大尺寸图像容易出现分配失败、保存失败的问题。
- 你遇到的
A generic error occurred in GDI+和MemoryStream没有关系,本质是上述两个限制共同导致的。
不拆分页面的解决方案
方案1:下调DPI(改动最小,优先推荐)
只要将DPI下调到130及以下,40页A4 PDF拼接后的总高度就会低于65535像素,完全符合JPEG格式限制,无需修改其他逻辑:
// 原DPI=200改为130即可适配40页A4的需求 private readonly int DPI = 130;
如果需要更高清晰度,可根据实际最大页数计算DPI上限:最大DPI = 65535 * 72 / (单页高度点数 * 最大页数),A4单页高度为842点,40页的话计算下来最大DPI约为139。
方案2:更换图像编码API(无需降DPI)
弃用GDI+的Image.Save方法,改用WPF成像体系的JpegBitmapEncoder或者System.Drawing.Common的更新版本编码,这类API不受GDI内存池限制,对大尺寸图像的支持更好。如果边长必须超过65535,建议更换输出格式为PNG,PNG没有65535的边长限制。
方案3:优化像素拷贝逻辑(降低内存占用)
现有代码中每页需要经过PDFiumBitmap -> 流 -> Image -> 绘制到总Bitmap的流程,中间存在多次内存拷贝,大文件下容易触发内存不足。可以直接将PDFiumBitmap的像素数据拷贝到总Bitmap的内存缓冲区,减少开销:
// 总Bitmap创建后锁定像素缓冲区 var bitmapData = img.LockBits(new Rectangle(0, 0, maxWidth, totalHeight), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); var scan0 = bitmapData.Scan0; var stride = bitmapData.Stride; // 每页渲染后直接拷贝像素 foreach (var page in doc.Pages) { int pageWidth = PointsToPixels(page.Width); int pageHeight = PointsToPixels(page.Height); using (var pdfBitmap = new PDFiumBitmap(pageWidth, pageHeight, true)) { pdfBitmap.Fill(new FPDF_COLOR(255,255,255,255)); page.Render(pdfBitmap); int widthOffset = (maxWidth - pageWidth) / 2; // 逐行拷贝像素到总缓冲区对应位置 for (int y = 0; y < pageHeight; y++) { var sourcePtr = pdfBitmap.Buffer + y * pdfBitmap.Stride; var destPtr = scan0 + (heightOffset + y) * stride + widthOffset * 4; Buffer.MemoryCopy(sourcePtr.ToPointer(), destPtr.ToPointer(), pageWidth *4, pageWidth *4); } heightOffset += pageHeight; } } // 解锁缓冲区后再保存 img.UnlockBits(bitmapData);
其他代码优化建议
- 图像保存后记得重置
outputStream.Position = 0,否则调用方直接读取流会拿到空数据。 - 可以给Graphics对象添加高质量渲染配置,提升输出画质:
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
内容的提问来源于stack exchange,提问作者MasterOfTwo
相关产品推荐
相关产品推荐

