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 | |
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 | |
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); |
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 | |
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 |
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(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 | |
220 | static |
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(data: (char*)data, len: length); |
227 | if (nr != length) { |
228 | png_error(png_ptr, error_message: "Write Error" ); |
229 | return; |
230 | } |
231 | } |
232 | |
233 | |
234 | static |
235 | void qpiw_flush_fn(png_structp /* png_ptr */) |
236 | { |
237 | } |
238 | |
239 | } |
240 | |
241 | static |
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: &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 | |
443 | static void read_image_scaled(QImage *outImage, png_structp png_ptr, png_infop info_ptr, |
444 | QPngHandlerPrivate::AllocatedMemoryPointers &, 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 | |
522 | extern "C" { |
523 | static 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 | |
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_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 | |
562 | bool 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 | |
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_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 | |
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: &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 | |
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 | |
820 | QPNGImageWriter::~QPNGImageWriter() |
821 | { |
822 | } |
823 | |
824 | void QPNGImageWriter::setDisposalMethod(DisposalMethod dm) |
825 | { |
826 | disposal = dm; |
827 | } |
828 | |
829 | void QPNGImageWriter::setLooping(int loops) |
830 | { |
831 | looping = loops; |
832 | } |
833 | |
834 | void QPNGImageWriter::setFrameDelay(int msecs) |
835 | { |
836 | ms_delay = msecs; |
837 | } |
838 | |
839 | void QPNGImageWriter::setGamma(float g) |
840 | { |
841 | gamma = g; |
842 | } |
843 | |
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(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 | |
902 | bool 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 | |
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,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 | |
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(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 | |
1206 | QPngHandler::QPngHandler() |
1207 | : d(new QPngHandlerPrivate(this)) |
1208 | { |
1209 | } |
1210 | |
1211 | QPngHandler::~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 | |
1218 | bool 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 | |
1231 | bool 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 | |
1241 | bool QPngHandler::read(QImage *image) |
1242 | { |
1243 | if (!canRead()) |
1244 | return false; |
1245 | return d->readPngImage(outImage: image); |
1246 | } |
1247 | |
1248 | bool 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 | |
1253 | 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 | |
1264 | 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(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 | |
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 | |