From fdd338981ae4134f4a4fa224a99502998fe5ef20 Mon Sep 17 00:00:00 2001 From: Andreas Huggel Date: Sat, 21 Feb 2004 12:02:24 +0000 Subject: [PATCH] MakerNote related changes and Entry clean-up --- src/exif.cpp | 200 +++++++++++++++++++++++++++++++------------ src/exif.hpp | 178 ++++++++++++++++++++++---------------- src/ifd.cpp | 236 ++++++++++++++++++++++++--------------------------- src/ifd.hpp | 179 +++++++++++++++++--------------------- 4 files changed, 437 insertions(+), 356 deletions(-) diff --git a/src/exif.cpp b/src/exif.cpp index 8977a4e1..a33f98e7 100644 --- a/src/exif.cpp +++ b/src/exif.cpp @@ -20,14 +20,14 @@ */ /* File: exif.cpp - Version: $Name: $ $Revision: 1.20 $ + Version: $Name: $ $Revision: 1.21 $ Author(s): Andreas Huggel (ahu) History: 26-Jan-04, ahu: created 11-Feb-04, ahu: isolated as a component */ // ***************************************************************************** #include "rcsid.hpp" -EXIV2_RCSID("@(#) $Name: $ $Revision: 1.20 $ $RCSfile: exif.cpp,v $") +EXIV2_RCSID("@(#) $Name: $ $Revision: 1.21 $ $RCSfile: exif.cpp,v $") // ***************************************************************************** // included header files @@ -38,6 +38,7 @@ EXIV2_RCSID("@(#) $Name: $ $Revision: 1.20 $ $RCSfile: exif.cpp,v $") #include "ifd.hpp" #include "tags.hpp" #include "image.hpp" +#include "makernote.hpp" // + standard includes #include @@ -49,23 +50,42 @@ EXIV2_RCSID("@(#) $Name: $ $Revision: 1.20 $ $RCSfile: exif.cpp,v $") #include #include +// ***************************************************************************** +// local declarations +namespace { + + /* + Set the data of the entry identified by tag in ifd to an unsigned long + with the value of offset. If no entry with this tag exists in ifd, an + entry of type unsigned long with one component is created. + */ + void setOffsetTag(Exif::Ifd& ifd, + Exif::uint16 tag, + Exif::uint32 offset, + Exif::ByteOrder byteOrder); + +} + // ***************************************************************************** // class member definitions namespace Exif { Metadatum::Metadatum(const Entry& e, ByteOrder byteOrder) - : tag_(e.tag()), ifdId_(e.ifdId()), ifdIdx_(e.ifdIdx()), value_(0) + : tag_(e.tag()), ifdId_(e.ifdId()), makerNote_(e.makerNote()), + value_(0) { value_ = Value::create(TypeId(e.type())); value_->read(e.data(), e.size(), byteOrder); - key_ = ExifTags::makeKey(tag_, ifdId_); + key_ = makeKey(tag_, ifdId_, makerNote_); } - Metadatum::Metadatum(const std::string& key, Value* value) - : ifdIdx_(-1), value_(0), key_(key) + Metadatum::Metadatum(const std::string& key, + const Value* value, + MakerNote* makerNote) + : makerNote_(makerNote), value_(0), key_(key) { if (value) value_ = value->clone(); - std::pair p = ExifTags::decomposeKey(key); + std::pair p = decomposeKey(key, makerNote); if (p.first == 0xffff) throw Error("Invalid key"); tag_ = p.first; if (p.second == ifdIdNotSet) throw Error("Invalid key"); @@ -75,11 +95,12 @@ namespace Exif { Metadatum::~Metadatum() { delete value_; + // do *not* delete the MakerNote } Metadatum::Metadatum(const Metadatum& rhs) - : tag_(rhs.tag_), ifdId_(rhs.ifdId_), - ifdIdx_(rhs.ifdIdx_), value_(0), key_(rhs.key_) + : tag_(rhs.tag_), ifdId_(rhs.ifdId_), makerNote_(rhs.makerNote_), + value_(0), key_(rhs.key_) { if (rhs.value_ != 0) value_ = rhs.value_->clone(); // deep copy } @@ -89,7 +110,7 @@ namespace Exif { if (this == &rhs) return *this; tag_ = rhs.tag_; ifdId_ = rhs.ifdId_; - ifdIdx_ = rhs.ifdIdx_; + makerNote_ = rhs.makerNote_; delete value_; value_ = 0; if (rhs.value_ != 0) value_ = rhs.value_->clone(); // deep copy @@ -109,6 +130,22 @@ namespace Exif { value_->read(buf); } + std::string Metadatum::tagName() const + { + if (ifdId_ == makerIfd && makerNote_ != 0) { + return makerNote_->tagName(tag_); + } + return ExifTags::tagName(tag_, ifdId_); + } + + std::string Metadatum::sectionName() const + { + if (ifdId_ == makerIfd && makerNote_ != 0) { + return makerNote_->sectionName(tag_); + } + return ExifTags::sectionName(tag_, ifdId_); + } + Thumbnail::Thumbnail() : type_(none), size_(0), image_(0), ifd_(ifd1, 0, false) { @@ -392,7 +429,7 @@ namespace Exif { { Ifd::iterator pos = ifd1.findTag(0x0201); if (pos == ifd1.end()) throw Error("Bad thumbnail (0x0201)"); - pos->setOffset(ifd1.offset() + ifd1.size() + ifd1.dataSize(), byteOrder); + pos->setValue(ifd1.offset() + ifd1.size() + ifd1.dataSize(), byteOrder); } void Thumbnail::setTiffImageOffsets(Ifd& ifd1, ByteOrder byteOrder) const @@ -416,7 +453,7 @@ namespace Exif { } // Thumbnail::setTiffImageOffsets ExifData::ExifData() - : ifd0_(ifd0, 0, false), exifIfd_(exifIfd, 0, false), + : makerNote_(0), ifd0_(ifd0, 0, false), exifIfd_(exifIfd, 0, false), iopIfd_(iopIfd, 0, false), gpsIfd_(gpsIfd, 0, false), ifd1_(ifd1, 0, false), valid_(false), size_(0), data_(0) { @@ -424,6 +461,7 @@ namespace Exif { ExifData::~ExifData() { + delete makerNote_; delete[] data_; } @@ -469,8 +507,34 @@ namespace Exif { if (rc) return rc; } + // Find MakerNote in ExifIFD, create a MakerNote class + Ifd::iterator pos = exifIfd_.findTag(0x927c); + Ifd::iterator make = ifd0_.findTag(0x010f); + Ifd::iterator model = ifd0_.findTag(0x0110); + if (pos != exifIfd_.end() && make != ifd0_.end() && model != ifd0_.end()) { + MakerNoteFactory& makerNoteFactory = MakerNoteFactory::instance(); + // Todo: The conversion to string assumes that there is a \0 at the end + makerNote_ = makerNoteFactory.create(make->data(), model->data()); + } + // Read the MakerNote + if (makerNote_) { + rc = makerNote_->read(pos->data(), + pos->size(), + byteOrder(), + exifIfd_.offset() + pos->offset()); + if (rc) { + delete makerNote_; + makerNote_ = 0; + } + } + // If we successfully parsed the MakerNote, delete the raw MakerNote, + // the parsed MakerNote is the primary MakerNote from now on + if (makerNote_) { + exifIfd_.erase(pos); + } + // Find and delete ExifIFD sub-IFD of IFD1 - Ifd::iterator pos = ifd1_.findTag(0x8769); + pos = ifd1_.findTag(0x8769); if (pos != ifd1_.end()) { ifd1_.erase(pos); ret = -99; @@ -482,10 +546,13 @@ namespace Exif { ret = -99; } - // Copy all entries from the IFDs to the internal metadata + // Copy all entries from the IFDs and the MakerNote to the metadata metadata_.clear(); add(ifd0_.begin(), ifd0_.end(), byteOrder()); add(exifIfd_.begin(), exifIfd_.end(), byteOrder()); + if (makerNote_) { + add(makerNote_->begin(), makerNote_->end(), byteOrder()); + } add(iopIfd_.begin(), iopIfd_.end(), byteOrder()); add(gpsIfd_.begin(), gpsIfd_.end(), byteOrder()); add(ifd1_.begin(), ifd1_.end(), byteOrder()); @@ -506,6 +573,7 @@ namespace Exif { } JpegImage img; img.setExifData(buf, actualSize); + delete[] buf; return img.writeExifData(path); } // ExifData::write @@ -551,7 +619,7 @@ std::cout << "->>>>>> writing from metadata <<<<<<-\n"; // Set the offset to the Exif IFD in IFD0 ifd0.erase(0x8769); if (exifIfd.size() > 0) { - ifd0.setOffset(0x8769, exifIfdOffset, byteOrder()); + setOffsetTag(ifd0, 0x8769, exifIfdOffset, byteOrder()); } // Build Interoperability IFD from metadata @@ -562,7 +630,7 @@ std::cout << "->>>>>> writing from metadata <<<<<<-\n"; // Set the offset to the Interoperability IFD in Exif IFD exifIfd.erase(0xa005); if (iopIfd.size() > 0) { - exifIfd.setOffset(0xa005, iopIfdOffset, byteOrder()); + setOffsetTag(exifIfd, 0xa005, iopIfdOffset, byteOrder()); } // Build GPSInfo IFD from metadata @@ -573,7 +641,7 @@ std::cout << "->>>>>> writing from metadata <<<<<<-\n"; // Set the offset to the GPSInfo IFD in IFD0 ifd0.erase(0x8825); if (gpsIfd.size() > 0) { - ifd0.setOffset(0x8825, gpsIfdOffset, byteOrder()); + setOffsetTag(ifd0, 0x8825, gpsIfdOffset, byteOrder()); } // Update Exif data from thumbnail, build IFD1 from updated metadata @@ -633,11 +701,11 @@ std::cout << "->>>>>> writing from metadata <<<<<<-\n"; return size; } - void ExifData::add(Ifd::const_iterator begin, - Ifd::const_iterator end, + void ExifData::add(Entries::const_iterator begin, + Entries::const_iterator end, ByteOrder byteOrder) { - Ifd::const_iterator i = begin; + Entries::const_iterator i = begin; for (; i != end; ++i) { add(Metadatum(*i, byteOrder)); } @@ -650,13 +718,8 @@ std::cout << "->>>>>> writing from metadata <<<<<<-\n"; void ExifData::add(const Metadatum& metadatum) { - iterator i = findKey(metadatum.key()); - if (i != end()) { - i->setValue(&metadatum.value()); - } - else { - metadata_.push_back(metadatum); - } + // allow duplicates + metadata_.push_back(metadatum); } ExifData::const_iterator ExifData::findKey(const std::string& key) const @@ -681,12 +744,6 @@ std::cout << "->>>>>> writing from metadata <<<<<<-\n"; std::sort(metadata_.begin(), metadata_.end(), cmpMetadataByTag); } - void ExifData::erase(const std::string& key) - { - iterator pos = findKey(key); - if (pos != end()) erase(pos); - } - void ExifData::erase(ExifData::iterator pos) { metadata_.erase(pos); @@ -714,7 +771,8 @@ std::cout << "->>>>>> writing from metadata <<<<<<-\n"; Ifd::iterator end = ifd.end(); for (Ifd::iterator entry = ifd.begin(); entry != end; ++entry) { // find the corresponding metadatum - std::string key = ExifTags::makeKey(entry->tag(), entry->ifdId()); + std::string key = + makeKey(entry->tag(), entry->ifdId(), entry->makerNote()); const_iterator md = findKey(key); if (md == this->end()) { // corresponding metadatum was deleted: this is not (yet) a @@ -724,7 +782,7 @@ std::cout << "->>>>>> writing from metadata <<<<<<-\n"; } char* buf = new char[md->size()]; md->copy(buf, byteOrder()); - entry->setValue(md->typeId(), buf, md->size()); + entry->setValue(md->typeId(), md->count(), buf, md->size()); delete[] buf; } return compatible; @@ -802,23 +860,14 @@ std::cout << "->>>>>> writing from metadata <<<<<<-\n"; // Todo: Implement Assert (Stroustup 24.3.7.2) if (!ifd.alloc()) throw Error("Invariant violated in addToIfd"); - RawEntry e; - e.ifdId_ = metadatum.ifdId(); - e.ifdIdx_ = metadatum.ifdIdx(); - e.tag_ = metadatum.tag(); - e.type_ = metadatum.typeId(); - e.count_ = metadatum.count(); - e.size_ = metadatum.size(); - e.offset_ = 0; // will be calculated when the IFD is written - char* buf = 0; - if (e.size_ > 4) { - buf = new char[e.size_]; - metadatum.copy(buf, byteOrder); - } - else { - metadatum.copy(e.offsetData_, byteOrder); - } - ifd.add(Entry(e, buf, ifd.alloc())); + Entry e(ifd.alloc()); + e.setIfdId(metadatum.ifdId()); + e.setTag(metadatum.tag()); + e.setOffset(0); // will be calculated when the IFD is written + char* buf = new char[metadatum.size()]; + metadatum.copy(buf, byteOrder); + e.setValue(metadatum.typeId(), metadatum.count(), buf, metadatum.size()); + ifd.add(e); delete[] buf; } // addToIfd @@ -834,8 +883,51 @@ std::cout << "->>>>>> writing from metadata <<<<<<-\n"; std::ostream& operator<<(std::ostream& os, const Metadatum& md) { - PrintFct fct = ExifTags::printFct(md.tag(), md.ifdId()); - return fct(os, md.value()); + if (md.ifdId() == makerIfd && md.makerNote() != 0) { + return md.makerNote()->printTag(os, md.tag(), md.value()); + } + return ExifTags::printTag(os, md.tag(), md.ifdId(), md.value()); + } + + std::string makeKey(uint16 tag, IfdId ifdId, const MakerNote* makerNote) + { + if (ifdId == makerIfd && makerNote != 0) { + return makerNote->makeKey(tag); + } + return ExifTags::makeKey(tag, ifdId); + } + + std::pair decomposeKey(const std::string& key, + const MakerNote* makerNote) + { + std::pair p = ExifTags::decomposeKey(key); + if (p.second == makerIfd && makerNote != 0) { + p.first = makerNote->decomposeKey(key); + } + return p; } } // namespace Exif + +// ***************************************************************************** +// local definitions +namespace { + + void setOffsetTag(Exif::Ifd& ifd, + Exif::uint16 tag, + Exif::uint32 offset, + Exif::ByteOrder byteOrder) + { + Exif::Ifd::iterator pos = ifd.findTag(tag); + if (pos == ifd.end()) { + Exif::Entry e(ifd.alloc()); + e.setIfdId(ifd.ifdId()); + e.setTag(tag); + e.setOffset(0); // will be calculated when the IFD is written + ifd.add(e); + pos = ifd.findTag(tag); + } + pos->setValue(offset, byteOrder); + } + +} diff --git a/src/exif.hpp b/src/exif.hpp index c1607b9a..80e10be1 100644 --- a/src/exif.hpp +++ b/src/exif.hpp @@ -21,7 +21,7 @@ /*! @file exif.hpp @brief Encoding and decoding of %Exif data - @version $Name: $ $Revision: 1.22 $ + @version $Name: $ $Revision: 1.23 $ @author Andreas Huggel (ahu) ahuggel@gmx.net @date 09-Jan-04, ahu: created @@ -37,6 +37,7 @@ #include "value.hpp" #include "ifd.hpp" #include "tags.hpp" +#include "makernote.hpp" // + standard includes #include @@ -73,10 +74,14 @@ namespace Exif { @param key The key of the metadatum. @param value Pointer to a metadatum value. + @param makerNote Pointer to the associated MakerNote (only needed for + MakerNote tags). @throw Error ("Invalid key") if the key cannot be parsed and converted to a tag number and an IFD id or the section name does not match. */ - explicit Metadatum(const std::string& key, Value* value =0); + explicit Metadatum(const std::string& key, + const Value* value =0, + MakerNote* makerNote =0); //! Destructor ~Metadatum(); //! Copy constructor @@ -94,41 +99,52 @@ namespace Exif { */ void setValue(const std::string& buf); /*! - @brief Return a pointer to a copy (clone) of the value. The caller - is responsible to delete this copy when it's no longer needed. + @brief Write value to a character data buffer and return the number + of characters (bytes) written. - This method is provided for users who need full control over the - value. A caller may, e.g., downcast the pointer to the appropriate - subclass of Value to make use of the interface of the subclass to set - or modify its contents. - - @return A pointer to a copy (clone) of the value, 0 if the value is - not set. - */ - Value* getValue() const { return value_ == 0 ? 0 : value_->clone(); } - //! Return the name of the tag - const char* tagName() const { return ExifTags::tagName(tag_, ifdId_); } - //! Return the name of the type - const char* typeName() const { return TypeInfo::typeName(typeId()); } - //! Return the size in bytes of one component of this type - long typeSize() const { return TypeInfo::typeSize(typeId()); } - //! Return the name of the IFD - const char* ifdName() const { return ExifTags::ifdName(ifdId_); } - //! Return the related image item (image or thumbnail) - const char* ifdItem() const { return ExifTags::ifdItem(ifdId_); } - //! Return the name of the section - const char* sectionName() const - { return ExifTags::sectionName(tag_, ifdId_); } + The user must ensure that the buffer has enough memory. Otherwise + the call results in undefined behaviour. + + @param buf Data buffer to write to. + @param byteOrder Applicable byte order (little or big endian). + @return Number of characters written. + */ + long copy(char* buf, ByteOrder byteOrder) const + { return value_ == 0 ? 0 : value_->copy(buf, byteOrder); } //! @name Accessors //@{ + /*! + @brief Return a key for the tag. The key is of the form + 'ifdItem.sectionName.tagName'. + */ + std::string key() const { return key_; } + //! Return the related image item + const char* ifdItem() const { return ExifTags::ifdItem(ifdId_); } + //! Return the name of the section + std::string sectionName() const; + //! Return the name of the tag + std::string tagName() const; //! Return the tag uint16 tag() const { return tag_; } //! Return the type id. TypeId typeId() const { return value_ == 0 ? invalid : value_->typeId(); } + //! Return the name of the type + const char* typeName() const { return TypeInfo::typeName(typeId()); } + //! Return the size in bytes of one component of this type + long typeSize() const { return TypeInfo::typeSize(typeId()); } //! Return the number of components in the value long count() const { return value_ == 0 ? 0 : value_->count(); } //! Return the size of the value in bytes long size() const { return value_ == 0 ? 0 : value_->size(); } + //! Return the IFD id + IfdId ifdId() const { return ifdId_; } + //! Return the name of the IFD + const char* ifdName() const { return ExifTags::ifdName(ifdId_); } + //! Return the pointer to the associated MakerNote + MakerNote* makerNote() const { return makerNote_; } + //! Return the value as a string. + std::string toString() const + { return value_ == 0 ? "" : value_->toString(); } /*! @brief Return the n-th component of the value converted to long. The return value is -1 if the value of the Metadatum is not set and @@ -153,26 +169,19 @@ namespace Exif { */ Rational toRational(long n =0) const { return value_ == 0 ? Rational(-1, 1) : value_->toRational(n); } - //! Return the value as a string. - std::string toString() const - { return value_ == 0 ? "" : value_->toString(); } /*! - @brief Write value to a character data buffer and return the number - of characters (bytes) written. + @brief Return a pointer to a copy (clone) of the value. The caller + is responsible to delete this copy when it's no longer needed. - The user must ensure that the buffer has enough memory. Otherwise - the call results in undefined behaviour. - - @param buf Data buffer to write to. - @param byteOrder Applicable byte order (little or big endian). - @return Number of characters written. - */ - long copy(char* buf, ByteOrder byteOrder) const - { return value_ == 0 ? 0 : value_->copy(buf, byteOrder); } - //! Return the IFD id - IfdId ifdId() const { return ifdId_; } - //! Return the position in the IFD (-1: not set) - int ifdIdx() const { return ifdIdx_; } + This method is provided for users who need full control over the + value. A caller may, e.g., downcast the pointer to the appropriate + subclass of Value to make use of the interface of the subclass to set + or modify its contents. + + @return A pointer to a copy (clone) of the value, 0 if the value is + not set. + */ + Value* getValue() const { return value_ == 0 ? 0 : value_->clone(); } /*! @brief Return a constant reference to the value. @@ -194,19 +203,14 @@ namespace Exif { */ const Value& value() const { if (value_) return *value_; throw Error("Value not set"); } - /*! - @brief Return a unique key for the tag. The key is of the form - 'ifdItem.sectionName.tagName'. - */ - std::string key() const { return key_; } //@} private: uint16 tag_; //!< Tag value IfdId ifdId_; //!< The IFD associated with this tag - int ifdIdx_; //!< Position in the IFD (-1: not set) + MakerNote* makerNote_; //!< Pointer to the associated MakerNote Value* value_; //!< Pointer to the value - std::string key_; //!< Unique key + std::string key_; //!< Key }; // class Metadatum @@ -344,7 +348,7 @@ namespace Exif { Provide high-level access to the %Exif data of an image: - read %Exif information from JPEG files - - access metadata through unique keys and standard C++ iterators + - access metadata through keys and standard C++ iterators - add, modify and delete metadata - write %Exif data to JPEG files - extract %Exif metadata to files, insert from these files @@ -411,25 +415,25 @@ namespace Exif { */ long copy(char* buf); /*! - @brief Add all IFD entries in the range from iterator position begin - to iterator position end to the %Exif metadata. Checks for - duplicates: if a metadatum already exists, its value is - overwritten. + @brief Add all (IFD) entries in the range from iterator position begin + to iterator position end to the %Exif metadata. No duplicate + checks are performed, i.e., it is possible to add multiple + metadata with the same key. */ - void add(Ifd::const_iterator begin, - Ifd::const_iterator end, + void add(Entries::const_iterator begin, + Entries::const_iterator end, ByteOrder byteOrder); /*! - @brief Add a metadatum from the supplied key and value pair. - This method copies (clones) the value. If a metadatum with the - given key already exists, its value is overwritten and no new - metadatum is added. + @brief Add a metadatum from the supplied key and value pair. This + method copies (clones) the value. No duplicate checks are + performed, i.e., it is possible to add multiple metadata with + the same key. */ void add(const std::string& key, Value* value); /*! - @brief Add a copy of the metadatum to the %Exif metadata. If a - metadatum with the given key already exists, its value is - overwritten and no new metadatum is added. + @brief Add a copy of the metadatum to the %Exif metadata. No + duplicate checks are performed, i.e., it is possible to add + multiple metadata with the same key. */ void add(const Metadatum& metadatum); @@ -455,16 +459,22 @@ namespace Exif { iterator begin() { return metadata_.begin(); } //! End of the metadata iterator end() { return metadata_.end(); } - //! Find a metadatum by its key, return an iterator to it + /*! + @brief Find a metadatum with the given key, return an iterator to it. + If multiple metadata with the same key exist, it is undefined + which of the matching metadata is found. + */ iterator findKey(const std::string& key); - //! Find a metadatum by its key, return a const iterator to it + /*! + @brief Find a metadatum with the given key, return a const iterator to + it. If multiple metadata with the same key exist, it is + undefined which of the matching metadata is found. + */ const_iterator findKey(const std::string& key) const; //! Sort metadata by key void sortByKey(); //! Sort metadata by tag void sortByTag(); - //! Delete the metadatum with a given key - void erase(const std::string& key); //! Delete the metadatum at iterator position pos void erase(iterator pos); @@ -520,6 +530,7 @@ namespace Exif { TiffHeader tiffHeader_; Metadata metadata_; Thumbnail thumbnail_; + MakerNote* makerNote_; Ifd ifd0_; Ifd exifIfd_; @@ -539,17 +550,18 @@ namespace Exif { /*! @brief Add all metadata in the range from iterator position begin to iterator position end, which have an IFD id matching that of the - IFD to the list of directory entries of ifd. Checks for duplicates: - if an entry with the same tag already exists, the entry is - overwritten. + IFD to the list of directory entries of ifd. No duplicate checks + are performed, i.e., it is possible to add multiple metadata with + the same key to an IFD. */ void addToIfd(Ifd& ifd, Metadata::const_iterator begin, Metadata::const_iterator end, ByteOrder byteOrder); /*! - @brief Add the metadatum to the IFD. Checks for duplicates: if an entry - with the same tag already exists, the entry is overwritten. + @brief Add the metadatum to the IFD. No duplicate checks are performed, + i.e., it is possible to add multiple metadata with the same key to + an IFD. */ void addToIfd(Ifd& ifd, const Metadatum& metadatum, ByteOrder byteOrder); /*! @@ -562,7 +574,23 @@ namespace Exif { lhs is less than that of rhs. */ bool cmpMetadataByKey(const Metadatum& lhs, const Metadatum& rhs); - + /*! + @brief Return a key for the tag and IFD id. The key is of the form + 'ifdItem.sectionName.tagName'. This function knows about + MakerNotes, i.e., it will invoke MakerNote::makeKey if necessary. + */ + std::string makeKey(uint16 tag, IfdId ifdId, const MakerNote* makerNote); + /*! + @brief Return the tag and IFD id pair for the key. This function knows + about MakerNotes, i.e., it will forward the request to + MakerNote::decomposeKey if necessary. + @return A pair consisting of the tag and IFD id. + @throw Error ("Invalid key") if the key cannot be parsed into + item item, section name and tag name parts. + */ + std::pair decomposeKey(const std::string& key, + const MakerNote* makerNote); + } // namespace Exif #endif // #ifndef EXIF_HPP_ diff --git a/src/ifd.cpp b/src/ifd.cpp index 89f7cd61..cea72f19 100644 --- a/src/ifd.cpp +++ b/src/ifd.cpp @@ -20,14 +20,14 @@ */ /* File: ifd.cpp - Version: $Name: $ $Revision: 1.1 $ + Version: $Name: $ $Revision: 1.2 $ Author(s): Andreas Huggel (ahu) History: 26-Jan-04, ahu: created 11-Feb-04, ahu: isolated as a component */ // ***************************************************************************** #include "rcsid.hpp" -EXIV2_RCSID("@(#) $Name: $ $Revision: 1.1 $ $RCSfile: ifd.cpp,v $") +EXIV2_RCSID("@(#) $Name: $ $Revision: 1.2 $ $RCSfile: ifd.cpp,v $") // ***************************************************************************** // included header files @@ -41,55 +41,55 @@ EXIV2_RCSID("@(#) $Name: $ $Revision: 1.1 $ $RCSfile: ifd.cpp,v $") #include #include #include +#include + +// ***************************************************************************** +// local declarations +namespace { + + // Helper structure to build IFD entries + struct PreEntry { + Exif::uint16 tag_; + Exif::uint16 type_; + Exif::uint32 count_; + long size_; + long offsetLoc_; + Exif::uint32 offset_; + }; + + // Container for 'pre-entries' + typedef std::vector PreEntries; + + /* + Compare two 'pre-IFD entries' by offset, taking care of special cases + where one or both of the entries don't have an offset. Return true if the + offset of entry lhs is less than that of rhs, else false. By definition, + entries without an offset are greater than those with an offset. + */ + bool cmpPreEntriesByOffset(const PreEntry& lhs, const PreEntry& rhs); +} // ***************************************************************************** // class member definitions namespace Exif { - RawEntry::RawEntry() - : ifdId_(ifdIdNotSet), ifdIdx_(-1), - tag_(0), type_(0), count_(0), offset_(0), size_(0) - { - memset(offsetData_, 0x0, 4); - } - Entry::Entry(bool alloc) - : alloc_(alloc), ifdId_(ifdIdNotSet), ifdIdx_(-1), + : alloc_(alloc), ifdId_(ifdIdNotSet), makerNote_(0), tag_(0), type_(0), count_(0), offset_(0), size_(0), data_(0) { - memset(offsetData_, 0x0, 4); - } - - Entry::Entry(const RawEntry& e, const char* buf, bool alloc) - : alloc_(alloc), ifdId_(e.ifdId_), ifdIdx_(e.ifdIdx_), - tag_(e.tag_), type_(e.type_), count_(e.count_), offset_(e.offset_), - size_(e.size_), data_(0) - { - if (size_ > 4) { - if (alloc_) { - data_ = new char[size_]; - memcpy(data_, buf + offset_, size_); - } - else { - data_ = const_cast(buf) + offset_; - } - } - else { - memcpy(offsetData_, e.offsetData_, 4); - } } Entry::~Entry() { if (alloc_) delete[] data_; + // do *not* delete the MakerNote } Entry::Entry(const Entry& rhs) - : alloc_(rhs.alloc_), ifdId_(rhs.ifdId_), - ifdIdx_(rhs.ifdIdx_), tag_(rhs.tag_), type_(rhs.type_), - count_(rhs.count_), offset_(rhs.offset_), size_(rhs.size_), data_(0) + : alloc_(rhs.alloc_), ifdId_(rhs.ifdId_), makerNote_(rhs.makerNote_), + tag_(rhs.tag_), type_(rhs.type_), count_(rhs.count_), + offset_(rhs.offset_), size_(rhs.size_), data_(0) { - memcpy(offsetData_, rhs.offsetData_, 4); if (alloc_) { if (rhs.data_) { data_ = new char[rhs.size()]; @@ -106,12 +106,11 @@ namespace Exif { if (this == &rhs) return *this; alloc_ = rhs.alloc_; ifdId_ = rhs.ifdId_; - ifdIdx_ = rhs.ifdIdx_; + makerNote_ = rhs.makerNote_; tag_ = rhs.tag_; type_ = rhs.type_; count_ = rhs.count_; offset_ = rhs.offset_; - memcpy(offsetData_, rhs.offsetData_, 4); size_ = rhs.size_; if (alloc_) { delete[] data_; @@ -127,48 +126,47 @@ namespace Exif { return *this; } // Entry::operator= - const char* Entry::data() const + void Entry::setValue(uint32 data, ByteOrder byteOrder) { - if (size_ > 4) return data_; - return offsetData_; + if (data_ == 0) { + if (!alloc_) { + throw Error("Invariant alloc violated in Entry::setValue"); + } + data_ = new char[4]; + } + // No need to resize previously allocated memory + ul2Data(data_, data, byteOrder); + size_ = 4; + type_ = unsignedLong; + count_ = 1; } - void Entry::setOffset(uint32 offset, ByteOrder byteOrder) + void Entry::setValue(uint16 type, uint32 count, const char* data, long size) { - if (size_ > 4) { - offset_ = offset; + // Make sure size is always at least four bytes + long newSize = std::max(long(4), size); + if (alloc_) { + delete[] data_; + data_ = new char[newSize]; + memset(data_, 0x0, 4); + memcpy(data_, data, size); } else { - ul2Data(offsetData_, offset, byteOrder); + if (size_ == 0) { + // Set the data pointer of a virgin entry + if (size < 4) throw Error("Size too small"); + data_ = const_cast(data); + } + else { + // Overwrite existing data if it fits into the buffer + if (newSize > size_) throw Error("Size too large"); + memset(data_, 0x0, std::max(long(4), size_)); + memcpy(data_, data, size); + } } - } - - void Entry::setValue(uint16 type, const char* buf, long size) - { - if (size > 4 && alloc_) { - delete[] data_; - data_ = new char[size]; - memcpy(data_, buf, size); - } - if (size <= 4 && alloc_) { - delete[] data_; - data_ = 0; - memset(offsetData_, 0x0, 4); - memcpy(offsetData_, buf, size); - } - if (size > 4 && !alloc_) { - if (size > size_) throw Error("Size too large"); - memset(data_, 0x0, size_); - memcpy(data_, buf, size); - } - if (size <= 4 && !alloc_) { - data_ = 0; - memset(offsetData_, 0x0, 4); - memcpy(offsetData_, buf, size); - } - size_ = size; type_ = type; - count_ = size / TypeInfo::typeSize(TypeId(type)); + count_ = count; + size_ = newSize; } // Entry::setValue Ifd::Ifd(IfdId ifdId) @@ -189,22 +187,21 @@ namespace Exif { int Ifd::read(const char* buf, ByteOrder byteOrder, long offset) { offset_ = offset; - int n = getUShort(buf, byteOrder); - long o = 2; - // Create an array of raw entries - RawEntries rawEntries; + PreEntries preEntries; + + int n = getUShort(buf, byteOrder); + long o = 2; + for (int i = 0; i < n; ++i) { - RawEntry e; - e.ifdId_ = ifdId_; - e.ifdIdx_ = i; - e.tag_ = getUShort(buf+o, byteOrder); - e.type_ = getUShort(buf+o+2, byteOrder); - e.count_ = getULong(buf+o+4, byteOrder); - e.size_ = e.count_ * TypeInfo::typeSize(TypeId(e.type_)); - e.offset_ = e.size_ > 4 ? getULong(buf+o+8, byteOrder) : 0; - memcpy(e.offsetData_, buf+o+8, 4); - rawEntries.push_back(e); + PreEntry pe; + pe.tag_ = getUShort(buf+o, byteOrder); + pe.type_ = getUShort(buf+o+2, byteOrder); + pe.count_ = getULong(buf+o+4, byteOrder); + pe.size_ = pe.count_ * TypeInfo::typeSize(TypeId(pe.type_)); + pe.offsetLoc_ = o + 8; + pe.offset_ = pe.size_ > 4 ? getULong(buf+o+8, byteOrder) : 0; + preEntries.push_back(pe); o += 12; } next_ = getULong(buf+o, byteOrder); @@ -213,11 +210,10 @@ namespace Exif { // on the assumption that the smallest offset points to a data buffer // directly following the IFD. Subsequently all offsets of IFD entries // will need to be recalculated. - if (offset_ == 0 && rawEntries.size() > 0) { + if (offset_ == 0 && preEntries.size() > 0) { // Find the entry with the smallest offset - RawEntries::const_iterator i; - i = std::min_element( - rawEntries.begin(), rawEntries.end(), cmpRawEntriesByOffset); + PreEntries::const_iterator i = std::min_element( + preEntries.begin(), preEntries.end(), cmpPreEntriesByOffset); // Set the 'guessed' IFD offset, the test is needed for the case when // all entries have data sizes not exceeding 4. if (i->size_ > 4) { @@ -225,17 +221,21 @@ namespace Exif { } } - // Convert 'raw' IFD entries to the actual entries, assign the data + // Convert the pre-IFD entries to the actual entries, assign the data // to each IFD entry and calculate relative offsets, relative to the // start of the IFD entries_.clear(); - const RawEntries::iterator begin = rawEntries.begin(); - const RawEntries::iterator end = rawEntries.end(); - for (RawEntries::iterator i = begin; i != end; ++i) { - if (i->size_ > 4) { - i->offset_ = i->offset_ - offset_; - } - add(Entry(*i, buf, alloc_)); + const PreEntries::iterator begin = preEntries.begin(); + const PreEntries::iterator end = preEntries.end(); + for (PreEntries::iterator i = begin; i != end; ++i) { + Entry e(alloc_); + e.setIfdId(ifdId_); + e.setTag(i->tag_); + // Set the offset to the data, relative to start of IFD + e.setOffset(i->size_ > 4 ? i->offset_ - offset_ : i->offsetLoc_); + e.setValue(i->type_, i->count_, buf + e.offset(), + std::max(long(4), i->size_)); + this->add(e); } return 0; @@ -323,7 +323,7 @@ namespace Exif { if (ifdId_ != entry.ifdId()) { throw Error("Invariant ifdId violated in Ifd::add"); } - erase(entry.tag()); + // allow duplicates entries_.push_back(entry); } @@ -335,29 +335,9 @@ namespace Exif { void Ifd::erase(iterator pos) { - if (alloc_) { - entries_.erase(pos); - } + entries_.erase(pos); } - void Ifd::setOffset(uint16 tag, uint32 offset, ByteOrder byteOrder) - { - iterator pos = findTag(tag); - if (pos == entries_.end()) { - RawEntry e; - e.ifdId_ = ifdId_; - e.ifdIdx_ = 0; - e.tag_ = tag; - e.type_ = unsignedLong; - e.count_ = 1; - e.offset_ = 0; - e.size_ = 4; - add(Entry(e, 0, alloc_)); - pos = findTag(tag); - } - pos->setOffset(offset, byteOrder); - } // Ifd::setOffset - long Ifd::size() const { if (entries_.size() == 0) return 0; @@ -435,7 +415,18 @@ namespace Exif { // ************************************************************************* // free functions - bool cmpRawEntriesByOffset(const RawEntry& lhs, const RawEntry& rhs) + bool cmpEntriesByTag(const Entry& lhs, const Entry& rhs) + { + return lhs.tag() < rhs.tag(); + } + +} // namespace Exif + +// ***************************************************************************** +// local definitions +namespace { + + bool cmpPreEntriesByOffset(const PreEntry& lhs, const PreEntry& rhs) { // We need to ignore entries with size <= 4, so by definition, // entries with size <= 4 are greater than those with size > 4 @@ -449,9 +440,4 @@ namespace Exif { return lhs.offset_ < rhs.offset_; } - bool cmpEntriesByTag(const Entry& lhs, const Entry& rhs) - { - return lhs.tag() < rhs.tag(); - } - -} // namespace Exif +} diff --git a/src/ifd.hpp b/src/ifd.hpp index 3c37a1fc..2af3bb80 100644 --- a/src/ifd.hpp +++ b/src/ifd.hpp @@ -21,7 +21,7 @@ /*! @file ifd.hpp @brief Encoding and decoding of IFD (Image File Directory) data - @version $Name: $ $Revision: 1.2 $ + @version $Name: $ $Revision: 1.3 $ @author Andreas Huggel (ahu) ahuggel@gmx.net @date 09-Jan-04, ahu: created @@ -43,37 +43,11 @@ // namespace extensions namespace Exif { + class MakerNote; + // ***************************************************************************** // class definitions - /*! - @brief Simple structure for 'raw' IFD directory entries (without any data - greater than four bytes) - */ - struct RawEntry { - //! Default constructor - RawEntry(); - //! The IFD id - IfdId ifdId_; - //! Position in the IFD - int ifdIdx_; - //! Tag - uint16 tag_; - //! Type - uint16 type_; - //! Number of components - uint32 count_; - //! Offset, unprocessed - uint32 offset_; - //! Data from the IFD offset field if size is less or equal to four - char offsetData_[4]; - //! Size of the data in bytes - long size_; - }; // struct RawEntry - - //! Container type to hold 'raw' IFD directory entries - typedef std::vector RawEntries; - /*! @brief Data structure for one IFD directory entry. See the description of class Ifd for an explanation of the supported modes for memory @@ -88,91 +62,107 @@ namespace Exif { it doesn't allocate or delete. */ explicit Entry(bool alloc =true); - /*! - @brief Constructor to create an %Entry from a raw entry structure - and a data buffer. - - @param e 'Raw' entry structure filled with the relevant data. The - offset_ field will only be used if size_ is greater than four. - @param buf Character buffer with the data of the tag. If size_ is - less or equal four, the data from the original IFD offset - field must be available in the field offsetData_. The buf is - not needed in this case and can be 0. - @param alloc Determines if memory management is required. If alloc - is true, a data buffer will be allocated to store data - of more than four bytes, else only the pointer will be - stored. Data less or equal than four bytes is stored - locally in the %Entry. - */ - Entry(const RawEntry& e, const char* buf, bool alloc =true); //! Destructor ~Entry(); //! Copy constructor Entry(const Entry& rhs); //! Assignment operator Entry& operator=(const Entry& rhs); + + //! Set the tag + void setTag(uint16 tag) { tag_ = tag; } + //! Set the IFD id + void setIfdId(IfdId ifdId) { ifdId_ = ifdId; } + //! Set the pointer to the MakerNote + void setMakerNote(MakerNote* makerNote) { makerNote_ = makerNote; } + //! Set the offset. The offset is relative to the start of the IFD. + void setOffset(uint32 offset) { offset_ = offset; } + /*! + @brief Set the value of the entry to a single unsigned long component, + i.e., set the type of the entry to unsigned long, number of + components to one, size to four bytes and the value according + to the data provided. This method can be used to set the value + of a tag which contains a pointer (offset) to a location in the + %Exif data (like e.g., ExifTag, 0x8769 in IFD0, which contains a + pointer to the Exif IFD). This method cannot be used to set the + value of a newly created %Entry in non-alloc mode. + */ + void setValue(uint32 data, ByteOrder byteOrder); + /*! + @brief Set type, count, size and the data of the entry. + + Copies the provided buffer when called in memory allocation mode. In + non-alloc mode, use this method to set the data of a newly created + %Entry. The data buffer provided must be at least four bytes to + initialise an %Entry in non-alloc mode. In this case, only the pointer + to the buffer is copied, i.e., the buffer must remain valid throughout + the life of the %Entry. Subsequent calls in non-alloc mode overwrite + the data pointed to by this pointer with the data provided, i.e., the + buffer provided in subsequent calls can be deleted after the call. +
Todo: This sounds too complicated: should I isolate the init + functionality into a separate method? + + @param type The type of the data. + @param count Number of components in the buffer. + @param data Pointer to the data buffer. + @param size Size of the data buffer in bytes. + @throw Error ("Size too large") if no memory allocation is allowed and + the size of the data in buf is greater than the existing size + of the data of the entry.
+ @throw Error ("Size too small") if an attempt is made to initialise an + %Entry in non-alloc mode with a buffer with size less than four. + */ + void setValue(uint16 type, uint32 count, const char* data, long size); + //! @name Accessors //@{ - //! Return the IFD id - IfdId ifdId() const { return ifdId_; } - //! Return the index in the IFD - int ifdIdx() const { return ifdIdx_; } //! Return the tag uint16 tag() const { return tag_; } //! Return the type id. uint16 type() const { return type_; } + //! Return the name of the type + const char* typeName() const + { return TypeInfo::typeName(TypeId(type_)); } + //! Return the size in bytes of one element of this type + long typeSize() const + { return TypeInfo::typeSize(TypeId(type_)); } + //! Return the IFD id + IfdId ifdId() const { return ifdId_; } + //! Return the pointer to the associated MakerNote + MakerNote* makerNote() const { return makerNote_; } //! Return the number of components in the value uint32 count() const { return count_; } + /*! + @brief Return the size of the value in bytes, it is at least four + bytes unless it is 0. + */ + long size() const { return size_; } //! Return the offset from the start of the IFD uint32 offset() const { return offset_; } - //! Return the size of the value in bytes - long size() const { return size_; } /*! @brief Return a pointer to the data area. Do not attempt to write to this pointer. */ - const char* data() const; + const char* data() const { return data_; } //! Get the memory allocation mode bool alloc() const { return alloc_; } //@} - //! Return the size in bytes of one element of this type - long typeSize() const - { return TypeInfo::typeSize(TypeId(type_)); } - //! Return the name of the type - const char* typeName() const - { return TypeInfo::typeName(TypeId(type_)); } - - /*! - @brief Set the offset. If the size of the data is not greater than - four, the offset is written into the offset field as an - unsigned long using the byte order given to encode it. - */ - void setOffset(uint32 offset, ByteOrder byteOrder); - /*! - @brief Set type, count and the data of the entry. - @throw Error ("Size too large") if no memory allocation is allowed and - the size of the data in buf is greater than the existing size - of the data of the entry. - */ - void setValue(uint16 type, const char* buf, long size); - private: /*! @brief True: Requires memory allocation and deallocation,
False: No memory management needed. */ bool alloc_; - IfdId ifdId_; // Redundant IFD id (it is also at the IFD) - int ifdIdx_; // Position in the IFD - uint16 tag_; // Tag - uint16 type_; // Type - uint32 count_; // Number of components - uint32 offset_; // Offset from the start of the IFD, - // 0 if size <=4, i.e., if there is no offset - char offsetData_[4]; // Data from the offset field if size <= 4 - long size_; // Size of the data in bytes - char* data_; // Pointer to the data buffer + IfdId ifdId_; // Redundant IFD id (it is also at the IFD) + MakerNote* makerNote_; // Pointer to the associated MakerNote + uint16 tag_; // Tag + uint16 type_; // Type + uint32 count_; // Number of components + uint32 offset_; // Offset from the start of the IFD to the data + long size_; // Size of the data in bytes, at least four bytes + char* data_; // Pointer to the data buffer, which is always at + // least four bytes big (or 0, if not allocated) }; // class Entry //! Container type to hold all IFD directory entries @@ -256,19 +246,12 @@ namespace Exif { void erase(uint16 tag); //! Delete the directory entry at iterator position pos void erase(iterator pos); - /*! - @brief Set the offset of the entry identified by tag. If no entry with - this tag exists, an entry of type unsigned long with one - component is created. If the size of the data is greater than - four, the offset of the entry is set to the value provided in - offset, else it is written to the offset field of the entry as - an unsigned long, encoded according to the byte order. - */ - void setOffset(uint16 tag, uint32 offset, ByteOrder byteOrder); + //! Set the offset of the next IFD void setNext(uint32 next) { next_ = next; } /*! - @brief Add the Entry to the IFD. Checks for duplicates: if an entry + @brief Add the Entry to the IFD. No duplicate-check is performed, i.e., + it is possible to add multiple entries with the same tag. with the same tag already exists, the entry is overwritten. The memory allocation mode of the entry to be added must match that of the IFD and the IFD ids of the IFD and Entry must match. @@ -369,14 +352,6 @@ namespace Exif { // ***************************************************************************** // free functions - /*! - @brief Compare two 'raw' IFD entries by offset, taking care of special - cases where one or both of the entries don't have an offset. - Return true if the offset of entry lhs is less than that of rhs, - else false. By definition, entries without an offset are greater - than those with an offset. - */ - bool cmpRawEntriesByOffset(const RawEntry& lhs, const RawEntry& rhs); /*! @brief Compare two IFD entries by tag. Return true if the tag of entry lhs is less than that of rhs.