// Multi-threaded rendering of all pages in a document to PNG images. // First look at doc/example.c and make sure you understand it. // Then read the multi-threading section in doc/overview.txt, // before coming back here to see an example of multi-threading. // This example will create one main thread for reading pages from the // document, and one thread per page for rendering. After rendering // the main thread will wait for each rendering thread to complete before // writing that thread's rendered image to a PNG image. There is // nothing in MuPDF requiring a rendering thread to only render a // single page, this is just a design decision taken for this example. // Compile a debug build of mupdf, then compile and run this example: // // gcc -g -o build/debug/example-mt -Iinclude docs/multi-threaded.c \ // build/debug/libmupdf.a \ // build/debug/libmupdfthird.a \ // -lpthread -lcrypto -lm // // build/debug/example-mt /path/to/document.pdf // // Caution! As all pages are rendered simultaneously, please choose a // file with just a few pages to avoid stressing your machine too // much. Also you may run in to a limitation on the number of threads // depending on your environment. // Include the MuPDF header file, and pthread's header file. #include #include // A convenience function for dying abruptly on pthread errors. void fail(char *msg) { fprintf(stderr, "%s\n", msg); abort(); } // The data structure passed between the requesting main thread and // each rendering thread. struct data { // A pointer to the original context in the main thread sent // from main to rendering thread. It will be used to create // each rendering thread's context clone. fz_context *ctx; // Page number sent from main to rendering thread for printing int pagenumber; // The display list as obtained by the main thread and sent // from main to rendering thread. This contains the drawing // commands (text, images, etc.) for the page that should be // rendered. fz_display_list *list; // The area of the page to render as obtained by the main // thread and sent from main to rendering thread. fz_rect bbox; // This is the result, a pixmap containing the rendered page. // It is passed first from main thread to the rendering // thread, then its samples are changed by the rendering // thread, and then back from the rendering thread to the main // thread. fz_pixmap *pix; }; // This is the function run by each rendering function. It takes // pointer to an instance of the data structure described above and // renders the display list into the pixmap before exiting. void * renderer(void *data) { int pagenumber = ((struct data *) data)->pagenumber; fz_context *ctx = ((struct data *) data)->ctx; fz_display_list *list = ((struct data *) data)->list; fz_rect bbox = ((struct data *) data)->bbox; fz_pixmap *pix = ((struct data *) data)->pix; fz_device *dev; fprintf(stderr, "thread at page %d loading!\n", pagenumber); // The context pointer is pointing to the main thread's // context, so here we create a new context based on it for // use in this thread. ctx = fz_clone_context(ctx); // Next we run the display list through the draw device which // will render the request area of the page to the pixmap. fprintf(stderr, "thread at page %d rendering!\n", pagenumber); dev = fz_new_draw_device(ctx, &fz_identity, pix); fz_run_display_list(ctx, list, dev, &fz_identity, &bbox, NULL); fz_close_device(ctx, dev); fz_drop_device(ctx, dev); // This threads context is freed. fz_drop_context(ctx); fprintf(stderr, "thread at page %d done!\n", pagenumber); return data; } // These are the two locking functions required by MuPDF when // operating in a multi-threaded environment. They each take a user // argument that can be used to transfer some state, in this case a // pointer to the array of mutexes. void lock_mutex(void *user, int lock) { pthread_mutex_t *mutex = (pthread_mutex_t *) user; if (pthread_mutex_lock(&mutex[lock]) != 0) fail("pthread_mutex_lock()"); } void unlock_mutex(void *user, int lock) { pthread_mutex_t *mutex = (pthread_mutex_t *) user; if (pthread_mutex_unlock(&mutex[lock]) != 0) fail("pthread_mutex_unlock()"); } int main(int argc, char **argv) { char *filename = argc >= 2 ? argv[1] : ""; pthread_t *thread = NULL; fz_locks_context locks; pthread_mutex_t mutex[FZ_LOCK_MAX]; fz_context *ctx; fz_document *doc; int threads; int i; // Initialize FZ_LOCK_MAX number of non-recursive mutexes. for (i = 0; i < FZ_LOCK_MAX; i++) { if (pthread_mutex_init(&mutex[i], NULL) != 0) fail("pthread_mutex_init()"); } // Initialize the locking structure with function pointers to // the locking functions and to the user data. In this case // the user data is a pointer to the array of mutexes so the // locking functions can find the relevant lock to change when // they are called. This way we avoid global variables. locks.user = mutex; locks.lock = lock_mutex; locks.unlock = unlock_mutex; // This is the main threads context function, so supply the // locking structure. This context will be used to parse all // the pages from the document. ctx = fz_new_context(NULL, &locks, FZ_STORE_UNLIMITED); // Register default file types. fz_register_document_handlers(ctx); // Open the PDF, XPS or CBZ document. Note, this binds doc to ctx. // You must only ever use doc with ctx - never a clone of it! doc = fz_open_document(ctx, filename); // Retrieve the number of pages, which translates to the // number of threads used for rendering pages. threads = fz_count_pages(ctx, doc); fprintf(stderr, "spawning %d threads, one per page...\n", threads); thread = malloc(threads * sizeof (pthread_t)); for (i = 0; i < threads; i++) { fz_page *page; fz_rect bbox; fz_irect rbox; fz_display_list *list; fz_device *dev; fz_pixmap *pix; struct data *data; // Load the relevant page for each thread. Note, that this // cannot be done on the worker threads, as each use of doc // uses ctx, and only one thread can be using ctx at a time. page = fz_load_page(ctx, doc, i); // Compute the bounding box for each page. fz_bound_page(ctx, page, &bbox); // Create a display list that will hold the drawing // commands for the page. Once we have the display list // this can safely be used on any other thread as it is // not bound to a given context. list = fz_new_display_list(ctx, &bbox); // Run the loaded page through a display list device // to populate the page's display list. dev = fz_new_list_device(ctx, list); fz_run_page(ctx, page, dev, &fz_identity, NULL); fz_close_device(ctx, dev); fz_drop_device(ctx, dev); // The page is no longer needed, all drawing commands // are now in the display list. fz_drop_page(ctx, page); // Create a white pixmap using the correct dimensions. pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), fz_round_rect(&rbox, &bbox), 0); fz_clear_pixmap_with_value(ctx, pix, 0xff); // Populate the data structure to be sent to the // rendering thread for this page. data = malloc(sizeof (struct data)); data->pagenumber = i + 1; data->ctx = ctx; data->list = list; data->bbox = bbox; data->pix = pix; // Create the thread and pass it the data structure. if (pthread_create(&thread[i], NULL, renderer, data) != 0) fail("pthread_create()"); } // Now each thread is rendering pages, so wait for each thread // to complete its rendering. fprintf(stderr, "joining %d threads...\n", threads); for (i = threads - 1; i >= 0; i--) { char filename[42]; struct data *data; if (pthread_join(thread[i], (void **) &data) != 0) fail("pthread_join"); sprintf(filename, "out%04d.png", i); fprintf(stderr, "\tSaving %s...\n", filename); // Write the rendered image to a PNG file fz_save_pixmap_as_png(ctx, data->pix, filename); // Free the thread's pixmap and display list since // they were allocated by the main thread above. fz_drop_pixmap(ctx, data->pix); fz_drop_display_list(ctx, data->list); // Free the data structured passed back and forth // between the main thread and rendering thread. free(data); } fprintf(stderr, "finally!\n"); fflush(NULL); free(thread); // Finally the document is closed and the main thread's // context is freed. fz_drop_document(ctx, doc); fz_drop_context(ctx); return 0; }