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