Azure Function中PdfSharp转换大尺寸PNG为PDF遇格式不支持错误
问题描述
开发Azure Function实现Blob存储中的PNG转PDF并回传至Blob存储,处理4000像素以上的PNG时代码失败。已完成以下优化与排查:
- 引入分块处理优化内存占用
- 确认PNG签名正确(
89 50 4E 47 0D 0A 1A 0A) - 验证文件未损坏
- 多次重置流位置
但仍触发System.InvalidOperationException: Unsupported image format错误,错误定位在代码行:using (var xImage = XImage.FromStream(reconstructedStream))
运行日志
[2025-04-23T08:13:37.124Z] ConvertPngToPdf function started. [2025-04-23T08:13:37.132Z] Request body read successfully. [2025-04-23T08:13:37.273Z] Parsed URLs from request body. [2025-04-23T08:13:37.313Z] Retrieved blob container. [2025-04-23T08:13:37.319Z] Processing URL... 989f6b77-cec9-4f78-8f89-39fe1a1b165b.png [2025-04-23T08:13:37.846Z] Blob stream opened. [2025-04-23T08:13:37.859Z] Converting PNG stream to PDF stream. [2025-04-23T08:13:37.894Z] Starting chunk-based PNG to PDF conversion. [2025-04-23T08:13:38.384Z] Read 2853026 bytes from PNG stream. [2025-04-23T08:13:42.492Z] Stream length: 2853026 [2025-04-23T08:13:45.838Z] Stream header: 89-50-4E-47-0D-0A-1A-0A [2025-04-23T08:13:54.478Z] Image loaded successfully. Dimensions: 4965x3511 [2025-04-23T08:14:40.226Z] An error occurred during PNG to PDF conversion. [2025-04-23T08:14:40.229Z] Result: An error occurred during PNG to conversion.
转换方法代码
/// <summary> /// Convert png stream to pdf. /// </summary> /// <param name="pngStream">Png stream.</param> /// <param name="pdfStream">Pdf stream.</param> [SupportedOSPlatform("windows")] public void ConvertPngStreamToPdfStream(Stream pngStream, Stream pdfStream) { try { this.log.LogInformation("Starting chunk-based PNG to PDF conversion."); // Define the chunk size (e.g., 4MB) const int chunkSize = 4 * 1024 * 1024; // 4 MiB byte[] buffer = new byte[chunkSize]; using var reconstructedStream = new MemoryStream(); // Read the PNG stream in chunks and write to a memory stream int bytesRead; while ((bytesRead = pngStream.Read(buffer, 0, buffer.Length)) > 0) { reconstructedStream.Write(buffer, 0, bytesRead); this.log.LogInformation("Read {BytesRead} bytes from PNG stream.", bytesRead); } // Reset the position of the reconstructed stream this.log.LogInformation("Stream length: {Length}", reconstructedStream.Length); reconstructedStream.Position = 0; byte[] buffer2 = new byte[8]; int bytesRead2 = reconstructedStream.Read(buffer2, 0, buffer2.Length); this.log.LogInformation("Stream header: {Header}", BitConverter.ToString(buffer2, 0, bytesRead2)); reconstructedStream.Position = 0; // Load the image from the reconstructed stream using (var image = Image.FromStream(reconstructedStream)) { this.log.LogInformation("Image loaded successfully. Dimensions: {Width}x{Height}", image.Width, image.Height); // Create a new PDF document using (var document = new PdfDocument()) { // Add a page to the PDF document var page = document.AddPage(); page.Width = XUnit.FromPoint(image.Width); page.Height = XUnit.FromPoint(image.Height); // Draw the image onto the PDF page using (var gfx = XGraphics.FromPdfPage(page)) { reconstructedStream.Position = 0; using (var xImage = XImage.FromStream(reconstructedStream)) { gfx.DrawImage(xImage, 0, 0, image.Width, image.Height); } } // Save the PDF document to the provided PDF stream document.Save(pdfStream, false); this.log.LogInformation("PDF saved successfully to the provided stream."); } } // Reset the position of the PDF stream to the beginning pdfStream.Position = 0; } catch (ArgumentException ex) { this.log.LogError(ex, "Invalid image format or corrupted PNG stream."); throw; } catch (Exception ex) { this.log.LogError(ex, "An error occurred during PNG to PDF conversion."); throw; } }
解决方案
核心问题分析
错误根源在于:虽然手动重置了流位置,但Image.FromStream(reconstructedStream)内部可能已修改流的状态(如将读取指针移至末尾、锁定流资源),导致后续XImage.FromStream无法正确解析流内容。此外,大尺寸PNG读入单个MemoryStream仍存在内存压力风险。
修复方案
方案1:复用已加载的Image对象(最简洁)
直接通过XImage.FromGdiPlusImage将已加载的Image对象转换为XImage,避免重复读取流:
// 替换原XImage.FromStream代码块 using (var gfx = XGraphics.FromPdfPage(page)) { using (var xImage = XImage.FromGdiPlusImage(image)) { gfx.DrawImage(xImage, 0, 0, image.Width, image.Height); } }
方案2:创建独立流副本
将读取到的PNG数据复制为两个独立的MemoryStream,分别供Image和XImage使用:
// 读取完pngStream到reconstructedStream后,创建两个独立副本 using var imageStream = new MemoryStream(reconstructedStream.ToArray()); using var xImageStream = new MemoryStream(reconstructedStream.ToArray()); // 使用imageStream加载Image using (var image = Image.FromStream(imageStream)) { this.log.LogInformation("Image loaded successfully. Dimensions: {Width}x{Height}", image.Width, image.Height); using (var document = new PdfDocument()) { var page = document.AddPage(); page.Width = XUnit.FromPoint(image.Width); page.Height = XUnit.FromPoint(image.Height); using (var gfx = XGraphics.FromPdfPage(page)) { // 使用xImageStream加载XImage using (var xImage = XImage.FromStream(xImageStream)) { gfx.DrawImage(xImage, 0, 0, image.Width, image.Height); } } document.Save(pdfStream, false); this.log.LogInformation("PDF saved successfully to the provided stream."); } }
方案3:直接操作BlobStream(优化内存)
BlobStream本身支持Seek操作,无需先读入MemoryStream,直接复制为独立流使用:
[SupportedOSPlatform("windows")] public void ConvertPngStreamToPdfStream(Stream pngStream, Stream pdfStream) { try { this.log.LogInformation("Starting PNG to PDF conversion."); // 重置BlobStream位置并验证签名 pngStream.Position = 0; byte[] buffer2 = new byte[8]; int bytesRead2 = pngStream.Read(buffer2, 0, buffer2.Length); this.log.LogInformation("Stream header: {Header}", BitConverter.ToString(buffer2, 0, bytesRead2)); pngStream.Position = 0; // 复制BlobStream到内存流副本 using var imageStream = new MemoryStream(); pngStream.CopyTo(imageStream); imageStream.Position = 0; using var xImageStream = new MemoryStream(imageStream.ToArray()); using (var image = Image.FromStream(imageStream)) { this.log.LogInformation("Image loaded successfully. Dimensions: {Width}x{Height}", image.Width, image.Height); using (var document = new PdfDocument()) { var page = document.AddPage(); page.Width = XUnit.FromPoint(image.Width); page.Height = XUnit.FromPoint(image.Height); using (var gfx = XGraphics.FromPdfPage(page)) { using (var xImage = XImage.FromStream(xImageStream)) { gfx.DrawImage(xImage, 0, 0, image.Width, image.Height); } } document.Save(pdfStream, false); this.log.LogInformation("PDF saved successfully to the provided stream."); } } pdfStream.Position = 0; } catch (ArgumentException ex) { this.log.LogError(ex, "Invalid image format or corrupted PNG stream."); throw; } catch (Exception ex) { this.log.LogError(ex, "An error occurred during PNG to PDF conversion."); throw; } }
额外优化建议
- 超大尺寸PNG推荐使用ImageSharp等轻量图像处理库替代System.Drawing,降低内存占用
- 调整Azure Function的内存配额,避免大文件处理时内存不足
- 添加流长度校验,确保完整读取Blob内容
内容的提问来源于stack exchange,提问作者f_olivere
相关产品推荐
相关产品推荐

