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 
83 QT_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 
94 class QPngHandlerPrivate
95 {
96 public:
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 
QPngHandlerPrivate(QPngHandler * qq)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 {
AllocatedMemoryPointersQPngHandlerPrivate::AllocatedMemoryPointers136         AllocatedMemoryPointers()
137             : row_pointers(nullptr), accRow(nullptr), inRow(nullptr), outRow(nullptr)
138         { }
deallocateQPngHandlerPrivate::AllocatedMemoryPointers139         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 
165 class QPNGImageWriter {
166 public:
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);
writeImage(const QImage & img)178     bool writeImage(const QImage& img)
179         { return writeImage(img, 0, 0); }
writeImage(const QImage & img,int compression,const QString & description)180     bool writeImage(const QImage& img, int compression, const QString &description)
181         { return writeImage(img, compression, description, 0, 0); }
182 
device()183     QIODevice* device() { return dev; }
184 
185 private:
186     QIODevice* dev;
187     int frames_written;
188     DisposalMethod disposal;
189     int looping;
190     int ms_delay;
191     float gamma;
192 };
193 
194 extern "C" {
195 static
iod_read_fn(png_structp png_ptr,png_bytep data,png_size_t length)196 void 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(data, endcrc, 4);
205         in->seek(in->size());
206         return;
207     }
208 
209     while (length) {
210         int nr = in->read((char*)data, length);
211         if (nr <= 0) {
212             png_error(png_ptr, "Read Error");
213             return;
214         }
215         length -= nr;
216     }
217 }
218 
219 
220 static
qpiw_write_fn(png_structp png_ptr,png_bytep data,png_size_t length)221 void 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((char*)data, length);
227     if (nr != length) {
228         png_error(png_ptr, "Write Error");
229         return;
230     }
231 }
232 
233 
234 static
qpiw_flush_fn(png_structp)235 void qpiw_flush_fn(png_structp /* png_ptr */)
236 {
237 }
238 
239 }
240 
241 static
setup_qt(QImage & image,png_structp png_ptr,png_infop info_ptr,QSize scaledSize,bool * doScaledRead)242 void 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, &height, &bit_depth, &color_type, &interlace_method, nullptr, 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(1, qRgb(0,0,0));
269             image.setColor(0, qRgb(255,255,255));
270             if (png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &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(1, qRgba(0, 0, 0, 0));
276                 else if (g == 1)
277                     image.setColor(0, qRgba(255, 255, 255, 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, 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, qRgba(c,c,c,0xff));
330             }
331             if (png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color_p) && trans_color_p) {
332                 const int g = trans_color_p->gray;
333                 if (g < ncols) {
334                     image.setColor(g, 0);
335                 }
336             }
337         }
338     } else if (color_type == PNG_COLOR_TYPE_PALETTE
339                && png_get_PLTE(png_ptr, info_ptr, &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, &height, &bit_depth, &color_type, nullptr, nullptr, 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, &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, &num_trans, &trans_color_p) && trans_alpha) {
357             while (i < num_trans) {
358                 image.setColor(i, qRgba(
359                     palette[i].red,
360                     palette[i].green,
361                     palette[i].blue,
362                     trans_alpha[i]
363                    )
364                );
365                 i++;
366             }
367         }
368         while (i < num_palette) {
369             image.setColor(i, qRgba(
370                 palette[i].red,
371                 palette[i].green,
372                 palette[i].blue,
373                 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, 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, 0xff, 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 
read_image_scaled(QImage * outImage,png_structp png_ptr,png_infop info_ptr,QPngHandlerPrivate::AllocatedMemoryPointers & amp,QSize scaledSize)443 static 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, &height, &bit_depth, &color_type, nullptr, nullptr, nullptr);
456     png_get_oFFs(png_ptr, info_ptr, &offset_x, &offset_y, &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(amp.accRow, 0, ibw*sizeof(quint32));
470     amp.inRow = new png_byte[ibw];
471     memset(amp.inRow, 0, ibw*sizeof(png_byte));
472     amp.outRow = new uchar[ibw];
473     memset(amp.outRow, 0, 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, amp.inRow, nullptr);
482             quint32 fact = qMin(oysz, 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(oxsz, 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 
522 extern "C" {
qt_png_warning(png_structp,png_const_charp message)523 static void qt_png_warning(png_structp /*png_ptr*/, png_const_charp message)
524 {
525     qWarning("libpng warning: %s", message);
526 }
527 
528 }
529 
530 
readPngTexts(png_info * info)531 void 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, &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 #else
557     Q_UNUSED(info)
558 #endif
559 }
560 
561 
readPngHeader()562 bool QPngHandlerPrivate::readPngHeader()
563 {
564     state = Error;
565     png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,nullptr,nullptr,nullptr);
566     if (!png_ptr)
567         return false;
568 
569     png_set_error_fn(png_ptr, nullptr, nullptr, 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, nullptr, 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, &info_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, &info_ptr, &end_info);
593         png_ptr = nullptr;
594         return false;
595     }
596 
597     png_set_read_fn(png_ptr, this, iod_read_fn);
598     png_read_info(png_ptr, info_ptr);
599 
600     readPngTexts(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, &compressionType, &profileData, &profLen);
613         colorSpace = QColorSpace::fromIccProfile(QByteArray((const char *)profileData, profLen));
614         if (!colorSpace.isValid()) {
615             qWarning() << "QPngHandler: Failed to parse ICC profile";
616         } else {
617             QColorSpacePrivate *csD = QColorSpacePrivate::getWritable(colorSpace);
618             if (csD->description.isEmpty())
619                 csD->description = QString::fromLatin1((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, &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);
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_y, &red_x, &red_y,
644                              &green_x, &green_y, &blue_x, &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 
readPngImage(QImage * outImage)665 bool 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, &info_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, 1.0f / gamma, fileGamma);
687         colorSpace = colorSpace.withTransferFunction(QColorSpace::TransferFunction::Gamma, 1.0f / gamma);
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 = 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, &height, &bit_depth, &color_type, nullptr, nullptr, nullptr);
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 = nullptr;
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 
readImageFormat()767 QImage::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, nullptr, nullptr, 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, &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 
QPNGImageWriter(QIODevice * iod)810 QPNGImageWriter::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 
~QPNGImageWriter()820 QPNGImageWriter::~QPNGImageWriter()
821 {
822 }
823 
setDisposalMethod(DisposalMethod dm)824 void QPNGImageWriter::setDisposalMethod(DisposalMethod dm)
825 {
826     disposal = dm;
827 }
828 
setLooping(int loops)829 void QPNGImageWriter::setLooping(int loops)
830 {
831     looping = loops;
832 }
833 
setFrameDelay(int msecs)834 void QPNGImageWriter::setFrameDelay(int msecs)
835 {
836     ms_delay = msecs;
837 }
838 
setGamma(float g)839 void QPNGImageWriter::setGamma(float g)
840 {
841     gamma = g;
842 }
843 
set_text(const QImage & image,png_structp png_ptr,png_infop info_ptr,const QString & description)844 static 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 
writeImage(const QImage & image,int off_x,int off_y)902 bool QPNGImageWriter::writeImage(const QImage& image, int off_x, int off_y)
903 {
904     return writeImage(image, -1, QString(), off_x, off_y);
905 }
906 
writeImage(const QImage & image,int compression_in,const QString & description,int off_x_in,int off_y_in)907 bool 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,nullptr,nullptr,nullptr);
918     if (!png_ptr) {
919         return false;
920     }
921 
922     png_set_error_fn(png_ptr, nullptr, nullptr, qt_png_warning);
923 #ifdef PNG_BENIGN_ERRORS_SUPPORTED
924     png_set_benign_errors(png_ptr, 1);
925 #endif
926 
927     info_ptr = png_create_info_struct(png_ptr);
928     if (!info_ptr) {
929         png_destroy_write_struct(&png_ptr, nullptr);
930         return false;
931     }
932 
933     if (setjmp(png_jmpbuf(png_ptr))) {
934         png_destroy_write_struct(&png_ptr, &info_ptr);
935         return false;
936     }
937 
938     int compression = compression_in;
939     if (compression >= 0) {
940         if (compression > 9) {
941             qWarning("PNG: Compression %d out of range", compression);
942             compression = 9;
943         }
944         png_set_compression_level(png_ptr, compression);
945     }
946 
947     png_set_write_fn(png_ptr, (void*)this, qpiw_write_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, image.width(), image.height(),
983                  bpc, // per channel
984                  color_type, 0, 0, 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(cs.gamma(), 1.0f / gamma))
991             cs = cs.withTransferFunction(QColorSpace::TransferFunction::Gamma, 1.0f / gamma);
992         QByteArray iccProfileName = QColorSpacePrivate::get(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                      iccProfileName.constData(), PNG_COMPRESSION_TYPE_BASE,
1001                      (png_const_bytep)iccProfile.constData(),
1002              #endif
1003                      iccProfile.length());
1004     } else
1005 #endif
1006     if (gamma != 0.0) {
1007         png_set_gAMA(png_ptr, info_ptr, 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(256, 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(rgba);
1022             palette[i].green = qGreen(rgba);
1023             palette[i].blue = qBlue(rgba);
1024             trans[i] = qAlpha(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, num_trans, 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, off_x, off_y, PNG_OFFSET_PIXEL);
1068     }
1069 
1070     if (frames_written > 0)
1071         png_set_sig_bytes(png_ptr, 8);
1072 
1073     if (image.dotsPerMeterX() > 0 || image.dotsPerMeterY() > 0) {
1074         png_set_pHYs(png_ptr, info_ptr,
1075                 image.dotsPerMeterX(), 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, 0, PNG_FILLER_AFTER);
1094             break;
1095         default:
1096             png_set_filler(png_ptr, 0,
1097                 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, const_cast<png_bytep>((const png_byte *)"gIFx"), data, 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, const_cast<png_bytep>((const png_byte *)"gIFg"), data, 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, 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(0, y, width, 1).convertToFormat(QImage::Format_RGBA64);
1161                 row_pointers[0] = const_cast<png_bytep>(row.constScanLine(0));
1162                 png_write_rows(png_ptr, row_pointers, 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(0, y, width, 1).convertToFormat(fmt);
1173                 row_pointers[0] = const_cast<png_bytep>(row.constScanLine(0));
1174                 png_write_rows(png_ptr, row_pointers, 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, &info_ptr);
1184 
1185     return true;
1186 }
1187 
write_png_image(const QImage & image,QIODevice * device,int compression,int quality,float gamma,const QString & description)1188 static 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(compression, 100);
1196     else if (quality >= 0)
1197         compression = 100 - qMin(quality, 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(image, compression, description);
1204 }
1205 
QPngHandler()1206 QPngHandler::QPngHandler()
1207     : d(new QPngHandlerPrivate(this))
1208 {
1209 }
1210 
~QPngHandler()1211 QPngHandler::~QPngHandler()
1212 {
1213     if (d->png_ptr)
1214         png_destroy_read_struct(&d->png_ptr, &d->info_ptr, &d->end_info);
1215     delete d;
1216 }
1217 
canRead() const1218 bool QPngHandler::canRead() const
1219 {
1220     if (d->state == QPngHandlerPrivate::Ready && !canRead(device()))
1221         return false;
1222 
1223     if (d->state != QPngHandlerPrivate::Error) {
1224         setFormat("png");
1225         return true;
1226     }
1227 
1228     return false;
1229 }
1230 
canRead(QIODevice * device)1231 bool QPngHandler::canRead(QIODevice *device)
1232 {
1233     if (!device) {
1234         qWarning("QPngHandler::canRead() called with no device");
1235         return false;
1236     }
1237 
1238     return device->peek(8) == "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
1239 }
1240 
read(QImage * image)1241 bool QPngHandler::read(QImage *image)
1242 {
1243     if (!canRead())
1244         return false;
1245     return d->readPngImage(image);
1246 }
1247 
write(const QImage & image)1248 bool QPngHandler::write(const QImage &image)
1249 {
1250     return write_png_image(image, device(), d->compression, d->quality, d->gamma, d->description);
1251 }
1252 
supportsOption(ImageOption option) const1253 bool 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 
option(ImageOption option) const1264 QVariant 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(d->png_ptr, d->info_ptr),
1281                      png_get_image_height(d->png_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 
setOption(ImageOption option,const QVariant & value)1289 void 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 
1303 QT_END_NAMESPACE
1304 
1305 #endif // QT_NO_IMAGEFORMAT_PNG
1306