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

source code of qtbase/src/gui/image/qppmhandler.cpp