Table of Contents

Render PDF Pages to Images

LM-Kit.NET ships two complementary APIs for turning PDF pages into raster images. They share the same underlying renderer; they differ in surface area and ergonomics. This guide explains the decision tree, then walks through the production-grade PdfRenderer workflow end to end.


🎯 Pick the right API

Need Use
Drop-in convenience, one or two lines of code, file paths only PdfToImage
Full format coverage (PNG / JPEG / WebP / BMP / TIFF / TGA / PNM), async, CancellationToken, progress, Stream and byte[] input/output, encrypted PDFs PdfRenderer
Pack every rendered page into one multi-page TIFF PdfRenderer.SavePagesAsMultipageTiff
The image data, but no file on disk PdfRenderer.RenderPageAs{Format}Bytes

If you are unsure, start with PdfRenderer. Its defaults match PdfToImage for the common case, and you get the production knobs for free as your needs grow.


⚡ The 30-second version (PdfToImage)

PdfToImage is the quick-start API. PNG, JPEG, BMP only. Synchronous. No cancellation. No progress.

using LMKit.Document.Conversion;

// Render one page to a PNG file
PdfToImage.RenderToFile("report.pdf", pageIndex: 0, "page-1.png");

// Render every page; outputs are named report-001.png, report-002.png, ...
PdfToImage.RenderToFiles("report.pdf", "thumbs/");

That is the whole API. When you need more — Async, a different format, a Stream output, an encrypted PDF, a clean cancellation — switch to PdfRenderer.


🏭 The production version (PdfRenderer)

PdfRenderer is a static class in LMKit.Document.Pdf. Every method comes in sync and async flavours, takes a CancellationToken, and lets you pick path / Stream / byte[] for input. Single-page methods write to a file, a Stream, or return a byte[]. Multi-page methods write one file per page or pack everything into a single multi-page TIFF.

Encoder knobs (PNG compression level, JPEG / WebP quality, BMP / TGA RLE) live on the method signature; render-universal knobs (zoom, pixel format, orientation, annotations, page range, password) live on a PdfRenderOptions instance shared by every overload.


✨ Format matrix

Format Single page → file Single page → Stream Single page → byte[] Many pages → directory Many pages → one file
PNG SavePageAsPng SavePageAsPng(... Stream) RenderPageAsPngBytes SavePagesAsPngs
JPEG SavePageAsJpeg SavePageAsJpeg(... Stream) RenderPageAsJpegBytes SavePagesAsJpegs
WebP SavePageAsWebp SavePageAsWebp(... Stream) RenderPageAsWebpBytes SavePagesAsWebps
BMP SavePageAsBmp SavePageAsBmp(... Stream) RenderPageAsBmpBytes SavePagesAsBmps
TIFF SavePageAsTiff SavePageAsTiff(... Stream) RenderPageAsTiffBytes SavePagesAsTiffs SavePagesAsMultipageTiff
TGA SavePageAsTga SavePageAsTga(... Stream) RenderPageAsTgaBytes SavePagesAsTgas
PNM SavePageAsPnm SavePageAsPnm(... Stream) RenderPageAsPnmBytes SavePagesAsPnms

Every method also has an Async variant. Cell entries marked are not supported (no native multi-page container for that format).


🚀 Common recipes

Render every page to PNG files in a directory

using LMKit.Document.Pdf;

// Streams pages one at a time; memory stays flat regardless of page count
IReadOnlyList<string> files = PdfRenderer.SavePagesAsPngs(
    "report.pdf",
    outputDirectory: "thumbs/",
    options: new PdfRenderOptions { Zoom = 2.0 });

Stream a single page directly into an HTTP response

using LMKit.Document.Pdf;
using Microsoft.AspNetCore.Mvc;

[HttpGet("/preview/{pdfId}/{pageIndex:int}")]
public IActionResult Preview(string pdfId, int pageIndex)
{
    byte[] bytes = PdfRenderer.RenderPageAsJpegBytes(
        LookupPdfPath(pdfId), pageIndex, quality: 85);
    return File(bytes, "image/jpeg");
}

Render an encrypted PDF

using LMKit.Document.Pdf;

var options = new PdfRenderOptions { Password = "s3cret!" };
PdfRenderer.SavePageAsPng("locked.pdf", 0, "page-1.png", options);

Render with progress and cancellation

using System.Threading;
using LMKit.Document.Pdf;

using var cts = new CancellationTokenSource();
var progress = new Progress<PdfRenderProgressEventArgs>(e =>
{
    Console.WriteLine($"[{(e.PageIndex + 1)}/{e.TotalPages}] -> {e.OutputPath}");
});

await PdfRenderer.SavePagesAsPngsAsync(
    "report.pdf",
    outputDirectory: "thumbs/",
    progress: progress,
    cancellationToken: cts.Token);

Pack every page into a single archival multi-page TIFF

using LMKit.Document.Pdf;
using LMKit.Media.Image;

var options = new PdfRenderOptions
{
    Zoom = 300.0 / 72.0,             // 300 DPI
    PixelFormat = ImagePixelFormat.GRAY8, // compact archival grayscale
};

PdfRenderer.SavePagesAsMultipageTiff("scan.pdf", "archive.tif", options);
Note

SavePagesAsMultipageTiff must hold every page in memory because the native TIFF writer emits a single shared header. For very large documents, prefer SavePagesAsTiffs (one file per page) plus an external assembly step.

Render a PDF that lives in a stream or byte array

using System.IO;
using LMKit.Document.Pdf;
using LMKit.Media.Image;

// From a Stream (HTTP response body, blob storage, etc.)
using FileStream pdfStream = File.OpenRead("report.pdf");
using ImageBuffer page = PdfRenderer.RenderPage(pdfStream, pageIndex: 0);
page.SaveAsPng("page-1.png");

// From a byte array
byte[] pdfBytes = File.ReadAllBytes("report.pdf");
foreach ((int pageIndex, ImageBuffer img) in PdfRenderer.RenderPages(pdfBytes))
{
    using (img)
    {
        img.SaveAsWebp($"page-{pageIndex + 1}.webp", quality: 80);
    }
}

Probe page dimensions before rendering

using LMKit.Document.Pdf;

int pages = PdfRenderer.GetPageCount("report.pdf");
(int w, int h) = PdfRenderer.GetRenderedPageSize(
    "report.pdf",
    pageIndex: 0,
    new PdfRenderOptions { Zoom = 2.0 });
Console.WriteLine($"Document has {pages} pages; first page renders to {w}x{h} px.");

🎚️ Tuning quality vs. size

The render pipeline applies in this order: rasterise → optionally convert format → encode. The biggest knobs are zoom, pixel format, and the per-format encoder quality.

Knob Where Effect
Zoom PdfRenderOptions 1.0 = 96 DPI (matches PdfToImage); 2.0 = 192 DPI; 300.0/72.0 ≈ 300 DPI archival.
PixelFormat PdfRenderOptions RGB24 (default) for color; GRAY8 for compact archival scans; RGBA32 for compositing.
compressionLevel Per-method (PNG) 0-9. Higher = smaller file, slower encode. Default 6 is a good baseline.
quality Per-method (JPEG / WebP) 1-100. 85 is a good default; 92+ for prints; 65-75 for thumbnails.
rle Per-method (BMP / TGA) true enables RLE compression for legacy pipelines.

🧪 Verifying renders without writing to disk

PdfRenderer.RenderPageAs*Bytes is useful in tests: the returned byte array can be parsed in-memory or compared to a golden snapshot without touching the filesystem.

byte[] png = PdfRenderer.RenderPageAsPngBytes("report.pdf", 0);
Assert.That(png[0..4], Is.EqualTo(new byte[] { 0x89, (byte)'P', (byte)'N', (byte)'G' }));

📚 Additional resources

Share