use image::{DynamicImage, ImageFormat, RgbaImage};
use crate::{
PdfiumColor,
error::{PdfiumError, PdfiumResult},
lib, pdfium_constants,
pdfium_types::{BitmapHandle, FPDF_BITMAP, Handle},
try_lib,
};
#[derive(Debug, Clone)]
pub struct PdfiumBitmap {
handle: BitmapHandle,
}
impl PdfiumBitmap {
pub(crate) fn new_from_handle(handle: FPDF_BITMAP) -> PdfiumResult<Self> {
if handle.is_null() {
Err(PdfiumError::NullHandle)
} else {
Ok(Self {
handle: Handle::new(handle, Some(close_bitmap)),
})
}
}
pub fn empty(width: i32, height: i32, format: PdfiumBitmapFormat) -> PdfiumResult<Self> {
let lib = try_lib()?;
lib.FPDFBitmap_CreateEx(
width,
height,
format.into(),
None, 0, )
}
pub fn fill(&self, color: &PdfiumColor) -> PdfiumResult<()> {
let lib = lib();
lib.FPDFBitmap_FillRect(
self,
0,
0,
lib.FPDFBitmap_GetWidth(self),
lib.FPDFBitmap_GetHeight(self),
color.into(),
)
}
#[inline]
pub fn width(&self) -> i32 {
lib().FPDFBitmap_GetWidth(self) as i32
}
#[inline]
pub fn height(&self) -> i32 {
lib().FPDFBitmap_GetHeight(self) as i32
}
#[inline]
pub fn format(&self) -> PdfiumBitmapFormat {
lib().FPDFBitmap_GetFormat(self).into()
}
pub fn as_raw_bytes<'a>(&self) -> &'a [u8] {
let lib = lib();
let buffer = lib.FPDFBitmap_GetBuffer(self.handle.handle());
let len = lib.FPDFBitmap_GetStride(self) * lib.FPDFBitmap_GetHeight(self);
unsafe { std::slice::from_raw_parts(buffer as *const u8, len as usize) }
}
pub fn as_rgba_bytes(&self) -> PdfiumResult<Vec<u8>> {
match self.format() {
PdfiumBitmapFormat::Bgra => Ok(self
.as_raw_bytes()
.chunks_exact(4)
.flat_map(|pixel| [pixel[2], pixel[1], pixel[0], pixel[3]]) .collect()),
PdfiumBitmapFormat::Bgr => Ok(self
.as_raw_bytes()
.chunks_exact(3)
.flat_map(|pixel| [pixel[2], pixel[1], pixel[0], 255]) .collect()),
PdfiumBitmapFormat::Gray => Ok(self
.as_raw_bytes()
.chunks_exact(1)
.flat_map(|pixel| [pixel[0], pixel[0], pixel[0], 255]) .collect()),
PdfiumBitmapFormat::Unknown
| PdfiumBitmapFormat::Bgrx
| PdfiumBitmapFormat::BgraPremul => Err(PdfiumError::UnsupportedImageFormat),
}
}
pub fn as_rgba8_image(&self) -> PdfiumResult<DynamicImage> {
let rgba_bytes = self.as_rgba_bytes()?;
match RgbaImage::from_raw(self.width() as u32, self.height() as u32, rgba_bytes) {
Some(image) => Ok(DynamicImage::ImageRgba8(image)),
None => Err(PdfiumError::ImageError),
}
}
pub fn as_rgb8_image(&self) -> PdfiumResult<DynamicImage> {
Ok(self
.as_rgba8_image()?
.into_rgb8() .into())
}
pub fn save(&self, path: &str, format: ImageFormat) -> PdfiumResult<()> {
let image = if format == ImageFormat::Png {
self.as_rgba8_image()?
} else {
self.as_rgb8_image()?
};
image
.save_with_format(path, format)
.or(Err(PdfiumError::ImageError))
}
}
impl From<&PdfiumBitmap> for FPDF_BITMAP {
#[inline]
fn from(value: &PdfiumBitmap) -> Self {
value.handle.handle()
}
}
fn close_bitmap(bitmap: FPDF_BITMAP) {
lib().FPDFBitmap_Destroy(bitmap);
}
#[derive(Copy, Clone, Debug, PartialEq, Default)]
#[repr(i32)]
pub enum PdfiumBitmapFormat {
Unknown = pdfium_constants::FPDFBitmap_Unknown,
Gray = pdfium_constants::FPDFBitmap_Gray,
Bgr = pdfium_constants::FPDFBitmap_BGR,
Bgrx = pdfium_constants::FPDFBitmap_BGRx,
#[default]
Bgra = pdfium_constants::FPDFBitmap_BGRA,
BgraPremul = pdfium_constants::FPDFBitmap_BGRA_Premul,
}
impl From<i32> for PdfiumBitmapFormat {
fn from(value: i32) -> Self {
match value {
pdfium_constants::FPDFBitmap_Gray => PdfiumBitmapFormat::Gray,
pdfium_constants::FPDFBitmap_BGR => PdfiumBitmapFormat::Bgr,
pdfium_constants::FPDFBitmap_BGRx => PdfiumBitmapFormat::Bgrx,
pdfium_constants::FPDFBitmap_BGRA => PdfiumBitmapFormat::Bgra,
pdfium_constants::FPDFBitmap_BGRA_Premul => PdfiumBitmapFormat::BgraPremul,
_ => PdfiumBitmapFormat::Unknown,
}
}
}
impl From<PdfiumBitmapFormat> for i32 {
fn from(value: PdfiumBitmapFormat) -> Self {
match value {
PdfiumBitmapFormat::Unknown => pdfium_constants::FPDFBitmap_Unknown,
PdfiumBitmapFormat::Gray => pdfium_constants::FPDFBitmap_Gray,
PdfiumBitmapFormat::Bgr => pdfium_constants::FPDFBitmap_BGR,
PdfiumBitmapFormat::Bgrx => pdfium_constants::FPDFBitmap_BGRx,
PdfiumBitmapFormat::Bgra => pdfium_constants::FPDFBitmap_BGRA,
PdfiumBitmapFormat::BgraPremul => pdfium_constants::FPDFBitmap_BGRA_Premul,
}
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn test_render_to_image() {
let document = PdfiumDocument::new_from_path("resources/groningen.pdf", None).unwrap();
let page = document.page(1).unwrap();
drop(document); let bounds = page.boundaries().media().unwrap();
let height = 1080;
let scale = height as f32 / bounds.height();
let width = (bounds.width() * scale) as i32;
let matrix = PdfiumMatrix::new_scale(scale);
let config = PdfiumRenderConfig::new()
.with_size(width, height)
.with_format(PdfiumBitmapFormat::Bgra)
.with_background(PdfiumColor::WHITE)
.with_matrix(matrix);
let bitmap = page.render(&config).unwrap();
assert_eq!(bitmap.width(), width);
assert_eq!(bitmap.height(), height);
bitmap
.save("groningen-page-2.png", image::ImageFormat::Png)
.unwrap();
}
}