comparison mupdf-source/platform/java/example/MultiThreaded.java @ 3:2c135c81b16c

MERGE: upstream PyMuPDF 1.26.4 with MuPDF 1.26.7
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:44:09 +0200
parents b50eed0cc0ef
children
comparison
equal deleted inserted replaced
0:6015a75abc2d 3:2c135c81b16c
1 // Copyright (C) 2022 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 /**
24 * Multi-threaded rendering of all pages in a document to PNG images.
25 *
26 * First look at Example.java and make sure you understand it.
27 *
28 * MuPDF can also be used in a more complex way; where the MuPDF
29 * library is called concurrently from multiple threads within a
30 * single application. The MuPDF JNI library internally uses a set of
31 * mutexes (critical section objects or pthreads depending on
32 * platform) to handle locking around operations that would conflict
33 * if performed concurrently in multiple threads.
34 *
35 * The following simple rules should be followed to ensure that
36 * multi-threaded operations run smoothly:
37 *
38 * * The Document can only be accessed by one thread at a time, but
39 * once DisplayLists are created from that Document, multiple
40 * threads can operate on those.
41 *
42 * * Each Device may only be accessed by one thread at a time.
43 *
44 * This example will create one main thread for reading pages from the
45 * document, and one thread per page for rendering. After rendering
46 * the main thread will wait for each rendering thread to complete
47 * before writing that thread's rendered image to a PNG image. There
48 * is nothing in MuPDF requiring a rendering thread to only render a
49 * single page, this is just a design decision taken for this example.
50 *
51 * To build this example in a source tree:
52 * make -C platform/java examples
53 *
54 * To render all page from a document and output PNGs, run:
55 * java -classpath build/java/debug -Djava.library.path=build/java/debug \
56 * example.MultiThreaded document.pdf
57 *
58 * Caution! As all pages are rendered simultaneously, please choose a
59 * file with just a few pages to avoid stressing your machine too
60 * much. Also you may run in to a limitation on the number of threads
61 * depending on your environment.
62 */
63
64 package example;
65
66 /* Import all MuPDF java classes. */
67 import com.artifex.mupdf.fitz.*;
68
69 class MultiThreaded
70 {
71 public static void main(String args[])
72 {
73 /* Parse arguments. */
74 if (args.length < 1)
75 {
76 System.err.println("usage: MultiThreaded input-file");
77 System.err.println("\tinput-file: path of PDF, XPS, CBZ or EPUB document to open");
78 return;
79 }
80
81 String filename = args[0];
82
83 /* Open the document on the main thread.
84 * You may not reference doc in any other thread.
85 */
86 Document doc;
87 try {
88 doc = Document.openDocument(filename);
89 } catch (RuntimeException ex) {
90 System.err.println("cannot open document: " + ex.getMessage());
91 return;
92 }
93
94 /* Count the pages on the main thread. */
95 int pageCount;
96 try {
97 pageCount = doc.countPages();
98 } catch (RuntimeException ex) {
99 System.err.println("cannot count document pages: " + ex.getMessage());
100 return;
101 }
102
103 /* Create a thread, an output pixmap and an exception placeholder per page. */
104 final Thread[] threads = new Thread[pageCount];
105 final Pixmap[] pixmaps = new Pixmap[pageCount];
106 final Exception[] exceptions = new Exception[pageCount];
107
108 for (int i = 0; i < pageCount; ++i)
109 {
110 final int pageNumber = i;
111 try {
112 /* Load page and convert it to a display list on
113 * the main thread. Note that this cannot be done
114 * in worker threads since doc should only be used
115 * by the main thread.
116 */
117 Page page = doc.loadPage(pageNumber);
118
119 /* Determine the bounding box for the page */
120 final Rect bounds = page.getBounds();
121
122 /* Convert the page into a display list. The display
123 * list can be used by any other thread as it is not
124 * bound to doc.
125 */
126 final DisplayList displayList = page.toDisplayList();
127
128 /* Pass display list, page size and destination pixmap to each
129 * thread for rendering. Also pass page number for debug printing.
130 * Everything inside run() takes place in each worker thread.
131 */
132 threads[pageNumber] = new Thread() {
133 public void run() {
134 try {
135 System.out.println(pageNumber + ": creating pixmap");
136
137 /* Create a white destination pixmap with correct dimensions. */
138 pixmaps[pageNumber] = new Pixmap(ColorSpace.DeviceRGB, bounds);
139 pixmaps[pageNumber].clear(0xff);
140
141 System.out.println(pageNumber + ": rendering display list to pixmap");
142
143 /* Run the display list through a DrawDevice which
144 * will render the requested area of the page to the
145 * given pixmap.
146 */
147 DrawDevice dev = new DrawDevice(pixmaps[pageNumber]);
148 displayList.run(dev, Matrix.Identity(), bounds, null);
149 dev.close();
150
151 } catch (RuntimeException ex) {
152 pixmaps[pageNumber] = null;
153 exceptions[pageNumber] = ex;
154 }
155 }
156 };
157
158 threads[pageNumber].start();
159
160 } catch (RuntimeException ex) {
161 System.err.println(pageNumber + ": cannot load page, skipping render: " + ex.getMessage());
162 exceptions[pageNumber] = ex;
163 }
164 }
165
166 /* Wait for threads to finish in reverse order. */
167 System.out.println("joining " + pageCount + " threads");
168 for (int i = 0; i < pageCount; ++i)
169 {
170 if (threads[i] == null) {
171 System.err.println(i + ": skipping save, page loading failed: " + exceptions[i].toString());
172 continue;
173 }
174
175 try {
176 threads[i].join();
177 } catch (InterruptedException ex) {
178 System.err.println(i + ": interrupted while waiting for rendering result, skipping all remaining pages: " + ex.getMessage());
179 break;
180 }
181
182 if (pixmaps[i] == null) {
183 System.err.println(i + ": skipping save, page rendering failed: " + exceptions[i].toString());
184 continue;
185 }
186
187 /* Save destination pixmap from each thread to a PNG. */
188 String pngfilename = String.format("out-%04d.png", i);
189 System.out.println(i + ": saving rendered pixmap as " + pngfilename);
190 pixmaps[i].saveAsPNG(pngfilename);
191 }
192
193 System.out.println("finally!");
194 }
195 }