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.
v0.27.3
HumanDynamo 17 years ago
parent 3c77461e51
commit 3fe5ebb8ca

@ -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
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,
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)
} // PngChunk::decodeIHDRChunk
void PngChunk::decodeTXTChunk(Image* pImage,
const DataBuf& data,
TxtChunkType type)
{
*outWidth = getLong((const byte*)&PNG_CHUNK_DATA(pData, index, 0), bigEndian);
*outHeight = getLong((const byte*)&PNG_CHUNK_DATA(pData, index, 4), bigEndian);
}
DataBuf key = keyTXTChunk(data);
// look for a tEXt chunk
index += chunkLength(pData, index) + PNG_CHUNK_HEADER_SIZE;
#ifdef DEBUG
std::cout << "Exiv2::PngChunk::decodeTXTChunk: TXT chunk key: "
<< std::string((const char*)key.pData_) << "\n";
#endif
DataBuf arr = parseTXTChunk(data, key.size_, type);
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);
#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
if (index < size-PNG_CHUNK_HEADER_SIZE)
DataBuf PngChunk::keyTXTChunk(const DataBuf& data, bool stripHeader)
{
// we found a tEXt, zTXt, or iTXt field
// From a tEXt, zTXt, or iTXt chunk,
// we get the key, it's a null terminated string at the chunk start
// get the key, it's a null terminated string at the chunk start
const byte *key = &PNG_CHUNK_DATA(pData, index, 0);
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 we reached the end of the file (it might be corrupted)
if (8+index+keysize >= size)
// look if keysize is valid.
if (keysize >= data.size_)
throw Error(14);
}
DataBuf arr = parsePngChunk(pData, size, index, keysize);
return DataBuf(key, keysize);
#ifdef DEBUG
std::cerr << "Exiv2::PngChunk::decode: Found PNG chunk: "
<< std::string((const char*)key) << " :: "
<< std::string((const char*)arr.pData_, 32) << "\n";
#endif
parseChunkContent(pImage, key, arr);
index += chunkLength(pData, index) + PNG_CHUNK_HEADER_SIZE;
}
}
} // PngChunk::keyTXTChunk
} // PngChunk::decode
DataBuf PngChunk::parsePngChunk(const byte* pData, long size, long& index, int keysize)
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);
// the text comes after the key, but isn't null terminated
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,7 +212,7 @@ 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);
}
@ -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(),
@ -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,161 +676,62 @@ namespace Exiv2 {
} // PngChunk::readRawProfile
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::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)
DataBuf PngChunk::writeRawProfile(const DataBuf& profile_data, const DataBuf& profile_type)
{
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);
png_free(ping, text[0].text);
png_free(ping, text[0].key);
png_free(ping, text);
} // 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));
text_length = (uint)(dp-text);
while (*p != '\0')
if (text_length <= allocated_length)
{
if (i != 1)
{
*q++=(*p);
i--;
}
p++;
formatedData.alloc(text_length);
memcpy(formatedData.pData_, text, text_length);
}
*q='\0';
return(count+(p-source));
delete [] text;
return formatedData;
} // PngChunk::concatenateString
} // PngChunk::writeRawProfile
size_t PngChunk::copyString(char* destination,
const char* source,
@ -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);
}
}

@ -45,7 +45,8 @@
// *****************************************************************************
// namespace extensions
namespace Exiv2 {
namespace Exiv2
{
// *****************************************************************************
// class declarations
@ -59,37 +60,83 @@ 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:
// 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 chunk metadata from a data buffer \em pData of length
\em size into \em pImage.
@brief Decode PNG IHDR chunk data from a data buffer
\em data and return image size to \em outWidth and \em outHeight.
@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.
@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,
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.
@ -105,9 +152,24 @@ namespace Exiv2 {
const DataBuf arr);
/*!
@brief Decode from ImageMagick raw text profile which host encoded Exif/Iptc/Xmp metadata byte array.
@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 readRawProfile(const DataBuf& text);
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.
@ -116,13 +178,22 @@ namespace Exiv2 {
unsigned int compressedTextSize,
DataBuf& arr);
/* TODO : code backported from digiKam. Not yet adapted and used.
/*!
@brief Wrapper around zlib to compress a PNG chunk content.
*/
static void zlibCompress(const byte* text,
unsigned int textSize,
DataBuf& arr);
static DataBuf writeRawProfile(const DataBuf& text);
/*!
@brief Decode from ImageMagick raw text profile which host encoded Exif/Iptc/Xmp metadata byte array.
*/
static DataBuf readRawProfile(const DataBuf& text);
static size_t concatenateString(char* destination,
const char* source,
const size_t length);
/*!
@brief Encode to ImageMagick raw text profile which host encoded Exif/Iptc/Xmp metadata byte array.
*/
static DataBuf writeRawProfile(const DataBuf& profile_data, const DataBuf& profile_type);
static size_t copyString(char* destination,
const char* source,
@ -136,7 +207,7 @@ namespace Exiv2 {
static long formatStringList(char *string,
const size_t length,
const char *format,
va_list operands);*/
va_list operands);
//@}
}; // class PngChunk

@ -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…
Cancel
Save