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