-
Notifications
You must be signed in to change notification settings - Fork 20
Multithreading
None of the "standard" methods of the MuPDFDocument
class are inherently thread-safe, e.g. you cannot render multiple pages of the same document (nor multiple regions of a single page) by simply performing multiple calls to MuPDFDocument.Render
in parallel.
For multi-threaded operation, you must instead use a MuPDFMultiThreadedPageRender
. You can obtain one from a document:
MuPDFMultiThreadedPageRenderer renderer = document.GetMultiThreadedRenderer(0, 2);
This method obtains an object that can be used to render the first page of the document using two threads. By using the Render
method of this object, the page can be rendered. The page will be rendered to a number of separate tiles equal to the number of threads, which will then be your responsibility to appropriately "stitch up" (e.g. if you want to display them on screen, you could just place them appropriately). The size of each tile (and the position it should occupy) can be computed by using the Split
method of the RoundedSize
struct.
Furthermore, multiple MuPDFMultiThreadedPageRenderer
s can be used in parallel, which makes it possible e.g. to render every page in the document at the same time (while also using multiple threads to render each page). The following example will render all the pages in a document at the same time in RGB format at a 1.5x zoom, using 2 threads for each page:
//Create a MuPDFContext with a using statement, so that it gets disposed at the right time.
using MuPDFContext context = new MuPDFContext();
//Open the document also with a using statement.
using MuPDFDocument document = new MuPDFDocument(context, "path/to/file.pdf");
//Create arrays to hold the objects for the various pages
//Renderers: one per page
MuPDFMultiThreadedPageRenderer[] renderers = new MuPDFMultiThreadedPageRenderer[document.Pages.Count];
//Page size: one per page
RoundedSize[] renderedPageSizes = new RoundedSize[document.Pages.Count];
//Boundaries of the tiles that make up each page: one array per page, with one element per thread
RoundedRectangle[][] tileBounds = new RoundedRectangle[document.Pages.Count][];
//Addresses of the memory areas where the image data of the tiles will be stored: one array per page, with one element per thread
IntPtr[][] destinations = new IntPtr[document.Pages.Count][];
//Cycle through the pages in the document to initialise everything
for (int i = 0; i < document.Pages.Count; i++)
{
//Initialise the renderer for the current page, using two threads (total number of threads: number of pages x 2)
renderers[i] = document.GetMultiThreadedRenderer(i, 2);
//Determine the boundaries of the page when it is rendered with a 1.5x zoom factor
RoundedRectangle roundedBounds = document.Pages[i].Bounds.Round(1.5);
renderedPageSizes[i] = new RoundedSize(roundedBounds.Width, roundedBounds.Height);
//Determine the boundaries of each tile by splitting the total size of the page by the number of threads.
tileBounds[i] = renderedPageSizes[i].Split(renderers[i].ThreadCount);
destinations[i] = new IntPtr[renderers[i].ThreadCount];
for (int j = 0; j < renderers[i].ThreadCount; j++)
{
//Allocate the required memory for the j-th tile of the i-th page.
//Since we will be rendering with a 24-bit-per-pixel format, the required memory in bytes is height x width x 3.
destinations[i][j] = Marshal.AllocHGlobal(tileBounds[i][j].Height * tileBounds[i][j].Width * 3);
}
}
//Start the actual rendering operations in parallel.
Parallel.For(0, document.Pages.Count, i =>
{
renderers[i].Render(renderedPageSizes[i], document.Pages[i].Bounds, destinations[i], PixelFormats.RGB);
});
//The code in this for-loop is not really part of MuPDFCore - it just shows an example of using VectSharp to "stitch" up the tiles and produce the full image.
for (int i = 0; i < document.Pages.Count; i++)
{
//Create a new (empty) image to hold the whole page.
VectSharp.Page renderedPage = new VectSharp.Page(renderedPageSizes[i].Width, renderedPageSizes[i].Height);
//Draw each tile onto the image.
for (int j = 0; j < renderers[i].ThreadCount; j++)
{
//Create a raster image object containing the pixel data. Yay, we do not need to copy/marshal anything!
VectSharp.RasterImage tile = new VectSharp.RasterImage(destinations[i][j], tileBounds[i][j].Width, tileBounds[i][j].Height, false, false);
//Draw the tile on the main image page.
renderedPage.Graphics.DrawRasterImage(tileBounds[i][j].X0, tileBounds[i][j].Y0, tile);
}
//Save the full page as a PNG image.
renderedPage.SaveAsPNG("page" + i.ToString() + ".png");
}
//Clean-up code.
for (int i = 0; i < document.Pages.Count; i++)
{
//Release the allocated memory.
for (int j = 0; j < renderers[i].ThreadCount; j++)
{
Marshal.FreeHGlobal(destinations[i][j]);
}
//Release the renderer (if you skip this, the quiescent renderer's threads will not be stopped, and your application will never exit!
renderers[i].Dispose();
}