use crate::bindgen::{
FPDFBitmap_BGR, FPDFBitmap_BGRA, FPDFBitmap_BGRx, FPDFBitmap_Gray, FPDFBitmap_Unknown,
FPDF_BITMAP,
};
use crate::bindings::PdfiumLibraryBindings;
use crate::error::{PdfiumError, PdfiumInternalError};
use crate::pdf::document::page::render_config::PdfPageRenderSettings;
use crate::pdfium::PdfiumLibraryBindingsAccessor;
use crate::utils::pixels::{aligned_bgr_to_rgba, aligned_rgb_to_rgba, bgra_to_rgba};
use std::marker::PhantomData;
use std::os::raw::c_int;
#[cfg(feature = "image_025")]
use image_025::{DynamicImage, GrayImage, RgbaImage};
#[cfg(feature = "image_024")]
use image_024::{DynamicImage, GrayImage, RgbaImage};
#[cfg(feature = "image_023")]
use image_023::{DynamicImage, GrayImage, RgbaImage};
#[cfg(not(target_arch = "wasm32"))]
use std::os::raw::c_void;
#[cfg(target_arch = "wasm32")]
use {
js_sys::Uint8Array,
wasm_bindgen::{Clamped, JsValue},
web_sys::ImageData,
};
#[cfg(doc)]
struct Uint8Array;
#[cfg(doc)]
struct ImageData;
#[cfg(doc)]
struct JsValue;
pub type Pixels = i32;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PdfBitmapFormat {
Gray = FPDFBitmap_Gray as isize,
BGR = FPDFBitmap_BGR as isize,
BGRx = FPDFBitmap_BGRx as isize,
BGRA = FPDFBitmap_BGRA as isize,
}
impl PdfBitmapFormat {
#[inline]
#[allow(non_upper_case_globals)]
pub(crate) fn from_pdfium(format: u32) -> Result<Self, PdfiumError> {
match format {
FPDFBitmap_Unknown => Err(PdfiumError::UnknownBitmapFormat),
FPDFBitmap_Gray => Ok(PdfBitmapFormat::Gray),
FPDFBitmap_BGR => Ok(PdfBitmapFormat::BGR),
FPDFBitmap_BGRx => Ok(PdfBitmapFormat::BGRx),
FPDFBitmap_BGRA => Ok(PdfBitmapFormat::BGRA),
_ => Err(PdfiumError::UnknownBitmapFormat),
}
}
#[inline]
pub(crate) fn as_pdfium(&self) -> u32 {
match self {
PdfBitmapFormat::Gray => FPDFBitmap_Gray,
PdfBitmapFormat::BGR => FPDFBitmap_BGR,
PdfBitmapFormat::BGRx => FPDFBitmap_BGRx,
PdfBitmapFormat::BGRA => FPDFBitmap_BGRA,
}
}
}
#[allow(clippy::derivable_impls)]
impl Default for PdfBitmapFormat {
#[inline]
fn default() -> Self {
PdfBitmapFormat::BGRA
}
}
pub struct PdfBitmap<'a> {
handle: FPDF_BITMAP,
was_byte_order_reversed_during_rendering: bool,
lifetime: PhantomData<&'a FPDF_BITMAP>,
}
impl<'a> PdfBitmap<'a> {
pub(crate) fn from_pdfium(handle: FPDF_BITMAP) -> Self {
PdfBitmap {
handle,
was_byte_order_reversed_during_rendering: false,
lifetime: PhantomData,
}
}
pub fn empty(
width: Pixels,
height: Pixels,
format: PdfBitmapFormat,
bindings: &'a dyn PdfiumLibraryBindings,
) -> Result<PdfBitmap<'a>, PdfiumError> {
let handle = unsafe {
bindings.FPDFBitmap_CreateEx(
width as c_int,
height as c_int,
format.as_pdfium() as c_int,
std::ptr::null_mut(),
0, )
};
if handle.is_null() {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
} else {
Ok(Self::from_pdfium(handle))
}
}
#[cfg(not(target_arch = "wasm32"))]
pub unsafe fn from_bytes(
width: Pixels,
height: Pixels,
format: PdfBitmapFormat,
buffer: &'a mut [u8],
bindings: &'a dyn PdfiumLibraryBindings,
) -> Result<PdfBitmap<'a>, PdfiumError> {
let handle = bindings.FPDFBitmap_CreateEx(
width as c_int,
height as c_int,
format.as_pdfium() as c_int,
buffer.as_mut_ptr() as *mut c_void,
0, );
if handle.is_null() {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
} else {
Ok(Self::from_pdfium(handle))
}
}
#[inline]
pub(crate) fn handle(&self) -> FPDF_BITMAP {
self.handle
}
#[inline]
pub(crate) fn set_byte_order_from_render_settings(&mut self, settings: &PdfPageRenderSettings) {
self.was_byte_order_reversed_during_rendering = settings.is_reversed_byte_order_flag_set
}
#[inline]
pub fn width(&self) -> Pixels {
(unsafe { self.bindings().FPDFBitmap_GetWidth(self.handle()) }) as Pixels
}
#[inline]
pub fn height(&self) -> Pixels {
(unsafe { self.bindings().FPDFBitmap_GetHeight(self.handle()) }) as Pixels
}
#[inline]
pub fn format(&self) -> Result<PdfBitmapFormat, PdfiumError> {
PdfBitmapFormat::from_pdfium(
unsafe { self.bindings().FPDFBitmap_GetFormat(self.handle()) } as u32,
)
}
pub fn as_raw_bytes(&self) -> Vec<u8> {
unsafe { self.bindings().FPDFBitmap_GetBuffer_as_vec(self.handle) }
}
pub fn as_rgba_bytes(&self) -> Vec<u8> {
let bytes = self.as_raw_bytes();
let format = self.format().unwrap_or_default();
let width = self.width() as usize;
let stride = bytes.len() / self.height() as usize;
if self.was_byte_order_reversed_during_rendering {
match format {
PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx => {
bytes
}
PdfBitmapFormat::BGR => aligned_rgb_to_rgba(bytes.as_slice(), width, stride),
PdfBitmapFormat::Gray => bytes,
}
} else {
match format {
PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx => bgra_to_rgba(bytes.as_slice()),
PdfBitmapFormat::BGR => aligned_bgr_to_rgba(bytes.as_slice(), width, stride),
PdfBitmapFormat::Gray => bytes,
}
}
}
#[cfg(feature = "image_api")]
pub fn as_image(&self) -> Result<DynamicImage, PdfiumError> {
let bytes = self.as_rgba_bytes();
let width = self.width() as u32;
let height = self.height() as u32;
let image = match self.format().unwrap_or_default() {
PdfBitmapFormat::BGRA | PdfBitmapFormat::BGRx | PdfBitmapFormat::BGR => {
RgbaImage::from_raw(width, height, bytes).map(DynamicImage::ImageRgba8)
}
PdfBitmapFormat::Gray => {
GrayImage::from_raw(width, height, bytes).map(DynamicImage::ImageLuma8)
}
};
match image {
Some(image) => Ok(image),
None => Err(PdfiumError::ImageError),
}
}
#[cfg(any(doc, target_arch = "wasm32"))]
#[inline]
pub fn as_array(&self) -> Uint8Array {
unsafe { self.bindings().FPDFBitmap_GetBuffer_as_array(self.handle()) }
}
#[cfg(any(doc, target_arch = "wasm32"))]
#[inline]
pub fn as_image_data(&self) -> Result<ImageData, JsValue> {
ImageData::new_with_u8_clamped_array_and_sh(
Clamped(&self.as_rgba_bytes()),
self.width() as u32,
self.height() as u32,
)
}
#[inline]
pub fn bytes_required_for_size(width: Pixels, height: Pixels) -> usize {
4 * width as usize * height as usize
}
}
impl<'a> Drop for PdfBitmap<'a> {
#[inline]
fn drop(&mut self) {
unsafe {
self.bindings().FPDFBitmap_Destroy(self.handle());
}
}
}
impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfBitmap<'a> {}
#[cfg(feature = "thread_safe")]
unsafe impl<'a> Send for PdfBitmap<'a> {}
#[cfg(feature = "thread_safe")]
unsafe impl<'a> Sync for PdfBitmap<'a> {}
#[cfg(test)]
mod tests {
use crate::prelude::*;
use crate::utils::mem::create_sized_buffer;
use crate::utils::test::test_bind_to_pdfium;
#[test]
fn test_from_bytes() -> Result<(), PdfiumError> {
let pdfium = test_bind_to_pdfium();
let test_width = 2000;
let test_height = 4000;
let mut buffer =
create_sized_buffer(PdfBitmap::bytes_required_for_size(test_width, test_height));
let buffer_ptr = buffer.as_ptr();
let bitmap = unsafe {
PdfBitmap::from_bytes(
test_width,
test_height,
PdfBitmapFormat::BGRx,
buffer.as_mut_slice(),
pdfium.bindings(),
)?
};
assert_eq!(bitmap.width(), test_width);
assert_eq!(bitmap.height(), test_height);
assert_eq!(
unsafe { pdfium.bindings().FPDFBitmap_GetBuffer(bitmap.handle) } as usize,
buffer_ptr as usize
);
assert_eq!(
unsafe { pdfium.bindings().FPDFBitmap_GetStride(bitmap.handle) },
test_width * 4
);
Ok(())
}
}