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.
exiv2/src/crwimage.cpp

1355 lines
46 KiB
C++

// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 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: crwimage.cpp
Version: $Rev$
Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
History: 28-Aug-05, ahu: created
*/
// *****************************************************************************
#include "rcsid.hpp"
EXIV2_RCSID("@(#) $Id$");
// Define DEBUG to output debug information to std::cerr, e.g, by calling make
// like this: make DEFS=-DDEBUG crwimage.o
//#define DEBUG
// *****************************************************************************
// included header files
#ifdef _MSC_VER
# include "exv_msvc.h"
#else
# include "exv_conf.h"
#endif
#include "crwimage.hpp"
#include "error.hpp"
#include "futils.hpp"
#include "value.hpp"
#include "tags.hpp"
#include "canonmn.hpp"
// + standard includes
#include <iostream>
#include <iomanip>
#include <stack>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cmath>
#include <cassert>
#ifndef EXV_HAVE_TIMEGM
# include "timegm.h"
#endif
// *****************************************************************************
// class member definitions
namespace Exiv2 {
const CrwMapping CrwMap::crwMapping_[] = {
// CrwTag CrwDir Size ExifTag IfdId decodeFct encodeFct
// ------ ------ ---- ------- ----- --------- ---------
CrwMapping(0x0805, 0x300a, 0, 0, canonIfdId, decode0x0805, encode0x0805),
CrwMapping(0x080a, 0x2807, 0, 0, canonIfdId, decode0x080a, encode0x080a),
CrwMapping(0x080b, 0x3004, 0, 0x0007, canonIfdId, decodeBasic, encodeBasic),
CrwMapping(0x0810, 0x2807, 0, 0x0009, canonIfdId, decodeBasic, encodeBasic),
CrwMapping(0x0815, 0x2804, 0, 0x0006, canonIfdId, decodeBasic, encodeBasic),
CrwMapping(0x1029, 0x300b, 0, 0x0002, canonIfdId, decodeBasic, encodeBasic),
CrwMapping(0x102a, 0x300b, 0, 0x0004, canonIfdId, decodeArray, encodeArray),
CrwMapping(0x102d, 0x300b, 0, 0x0001, canonIfdId, decodeArray, encodeArray),
CrwMapping(0x1033, 0x300b, 0, 0x000f, canonIfdId, decodeArray, encodeArray),
CrwMapping(0x1038, 0x300b, 0, 0x0012, canonIfdId, decodeBasic, encodeBasic),
CrwMapping(0x10a9, 0x300b, 0, 0x00a9, canonIfdId, decodeBasic, encodeBasic),
// Mapped to Exif.Photo.ColorSpace instead (see below)
//CrwMapping(0x10b4, 0x300b, 0, 0x00b4, canonIfdId, decodeBasic, encodeBasic),
CrwMapping(0x10b4, 0x300b, 0, 0xa001, exifIfdId, decodeBasic, encodeBasic),
CrwMapping(0x10b5, 0x300b, 0, 0x00b5, canonIfdId, decodeBasic, encodeBasic),
CrwMapping(0x10c0, 0x300b, 0, 0x00c0, canonIfdId, decodeBasic, encodeBasic),
CrwMapping(0x10c1, 0x300b, 0, 0x00c1, canonIfdId, decodeBasic, encodeBasic),
CrwMapping(0x1807, 0x3002, 0, 0x9206, exifIfdId, decodeBasic, encodeBasic),
CrwMapping(0x180b, 0x3004, 0, 0x000c, canonIfdId, decodeBasic, encodeBasic),
CrwMapping(0x180e, 0x300a, 0, 0x9003, exifIfdId, decode0x180e, encode0x180e),
CrwMapping(0x1810, 0x300a, 0, 0xa002, exifIfdId, decode0x1810, encode0x1810),
CrwMapping(0x1817, 0x300a, 4, 0x0008, canonIfdId, decodeBasic, encodeBasic),
//CrwMapping(0x1818, 0x3002, 0, 0x9204, exifIfdId, decodeBasic, encodeBasic),
CrwMapping(0x183b, 0x300b, 0, 0x0015, canonIfdId, decodeBasic, encodeBasic),
CrwMapping(0x2008, 0x0000, 0, 0, ifd1Id, decode0x2008, encode0x2008),
// End of list marker
CrwMapping(0x0000, 0x0000, 0, 0x0000, ifdIdNotSet, 0, 0)
}; // CrwMap::crwMapping_[]
/*
CIFF directory hierarchy
root
|
300a
|
+----+----+----+----+
| | | | |
2804 2807 3002 3003 300b
|
3004
The array is arranged bottom-up so that starting with a directory at the
bottom, the (unique) path to root can be determined in a single loop.
*/
const CrwSubDir CrwMap::crwSubDir_[] = {
// dir, parent
{ 0x3004, 0x2807 },
{ 0x300b, 0x300a },
{ 0x3003, 0x300a },
{ 0x3002, 0x300a },
{ 0x2807, 0x300a },
{ 0x2804, 0x300a },
{ 0x300a, 0x0000 },
{ 0x0000, 0xffff },
// End of list marker
{ 0xffff, 0xffff }
};
CrwImage::CrwImage(BasicIo::AutoPtr io, bool create)
: io_(io)
{
if (create) {
IoCloser closer(*io_);
io_->open();
}
} // CrwImage::CrwImage
bool CrwImage::good() const
{
if (io_->open() != 0) return false;
IoCloser closer(*io_);
return isThisType(*io_, false);
}
void CrwImage::clearMetadata()
{
clearExifData();
clearComment();
}
void CrwImage::setMetadata(const Image& image)
{
setExifData(image.exifData());
setComment(image.comment());
}
void CrwImage::clearExifData()
{
exifData_.clear();
}
void CrwImage::setExifData(const ExifData& exifData)
{
exifData_ = exifData;
}
void CrwImage::clearIptcData()
{
throw Error(31, "CrwImage::clearIptcData");
}
void CrwImage::setIptcData(const IptcData& iptcData)
{
throw Error(31, "CrwImage::setIptcData");
}
void CrwImage::clearComment()
{
comment_.erase();
}
void CrwImage::setComment(const std::string& comment)
{
comment_ = comment;
}
void CrwImage::readMetadata()
{
#ifdef DEBUG
std::cerr << "Reading CRW file " << io_->path() << "\n";
#endif
if (io_->open() != 0) {
throw Error(9, io_->path(), strError());
}
IoCloser closer(*io_);
// Ensure that this is the correct image type
if (!isThisType(*io_, false)) {
if (io_->error() || io_->eof()) throw Error(14);
throw Error(33);
}
clearMetadata();
// Read the image into a memory buffer
long len = io_->size();
DataBuf buf(len);
io_->read(buf.pData_, len);
if (io_->error() || io_->eof()) throw Error(14);
CrwParser::decode(this, buf.pData_, buf.size_);
} // CrwImage::readMetadata
void CrwImage::writeMetadata()
{
#ifdef DEBUG
std::cerr << "Writing CRW file " << io_->path() << "\n";
#endif
// Read existing image
DataBuf buf;
if (io_->open() == 0) {
IoCloser closer(*io_);
// Ensure that this is the correct image type
if (isThisType(*io_, false)) {
// Read the image into a memory buffer
buf.alloc(io_->size());
io_->read(buf.pData_, buf.size_);
if (io_->error() || io_->eof()) {
buf.reset();
}
}
}
// Parse image, starting with a CIFF header component
CiffHeader::AutoPtr head(new CiffHeader);
if (buf.size_ != 0) {
head->read(buf.pData_, buf.size_);
}
Blob blob;
CrwParser::encode(blob, head.get(), this);
// Write new buffer to file
BasicIo::AutoPtr tempIo(io_->temporary()); // may throw
assert (tempIo.get() != 0);
tempIo->write(&blob[0], static_cast<long>(blob.size()));
io_->close();
io_->transfer(*tempIo); // may throw
} // CrwImage::writeMetadata
bool CrwImage::isThisType(BasicIo& iIo, bool advance) const
{
return isCrwType(iIo, advance);
}
void CrwParser::decode(CrwImage* pCrwImage, const byte* pData, uint32_t size)
{
assert(pCrwImage != 0);
assert(pData != 0);
// Parse the image, starting with a CIFF header component
CiffHeader::AutoPtr head(new CiffHeader);
head->read(pData, size);
#ifdef DEBUG
head->print(std::cerr);
#endif
head->decode(*pCrwImage);
} // CrwParser::decode
void CrwParser::encode(Blob& blob, CiffHeader* pHead, const CrwImage* pCrwImage)
{
assert(pCrwImage != 0);
assert(pHead != 0);
// Encode Exif tags from image into the Crw parse tree and write the structure
// to the binary image blob
CrwMap::encode(pHead, *pCrwImage);
pHead->write(blob);
} // CrwParser::encode
const char CiffHeader::signature_[] = "HEAPCCDR";
CiffHeader::~CiffHeader()
{
delete pRootDir_;
delete[] pPadding_;
}
CiffComponent::~CiffComponent()
{
if (isAllocated_) delete[] pData_;
}
CiffDirectory::~CiffDirectory()
{
Components::iterator b = components_.begin();
Components::iterator e = components_.end();
for (Components::iterator i = b; i != e; ++i) {
delete *i;
}
}
void CiffComponent::add(AutoPtr component)
{
doAdd(component);
}
void CiffEntry::doAdd(AutoPtr component)
{
throw Error(34, "CiffEntry::add");
} // CiffEntry::doAdd
void CiffDirectory::doAdd(AutoPtr component)
{
components_.push_back(component.release());
} // CiffDirectory::doAdd
void CiffHeader::read(const byte* pData, uint32_t size)
{
if (size < 14) throw Error(33);
if (pData[0] == 0x49 && pData[1] == 0x49) {
byteOrder_ = littleEndian;
}
else if (pData[0] == 0x4d && pData[1] == 0x4d) {
byteOrder_ = bigEndian;
}
else {
throw Error(33);
}
offset_ = getULong(pData + 2, byteOrder_);
if (offset_ < 14 || offset_ > size) throw Error(33);
if (std::memcmp(pData + 6, signature(), 8) != 0) {
throw Error(33);
}
delete pPadding_;
pPadding_ = new byte[offset_ - 14];
padded_ = offset_ - 14;
std::memcpy(pPadding_, pData + 14, padded_);
pRootDir_ = new CiffDirectory;
pRootDir_->readDirectory(pData + offset_, size - offset_, byteOrder_);
} // CiffHeader::read
void CiffComponent::read(const byte* pData,
uint32_t size,
uint32_t start,
ByteOrder byteOrder)
{
doRead(pData, size, start, byteOrder);
}
void CiffComponent::doRead(const byte* pData,
uint32_t size,
uint32_t start,
ByteOrder byteOrder)
{
if (size < 10) throw Error(33);
tag_ = getUShort(pData + start, byteOrder);
DataLocId dl = dataLocation();
assert(dl == directoryData || dl == valueData);
if (dl == valueData) {
size_ = getULong(pData + start + 2, byteOrder);
offset_ = getULong(pData + start + 6, byteOrder);
}
if (dl == directoryData) {
size_ = 8;
offset_ = start + 2;
}
pData_ = pData + offset_;
#ifdef DEBUG
std::cout << " Entry for tag 0x"
<< std::hex << tagId() << " (0x" << tag()
<< "), " << std::dec << size_
<< " Bytes, Offset is " << offset_ << "\n";
#endif
} // CiffComponent::doRead
void CiffDirectory::doRead(const byte* pData,
uint32_t size,
uint32_t start,
ByteOrder byteOrder)
{
CiffComponent::doRead(pData, size, start, byteOrder);
#ifdef DEBUG
std::cout << "Reading directory 0x" << std::hex << tag() << "\n";
#endif
readDirectory(pData + offset(), this->size(), byteOrder);
#ifdef DEBUG
std::cout << "<---- 0x" << std::hex << tag() << "\n";
#endif
} // CiffDirectory::doRead
void CiffDirectory::readDirectory(const byte* pData,
uint32_t size,
ByteOrder byteOrder)
{
uint32_t o = getULong(pData + size - 4, byteOrder);
if (o + 2 > size) throw Error(33);
uint16_t count = getUShort(pData + o, byteOrder);
#ifdef DEBUG
std::cout << "Directory at offset " << std::dec << o
<<", " << count << " entries \n";
#endif
o += 2;
for (uint16_t i = 0; i < count; ++i) {
if (o + 10 > size) throw Error(33);
uint16_t tag = getUShort(pData + o, byteOrder);
CiffComponent::AutoPtr m;
switch (CiffComponent::typeId(tag)) {
case directory: m = CiffComponent::AutoPtr(new CiffDirectory); break;
default: m = CiffComponent::AutoPtr(new CiffEntry); break;
}
m->setDir(this->tag());
m->read(pData, size, o, byteOrder);
add(m);
o += 10;
}
} // CiffDirectory::readDirectory
void CiffHeader::decode(Image& image) const
{
// Nothing to decode from the header itself, just add correct byte order
if (pRootDir_) pRootDir_->decode(image, byteOrder_);
} // CiffHeader::decode
void CiffComponent::decode(Image& image, ByteOrder byteOrder) const
{
doDecode(image, byteOrder);
}
void CiffEntry::doDecode(Image& image, ByteOrder byteOrder) const
{
CrwMap::decode(*this, image, byteOrder);
} // CiffEntry::doDecode
void CiffDirectory::doDecode(Image& image, ByteOrder byteOrder) const
{
Components::const_iterator b = components_.begin();
Components::const_iterator e = components_.end();
for (Components::const_iterator i = b; i != e; ++i) {
(*i)->decode(image, byteOrder);
}
} // CiffDirectory::doDecode
void CiffHeader::write(Blob& blob) const
{
assert( byteOrder_ == littleEndian
|| byteOrder_ == bigEndian);
if (byteOrder_ == littleEndian) {
blob.push_back(0x49);
blob.push_back(0x49);
}
else {
blob.push_back(0x4d);
blob.push_back(0x4d);
}
uint32_t o = 2;
byte buf[4];
ul2Data(buf, offset_, byteOrder_);
append(blob, buf, 4);
o += 4;
append(blob, reinterpret_cast<const byte*>(signature_), 8);
o += 8;
// Pad as needed
if (pPadding_) {
assert(padded_ == offset_ - o);
append(blob, pPadding_, padded_);
}
else {
for (uint32_t i = o; i < offset_; ++i) {
blob.push_back(0);
++o;
}
}
if (pRootDir_) {
pRootDir_->write(blob, byteOrder_, offset_);
}
}
uint32_t CiffComponent::write(Blob& blob,
ByteOrder byteOrder,
uint32_t offset)
{
return doWrite(blob, byteOrder, offset);
}
uint32_t CiffEntry::doWrite(Blob& blob,
ByteOrder /*byteOrder*/,
uint32_t offset)
{
return writeValueData(blob, offset);
} // CiffEntry::doWrite
uint32_t CiffComponent::writeValueData(Blob& blob, uint32_t offset)
{
if (dataLocation() == valueData) {
#ifdef DEBUG
std::cout << " Data for tag 0x" << std::hex << tagId()
<< ", " << std::dec << size_ << " Bytes\n";
#endif
offset_ = offset;
append(blob, pData_, size_);
offset += size_;
// Pad the value to an even number of bytes
if (size_ % 2 == 1) {
blob.push_back(0);
++offset;
}
}
return offset;
} // CiffComponent::writeValueData
uint32_t CiffDirectory::doWrite(Blob& blob,
ByteOrder byteOrder,
uint32_t offset)
{
#ifdef DEBUG
std::cout << "Writing directory 0x" << std::hex << tag() << "---->\n";
#endif
// Ciff offsets are relative to the start of the directory
uint32_t dirOffset = 0;
// Value data
const Components::iterator b = components_.begin();
const Components::iterator e = components_.end();
for (Components::iterator i = b; i != e; ++i) {
dirOffset = (*i)->write(blob, byteOrder, dirOffset);
}
const uint32_t dirStart = dirOffset;
// Number of directory entries
byte buf[4];
us2Data(buf, static_cast<uint16_t>(components_.size()), byteOrder);
append(blob, buf, 2);
dirOffset += 2;
// Directory entries
for (Components::iterator i = b; i != e; ++i) {
(*i)->writeDirEntry(blob, byteOrder);
dirOffset += 10;
}
// Offset of directory
ul2Data(buf, dirStart, byteOrder);
append(blob, buf, 4);
dirOffset += 4;
// Update directory entry
setOffset(offset);
setSize(dirOffset);
#ifdef DEBUG
std::cout << "Directory is at offset " << std::dec << dirStart
<< ", " << components_.size() << " entries\n"
<< "<---- 0x" << std::hex << tag() << "\n";
#endif
return offset + dirOffset;
} // CiffDirectory::doWrite
void CiffComponent::writeDirEntry(Blob& blob, ByteOrder byteOrder) const
{
#ifdef DEBUG
std::cout << " Directory entry for tag 0x"
<< std::hex << tagId() << " (0x" << tag()
<< "), " << std::dec << size_
<< " Bytes, Offset is " << offset_ << "\n";
#endif
byte buf[4];
DataLocId dl = dataLocation();
assert(dl == directoryData || dl == valueData);
if (dl == valueData) {
us2Data(buf, tag_, byteOrder);
append(blob, buf, 2);
ul2Data(buf, size_, byteOrder);
append(blob, buf, 4);
ul2Data(buf, offset_, byteOrder);
append(blob, buf, 4);
}
if (dl == directoryData) {
// Only 8 bytes fit in the directory entry
assert(size_ <= 8);
us2Data(buf, tag_, byteOrder);
append(blob, buf, 2);
// Copy value instead of size and offset
append(blob, pData_, size_);
// Pad with 0s
for (uint32_t i = size_; i < 8; ++i) {
blob.push_back(0);
}
}
} // CiffComponent::writeDirEntry
void CiffHeader::print(std::ostream& os, const std::string& prefix) const
{
os << prefix
<< "Header, offset = 0x" << std::setw(8) << std::setfill('0')
<< std::hex << std::right << offset_ << "\n";
if (pRootDir_) pRootDir_->print(os, byteOrder_, prefix);
} // CiffHeader::print
void CiffComponent::print(std::ostream& os,
ByteOrder byteOrder,
const std::string& prefix) const
{
doPrint(os, byteOrder, prefix);
}
void CiffComponent::doPrint(std::ostream& os,
ByteOrder byteOrder,
const std::string& prefix) const
{
os << prefix
<< "tag = 0x" << std::setw(4) << std::setfill('0')
<< std::hex << std::right << tagId()
<< ", dir = 0x" << std::setw(4) << std::setfill('0')
<< std::hex << std::right << dir()
<< ", type = " << TypeInfo::typeName(typeId())
<< ", size = " << std::dec << size_
<< ", offset = " << offset_ << "\n";
Value::AutoPtr value;
if (typeId() != directory) {
value = Value::create(typeId());
value->read(pData_, size_, byteOrder);
if (value->size() < 100) {
os << prefix << *value << "\n";
}
}
} // CiffComponent::doPrint
void CiffDirectory::doPrint(std::ostream& os,
ByteOrder byteOrder,
const std::string& prefix) const
{
CiffComponent::doPrint(os, byteOrder, prefix);
Components::const_iterator b = components_.begin();
Components::const_iterator e = components_.end();
for (Components::const_iterator i = b; i != e; ++i) {
(*i)->print(os, byteOrder, prefix + " ");
}
} // CiffDirectory::doPrint
void CiffComponent::setValue(DataBuf buf)
{
if (isAllocated_) {
delete pData_;
pData_ = 0;
size_ = 0;
}
isAllocated_ = true;
std::pair<byte *, long> p = buf.release();
pData_ = p.first;
size_ = p.second;
if (size_ > 8 && dataLocation() == directoryData) {
tag_ &= 0x3fff;
}
} // CiffComponent::setValue
TypeId CiffComponent::typeId(uint16_t tag)
{
TypeId ti = invalidTypeId;
switch (tag & 0x3800) {
case 0x0000: ti = unsignedByte; break;
case 0x0800: ti = asciiString; break;
case 0x1000: ti = unsignedShort; break;
case 0x1800: ti = unsignedLong; break;
case 0x2000: ti = undefined; break;
case 0x2800: // fallthrough
case 0x3000: ti = directory; break;
}
return ti;
} // CiffComponent::typeId
DataLocId CiffComponent::dataLocation(uint16_t tag)
{
DataLocId di = invalidDataLocId;
switch (tag & 0xc000) {
case 0x0000: di = valueData; break;
case 0x4000: di = directoryData; break;
}
return di;
} // CiffComponent::dataLocation
/*!
@brief Finds \em crwTagId in directory \em crwDir, returning a pointer to
the component or 0 if not found.
*/
CiffComponent* CiffHeader::findComponent(uint16_t crwTagId,
uint16_t crwDir) const
{
if (pRootDir_ == 0) return 0;
return pRootDir_->findComponent(crwTagId, crwDir);
} // CiffHeader::findComponent
CiffComponent* CiffComponent::findComponent(uint16_t crwTagId,
uint16_t crwDir) const
{
return doFindComponent(crwTagId, crwDir);
} // CiffComponent::findComponent
CiffComponent* CiffComponent::doFindComponent(uint16_t crwTagId,
uint16_t crwDir) const
{
if (tagId() == crwTagId && dir() == crwDir) {
return const_cast<CiffComponent*>(this);
}
return 0;
} // CiffComponent::doFindComponent
CiffComponent* CiffDirectory::doFindComponent(uint16_t crwTagId,
uint16_t crwDir) const
{
CiffComponent* cc = 0;
const Components::const_iterator b = components_.begin();
const Components::const_iterator e = components_.end();
for (Components::const_iterator i = b; i != e; ++i) {
cc = (*i)->findComponent(crwTagId, crwDir);
if (cc) return cc;
}
return 0;
} // CiffDirectory::doFindComponent
void CiffHeader::add(uint16_t crwTagId, uint16_t crwDir, DataBuf buf)
{
CrwDirs crwDirs;
CrwMap::loadStack(crwDirs, crwDir);
uint16_t rootDirectory = crwDirs.top().crwDir_;
assert(rootDirectory == 0x0000);
crwDirs.pop();
if (!pRootDir_) pRootDir_ = new CiffDirectory;
CiffComponent* cc = pRootDir_->add(crwDirs, crwTagId);
cc->setValue(buf);
} // CiffHeader::add
CiffComponent* CiffComponent::add(CrwDirs& crwDirs, uint16_t crwTagId)
{
return doAdd(crwDirs, crwTagId);
} // CiffComponent::add
CiffComponent* CiffComponent::doAdd(CrwDirs& crwDirs, uint16_t crwTagId)
{
return 0;
} // CiffComponent::doAdd
CiffComponent* CiffDirectory::doAdd(CrwDirs& crwDirs, uint16_t crwTagId)
{
/*
add()
if stack not empty
pop from stack
find dir among components
if not found, create it
add()
else
find tag among components
if not found, create it
set value
*/
CiffComponent* cc = 0;
const Components::iterator b = components_.begin();
const Components::iterator e = components_.end();
if (!crwDirs.empty()) {
CrwSubDir csd = crwDirs.top();
crwDirs.pop();
// Find the directory
for (Components::iterator i = b; i != e; ++i) {
if ((*i)->tag() == csd.crwDir_) {
cc = *i;
break;
}
}
if (cc == 0) {
// Directory doesn't exist yet, add it
AutoPtr m(new CiffDirectory(csd.crwDir_, csd.parent_));
cc = m.get();
add(m);
}
// Recursive call to next lower level directory
cc = cc->add(crwDirs, crwTagId);
}
else {
// Find the tag
for (Components::iterator i = b; i != e; ++i) {
if ((*i)->tagId() == crwTagId) {
cc = *i;
break;
}
}
if (cc == 0) {
// Tag doesn't exist yet, add it
AutoPtr m(new CiffEntry(crwTagId, tag()));
cc = m.get();
add(m);
}
}
return cc;
} // CiffDirectory::doAdd
void CiffHeader::remove(uint16_t crwTagId, uint16_t crwDir)
{
if (pRootDir_) {
CrwDirs crwDirs;
CrwMap::loadStack(crwDirs, crwDir);
uint16_t rootDirectory = crwDirs.top().crwDir_;
assert(rootDirectory == 0x0000);
crwDirs.pop();
pRootDir_->remove(crwDirs, crwTagId);
}
} // CiffHeader::remove
void CiffComponent::remove(CrwDirs& crwDirs, uint16_t crwTagId)
{
return doRemove(crwDirs, crwTagId);
} // CiffComponent::remove
void CiffComponent::doRemove(CrwDirs& crwDirs, uint16_t crwTagId)
{
// do nothing
} // CiffComponent::doRemove
void CiffDirectory::doRemove(CrwDirs& crwDirs, uint16_t crwTagId)
{
const Components::iterator b = components_.begin();
const Components::iterator e = components_.end();
Components::iterator i;
if (!crwDirs.empty()) {
CrwSubDir csd = crwDirs.top();
crwDirs.pop();
// Find the directory
for (i = b; i != e; ++i) {
if ((*i)->tag() == csd.crwDir_) {
// Recursive call to next lower level directory
(*i)->remove(crwDirs, crwTagId);
if ((*i)->empty()) components_.erase(i);
break;
}
}
}
else {
// Find the tag
for (i = b; i != e; ++i) {
if ((*i)->tagId() == crwTagId) {
// Remove the entry and abort the loop
delete *i;
components_.erase(i);
break;
}
}
}
} // CiffDirectory::doRemove
bool CiffComponent::empty() const
{
return doEmpty();
}
bool CiffComponent::doEmpty() const
{
return size_ == 0;
}
bool CiffDirectory::doEmpty() const
{
return components_.empty();
}
void CrwMap::decode(const CiffComponent& ciffComponent,
Image& image,
ByteOrder byteOrder)
{
const CrwMapping* cmi = crwMapping(ciffComponent.dir(),
ciffComponent.tagId());
if (cmi && cmi->toExif_) {
cmi->toExif_(ciffComponent, cmi, image, byteOrder);
}
} // CrwMap::decode
const CrwMapping* CrwMap::crwMapping(uint16_t crwDir, uint16_t crwTagId)
{
for (int i = 0; crwMapping_[i].ifdId_ != ifdIdNotSet; ++i) {
if ( crwMapping_[i].crwDir_ == crwDir
&& crwMapping_[i].crwTagId_ == crwTagId) {
return &(crwMapping_[i]);
}
}
return 0;
} // CrwMap::crwMapping
void CrwMap::decode0x0805(const CiffComponent& ciffComponent,
const CrwMapping* /*pCrwMapping*/,
Image& image,
ByteOrder /*byteOrder*/)
{
std::string s(reinterpret_cast<const char*>(ciffComponent.pData()));
image.setComment(s);
} // CrwMap::decode0x0805
void CrwMap::decode0x080a(const CiffComponent& ciffComponent,
const CrwMapping* pCrwMapping,
Image& image,
ByteOrder byteOrder)
{
if (ciffComponent.typeId() != asciiString) return;
// Make
ExifKey key1("Exif.Image.Make");
Value::AutoPtr value1 = Value::create(ciffComponent.typeId());
uint32_t i = 0;
for (; i < ciffComponent.size()
&& ciffComponent.pData()[i] != '\0'; ++i) {
// empty
}
value1->read(ciffComponent.pData(), ++i, byteOrder);
image.exifData().add(key1, value1.get());
// Model
ExifKey key2("Exif.Image.Model");
Value::AutoPtr value2 = Value::create(ciffComponent.typeId());
uint32_t j = i;
for (; i < ciffComponent.size()
&& ciffComponent.pData()[i] != '\0'; ++i) {
// empty
}
value2->read(ciffComponent.pData() + j, i - j + 1, byteOrder);
image.exifData().add(key2, value2.get());
} // CrwMap::decode0x080a
void CrwMap::decodeArray(const CiffComponent& ciffComponent,
const CrwMapping* pCrwMapping,
Image& image,
ByteOrder byteOrder)
{
if (ciffComponent.typeId() != unsignedShort) {
return decodeBasic(ciffComponent, pCrwMapping, image, byteOrder);
}
long aperture = 0;
long shutterSpeed = 0;
IfdId ifdId = ifdIdNotSet;
switch (pCrwMapping->tag_) {
case 0x0001: ifdId = canonCs1IfdId; break;
case 0x0004: ifdId = canonCs2IfdId; break;
case 0x000f: ifdId = canonCfIfdId; break;
}
assert(ifdId != ifdIdNotSet);
std::string ifdItem(ExifTags::ifdItem(ifdId));
uint16_t c = 1;
while (uint32_t(c)*2 < ciffComponent.size()) {
uint16_t n = 1;
ExifKey key(c, ifdItem);
UShortValue value;
if (ifdId == canonCs1IfdId && c == 23 && ciffComponent.size() > 50) n = 3;
value.read(ciffComponent.pData() + c*2, n*2, byteOrder);
image.exifData().add(key, &value);
if (ifdId == canonCs2IfdId && c == 21) aperture = value.toLong();
if (ifdId == canonCs2IfdId && c == 22) shutterSpeed = value.toLong();
c += n;
}
if (ifdId == canonCs2IfdId) {
// Exif.Photo.FNumber
float f = fnumber(canonEv(aperture));
// Beware: primitive conversion algorithm
uint32_t den = 1000000;
uint32_t nom = static_cast<uint32_t>(f * den);
uint32_t g = gcd(nom, den);
URational ur(nom/g, den/g);
URationalValue fn;
fn.value_.push_back(ur);
image.exifData().add(ExifKey("Exif.Photo.FNumber"), &fn);
// Exif.Photo.ExposureTime
ur = exposureTime(canonEv(shutterSpeed));
URationalValue et;
et.value_.push_back(ur);
image.exifData().add(ExifKey("Exif.Photo.ExposureTime"), &et);
}
} // CrwMap::decodeArray
void CrwMap::decode0x180e(const CiffComponent& ciffComponent,
const CrwMapping* pCrwMapping,
Image& image,
ByteOrder byteOrder)
{
if (ciffComponent.size() < 8 || ciffComponent.typeId() != unsignedLong) {
return decodeBasic(ciffComponent, pCrwMapping, image, byteOrder);
}
assert(pCrwMapping != 0);
ULongValue v;
v.read(ciffComponent.pData(), 8, byteOrder);
time_t t = v.value_[0];
#ifdef EXV_HAVE_GMTIME_R
struct tm tms;
struct tm* tm = &tms;
tm = gmtime_r(&t, tm);
#else
struct tm* tm = std::gmtime(&t);
#endif
if (tm) {
const size_t m = 20;
char s[m];
std::strftime(s, m, "%Y:%m:%d %H:%M:%S", tm);
ExifKey key(pCrwMapping->tag_, ExifTags::ifdItem(pCrwMapping->ifdId_));
AsciiValue value;
value.read(std::string(s));
image.exifData().add(key, &value);
}
} // CrwMap::decode0x180e
void CrwMap::decode0x1810(const CiffComponent& ciffComponent,
const CrwMapping* pCrwMapping,
Image& image,
ByteOrder byteOrder)
{
if (ciffComponent.typeId() != unsignedLong || ciffComponent.size() < 28) {
return decodeBasic(ciffComponent, pCrwMapping, image, byteOrder);
}
ExifKey key1("Exif.Photo.PixelXDimension");
ULongValue value1;
value1.read(ciffComponent.pData(), 4, byteOrder);
image.exifData().add(key1, &value1);
ExifKey key2("Exif.Photo.PixelYDimension");
ULongValue value2;
value2.read(ciffComponent.pData() + 4, 4, byteOrder);
image.exifData().add(key2, &value2);
} // CrwMap::decode0x1810
void CrwMap::decode0x2008(const CiffComponent& ciffComponent,
const CrwMapping* /*pCrwMapping*/,
Image& image,
ByteOrder /*byteOrder*/)
{
image.exifData().setJpegThumbnail(ciffComponent.pData(),
ciffComponent.size());
} // CrwMap::decode0x2008
void CrwMap::decodeBasic(const CiffComponent& ciffComponent,
const CrwMapping* pCrwMapping,
Image& image,
ByteOrder byteOrder)
{
assert(pCrwMapping != 0);
// create a key and value pair
ExifKey key(pCrwMapping->tag_, ExifTags::ifdItem(pCrwMapping->ifdId_));
Value::AutoPtr value;
if (ciffComponent.typeId() != directory) {
value = Value::create(ciffComponent.typeId());
uint32_t size = 0;
if (pCrwMapping->size_ != 0) {
// size in the mapping table overrides all
size = pCrwMapping->size_;
}
else if (ciffComponent.typeId() == asciiString) {
// determine size from the data, by looking for the first 0
uint32_t i = 0;
for (; i < ciffComponent.size()
&& ciffComponent.pData()[i] != '\0'; ++i) {
// empty
}
size = ++i;
}
else {
// by default, use the size from the directory entry
size = ciffComponent.size();
}
value->read(ciffComponent.pData(), size, byteOrder);
}
// Add metadatum to exif data
image.exifData().add(key, value.get());
} // CrwMap::decodeBasic
void CrwMap::loadStack(CrwDirs& crwDirs, uint16_t crwDir)
{
for (int i = 0; crwSubDir_[i].crwDir_ != 0xffff; ++i) {
if (crwSubDir_[i].crwDir_ == crwDir) {
crwDirs.push(crwSubDir_[i]);
crwDir = crwSubDir_[i].parent_;
}
}
} // CrwMap::loadStack
void CrwMap::encode(CiffHeader* pHead, const Image& image)
{
for (const CrwMapping* cmi = crwMapping_;
cmi->ifdId_ != ifdIdNotSet; ++cmi) {
if (cmi->fromExif_ != 0) {
cmi->fromExif_(image, cmi, pHead);
}
}
} // CrwMap::encode
void CrwMap::encodeBasic(const Image& image,
const CrwMapping* pCrwMapping,
CiffHeader* pHead)
{
assert(pCrwMapping != 0);
assert(pHead != 0);
// Determine the source Exif metadatum
ExifKey ek(pCrwMapping->tag_, ExifTags::ifdItem(pCrwMapping->ifdId_));
ExifData::const_iterator ed = image.exifData().findKey(ek);
// Set the new value or remove the entry
if (ed != image.exifData().end()) {
DataBuf buf(ed->size());
ed->copy(buf.pData_, pHead->byteOrder());
pHead->add(pCrwMapping->crwTagId_, pCrwMapping->crwDir_, buf);
}
else {
pHead->remove(pCrwMapping->crwTagId_, pCrwMapping->crwDir_);
}
} // CrwMap::encodeBasic
void CrwMap::encode0x0805(const Image& image,
const CrwMapping* pCrwMapping,
CiffHeader* pHead)
{
assert(pCrwMapping != 0);
assert(pHead != 0);
std::string comment = image.comment();
CiffComponent* cc = pHead->findComponent(pCrwMapping->crwTagId_,
pCrwMapping->crwDir_);
if (!comment.empty()) {
uint32_t size = static_cast<uint32_t>(comment.size());
if (cc && cc->size() > size) size = cc->size();
DataBuf buf(size);
memset(buf.pData_, 0x0, buf.size_);
memcpy(buf.pData_, comment.data(), comment.size());
pHead->add(pCrwMapping->crwTagId_, pCrwMapping->crwDir_, buf);
}
else {
if (cc) {
// Just delete the value, do not remove the tag
DataBuf buf(cc->size());
memset(buf.pData_, 0x0, buf.size_);
cc->setValue(buf);
}
}
} // CrwMap::encode0x0805
void CrwMap::encode0x080a(const Image& image,
const CrwMapping* pCrwMapping,
CiffHeader* pHead)
{
assert(pCrwMapping != 0);
assert(pHead != 0);
const ExifKey k1("Exif.Image.Make");
const ExifKey k2("Exif.Image.Model");
const ExifData::const_iterator ed1 = image.exifData().findKey(k1);
const ExifData::const_iterator ed2 = image.exifData().findKey(k2);
const ExifData::const_iterator edEnd = image.exifData().end();
long size = 0;
if (ed1 != edEnd) size += ed1->size();
if (ed2 != edEnd) size += ed2->size();
if (size != 0) {
DataBuf buf(size);
if (ed1 != edEnd) ed1->copy(buf.pData_, pHead->byteOrder());
if (ed2 != edEnd) ed2->copy(buf.pData_ + ed1->size(), pHead->byteOrder());
pHead->add(pCrwMapping->crwTagId_, pCrwMapping->crwDir_, buf);
}
else {
pHead->remove(pCrwMapping->crwTagId_, pCrwMapping->crwDir_);
}
} // CrwMap::encode0x080a
void CrwMap::encodeArray(const Image& image,
const CrwMapping* pCrwMapping,
CiffHeader* pHead)
{
assert(pCrwMapping != 0);
assert(pHead != 0);
IfdId ifdId = ifdIdNotSet;
switch (pCrwMapping->tag_) {
case 0x0001: ifdId = canonCs1IfdId; break;
case 0x0004: ifdId = canonCs2IfdId; break;
case 0x000f: ifdId = canonCfIfdId; break;
}
assert(ifdId != ifdIdNotSet);
DataBuf buf = packIfdId(image.exifData(), ifdId, pHead->byteOrder());
if (buf.size_ == 0) {
// Try the undecoded tag
encodeBasic(image, pCrwMapping, pHead);
}
if (buf.size_ > 0) {
// Write the number of shorts to the beginning of buf
us2Data(buf.pData_, static_cast<uint16_t>(buf.size_), pHead->byteOrder());
pHead->add(pCrwMapping->crwTagId_, pCrwMapping->crwDir_, buf);
}
else {
pHead->remove(pCrwMapping->crwTagId_, pCrwMapping->crwDir_);
}
} // CrwMap::encodeArray
void CrwMap::encode0x180e(const Image& image,
const CrwMapping* pCrwMapping,
CiffHeader* pHead)
{
assert(pCrwMapping != 0);
assert(pHead != 0);
time_t t = 0;
const ExifKey key(pCrwMapping->tag_, ExifTags::ifdItem(pCrwMapping->ifdId_));
const ExifData::const_iterator ed = image.exifData().findKey(key);
if (ed != image.exifData().end()) {
struct tm tm;
memset(&tm, 0x0, sizeof(tm));
int rc = exifTime(ed->toString().c_str(), &tm);
if (rc == 0) t = timegm(&tm);
}
if (t != 0) {
DataBuf buf(12);
memset(buf.pData_, 0x0, 12);
ul2Data(buf.pData_, static_cast<uint32_t>(t), pHead->byteOrder());
pHead->add(pCrwMapping->crwTagId_, pCrwMapping->crwDir_, buf);
}
else {
pHead->remove(pCrwMapping->crwTagId_, pCrwMapping->crwDir_);
}
} // CrwMap::encode0x180e
void CrwMap::encode0x1810(const Image& image,
const CrwMapping* pCrwMapping,
CiffHeader* pHead)
{
assert(pCrwMapping != 0);
assert(pHead != 0);
const ExifKey kX("Exif.Photo.PixelXDimension");
const ExifKey kY("Exif.Photo.PixelYDimension");
const ExifData::const_iterator edX = image.exifData().findKey(kX);
const ExifData::const_iterator edY = image.exifData().findKey(kY);
const ExifData::const_iterator edEnd = image.exifData().end();
CiffComponent* cc = pHead->findComponent(pCrwMapping->crwTagId_,
pCrwMapping->crwDir_);
if (edX != edEnd || edY != edEnd) {
uint32_t size = 28;
if (cc && cc->size() > size) size = cc->size();
DataBuf buf(size);
memset(buf.pData_, 0x0, buf.size_);
if (cc) memcpy(buf.pData_ + 8, cc->pData() + 8, cc->size() - 8);
if (edX != edEnd && edX->size() == 4) {
edX->copy(buf.pData_, pHead->byteOrder());
}
if (edY != edEnd && edY->size() == 4) {
edY->copy(buf.pData_ + 4, pHead->byteOrder());
}
pHead->add(pCrwMapping->crwTagId_, pCrwMapping->crwDir_, buf);
}
else {
pHead->remove(pCrwMapping->crwTagId_, pCrwMapping->crwDir_);
}
} // CrwMap::encode0x1810
void CrwMap::encode0x2008(const Image& image,
const CrwMapping* pCrwMapping,
CiffHeader* pHead)
{
assert(pCrwMapping != 0);
assert(pHead != 0);
DataBuf buf = image.exifData().copyThumbnail();
if (buf.size_ != 0) {
pHead->add(pCrwMapping->crwTagId_, pCrwMapping->crwDir_, buf);
}
else {
pHead->remove(pCrwMapping->crwTagId_, pCrwMapping->crwDir_);
}
} // CrwMap::encode0x1810
// *************************************************************************
// free functions
Image::AutoPtr newCrwInstance(BasicIo::AutoPtr io, bool create)
{
Image::AutoPtr image = Image::AutoPtr(new CrwImage(io, create));
if (!image->good()) {
image.reset();
}
return image;
}
bool isCrwType(BasicIo& iIo, bool advance)
{
bool result = true;
byte tmpBuf[14];
iIo.read(tmpBuf, 14);
if (iIo.error() || iIo.eof()) {
return false;
}
if (!( ('I' == tmpBuf[0] && 'I' == tmpBuf[1])
|| ('M' == tmpBuf[0] && 'M' == tmpBuf[1]))) {
result = false;
}
if ( true == result
&& std::memcmp(tmpBuf + 6, CiffHeader::signature(), 8) != 0) {
result = false;
}
if (!advance || !result) iIo.seek(-14, BasicIo::cur);
return result;
}
DataBuf packIfdId(const ExifData& exifData,
IfdId ifdId,
ByteOrder byteOrder)
{
const uint16_t size = 1024;
DataBuf buf(size);
memset(buf.pData_, 0x0, buf.size_);
uint16_t len = 0;
const ExifData::const_iterator b = exifData.begin();
const ExifData::const_iterator e = exifData.end();
for (ExifData::const_iterator i = b; i != e; ++i) {
if (i->ifdId() != ifdId) continue;
const uint16_t s = i->tag()*2 + static_cast<uint16_t>(i->size());
assert(s <= size);
if (len < s) len = s;
i->copy(buf.pData_ + i->tag()*2, byteOrder);
}
// Round the size to make it even.
buf.size_ = len + len%2;
return buf;
}
} // namespace Exiv2