Coverage Report

Created: 2025-07-16 07:53

/src/qtbase/src/gui/image/qpnghandler.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (C) 2013 Samuel Gaist <samuel.gaist@edeltech.ch>
2
// Copyright (C) 2016 The Qt Company Ltd.
3
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
// Qt-Security score:critical reason:data-parser
5
6
#include "private/qpnghandler_p.h"
7
8
#ifndef QT_NO_IMAGEFORMAT_PNG
9
#include <qcoreapplication.h>
10
#include <qdebug.h>
11
#include <qiodevice.h>
12
#include <qimage.h>
13
#include <qloggingcategory.h>
14
#include <qvariant.h>
15
16
#include <private/qimage_p.h> // for qt_getImageText
17
18
#include <qcolorspace.h>
19
#include <private/qcolorspace_p.h>
20
21
#include <png.h>
22
#include <pngconf.h>
23
24
#if PNG_LIBPNG_VER >= 10400 && PNG_LIBPNG_VER <= 10502 \
25
        && defined(PNG_PEDANTIC_WARNINGS_SUPPORTED)
26
/*
27
    Versions 1.4.0 to 1.5.2 of libpng declare png_longjmp_ptr to
28
    have a noreturn attribute if PNG_PEDANTIC_WARNINGS_SUPPORTED
29
    is enabled, but most declarations of longjmp in the wild do
30
    not add this attribute. This causes problems when the png_jmpbuf
31
    macro expands to calling png_set_longjmp_fn with a mismatched
32
    longjmp, as compilers such as Clang will treat this as an error.
33
34
    To work around this we override the png_jmpbuf macro to cast
35
    longjmp to a png_longjmp_ptr.
36
*/
37
#   undef png_jmpbuf
38
#   ifdef PNG_SETJMP_SUPPORTED
39
#       define png_jmpbuf(png_ptr) \
40
            (*png_set_longjmp_fn((png_ptr), (png_longjmp_ptr)longjmp, sizeof(jmp_buf)))
41
#   else
42
#       define png_jmpbuf(png_ptr) \
43
            (LIBPNG_WAS_COMPILED_WITH__PNG_NO_SETJMP)
44
#   endif
45
#endif
46
47
QT_BEGIN_NAMESPACE
48
49
using namespace Qt::StringLiterals;
50
51
// avoid going through QImage::scanLine() which calls detach
52
0
#define FAST_SCAN_LINE(data, bpl, y) (data + (y) * bpl)
53
54
/*
55
  All PNG files load to the minimal QImage equivalent.
56
57
  All QImage formats output to reasonably efficient PNG equivalents.
58
*/
59
60
class QPngHandlerPrivate
61
{
62
public:
63
    enum State {
64
        Ready,
65
        ReadHeader,
66
        ReadingEnd,
67
        Error
68
    };
69
    // Defines the order of how the various ways of setting colorspace overrides each other:
70
    enum ColorSpaceState {
71
        Undefined = 0,
72
        GammaChrm = 1, // gAMA+cHRM chunks
73
        Srgb = 2,      // sRGB chunk
74
        Icc = 3        // iCCP chunk
75
    };
76
77
    QPngHandlerPrivate(QPngHandler *qq)
78
8.23k
        : gamma(0.0), fileGamma(0.0), quality(50), compression(50), colorSpaceState(Undefined),
79
8.23k
          png_ptr(nullptr), info_ptr(nullptr), end_info(nullptr), row_pointers(nullptr), state(Ready), q(qq)
80
8.23k
    { }
81
82
    float gamma;
83
    float fileGamma;
84
    int quality; // quality is used for backward compatibility, maps to compression
85
    int compression;
86
    QString description;
87
    QStringList readTexts;
88
    QColorSpace colorSpace;
89
    ColorSpaceState colorSpaceState;
90
91
    png_struct *png_ptr;
92
    png_info *info_ptr;
93
    png_info *end_info;
94
    png_byte **row_pointers;
95
96
    bool readPngHeader();
97
    bool readPngImage(QImage *image);
98
    void readPngTexts(png_info *info);
99
100
    QImage::Format readImageFormat();
101
102
    State state;
103
104
    QPngHandler *q;
105
};
106
107
108
class QPNGImageWriter {
109
public:
110
    explicit QPNGImageWriter(QIODevice*);
111
    ~QPNGImageWriter();
112
113
    enum DisposalMethod { Unspecified, NoDisposal, RestoreBackground, RestoreImage };
114
    void setDisposalMethod(DisposalMethod);
115
    void setLooping(int loops=0); // 0 == infinity
116
    void setFrameDelay(int msecs);
117
    void setGamma(float);
118
119
    bool writeImage(const QImage& img, int x, int y);
120
    bool writeImage(const QImage& img, int compression_in, const QString &description, int x, int y);
121
    bool writeImage(const QImage& img)
122
0
        { return writeImage(img, 0, 0); }
123
    bool writeImage(const QImage& img, int compression, const QString &description)
124
0
        { return writeImage(img, compression, description, 0, 0); }
125
126
0
    QIODevice* device() { return dev; }
127
128
private:
129
    QIODevice* dev;
130
    int frames_written;
131
    DisposalMethod disposal;
132
    int looping;
133
    int ms_delay;
134
    float gamma;
135
};
136
137
extern "C" {
138
static
139
void iod_read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
140
338k
{
141
338k
    QPngHandlerPrivate *d = (QPngHandlerPrivate *)png_get_io_ptr(png_ptr);
142
338k
    QIODevice *in = d->q->device();
143
144
338k
    if (d->state == QPngHandlerPrivate::ReadingEnd && !in->isSequential() && in->size() > 0 && (in->size() - in->pos()) < 4 && length == 4) {
145
        // Workaround for certain malformed PNGs that lack the final crc bytes
146
1
        uchar endcrc[4] = { 0xae, 0x42, 0x60, 0x82 };
147
1
        memcpy(data, endcrc, 4);
148
1
        in->seek(in->size());
149
1
        return;
150
1
    }
151
152
676k
    while (length) {
153
341k
        int nr = in->read((char*)data, length);
154
341k
        if (nr <= 0) {
155
4.05k
            png_error(png_ptr, "Read Error");
156
0
            return;
157
4.05k
        }
158
337k
        length -= nr;
159
337k
    }
160
338k
}
161
162
163
static
164
void qpiw_write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
165
0
{
166
0
    QPNGImageWriter* qpiw = (QPNGImageWriter*)png_get_io_ptr(png_ptr);
167
0
    QIODevice* out = qpiw->device();
168
169
0
    uint nr = out->write((char*)data, length);
170
0
    if (nr != length) {
171
0
        png_error(png_ptr, "Write Error");
172
0
        return;
173
0
    }
174
0
}
175
176
177
static
178
void qpiw_flush_fn(png_structp /* png_ptr */)
179
0
{
180
0
}
181
182
}
183
184
static
185
bool setup_qt(QImage& image, png_structp png_ptr, png_infop info_ptr)
186
978
{
187
978
    png_uint_32 width = 0;
188
978
    png_uint_32 height = 0;
189
978
    int bit_depth = 0;
190
978
    int color_type = 0;
191
978
    png_bytep trans_alpha = nullptr;
192
978
    png_color_16p trans_color_p = nullptr;
193
978
    int num_trans;
194
978
    png_colorp palette = nullptr;
195
978
    int num_palette;
196
978
    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr, nullptr);
197
978
    QSize size(width, height);
198
978
    png_set_interlace_handling(png_ptr);
199
200
978
    if (color_type == PNG_COLOR_TYPE_GRAY) {
201
        // Black & White or grayscale
202
295
        if (bit_depth == 1 && png_get_channels(png_ptr, info_ptr) == 1) {
203
0
            png_set_invert_mono(png_ptr);
204
0
            png_read_update_info(png_ptr, info_ptr);
205
0
            if (!QImageIOHandler::allocateImage(size, QImage::Format_Mono, &image))
206
0
                return false;
207
0
            image.setColorCount(2);
208
0
            image.setColor(1, qRgb(0,0,0));
209
0
            image.setColor(0, qRgb(255,255,255));
210
0
            if (png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color_p) && trans_color_p) {
211
0
                const int g = trans_color_p->gray;
212
                // the image has white in the first position of the color table,
213
                // black in the second. g is 0 for black, 1 for white.
214
0
                if (g == 0)
215
0
                    image.setColor(1, qRgba(0, 0, 0, 0));
216
0
                else if (g == 1)
217
0
                    image.setColor(0, qRgba(255, 255, 255, 0));
218
0
            }
219
295
        } else if (bit_depth == 16
220
295
                   && png_get_channels(png_ptr, info_ptr) == 1
221
295
                   && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
222
0
            if (!QImageIOHandler::allocateImage(size, QImage::Format_Grayscale16, &image))
223
0
                return false;
224
0
            png_read_update_info(png_ptr, info_ptr);
225
0
            if (QSysInfo::ByteOrder == QSysInfo::LittleEndian)
226
0
                png_set_swap(png_ptr);
227
295
        } else if (bit_depth == 16) {
228
0
            bool hasMask = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
229
0
            if (!hasMask)
230
0
                png_set_filler(png_ptr, 0xffff, PNG_FILLER_AFTER);
231
0
            else
232
0
                png_set_expand(png_ptr);
233
0
            png_set_gray_to_rgb(png_ptr);
234
0
            QImage::Format format = hasMask ? QImage::Format_RGBA64 : QImage::Format_RGBX64;
235
0
            if (!QImageIOHandler::allocateImage(size, format, &image))
236
0
                return false;
237
0
            png_read_update_info(png_ptr, info_ptr);
238
0
            if (QSysInfo::ByteOrder == QSysInfo::LittleEndian)
239
0
                png_set_swap(png_ptr);
240
295
        } else if (bit_depth == 8 && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
241
295
            png_set_expand(png_ptr);
242
295
            if (!QImageIOHandler::allocateImage(size, QImage::Format_Grayscale8, &image))
243
0
                return false;
244
295
            png_read_update_info(png_ptr, info_ptr);
245
295
        } else {
246
0
            if (bit_depth < 8)
247
0
                png_set_packing(png_ptr);
248
0
            int ncols = bit_depth < 8 ? 1 << bit_depth : 256;
249
0
            png_read_update_info(png_ptr, info_ptr);
250
0
            if (!QImageIOHandler::allocateImage(size, QImage::Format_Indexed8, &image))
251
0
                return false;
252
0
            image.setColorCount(ncols);
253
0
            for (int i=0; i<ncols; i++) {
254
0
                int c = i*255/(ncols-1);
255
0
                image.setColor(i, qRgba(c,c,c,0xff));
256
0
            }
257
0
            if (png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color_p) && trans_color_p) {
258
0
                const int g = trans_color_p->gray;
259
0
                if (g < ncols) {
260
0
                    image.setColor(g, 0);
261
0
                }
262
0
            }
263
0
        }
264
683
    } else if (color_type == PNG_COLOR_TYPE_PALETTE
265
683
               && png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette)
266
683
               && num_palette <= 256)
267
0
    {
268
        // 1-bit and 8-bit color
269
0
        if (bit_depth != 1)
270
0
            png_set_packing(png_ptr);
271
0
        png_read_update_info(png_ptr, info_ptr);
272
0
        png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr, nullptr);
273
0
        size = QSize(width, height);
274
0
        QImage::Format format = bit_depth == 1 ? QImage::Format_Mono : QImage::Format_Indexed8;
275
0
        if (!QImageIOHandler::allocateImage(size, format, &image))
276
0
            return false;
277
0
        png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
278
0
        image.setColorCount((format == QImage::Format_Mono) ? 2 : num_palette);
279
0
        int i = 0;
280
0
        if (png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color_p) && trans_alpha) {
281
0
            while (i < num_trans) {
282
0
                image.setColor(i, qRgba(
283
0
                    palette[i].red,
284
0
                    palette[i].green,
285
0
                    palette[i].blue,
286
0
                    trans_alpha[i]
287
0
                   )
288
0
               );
289
0
                i++;
290
0
            }
291
0
        }
292
0
        while (i < num_palette) {
293
0
            image.setColor(i, qRgba(
294
0
                palette[i].red,
295
0
                palette[i].green,
296
0
                palette[i].blue,
297
0
                0xff
298
0
               )
299
0
           );
300
0
            i++;
301
0
        }
302
        // Qt==ARGB==Big(ARGB)==Little(BGRA)
303
0
        if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
304
0
            png_set_bgr(png_ptr);
305
0
        }
306
683
    } else if (bit_depth == 16 && !(color_type & PNG_COLOR_MASK_PALETTE)) {
307
0
        QImage::Format format = QImage::Format_RGBA64;
308
0
        if (!(color_type & PNG_COLOR_MASK_ALPHA) && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
309
0
            png_set_filler(png_ptr, 0xffff, PNG_FILLER_AFTER);
310
0
            format = QImage::Format_RGBX64;
311
0
        }
312
0
        if (!(color_type & PNG_COLOR_MASK_COLOR))
313
0
            png_set_gray_to_rgb(png_ptr);
314
0
        if (!QImageIOHandler::allocateImage(size, format, &image))
315
0
            return false;
316
0
        png_read_update_info(png_ptr, info_ptr);
317
0
        if (QSysInfo::ByteOrder == QSysInfo::LittleEndian)
318
0
            png_set_swap(png_ptr);
319
683
    } else {
320
        // 32-bit
321
683
        if (bit_depth == 16)
322
0
            png_set_strip_16(png_ptr);
323
324
683
        png_set_expand(png_ptr);
325
326
683
        if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
327
0
            png_set_gray_to_rgb(png_ptr);
328
329
683
        QImage::Format format = QImage::Format_ARGB32;
330
        // Only add filler if no alpha, or we can get 5 channel data.
331
683
        if (!(color_type & PNG_COLOR_MASK_ALPHA)
332
683
            && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
333
0
            png_set_filler(png_ptr, 0xff, QSysInfo::ByteOrder == QSysInfo::BigEndian ?
334
0
                           PNG_FILLER_BEFORE : PNG_FILLER_AFTER);
335
            // We want 4 bytes, but it isn't an alpha channel
336
0
            format = QImage::Format_RGB32;
337
0
        }
338
683
        if (!QImageIOHandler::allocateImage(size, format, &image))
339
0
            return false;
340
341
683
        if (QSysInfo::ByteOrder == QSysInfo::BigEndian)
342
0
            png_set_swap_alpha(png_ptr);
343
344
        // Qt==ARGB==Big(ARGB)==Little(BGRA)
345
683
        if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
346
683
            png_set_bgr(png_ptr);
347
683
        }
348
349
683
        png_read_update_info(png_ptr, info_ptr);
350
683
    }
351
978
    return true;
352
978
}
353
354
extern "C" {
355
static void qt_png_warning(png_structp /*png_ptr*/, png_const_charp message)
356
85.5k
{
357
85.5k
    qCInfo(lcImageIo, "libpng warning: %s", message);
358
85.5k
}
359
360
}
361
362
363
void QPngHandlerPrivate::readPngTexts(png_info *info)
364
985
{
365
985
#ifndef QT_NO_IMAGEIO_TEXT_LOADING
366
985
    png_textp text_ptr;
367
985
    int num_text=0;
368
985
    png_get_text(png_ptr, info, &text_ptr, &num_text);
369
370
985
    while (num_text--) {
371
0
        QString key, value;
372
0
        key = QString::fromLatin1(text_ptr->key);
373
0
#if defined(PNG_iTXt_SUPPORTED)
374
0
        if (text_ptr->itxt_length) {
375
0
            value = QString::fromUtf8(text_ptr->text, int(text_ptr->itxt_length));
376
0
        } else
377
0
#endif
378
0
        {
379
0
            value = QString::fromLatin1(text_ptr->text, int(text_ptr->text_length));
380
0
        }
381
0
        if (!description.isEmpty())
382
0
            description += "\n\n"_L1;
383
0
        description += key + ": "_L1 + value.simplified();
384
0
        readTexts.append(key);
385
0
        readTexts.append(value);
386
0
        text_ptr++;
387
0
    }
388
#else
389
    Q_UNUSED(info);
390
#endif
391
985
}
392
393
394
bool QPngHandlerPrivate::readPngHeader()
395
6.20k
{
396
6.20k
    state = Error;
397
6.20k
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,nullptr,nullptr,nullptr);
398
6.20k
    if (!png_ptr)
399
0
        return false;
400
401
6.20k
    png_set_error_fn(png_ptr, nullptr, nullptr, qt_png_warning);
402
403
6.20k
#if defined(PNG_SET_OPTION_SUPPORTED) && defined(PNG_MAXIMUM_INFLATE_WINDOW)
404
    // Trade off a little bit of memory for better compatibility with existing images
405
    // Ref. "invalid distance too far back" explanation in libpng-manual.txt
406
6.20k
    png_set_option(png_ptr, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON);
407
6.20k
#endif
408
409
6.20k
    info_ptr = png_create_info_struct(png_ptr);
410
6.20k
    if (!info_ptr) {
411
0
        png_destroy_read_struct(&png_ptr, nullptr, nullptr);
412
0
        png_ptr = nullptr;
413
0
        return false;
414
0
    }
415
416
6.20k
    end_info = png_create_info_struct(png_ptr);
417
6.20k
    if (!end_info) {
418
0
        png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
419
0
        png_ptr = nullptr;
420
0
        return false;
421
0
    }
422
423
6.20k
    if (setjmp(png_jmpbuf(png_ptr))) {
424
5.22k
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
425
5.22k
        png_ptr = nullptr;
426
5.22k
        return false;
427
5.22k
    }
428
429
978
    png_set_read_fn(png_ptr, this, iod_read_fn);
430
978
    png_read_info(png_ptr, info_ptr);
431
432
978
    readPngTexts(info_ptr);
433
434
978
#ifdef PNG_iCCP_SUPPORTED
435
978
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) {
436
0
        png_charp name = nullptr;
437
0
        int compressionType = 0;
438
#if (PNG_LIBPNG_VER < 10500)
439
        png_charp profileData = nullptr;
440
#else
441
0
        png_bytep profileData = nullptr;
442
0
#endif
443
0
        png_uint_32 profLen;
444
0
        png_get_iCCP(png_ptr, info_ptr, &name, &compressionType, &profileData, &profLen);
445
0
        Q_UNUSED(name);
446
0
        Q_UNUSED(compressionType);
447
0
        if (profLen > 0) {
448
0
            colorSpace = QColorSpace::fromIccProfile(QByteArray((const char *)profileData, profLen));
449
0
            QColorSpacePrivate *csD = QColorSpacePrivate::get(colorSpace);
450
0
            if (csD->description.isEmpty())
451
0
                csD->description = QString::fromLatin1((const char *)name);
452
0
            colorSpaceState = Icc;
453
0
        }
454
0
    }
455
978
#endif
456
978
    if (colorSpaceState <= Srgb && png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
457
536
        int rendering_intent = -1;
458
536
        png_get_sRGB(png_ptr, info_ptr, &rendering_intent);
459
        // We don't actually care about the rendering_intent, just that it is valid
460
536
        if (rendering_intent >= 0 && rendering_intent <= 3) {
461
536
            colorSpace = QColorSpace::SRgb;
462
536
            colorSpaceState = Srgb;
463
536
        }
464
536
    }
465
978
    if (colorSpaceState <= GammaChrm && png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA)) {
466
0
        double file_gamma = 0.0;
467
0
        png_get_gAMA(png_ptr, info_ptr, &file_gamma);
468
0
        fileGamma = file_gamma;
469
0
        if (fileGamma > 0.0f) {
470
0
            QColorSpace::PrimaryPoints primaries;
471
0
            if (png_get_valid(png_ptr, info_ptr, PNG_INFO_cHRM)) {
472
0
                double white_x, white_y, red_x, red_y;
473
0
                double green_x, green_y, blue_x, blue_y;
474
0
                png_get_cHRM(png_ptr, info_ptr,
475
0
                             &white_x, &white_y, &red_x, &red_y,
476
0
                             &green_x, &green_y, &blue_x, &blue_y);
477
0
                primaries.whitePoint = QPointF(white_x, white_y);
478
0
                primaries.redPoint = QPointF(red_x, red_y);
479
0
                primaries.greenPoint = QPointF(green_x, green_y);
480
0
                primaries.bluePoint = QPointF(blue_x, blue_y);
481
0
            }
482
0
            if (primaries.isValid()) {
483
0
                colorSpace = QColorSpace(primaries.whitePoint, primaries.redPoint, primaries.greenPoint, primaries.bluePoint,
484
0
                                         QColorSpace::TransferFunction::Gamma, 1.0f / fileGamma);
485
0
            } else {
486
0
                colorSpace = QColorSpace(QColorSpace::Primaries::SRgb,
487
0
                                         QColorSpace::TransferFunction::Gamma, 1.0f / fileGamma);
488
0
            }
489
0
            colorSpaceState = GammaChrm;
490
0
        }
491
0
    }
492
493
978
    state = ReadHeader;
494
978
    return true;
495
6.20k
}
496
497
bool QPngHandlerPrivate::readPngImage(QImage *outImage)
498
6.20k
{
499
6.20k
    if (state == Error)
500
0
        return false;
501
502
6.20k
    if (state == Ready && !readPngHeader()) {
503
5.22k
        state = Error;
504
5.22k
        return false;
505
5.22k
    }
506
507
978
    row_pointers = nullptr;
508
978
    if (setjmp(png_jmpbuf(png_ptr))) {
509
971
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
510
971
        png_ptr = nullptr;
511
971
        delete[] row_pointers;
512
971
        state = Error;
513
971
        return false;
514
971
    }
515
516
7
    if (gamma != 0.0 && fileGamma != 0.0) {
517
        // This configuration forces gamma correction and
518
        // thus changes the output colorspace
519
0
        png_set_gamma(png_ptr, 1.0f / gamma, fileGamma);
520
0
        colorSpace.setTransferFunction(QColorSpace::TransferFunction::Gamma, 1.0f / gamma);
521
0
        colorSpaceState = GammaChrm;
522
0
    }
523
524
7
    if (!setup_qt(*outImage, png_ptr, info_ptr)) {
525
0
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
526
0
        png_ptr = nullptr;
527
0
        delete[] row_pointers;
528
0
        state = Error;
529
0
        return false;
530
0
    }
531
532
7
    png_uint_32 width = 0;
533
7
    png_uint_32 height = 0;
534
7
    png_int_32 offset_x = 0;
535
7
    png_int_32 offset_y = 0;
536
537
7
    int bit_depth = 0;
538
7
    int color_type = 0;
539
7
    int unit_type = PNG_OFFSET_PIXEL;
540
7
    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr, nullptr);
541
7
    png_get_oFFs(png_ptr, info_ptr, &offset_x, &offset_y, &unit_type);
542
7
    uchar *data = outImage->bits();
543
7
    qsizetype bpl = outImage->bytesPerLine();
544
7
    row_pointers = new png_bytep[height];
545
546
342k
    for (uint y = 0; y < height; y++)
547
342k
        row_pointers[y] = data + y * bpl;
548
549
7
    png_read_image(png_ptr, row_pointers);
550
551
7
    outImage->setDotsPerMeterX(png_get_x_pixels_per_meter(png_ptr,info_ptr));
552
7
    outImage->setDotsPerMeterY(png_get_y_pixels_per_meter(png_ptr,info_ptr));
553
554
7
    if (unit_type == PNG_OFFSET_PIXEL)
555
12
        outImage->setOffset(QPoint(offset_x, offset_y));
556
557
           // sanity check palette entries
558
7
    if (color_type == PNG_COLOR_TYPE_PALETTE && outImage->format() == QImage::Format_Indexed8) {
559
0
        int color_table_size = outImage->colorCount();
560
0
        for (int y=0; y<(int)height; ++y) {
561
0
            uchar *p = FAST_SCAN_LINE(data, bpl, y);
562
0
            uchar *end = p + width;
563
0
            while (p < end) {
564
0
                if (*p >= color_table_size)
565
0
                    *p = 0;
566
0
                ++p;
567
0
            }
568
0
        }
569
0
    }
570
571
7
    state = ReadingEnd;
572
7
    png_read_end(png_ptr, end_info);
573
574
7
    readPngTexts(end_info);
575
7
    for (int i = 0; i < readTexts.size()-1; i+=2)
576
0
        outImage->setText(readTexts.at(i), readTexts.at(i+1));
577
578
7
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
579
7
    png_ptr = nullptr;
580
7
    delete[] row_pointers;
581
7
    row_pointers = nullptr;
582
7
    state = Ready;
583
584
7
    if (colorSpaceState > Undefined && colorSpace.isValid())
585
6
        outImage->setColorSpace(colorSpace);
586
587
7
    return true;
588
7
}
589
590
QImage::Format QPngHandlerPrivate::readImageFormat()
591
0
{
592
0
        QImage::Format format = QImage::Format_Invalid;
593
0
        png_uint_32 width = 0, height = 0;
594
0
        int bit_depth = 0, color_type = 0;
595
0
        png_colorp palette;
596
0
        int num_palette;
597
0
        png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr, nullptr);
598
0
        if (color_type == PNG_COLOR_TYPE_GRAY) {
599
            // Black & White or grayscale
600
0
            if (bit_depth == 1 && png_get_channels(png_ptr, info_ptr) == 1) {
601
0
                format = QImage::Format_Mono;
602
0
            } else if (bit_depth == 16) {
603
0
                format = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ? QImage::Format_RGBA64 : QImage::Format_Grayscale16;
604
0
            } else if (bit_depth == 8 && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
605
0
                format = QImage::Format_Grayscale8;
606
0
            } else {
607
0
                format = QImage::Format_Indexed8;
608
0
            }
609
0
        } else if (color_type == PNG_COLOR_TYPE_PALETTE
610
0
                   && png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette)
611
0
                   && num_palette <= 256)
612
0
        {
613
            // 1-bit and 8-bit color
614
0
            format = bit_depth == 1 ? QImage::Format_Mono : QImage::Format_Indexed8;
615
0
        } else if (bit_depth == 16 && !(color_type & PNG_COLOR_MASK_PALETTE)) {
616
0
            format = QImage::Format_RGBA64;
617
0
            if (!(color_type & PNG_COLOR_MASK_ALPHA) && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
618
0
                format = QImage::Format_RGBX64;
619
0
        } else {
620
            // 32-bit
621
0
            format = QImage::Format_ARGB32;
622
            // Only add filler if no alpha, or we can get 5 channel data.
623
0
            if (!(color_type & PNG_COLOR_MASK_ALPHA)
624
0
                && !png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
625
                // We want 4 bytes, but it isn't an alpha channel
626
0
                format = QImage::Format_RGB32;
627
0
            }
628
0
        }
629
630
0
        return format;
631
0
}
632
633
QPNGImageWriter::QPNGImageWriter(QIODevice* iod) :
634
0
    dev(iod),
635
0
    frames_written(0),
636
0
    disposal(Unspecified),
637
0
    looping(-1),
638
0
    ms_delay(-1),
639
0
    gamma(0.0)
640
0
{
641
0
}
642
643
QPNGImageWriter::~QPNGImageWriter()
644
0
{
645
0
}
646
647
void QPNGImageWriter::setDisposalMethod(DisposalMethod dm)
648
0
{
649
0
    disposal = dm;
650
0
}
651
652
void QPNGImageWriter::setLooping(int loops)
653
0
{
654
0
    looping = loops;
655
0
}
656
657
void QPNGImageWriter::setFrameDelay(int msecs)
658
0
{
659
0
    ms_delay = msecs;
660
0
}
661
662
void QPNGImageWriter::setGamma(float g)
663
0
{
664
0
    gamma = g;
665
0
}
666
667
static void set_text(const QImage &image, png_structp png_ptr, png_infop info_ptr,
668
                     const QString &description)
669
0
{
670
0
    const QMap<QString, QString> text = qt_getImageText(image, description);
671
672
0
    if (text.isEmpty())
673
0
        return;
674
675
0
    png_textp text_ptr = new png_text[text.size()];
676
0
    memset(text_ptr, 0, text.size() * sizeof(png_text));
677
678
0
    QMap<QString, QString>::ConstIterator it = text.constBegin();
679
0
    int i = 0;
680
0
    while (it != text.constEnd()) {
681
0
        text_ptr[i].key = qstrdup(QStringView{it.key()}.left(79).toLatin1().constData());
682
0
        bool noCompress = (it.value().size() < 40);
683
684
0
#ifdef PNG_iTXt_SUPPORTED
685
0
        bool needsItxt = false;
686
0
        for (QChar c : it.value()) {
687
0
            uchar ch = c.cell();
688
0
            if (c.row() || (ch < 0x20 && ch != '\n') || (ch > 0x7e && ch < 0xa0)) {
689
0
                needsItxt = true;
690
0
                break;
691
0
            }
692
0
        }
693
694
0
        if (needsItxt) {
695
0
            text_ptr[i].compression = noCompress ? PNG_ITXT_COMPRESSION_NONE : PNG_ITXT_COMPRESSION_zTXt;
696
0
            QByteArray value = it.value().toUtf8();
697
0
            text_ptr[i].text = qstrdup(value.constData());
698
0
            text_ptr[i].itxt_length = value.size();
699
0
            text_ptr[i].lang = const_cast<char*>("UTF-8");
700
0
            text_ptr[i].lang_key = qstrdup(it.key().toUtf8().constData());
701
0
        }
702
0
        else
703
0
#endif
704
0
        {
705
0
            text_ptr[i].compression = noCompress ? PNG_TEXT_COMPRESSION_NONE : PNG_TEXT_COMPRESSION_zTXt;
706
0
            QByteArray value = it.value().toLatin1();
707
0
            text_ptr[i].text = qstrdup(value.constData());
708
0
            text_ptr[i].text_length = value.size();
709
0
        }
710
0
        ++i;
711
0
        ++it;
712
0
    }
713
714
0
    png_set_text(png_ptr, info_ptr, text_ptr, i);
715
0
    for (i = 0; i < text.size(); ++i) {
716
0
        delete [] text_ptr[i].key;
717
0
        delete [] text_ptr[i].text;
718
0
#ifdef PNG_iTXt_SUPPORTED
719
0
        delete [] text_ptr[i].lang_key;
720
0
#endif
721
0
    }
722
0
    delete [] text_ptr;
723
0
}
724
725
bool QPNGImageWriter::writeImage(const QImage& image, int off_x, int off_y)
726
0
{
727
0
    return writeImage(image, -1, QString(), off_x, off_y);
728
0
}
729
730
bool QPNGImageWriter::writeImage(const QImage& image, int compression_in, const QString &description,
731
                                 int off_x_in, int off_y_in)
732
0
{
733
0
    QPoint offset = image.offset();
734
0
    int off_x = off_x_in + offset.x();
735
0
    int off_y = off_y_in + offset.y();
736
737
0
    png_structp png_ptr;
738
0
    png_infop info_ptr;
739
740
0
    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,nullptr,nullptr,nullptr);
741
0
    if (!png_ptr) {
742
0
        return false;
743
0
    }
744
745
0
    png_set_error_fn(png_ptr, nullptr, nullptr, qt_png_warning);
746
0
#ifdef PNG_BENIGN_ERRORS_SUPPORTED
747
0
    png_set_benign_errors(png_ptr, 1);
748
0
#endif
749
750
0
    info_ptr = png_create_info_struct(png_ptr);
751
0
    if (!info_ptr) {
752
0
        png_destroy_write_struct(&png_ptr, nullptr);
753
0
        return false;
754
0
    }
755
756
0
    if (setjmp(png_jmpbuf(png_ptr))) {
757
0
        png_destroy_write_struct(&png_ptr, &info_ptr);
758
0
        return false;
759
0
    }
760
761
0
    int compression = compression_in;
762
0
    if (compression >= 0) {
763
0
        if (compression > 9) {
764
0
            qCWarning(lcImageIo, "PNG: Compression %d out of range", compression);
765
0
            compression = 9;
766
0
        }
767
0
        png_set_compression_level(png_ptr, compression);
768
0
    }
769
770
0
    png_set_write_fn(png_ptr, (void*)this, qpiw_write_fn, qpiw_flush_fn);
771
772
773
0
    int color_type = 0;
774
0
    if (image.format() <= QImage::Format_Indexed8) {
775
0
        if (image.isGrayscale())
776
0
            color_type = PNG_COLOR_TYPE_GRAY;
777
0
        else
778
0
            color_type = PNG_COLOR_TYPE_PALETTE;
779
0
    }
780
0
    else if (image.format() == QImage::Format_Grayscale8
781
0
             || image.format() == QImage::Format_Grayscale16)
782
0
        color_type = PNG_COLOR_TYPE_GRAY;
783
0
    else if (image.hasAlphaChannel())
784
0
        color_type = PNG_COLOR_TYPE_RGB_ALPHA;
785
0
    else
786
0
        color_type = PNG_COLOR_TYPE_RGB;
787
788
0
    int bpc = 0;
789
0
    switch (image.format()) {
790
0
    case QImage::Format_Mono:
791
0
    case QImage::Format_MonoLSB:
792
0
        bpc = 1;
793
0
        break;
794
0
    case QImage::Format_RGBX64:
795
0
    case QImage::Format_RGBA64:
796
0
    case QImage::Format_RGBA64_Premultiplied:
797
0
    case QImage::Format_Grayscale16:
798
0
        bpc = 16;
799
0
        break;
800
0
    default:
801
0
        bpc = 8;
802
0
        break;
803
0
    }
804
805
0
    png_set_IHDR(png_ptr, info_ptr, image.width(), image.height(),
806
0
                 bpc, // per channel
807
0
                 color_type, 0, 0, 0);       // sets #channels
808
809
0
#ifdef PNG_iCCP_SUPPORTED
810
0
    QColorSpace cs = image.colorSpace();
811
    // Support the old gamma making it override transferfunction (if possible)
812
0
    if (cs.isValid() && gamma != 0.0 && !qFuzzyCompare(cs.gamma(), 1.0f / gamma))
813
0
        cs = cs.withTransferFunction(QColorSpace::TransferFunction::Gamma, 1.0f / gamma);
814
0
    QByteArray iccProfile = cs.iccProfile();
815
0
    if (!iccProfile.isEmpty()) {
816
0
        QByteArray iccProfileName = cs.description().toLatin1();
817
0
        if (iccProfileName.isEmpty())
818
0
            iccProfileName = QByteArrayLiteral("Custom");
819
0
        png_set_iCCP(png_ptr, info_ptr,
820
             #if PNG_LIBPNG_VER < 10500
821
                     iccProfileName.data(), PNG_COMPRESSION_TYPE_BASE, iccProfile.data(),
822
             #else
823
0
                     iccProfileName.constData(), PNG_COMPRESSION_TYPE_BASE,
824
0
                     (png_const_bytep)iccProfile.constData(),
825
0
             #endif
826
0
                     iccProfile.size());
827
0
    } else
828
0
#endif
829
0
    if (gamma != 0.0) {
830
0
        png_set_gAMA(png_ptr, info_ptr, 1.0/gamma);
831
0
    }
832
833
0
    if (image.format() == QImage::Format_MonoLSB)
834
0
       png_set_packswap(png_ptr);
835
836
0
    if (color_type == PNG_COLOR_TYPE_PALETTE) {
837
        // Paletted
838
0
        int num_palette = qMin(256, image.colorCount());
839
0
        png_color palette[256];
840
0
        png_byte trans[256];
841
0
        int num_trans = 0;
842
0
        for (int i=0; i<num_palette; i++) {
843
0
            QRgb rgba=image.color(i);
844
0
            palette[i].red = qRed(rgba);
845
0
            palette[i].green = qGreen(rgba);
846
0
            palette[i].blue = qBlue(rgba);
847
0
            trans[i] = qAlpha(rgba);
848
0
            if (trans[i] < 255) {
849
0
                num_trans = i+1;
850
0
            }
851
0
        }
852
0
        png_set_PLTE(png_ptr, info_ptr, palette, num_palette);
853
854
0
        if (num_trans) {
855
0
            png_set_tRNS(png_ptr, info_ptr, trans, num_trans, nullptr);
856
0
        }
857
0
    }
858
859
    // Swap ARGB to RGBA (normal PNG format) before saving on
860
    // BigEndian machines
861
0
    if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
862
0
        switch (image.format()) {
863
0
        case QImage::Format_RGBX8888:
864
0
        case QImage::Format_RGBA8888:
865
0
        case QImage::Format_RGBX64:
866
0
        case QImage::Format_RGBA64:
867
0
        case QImage::Format_RGBA64_Premultiplied:
868
0
            break;
869
0
        default:
870
0
            png_set_swap_alpha(png_ptr);
871
0
        }
872
0
    }
873
874
    // Qt==ARGB==Big(ARGB)==Little(BGRA). But RGB888 is RGB regardless
875
0
    if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
876
0
        switch (image.format()) {
877
0
        case QImage::Format_RGB888:
878
0
        case QImage::Format_RGBX8888:
879
0
        case QImage::Format_RGBA8888:
880
0
        case QImage::Format_RGBX64:
881
0
        case QImage::Format_RGBA64:
882
0
        case QImage::Format_RGBA64_Premultiplied:
883
0
            break;
884
0
        default:
885
0
            png_set_bgr(png_ptr);
886
0
        }
887
0
    }
888
889
0
    if (off_x || off_y) {
890
0
        png_set_oFFs(png_ptr, info_ptr, off_x, off_y, PNG_OFFSET_PIXEL);
891
0
    }
892
893
0
    if (frames_written > 0)
894
0
        png_set_sig_bytes(png_ptr, 8);
895
896
0
    if (image.dotsPerMeterX() > 0 || image.dotsPerMeterY() > 0) {
897
0
        png_set_pHYs(png_ptr, info_ptr,
898
0
                image.dotsPerMeterX(), image.dotsPerMeterY(),
899
0
                PNG_RESOLUTION_METER);
900
0
    }
901
902
0
    set_text(image, png_ptr, info_ptr, description);
903
904
0
    png_write_info(png_ptr, info_ptr);
905
906
0
    if (image.depth() != 1)
907
0
        png_set_packing(png_ptr);
908
909
0
    if (color_type == PNG_COLOR_TYPE_RGB) {
910
0
        switch (image.format()) {
911
0
        case QImage::Format_RGB888:
912
0
        case QImage::Format_BGR888:
913
0
            break;
914
0
        case QImage::Format_RGBX8888:
915
0
        case QImage::Format_RGBX64:
916
0
            png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
917
0
            break;
918
0
        default:
919
0
            png_set_filler(png_ptr, 0,
920
0
                QSysInfo::ByteOrder == QSysInfo::BigEndian ?
921
0
                    PNG_FILLER_BEFORE : PNG_FILLER_AFTER);
922
0
        }
923
0
    }
924
925
0
    if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
926
0
        switch (image.format()) {
927
0
        case QImage::Format_RGBX64:
928
0
        case QImage::Format_RGBA64:
929
0
        case QImage::Format_RGBA64_Premultiplied:
930
0
        case QImage::Format_Grayscale16:
931
0
            png_set_swap(png_ptr);
932
0
            break;
933
0
        default:
934
0
            break;
935
0
        }
936
0
    }
937
938
0
    if (looping >= 0 && frames_written == 0) {
939
0
        uchar data[13] = "NETSCAPE2.0";
940
        //                0123456789aBC
941
0
        data[0xB] = looping%0x100;
942
0
        data[0xC] = looping/0x100;
943
0
        png_write_chunk(png_ptr, const_cast<png_bytep>((const png_byte *)"gIFx"), data, 13);
944
0
    }
945
0
    if (ms_delay >= 0 || disposal!=Unspecified) {
946
0
        uchar data[4];
947
0
        data[0] = disposal;
948
0
        data[1] = 0;
949
0
        data[2] = (ms_delay/10)/0x100; // hundredths
950
0
        data[3] = (ms_delay/10)%0x100;
951
0
        png_write_chunk(png_ptr, const_cast<png_bytep>((const png_byte *)"gIFg"), data, 4);
952
0
    }
953
954
0
    int height = image.height();
955
0
    int width = image.width();
956
0
    switch (image.format()) {
957
0
    case QImage::Format_Mono:
958
0
    case QImage::Format_MonoLSB:
959
0
    case QImage::Format_Indexed8:
960
0
    case QImage::Format_Grayscale8:
961
0
    case QImage::Format_Grayscale16:
962
0
    case QImage::Format_RGB32:
963
0
    case QImage::Format_ARGB32:
964
0
    case QImage::Format_RGB888:
965
0
    case QImage::Format_BGR888:
966
0
    case QImage::Format_RGBX8888:
967
0
    case QImage::Format_RGBA8888:
968
0
    case QImage::Format_RGBX64:
969
0
    case QImage::Format_RGBA64:
970
0
        {
971
0
            png_bytep* row_pointers = new png_bytep[height];
972
0
            for (int y=0; y<height; y++)
973
0
                row_pointers[y] = const_cast<png_bytep>(image.constScanLine(y));
974
0
            png_write_image(png_ptr, row_pointers);
975
0
            delete [] row_pointers;
976
0
        }
977
0
        break;
978
0
    case QImage::Format_RGBA64_Premultiplied:
979
0
        {
980
0
            QImage row;
981
0
            png_bytep row_pointers[1];
982
0
            for (int y=0; y<height; y++) {
983
0
                row = image.copy(0, y, width, 1).convertToFormat(QImage::Format_RGBA64);
984
0
                row_pointers[0] = const_cast<png_bytep>(row.constScanLine(0));
985
0
                png_write_rows(png_ptr, row_pointers, 1);
986
0
            }
987
0
        }
988
0
        break;
989
0
    default:
990
0
        {
991
0
            QImage::Format fmt = image.hasAlphaChannel() ? QImage::Format_ARGB32 : QImage::Format_RGB32;
992
0
            QImage row;
993
0
            png_bytep row_pointers[1];
994
0
            for (int y=0; y<height; y++) {
995
0
                row = image.copy(0, y, width, 1).convertToFormat(fmt);
996
0
                row_pointers[0] = const_cast<png_bytep>(row.constScanLine(0));
997
0
                png_write_rows(png_ptr, row_pointers, 1);
998
0
            }
999
0
        }
1000
0
        break;
1001
0
    }
1002
1003
0
    png_write_end(png_ptr, info_ptr);
1004
0
    frames_written++;
1005
1006
0
    png_destroy_write_struct(&png_ptr, &info_ptr);
1007
1008
0
    return true;
1009
0
}
1010
1011
static bool write_png_image(const QImage &image, QIODevice *device,
1012
                            int compression, int quality, float gamma, const QString &description)
1013
0
{
1014
    // quality is used for backward compatibility, maps to compression
1015
1016
0
    QPNGImageWriter writer(device);
1017
0
    if (compression >= 0)
1018
0
        compression = qMin(compression, 100);
1019
0
    else if (quality >= 0)
1020
0
        compression = 100 - qMin(quality, 100);
1021
1022
0
    if (compression >= 0)
1023
0
        compression = (compression * 9) / 91; // map [0,100] -> [0,9]
1024
1025
0
    writer.setGamma(gamma);
1026
0
    return writer.writeImage(image, compression, description);
1027
0
}
1028
1029
QPngHandler::QPngHandler()
1030
8.23k
    : d(new QPngHandlerPrivate(this))
1031
8.23k
{
1032
8.23k
}
1033
1034
QPngHandler::~QPngHandler()
1035
8.23k
{
1036
8.23k
    if (d->png_ptr)
1037
0
        png_destroy_read_struct(&d->png_ptr, &d->info_ptr, &d->end_info);
1038
8.23k
    delete d;
1039
8.23k
}
1040
1041
bool QPngHandler::canRead() const
1042
8.23k
{
1043
8.23k
    if (d->state == QPngHandlerPrivate::Ready && !canRead(device()))
1044
2.02k
        return false;
1045
1046
6.20k
    if (d->state != QPngHandlerPrivate::Error) {
1047
6.20k
        setFormat("png");
1048
6.20k
        return true;
1049
6.20k
    }
1050
1051
0
    return false;
1052
6.20k
}
1053
1054
bool QPngHandler::canRead(QIODevice *device)
1055
16.6k
{
1056
16.6k
    if (!device) {
1057
0
        qCWarning(lcImageIo, "QPngHandler::canRead() called with no device");
1058
0
        return false;
1059
0
    }
1060
1061
16.6k
    return device->peek(8) == "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
1062
16.6k
}
1063
1064
bool QPngHandler::read(QImage *image)
1065
8.23k
{
1066
8.23k
    if (!canRead())
1067
2.02k
        return false;
1068
6.20k
    return d->readPngImage(image);
1069
8.23k
}
1070
1071
bool QPngHandler::write(const QImage &image)
1072
0
{
1073
0
    return write_png_image(image, device(), d->compression, d->quality, d->gamma, d->description);
1074
0
}
1075
1076
bool QPngHandler::supportsOption(ImageOption option) const
1077
32.9k
{
1078
32.9k
    return option == Gamma
1079
32.9k
        || option == Description
1080
32.9k
        || option == ImageFormat
1081
32.9k
        || option == Quality
1082
32.9k
        || option == CompressionRatio
1083
32.9k
        || option == Size;
1084
32.9k
}
1085
1086
QVariant QPngHandler::option(ImageOption option) const
1087
0
{
1088
0
    if (d->state == QPngHandlerPrivate::Error)
1089
0
        return QVariant();
1090
0
    if (d->state == QPngHandlerPrivate::Ready && !d->readPngHeader())
1091
0
        return QVariant();
1092
1093
0
    if (option == Gamma)
1094
0
        return d->gamma == 0.0 ? d->fileGamma : d->gamma;
1095
0
    else if (option == Quality)
1096
0
        return d->quality;
1097
0
    else if (option == CompressionRatio)
1098
0
        return d->compression;
1099
0
    else if (option == Description)
1100
0
        return d->description;
1101
0
    else if (option == Size)
1102
0
        return QSize(png_get_image_width(d->png_ptr, d->info_ptr),
1103
0
                     png_get_image_height(d->png_ptr, d->info_ptr));
1104
0
    else if (option == ImageFormat)
1105
0
        return d->readImageFormat();
1106
0
    return QVariant();
1107
0
}
1108
1109
void QPngHandler::setOption(ImageOption option, const QVariant &value)
1110
8.23k
{
1111
8.23k
    if (option == Gamma)
1112
0
        d->gamma = value.toFloat();
1113
8.23k
    else if (option == Quality)
1114
8.23k
        d->quality = value.toInt();
1115
0
    else if (option == CompressionRatio)
1116
0
        d->compression = value.toInt();
1117
0
    else if (option == Description)
1118
0
        d->description = value.toString();
1119
8.23k
}
1120
1121
QT_END_NAMESPACE
1122
1123
#endif // QT_NO_IMAGEFORMAT_PNG