comparison 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
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 /*
2 Multi-threaded rendering of all pages in a document to PNG images.
3
4 First look at docs/example.c and make sure you understand it.
5 Then, before coming back here to see an example of multi-threading,
6 please read the multi-threading section in:
7 https://mupdf.readthedocs.io/en/latest/using-mupdf.html#multi-threading
8
9 This example will create one main thread for reading pages from the
10 document, and one thread per page for rendering. After rendering
11 the main thread will wait for each rendering thread to complete before
12 writing that thread's rendered image to a PNG image. There is
13 nothing in MuPDF requiring a rendering thread to only render a
14 single page, this is just a design decision taken for this example.
15
16 To build this example in a source tree and render every page as a
17 separate PNG, run:
18 make examples
19 ./build/debug/multi-threaded document.pdf
20
21 To build from installed sources, and render the same document, run:
22 gcc -I/usr/local/include -o multi-threaded \
23 /usr/local/share/doc/mupdf/examples/multi-threaded.c \
24 /usr/local/lib/libmupdf.a \
25 /usr/local/lib/libmupdfthird.a \
26 -lpthread -lm
27 ./multi-threaded document.pdf
28
29 Caution! As all pages are rendered simultaneously, please choose a
30 file with just a few pages to avoid stressing your machine too
31 much. Also you may run in to a limitation on the number of threads
32 depending on your environment.
33 */
34
35 //Include the MuPDF header file, and pthread's header file.
36 #include <mupdf/fitz.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <pthread.h>
40
41 // A convenience function for dying abruptly on pthread errors.
42
43 void
44 fail(const char *msg)
45 {
46 fprintf(stderr, "%s\n", msg);
47 abort();
48 }
49
50 // The data structure passed between the requesting main thread and
51 // each rendering thread.
52
53 struct thread_data {
54 // A pointer to the original context in the main thread sent
55 // from main to rendering thread. It will be used to create
56 // each rendering thread's context clone.
57 fz_context *ctx;
58
59 // Page number sent from main to rendering thread for printing
60 int pagenumber;
61
62 // The display list as obtained by the main thread and sent
63 // from main to rendering thread. This contains the drawing
64 // commands (text, images, etc.) for the page that should be
65 // rendered.
66 fz_display_list *list;
67
68 // The area of the page to render as obtained by the main
69 // thread and sent from main to rendering thread.
70 fz_rect bbox;
71
72 // This is the result, a pixmap containing the rendered page.
73 // It is passed first from main thread to the rendering
74 // thread, then its samples are changed by the rendering
75 // thread, and then back from the rendering thread to the main
76 // thread.
77 fz_pixmap *pix;
78
79 // This is a note of whether a given thread failed or not.
80 int failed;
81 };
82
83 // This is the function run by each rendering function. It takes
84 // pointer to an instance of the data structure described above and
85 // renders the display list into the pixmap before exiting.
86
87 void *
88 renderer(void *data_)
89 {
90 struct thread_data *data = (struct thread_data *)data_;
91 int pagenumber = data->pagenumber;
92 fz_context *ctx = data->ctx;
93 fz_display_list *list = data->list;
94 fz_rect bbox = data->bbox;
95 fz_device *dev = NULL;
96
97 fprintf(stderr, "thread at page %d loading!\n", pagenumber);
98
99 // The context pointer is pointing to the main thread's
100 // context, so here we create a new context based on it for
101 // use in this thread.
102 ctx = fz_clone_context(ctx);
103
104 // Next we run the display list through the draw device which
105 // will render the request area of the page to the pixmap.
106
107 fz_var(dev);
108
109 fprintf(stderr, "thread at page %d rendering!\n", pagenumber);
110 fz_try(ctx)
111 {
112 // Create a white pixmap using the correct dimensions.
113 data->pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), fz_round_rect(bbox), NULL, 0);
114 fz_clear_pixmap_with_value(ctx, data->pix, 0xff);
115
116 // Do the actual rendering.
117 dev = fz_new_draw_device(ctx, fz_identity, data->pix);
118 fz_run_display_list(ctx, list, dev, fz_identity, bbox, NULL);
119 fz_close_device(ctx, dev);
120 }
121 fz_always(ctx)
122 fz_drop_device(ctx, dev);
123 fz_catch(ctx)
124 data->failed = 1;
125
126 // Free this thread's context.
127 fz_drop_context(ctx);
128
129 fprintf(stderr, "thread at page %d done!\n", pagenumber);
130
131 return data;
132 }
133
134 // These are the two locking functions required by MuPDF when
135 // operating in a multi-threaded environment. They each take a user
136 // argument that can be used to transfer some state, in this case a
137 // pointer to the array of mutexes.
138
139 void lock_mutex(void *user, int lock)
140 {
141 pthread_mutex_t *mutex = (pthread_mutex_t *) user;
142
143 if (pthread_mutex_lock(&mutex[lock]) != 0)
144 fail("pthread_mutex_lock()");
145 }
146
147 void unlock_mutex(void *user, int lock)
148 {
149 pthread_mutex_t *mutex = (pthread_mutex_t *) user;
150
151 if (pthread_mutex_unlock(&mutex[lock]) != 0)
152 fail("pthread_mutex_unlock()");
153 }
154
155 int main(int argc, char **argv)
156 {
157 char *filename = argc >= 2 ? argv[1] : "";
158 pthread_t *thread = NULL;
159 fz_locks_context locks;
160 pthread_mutex_t mutex[FZ_LOCK_MAX];
161 fz_context *ctx;
162 fz_document *doc = NULL;
163 int threads;
164 int i;
165
166 // Initialize FZ_LOCK_MAX number of non-recursive mutexes.
167 for (i = 0; i < FZ_LOCK_MAX; i++)
168 {
169 if (pthread_mutex_init(&mutex[i], NULL) != 0)
170 fail("pthread_mutex_init()");
171 }
172
173 // Initialize the locking structure with function pointers to
174 // the locking functions and to the user data. In this case
175 // the user data is a pointer to the array of mutexes so the
176 // locking functions can find the relevant lock to change when
177 // they are called. This way we avoid global variables.
178 locks.user = mutex;
179 locks.lock = lock_mutex;
180 locks.unlock = unlock_mutex;
181
182 // This is the main thread's context function, so supply the
183 // locking structure. This context will be used to parse all
184 // the pages from the document.
185 ctx = fz_new_context(NULL, &locks, FZ_STORE_UNLIMITED);
186
187 fz_var(thread);
188 fz_var(doc);
189
190 fz_try(ctx)
191 {
192 // Register default file types.
193 fz_register_document_handlers(ctx);
194
195 // Open the PDF, XPS or CBZ document.
196 doc = fz_open_document(ctx, filename);
197
198 // Retrieve the number of pages, which translates to the
199 // number of threads used for rendering pages.
200 threads = fz_count_pages(ctx, doc);
201 fprintf(stderr, "spawning %d threads, one per page...\n", threads);
202
203 thread = malloc(threads * sizeof (*thread));
204
205 for (i = 0; i < threads; i++)
206 {
207 fz_page *page;
208 fz_rect bbox;
209 fz_display_list *list;
210 fz_device *dev = NULL;
211 fz_pixmap *pix;
212 struct thread_data *data;
213
214 fz_var(dev);
215
216 fz_try(ctx)
217 {
218 // Load the relevant page for each thread. Note, that this
219 // cannot be done on the worker threads, as only one thread
220 // at a time can ever be accessing the document.
221 page = fz_load_page(ctx, doc, i);
222
223 // Compute the bounding box for each page.
224 bbox = fz_bound_page(ctx, page);
225
226 // Create a display list that will hold the drawing
227 // commands for the page. Once we have the display list
228 // this can safely be used on any other thread.
229 list = fz_new_display_list(ctx, bbox);
230
231 // Create a display list device to populate the page's display list.
232 dev = fz_new_list_device(ctx, list);
233
234 // Run the page to that device.
235 fz_run_page(ctx, page, dev, fz_identity, NULL);
236
237 // Close the device neatly, so everything is flushed to the list.
238 fz_close_device(ctx, dev);
239 }
240 fz_always(ctx)
241 {
242 // Throw away the device.
243 fz_drop_device(ctx, dev);
244
245 // The page is no longer needed, all drawing commands
246 // are now in the display list.
247 fz_drop_page(ctx, page);
248 }
249 fz_catch(ctx)
250 fz_rethrow(ctx);
251
252 // Populate the data structure to be sent to the
253 // rendering thread for this page.
254 data = malloc(sizeof (*data));
255
256 data->pagenumber = i + 1;
257 data->ctx = ctx;
258 data->list = list;
259 data->bbox = bbox;
260 data->pix = NULL;
261 data->failed = 0;
262
263 // Create the thread and pass it the data structure.
264 if (pthread_create(&thread[i], NULL, renderer, data) != 0)
265 fail("pthread_create()");
266 }
267
268 // Now each thread is rendering pages, so wait for each thread
269 // to complete its rendering.
270 fprintf(stderr, "joining %d threads...\n", threads);
271 for (i = 0; i < threads; i++)
272 {
273 char filename[42];
274 struct thread_data *data;
275
276 if (pthread_join(thread[i], (void **) &data) != 0)
277 fail("pthread_join");
278
279 if (data->failed)
280 {
281 fprintf(stderr, "\tRendering for page %d failed\n", i + 1);
282 }
283 else
284 {
285 sprintf(filename, "out%04d.png", i);
286 fprintf(stderr, "\tSaving %s...\n", filename);
287
288 // Write the rendered image to a PNG file
289 fz_save_pixmap_as_png(ctx, data->pix, filename);
290 }
291
292 // Free the thread's pixmap and display list.
293 fz_drop_pixmap(ctx, data->pix);
294 fz_drop_display_list(ctx, data->list);
295
296 // Free the data structure passed back and forth
297 // between the main thread and rendering thread.
298 free(data);
299 }
300 }
301 fz_always(ctx)
302 {
303 // Free the thread structure
304 free(thread);
305
306 // Drop the document
307 fz_drop_document(ctx, doc);
308 }
309 fz_catch(ctx)
310 {
311 fz_report_error(ctx);
312 fail("error");
313 }
314
315 // Finally the main thread's context is freed.
316 fz_drop_context(ctx);
317
318 fprintf(stderr, "finally!\n");
319 fflush(NULL);
320
321 return 0;
322 }