From 823a84d3d218e5355d73330b05bb189e011eda86 Mon Sep 17 00:00:00 2001 From: Andreas Huggel Date: Fri, 26 May 2006 16:39:11 +0000 Subject: [PATCH] Added support for IPTC data found in an Exif Photoshop IRB tag of a TIFF/RAW image --- src/jpgimage.cpp | 98 +++++++++++++++++++++++++-------------------- src/jpgimage.hpp | 33 +++++++++++++-- src/mrwimage.cpp | 8 ++-- src/mrwimage.hpp | 12 +----- src/tiffimage.cpp | 8 ++-- src/tiffimage.hpp | 12 +----- src/tiffvisitor.cpp | 42 +++++++++++++++++-- src/tiffvisitor.hpp | 2 + 8 files changed, 136 insertions(+), 79 deletions(-) diff --git a/src/jpgimage.cpp b/src/jpgimage.cpp index db9a8c55..62110673 100644 --- a/src/jpgimage.cpp +++ b/src/jpgimage.cpp @@ -48,6 +48,7 @@ EXIV2_RCSID("@(#) $Id$"); // ***************************************************************************** // class member definitions + namespace Exiv2 { const byte JpegBase::sos_ = 0xda; @@ -56,11 +57,8 @@ namespace Exiv2 { const byte JpegBase::app1_ = 0xe1; const byte JpegBase::app13_ = 0xed; const byte JpegBase::com_ = 0xfe; - const uint16_t JpegBase::iptc_ = 0x0404; const char JpegBase::exifId_[] = "Exif\0\0"; const char JpegBase::jfifId_[] = "JFIF\0"; - const char JpegBase::ps3Id_[] = "Photoshop 3.0\0"; - const char JpegBase::bimId_[] = "8BIM"; JpegBase::JpegBase(BasicIo::AutoPtr io, bool create, const byte initData[], long dataSize) @@ -187,7 +185,7 @@ namespace Exiv2 { if (exifData_.load(rawExif.pData_, sizeExifData)) throw Error(36, "Exif"); --search; } - else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) { + else if (marker == app13_ && memcmp(buf.pData_ + 2, Photoshop::ps3Id, 14) == 0) { if (size < 16) throw Error(15); // Read the rest of the APP13 segment // needed if bufMinSize!=16: io_->seek(16-bufRead, BasicIo::cur); @@ -233,7 +231,6 @@ namespace Exiv2 { } } // JpegBase::readMetadata - // Operates on raw data (rather than file streams) to simplify reuse int JpegBase::locateIptcData(const byte *pPsData, long sizePsData, @@ -241,41 +238,8 @@ namespace Exiv2 { uint16_t *const sizeHdr, uint16_t *const sizeIptc) const { - assert(record); - assert(sizeHdr); - assert(sizeIptc); - // Used for error checking - long position = 0; - - // Data should follow Photoshop format, if not exit - while (position <= (sizePsData - 14) && - memcmp(pPsData + position, bimId_, 4)==0) { - const byte *hrd = pPsData + position; - position += 4; - uint16_t type = getUShort(pPsData+ position, bigEndian); - position += 2; - - // Pascal string is padded to have an even size (including size byte) - byte psSize = pPsData[position] + 1; - psSize += (psSize & 1); - position += psSize; - if (position >= sizePsData) return -2; - - // Data is also padded to be even - long dataSize = getULong(pPsData + position, bigEndian); - position += 4; - if (dataSize > sizePsData - position) return -2; - - if (type == iptc_) { - *sizeIptc = static_cast(dataSize); - *sizeHdr = psSize + 10; - *record = hrd; - return 0; - } - position += dataSize + (dataSize & 1); - } - return 3; - } // JpegBase::locateIptcData + return locate8BimData(pPsData, sizePsData, Photoshop::iptc, record, sizeHdr, sizeIptc); + } void JpegBase::writeMetadata() { @@ -341,7 +305,7 @@ namespace Exiv2 { ++search; if (io_->seek(size-bufRead, BasicIo::cur)) throw Error(22); } - else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) { + else if (marker == app13_ && memcmp(buf.pData_ + 2, Photoshop::ps3Id, 14) == 0) { if (size < 16) throw Error(22); skipApp13Ps3 = count; ++search; @@ -439,7 +403,7 @@ namespace Exiv2 { us2Data(tmpBuf + 2, static_cast(psData.size_-sizeOldData+sizeNewData+16), bigEndian); - memcpy(tmpBuf + 4, ps3Id_, 14); + memcpy(tmpBuf + 4, Photoshop::ps3Id, 14); if (outIo.write(tmpBuf, 18) != 18) throw Error(21); if (outIo.error()) throw Error(21); @@ -450,8 +414,8 @@ namespace Exiv2 { // write new iptc record if we have it if (iptcData_.count() > 0) { - memcpy(tmpBuf, bimId_, 4); - us2Data(tmpBuf+4, iptc_, bigEndian); + memcpy(tmpBuf, Photoshop::bimId, 4); + us2Data(tmpBuf+4, Photoshop::iptc, bigEndian); tmpBuf[6] = 0; tmpBuf[7] = 0; ul2Data(tmpBuf + 8, rawIptc.size_, bigEndian); @@ -623,4 +587,50 @@ namespace Exiv2 { return result; } + // Todo: Generalised from JpegBase::locateIptcData without really understanding + // the format (in particular the header). So it remains to be confirmed + // if this also makes sense for psTag != Photoshop::iptc + int locate8BimData(const byte *pPsData, + long sizePsData, + uint16_t psTag, + const byte **record, + uint16_t *const sizeHdr, + uint16_t *const sizeData) + { + assert(record); + assert(sizeHdr); + assert(sizeData); + // Used for error checking + long position = 0; + + // Data should follow Photoshop format, if not exit + while ( position <= sizePsData - 14 + && memcmp(pPsData + position, Photoshop::bimId, 4) == 0) { + const byte *hrd = pPsData + position; + position += 4; + uint16_t type = getUShort(pPsData + position, bigEndian); + position += 2; + + // Pascal string is padded to have an even size (including size byte) + byte psSize = pPsData[position] + 1; + psSize += (psSize & 1); + position += psSize; + if (position >= sizePsData) return -2; + + // Data is also padded to be even + long dataSize = getULong(pPsData + position, bigEndian); + position += 4; + if (dataSize > sizePsData - position) return -2; + + if (type == psTag) { + *sizeData = static_cast(dataSize); + *sizeHdr = psSize + 10; + *record = hrd; + return 0; + } + position += dataSize + (dataSize & 1); + } + return 3; + } // locate8BimData + } // namespace Exiv2 diff --git a/src/jpgimage.hpp b/src/jpgimage.hpp index 89777955..d193292e 100644 --- a/src/jpgimage.hpp +++ b/src/jpgimage.hpp @@ -56,6 +56,13 @@ namespace Exiv2 { const int exv = 2; //!< Exv image type (see class ExvImage) } + //! %Photoshop constants + namespace Photoshop { + const char ps3Id[] = "Photoshop 3.0\0"; //!< %Photoshop marker + const char bimId[] = "8BIM"; //!< %Photoshop marker + const uint16_t iptc = 0x0404; //!< %Photoshop IPTC marker + } + /*! @brief Abstract helper base class to access JPEG images. */ @@ -156,9 +163,6 @@ namespace Exiv2 { static const byte com_; //!< JPEG Comment marker static const char exifId_[]; //!< Exif identifier static const char jfifId_[]; //!< JFIF identifier - static const char ps3Id_[]; //!< Photoshop marker - static const char bimId_[]; //!< Photoshop marker - static const uint16_t iptc_; //!< Photoshop Iptc marker private: // DATA @@ -353,6 +357,29 @@ namespace Exiv2 { Image::AutoPtr newExvInstance(BasicIo::AutoPtr io, bool create); //! Check if the file iIo is an EXV file bool isExvType(BasicIo& iIo, bool advance); + /*! + @brief Locates the data for a Photoshop tag in a Photoshop formated memory + buffer. Operates on raw data to simplify reuse. + @param pPsData Pointer to buffer containing entire payload of + Photoshop formated APP13 Jpeg segment. + @param sizePsData Size in bytes of pPsData. + @param psTag Tag number of the block to look for. + @param record Output value that is set to the start of the + data block within pPsData (may not be null). + @param sizeHdr Output value that is set to the size of the header + within the data block pointed to by record (may not be null). + @param sizeData Output value that is set to the size of the actual + data within the data block pointed to by record (may not be null). + @return 0 if successful;
+ 3 if no data for psTag was found in pPsData;
+ -2 if the pPsData buffer does not contain valid data. + */ + int locate8BimData(const byte *pPsData, + long sizePsData, + uint16_t psTag, + const byte **record, + uint16_t *const sizeHdr, + uint16_t *const sizeData); } // namespace Exiv2 diff --git a/src/mrwimage.cpp b/src/mrwimage.cpp index 8a1d0c69..f5ac395e 100644 --- a/src/mrwimage.cpp +++ b/src/mrwimage.cpp @@ -55,7 +55,7 @@ EXIV2_RCSID("@(#) $Id$"); namespace Exiv2 { MrwImage::MrwImage(BasicIo::AutoPtr io, bool create) - : Image(mdExif), io_(io) + : Image(mdExif | mdIptc), io_(io) { if (create) { IoCloser closer(*io_); @@ -92,12 +92,12 @@ namespace Exiv2 { void MrwImage::clearIptcData() { - // not supported + iptcData_.clear(); } - void MrwImage::setIptcData(const IptcData& /*iptcData*/) + void MrwImage::setIptcData(const IptcData& iptcData) { - // not supported + iptcData_ = iptcData; } void MrwImage::clearComment() diff --git a/src/mrwimage.hpp b/src/mrwimage.hpp index 8a0a331d..10f3b191 100644 --- a/src/mrwimage.hpp +++ b/src/mrwimage.hpp @@ -52,8 +52,8 @@ namespace Exiv2 { } /*! - @brief Class to access raw MRW images. Only Exif metadata is currently - supported. + @brief Class to access raw MRW images. Exif metadata is supported + directly, IPTC is read from the Exif data, if present. */ class MrwImage : public Image { friend bool isMrwType(BasicIo& iIo, bool advance); @@ -99,15 +99,7 @@ namespace Exiv2 { void writeMetadata(); void setExifData(const ExifData& exifData); void clearExifData(); - /*! - @brief Not supported. MRW format does not contain IPTC metadata. - Calling this function will do nothing. - */ void setIptcData(const IptcData& iptcData); - /*! - @brief Not supported. MRW format does not contain IPTC metadata. - Calling this function will do nothing. - */ void clearIptcData(); /*! @brief Not supported. MRW format does not contain a comment. diff --git a/src/tiffimage.cpp b/src/tiffimage.cpp index 2f8dcdee..96c38354 100644 --- a/src/tiffimage.cpp +++ b/src/tiffimage.cpp @@ -54,7 +54,7 @@ EXIV2_RCSID("@(#) $Id$"); namespace Exiv2 { TiffImage::TiffImage(BasicIo::AutoPtr io, bool create) - : Image(mdExif | mdComment), io_(io) + : Image(mdExif | mdIptc | mdComment), io_(io) { if (create) { IoCloser closer(*io_); @@ -93,12 +93,12 @@ namespace Exiv2 { void TiffImage::clearIptcData() { - // not supported + iptcData_.clear(); } - void TiffImage::setIptcData(const IptcData& /*iptcData*/) + void TiffImage::setIptcData(const IptcData& iptcData) { - // not supported + iptcData_ = iptcData; } void TiffImage::clearComment() diff --git a/src/tiffimage.hpp b/src/tiffimage.hpp index 625d9a8e..2b84055f 100644 --- a/src/tiffimage.hpp +++ b/src/tiffimage.hpp @@ -51,8 +51,8 @@ namespace Exiv2 { } /*! - @brief Class to access raw TIFF images. Only Exif metadata and a comment - are supported. TIFF format does not contain IPTC metadata. + @brief Class to access raw TIFF images. Exif metadata and a comment + are supported directly, IPTC is read from the Exif data, if present. */ class TiffImage : public Image { friend bool isTiffType(BasicIo& iIo, bool advance); @@ -98,15 +98,7 @@ namespace Exiv2 { void writeMetadata(); void setExifData(const ExifData& exifData); void clearExifData(); - /*! - @brief Not supported. TIFF format does not contain IPTC metadata. - Calling this function will do nothing. - */ void setIptcData(const IptcData& iptcData); - /*! - @brief Not supported. TIFF format does not contain IPTC metadata. - Calling this function will do nothing. - */ void clearIptcData(); void setComment(const std::string& comment); void clearComment(); diff --git a/src/tiffvisitor.cpp b/src/tiffvisitor.cpp index 54f99318..a69ed2f1 100644 --- a/src/tiffvisitor.cpp +++ b/src/tiffvisitor.cpp @@ -40,8 +40,10 @@ EXIV2_RCSID("@(#) $Id$"); #include "tiffcomposite.hpp" #include "makernote2.hpp" #include "exif.hpp" +#include "iptc.hpp" #include "value.hpp" #include "image.hpp" +#include "jpgimage.hpp" // + standard includes #include @@ -59,7 +61,8 @@ namespace Exiv2 { { "OLYMPUS", 0x0100, Group::olympmn, &TiffMetadataDecoder::decodeOlympThumb }, { "*", 0x014a, Group::ifd0, 0 }, // Todo: Controversial, causes problems with Exiftool { "*", Tag::all, Group::sub0_0, &TiffMetadataDecoder::decodeSubIfd }, - { "*", Tag::all, Group::sub0_1, &TiffMetadataDecoder::decodeSubIfd } + { "*", Tag::all, Group::sub0_1, &TiffMetadataDecoder::decodeSubIfd }, + { "*", 0x8649, Group::ifd0, &TiffMetadataDecoder::decodeIrbIptc } }; bool TiffDecoderInfo::operator==(const TiffDecoderInfo::Key& key) const @@ -194,6 +197,37 @@ namespace Exiv2 { } } + void TiffMetadataDecoder::decodeIrbIptc(const TiffEntryBase* object) + { + assert(object != 0); + assert(pImage_ != 0); + if (!object->pData()) return; + byte const* record = 0; + uint16_t sizeHdr = 0; + uint16_t sizeData = 0; + if (0 != locate8BimData(object->pData(), + object->size(), + Photoshop::iptc, + &record, + &sizeHdr, + &sizeData)) { + return; + } + if (0 != pImage_->iptcData().load(record + sizeHdr, sizeData)) { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: Failed to decode IPTC block found in " + << "Directory " << object->groupName() + << ", entry 0x" << std::setw(4) + << std::setfill('0') << std::hex << object->tag() + << "\n"; +#endif + // Todo: ExifKey should have an appropriate c'tor, it should not be + // necessary to use groupName here + ExifKey key(object->tag(), object->groupName()); + setExifTag(key, object->pValue()); + } + } // TiffMetadataDecoder::decodeIrbIptc + void TiffMetadataDecoder::decodeSubIfd(const TiffEntryBase* object) { assert(object); @@ -396,7 +430,7 @@ namespace Exiv2 { TiffRwState::AutoPtr state) : pData_(pData), size_(size), - pLast_(pData + size - 1), + pLast_(pData + size), pRoot_(pRoot), pState_(state.release()), pOrigState_(pState_) @@ -724,8 +758,8 @@ namespace Exiv2 { if (object->pData() + object->size() > pLast_) { #ifndef SUPPRESS_WARNINGS std::cerr << "Warning: Upper boundary of data for " - << "directory " << object->groupName() << ", " - << " entry 0x" << std::setw(4) + << "directory " << object->groupName() + << ", entry 0x" << std::setw(4) << std::setfill('0') << std::hex << object->tag() << " is out of bounds:\n" << "Offset = 0x" << std::setw(8) diff --git a/src/tiffvisitor.hpp b/src/tiffvisitor.hpp index 13e3ba36..df33a57f 100644 --- a/src/tiffvisitor.hpp +++ b/src/tiffvisitor.hpp @@ -282,6 +282,8 @@ namespace Exiv2 { void decodeOlympThumb(const TiffEntryBase* object); //! Decode SubIFD contents to Image group if it contains primary image data void decodeSubIfd(const TiffEntryBase* object); + //! Decode IPTC data from a Photoshop IRB tag + void decodeIrbIptc(const TiffEntryBase* object); //@} private: