# Pdfium Impl Output Formats

> Use when turning a rendered pdfium-render PdfBitmap into a PNG or JPEG file, into an image crate DynamicImage, or into raw pixel bytes, and when the exported colors look wrong with red and blue swapped. Prevents the BGRA-versus-RGBA channel-swap bug and the removed as_bytes method, and prevents image-crate version mismatches. Covers as_image, as_rgba_bytes, as_raw_bytes, bytes_required_for_size, saving via DynamicImage with ImageFormat, and the image_025 feature coupling. Keywords: pdfium-render save PDF as PNG, export PDF page to JPEG, PdfBitmap as_image, as_rgba_bytes, as_raw_bytes, DynamicImage, image crate, ImageFormat, save_with_format, BGRA RGBA channel swap, red and blue swapped, colors wrong, image looks blue, as_bytes removed, how do I export a PDF page as an image

- Skill: `impertio-studio/pdfium-impl-output-formats` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add impertio-studio/pdfium-impl-output-formats`
- Raw SKILL.md: https://api.skillmd.com/api/skills/impertio-studio/pdfium-impl-output-formats/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- License: MIT
- Author: Impertio-Studio (https://skillmd.com/u/impertio-studio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/impertio-studio/pdfium-impl-output-formats

---


# 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 a `DynamicImage`: it performs the correct
  channel conversion internally.
- ALWAYS use `as_rgba_bytes()` when you need raw bytes for the `image` crate
  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 by `bitmap.format()`.

### Core rules

- NEVER call `as_bytes()`: it was removed in 0.9.0. Use `as_raw_bytes()` or
  `as_rgba_bytes()`.
- ALWAYS keep the `pdfium-render` `image_*` feature matched to the `image`
  crate version in `Cargo.toml`, or `as_image()` returns a `DynamicImage` your
  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?

```text
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?

```text
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

```rust
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

```rust
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

```rust
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

```rust
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:

```toml
[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 with `as_raw_bytes()` (native order) or `as_rgba_bytes()`
  (normalized RGBA8).
- `as_rgba_bytes()` was added in 0.8.16. On older versions only `as_raw_bytes`
  and the now-removed `as_bytes` existed.
- The `image_023` / `image_024` / `image_025` feature family was introduced in
  0.8.26. Before that the `image` crate version was fixed by the crate.
- 0.9.2 changed `PdfBitmap::empty()` and `from_bytes()`: the `bindings`
  argument was dropped, `from_bytes()` became safe, and an `unsafe`
  `from_bytes_unchecked()` was added. On 0.9.1 and earlier `from_bytes()` is
  `unsafe` and both take a `bindings` argument.

## 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 for `PdfBitmap`,
  `PdfBitmapFormat`, and the `image` crate 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 the `PdfBitmap` with `PdfRenderConfig`
  and `render_with_config`.
- `pdfium-errors-runtime`: the `PdfiumError` variants, including the
  byte-order bug class.
- `pdfium-core-coordinates`: `Pixels` versus `PdfPoints` and rendered raster
  dimensions.

