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