Skip to content

Commit

Permalink
Render with external buffer and remove BitmapDataHolder
Browse files Browse the repository at this point in the history
This is a lot better (and shorter).
Also it's more official PDFium API use, as FPDFBitmap_CreateEx() wasn't
meant to get a stride value if the buffer parameter is None.
  • Loading branch information
mara004 committed Aug 25, 2022
1 parent b42b5a4 commit 367b55e
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 31 deletions.
1 change: 1 addition & 0 deletions docs/devel/changelog_staging.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@

- Rewrote the project's `README.md`. Added more support model examples and an extensive guide regarding the raw PDFium/ctypes API.
- Improved support model code style while writing the raw API guide.
- pypdfium2 now provides PDFium with an external buffer for rendering. This has numerous advantages, most notably that callers don't need to free resources anymore. The `BitmapDataHolder` class has been removed. The `render_base()` API was semi-public, so technically this is an API-breaking change.
- PDFium's commit log is now shown with GitHub releases.
- Tweaked dependency pinning.
43 changes: 12 additions & 31 deletions src/pypdfium2/_helpers/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,8 @@ def render_base(
A list of item types that shall not be smoothed (text, image, path).
Returns:
(BitmapDataHolder, str, (int, int)):
Bitmap data holder, colour format, and image size.
(ctypes.c_ubyte_Array_N, str, (int, int)):
Ctypes array, colour format, and image size.
The colour format can be ``BGRA``, ``BGR``, or ``L``, depending on the parameters *colour* and *greyscale*.
Image size is given in pixels as a tuple of width and height.
"""
Expand All @@ -323,7 +323,8 @@ def render_base(
if any(d < 1 for d in (width, height)):
raise ValueError("Crop exceeds page dimensions (in px): width %s, height %s, crop %s" % (src_width, src_height, crop))

bitmap = pdfium.FPDFBitmap_CreateEx(width, height, cl_pdfium, None, width*n_colours)
buffer = (ctypes.c_ubyte * (width*height*n_colours))()
bitmap = pdfium.FPDFBitmap_CreateEx(width, height, cl_pdfium, buffer, width*n_colours)
if fpdf_colour is not None:
pdfium.FPDFBitmap_FillRect(bitmap, 0, 0, width, height, fpdf_colour)

Expand Down Expand Up @@ -353,14 +354,12 @@ def render_base(
render_args = (bitmap, self._page, -crop[0], -crop[3], src_width, src_height, RotationToConst[rotation], render_flags)
pdfium.FPDF_RenderPageBitmap(*render_args)
pdfium.FPDF_FFLDraw(form_fill, *render_args)

cbuf_ptr = pdfium.FPDFBitmap_GetBuffer(bitmap)
cbuf_array_ptr = ctypes.cast(cbuf_ptr, ctypes.POINTER(ctypes.c_ubyte * (width*height*n_colours)))
data_holder = BitmapDataHolder(bitmap, cbuf_array_ptr)

pdfium.FPDFDOC_ExitFormFillEnvironment(form_fill)

return data_holder, cl_format, (width, height)
# No need to call FPDFBitmap_Destroy() because we're using an external buffer.
# Python will take care of freeing the memory when `buffer` isn't referenced anymore.

return buffer, cl_format, (width, height)


def render_tobytes(self, *args, **kwargs):
Expand All @@ -370,12 +369,8 @@ def render_tobytes(self, *args, **kwargs):
Returns:
(bytes, str, (int, int)): Image data, colour format, and size.
"""

data_holder, cl_format, size = self.render_base(*args, **kwargs)
data = bytes( data_holder.get_data() )
data_holder.close()

return data, cl_format, size
c_array, cl_format, size = self.render_base(*args, **kwargs)
return bytes(c_array), cl_format, size


def render_topil(self, *args, **kwargs):
Expand All @@ -391,26 +386,12 @@ def render_topil(self, *args, **kwargs):
if not have_pil:
raise RuntimeError("Pillow library needs to be installed for render_topil().")

data_holder, cl_format, size = self.render_base(*args, **kwargs)
pil_image = PIL.Image.frombytes(ColourMapping[cl_format], size, data_holder.get_data(), "raw", cl_format, 0, 1)
data_holder.close()
c_array, cl_format, size = self.render_base(*args, **kwargs)
pil_image = PIL.Image.frombytes(ColourMapping[cl_format], size, c_array, "raw", cl_format, 0, 1)

return pil_image


class BitmapDataHolder:

def __init__(self, bm_handle, bm_array_ptr):
self.bm_handle = bm_handle
self.bm_array_ptr = bm_array_ptr

def get_data(self):
return self.bm_array_ptr.contents

def close(self):
pdfium.FPDFBitmap_Destroy(self.bm_handle)


class PdfPageObject:
""" Page object helper class. """

Expand Down

0 comments on commit 367b55e

Please sign in to comment.