File indexing completed on 2024-05-12 16:01:40

0001 /*
0002  *  SPDX-FileCopyrightText: 2005-2007 Cyrille Berger <cberger@cberger.net>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  *
0006  */
0007 
0008 #include "kis_png_converter.h"
0009 // A big thank to Glenn Randers-Pehrson for his wonderful
0010 // documentation of libpng available at
0011 // http://www.libpng.org/pub/png/libpng-1.2.5-manual.html
0012 
0013 #ifndef PNG_MAX_UINT // Removed in libpng 1.4
0014 #define PNG_MAX_UINT PNG_UINT_31_MAX
0015 #endif
0016 
0017 #include <KoConfig.h> // WORDS_BIGENDIAN
0018 #include <KoStore.h>
0019 #include <KoStoreDevice.h>
0020 
0021 #include <limits.h>
0022 #include <stdio.h>
0023 #include <zlib.h>
0024 
0025 #include <QBuffer>
0026 #include <QFile>
0027 #include <QApplication>
0028 
0029 #include <klocalizedstring.h>
0030 #include <QUrl>
0031 
0032 #include <KoColorSpace.h>
0033 #include <KoDocumentInfo.h>
0034 #include <KoID.h>
0035 #include <KoColorSpaceRegistry.h>
0036 #include <KoColorProfile.h>
0037 #include <KoColor.h>
0038 #include <KoUnit.h>
0039 
0040 #include "dialogs/kis_dlg_png_import.h"
0041 #include "kis_clipboard.h"
0042 #include "kis_undo_stores.h"
0043 #include <KisDocument.h>
0044 #include <KoColorModelStandardIds.h>
0045 #include <kis_config.h>
0046 #include <kis_cursor_override_hijacker.h>
0047 #include <kis_group_layer.h>
0048 #include <kis_image.h>
0049 #include <kis_iterator_ng.h>
0050 #include <kis_layer.h>
0051 #include <kis_meta_data_backend_registry.h>
0052 #include <kis_meta_data_store.h>
0053 #include <kis_paint_device.h>
0054 #include <kis_paint_layer.h>
0055 #include <kis_painter.h>
0056 #include <kis_transaction.h>
0057 
0058 #include <kis_assert.h>
0059 
0060 namespace
0061 {
0062 
0063 int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha)
0064 {
0065 
0066     QString id = cs->id();
0067 
0068     if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") {
0069         return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY;
0070     }
0071     if (id == "RGBA" || id == "RGBA16") {
0072         return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB;
0073     }
0074 
0075     return -1;
0076 
0077 }
0078 
0079 bool colorSpaceIdSupported(const QString &id)
0080 {
0081     return id == "RGBA" || id == "RGBA16" ||
0082         id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16";
0083 }
0084 
0085 QPair<QString, QString> getColorSpaceForColorType(int color_type, int color_nb_bits)
0086 {
0087     QPair<QString, QString> r;
0088 
0089     if (color_type ==  PNG_COLOR_TYPE_PALETTE) {
0090         r.first = RGBAColorModelID.id();
0091         r.second = Integer8BitsColorDepthID.id();
0092     } else {
0093         if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
0094             r.first = GrayAColorModelID.id();
0095         } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) {
0096             r.first = RGBAColorModelID.id();
0097         }
0098         if (color_nb_bits == 16) {
0099             r.second = Integer16BitsColorDepthID.id();
0100         } else if (color_nb_bits <= 8) {
0101             r.second = Integer8BitsColorDepthID.id();
0102         }
0103     }
0104     return r;
0105 }
0106 
0107 
0108 void fillText(png_text* p_text, const char* key, QString& text)
0109 {
0110     p_text->compression = PNG_TEXT_COMPRESSION_zTXt;
0111     p_text->key = const_cast<char *>(key);
0112     char* textc = new char[text.length()+1];
0113     strcpy(textc, text.toLatin1());
0114     p_text->text = textc;
0115     p_text->text_length = text.length() + 1;
0116 }
0117 
0118 long formatStringList(char *string, const size_t length, const char *format, va_list operands)
0119 {
0120     int n = vsnprintf(string, length, format, operands);
0121 
0122     if (n < 0)
0123         string[length-1] = '\0';
0124 
0125     return((long) n);
0126 }
0127 
0128 long formatString(char *string, const size_t length, const char *format, ...)
0129 {
0130     long n;
0131 
0132     va_list operands;
0133 
0134     va_start(operands, format);
0135     n = (long) formatStringList(string, length, format, operands);
0136     va_end(operands);
0137     return(n);
0138 }
0139 
0140 void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data)
0141 {
0142 
0143     png_textp      text;
0144 
0145     png_uint_32    allocated_length, description_length;
0146 
0147     const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
0148 
0149     dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << endl;
0150 
0151     text               = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text));
0152     description_length = profile_type.length();
0153     allocated_length   = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length);
0154 
0155     text[0].text   = (png_charp) png_malloc(ping, allocated_length);
0156     memset(text[0].text, 0, allocated_length);
0157 
0158     QString key = QLatin1Literal("Raw profile type ") + profile_type.toLatin1();
0159     QByteArray keyData = key.toLatin1();
0160     text[0].key = keyData.data();
0161 
0162     uchar* sp = (uchar*)profile_data.data();
0163     png_charp dp = text[0].text;
0164     *dp++ = '\n';
0165 
0166     memcpy(dp, profile_type.toLatin1().constData(), profile_type.length());
0167 
0168     dp += description_length;
0169     *dp++ = '\n';
0170 
0171     formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", (unsigned long)profile_data.length());
0172 
0173     dp += 8;
0174 
0175     for (long i = 0; i < (long) profile_data.length(); i++) {
0176         if (i % 36 == 0)
0177             *dp++ = '\n';
0178 
0179         *(dp++) = (char) hex[((*sp >> 4) & 0x0f)];
0180         *(dp++) = (char) hex[((*sp++) & 0x0f)];
0181     }
0182 
0183     *dp++ = '\n';
0184     *dp = '\0';
0185     text[0].text_length = (png_size_t)(dp - text[0].text);
0186     text[0].compression = -1;
0187 
0188     if (text[0].text_length <= allocated_length)
0189         png_set_text(ping, ping_info, text, 1);
0190 
0191     png_free(ping, text[0].text);
0192     png_free(ping, text);
0193 }
0194 
0195 QByteArray png_read_raw_profile(png_textp text)
0196 {
0197     QByteArray profile;
0198 
0199     static const unsigned char unhex[103] = {
0200         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0201         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0202         0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0,
0203         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0204         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12,
0205         13, 14, 15
0206     };
0207 
0208     png_charp sp = text[0].text + 1;
0209     /* look for newline */
0210     while (*sp != '\n')
0211         sp++;
0212     /* look for length */
0213     while (*sp == '\0' || *sp == ' ' || *sp == '\n')
0214         sp++;
0215     png_uint_32 length = (png_uint_32) atol(sp);
0216     while (*sp != ' ' && *sp != '\n')
0217         sp++;
0218     if (length == 0) {
0219         return profile;
0220     }
0221     profile.resize(length);
0222     /* copy profile, skipping white space and column 1 "=" signs */
0223     unsigned char *dp = (unsigned char*)profile.data();
0224     png_uint_32 nibbles = length * 2;
0225     for (png_uint_32 i = 0; i < nibbles; i++) {
0226         while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') {
0227             if (*sp == '\0') {
0228                 return QByteArray();
0229             }
0230             sp++;
0231         }
0232         if (i % 2 == 0)
0233             *dp = (unsigned char)(16 * unhex[(int) *sp++]);
0234         else
0235             (*dp++) += unhex[(int) *sp++];
0236     }
0237     return profile;
0238 }
0239 
0240 void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize)
0241 {
0242     dbgFile << "Decoding " << type << " " << text[0].key;
0243     KisMetaData::IOBackend *exifIO = KisMetadataBackendRegistry::instance()->value(type);
0244     Q_ASSERT(exifIO);
0245 
0246     QByteArray rawProfile = png_read_raw_profile(text);
0247     if (headerSize > 0) {
0248         rawProfile.remove(0, headerSize);
0249     }
0250     if (rawProfile.size() > 0) {
0251         QBuffer buffer;
0252         buffer.setData(rawProfile);
0253         exifIO->loadFrom(store, &buffer);
0254     } else {
0255         dbgFile << "Decoding failed";
0256     }
0257 }
0258 }
0259 
0260 extern "C" {
0261 static void kis_png_warning(png_structp /*png_ptr*/, png_const_charp message)
0262 {
0263     qWarning("libpng warning: %s", message);
0264 }
0265 
0266 }
0267 
0268 
0269 
0270 
0271 KisPNGConverter::KisPNGConverter(KisDocument *doc, bool batchMode)
0272 {
0273     //     Q_ASSERT(doc);
0274     //     Q_ASSERT(adapter);
0275 
0276     m_doc = doc;
0277     m_stop = false;
0278     m_max_row = 0;
0279     m_image = 0;
0280     m_batchMode = batchMode;
0281 }
0282 
0283 KisPNGConverter::~KisPNGConverter()
0284 {
0285 }
0286 
0287 class KisPNGReadStream
0288 {
0289 public:
0290     KisPNGReadStream(quint8* buf,  quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
0291     }
0292     int nextValue() {
0293         if (m_posinc == 0) {
0294             m_posinc = 8;
0295             m_buf++;
0296         }
0297         m_posinc -= m_depth;
0298         return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1));
0299     }
0300 private:
0301     quint32 m_posinc, m_depth;
0302     quint8* m_buf;
0303 };
0304 
0305 class KisPNGWriteStream
0306 {
0307 public:
0308     KisPNGWriteStream(quint8* buf,  quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
0309         *m_buf = 0;
0310     }
0311     void setNextValue(int v) {
0312         if (m_posinc == 0) {
0313             m_posinc = 8;
0314             m_buf++;
0315             *m_buf = 0;
0316         }
0317         m_posinc -= m_depth;
0318         *m_buf = (v << m_posinc) | *m_buf;
0319     }
0320 private:
0321     quint32 m_posinc, m_depth;
0322     quint8* m_buf;
0323 };
0324 
0325 class KisPNGReaderAbstract
0326 {
0327 public:
0328     KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {}
0329     virtual ~KisPNGReaderAbstract() {}
0330     virtual png_bytep readLine() = 0;
0331 protected:
0332     png_structp png_ptr;
0333     int width, height;
0334 };
0335 
0336 class KisPNGReaderLineByLine : public KisPNGReaderAbstract
0337 {
0338 public:
0339     KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) {
0340         std::size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
0341         row_pointer = new png_byte[rowbytes];
0342     }
0343     ~KisPNGReaderLineByLine() override {
0344         delete[] row_pointer;
0345     }
0346     png_bytep readLine() override {
0347         png_read_row(png_ptr, row_pointer, 0);
0348         return row_pointer;
0349     }
0350 private:
0351     png_bytep row_pointer;
0352 };
0353 
0354 class KisPNGReaderFullImage : public KisPNGReaderAbstract
0355 {
0356 public:
0357     KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) {
0358         row_pointers = new png_bytep[height];
0359         std::size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
0360         for (int i = 0; i < height; i++) {
0361             row_pointers[i] = new png_byte[rowbytes];
0362         }
0363         png_read_image(png_ptr, row_pointers);
0364     }
0365     ~KisPNGReaderFullImage() override {
0366         for (int i = 0; i < height; i++) {
0367             delete[] row_pointers[i];
0368         }
0369         delete[] row_pointers;
0370     }
0371     png_bytep readLine() override {
0372         return row_pointers[y++];
0373     }
0374 private:
0375     png_bytepp row_pointers;
0376     int y;
0377 };
0378 
0379 
0380 static
0381 void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
0382 {
0383     QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr);
0384 
0385     while (length) {
0386         int nr = in->read((char*)data, length);
0387         if (nr <= 0) {
0388             png_error(png_ptr, "Read Error");
0389             return;
0390         }
0391         length -= nr;
0392     }
0393 }
0394 
0395 static
0396 void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
0397 {
0398     QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr);
0399 
0400     uint nr = out->write((char*)data, length);
0401     if (nr != length) {
0402         png_error(png_ptr, "Write Error");
0403         return;
0404     }
0405 }
0406 
0407 static
0408 void _flush_fn(png_structp png_ptr)
0409 {
0410     Q_UNUSED(png_ptr);
0411 }
0412 
0413 KisImportExportErrorCode KisPNGConverter::buildImage(QIODevice* iod)
0414 {
0415     dbgFile << "Start decoding PNG File";
0416 
0417     png_byte signature[8];
0418     iod->peek((char*)signature, 8);
0419 
0420 #if PNG_LIBPNG_VER < 10400
0421     if (!png_check_sig(signature, 8)) {
0422 #else
0423     if (png_sig_cmp(signature, 0, 8) != 0) {
0424 #endif
0425         iod->close();
0426         return (ImportExportCodes::FileFormatIncorrect);
0427     }
0428 
0429     // Initialize the internal structures
0430     png_structp png_ptr =  png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
0431 
0432     if (!png_ptr) {
0433         iod->close();
0434     }
0435 
0436 #ifdef PNG_SET_USER_LIMITS_SUPPORTED
0437       /* Remove the user limits, if any */
0438       png_set_user_limits(png_ptr, 0x7fffffff, 0x7fffffff);
0439       png_set_chunk_cache_max(png_ptr, 0);
0440       png_set_chunk_malloc_max(png_ptr, 0);
0441 #endif
0442 
0443     png_set_error_fn(png_ptr, nullptr, nullptr, kis_png_warning);
0444     #ifdef PNG_BENIGN_ERRORS_SUPPORTED
0445         png_set_benign_errors(png_ptr, 1);
0446     #endif
0447 
0448     png_infop info_ptr = png_create_info_struct(png_ptr);
0449     if (!info_ptr) {
0450         png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
0451         iod->close();
0452         return (ImportExportCodes::Failure);
0453     }
0454 
0455     png_infop end_info = png_create_info_struct(png_ptr);
0456     if (!end_info) {
0457         png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0);
0458         iod->close();
0459         return (ImportExportCodes::Failure);
0460     }
0461 
0462     // Catch errors
0463     if (setjmp(png_jmpbuf(png_ptr))) {
0464         png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
0465         iod->close();
0466         return (ImportExportCodes::Failure);
0467     }
0468 
0469     // Initialize the special
0470     png_set_read_fn(png_ptr, iod, _read_fn);
0471 
0472 #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
0473     png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
0474 #endif
0475 
0476     // read all PNG info up to image data
0477     png_read_info(png_ptr, info_ptr);
0478 
0479 
0480     if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) {
0481         png_set_expand(png_ptr);
0482     }
0483 
0484     if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) {
0485         png_set_packing(png_ptr);
0486     }
0487 
0488 
0489     if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE &&
0490             (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) {
0491         png_set_expand(png_ptr);
0492     }
0493     png_read_update_info(png_ptr, info_ptr);
0494 
0495     // Read information about the png
0496     png_uint_32 width, height;
0497     int color_nb_bits, color_type, interlace_type;
0498     png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0);
0499     dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << endl;
0500     // swap byteorder on little endian machines.
0501 #ifndef WORDS_BIGENDIAN
0502     if (color_nb_bits > 8)
0503         png_set_swap(png_ptr);
0504 #endif
0505 
0506     // Determine the colorspace
0507     QPair<QString, QString> csName = getColorSpaceForColorType(color_type, color_nb_bits);
0508     if (csName.first.isEmpty()) {
0509         png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
0510         iod->close();
0511         return ImportExportCodes::FormatColorSpaceUnsupported;
0512     }
0513     bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA);
0514 
0515     // Read image profile
0516     png_charp profile_name;
0517 #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
0518     png_bytep profile_data;
0519 #else
0520     png_charp profile_data;
0521 #endif
0522     int compression_type;
0523     png_uint_32 proflen;
0524 
0525     // Get the various optional chunks
0526 
0527     // https://www.w3.org/TR/PNG/#11cHRM
0528 #if defined(PNG_cHRM_SUPPORTED)
0529     double whitePointX, whitePointY;
0530     double redX, redY;
0531     double greenX, greenY;
0532     double blueX, blueY;
0533     png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY);
0534     dbgFile << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY;
0535 #endif
0536 
0537     // https://www.w3.org/TR/PNG/#11gAMA
0538 #if defined(PNG_GAMMA_SUPPORTED)
0539     double gamma;
0540     png_get_gAMA(png_ptr, info_ptr, &gamma);
0541     dbgFile << "gAMA" << gamma;
0542 #endif
0543 
0544     // https://www.w3.org/TR/PNG/#11sRGB
0545 #if defined(PNG_sRGB_SUPPORTED)
0546     int sRGBIntent;
0547     png_get_sRGB(png_ptr, info_ptr, &sRGBIntent);
0548     dbgFile << "sRGB" << sRGBIntent;
0549 #endif
0550 
0551     bool fromBlender = false;
0552 
0553     png_text* text_ptr;
0554     int num_comments;
0555     png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
0556 
0557     for (int i = 0; i < num_comments; i++) {
0558         QString key = QString(text_ptr[i].key).toLower();
0559         if (key == "file") {
0560             QString relatedFile = text_ptr[i].text;
0561             if (relatedFile.contains(".blend", Qt::CaseInsensitive)){
0562                 fromBlender=true;
0563             }
0564         }
0565     }
0566 
0567     bool loadedImageIsHDR = false;
0568     const KoColorProfile* profile = 0;
0569     if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) {
0570         QByteArray profile_rawdata;
0571         // XXX: Hardcoded for icc type -- is that correct for us?
0572         profile_rawdata.resize(proflen);
0573         memcpy(profile_rawdata.data(), profile_data, proflen);
0574         profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata);
0575         Q_CHECK_PTR(profile);
0576         if (profile) {
0577             //                 dbgFile << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo();
0578             if (!profile->isSuitableForOutput()) {
0579                 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"; // TODO: in ko2 popup a selection menu to inform the user
0580             }
0581         }
0582 
0583         loadedImageIsHDR = strcmp(profile_name, "ITUR_2100_PQ_FULL") == 0;
0584     }
0585     else {
0586         dbgFile << "no embedded profile, will use the default profile";
0587         if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) {
0588             KisConfig cfg(true);
0589             quint32 behaviour = cfg.pasteBehaviour();
0590             if (behaviour == KisClipboard::PASTE_ASK) {
0591                 KisDlgPngImport dlg(m_path, csName.first, csName.second);
0592                 KisCursorOverrideHijacker hijacker;
0593                 Q_UNUSED(hijacker);
0594                 dlg.exec();
0595                 if (!dlg.profile().isEmpty()) {
0596                     profile = KoColorSpaceRegistry::instance()->profileByName(dlg.profile());
0597                 }
0598             }
0599         }
0600         dbgFile << "no embedded profile, will use the default profile";
0601     }
0602 
0603     const QString colorSpaceId =
0604         KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second);
0605 
0606     // Check that the profile is used by the color space
0607     if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) {
0608         warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second;
0609         profile = 0;
0610     }
0611 
0612     // Retrieve a pointer to the colorspace
0613     KoColorConversionTransformation* transform = 0;
0614     const KoColorSpace* cs = 0;
0615 
0616     if (loadedImageIsHDR &&
0617         csName.first == RGBAColorModelID.id() &&
0618         csName.second == Integer16BitsColorDepthID.id()) {
0619 
0620         const KoColorSpace *p2020PQCS =
0621             KoColorSpaceRegistry::instance()->colorSpace(
0622                 RGBAColorModelID.id(),
0623                 Integer16BitsColorDepthID.id(),
0624                 KoColorSpaceRegistry::instance()->p2020PQProfile());
0625 
0626         cs = p2020PQCS;
0627 
0628     } else if (profile && profile->isSuitableForOutput()) {
0629         dbgFile << "image has embedded profile: " << profile->name() << "\n";
0630         cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile);
0631     }
0632     else {
0633         if (csName.first == RGBAColorModelID.id()) {
0634             cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "sRGB-elle-V2-srgbtrc.icc");
0635         } else if (csName.first == GrayAColorModelID.id()) {
0636             cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "Gray-D50-elle-V2-srgbtrc.icc");
0637         } else {
0638             cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, 0);
0639         }
0640 
0641         //TODO: two fixes : one tell the user about the problem and ask for a solution, and two once the kocolorspace include KoColorTransformation, use that instead of hacking a lcms transformation
0642         // Create the cmsTransform if needed
0643         if (profile) {
0644             transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
0645         }
0646     }
0647 
0648     if (cs == 0) {
0649         png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
0650         return ImportExportCodes::FormatColorSpaceUnsupported;
0651     }
0652 
0653     // Creating the KisImageSP
0654     if (m_image == 0) {
0655         KisUndoStore *store = m_doc ? m_doc->createUndoStore() : new KisSurrogateUndoStore();
0656         m_image = new KisImage(store, width, height, cs, "built image");
0657     }
0658 
0659     // Read resolution
0660     int unit_type;
0661     png_uint_32 x_resolution, y_resolution;
0662 
0663     png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type);
0664     if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) {
0665         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 pointer-per-inchs to points
0666     }
0667 
0668     double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1);
0669     KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX);
0670 
0671     // Read comments/texts...
0672     png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
0673     if (m_doc) {
0674         KoDocumentInfo * info = m_doc->documentInfo();
0675         dbgFile << "There are " << num_comments << " comments in the text";
0676         for (int i = 0; i < num_comments; i++) {
0677             QString key = QString(text_ptr[i].key).toLower();
0678             dbgFile << "key: " << text_ptr[i].key
0679                     << ", containing: " << text_ptr[i].text
0680                     << ": " << (key == "raw profile type exif " ? "isExif" : "something else");
0681             if (key == "title") {
0682                 info->setAboutInfo("title", text_ptr[i].text);
0683             } else if (key == "description") {
0684                 info->setAboutInfo("comment", text_ptr[i].text);
0685             } else if (key == "author") {
0686                 info->setAuthorInfo("creator", text_ptr[i].text);
0687             } else if (key.contains("raw profile type exif")) {
0688                 decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6);
0689             } else if (key.contains("raw profile type iptc")) {
0690                 decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14);
0691             } else if (key.contains("raw profile type xmp")) {
0692                 decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0);
0693             } else if (key == "version") {
0694                 m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text)));
0695             } else if (key == "preset") {
0696                 m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text)));
0697             }
0698         }
0699     }
0700     // Read image data
0701     QScopedPointer<KisPNGReaderAbstract> reader;
0702     try {
0703         if (interlace_type == PNG_INTERLACE_ADAM7) {
0704             reader.reset(new KisPNGReaderFullImage(png_ptr, info_ptr, width, height));
0705         } else {
0706             reader.reset(new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height));
0707         }
0708     } catch (const std::bad_alloc& e) {
0709         // new png_byte[] may raise such an exception if the image
0710         // is invalid / to large.
0711         dbgFile << "bad alloc: " << e.what();
0712         // Free only the already allocated png_byte instances.
0713         png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
0714         return (ImportExportCodes::Failure);
0715     }
0716 
0717     // Read the palette if the file is indexed
0718     png_colorp palette ;
0719     int num_palette;
0720     if (color_type == PNG_COLOR_TYPE_PALETTE) {
0721         png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
0722     }
0723 
0724     // Read the transparency palette
0725     quint8 palette_alpha[256];
0726     memset(palette_alpha, 255, 256);
0727     if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
0728         if (color_type == PNG_COLOR_TYPE_PALETTE) {
0729             png_bytep alpha_ptr;
0730             int num_alpha;
0731             png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0);
0732             for (int i = 0; i < num_alpha; ++i) {
0733                 palette_alpha[i] = alpha_ptr[i];
0734             }
0735         }
0736     }
0737 
0738     for (png_uint_32 y = 0; y < height; y++) {
0739         KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width);
0740 
0741         png_bytep row_pointer = reader->readLine();
0742 
0743         switch (color_type) {
0744         case PNG_COLOR_TYPE_GRAY:
0745         case PNG_COLOR_TYPE_GRAY_ALPHA:
0746             if (color_nb_bits == 16) {
0747                 quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
0748                 do {
0749                     quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
0750                     d[0] = *(src++);
0751                     if (hasalpha) {
0752                         d[1] = *(src++);
0753                     } else {
0754                         d[1] = quint16_MAX;
0755                     }
0756                     if (transform) transform->transformInPlace(reinterpret_cast<quint8*>(d), reinterpret_cast<quint8*>(d), 1);
0757                 } while (it->nextPixel());
0758             } else  {
0759                 KisPNGReadStream stream(row_pointer, color_nb_bits);
0760                 do {
0761                     quint8 *d = it->rawData();
0762                     d[0] = (quint8)(stream.nextValue() * coeff);
0763                     if (hasalpha) {
0764                         d[1] = (quint8)(stream.nextValue() * coeff);
0765                     } else {
0766                         d[1] = UCHAR_MAX;
0767                     }
0768                     if (transform) transform->transformInPlace(d, d, 1);
0769                 } while (it->nextPixel());
0770             }
0771             // FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits"
0772             break;
0773         case PNG_COLOR_TYPE_RGB:
0774         case PNG_COLOR_TYPE_RGB_ALPHA:
0775             if (color_nb_bits == 16) {
0776                 quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
0777                 do {
0778                     quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
0779                     d[2] = *(src++);
0780                     d[1] = *(src++);
0781                     d[0] = *(src++);
0782                     if (hasalpha) d[3] = *(src++);
0783                     else d[3] = quint16_MAX;
0784                     if (transform) transform->transformInPlace(reinterpret_cast<quint8 *>(d), reinterpret_cast<quint8*>(d), 1);
0785                 } while (it->nextPixel());
0786             } else {
0787                 KisPNGReadStream stream(row_pointer, color_nb_bits);
0788                 do {
0789                     quint8 *d = it->rawData();
0790                     d[2] = (quint8)(stream.nextValue() * coeff);
0791                     d[1] = (quint8)(stream.nextValue() * coeff);
0792                     d[0] = (quint8)(stream.nextValue() * coeff);
0793                     if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff);
0794                     else d[3] = UCHAR_MAX;
0795                     if (transform) transform->transformInPlace(d, d, 1);
0796                 } while (it->nextPixel());
0797             }
0798             break;
0799         case PNG_COLOR_TYPE_PALETTE: {
0800             KisPNGReadStream stream(row_pointer, color_nb_bits);
0801             do {
0802                 quint8 *d = it->rawData();
0803                 quint8 index = stream.nextValue();
0804                 quint8 alpha = palette_alpha[ index ];
0805                 if (alpha == 0) {
0806                     memset(d, 0, 4);
0807                 } else {
0808                     png_color c = palette[ index ];
0809                     d[2] = c.red;
0810                     d[1] = c.green;
0811                     d[0] = c.blue;
0812                     d[3] = alpha;
0813                 }
0814             } while (it->nextPixel());
0815         }
0816             break;
0817         default:
0818             return ImportExportCodes::FormatFeaturesUnsupported;
0819         }
0820     }
0821     m_image->addNode(layer.data(), m_image->rootLayer().data());
0822 
0823     png_read_end(png_ptr, end_info);
0824     iod->close();
0825 
0826     // Freeing memory
0827     png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
0828     return ImportExportCodes::OK;
0829 
0830 }
0831 
0832 KisImportExportErrorCode KisPNGConverter::buildImage(const QString &filename)
0833 {
0834     m_path = filename;
0835 
0836     QFile fp(filename);
0837     if (fp.exists()) {
0838         if (!fp.open(QIODevice::ReadOnly)) {
0839             dbgFile << "Failed to open PNG File";
0840             return (ImportExportCodes::FileFormatIncorrect);
0841         }
0842 
0843         return buildImage(&fp);
0844     }
0845     return (ImportExportCodes::FileNotExist);
0846 
0847 }
0848 
0849 
0850 KisImageSP KisPNGConverter::image()
0851 {
0852     return m_image;
0853 }
0854 
0855 bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData)
0856 {
0857     if (store->open(filename)) {
0858         KoStoreDevice io(store);
0859         if (!io.open(QIODevice::WriteOnly)) {
0860             dbgFile << "Could not open for writing:" << filename;
0861             return false;
0862         }
0863         KisPNGConverter pngconv(0);
0864         vKisAnnotationSP_it annotIt = 0;
0865         KisMetaData::Store* metaDataStore = 0;
0866         if (metaData) {
0867             metaDataStore = new KisMetaData::Store(*metaData);
0868         }
0869         KisPNGOptions options;
0870         options.compression = 3;
0871         options.interlace = false;
0872         options.tryToSaveAsIndexed = false;
0873         options.alpha = true;
0874         options.saveSRGBProfile = false;
0875         options.downsample = false;
0876 
0877         if (dev->colorSpace()->id() != "RGBA") {
0878             dev = new KisPaintDevice(*dev.data());
0879             dev->convertTo(KoColorSpaceRegistry::instance()->rgb8());
0880         }
0881 
0882         KisImportExportErrorCode success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore);
0883         if (!success.isOk()) {
0884             dbgFile << "Saving PNG failed:" << filename;
0885             delete metaDataStore;
0886             return false;
0887         }
0888         delete metaDataStore;
0889         io.close();
0890         if (!store->close()) {
0891             return false;
0892         }
0893     } else {
0894         dbgFile << "Opening of data file failed :" << filename;
0895         return false;
0896     }
0897     return true;
0898 
0899 }
0900 
0901 
0902 KisImportExportErrorCode 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)
0903 {
0904     dbgFile << "Start writing PNG File " << filename;
0905     // Open a QIODevice for writing
0906     QFile fp (filename);
0907     if (!fp.open(QIODevice::WriteOnly)) {
0908         dbgFile << "Failed to open PNG File for writing";
0909         return (KisImportExportErrorCannotWrite(fp.error()));
0910     }
0911 
0912     KisImportExportErrorCode result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData);
0913 
0914     return result;
0915 }
0916 
0917 KisImportExportErrorCode 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)
0918 {
0919     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(device, ImportExportCodes::InternalError);
0920 
0921     if (!options.alpha) {
0922         KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace());
0923         KoColor c(options.transparencyFillColor, device->colorSpace());
0924         tmp->fill(imageRect, c);
0925         KisPainter gc(tmp);
0926         gc.bitBlt(imageRect.topLeft(), device, imageRect);
0927         gc.end();
0928         device = tmp;
0929     }
0930 
0931     if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID
0932             || device->colorSpace()->colorDepthId() == Float32BitsColorDepthID
0933             || device->colorSpace()->colorDepthId() == Float64BitsColorDepthID
0934             || options.saveAsHDR
0935             || (device->colorSpace()->colorDepthId() != Integer8BitsColorDepthID && options.downsample)) {
0936 
0937         const KoColorSpace *dstCS =
0938             KoColorSpaceRegistry::instance()->colorSpace(
0939                 device->colorSpace()->colorModelId().id(),
0940                 options.downsample ? Integer8BitsColorDepthID.id() : Integer16BitsColorDepthID.id(),
0941                 device->colorSpace()->profile());
0942 
0943         if (options.saveAsHDR) {
0944             dstCS =
0945                 KoColorSpaceRegistry::instance()->colorSpace(
0946                         RGBAColorModelID.id(),
0947                         Integer16BitsColorDepthID.id(),
0948                         KoColorSpaceRegistry::instance()->p2020PQProfile());
0949         }
0950 
0951         KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace());
0952         tmp->makeCloneFromRough(device, imageRect);
0953         tmp->convertTo(dstCS);
0954 
0955         device = tmp;
0956     }
0957 
0958     KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.forceSRGB) {
0959         options.forceSRGB = false;
0960     }
0961 
0962     KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.tryToSaveAsIndexed) {
0963         options.tryToSaveAsIndexed = false;
0964     }
0965 
0966     QStringList colormodels = QStringList() << RGBAColorModelID.id() << GrayAColorModelID.id();
0967     if (options.forceSRGB || !colormodels.contains(device->colorSpace()->colorModelId().id())) {
0968         const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), options.downsample ? Integer8BitsColorDepthID.id() : device->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)");
0969         device = new KisPaintDevice(*device);
0970         device->convertTo(cs);
0971     }
0972 
0973     // Initialize structures
0974     png_structp png_ptr =  png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
0975     if (!png_ptr) {
0976         return (ImportExportCodes::Failure);
0977     }
0978 
0979 #ifdef PNG_SET_USER_LIMITS_SUPPORTED
0980       /* Remove the user limits, if any */
0981       png_set_user_limits(png_ptr, 0x7fffffff, 0x7fffffff);
0982       png_set_chunk_cache_max(png_ptr, 0);
0983       png_set_chunk_malloc_max(png_ptr, 0);
0984 #endif
0985 
0986     png_set_error_fn(png_ptr, nullptr, nullptr, kis_png_warning);
0987     #ifdef PNG_BENIGN_ERRORS_SUPPORTED
0988         png_set_benign_errors(png_ptr, 1);
0989     #endif
0990 
0991 #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
0992     png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
0993 #endif
0994 
0995 
0996 #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
0997     png_set_check_for_invalid_index(png_ptr, 0);
0998 #endif
0999 
1000     png_infop info_ptr = png_create_info_struct(png_ptr);
1001     if (!info_ptr) {
1002         png_destroy_write_struct(&png_ptr, (png_infopp)0);
1003         return (ImportExportCodes::Failure);
1004     }
1005 
1006     // If an error occurs during writing, libpng will jump here
1007     if (setjmp(png_jmpbuf(png_ptr))) {
1008         png_destroy_write_struct(&png_ptr, &info_ptr);
1009         return (ImportExportCodes::Failure);
1010     }
1011     // Initialize the writing
1012     //     png_init_io(png_ptr, fp);
1013     // Setup the progress function
1014     // XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);"
1015     //     setProgressTotalSteps(100/*height*/);
1016 
1017     /* set the zlib compression level */
1018     png_set_compression_level(png_ptr, options.compression);
1019 
1020     png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn);
1021 
1022     /* set other zlib parameters */
1023     png_set_compression_mem_level(png_ptr, 8);
1024     png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY);
1025     png_set_compression_window_bits(png_ptr, 15);
1026     png_set_compression_method(png_ptr, 8);
1027     png_set_compression_buffer_size(png_ptr, 8192);
1028 
1029     int color_nb_bits = 8 * device->pixelSize() / device->channelCount();
1030     int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha);
1031 
1032     Q_ASSERT(color_type > -1);
1033 
1034     // Try to compute a table of color if the colorspace is RGB8f
1035     QScopedArrayPointer<png_color> palette;
1036     int num_palette = 0;
1037     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
1038         palette.reset(new png_color[255]);
1039 
1040         KisSequentialIterator it(device, imageRect);
1041 
1042         bool toomuchcolor = false;
1043         while (it.nextPixel()) {
1044             const quint8* c = it.oldRawData();
1045             bool findit = false;
1046             for (int i = 0; i < num_palette; i++) {
1047                 if (palette[i].red == c[2] &&
1048                         palette[i].green == c[1] &&
1049                         palette[i].blue == c[0]) {
1050                     findit = true;
1051                     break;
1052                 }
1053             }
1054             if (!findit) {
1055                 if (num_palette == 255) {
1056                     toomuchcolor = true;
1057                     break;
1058                 }
1059                 palette[num_palette].red = c[2];
1060                 palette[num_palette].green = c[1];
1061                 palette[num_palette].blue = c[0];
1062                 num_palette++;
1063             }
1064         }
1065 
1066         if (!toomuchcolor) {
1067             dbgFile << "Found a palette of " << num_palette << " colors";
1068             color_type = PNG_COLOR_TYPE_PALETTE;
1069             if (num_palette <= 2) {
1070                 color_nb_bits = 1;
1071             } else if (num_palette <= 4) {
1072                 color_nb_bits = 2;
1073             } else if (num_palette <= 16) {
1074                 color_nb_bits = 4;
1075             } else {
1076                 color_nb_bits = 8;
1077             }
1078         } else {
1079             palette.reset();
1080         }
1081     }
1082 
1083     int interlacetype = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
1084 
1085     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(color_type >= 0, ImportExportCodes::Failure);
1086 
1087     png_set_IHDR(png_ptr, info_ptr,
1088                  imageRect.width(),
1089                  imageRect.height(),
1090                  color_nb_bits,
1091                  color_type, interlacetype,
1092                  PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1093 
1094     // set sRGB only if the profile is sRGB  -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present
1095 
1096     const bool sRGB = *device->colorSpace()->profile() == *KoColorSpaceRegistry::instance()->p709SRGBProfile();
1097     /*
1098      * This automatically writes the correct gamma and chroma chunks along with the sRGB chunk, but firefox's
1099      * color management is bugged, so once you give it any incentive to start color managing an sRGB image it
1100      * will turn, for example, a nice desaturated rusty red into bright poppy red. So this is disabled for now.
1101      */
1102     /*if (!options.saveSRGBProfile && sRGB) {
1103         png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);
1104     }*/
1105 
1106 
1107     /** TODO: Firefox still opens the image incorrectly if there is gAMA+cHRM tags
1108      * present. According to the standard it should use iCCP tag with higher priority,
1109      * but it doesn't:
1110      *
1111      * "When the iCCP chunk is present, PNG decoders that recognize it and are capable
1112      *  of color management [ICC] shall ignore the gAMA and cHRM chunks and use
1113      *  the iCCP chunk instead and interpret it according to [ICC-1] and [ICC-1A]"
1114      */
1115 
1116 #if 0
1117     if (options.saveAsHDR) {
1118         // https://www.w3.org/TR/PNG/#11gAMA
1119 #if defined(PNG_GAMMA_SUPPORTED)
1120         // the values are set in accordance of HDR-PNG standard:
1121         // https://www.w3.org/TR/png-hdr-pq/
1122 
1123         png_set_gAMA_fixed(png_ptr, info_ptr, 15000);
1124         dbgFile << "gAMA" << "(Rec 2100)";
1125 #endif
1126 
1127 #if defined PNG_cHRM_SUPPORTED
1128         png_set_cHRM_fixed(png_ptr, info_ptr,
1129                            31270, 32900, // white point
1130                            70800, 29200, // red
1131                            17000, 79700, // green
1132                            13100, 4600 // blue
1133                            );
1134         dbgFile << "cHRM" << "(Rec 2100)";
1135 #endif
1136     }
1137 #endif
1138 
1139 
1140     // we should ensure we don't access non-existing palette object
1141     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(palette || color_type != PNG_COLOR_TYPE_PALETTE, ImportExportCodes::Failure);
1142 
1143     // set the palette
1144     if (color_type == PNG_COLOR_TYPE_PALETTE) {
1145         png_set_PLTE(png_ptr, info_ptr, palette.data(), num_palette);
1146     }
1147     // Save annotation
1148     vKisAnnotationSP_it it = annotationsStart;
1149     while (it != annotationsEnd) {
1150         if (!(*it) || (*it)->type().isEmpty()) {
1151             dbgFile << "Warning: empty annotation";
1152             it++;
1153             continue;
1154         }
1155 
1156         dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size();
1157 
1158         if ((*it) -> type().startsWith(QString("krita_attribute:"))) { //
1159             // Attribute
1160             // XXX: it should be possible to save krita_attributes in the \"CHUNKs\""
1161             dbgFile << "cannot save this annotation : " << (*it) -> type();
1162         } else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) {
1163             dbgFile << "Saving preset information " << (*it)->description();
1164             png_textp      text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text));
1165 
1166             QByteArray keyData = (*it)->description().toLatin1();
1167             text[0].key = keyData.data();
1168             text[0].text = (char*)(*it)->annotation().data();
1169             text[0].text_length = (*it)->annotation().size();
1170             text[0].compression = -1;
1171 
1172             png_set_text(png_ptr, info_ptr, text, 1);
1173             png_free(png_ptr, text);
1174         }
1175         it++;
1176     }
1177 
1178     // Save the color profile
1179     const KoColorProfile* colorProfile = device->colorSpace()->profile();
1180     QByteArray colorProfileData = colorProfile->rawData();
1181     if (!sRGB || options.saveSRGBProfile) {
1182 
1183 #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
1184         const char *typeString = !options.saveAsHDR ? "icc" : "ITUR_2100_PQ_FULL";
1185         png_set_iCCP(png_ptr, info_ptr, (png_const_charp)typeString, PNG_COMPRESSION_TYPE_BASE, (png_const_bytep)colorProfileData.constData(), colorProfileData . size());
1186 #else
1187         // older version of libpng has a problem with constness on the parameters
1188         char typeStringICC[] = "icc";
1189         char typeStringHDR[] = "ITUR_2100_PQ_FULL";
1190         char *typeString = !options.saveAsHDR ? typeStringICC : typeStringHDR;
1191         png_set_iCCP(png_ptr, info_ptr, typeString, PNG_COMPRESSION_TYPE_BASE, colorProfileData.data(), colorProfileData . size());
1192 #endif
1193     }
1194 
1195     // save comments from the document information
1196     // warning: according to the official png spec, the keys need to be capitalized!
1197     if (m_doc) {
1198         png_text texts[4];
1199         int nbtexts = 0;
1200         KoDocumentInfo * info = m_doc->documentInfo();
1201         QString title = info->aboutInfo("title");
1202         if (!title.isEmpty() && options.storeMetaData) {
1203             fillText(texts + nbtexts, "Title", title);
1204             nbtexts++;
1205         }
1206         QString abstract = info->aboutInfo("subject");
1207         if (abstract.isEmpty()) {
1208             abstract = info->aboutInfo("abstract");
1209         }
1210         if (!abstract.isEmpty() && options.storeMetaData) {
1211             QString keywords = info->aboutInfo("keyword");
1212             if (!keywords.isEmpty()) {
1213                 abstract = abstract + " keywords: " + keywords;
1214             }
1215             fillText(texts + nbtexts, "Description", abstract);
1216             nbtexts++;
1217         }
1218 
1219         QString license = info->aboutInfo("license");
1220         if (!license.isEmpty() && options.storeMetaData) {
1221             fillText(texts + nbtexts, "Copyright", license);
1222             nbtexts++;
1223         }
1224 
1225         QString author = info->authorInfo("creator");
1226         if (!author.isEmpty() && options.storeAuthor) {
1227             if (!info->authorContactInfo().isEmpty()) {
1228                 QString contact = info->authorContactInfo().at(0);
1229                 if (!contact.isEmpty()) {
1230                     author = author+"("+contact+")";
1231                 }
1232             }
1233             fillText(texts + nbtexts, "Author", author);
1234             nbtexts++;
1235         }
1236 
1237         png_set_text(png_ptr, info_ptr, texts, nbtexts);
1238     }
1239 
1240     // Save metadata following imagemagick way
1241 
1242     // Save exif
1243     if (metaData && !metaData->empty()) {
1244         if (options.exif) {
1245             dbgFile << "Trying to save exif information";
1246 
1247             KisMetaData::IOBackend *exifIO = KisMetadataBackendRegistry::instance()->value("exif");
1248             Q_ASSERT(exifIO);
1249 
1250             QBuffer buffer;
1251             exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
1252             writeRawProfile(png_ptr, info_ptr, "exif", buffer.data());
1253         }
1254         // Save IPTC
1255         if (options.iptc) {
1256             dbgFile << "Trying to save iptc information";
1257             KisMetaData::IOBackend *iptcIO = KisMetadataBackendRegistry::instance()->value("iptc");
1258             Q_ASSERT(iptcIO);
1259 
1260             QBuffer buffer;
1261             iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
1262 
1263             dbgFile << "IPTC information size is" << buffer.data().size();
1264             writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data());
1265         }
1266         // Save XMP
1267         if (options.xmp) {
1268             dbgFile << "Trying to save XMP information";
1269             KisMetaData::IOBackend *xmpIO = KisMetadataBackendRegistry::instance()->value("xmp");
1270             Q_ASSERT(xmpIO);
1271 
1272             QBuffer buffer;
1273             xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::NoHeader);
1274 
1275             dbgFile << "XMP information size is" << buffer.data().size();
1276             writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data());
1277         }
1278     }
1279 #if 0 // Unimplemented?
1280     // Save resolution
1281     int unit_type;
1282     png_uint_32 x_resolution, y_resolution;
1283 #endif
1284     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 pointer-per-inchs to points
1285 
1286     // Save the information to the file
1287     png_write_info(png_ptr, info_ptr);
1288     png_write_flush(png_ptr);
1289 
1290     // swap byteorder on little endian machines.
1291 #ifndef WORDS_BIGENDIAN
1292     if (color_nb_bits > 8)
1293         png_set_swap(png_ptr);
1294 #endif
1295 
1296     // Write the PNG
1297     //     png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0);
1298 
1299     struct RowPointersStruct {
1300         RowPointersStruct(const QSize &size, int pixelSize)
1301             : numRows(size.height())
1302         {
1303             rows = new png_byte*[numRows];
1304 
1305             for (int i = 0; i < numRows; i++) {
1306                 rows[i] = new png_byte[size.width() * pixelSize];
1307             }
1308         }
1309 
1310         ~RowPointersStruct() {
1311             for (int i = 0; i < numRows; i++) {
1312                 delete[] rows[i];
1313             }
1314             delete[] rows;
1315         }
1316 
1317         const int numRows = 0;
1318         png_byte** rows = 0;
1319     };
1320 
1321 
1322     // Fill the data structure
1323     RowPointersStruct rowPointers(imageRect.size(), device->pixelSize());
1324 
1325     int row = 0;
1326     for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) {
1327         KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width());
1328 
1329         switch (color_type) {
1330         case PNG_COLOR_TYPE_GRAY:
1331         case PNG_COLOR_TYPE_GRAY_ALPHA:
1332             if (color_nb_bits == 16) {
1333                 quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
1334                 do {
1335                     const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
1336                     *(dst++) = d[0];
1337                     if (options.alpha) *(dst++) = d[1];
1338                 } while (it->nextPixel());
1339             } else {
1340                 quint8 *dst = rowPointers.rows[row];
1341                 do {
1342                     const quint8 *d = it->oldRawData();
1343                     *(dst++) = d[0];
1344                     if (options.alpha) *(dst++) = d[1];
1345                 } while (it->nextPixel());
1346             }
1347             break;
1348         case PNG_COLOR_TYPE_RGB:
1349         case PNG_COLOR_TYPE_RGB_ALPHA:
1350             if (color_nb_bits == 16) {
1351                 quint16 *dst = reinterpret_cast<quint16 *>(rowPointers.rows[row]);
1352                 do {
1353                     const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
1354                     *(dst++) = d[2];
1355                     *(dst++) = d[1];
1356                     *(dst++) = d[0];
1357                     if (options.alpha) *(dst++) = d[3];
1358                 } while (it->nextPixel());
1359             } else {
1360                 quint8 *dst = rowPointers.rows[row];
1361                 do {
1362                     const quint8 *d = it->oldRawData();
1363                     *(dst++) = d[2];
1364                     *(dst++) = d[1];
1365                     *(dst++) = d[0];
1366                     if (options.alpha) *(dst++) = d[3];
1367                 } while (it->nextPixel());
1368             }
1369             break;
1370         case PNG_COLOR_TYPE_PALETTE: {
1371             quint8 *dst = rowPointers.rows[row];
1372             KisPNGWriteStream writestream(dst, color_nb_bits);
1373             do {
1374                 const quint8 *d = it->oldRawData();
1375                 int i;
1376                 for (i = 0; i < num_palette; i++) {
1377                     if (palette[i].red == d[2] &&
1378                             palette[i].green == d[1] &&
1379                             palette[i].blue == d[0]) {
1380                         break;
1381                     }
1382                 }
1383                 writestream.setNextValue(i);
1384             } while (it->nextPixel());
1385         }
1386             break;
1387         default:
1388             return ImportExportCodes::FormatColorSpaceUnsupported;
1389         }
1390     }
1391 
1392     png_write_image(png_ptr, rowPointers.rows);
1393 
1394     // Writing is over
1395     png_write_end(png_ptr, info_ptr);
1396 
1397     // Free memory
1398     png_destroy_write_struct(&png_ptr, &info_ptr);
1399     return ImportExportCodes::OK;
1400 }
1401 
1402 
1403 void KisPNGConverter::cancel()
1404 {
1405     m_stop = true;
1406 }
1407 
1408 void KisPNGConverter::progress(png_structp png_ptr, png_uint_32 row_number, int pass)
1409 {
1410     if (png_ptr == 0 || row_number > PNG_MAX_UINT || pass > 7) return;
1411     //     setProgress(row_number);
1412 }
1413 
1414 bool KisPNGConverter::isColorSpaceSupported(const KoColorSpace *cs)
1415 {
1416     return colorSpaceIdSupported(cs->id());
1417 }
1418 
1419