You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

617 lines
25 KiB
C++

// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2004, 2005, 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 ifd.hpp
@brief Encoding and decoding of IFD (%Image File Directory) data
@version $Rev$
@author Andreas Huggel (ahu)
<a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a>
@date 09-Jan-04, ahu: created<BR>
11-Feb-04, ahu: isolated as a component
*/
#ifndef IFD_HPP_
#define IFD_HPP_
// *****************************************************************************
// included header files
#include "types.hpp"
// + standard includes
#include <string>
#include <vector>
#include <iosfwd>
// *****************************************************************************
// namespace extensions
namespace Exiv2 {
// *****************************************************************************
// class declarations
class Ifd;
// *****************************************************************************
// class definitions
/*!
@brief Data structure for one IFD directory entry. See the description of
class Ifd for an explanation of the supported modes for memory
allocation.
*/
class Entry {
public:
//! @name Creators
//@{
/*!
@brief Default constructor. The entry allocates memory for its
data if alloc is true (the default), otherwise it remembers
just the pointers into a read and writeable data buffer which
it doesn't allocate or delete.
*/
explicit Entry(bool alloc =true);
//! Destructor
~Entry();
//! Copy constructor
Entry(const Entry& rhs);
//@}
//! @name Manipulators
//@{
//! Assignment operator
Entry& operator=(const Entry& rhs);
//! Set the tag
void setTag(uint16_t tag) { tag_ = tag; }
//! Set the IFD id
void setIfdId(IfdId ifdId) { ifdId_ = ifdId; }
//! Set the index (unique id of an entry within one IFD)
void setIdx(int idx) { idx_ = idx; }
//! Set the offset. The offset is relative to the start of the IFD.
void setOffset(long 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 and the value according to the data provided.
The size of the data buffer is set to at least four bytes, but is left
unchanged if it can accomodate the pointer. 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).
<BR>This method cannot be used to set the value of a newly created
%Entry in non-alloc mode.
@note This method is now deprecated, use data area related methods
instead.
*/
void setValue(uint32_t data, ByteOrder byteOrder);
/*!
@brief Set type, count, the data buffer and its size.
Copies the provided buffer when called in memory allocation mode.
<BR>In non-alloc mode, use this method to initialise the data of a
newly created %Entry. 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 will 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.
<BR>In either memory allocation mode, the data buffer provided must be
large enough to hold count components of type. The size of the buffer
will be as indicated in the size argument. I.e., it is possible to
allocate (set) a data buffer larger than required to hold count
components of the given type.
@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 desired data buffer in bytes.
@param byteOrder Optional byte order.
@throw Error if no memory allocation is allowed
and the size of the data buffer is larger than the existing
data buffer of the entry or if size is not large enough to hold
count components of the given type.
*/
void setValue(uint16_t type, uint32_t count, const byte* data, long size, ByteOrder byteOrder =invalidByteOrder);
/*!
@brief Set the data area. Memory management as for
setValue(uint16_t, uint32_t, const byte*, long)
For certain tags the regular value of an IFD entry is an offset to a
data area outside of the IFD. Examples are Exif tag 0x8769 in IFD0
(Exif.Image.ExifTag) or tag 0x0201 in IFD1
(Exif.Thumbnail.JPEGInterchangeFormat). The offset of ExifTag points
to a data area containing the Exif IFD. That of JPEGInterchangeFormat
contains the JPEG thumbnail image.
This method sets the data area of a tag in accordance with the memory
allocation mode.
@param buf Pointer to the data area.
@param len Size of the data area.
@throw Error in non-alloc mode, if there already is a dataarea but the
size of the existing dataarea is not large enough for the
new buffer.
*/
void setDataArea(const byte* buf, long len);
/*!
@brief Set the offset(s) to the data area of an entry.
Add @em offset to each data component of the entry. This is used by
Ifd::copy to convert the data components of an entry containing
offsets relative to the data area to become offsets from the start of
the TIFF header. Usually, entries with a data area have exactly one
unsigned long data component, which is 0.
@param offset Offset
@param byteOrder Byte order
@throw Error if the offset is out of range for the data type of the
tag or the data type is not supported.
*/
void setDataAreaOffsets(uint32_t offset, ByteOrder byteOrder);
/*!
@brief Update the base pointer of the Entry from \em pOldBase
to \em pNewBase.
Allows to re-locate the underlying data buffer to a new location
\em pNewBase. This method only has an effect in non-alloc mode.
@param pOldBase Base pointer of the old data buffer
@param pNewBase Base pointer of the new data buffer
*/
void updateBase(byte* pOldBase, byte* pNewBase);
//@}
//! @name Accessors
//@{
//! Return the tag
uint16_t tag() const { return tag_; }
//! Return the type id.
uint16_t 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 index (unique id >0 of an entry within an IFD, 0 if not set)
int idx() const { return idx_; }
//! Return the number of components in the value
uint32_t count() const { return count_; }
/*!
@brief Return the size of the data buffer in bytes.
@note There is no minimum size for the data buffer, except that it
must be large enough to hold the data.
*/
long size() const { return size_; }
//! Return the offset from the start of the IFD to the data of the entry
long offset() const { return offset_; }
/*!
@brief Return a pointer to the data buffer. Do not attempt to write
to this pointer.
*/
const byte* data() const { return pData_; }
/*!
@brief Return a pointer to the n-th component, 0 if there is no
n-th component. Do not attempt to write to this pointer.
*/
const byte* component(uint32_t n) const;
//! Get the memory allocation mode
bool alloc() const { return alloc_; }
//! Return the size of the data area.
long sizeDataArea() const { return sizeDataArea_; }
/*!
@brief Return a pointer to the data area. Do not attempt to write to
this pointer.
For certain tags the regular value of an IFD entry is an offset to a
data area outside of the IFD. Examples are Exif tag 0x8769 in IFD0
(Exif.Image.ExifTag) or tag 0x0201 in IFD1
(Exif.Thumbnail.JPEGInterchangeFormat). The offset of ExifTag points
to a data area containing the Exif IFD. That of JPEGInterchangeFormat
contains the JPEG thumbnail image.
Use this method to access (read-only) the data area of a tag. Use
setDataArea() to write to the data area.
@return Return a pointer to the data area.
*/
const byte* dataArea() const { return pDataArea_; }
/*!
@brief Return the byte order of the entry. There should generally
not be a need for this, it is only used in special cases
(Minolta Makernote CameraSettings tags).
*/
ByteOrder byteOrder() const { return byteOrder_; }
//@}
private:
// DATA
/*!
True: Requires memory allocation and deallocation,<BR>
False: No memory management needed.
*/
bool alloc_;
//! Redundant IFD id (it is also at the IFD)
IfdId ifdId_;
//! Unique id of an entry within an IFD (0 if not set)
int idx_;
//! Tag
uint16_t tag_;
//! Type
uint16_t type_;
//! Number of components
uint32_t count_;
//! Offset from the start of the IFD to the data
long offset_;
/*!
Size of the data buffer holding the value in bytes, there is
no minimum size.
*/
long size_;
//! Pointer to the data buffer
byte* pData_;
//! Size of the data area
long sizeDataArea_;
//! Pointer to the data area
byte* pDataArea_;
//! Byte order (optional, only used for special cases)
ByteOrder byteOrder_;
}; // class Entry
//! Container type to hold all IFD directory entries
typedef std::vector<Entry> Entries;
//! Unary predicate that matches an Entry with a given index
class FindEntryByIdx {
public:
//! Constructor, initializes the object with the index to look for
FindEntryByIdx(int idx) : idx_(idx) {}
/*!
@brief Returns true if the idx of the argument entry is equal
to that of the object.
*/
bool operator()(const Entry& entry) const
{ return idx_ == entry.idx(); }
private:
int idx_;
}; // class FindEntryByIdx
//! Unary predicate that matches an Entry with a given tag
class FindEntryByTag {
public:
//! Constructor, initializes the object with the tag to look for
FindEntryByTag(uint16_t tag) : tag_(tag) {}
/*!
@brief Returns true if the tag of the argument entry is equal
to that of the object.
*/
bool operator()(const Entry& entry) const
{ return tag_ == entry.tag(); }
private:
uint16_t tag_;
}; // class FindEntryByTag
/*!
@brief Models an IFD (%Image File Directory)
This class models an IFD as described in the TIFF 6.0 specification.
An instance of class %Ifd can operate in two modes, one that allocates and
deallocates the memory required to store data, and one that doesn't
perform such memory management.
<BR>An external data buffer (not managed by %Ifd) is needed for an instance
of %Ifd which operates in no memory management mode. The %Ifd will
maintain only pointers into this buffer.
<BR> The mode without memory management is used to make "non-intrusive
write support" possible. This allows writing to Exif data of an image
without changing the data layout of the Exif data, to maximize chances
that tag data, which the Exif reader may not understand (e.g., the
Makernote) remains valid. A "non-intrusive write operation" is the
modification of tag data without increasing the data size.
@note Use the mode with memory management (the default) if you are unsure
or if these memory management considerations are of no concern to you.
@note The two different modes imply completely different copy and
assignment behaviours, with the first resulting in entirely separate
classes and the second mode resulting in multiple classes using one
and the same data buffer.
*/
class Ifd {
//! @name Not implemented
//@{
//! Assignment not allowed (memory management mode alloc_ is const)
Ifd& operator=(const Ifd& rhs);
//@}
public:
//! %Entries const iterator type
typedef Entries::const_iterator const_iterator;
//! %Entries iterator type
typedef Entries::iterator iterator;
//! @name Creators
//@{
/*!
@brief Constructor. Allows to set the IFD identifier. Memory management
is enabled, offset is set to 0. Serves as default constructor.
*/
explicit Ifd(IfdId ifdId =ifdIdNotSet);
/*!
@brief Constructor. Allows to set the IFD identifier and the offset of
the IFD from the start of TIFF header. Memory management is
enabled.
*/
Ifd(IfdId ifdId, long offset);
/*!
@brief Constructor. Allows to set the IFD identifier, offset of the
IFD from the start of TIFF header, choose whether or not
memory management is required for the Entries, and decide
whether this IFD has a next pointer.
*/
Ifd(IfdId ifdId, long offset, bool alloc, bool hasNext =true);
//! Copy constructor
Ifd(const Ifd& rhs);
//! Destructor
~Ifd();
//@}
//! @name Manipulators
//@{
/*!
@brief Read a complete IFD and its data from a data buffer
@param buf Pointer to the Exif data buffer that contains the IFD to
decode. Usually, the buffer will contain all Exif data
starting from the TIFF header.
@param len Number of bytes in the Exif data buffer.
@param start IFD starts at buf + start.
@param byteOrder Applicable byte order (little or big endian).
@param shift IFD offsets are relative to buf + shift.
@return 0 if successful;<BR>
6 if the data buffer is too small, e.g., if an offset points
beyond the provided buffer. The IFD is cleared in this
case.
*/
int read(const byte* buf,
long len,
long start,
ByteOrder byteOrder,
long shift =0);
/*!
@brief Copy the IFD to a data array, update the offsets of the IFD and
all its entries, return the number of bytes written.
First the number of IFD entries is written (2 bytes), followed
by all directory entries: tag (2), type (2), number of data
components (4) and offset to the data or the data, if it
occupies not more than four bytes (4). The directory entries
are followed by the offset of the next IFD (4). All these
fields are encoded according to the byte order argument. Data
that doesn't fit into the offset fields follows immediately
after the IFD entries. The offsets in the IFD are set to
correctly point to the data fields, using the offset parameter
or the offset of the IFD.
@param buf Pointer to the data buffer. The user must ensure that the
buffer has enough memory. Otherwise the call results in
undefined behaviour.
@param byteOrder Applicable byte order (little or big endian).
@param offset Target offset from the start of the TIFF header of the
data array. The IFD offsets will be adjusted as necessary. If
not given, then it is assumed that the IFD will remain at its
original position, i.e., the offset of the IFD will be used.
@return Returns the number of characters written.
*/
long copy(byte* buf, ByteOrder byteOrder, long offset =0);
/*!
@brief Reset the IFD. Delete all IFD entries from the class and put
the object in a state where it can accept completely new
entries.
*/
void clear();
/*!
@brief Set the offset of the next IFD. Byte order is needed to update
the underlying data buffer in non-alloc mode. This method only
has an effect if the IFD was instantiated with hasNext = true.
*/
void setNext(uint32_t next, ByteOrder byteOrder);
/*!
@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.
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.
*/
void add(const Entry& entry);
/*!
@brief Delete the directory entry with the given tag. Return the index
of the deleted entry or 0 if no entry with tag was found.
*/
int erase(uint16_t tag);
/*!
@brief Delete the directory entry at iterator position pos, return the
position of the next entry. Note that iterators into the
directory, including pos, are potentially invalidated by this
call.
*/
iterator erase(iterator pos);
//! Sort the IFD entries by tag
void sortByTag();
//! The first entry
iterator begin() { return entries_.begin(); }
//! End of the entries
iterator end() { return entries_.end(); }
//! Find an IFD entry by idx, return an iterator into the entries list
iterator findIdx(int idx);
//! Find an IFD entry by tag, return an iterator into the entries list
iterator findTag(uint16_t tag);
/*!
@brief Update the base pointer of the Ifd and all entries to \em pNewBase.
Allows to re-locate the underlying data buffer to a new location
\em pNewBase. This method only has an effect in non-alloc mode.
@param pNewBase Pointer to the new data buffer
@return Old base pointer or 0 if called in alloc mode
*/
byte* updateBase(byte* pNewBase);
//@}
//! @name Accessors
//@{
/*!
@brief Read a sub-IFD from the location pointed to by the directory entry
with the given tag.
@param dest References the destination IFD.
@param buf The data buffer to read from. The buffer must contain all Exif
data starting from the TIFF header.
@param len Number of bytes in the data buffer
@param byteOrder Applicable byte order (little or big endian).
@param tag Tag to look for.
@return 0 if successful;<BR>
6 if reading the sub-IFD failed (see read() above) or
the location pointed to by the directory entry with the
given tag is outside of the data buffer.
@note It is not considered an error if the tag cannot be found in the
IFD. 0 is returned and no action is taken in this case.
*/
int readSubIfd(
Ifd& dest, const byte* buf, long len, ByteOrder byteOrder, uint16_t tag
) const;
//! Get the memory allocation mode, see the Ifd class description for details
bool alloc() const { return alloc_; }
//! The first entry
const_iterator begin() const { return entries_.begin(); }
//! End of the entries
const_iterator end() const { return entries_.end(); }
//! Find an IFD entry by idx, return a const iterator into the entries list
const_iterator findIdx(int idx) const;
//! Find an IFD entry by tag, return a const iterator into the entries list
const_iterator findTag(uint16_t tag) const;
//! Get the IfdId of the IFD
IfdId ifdId() const { return ifdId_; }
//! Get the offset of the IFD from the start of the TIFF header
long offset() const { return offset_; }
/*!
@brief Get the offset of the first data entry outside of the IFD from
the start of the TIFF header, return 0 if there is none. The
data offset is determined when the IFD is read.
*/
long dataOffset() const { return dataOffset_; }
//! Get the offset to the next IFD from the start of the TIFF header
uint32_t next() const { return next_; }
//! Get the number of directory entries in the IFD
long count() const { return static_cast<long>(entries_.size()); }
//! Get the size of this IFD in bytes (IFD only, without data)
long size() const;
/*!
@brief Return the total size of the data of this IFD in bytes; sums
the size of all directory entries where size is greater than
four plus the size of all data areas, i.e., all data that
requires memory outside the IFD directory entries is counted.
*/
long dataSize() const;
/*!
@brief Print the IFD in human readable format to the given stream;
begin each line with prefix.
*/
void print(std::ostream& os, const std::string& prefix ="") const;
//@}
private:
//! Helper structure to build IFD entries
struct PreEntry {
uint16_t tag_;
uint16_t type_;
uint32_t count_;
long size_;
long offsetLoc_;
long offset_;
};
//! cmpPreEntriesByOffset needs to know about PreEntry, that's all.
friend bool cmpPreEntriesByOffset(const PreEntry&, const PreEntry&);
//! Container for 'pre-entries'
typedef std::vector<PreEntry> PreEntries;
// DATA
/*!
True: requires memory allocation and deallocation,
False: no memory management needed.
*/
const bool alloc_;
//! IFD entries
Entries entries_;
//! IFD Id
IfdId ifdId_;
//! Pointer to IFD
byte* pBase_;
//! Offset of the IFD from the start of the TIFF header
long offset_;
//! Offset of the first data entry outside of the IFD directory
long dataOffset_;
//! Indicates whether the IFD has a next pointer
bool hasNext_;
//! Pointer to the offset of next IFD
byte* pNext_;
/*!
The offset of the next IFD from the start of the TIFF header as data
value (always in sync with *pNext_)
*/
uint32_t next_;
}; // class Ifd
// *****************************************************************************
// free functions
/*!
@brief Compare two IFD entries by tag. Return true if the tag of entry
lhs is less than that of rhs.
*/
bool cmpEntriesByTag(const Entry& lhs, const Entry& rhs);
/*!
@brief 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 Ifd::PreEntry& lhs, const Ifd::PreEntry& rhs);
} // namespace Exiv2
#endif // #ifndef IFD_HPP_