1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtGui module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "private/qppmhandler_p.h" |
41 | |
42 | #ifndef QT_NO_IMAGEFORMAT_PPM |
43 | |
44 | #include <qdebug.h> |
45 | #include <qimage.h> |
46 | #include <qlist.h> |
47 | #include <qloggingcategory.h> |
48 | #include <qrgba64.h> |
49 | #include <qvariant.h> |
50 | |
51 | #include <ctype.h> |
52 | |
53 | QT_BEGIN_NAMESPACE |
54 | |
55 | Q_DECLARE_LOGGING_CATEGORY(lcImageIo) |
56 | |
57 | /***************************************************************************** |
58 | PBM/PGM/PPM (ASCII and RAW) image read/write functions |
59 | *****************************************************************************/ |
60 | |
61 | static void discard_pbm_line(QIODevice *d) |
62 | { |
63 | const int buflen = 100; |
64 | char buf[buflen]; |
65 | int res = 0; |
66 | do { |
67 | res = d->readLine(buf, buflen); |
68 | } while (res > 0 && buf[res-1] != '\n'); |
69 | } |
70 | |
71 | static int read_pbm_int(QIODevice *d) |
72 | { |
73 | char c; |
74 | int val = -1; |
75 | bool digit; |
76 | bool hasOverflow = false; |
77 | for (;;) { |
78 | if (!d->getChar(&c)) // end of file |
79 | break; |
80 | digit = isdigit((uchar) c); |
81 | if (val != -1) { |
82 | if (digit) { |
83 | const int cValue = c - '0'; |
84 | if (val <= (INT_MAX - cValue) / 10) { |
85 | val = 10*val + cValue; |
86 | } else { |
87 | hasOverflow = true; |
88 | } |
89 | continue; |
90 | } else { |
91 | if (c == '#') // comment |
92 | discard_pbm_line(d); |
93 | break; |
94 | } |
95 | } |
96 | if (digit) // first digit |
97 | val = c - '0'; |
98 | else if (isspace((uchar) c)) |
99 | continue; |
100 | else if (c == '#') |
101 | discard_pbm_line(d); |
102 | else |
103 | break; |
104 | } |
105 | return hasOverflow ? -1 : val; |
106 | } |
107 | |
108 | static bool (QIODevice *device, char& type, int& w, int& h, int& mcc) |
109 | { |
110 | char buf[3]; |
111 | if (device->read(buf, 3) != 3) // read P[1-6]<white-space> |
112 | return false; |
113 | |
114 | if (!(buf[0] == 'P' && isdigit((uchar) buf[1]) && isspace((uchar) buf[2]))) |
115 | return false; |
116 | |
117 | type = buf[1]; |
118 | if (type < '1' || type > '6') |
119 | return false; |
120 | |
121 | w = read_pbm_int(device); // get image width |
122 | h = read_pbm_int(device); // get image height |
123 | |
124 | if (type == '1' || type == '4') |
125 | mcc = 1; // ignore max color component |
126 | else |
127 | mcc = read_pbm_int(device); // get max color component |
128 | |
129 | if (w <= 0 || w > 32767 || h <= 0 || h > 32767 || mcc <= 0 || mcc > 0xffff) |
130 | return false; // weird P.M image |
131 | |
132 | return true; |
133 | } |
134 | |
135 | static inline QRgb scale_pbm_color(quint16 mx, quint16 rv, quint16 gv, quint16 bv) |
136 | { |
137 | return QRgba64::fromRgba64((rv * 0xffffu) / mx, (gv * 0xffffu) / mx, (bv * 0xffffu) / mx, 0xffff).toArgb32(); |
138 | } |
139 | |
140 | static bool read_pbm_body(QIODevice *device, char type, int w, int h, int mcc, QImage *outImage) |
141 | { |
142 | int nbits, y; |
143 | qsizetype pbm_bpl; |
144 | bool raw; |
145 | |
146 | QImage::Format format; |
147 | switch (type) { |
148 | case '1': // ascii PBM |
149 | case '4': // raw PBM |
150 | nbits = 1; |
151 | format = QImage::Format_Mono; |
152 | break; |
153 | case '2': // ascii PGM |
154 | case '5': // raw PGM |
155 | nbits = 8; |
156 | format = QImage::Format_Grayscale8; |
157 | break; |
158 | case '3': // ascii PPM |
159 | case '6': // raw PPM |
160 | nbits = 32; |
161 | format = QImage::Format_RGB32; |
162 | break; |
163 | default: |
164 | return false; |
165 | } |
166 | raw = type >= '4'; |
167 | |
168 | if (!QImageIOHandler::allocateImage(QSize(w, h), format, outImage)) |
169 | return false; |
170 | |
171 | pbm_bpl = (qsizetype(w) * nbits + 7) / 8; // bytes per scanline in PBM |
172 | |
173 | if (raw) { // read raw data |
174 | if (nbits == 32) { // type 6 |
175 | pbm_bpl = mcc < 256 ? 3*w : 6*w; |
176 | uchar *buf24 = new uchar[pbm_bpl], *b; |
177 | QRgb *p; |
178 | QRgb *end; |
179 | for (y=0; y<h; y++) { |
180 | if (device->read((char *)buf24, pbm_bpl) != pbm_bpl) { |
181 | delete[] buf24; |
182 | return false; |
183 | } |
184 | p = (QRgb *)outImage->scanLine(y); |
185 | end = p + w; |
186 | b = buf24; |
187 | while (p < end) { |
188 | if (mcc < 256) { |
189 | if (mcc == 255) |
190 | *p++ = qRgb(b[0],b[1],b[2]); |
191 | else |
192 | *p++ = scale_pbm_color(mcc, b[0], b[1], b[2]); |
193 | b += 3; |
194 | } else { |
195 | quint16 rv = b[0] << 8 | b[1]; |
196 | quint16 gv = b[2] << 8 | b[3]; |
197 | quint16 bv = b[4] << 8 | b[5]; |
198 | if (mcc == 0xffff) |
199 | *p++ = QRgba64::fromRgba64(rv, gv, bv, 0xffff).toArgb32(); |
200 | else |
201 | *p++ = scale_pbm_color(mcc, rv, gv, bv); |
202 | b += 6; |
203 | } |
204 | } |
205 | } |
206 | delete[] buf24; |
207 | } else if (nbits == 8 && mcc > 255) { // type 5 16bit |
208 | pbm_bpl = 2*w; |
209 | uchar *buf16 = new uchar[pbm_bpl]; |
210 | for (y=0; y<h; y++) { |
211 | if (device->read((char *)buf16, pbm_bpl) != pbm_bpl) { |
212 | delete[] buf16; |
213 | return false; |
214 | } |
215 | uchar *p = outImage->scanLine(y); |
216 | uchar *end = p + w; |
217 | uchar *b = buf16; |
218 | while (p < end) { |
219 | *p++ = (b[0] << 8 | b[1]) * 255 / mcc; |
220 | b += 2; |
221 | } |
222 | } |
223 | delete[] buf16; |
224 | } else { // type 4,5 |
225 | for (y=0; y<h; y++) { |
226 | uchar *p = outImage->scanLine(y); |
227 | if (device->read((char *)p, pbm_bpl) != pbm_bpl) |
228 | return false; |
229 | if (nbits == 8 && mcc < 255) { |
230 | for (qsizetype i = 0; i < pbm_bpl; i++) |
231 | p[i] = (p[i] * 255) / mcc; |
232 | } |
233 | } |
234 | } |
235 | } else { // read ascii data |
236 | uchar *p; |
237 | qsizetype n; |
238 | char buf; |
239 | for (y = 0; (y < h) && (device->peek(&buf, 1) == 1); y++) { |
240 | p = outImage->scanLine(y); |
241 | n = pbm_bpl; |
242 | if (nbits == 1) { |
243 | int b; |
244 | int bitsLeft = w; |
245 | while (n--) { |
246 | b = 0; |
247 | for (int i=0; i<8; i++) { |
248 | if (i < bitsLeft) |
249 | b = (b << 1) | (read_pbm_int(device) & 1); |
250 | else |
251 | b = (b << 1) | (0 & 1); // pad it our self if we need to |
252 | } |
253 | bitsLeft -= 8; |
254 | *p++ = b; |
255 | } |
256 | } else if (nbits == 8) { |
257 | if (mcc == 255) { |
258 | while (n--) { |
259 | *p++ = read_pbm_int(device); |
260 | } |
261 | } else { |
262 | while (n--) { |
263 | *p++ = (read_pbm_int(device) & 0xffff) * 255 / mcc; |
264 | } |
265 | } |
266 | } else { // 32 bits |
267 | n /= 4; |
268 | int r, g, b; |
269 | if (mcc == 255) { |
270 | while (n--) { |
271 | r = read_pbm_int(device); |
272 | g = read_pbm_int(device); |
273 | b = read_pbm_int(device); |
274 | *((QRgb*)p) = qRgb(r, g, b); |
275 | p += 4; |
276 | } |
277 | } else { |
278 | while (n--) { |
279 | r = read_pbm_int(device); |
280 | g = read_pbm_int(device); |
281 | b = read_pbm_int(device); |
282 | *((QRgb*)p) = scale_pbm_color(mcc, r, g, b); |
283 | p += 4; |
284 | } |
285 | } |
286 | } |
287 | } |
288 | } |
289 | |
290 | if (format == QImage::Format_Mono) { |
291 | outImage->setColorCount(2); |
292 | outImage->setColor(0, qRgb(255,255,255)); // white |
293 | outImage->setColor(1, qRgb(0,0,0)); // black |
294 | } |
295 | |
296 | return true; |
297 | } |
298 | |
299 | static bool write_pbm_image(QIODevice *out, const QImage &sourceImage, const QByteArray &sourceFormat) |
300 | { |
301 | QByteArray str; |
302 | QImage image = sourceImage; |
303 | QByteArray format = sourceFormat; |
304 | |
305 | format = format.left(3); // ignore RAW part |
306 | bool gray = format == "pgm" ; |
307 | |
308 | if (format == "pbm" ) { |
309 | image = image.convertToFormat(QImage::Format_Mono); |
310 | } else if (gray) { |
311 | image = image.convertToFormat(QImage::Format_Grayscale8); |
312 | } else { |
313 | switch (image.format()) { |
314 | case QImage::Format_Mono: |
315 | case QImage::Format_MonoLSB: |
316 | image = image.convertToFormat(QImage::Format_Indexed8); |
317 | break; |
318 | case QImage::Format_Indexed8: |
319 | case QImage::Format_RGB32: |
320 | case QImage::Format_ARGB32: |
321 | break; |
322 | default: |
323 | if (image.hasAlphaChannel()) |
324 | image = image.convertToFormat(QImage::Format_ARGB32); |
325 | else |
326 | image = image.convertToFormat(QImage::Format_RGB32); |
327 | break; |
328 | } |
329 | } |
330 | |
331 | if (image.depth() == 1 && image.colorCount() == 2) { |
332 | if (qGray(image.color(0)) < qGray(image.color(1))) { |
333 | // 0=dark/black, 1=light/white - invert |
334 | image.detach(); |
335 | for (int y=0; y<image.height(); y++) { |
336 | uchar *p = image.scanLine(y); |
337 | uchar *end = p + image.bytesPerLine(); |
338 | while (p < end) |
339 | *p++ ^= 0xff; |
340 | } |
341 | } |
342 | } |
343 | |
344 | uint w = image.width(); |
345 | uint h = image.height(); |
346 | |
347 | str = "P\n" ; |
348 | str += QByteArray::number(w); |
349 | str += ' '; |
350 | str += QByteArray::number(h); |
351 | str += '\n'; |
352 | |
353 | switch (image.depth()) { |
354 | case 1: { |
355 | str.insert(1, '4'); |
356 | if (out->write(str, str.length()) != str.length()) |
357 | return false; |
358 | w = (w+7)/8; |
359 | for (uint y=0; y<h; y++) { |
360 | uchar* line = image.scanLine(y); |
361 | if (w != (uint)out->write((char*)line, w)) |
362 | return false; |
363 | } |
364 | } |
365 | break; |
366 | |
367 | case 8: { |
368 | str.insert(1, gray ? '5' : '6'); |
369 | str.append("255\n" ); |
370 | if (out->write(str, str.length()) != str.length()) |
371 | return false; |
372 | qsizetype bpl = qsizetype(w) * (gray ? 1 : 3); |
373 | uchar *buf = new uchar[bpl]; |
374 | if (image.format() == QImage::Format_Indexed8) { |
375 | QList<QRgb> color = image.colorTable(); |
376 | for (uint y=0; y<h; y++) { |
377 | const uchar *b = image.constScanLine(y); |
378 | uchar *p = buf; |
379 | uchar *end = buf+bpl; |
380 | if (gray) { |
381 | while (p < end) { |
382 | uchar g = (uchar)qGray(color[*b++]); |
383 | *p++ = g; |
384 | } |
385 | } else { |
386 | while (p < end) { |
387 | QRgb rgb = color[*b++]; |
388 | *p++ = qRed(rgb); |
389 | *p++ = qGreen(rgb); |
390 | *p++ = qBlue(rgb); |
391 | } |
392 | } |
393 | if (bpl != (qsizetype)out->write((char*)buf, bpl)) |
394 | return false; |
395 | } |
396 | } else { |
397 | for (uint y=0; y<h; y++) { |
398 | const uchar *b = image.constScanLine(y); |
399 | uchar *p = buf; |
400 | uchar *end = buf + bpl; |
401 | if (gray) { |
402 | while (p < end) |
403 | *p++ = *b++; |
404 | } else { |
405 | while (p < end) { |
406 | uchar color = *b++; |
407 | *p++ = color; |
408 | *p++ = color; |
409 | *p++ = color; |
410 | } |
411 | } |
412 | if (bpl != (qsizetype)out->write((char*)buf, bpl)) |
413 | return false; |
414 | } |
415 | } |
416 | delete [] buf; |
417 | break; |
418 | } |
419 | |
420 | case 32: { |
421 | str.insert(1, '6'); |
422 | str.append("255\n" ); |
423 | if (out->write(str, str.length()) != str.length()) |
424 | return false; |
425 | qsizetype bpl = qsizetype(w) * 3; |
426 | uchar *buf = new uchar[bpl]; |
427 | for (uint y=0; y<h; y++) { |
428 | const QRgb *b = reinterpret_cast<const QRgb *>(image.constScanLine(y)); |
429 | uchar *p = buf; |
430 | uchar *end = buf+bpl; |
431 | while (p < end) { |
432 | QRgb rgb = *b++; |
433 | *p++ = qRed(rgb); |
434 | *p++ = qGreen(rgb); |
435 | *p++ = qBlue(rgb); |
436 | } |
437 | if (bpl != (qsizetype)out->write((char*)buf, bpl)) |
438 | return false; |
439 | } |
440 | delete [] buf; |
441 | break; |
442 | } |
443 | |
444 | default: |
445 | return false; |
446 | } |
447 | |
448 | return true; |
449 | } |
450 | |
451 | QPpmHandler::QPpmHandler() |
452 | : state(Ready) |
453 | { |
454 | } |
455 | |
456 | bool QPpmHandler::readHeader() |
457 | { |
458 | state = Error; |
459 | if (!read_pbm_header(device(), type, width, height, mcc)) |
460 | return false; |
461 | state = ReadHeader; |
462 | return true; |
463 | } |
464 | |
465 | bool QPpmHandler::canRead() const |
466 | { |
467 | if (state == Ready && !canRead(device(), &subType)) |
468 | return false; |
469 | |
470 | if (state != Error) { |
471 | setFormat(subType); |
472 | return true; |
473 | } |
474 | |
475 | return false; |
476 | } |
477 | |
478 | bool QPpmHandler::canRead(QIODevice *device, QByteArray *subType) |
479 | { |
480 | if (!device) { |
481 | qCWarning(lcImageIo, "QPpmHandler::canRead() called with no device" ); |
482 | return false; |
483 | } |
484 | |
485 | char head[2]; |
486 | if (device->peek(head, sizeof(head)) != sizeof(head)) |
487 | return false; |
488 | |
489 | if (head[0] != 'P') |
490 | return false; |
491 | |
492 | if (head[1] == '1' || head[1] == '4') { |
493 | if (subType) |
494 | *subType = "pbm" ; |
495 | } else if (head[1] == '2' || head[1] == '5') { |
496 | if (subType) |
497 | *subType = "pgm" ; |
498 | } else if (head[1] == '3' || head[1] == '6') { |
499 | if (subType) |
500 | *subType = "ppm" ; |
501 | } else { |
502 | return false; |
503 | } |
504 | return true; |
505 | } |
506 | |
507 | bool QPpmHandler::read(QImage *image) |
508 | { |
509 | if (state == Error) |
510 | return false; |
511 | |
512 | if (state == Ready && !readHeader()) { |
513 | state = Error; |
514 | return false; |
515 | } |
516 | |
517 | if (!read_pbm_body(device(), type, width, height, mcc, image)) { |
518 | state = Error; |
519 | return false; |
520 | } |
521 | |
522 | state = Ready; |
523 | return true; |
524 | } |
525 | |
526 | bool QPpmHandler::write(const QImage &image) |
527 | { |
528 | return write_pbm_image(device(), image, subType); |
529 | } |
530 | |
531 | bool QPpmHandler::supportsOption(ImageOption option) const |
532 | { |
533 | return option == SubType |
534 | || option == Size |
535 | || option == ImageFormat; |
536 | } |
537 | |
538 | QVariant QPpmHandler::option(ImageOption option) const |
539 | { |
540 | if (option == SubType) { |
541 | return subType; |
542 | } else if (option == Size) { |
543 | if (state == Error) |
544 | return QVariant(); |
545 | if (state == Ready && !const_cast<QPpmHandler*>(this)->readHeader()) |
546 | return QVariant(); |
547 | return QSize(width, height); |
548 | } else if (option == ImageFormat) { |
549 | if (state == Error) |
550 | return QVariant(); |
551 | if (state == Ready && !const_cast<QPpmHandler*>(this)->readHeader()) |
552 | return QVariant(); |
553 | QImage::Format format = QImage::Format_Invalid; |
554 | switch (type) { |
555 | case '1': // ascii PBM |
556 | case '4': // raw PBM |
557 | format = QImage::Format_Mono; |
558 | break; |
559 | case '2': // ascii PGM |
560 | case '5': // raw PGM |
561 | format = QImage::Format_Grayscale8; |
562 | break; |
563 | case '3': // ascii PPM |
564 | case '6': // raw PPM |
565 | format = QImage::Format_RGB32; |
566 | break; |
567 | default: |
568 | break; |
569 | } |
570 | return format; |
571 | } |
572 | return QVariant(); |
573 | } |
574 | |
575 | void QPpmHandler::setOption(ImageOption option, const QVariant &value) |
576 | { |
577 | if (option == SubType) |
578 | subType = value.toByteArray().toLower(); |
579 | } |
580 | |
581 | QT_END_NAMESPACE |
582 | |
583 | #endif // QT_NO_IMAGEFORMAT_PPM |
584 | |