Coverage Report

Created: 2025-09-08 07:52

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