From 3fe5ebb8ca702635ef5cbdb91ff75d4fa1085fa7 Mon Sep 17 00:00:00 2001 From: HumanDynamo Date: Sun, 27 Jul 2008 09:23:08 +0000 Subject: [PATCH] PNG file format parser. - Refactoring code: main loop to parse PNG chunk contents go to pngimage class. pngchunk only play with chunk contents - Implement PNG writting mode : all metadata are supported: * UTF8 comment as "Description" iTXt chunk (compressed) * XMP data as iTXt chunk (uncompressed as XMP spec instruction) * IPTC data as zTXt chunk (compressed and encoded as ImageMagick method) * EXIF data as zTXt chunk (compressed and encoded as ImageMagick method) Note: writting mode resample metadata chunk to follow list given behind. There are several ways where other programs writte metadata in other place. For ex : digiKam 0.9.x or ImageMagick 5.x writte Exif and Iptc to an tEXt chunk (uncompressed) ImageMagick 5.x writte Xmp to an uncompressed tEXt chunk ImageMagick 6.x writte Xmp to a compressed zTXt chunk. --- src/pngchunk.cpp | 645 ++++++++++++++++++++++++++--------------------- src/pngchunk.hpp | 157 ++++++++---- src/pngimage.cpp | 312 ++++++++++++++++++++--- src/pngimage.hpp | 38 ++- 4 files changed, 765 insertions(+), 387 deletions(-) diff --git a/src/pngchunk.cpp b/src/pngchunk.cpp index bcf0e66f..a7856122 100644 --- a/src/pngchunk.cpp +++ b/src/pngchunk.cpp @@ -39,12 +39,7 @@ EXIV2_RCSID("@(#) $Id: pngchunk.cpp 823 2006-06-23 07:35:00Z cgilles $") //#define DEBUG 1 -// some defines to make it easier -#define PNG_CHUNK_TYPE(data, index) &data[index+4] -#define PNG_CHUNK_DATA(data, index, offset) data[8+index+offset] -#define PNG_CHUNK_HEADER_SIZE 12 - -// To uncompress text chunck +// To uncompress or compress text chunk #include #include "pngchunk.hpp" @@ -64,200 +59,144 @@ EXIV2_RCSID("@(#) $Id: pngchunk.cpp 823 2006-06-23 07:35:00Z cgilles $") URLs to find informations about PNG chunks : -tEXt and zTXt chuncks : http://www.vias.org/pngguide/chapter11_04.html -iTXt chunck : http://www.vias.org/pngguide/chapter11_05.html -PNG tags : http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html#TextualData +tEXt and zTXt chunks : http://www.vias.org/pngguide/chapter11_04.html +iTXt chunk : http://www.vias.org/pngguide/chapter11_05.html +PNG tags : http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html#TextualData */ -// ***************************************************************************** -// local declarations -namespace { - // Return the checked length of a PNG chunk - long chunkLength(const Exiv2::byte* pData, long index); -} - // ***************************************************************************** // class member definitions -namespace Exiv2 { - - void PngChunk::decode(Image* pImage, - const byte* pData, - long size, - int* outWidth, - int* outHeight) +namespace Exiv2 +{ + void PngChunk::decodeIHDRChunk(const DataBuf& data, + int* outWidth, + int* outHeight) { - assert(pImage != 0); - assert(pData != 0); - assert(outWidth != 0); - assert(outHeight != 0); + // Extract image width and height from IHDR chunk. - long index = 8; + *outWidth = getLong((const byte*)data.pData_, bigEndian); + *outHeight = getLong((const byte*)data.pData_ + 4, bigEndian); - // extract width and height from IHDR chunk, which *must* be the first chunk in the PNG file - if (strncmp((const char *)PNG_CHUNK_TYPE(pData, index), "IHDR", 4) == 0) - { - *outWidth = getLong((const byte*)&PNG_CHUNK_DATA(pData, index, 0), bigEndian); - *outHeight = getLong((const byte*)&PNG_CHUNK_DATA(pData, index, 4), bigEndian); - } + } // PngChunk::decodeIHDRChunk - // look for a tEXt chunk - index += chunkLength(pData, index) + PNG_CHUNK_HEADER_SIZE; - - while(index < size-PNG_CHUNK_HEADER_SIZE) - { - while (index < size-PNG_CHUNK_HEADER_SIZE && - strncmp((char*)PNG_CHUNK_TYPE(pData, index), "tEXt", 4) && - strncmp((char*)PNG_CHUNK_TYPE(pData, index), "zTXt", 4) && - strncmp((char*)PNG_CHUNK_TYPE(pData, index), "iTXt", 4)) - { - if (!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "IEND", 4)) - throw Error(14); - - index += chunkLength(pData, index) + PNG_CHUNK_HEADER_SIZE; - } - - if (index < size-PNG_CHUNK_HEADER_SIZE) - { - // we found a tEXt, zTXt, or iTXt field - - // get the key, it's a null terminated string at the chunk start - const byte *key = &PNG_CHUNK_DATA(pData, index, 0); - - int keysize=0; - for ( ; key[keysize] != 0 ; keysize++) - { - // look if we reached the end of the file (it might be corrupted) - if (8+index+keysize >= size) - throw Error(14); - } - - DataBuf arr = parsePngChunk(pData, size, index, keysize); + void PngChunk::decodeTXTChunk(Image* pImage, + const DataBuf& data, + TxtChunkType type) + { + DataBuf key = keyTXTChunk(data); #ifdef DEBUG - std::cerr << "Exiv2::PngChunk::decode: Found PNG chunk: " - << std::string((const char*)key) << " :: " - << std::string((const char*)arr.pData_, 32) << "\n"; + std::cout << "Exiv2::PngChunk::decodeTXTChunk: TXT chunk key: " + << std::string((const char*)key.pData_) << "\n"; #endif + DataBuf arr = parseTXTChunk(data, key.size_, type); - parseChunkContent(pImage, key, arr); +#ifdef DEBUG + std::cout << "Exiv2::PngChunk::decodeTXTChunk: TXT chunk data: " + << std::string((const char*)arr.pData_, 32) << "\n"; +#endif + parseChunkContent(pImage, key.pData_, arr); - index += chunkLength(pData, index) + PNG_CHUNK_HEADER_SIZE; - } + } // PngChunk::decodeTXTChunk + + DataBuf PngChunk::keyTXTChunk(const DataBuf& data, bool stripHeader) + { + // From a tEXt, zTXt, or iTXt chunk, + // we get the key, it's a null terminated string at the chunk start + + const byte *key = data.pData_ + (stripHeader ? 8 : 0); + + // Find null string at end of key. + int keysize=0; + for ( ; key[keysize] != 0 ; keysize++) + { + // look if keysize is valid. + if (keysize >= data.size_) + throw Error(14); } - } // PngChunk::decode + return DataBuf(key, keysize); - DataBuf PngChunk::parsePngChunk(const byte* pData, long size, long& index, int keysize) + } // PngChunk::keyTXTChunk + + DataBuf PngChunk::parseTXTChunk(const DataBuf& data, + int keysize, + TxtChunkType type) { DataBuf arr; - if(!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "zTXt", 4)) + if(type == zTXt_Chunk) { // Extract a deflate compressed Latin-1 text chunk -#ifdef DEBUG - std::cerr << "Exiv2::PngChunk::parsePngChunk: We found a zTXt field\n"; -#endif // we get the compression method after the key - const byte* compressionMethod = &PNG_CHUNK_DATA(pData, index, keysize+1); + const byte* compressionMethod = data.pData_ + keysize + 1; if ( *compressionMethod != 0x00 ) { // then it isn't zlib compressed and we are sunk #ifdef DEBUG - std::cerr << "Exiv2::PngChunk::parsePngChunk: Non-standard zTXt compression method.\n"; + std::cerr << "Exiv2::PngChunk::parseTXTChunk: Non-standard zTXt compression method.\n"; #endif throw Error(14); } // compressed string after the compression technique spec - const byte* compressedText = &PNG_CHUNK_DATA(pData, index, keysize+2); - unsigned int compressedTextSize = getLong(&pData[index], bigEndian)-keysize-2; - - // security check, also considering overflow wraparound from the addition -- - // we may endup with a /smaller/ index if we wrap all the way around - long firstIndex = (long)(compressedText - pData); - long onePastLastIndex = firstIndex + compressedTextSize; - if ( onePastLastIndex > size || onePastLastIndex <= firstIndex) - throw Error(14); + const byte* compressedText = data.pData_ + keysize + 2; + unsigned int compressedTextSize = data.size_ - keysize - 2; zlibUncompress(compressedText, compressedTextSize, arr); } - else if (!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "tEXt", 4)) + else if(type == tEXt_Chunk) { // Extract a non-compressed Latin-1 text chunk -#ifdef DEBUG - std::cerr << "Exiv2::PngChunk::parsePngChunk: We found a tEXt field\n"; -#endif + // the text comes after the key, but isn't null terminated - const byte* text = &PNG_CHUNK_DATA(pData, index, keysize+1); - long textsize = getLong(&pData[index], bigEndian)-keysize-1; - - // security check, also considering overflow wraparound from the addition -- - // we may endup with a /smaller/ index if we wrap all the way around - long firstIndex = (long)(text - pData); - long onePastLastIndex = firstIndex + textsize; - - if ( onePastLastIndex > size || onePastLastIndex <= firstIndex) - throw Error(14); + const byte* text = data.pData_ + keysize + 1; + long textsize = data.size_ - keysize - 1; arr.alloc(textsize); arr = DataBuf(text, textsize); } - else if(!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "iTXt", 4)) + else if(type == iTXt_Chunk) { // Extract a deflate compressed or uncompressed UTF-8 text chunk // we get the compression flag after the key - const byte* compressionFlag = &PNG_CHUNK_DATA(pData, index, keysize+1); + const byte* compressionFlag = data.pData_ + keysize + 1; // we get the compression method after the compression flag - const byte* compressionMethod = &PNG_CHUNK_DATA(pData, index, keysize+1); + const byte* compressionMethod = data.pData_ + keysize + 2; // language description string after the compression technique spec - const byte* languageText = &PNG_CHUNK_DATA(pData, index, keysize+1); - unsigned int languageTextSize = getLong(&pData[index], bigEndian)-keysize-1; + std::string languageText((const char*)(data.pData_ + keysize + 3)); + unsigned int languageTextSize = languageText.size(); // translated keyword string after the language description - const byte* translatedKeyText = &PNG_CHUNK_DATA(pData, index, keysize+1); - unsigned int translatedKeyTextSize = getLong(&pData[index], bigEndian)-keysize-1; + std::string translatedKeyText((const char*)(data.pData_ + keysize + 3 + languageTextSize)); + unsigned int translatedKeyTextSize = translatedKeyText.size(); - if ( *compressionFlag == 0x00 ) + if ( compressionFlag[0] == 0x00 ) { // then it's an uncompressed iTXt chunk #ifdef DEBUG - std::cerr << "Exiv2::PngChunk::parsePngChunk: We found an uncompressed iTXt field\n"; + std::cout << "Exiv2::PngChunk::parseTXTChunk: We found an uncompressed iTXt field\n"; #endif // the text comes after the translated keyword, but isn't null terminated - const byte* text = &PNG_CHUNK_DATA(pData, index, keysize+1); - long textsize = getLong(&pData[index], bigEndian)-keysize-1; - - // security check, also considering overflow wraparound from the addition -- - // we may endup with a /smaller/ index if we wrap all the way around - long firstIndex = (long)(text - pData); - long onePastLastIndex = firstIndex + textsize; - - if ( onePastLastIndex > size || onePastLastIndex <= firstIndex) - throw Error(14); + const byte* text = data.pData_ + keysize + 3 + languageTextSize + translatedKeyTextSize; + long textsize = data.size_ - (keysize + 3 + languageTextSize + translatedKeyTextSize); arr.alloc(textsize); arr = DataBuf(text, textsize); } - else if ( *compressionMethod == 0x00 ) + else if ( compressionFlag[0] == 0x01 && compressionMethod[0] == 0x00 ) { // then it's a zlib compressed iTXt chunk #ifdef DEBUG - std::cerr << "Exiv2::PngChunk::parsePngChunk: We found a zlib compressed iTXt field\n"; + std::cout << "Exiv2::PngChunk::parseTXTChunk: We found a zlib compressed iTXt field\n"; #endif // the compressed text comes after the translated keyword, but isn't null terminated - const byte* compressedText = &PNG_CHUNK_DATA(pData, index, keysize+1); - long compressedTextSize = getLong(&pData[index], bigEndian)-keysize-1; - - // security check, also considering overflow wraparound from the addition -- - // we may endup with a /smaller/ index if we wrap all the way around - long firstIndex = (long)(compressedText - pData); - long onePastLastIndex = firstIndex + compressedTextSize; - if ( onePastLastIndex > size || onePastLastIndex <= firstIndex) - throw Error(14); + const byte* compressedText = data.pData_ + keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1; + long compressedTextSize = data.size_ - (keysize + 3 + languageTextSize + 1 + translatedKeyTextSize + 1); zlibUncompress(compressedText, compressedTextSize, arr); } @@ -265,7 +204,7 @@ namespace Exiv2 { { // then it isn't zlib compressed and we are sunk #ifdef DEBUG - std::cerr << "Exiv2::PngChunk::parsePngChunk: Non-standard iTXt compression method.\n"; + std::cerr << "Exiv2::PngChunk::parseTXTChunk: Non-standard iTXt compression method.\n"; #endif throw Error(14); } @@ -273,12 +212,12 @@ namespace Exiv2 { else { #ifdef DEBUG - std::cerr << "Exiv2::PngChunk::parsePngChunk: We found a field, not expected though\n"; + std::cerr << "Exiv2::PngChunk::parseTXTChunk: We found a field, not expected though\n"; #endif throw Error(14); } - return arr; + return arr; } // PngChunk::parsePngChunk @@ -314,7 +253,7 @@ namespace Exiv2 { if (pos !=-1) { #ifdef DEBUG - std::cerr << "Exiv2::PngChunk::decode: Exif header found at position " << pos << "\n"; + std::cout << "Exiv2::PngChunk::decode: Exif header found at position " << pos << "\n"; #endif pos = pos + sizeof(exifHeader); ByteOrder bo = TiffParser::decode(pImage->exifData(), @@ -360,7 +299,7 @@ namespace Exiv2 { #endif xmpPacket = xmpPacket.substr(idx); } - if (XmpParser::decode(pImage->xmpData(), xmpPacket)) + if (XmpParser::decode(pImage->xmpData(), xmpPacket)) { #ifndef SUPPRESS_WARNINGS std::cerr << "Warning: Failed to decode XMP metadata.\n"; @@ -387,7 +326,7 @@ namespace Exiv2 { #endif xmpPacket = xmpPacket.substr(idx); } - if (XmpParser::decode(pImage->xmpData(), xmpPacket)) + if (XmpParser::decode(pImage->xmpData(), xmpPacket)) { #ifndef SUPPRESS_WARNINGS std::cerr << "Warning: Failed to decode XMP metadata.\n"; @@ -407,6 +346,254 @@ namespace Exiv2 { } // PngChunk::parseChunkContent + DataBuf PngChunk::makeMetadataChunk(const DataBuf& metadata, MetadataType type, bool compress) + { + if (type == comment_Data) + { + DataBuf key(11); + memcpy(key.pData_, "Description", 11); + DataBuf rawData = makeUtf8TxtChunk(key, metadata, compress); + return rawData; + } + else if (type == exif_Data) + { + DataBuf tmp(4); + memcpy(tmp.pData_, "exif", 4); + DataBuf rawProfile = writeRawProfile(metadata, tmp); + DataBuf key(17 + tmp.size_); + memcpy(key.pData_, "Raw profile type ", 17); + memcpy(key.pData_ + 17, tmp.pData_, tmp.size_); + DataBuf rawData = makeAsciiTxtChunk(key, rawProfile, compress); + return rawData; + } + else if (type == iptc_Data) + { + DataBuf tmp(4); + memcpy(tmp.pData_, "iptc", 4); + DataBuf rawProfile = writeRawProfile(metadata, tmp); + DataBuf key(17 + tmp.size_); + memcpy(key.pData_, "Raw profile type ", 17); + memcpy(key.pData_ + 17, tmp.pData_, tmp.size_); + DataBuf rawData = makeAsciiTxtChunk(key, rawProfile, compress); + return rawData; + } + else if (type == xmp_Data) + { + DataBuf key(17); + memcpy(key.pData_, "XML:com.adobe.xmp", 17); + DataBuf rawData = makeUtf8TxtChunk(key, metadata, compress); + return rawData; + } + + return DataBuf(); + + } // PngChunk::makeMetadataChunk + + void PngChunk::zlibUncompress(const byte* compressedText, + unsigned int compressedTextSize, + DataBuf& arr) + { + uLongf uncompressedLen = compressedTextSize * 2; // just a starting point + int zlibResult; + + do + { + arr.alloc(uncompressedLen); + zlibResult = uncompress((Bytef*)arr.pData_, &uncompressedLen, + compressedText, compressedTextSize); + + if (zlibResult == Z_OK) + { + // then it is all OK + arr.alloc(uncompressedLen); + } + else if (zlibResult == Z_BUF_ERROR) + { + // the uncompressedArray needs to be larger +#ifdef DEBUG + std::cout << "Exiv2::PngChunk::parsePngChunk: doubling size for decompression.\n"; +#endif + uncompressedLen *= 2; + + // DoS protection. can't be bigger than 64k + if ( uncompressedLen > 131072 ) + break; + } + else + { + // something bad happened + throw Error(14); + } + } + while (zlibResult == Z_BUF_ERROR); + + if (zlibResult != Z_OK) + throw Error(14); + + } // PngChunk::zlibUncompress + + void PngChunk::zlibCompress(const byte* text, + unsigned int textSize, + DataBuf& arr) + { + uLongf compressedLen = textSize * 2; // just a starting point + int zlibResult; + + do + { + arr.alloc(compressedLen); + zlibResult = compress2((Bytef*)arr.pData_, &compressedLen, + text, textSize, Z_BEST_COMPRESSION); + + if (zlibResult == Z_OK) + { + // then it is all OK + arr.alloc(compressedLen); + } + else if (zlibResult == Z_BUF_ERROR) + { + // the compressedArray needs to be larger +#ifdef DEBUG + std::cout << "Exiv2::PngChunk::parsePngChunk: doubling size for compression.\n"; +#endif + compressedLen *= 2; + + // DoS protection. can't be bigger than 64k + if ( compressedLen > 131072 ) + break; + } + else + { + // something bad happened + throw Error(14); + } + } + while (zlibResult == Z_BUF_ERROR); + + if (zlibResult != Z_OK) + throw Error(14); + + } // PngChunk::zlibCompress + + DataBuf PngChunk::makeAsciiTxtChunk(const DataBuf& key, const DataBuf& data, bool compress) + { + DataBuf type(4); + DataBuf data4crc; + DataBuf chunkData; + byte chunkDataSize[4]; + byte chunkCRC[4]; + + if (compress) + { + // Compressed text chunk using ZLib. + // Data format : key ("zTXt") + 0x00 + compression type (0x00) + compressed data + // Chunk structure: data lenght (4 bytes) + chunk type (4 bytes) + compressed data + CRC (4 bytes) + + memcpy(type.pData_, "zTXt", 4); + + DataBuf compressedData; + zlibCompress(data.pData_, data.size_, compressedData); + + data4crc.alloc(key.size_ + 1 + 1 + compressedData.size_); + memcpy(data4crc.pData_, key.pData_, key.size_); + memcpy(data4crc.pData_ + key.size_, "\0\0", 2); + memcpy(data4crc.pData_ + key.size_ + 2, compressedData.pData_, compressedData.size_); + + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, data4crc.pData_, data4crc.size_); + + ul2Data(chunkCRC, crc, Exiv2::bigEndian); + ul2Data(chunkDataSize, data4crc.size_, Exiv2::bigEndian); + + chunkData.alloc(4 + type.size_ + data4crc.size_ + 4); + memcpy(chunkData.pData_, chunkDataSize, 4); + memcpy(chunkData.pData_ + 4, type.pData_, type.size_); + memcpy(chunkData.pData_ + 4 + type.size_, data4crc.pData_, data4crc.size_); + memcpy(chunkData.pData_ + 4 + type.size_ + data4crc.size_, chunkCRC, 4); + } + else + { + // Not Compressed text chunk. + // Data Format : key ("tEXt") + 0x00 + data + // Chunk Structure: data lenght (4 bytes) + chunk type (4 bytes) + data + CRC (4 bytes) + + memcpy(type.pData_, "tEXt", 4); + + data4crc.alloc(key.size_ + 1 + data.size_); + memcpy(data4crc.pData_, key.pData_, key.size_); + memcpy(data4crc.pData_ + key.size_, "\0", 1); + memcpy(data4crc.pData_ + key.size_ + 1, data.pData_, data.size_); + + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, data4crc.pData_, data4crc.size_); + + ul2Data(chunkCRC, crc, Exiv2::bigEndian); + ul2Data(chunkDataSize, data4crc.size_, Exiv2::bigEndian); + + chunkData.alloc(4 + type.size_ + data4crc.size_ + 4); + memcpy(chunkData.pData_, chunkDataSize, 4); + memcpy(chunkData.pData_ + 4, type.pData_, type.size_); + memcpy(chunkData.pData_ + 4 + type.size_, data4crc.pData_, data4crc.size_); + memcpy(chunkData.pData_ + 4 + type.size_ + data4crc.size_, chunkCRC, 4); + } + + return chunkData; + + } // PngChunk::makeAsciiTxtChunk + + DataBuf PngChunk::makeUtf8TxtChunk(const DataBuf& key, const DataBuf& data, bool compress) + { + DataBuf type(4); + DataBuf textData; // text compressed or not. + DataBuf data4crc; + DataBuf chunkData; + byte chunkDataSize[4]; + byte chunkCRC[4]; + + // Compressed text chunk using ZLib. + // Data format : key ("iTXt") + 0x00 + compression flag (0x00: uncompressed - 0x01: compressed) + + // compression method (0x00) + language id (null) + 0x00 + + // translated key (null) + 0x00 + text (compressed or not) + // Chunk structure: data lenght (4 bytes) + chunk type (4 bytes) + data + CRC (4 bytes) + + memcpy(type.pData_, "iTXt", 4); + + if (compress) + { + const unsigned char flags[] = {0x00, 0x01, 0x00, 0x00, 0x00}; + + zlibCompress(data.pData_, data.size_, textData); + data4crc.alloc(key.size_ + 5 + textData.size_); + memcpy(data4crc.pData_, key.pData_, key.size_); + memcpy(data4crc.pData_ + key.size_, flags, 5); + } + else + { + const unsigned char flags[] = {0x00, 0x00, 0x00, 0x00, 0x00}; + + textData = DataBuf(data.pData_, data.size_); + data4crc.alloc(key.size_ + 5 + textData.size_); + memcpy(data4crc.pData_, key.pData_, key.size_); + memcpy(data4crc.pData_ + key.size_, flags, 5); + } + memcpy(data4crc.pData_ + key.size_ + 5, textData.pData_, textData.size_); + + uLong crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, data4crc.pData_, data4crc.size_); + + ul2Data(chunkCRC, crc, Exiv2::bigEndian); + ul2Data(chunkDataSize, data4crc.size_, Exiv2::bigEndian); + + chunkData.alloc(4 + type.size_ + data4crc.size_ + 4); + memcpy(chunkData.pData_, chunkDataSize, 4); + memcpy(chunkData.pData_ + 4, type.pData_, type.size_); + memcpy(chunkData.pData_ + 4 + type.size_, data4crc.pData_, data4crc.size_); + memcpy(chunkData.pData_ + 4 + type.size_ + data4crc.size_, chunkCRC, 4); + + return chunkData; + + } // PngChunk::makeUtf8TxtChunk + DataBuf PngChunk::readRawProfile(const DataBuf& text) { DataBuf info; @@ -489,164 +676,65 @@ namespace Exiv2 { } // PngChunk::readRawProfile - void PngChunk::zlibUncompress(const byte* compressedText, - unsigned int compressedTextSize, - DataBuf& arr) + DataBuf PngChunk::writeRawProfile(const DataBuf& profile_data, const DataBuf& profile_type) { - uLongf uncompressedLen = compressedTextSize * 2; // just a starting point - int zlibResult; - - do - { - arr.alloc(uncompressedLen); - zlibResult = uncompress((Bytef*)arr.pData_, &uncompressedLen, - compressedText, compressedTextSize); - - if (zlibResult == Z_OK) - { - // then it is all OK - arr.alloc(uncompressedLen); - } - else if (zlibResult == Z_BUF_ERROR) - { - // the uncompressedArray needs to be larger -#ifdef DEBUG - std::cerr << "Exiv2::PngChunk::parsePngChunk: doubling size for decompression.\n"; -#endif - uncompressedLen *= 2; - - // DoS protection. can't be bigger than 64k - if ( uncompressedLen > 131072 ) - break; - } - else - { - // something bad happened - throw Error(14); - } - } - while (zlibResult == Z_BUF_ERROR); - - if (zlibResult != Z_OK) - throw Error(14); - - } // PngChunk::zlibUncompress - -/* TODO : code backported from digiKam. Not yet adapted and used. - - void PngChunk::writeRawProfile(png_struct *ping, - png_info* ping_info, - char* profile_type, - char* profile_data, - png_uint_32 length) - { - png_textp text; - register long i; - uchar *sp; + char *sp=0; + char *dp=0; + char *text=0; - png_charp dp; + uint allocated_length, description_length, text_length; - png_uint_32 allocated_length, description_length; + unsigned char hex[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; - const uchar hex[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; + DataBuf formatedData; - DDebug() << "Writing Raw profile: type=" << profile_type << ", length=" << length << endl; + description_length = profile_type.size_; + allocated_length = profile_data.size_*2 + (profile_data.size_ >> 5) + 20 + description_length; - text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text)); - description_length = std::strlen((const char *) profile_type); - allocated_length = (png_uint_32) (length*2 + (length >> 5) + 20 + description_length); + text = new char[allocated_length]; - text[0].text = (png_charp) png_malloc(ping, allocated_length); - text[0].key = (png_charp) png_malloc(ping, (png_uint_32) 80); - text[0].key[0] = '\0'; - - concatenateString(text[0].key, "Raw profile type ", 4096); - concatenateString(text[0].key, (const char *) profile_type, 62); - - sp = (uchar*)profile_data; - dp = text[0].text; + sp = (char*)profile_data.pData_; + dp = text; *dp++='\n'; - copyString(dp, (const char *) profile_type, allocated_length); + copyString(dp, (const char *)profile_type.pData_, allocated_length); dp += description_length; *dp++='\n'; - formatString(dp, allocated_length-strlen(text[0].text), "%8lu ", length); + formatString(dp, allocated_length - strlen(text), "%8lu ", profile_data.size_); dp += 8; - for (i=0; i < (long) length; i++) + for (i=0; i < (long)profile_data.size_; i++) { if (i%36 == 0) *dp++='\n'; *(dp++)=(char) hex[((*sp >> 4) & 0x0f)]; - *(dp++)=(char) hex[((*sp++ ) & 0x0f)]; + *(dp++)=(char) hex[((*sp++) & 0x0f)]; } *dp++='\n'; *dp='\0'; - text[0].text_length = (png_size_t) (dp-text[0].text); - text[0].compression = -1; - if (text[0].text_length <= allocated_length) - png_set_text(ping, ping_info,text, 1); + text_length = (uint)(dp-text); - png_free(ping, text[0].text); - png_free(ping, text[0].key); - png_free(ping, text); + if (text_length <= allocated_length) + { + formatedData.alloc(text_length); + memcpy(formatedData.pData_, text, text_length); + } + + delete [] text; + return formatedData; } // PngChunk::writeRawProfile - size_t PngChunk::concatenateString(char* destination, - const char* source, - const size_t length) - { - register char *q; - - register const char *p; - - register size_t i; - - size_t count; - - if ( !destination || !source || length == 0 ) - return 0; - - p = source; - q = destination; - i = length; - - while ((i-- != 0) && (*q != '\0')) - q++; - - count = (size_t) (q-destination); - i = length-count; - - if (i == 0) - return(count+strlen(p)); - - while (*p != '\0') - { - if (i != 1) - { - *q++=(*p); - i--; - } - p++; - } - - *q='\0'; - - return(count+(p-source)); - - } // PngChunk::concatenateString - - size_t PngChunk::copyString(char* destination, - const char* source, + size_t PngChunk::copyString(char* destination, + const char* source, const size_t length) { register char *q; @@ -668,7 +756,7 @@ namespace Exiv2 { { if ((*q++=(*p++)) == '\0') break; - } + } while (--i != 0); } @@ -684,9 +772,9 @@ namespace Exiv2 { } // PngChunk::copyString - long PngChunk::formatString(char* string, - const size_t length, - const char* format, + long PngChunk::formatString(char* string, + const size_t length, + const char* format, ...) { long n; @@ -700,9 +788,9 @@ namespace Exiv2 { } // PngChunk::formatString - long PngChunk::formatStringList(char* string, - const size_t length, - const char* format, + long PngChunk::formatStringList(char* string, + const size_t length, + const char* format, va_list operands) { int n = vsnprintf(string, length, format, operands); @@ -711,18 +799,7 @@ namespace Exiv2 { string[length-1] = '\0'; return((long) n); + } // PngChunk::formatStringList -*/ } // namespace Exiv2 - -// ***************************************************************************** -// local definitions -namespace { - long chunkLength(const Exiv2::byte* pData, long index) - { - uint32_t length = Exiv2::getULong(&pData[index], Exiv2::bigEndian); - if (length > 0x7FFFFFFF) throw Exiv2::Error(14); - return static_cast(length); - } -} diff --git a/src/pngchunk.hpp b/src/pngchunk.hpp index 9e7a4fc2..0fab7525 100644 --- a/src/pngchunk.hpp +++ b/src/pngchunk.hpp @@ -45,7 +45,8 @@ // ***************************************************************************** // namespace extensions -namespace Exiv2 { +namespace Exiv2 +{ // ***************************************************************************** // class declarations @@ -59,40 +60,86 @@ namespace Exiv2 { @brief Stateless parser class for data in PNG chunk format. Images use this class to decode and encode PNG-based data. */ - class PngChunk { + class PngChunk + { public: - /*! - @brief Decode PNG chunk metadata from a data buffer \em pData of length - \em size into \em pImage. - @param pImage Pointer to the image to hold the metadata - @param pData Pointer to the data buffer. Must point to PNG chunk data; - no checks are performed. - @param size Length of the data buffer. + // Text Chunk types. + enum TxtChunkType + { + tEXt_Chunk = 0, + zTXt_Chunk = 1, + iTXt_Chunk = 2 + }; + + // Metadata Chunk types. + enum MetadataType + { + exif_Data = 0, + iptc_Data = 1, + xmp_Data = 2, + comment_Data = 3 + }; + + public: + + /*! + @brief Decode PNG IHDR chunk data from a data buffer + \em data and return image size to \em outWidth and \em outHeight. + + @param pData PNG Chunk data buffer. @param outWidth Integer pointer to be set to the width of the image. @param outHeight Integer pointer to be set to the height of the image. */ - static void decode(Image* pImage, - const byte* pData, - long size, - int* outWidth, - int* outHeight); + static void decodeIHDRChunk(const DataBuf& data, + int* outWidth, + int* outHeight); + + /*! + @brief Decode PNG tEXt, zTXt, or iTXt chunk data from \em pImage passed by data buffer + \em data and extract Comment, Exif, Iptc, Xmp metadata accordingly. + + @param pImage Pointer to the image to hold the metadata + @param data PNG Chunk data buffer. + @param type PNG Chunk TXT type. + */ + static void decodeTXTChunk(Image* pImage, + const DataBuf& data, + TxtChunkType type); + + /*! + @brief Return PNG TXT chunk key as data buffer. + + @param data PNG Chunk data buffer. + @param stripHeader Set true if chunk data start with header bytes, else false (default). + */ + static DataBuf keyTXTChunk(const DataBuf& data, bool stripHeader=false); + + /*! + @brief Return a complete PNG chunk data compressed or not as buffer. Data returned is formated + accordingly with metadata \em type to host passed by \em data. + + @param data metadata buffer. + @param type metadata type. + @param compress compress or not metadata. + */ + static DataBuf makeMetadataChunk(const DataBuf& metadata, MetadataType type, bool compress); private: + //! @name Accessors //@{ /*! - @brief Parse PNG chunk to determine type and extract content. + @brief Parse PNG Text chunk to determine type and extract content. Supported Chunk types are tTXt, zTXt, and iTXt. */ - static DataBuf parsePngChunk(const byte* pData, - long size, - long& index, - int keysize); + static DataBuf parseTXTChunk(const DataBuf& data, + int keysize, + TxtChunkType type); /*! - @brief Parse PNG chunk contents to extract metadata container and assign it to image. + @brief Parse PNG chunk contents to extract metadata container and assign it to image. Supported contents are: Exif raw text profile generated by ImageMagick ==> Image Exif metadata. Iptc raw text profile generated by ImageMagick ==> Image Iptc metadata. @@ -100,43 +147,67 @@ namespace Exiv2 { Xmp packet generated by Adobe ==> Image Xmp metadata. Description string ==> Image Comments. */ - static void parseChunkContent(Image* pImage, - const byte* key, + static void parseChunkContent(Image* pImage, + const byte* key, const DataBuf arr); + /*! + @brief Return a compressed (zTXt) or uncompressed (tEXt) PNG ASCII text chunk + (header + key + flags + data + CRC) as data buffer. + + @param key PNG Chunk key. + @param data PNG Chunk raw data. + @param compress Compress or not PNG Chunk data. + */ + static DataBuf makeAsciiTxtChunk(const DataBuf& key, const DataBuf& data, bool compress); + + /*! + @brief Return a compressed or uncompressed (iTXt) PNG UTF8 text chunk + (header + key + flags + data + CRC) as data buffer. + + @param key PNG Chunk key. + @param data PNG Chunk raw data. + @param compress Compress or not PNG Chunk data. + */ + static DataBuf makeUtf8TxtChunk(const DataBuf& key, const DataBuf& data, bool compress); + + /*! + @brief Wrapper around zlib to uncompress a PNG chunk content. + */ + static void zlibUncompress(const byte* compressedText, + unsigned int compressedTextSize, + DataBuf& arr); + + /*! + @brief Wrapper around zlib to compress a PNG chunk content. + */ + static void zlibCompress(const byte* text, + unsigned int textSize, + DataBuf& arr); + /*! @brief Decode from ImageMagick raw text profile which host encoded Exif/Iptc/Xmp metadata byte array. */ static DataBuf readRawProfile(const DataBuf& text); /*! - @brief Wrapper around zlib to uncompress a PNG chunk content. + @brief Encode to ImageMagick raw text profile which host encoded Exif/Iptc/Xmp metadata byte array. */ - static void zlibUncompress(const byte* compressedText, - unsigned int compressedTextSize, - DataBuf& arr); + static DataBuf writeRawProfile(const DataBuf& profile_data, const DataBuf& profile_type); -/* TODO : code backported from digiKam. Not yet adapted and used. - - static DataBuf writeRawProfile(const DataBuf& text); - - static size_t concatenateString(char* destination, - const char* source, - const size_t length); - - static size_t copyString(char* destination, - const char* source, + static size_t copyString(char* destination, + const char* source, const size_t length); - static long formatString(char* string, - const size_t length, - const char* format, + static long formatString(char* string, + const size_t length, + const char* format, ...); - static long formatStringList(char *string, - const size_t length, - const char *format, - va_list operands);*/ + static long formatStringList(char *string, + const size_t length, + const char *format, + va_list operands); //@} }; // class PngChunk diff --git a/src/pngimage.cpp b/src/pngimage.cpp index 208bb0a7..91ad724e 100644 --- a/src/pngimage.cpp +++ b/src/pngimage.cpp @@ -54,34 +54,18 @@ EXIV2_RCSID("@(#) $Id: pngimage.cpp 823 2006-06-12 07:35:00Z cgilles $") #include #include +// Signature from front of PNG file +const unsigned char pngSignature[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; + // ***************************************************************************** // class member definitions -namespace Exiv2 { - +namespace Exiv2 +{ PngImage::PngImage(BasicIo::AutoPtr io, bool /*create*/) - : Image(ImageType::png, mdExif | mdIptc, io) + : Image(ImageType::png, mdExif | mdIptc | mdComment, io) { } // PngImage::PngImage - void PngImage::setExifData(const ExifData& /*exifData*/) - { - // Todo: implement me! - throw(Error(32, "Exif metadata", "PNG")); - } - - void PngImage::setIptcData(const IptcData& /*iptcData*/) - { - // Todo: implement me! - throw(Error(32, "IPTC metadata", "PNG")); - } - - void PngImage::setComment(const std::string& /*comment*/) - { - // Todo: implement me! - // Add 'iTXt' chunk 'Description' tag support here - throw(Error(32, "Image comment", "PNG")); - } - void PngImage::readMetadata() { #ifdef DEBUG @@ -93,34 +77,289 @@ namespace Exiv2 { } IoCloser closer(*io_); // Ensure that this is the correct image type - if (!isPngType(*io_, false)) + if (!isPngType(*io_, true)) { if (io_->error() || io_->eof()) throw Error(14); throw Error(3, "PNG"); } clearMetadata(); - PngChunk::decode(this, io_->mmap(), io_->size(), &pixelWidth_, &pixelHeight_); - /* - Todo: + DataBuf cheaderBuf(8); // Chunk header size : 4 bytes (data size) + 4 bytes (chunk type). + DataBuf cdataBuf; // Chunk data size (not fixed size - can be null). - - readMetadata implements the loop over the chunks in the image and - makes decisions what to do. It reads only one chunk at a time - instead of the whole file. + while(!io_->eof()) + { + // Read chunk header. - - new class PngChunk, initialized with the memory of one chunk and - has all the access methods required to determine the field, key, - to access and decompress data etc. - */ +#ifdef DEBUG + std::cout << "Exiv2::PngImage::readMetadata: Position: " << io_->tell() << "\n"; +#endif + std::memset(cheaderBuf.pData_, 0x0, cheaderBuf.size_); + long bufRead = io_->read(cheaderBuf.pData_, cheaderBuf.size_); + if (io_->error()) throw Error(14); + if (bufRead != cheaderBuf.size_) throw Error(20); + +#ifdef DEBUG + std::cout << "Exiv2::PngImage::readMetadata: Next Chunk: " << cheaderBuf.pData_ + 4 << "\n"; +#endif + // Decode chunk data length. + + uint32_t dataOffset = Exiv2::getULong(cheaderBuf.pData_, Exiv2::bigEndian); + if (dataOffset > 0x7FFFFFFF) throw Exiv2::Error(14); + + // Perform a chunk triage for item that we need. + + if (!memcmp(cheaderBuf.pData_ + 4, "IEND", 4) || + !memcmp(cheaderBuf.pData_ + 4, "IHDR", 4) || + !memcmp(cheaderBuf.pData_ + 4, "tEXt", 4) || + !memcmp(cheaderBuf.pData_ + 4, "zTXt", 4) || + !memcmp(cheaderBuf.pData_ + 4, "iTXt", 4)) + { + // Extract chunk data. + + cdataBuf.alloc(dataOffset); + bufRead = io_->read(cdataBuf.pData_, dataOffset); + if (io_->error()) throw Error(14); + if (bufRead != (long)dataOffset) throw Error(20); + + if (!memcmp(cheaderBuf.pData_ + 4, "IEND", 4)) + { + // Last chunk found: we stop parsing. +#ifdef DEBUG + std::cout << "Exiv2::PngImage::readMetadata: Found IEND chunk (lenght: " << dataOffset << ")\n"; +#endif + return; + } + else if (!memcmp(cheaderBuf.pData_ + 4, "IHDR", 4)) + { +#ifdef DEBUG + std::cout << "Exiv2::PngImage::readMetadata: Found IHDR chunk (lenght: " << dataOffset << ")\n"; +#endif + PngChunk::decodeIHDRChunk(cdataBuf, &pixelWidth_, &pixelHeight_); + } + else if (!memcmp(cheaderBuf.pData_ + 4, "tEXt", 4)) + { +#ifdef DEBUG + std::cout << "Exiv2::PngImage::readMetadata: Found tEXt chunk (lenght: " << dataOffset << ")\n"; +#endif + PngChunk::decodeTXTChunk(this, cdataBuf, PngChunk::tEXt_Chunk); + } + else if (!memcmp(cheaderBuf.pData_ + 4, "zTXt", 4)) + { +#ifdef DEBUG + std::cout << "Exiv2::PngImage::readMetadata: Found zTXt chunk (lenght: " << dataOffset << ")\n"; +#endif + PngChunk::decodeTXTChunk(this, cdataBuf, PngChunk::zTXt_Chunk); + } + else if (!memcmp(cheaderBuf.pData_ + 4, "iTXt", 4)) + { +#ifdef DEBUG + std::cout << "Exiv2::PngImage::readMetadata: Found iTXt chunk (lenght: " << dataOffset << ")\n"; +#endif + PngChunk::decodeTXTChunk(this, cdataBuf, PngChunk::iTXt_Chunk); + } + + // Set dataOffset to null like chunk data have been extracted previously. + dataOffset = 0; + } + + // Move to the next chunk: chunk data size + 4 CRC bytes. +#ifdef DEBUG + std::cout << "Exiv2::PngImage::readMetadata: Seek to offset: " << dataOffset + 4 << "\n"; +#endif + io_->seek(dataOffset + 4 , BasicIo::cur); + if (io_->error() || io_->eof()) throw Error(14); + } } // PngImage::readMetadata void PngImage::writeMetadata() { - //! Todo: implement me! - throw(Error(31, "PNG")); + if (io_->open() != 0) + { + throw Error(9, io_->path(), strError()); + } + IoCloser closer(*io_); + BasicIo::AutoPtr tempIo(io_->temporary()); // may throw + 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(20); + if (!outIo.isopen()) throw Error(21); + +#ifdef DEBUG + std::cout << "Exiv2::PngImage::doWriteMetadata: Writing PNG file " << io_->path() << "\n"; + std::cout << "Exiv2::PngImage::doWriteMetadata: tmp file created " << outIo.path() << "\n"; +#endif + + // Ensure that this is the correct image type + if (!isPngType(*io_, true)) + { + if (io_->error() || io_->eof()) throw Error(20); + throw Error(22); + } + + // Write PNG Signature. + if (outIo.write(pngSignature, 8) != 8) throw Error(21); + + 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(14); + if (bufRead != cheaderBuf.size_) throw Error(20); + + // Decode chunk data length. + + uint32_t dataOffset = getULong(cheaderBuf.pData_, bigEndian); + if (dataOffset > 0x7FFFFFFF) throw Exiv2::Error(14); + + // 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(14); + if (bufRead != (long)(dataOffset + 4)) throw Error(20); + + if (!memcmp(cheaderBuf.pData_ + 4, "IEND", 4)) + { + // Last chunk found: we write it and done. +#ifdef DEBUG + std::cout << "Exiv2::PngImage::doWriteMetadata: Write IEND chunk (lenght: " << dataOffset << ")\n"; +#endif + if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(21); + return; + } + else if (!memcmp(cheaderBuf.pData_ + 4, "IHDR", 4)) + { +#ifdef DEBUG + std::cout << "Exiv2::PngImage::doWriteMetadata: Write IHDR chunk (lenght: " << dataOffset << ")\n"; +#endif + if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(21); + + // Write all updated metadata here, just after IHDR. + + if (!comment_.empty()) + { + // Update Comment data to a new compressed iTXt PNG chunk + + DataBuf com(reinterpret_cast(comment_.data()), static_cast(comment_.size())); + DataBuf chunkData = PngChunk::makeMetadataChunk(com, PngChunk::comment_Data, true); + +#ifdef DEBUG + std::cout << "Exiv2::PngImage::doWriteMetadata: Write chunk with Comment metadata (lenght: " + << chunkData.size_ << ")\n"; +#endif + if (outIo.write(chunkData.pData_, chunkData.size_) != chunkData.size_) throw Error(21); + } + + if (exifData_.count() > 0) + { + // Update Exif data to a new zTXt PNG chunk + + Blob blob; + ExifParser::encode(blob, littleEndian, exifData_); + if (blob.size()) + { + const unsigned char ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; + + DataBuf rawExif(sizeof(ExifHeader) + blob.size()); + memcpy(rawExif.pData_, ExifHeader, sizeof(ExifHeader)); + memcpy(rawExif.pData_ + sizeof(ExifHeader), &blob[0], blob.size()); + DataBuf chunkData = PngChunk::makeMetadataChunk(rawExif, PngChunk::exif_Data, true); + +#ifdef DEBUG + std::cout << "Exiv2::PngImage::doWriteMetadata: Write chunk with Exif metadata (lenght: " + << chunkData.size_ << ")\n"; +#endif + if (outIo.write(chunkData.pData_, chunkData.size_) != chunkData.size_) throw Error(21); + } + } + + if (iptcData_.count() > 0) + { + // Update Iptc data to a new zTXt PNG chunk + + DataBuf rawIptc = IptcParser::encode(iptcData_); + if (rawIptc.size_ > 0) + { + DataBuf chunkData = PngChunk::makeMetadataChunk(rawIptc, PngChunk::iptc_Data, true); + +#ifdef DEBUG + std::cout << "Exiv2::PngImage::doWriteMetadata: Write chunk with Iptc metadata (lenght: " + << chunkData.size_ << ")\n"; +#endif + if (outIo.write(chunkData.pData_, chunkData.size_) != chunkData.size_) throw Error(21); + } + } + + if (xmpPacket_.size() > 0) + { + // Update Xmp data to a new uncompressed iTXt PNG chunk + // Note than XMP spec. Ver September 2005, page 97 require an uncompressed chunk to host XMP data + + DataBuf xmp(reinterpret_cast(xmpPacket_.data()), static_cast(xmpPacket_.size())); + DataBuf chunkData = PngChunk::makeMetadataChunk(xmp, PngChunk::xmp_Data, false); + +#ifdef DEBUG + std::cout << "Exiv2::PngImage::doWriteMetadata: Write chunk with XMP metadata (lenght: " + << chunkData.size_ << ")\n"; +#endif + if (outIo.write(chunkData.pData_, chunkData.size_) != chunkData.size_) throw Error(21); + } + } + else if (!memcmp(cheaderBuf.pData_ + 4, "tEXt", 4) || + !memcmp(cheaderBuf.pData_ + 4, "zTXt", 4) || + !memcmp(cheaderBuf.pData_ + 4, "iTXt", 4)) + { + DataBuf key = PngChunk::keyTXTChunk(chunkBuf, true); + if (memcmp("Raw profile type exif", key.pData_, 21) == 0 || + memcmp("Raw profile type APP1", key.pData_, 21) == 0 || + memcmp("Raw profile type iptc", key.pData_, 21) == 0 || + memcmp("Raw profile type xmp", key.pData_, 20) == 0 || + memcmp("XML:com.adobe.xmp", key.pData_, 17) == 0 || + memcmp("Description", key.pData_, 11) == 0) + { +#ifdef DEBUG + std::cout << "Exiv2::PngImage::doWriteMetadata: strip " << cheaderBuf.pData_ + 4 + << " chunk (key: " << key.pData_ << ")\n"; +#endif + } + else + { +#ifdef DEBUG + std::cout << "Exiv2::PngImage::doWriteMetadata: write " << cheaderBuf.pData_ + 4 + << " chunk (lenght: " << dataOffset << ")\n"; +#endif + if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(21); + } + } + else + { + // Write all others chunk as well. +#ifdef DEBUG + std::cout << "Exiv2::PngImage::doWriteMetadata: write " << cheaderBuf.pData_ + 4 + << " chunk (lenght: " << dataOffset << ")\n"; +#endif + if (outIo.write(chunkBuf.pData_, chunkBuf.size_) != chunkBuf.size_) throw Error(21); + + } + } + + } // PngImage::doWriteMetadata + // ************************************************************************* // free functions Image::AutoPtr newPngInstance(BasicIo::AutoPtr io, bool create) @@ -136,14 +375,13 @@ namespace Exiv2 { bool isPngType(BasicIo& iIo, bool advance) { const int32_t len = 8; - const unsigned char pngId[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; byte buf[len]; iIo.read(buf, len); if (iIo.error() || iIo.eof()) { return false; } - int rc = memcmp(buf, pngId, 8); + int rc = memcmp(buf, pngSignature, 8); if (!advance || rc != 0) { iIo.seek(-len, BasicIo::cur); diff --git a/src/pngimage.hpp b/src/pngimage.hpp index 642c3638..a7212bb9 100644 --- a/src/pngimage.hpp +++ b/src/pngimage.hpp @@ -43,13 +43,15 @@ // ***************************************************************************** // namespace extensions -namespace Exiv2 { +namespace Exiv2 +{ // ***************************************************************************** // class definitions // Add PNG to the supported image formats - namespace ImageType { + namespace ImageType + { const int png = 6; //!< PNG image type (see class PngImage) } @@ -57,7 +59,9 @@ namespace Exiv2 { @brief Class to access PNG images. Exif and IPTC metadata are supported directly. */ - class PngImage : public Image { + class PngImage : public Image + { + public: //! @name Creators //@{ @@ -82,26 +86,7 @@ namespace Exiv2 { //! @name Manipulators //@{ void readMetadata(); - /*! - @brief Todo: Write metadata back to the image. This method is not - yet implemented. Calling it will throw an Error(31). - */ void writeMetadata(); - /*! - @brief Todo: Not supported yet, requires writeMetadata(). Calling - this function will throw an Error(32). - */ - void setExifData(const ExifData& exifData); - /*! - @brief Todo: Not supported yet, requires writeMetadata(). Calling - this function will throw an Error(32). - */ - void setIptcData(const IptcData& iptcData); - /*! - @brief Todo: Not supported yet, requires writeMetadata(). Calling - this function will throw an Error(32). - */ - void setComment(const std::string& comment); //@} //! @name Accessors @@ -116,8 +101,15 @@ namespace Exiv2 { PngImage(const PngImage& rhs); //! Assignment operator PngImage& operator=(const PngImage& rhs); - //@} + /*! + @brief Provides the main implementation of writeMetadata() by + writing all buffered metadata to the provided BasicIo. + @param oIo BasicIo instance to write to (a temporary location). + @return 4 if opening or writing to the associated BasicIo fails + */ + void doWriteMetadata(BasicIo& oIo); + //@} }; // class PngImage // *****************************************************************************