pdfium-impl-output-formats
Rendering a page produces a PdfBitmap. This skill turns that bitmap into a
deliverable: a PNG or JPEG file, an image crate DynamicImage, or raw pixel
bytes for a downstream pipeline. The one trap that dominates this area is byte
order: a PDFium bitmap is BGRA-family, the image crate expects RGBA, and
mixing them swaps red and blue.
Scope: bitmap-to-output conversion. For producing the PdfBitmap (the
PdfRenderConfig builder, render_with_config) see pdfium-syntax-rendering.
Quick Reference
The output API (0.9.x)
| You want | Call | Returns |
|---|---|---|
an image crate image |
bitmap.as_image() |
Result<DynamicImage, PdfiumError> |
| normalized RGBA8 bytes | bitmap.as_rgba_bytes() |
Vec<u8> |
| native-order raw bytes | bitmap.as_raw_bytes() |
Vec<u8> |
| pixel width / height | bitmap.width() / bitmap.height() |
Pixels |
| the source pixel format | bitmap.format() |
Result<PdfBitmapFormat, _> |
| buffer size for a size | PdfBitmap::bytes_required_for_size(w, h) |
usize |
The byte-order rule
A native PdfBitmap buffer is BGRA-ordered (PdfBitmapFormat::BGRA, BGRx,
BGR, or Gray). The image crate expects RGBA / RGB order.
- ALWAYS use
as_image()to get aDynamicImage: it performs the correct channel conversion internally. - ALWAYS use
as_rgba_bytes()when you need raw bytes for theimagecrate or any RGBA consumer: it normalizes to RGBA8 regardless of source format. - NEVER feed
as_raw_bytes()output to an RGBA consumer: those bytes are in the bitmap's native BGRA order, so red and blue come out swapped (issue #50). - Use
as_raw_bytes()only when the consumer expects the exact native format reported bybitmap.format().
Core rules
- NEVER call
as_bytes(): it was removed in 0.9.0. Useas_raw_bytes()oras_rgba_bytes(). - ALWAYS keep the
pdfium-renderimage_*feature matched to theimagecrate version inCargo.toml, oras_image()returns aDynamicImageyour code cannot use. - ALWAYS convert to RGB with
into_rgb8()before saving JPEG: JPEG has no alpha channel.
Decision Trees
Which accessor do I use?
What does the consumer expect?
An image file (PNG / JPEG) -> as_image(), then save (see Patterns)
An image crate DynamicImage -> as_image()
Raw RGBA8 bytes (web canvas, GPU,
another RGBA library) -> as_rgba_bytes()
Raw bytes in the exact native
PDFium format -> as_raw_bytes() + check format()
Which file format do I save?
Does the output need transparency or lossless pixels?
YES -> PNG. save_with_format(path, ImageFormat::Png). Alpha is preserved.
NO, it is a photo-like page and size matters
-> JPEG. into_rgb8() first (drops alpha), then
save_with_format(path, ImageFormat::Jpeg).
Patterns
These examples assume bitmap is a PdfBitmap from
page.render_with_config(&config)? (see pdfium-syntax-rendering). Functions
return Result<(), Box<dyn std::error::Error>> because saving mixes
PdfiumError and the image crate's ImageError.
Pattern 1: Save a page as PNG
use pdfium_render::prelude::*;
fn save_png(bitmap: &PdfBitmap, path: &str)
-> Result<(), Box<dyn std::error::Error>> {
// as_image() converts BGRA to the correct channel order.
bitmap.as_image()?.save_with_format(path, image::ImageFormat::Png)?;
Ok(())
}
as_image() returns a Result<DynamicImage, PdfiumError>. The ? propagates
the error; DynamicImage::save_with_format handles PNG encoding.
Pattern 2: Save a page as JPEG
use pdfium_render::prelude::*;
fn save_jpeg(bitmap: &PdfBitmap, path: &str)
-> Result<(), Box<dyn std::error::Error>> {
bitmap
.as_image()? // DynamicImage, correct color order
.into_rgb8() // drop the alpha channel: JPEG has none
.save_with_format(path, image::ImageFormat::Jpeg)?;
Ok(())
}
This is the canonical shape from the pdfium-render export.rs example.
into_rgb8() is required for JPEG; skipping it on an image with alpha is an
error.
Pattern 3: Raw RGBA bytes for a pipeline
use pdfium_render::prelude::*;
/// Hand pixels to a consumer that expects tightly packed RGBA8 rows.
fn rgba_pixels(bitmap: &PdfBitmap) -> (Vec<u8>, Pixels, Pixels) {
// as_rgba_bytes() normalizes to RGBA8 regardless of the source format.
let bytes = bitmap.as_rgba_bytes();
(bytes, bitmap.width(), bitmap.height())
}
For a web canvas, a GPU texture upload, or another RGBA library, this is the
correct accessor. NEVER substitute as_raw_bytes() here.
Pattern 4: Native bytes and buffer sizing
use pdfium_render::prelude::*;
fn native_buffer(bitmap: &PdfBitmap)
-> Result<(Vec<u8>, PdfBitmapFormat), PdfiumError> {
let format = bitmap.format()?; // BGRA / BGRx / BGR / Gray
let bytes = bitmap.as_raw_bytes(); // bytes in exactly that format
Ok((bytes, format))
}
fn expected_size(width: Pixels, height: Pixels) -> usize {
// The buffer length a bitmap of this size needs.
PdfBitmap::bytes_required_for_size(width, height)
}
Use as_raw_bytes() only when the consumer is told the exact
PdfBitmapFormat; otherwise the BGRA order will be misread.
Pattern 5: Match the image crate feature
Cargo.toml must keep the image crate version and the pdfium-render
image_* feature in step:
[dependencies]
pdfium-render = { version = "0.9", features = ["image_025"] }
image = "0.25"
image_025 pairs with image 0.25, image_024 with 0.24, image_023 with
0.23. image_latest tracks the newest. A mismatch makes as_image() return a
DynamicImage from a different image version than the one your code imports,
which is a distinct, incompatible type.
Version Notes: 0.8.x vs 0.9.x
PdfBitmap::as_bytes()was deprecated in 0.8.16 and REMOVED in 0.9.0. Replace it withas_raw_bytes()(native order) oras_rgba_bytes()(normalized RGBA8).as_rgba_bytes()was added in 0.8.16. On older versions onlyas_raw_bytesand the now-removedas_bytesexisted.- The
image_023/image_024/image_025feature family was introduced in 0.8.26. Before that theimagecrate version was fixed by the crate. - 0.9.2 changed
PdfBitmap::empty()andfrom_bytes(): thebindingsargument was dropped,from_bytes()became safe, and anunsafefrom_bytes_unchecked()was added. On 0.9.1 and earlierfrom_bytes()isunsafeand both take abindingsargument.
Anti-Patterns (summary)
| Anti-pattern | Why it fails | Fix |
|---|---|---|
as_raw_bytes() into an RGBA consumer |
native bytes are BGRA: red and blue swap | as_rgba_bytes() or as_image() |
as_bytes() |
removed in 0.9.0 | as_raw_bytes() / as_rgba_bytes() |
saving an RGBA image as JPEG without into_rgb8() |
JPEG has no alpha channel | into_rgb8() before save_with_format |
image crate version not matching the image_* feature |
as_image() yields an incompatible DynamicImage type |
align Cargo.toml |
Full failure transcripts and fixes are in references/anti-patterns.md.
Reference Files
references/methods.md: exact verified signatures forPdfBitmap,PdfBitmapFormat, and theimagecrate methods used here.references/examples.md: complete, verified Rust programs for PNG export, JPEG export, raw-byte handoff, and feature configuration.references/anti-patterns.md: each output failure with the cause and the corrected code.
Related Skills
pdfium-syntax-rendering: producing thePdfBitmapwithPdfRenderConfigandrender_with_config.pdfium-errors-runtime: thePdfiumErrorvariants, including the byte-order bug class.pdfium-core-coordinates:PixelsversusPdfPointsand rendered raster dimensions.