// ***************************************************************** -*- C++ -*-
/*
 * Copyright (C) 2004-2018 Exiv2 authors
 * This program is part of the Exiv2 distribution.
 *
 * 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, 5th Floor, Boston, MA 02110-1301 USA.
 */
/*
  File:    pngimage.cpp
 */
// *****************************************************************************
// included header files
#include "config.h"

#ifdef   EXV_HAVE_LIBZ
#include "pngchunk_int.hpp"
#include "pngimage.hpp"
#include "jpgimage.hpp"
#include "tiffimage.hpp"
#include "image.hpp"
#include "image_int.hpp"
#include "basicio.hpp"
#include "error.hpp"
#include "enforce.hpp"
#include "futils.hpp"
#include "types.hpp"

// + standard includes
#include <string>
#include <iterator>
#include <cstring>
#include <iostream>
#include <cassert>

#include <zlib.h>     // To uncompress IccProfiles
// Signature from front of PNG file
const unsigned char pngSignature[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };

const unsigned char pngBlank[] = { 0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,
                                   0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x08,0x02,0x00,0x00,0x00,0x90,0x77,0x53,
                                   0xde,0x00,0x00,0x00,0x01,0x73,0x52,0x47,0x42,0x00,0xae,0xce,0x1c,0xe9,0x00,0x00,
                                   0x00,0x09,0x70,0x48,0x59,0x73,0x00,0x00,0x0b,0x13,0x00,0x00,0x0b,0x13,0x01,0x00,
                                   0x9a,0x9c,0x18,0x00,0x00,0x00,0x0c,0x49,0x44,0x41,0x54,0x08,0xd7,0x63,0xf8,0xff,
                                   0xff,0x3f,0x00,0x05,0xfe,0x02,0xfe,0xdc,0xcc,0x59,0xe7,0x00,0x00,0x00,0x00,0x49,
                                   0x45,0x4e,0x44,0xae,0x42,0x60,0x82
                                 };

namespace
{
    inline bool compare(const char* str, const Exiv2::DataBuf& buf, size_t length)
    {
        assert(strlen(str) <= length);
        return memcmp(str, buf.pData_, std::min(static_cast<long>(length), buf.size_)) == 0;
    }
}  // namespace

// *****************************************************************************
// class member definitions
namespace Exiv2 {

    using namespace Internal;

    PngImage::PngImage(BasicIo::AutoPtr io, bool create)
            : Image(ImageType::png, mdExif | mdIptc | mdXmp | mdComment, io)
    {
        if (create)
        {
            if (io_->open() == 0)
            {
#ifdef EXIV2_DEBUG_MESSAGES
                std::cerr << "Exiv2::PngImage:: Creating PNG image to memory\n";
#endif
                IoCloser closer(*io_);
                if (io_->write(pngBlank, sizeof(pngBlank)) != sizeof(pngBlank))
                {
#ifdef EXIV2_DEBUG_MESSAGES
                    std::cerr << "Exiv2::PngImage:: Failed to create PNG image on memory\n";
#endif
                }
            }
        }
    } // PngImage::PngImage

    std::string PngImage::mimeType() const
    {
        return "image/png";
    }

    static bool zlibToDataBuf(const byte* bytes,long length, DataBuf& result)
    {
        uLongf uncompressedLen = length * 2; // just a starting point
        int    zlibResult;

        do {
            result.alloc(uncompressedLen);
            zlibResult = uncompress((Bytef*)result.pData_,&uncompressedLen,bytes,length);
            // if result buffer is large than necessary, redo to fit perfectly.
            if (zlibResult == Z_OK && (long) uncompressedLen < result.size_ ) {
                result.free();

                result.alloc(uncompressedLen);
                zlibResult = uncompress((Bytef*)result.pData_,&uncompressedLen,bytes,length);
            }
            if (zlibResult == Z_BUF_ERROR) {
                // the uncompressed buffer needs to be larger
                result.free();

                // Sanity - never bigger than 16mb
                if  (uncompressedLen > 16*1024*1024) zlibResult = Z_DATA_ERROR;
                else uncompressedLen *= 2;
            }
        } while (zlibResult == Z_BUF_ERROR);

        return zlibResult == Z_OK ;
    }

    static bool zlibToCompressed(const byte* bytes,long length, DataBuf& result)
    {
        uLongf compressedLen = length; // just a starting point
        int    zlibResult;

        do {
            result.alloc(compressedLen);
            zlibResult = compress((Bytef*)result.pData_,&compressedLen,bytes,length);
            if (zlibResult == Z_BUF_ERROR) {
                // the compressedArray needs to be larger
                result.free();
                compressedLen *= 2;
            } else {
                result.free();
                result.alloc(compressedLen);
                zlibResult = compress((Bytef*)result.pData_,&compressedLen,bytes,length);
            }
        } while (zlibResult == Z_BUF_ERROR);

        return zlibResult == Z_OK ;
    }

    static bool tEXtToDataBuf(const byte* bytes,long length,DataBuf& result)
    {
        static const char* hexdigits = "0123456789ABCDEF";
        static int         value   [256] ;
        static bool        bFirst = true ;
        if ( bFirst ) {
            for ( int i = 0 ; i < 256 ; i++ )
                value[i] = 0;
            for ( int i = 0 ; i < 16 ; i++ ) {
                value[tolower(hexdigits[i])]=i+1;
                value[toupper(hexdigits[i])]=i+1;
            }
            bFirst = false;
        }

        // calculate length and allocate result;
        // count: number of \n in the header
        long        count=0;
        // p points to the current position in the array bytes
        const byte* p = bytes ;

        // header is '\nsomething\n number\n hex'
        // => increment p until it points to the byte after the last \n
        //    p must stay within bounds of the bytes array!
        while ((count < 3) && (p - bytes < length)) {
            // length is later used for range checks of p => decrement it for each increment of p
            --length;
            if ( *p++ == '\n' ) {
                count++;
            }
        }
        for ( long i = 0 ; i < length ; i++ )
            if ( value[p[i]] )
                ++count;
        result.alloc((count+1)/2) ;

        // hex to binary
        count   = 0 ;
        byte* r = result.pData_;
        int   n = 0 ; // nibble
        for ( long i = 0 ; i < length ; i++ ) {
            if ( value[p[i]] ) {
                int v = value[p[i]]-1 ;
                if ( ++count % 2 ) n = v*16 ; // leading digit
                else *r++ =        n + v    ; // trailing
            }
        }
        return true;
    }

    std::string upper(const std::string& str)
    {
        std::string result;
        transform(str.begin(), str.end(), std::back_inserter(result), toupper);
        return result;
    }

    std::string::size_type findi(const std::string& str, const std::string& substr)
    {
        return upper(str).find(upper(substr) );
    }

    void PngImage::printStructure(std::ostream& out, PrintStructureOption option, int depth)
    {
        if (io_->open() != 0) {
            throw Error(kerDataSourceOpenFailed, io_->path(), strError());
        }
        if (!isPngType(*io_, true)) {
            throw Error(kerNotAnImage, "PNG");
        }

        char    chType[5];
        chType[0]=0;
        chType[4]=0;

        if ( option == kpsBasic || option == kpsXMP || option == kpsIccProfile || option == kpsRecursive ) {

            const std::string xmpKey  = "XML:com.adobe.xmp";
            const std::string exifKey = "Raw profile type exif";
            const std::string app1Key = "Raw profile type APP1";
            const std::string iptcKey = "Raw profile type iptc";
            const std::string iccKey  = "icc";
            const std::string softKey = "Software";
            const std::string commKey = "Comment";
            const std::string descKey = "Description";

            bool bPrint = option == kpsBasic || option == kpsRecursive ;
            if ( bPrint ) {
                out << "STRUCTURE OF PNG FILE: " << io_->path() << std::endl;
                out << " address | chunk |  length | data                           | checksum" << std::endl;
            }

            const long imgSize = (long) io_->size();
            DataBuf    cheaderBuf(8);

            while( !io_->eof() && ::strcmp(chType,"IEND") ) {
                size_t address = io_->tell();

                std::memset(cheaderBuf.pData_, 0x0, cheaderBuf.size_);
                long bufRead = io_->read(cheaderBuf.pData_, cheaderBuf.size_);
                if (io_->error()) throw Error(kerFailedToReadImageData);
                if (bufRead != cheaderBuf.size_) throw Error(kerInputDataReadFailed);

                // Decode chunk data length.
                const uint32_t dataOffset = Exiv2::getULong(cheaderBuf.pData_, Exiv2::bigEndian);
                for (int i = 4; i < 8; i++) {
                    chType[i-4]=cheaderBuf.pData_[i];
                }

                // test that we haven't hit EOF, or wanting to read excessive data
                long restore = io_->tell();
                if(  restore == -1
                ||  dataOffset > uint32_t(0x7FFFFFFF)
                ||  static_cast<long>(dataOffset) > imgSize - restore
                ){
                    throw Exiv2::Error(kerFailedToReadImageData);
                }

                DataBuf   buff(dataOffset);
                bufRead = io_->read(buff.pData_,dataOffset);
                enforce(bufRead == static_cast<long>(dataOffset), kerFailedToReadImageData);
                io_->seek(restore, BasicIo::beg);

                // format output
                const int    iMax = 30 ;
                const uint32_t blen = dataOffset > iMax ? iMax : dataOffset ;
                std::string dataString = "";
                // if blen == 0 => slice construction fails
                if (blen > 0) {
                    std::stringstream ss;
                    ss << Internal::binaryToString(makeSlice(buff, 0, blen));
                    dataString = ss.str();
                }
                while (      dataString.size() < iMax ) dataString += ' ';
                dataString = dataString.substr(0,iMax);

                if ( bPrint ) {
                    io_->seek(dataOffset, BasicIo::cur);// jump to checksum
                    byte checksum[4];
                    bufRead = io_->read(checksum,4);
                    enforce(bufRead == 4, kerFailedToReadImageData);
                    io_->seek(restore, BasicIo::beg)   ;// restore file pointer

                    out << Internal::stringFormat("%8d | %-5s |%8d | "
                                                  ,(uint32_t)address, chType,dataOffset)
                        << dataString
                        << Internal::stringFormat(" | 0x%02x%02x%02x%02x"
                                                   ,checksum[0],checksum[1],checksum[2],checksum[3])
                        << std::endl;
                }

                // chunk type
                bool tEXt  = std::strcmp(chType,"tEXt")== 0;
                bool zTXt  = std::strcmp(chType,"zTXt")== 0;
                bool iCCP  = std::strcmp(chType,"iCCP")== 0;
                bool iTXt  = std::strcmp(chType,"iTXt")== 0;

                // for XMP, ICC etc: read and format data
                bool bXMP  = option == kpsXMP        && findi(dataString,xmpKey)==0;
                bool bICC  = option == kpsIccProfile && findi(dataString,iccKey)==0;
                bool bExif = option == kpsRecursive  &&(findi(dataString,exifKey)==0 || findi(dataString,app1Key)==0);
                bool bIptc = option == kpsRecursive  && findi(dataString,iptcKey)==0;
                bool bSoft = option == kpsRecursive  && findi(dataString,softKey)==0;
                bool bComm = option == kpsRecursive  && findi(dataString,commKey)==0;
                bool bDesc = option == kpsRecursive  && findi(dataString,descKey)==0;
                bool bDump = bXMP || bICC || bExif || bIptc || bSoft || bComm || bDesc ;

                if( bDump ) {
                    DataBuf   dataBuf;
                    byte*     data   = new byte[dataOffset+1];
                    data[dataOffset] = 0;
                    bufRead = io_->read(data,dataOffset);
                    enforce(bufRead == static_cast<long>(dataOffset), kerFailedToReadImageData);
                    io_->seek(restore, BasicIo::beg);
                    uint32_t  name_l = (uint32_t) std::strlen((const char*)data)+1; // leading string length
                    enforce(name_l <= dataOffset, kerCorruptedMetadata);

                    uint32_t  start  = name_l;
                    bool      bLF    = false;

                    // decode the chunk
                    bool bGood = false;
                    if ( tEXt ) {
                        bGood = tEXtToDataBuf(data+name_l,dataOffset-name_l,dataBuf);
                    }
                    if ( zTXt || iCCP ) {
                        bGood = zlibToDataBuf(data+name_l+1,dataOffset-name_l-1,dataBuf); // +1 = 'compressed' flag
                    }
                    if ( iTXt ) {
                        bGood = (start+3) < dataOffset ;    // good if not a nul chunk
                    }

                    // format is content dependent
                    if ( bGood ) {
                        if ( bXMP ) {
                            while ( !data[start] && start < dataOffset) start++; // skip leading nul bytes
                            out <<  data+start;             // output the xmp
                        }

                        if ( bExif || bIptc ) {
                            DataBuf parsedBuf = PngChunk::readRawProfile(dataBuf,tEXt);
#if EXIV2_DEBUG_MESSAGES
                            std::cerr << Exiv2::Internal::binaryToString(makeSlice(parsedBuf.pData_, parsedBuf.size_>50?50:parsedBuf.size_,0)) << std::endl;
#endif
                            if ( parsedBuf.size_ ) {
                                if ( bExif ) {
                                    // create memio object with the data, then print the structure
                                    BasicIo::AutoPtr p = BasicIo::AutoPtr(new MemIo(parsedBuf.pData_+6,parsedBuf.size_-6));
                                    printTiffStructure(*p,out,option,depth);
                                }
                                if ( bIptc ) {
                                    IptcData::printStructure(out, makeSlice(parsedBuf.pData_, 0, parsedBuf.size_), depth);
                                }
                            }
                        }

                        if ( bSoft && dataBuf.size_ > 0) {
                            DataBuf     s(dataBuf.size_+1);               // allocate buffer with an extra byte
                            memcpy(s.pData_,dataBuf.pData_,dataBuf.size_);// copy in the dataBuf
                            s.pData_[dataBuf.size_] = 0 ;                 // nul terminate it
                            const char* str = (const char*) s.pData_;     // give it name
                            out << Internal::indent(depth) << (const char*) buff.pData_ << ": " << str ;
                            bLF=true;
                        }

                        if ( bICC || bComm ) {
                            out.write((const char*) dataBuf.pData_,dataBuf.size_);
                            bLF = bComm ;
                        }

                        if ( bDesc && iTXt ) {
                            DataBuf decoded = PngChunk::decodeTXTChunk(buff,PngChunk::iTXt_Chunk );
                            out.write((const char*)decoded.pData_,decoded.size_);
                            bLF = true;
                        }

                        if ( bLF ) out << std::endl;
                    }
                    delete[] data;
                }
                io_->seek(dataOffset+4, BasicIo::cur);// jump past checksum
                if (io_->error()) throw Error(kerFailedToReadImageData);
            }
        }
    }

    void readChunk(DataBuf& buffer, BasicIo& io)
    {
#ifdef EXIV2_DEBUG_MESSAGES
        std::cout << "Exiv2::PngImage::readMetadata: Position: " << io.tell() << std::endl;
#endif
        long bufRead = io.read(buffer.pData_, buffer.size_);
        if (io.error()) {
            throw Error(kerFailedToReadImageData);
        }
        if (bufRead != buffer.size_) {
            throw Error(kerInputDataReadFailed);
        }
    }

    void PngImage::readMetadata()
    {
#ifdef EXIV2_DEBUG_MESSAGES
        std::cerr << "Exiv2::PngImage::readMetadata: Reading PNG file " << io_->path() << std::endl;
#endif
        if (io_->open() != 0)
        {
            throw Error(kerDataSourceOpenFailed, io_->path(), strError());
        }
        IoCloser closer(*io_);
        if (!isPngType(*io_, true)) {
            throw Error(kerNotAnImage, "PNG");
        }
        clearMetadata();

        const long imgSize = (long) io_->size();
        DataBuf cheaderBuf(8);       // Chunk header: 4 bytes (data size) + 4 bytes (chunk type).

        while(!io_->eof())
        {
            std::memset(cheaderBuf.pData_, 0x0, cheaderBuf.size_);
            readChunk(cheaderBuf, *io_); // Read chunk header.

            // Decode chunk data length.
            uint32_t chunkLength = Exiv2::getULong(cheaderBuf.pData_, Exiv2::bigEndian);
            long pos = io_->tell();
            if (pos == -1 ||
                chunkLength > uint32_t(0x7FFFFFFF) ||
                static_cast<long>(chunkLength) > imgSize - pos) {
                throw Exiv2::Error(kerFailedToReadImageData);
            }

            std::string chunkType(reinterpret_cast<char *>(cheaderBuf.pData_) + 4, 4);
#ifdef EXIV2_DEBUG_MESSAGES
            std::cout << "Exiv2::PngImage::readMetadata: chunk type: " << chunkType
                      << " length: " << chunkLength << std::endl;
#endif

            /// \todo analyse remaining chunks of the standard
            // Perform a chunk triage for item that we need.
            if (chunkType == "IEND" || chunkType == "IHDR" || chunkType == "tEXt" || chunkType == "zTXt" ||
                chunkType == "iTXt" || chunkType == "iCCP") {
                DataBuf chunkData(chunkLength);
                readChunk(chunkData, *io_);  // Extract chunk data.

                if (chunkType == "IEND") {
                    return;  // Last chunk found: we stop parsing.
                } else if (chunkType == "IHDR" && chunkData.size_ >= 8) {
                    PngChunk::decodeIHDRChunk(chunkData, &pixelWidth_, &pixelHeight_);
                } else if (chunkType == "tEXt") {
                    PngChunk::decodeTXTChunk(this, chunkData, PngChunk::tEXt_Chunk);
                } else if (chunkType == "zTXt") {
                    PngChunk::decodeTXTChunk(this, chunkData, PngChunk::zTXt_Chunk);
                } else if (chunkType == "iTXt") {
                    PngChunk::decodeTXTChunk(this, chunkData, PngChunk::iTXt_Chunk);
                } else if (chunkType == "iCCP") {
                    // The ICC profile name can vary from 1-79 characters.
                    uint32_t iccOffset = 0;
                    do {
                      enforce(iccOffset < 80 && iccOffset < chunkLength,
                              Exiv2::kerCorruptedMetadata);
                    } while(chunkData.pData_[iccOffset++] != 0x00);

                    profileName_ = std::string(reinterpret_cast<char *>(chunkData.pData_), iccOffset-1);
                    ++iccOffset; // +1 = 'compressed' flag
                    enforce(iccOffset <= chunkLength, Exiv2::kerCorruptedMetadata);

                    zlibToDataBuf(chunkData.pData_ + iccOffset, chunkLength - iccOffset, iccProfile_);
#ifdef EXIV2_DEBUG_MESSAGES
                    std::cout << "Exiv2::PngImage::readMetadata: profile name: " << profileName_ << std::endl;
                    std::cout << "Exiv2::PngImage::readMetadata: iccProfile.size_ (uncompressed) : "
                              << iccProfile_.size_ << std::endl;
#endif
                }

                // Set chunkLength to 0 in case we have read a supported chunk type. Otherwise, we need to seek the
                // file to the next chunk position.
                chunkLength = 0;
            }


            // Move to the next chunk: chunk data size + 4 CRC bytes.
#ifdef EXIV2_DEBUG_MESSAGES
            std::cout << "Exiv2::PngImage::readMetadata: Seek to offset: " << chunkLength + 4 << std::endl;
#endif
            io_->seek(chunkLength + 4 , BasicIo::cur);
            if (io_->error() || io_->eof()) {
                throw Error(kerFailedToReadImageData);
            }
        }
    } // PngImage::readMetadata

    void PngImage::writeMetadata()
    {
        if (io_->open() != 0)
        {
            throw Error(kerDataSourceOpenFailed, io_->path(), strError());
        }
        IoCloser closer(*io_);
        BasicIo::AutoPtr tempIo(new MemIo);
        assert (tempIo.get() != 0);

        doWriteMetadata(*tempIo); // may throw
        io_->close();
        io_->transfer(*tempIo); // may throw

    } // PngImage::writeMetadata

    void PngImage::doWriteMetadata(BasicIo& outIo)
    {
        if (!io_->isopen()) throw Error(kerInputDataReadFailed);
        if (!outIo.isopen()) throw Error(kerImageWriteFailed);

#ifdef EXIV2_DEBUG_MESSAGES
        std::cout << "Exiv2::PngImage::doWriteMetadata: Writing PNG file " << io_->path() << "\n";
        std::cout << "Exiv2::PngImage::doWriteMetadata: tmp file created " << outIo.path() << "\n";
#endif

        if (!isPngType(*io_, true)) {
            throw Error(kerNoImageInInputData);
        }

        // Write PNG Signature.
        if (outIo.write(pngSignature, 8) != 8) throw Error(kerImageWriteFailed);

        DataBuf cheaderBuf(8);       // Chunk header : 4 bytes (data size) + 4 bytes (chunk type).

        while(!io_->eof())
        {
            // Read chunk header.

            std::memset(cheaderBuf.pData_, 0x00, cheaderBuf.size_);
            long bufRead = io_->read(cheaderBuf.pData_, cheaderBuf.size_);
            if (io_->error()) throw Error(kerFailedToReadImageData);
            if (bufRead != cheaderBuf.size_) throw Error(kerInputDataReadFailed);

            // Decode chunk data length.

            uint32_t dataOffset = getULong(cheaderBuf.pData_, bigEndian);
            if (dataOffset > 0x7FFFFFFF) throw Exiv2::Error(kerFailedToReadImageData);

            // Read whole chunk : Chunk header + Chunk data (not fixed size - can be null) + CRC (4 bytes).

            DataBuf chunkBuf(8 + dataOffset + 4);                     // Chunk header (8 bytes) + Chunk data + CRC (4 bytes).
            memcpy(chunkBuf.pData_, cheaderBuf.pData_, 8);            // Copy header.
            bufRead = io_->read(chunkBuf.pData_ + 8, dataOffset + 4); // Extract chunk data + CRC
            if (io_->error()) throw Error(kerFailedToReadImageData);
            if (bufRead != (long)(dataOffset + 4)) throw Error(kerInputDataReadFailed);

            char szChunk[5];
            memcpy(szChunk,cheaderBuf.pData_ + 4,4);
            szChunk[4]  = 0;

            if (!memcmp(cheaderBuf.pData_ + 4, "IEND", 4))
            {
                // Last chunk found: we write it and done.
#ifdef EXIV2_DEBUG_MESSAGES
                std::cout << "Exiv2::PngImage::doWriteMetadata: Write IEND chunk (length: " << dataOffset << ")\n";
#endif
                if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(kerImageWriteFailed);
                return;
            }
            else if (!memcmp(cheaderBuf.pData_ + 4, "IHDR", 4))
            {
#ifdef EXIV2_DEBUG_MESSAGES
                std::cout << "Exiv2::PngImage::doWriteMetadata: Write IHDR chunk (length: " << dataOffset << ")\n";
#endif
                if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(kerImageWriteFailed);

                // Write all updated metadata here, just after IHDR.
                if (!comment_.empty())
                {
                    // Update Comment data to a new PNG chunk
                    std::string chunk = PngChunk::makeMetadataChunk(comment_, mdComment);
                    if (outIo.write((const byte*)chunk.data(), static_cast<long>(chunk.size())) != (long)chunk.size())
                    {
                        throw Error(kerImageWriteFailed);
                    }
                }

                if (exifData_.count() > 0)
                {
                    // Update Exif data to a new PNG chunk
                    Blob blob;
                    ExifParser::encode(blob, littleEndian, exifData_);
                    if (blob.size() > 0)
                    {
                        static const char exifHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
                        std::string rawExif =   std::string(exifHeader, 6)
                                              + std::string((const char*)&blob[0], blob.size());
                        std::string chunk = PngChunk::makeMetadataChunk(rawExif, mdExif);
                        if (outIo.write((const byte*)chunk.data(), static_cast<long>(chunk.size())) != (long)chunk.size())
                        {
                            throw Error(kerImageWriteFailed);
                        }
                    }
                }

                if (iptcData_.count() > 0)
                {
                    // Update IPTC data to a new PNG chunk
                    DataBuf newPsData = Photoshop::setIptcIrb(0, 0, iptcData_);
                    if (newPsData.size_ > 0)
                    {
                        std::string rawIptc((const char*)newPsData.pData_, newPsData.size_);
                        std::string chunk = PngChunk::makeMetadataChunk(rawIptc, mdIptc);
                        if (outIo.write((const byte*)chunk.data(), static_cast<long>(chunk.size())) != (long)chunk.size())
                        {
                            throw Error(kerImageWriteFailed);
                        }
                    }
                }

                if ( iccProfileDefined() ) {
                    DataBuf compressed;
                    if ( zlibToCompressed(iccProfile_.pData_,iccProfile_.size_,compressed) ) {

                        const byte* nullComp = (const byte*) "\0\0";
                        const byte*  type    = (const byte*) "iCCP";
                        const uint32_t nameLength  = static_cast<uint32_t>(profileName_.size());
                        const uint32_t chunkLength = nameLength + 2 + compressed.size_ ;
                        byte     length[4];
                        ul2Data (length,chunkLength,bigEndian);

                        // calculate CRC
                        uLong   tmp = crc32(0L, Z_NULL, 0);
                        tmp         = crc32(tmp, (const Bytef*)type, 4);
                        tmp         = crc32(tmp, (const Bytef*)profileName_.data(), nameLength);
                        tmp         = crc32(tmp, (const Bytef*)nullComp, 2);
                        tmp         = crc32(tmp, (const Bytef*)compressed.pData_,compressed.size_);
                        byte    crc[4];
                        ul2Data(crc, tmp, bigEndian);

                        if( outIo.write(length, 4) != 4
                        ||  outIo.write(type, 4) != 4
                        ||  outIo.write(reinterpret_cast<const byte*>(profileName_.data()), nameLength) != nameLength
                        ||  outIo.write(nullComp,2) != 2
                        ||  outIo.write (compressed.pData_,compressed.size_) != compressed.size_
                        ||  outIo.write(crc,4)            != 4
                        ){
                            throw Error(kerImageWriteFailed);
                        }
#ifdef EXIV2_DEBUG_MESSAGES
                        std::cout << "Exiv2::PngImage::doWriteMetadata: build iCCP"
                        << " chunk (length: " << compressed.size_ + chunkLength << ")" << std::endl;
#endif
                    }
                }

                if (writeXmpFromPacket() == false) {
                    if (XmpParser::encode(xmpPacket_, xmpData_) > 1) {
#ifndef SUPPRESS_WARNINGS
                        EXV_ERROR << "Failed to encode XMP metadata.\n";
#endif
                    }
                }
                if (xmpPacket_.size() > 0) {
                    // Update XMP data to a new PNG chunk
                    std::string chunk = PngChunk::makeMetadataChunk(xmpPacket_, mdXmp);
                    if (outIo.write((const byte*)chunk.data(), static_cast<long>(chunk.size())) != (long)chunk.size()) {
                        throw Error(kerImageWriteFailed);
                    }
                }
            }
            else if (!memcmp(cheaderBuf.pData_ + 4, "tEXt", 4) ||
                     !memcmp(cheaderBuf.pData_ + 4, "zTXt", 4) ||
                     !memcmp(cheaderBuf.pData_ + 4, "iTXt", 4) ||
                     !memcmp(cheaderBuf.pData_ + 4, "iCCP", 4))
            {
                DataBuf key = PngChunk::keyTXTChunk(chunkBuf, true);
                if (compare("Raw profile type exif", key, 21) ||
                    compare("Raw profile type APP1", key, 21) ||
                    compare("Raw profile type iptc", key, 21) ||
                    compare("Raw profile type xmp",  key, 20) ||
                    compare("XML:com.adobe.xmp",     key, 17) ||
                    compare("icc",                   key,  3) || // see test/data/imagemagick.png
                    compare("ICC",                   key,  3) ||
                    compare("Description",           key, 11))
                {
#ifdef EXIV2_DEBUG_MESSAGES
                    std::cout << "Exiv2::PngImage::doWriteMetadata: strip " << szChunk
                              << " chunk (length: " << dataOffset << ")" << std::endl;
#endif
                }
                else
                {
#ifdef EXIV2_DEBUG_MESSAGES
                    std::cout << "Exiv2::PngImage::doWriteMetadata: write " << szChunk
                              << " chunk (length: " << dataOffset << ")" << std::endl;
#endif
                    if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(kerImageWriteFailed);
                }
            }
            else
            {
                // Write all others chunk as well.
#ifdef EXIV2_DEBUG_MESSAGES
                std::cout << "Exiv2::PngImage::doWriteMetadata:  copy " << szChunk
                          << " chunk (length: " << dataOffset << ")" << std::endl;
#endif
                if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(kerImageWriteFailed);

            }
        }

    } // PngImage::doWriteMetadata

    // *************************************************************************
    // free functions
    Image::AutoPtr newPngInstance(BasicIo::AutoPtr io, bool create)
    {
        Image::AutoPtr image(new PngImage(io, create));
        if (!image->good())
        {
            image.reset();
        }
        return image;
    }

    bool isPngType(BasicIo& iIo, bool advance)
    {
        if (iIo.error() || iIo.eof()) {
            throw Error(kerInputDataReadFailed);
        }
        const int32_t len = 8;
        byte buf[len];
        iIo.read(buf, len);
        if (iIo.error() || iIo.eof())
        {
            return false;
        }
        int rc = memcmp(buf, pngSignature, 8);
        if (!advance || rc != 0)
        {
            iIo.seek(-len, BasicIo::cur);
        }

        return rc == 0;
    }
}                                       // namespace Exiv2
#endif
