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

source code of qtbase/src/gui/image/qpnghandler.cpp