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

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