Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_png_converter.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2005-2007 Cyrille Berger <cberger@cberger.net>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 *
6 */
7
8#include "kis_png_converter.h"
9// A big thank to Glenn Randers-Pehrson for his wonderful
10// documentation of libpng available at
11// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html
12
13#ifndef PNG_MAX_UINT // Removed in libpng 1.4
14#define PNG_MAX_UINT PNG_UINT_31_MAX
15#endif
16
17#include <KoConfig.h> // WORDS_BIGENDIAN
18#include <KoStore.h>
19#include <KoStoreDevice.h>
20
21#include <limits.h>
22#include <stdio.h>
23#include <zlib.h>
24
25#include <QBuffer>
26#include <QFile>
27#include <QApplication>
28
29#include <klocalizedstring.h>
30#include <QUrl>
31
32#include <KoColorSpace.h>
33#include <KoDocumentInfo.h>
34#include <KoID.h>
36#include <KoColorProfile.h>
37#include <KoColorProfileQuery.h>
39#include <KoColor.h>
40#include <KoUnit.h>
41
43#include "kis_clipboard.h"
44#include "kis_undo_stores.h"
45#include <KisDocument.h>
47#include <kis_config.h>
49#include <kis_group_layer.h>
50#include <kis_image.h>
51#include <kis_iterator_ng.h>
52#include <kis_layer.h>
54#include <kis_meta_data_store.h>
55#include <kis_paint_device.h>
56#include <kis_paint_layer.h>
57#include <kis_painter.h>
58#include <kis_transaction.h>
59#include <kis_hdr_metadata.h>
60
61#include <kis_assert.h>
62
63namespace
64{
65
66int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha)
67{
68
69 QString id = cs->id();
70
71 if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") {
72 return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY;
73 }
74 if (id == "RGBA" || id == "RGBA16" || id == "RGBAF16" || id == "RGBAF32") {
75 return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB;
76 }
77
78 return -1;
79
80}
81
82bool colorSpaceIdSupported(const QString &id)
83{
84 return id == "RGBA" || id == "RGBA16" ||
85 id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16";
86}
87
88QPair<QString, QString> getColorSpaceForColorType(int color_type, int color_nb_bits)
89{
90 QPair<QString, QString> r;
91
92 if (color_type == PNG_COLOR_TYPE_PALETTE) {
93 r.first = RGBAColorModelID.id();
94 r.second = Integer8BitsColorDepthID.id();
95 } else {
96 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
97 r.first = GrayAColorModelID.id();
98 } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) {
99 r.first = RGBAColorModelID.id();
100 }
101 if (color_nb_bits == 16) {
102 r.second = Integer16BitsColorDepthID.id();
103 } else if (color_nb_bits <= 8) {
104 r.second = Integer8BitsColorDepthID.id();
105 }
106 }
107 return r;
108}
109
110
111void fillText(png_text* p_text, const char* key, QString& text)
112{
113 p_text->compression = PNG_TEXT_COMPRESSION_zTXt;
114 p_text->key = const_cast<char *>(key);
115 char* textc = new char[text.length()+1];
116 strcpy(textc, text.toLatin1());
117 p_text->text = textc;
118 p_text->text_length = text.length() + 1;
119}
120
121long formatStringList(char *string, const size_t length, const char *format, va_list operands)
122{
123 int n = vsnprintf(string, length, format, operands);
124
125 if (n < 0)
126 string[length-1] = '\0';
127
128 return((long) n);
129}
130
131long formatString(char *string, const size_t length, const char *format, ...)
132{
133 long n;
134
135 va_list operands;
136
137 va_start(operands, format);
138 n = (long) formatStringList(string, length, format, operands);
139 va_end(operands);
140 return(n);
141}
142
143void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data)
144{
145
146 png_textp text;
147
148 png_uint_32 allocated_length, description_length;
149
150 const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
151
152 dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << Qt::endl;
153
154 text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text));
155 description_length = profile_type.length();
156 allocated_length = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length);
157
158 text[0].text = (png_charp) png_malloc(ping, allocated_length);
159 memset(text[0].text, 0, allocated_length);
160
161 QString key = QLatin1String("Raw profile type ") + profile_type.toLatin1();
162 QByteArray keyData = key.toLatin1();
163 text[0].key = keyData.data();
164
165 uchar* sp = (uchar*)profile_data.data();
166 png_charp dp = text[0].text;
167 *dp++ = '\n';
168
169 memcpy(dp, profile_type.toLatin1().constData(), profile_type.length());
170
171 dp += description_length;
172 *dp++ = '\n';
173
174 formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", (unsigned long)profile_data.length());
175
176 dp += 8;
177
178 for (long i = 0; i < (long) profile_data.length(); i++) {
179 if (i % 36 == 0)
180 *dp++ = '\n';
181
182 *(dp++) = (char) hex[((*sp >> 4) & 0x0f)];
183 *(dp++) = (char) hex[((*sp++) & 0x0f)];
184 }
185
186 *dp++ = '\n';
187 *dp = '\0';
188 text[0].text_length = (png_size_t)(dp - text[0].text);
189 text[0].compression = -1;
190
191 if (text[0].text_length <= allocated_length)
192 png_set_text(ping, ping_info, text, 1);
193
194 png_free(ping, text[0].text);
195 png_free(ping, text);
196}
197
198QByteArray png_read_raw_profile(png_textp text)
199{
200 QByteArray profile;
201
202 static const unsigned char unhex[103] = {
203 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
204 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
205 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0,
206 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12,
208 13, 14, 15
209 };
210
211 png_charp sp = text[0].text + 1;
212 /* look for newline */
213 while (*sp != '\n')
214 sp++;
215 /* look for length */
216 while (*sp == '\0' || *sp == ' ' || *sp == '\n')
217 sp++;
218 png_uint_32 length = (png_uint_32) atol(sp);
219 while (*sp != ' ' && *sp != '\n')
220 sp++;
221 if (length == 0) {
222 return profile;
223 }
224 profile.resize(length);
225 /* copy profile, skipping white space and column 1 "=" signs */
226 unsigned char *dp = (unsigned char*)profile.data();
227 png_uint_32 nibbles = length * 2;
228 for (png_uint_32 i = 0; i < nibbles; i++) {
229 while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') {
230 if (*sp == '\0') {
231 return QByteArray();
232 }
233 sp++;
234 }
235 if (i % 2 == 0)
236 *dp = (unsigned char)(16 * unhex[(int) *sp++]);
237 else
238 (*dp++) += unhex[(int) *sp++];
239 }
240 return profile;
241}
242
243void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize)
244{
245 dbgFile << "Decoding " << type << " " << text[0].key;
247 Q_ASSERT(exifIO);
248
249 QByteArray rawProfile = png_read_raw_profile(text);
250 if (headerSize > 0) {
251 rawProfile.remove(0, headerSize);
252 }
253 if (rawProfile.size() > 0) {
254 QBuffer buffer;
255 buffer.setData(rawProfile);
256 exifIO->loadFrom(store, &buffer);
257 } else {
258 dbgFile << "Decoding failed";
259 }
260}
261}
262
263extern "C" {
264static void kis_png_warning(png_structp /*png_ptr*/, png_const_charp message)
265{
266 qWarning("libpng warning: %s", message);
267}
268
269}
270
271
272
273
275{
276 // Q_ASSERT(doc);
277 // Q_ASSERT(adapter);
278
279 m_doc = doc;
280 m_stop = false;
281 m_max_row = 0;
282 m_image = 0;
283 m_batchMode = batchMode;
284}
285
289
291{
292public:
293 KisPNGReadStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
294 }
295 int nextValue() {
296 if (m_posinc == 0) {
297 m_posinc = 8;
298 m_buf++;
299 }
300 m_posinc -= m_depth;
301 return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1));
302 }
303private:
305 quint8* m_buf;
306};
307
309{
310public:
311 KisPNGWriteStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
312 *m_buf = 0;
313 }
314 void setNextValue(int v) {
315 if (m_posinc == 0) {
316 m_posinc = 8;
317 m_buf++;
318 *m_buf = 0;
319 }
320 m_posinc -= m_depth;
321 *m_buf = (v << m_posinc) | *m_buf;
322 }
323private:
325 quint8* m_buf;
326};
327
329{
330public:
331 KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {}
333 virtual png_bytep readLine() = 0;
334protected:
335 png_structp png_ptr;
337};
338
340{
341public:
342 KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) {
343 std::size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
344 row_pointer = new png_byte[rowbytes];
345 }
347 delete[] row_pointer;
348 }
349 png_bytep readLine() override {
350 png_read_row(png_ptr, row_pointer, 0);
351 return row_pointer;
352 }
353private:
354 png_bytep row_pointer;
355};
356
358{
359public:
360 KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) {
361 row_pointers = new png_bytep[height];
362 std::size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
363 for (int i = 0; i < height; i++) {
364 row_pointers[i] = new png_byte[rowbytes];
365 }
366 png_read_image(png_ptr, row_pointers);
367 }
369 for (int i = 0; i < height; i++) {
370 delete[] row_pointers[i];
371 }
372 delete[] row_pointers;
373 }
374 png_bytep readLine() override {
375 return row_pointers[y++];
376 }
377private:
378 png_bytepp row_pointers;
379 int y;
380};
381
382
383static
384void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
385{
386 QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr);
387
388 while (length) {
389 int nr = in->read((char*)data, length);
390 if (nr <= 0) {
391 png_error(png_ptr, "Read Error");
392 return;
393 }
394 length -= nr;
395 }
396}
397
398static
399void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
400{
401 QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr);
402
403 uint nr = out->write((char*)data, length);
404 if (nr != length) {
405 png_error(png_ptr, "Write Error");
406 return;
407 }
408}
409
410static
411void _flush_fn(png_structp png_ptr)
412{
413 Q_UNUSED(png_ptr);
414}
415
416// Templates for converting the HDR curves.
417
418template <typename src_channel_type,
419 typename dst_channel_type>
459};
460
462{
463 dbgFile << "Start decoding PNG File";
464
465 png_byte signature[8];
466 iod->peek((char*)signature, 8);
467
468#if PNG_LIBPNG_VER < 10400
469 if (!png_check_sig(signature, 8)) {
470#else
471 if (png_sig_cmp(signature, 0, 8) != 0) {
472#endif
473 iod->close();
475 }
476
477 // Initialize the internal structures
478 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
479
480 if (!png_ptr) {
481 iod->close();
482 }
483
484#ifdef PNG_SET_USER_LIMITS_SUPPORTED
485 /* Remove the user limits, if any */
486 png_set_user_limits(png_ptr, 0x7fffffff, 0x7fffffff);
487 png_set_chunk_cache_max(png_ptr, 0);
488 png_set_chunk_malloc_max(png_ptr, 0);
489#endif
490
491 png_set_error_fn(png_ptr, nullptr, nullptr, kis_png_warning);
492 #ifdef PNG_BENIGN_ERRORS_SUPPORTED
493 png_set_benign_errors(png_ptr, 1);
494 #endif
495
496 png_infop info_ptr = png_create_info_struct(png_ptr);
497 if (!info_ptr) {
498 png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
499 iod->close();
501 }
502
503 png_infop end_info = png_create_info_struct(png_ptr);
504 if (!end_info) {
505 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0);
506 iod->close();
508 }
509
510 // Catch errors
511 if (setjmp(png_jmpbuf(png_ptr))) {
512 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
513 iod->close();
515 }
516
517 // Initialize the special
518 png_set_read_fn(png_ptr, iod, _read_fn);
519
520#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
521 png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
522#endif
523
524 // read all PNG info up to image data
525 png_read_info(png_ptr, info_ptr);
526
527
528 if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) {
529 png_set_expand(png_ptr);
530 }
531
532 if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) {
533 png_set_packing(png_ptr);
534 }
535
536
537 if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE &&
538 (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) {
539 png_set_expand(png_ptr);
540 }
541 png_read_update_info(png_ptr, info_ptr);
542
543 // Read information about the png
544 png_uint_32 width, height;
545 int color_nb_bits, color_type, interlace_type;
546 png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0);
547 dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << Qt::endl;
548 // swap byte order on little endian machines.
549#ifndef WORDS_BIGENDIAN
550 if (color_nb_bits > 8)
551 png_set_swap(png_ptr);
552#endif
553
554 // Determine the colorspace
555 QPair<QString, QString> csName = getColorSpaceForColorType(color_type, color_nb_bits);
556 if (csName.first.isEmpty()) {
557 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
558 iod->close();
560 }
561 bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA);
562
563 // Read image profile
564 png_charp profile_name;
565#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
566 png_bytep profile_data;
567#else
568 png_charp profile_data;
569#endif
570 int compression_type;
571 png_uint_32 proflen;
572
573 // Get the various optional chunks
574
575 // https://www.w3.org/TR/PNG/#11cHRM
576#if defined(PNG_cHRM_SUPPORTED)
577 double whitePointX, whitePointY;
578 double redX, redY;
579 double greenX, greenY;
580 double blueX, blueY;
581 png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY);
582 dbgFile << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY;
583#endif
584
585 // https://www.w3.org/TR/PNG/#11gAMA
586#if defined(PNG_GAMMA_SUPPORTED)
587 double gamma;
588 png_get_gAMA(png_ptr, info_ptr, &gamma);
589 dbgFile << "gAMA" << gamma;
590#endif
591
592 // https://www.w3.org/TR/PNG/#11sRGB
593#if defined(PNG_sRGB_SUPPORTED)
594 int sRGBIntent;
595 png_get_sRGB(png_ptr, info_ptr, &sRGBIntent);
596 dbgFile << "sRGB" << sRGBIntent;
597#endif
598
599 bool fromBlender = false;
600
601 png_text* text_ptr;
602 int num_comments;
603 png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
604
605 for (int i = 0; i < num_comments; i++) {
606 QString key = QString(text_ptr[i].key).toLower();
607 if (key == "file") {
608 QString relatedFile = text_ptr[i].text;
609 if (relatedFile.contains(".blend", Qt::CaseInsensitive)){
610 fromBlender=true;
611 }
612 }
613 }
614
615 bool loadedImageHasLegacyHDRDummyProfile = false;
617
618 bool loadedCICP = false;
619
620 ConversionPolicy linearConversionPolicy = ConversionPolicy::KeepTheSame;
621#if defined(PNG_cICP_SUPPORTED)
622 png_byte primaries = 0;
623 png_byte transfer = 0;
624 png_byte matrix = 0;
625 png_byte fullrange = 0;
626 if (png_get_cICP(png_ptr, info_ptr, &primaries, &transfer, &matrix, &fullrange)) {
627 // TODO: in theory we could use chroma chunk here if non-zero.
628 dbgFile << "load cicp" << primaries << transfer << matrix << fullrange;
629 if (transfer == TRC_ITU_R_BT_2100_0_HLG) {
630 linearConversionPolicy = ConversionPolicy::ApplyHLG;
631 transfer = TRC_LINEAR;
632 csName.second = Float32BitsColorDepthID.id();
633 } else if (transfer == TRC_SMPTE_ST_428_1){
634 linearConversionPolicy = ConversionPolicy::ApplySMPTE428;
635 transfer = TRC_LINEAR;
636 csName.second = Float32BitsColorDepthID.id();
637 }
639
640 if (cicpProfile && cicpProfile->isSuitableForWorkspace()) {
641 profile = cicpProfile;
642 loadedCICP = true;
643 }
644 }
645#endif
646
647 if (!loadedCICP) {
648 if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) {
649 QByteArray profile_rawdata(reinterpret_cast<char*>(profile_data), proflen);
650 profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata);
651 if (profile) {
652 if (!profile->isSuitableForWorkspace()) {
653 dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile";
654 }
655 }
656
657 loadedImageHasLegacyHDRDummyProfile = strcmp(profile_name, "ITUR_2100_PQ_FULL") == 0;
658 }
659 else if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) {
660 // Ask the user which color profile to use
661 KisConfig cfg(true);
662 quint32 behaviour = cfg.pasteBehaviour();
663 if (behaviour == KisClipboard::PASTE_ASK) {
664 KisDlgPngImport dlg(m_path, csName.first, csName.second);
666 Q_UNUSED(hijacker);
667 dlg.exec();
668 if (!dlg.profile().isEmpty()) {
670 }
671 }
672 }
673 else {
674 dbgFile << "no embedded profile, will use the default sRGB profile";
675 }
676 }
677
678 const QString colorSpaceId =
679 KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second);
680
681 // Check that the profile is used by the color space
682 if (profile
683 && (!KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)
684 || !(profile->isSuitableForOutput() || profile->isSuitableForInput()))) {
685 warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second;
686 profile = 0;
687 }
688
689 // Retrieve a pointer to the colorspace
690 KoColorConversionTransformation* transform = 0;
691 const KoColorSpace* cs = 0;
692
693 if (loadedImageHasLegacyHDRDummyProfile &&
694 csName.first == RGBAColorModelID.id() &&
695 csName.second == Integer16BitsColorDepthID.id()) {
696
697 const KoColorSpace *p2020PQCS =
702
703 cs = p2020PQCS;
704
705 } else if (profile && profile->isSuitableForWorkspace()) {
706 dbgFile << "image has embedded profile: " << profile->name() << "\n";
707 cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile);
708 }
709 else {
710 // Loading a backup colorspace
711 cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "");
712 }
713
714 // Create the cmsTransform if needed
715 if (profile && !profile->isSuitableForWorkspace() && profile->isSuitableForInput()) {
717 }
718
719 if (cs == 0) {
720 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
722 }
723
724 // Creating the KisImageSP
725 if (m_image == 0) {
727 m_image = new KisImage(store, width, height, cs, "built image");
728 }
729
730 // Read resolution
731 int unit_type;
732 png_uint_32 x_resolution = 0, y_resolution = 0;
733
734 png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type);
735 if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) {
736 m_image->setResolution((double) POINT_TO_CM(x_resolution) / 100.0, (double) POINT_TO_CM(y_resolution) / 100.0); // It is the "invert" macro because we convert from point-per-inch to points
737 } else if (unit_type == PNG_RESOLUTION_UNKNOWN) {
738 m_image->setResolution(100.0, 100.0);
739 }
740
741#if defined(PNG_cLLI_SUPPORTED)
743 if (png_get_cLLI(png_ptr, info_ptr, &clli.maxContentLightLevel, &clli.maxFrameAverageLightLevel)) {
744 const double refWhiteMultiplier = 1.0/profile->hdrReferenceWhite().value_or(203.0);
745 clli.maxContentLightLevel *= refWhiteMultiplier;
746 clli.maxFrameAverageLightLevel *= refWhiteMultiplier;
748 }
749#endif
750
751#if defined(PNG_mDCV_SUPPORTED)
753 if (png_get_mDCV(png_ptr, info_ptr,
754 &cvi.white.x, &cvi.white.y,
755 &cvi.red.x, &cvi.red.y,
756 &cvi.green.x, &cvi.green.y,
757 &cvi.blue.x, &cvi.blue.y,
758 &cvi.maxLuminance, &cvi.minLuminance)) {
760 }
761#endif
762
763 double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1);
764 KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX);
765
766 // Read comments/texts...
767 png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
768 if (m_doc) {
770 dbgFile << "There are " << num_comments << " comments in the text";
771 for (int i = 0; i < num_comments; i++) {
772 QString key = QString(text_ptr[i].key).toLower();
773 dbgFile << "key: " << text_ptr[i].key
774 << ", containing: " << text_ptr[i].text
775 << ": " << (key == "raw profile type exif " ? "isExif" : "something else");
776 if (key == "title") {
777 info->setAboutInfo("title", text_ptr[i].text);
778 } else if (key == "description") {
779 info->setAboutInfo("comment", text_ptr[i].text);
780 } else if (key == "author") {
781 info->setAuthorInfo("creator", text_ptr[i].text);
782 } else if (key.contains("raw profile type exif")) {
783 decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6);
784 } else if (key.contains("raw profile type iptc")) {
785 decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14);
786 } else if (key.contains("raw profile type xmp")) {
787 decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0);
788 } else if (key == "version") {
789 m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text)));
790 } else if (key == "preset") {
791 m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text)));
792 }
793 }
794 }
795 // Read image data
796 QScopedPointer<KisPNGReaderAbstract> reader;
797 try {
798 if (interlace_type == PNG_INTERLACE_ADAM7) {
799 reader.reset(new KisPNGReaderFullImage(png_ptr, info_ptr, width, height));
800 } else {
801 reader.reset(new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height));
802 }
803 } catch (const std::bad_alloc& e) {
804 // new png_byte[] may raise such an exception if the image
805 // is invalid / to large.
806 dbgFile << "bad alloc: " << e.what();
807 // Free only the already allocated png_byte instances.
808 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
810 }
811
812 // Read the palette if the file is indexed
813 png_colorp palette ;
814 int num_palette;
815 if (color_type == PNG_COLOR_TYPE_PALETTE) {
816 png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
817 }
818
819 // Read the transparency palette
820 quint8 palette_alpha[256];
821 memset(palette_alpha, 255, 256);
822 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
823 if (color_type == PNG_COLOR_TYPE_PALETTE) {
824 png_bytep alpha_ptr;
825 int num_alpha;
826 png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0);
827 for (int i = 0; i < num_alpha; ++i) {
828 palette_alpha[i] = alpha_ptr[i];
829 }
830 }
831 }
832
833 for (png_uint_32 y = 0; y < height; y++) {
834 KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width);
835
836 png_bytep row_pointer = reader->readLine();
837
838 switch (color_type) {
839 case PNG_COLOR_TYPE_GRAY:
840 case PNG_COLOR_TYPE_GRAY_ALPHA:
841 if (color_nb_bits == 16) {
842 quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
843 do {
844 quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
845 d[0] = *(src++);
846 if (hasalpha) {
847 d[1] = *(src++);
848 } else {
849 d[1] = quint16_MAX;
850 }
851 if (transform) transform->transformInPlace(reinterpret_cast<quint8*>(d), reinterpret_cast<quint8*>(d), 1);
852 } while (it->nextPixel());
853 } else {
854 KisPNGReadStream stream(row_pointer, color_nb_bits);
855 do {
856 quint8 *d = it->rawData();
857 d[0] = (quint8)(stream.nextValue() * coeff);
858 if (hasalpha) {
859 d[1] = (quint8)(stream.nextValue() * coeff);
860 } else {
861 d[1] = UCHAR_MAX;
862 }
863 if (transform) transform->transformInPlace(d, d, 1);
864 } while (it->nextPixel());
865 }
866 // FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits"
867 break;
868 case PNG_COLOR_TYPE_RGB:
869 case PNG_COLOR_TYPE_RGB_ALPHA:
870 if (linearConversionPolicy != ConversionPolicy::KeepTheSame) {
871 if (color_nb_bits == 16) {
873 quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
874 do {
875 float *d = reinterpret_cast<float *>(it->rawData());
876 d[0] = policy.remove(linearConversionPolicy, *(src++));
877 d[1] = policy.remove(linearConversionPolicy, *(src++));
878 d[2] = policy.remove(linearConversionPolicy, *(src++));
879 if (hasalpha) d[3] = policy.remove(ConversionPolicy::KeepTheSame, *(src++));
880 else d[3] = 1.0;
881 if (transform) transform->transformInPlace(reinterpret_cast<quint8 *>(d), reinterpret_cast<quint8*>(d), 1);
882 } while (it->nextPixel());
883 } else {
885 quint8 *src = reinterpret_cast<quint8 *>(row_pointer);
886 do {
887 float *d = reinterpret_cast<float *>(it->rawData());
888 d[0] = policy.remove(linearConversionPolicy, *(src++));
889 d[1] = policy.remove(linearConversionPolicy, *(src++));
890 d[2] = policy.remove(linearConversionPolicy, *(src++));
891 if (hasalpha) d[3] = policy.remove(ConversionPolicy::KeepTheSame, *(src++));
892 else d[3] = 1.0;
893 if (transform) transform->transformInPlace(reinterpret_cast<quint8 *>(d), reinterpret_cast<quint8*>(d), 1);
894 } while (it->nextPixel());
895 }
896 } else {
897 if (color_nb_bits == 16) {
898 quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
899 do {
900 quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
901 d[2] = *(src++);
902 d[1] = *(src++);
903 d[0] = *(src++);
904 if (hasalpha) d[3] = *(src++);
905 else d[3] = quint16_MAX;
906 if (transform) transform->transformInPlace(reinterpret_cast<quint8 *>(d), reinterpret_cast<quint8*>(d), 1);
907 } while (it->nextPixel());
908 } else {
909 KisPNGReadStream stream(row_pointer, color_nb_bits);
910 do {
911 quint8 *d = it->rawData();
912 d[2] = (quint8)(stream.nextValue() * coeff);
913 d[1] = (quint8)(stream.nextValue() * coeff);
914 d[0] = (quint8)(stream.nextValue() * coeff);
915 if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff);
916 else d[3] = UCHAR_MAX;
917 if (transform) transform->transformInPlace(d, d, 1);
918 } while (it->nextPixel());
919 }
920 }
921 break;
922 case PNG_COLOR_TYPE_PALETTE: {
923 KisPNGReadStream stream(row_pointer, color_nb_bits);
924 do {
925 quint8 *d = it->rawData();
926 quint8 index = stream.nextValue();
927 quint8 alpha = palette_alpha[ index ];
928 if (alpha == 0) {
929 memset(d, 0, 4);
930 } else {
931 png_color c = palette[ index ];
932 d[2] = c.red;
933 d[1] = c.green;
934 d[0] = c.blue;
935 d[3] = alpha;
936 }
937 } while (it->nextPixel());
938 }
939 break;
940 default:
942 }
943 }
944 m_image->addNode(layer.data(), m_image->rootLayer().data());
945
946 png_read_end(png_ptr, end_info);
947 iod->close();
948
949 // Freeing memory
950 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
952
953}
954
956{
957 m_path = filename;
958
959 QFile fp(filename);
960 if (fp.exists()) {
961 if (!fp.open(QIODevice::ReadOnly)) {
962 dbgFile << "Failed to open PNG File";
964 }
965
966 return buildImage(&fp);
967 }
969
970}
971
972
977
978bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData)
979{
980 if (store->open(filename)) {
981 KoStoreDevice io(store);
982 if (!io.open(QIODevice::WriteOnly)) {
983 dbgFile << "Could not open for writing:" << filename;
984 return false;
985 }
986 KisPNGConverter pngconv(0);
987 vKisAnnotationSP_it annotIt;
988 KisMetaData::Store* metaDataStore = 0;
989 if (metaData) {
990 metaDataStore = new KisMetaData::Store(*metaData);
991 }
992 KisPNGOptions options;
993 options.compression = 3;
994 options.interlace = false;
995 options.tryToSaveAsIndexed = false;
996 options.alpha = true;
997 options.storeColorSpaceInfo = true;
998 options.downsample = false;
999
1000 if (dev->colorSpace()->id() != "RGBA") {
1001 dev = new KisPaintDevice(*dev.data());
1003 }
1004
1005 KisImportExportErrorCode success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore);
1006 if (!success.isOk()) {
1007 dbgFile << "Saving PNG failed:" << filename;
1008 delete metaDataStore;
1009 return false;
1010 }
1011 delete metaDataStore;
1012 io.close();
1013 if (!store->close()) {
1014 return false;
1015 }
1016 } else {
1017 dbgFile << "Opening of data file failed :" << filename;
1018 return false;
1019 }
1020 return true;
1021
1022}
1023
1024
1025KisImportExportErrorCode KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData)
1026{
1027 dbgFile << "Start writing PNG File " << filename;
1028 // Open a QIODevice for writing
1029 QFile fp (filename);
1030 if (!fp.open(QIODevice::WriteOnly)) {
1031 dbgFile << "Failed to open PNG File for writing";
1032 return (KisImportExportErrorCannotWrite(fp.error()));
1033 }
1034
1035 KisImportExportErrorCode result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData);
1036
1037 return result;
1038}
1039
1040KisImportExportErrorCode KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData)
1041{
1043
1044 if (!options.alpha) {
1046 KoColor c(options.transparencyFillColor, device->colorSpace());
1047 tmp->fill(imageRect, c);
1048 KisPainter gc(tmp);
1049 gc.bitBlt(imageRect.topLeft(), device, imageRect);
1050 gc.end();
1051 device = tmp;
1052 }
1053
1055 options.forceSRGB = false;
1056 }
1057
1059 QString dstModel = device->colorSpace()->colorModelId().id();
1060 QString dstDepth = device->colorSpace()->colorDepthId().id();
1061 bool isFloatingPoint = device->colorSpace()->colorDepthId() == Float16BitsColorDepthID
1064 const KoColorProfile *dstProfile = device->colorSpace()->profile();
1065 bool needColorTransform = false;
1066
1067 dbgFile << "Converting to... sRgb" << options.forceSRGB << "rec2020" << options.convertFloatToRec2020 << "policy" << int(options.floatingPointConversion);
1068 if ((options.convertFloatToRec2020 & isFloatingPoint) || options.forceSRGB || !colormodels.contains(device->colorSpace()->colorModelId().id())) {
1069 dstModel = RGBAColorModelID.id();
1071
1072 needColorTransform = true;
1073
1074 if (options.convertFloatToRec2020 && isFloatingPoint) {
1077 } else {
1079 }
1080 }
1081 }
1082 // If the profile is a pq profile, but not one with 203 reference white, use the default rec2100 203 profile.
1083 // Once png can export diffuse white, we won't need this anymore.
1085 && !qFuzzyCompare(dstProfile->hdrReferenceWhite().value_or(203.0), 203.0)) {
1087 }
1088
1089 // We want to downsample when...
1090 // Option.downsample is on and we're not fp (because we manually handle fp 8bit)
1091 // When model is not rgba (no cicp writing, so no hdr, sadly).
1092 // When we're fp but the tf is not linear (fp+linear is also a situation we handle ourselves!)
1093 if (dstModel != RGBAColorModelID.id()
1094 || (!isFloatingPoint && options.downsample)
1095 || (isFloatingPoint && dstProfile->getTransferCharacteristics() != TRC_LINEAR)) {
1096 dstDepth = Integer16BitsColorDepthID.id();
1097
1098 needColorTransform = true;
1099
1100 if (options.downsample) {
1101 dstDepth = Integer8BitsColorDepthID.id();
1102 }
1103 }
1104
1105 if (needColorTransform) {
1106 const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(dstModel, dstDepth, dstProfile);
1107
1108 if (!dstCs) {
1110 }
1111
1112 device = new KisPaintDevice(*device);
1113 device->convertTo(dstCs);
1114 isFloatingPoint = device->colorSpace()->colorDepthId() == Float16BitsColorDepthID
1117 }
1118
1120 options.tryToSaveAsIndexed = false;
1121 }
1122
1123 // Initialize structures
1124 png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
1125 if (!png_ptr) {
1127 }
1128
1129#ifdef PNG_SET_USER_LIMITS_SUPPORTED
1130 /* Remove the user limits, if any */
1131 png_set_user_limits(png_ptr, 0x7fffffff, 0x7fffffff);
1132 png_set_chunk_cache_max(png_ptr, 0);
1133 png_set_chunk_malloc_max(png_ptr, 0);
1134#endif
1135
1136 png_set_error_fn(png_ptr, nullptr, nullptr, kis_png_warning);
1137 #ifdef PNG_BENIGN_ERRORS_SUPPORTED
1138 png_set_benign_errors(png_ptr, 1);
1139 #endif
1140
1141#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
1142 png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
1143#endif
1144
1145
1146#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
1147 png_set_check_for_invalid_index(png_ptr, 0);
1148#endif
1149
1150 png_infop info_ptr = png_create_info_struct(png_ptr);
1151 if (!info_ptr) {
1152 png_destroy_write_struct(&png_ptr, (png_infopp)0);
1154 }
1155
1156 // If an error occurs during writing, libpng will jump here
1157 if (setjmp(png_jmpbuf(png_ptr))) {
1158 png_destroy_write_struct(&png_ptr, &info_ptr);
1160 }
1161 // Initialize the writing
1162 // png_init_io(png_ptr, fp);
1163 // Setup the progress function
1164 // XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);"
1165 // setProgressTotalSteps(100/*height*/);
1166
1167 /* set the zlib compression level */
1168 png_set_compression_level(png_ptr, options.compression);
1169
1170 png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn);
1171
1172 /* set other zlib parameters */
1173 png_set_compression_mem_level(png_ptr, 8);
1174 png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY);
1175 png_set_compression_window_bits(png_ptr, 15);
1176 png_set_compression_method(png_ptr, 8);
1177 png_set_compression_buffer_size(png_ptr, 8192);
1178
1179 int color_nb_bits = isFloatingPoint? options.downsample? 8: 16: 8 * device->pixelSize() / device->channelCount();
1180 int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha);
1181
1182 Q_ASSERT(color_type > -1);
1183
1184 // Try to compute a table of color if the colorspace is RGB8f
1185 QScopedArrayPointer<png_color> palette;
1186 int num_palette = 0;
1187 if (!options.alpha && options.tryToSaveAsIndexed && KoID(device->colorSpace()->id()) == KoID("RGBA")) { // png doesn't handle indexed images and alpha, and only have indexed for RGB8
1188 palette.reset(new png_color[255]);
1189
1190 KisSequentialIterator it(device, imageRect);
1191
1192 bool toomuchcolor = false;
1193 while (it.nextPixel()) {
1194 const quint8* c = it.oldRawData();
1195 bool findit = false;
1196 for (int i = 0; i < num_palette; i++) {
1197 if (palette[i].red == c[2] &&
1198 palette[i].green == c[1] &&
1199 palette[i].blue == c[0]) {
1200 findit = true;
1201 break;
1202 }
1203 }
1204 if (!findit) {
1205 if (num_palette == 255) {
1206 toomuchcolor = true;
1207 break;
1208 }
1209 palette[num_palette].red = c[2];
1210 palette[num_palette].green = c[1];
1211 palette[num_palette].blue = c[0];
1212 num_palette++;
1213 }
1214 }
1215
1216 if (!toomuchcolor) {
1217 dbgFile << "Found a palette of " << num_palette << " colors";
1218 color_type = PNG_COLOR_TYPE_PALETTE;
1219 if (num_palette <= 2) {
1220 color_nb_bits = 1;
1221 } else if (num_palette <= 4) {
1222 color_nb_bits = 2;
1223 } else if (num_palette <= 16) {
1224 color_nb_bits = 4;
1225 } else {
1226 color_nb_bits = 8;
1227 }
1228 } else {
1229 palette.reset();
1230 }
1231 }
1232
1233 int interlace_type = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
1234
1236
1237 png_set_IHDR(png_ptr, info_ptr,
1238 imageRect.width(),
1239 imageRect.height(),
1240 color_nb_bits,
1241 color_type, interlace_type,
1242 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1243
1244 // set sRGB only if the profile is sRGB -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present
1245
1246 const bool sRGB = *device->colorSpace()->profile() == *KoColorSpaceRegistry::instance()->p709SRGBProfile();
1247 const bool colorProfilePQ = (device->colorSpace()->profile()->getTransferCharacteristics() == TRC_ITU_R_BT_2100_0_PQ
1249 /*
1250 * This automatically writes the correct gamma and chroma chunks along with the sRGB chunk, but firefox's
1251 * color management is bugged, so once you give it any incentive to start color managing an sRGB image it
1252 * will turn, for example, a nice desaturated rusty red into bright poppy red. So this is disabled for now.
1253 */
1254 if (options.storeExtraColorChunks && options.storeColorSpaceInfo && sRGB) {
1255 png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);
1256 }
1257
1258
1268 if (options.storeExtraColorChunks && options.storeColorSpaceInfo && !sRGB) {
1269 // https://www.w3.org/TR/PNG/#11gAMA
1270#if defined(PNG_GAMMA_SUPPORTED)
1271
1272
1273 if (colorProfilePQ) {
1274 // the values are set in accordance of HDR-PNG standard:
1275 // https://www.w3.org/TR/png-hdr-pq/
1276 png_set_gAMA_fixed(png_ptr, info_ptr, 15000);
1277 dbgFile << "gAMA" << "(Rec 2100)";
1278 } else {
1279 double gamma = device->colorSpace()->profile()->getEstimatedTRC().first();
1280 png_set_gAMA(png_ptr, info_ptr, gamma);
1281 }
1282#endif
1283
1284#if defined PNG_cHRM_SUPPORTED
1285 if (colorProfilePQ) {
1286 png_set_cHRM_fixed(png_ptr, info_ptr,
1287 31270, 32900, // white point
1288 70800, 29200, // red
1289 17000, 79700, // green
1290 13100, 4600 // blue
1291 );
1292 dbgFile << "cHRM" << "(Rec 2100)";
1293 } else {
1294 const QVector<KoColorimetryUtils::xyY> colorants = device->colorSpace()->profile()->getColorantsxyY();
1295 const KoColorimetryUtils::xyY whitePoint = device->colorSpace()->profile()->getWhitePointxyY();
1296 png_set_cHRM(png_ptr, info_ptr,
1297 whitePoint.x, whitePoint.y,
1298 colorants[0].x, colorants[0].y,
1299 colorants[1].x, colorants[1].y,
1300 colorants[2].x, colorants[2].y);
1301 }
1302#endif
1303 }
1304
1305
1306 // we should ensure we don't access non-existing palette object
1307 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(palette || color_type != PNG_COLOR_TYPE_PALETTE, ImportExportCodes::Failure);
1308
1309 // set the palette
1310 if (color_type == PNG_COLOR_TYPE_PALETTE) {
1311 png_set_PLTE(png_ptr, info_ptr, palette.data(), num_palette);
1312 }
1313 // Save annotation
1314 vKisAnnotationSP_it it = annotationsStart;
1315 while (it != annotationsEnd) {
1316 if (!(*it) || (*it)->type().isEmpty()) {
1317 dbgFile << "Warning: empty annotation";
1318 it++;
1319 continue;
1320 }
1321
1322 dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size();
1323
1324 if ((*it) -> type().startsWith(QString("krita_attribute:"))) { //
1325 // Attribute
1326 // XXX: it should be possible to save krita_attributes in the \"CHUNKs\""
1327 dbgFile << "cannot save this annotation : " << (*it) -> type();
1328 } else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) {
1329 dbgFile << "Saving preset information " << (*it)->description();
1330 png_textp text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text));
1331
1332 QByteArray keyData = (*it)->description().toLatin1();
1333 text[0].key = keyData.data();
1334 text[0].text = (char*)(*it)->annotation().data();
1335 text[0].text_length = (*it)->annotation().size();
1336 text[0].compression = -1;
1337
1338 png_set_text(png_ptr, info_ptr, text, 1);
1339 png_free(png_ptr, text);
1340 }
1341 it++;
1342 }
1343
1344 // Save the color profile
1345 const KoColorProfile* colorProfile = device->colorSpace()->profile();
1346 QByteArray colorProfileData = colorProfile->rawData();
1347
1348 if (options.storeColorSpaceInfo && !(sRGB && options.storeExtraColorChunks)) {
1349
1350
1351 const bool cicpPossible = (colorProfile->getColorPrimaries() != PRIMARIES_UNSPECIFIED && colorProfile->getTransferCharacteristics() != TRC_UNSPECIFIED
1352 && colorProfile->getColorPrimaries() < 256 && colorProfile->getTransferCharacteristics() < 256);
1353 dbgFile << options.writeCicpIfPossible << cicpPossible << colorProfilePQ << colorProfile->name();
1354 bool wroteCICP = false;
1355 if (options.writeCicpIfPossible && (cicpPossible || colorProfilePQ)) {
1356 png_byte primaries = colorProfile->getColorPrimaries();
1357 png_byte transfer = colorProfile->getTransferCharacteristics();
1358 if (colorProfilePQ) {
1359 transfer = TRC_ITU_R_BT_2100_0_PQ;
1361 }
1363 transfer = TRC_ITU_R_BT_2100_0_PQ;
1365 transfer = TRC_ITU_R_BT_2100_0_HLG;
1367 transfer = TRC_SMPTE_ST_428_1;
1368 }
1369 png_byte matrixCoef = 0;
1370 png_byte fullRange = 1;
1371 dbgFile << "Writing cicp" << primaries << transfer << matrixCoef << fullRange;
1372 png_set_cICP(png_ptr, info_ptr, primaries, transfer, matrixCoef, fullRange);
1373 wroteCICP = true;
1374 }
1375 if (!wroteCICP) {
1376#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
1377 const char *typeString = "icc";
1378 png_set_iCCP(png_ptr, info_ptr, (png_const_charp)typeString, PNG_COMPRESSION_TYPE_BASE, (png_const_bytep)colorProfileData.constData(), colorProfileData . size());
1379#else
1380 // older version of libpng has a problem with constness on the parameters
1381 char typeStringICC[] = "icc";
1382 char *typeString = typeStringICC;
1383 png_set_iCCP(png_ptr, info_ptr, typeString, PNG_COMPRESSION_TYPE_BASE, colorProfileData.data(), colorProfileData . size());
1384#endif
1385 }
1386 }
1387#if defined(PNG_cLLI_SUPPORTED)
1388 if (colorProfilePQ && m_doc->image().toStrongRef()->relativeContentLightLevelInformation()) {
1389 const std::optional<KisRelativeContentLightLevelInformation> clli = m_doc->image().toStrongRef()->relativeContentLightLevelInformation();
1390 const double hdrRefWhite = colorProfile->hdrReferenceWhite().value_or(203.0);
1391 const double maxcll = clli->maxContentLightLevel * hdrRefWhite;
1392 const double maxfall = clli->maxFrameAverageLightLevel * hdrRefWhite;
1393 png_set_cLLI(png_ptr, info_ptr, maxcll, maxfall);
1394 }
1395#endif
1396#if defined(PNG_mDCV_SUPPORTED)
1397 if (colorProfilePQ && m_doc->image().toStrongRef()->colorVolumeInformation()) {
1398 const std::optional<KisColorVolumeInformation> cvi = m_doc->image().toStrongRef()->colorVolumeInformation();
1399 png_set_mDCV(png_ptr, info_ptr,
1400 cvi->white.x, cvi->white.y,
1401 cvi->red.x, cvi->red.y,
1402 cvi->green.x,cvi->green.y,
1403 cvi->blue.x, cvi->blue.y, cvi->maxLuminance, cvi->minLuminance);
1404 }
1405#endif
1406
1407 // save comments from the document information
1408 // warning: according to the official png spec, the keys need to be capitalized!
1409 if (m_doc) {
1410 png_text texts[4];
1411 int nbtexts = 0;
1412 KoDocumentInfo * info = m_doc->documentInfo();
1413 QString title = info->aboutInfo("title");
1414 if (!title.isEmpty() && options.storeMetaData) {
1415 fillText(texts + nbtexts, "Title", title);
1416 nbtexts++;
1417 }
1418 QString abstract = info->aboutInfo("subject");
1419 if (abstract.isEmpty()) {
1420 abstract = info->aboutInfo("abstract");
1421 }
1422 if (!abstract.isEmpty() && options.storeMetaData) {
1423 QString keywords = info->aboutInfo("keyword");
1424 if (!keywords.isEmpty()) {
1425 abstract = abstract + " keywords: " + keywords;
1426 }
1427 fillText(texts + nbtexts, "Description", abstract);
1428 nbtexts++;
1429 }
1430
1431 QString license = info->aboutInfo("license");
1432 if (!license.isEmpty() && options.storeMetaData) {
1433 fillText(texts + nbtexts, "Copyright", license);
1434 nbtexts++;
1435 }
1436
1437 QString author = info->authorInfo("creator");
1438 if (!author.isEmpty() && options.storeAuthor) {
1439 if (!info->authorContactInfo().isEmpty()) {
1440 QString contact = info->authorContactInfo().at(0);
1441 if (!contact.isEmpty()) {
1442 author = author+"("+contact+")";
1443 }
1444 }
1445 fillText(texts + nbtexts, "Author", author);
1446 nbtexts++;
1447 }
1448
1449 png_set_text(png_ptr, info_ptr, texts, nbtexts);
1450 }
1451
1452 // Save metadata following imagemagick way
1453
1454 // Save exif
1455 if (metaData && !metaData->empty()) {
1456 if (options.exif) {
1457 dbgFile << "Trying to save exif information";
1458
1460 Q_ASSERT(exifIO);
1461
1462 QBuffer buffer;
1463 exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
1464 writeRawProfile(png_ptr, info_ptr, "exif", buffer.data());
1465 }
1466 // Save IPTC
1467 if (options.iptc) {
1468 dbgFile << "Trying to save iptc information";
1470 Q_ASSERT(iptcIO);
1471
1472 QBuffer buffer;
1473 iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
1474
1475 dbgFile << "IPTC information size is" << buffer.data().size();
1476 writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data());
1477 }
1478 // Save XMP
1479 if (options.xmp) {
1480 dbgFile << "Trying to save XMP information";
1482 Q_ASSERT(xmpIO);
1483
1484 QBuffer buffer;
1485 xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::NoHeader);
1486
1487 dbgFile << "XMP information size is" << buffer.data().size();
1488 writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data());
1489 }
1490 }
1491#if 0 // Unimplemented?
1492 // Save resolution
1493 int unit_type;
1494 png_uint_32 x_resolution, y_resolution;
1495#endif
1496 png_set_pHYs(png_ptr, info_ptr, CM_TO_POINT(xRes) * 100.0, CM_TO_POINT(yRes) * 100.0, PNG_RESOLUTION_METER); // It is the "invert" macro because we convert from point-per-inch to points
1497
1498 // Save the information to the file
1499 png_write_info(png_ptr, info_ptr);
1500 png_write_flush(png_ptr);
1501
1502 // swap byteorder on little endian machines.
1503#ifndef WORDS_BIGENDIAN
1504 if (color_nb_bits > 8)
1505 png_set_swap(png_ptr);
1506#endif
1507
1508 // Write the PNG
1509 // png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0);
1510
1511 struct RowPointersStruct {
1512 RowPointersStruct(const QSize &size, int pixelSize)
1513 : numRows(size.height())
1514 {
1515 rows = new png_byte*[numRows];
1516
1517 for (int i = 0; i < numRows; i++) {
1518 rows[i] = new png_byte[size.width() * pixelSize];
1519 }
1520 }
1521
1522 ~RowPointersStruct() {
1523 for (int i = 0; i < numRows; i++) {
1524 delete[] rows[i];
1525 }
1526 delete[] rows;
1527 }
1528
1529 const int numRows = 0;
1530 png_byte** rows = 0;
1531 };
1532
1533
1534 // Fill the data structure
1535 RowPointersStruct rowPointers(imageRect.size(), device->pixelSize());
1536
1537 int row = 0;
1538 for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) {
1539 KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width());
1540
1541 switch (color_type) {
1542 case PNG_COLOR_TYPE_GRAY:
1543 case PNG_COLOR_TYPE_GRAY_ALPHA:
1544 if (color_nb_bits == 16) {
1545 quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
1546 do {
1547 const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
1548 *(dst++) = d[0];
1549 if (options.alpha) *(dst++) = d[1];
1550 } while (it->nextPixel());
1551 } else {
1552 quint8 *dst = rowPointers.rows[row];
1553 do {
1554 const quint8 *d = it->oldRawData();
1555 *(dst++) = d[0];
1556 if (options.alpha) *(dst++) = d[1];
1557 } while (it->nextPixel());
1558 }
1559 break;
1560 case PNG_COLOR_TYPE_RGB:
1561 case PNG_COLOR_TYPE_RGB_ALPHA:
1562 if (isFloatingPoint) {
1563 if (color_nb_bits == 16) {
1564 if (device->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
1565 auto policyApplicator = ConversionPolicyApplicator<float, quint16>();
1566 quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
1567 do {
1568 const float *d = reinterpret_cast<const float *>(it->oldRawData());
1569 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[0]);
1570 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[1]);
1571 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[2]);
1572 if (options.alpha) *(dst++) = policyApplicator.apply(ConversionPolicy::KeepTheSame, d[3]);
1573 } while (it->nextPixel());
1574 }
1575#ifdef HAVE_OPENEXR
1576 else if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
1577 auto policyApplicator = ConversionPolicyApplicator<half, quint16>();
1578 quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
1579 do {
1580 const half *d = reinterpret_cast<const half *>(it->oldRawData());
1581 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[0]);
1582 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[1]);
1583 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[2]);
1584 if (options.alpha) *(dst++) = policyApplicator.apply(ConversionPolicy::KeepTheSame, d[3]);
1585 } while (it->nextPixel());
1586 }
1587#endif
1588 // forcing to 8bit.
1589 } else {
1590 if (device->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
1591 auto policyApplicator = ConversionPolicyApplicator<float, quint8>();
1592 quint8 *dst = reinterpret_cast<quint8 *>(rowPointers.rows[row]);
1593 do {
1594 const float *d = reinterpret_cast<const float *>(it->oldRawData());
1595 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[0]);
1596 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[1]);
1597 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[2]);
1598 if (options.alpha) *(dst++) = policyApplicator.apply(ConversionPolicy::KeepTheSame, d[3]);
1599 } while (it->nextPixel());
1600 }
1601#ifdef HAVE_OPENEXR
1602 else if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
1603 auto policyApplicator = ConversionPolicyApplicator<half, quint8>();
1604 quint8 *dst = reinterpret_cast<quint8 *>(rowPointers.rows[row]);
1605 do {
1606 const half *d = reinterpret_cast<const half *>(it->oldRawData());
1607 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[0]);
1608 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[1]);
1609 *(dst++) = policyApplicator.apply(options.floatingPointConversion, d[2]);
1610 if (options.alpha) *(dst++) = policyApplicator.apply(ConversionPolicy::KeepTheSame, d[3]);
1611 } while (it->nextPixel());
1612 }
1613#endif
1614 }
1615 } else {
1616 if (color_nb_bits == 16) {
1617 quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
1618 do {
1619 const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
1620 *(dst++) = d[2];
1621 *(dst++) = d[1];
1622 *(dst++) = d[0];
1623 if (options.alpha) *(dst++) = d[3];
1624 } while (it->nextPixel());
1625 } else {
1626 quint8 *dst = rowPointers.rows[row];
1627 do {
1628 const quint8 *d = it->oldRawData();
1629 *(dst++) = d[2];
1630 *(dst++) = d[1];
1631 *(dst++) = d[0];
1632 if (options.alpha) *(dst++) = d[3];
1633 } while (it->nextPixel());
1634 }
1635 }
1636 break;
1637 case PNG_COLOR_TYPE_PALETTE: {
1638 quint8 *dst = rowPointers.rows[row];
1639 KisPNGWriteStream writestream(dst, color_nb_bits);
1640 do {
1641 const quint8 *d = it->oldRawData();
1642 int i;
1643 for (i = 0; i < num_palette; i++) {
1644 if (palette[i].red == d[2] &&
1645 palette[i].green == d[1] &&
1646 palette[i].blue == d[0]) {
1647 break;
1648 }
1649 }
1650 writestream.setNextValue(i);
1651 } while (it->nextPixel());
1652 }
1653 break;
1654 default:
1656 }
1657 }
1658
1659 png_write_image(png_ptr, rowPointers.rows);
1660
1661 // Writing is over
1662 png_write_end(png_ptr, info_ptr);
1663
1664 // Free memory
1665 png_destroy_write_struct(&png_ptr, &info_ptr);
1666 return ImportExportCodes::OK;
1667}
1668
1669
1671{
1672 m_stop = true;
1673}
1674
1675void KisPNGConverter::progress(png_structp png_ptr, png_uint_32 row_number, int pass)
1676{
1677 if (png_ptr == 0 || row_number > PNG_MAX_UINT || pass > 7) return;
1678 // setProgress(row_number);
1679}
1680
1682{
1683 return colorSpaceIdSupported(cs->id());
1684}
1685
1686
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
float value(const T *src, size_t ch)
qreal v
QList< QString > QStringList
#define ALWAYS_INLINE
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID Float64BitsColorDepthID("F64", ki18n("64-bit float/channel"))
const KoID GrayAColorModelID("GRAYA", ki18n("Grayscale/Alpha"))
const KoID Float16BitsColorDepthID("F16", ki18n("16-bit float/channel"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
ColorPrimaries
The colorPrimaries enum Enum of colorants, follows ITU H.273 for values 0 to 255, and has extra known...
@ PRIMARIES_ITU_R_BT_2020_2_AND_2100_0
@ PRIMARIES_UNSPECIFIED
TransferCharacteristics
The transferCharacteristics enum Enum of transfer characteristics, follows ITU H.273 for values 0 to ...
@ TRC_ITU_R_BT_2100_0_HLG
@ TRC_ITU_R_BT_2100_0_PQ
@ TRC_SMPTE_ST_428_1
ALWAYS_INLINE float removeHLGCurve(float x) noexcept
ALWAYS_INLINE float applySmpte2048Curve(float x, float refWhite=203.0) noexcept
ALWAYS_INLINE float removeSMPTE_ST_428Curve(float x) noexcept
ALWAYS_INLINE float applySMPTE_ST_428Curve(float x) noexcept
ALWAYS_INLINE float removeSmpte2048Curve(float x, float refWhite=203.0) noexcept
ALWAYS_INLINE float applyHLGCurve(float x) noexcept
unsigned int uint
constexpr qreal POINT_TO_CM(qreal px)
Definition KoUnit.h:33
constexpr qreal CM_TO_POINT(qreal cm)
Definition KoUnit.h:34
A data extension mechanism for Krita.
virtual const quint8 * oldRawData() const =0
virtual bool nextPixel()=0
qint32 pasteBehaviour(bool defaultValue=false) const
The KisCursorOverrideHijacker class stores all override cursors in a stack, and resets them back afte...
QString profile() const
KisUndoStore * createUndoStore()
KoDocumentInfo * documentInfo() const
KisImageSP image
std::optional< KisRelativeContentLightLevelInformation > relativeContentLightLevelInformation() const
relativeContentLightLevelInformation This returns (optionally) a KisRelativeContentLightLevelInformat...
void setColorVolumeInformation(const std::optional< KisColorVolumeInformation > cvi)
Set the color volume information.
void addAnnotation(KisAnnotationSP annotation)
KisGroupLayerSP rootLayer() const
std::optional< KisColorVolumeInformation > colorVolumeInformation() const
colorVolumeInformation
void setRelativeContentLightLevelInformation(const std::optional< KisRelativeContentLightLevelInformation > clli)
void setResolution(double xres, double yres)
@ JpegHeader
Append Jpeg-style header.
@ NoHeader
Don't append any header.
virtual bool loadFrom(Store *store, QIODevice *ioDevice) const =0
virtual bool saveTo(const Store *store, QIODevice *ioDevice, HeaderType headerType=NoHeader) const =0
static KisMetadataBackendRegistry * instance()
void progress(png_structp png_ptr, png_uint_32 row_number, int pass)
static bool isColorSpaceSupported(const KoColorSpace *cs)
static bool saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store *metaData=0)
saveDeviceToStore saves the given paint device to the KoStore. If the device is not 8 bits sRGB,...
KisPNGConverter(KisDocument *doc, bool batchMode=false)
KisImportExportErrorCode buildImage(const QString &filename)
KisDocument * m_doc
KisImportExportErrorCode buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store *metaData)
KisPNGReadStream(quint8 *buf, quint32 depth)
KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height)
virtual png_bytep readLine()=0
png_bytep readLine() override
KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height)
KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height)
png_bytep readLine() override
KisPNGWriteStream(quint8 *buf, quint32 depth)
quint32 pixelSize() const
quint32 channelCount() const
const KoColorSpace * colorSpace() const
void convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags(), KUndo2Command *parentCommand=nullptr, KoUpdater *progressUpdater=nullptr)
KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
ALWAYS_INLINE const quint8 * oldRawData() const
static _Tdst scaleToA(_T a)
virtual KoID colorModelId() const =0
virtual KoID colorDepthId() const =0
virtual const KoColorProfile * profile() const =0
virtual KoColorConversionTransformation * createColorConverter(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
The class containing all meta information about a document.
void setAboutInfo(const QString &info, const QString &data)
QStringList authorContactInfo() const
authorContactInfo
QString authorInfo(const QString &info) const
void setAuthorInfo(const QString &info, const QString &data)
QString aboutInfo(const QString &info) const
const T value(const QString &id) const
Definition KoID.h:30
QString id() const
Definition KoID.cpp:63
void close() override
bool open(OpenMode m) override
bool close()
Definition KoStore.cpp:156
bool open(const QString &name)
Definition KoStore.cpp:109
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define warnFile
Definition kis_debug.h:99
#define dbgFile
Definition kis_debug.h:56
const quint16 quint16_MAX
Definition kis_global.h:25
const quint8 quint8_MAX
Definition kis_global.h:24
static void _flush_fn(png_structp png_ptr)
static void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
static void kis_png_warning(png_structp, png_const_charp message)
static void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
#define PNG_MAX_UINT
QString getColorSpaceForColorType(uint16_t sampletype, uint16_t color_type, uint16_t color_nb_bits, TIFF *image, uint16_t &nbchannels, uint16_t &extrasamplescount, uint8_t &destDepth)
vKisAnnotationSP::iterator vKisAnnotationSP_it
Definition kis_types.h:181
static ALWAYS_INLINE dst_channel_type remove(ConversionPolicy linearizePolicy, src_channel_type value, float refWhite=203.0)
static ALWAYS_INLINE dst_channel_type apply(ConversionPolicy linearizePolicy, src_channel_type value, float refWhite=203.0)
The KisColorVolumeInformation class is a struct that represents the 'mastering' display....
double maxLuminance
Maximum screen brightness in cd/m²
KoColorimetryUtils::xy blue
xyY location of the blue colorant.
double minLuminance
Minimum screen brightness in cd/m²
KoColorimetryUtils::xy white
xyY location of the whitepoint.
KoColorimetryUtils::xy red
xyY location of the red colorant.
KoColorimetryUtils::xy green
xyY location of the green colorant.
KisMetaData::Store * metaData()
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
ConversionPolicy floatingPointConversion
QColor transparencyFillColor
double maxFrameAverageLightLevel
maxFrameAverage MaxFrameAverageLightLevel or MaxFALL is the average of average pixel brightnesses in ...
void transformInPlace(const quint8 *src, quint8 *dst, qint32 nPixels) const
The KoColorProfileQuery struct.
virtual bool isSuitableForOutput() const =0
virtual std::optional< double > hdrReferenceWhite() const =0
hdrReferenceWhite HDR reference white is only available for Perceptual Quantizer profiles that save t...
virtual bool isSuitableForWorkspace() const =0
virtual QByteArray rawData() const
virtual KoColorimetryUtils::xyY getWhitePointxyY() const =0
virtual ColorPrimaries getColorPrimaries() const
getColorPrimaries
virtual QVector< qreal > getEstimatedTRC() const =0
virtual bool isSuitableForInput() const =0
virtual QVector< KoColorimetryUtils::xyY > getColorantsxyY() const =0
virtual TransferCharacteristics getTransferCharacteristics() const
getTransferCharacteristics This function should be subclassed at some point so we can get the value f...
const KoColorProfile * profileByName(const QString &name) const
QString colorSpaceId(const QString &colorModelId, const QString &colorDepthId) const
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorProfile * profileFor(const KoColorProfileQuery &query, const bool generate=true) const
profileFor tries to find the profile that matches these characteristics, if no such profile is found,...
const KoColorProfile * p709SRGBProfile() const
const KoColorProfile * p2020PQProfile() const
const KoColorProfile * p2020G10Profile() const
const KoColorProfile * createColorProfile(const QString &colorModelId, const QString &colorDepthId, const QByteArray &rawData)