diff mupdf-source/docs/examples/multi-threaded.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/docs/examples/multi-threaded.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,322 @@
+/*
+Multi-threaded rendering of all pages in a document to PNG images.
+
+First look at docs/example.c and make sure you understand it.
+Then, before coming back here to see an example of multi-threading,
+please read the multi-threading section in:
+https://mupdf.readthedocs.io/en/latest/using-mupdf.html#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.
+
+To build this example in a source tree and render every page as a
+separate PNG, run:
+make examples
+./build/debug/multi-threaded document.pdf
+
+To build from installed sources, and render the same document, run:
+gcc -I/usr/local/include -o multi-threaded \
+	/usr/local/share/doc/mupdf/examples/multi-threaded.c \
+	/usr/local/lib/libmupdf.a \
+	/usr/local/lib/libmupdfthird.a \
+	-lpthread -lm
+./multi-threaded 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 <mupdf/fitz.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+// A convenience function for dying abruptly on pthread errors.
+
+void
+fail(const char *msg)
+{
+	fprintf(stderr, "%s\n", msg);
+	abort();
+}
+
+// The data structure passed between the requesting main thread and
+// each rendering thread.
+
+struct thread_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 a note of whether a given thread failed or not.
+	int failed;
+};
+
+// 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_)
+{
+	struct thread_data *data = (struct thread_data *)data_;
+	int pagenumber = data->pagenumber;
+	fz_context *ctx = data->ctx;
+	fz_display_list *list = data->list;
+	fz_rect bbox = data->bbox;
+	fz_device *dev = NULL;
+
+	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.
+
+	fz_var(dev);
+
+	fprintf(stderr, "thread at page %d rendering!\n", pagenumber);
+	fz_try(ctx)
+	{
+		// Create a white pixmap using the correct dimensions.
+		data->pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), fz_round_rect(bbox), NULL, 0);
+		fz_clear_pixmap_with_value(ctx, data->pix, 0xff);
+
+		// Do the actual rendering.
+		dev = fz_new_draw_device(ctx, fz_identity, data->pix);
+		fz_run_display_list(ctx, list, dev, fz_identity, bbox, NULL);
+		fz_close_device(ctx, dev);
+	}
+	fz_always(ctx)
+		fz_drop_device(ctx, dev);
+	fz_catch(ctx)
+		data->failed = 1;
+
+	// Free this thread's context.
+	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 = NULL;
+	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 thread's 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);
+
+	fz_var(thread);
+	fz_var(doc);
+
+	fz_try(ctx)
+	{
+		// Register default file types.
+		fz_register_document_handlers(ctx);
+
+		// Open the PDF, XPS or CBZ document.
+		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 (*thread));
+
+		for (i = 0; i < threads; i++)
+		{
+			fz_page *page;
+			fz_rect bbox;
+			fz_display_list *list;
+			fz_device *dev = NULL;
+			fz_pixmap *pix;
+			struct thread_data *data;
+
+			fz_var(dev);
+
+			fz_try(ctx)
+			{
+				// Load the relevant page for each thread. Note, that this
+				// cannot be done on the worker threads, as only one thread
+				// at a time can ever be accessing the document.
+				page = fz_load_page(ctx, doc, i);
+
+				// Compute the bounding box for each page.
+				bbox = fz_bound_page(ctx, page);
+
+				// 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.
+				list = fz_new_display_list(ctx, bbox);
+
+				// Create a display list device to populate the page's display list.
+				dev = fz_new_list_device(ctx, list);
+
+				// Run the page to that device.
+				fz_run_page(ctx, page, dev, fz_identity, NULL);
+
+				// Close the device neatly, so everything is flushed to the list.
+				fz_close_device(ctx, dev);
+			}
+			fz_always(ctx)
+			{
+				// Throw away the device.
+				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);
+			}
+			fz_catch(ctx)
+				fz_rethrow(ctx);
+
+			// Populate the data structure to be sent to the
+			// rendering thread for this page.
+			data = malloc(sizeof (*data));
+
+			data->pagenumber = i + 1;
+			data->ctx = ctx;
+			data->list = list;
+			data->bbox = bbox;
+			data->pix = NULL;
+			data->failed = 0;
+
+			// 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 = 0; i < threads; i++)
+		{
+			char filename[42];
+			struct thread_data *data;
+
+			if (pthread_join(thread[i], (void **) &data) != 0)
+				fail("pthread_join");
+
+			if (data->failed)
+			{
+				fprintf(stderr, "\tRendering for page %d failed\n", i + 1);
+			}
+			else
+			{
+				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.
+			fz_drop_pixmap(ctx, data->pix);
+			fz_drop_display_list(ctx, data->list);
+
+			// Free the data structure passed back and forth
+			// between the main thread and rendering thread.
+			free(data);
+		}
+	}
+	fz_always(ctx)
+	{
+		// Free the thread structure
+		free(thread);
+
+		// Drop the document
+		fz_drop_document(ctx, doc);
+	}
+	fz_catch(ctx)
+	{
+		fz_report_error(ctx);
+		fail("error");
+	}
+
+	// Finally the main thread's context is freed.
+	fz_drop_context(ctx);
+
+	fprintf(stderr, "finally!\n");
+	fflush(NULL);
+
+	return 0;
+}