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

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