New PNG image parser to extract EXIF/IPTC RAW profiles embeded in text tags by ImageMagick/GraphicsMagick during image convertion

Current implementation is read only.

TODO : Fix configure rules about zlib depency require by this implementation.
       Optimize image loading.
This commit is contained in:
HumanDynamo 2006-06-12 05:22:12 +00:00
parent 920da80d70
commit fa7223d103
6 changed files with 801 additions and 2 deletions

View File

@ -75,6 +75,8 @@ CCSRC = basicio.cpp \
nikonmn.cpp \
olympusmn.cpp \
panasonicmn.cpp \
pngimage.cpp \
pngchunk.cpp \
sigmamn.cpp \
sonymn.cpp \
tags.cpp \
@ -193,7 +195,7 @@ endif
# Compilation shortcuts
COMPILE.cc = $(CXX) $(CXXFLAGS) $(CXXDEFS) $(CXXINCS) -c
COMPILE.c = $(CC) $(CFLAGS) $(DEFS) $(INCS) -c
LINK.cc = $(CXX) $(CXXFLAGS) $(LDLIBS) $(LDFLAGS) -rpath $(libdir)
LINK.cc = $(CXX) $(CXXFLAGS) $(LDLIBS) $(LDFLAGS) -lz -rpath $(libdir)
# ******************************************************************************
# Rules

View File

@ -37,6 +37,7 @@ EXIV2_RCSID("@(#) $Id$");
#include "crwimage.hpp"
#include "mrwimage.hpp"
#include "tiffimage.hpp"
#include "pngimage.hpp"
// + standard includes
@ -50,7 +51,8 @@ namespace Exiv2 {
// Registry(ImageType::cr2, newCr2Instance, isCr2Type),
Registry(ImageType::crw, newCrwInstance, isCrwType),
Registry(ImageType::mrw, newMrwInstance, isMrwType),
Registry(ImageType::tiff, newTiffInstance, isTiffType)
Registry(ImageType::tiff, newTiffInstance, isTiffType),
Registry(ImageType::png, newPngInstance, isPngType)
};
} // namespace Exiv2

334
src/pngchunk.cpp Normal file
View File

@ -0,0 +1,334 @@
// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2006 Andreas Huggel <ahuggel@gmx.net>
*
* This program is part of the Exiv2 distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
*/
/*
File: pngchunk.cpp
Version: $Rev: 816 $
History: 07-Jun-06, gc: submitted
Credits: See header file
*/
// *****************************************************************************
#include "rcsid.hpp"
EXIV2_RCSID("@(#) $Id: pngchunk.cpp 816 2006-06-04 15:21:19Z ahuggel $");
// *****************************************************************************
// included header files
#ifdef _MSC_VER
# include "exv_msvc.h"
#else
# include "exv_conf.h"
#endif
#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
#include <zlib.h>
#include "pngchunk.hpp"
#include "tiffparser.hpp"
#include "exif.hpp"
#include "iptc.hpp"
#include "image.hpp"
#include "error.hpp"
// + standard includes
#include <string>
#include <cstring>
#include <iostream>
#include <cassert>
// *****************************************************************************
// class member definitions
namespace Exiv2 {
void PngChunk::decode(Image* pImage,
const byte* pData,
long size)
{
assert(pImage != 0);
assert(pData != 0);
// look for a tEXt chunk
long index = 8;
index += getLong(&pData[index], bigEndian) + 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))
{
if (!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "IEND", 4))
throw Error(14);
index += getLong(&pData[index], bigEndian) + PNG_CHUNK_HEADER_SIZE;
}
if (index < size-PNG_CHUNK_HEADER_SIZE)
{
// we found a tEXt or zTXt 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;
if(!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "zTXt", 4))
{
// Extract a deflate compressed Latin-1 text chunk
#ifdef DEBUG
std::cerr << "Exiv2::PngChunk::decode: 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);
if ( *compressionMethod != 0x00 )
{
// then it isn't zlib compressed and we are sunk
#ifdef DEBUG
std::cerr << "Exiv2::PngChunk::decode: Non-standard compression method.\n";
#endif
throw Error(14);
}
// compressed string after the compression technique spec
const byte* compressedText = &PNG_CHUNK_DATA(pData, index, keysize+2);
uint 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);
uLongf uncompressedLen = compressedTextSize * 2; // just a starting point
int zlibResult;
do
{
arr.alloc(uncompressedLen);
zlibResult = uncompress((Bytef*)arr.pData_, &uncompressedLen,
compressedText, compressedTextSize);
if (Z_OK == zlibResult)
{
// then it is all OK
arr.alloc(uncompressedLen);
}
else if (Z_BUF_ERROR == zlibResult)
{
// the uncompressedArray needs to be larger
#ifdef DEBUG
std::cerr << "Exiv2::PngChunk::decode: 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 (Z_BUF_ERROR == zlibResult);
if (zlibResult != Z_OK)
throw Error(14);
}
else if (!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "tEXt", 4))
{
// Extract a non-compressed Latin-1 text chunk
#ifdef DEBUG
std::cerr << "Exiv2::PngChunk::decode: 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);
arr.alloc(textsize);
arr = DataBuf(text, textsize);
}
else
{
#ifdef DEBUG
std::cerr << "Exiv2::PngChunk::decode: We found a field, not expected though\n";
#endif
throw Error(14);
}
#ifdef DEBUG
std::cerr << "Exiv2::PngChunk::decode: Found PNG entry " << std::string((const char*)key) << " / "
<< std::string((const char*)arr.pData_, 64) << "\n";
#endif
// We look if an EXIF raw profile exist.
if ( memcmp("Raw profile type exif", key, 21) == 0 ||
memcmp("Raw profile type APP1", key, 21) == 0 )
{
DataBuf exifData = readRawProfile(arr);
long length = exifData.size_;
if (length > 0)
{
// Find the position of Exif header in bytes array.
const byte exifHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
long pos = -1;
for (long i=0 ; i < length-(long)sizeof(exifHeader) ; i++)
{
if (memcmp(exifHeader, &exifData.pData_[i], sizeof(exifHeader)) == 0)
{
pos = i;
break;
}
}
// If found it, store only these data at from this place.
if (pos !=-1)
{
#ifdef DEBUG
std::cerr << "Exiv2::PngChunk::decode: Exif header found at position " << pos << "\n";
#endif
pos = pos + sizeof(exifHeader);
TiffParser::decode(pImage, exifData.pData_+pos, length-pos, TiffCreator::create);
}
}
}
// We look if an IPTC raw profile exist.
if ( memcmp("Raw profile type iptc", key, 21) == 0 )
{
DataBuf iptcData = readRawProfile(arr);
long length = iptcData.size_;
if (length > 0)
pImage->iptcData().load(iptcData.pData_, length);
}
index += getLong(&pData[index], bigEndian) + PNG_CHUNK_HEADER_SIZE;
}
}
} // PngChunk::decode
DataBuf PngChunk::readRawProfile(const DataBuf& text)
{
DataBuf info;
register long i;
register unsigned char *dp;
const char *sp;
uint nibbles;
long length;
unsigned char unhex[103]={0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,1, 2,3,4,5,6,7,8,9,0,0,
0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,10,11,12,
13,14,15};
sp = (char*)text.pData_+1;
// Look for newline
while (*sp != '\n')
sp++;
// Look for length
while (*sp == '\0' || *sp == ' ' || *sp == '\n')
sp++;
length = (long) atol(sp);
while (*sp != ' ' && *sp != '\n')
sp++;
// Allocate space
if (length == 0)
{
std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: invalid profile length\n";
return DataBuf();
}
info.alloc(length);
if (info.size_ != length)
{
std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: cannot allocate memory\n";
return DataBuf();
}
// Copy profile, skipping white space and column 1 "=" signs
dp = (unsigned char*)info.pData_;
nibbles = length * 2;
for (i = 0; i < (long) nibbles; i++)
{
while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f')
{
if (*sp == '\0')
{
std::cerr << "Exiv2::PngChunk::readRawProfile: Unable To Copy Raw Profile: ran out of data\n";
return DataBuf();
}
sp++;
}
if (i%2 == 0)
*dp = (unsigned char) (16*unhex[(int) *sp++]);
else
(*dp++) += unhex[(int) *sp++];
}
return info;
} // PngChunk::readRawProfile
} // namespace Exiv2

88
src/pngchunk.hpp Normal file
View File

@ -0,0 +1,88 @@
// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2006 Andreas Huggel <ahuggel@gmx.net>
*
* This program is part of the Exiv2 distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
*/
/*!
@file pngchunk.hpp
@brief Class PngChunk to parse PNG chunk data.
@version $Rev: 808 $
@author Andreas Huggel (ahu)
<a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a>
@author Gilles Caulier (gc)
<a href="mailto:caulier dot gilles at kdemail dot net">caulier dot gilles at kdemail dot net</a>
@date 13-Jun-06, gc: submitted
*/
#ifndef PNGCHUNK_HPP_
#define PNGCHUNK_HPP_
// *****************************************************************************
// included header files
#include "types.hpp"
// + standard includes
#include <iosfwd>
#include <cassert>
// *****************************************************************************
// namespace extensions
namespace Exiv2 {
// *****************************************************************************
// class declarations
class Image;
// *****************************************************************************
// class definitions
/*!
@brief Stateless parser class for data in PNG chunk format. Images use this
class to decode and encode PNG-based data.
*/
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.
*/
static void decode(Image* pImage,
const byte* pData,
long size);
private:
//! @name Accessors
//@{
/*!
@brief Todo: Decode ImageMagick raw text profile including encoded Exif/Iptc metadata byte array.
*/
static DataBuf readRawProfile(const DataBuf& text);
//@}
}; // class PngChunk
} // namespace Exiv2
#endif // #ifndef PNGCHUNK_HPP_

187
src/pngimage.cpp Normal file
View File

@ -0,0 +1,187 @@
// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2006 Andreas Huggel <ahuggel@gmx.net>
*
* This program is part of the Exiv2 distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
*/
/*
File: pngimage.cpp
Version: $Rev: 808 $
History: 07-Jun-06, gc: submitted
Credits: See header file
*/
// *****************************************************************************
#include "rcsid.hpp"
EXIV2_RCSID("@(#) $Id: pngimage.cpp 808 2006-06-01 15:09:39Z ahuggel $");
// *****************************************************************************
#define DEBUG 1
// *****************************************************************************
// included header files
#ifdef _MSC_VER
# include "exv_msvc.h"
#else
# include "exv_conf.h"
#endif
#include "pngchunk.hpp"
#include "pngimage.hpp"
#include "image.hpp"
#include "basicio.hpp"
#include "error.hpp"
#include "futils.hpp"
// + standard includes
#include <string>
#include <cstring>
#include <iostream>
#include <cassert>
// *****************************************************************************
// class member definitions
namespace Exiv2 {
PngImage::PngImage(BasicIo::AutoPtr io, bool create)
: Image(mdExif | mdIptc), io_(io)
{
if (create)
{
IoCloser closer(*io_);
io_->open();
}
} // PngImage::PngImage
bool PngImage::good() const
{
if (io_->open() != 0) return false;
IoCloser closer(*io_);
return isThisType(*io_, false);
}
void PngImage::clearMetadata()
{
clearExifData();
}
void PngImage::setMetadata(const Image& image)
{
setExifData(image.exifData());
}
void PngImage::clearExifData()
{
exifData_.clear();
}
void PngImage::setExifData(const ExifData& exifData)
{
exifData_ = exifData;
}
void PngImage::clearIptcData()
{
iptcData_.clear();
}
void PngImage::setIptcData(const IptcData& iptcData)
{
iptcData_ = iptcData;
}
void PngImage::clearComment()
{
// not supported
}
void PngImage::setComment(const std::string& comment)
{
// not supported
}
void PngImage::readMetadata()
{
#ifdef DEBUG
std::cerr << "Exiv2::PngImage::readMetadata: Reading PNG file " << io_->path() << "\n";
#endif
if (io_->open() != 0)
{
throw Error(9, io_->path(), strError());
}
IoCloser closer(*io_);
// Ensure that this is the correct image type
if (!isThisType(*io_, false))
{
if (io_->error() || io_->eof()) throw Error(14);
throw Error(3, "PNG");
}
clearMetadata();
DataBuf buf = io_->read(io_->size());
if (io_->error() || io_->eof()) throw Error(14);
PngChunk::decode(this, buf.pData_, buf.size_);
} // PngImage::readMetadata
void PngImage::writeMetadata()
{
/*
TODO: implement me!
*/
} // PngImage::writeMetadata
bool PngImage::isThisType(BasicIo& iIo, bool advance) const
{
return isPngType(iIo, advance);
}
// *************************************************************************
// free functions
Image::AutoPtr newPngInstance(BasicIo::AutoPtr io, bool create)
{
Image::AutoPtr image(new PngImage(io, create));
if (!image->good())
{
image.reset();
}
return image;
}
bool isPngType(BasicIo& iIo, bool advance)
{
const int32_t len = 8;
const unsigned char pngID[8] = {'\211', 'P', 'N', 'G', '\r', '\n', '\032', '\n'};
byte buf[len];
iIo.read(buf, len);
if (iIo.error() || iIo.eof())
{
return false;
}
int rc = memcmp(buf, pngID, 8);
if (!advance || rc != 0)
{
iIo.seek(-len, BasicIo::cur);
}
return rc == 0;
}
} // namespace Exiv2

186
src/pngimage.hpp Normal file
View File

@ -0,0 +1,186 @@
// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2006 Andreas Huggel <ahuggel@gmx.net>
*
* This program is part of the Exiv2 distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
*/
/*!
@file pngimage.hpp
@brief PNG image, implemented using the following references:
<a href="http://www.w3.org/TR/PNG/">PNG specification</a> by W3C<br>
<a href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html">PNG tags list</a> by Phil Harvey<br>
@version $Rev: 797 $
@author Andreas Huggel (ahu)
<a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a>
@author Gilles Caulier (gc)
<a href="mailto:caulier dot gilles at kdemail dot net">caulier dot gilles at kdemail dot net</a>
@date 07-Jun-06, gc: submitted
*/
#ifndef PNGIMAGE_HPP_
#define PNGIMAGE_HPP_
// *****************************************************************************
// included header files
#include "exif.hpp"
#include "iptc.hpp"
#include "image.hpp"
// + standard includes
#include <string>
// *****************************************************************************
// namespace extensions
namespace Exiv2 {
// *****************************************************************************
// class definitions
// Add PNG to the supported image formats
namespace ImageType {
const int png = 6; //!< PNG image type (see class PngImage)
}
/*!
@brief Class to access raw PNG images. Exif/IPTC metadata are supported
directly.
*/
class PngImage : public Image {
friend bool isPngType(BasicIo& iIo, bool advance);
//! @name NOT Implemented
//@{
//! Copy constructor
PngImage(const PngImage& rhs);
//! Assignment operator
PngImage& operator=(const PngImage& rhs);
//@}
public:
//! @name Creators
//@{
/*!
@brief Constructor that can either open an existing PNG image or create
a new image from scratch. If a new image is to be created, any
existing data is overwritten. Since the constructor can not return
a result, callers should check the good() method after object
construction to determine success or failure.
@param io An auto-pointer that owns a BasicIo instance used for
reading and writing image metadata. \b Important: The constructor
takes ownership of the passed in BasicIo instance through the
auto-pointer. Callers should not continue to use the BasicIo
instance after it is passed to this method. Use the Image::io()
method to get a temporary reference.
@param create Specifies if an existing image should be read (false)
or if a new file should be created (true).
*/
PngImage(BasicIo::AutoPtr io, bool create);
//! Destructor
~PngImage() {}
//@}
//! @name Manipulators
//@{
void readMetadata();
/*!
@brief Todo: Write metadata back to the image. This method is not
yet implemented.
*/
void writeMetadata();
void setExifData(const ExifData& exifData);
void clearExifData();
void setIptcData(const IptcData& iptcData);
void clearIptcData();
/*!
@brief Not supported. PNG format does not contain a comment.
Calling this function will do nothing.
*/
void setComment(const std::string& comment);
/*!
@brief Not supported. PNG format does not contain a comment.
Calling this function will do nothing.
*/
void clearComment();
void setMetadata(const Image& image);
void clearMetadata();
ExifData& exifData() { return exifData_; }
IptcData& iptcData() { return iptcData_; }
//@}
//! @name Accessors
//@{
bool good() const;
const ExifData& exifData() const { return exifData_; }
const IptcData& iptcData() const { return iptcData_; }
std::string comment() const { return comment_; }
BasicIo& io() const { return *io_; }
//@}
private:
//! @name Accessors
//@{
/*!
@brief Determine if the content of the BasicIo instance is a PNG image.
The advance flag determines if the read position in the stream is
moved (see below). This applies only if the type matches and the
function returns true. If the type does not match, the stream
position is not changed. However, if reading from the stream fails,
the stream position is undefined. Consult the stream state to obtain
more information in this case.
@param iIo BasicIo instance to read from.
@param advance Flag indicating whether the position of the io
should be advanced by the number of characters read to
analyse the data (true) or left at its original
position (false). This applies only if the type matches.
@return true if the data matches the type of this class;<BR>
false if the data does not match
*/
bool isThisType(BasicIo& iIo, bool advance) const;
/*!
@brief Todo: Write PNG header. Not implemented yet.
*/
int writeHeader(BasicIo& oIo) const;
//@}
// DATA
BasicIo::AutoPtr io_; //!< Image data io pointer
ExifData exifData_; //!< Exif data container
IptcData iptcData_; //!< IPTC data container
std::string comment_; //!< User comment
}; // class PngImage
// *****************************************************************************
// template, inline and free functions
// These could be static private functions on Image subclasses but then
// ImageFactory needs to be made a friend.
/*!
@brief Create a new PngImage instance and return an auto-pointer to it.
Caller owns the returned object and the auto-pointer ensures that
it will be deleted.
*/
Image::AutoPtr newPngInstance(BasicIo::AutoPtr io, bool create);
//! Check if the file iIo is a PNG image.
bool isPngType(BasicIo& iIo, bool advance);
} // namespace Exiv2
#endif // #ifndef PNGIMAGE_HPP_