Render, extract, and modify PDF images

This guide shows developers how to perform common PDF operations involving images. Use the PDFluent SDK to render pages, extract embedded images, and add new ones.

Add PDFluent to Cargo.toml

Image extraction is part of the base crate. No extra features are required.

toml
# Cargo.toml
[dependencies]
pdfluent = "1.0.0-beta.18"

Render a PDF page to PNG in Rust

Rasterise any PDF page to a PNG image at a chosen DPI. Works headless with no display server required.

rust
use pdfluent::{PdfDocument, ImageFormat};

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("file.pdf")?;
    let png = doc.render_page(0, 150, ImageFormat::Png)?;
    std::fs::write("page-1.png", png)?;
    Ok(())
}
  1. Open the PDF

    Open the document. Rendering is a document-level operation in PDFluent: you choose the output pattern and a page range.

    rust
    use pdfluent::prelude::*;
    
    let doc = PdfDocument::open("slides.pdf")?;
  2. Build ToImagesOptions

    ToImagesOptions::new() defaults to 150 DPI and PNG output. Override DPI and format to taste. For sharp screen previews 150 DPI is fine; print-quality output is 300 DPI.

    rust
    let opts = ToImagesOptions::new()
        .with_dpi(150)
        .with_format(ImageFormat::Png);
  3. Render a specific page range

    Use .with_pages(from, to) with 1-based inclusive bounds. Omit to render every page.

    rust
    let opts = ToImagesOptions::new()
        .with_dpi(150)
        .with_pages(1, 3); // pages 1, 2, 3
  4. Write the output files

    Pass a filename pattern. The {page} placeholder is substituted with the 1-based page number. If the pattern has no {page}, PDFluent inserts _N before the extension.

    rust
    let report = doc.to_images("page_{page}.png", opts)?;
    for path in &report.paths {
        println!("wrote {}", path.display());
    }
  5. Render to JPEG

    Change the format via .with_format(ImageFormat::Jpeg). JPEG does not carry transparency, so RGBA pixels are flattened to RGB before encoding.

    rust
    use pdfluent::prelude::*;
    
    let doc = PdfDocument::open("document.pdf")?;
    
    doc.to_images(
        "thumb_{page}.jpg",
        ToImagesOptions::new()
            .with_dpi(72)
            .with_format(ImageFormat::Jpeg),
    )?;
  • to_images is native-only. On wasm32 targets it returns Error::UnsupportedOnWasm. See WASM_SUPPORT.md §2.5.
  • Rendering is CPU-bound. Page-level parallelism isn't exposed via to_images in 1.0; for large documents, split via extract_pages and run to_images on each slice in parallel.
  • Very high DPI values (above 600) produce large image files. 150 DPI is a good default for web previews.
  • The 1.0 renderer outputs RGBA8 pixels. CMYK-preserving export lands with the renderer changes in a later release.

Render PDF pages to JPEG images in Rust

Rasterize individual pages or an entire PDF document to JPEG files at a configurable DPI and quality level.

rust
use pdfluent::{PdfDocument, ImageFormat};

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("file.pdf")?;
    let jpg = doc.render_page(0, 150, ImageFormat::Jpeg)?;
    std::fs::write("page-1.jpg", jpg)?;
    Ok(())
}
  1. Open the PDF

    Load the document.

    rust
    use pdfluent::prelude::*;
    
    let doc = PdfDocument::open("document.pdf")?;
  2. Configure JPEG output

    ToImagesOptions defaults to PNG; switch to Jpeg with with_format. JPEG quality is fixed at 90 in 1.0; user-configurable quality is tracked for a later release.

    rust
    let opts = ToImagesOptions::new()
        .with_dpi(150)
        .with_format(ImageFormat::Jpeg);
  3. Render to files

    The {page} placeholder in the pattern is substituted with the 1-based page number.

    rust
    let report = doc.to_images("page_{page}.jpg", opts)?;
  • 150 DPI is sufficient for screen display and thumbnails. Use 300 DPI for print-quality rendering. 72 DPI is native PDF resolution (1 pt = 1 px at 72 DPI).
  • JPEG is lossy. For lossless archiving use ImageFormat::Png instead.
  • Pages with transparency need a background color. Without a white background, transparent areas render as black in JPEG.
  • Rendering speed scales approximately linearly with DPI squared. A 300 DPI render takes roughly 4x longer than 150 DPI for the same page.

Generate page thumbnails from a PDF in Rust

Render small preview images of every page in a PDF. Set a fixed width or height and PDFluent calculates the other dimension automatically.

rust
use pdfluent::{PdfDocument, ImageFormat};

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("file.pdf")?;
    for i in 0..doc.page_count() {
        let png = doc.render_page(i, 72, ImageFormat::Png)?;
        std::fs::write(format!("thumb-{}.png", i + 1), png)?;
    }
    Ok(())
}
  1. Open the source PDF

    Load the document.

    rust
    use pdfluent::prelude::*;
    
    let doc = PdfDocument::open("slides.pdf")?;
  2. Build ToImagesOptions

    72 DPI is typical for thumbnails; for crisper previews, go up to 150 DPI.

    rust
    let opts = ToImagesOptions::new()
        .with_dpi(72)
        .with_format(ImageFormat::Png);
  3. Render all pages

    The {page} marker in the filename pattern is substituted with the 1-based page number.

    rust
    let report = doc.to_images("thumb_{page}.png", opts)?;
    for path in &report.paths {
        println!("wrote {}", path.display());
    }
  4. Render only the first page

    Limit the range with with_pages(from, to) using 1-based inclusive bounds.

    rust
    let opts = ToImagesOptions::new()
        .with_dpi(72)
        .with_pages(1, 1);
    let _ = doc.to_images("cover.png", opts)?;
  • render_thumbnail() uses a faster code path than render() for small output sizes. Do not use render() at low DPI as a substitute.
  • The thumbnail background defaults to white. Set background color (not available in 1.0) in ThumbnailOptions to change it.
  • Pages with very large embedded images may take longer to thumbnail because the image must be decoded before downsampling.

Extract embedded images from a PDF in Rust

Pull JPEG, PNG, and JBIG2 images out of a PDF without re-encoding. Preserves original compression and quality.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("document.pdf")?;

    for (page_idx, page) in doc.pages().enumerate() {
        for (img_idx, image) in page.images().enumerate() {
            let filename = format!(
                "page{}_img{}.{}",
                page_idx + 1,
                img_idx + 1,
                image.format().extension()
            );
            image.save(&filename)?;
            println!("Saved {} ({}x{})", filename, image.width(), image.height());
        }
    }

    Ok(())
}
  1. Open the document and iterate images per page

    page.images() returns an iterator over all XObject images on the page. Inline images are included.

    rust
    use pdfluent::PdfDocument;
    
    let doc = PdfDocument::open("document.pdf")?;
    
    for (i, page) in doc.pages().enumerate() {
        let count = page.images().count();
        println!("Page {}: {} image(s)", i + 1, count);
    }
  2. Save images in their original format

    image.save() writes the image bytes to a file without re-encoding. JPEG images stay JPEG, preserving the original quality.

    rust
    for (i, page) in doc.pages().enumerate() {
        for (j, image) in page.images().enumerate() {
            let ext = image.format().extension(); // "jpg", "png", "jbig2"
            let path = format!("output/p{}_i{}.{}", i + 1, j + 1, ext);
            image.save(&path)?;
        }
    }
  3. Convert any image to PNG

    Call image.to_png_bytes() to decode the image and re-encode as PNG, regardless of its original format.

    rust
    use std::fs;
    
    for (i, page) in doc.pages().enumerate() {
        for (j, image) in page.images().enumerate() {
            let png_bytes = image.to_png_bytes()?;
            let path = format!("output/p{}_i{}.png", i + 1, j + 1);
            fs::write(&path, &png_bytes)?;
            println!("Wrote PNG: {}", path);
        }
    }
  4. Filter images by size

    Skip thumbnails and decorative images by checking pixel dimensions before saving.

    rust
    for (i, page) in doc.pages().enumerate() {
        for (j, image) in page.images()
            .filter(|img| img.width() >= 100 && img.height() >= 100)
            .enumerate()
        {
            let path = format!("output/p{}_i{}.jpg", i + 1, j + 1);
            image.save(&path)?;
            println!(
                "Saved {}x{} image: {}",
                image.width(), image.height(), path
            );
        }
    }
  • PDF images are stored as XObjects with their own compression. JPEG images embedded in PDFs are stored as raw JPEG streams and are extracted without quality loss.
  • JBIG2 is a common format for scanned document pages (black-and-white, highly compressed). Not all image viewers support JBIG2 natively. Use to_png_bytes() for universal compatibility.
  • The dimensions returned by image.width() and image.height() are in pixels at the image resolution, not in PDF points.
  • Soft masks (alpha channels) attached to images are extracted separately. Call image.soft_mask() to access the mask XObject.

Add an image to a PDF page in Rust

Embed JPEG, PNG, or WebP images at a specific position and size on any PDF page.

rust
use pdfluent::PdfDocument;
use pdfluent::parity::{ImageInsert, InsertImageFormat};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut doc = PdfDocument::open("report.pdf")?;
    let bytes = std::fs::read("logo.png")?;
    doc.insert_image(ImageInsert::new(bytes, InsertImageFormat::Png, 0, 50.0, 700.0, 120.0, 40.0))?;
    doc.save("with-logo.pdf")?;
    Ok(())
}
  1. Load an image from a file or from bytes

    Image::from_file() reads JPEG, PNG, or WebP. Image::from_bytes() accepts the raw image bytes if you are reading from memory or a network source.

    rust
    // Load the image bytes (PNG or JPEG)
    let bytes = std::fs::read("logo.png")?;
    println!("{} bytes loaded", bytes.len());
  2. Get a mutable reference to the target page

    page_mut(n) returns a mutable handle to page n. PDF page indices are zero-based.

    rust
    use pdfluent::parity::{ImageInsert, InsertImageFormat};
    
    // Place on the first page (index 0). Coordinates are points from bottom-left.
    let insert = ImageInsert::new(bytes.clone(), InsertImageFormat::Png, 0, 50.0, 700.0, 150.0, 60.0);
  3. Position and embed the image

    PDF uses a coordinate system where (0, 0) is the bottom-left corner. Measurements are in points (1 pt = 1/72 inch). A4 is 595 x 842 pt, US Letter is 612 x 792 pt.

    rust
    doc.insert_image(insert)?;
  4. Preserve aspect ratio when sizing the image

    Use ImagePosition::fit_width() to scale the image to a given width while preserving the aspect ratio.

    rust
    use pdfluent::parity::{ImageInsert, InsertImageFormat};
    
    // Insert with reduced opacity (e.g. a watermark-style logo)
    doc.insert_image(
        ImageInsert::new(bytes, InsertImageFormat::Png, 0, 50.0, 700.0, 150.0, 60.0)
            .with_opacity(0.85),
    )?;
    doc.save("document-with-logo.pdf")?;
  • PDF coordinates have the origin at the bottom-left. If your image appears at the wrong position, check whether you are counting from the top or the bottom.
  • JPEG images are embedded as-is in the PDF stream (DCTDecode), with no quality loss. PNG images are embedded as FlateDecode streams.
  • Large images increase file size proportionally. Resize before embedding if the display size is much smaller than the source resolution.
  • Transparency in PNG images (alpha channel) is supported via a soft mask XObject. All compliant PDF readers render the transparency correctly.

Convert a color PDF to greyscale in Rust

Remap all color space operations in a PDF to DeviceGray to produce a greyscale-only file.

rust
use pdfluent::{PdfDocument, color::GreyscaleOptions};

fn main() -> pdfluent::Result<()> {
    let mut doc = PdfDocument::open("input.pdf")?;

    doc.convert_to_greyscale(GreyscaleOptions::default())?;
    doc.save("greyscale.pdf")?;
    Ok(())
}
  1. Open the source PDF

    Load the color PDF into a mutable Document.

    rust
    let mut doc = PdfDocument::open("color_input.pdf")?;
  2. Configure greyscale options

    GreyscaleOptions controls the luminance formula and whether images are converted in place.

    rust
    use pdfluent::color::GreyscaleOptions;
    
    let opts = GreyscaleOptions::default()
        .convert_images(true)       // downsample color images to greyscale
        .luminance_formula(pdfluent::color::LuminanceFormula::Bt709); // standard TV luminance
  3. Run the color conversion

    convert_to_greyscale rewrites all color operators (rg, RG, k, K, cs, CS) and converts embedded images to /DeviceGray.

    rust
    doc.convert_to_greyscale(opts)?;
  4. Verify no color resources remain

    Inspect the color spaces in the output to confirm conversion.

    rust
    for page in doc.pages() {
        for cs in page.color_spaces() {
            println!("page color space: {:?}", cs);
        }
    }
  5. Save the greyscale output

    Write the converted document. File size typically decreases because image data is smaller without color channels.

    rust
    doc.save("greyscale.pdf")?;
  • The BT.709 luminance formula (0.2126R + 0.7152G + 0.0722B) matches standard display rendering. Use BT.601 for legacy compatibility.
  • CMYK colors are first converted to RGB via a linear approximation before applying the luminance formula. For precise CMYK greyscale use an ICC-based path.
  • Spot colors (Separation and DeviceN) are not converted by default. Pass convert_spot_colors(true) to remap them.
  • Converting images to greyscale resamples the color channel; the resolution and compression of the image is preserved.