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
53QT_BEGIN_NAMESPACE
54
55Q_DECLARE_LOGGING_CATEGORY(lcImageIo)
56
57/*****************************************************************************
58 PBM/PGM/PPM (ASCII and RAW) image read/write functions
59 *****************************************************************************/
60
61static 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
71static 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
108static bool read_pbm_header(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
135static 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
140static 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
299static 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
451QPpmHandler::QPpmHandler()
452 : state(Ready)
453{
454}
455
456bool 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
465bool 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
478bool 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
507bool 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
526bool QPpmHandler::write(const QImage &image)
527{
528 return write_pbm_image(device(), image, subType);
529}
530
531bool QPpmHandler::supportsOption(ImageOption option) const
532{
533 return option == SubType
534 || option == Size
535 || option == ImageFormat;
536}
537
538QVariant 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
575void QPpmHandler::setOption(ImageOption option, const QVariant &value)
576{
577 if (option == SubType)
578 subType = value.toByteArray().toLower();
579}
580
581QT_END_NAMESPACE
582
583#endif // QT_NO_IMAGEFORMAT_PPM
584