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