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.
This commit is contained in:
parent
3c77461e51
commit
3fe5ebb8ca
645
src/pngchunk.cpp
645
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 <zlib.h>
|
||||
|
||||
#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<long>(length);
|
||||
}
|
||||
}
|
||||
|
||||
157
src/pngchunk.hpp
157
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
|
||||
|
||||
312
src/pngimage.cpp
312
src/pngimage.cpp
@ -54,34 +54,18 @@ EXIV2_RCSID("@(#) $Id: pngimage.cpp 823 2006-06-12 07:35:00Z cgilles $")
|
||||
#include <iostream>
|
||||
#include <cassert>
|
||||
|
||||
// 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<const byte*>(comment_.data()), static_cast<long>(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<const byte*>(xmpPacket_.data()), static_cast<long>(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);
|
||||
|
||||
@ -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
|
||||
|
||||
// *****************************************************************************
|
||||
|
||||
Loading…
Reference in New Issue
Block a user