Mercurial > hgrepos > Python2 > PyMuPDF
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 } |
