/src/kdegraphics-thumbnailers/ps/gscreator.cpp
Line | Count | Source |
1 | | /* This file is part of the KDE libraries |
2 | | SPDX-FileCopyrightText: 2001 Malte Starostik <malte@kde.org> |
3 | | |
4 | | Handling of EPS previews SPDX-FileCopyrightText: 2003 Philipp Hullmann <phull@gmx.de> |
5 | | |
6 | | SPDX-License-Identifier: LGPL-2.0-or-later |
7 | | */ |
8 | | |
9 | | /* This function gets a path of a DVI, EPS, PS or PDF file and |
10 | | produces a PNG-Thumbnail which is stored as a QImage |
11 | | |
12 | | The program works as follows |
13 | | |
14 | | 1. Test if file is a DVI file |
15 | | |
16 | | 2. Create a child process (1), in which the |
17 | | file is to be changed into a PNG |
18 | | |
19 | | 3. Child-process (1) : |
20 | | |
21 | | 4. If file is DVI continue with 6 |
22 | | |
23 | | 5. If file is no DVI continue with 9 |
24 | | |
25 | | 6. Create another child process (2), in which the DVI is |
26 | | turned into PS using dvips |
27 | | |
28 | | 7. Parent process (2) : |
29 | | Turn the recently created PS file into a PNG file using gs |
30 | | |
31 | | 8. continue with 10 |
32 | | |
33 | | 9. Turn the PS,PDF or EPS file into a PNG file using gs |
34 | | |
35 | | 10. Parent process (1) |
36 | | store data in a QImage |
37 | | */ |
38 | | |
39 | | #ifdef HAVE_CONFIG_H |
40 | | #include <config.h> |
41 | | #endif |
42 | | |
43 | | |
44 | | #include <assert.h> |
45 | | #include <ctype.h> |
46 | | #include <stdlib.h> |
47 | | #include <stdio.h> |
48 | | #include <unistd.h> |
49 | | #include <signal.h> |
50 | | #ifdef HAVE_SYS_SELECT_H |
51 | | #include <sys/select.h> |
52 | | #endif |
53 | | #include <sys/time.h> |
54 | | #include <sys/wait.h> |
55 | | #include <fcntl.h> |
56 | | #include <errno.h> |
57 | | |
58 | | #include <QColor> |
59 | | #include <QFile> |
60 | | #include <QImage> |
61 | | #include <QVector> |
62 | | |
63 | | |
64 | | #include "gscreator.h" |
65 | | #include "dscparse.h" |
66 | | |
67 | | #include <KPluginFactory> |
68 | | |
69 | 0 | K_PLUGIN_CLASS_WITH_JSON(GSCreator, "gsthumbnail.json") Unexecuted instantiation: gsthumbnail_factory::tr(char const*, char const*, int) Unexecuted instantiation: gsthumbnail_factory::~gsthumbnail_factory() |
70 | 0 |
|
71 | 0 | // This PS snippet will be prepended to the actual file so that only |
72 | 0 | // the first page is output. |
73 | 0 | static const char *psprolog = |
74 | 0 | "%!PS-Adobe-3.0\n" |
75 | 0 | "/.showpage.orig /showpage load def\n" |
76 | 0 | "/.showpage.firstonly {\n" |
77 | 0 | " .showpage.orig\n" |
78 | 0 | " quit\n" |
79 | 0 | "} def\n" |
80 | 0 | "/showpage { .showpage.firstonly } def\n"; |
81 | 0 |
|
82 | 0 | // This is the code recommended by Adobe tech note 5002 for including |
83 | 0 | // EPS files. |
84 | 0 | static const char *epsprolog = |
85 | 0 | "%!PS-Adobe-3.0\n" |
86 | 0 | "userdict begin /pagelevel save def /showpage { } def\n" |
87 | 0 | "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit\n" |
88 | 0 | "[ ] 0 setdash newpath false setoverprint false setstrokeadjust\n"; |
89 | 0 |
|
90 | 0 | static const char * gsargs_ps[] = { |
91 | 0 | "gs", |
92 | 0 | "-sDEVICE=png16m", |
93 | 0 | "-sOutputFile=-", |
94 | 0 | "-dSAFER", |
95 | 0 | "-dPARANOIDSAFER", |
96 | 0 | "-dNOPAUSE", |
97 | 0 | "-dFirstPage=1", |
98 | 0 | "-dLastPage=1", |
99 | 0 | "-q", |
100 | 0 | "-", |
101 | 0 | nullptr, // file name |
102 | 0 | "-c", |
103 | 0 | "showpage", |
104 | 0 | "-c", |
105 | 0 | "quit", |
106 | 0 | nullptr |
107 | 0 | }; |
108 | 0 |
|
109 | 0 | static const char * gsargs_eps[] = { |
110 | 0 | "gs", |
111 | 0 | "-sDEVICE=png16m", |
112 | 0 | "-sOutputFile=-", |
113 | 0 | "-dSAFER", |
114 | 0 | "-dPARANOIDSAFER", |
115 | 0 | "-dNOPAUSE", |
116 | 0 | nullptr, // page size |
117 | 0 | nullptr, // resolution |
118 | 0 | "-q", |
119 | 0 | "-", |
120 | 0 | nullptr, // file name |
121 | 0 | "-c", |
122 | 0 | "pagelevel", |
123 | 0 | "-c", |
124 | 0 | "restore", |
125 | 0 | "-c", |
126 | 0 | "end", |
127 | 0 | "-c", |
128 | 0 | "showpage", |
129 | 0 | "-c", |
130 | 0 | "quit", |
131 | 0 | nullptr |
132 | 0 | }; |
133 | 0 |
|
134 | 0 | static const char *dvipsargs[] = { |
135 | 0 | "dvips", |
136 | 0 | "-n", |
137 | 0 | "1", |
138 | 0 | "-q", |
139 | 0 | "-o", |
140 | 0 | "-", |
141 | 0 | nullptr, // file name |
142 | 0 | nullptr |
143 | 0 | }; |
144 | 0 |
|
145 | 0 | static bool correctDVI(const QString& filename); |
146 | 0 |
|
147 | 0 |
|
148 | 0 | namespace { |
149 | 0 | bool got_sig_term = false; |
150 | 0 | void handle_sigterm( int ) { |
151 | 0 | got_sig_term = true; |
152 | 0 | } |
153 | | } |
154 | | |
155 | | GSCreator::GSCreator(QObject *parent, const QVariantList &args) |
156 | 19.2k | : KIO::ThumbnailCreator(parent, args) |
157 | 19.2k | { |
158 | 19.2k | } |
159 | | |
160 | | KIO::ThumbnailResult GSCreator::create(const KIO::ThumbnailRequest &request) |
161 | 19.2k | { |
162 | 19.2k | const QString path = request.url().toLocalFile(); |
163 | 19.2k | const int width = request.targetSize().width(); |
164 | 19.2k | const int height = request.targetSize().height(); |
165 | | // The code in the loop (when testing whether got_sig_term got set) |
166 | | // should read some variation of: |
167 | | // parentJob()->wasKilled() |
168 | | // |
169 | | // Unfortunatelly, that's currently impossible without breaking BIC. |
170 | | // So we need to catch the signal ourselves. |
171 | | // Otherwise, on certain funny PS files (for example |
172 | | // http://www.tjhsst.edu/~Eedanaher/pslife/life.ps ) |
173 | | // gs would run forever after we were dead. |
174 | | // #### Reconsider for KDE 4 ### |
175 | | // (24/12/03 - luis_pedro) |
176 | | // |
177 | 19.2k | typedef void ( *sighandler_t )( int ); |
178 | | // according to linux's "man signal" the above typedef is a gnu extension |
179 | 19.2k | sighandler_t oldhandler = signal( SIGTERM, handle_sigterm ); |
180 | | |
181 | 19.2k | int input[2]; |
182 | 19.2k | int output[2]; |
183 | 19.2k | int dvipipe[2]; |
184 | | |
185 | 19.2k | QByteArray data(1024, '\0'); |
186 | | |
187 | 19.2k | bool ok = false; |
188 | | |
189 | | // Test if file is DVI |
190 | 19.2k | bool no_dvi =!correctDVI(request.url().toLocalFile()); |
191 | | |
192 | 19.2k | if (pipe(input) == -1) { |
193 | 8.58k | return KIO::ThumbnailResult::fail(); |
194 | 8.58k | } |
195 | 10.6k | if (pipe(output) == -1) { |
196 | 0 | close(input[0]); |
197 | 0 | close(input[1]); |
198 | 0 | return KIO::ThumbnailResult::fail(); |
199 | 0 | } |
200 | | |
201 | 10.6k | KDSC dsc; |
202 | 10.6k | endComments = false; |
203 | 10.6k | dsc.setCommentHandler(this); |
204 | | |
205 | 10.6k | if (no_dvi) |
206 | 10.6k | { |
207 | 10.6k | FILE* fp = fopen(QFile::encodeName(path), "r"); |
208 | 10.6k | if (fp == nullptr) return KIO::ThumbnailResult::fail(); |
209 | | |
210 | 10.6k | char buf[4096]; |
211 | 10.6k | int count; |
212 | 21.2k | while ((count = fread(buf, sizeof(char), 4096, fp)) != 0 |
213 | 10.6k | && !endComments) { |
214 | 10.6k | dsc.scanData(buf, count); |
215 | 10.6k | } |
216 | 10.6k | fclose(fp); |
217 | | |
218 | 10.6k | if (dsc.pjl() || dsc.ctrld()) { |
219 | | // this file is a mess. |
220 | 254 | return KIO::ThumbnailResult::fail(); |
221 | 254 | } |
222 | 10.6k | } |
223 | | |
224 | 10.3k | std::unique_ptr<KDSCBBOX> bbox = dsc.bbox(); |
225 | | |
226 | 10.3k | const bool is_encapsulated = no_dvi |
227 | 10.3k | && (path.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive) |
228 | 10.3k | || path.endsWith(QLatin1String(".epsi"), Qt::CaseInsensitive)) |
229 | 0 | && bbox.get() != nullptr |
230 | 0 | && (bbox->width() > 0) |
231 | 0 | && (bbox->height() > 0) |
232 | 0 | && (dsc.page_count() <= 1); |
233 | | |
234 | 10.3k | char translation[64] = ""; |
235 | 10.3k | char pagesize[32] = ""; |
236 | 10.3k | char resopt[32] = ""; |
237 | | |
238 | 10.3k | if (is_encapsulated) { |
239 | | // GhostScript's rendering at the extremely low resolutions |
240 | | // required for thumbnails leaves something to be desired. To |
241 | | // get nicer images, we render to four times the required |
242 | | // resolution and let QImage scale the result. |
243 | 0 | const int hres = (width * 72) / bbox->width(); |
244 | 0 | const int vres = (height * 72) / bbox->height(); |
245 | 0 | const int resolution = (hres > vres ? vres : hres) * 4; |
246 | 0 | const int gswidth = ((bbox->urx() - bbox->llx()) * resolution) / 72; |
247 | 0 | const int gsheight = ((bbox->ury() - bbox->lly()) * resolution) / 72; |
248 | |
|
249 | 0 | snprintf(pagesize, 31, "-g%ix%i", gswidth, gsheight); |
250 | 0 | snprintf(resopt, 31, "-r%i", resolution); |
251 | 0 | snprintf(translation, 63, |
252 | 0 | " 0 %i sub 0 %i sub translate\n", bbox->llx(), |
253 | 0 | bbox->lly()); |
254 | 0 | } |
255 | | |
256 | 10.3k | const CDSC_PREVIEW_TYPE previewType = |
257 | 10.3k | static_cast<CDSC_PREVIEW_TYPE>(dsc.preview()); |
258 | | |
259 | 10.3k | switch (previewType) { |
260 | 15 | case CDSC_TIFF: |
261 | 429 | case CDSC_WMF: |
262 | 429 | case CDSC_PICT: |
263 | | // FIXME: these should take precedence, since they can hold |
264 | | // color previews, which EPSI can't (or can it?). |
265 | 429 | break; |
266 | 205 | case CDSC_EPSI: |
267 | 205 | { |
268 | 205 | if (!bbox) { |
269 | 205 | break; |
270 | 205 | } |
271 | 0 | const int xscale = bbox->width() / width; |
272 | 0 | const int yscale = bbox->height() / height; |
273 | 0 | const int scale = xscale < yscale ? xscale : yscale; |
274 | 0 | if (scale == 0) break; |
275 | 0 | if (auto result = getEPSIPreview(path, |
276 | 0 | dsc.beginpreview(), |
277 | 0 | dsc.endpreview(), |
278 | 0 | bbox->width() / scale, |
279 | 0 | bbox->height() / scale); result.isValid()) |
280 | 0 | return result; |
281 | | // If the preview extraction routine fails, gs is used to |
282 | | // create a thumbnail. |
283 | 0 | } |
284 | 0 | break; |
285 | 9.74k | case CDSC_NOPREVIEW: |
286 | 9.74k | default: |
287 | | // need to run ghostscript in these cases |
288 | 9.74k | break; |
289 | 10.3k | } |
290 | | |
291 | 10.3k | pid_t pid = fork(); |
292 | 10.3k | if (pid == 0) { |
293 | | // Child process (1) |
294 | | |
295 | | // close(STDERR_FILENO); |
296 | | |
297 | | // find first zero entry in gsargs and put the filename |
298 | | // or - (stdin) there, if DVI |
299 | 0 | const char **gsargs = gsargs_ps; |
300 | 0 | const char **arg = gsargs; |
301 | |
|
302 | 0 | if (no_dvi && is_encapsulated) { |
303 | 0 | gsargs = gsargs_eps; |
304 | 0 | arg = gsargs; |
305 | | |
306 | | // find first zero entry and put page size there |
307 | 0 | while (*arg) ++arg; |
308 | 0 | *arg = pagesize; |
309 | | |
310 | | // find second zero entry and put resolution there |
311 | 0 | while (*arg) ++arg; |
312 | 0 | *arg = resopt; |
313 | 0 | } |
314 | | |
315 | | // find next zero entry and put the filename there |
316 | 0 | QByteArray fname = QFile::encodeName( path ); |
317 | 0 | while (*arg) |
318 | 0 | ++arg; |
319 | 0 | if( no_dvi ) |
320 | 0 | *arg = fname.data(); |
321 | 0 | else |
322 | 0 | *arg = "-"; |
323 | | |
324 | | // find first zero entry in dvipsargs and put the filename there |
325 | 0 | arg = dvipsargs; |
326 | 0 | while (*arg) |
327 | 0 | ++arg; |
328 | 0 | *arg = fname.data(); |
329 | |
|
330 | 0 | if( !no_dvi ){ |
331 | 0 | pipe(dvipipe); |
332 | 0 | pid_t pid_two = fork(); |
333 | 0 | if( pid_two == 0 ){ |
334 | | // Child process (2), reopen stdout on the pipe "dvipipe" and exec dvips |
335 | |
|
336 | 0 | close(input[0]); |
337 | 0 | close(input[1]); |
338 | 0 | close(output[0]); |
339 | 0 | close(output[1]); |
340 | 0 | close(dvipipe[0]); |
341 | |
|
342 | 0 | dup2( dvipipe[1], STDOUT_FILENO); |
343 | |
|
344 | 0 | execvp(dvipsargs[0], const_cast<char *const *>(dvipsargs)); |
345 | 0 | _exit(1); |
346 | 0 | } |
347 | 0 | else if(pid_two != -1){ |
348 | 0 | close(input[1]); |
349 | 0 | close(output[0]); |
350 | 0 | close(dvipipe[1]); |
351 | |
|
352 | 0 | dup2( dvipipe[0], STDIN_FILENO); |
353 | 0 | dup2( output[1], STDOUT_FILENO); |
354 | |
|
355 | 0 | execvp(gsargs[0], const_cast<char *const *>(gsargs)); |
356 | 0 | _exit(1); |
357 | 0 | } |
358 | 0 | else{ |
359 | | // fork() (2) failed, close these |
360 | 0 | close(dvipipe[0]); |
361 | 0 | close(dvipipe[1]); |
362 | 0 | } |
363 | |
|
364 | 0 | } |
365 | 0 | else if( no_dvi ){ |
366 | | // Reopen stdin/stdout on the pipes and exec gs |
367 | 0 | close(input[1]); |
368 | 0 | close(output[0]); |
369 | |
|
370 | 0 | dup2(input[0], STDIN_FILENO); |
371 | 0 | dup2(output[1], STDOUT_FILENO); |
372 | |
|
373 | 0 | execvp(gsargs[0], const_cast<char *const *>(gsargs)); |
374 | 0 | _exit(1); |
375 | 0 | } |
376 | 0 | } |
377 | 10.3k | else if (pid != -1) { |
378 | | // Parent process, write first-page-only-hack (the hack is not |
379 | | // used if DVI) and read the png output |
380 | 10.3k | close(input[0]); |
381 | 10.3k | close(output[1]); |
382 | 10.3k | const char *prolog; |
383 | 10.3k | if (is_encapsulated) |
384 | 0 | prolog = epsprolog; |
385 | 10.3k | else |
386 | 10.3k | prolog = psprolog; |
387 | 10.3k | int count = write(input[1], prolog, strlen(prolog)); |
388 | 10.3k | if (is_encapsulated) |
389 | 0 | write(input[1], translation, strlen(translation)); |
390 | | |
391 | 10.3k | close(input[1]); |
392 | 10.3k | if (count == static_cast<int>(strlen(prolog))) { |
393 | 10.3k | int offset = 0; |
394 | 20.7k | while (!ok) { |
395 | 10.3k | fd_set fds; |
396 | 10.3k | FD_ZERO(&fds); |
397 | 10.3k | FD_SET(output[0], &fds); |
398 | 10.3k | struct timeval tv; |
399 | 10.3k | tv.tv_sec = 20; |
400 | 10.3k | tv.tv_usec = 0; |
401 | | |
402 | 10.3k | got_sig_term = false; |
403 | 10.3k | if (select(output[0] + 1, &fds, nullptr, nullptr, &tv) <= 0) { |
404 | 0 | if ( ( errno == EINTR || errno == EAGAIN ) && !got_sig_term ) continue; |
405 | 0 | break; // error, timeout or master wants us to quit (SIGTERM) |
406 | 0 | } |
407 | 10.3k | if (FD_ISSET(output[0], &fds)) { |
408 | 10.3k | count = read(output[0], data.data() + offset, 1024); |
409 | 10.3k | if (count == -1) |
410 | 0 | break; |
411 | 10.3k | else |
412 | 10.3k | if (count) // prepare for next block |
413 | 0 | { |
414 | 0 | offset += count; |
415 | 0 | data.resize(offset + 1024); |
416 | 0 | } |
417 | 10.3k | else // got all data |
418 | 10.3k | { |
419 | 10.3k | data.resize(offset); |
420 | 10.3k | ok = true; |
421 | 10.3k | } |
422 | 10.3k | } |
423 | 10.3k | } |
424 | 10.3k | } |
425 | 10.3k | if (!ok) // error or timeout, gs probably didn't exit yet |
426 | 0 | { |
427 | 0 | kill(pid, SIGTERM); |
428 | 0 | } |
429 | | |
430 | 10.3k | int status = 0; |
431 | 10.3k | int ret; |
432 | 10.3k | do { |
433 | 10.3k | ret = waitpid(pid, &status, 0); |
434 | 10.3k | } while (ret == -1 && errno == EINTR); |
435 | 10.3k | if (ret != pid || (status != 0 && status != 256) ) |
436 | 0 | ok = false; |
437 | 10.3k | } |
438 | 0 | else { |
439 | | // fork() (1) failed, close these |
440 | 0 | close(input[0]); |
441 | 0 | close(input[1]); |
442 | 0 | close(output[1]); |
443 | 0 | } |
444 | 10.3k | close(output[0]); |
445 | | |
446 | 10.3k | QImage img; |
447 | 10.3k | bool loaded = img.loadFromData( data ); |
448 | | |
449 | 10.3k | if (!loaded) { |
450 | | // Sometimes gs spits some warning messages before the actual image |
451 | | // try to skip them |
452 | 10.3k | const QByteArray pngHeader = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"; |
453 | 10.3k | const int pngMarkerIndex = data.indexOf(pngHeader); |
454 | 10.3k | if (pngMarkerIndex > 0) { |
455 | 0 | data = data.mid(pngMarkerIndex); |
456 | 0 | loaded = img.loadFromData( data ); |
457 | 0 | } |
458 | 10.3k | } |
459 | | |
460 | 10.3k | if ( got_sig_term && |
461 | 0 | oldhandler != SIG_ERR && |
462 | 0 | oldhandler != SIG_DFL && |
463 | 0 | oldhandler != SIG_IGN ) { |
464 | 0 | oldhandler( SIGTERM ); // propagate the signal. Other things might rely on it |
465 | 0 | } |
466 | 10.3k | if ( oldhandler != SIG_ERR ) signal( SIGTERM, oldhandler ); |
467 | | |
468 | 10.3k | if (loaded) { |
469 | 0 | return KIO::ThumbnailResult::pass(img); |
470 | 0 | } |
471 | | |
472 | 10.3k | return KIO::ThumbnailResult::fail(); |
473 | 10.3k | } |
474 | | |
475 | | void GSCreator::comment(Name name) |
476 | 12.8k | { |
477 | 12.8k | switch (name) { |
478 | 11 | case EndPreview: |
479 | 832 | case BeginProlog: |
480 | 1.58k | case Page: |
481 | 1.58k | endComments = true; |
482 | 1.58k | break; |
483 | | |
484 | 11.2k | default: |
485 | 11.2k | break; |
486 | 12.8k | } |
487 | 12.8k | } |
488 | | |
489 | | // Quick function to check if the filename corresponds to a valid DVI |
490 | | // file. Returns true if <filename> is a DVI file, false otherwise. |
491 | | |
492 | | static bool correctDVI(const QString& filename) |
493 | 19.2k | { |
494 | 19.2k | QFile f(filename); |
495 | 19.2k | if (!f.open(QIODevice::ReadOnly)) |
496 | 0 | return false; |
497 | | |
498 | 19.2k | unsigned char test[4]; |
499 | 19.2k | if ( f.read( (char *)test,2)<2 || test[0] != 247 || test[1] != 2 ) |
500 | 19.1k | return false; |
501 | | |
502 | 110 | int n = f.size(); |
503 | 110 | if ( n < 134 ) // Too short for a dvi file |
504 | 21 | return false; |
505 | 89 | f.seek( n-4 ); |
506 | | |
507 | 89 | unsigned char trailer[4] = { 0xdf,0xdf,0xdf,0xdf }; |
508 | | |
509 | 89 | if ( f.read( (char *)test, 4 )<4 || strncmp( (char *)test, (char*) trailer, 4 ) ) |
510 | 89 | return false; |
511 | | // We suppose now that the dvi file is complete and OK |
512 | 0 | return true; |
513 | 89 | } |
514 | | |
515 | | KIO::ThumbnailResult GSCreator::getEPSIPreview(const QString &path, long start, long |
516 | | end, int imgwidth, int imgheight) |
517 | 0 | { |
518 | 0 | FILE *fp; |
519 | 0 | fp = fopen(QFile::encodeName(path), "r"); |
520 | 0 | if (fp == nullptr) return KIO::ThumbnailResult::fail(); |
521 | | |
522 | 0 | const long previewsize = end - start + 1; |
523 | |
|
524 | 0 | char *buf = (char *) malloc(previewsize); |
525 | 0 | fseek(fp, start, SEEK_SET); |
526 | 0 | int count = fread(buf, sizeof(char), previewsize - 1, fp); |
527 | 0 | fclose(fp); |
528 | 0 | buf[previewsize - 1] = 0; |
529 | 0 | if (count != previewsize - 1) |
530 | 0 | { |
531 | 0 | free(buf); |
532 | 0 | return KIO::ThumbnailResult::fail(); |
533 | 0 | } |
534 | | |
535 | 0 | QString previewstr = QString::fromLatin1(buf); |
536 | 0 | free(buf); |
537 | |
|
538 | 0 | int offset = 0; |
539 | 0 | while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++; |
540 | 0 | int digits = 0; |
541 | 0 | while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++; |
542 | 0 | int width = previewstr.mid(offset, digits).toInt(); |
543 | 0 | offset += digits + 1; |
544 | 0 | while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++; |
545 | 0 | digits = 0; |
546 | 0 | while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++; |
547 | 0 | int height = previewstr.mid(offset, digits).toInt(); |
548 | 0 | offset += digits + 1; |
549 | 0 | while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++; |
550 | 0 | digits = 0; |
551 | 0 | while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++; |
552 | 0 | int depth = previewstr.mid(offset, digits).toInt(); |
553 | | |
554 | | // skip over the rest of the BeginPreview comment |
555 | 0 | while ((offset < previewsize) && |
556 | 0 | previewstr[offset] != QLatin1Char('\n') && |
557 | 0 | previewstr[offset] != QLatin1Char('\r')) offset++; |
558 | 0 | while ((offset < previewsize) && previewstr[offset] != QLatin1Char('%')) offset++; |
559 | |
|
560 | 0 | unsigned int imagedepth; |
561 | 0 | switch (depth) { |
562 | 0 | case 1: |
563 | 0 | case 2: |
564 | 0 | case 4: |
565 | 0 | case 8: |
566 | 0 | imagedepth = 8; |
567 | 0 | break; |
568 | 0 | case 12: // valid, but not (yet) supported |
569 | 0 | default: // illegal value |
570 | 0 | return KIO::ThumbnailResult::fail(); |
571 | 0 | } |
572 | | |
573 | 0 | unsigned int colors = (1U << depth); |
574 | 0 | QImage img(width, height, QImage::Format_Indexed8); |
575 | 0 | img.setColorCount(colors); |
576 | |
|
577 | 0 | if (imagedepth <= 8) { |
578 | 0 | for (unsigned int gray = 0; gray < colors; gray++) { |
579 | 0 | unsigned int grayvalue = (255U * (colors - 1 - gray)) / |
580 | 0 | (colors - 1); |
581 | 0 | img.setColor(gray, qRgb(grayvalue, grayvalue, grayvalue)); |
582 | 0 | } |
583 | 0 | } |
584 | |
|
585 | 0 | const unsigned int bits_per_scan_line = width * depth; |
586 | 0 | unsigned int bytes_per_scan_line = bits_per_scan_line / 8; |
587 | 0 | if (bits_per_scan_line % 8) bytes_per_scan_line++; |
588 | 0 | const unsigned int bindatabytes = height * bytes_per_scan_line; |
589 | 0 | QVector<unsigned char> bindata(bindatabytes); |
590 | |
|
591 | 0 | for (unsigned int i = 0; i < bindatabytes; i++) { |
592 | 0 | if (offset >= previewsize) |
593 | 0 | return KIO::ThumbnailResult::fail(); |
594 | | |
595 | 0 | while (!isxdigit(previewstr[offset].toLatin1()) && |
596 | 0 | offset < previewsize) |
597 | 0 | offset++; |
598 | |
|
599 | 0 | bool ok = false; |
600 | 0 | bindata[i] = static_cast<unsigned char>(previewstr.mid(offset, 2).toUInt(&ok, 16)); |
601 | 0 | if (!ok) |
602 | 0 | return KIO::ThumbnailResult::fail(); |
603 | | |
604 | 0 | offset += 2; |
605 | 0 | } |
606 | | |
607 | 0 | for (int scanline = 0; scanline < height; scanline++) { |
608 | 0 | unsigned char *scanlineptr = img.scanLine(scanline); |
609 | |
|
610 | 0 | for (int pixelindex = 0; pixelindex < width; pixelindex++) { |
611 | 0 | unsigned char pixelvalue = 0; |
612 | 0 | const unsigned int bitoffset = |
613 | 0 | scanline * bytes_per_scan_line * 8U + pixelindex * depth; |
614 | 0 | for (int depthindex = 0; depthindex < depth; |
615 | 0 | depthindex++) { |
616 | 0 | const unsigned int byteindex = (bitoffset + depthindex) / 8U; |
617 | 0 | const unsigned int bitindex = |
618 | 0 | 7 - ((bitoffset + depthindex) % 8U); |
619 | 0 | const unsigned char bitvalue = |
620 | 0 | (bindata[byteindex] & static_cast<unsigned char>(1U << bitindex)) >> bitindex; |
621 | 0 | pixelvalue |= (bitvalue << depthindex); |
622 | 0 | } |
623 | 0 | scanlineptr[pixelindex] = pixelvalue; |
624 | 0 | } |
625 | 0 | } |
626 | |
|
627 | 0 | QImage outimg = img.convertToFormat(QImage::Format_RGB32).scaled(imgwidth, imgheight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); |
628 | |
|
629 | 0 | return !outimg.isNull() ? KIO::ThumbnailResult::pass(outimg) : KIO::ThumbnailResult::fail(); |
630 | 0 | } |
631 | | |
632 | | #include "gscreator.moc" |