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