You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 07:42:32