Coverage Report

Created: 2025-12-31 07:23

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