|
|
|
@ -215,9 +215,7 @@ namespace Exiv2 {
|
|
|
|
|
if (io_->open() != 0) {
|
|
|
|
|
throw Error(kerDataSourceOpenFailed, io_->path(), strError());
|
|
|
|
|
}
|
|
|
|
|
// Ensure that this is the correct image type
|
|
|
|
|
if (!isPngType(*io_, true)) {
|
|
|
|
|
if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
|
|
|
|
|
throw Error(kerNotAnImage, "PNG");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -391,6 +389,20 @@ namespace Exiv2 {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void readChunk(DataBuf& buffer, BasicIo& io)
|
|
|
|
|
{
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: Position: " << io.tell() << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
long bufRead = io.read(buffer.pData_, buffer.size_);
|
|
|
|
|
if (io.error()) {
|
|
|
|
|
throw Error(kerFailedToReadImageData);
|
|
|
|
|
}
|
|
|
|
|
if (bufRead != buffer.size_) {
|
|
|
|
|
throw Error(kerInputDataReadFailed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PngImage::readMetadata()
|
|
|
|
|
{
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
@ -401,127 +413,85 @@ namespace Exiv2 {
|
|
|
|
|
throw Error(kerDataSourceOpenFailed, io_->path(), strError());
|
|
|
|
|
}
|
|
|
|
|
IoCloser closer(*io_);
|
|
|
|
|
// Ensure that this is the correct image type
|
|
|
|
|
if (!isPngType(*io_, true))
|
|
|
|
|
{
|
|
|
|
|
if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
|
|
|
|
|
if (!isPngType(*io_, true)) {
|
|
|
|
|
throw Error(kerNotAnImage, "PNG");
|
|
|
|
|
}
|
|
|
|
|
clearMetadata();
|
|
|
|
|
|
|
|
|
|
const long imgSize = (long) io_->size();
|
|
|
|
|
DataBuf cheaderBuf(8); // Chunk header size : 4 bytes (data size) + 4 bytes (chunk type).
|
|
|
|
|
DataBuf cheaderBuf(8); // Chunk header: 4 bytes (data size) + 4 bytes (chunk type).
|
|
|
|
|
|
|
|
|
|
while(!io_->eof())
|
|
|
|
|
{
|
|
|
|
|
// Read chunk header.
|
|
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: Position: " << io_->tell() << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
std::memset(cheaderBuf.pData_, 0x0, cheaderBuf.size_);
|
|
|
|
|
long bufRead = io_->read(cheaderBuf.pData_, cheaderBuf.size_);
|
|
|
|
|
if (io_->error()) {
|
|
|
|
|
throw Error(kerFailedToReadImageData);
|
|
|
|
|
}
|
|
|
|
|
if (bufRead != cheaderBuf.size_) {
|
|
|
|
|
throw Error(kerInputDataReadFailed);
|
|
|
|
|
}
|
|
|
|
|
readChunk(cheaderBuf, *io_); // Read chunk header.
|
|
|
|
|
|
|
|
|
|
// Decode chunk data length.
|
|
|
|
|
uint32_t dataOffset = Exiv2::getULong(cheaderBuf.pData_, Exiv2::bigEndian);
|
|
|
|
|
uint32_t chunkLength = Exiv2::getULong(cheaderBuf.pData_, Exiv2::bigEndian);
|
|
|
|
|
long pos = io_->tell();
|
|
|
|
|
if (pos == -1 ||
|
|
|
|
|
dataOffset > uint32_t(0x7FFFFFFF) ||
|
|
|
|
|
static_cast<long>(dataOffset) > imgSize - pos) {
|
|
|
|
|
chunkLength > uint32_t(0x7FFFFFFF) ||
|
|
|
|
|
static_cast<long>(chunkLength) > imgSize - pos) {
|
|
|
|
|
throw Exiv2::Error(kerFailedToReadImageData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string chunkType(reinterpret_cast<char *>(cheaderBuf.pData_) + 4, 4);
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
|
std::string chunkName(reinterpret_cast<char *>(cheaderBuf.pData_) + 4, 4);
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: chunk name: " << chunkName << std::endl;
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: chunk type: " << chunkType
|
|
|
|
|
<< " length: " << chunkLength << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/// \todo analyse remaining chunks of the standard
|
|
|
|
|
// Perform a chunk triage for item that we need.
|
|
|
|
|
if (!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) ||
|
|
|
|
|
!memcmp(cheaderBuf.pData_ + 4, "iCCP", 4))
|
|
|
|
|
{
|
|
|
|
|
// Extract chunk data.
|
|
|
|
|
|
|
|
|
|
DataBuf cdataBuf(dataOffset);
|
|
|
|
|
bufRead = io_->read(cdataBuf.pData_, dataOffset);
|
|
|
|
|
if (io_->error()) {
|
|
|
|
|
throw Error(kerFailedToReadImageData);
|
|
|
|
|
}
|
|
|
|
|
if (bufRead != (long)dataOffset) {
|
|
|
|
|
throw Error(kerInputDataReadFailed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!memcmp(cheaderBuf.pData_ + 4, "IEND", 4))
|
|
|
|
|
{
|
|
|
|
|
// Last chunk found: we stop parsing.
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: Found IEND chunk with length: " << dataOffset << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else if (!memcmp(cheaderBuf.pData_ + 4, "IHDR", 4))
|
|
|
|
|
{
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: Found IHDR chunk with length: " << dataOffset << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
if (cdataBuf.size_ >= 8) {
|
|
|
|
|
PngChunk::decodeIHDRChunk(cdataBuf, &pixelWidth_, &pixelHeight_);
|
|
|
|
|
if (chunkType == "IEND" || chunkType == "IHDR" || chunkType == "tEXt" || chunkType == "zTXt" ||
|
|
|
|
|
chunkType == "iTXt" || chunkType == "iCCP") {
|
|
|
|
|
DataBuf chunkData(chunkLength);
|
|
|
|
|
readChunk(chunkData, *io_); // Extract chunk data.
|
|
|
|
|
|
|
|
|
|
if (chunkType == "IEND") {
|
|
|
|
|
return; // Last chunk found: we stop parsing.
|
|
|
|
|
} else if (chunkType == "IHDR" && chunkData.size_ >= 8) {
|
|
|
|
|
PngChunk::decodeIHDRChunk(chunkData, &pixelWidth_, &pixelHeight_);
|
|
|
|
|
} else if (chunkType == "tEXt") {
|
|
|
|
|
PngChunk::decodeTXTChunk(this, chunkData, PngChunk::tEXt_Chunk);
|
|
|
|
|
} else if (chunkType == "zTXt") {
|
|
|
|
|
PngChunk::decodeTXTChunk(this, chunkData, PngChunk::zTXt_Chunk);
|
|
|
|
|
} else if (chunkType == "iTXt") {
|
|
|
|
|
PngChunk::decodeTXTChunk(this, chunkData, PngChunk::iTXt_Chunk);
|
|
|
|
|
} else if (chunkType == "iCCP") {
|
|
|
|
|
// The ICC profile name can vary from 1-79 characters.
|
|
|
|
|
uint32_t iccOffset = 0;
|
|
|
|
|
while (iccOffset < 80 && iccOffset < chunkLength) {
|
|
|
|
|
if (chunkData.pData_[iccOffset++] == 0x00) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (!memcmp(cheaderBuf.pData_ + 4, "tEXt", 4))
|
|
|
|
|
{
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: Found tEXt chunk with length: " << dataOffset << std::endl;
|
|
|
|
|
#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 with length: " << dataOffset << std::endl;
|
|
|
|
|
#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 with length: " << dataOffset << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
PngChunk::decodeTXTChunk(this, cdataBuf, PngChunk::iTXt_Chunk);
|
|
|
|
|
}
|
|
|
|
|
else if (!memcmp(cheaderBuf.pData_ + 4, "iCCP", 4))
|
|
|
|
|
{
|
|
|
|
|
zlibToDataBuf(cdataBuf.pData_ +12+1,dataOffset-13,iccProfile_); // +1 = 'compressed' flag
|
|
|
|
|
profileName_ = std::string(reinterpret_cast<char *>(chunkData.pData_), iccOffset-1);
|
|
|
|
|
++iccOffset; // +1 = 'compressed' flag
|
|
|
|
|
|
|
|
|
|
zlibToDataBuf(chunkData.pData_ + iccOffset, chunkLength - iccOffset, iccProfile_);
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: Found iCCP chunk length: " << dataOffset << std::endl;
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: iccProfile.size_ : " << iccProfile_.size_ << std::endl;
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: profile name: " << profileName_ << std::endl;
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: iccProfile.size_ (uncompressed) : "
|
|
|
|
|
<< iccProfile_.size_ << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set dataOffset to null like chunk data have been extracted previously.
|
|
|
|
|
dataOffset = 0;
|
|
|
|
|
// Set chunkLength to 0 in case we have read a supported chunk type. Otherwise, we need to seek the
|
|
|
|
|
// file to the next chunk position.
|
|
|
|
|
chunkLength = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Move to the next chunk: chunk data size + 4 CRC bytes.
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: Seek to offset: " << dataOffset + 4 << std::endl;
|
|
|
|
|
std::cout << "Exiv2::PngImage::readMetadata: Seek to offset: " << chunkLength + 4 << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
io_->seek(dataOffset + 4 , BasicIo::cur);
|
|
|
|
|
io_->seek(chunkLength + 4 , BasicIo::cur);
|
|
|
|
|
if (io_->error() || io_->eof()) {
|
|
|
|
|
throw Error(kerFailedToReadImageData);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // PngImage::readMetadata
|
|
|
|
|
|
|
|
|
|
void PngImage::writeMetadata()
|
|
|
|
@ -550,10 +520,7 @@ namespace Exiv2 {
|
|
|
|
|
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(kerInputDataReadFailed);
|
|
|
|
|
if (!isPngType(*io_, true)) {
|
|
|
|
|
throw Error(kerNoImageInInputData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -652,24 +619,26 @@ namespace Exiv2 {
|
|
|
|
|
DataBuf compressed;
|
|
|
|
|
if ( zlibToCompressed(iccProfile_.pData_,iccProfile_.size_,compressed) ) {
|
|
|
|
|
|
|
|
|
|
const byte* header = (const byte*) "ICC PROFILE\0\0" ; // \0 = default compression
|
|
|
|
|
const byte* nullComp = (const byte*) "\0\0";
|
|
|
|
|
const byte* type = (const byte*) "iCCP";
|
|
|
|
|
uint32_t headerLen = 13 ;
|
|
|
|
|
uint32_t typeLen = 4;
|
|
|
|
|
uint32_t chunkLength = headerLen + compressed.size_ ;
|
|
|
|
|
const uint32_t nameLength = static_cast<uint32_t>(profileName_.size());
|
|
|
|
|
const uint32_t chunkLength = nameLength + 2 + compressed.size_ ;
|
|
|
|
|
byte length[4];
|
|
|
|
|
ul2Data (length,chunkLength,bigEndian);
|
|
|
|
|
|
|
|
|
|
// calculate CRC
|
|
|
|
|
uLong tmp = crc32(0L, Z_NULL, 0);
|
|
|
|
|
tmp = crc32(tmp, (const Bytef*)header ,headerLen);
|
|
|
|
|
tmp = crc32(tmp, (const Bytef*)type, 4);
|
|
|
|
|
tmp = crc32(tmp, (const Bytef*)profileName_.data(), nameLength);
|
|
|
|
|
tmp = crc32(tmp, (const Bytef*)nullComp, 2);
|
|
|
|
|
tmp = crc32(tmp, (const Bytef*)compressed.pData_,compressed.size_);
|
|
|
|
|
byte crc[4];
|
|
|
|
|
ul2Data(crc, tmp, bigEndian);
|
|
|
|
|
|
|
|
|
|
if( outIo.write(length,4) != 4
|
|
|
|
|
|| outIo.write(type ,typeLen) != (long) typeLen
|
|
|
|
|
|| outIo.write(header,headerLen) != (long) headerLen
|
|
|
|
|
if( outIo.write(length, 4) != 4
|
|
|
|
|
|| outIo.write(type, 4) != 4
|
|
|
|
|
|| outIo.write(reinterpret_cast<const byte*>(profileName_.data()), nameLength) != nameLength
|
|
|
|
|
|| outIo.write(nullComp,2) != 2
|
|
|
|
|
|| outIo.write (compressed.pData_,compressed.size_) != compressed.size_
|
|
|
|
|
|| outIo.write(crc,4) != 4
|
|
|
|
|
){
|
|
|
|
@ -677,7 +646,7 @@ namespace Exiv2 {
|
|
|
|
|
}
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
|
std::cout << "Exiv2::PngImage::doWriteMetadata: build iCCP"
|
|
|
|
|
<< " chunk (length: " << compressed.size_ + headerLen << ")" << std::endl;
|
|
|
|
|
<< " chunk (length: " << compressed.size_ + chunkLength << ")" << std::endl;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -754,6 +723,9 @@ namespace Exiv2 {
|
|
|
|
|
|
|
|
|
|
bool isPngType(BasicIo& iIo, bool advance)
|
|
|
|
|
{
|
|
|
|
|
if (iIo.error() || iIo.eof()) {
|
|
|
|
|
throw Error(kerInputDataReadFailed);
|
|
|
|
|
}
|
|
|
|
|
const int32_t len = 8;
|
|
|
|
|
byte buf[len];
|
|
|
|
|
iIo.read(buf, len);
|
|
|
|
|