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