Coverage Report

Created: 2026-06-30 07:44

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