Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qppmhandler.cpp
Go to the documentation of this file.
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// Qt-Security score:critical reason:data-parser
4
5#include "private/qppmhandler_p.h"
6
7#ifndef QT_NO_IMAGEFORMAT_PPM
8
9#include <qdebug.h>
10#include <qimage.h>
11#include <qlist.h>
12#include <qloggingcategory.h>
13#include <qrgba64.h>
14#include <qvariant.h>
15#include <private/qlocale_p.h>
16#include <private/qtools_p.h>
17#include <private/qimage_p.h>
18
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(buf, buflen);
32 } while (res > 0 && buf[res-1] != '\n');
33}
34
35static quint16 read_pbm_int(QIODevice *d, bool *ok, int maxDigits = -1)
36{
37 char c;
38 int val = -1;
39 bool digit;
40 for (;;) {
41 if (!d->getChar(&c)) // end of file
42 break;
43 digit = QtMiscUtils::isAsciiDigit(c);
44 if (val != -1) {
45 if (digit) {
46 const int cValue = c - '0';
47 if (val <= (INT_MAX - cValue) / 10)
48 val = 10*val + cValue;
49 if (maxDigits > 0 && --maxDigits == 0)
50 break;
51 continue;
52 } else {
53 if (c == '#') // comment
55 break;
56 }
57 }
58 if (digit) // first digit
59 val = c - '0';
60 else if (ascii_isspace(c))
61 continue;
62 else if (c == '#')
64 else
65 break;
66 if (maxDigits > 0 && --maxDigits == 0)
67 break;
68 }
69 if (val < 0 || val > 0xffff)
70 *ok = false;
71 return qBound(0, val, 0xffff);
72}
73
74static bool read_pbm_header(QIODevice *device, char& type, int& w, int& h, quint16& mcc)
75{
76 char buf[3];
77 if (device->read(buf, 3) != 3) // read P[1-6]<white-space>
78 return false;
79
80 if (!(buf[0] == 'P' && QtMiscUtils::isAsciiDigit(buf[1]) && ascii_isspace(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(device, &ok); // get image width
89 h = read_pbm_int(device, &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(device, &ok); // get max color component
95
96 if (!ok || w <= 0 || w > 32767 || h <= 0 || h > 32767 || mcc == 0)
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((rv * 0xffffu) / mx, (gv * 0xffffu) / mx, (bv * 0xffffu) / mx, 0xffff).toArgb32();
105}
106
107static bool read_pbm_body(QIODevice *device, char type, int w, int h, quint16 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 = mcc <= std::numeric_limits<uint8_t>::max() ? 8 : 16;
123 format = mcc <= std::numeric_limits<uint8_t>::max() ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16;
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(QSize(w, h), format, 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((char *)buf24, 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(b[0],b[1],b[2]);
158 else
159 *p++ = scale_pbm_color(mcc, b[0], b[1], 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(rv, gv, bv, 0xffff).toArgb32();
167 else
168 *p++ = scale_pbm_color(mcc, rv, gv, bv);
169 b += 6;
170 }
171 }
172 }
173 delete[] buf24;
174 } else if (nbits == 16) { // type 5 16bit
175 pbm_bpl = sizeof(uint16_t)*w;
176 uchar *buf16 = new uchar[pbm_bpl];
177 for (y=0; y<h; y++) {
178 if (device->read((char *)buf16, pbm_bpl) != pbm_bpl) {
179 delete[] buf16;
180 return false;
181 }
182 uint16_t *p = reinterpret_cast<uint16_t *>(outImage->scanLine(y));
183 uint16_t *end = p + w;
184 uint16_t *b = reinterpret_cast<uint16_t *>(buf16);
185 while (p < end) {
186 *p++ = qFromBigEndian(*b) * 0xffffu / mcc;
187 b++;
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((char *)p, 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] * 0xffu) / 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 uchar 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(device, &ok, 1) & 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 == std::numeric_limits<uint8_t>::max()) {
225 while (n-- && ok) {
226 *p++ = read_pbm_int(device, &ok);
227 }
228 } else {
229 while (n-- && ok) {
230 *p++ = read_pbm_int(device, &ok) * 0xffu / mcc;
231 }
232 }
233 } else if (nbits == 16) {
234 uint16_t* data = reinterpret_cast<uint16_t*>(p);
235 qsizetype numPixel = n/2;
236 if (mcc == std::numeric_limits<uint16_t>::max()) {
237 while (numPixel-- && ok) {
238 *data++ = read_pbm_int(device, &ok);
239 }
240 } else {
241 while (numPixel-- && ok) {
242 *data++ = read_pbm_int(device, &ok) * 0xffffu / mcc;
243 }
244 }
245 } else { // 32 bits
246 n /= 4;
247 quint16 r, g, b;
248 if (mcc == 255) {
249 while (n-- && ok) {
250 r = read_pbm_int(device, &ok);
251 g = read_pbm_int(device, &ok);
252 b = read_pbm_int(device, &ok);
253 *((QRgb*)p) = qRgb(r, g, b);
254 p += 4;
255 }
256 } else {
257 while (n-- && ok) {
258 r = read_pbm_int(device, &ok);
259 g = read_pbm_int(device, &ok);
260 b = read_pbm_int(device, &ok);
261 *((QRgb*)p) = scale_pbm_color(mcc, r, g, b);
262 p += 4;
263 }
264 }
265 }
266 }
267 if (!ok)
268 return false;
269 }
270
271 if (format == QImage::Format_Mono) {
272 outImage->setColorCount(2);
273 outImage->setColor(0, qRgb(255,255,255)); // white
274 outImage->setColor(1, qRgb(0,0,0)); // black
275 }
276
277 return true;
278}
279
280static bool write_pbm_image(QIODevice *out, const QImage &sourceImage, QByteArrayView sourceFormat)
281{
282 QByteArray str;
283 QImage image = sourceImage;
284 const QByteArrayView format = sourceFormat.left(3); // ignore RAW part
285
286 bool gray = format == "pgm";
287
288 if (format == "pbm") {
289 image = image.convertToFormat(QImage::Format_Mono);
290 } else if (gray) {
291 image = image.depth() <= 8 ? image.convertToFormat(QImage::Format_Grayscale8) : image.convertToFormat(QImage::Format_Grayscale16);
292 } else {
293 switch (image.format()) {
294 case QImage::Format_Mono:
295 case QImage::Format_MonoLSB:
296 image = image.convertToFormat(QImage::Format_Indexed8);
297 break;
298 case QImage::Format_Indexed8:
299 case QImage::Format_RGB32:
300 case QImage::Format_ARGB32:
301 break;
302 default:
303 if (image.hasAlphaChannel())
304 image = image.convertToFormat(QImage::Format_ARGB32);
305 else
306 image = image.convertToFormat(QImage::Format_RGB32);
307 break;
308 }
309 }
310
311 if (image.depth() == 1 && image.colorCount() == 2) {
312 if (qGray(image.color(0)) < qGray(image.color(1))) {
313 // 0=dark/black, 1=light/white - invert
314 image.detach();
315 for (int y=0; y<image.height(); y++) {
316 uchar *p = image.scanLine(y);
317 uchar *end = p + image.bytesPerLine();
318 while (p < end)
319 *p++ ^= 0xff;
320 }
321 }
322 }
323
324 uint w = image.width();
325 uint h = image.height();
326
327 str = "P\n";
328 str += QByteArray::number(w);
329 str += ' ';
330 str += QByteArray::number(h);
331 str += '\n';
332
333 switch (image.depth()) {
334 case 1: {
335 str.insert(1, '4');
336 if (out->write(str, str.size()) != str.size())
337 return false;
338 w = (w+7)/8;
339 for (uint y=0; y<h; y++) {
340 uchar* line = image.scanLine(y);
341 if (w != (uint)out->write((char*)line, w))
342 return false;
343 }
344 }
345 break;
346
347 case 8: {
348 str.insert(1, gray ? '5' : '6');
349 str.append("255\n");
350 if (out->write(str, str.size()) != str.size())
351 return false;
352 qsizetype bpl = qsizetype(w) * (gray ? 1 : 3);
353 uchar *buf = new uchar[bpl];
354 if (image.format() == QImage::Format_Indexed8) {
355 const QList<QRgb> color = image.colorTable();
356 for (uint y=0; y<h; y++) {
357 const uchar *b = image.constScanLine(y);
358 uchar *p = buf;
359 uchar *end = buf+bpl;
360 if (gray) {
361 while (p < end) {
362 uchar g = (uchar)qGray(color[*b++]);
363 *p++ = g;
364 }
365 } else {
366 while (p < end) {
367 QRgb rgb = color[*b++];
368 *p++ = qRed(rgb);
369 *p++ = qGreen(rgb);
370 *p++ = qBlue(rgb);
371 }
372 }
373 if (bpl != (qsizetype)out->write((char*)buf, bpl))
374 return false;
375 }
376 } else {
377 for (uint y=0; y<h; y++) {
378 const uchar *b = image.constScanLine(y);
379 uchar *p = buf;
380 uchar *end = buf + bpl;
381 if (gray) {
382 while (p < end)
383 *p++ = *b++;
384 } else {
385 while (p < end) {
386 uchar color = *b++;
387 *p++ = color;
388 *p++ = color;
389 *p++ = color;
390 }
391 }
392 if (bpl != (qsizetype)out->write((char*)buf, bpl))
393 return false;
394 }
395 }
396 delete [] buf;
397 break;
398 }
399 case 16: {
400 str.insert(1, gray ? '5' : '6');
401 str.append("65535\n");
402 if (out->write(str, str.size()) != str.size())
403 return false;
404 qsizetype bpl = sizeof(uint16_t) * qsizetype(w) * (gray ? 1 : 3);
405 uchar *buf = new uchar[bpl];
406 for (uint y=0; y<h; y++) {
407 const uint16_t *b = reinterpret_cast<const uint16_t *>(image.constScanLine(y));
408 uint16_t *p = reinterpret_cast<uint16_t *>(buf);
409 uint16_t *end = reinterpret_cast<uint16_t *>(buf + bpl);
410 if (gray) {
411 while (p < end)
412 *p++ = qToBigEndian(*b++);
413 } else {
414 while (p < end) {
415 uchar color = qToBigEndian(*b++);
416 *p++ = color;
417 *p++ = color;
418 *p++ = color;
419 }
420 }
421 if (bpl != (qsizetype)out->write((char*)buf, bpl))
422 return false;
423 }
424 delete [] buf;
425 break;
426 }
427
428 case 32: {
429 str.insert(1, '6');
430 str.append("255\n");
431 if (out->write(str, str.size()) != str.size())
432 return false;
433 qsizetype bpl = qsizetype(w) * 3;
434 uchar *buf = new uchar[bpl];
435 for (uint y=0; y<h; y++) {
436 const QRgb *b = reinterpret_cast<const QRgb *>(image.constScanLine(y));
437 uchar *p = buf;
438 uchar *end = buf+bpl;
439 while (p < end) {
440 QRgb rgb = *b++;
441 *p++ = qRed(rgb);
442 *p++ = qGreen(rgb);
443 *p++ = qBlue(rgb);
444 }
445 if (bpl != (qsizetype)out->write((char*)buf, bpl))
446 return false;
447 }
448 delete [] buf;
449 break;
450 }
451
452 default:
453 return false;
454 }
455
456 return true;
457}
458
459QPpmHandler::QPpmHandler()
460 : state(Ready)
461{
462}
463
464bool QPpmHandler::readHeader()
465{
466 state = Error;
467 if (!read_pbm_header(device(), type, width, height, mcc))
468 return false;
469 state = ReadHeader;
470 return true;
471}
472
473bool QPpmHandler::canRead() const
474{
475 if (state == Ready && !canRead(device(), &subType))
476 return false;
477
478 if (state != Error) {
479 setFormat(subType);
480 return true;
481 }
482
483 return false;
484}
485
486bool QPpmHandler::canRead(QIODevice *device, QByteArray *subType)
487{
488 if (!device) {
489 qCWarning(lcImageIo, "QPpmHandler::canRead() called with no device");
490 return false;
491 }
492
493 char head[2];
494 if (device->peek(head, sizeof(head)) != sizeof(head))
495 return false;
496
497 if (head[0] != 'P')
498 return false;
499
500 if (head[1] == '1' || head[1] == '4') {
501 if (subType)
502 *subType = "pbm";
503 } else if (head[1] == '2' || head[1] == '5') {
504 if (subType)
505 *subType = "pgm";
506 } else if (head[1] == '3' || head[1] == '6') {
507 if (subType)
508 *subType = "ppm";
509 } else {
510 return false;
511 }
512 return true;
513}
514
515bool QPpmHandler::read(QImage *image)
516{
517 if (state == Error)
518 return false;
519
520 if (state == Ready && !readHeader()) {
521 state = Error;
522 return false;
523 }
524
525 if (!read_pbm_body(device(), type, width, height, mcc, image)) {
526 state = Error;
527 return false;
528 }
529
530 state = Ready;
531 return true;
532}
533
534bool QPpmHandler::write(const QImage &image)
535{
536 return write_pbm_image(device(), image, subType);
537}
538
539bool QPpmHandler::supportsOption(ImageOption option) const
540{
541 return option == SubType
542 || option == Size
543 || option == ImageFormat;
544}
545
546QVariant QPpmHandler::option(ImageOption option) const
547{
548 if (option == SubType) {
549 return subType;
550 } else if (option == Size) {
551 if (state == Error)
552 return QVariant();
553 if (state == Ready && !const_cast<QPpmHandler*>(this)->readHeader())
554 return QVariant();
555 return QSize(width, height);
556 } else if (option == ImageFormat) {
557 if (state == Error)
558 return QVariant();
559 if (state == Ready && !const_cast<QPpmHandler*>(this)->readHeader())
560 return QVariant();
561 QImage::Format format = QImage::Format_Invalid;
562 switch (type) {
563 case '1': // ascii PBM
564 case '4': // raw PBM
565 format = QImage::Format_Mono;
566 break;
567 case '2': // ascii PGM
568 case '5': // raw PGM
569 if (mcc <= std::numeric_limits<uint8_t>::max())
570 format = QImage::Format_Grayscale8;
571 else
572 format = QImage::Format_Grayscale16;
573 break;
574 case '3': // ascii PPM
575 case '6': // raw PPM
576 format = QImage::Format_RGB32;
577 break;
578 default:
579 break;
580 }
581 return format;
582 }
583 return QVariant();
584}
585
586void QPpmHandler::setOption(ImageOption option, const QVariant &value)
587{
588 if (option == SubType)
589 subType = value.toByteArray().toLower();
590}
591
592QT_END_NAMESPACE
593
594#endif // QT_NO_IMAGEFORMAT_PPM
\inmodule QtGui
Definition qimage.h:37
Combined button and popup list for selecting options.
#define qCWarning(category,...)
static bool write_pbm_image(QIODevice *out, const QImage &sourceImage, QByteArrayView sourceFormat)
static bool read_pbm_header(QIODevice *device, char &type, int &w, int &h, quint16 &mcc)
static quint16 read_pbm_int(QIODevice *d, bool *ok, int maxDigits=-1)
static bool read_pbm_body(QIODevice *device, char type, int w, int h, quint16 mcc, QImage *outImage)
static QRgb scale_pbm_color(quint16 mx, quint16 rv, quint16 gv, quint16 bv)
static QT_BEGIN_NAMESPACE void discard_pbm_line(QIODevice *d)