// Multi-threaded rendering of all pages in a document to PNG images.12// First look at doc/example.c and make sure you understand it.3// Then read the multi-threading section in doc/overview.txt,4// before coming back here to see an example of multi-threading.56// This example will create one main thread for reading pages from the7// document, and one thread per page for rendering. After rendering8// the main thread will wait for each rendering thread to complete before9// writing that thread's rendered image to a PNG image. There is10// nothing in MuPDF requiring a rendering thread to only render a11// single page, this is just a design decision taken for this example.1213// Compile a debug build of mupdf, then compile and run this example:14//15// gcc -g -o build/debug/example-mt -Iinclude docs/multi-threaded.c \16// build/debug/libmupdf.a \17// build/debug/libfreetype.a build/debug/libjbig2dec.a \18// build/debug/libjpeg.a build/debug/libopenjpeg.a \19// build/debug/libmujs.a \20// build/debug/libz.a -lpthread -lm21//22// build/debug/example-mt /path/to/document.pdf23//24// Caution! As all pages are rendered simultaneously, please choose a25// file with just a few pages to avoid stressing your machine too26// much. Also you may run in to a limitation on the number of threads27// depending on your environment.2829// Include the MuPDF header file, and pthread's header file.30#include <mupdf/fitz.h>31#include <pthread.h>3233// A convenience function for dying abruptly on pthread errors.3435void36fail(char *msg)37{38fprintf(stderr, "%s\n", msg);39abort();40}4142// The data structure passed between the requesting main thread and43// each rendering thread.4445struct data {46// A pointer to the original context in the main thread sent47// from main to rendering thread. It will be used to create48// each rendering thread's context clone.49fz_context *ctx;5051// Page number sent from main to rendering thread for printing52int pagenumber;5354// The display list as obtained by the main thread and sent55// from main to rendering thread. This contains the drawing56// commands (text, images, etc.) for the page that should be57// rendered.58fz_display_list *list;5960// The area of the page to render as obtained by the main61// thread and sent from main to rendering thread.62fz_rect bbox;6364// This is the result, a pixmap containing the rendered page.65// It is passed first from main thread to the rendering66// thread, then its samples are changed by the rendering67// thread, and then back from the rendering thread to the main68// thread.69fz_pixmap *pix;70};7172// This is the function run by each rendering function. It takes73// pointer to an instance of the data structure described above and74// renders the display list into the pixmap before exiting.7576void *77renderer(void *data)78{79int pagenumber = ((struct data *) data)->pagenumber;80fz_context *ctx = ((struct data *) data)->ctx;81fz_display_list *list = ((struct data *) data)->list;82fz_rect bbox = ((struct data *) data)->bbox;83fz_pixmap *pix = ((struct data *) data)->pix;84fz_device *dev;8586fprintf(stderr, "thread at page %d loading!\n", pagenumber);8788// The context pointer is pointing to the main thread's89// context, so here we create a new context based on it for90// use in this thread.9192ctx = fz_clone_context(ctx);9394// Next we run the display list through the draw device which95// will render the request area of the page to the pixmap.9697fprintf(stderr, "thread at page %d rendering!\n", pagenumber);98dev = fz_new_draw_device(ctx, pix);99fz_run_display_list(ctx, list, dev, &fz_identity, &bbox, NULL);100fz_drop_device(ctx, dev);101102// This threads context is freed.103104fz_drop_context(ctx);105106fprintf(stderr, "thread at page %d done!\n", pagenumber);107108return data;109}110111// These are the two locking functions required by MuPDF when112// operating in a multi-threaded environment. They each take a user113// argument that can be used to transfer some state, in this case a114// pointer to the array of mutexes.115116void lock_mutex(void *user, int lock)117{118pthread_mutex_t *mutex = (pthread_mutex_t *) user;119120if (pthread_mutex_lock(&mutex[lock]) != 0)121fail("pthread_mutex_lock()");122}123124void unlock_mutex(void *user, int lock)125{126pthread_mutex_t *mutex = (pthread_mutex_t *) user;127128if (pthread_mutex_unlock(&mutex[lock]) != 0)129fail("pthread_mutex_unlock()");130}131132int main(int argc, char **argv)133{134char *filename = argc >= 2 ? argv[1] : "";135pthread_t *thread = NULL;136fz_locks_context locks;137pthread_mutex_t mutex[FZ_LOCK_MAX];138fz_context *ctx;139fz_document *doc;140int threads;141int i;142143// Initialize FZ_LOCK_MAX number of non-recursive mutexes.144145for (i = 0; i < FZ_LOCK_MAX; i++)146{147if (pthread_mutex_init(&mutex[i], NULL) != 0)148fail("pthread_mutex_init()");149}150151// Initialize the locking structure with function pointers to152// the locking functions and to the user data. In this case153// the user data is a pointer to the array of mutexes so the154// locking functions can find the relevant lock to change when155// they are called. This way we avoid global variables.156157locks.user = mutex;158locks.lock = lock_mutex;159locks.unlock = unlock_mutex;160161// This is the main threads context function, so supply the162// locking structure. This context will be used to parse all163// the pages from the document.164165ctx = fz_new_context(NULL, &locks, FZ_STORE_UNLIMITED);166167// Register default file types.168169fz_register_document_handlers(ctx);170171// Open the PDF, XPS or CBZ document. Note, this binds doc to ctx.172// You must only ever use doc with ctx - never a clone of it!173174doc = fz_open_document(ctx, filename);175176// Retrieve the number of pages, which translates to the177// number of threads used for rendering pages.178179threads = fz_count_pages(ctx, doc);180fprintf(stderr, "spawning %d threads, one per page...\n", threads);181182thread = malloc(threads * sizeof (pthread_t));183184for (i = 0; i < threads; i++)185{186fz_page *page;187fz_rect bbox;188fz_irect rbox;189fz_display_list *list;190fz_device *dev;191fz_pixmap *pix;192struct data *data;193194// Load the relevant page for each thread. Note, that this195// cannot be done on the worker threads, as each use of doc196// uses ctx, and only one thread can be using ctx at a time.197198page = fz_load_page(ctx, doc, i);199200// Compute the bounding box for each page.201202fz_bound_page(ctx, page, &bbox);203204// Create a display list that will hold the drawing205// commands for the page. Once we have the display list206// this can safely be used on any other thread as it is207// not bound to a given context.208209list = fz_new_display_list(ctx);210211// Run the loaded page through a display list device212// to populate the page's display list.213214dev = fz_new_list_device(ctx, list);215fz_run_page(ctx, page, dev, &fz_identity, NULL);216fz_drop_device(ctx, dev);217218// The page is no longer needed, all drawing commands219// are now in the display list.220221fz_drop_page(ctx, page);222223// Create a white pixmap using the correct dimensions.224225pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), fz_round_rect(&rbox, &bbox));226fz_clear_pixmap_with_value(ctx, pix, 0xff);227228// Populate the data structure to be sent to the229// rendering thread for this page.230231data = malloc(sizeof (struct data));232233data->pagenumber = i + 1;234data->ctx = ctx;235data->list = list;236data->bbox = bbox;237data->pix = pix;238239// Create the thread and pass it the data structure.240241if (pthread_create(&thread[i], NULL, renderer, data) != 0)242fail("pthread_create()");243}244245// Now each thread is rendering pages, so wait for each thread246// to complete its rendering.247248fprintf(stderr, "joining %d threads...\n", threads);249for (i = threads - 1; i >= 0; i--)250{251char filename[42];252struct data *data;253254if (pthread_join(thread[i], (void **) &data) != 0)255fail("pthread_join");256257sprintf(filename, "out%04d.png", i);258fprintf(stderr, "\tSaving %s...\n", filename);259260// Write the rendered image to a PNG file261262fz_write_png(ctx, data->pix, filename, 0);263264// Free the thread's pixmap and display list since265// they were allocated by the main thread above.266267fz_drop_pixmap(ctx, data->pix);268fz_drop_display_list(ctx, data->list);269270// Free the data structured passed back and forth271// between the main thread and rendering thread.272273free(data);274}275276fprintf(stderr, "finally!\n");277fflush(NULL);278279free(thread);280281// Finally the document is closed and the main thread's282// context is freed.283284fz_drop_document(ctx, doc);285fz_drop_context(ctx);286287return 0;288}289290291