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' }));