// ***************************************************************** -*- C++ -*- /* * Copyright (C) 2005 Andreas Huggel * * 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: crwimage.cpp Version: $Rev$ Author(s): Andreas Huggel (ahu) 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 #include #include #include #include #include // ***************************************************************************** // class member definitions namespace Exiv2 { // Local functions. These could be static private functions on Image // subclasses but then ImageFactory needs to be made a friend. /*! @brief Create a new CrwImage instance and return an auto-pointer to it. Caller owns the returned object and the auto-pointer ensures that it will be deleted. */ Image::AutoPtr newCrwInstance(BasicIo::AutoPtr io, bool create); //! Check if the file iIo is a CRW image. bool isCrwType(BasicIo& iIo, bool advance); const byte CrwImage::blank_[] = { 0x00 }; CrwImage::CrwImage(BasicIo::AutoPtr io, bool create) : io_(io) { if (create) { initImage(blank_, sizeof(blank_)); } } // CrwImage::CrwImage int CrwImage::initImage(const byte initData[], size_t dataSize) { if (io_->open() != 0) { return 4; } IoCloser closer(*io_); if (static_cast(io_->write(initData, dataSize)) != dataSize) { return 4; } return 0; } // CrwImage::initImage 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 size_t imageSize = io_->size(); DataBuf image(imageSize); io_->read(image.pData_, imageSize); if (io_->error() || io_->eof()) throw Error(14); // Parse the image RawMetadata::AutoPtr parseTree(new CiffHeader); parseTree->read(image.pData_, image.size_, 0, invalidByteOrder); #ifdef DEBUG parseTree->print(std::cerr, invalidByteOrder); #endif parseTree->extract(*this, invalidByteOrder); } // CrwImage::readMetadata void CrwImage::writeMetadata() { // Todo: implement me! } // CrwImage::writeMetadata //! @cond IGNORE CrwImage::CrwRegister::CrwRegister() { ImageFactory::registerImage( Image::crw, newCrwInstance, isCrwType); } //! @endcond bool CrwImage::isThisType(BasicIo& iIo, bool advance) const { return isCrwType(iIo, advance); } void CiffComponent::read(const byte* buf, size_t len, size_t start, ByteOrder byteOrder, long /*shift*/) { if (len < 10) throw Error(33); tag_ = getUShort(buf + start, byteOrder); switch (dataLocation()) { case valueData: size_ = getULong(buf + start + 2, byteOrder); offset_ = getULong(buf + start + 6, byteOrder); break; case directoryData: size_ = 8; offset_ = start + 2; break; case invalidDataLocId: case lastDataLocId: // empty break; } pData_ = buf + offset_; } // CiffComponent::read void CiffComponent::print(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::print 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 void CiffEntry::extract(Image& image, ByteOrder byteOrder) const { CrwMap::extract(*this, image, byteOrder); } // CiffEntry::extract void CiffEntry::add(RawMetadata::AutoPtr component) { throw Error(34, "CiffEntry::add"); } // CiffEntry::add CiffDirectory::~CiffDirectory() { RawMetadata::Components::iterator b = components_.begin(); RawMetadata::Components::iterator e = components_.end(); for (RawMetadata::Components::iterator i = b; i != e; ++i) { delete *i; } } void CiffDirectory::add(RawMetadata::AutoPtr component) { components_.push_back(component.release()); } // CiffDirectory::add void CiffDirectory::read(const byte* buf, size_t len, size_t start, ByteOrder byteOrder, long /*shift*/) { CiffComponent::read(buf, len, start, byteOrder); readDirectory(buf + offset(), size(), 0, byteOrder, 0); } // CiffDirectory::read void CiffDirectory::extract(Image& image, ByteOrder byteOrder) const { RawMetadata::Components::const_iterator b = components_.begin(); RawMetadata::Components::const_iterator e = components_.end(); for (RawMetadata::Components::const_iterator i = b; i != e; ++i) { (*i)->extract(image, byteOrder); } } // CiffDirectory::extract void CiffDirectory::print(std::ostream& os, ByteOrder byteOrder, const std::string& prefix) const { CiffComponent::print(os, byteOrder, prefix); RawMetadata::Components::const_iterator b = components_.begin(); RawMetadata::Components::const_iterator e = components_.end(); for (RawMetadata::Components::const_iterator i = b; i != e; ++i) { (*i)->print(os, byteOrder, prefix + " "); } } // CiffDirectory::print void CiffDirectory::readDirectory(const byte* buf, size_t len, size_t start, ByteOrder byteOrder, long /*shift*/) { uint32_t dataSize = getULong(buf + len - 4, byteOrder); uint32_t o = start + dataSize; if (o + 2 > len) throw Error(33); uint16_t count = getUShort(buf + o, byteOrder); o += 2; for (uint16_t i = 0; i < count; ++i) { if (o + 10 > len) throw Error(33); uint16_t tag = getUShort(buf + o, byteOrder); CiffComponent* p = 0; switch (CiffComponent::typeId(tag)) { case directory: p = new CiffDirectory; break; default: p = new CiffEntry; break; } p->setDir(this->tag()); RawMetadata::AutoPtr m(p); m->read(buf, len, o, byteOrder); add(m); o += 10; } } // CiffDirectory::readDirectory const char CiffHeader::signature_[] = "HEAPCCDR"; CiffHeader::~CiffHeader() { delete rootDirectory_; } void CiffHeader::add(RawMetadata::AutoPtr component) { throw Error(34, "CiffHeader::add"); } // CiffHeader::add void CiffHeader::read(const byte* buf, size_t len, size_t start, ByteOrder byteOrder, long /*shift*/) { if (len < 14) throw Error(33); if (buf[0] == 0x49 && buf[1] == 0x49) { byteOrder_ = littleEndian; } else if (buf[0] == 0x4d && buf[1] == 0x4d) { byteOrder_ = bigEndian; } else { throw Error(33); } offset_ = getULong(buf + 2, byteOrder_); if (std::memcmp(buf + 6, signature_, 8) != 0) { throw Error(33); } rootDirectory_ = new CiffDirectory; rootDirectory_->readDirectory(buf + offset_, len - offset_, 0, byteOrder_); } // CiffHeader::read void CiffHeader::extract(Image& image, ByteOrder byteOrder) const { // Nothing to extract from the header itself, just add correct byte order if (rootDirectory_) rootDirectory_->extract(image, byteOrder_); } // CiffHeader::extract void CiffHeader::print(std::ostream& os, ByteOrder byteOrder, const std::string& prefix) const { os << prefix << "Header, offset = 0x" << std::setw(8) << std::setfill('0') << std::hex << std::right << offset_ << "\n"; if (rootDirectory_) rootDirectory_->print(os, byteOrder_, prefix); } // CiffHeader::print const CrwMapInfo CrwMap::crwMapInfos_[] = { CrwMapInfo(0x0805, 0x300a, 0, 0x9286, exifIfdId, extract0x0805, 0), CrwMapInfo(0x080a, 0x2807, 0, 0x010f, ifd0Id, extract0x080a, 0), CrwMapInfo(0x080a, 0x2807, 0, 0x0110, ifd0Id, 0, 0), CrwMapInfo(0x080b, 0x3004, 0, 0x0007, canonIfdId, extractBasic, 0), CrwMapInfo(0x0810, 0x2807, 0, 0x0009, canonIfdId, extractBasic, 0), CrwMapInfo(0x0815, 0x2804, 0, 0x0006, canonIfdId, extractBasic, 0), CrwMapInfo(0x1029, 0x300b, 0, 0x0002, canonIfdId, extractBasic, 0), CrwMapInfo(0x102a, 0x300b, 0, 0x0004, canonIfdId, extract0x102a, 0), CrwMapInfo(0x102d, 0x300b, 0, 0x0001, canonIfdId, extract0x102d, 0), CrwMapInfo(0x1033, 0x300b, 0, 0x000f, canonIfdId, extractBasic, 0), CrwMapInfo(0x1038, 0x300b, 0, 0x0012, canonIfdId, extractBasic, 0), CrwMapInfo(0x10a9, 0x300b, 0, 0x00a9, canonIfdId, extractBasic, 0), // CrwMapInfo(0x10b4, 0x300b, 0, 0x00b4, canonIfdId, extractBasic, 0), CrwMapInfo(0x10b4, 0x300b, 0, 0xa001, exifIfdId, extractBasic, 0), CrwMapInfo(0x10b5, 0x300b, 0, 0x00b5, canonIfdId, extractBasic, 0), CrwMapInfo(0x10c0, 0x300b, 0, 0x00c0, canonIfdId, extractBasic, 0), CrwMapInfo(0x10c1, 0x300b, 0, 0x00c1, canonIfdId, extractBasic, 0), CrwMapInfo(0x1807, 0x3002, 0, 0x9206, exifIfdId, extractBasic, 0), CrwMapInfo(0x180b, 0x2807, 0, 0x000c, canonIfdId, extractBasic, 0), CrwMapInfo(0x180e, 0x300a, 0, 0x9003, exifIfdId, extract0x180e, 0), CrwMapInfo(0x1810, 0x300a, 0, 0xa002, exifIfdId, extract0x1810, 0), CrwMapInfo(0x1810, 0x300a, 0, 0xa003, exifIfdId, extract0x1810, 0), CrwMapInfo(0x1817, 0x300a, 4, 0x0008, canonIfdId, extractBasic, 0), // CrwMapInfo(0x1818, 0x3002, 0, 0x9204, exifIfdId, extractBasic, 0), CrwMapInfo(0x183b, 0x300b, 0, 0x0015, canonIfdId, extractBasic, 0), CrwMapInfo(0x2008, 0x0000, 0, 0x0201, ifd1Id, extract0x2008, 0), CrwMapInfo(0x2008, 0x0000, 0, 0x0202, ifd1Id, 0, 0), CrwMapInfo(0x2008, 0x0000, 0, 0x0103, ifd1Id, 0, 0), CrwMapInfo(0x0000, 0x0000, 0, 0x0000, ifdIdNotSet, extractBasic, 0) }; // CrwMap::crwMapInfos_[] void CrwMap::extract(const CiffComponent& ciffComponent, Image& image, ByteOrder byteOrder) { const CrwMapInfo* cmi = crwMapInfo(ciffComponent.dir(), ciffComponent.tagId()); if (cmi && cmi->toExif_) { cmi->toExif_(ciffComponent, cmi, image, byteOrder); } } // CrwMap::extract const CrwMapInfo* CrwMap::crwMapInfo(uint16_t dir, uint16_t tagId) { for (int i = 0; crwMapInfos_[i].ifdId_ != ifdIdNotSet; ++i) { if ( crwMapInfos_[i].crwDir_ == dir && crwMapInfos_[i].crwTagId_ == tagId) { return &(crwMapInfos_[i]); } } return 0; } // CrwMap::crwMapInfo void CrwMap::extract0x0805(const CiffComponent& ciffComponent, const CrwMapInfo* crwMapInfo, Image& image, ByteOrder /*byteOrder*/) { std::string s(reinterpret_cast(ciffComponent.pData())); image.setComment(s); } // CrwMap::extract0x0805 void CrwMap::extract0x080a(const CiffComponent& ciffComponent, const CrwMapInfo* crwMapInfo, Image& image, ByteOrder byteOrder) { if (ciffComponent.typeId() != asciiString) { return extractBasic(ciffComponent, crwMapInfo, image, byteOrder); } // 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::extract0x080a void CrwMap::extract0x102a(const CiffComponent& ciffComponent, const CrwMapInfo* crwMapInfo, Image& image, ByteOrder byteOrder) { if (ciffComponent.typeId() != unsignedShort) { return extractBasic(ciffComponent, crwMapInfo, image, byteOrder); } long aperture = 0; long shutterSpeed = 0; std::string ifdItem(ExifTags::ifdItem(canonCs2IfdId)); uint16_t c = 1; while (uint32_t(c)*2 < ciffComponent.size()) { uint16_t n = 1; ExifKey key(c, ifdItem); UShortValue value; value.read(ciffComponent.pData() + c*2, n*2, byteOrder); image.exifData().add(key, &value); if (c == 21) aperture = value.toLong(); if (c == 22) shutterSpeed = value.toLong(); c += n; } // Exif.Photo.FNumber float f = fnumber(canonEv(aperture)); // Beware: primitive conversion algorithm uint32_t den = 1000000; uint32_t nom = static_cast(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::extract0x102a void CrwMap::extract0x102d(const CiffComponent& ciffComponent, const CrwMapInfo* crwMapInfo, Image& image, ByteOrder byteOrder) { if (ciffComponent.typeId() != unsignedShort) { return extractBasic(ciffComponent, crwMapInfo, image, byteOrder); } std::string ifdItem(ExifTags::ifdItem(canonCs1IfdId)); uint16_t c = 1; while (uint32_t(c)*2 < ciffComponent.size()) { uint16_t n = 1; ExifKey key(c, ifdItem); UShortValue value; if (c == 23 && ciffComponent.size() > 50) n = 3; value.read(ciffComponent.pData() + c*2, n*2, byteOrder); image.exifData().add(key, &value); c += n; } } // CrwMap::extract0x102d void CrwMap::extract0x180e(const CiffComponent& ciffComponent, const CrwMapInfo* crwMapInfo, Image& image, ByteOrder byteOrder) { if (ciffComponent.size() < 8 || ciffComponent.typeId() != unsignedLong) { return extractBasic(ciffComponent, crwMapInfo, image, byteOrder); } ULongValue v; v.read(ciffComponent.pData(), 8, byteOrder); time_t t = v.value_[0]; // Todo: use _r version struct tm* tm = std::gmtime(&t); size_t m = 20; char s[m]; std::strftime(s, m, "%Y:%m:%d %T", tm); ExifKey key(crwMapInfo->tag_, ExifTags::ifdItem(crwMapInfo->ifdId_)); AsciiValue value; value.read(std::string(s)); image.exifData().add(key, &value); } // CrwMap::extract0x180e void CrwMap::extract0x1810(const CiffComponent& ciffComponent, const CrwMapInfo* crwMapInfo, Image& image, ByteOrder byteOrder) { if (ciffComponent.typeId() != unsignedLong || ciffComponent.size() < 28) { return extractBasic(ciffComponent, crwMapInfo, 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::extract0x1810 void CrwMap::extract0x2008(const CiffComponent& ciffComponent, const CrwMapInfo* /*crwMapInfo*/, Image& image, ByteOrder /*byteOrder*/) { image.exifData().setJpegThumbnail(ciffComponent.pData(), ciffComponent.size()); } // CrwMap::extract0x2008 void CrwMap::extractBasic(const CiffComponent& ciffComponent, const CrwMapInfo* crwMapInfo, Image& image, ByteOrder byteOrder) { // create a key and value pair ExifKey key(crwMapInfo->tag_, ExifTags::ifdItem(crwMapInfo->ifdId_)); Value::AutoPtr value; if (ciffComponent.typeId() != directory) { value = Value::create(ciffComponent.typeId()); uint32_t size = 0; if (crwMapInfo->size_ != 0) { // size in the mapping table overrides all size = crwMapInfo->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::extractBasic // ************************************************************************* // 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; } } // namespace Exiv2