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