-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow reuse of PdfBitmap when rendering pages #35
Comments
The obvious solution would be to just cache the bitmap created by |
I have thought a bit about how one would implement a web-based PDF viewer on top of pdfium-render. While that's certainly possible right now, a few features are missing to achieve performance similar to PDF.js:
With these two features, a "normal" PDF (in which every page has the same size) could be viewed using just one buffer. Zooming and panning could be done by changing the viewport only. @ajrcarey what do you think? |
Ok, I see what you're trying to do. There's two problems that I can see straight away:
For both these reasons, I don't think bitmap buffer sharing should be provided directly by Specifically, I propose the following adjustments:
The For reference, the source code for the |
Ah, I see we both reached the same conclusion of separating page rendering from bitmaps independently, and at almost precisely the same time! Great minds think alike :)
I think it's possible to address both of these. Let's deal with item 1 first. |
😄 For example, you already have a distinction between Then, you'd either do |
The basic idea I have is:
I should have time to code this up tomorrow. I will then look into the |
Sounds good! |
get_bitmap_with_config
is slow on WASM
Ok, I've pushed some changes that adds the option of re-using an existing fn test_page_rendering_reusing_bitmap() -> Result<(), PdfiumError> {
// Renders each page in the given test PDF file to a separate JPEG file
// by re-using the same bitmap buffer for each render.
let pdfium = Pdfium::new(
Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path("./"))
.or_else(|_| Pdfium::bind_to_system_library())?,
);
let document = pdfium.load_pdf_from_file("./test/export-test.pdf", None)?;
let render_config = PdfRenderConfig::new()
.set_target_width(2000)
.set_maximum_height(2000)
.rotate_if_landscape(PdfBitmapRotation::Degrees90, true);
let mut bitmap = PdfBitmap::empty(
2500, // Deliberately larger than any page will be
2500,
PdfBitmapFormat::default(),
pdfium.get_bindings(),
)?;
for (index, page) in document.pages().iter().enumerate() {
page.render_into_bitmap_with_config(&mut bitmap, &render_config)?; // Re-uses the same bitmap for rendering each page.
bitmap
.as_image()
.as_rgba8()
.ok_or(PdfiumError::ImageError)?
.save_with_format(format!("test-page-{}.jpg", index), image::ImageFormat::Jpeg)
.map_err(|_| PdfiumError::ImageError)?;
}
Ok(())
} This example creates a bitmap that is deliberately larger than any of the rendered pages will be. The saved images include the entire bitmap dimensions, not just the portion used for rendering each page; this makes perfect sense, but is perhaps not the ideal (or expected) result. Does the resulting image need to be clipped to the rendered dimensions? |
The documentation for
|
@ajrcarey Awesome, I'll check that out tonight! |
That was my thought as well - that every transform operation supported by the matrix is inherently invertible. It seemed odd to me that they would mention it at all. Maybe I'm missing something. Perhaps it will become apparent when adding the transform operations to |
@ajrcarey How about exposing the transformation matrix more or less directly, instead of sticking with the fields in PdfRenderConfig? #[derive(Copy, Clone)]
pub struct Transform([[f64; 3]; 3]);
impl Transform {
pub fn new() -> Self {
Self([
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]
])
}
pub fn scale(self, scale: f64) -> Self {
self.scale_axis(scale, scale)
}
pub fn scale_axis(self, scale_x: f64, scale_y: f64) -> Self {
let transform = Transform([
[scale_x, 0.0, 0.0],
[0.0, scale_y, 0.0],
[0.0, 0.0, 1.0]
]);
self * transform
}
pub fn translate(self, x: f64, y: f64) -> Self {
let transform = Transform([
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[x, y, 1.0]
]);
self * transform
}
pub fn rotate(self, rad: f64) -> Self {
let sin = rad.sin();
let cos = rad.cos();
let transform = Transform([
[cos, sin, 0.0],
[-sin, cos, 0.0],
[0.0, 0.0, 1.0]
]);
self * transform
}
}
impl Mul<Transform> for Transform {
type Output = Transform;
fn mul(self, rhs: Transform) -> Self {
let Self(l) = self;
let Self(r) = rhs;
let mut result = [[0f64; 3]; 3];
for row in 0..3 {
for col in 0..3 {
for k in 0..3 {
result[row][col] += l[row][k] * r[k][col];
}
}
}
Transform(result)
}
} |
Yes, that looks good. I like your function chaining approach. If we were to introduce the ability to pass a You suggested exposing the transformation matrix separately from I've push a commit that adds the new functionality to Pdfium's API offers two rendering functions: I have added the option to specify a custom clipping area via the There's a lot of new functionality here and so it's good to have you test at least the bits you're interested in before I push a public release. I have already dealt with some corner cases in |
Confirmed all existing tests and examples work when compiling to both native and WASM. Updated documentation. I'm reasonably confident this is all backwards-compatible and as such I'm happy to upload it to crates.io as release 0.7.13. Waiting for a few days to see if @NyxCode has any feedback / has discovered any bugs that require resolution before publishing. |
@ajrcarey Awesome! |
If you're not in a hurry, you can also wait a few days until I get everything up and running |
No hurry here, take your time. I'm away until next Wednesday and won't publish anything until I get back. |
@ajrcarey re-using the bitmap works great, and speeds up the rendering process a lot. let bindings = Pdfium::bind_to_system_library().map_err(js_err)?;
let pdfium: &'static Pdfium = Box::leak(Box::new(Pdfium::new(bindings)));
let bitmap: PdfBitmap<'static> =
PdfBitmap::empty(max_width, max_height, PdfBitmapFormat::default(), pdfium.get_bindings())
.map_err(js_err)?;
let bitmap = Rc::new(UnsafeCell::new(bitmap)); |
Ok, I'm pleased it's all working well. I'm going to release this as 0.7.13 momentarily. Is the use of |
@ajrcarey Exactly, that was the intent since |
Ah, right. So you want to pass |
Additionally, |
Yes, but the lifetime of |
Super impressed by your demo at https://svelte-pdf-viewer.nyxcode.com/, by the way, @NyxCode! Looks and feels terrific. |
@ajrcarey thanks! |
get_bitmap_with_config
is quite slow on WASM. For a 2000x2000 bitmap, it takes ~11ms on my machine.Most of that time is spent in
create_empty_bitmap_handle
. I will explore what optimizations can be done to improve this, especially on WASM.After #32, which made the actual rendering process much faster, here is the crude flamegraph I generated.
Unlike the graphs in #32, this test is more realistic, rendering a real page with a lot of content:
"TOTAL" is the whole rendering process, starting with getting a specific page from a document, and ending with drawing the bitmap to a canvas.
The text was updated successfully, but these errors were encountered: