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.

453 lines
19 KiB
C++

// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2004 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*!
@file ifd.hpp
@brief Encoding and decoding of IFD (Image File Directory) data
@version $Name: $ $Revision: 1.10 $
@author Andreas Huggel (ahu)
<a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a>
@date 09-Jan-04, ahu: created
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 Exif {
22 years ago
// *****************************************************************************
// class declarations
class MakerNote;
// *****************************************************************************
// 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 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 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.
<BR>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.<BR>
@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 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 index (unique id >0 of an entry within an IFD, 0 if not set)
int idx() const { return idx_; }
//! 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 to the data of the entry
uint32 offset() const { return offset_; }
/*!
@brief Return a pointer to the data area. Do not attempt to write
to this pointer.
*/
const char* data() const { return data_; }
/*!
@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 char* component(uint32 n) const;
//! Get the memory allocation mode
bool alloc() const { return alloc_; }
//@}
private:
/*!
@brief True: Requires memory allocation and deallocation,<BR>
False: No memory management needed.
*/
bool alloc_;
IfdId ifdId_; // Redundant IFD id (it is also at the IFD)
int idx_; // Unique id of an entry within an IFD (0 if not set)
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
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 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 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 {
public:
//! @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, uint32 offset);
/*!
@brief Constructor. Allows to set the IFD identifier, offset of the
IFD from the start of TIFF header and choose whether or not
memory management is required for the Entries.
*/
Ifd(IfdId ifdId, uint32 offset, bool alloc);
//@}
//! Entries const iterator type
typedef Entries::const_iterator const_iterator;
//! Entries iterator type
typedef Entries::iterator iterator;
//! @name Manipulators
//@{
/*!
@brief Read a complete IFD and its data from a data buffer
@param buf Pointer to the data to decode. The buffer must start with the
IFD data (unlike the readSubIfd() method).
@param byteOrder Applicable byte order (little or big endian).
@param offset (Optional) offset of the IFD from the start of the TIFF
header, if known. If not given, the offset will be guessed
using the assumption that the smallest offset of all IFD
directory entries points to a data buffer immediately follwing
the IFD.
@return 0 if successful
*/
int read(const char* buf, ByteOrder byteOrder, long offset =0);
/*!
@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 (unlike the read() method).
@param byteOrder Applicable byte order (little or big endian).
@param tag Tag to look for.
@return 0 if successful
*/
int readSubIfd(
Ifd& dest, const char* buf, ByteOrder byteOrder, uint16 tag
) const;
/*!
@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(char* 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();
//! Set the offset of the next IFD
void setNext(uint32 next) { next_ = next; }
/*!
@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 tag);
//! Delete the directory entry at iterator position pos
void 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 tag);
//@}
//! @name Accessors
//@{
//! 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 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_; }
//! Get the offset to the next IFD from the start of the TIFF header
long next() const { return next_; }
//! Get the number of directory entries in the IFD
long count() const { return 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 (i.e., only 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 tag_;
uint16 type_;
uint32 count_;
long size_;
long offsetLoc_;
uint32 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;
const bool alloc_; // True: requires memory allocation and deallocation,
// False: no memory management needed.
Entries entries_; // IFD entries
IfdId ifdId_; // IFD Id
long offset_; // offset of the IFD from the start of TIFF header
long next_; // offset of next IFD from the start of the TIFF header
}; // 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 Exif
#endif // #ifndef IFD_HPP_