kis_png_converter.cpp 48.2 KB
Newer Older
1
/*
2
 *  Copyright (c) 2005-2007 Cyrille Berger <cberger@cberger.net>
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

Boudewijn Rempt's avatar
Boudewijn Rempt committed
20
#include "kis_png_converter.h"
Boudewijn Rempt's avatar
Boudewijn Rempt committed
21 22
// A big thank to Glenn Randers-Pehrson for his wonderful
// documentation of libpng available at
23 24
// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html

25
#ifndef PNG_MAX_UINT // Removed in libpng 1.4
26
#define PNG_MAX_UINT PNG_UINT_31_MAX
27 28
#endif

29
#include <KoConfig.h> // WORDS_BIGENDIAN
30 31
#include <KoStore.h>
#include <KoStoreDevice.h>
32 33 34

#include <limits.h>
#include <stdio.h>
35
#include <zlib.h>
36

37
#include <QBuffer>
38
#include <QFile>
39
#include <QApplication>
40

41
#include <klocalizedstring.h>
42
#include <QUrl>
43

44
#include <KoColorSpace.h>
45 46 47
#include <KoDocumentInfo.h>
#include <KoID.h>
#include <KoColorSpaceRegistry.h>
48
#include <KoColorProfile.h>
49
#include <KoColor.h>
50
#include <KoUnit.h>
51

52
#include <kis_config.h>
53
#include <kis_painter.h>
54
#include <KisDocument.h>
55
#include <kis_image.h>
56
#include <kis_iterator_ng.h>
57
#include <kis_layer.h>
58
#include <kis_paint_device.h>
59
#include <kis_transaction.h>
60 61
#include <kis_paint_layer.h>
#include <kis_group_layer.h>
62 63
#include <kis_meta_data_io_backend.h>
#include <kis_meta_data_store.h>
64
#include <KoColorModelStandardIds.h>
65 66
#include "dialogs/kis_dlg_png_import.h"
#include "kis_clipboard.h"
67
#include <kis_cursor_override_hijacker.h>
68
#include "kis_undo_stores.h"
69

Boudewijn Rempt's avatar
Boudewijn Rempt committed
70 71
namespace
{
72

Boudewijn Rempt's avatar
Boudewijn Rempt committed
73 74
int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha)
{
75 76 77 78

    QString id = cs->id();

    if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
79 80
        return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY;
    }
81
    if (id == "RGBA" || id == "RGBA16") {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
82 83
        return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB;
    }
84

Boudewijn Rempt's avatar
Boudewijn Rempt committed
85
    return -1;
86

Boudewijn Rempt's avatar
Boudewijn Rempt committed
87
}
88

89 90 91 92 93
bool colorSpaceIdSupported(const QString &id)
{
    return id == "RGBA" || id == "RGBA16" ||
        id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16";
}
94

95
QPair<QString, QString> getColorSpaceForColorType(int color_type, int color_nb_bits)
Boudewijn Rempt's avatar
Boudewijn Rempt committed
96
{
97
    QPair<QString, QString> r;
98

99 100 101 102 103 104 105 106
    if (color_type ==  PNG_COLOR_TYPE_PALETTE) {
        r.first = RGBAColorModelID.id();
        r.second = Integer8BitsColorDepthID.id();
    } else {
        if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
            r.first = GrayAColorModelID.id();
        } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) {
            r.first = RGBAColorModelID.id();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
107 108
        }
        if (color_nb_bits == 16) {
109
            r.second = Integer16BitsColorDepthID.id();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
110
        } else if (color_nb_bits <= 8) {
111
            r.second = Integer8BitsColorDepthID.id();
112 113
        }
    }
114
    return r;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
115
}
116 117


Boudewijn Rempt's avatar
Boudewijn Rempt committed
118 119 120 121 122
void fillText(png_text* p_text, const char* key, QString& text)
{
    p_text->compression = PNG_TEXT_COMPRESSION_zTXt;
    p_text->key = const_cast<char *>(key);
    char* textc = new char[text.length()+1];
123
    strcpy(textc, text.toLatin1());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
124 125 126
    p_text->text = textc;
    p_text->text_length = text.length() + 1;
}
127

Boudewijn Rempt's avatar
Boudewijn Rempt committed
128 129 130
long formatStringList(char *string, const size_t length, const char *format, va_list operands)
{
    int n = vsnprintf(string, length, format, operands);
131

Boudewijn Rempt's avatar
Boudewijn Rempt committed
132 133
    if (n < 0)
        string[length-1] = '\0';
134

Boudewijn Rempt's avatar
Boudewijn Rempt committed
135 136
    return((long) n);
}
137

Boudewijn Rempt's avatar
Boudewijn Rempt committed
138 139 140
long formatString(char *string, const size_t length, const char *format, ...)
{
    long n;
141

Boudewijn Rempt's avatar
Boudewijn Rempt committed
142
    va_list operands;
143

Boudewijn Rempt's avatar
Boudewijn Rempt committed
144 145 146 147 148
    va_start(operands, format);
    n = (long) formatStringList(string, length, format, operands);
    va_end(operands);
    return(n);
}
149

Boudewijn Rempt's avatar
Boudewijn Rempt committed
150 151
void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data)
{
152

Boudewijn Rempt's avatar
Boudewijn Rempt committed
153
    png_textp      text;
154

Boudewijn Rempt's avatar
Boudewijn Rempt committed
155
    png_uint_32    allocated_length, description_length;
156

Boudewijn Rempt's avatar
Boudewijn Rempt committed
157
    const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
158

Boudewijn Rempt's avatar
Boudewijn Rempt committed
159
    dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << endl;
160

Boudewijn Rempt's avatar
Boudewijn Rempt committed
161 162 163
    text               = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text));
    description_length = profile_type.length();
    allocated_length   = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length);
164

Boudewijn Rempt's avatar
Boudewijn Rempt committed
165
    text[0].text   = (png_charp) png_malloc(ping, allocated_length);
166

167
    QString key = QLatin1Literal("Raw profile type ") + profile_type.toLatin1();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
168 169
    QByteArray keyData = key.toLatin1();
    text[0].key = keyData.data();
170

Boudewijn Rempt's avatar
Boudewijn Rempt committed
171 172 173
    uchar* sp = (uchar*)profile_data.data();
    png_charp dp = text[0].text;
    *dp++ = '\n';
174

175
    memcpy(dp, profile_type.toLatin1().constData(), profile_type.length());
176

Boudewijn Rempt's avatar
Boudewijn Rempt committed
177 178
    dp += description_length;
    *dp++ = '\n';
179

Boudewijn Rempt's avatar
Boudewijn Rempt committed
180
    formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", profile_data.length());
181

Boudewijn Rempt's avatar
Boudewijn Rempt committed
182
    dp += 8;
183

Boudewijn Rempt's avatar
Boudewijn Rempt committed
184 185 186
    for (long i = 0; i < (long) profile_data.length(); i++) {
        if (i % 36 == 0)
            *dp++ = '\n';
187

Boudewijn Rempt's avatar
Boudewijn Rempt committed
188 189 190
        *(dp++) = (char) hex[((*sp >> 4) & 0x0f)];
        *(dp++) = (char) hex[((*sp++) & 0x0f)];
    }
191

Boudewijn Rempt's avatar
Boudewijn Rempt committed
192 193 194 195
    *dp++ = '\n';
    *dp = '\0';
    text[0].text_length = (png_size_t)(dp - text[0].text);
    text[0].compression = -1;
196

Boudewijn Rempt's avatar
Boudewijn Rempt committed
197 198
    if (text[0].text_length <= allocated_length)
        png_set_text(ping, ping_info, text, 1);
199

Boudewijn Rempt's avatar
Boudewijn Rempt committed
200 201 202
    png_free(ping, text[0].text);
    png_free(ping, text);
}
203

Boudewijn Rempt's avatar
Boudewijn Rempt committed
204 205 206 207
QByteArray png_read_raw_profile(png_textp text)
{
    QByteArray profile;

208 209 210 211 212 213 214 215
    static const unsigned char unhex[103] = {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12,
        13, 14, 15
    };
Boudewijn Rempt's avatar
Boudewijn Rempt committed
216 217 218 219 220 221 222 223 224 225 226 227 228

    png_charp sp = text[0].text + 1;
    /* look for newline */
    while (*sp != '\n')
        sp++;
    /* look for length */
    while (*sp == '\0' || *sp == ' ' || *sp == '\n')
        sp++;
    png_uint_32 length = (png_uint_32) atol(sp);
    while (*sp != ' ' && *sp != '\n')
        sp++;
    if (length == 0) {
        return profile;
229
    }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
230 231 232 233 234 235 236 237
    profile.resize(length);
    /* copy profile, skipping white space and column 1 "=" signs */
    unsigned char *dp = (unsigned char*)profile.data();
    png_uint_32 nibbles = length * 2;
    for (png_uint_32 i = 0; i < nibbles; i++) {
        while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') {
            if (*sp == '\0') {
                return QByteArray();
238
            }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
239
            sp++;
240
        }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
241 242 243 244
        if (i % 2 == 0)
            *dp = (unsigned char)(16 * unhex[(int) *sp++]);
        else
            (*dp++) += unhex[(int) *sp++];
245
    }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
246 247
    return profile;
}
248

Boudewijn Rempt's avatar
Boudewijn Rempt committed
249 250 251 252 253 254 255 256 257
void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize)
{
    dbgFile << "Decoding " << type << " " << text[0].key;
    KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value(type);
    Q_ASSERT(exifIO);

    QByteArray rawProfile = png_read_raw_profile(text);
    if (headerSize > 0) {
        rawProfile.remove(0, headerSize);
258
    }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
259 260 261 262 263 264 265 266
    if (rawProfile.size() > 0) {
        QBuffer buffer;
        buffer.setData(rawProfile);
        exifIO->loadFrom(store, &buffer);
    } else {
        dbgFile << "Decoding failed";
    }
}
267 268
}

269
KisPNGConverter::KisPNGConverter(KisDocument *doc, bool batchMode)
270
{
271 272
    //     Q_ASSERT(doc);
    //     Q_ASSERT(adapter);
273 274 275 276

    m_doc = doc;
    m_stop = false;
    m_max_row = 0;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
277
    m_image = 0;
278
    m_batchMode = batchMode;
279 280 281 282 283 284
}

KisPNGConverter::~KisPNGConverter()
{
}

Boudewijn Rempt's avatar
Boudewijn Rempt committed
285 286 287 288 289 290 291 292 293
class KisPNGReadStream
{
public:
    KisPNGReadStream(quint8* buf,  quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
    }
    int nextValue() {
        if (m_posinc == 0) {
            m_posinc = 8;
            m_buf++;
294
        }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
295 296 297 298 299 300
        m_posinc -= m_depth;
        return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1));
    }
private:
    quint32 m_posinc, m_depth;
    quint8* m_buf;
301 302
};

Boudewijn Rempt's avatar
Boudewijn Rempt committed
303 304 305 306 307 308 309 310 311 312
class KisPNGWriteStream
{
public:
    KisPNGWriteStream(quint8* buf,  quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
        *m_buf = 0;
    }
    void setNextValue(int v) {
        if (m_posinc == 0) {
            m_posinc = 8;
            m_buf++;
313 314
            *m_buf = 0;
        }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
315 316 317 318 319 320
        m_posinc -= m_depth;
        *m_buf = (v << m_posinc) | *m_buf;
    }
private:
    quint32 m_posinc, m_depth;
    quint8* m_buf;
321 322
};

Boudewijn Rempt's avatar
Boudewijn Rempt committed
323 324 325 326 327 328 329 330 331
class KisPNGReaderAbstract
{
public:
    KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {}
    virtual ~KisPNGReaderAbstract() {}
    virtual png_bytep readLine() = 0;
protected:
    png_structp png_ptr;
    int width, height;
332 333
};

Boudewijn Rempt's avatar
Boudewijn Rempt committed
334 335 336 337 338 339 340
class KisPNGReaderLineByLine : public KisPNGReaderAbstract
{
public:
    KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) {
        png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
        row_pointer = new png_byte[rowbytes];
    }
341
    ~KisPNGReaderLineByLine() override {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
342 343
        delete[] row_pointer;
    }
344
    png_bytep readLine() override {
345
        png_read_row(png_ptr, row_pointer, 0);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
346 347 348 349
        return row_pointer;
    }
private:
    png_bytep row_pointer;
350 351
};

Boudewijn Rempt's avatar
Boudewijn Rempt committed
352 353 354 355 356 357 358 359
class KisPNGReaderFullImage : public KisPNGReaderAbstract
{
public:
    KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) {
        row_pointers = new png_bytep[height];
        png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
        for (int i = 0; i < height; i++) {
            row_pointers[i] = new png_byte[rowbytes];
360
        }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
361 362
        png_read_image(png_ptr, row_pointers);
    }
363
    ~KisPNGReaderFullImage() override {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
364 365
        for (int i = 0; i < height; i++) {
            delete[] row_pointers[i];
366
        }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
367 368
        delete[] row_pointers;
    }
369
    png_bytep readLine() override {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
370 371 372 373 374
        return row_pointers[y++];
    }
private:
    png_bytepp row_pointers;
    int y;
375 376 377
};


378
static
Cyrille Berger's avatar
Cyrille Berger committed
379
void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
380 381 382 383 384 385 386 387 388 389 390 391 392
{
    QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr);

    while (length) {
        int nr = in->read((char*)data, length);
        if (nr <= 0) {
            png_error(png_ptr, "Read Error");
            return;
        }
        length -= nr;
    }
}

Cyrille Berger's avatar
Cyrille Berger committed
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
static
void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
{
    QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr);

    uint nr = out->write((char*)data, length);
    if (nr != length) {
        png_error(png_ptr, "Write Error");
        return;
    }
}

static
void _flush_fn(png_structp png_ptr)
{
408
    Q_UNUSED(png_ptr);
Cyrille Berger's avatar
Cyrille Berger committed
409 410
}

411
KisImageBuilder_Result KisPNGConverter::buildImage(QIODevice* iod)
412
{
413
    dbgFile << "Start decoding PNG File";
414

415
    png_byte signature[8];
416
    iod->peek((char*)signature, 8);
417

418
#if PNG_LIBPNG_VER < 10400
Boudewijn Rempt's avatar
Boudewijn Rempt committed
419
    if (!png_check_sig(signature, 8)) {
420 421 422
#else
    if (png_sig_cmp(signature, 0, 8) != 0) {
#endif
423
        iod->close();
424 425 426 427
        return (KisImageBuilder_RESULT_BAD_FETCH);
    }

    // Initialize the internal structures
428
    png_structp png_ptr =  png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
429
    if (!png_ptr) {
430
        iod->close();
431 432 433
    }

    png_infop info_ptr = png_create_info_struct(png_ptr);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
434
    if (!info_ptr) {
435
        png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
436
        iod->close();
437 438 439 440
        return (KisImageBuilder_RESULT_FAILURE);
    }

    png_infop end_info = png_create_info_struct(png_ptr);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
441
    if (!end_info) {
442
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0);
443
        iod->close();
444 445 446 447
        return (KisImageBuilder_RESULT_FAILURE);
    }

    // Catch errors
Boudewijn Rempt's avatar
Boudewijn Rempt committed
448
    if (setjmp(png_jmpbuf(png_ptr))) {
449
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
450
        iod->close();
451 452 453
        return (KisImageBuilder_RESULT_FAILURE);
    }

454
    // Initialize the special
455
    png_set_read_fn(png_ptr, iod, _read_fn);
456

457
#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
458
    png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
459 460
#endif

461 462 463
    // read all PNG info up to image data
    png_read_info(png_ptr, info_ptr);

464

465
    if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
466
        png_set_expand(png_ptr);
467 468
    }

469
    if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
470
        png_set_packing(png_ptr);
471 472
    }

473

474 475
    if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE &&
            (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
476
        png_set_expand(png_ptr);
477 478 479
    }
    png_read_update_info(png_ptr, info_ptr);

480 481 482
    // Read information about the png
    png_uint_32 width, height;
    int color_nb_bits, color_type, interlace_type;
483
    png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0);
484
    dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << endl;
485
    // swap byteorder on little endian machines.
Boudewijn Rempt's avatar
Boudewijn Rempt committed
486 487
#ifndef WORDS_BIGENDIAN
    if (color_nb_bits > 8)
488
        png_set_swap(png_ptr);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
489
#endif
490 491

    // Determine the colorspace
492
    QPair<QString, QString> csName = getColorSpaceForColorType(color_type, color_nb_bits);
Cyrille Berger's avatar
Cyrille Berger committed
493
    if (csName.first.isEmpty()) {
494
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
495
        iod->close();
496 497 498 499 500
        return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
    }
    bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA);

    // Read image profile
501
    png_charp profile_name;
502
#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
503
    png_bytep profile_data;
504 505 506
#else
    png_charp profile_data;
#endif
507 508 509
    int compression_type;
    png_uint_32 proflen;

510 511 512 513 514 515 516 517 518
    // Get the various optional chunks

    // https://www.w3.org/TR/PNG/#11cHRM
#if defined(PNG_cHRM_SUPPORTED)
    double whitePointX, whitePointY;
    double redX, redY;
    double greenX, greenY;
    double blueX, blueY;
    png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY);
519
    dbgFile << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY;
520 521 522 523 524 525
#endif

    // https://www.w3.org/TR/PNG/#11gAMA
#if defined(PNG_GAMMA_SUPPORTED)
    double gamma;
    png_get_gAMA(png_ptr, info_ptr, &gamma);
526
    dbgFile << "gAMA" << gamma;
527 528 529 530 531 532
#endif

    // https://www.w3.org/TR/PNG/#11sRGB
#if defined(PNG_sRGB_SUPPORTED)
    int sRGBIntent;
    png_get_sRGB(png_ptr, info_ptr, &sRGBIntent);
533
    dbgFile << "sRGB" << sRGBIntent;
534
#endif
535

536
    bool fromBlender = false;
537

538
    png_text* text_ptr;
539
    int num_comments;
540 541 542 543 544 545 546 547
    png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);

    for (int i = 0; i < num_comments; i++) {
        QString key = QString(text_ptr[i].key).toLower();
        if (key == "file") {
            QString relatedFile = text_ptr[i].text;
            if (relatedFile.contains(".blend", Qt::CaseInsensitive)){
                fromBlender=true;
548 549
            }
        }
550
    }
551

552
    bool loadedImageIsHDR = false;
553
    const KoColorProfile* profile = 0;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
554
    if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) {
555 556
        QByteArray profile_rawdata;
        // XXX: Hardcoded for icc type -- is that correct for us?
557 558 559 560 561
        profile_rawdata.resize(proflen);
        memcpy(profile_rawdata.data(), profile_data, proflen);
        profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata);
        Q_CHECK_PTR(profile);
        if (profile) {
562
            //                 dbgFile << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo();
563 564
            if (!profile->isSuitableForOutput()) {
                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
565 566
            }
        }
567 568

        loadedImageIsHDR = strcmp(profile_name, "ITUR_2100_PQ_FULL") == 0;
569 570 571
    }
    else {
        dbgFile << "no embedded profile, will use the default profile";
572
        if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) {
573
            KisConfig cfg(true);
574 575 576
            quint32 behaviour = cfg.pasteBehaviour();
            if (behaviour == PASTE_ASK) {
                KisDlgPngImport dlg(m_path, csName.first, csName.second);
577 578
                KisCursorOverrideHijacker hijacker;
                Q_UNUSED(hijacker);
579 580
                dlg.exec();
                if (!dlg.profile().isEmpty()) {
581
                    profile = KoColorSpaceRegistry::instance()->profileByName(dlg.profile());
582
                }
583

584 585
            }
        }
586
        dbgFile << "no embedded profile, will use the default profile";
587
    }
588

589 590 591
    const QString colorSpaceId =
        KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second);

592
    // Check that the profile is used by the color space
593
    if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) {
594 595 596
        warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second;
        profile = 0;
    }
597 598

    // Retrieve a pointer to the colorspace
599
    KoColorConversionTransformation* transform = 0;
600 601 602 603 604 605
    const KoColorSpace* cs = 0;

    if (loadedImageIsHDR &&
        csName.first == RGBAColorModelID.id() &&
        csName.second == Integer16BitsColorDepthID.id()) {

606 607 608 609
        const KoColorSpace *p2020PQCS =
            KoColorSpaceRegistry::instance()->colorSpace(
                RGBAColorModelID.id(),
                Integer16BitsColorDepthID.id(),
610
                KoColorSpaceRegistry::instance()->p2020PQProfile());
611

612
        cs = p2020PQCS;
613 614

    } else if (profile && profile->isSuitableForOutput()) {
615
        dbgFile << "image has embedded profile: " << profile->name() << "\n";
Cyrille Berger's avatar
Cyrille Berger committed
616
        cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile);
617 618 619 620
    }
    else {
        if (csName.first == RGBAColorModelID.id()) {
            cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "sRGB-elle-V2-srgbtrc.icc");
621 622 623
        } else if (csName.first == GrayAColorModelID.id()) {
            cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "Gray-D50-elle-V2-srgbtrc.icc");
        } else {
624 625
            cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, 0);
        }
626 627 628 629 630 631

        //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
        // Create the cmsTransform if needed
        if (profile) {
            transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
        }
632
    }
633

Boudewijn Rempt's avatar
Boudewijn Rempt committed
634
    if (cs == 0) {
635 636 637 638
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
    }

639
    // Creating the KisImageSP
Boudewijn Rempt's avatar
Boudewijn Rempt committed
640
    if (m_image == 0) {
641 642
        KisUndoStore *store = m_doc ? m_doc->createUndoStore() : new KisSurrogateUndoStore();
        m_image = new KisImage(store, width, height, cs, "built image");
643
    }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
644

645 646 647
    // Read resolution
    int unit_type;
    png_uint_32 x_resolution, y_resolution;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
648

649
    png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type);
650
    if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) {
651
        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
652
    }
653

Boudewijn Rempt's avatar
Boudewijn Rempt committed
654
    double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
655
    KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX);
656

657 658
    // Read comments/texts...
    png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
659
    if (m_doc) {
660
        KoDocumentInfo * info = m_doc->documentInfo();
661
        dbgFile << "There are " << num_comments << " comments in the text";
Boudewijn Rempt's avatar
Boudewijn Rempt committed
662
        for (int i = 0; i < num_comments; i++) {
663
            QString key = QString(text_ptr[i].key).toLower();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
664 665 666
            dbgFile << "key: " << text_ptr[i].key
                    << ", containing: " << text_ptr[i].text
                    << ": " << (key == "raw profile type exif " ? "isExif" : "something else");
Boudewijn Rempt's avatar
Boudewijn Rempt committed
667 668
            if (key == "title") {
                info->setAboutInfo("title", text_ptr[i].text);
669 670
            } else if (key == "description") {
                info->setAboutInfo("comment", text_ptr[i].text);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
671 672
            } else if (key == "author") {
                info->setAuthorInfo("creator", text_ptr[i].text);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
673
            } else if (key.contains("raw profile type exif")) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
674
                decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
675
            } else if (key.contains("raw profile type iptc")) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
676
                decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
677
            } else if (key.contains("raw profile type xmp")) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
678
                decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0);
679 680 681 682
            } else if (key == "version") {
                m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text)));
            } else if (key == "preset") {
                m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text)));
683
            }
684 685 686
        }
    }
    // Read image data
687
    QScopedPointer<KisPNGReaderAbstract> reader;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
688 689
    try {
        if (interlace_type == PNG_INTERLACE_ADAM7) {
690
            reader.reset(new KisPNGReaderFullImage(png_ptr, info_ptr, width, height));
691
        } else {
692
            reader.reset(new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height));
693
        }
694
    } catch (const std::bad_alloc& e) {
695 696
        // new png_byte[] may raise such an exception if the image
        // is invalid / to large.
697
        dbgFile << "bad alloc: " << e.what();
698 699 700 701 702 703 704 705
        // Free only the already allocated png_byte instances.
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        return (KisImageBuilder_RESULT_FAILURE);
    }

    // Read the palette if the file is indexed
    png_colorp palette ;
    int num_palette;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
706
    if (color_type == PNG_COLOR_TYPE_PALETTE) {
707 708
        png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
    }
709

710 711 712
    // Read the transparency palette
    quint8 palette_alpha[256];
    memset(palette_alpha, 255, 256);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
713 714
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
        if (color_type == PNG_COLOR_TYPE_PALETTE) {
715 716
            png_bytep alpha_ptr;
            int num_alpha;
717
            png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
718
            for (int i = 0; i < num_alpha; ++i) {
719 720 721 722
                palette_alpha[i] = alpha_ptr[i];
            }
        }
    }
723

724
    for (png_uint_32 y = 0; y < height; y++) {
725
        KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width);
726

727
        png_bytep row_pointer = reader->readLine();
728

Boudewijn Rempt's avatar
Boudewijn Rempt committed
729 730 731 732 733
        switch (color_type) {
        case PNG_COLOR_TYPE_GRAY:
        case PNG_COLOR_TYPE_GRAY_ALPHA:
            if (color_nb_bits == 16) {
                quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
734 735
                do {
                    quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
736 737
                    d[0] = *(src++);
                    if (hasalpha) {
738
                        d[1] = *(src++);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
739 740
                    } else {
                        d[1] = quint16_MAX;
741
                    }
742
                    if (transform) transform->transformInPlace(reinterpret_cast<quint8*>(d), reinterpret_cast<quint8*>(d), 1);
743
                } while (it->nextPixel());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
744 745
            } else  {
                KisPNGReadStream stream(row_pointer, color_nb_bits);
746 747
                do {
                    quint8 *d = it->rawData();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
748 749
                    d[0] = (quint8)(stream.nextValue() * coeff);
                    if (hasalpha) {
750
                        d[1] = (quint8)(stream.nextValue() * coeff);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
751 752
                    } else {
                        d[1] = UCHAR_MAX;
753
                    }
754
                    if (transform) transform->transformInPlace(d, d, 1);
755
                } while (it->nextPixel());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
756
            }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
757
            // FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits"
Boudewijn Rempt's avatar
Boudewijn Rempt committed
758 759 760 761 762
            break;
        case PNG_COLOR_TYPE_RGB:
        case PNG_COLOR_TYPE_RGB_ALPHA:
            if (color_nb_bits == 16) {
                quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
763 764
                do {
                    quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
765 766 767 768 769
                    d[2] = *(src++);
                    d[1] = *(src++);
                    d[0] = *(src++);
                    if (hasalpha) d[3] = *(src++);
                    else d[3] = quint16_MAX;
770
                    if (transform) transform->transformInPlace(reinterpret_cast<quint8 *>(d), reinterpret_cast<quint8*>(d), 1);
771
                } while (it->nextPixel());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
772 773
            } else {
                KisPNGReadStream stream(row_pointer, color_nb_bits);
774 775
                do {
                    quint8 *d = it->rawData();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
776 777 778 779 780
                    d[2] = (quint8)(stream.nextValue() * coeff);
                    d[1] = (quint8)(stream.nextValue() * coeff);
                    d[0] = (quint8)(stream.nextValue() * coeff);
                    if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff);
                    else d[3] = UCHAR_MAX;
781
                    if (transform) transform->transformInPlace(d, d, 1);
782
                } while (it->nextPixel());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
783 784 785 786
            }
            break;
        case PNG_COLOR_TYPE_PALETTE: {
            KisPNGReadStream stream(row_pointer, color_nb_bits);
787 788
            do {
                quint8 *d = it->rawData();
Boudewijn Rempt's avatar
Boudewijn Rempt committed
789 790 791 792 793 794 795 796 797 798 799
                quint8 index = stream.nextValue();
                quint8 alpha = palette_alpha[ index ];
                if (alpha == 0) {
                    memset(d, 0, 4);
                } else {
                    png_color c = palette[ index ];
                    d[2] = c.red;
                    d[1] = c.green;
                    d[0] = c.blue;
                    d[3] = alpha;
                }
800
            } while (it->nextPixel());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
801
        }
802
            break;
Boudewijn Rempt's avatar
Boudewijn Rempt committed
803 804
        default:
            return KisImageBuilder_RESULT_UNSUPPORTED;
805 806
        }
    }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
807
    m_image->addNode(layer.data(), m_image->rootLayer().data());
808 809

    png_read_end(png_ptr, end_info);
810
    iod->close();
811 812 813 814 815 816 817

    // Freeing memory
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
    return KisImageBuilder_RESULT_OK;

}

818
KisImageBuilder_Result KisPNGConverter::buildImage(const QString &filename)
819
{
820
    m_path = filename;
821

822
    QFile fp(filename);
823
    if (fp.exists()) {
824 825 826 827 828
        if (!fp.open(QIODevice::ReadOnly)) {
            dbgFile << "Failed to open PNG File";
            return (KisImageBuilder_RESULT_FAILURE);
        }

829
        return buildImage(&fp);
830
    }
831
    return (KisImageBuilder_RESULT_NOT_EXIST);
832 833 834 835

}


836
KisImageSP KisPNGConverter::image()
837
{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
838
    return m_image;
839 840
}

841
bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData)
842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859
{
    if (store->open(filename)) {
        KoStoreDevice io(store);
        if (!io.open(QIODevice::WriteOnly)) {
            dbgFile << "Could not open for writing:" << filename;
            return false;
        }
        KisPNGConverter pngconv(0);
        vKisAnnotationSP_it annotIt = 0;
        KisMetaData::Store* metaDataStore = 0;
        if (metaData) {
            metaDataStore = new KisMetaData::Store(*metaData);
        }
        KisPNGOptions options;
        options.compression = 0;
        options.interlace = false;
        options.tryToSaveAsIndexed = false;
        options.alpha = true;
860 861 862 863 864 865 866 867
        options.saveSRGBProfile = false;

        if (dev->colorSpace()->id() != "RGBA") {
            dev = new KisPaintDevice(*dev.data());
            KUndo2Command *cmd = dev->convertTo(KoColorSpaceRegistry::instance()->rgb8());
            delete cmd;
        }

868
        bool success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore);
869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
        if (success != KisImageBuilder_RESULT_OK) {
            dbgFile << "Saving PNG failed:" << filename;
            delete metaDataStore;
            return false;
        }
        delete metaDataStore;
        io.close();
        if (!store->close()) {
            return false;
        }
    } else {
        dbgFile << "Opening of data file failed :" << filename;
        return false;
    }
    return true;

}

887

888
KisImageBuilder_Result 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)
889
{
890
    dbgFile << "Start writing PNG File " << filename;
891
    // Open a QIODevice for writing
892
    QFile fp (filename);
893 894 895 896 897
    if (!fp.open(QIODevice::WriteOnly)) {
        dbgFile << "Failed to open PNG File for writing";
        return (KisImageBuilder_RESULT_FAILURE);
    }

898 899
    KisImageBuilder_Result result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData);

900
    return result;
Cyrille Berger's avatar
Cyrille Berger committed
901 902
}

903
KisImageBuilder_Result 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)
Cyrille Berger's avatar
Cyrille Berger committed
904 905 906 907
{
    if (!device)
        return KisImageBuilder_RESULT_INVALID_ARG;

908 909 910
    if (!options.alpha) {
        KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace());
        KoColor c(options.transparencyFillColor, device->colorSpace());
911
        tmp->fill(imageRect, c);
912
        KisPainter gc(tmp);
913
        gc.bitBlt(imageRect.topLeft(), device, imageRect);
914 915 916 917
        gc.end();
        device = tmp;
    }

918 919
    if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID
            || device->colorSpace()->colorDepthId() == Float32BitsColorDepthID
920 921
            || device->colorSpace()->colorDepthId() == Float64BitsColorDepthID
            || options.saveAsHDR) {
922

923 924 925 926 927
        const KoColorSpace *dstCS =
            KoColorSpaceRegistry::instance()->colorSpace(
                device->colorSpace()->colorModelId().id(),
                Integer16BitsColorDepthID.id(),
                device->colorSpace()->profile());
928

929 930 931 932 933 934 935
        if (options.saveAsHDR) {
            dstCS =
                KoColorSpaceRegistry::instance()->colorSpace(
                        RGBAColorModelID.id(),
                        Integer16BitsColorDepthID.id(),
                        KoColorSpaceRegistry::instance()->p2020PQProfile());
        }
936

937 938 939
        KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace());
        tmp->makeCloneFromRough(device, imageRect);
        delete tmp->convertTo(dstCS);
940

941
        device = tmp;
942 943 944 945 946

    }

    KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.forceSRGB) {
        options.forceSRGB = false;
947
    }
948 949 950 951 952

    KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.tryToSaveAsIndexed) {
        options.tryToSaveAsIndexed = false;
    }

953 954
    QStringList colormodels = QStringList() << RGBAColorModelID.id() << GrayAColorModelID.id();
    if (options.forceSRGB || !colormodels.contains(device->colorSpace()->colorModelId().id())) {
955 956
        const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), device->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)");
        device = new KisPaintDevice(*device);
957 958
        KUndo2Command *cmd = device->convertTo(cs);
        delete cmd;
959 960
    }

961
    // Initialize structures
962
    png_structp png_ptr =  png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
963
    if (!png_ptr) {
964 965 966
        return (KisImageBuilder_RESULT_FAILURE);
    }

967
#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
968
    png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
969 970 971
#endif


972 973 974 975
#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
    png_set_check_for_invalid_index(png_ptr, 0);
#endif

976
    png_infop info_ptr = png_create_info_struct(png_ptr);