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