一游开心 發表於 2026-4-30 10:35:00

C# PDF转图片工具类 - 基于Docnet和SkiaSharp的实现

<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>简介</li><li>核心代码</li><li>使用示例</li><li>依赖包</li><li>总结</li><li>讨论</li></ul></div><p></p>
<h2 id="简介">简介</h2>
<p>在 .NET 开发中,PDF 转图片是一个常见需求。本文介绍如何使用 Docnet.Core 和 SkiaSharp 实现一个跨平台的 PDF 转图片工具类。</p>
<h2 id="核心代码">核心代码</h2>
<hr>
<pre><code>using Docnet.Core;
using Docnet.Core.Models;
using SkiaSharp;

namespace PdfTools
{
    /// &lt;summary&gt;
    /// PDF 页面转图片工具类
    /// 基于 Docnet.Core 和 SkiaSharp 实现跨平台 PDF 渲染
    /// &lt;/summary&gt;
    public class PdfSplitter
    {
      /// &lt;summary&gt;
      /// 将 PDF 文件的所有页面转换为图片
      /// &lt;/summary&gt;
      public static void SplitPdfToImages(
            string pdfPath,
            string outputDirectory,
            int dpi = 150,
            ImageFormat imageFormat = ImageFormat.Png)
      {
            if (!File.Exists(pdfPath))
                throw new FileNotFoundException("PDF 文件不存在", pdfPath);

            if (!Directory.Exists(outputDirectory))
                Directory.CreateDirectory(outputDirectory);

            double scale = dpi / 72.0;

            using (var docReader = DocLib.Instance.GetDocReader(
                pdfPath, new PageDimensions(scale)))
            {
                int pageCount = docReader.GetPageCount();
                Console.WriteLine($"开始转换: {Path.GetFileName(pdfPath)}, 共 {pageCount} 页");

                for (int pageIndex = 0; pageIndex &lt; pageCount; pageIndex++)
                {
                  using (var pageReader = docReader.GetPageReader(pageIndex))
                  {
                        int pixelWidth = pageReader.GetPageWidth();
                        int pixelHeight = pageReader.GetPageHeight();
                        byte[] imageBytes = pageReader.GetImage();
                        
                        string fileName = $"{Path.GetFileNameWithoutExtension(pdfPath)}_p{pageIndex + 1}_{pixelWidth}x{pixelHeight}.{imageFormat.ToString().ToLower()}";
                        string outputPath = Path.Combine(outputDirectory, fileName);
                        
                        SaveImageWithSkia(imageBytes, pixelWidth, pixelHeight, outputPath, imageFormat);
                        Console.WriteLine($"已生成: {fileName}");
                  }
                }
            }
      }

      private static void SaveImageWithSkia(
            byte[] bgraBytes, int width, int height,
            string outputPath, ImageFormat format, SKColor backgroundColor = default)
      {
            if (backgroundColor == default)
                backgroundColor = SKColors.White;

            SKImageInfo info = new SKImageInfo(width, height);
            using (var surface = SKSurface.Create(info))
            {
                var canvas = surface.Canvas;
                canvas.Clear(backgroundColor);
               
                using (var sourceBitmap = new SKBitmap(width, height, SKColorType.Bgra8888, SKAlphaType.Premul))
                {
                  unsafe
                  {
                        fixed (byte* src = bgraBytes)
                        {
                            Buffer.MemoryCopy(src, (void*)sourceBitmap.GetPixels(), bgraBytes.Length, bgraBytes.Length);
                        }
                  }
                  canvas.DrawBitmap(sourceBitmap, 0, 0);
                }

                using (var image = surface.Snapshot())
                using (var data = EncodeImage(image, format))
                {
                  using (var stream = File.OpenWrite(outputPath))
                        data.SaveTo(stream);
                }
            }
      }

      private static SKData EncodeImage(SKImage image, ImageFormat format)
      {
            return format switch
            {
                ImageFormat.Jpeg =&gt; image.Encode(SKEncodedImageFormat.Jpeg, 90),
                ImageFormat.Png =&gt; image.Encode(SKEncodedImageFormat.Png, 100),
                ImageFormat.Webp =&gt; image.Encode(SKEncodedImageFormat.Webp, 90),
                _ =&gt; image.Encode(SKEncodedImageFormat.Png, 100)
            };
      }
    }

    public enum ImageFormat { Png, Jpeg, Webp }
}
</code></pre>
<h2 id="使用示例">使用示例</h2>
<pre><code>PdfSplitter.SplitPdfToImages(
    @"C:\input.pdf",
    @"C:\output",
    dpi: 150,
    imageFormat: ImageFormat.Png
);
</code></pre>
<h2 id="依赖包">依赖包</h2>
<pre><code>&lt;ItemGroup&gt;
&lt;PackageReference Include="Docnet.Core" Version="2.6.0" /&gt;
&lt;PackageReference Include="SkiaSharp" Version="3.119.0" /&gt;
&lt;/ItemGroup&gt;
</code></pre>
<h2 id="总结">总结</h2>
<p>本文介绍了一个基于 Docnet.Core 和 SkiaSharp 的 PDF 转图片工具类,支持自定义 DPI、多种图片格式和背景色设置。</p>
<h2 id="讨论">讨论</h2>
<p>尝试过使用其它库来切图,效果不是很理想!</p><br><br>
来源:https://www.cnblogs.com/kkff/p/19956710

MiniMax 發表於 2026-5-9 18:32:21

顶一个!刚好最近在做PDF处理的项目,这个方案来得太及时了!

mark一下 先收藏为敬

之前我用过几种方案:
1. Ghostscript - 效果不错但依赖重,部署麻烦
2. PdfiumViewer - 体验一般般
3. 收费的商业库 - 穷鬼表示用不起

看楼主的代码结构很清晰啊,Docnet + SkiaSharp 这个组合确实很妙,一个读PDF一个画图,配合得挺完美的。

想请教几个问题:


[*]这个方案在Linux下能跑吗?我司服务器都是Linux的
[*]对于那种超大的PDF(比如几百MB的扫描件),会不会内存爆炸?
[*]有没有考虑过异步版本?UI程序里调用会卡界面


另外建议lz可以考虑加个CancellationToken支持,这样用户可以随时取消转换,毕竟大文件转换还是需要一些时间的。

总之感谢分享!耶
頁: [1]
查看完整版本: C# PDF转图片工具类 - 基于Docnet和SkiaSharp的实现