Skip to main content

Pixmap

Struct Pixmap 

Source
pub struct Pixmap { /* private fields */ }
Expand description

A premultiplied RGBA8 image: four bytes per pixel, row-major, no padding.

Premultiplied means each colour byte has already been scaled by the alpha byte, which is what both rasterizers produce and what makes a source-over blit a plain weighted sum. Pixmap::to_straight_bgra undoes it at the output boundary, because the oracle’s PNGs and MD5s are taken over a straight-alpha BGRA buffer.

use pdfrum_render::Pixmap;

let red = Pixmap::filled(2, 2, peniko::Color::from_rgba8(255, 0, 0, 255));
assert_eq!((red.width(), red.height()), (2, 2));

// Premultiplied on the inside; straight at the output boundary.
assert_eq!(red.pixel(0, 0), Some([255, 0, 0, 255]));
assert_eq!(&red.to_straight_rgb()[..3], &[255, 0, 0]);

Implementations§

Source§

impl Pixmap

Source

pub fn new(width: u32, height: u32) -> Self

A fully transparent pixmap of the given size.

A zero in either axis is legal and yields an empty buffer, because the engine reaches this with clipped-away geometry all the time.

A size whose buffer cannot be allocated yields an empty pixmap rather than ending the process — see Pixmap::try_new, which is this without the fallback. Check Pixmap::width rather than assuming the size you asked for.

use pdfrum_render::Pixmap;

let empty = Pixmap::new(2, 2);
assert_eq!(empty.pixel(0, 0), Some([0, 0, 0, 0]));
// A zero in either axis is legal: clipped-away geometry reaches this.
assert!(Pixmap::new(0, 5).data().is_empty());
// So is a size no allocator can meet; it comes back empty.
assert_eq!(Pixmap::new(131_071, 131_071).width(), 0);
Source

pub fn try_new(width: u32, height: u32) -> Option<Self>

A fully transparent pixmap of the given size, or None when the buffer it needs cannot be allocated.

width * height * 4 is the whole of the request, and both axes can come from a file: an image declares its own /Width and /Height, and the largest pair the sample unpacker accepts asks for 68 GB. Two refusals, in order: the area cap first (pdfrum_page::image_area_is_workable), because Linux overcommit lets try_reserve_exact succeed for tens of gigabytes and the resize below then zeros them; then the fallible reserve, for a size inside the cap that this allocator still cannot meet. A Vec that cannot be grown calls handle_alloc_error, which aborts — no unwind, so no catch_unwind above it helps and no Result can carry it.

Pixmap::new is this with the answer taken as an empty pixmap, which is what a caller that has no way to report the failure wants.

use pdfrum_render::Pixmap;

assert!(Pixmap::try_new(2, 2).is_some());
// Nothing has this much memory, and asking for it must not end the
// process.
assert!(Pixmap::try_new(131_071, 131_071).is_none());
Source

pub fn filled(width: u32, height: u32, color: Color) -> Self

A pixmap of the given size, every pixel set to color.

use pdfrum_render::Pixmap;

let red = Pixmap::filled(2, 1, peniko::Color::from_rgba8(255, 0, 0, 255));
assert_eq!(red.data(), &[255, 0, 0, 255, 255, 0, 0, 255]);
Source

pub fn from_vec(width: u32, height: u32, data: Vec<u8>) -> Option<Self>

Wrap an existing premultiplied RGBA8 buffer.

Returns None when data is not exactly width * height * 4 bytes.

use pdfrum_render::Pixmap;

assert!(Pixmap::from_vec(1, 1, vec![255, 0, 0, 255]).is_some());
// Not exactly `width * height * 4` bytes.
assert!(Pixmap::from_vec(1, 1, vec![255, 0, 0]).is_none());
Source

pub fn width(&self) -> u32

The pixmap’s width in pixels.

use pdfrum_render::Pixmap;

assert_eq!(Pixmap::new(3, 2).width(), 3);
Source

pub fn height(&self) -> u32

The pixmap’s height in pixels.

use pdfrum_render::Pixmap;

assert_eq!(Pixmap::new(3, 2).height(), 2);
Source

pub fn data(&self) -> &[u8] ⓘ

The premultiplied RGBA8 bytes, row-major.

use pdfrum_render::Pixmap;

// Four bytes per pixel, row-major, no padding.
assert_eq!(Pixmap::new(2, 2).data().len(), 16);
Source

pub fn data_mut(&mut self) -> &mut [u8] ⓘ

The premultiplied RGBA8 bytes, mutably.

use pdfrum_render::Pixmap;

let mut p = Pixmap::new(1, 1);
p.data_mut().copy_from_slice(&[255, 0, 0, 255]);
assert_eq!(p.pixel(0, 0), Some([255, 0, 0, 255]));
Source

pub fn into_data(self) -> Vec<u8> ⓘ

Consume the pixmap, yielding its premultiplied RGBA8 bytes.

use pdfrum_render::Pixmap;

assert_eq!(Pixmap::filled(1, 1, peniko::Color::from_rgba8(255, 0, 0, 255)).into_data(), vec![255, 0, 0, 255]);
Source

pub fn cropped(&self, x: u32, y: u32, width: u32, height: u32) -> Self

A sub-rectangle of this pixmap.

Bytes outside this pixmap are left transparent. Used to lift a non-isolated group’s backdrop out of a parent snapshot without a second GPU round trip’s worth of host pixels.

use pdfrum_render::Pixmap;

let mut src = Pixmap::new(4, 2);
src.set_pixel(1, 0, [1, 2, 3, 255]);
let out = src.cropped(1, 0, 2, 1);
assert_eq!(out.pixel(0, 0), Some([1, 2, 3, 255]));
assert_eq!(out.pixel(1, 0), Some([0, 0, 0, 0]));
Source

pub fn pixel(&self, x: u32, y: u32) -> Option<[u8; 4]>

The premultiplied RGBA bytes of one pixel, or None when out of range.

use pdfrum_render::Pixmap;

let red = Pixmap::filled(2, 2, peniko::Color::from_rgba8(255, 0, 0, 255));
assert_eq!(red.pixel(1, 1), Some([255, 0, 0, 255]));
// Out of range, not a panic.
assert_eq!(red.pixel(2, 0), None);
Source

pub fn set_pixel(&mut self, x: u32, y: u32, px: [u8; 4])

Overwrite one pixel with premultiplied RGBA bytes. Out of range is a no-op — the shading rasterizers clip by construction and a stray write must not panic on a crafted file.

use pdfrum_render::Pixmap;

let mut p = Pixmap::new(2, 2);
p.set_pixel(1, 0, [255, 0, 0, 255]);
assert_eq!(p.pixel(1, 0), Some([255, 0, 0, 255]));
// Out of range is a no-op: a crafted file must not panic here.
p.set_pixel(9, 9, [255, 255, 255, 255]);
Source

pub fn fill(&mut self, color: Color)

Set every pixel to color.

use pdfrum_render::Pixmap;

let mut p = Pixmap::new(2, 2);
p.fill(peniko::Color::from_rgba8(255, 0, 0, 255));
assert_eq!(p.pixel(0, 0), Some([255, 0, 0, 255]));
Source

pub fn multiply_alpha(&mut self, alpha: f32)

Scale every channel by alpha.

The scalar is truncated to a byte (0.5 becomes 127, not 128) and the per-pixel product truncates too. On a premultiplied buffer all four channels scale, where the oracle scales only the alpha byte of a straight one — the same image either way.

use pdfrum_render::Pixmap;

let mut p = Pixmap::filled(1, 1, peniko::Color::from_rgba8(255, 0, 0, 255));
// The scalar truncates to a byte: `0.5` is 127, not 128.
p.multiply_alpha(0.5);
assert_eq!(p.pixel(0, 0), Some([127, 0, 0, 127]));
Source

pub fn remove_backdrop(&mut self, backdrop: &Self)

[oracle-bug] Remove a non-isolated group’s initial backdrop from its finished pixels (ISO 32000 §11.4.6, §11.6.6).

The group’s buffer starts as a copy of the page beneath it, so that copy would be counted a second time when the group is composited back. The spec’s formula, per channel and premultiplied:

C = Cn + (Cn - C0) * (a0 / agn - a0)

C0/a0 are the backdrop’s and Cn/agn the group’s. A pixel that still equals the backdrop is empty. Over an opaque backdrop alpha cannot rise, so a pixel the group replaced keeps the group’s colour — otherwise a later ca < 1 multiply has nothing to fade. backdrop must match this pixmap’s dimensions; a mismatch is a no-op, as for Self::multiply_alpha_mask.

use pdfrum_render::Pixmap;

let backdrop = Pixmap::filled(1, 1, peniko::Color::from_rgba8(255, 0, 0, 255));
let mut group = backdrop.clone();

// The group painted nothing over its backdrop, so removing it
// leaves a transparent pixel -- the same image once composited back.
group.remove_backdrop(&backdrop);
assert_eq!(group.pixel(0, 0), Some([0, 0, 0, 0]));
Source

pub fn knockout_over(&mut self, next: &Self)

[oracle-bug] Lay next over this pixmap under knockout composition (ISO 32000 §11.6.6): where next has any coverage it replaces what is here, rather than blending over it.

That is the whole of the knockout rule stated pixel-wise. Each object in a knockout group composites against the group’s initial backdrop, so an earlier object’s contribution at a pixel a later one also covers never reaches the result; only the coverage-weighted mix at the edges of the later object’s own antialiasing keeps any of it.

A mismatch in dimensions is a no-op, on the same invariant as Self::multiply_alpha_mask.

use pdfrum_render::Pixmap;

let mut base = Pixmap::filled(1, 1, peniko::Color::from_rgba8(0, 0, 255, 255));
let next = Pixmap::filled(1, 1, peniko::Color::from_rgba8(255, 0, 0, 255));

// Full coverage replaces rather than blending.
base.knockout_over(&next);
assert_eq!(base.pixel(0, 0), Some([255, 0, 0, 255]));
Source

pub fn multiply_alpha_mask(&mut self, mask: &AlphaMask)

Multiply every channel by a coverage mask, PDFium’s MultiplyAlphaMask. The mask must match the pixmap’s dimensions exactly — an invariant, not a preference: a mismatched mask silently disables masking on both backends, so the engine never emits one.

use pdfrum_render::Pixmap;

use pdfrum_render::AlphaMask;

let mut p = Pixmap::filled(1, 1, peniko::Color::from_rgba8(255, 0, 0, 255));
p.multiply_alpha_mask(&AlphaMask::filled(1, 1, 128));
assert_eq!(p.pixel(0, 0), Some([128, 0, 0, 128]));

// A mismatched mask is a no-op, which is why the engine never emits one.
p.multiply_alpha_mask(&AlphaMask::filled(4, 4, 0));
assert_eq!(p.pixel(0, 0), Some([128, 0, 0, 128]));
Source

pub fn luminosity_mask(&self) -> AlphaMask

The 8-bit luminosity of every pixel under the oracle’s gray weights, for a soft mask’s luminosity readback (ISO 32000 §11.6.5.2).

Deliberately not either backend’s luminance helper: both use BT.709 coefficients and the oracle uses NTSC ones on a 0..100 integer scale.

use pdfrum_render::Pixmap;

// The oracle's NTSC weights on a 0..100 integer scale, not BT.709.
let blue = Pixmap::filled(1, 1, peniko::Color::from_rgba8(0, 0, 255, 255));
assert_eq!(blue.luminosity_mask().data(), &[28]);
Source

pub fn alpha_mask(&self) -> AlphaMask

The alpha channel on its own, for an alpha-type soft mask.

use pdfrum_render::Pixmap;

let red = Pixmap::filled(1, 1, peniko::Color::from_rgba8(255, 0, 0, 255));
assert_eq!(red.alpha_mask().data(), &[255]);
Source

pub fn to_straight_bgra(&self) -> Vec<u8> ⓘ

The straight-alpha BGRA bytes the oracle hashes and encodes.

Straight-alpha BGRA bytes, four per pixel.

use pdfrum_render::Pixmap;

let red = Pixmap::filled(1, 1, peniko::Color::from_rgba8(255, 0, 0, 255));
assert_eq!(red.to_straight_bgra(), vec![0, 0, 255, 255]);
Source

pub fn to_opaque_bgra(&self) -> Vec<u8> ⓘ

Straight BGRA with the alpha byte forced to 0xFF.

A page with no transparency is rendered into a 32bpp buffer whose fourth byte is padding, and a hash taken over these bytes sees that padding as 0xFF rather than as whatever the render left there.

use pdfrum_render::Pixmap;

let clear = Pixmap::new(1, 1);
assert_eq!(clear.to_opaque_bgra(), vec![0, 0, 0, 255]);
Source

pub fn to_straight_rgb(&self) -> Vec<u8> ⓘ

The straight-alpha RGB bytes, three per pixel — the shape the oracle’s PNG encoder writes for a page with no transparency.

use pdfrum_render::Pixmap;

let red = Pixmap::filled(1, 1, peniko::Color::from_rgba8(255, 0, 0, 255));
assert_eq!(red.to_straight_rgb(), vec![255, 0, 0]);
Source

pub fn to_straight_rgba(&self) -> Vec<u8> ⓘ

The straight-alpha RGBA bytes, four per pixel — the shape the oracle’s PNG encoder writes for a page that has transparency.

use pdfrum_render::Pixmap;

let red = Pixmap::filled(1, 1, peniko::Color::from_rgba8(255, 0, 0, 255));
assert_eq!(red.to_straight_rgba(), vec![255, 0, 0, 255]);
Source§

impl Pixmap

Encoding to PNG, behind the png feature.

Source

pub fn encode_png(&self) -> Result<Vec<u8>, Error>

Available on crate feature png only.

The pixmap as a PNG file’s bytes: eight-bit RGBA, unpremultiplied.

The alpha is undone on the way out because PNG’s is straight (ISO 15948 §6.2) and this buffer’s is premultiplied. A viewer multiplies by alpha when it composites, so writing the premultiplied bytes would darken every partly transparent pixel a second time – a 50% red would leave here as 128,0,0,128 and land as 64,0,0. Fully opaque pixels are identical either way, which is why this is invisible on most pages.

§Errors

Error::Png when the encoder refuses the dimensions.

Source

pub fn save_png(&self, path: impl AsRef<Path>) -> Result<(), Error>

Available on crate feature png only.

Writes the pixmap to path as a PNG file.

§Errors

Error::Png as Pixmap::encode_png, and Error::Io when the file cannot be written.

Trait Implementations§

Source§

impl Clone for Pixmap

Source§

fn clone(&self) -> Pixmap

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Pixmap

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for Pixmap

Source§

impl PartialEq for Pixmap

Source§

fn eq(&self, other: &Pixmap) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Pixmap

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.