|
|
|
// ***************************************************************** -*- 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: exif.cpp
|
|
|
|
Version: $Name: $ $Revision: 1.54 $
|
|
|
|
Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
|
|
|
|
History: 26-Jan-04, ahu: created
|
|
|
|
11-Feb-04, ahu: isolated as a component
|
|
|
|
*/
|
|
|
|
// *****************************************************************************
|
|
|
|
#include "rcsid.hpp"
|
|
|
|
EXIV2_RCSID("@(#) $Name: $ $Revision: 1.54 $ $RCSfile: exif.cpp,v $");
|
|
|
|
|
|
|
|
// Define DEBUG_MAKERNOTE to output debug information to std::cerr
|
|
|
|
#undef DEBUG_MAKERNOTE
|
|
|
|
|
|
|
|
// *****************************************************************************
|
|
|
|
// included header files
|
|
|
|
#include "exif.hpp"
|
|
|
|
#include "types.hpp"
|
|
|
|
#include "error.hpp"
|
|
|
|
#include "value.hpp"
|
|
|
|
#include "ifd.hpp"
|
|
|
|
#include "tags.hpp"
|
|
|
|
#include "image.hpp"
|
|
|
|
#include "makernote.hpp"
|
|
|
|
|
|
|
|
// + standard includes
|
|
|
|
#include <iostream>
|
|
|
|
#include <sstream>
|
|
|
|
#include <utility>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <map>
|
|
|
|
#include <cstring>
|
|
|
|
#include <cassert>
|
|
|
|
|
|
|
|
// *****************************************************************************
|
|
|
|
// 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(Exiv2::Ifd& ifd,
|
|
|
|
int idx,
|
|
|
|
Exiv2::uint16 tag,
|
|
|
|
Exiv2::uint32 offset,
|
|
|
|
Exiv2::ByteOrder byteOrder);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// *****************************************************************************
|
|
|
|
// class member definitions
|
|
|
|
namespace Exiv2 {
|
|
|
|
|
|
|
|
ExifKey::ExifKey(const std::string& key)
|
|
|
|
: idx_(0), pMakerNote_(0), key_(key)
|
|
|
|
{
|
|
|
|
decomposeKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
ExifKey::ExifKey(const Entry& e)
|
|
|
|
: tag_(e.tag()), ifdId_(e.ifdId()), idx_(e.idx()),
|
|
|
|
pMakerNote_(e.makerNote()), key_(makeKey(e))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ExifKey::ExifKey(const ExifKey& rhs)
|
|
|
|
: tag_(rhs.tag_), ifdId_(rhs.ifdId_), idx_(rhs.idx_),
|
|
|
|
pMakerNote_(rhs.pMakerNote_), key_(rhs.key_)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ExifKey& ExifKey::operator=(const ExifKey& rhs)
|
|
|
|
{
|
|
|
|
if (this == &rhs) return *this;
|
|
|
|
Key::operator=(rhs);
|
|
|
|
tag_ = rhs.tag_;
|
|
|
|
ifdId_ = rhs.ifdId_;
|
|
|
|
idx_ = rhs.idx_;
|
|
|
|
pMakerNote_ = rhs.pMakerNote_;
|
|
|
|
key_ = rhs.key_;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExifKey::setMakerNote(MakerNote* pMakerNote)
|
|
|
|
{
|
|
|
|
if (ifdId_ == makerIfd && pMakerNote_ != pMakerNote) {
|
|
|
|
pMakerNote_ = pMakerNote;
|
|
|
|
decomposeKey();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string ExifKey::tagName() const
|
|
|
|
{
|
|
|
|
if (ifdId_ == makerIfd && pMakerNote_ != 0) {
|
|
|
|
return pMakerNote_->tagName(tag_);
|
|
|
|
}
|
|
|
|
return ExifTags::tagName(tag(), ifdId());
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16 ExifKey::tag() const
|
|
|
|
{
|
|
|
|
if (tag_ == 0xffff) throw Error("Invalid key");
|
|
|
|
return tag_;
|
|
|
|
}
|
|
|
|
|
|
|
|
IfdId ExifKey::ifdId() const
|
|
|
|
{
|
|
|
|
if (ifdId_ == ifdIdNotSet) throw Error("Invalid key");
|
|
|
|
return ifdId_;
|
|
|
|
}
|
|
|
|
|
|
|
|
ExifKey* ExifKey::clone() const
|
|
|
|
{
|
|
|
|
return new ExifKey(*this);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string ExifKey::sectionName() const
|
|
|
|
{
|
|
|
|
if (ifdId_ == makerIfd && pMakerNote_ != 0) {
|
|
|
|
return pMakerNote_->ifdItem();
|
|
|
|
}
|
|
|
|
return ExifTags::sectionName(tag(), ifdId());
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExifKey::decomposeKey()
|
|
|
|
{
|
|
|
|
std::pair<uint16, IfdId> p;
|
|
|
|
if (ifdId_ == makerIfd && pMakerNote_ != 0) {
|
|
|
|
p.first = pMakerNote_->decomposeKey(key_);
|
|
|
|
if (p.first == 0xffff) throw Error("Invalid key");
|
|
|
|
p.second = makerIfd;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
p = ExifTags::decomposeKey(key_);
|
|
|
|
// If it's couldn't be parsed, we assume it is an incomplete
|
|
|
|
// makernote key (pMakerNote_ not set)
|
|
|
|
if (p.second == ifdIdNotSet) p.second = makerIfd;
|
|
|
|
// No checks as this could still be an incomplete makernote key
|
|
|
|
}
|
|
|
|
tag_ = p.first;
|
|
|
|
ifdId_ = p.second;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::ostream& ExifKey::printTag(std::ostream& os, const Value& value) const
|
|
|
|
{
|
|
|
|
if (ifdId_ == makerIfd && pMakerNote_ != 0) {
|
|
|
|
return pMakerNote_->printTag(os, tag(), value);
|
|
|
|
}
|
|
|
|
return ExifTags::printTag(os, tag(), ifdId(), value);
|
|
|
|
}
|
|
|
|
|
|
|
|
Exifdatum::Exifdatum(const Entry& e, ByteOrder byteOrder)
|
|
|
|
: pKey_(new ExifKey(e)), pValue_(0)
|
|
|
|
{
|
|
|
|
pValue_ = Value::create(TypeId(e.type()));
|
|
|
|
pValue_->read(e.data(), e.count() * e.typeSize(), byteOrder);
|
|
|
|
}
|
|
|
|
|
|
|
|
Exifdatum::Exifdatum(const ExifKey& key, const Value* pValue)
|
|
|
|
: pKey_(key.clone()), pValue_(0)
|
|
|
|
{
|
|
|
|
if (pValue) pValue_ = pValue->clone();
|
|
|
|
}
|
|
|
|
|
|
|
|
Exifdatum::~Exifdatum()
|
|
|
|
{
|
|
|
|
delete pKey_;
|
|
|
|
delete pValue_;
|
|
|
|
}
|
|
|
|
|
|
|
|
Exifdatum::Exifdatum(const Exifdatum& rhs)
|
|
|
|
: Metadatum(rhs), pKey_(0), pValue_(0)
|
|
|
|
{
|
|
|
|
if (rhs.pKey_ != 0) pKey_ = rhs.pKey_->clone(); // deep copy
|
|
|
|
if (rhs.pValue_ != 0) pValue_ = rhs.pValue_->clone(); // deep copy
|
|
|
|
}
|
|
|
|
|
|
|
|
Exifdatum& Exifdatum::operator=(const Exifdatum& rhs)
|
|
|
|
{
|
|
|
|
if (this == &rhs) return *this;
|
|
|
|
Metadatum::operator=(rhs);
|
|
|
|
|
|
|
|
delete pKey_;
|
|
|
|
pKey_ = 0;
|
|
|
|
if (rhs.pKey_ != 0) pKey_ = rhs.pKey_->clone(); // deep copy
|
|
|
|
|
|
|
|
delete pValue_;
|
|
|
|
pValue_ = 0;
|
|
|
|
if (rhs.pValue_ != 0) pValue_ = rhs.pValue_->clone(); // deep copy
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
} // Exifdatum::operator=
|
|
|
|
|
|
|
|
void Exifdatum::setValue(const Value* pValue)
|
|
|
|
{
|
|
|
|
delete pValue_;
|
|
|
|
pValue_ = pValue->clone();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Exifdatum::setValue(const Entry& e, ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
delete pValue_;
|
|
|
|
pValue_ = Value::create(TypeId(e.type()));
|
|
|
|
pValue_->read(e.data(), e.count() * e.typeSize(), byteOrder);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Exifdatum::setValue(const std::string& buf)
|
|
|
|
{
|
|
|
|
if (pValue_ == 0) pValue_ = Value::create(asciiString);
|
|
|
|
pValue_->read(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
TiffThumbnail::TiffThumbnail()
|
|
|
|
: offset_(0), size_(0), pImage_(0), ifd_(ifd1, 0, false)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
TiffThumbnail::~TiffThumbnail()
|
|
|
|
{
|
|
|
|
delete[] pImage_;
|
|
|
|
}
|
|
|
|
|
|
|
|
TiffThumbnail::TiffThumbnail(const TiffThumbnail& rhs)
|
|
|
|
: offset_(rhs.offset_), size_(rhs.size_), pImage_(0),
|
|
|
|
ifd_(ifd1, 0, false)
|
|
|
|
{
|
|
|
|
if (rhs.pImage_ && rhs.size_ > 0) {
|
|
|
|
pImage_ = new byte[rhs.size_];
|
|
|
|
memcpy(pImage_, rhs.pImage_, rhs.size_);
|
|
|
|
tiffHeader_.read(pImage_);
|
|
|
|
ifd_.read(pImage_ + tiffHeader_.offset(),
|
|
|
|
size_ - tiffHeader_.offset(),
|
|
|
|
tiffHeader_.byteOrder(), tiffHeader_.offset());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TiffThumbnail& TiffThumbnail::operator=(const TiffThumbnail& rhs)
|
|
|
|
{
|
|
|
|
byte* pNewImage = 0;
|
|
|
|
if (rhs.pImage_ && rhs.size_ > 0) {
|
|
|
|
pNewImage = new byte[rhs.size_];
|
|
|
|
memcpy(pNewImage, rhs.pImage_, rhs.size_);
|
|
|
|
tiffHeader_.read(rhs.pImage_);
|
|
|
|
ifd_.read(pNewImage + tiffHeader_.offset(),
|
|
|
|
rhs.size_ - tiffHeader_.offset(),
|
|
|
|
tiffHeader_.byteOrder(), tiffHeader_.offset());
|
|
|
|
}
|
|
|
|
offset_ = rhs.offset_;
|
|
|
|
size_ = rhs.size_;
|
|
|
|
delete[] pImage_;
|
|
|
|
pImage_ = pNewImage;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
int TiffThumbnail::read(const byte* buf,
|
|
|
|
long len,
|
|
|
|
const ExifData& exifData,
|
|
|
|
ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
DataBuf img; // temporary buffer
|
|
|
|
img.alloc(len);
|
|
|
|
memset(img.pData_, 0x0, img.size_);
|
|
|
|
long buflen = 0; // actual number of bytes needed
|
|
|
|
|
|
|
|
int rc = 0;
|
|
|
|
// Copy the TIFF header
|
|
|
|
TiffHeader tiffHeader(byteOrder);
|
|
|
|
if (len < tiffHeader.size()) rc = 1;
|
|
|
|
Ifd ifd1(ifd1);
|
|
|
|
long ifdOffset = 0;
|
|
|
|
if (rc == 0) {
|
|
|
|
buflen += tiffHeader.copy(img.pData_);
|
|
|
|
|
|
|
|
// Create IFD (without Exif and GPS tags) from metadata
|
|
|
|
addToIfd(ifd1, exifData.begin(), exifData.end(),
|
|
|
|
tiffHeader.byteOrder());
|
|
|
|
ifd1.erase(0x8769);
|
|
|
|
ifd1.erase(0x8825);
|
|
|
|
|
|
|
|
// Do not copy the IFD yet, remember the location and leave a gap
|
|
|
|
ifdOffset = buflen;
|
|
|
|
buflen += ifd1.size() + ifd1.dataSize();
|
|
|
|
if (len < buflen) rc = 1;
|
|
|
|
}
|
|
|
|
ExifData::const_iterator offsets;
|
|
|
|
ExifData::const_iterator sizes;
|
|
|
|
if (rc == 0) {
|
|
|
|
// Copy thumbnail image data, remember the offsets used
|
|
|
|
ExifKey key("Exif.Thumbnail.StripOffsets");
|
|
|
|
offsets = exifData.findKey(key);
|
|
|
|
if (offsets == exifData.end()) rc = 2;
|
|
|
|
}
|
|
|
|
if (rc == 0) {
|
|
|
|
ExifKey key("Exif.Thumbnail.StripByteCounts");
|
|
|
|
sizes = exifData.findKey(key);
|
|
|
|
if (sizes == exifData.end()) rc = 2;
|
|
|
|
}
|
|
|
|
std::ostringstream os; // for the new strip offsets
|
|
|
|
long minOffset = 0;
|
|
|
|
if (rc == 0) {
|
|
|
|
for (long k = 0; k < offsets->count(); ++k) {
|
|
|
|
long offset = offsets->toLong(k);
|
|
|
|
long size = sizes->toLong(k);
|
|
|
|
if (len < offset + size || len < buflen + size) {
|
|
|
|
rc = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
memcpy(img.pData_ + buflen, buf + offset, size);
|
|
|
|
os << buflen << " ";
|
|
|
|
buflen += size;
|
|
|
|
|
|
|
|
minOffset = offset; // just to initialize minOffset
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rc == 0) {
|
|
|
|
for (long k = 0; k < offsets->count(); ++k) {
|
|
|
|
minOffset = std::min(minOffset, offsets->toLong(k));
|
|
|
|
}
|
|
|
|
// Update the IFD with the actual strip offsets (replace existing entry)
|
|
|
|
Exifdatum newOffsets(*offsets);
|
|
|
|
newOffsets.setValue(os.str());
|
|
|
|
ifd1.erase(0x0111);
|
|
|
|
addToIfd(ifd1, newOffsets, tiffHeader.byteOrder());
|
|
|
|
|
|
|
|
// Finally, sort and copy the IFD
|
|
|
|
ifd1.sortByTag();
|
|
|
|
ifd1.copy(img.pData_ + ifdOffset, tiffHeader.byteOrder(), ifdOffset);
|
|
|
|
|
|
|
|
delete[] pImage_;
|
|
|
|
pImage_ = new byte[buflen];
|
|
|
|
memcpy(pImage_, img.pData_, buflen);
|
|
|
|
size_ = buflen;
|
|
|
|
offset_ = minOffset;
|
|
|
|
tiffHeader_.read(pImage_);
|
|
|
|
rc = ifd_.read(pImage_ + tiffHeader_.offset(),
|
|
|
|
size_ - tiffHeader_.offset(),
|
|
|
|
tiffHeader_.byteOrder(), tiffHeader_.offset());
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
} // TiffThumbnail::read
|
|
|
|
|
|
|
|
const char* TiffThumbnail::format() const
|
|
|
|
{
|
|
|
|
return "TIFF";
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* TiffThumbnail::extension() const
|
|
|
|
{
|
|
|
|
return ".tif";
|
|
|
|
}
|
|
|
|
|
|
|
|
int TiffThumbnail::write(const std::string& path) const
|
|
|
|
{
|
|
|
|
std::string name = path + extension();
|
|
|
|
FILE *ofp = fopen(name.c_str(), "wb");
|
|
|
|
if (!ofp) return -1;
|
|
|
|
int rc = 0;
|
|
|
|
if (fwrite(pImage_, 1, size_, ofp) != (size_t)size_) rc = 4;
|
|
|
|
fclose(ofp);
|
|
|
|
return rc;
|
|
|
|
} // TiffThumbnail::write
|
|
|
|
|
|
|
|
void TiffThumbnail::update(ExifData& exifData) const
|
|
|
|
{
|
|
|
|
// Todo: properly synchronize the Exif data with the actual thumbnail,
|
|
|
|
// i.e., synch all relevant metadata
|
|
|
|
|
|
|
|
// Create metadata from the StripOffsets and StripByteCounts entries
|
|
|
|
// and update the Exif data accordingly
|
|
|
|
Entries::const_iterator entry = ifd_.findTag(0x0111);
|
|
|
|
if (entry == ifd_.end()) throw Error("Bad thumbnail (0x0111)");
|
|
|
|
ExifData::iterator md = exifData.findIfdIdIdx(entry->ifdId(), entry->idx());
|
|
|
|
if (md == exifData.end()) {
|
|
|
|
exifData.add(Exifdatum(*entry, tiffHeader_.byteOrder()));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
md->setValue(*entry, tiffHeader_.byteOrder());
|
|
|
|
}
|
|
|
|
|
|
|
|
entry = ifd_.findTag(0x0117);
|
|
|
|
if (entry == ifd_.end()) throw Error("Bad thumbnail (0x0117)");
|
|
|
|
md = exifData.findIfdIdIdx(entry->ifdId(), entry->idx());
|
|
|
|
if (md == exifData.end()) {
|
|
|
|
exifData.add(Exifdatum(*entry, tiffHeader_.byteOrder()));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
md->setValue(*entry, tiffHeader_.byteOrder());
|
|
|
|
}
|
|
|
|
|
|
|
|
} // TiffThumbnail::update
|
|
|
|
|
|
|
|
long TiffThumbnail::copy(byte* buf) const
|
|
|
|
{
|
|
|
|
long offset = ifd_.offset() + ifd_.size() + ifd_.dataSize();
|
|
|
|
long size = size_ - offset;
|
|
|
|
memcpy(buf, pImage_ + offset, size);
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
long TiffThumbnail::dataSize() const
|
|
|
|
{
|
|
|
|
return size_ - ifd_.offset() - ifd_.size() - ifd_.dataSize();
|
|
|
|
}
|
|
|
|
|
|
|
|
long TiffThumbnail::size() const
|
|
|
|
{
|
|
|
|
return size_;
|
|
|
|
}
|
|
|
|
|
|
|
|
long TiffThumbnail::offset() const
|
|
|
|
{
|
|
|
|
return offset_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TiffThumbnail::setOffsets(Ifd& ifd1, ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
// Adjust the StripOffsets, assuming that the existing TIFF strips
|
|
|
|
// start immediately after the thumbnail IFD
|
|
|
|
long shift = ifd1.offset() + ifd1.size() + ifd1.dataSize()
|
|
|
|
- ifd_.offset() - ifd_.size() - ifd_.dataSize();
|
|
|
|
Ifd::const_iterator pos = ifd_.findTag(0x0111);
|
|
|
|
if (pos == ifd_.end()) throw Error("Bad thumbnail (0x0111)");
|
|
|
|
Exifdatum offsets(*pos, tiffHeader_.byteOrder());
|
|
|
|
std::ostringstream os;
|
|
|
|
long minOffset = 0;
|
|
|
|
for (long k = 0; k < offsets.count(); ++k) {
|
|
|
|
os << offsets.toLong(k) + shift << " ";
|
|
|
|
minOffset = offsets.toLong(k) + shift; // initialize minOffset
|
|
|
|
}
|
|
|
|
offsets.setValue(os.str());
|
|
|
|
for (long k = 0; k < offsets.count(); ++k) {
|
|
|
|
minOffset = std::min(minOffset, offsets.toLong(k));
|
|
|
|
}
|
|
|
|
offset_ = minOffset;
|
|
|
|
// Update the IFD with the re-calculated strip offsets
|
|
|
|
// (replace existing entry)
|
|
|
|
ifd1.erase(0x0111);
|
|
|
|
addToIfd(ifd1, offsets, byteOrder);
|
|
|
|
|
|
|
|
} // TiffThumbnail::setOffsets
|
|
|
|
|
|
|
|
JpegThumbnail::JpegThumbnail()
|
|
|
|
: offset_(0), size_(0), pImage_(0)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
JpegThumbnail::~JpegThumbnail()
|
|
|
|
{
|
|
|
|
delete[] pImage_;
|
|
|
|
}
|
|
|
|
|
|
|
|
JpegThumbnail::JpegThumbnail(const JpegThumbnail& rhs)
|
|
|
|
: offset_(rhs.offset_), size_(rhs.size_), pImage_(0)
|
|
|
|
{
|
|
|
|
if (rhs.pImage_ && rhs.size_ > 0) {
|
|
|
|
pImage_ = new byte[rhs.size_];
|
|
|
|
memcpy(pImage_, rhs.pImage_, rhs.size_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
JpegThumbnail& JpegThumbnail::operator=(const JpegThumbnail& rhs)
|
|
|
|
{
|
|
|
|
byte* pNewImage = 0;
|
|
|
|
if (rhs.pImage_ && rhs.size_ > 0) {
|
|
|
|
pNewImage = new byte[rhs.size_];
|
|
|
|
memcpy(pNewImage, rhs.pImage_, rhs.size_);
|
|
|
|
}
|
|
|
|
offset_ = rhs.offset_;
|
|
|
|
size_ = rhs.size_;
|
|
|
|
delete[] pImage_;
|
|
|
|
pImage_ = pNewImage;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
int JpegThumbnail::read(const byte* buf,
|
|
|
|
long len,
|
|
|
|
const ExifData& exifData,
|
|
|
|
ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
ExifKey key("Exif.Thumbnail.JPEGInterchangeFormat");
|
|
|
|
ExifData::const_iterator pos = exifData.findKey(key);
|
|
|
|
if (pos == exifData.end()) return 2;
|
|
|
|
long offset = pos->toLong();
|
|
|
|
ExifKey key2("Exif.Thumbnail.JPEGInterchangeFormatLength");
|
|
|
|
pos = exifData.findKey(key2);
|
|
|
|
if (pos == exifData.end()) return 2;
|
|
|
|
long size = pos->toLong();
|
|
|
|
if (len < offset + size) return 1;
|
|
|
|
delete[] pImage_;
|
|
|
|
pImage_ = new byte[size];
|
|
|
|
memcpy(pImage_, buf + offset, size);
|
|
|
|
size_ = size;
|
|
|
|
offset_ = offset;
|
|
|
|
return 0;
|
|
|
|
} // JpegThumbnail::read
|
|
|
|
|
|
|
|
const char* JpegThumbnail::format() const
|
|
|
|
{
|
|
|
|
return "JPEG";
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* JpegThumbnail::extension() const
|
|
|
|
{
|
|
|
|
return ".jpg";
|
|
|
|
}
|
|
|
|
|
|
|
|
int JpegThumbnail::write(const std::string& path) const
|
|
|
|
{
|
|
|
|
std::string name = path + extension();
|
|
|
|
FILE *ofp = fopen(name.c_str(), "wb");
|
|
|
|
if (!ofp) return -1;
|
|
|
|
int rc = 0;
|
|
|
|
if (fwrite(pImage_, 1, size_, ofp) != (size_t)size_) rc = 4;
|
|
|
|
fclose(ofp);
|
|
|
|
return rc;
|
|
|
|
} // JpegThumbnail::write
|
|
|
|
|
|
|
|
void JpegThumbnail::update(ExifData& exifData) const
|
|
|
|
{
|
|
|
|
ExifKey key("Exif.Thumbnail.JPEGInterchangeFormat");
|
|
|
|
ExifData::iterator pos = exifData.findKey(key);
|
|
|
|
if (pos == exifData.end()) {
|
|
|
|
Value* value = Value::create(unsignedLong);
|
|
|
|
exifData.add(key, value);
|
|
|
|
delete value;
|
|
|
|
pos = exifData.findKey(key);
|
|
|
|
}
|
|
|
|
pos->setValue(toString(offset_));
|
|
|
|
|
|
|
|
ExifKey key2("Exif.Thumbnail.JPEGInterchangeFormatLength");
|
|
|
|
pos = exifData.findKey(key2);
|
|
|
|
if (pos == exifData.end()) {
|
|
|
|
Value *value = Value::create(unsignedLong);
|
|
|
|
exifData.add(key2, value);
|
|
|
|
delete value;
|
|
|
|
pos = exifData.findKey(key2);
|
|
|
|
}
|
|
|
|
pos->setValue(toString(size_));
|
|
|
|
|
|
|
|
} // JpegThumbnail::update
|
|
|
|
|
|
|
|
long JpegThumbnail::copy(byte* buf) const
|
|
|
|
{
|
|
|
|
memcpy(buf, pImage_, size_);
|
|
|
|
return size_;
|
|
|
|
}
|
|
|
|
|
|
|
|
long JpegThumbnail::dataSize() const
|
|
|
|
{
|
|
|
|
return size_;
|
|
|
|
}
|
|
|
|
|
|
|
|
long JpegThumbnail::size() const
|
|
|
|
{
|
|
|
|
return size_;
|
|
|
|
}
|
|
|
|
|
|
|
|
long JpegThumbnail::offset() const
|
|
|
|
{
|
|
|
|
return offset_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void JpegThumbnail::setOffsets(Ifd& ifd1, ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
Ifd::iterator pos = ifd1.findTag(0x0201);
|
|
|
|
if (pos == ifd1.end()) throw Error("Bad thumbnail (0x0201)");
|
|
|
|
offset_ = ifd1.offset() + ifd1.size() + ifd1.dataSize();
|
|
|
|
pos->setValue(offset_, byteOrder);
|
|
|
|
}
|
|
|
|
|
|
|
|
ExifData::ExifData()
|
|
|
|
: pThumbnail_(0), pMakerNote_(0), ifd0_(ifd0, 0, false),
|
|
|
|
exifIfd_(exifIfd, 0, false), iopIfd_(iopIfd, 0, false),
|
|
|
|
gpsIfd_(gpsIfd, 0, false), ifd1_(ifd1, 0, false),
|
|
|
|
size_(0), pData_(0), compatible_(true)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ExifData::~ExifData()
|
|
|
|
{
|
|
|
|
delete pMakerNote_;
|
|
|
|
delete pThumbnail_;
|
|
|
|
delete[] pData_;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ExifData::read(const std::string& path)
|
|
|
|
{
|
|
|
|
if (!fileExists(path, true)) return -1;
|
|
|
|
Image* pImage = ImageFactory::instance().open(path);
|
|
|
|
if (pImage) {
|
|
|
|
int rc = pImage->readMetadata();
|
|
|
|
if (rc == 0) {
|
|
|
|
if (pImage->sizeExifData() > 0) {
|
|
|
|
rc = read(pImage->exifData(), pImage->sizeExifData());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
rc = 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete pImage;
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
// We don't know this type of file
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ExifData::read(const byte* buf, long len)
|
|
|
|
{
|
|
|
|
// Copy the data buffer
|
|
|
|
delete[] pData_;
|
|
|
|
pData_ = new byte[len];
|
|
|
|
memcpy(pData_, buf, len);
|
|
|
|
size_ = len;
|
|
|
|
|
|
|
|
// Read the TIFF header
|
|
|
|
int ret = 0;
|
|
|
|
int rc = tiffHeader_.read(pData_);
|
|
|
|
if (rc) return rc;
|
|
|
|
|
|
|
|
// Read IFD0
|
|
|
|
rc = ifd0_.read(pData_ + tiffHeader_.offset(),
|
|
|
|
size_ - tiffHeader_.offset(),
|
|
|
|
byteOrder(),
|
|
|
|
tiffHeader_.offset());
|
|
|
|
if (rc) return rc;
|
|
|
|
// Find and read ExifIFD sub-IFD of IFD0
|
|
|
|
rc = ifd0_.readSubIfd(exifIfd_, pData_, size_, byteOrder(), 0x8769);
|
|
|
|
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& mnf = MakerNoteFactory::instance();
|
|
|
|
// Todo: The conversion to string assumes that there is a \0 at the end
|
|
|
|
// Todo: How to avoid the cast (is that a MSVC thing?)
|
|
|
|
pMakerNote_ = mnf.create(reinterpret_cast<const char*>(make->data()),
|
|
|
|
reinterpret_cast<const char*>(model->data()),
|
|
|
|
false,
|
|
|
|
pos->data(),
|
|
|
|
pos->size(),
|
|
|
|
byteOrder(),
|
|
|
|
exifIfd_.offset() + pos->offset());
|
|
|
|
}
|
|
|
|
// Read the MakerNote
|
|
|
|
if (pMakerNote_) {
|
|
|
|
rc = pMakerNote_->read(pos->data(),
|
|
|
|
pos->size(),
|
|
|
|
byteOrder(),
|
|
|
|
exifIfd_.offset() + pos->offset());
|
|
|
|
if (rc) {
|
|
|
|
// Todo: How to handle debug output like this
|
|
|
|
std::cerr << "Warning: Failed to read "
|
|
|
|
<< pMakerNote_->ifdItem()
|
|
|
|
<< " Makernote, rc = " << rc << "\n";
|
|
|
|
|
|
|
|
delete pMakerNote_;
|
|
|
|
pMakerNote_ = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If we successfully parsed the MakerNote, delete the raw MakerNote,
|
|
|
|
// the parsed MakerNote is the primary MakerNote from now on
|
|
|
|
if (pMakerNote_) {
|
|
|
|
exifIfd_.erase(pos);
|
|
|
|
}
|
|
|
|
// Find and read Interoperability IFD in ExifIFD
|
|
|
|
rc = exifIfd_.readSubIfd(iopIfd_, pData_, size_, byteOrder(), 0xa005);
|
|
|
|
if (rc) return rc;
|
|
|
|
// Find and read GPSInfo sub-IFD in IFD0
|
|
|
|
rc = ifd0_.readSubIfd(gpsIfd_, pData_, size_, byteOrder(), 0x8825);
|
|
|
|
if (rc) return rc;
|
|
|
|
// Read IFD1
|
|
|
|
if (ifd0_.next()) {
|
|
|
|
rc = ifd1_.read(pData_ + ifd0_.next(),
|
|
|
|
size_ - ifd0_.next(),
|
|
|
|
byteOrder(),
|
|
|
|
ifd0_.next());
|
|
|
|
if (rc) return rc;
|
|
|
|
}
|
|
|
|
// Find and delete ExifIFD sub-IFD of IFD1
|
|
|
|
pos = ifd1_.findTag(0x8769);
|
|
|
|
if (pos != ifd1_.end()) {
|
|
|
|
ifd1_.erase(pos);
|
|
|
|
ret = 7;
|
|
|
|
}
|
|
|
|
// Find and delete GPSInfo sub-IFD in IFD1
|
|
|
|
pos = ifd1_.findTag(0x8825);
|
|
|
|
if (pos != ifd1_.end()) {
|
|
|
|
ifd1_.erase(pos);
|
|
|
|
ret = 7;
|
|
|
|
}
|
|
|
|
// Copy all entries from the IFDs and the MakerNote to the metadata
|
|
|
|
exifMetadata_.clear();
|
|
|
|
add(ifd0_.begin(), ifd0_.end(), byteOrder());
|
|
|
|
add(exifIfd_.begin(), exifIfd_.end(), byteOrder());
|
|
|
|
if (pMakerNote_) {
|
|
|
|
add(pMakerNote_->begin(), pMakerNote_->end(), pMakerNote_->byteOrder());
|
|
|
|
}
|
|
|
|
add(iopIfd_.begin(), iopIfd_.end(), byteOrder());
|
|
|
|
add(gpsIfd_.begin(), gpsIfd_.end(), byteOrder());
|
|
|
|
add(ifd1_.begin(), ifd1_.end(), byteOrder());
|
|
|
|
// Read the thumbnail (but don't worry whether it was successful or not)
|
|
|
|
readThumbnail();
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
} // ExifData::read
|
|
|
|
|
|
|
|
int ExifData::erase(const std::string& path) const
|
|
|
|
{
|
|
|
|
if (!fileExists(path, true)) return -1;
|
|
|
|
Image* pImage = ImageFactory::instance().open(path);
|
|
|
|
if (pImage == 0) return -2;
|
|
|
|
|
|
|
|
// Read all metadata then erase only Exif data
|
|
|
|
int rc = pImage->readMetadata();
|
|
|
|
if (rc == 0) {
|
|
|
|
pImage->clearExifData();
|
|
|
|
rc = pImage->writeMetadata();
|
|
|
|
}
|
|
|
|
delete pImage;
|
|
|
|
return rc;
|
|
|
|
} // ExifData::erase
|
|
|
|
|
|
|
|
int ExifData::write(const std::string& path)
|
|
|
|
{
|
|
|
|
// Remove the Exif section from the file if there is no metadata
|
|
|
|
if (count() == 0 && !pThumbnail_) return erase(path);
|
|
|
|
|
|
|
|
if (!fileExists(path, true)) return -1;
|
|
|
|
Image* pImage = ImageFactory::instance().open(path);
|
|
|
|
if (pImage == 0) return -2;
|
|
|
|
|
|
|
|
DataBuf buf(size());
|
|
|
|
long actualSize = copy(buf.pData_);
|
|
|
|
assert(actualSize <= buf.size_);
|
|
|
|
|
|
|
|
// Read all metadata to preserve non-Exif data
|
|
|
|
int rc = pImage->readMetadata();
|
|
|
|
if (rc == 0) {
|
|
|
|
pImage->setExifData(buf.pData_, actualSize);
|
|
|
|
rc = pImage->writeMetadata();
|
|
|
|
}
|
|
|
|
delete pImage;
|
|
|
|
return rc;
|
|
|
|
} // ExifData::write
|
|
|
|
|
|
|
|
long ExifData::copy(byte* buf)
|
|
|
|
{
|
|
|
|
long size = 0;
|
|
|
|
// If we can update the internal IFDs and the underlying data buffer
|
|
|
|
// from the metadata without changing the data size, then it is enough
|
|
|
|
// to copy the data buffer.
|
|
|
|
if (compatible_ && updateEntries()) {
|
|
|
|
#ifdef DEBUG_MAKERNOTE
|
|
|
|
std::cerr << "->>>>>> using non-intrusive writing <<<<<<-\n";
|
|
|
|
#endif
|
|
|
|
memcpy(buf, pData_, size_);
|
|
|
|
size = size_;
|
|
|
|
}
|
|
|
|
// Else we have to do it the hard way...
|
|
|
|
else {
|
|
|
|
#ifdef DEBUG_MAKERNOTE
|
|
|
|
std::cerr << "->>>>>> writing from metadata <<<<<<-\n";
|
|
|
|
#endif
|
|
|
|
size = copyFromMetadata(buf);
|
|
|
|
}
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
long ExifData::copyFromMetadata(byte* buf)
|
|
|
|
{
|
|
|
|
// Build IFD0
|
|
|
|
Ifd ifd0(ifd0);
|
|
|
|
addToIfd(ifd0, begin(), end(), byteOrder());
|
|
|
|
|
|
|
|
// Build Exif IFD from metadata
|
|
|
|
Ifd exifIfd(exifIfd);
|
|
|
|
addToIfd(exifIfd, begin(), end(), byteOrder());
|
|
|
|
MakerNote* pMakerNote = 0;
|
|
|
|
if (pMakerNote_) {
|
|
|
|
// Build MakerNote from metadata
|
|
|
|
pMakerNote = pMakerNote_->clone();
|
|
|
|
addToMakerNote(pMakerNote, begin(), end(), pMakerNote_->byteOrder());
|
|
|
|
// Create a placeholder MakerNote entry of the correct size and
|
|
|
|
// add it to the Exif IFD (because we don't know the offset yet)
|
|
|
|
Entry e;
|
|
|
|
e.setIfdId(exifIfd.ifdId());
|
|
|
|
e.setTag(0x927c);
|
|
|
|
DataBuf buf(pMakerNote->size());
|
|
|
|
memset(buf.pData_, 0x0, buf.size_);
|
|
|
|
e.setValue(undefined, buf.size_, buf.pData_, buf.size_);
|
|
|
|
exifIfd.erase(0x927c);
|
|
|
|
exifIfd.add(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build Interoperability IFD from metadata
|
|
|
|
Ifd iopIfd(iopIfd);
|
|
|
|
addToIfd(iopIfd, begin(), end(), byteOrder());
|
|
|
|
|
|
|
|
// Build GPSInfo IFD from metadata
|
|
|
|
Ifd gpsIfd(gpsIfd);
|
|
|
|
addToIfd(gpsIfd, begin(), end(), byteOrder());
|
|
|
|
|
|
|
|
// Compute the new IFD offsets
|
|
|
|
int exifIdx = ifd0.erase(0x8769);
|
|
|
|
int gpsIdx = ifd0.erase(0x8825);
|
|
|
|
int iopIdx = exifIfd.erase(0xa005);
|
|
|
|
|
|
|
|
long ifd0Offset = tiffHeader_.size();
|
|
|
|
bool addOffsetTag = false;
|
|
|
|
long exifIfdOffset = ifd0Offset + ifd0.size() + ifd0.dataSize();
|
|
|
|
if (exifIfd.size() > 0 || iopIfd.size() > 0) {
|
|
|
|
exifIfdOffset += 12;
|
|
|
|
addOffsetTag = true;
|
|
|
|
}
|
|
|
|
if (gpsIfd.size() > 0) {
|
|
|
|
exifIfdOffset += 12;
|
|
|
|
addOffsetTag = true;
|
|
|
|
}
|
|
|
|
if (ifd0.size() == 0 && addOffsetTag) {
|
|
|
|
exifIfdOffset += 6;
|
|
|
|
}
|
|
|
|
addOffsetTag = false;
|
|
|
|
long iopIfdOffset = exifIfdOffset + exifIfd.size() + exifIfd.dataSize();
|
|
|
|
if (iopIfd.size() > 0) {
|
|
|
|
iopIfdOffset += 12;
|
|
|
|
addOffsetTag = true;
|
|
|
|
}
|
|
|
|
if (exifIfd.size() == 0 && addOffsetTag) {
|
|
|
|
iopIfdOffset += 6;
|
|
|
|
}
|
|
|
|
long gpsIfdOffset = iopIfdOffset + iopIfd.size() + iopIfd.dataSize();
|
|
|
|
long ifd1Offset = gpsIfdOffset + gpsIfd.size() + gpsIfd.dataSize();
|
|
|
|
|
|
|
|
// build IFD1 from updated metadata if there is a thumbnail
|
|
|
|
Ifd ifd1(Exiv2::ifd1, ifd1Offset);
|
|
|
|
if (pThumbnail_) {
|
|
|
|
// Update Exif data from thumbnail
|
|
|
|
pThumbnail_->update(*this);
|
|
|
|
addToIfd(ifd1, begin(), end(), byteOrder());
|
|
|
|
pThumbnail_->setOffsets(ifd1, byteOrder());
|
|
|
|
// Set the offset to IFD1 in IFD0
|
|
|
|
if (ifd1.size() > 0) {
|
|
|
|
ifd0.setNext(ifd1Offset, byteOrder());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the offset to the Exif IFD in IFD0
|
|
|
|
if (exifIfd.size() > 0 || iopIfd.size() > 0) {
|
|
|
|
setOffsetTag(ifd0, exifIdx, 0x8769, exifIfdOffset, byteOrder());
|
|
|
|
}
|
|
|
|
// Set the offset to the GPSInfo IFD in IFD0
|
|
|
|
if (gpsIfd.size() > 0) {
|
|
|
|
setOffsetTag(ifd0, gpsIdx, 0x8825, gpsIfdOffset, byteOrder());
|
|
|
|
}
|
|
|
|
// Set the offset to the Interoperability IFD in Exif IFD
|
|
|
|
if (iopIfd.size() > 0) {
|
|
|
|
setOffsetTag(exifIfd, iopIdx, 0xa005, iopIfdOffset, byteOrder());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the TIFF header, all IFDs, MakerNote and thumbnail to the buffer
|
|
|
|
tiffHeader_.copy(buf);
|
|
|
|
ifd0.sortByTag();
|
|
|
|
ifd0.copy(buf + ifd0Offset, byteOrder(), ifd0Offset);
|
|
|
|
exifIfd.sortByTag();
|
|
|
|
exifIfd.copy(buf + exifIfdOffset, byteOrder(), exifIfdOffset);
|
|
|
|
if (pMakerNote) {
|
|
|
|
// Copy the MakerNote over the placeholder data
|
|
|
|
Entries::iterator mn = exifIfd.findTag(0x927c);
|
|
|
|
// Do _not_ sort the makernote; vendors (at least Canon), don't seem
|
|
|
|
// to bother about this TIFF standard requirement, so writing the
|
|
|
|
// makernote as is might result in fewer deviations from the original
|
|
|
|
pMakerNote->copy(buf + exifIfdOffset + mn->offset(),
|
|
|
|
byteOrder(),
|
|
|
|
exifIfdOffset + mn->offset());
|
|
|
|
delete pMakerNote;
|
|
|
|
pMakerNote = 0;
|
|
|
|
}
|
|
|
|
iopIfd.sortByTag();
|
|
|
|
iopIfd.copy(buf + iopIfdOffset, byteOrder(), iopIfdOffset);
|
|
|
|
gpsIfd.sortByTag();
|
|
|
|
gpsIfd.copy(buf + gpsIfdOffset, byteOrder(), gpsIfdOffset);
|
|
|
|
long len = ifd1Offset;
|
|
|
|
if (pThumbnail_) {
|
|
|
|
ifd1.sortByTag();
|
|
|
|
ifd1.copy(buf + ifd1Offset, byteOrder(), ifd1Offset);
|
|
|
|
long thumbOffset = ifd1Offset + ifd1.size() + ifd1.dataSize();
|
|
|
|
len = thumbOffset;
|
|
|
|
len += pThumbnail_->copy(buf + thumbOffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
|
|
|
|
|
|
|
} // ExifData::copyFromMetadata
|
|
|
|
|
|
|
|
long ExifData::size() const
|
|
|
|
{
|
|
|
|
long size;
|
|
|
|
if (compatible()) {
|
|
|
|
size = size_;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
size = tiffHeader_.size();
|
|
|
|
std::map<IfdId, int> ifdEntries;
|
|
|
|
const_iterator mdEnd = this->end();
|
|
|
|
for (const_iterator md = begin(); md != mdEnd; ++md) {
|
|
|
|
size += md->size();
|
|
|
|
ifdEntries[md->ifdId()] += 1;
|
|
|
|
}
|
|
|
|
std::map<IfdId, int>::const_iterator eEnd = ifdEntries.end();
|
|
|
|
std::map<IfdId, int>::const_iterator e;
|
|
|
|
for (e = ifdEntries.begin(); e != eEnd; ++e) {
|
|
|
|
// Skip IFD1 entries if there is no thumbnail (maybe it was deleted)
|
|
|
|
if (e->first == ifd1 && !pThumbnail_) continue;
|
|
|
|
size += 2 + 12 * e->second + 4;
|
|
|
|
}
|
|
|
|
// Add the size of the thumbnail image data (w/o IFD for TIFF thumbs)
|
|
|
|
if (pThumbnail_) {
|
|
|
|
size += pThumbnail_->dataSize();
|
|
|
|
}
|
|
|
|
// Add 1k to account for the possibility that Thumbnail::update
|
|
|
|
// may add entries to IFD1
|
|
|
|
size += 1024;
|
|
|
|
}
|
|
|
|
return size;
|
|
|
|
} // ExifData::size
|
|
|
|
|
|
|
|
int ExifData::writeExifData(const std::string& path)
|
|
|
|
{
|
|
|
|
DataBuf buf(this->size());
|
|
|
|
long actualSize = copy(buf.pData_);
|
|
|
|
assert(actualSize <= buf.size_);
|
|
|
|
|
|
|
|
ExvImage exvImage(path, true);
|
|
|
|
if (!exvImage.good()) return -1;
|
|
|
|
exvImage.setExifData(buf.pData_, actualSize);
|
|
|
|
return exvImage.writeMetadata();
|
|
|
|
} // ExifData::writeExifData
|
|
|
|
|
|
|
|
void ExifData::add(Entries::const_iterator begin,
|
|
|
|
Entries::const_iterator end,
|
|
|
|
ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
Entries::const_iterator i = begin;
|
|
|
|
for (; i != end; ++i) {
|
|
|
|
add(Exifdatum(*i, byteOrder));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExifData::add(const ExifKey& key, Value* pValue)
|
|
|
|
{
|
|
|
|
// Todo: Implement a more suitable ExifKey c'tor
|
|
|
|
ExifKey k(key);
|
|
|
|
k.setMakerNote(pMakerNote_);
|
|
|
|
add(Exifdatum(k, pValue));
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExifData::add(const Exifdatum& exifdatum)
|
|
|
|
{
|
|
|
|
// allow duplicates
|
|
|
|
exifMetadata_.push_back(exifdatum);
|
|
|
|
}
|
|
|
|
|
|
|
|
ExifData::const_iterator ExifData::findKey(const ExifKey& key) const
|
|
|
|
{
|
|
|
|
return std::find_if(exifMetadata_.begin(), exifMetadata_.end(),
|
|
|
|
FindMetadatumByKey(key.key()));
|
|
|
|
}
|
|
|
|
|
|
|
|
ExifData::iterator ExifData::findKey(const ExifKey& key)
|
|
|
|
{
|
|
|
|
return std::find_if(exifMetadata_.begin(), exifMetadata_.end(),
|
|
|
|
FindMetadatumByKey(key.key()));
|
|
|
|
}
|
|
|
|
|
|
|
|
ExifData::const_iterator ExifData::findIfdIdIdx(IfdId ifdId, int idx) const
|
|
|
|
{
|
|
|
|
return std::find_if(exifMetadata_.begin(), exifMetadata_.end(),
|
|
|
|
FindMetadatumByIfdIdIdx(ifdId, idx));
|
|
|
|
}
|
|
|
|
|
|
|
|
ExifData::iterator ExifData::findIfdIdIdx(IfdId ifdId, int idx)
|
|
|
|
{
|
|
|
|
return std::find_if(exifMetadata_.begin(), exifMetadata_.end(),
|
|
|
|
FindMetadatumByIfdIdIdx(ifdId, idx));
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExifData::sortByKey()
|
|
|
|
{
|
|
|
|
std::sort(exifMetadata_.begin(), exifMetadata_.end(), cmpMetadataByKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ExifData::sortByTag()
|
|
|
|
{
|
|
|
|
std::sort(exifMetadata_.begin(), exifMetadata_.end(), cmpMetadataByTag);
|
|
|
|
}
|
|
|
|
|
|
|
|
ExifData::iterator ExifData::erase(ExifData::iterator pos)
|
|
|
|
{
|
|
|
|
return exifMetadata_.erase(pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
long ExifData::eraseThumbnail()
|
|
|
|
{
|
|
|
|
// Delete all Thumbnail.*.* (IFD1) metadata
|
|
|
|
ExifMetadata::iterator i = begin();
|
|
|
|
while (i != end()) {
|
|
|
|
if (i->ifdId() == ifd1) {
|
|
|
|
i = erase(i);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
long delta = 0;
|
|
|
|
if (stdThumbPosition()) {
|
|
|
|
delta = size_;
|
|
|
|
if (size_ > 0 && ifd0_.next() > 0) {
|
|
|
|
// Truncate IFD1 and thumbnail data from the data buffer
|
|
|
|
size_ = ifd0_.next();
|
|
|
|
ifd0_.setNext(0, byteOrder());
|
|
|
|
}
|
|
|
|
delta -= size_;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// We will have to write the hard way and re-arrange the data
|
|
|
|
compatible_ = false;
|
|
|
|
delta = ifd1_.size() + ifd1_.dataSize()
|
|
|
|
+ pThumbnail_ ? pThumbnail_->size() : 0;
|
|
|
|
}
|
|
|
|
// Delete the thumbnail itself
|
|
|
|
if (pThumbnail_) {
|
|
|
|
delete pThumbnail_;
|
|
|
|
pThumbnail_ = 0;
|
|
|
|
}
|
|
|
|
return delta;
|
|
|
|
} // ExifData::eraseThumbnail
|
|
|
|
|
|
|
|
bool ExifData::stdThumbPosition() const
|
|
|
|
{
|
|
|
|
// Todo: There is still an invalid assumption here: The data of an IFD
|
|
|
|
// can be stored in multiple non-contiguous blocks. In this case,
|
|
|
|
// dataOffset + dataSize does not point to the end of the IFD data.
|
|
|
|
// in particular, this is potentially the case for the remaining Exif
|
|
|
|
// data in the presence of a known Makernote.
|
|
|
|
bool rc = true;
|
|
|
|
if (pThumbnail_) {
|
|
|
|
long maxOffset;
|
|
|
|
maxOffset = std::max(ifd0_.offset(), ifd0_.dataOffset());
|
|
|
|
maxOffset = std::max(maxOffset, exifIfd_.offset());
|
|
|
|
maxOffset = std::max(maxOffset, exifIfd_.dataOffset()
|
|
|
|
+ exifIfd_.dataSize());
|
|
|
|
if (pMakerNote_) {
|
|
|
|
maxOffset = std::max(maxOffset, pMakerNote_->offset()
|
|
|
|
+ pMakerNote_->size());
|
|
|
|
}
|
|
|
|
maxOffset = std::max(maxOffset, iopIfd_.offset());
|
|
|
|
maxOffset = std::max(maxOffset, iopIfd_.dataOffset()
|
|
|
|
+ iopIfd_.dataSize());
|
|
|
|
maxOffset = std::max(maxOffset, gpsIfd_.offset());
|
|
|
|
maxOffset = std::max(maxOffset, gpsIfd_.dataOffset()
|
|
|
|
+ gpsIfd_.dataSize());
|
|
|
|
|
|
|
|
if ( maxOffset > ifd1_.offset()
|
|
|
|
|| maxOffset > ifd1_.dataOffset() && ifd1_.dataOffset() > 0
|
|
|
|
|| maxOffset > pThumbnail_->offset()) rc = false;
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
} // ExifData::stdThumbPosition
|
|
|
|
|
|
|
|
int ExifData::readThumbnail()
|
|
|
|
{
|
|
|
|
delete pThumbnail_;
|
|
|
|
pThumbnail_ = 0;
|
|
|
|
int rc = -1;
|
|
|
|
const_iterator pos
|
|
|
|
= findKey(ExifKey("Exif.Thumbnail.Compression"));
|
|
|
|
if (pos != end()) {
|
|
|
|
long compression = pos->toLong();
|
|
|
|
if (compression == 6) {
|
|
|
|
pThumbnail_ = new JpegThumbnail;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
pThumbnail_ = new TiffThumbnail;
|
|
|
|
}
|
|
|
|
rc = pThumbnail_->read(pData_, size_, *this, byteOrder());
|
|
|
|
if (rc != 0) {
|
|
|
|
delete pThumbnail_;
|
|
|
|
pThumbnail_ = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
} // ExifData::readThumbnail
|
|
|
|
|
|
|
|
bool ExifData::updateEntries()
|
|
|
|
{
|
|
|
|
if (!this->compatible()) return false;
|
|
|
|
|
|
|
|
bool compatible = true;
|
|
|
|
compatible &= updateRange(ifd0_.begin(), ifd0_.end(), byteOrder());
|
|
|
|
compatible &= updateRange(exifIfd_.begin(), exifIfd_.end(), byteOrder());
|
|
|
|
if (pMakerNote_) {
|
|
|
|
compatible &= updateRange(pMakerNote_->begin(),
|
|
|
|
pMakerNote_->end(),
|
|
|
|
pMakerNote_->byteOrder());
|
|
|
|
}
|
|
|
|
compatible &= updateRange(iopIfd_.begin(), iopIfd_.end(), byteOrder());
|
|
|
|
compatible &= updateRange(gpsIfd_.begin(), gpsIfd_.end(), byteOrder());
|
|
|
|
if (pThumbnail_) {
|
|
|
|
compatible &= updateRange(ifd1_.begin(), ifd1_.end(), byteOrder());
|
|
|
|
}
|
|
|
|
|
|
|
|
return compatible;
|
|
|
|
} // ExifData::updateEntries
|
|
|
|
|
|
|
|
bool ExifData::updateRange(const Entries::iterator& begin,
|
|
|
|
const Entries::iterator& end,
|
|
|
|
ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
bool compatible = true;
|
|
|
|
for (Entries::iterator entry = begin; entry != end; ++entry) {
|
|
|
|
// find the corresponding Exifdatum
|
|
|
|
const_iterator md = findIfdIdIdx(entry->ifdId(), entry->idx());
|
|
|
|
if (md == this->end()) {
|
|
|
|
// corresponding Exifdatum was deleted: this is not (yet) a
|
|
|
|
// supported non-intrusive write operation.
|
|
|
|
compatible = false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (entry->count() == 0 && md->count() == 0) {
|
|
|
|
// Special case: don't do anything if both the entry and
|
|
|
|
// Exifdatum have no data. This is to preserve the original
|
|
|
|
// data in the offset field of an IFD entry with count 0,
|
|
|
|
// if the Exifdatum was not changed.
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
DataBuf buf(md->size());
|
|
|
|
md->copy(buf.pData_, byteOrder);
|
|
|
|
entry->setValue(static_cast<uint16>(md->typeId()), md->count(),
|
|
|
|
buf.pData_, md->size());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return compatible;
|
|
|
|
} // ExifData::updateRange
|
|
|
|
|
|
|
|
bool ExifData::compatible() const
|
|
|
|
{
|
|
|
|
bool compatible = true;
|
|
|
|
// For each Exifdatum, check if it is compatible with the corresponding
|
|
|
|
// IFD or MakerNote entry
|
|
|
|
for (const_iterator md = begin(); md != this->end(); ++md) {
|
|
|
|
// Skip IFD1 entries if there is no thumbnail (maybe it was deleted)
|
|
|
|
if (md->ifdId() == ifd1 && !pThumbnail_) continue;
|
|
|
|
std::pair<bool, Entries::const_iterator> rc;
|
|
|
|
rc = findEntry(md->ifdId(), md->idx());
|
|
|
|
// Make sure that we have an entry
|
|
|
|
if (!rc.first) {
|
|
|
|
compatible = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Make sure that the size of the Exifdatum fits the available size
|
|
|
|
// of the entry
|
|
|
|
if (md->size() > rc.second->size()) {
|
|
|
|
compatible = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return compatible;
|
|
|
|
} // ExifData::compatible
|
|
|
|
|
|
|
|
std::pair<bool, Entries::const_iterator>
|
|
|
|
ExifData::findEntry(IfdId ifdId, int idx) const
|
|
|
|
{
|
|
|
|
Entries::const_iterator entry;
|
|
|
|
std::pair<bool, Entries::const_iterator> rc(false, entry);
|
|
|
|
|
|
|
|
if (ifdId == makerIfd && pMakerNote_) {
|
|
|
|
entry = pMakerNote_->findIdx(idx);
|
|
|
|
if (entry != pMakerNote_->end()) {
|
|
|
|
rc.first = true;
|
|
|
|
rc.second = entry;
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
const Ifd* ifd = getIfd(ifdId);
|
|
|
|
if (ifdId != makerIfd && ifd) {
|
|
|
|
entry = ifd->findIdx(idx);
|
|
|
|
if (entry != ifd->end()) {
|
|
|
|
rc.first = true;
|
|
|
|
rc.second = entry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
} // ExifData::findEntry
|
|
|
|
|
|
|
|
const Ifd* ExifData::getIfd(IfdId ifdId) const
|
|
|
|
{
|
|
|
|
const Ifd* ifd = 0;
|
|
|
|
switch (ifdId) {
|
|
|
|
case ifd0:
|
|
|
|
ifd = &ifd0_;
|
|
|
|
break;
|
|
|
|
case exifIfd:
|
|
|
|
ifd = &exifIfd_;
|
|
|
|
break;
|
|
|
|
case iopIfd:
|
|
|
|
ifd = &iopIfd_;
|
|
|
|
break;
|
|
|
|
case gpsIfd:
|
|
|
|
ifd = &gpsIfd_;
|
|
|
|
break;
|
|
|
|
case ifd1:
|
|
|
|
ifd = &ifd1_;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ifd = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return ifd;
|
|
|
|
} // ExifData::getIfd
|
|
|
|
|
|
|
|
std::string ExifData::strError(int rc, const std::string& path)
|
|
|
|
{
|
|
|
|
std::string error = path + ": ";
|
|
|
|
switch (rc) {
|
|
|
|
case -1:
|
|
|
|
error += "Failed to open the file";
|
|
|
|
break;
|
|
|
|
case -2:
|
|
|
|
error += "The file contains data of an unknown image type";
|
|
|
|
break;
|
|
|
|
case -3:
|
|
|
|
error += "Couldn't open temporary file";
|
|
|
|
break;
|
|
|
|
case -4:
|
|
|
|
error += "Renaming temporary file failed";
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
error += "Couldn't read from the input stream";
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
error += "This does not look like a JPEG image";
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
error += "No Exif data found in the file";
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
error += "Writing to the output stream failed";
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
error += "No JFIF APP0 or Exif APP1 segment found in the file";
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
error += "Exif data contains a broken IFD";
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
error += "Unsupported Exif or GPS data found in IFD1";
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
error += "Accessing Exif data failed, rc = " + toString(rc);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return error;
|
|
|
|
} // ExifData::strError
|
|
|
|
|
|
|
|
// *************************************************************************
|
|
|
|
// free functions
|
|
|
|
|
|
|
|
void addToIfd(Ifd& ifd,
|
|
|
|
ExifMetadata::const_iterator begin,
|
|
|
|
ExifMetadata::const_iterator end,
|
|
|
|
ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
for (ExifMetadata::const_iterator i = begin; i != end; ++i) {
|
|
|
|
// add only metadata with matching IFD id
|
|
|
|
if (i->ifdId() == ifd.ifdId()) {
|
|
|
|
addToIfd(ifd, *i, byteOrder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // addToIfd
|
|
|
|
|
|
|
|
void addToIfd(Ifd& ifd, const Exifdatum& md, ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
assert(ifd.alloc());
|
|
|
|
|
|
|
|
Entry e;
|
|
|
|
e.setIfdId(md.ifdId());
|
|
|
|
e.setIdx(md.idx());
|
|
|
|
e.setTag(md.tag());
|
|
|
|
e.setOffset(0); // will be calculated when the IFD is written
|
|
|
|
|
|
|
|
DataBuf buf(md.size());
|
|
|
|
md.copy(buf.pData_, byteOrder);
|
|
|
|
e.setValue(static_cast<uint16>(md.typeId()), md.count(), buf.pData_, md.size());
|
|
|
|
ifd.add(e);
|
|
|
|
} // addToIfd
|
|
|
|
|
|
|
|
void addToMakerNote(MakerNote* makerNote,
|
|
|
|
ExifMetadata::const_iterator begin,
|
|
|
|
ExifMetadata::const_iterator end,
|
|
|
|
ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
for (ExifMetadata::const_iterator i = begin; i != end; ++i) {
|
|
|
|
// add only metadata with IFD id 'makerIfd'
|
|
|
|
if (i->ifdId() == makerIfd) {
|
|
|
|
addToMakerNote(makerNote, *i, byteOrder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // addToMakerNote
|
|
|
|
|
|
|
|
void addToMakerNote(MakerNote* makerNote,
|
|
|
|
const Exifdatum& md,
|
|
|
|
ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
Entry e;
|
|
|
|
e.setIfdId(md.ifdId());
|
|
|
|
e.setIdx(md.idx());
|
|
|
|
e.setTag(md.tag());
|
|
|
|
e.setOffset(0); // will be calculated when the makernote is written
|
|
|
|
|
|
|
|
DataBuf buf(md.size());
|
|
|
|
md.copy(buf.pData_, byteOrder);
|
|
|
|
e.setValue(static_cast<uint16>(md.typeId()), md.count(),
|
|
|
|
buf.pData_, md.size());
|
|
|
|
makerNote->add(e);
|
|
|
|
} // addToMakerNote
|
|
|
|
|
|
|
|
std::ostream& operator<<(std::ostream& os, const Exifdatum& md)
|
|
|
|
{
|
|
|
|
return md.pKey_->printTag(os, md.value());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string makeKey(const Entry& entry)
|
|
|
|
{
|
|
|
|
if (entry.ifdId() == makerIfd && entry.makerNote() != 0) {
|
|
|
|
return entry.makerNote()->makeKey(entry.tag());
|
|
|
|
}
|
|
|
|
return ExifTags::makeKey(entry.tag(), entry.ifdId());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::pair<uint16, IfdId> decomposeKey(const std::string& key,
|
|
|
|
const MakerNote* makerNote)
|
|
|
|
{
|
|
|
|
std::pair<uint16, IfdId> p = ExifTags::decomposeKey(key);
|
|
|
|
if (p.second == makerIfd && makerNote != 0) {
|
|
|
|
p.first = makerNote->decomposeKey(key);
|
|
|
|
}
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Exiv2
|
|
|
|
|
|
|
|
// *****************************************************************************
|
|
|
|
// local definitions
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
void setOffsetTag(Exiv2::Ifd& ifd,
|
|
|
|
int idx,
|
|
|
|
Exiv2::uint16 tag,
|
|
|
|
Exiv2::uint32 offset,
|
|
|
|
Exiv2::ByteOrder byteOrder)
|
|
|
|
{
|
|
|
|
Exiv2::Ifd::iterator pos = ifd.findTag(tag);
|
|
|
|
if (pos == ifd.end()) {
|
|
|
|
Exiv2::Entry e(ifd.alloc());
|
|
|
|
e.setIfdId(ifd.ifdId());
|
|
|
|
e.setIdx(idx);
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|