diff options
author | Hubert Figuière <hub@figuiere.net> | 2016-07-25 22:37:15 -0400 |
---|---|---|
committer | Hubert Figuière <hub@figuiere.net> | 2016-07-25 22:37:15 -0400 |
commit | 92d410d2f44ea4fadb9e99f0ff54467b07a9c88d (patch) | |
tree | fa4c6f985b753503cd88f5de071dd74b7e92e61d | |
parent | 59f14131bd67ab590b22b3205f715a51423f1e7c (diff) | |
parent | 33a432bab8cb34f553e6be1da6d66839f7987448 (diff) |
Merge branch 'webp-handler'
This bring the webp file handler in.
From https://github.com/hfiguiere/exempi/pull/2
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | XMPFiles/source/FileHandlers/Makefile.am | 3 | ||||
-rw-r--r-- | XMPFiles/source/FileHandlers/WEBP_Handler.cpp | 197 | ||||
-rw-r--r-- | XMPFiles/source/FileHandlers/WEBP_Handler.hpp | 48 | ||||
-rw-r--r-- | XMPFiles/source/FormatSupport/Makefile.am | 1 | ||||
-rw-r--r-- | XMPFiles/source/FormatSupport/WEBP_Support.cpp | 273 | ||||
-rw-r--r-- | XMPFiles/source/FormatSupport/WEBP_Support.hpp | 174 | ||||
-rw-r--r-- | XMPFiles/source/HandlerRegistry.cpp | 2 | ||||
-rw-r--r-- | XMPFiles/source/XMPFiles_Impl.cpp | 1 | ||||
-rw-r--r-- | exempi/tests/Makefile.am | 8 | ||||
-rw-r--r-- | exempi/tests/test-webp.cpp | 119 | ||||
-rw-r--r-- | exempi/xmp.h | 1 | ||||
-rw-r--r-- | public/include/XMP_Const.h | 2 | ||||
-rw-r--r-- | samples/testfiles/BlueSquare.webp | bin | 0 -> 4974 bytes | |||
-rw-r--r-- | samples/testfiles/Makefile.am | 2 |
16 files changed, 830 insertions, 4 deletions
@@ -33,6 +33,7 @@ test-driver exempi/exempi-2.0.pc exempi/exempi exempi/tests/test.jpg +exempi/tests/test.webp exempi/tests/testexempicore exempi/tests/testserialise exempi/tests/testwritenewprop @@ -45,6 +46,7 @@ exempi/tests/testinit exempi/tests/testfdo18635 exempi/tests/testfdo83313 exempi/tests/testcpp +exempi/tests/testwebp exempi/tests/test*.log exempi/tests/test*.trs exempi/doc/Doxyfile @@ -14,6 +14,7 @@ - Lot of bug fixes. - New API: added XMP_OPEN_OPTIMIZEFILELAYOUT for new SDK. - Now require (partial) C++11 support to compile (gcc 4.4.7 tested) +- New: WebP format handler (contributed: Frankie Dintino, The Atlantic) 2.3.0 - 2016/03/15 diff --git a/XMPFiles/source/FileHandlers/Makefile.am b/XMPFiles/source/FileHandlers/Makefile.am index ea2ec41..1198856 100644 --- a/XMPFiles/source/FileHandlers/Makefile.am +++ b/XMPFiles/source/FileHandlers/Makefile.am @@ -67,6 +67,7 @@ UCF_Handler.cpp UCF_Handler.hpp\ XDCAMEX_Handler.cpp XDCAMEX_Handler.hpp\ XDCAM_Handler.cpp XDCAM_Handler.hpp\ WAVE_Handler.cpp WAVE_Handler.hpp \ -GIF_Handler.cpp GIF_Handler.hpp +GIF_Handler.cpp GIF_Handler.hpp \ +WEBP_Handler.cpp WEB_Handler.hpp $(NULL) diff --git a/XMPFiles/source/FileHandlers/WEBP_Handler.cpp b/XMPFiles/source/FileHandlers/WEBP_Handler.cpp new file mode 100644 index 0000000..c0b1f09 --- /dev/null +++ b/XMPFiles/source/FileHandlers/WEBP_Handler.cpp @@ -0,0 +1,197 @@ +#include "public/include/XMP_Const.h" +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "XMPFiles/source/FileHandlers/WEBP_Handler.hpp" + +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" +#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/WEBP_Support.hpp" +#include "source/XIO.hpp" + +using namespace std; + +/// File format handler for WEBP. + +XMPFileHandler* WEBP_MetaHandlerCTor(XMPFiles* parent) +{ + return new WEBP_MetaHandler(parent); +} + +// Check that the file begins with "RIFF", a 4 byte length, then the chunkType +// (WEBP). +bool WEBP_CheckFormat(XMP_FileFormat format, XMP_StringPtr filePath, + XMP_IO* file, XMPFiles* parent) +{ + IgnoreParam(format); + IgnoreParam(parent); + XMP_Assert(format == kXMP_WEBPFile); + + if (file->Length() < 12) + return false; + file->Rewind(); + + XMP_Uns8 chunkID[12]; + file->ReadAll(chunkID, 12); + if (!CheckBytes(&chunkID[0], "RIFF", 4)) { + return false; + } + if (CheckBytes(&chunkID[8], "WEBP", 4) && format == kXMP_WEBPFile) { + return true; + } + return false; +} + +WEBP_MetaHandler::WEBP_MetaHandler(XMPFiles* parent) + : exifMgr(0) +{ + this->parent = parent; + this->handlerFlags = kWEBP_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + this->initialFileSize = 0; + this->mainChunk = 0; +} + +WEBP_MetaHandler::~WEBP_MetaHandler() +{ + if (this->mainChunk) { + delete this->mainChunk; + } + if (this->exifMgr) { + delete this->exifMgr; + } + if (this->iptcMgr) { + delete this->iptcMgr; + } + if (this->psirMgr) { + delete this->psirMgr; + } +} + +void WEBP_MetaHandler::CacheFileData() +{ + this->containsXMP = false; // assume for now + + XMP_IO* file = this->parent->ioRef; + this->initialFileSize = file->Length(); + + file->Rewind(); + + XMP_Int64 filePos = 0; + while (filePos < this->initialFileSize) { + this->mainChunk = new WEBP::Container(this); + filePos = file->Offset(); + } + + // covered before => internal error if it occurs + XMP_Validate(file->Offset() == this->initialFileSize, + "WEBP_MetaHandler::CacheFileData: unknown data at end of file", + kXMPErr_InternalFailure); +} + +void WEBP_MetaHandler::ProcessXMP() +{ + SXMPUtils::RemoveProperties(&this->xmpObj, 0, 0, kXMPUtil_DoAllProperties); + + bool readOnly = false; + bool xmpOnly = false; + bool haveExif = false; + if (this->parent) { + readOnly = + !XMP_OptionIsSet(this->parent->openFlags, kXMPFiles_OpenForUpdate); + xmpOnly = + XMP_OptionIsSet(this->parent->openFlags, kXMPFiles_OpenOnlyXMP); + } + if (!xmpOnly) { + if (readOnly) { + this->exifMgr = new TIFF_MemoryReader(); + } + else { + this->exifMgr = new TIFF_FileWriter(); + } + this->psirMgr = new PSIR_MemoryReader(); + this->iptcMgr = new IPTC_Reader(); + if (this->parent) { + exifMgr->SetErrorCallback(&this->parent->errorCallback); + } + if (this->mainChunk) { + WEBP::Chunk* exifChunk = this->mainChunk->getExifChunk(); + if (exifChunk != NULL) { + haveExif = true; + this->exifMgr->ParseMemoryStream(exifChunk->data.data() + 6, + exifChunk->data.size() - 6); + } + } + } + + if (this->containsXMP) { + this->xmpObj.ParseFromBuffer(this->xmpPacket.c_str(), + (XMP_StringLen) this->xmpPacket.size()); + } + if (haveExif) { + XMP_OptionBits options = k2XMP_FileHadExif; + if (this->containsXMP) { + options |= k2XMP_FileHadXMP; + } + TIFF_Manager& exif = *this->exifMgr; + PSIR_Manager& psir = *this->psirMgr; + IPTC_Manager& iptc = *this->iptcMgr; + ImportPhotoData(exif, iptc, psir, kDigestMatches, &this->xmpObj, + options); + // Assume that, since the file had EXIF data, some of it was mapped to + // XMP + this->containsXMP = true; + } + this->processedXMP = true; +} + +void WEBP_MetaHandler::UpdateFile(bool doSafeUpdate) +{ + XMP_Validate(this->needsUpdate, "nothing to update", + kXMPErr_InternalFailure); + + bool xmpOnly = false; + if (this->parent) { + xmpOnly = + XMP_OptionIsSet(this->parent->openFlags, kXMPFiles_OpenOnlyXMP); + } + + if (!xmpOnly && this->exifMgr) { + WEBP::Chunk* exifChunk = this->mainChunk->getExifChunk(); + if (exifChunk != NULL) { + ExportPhotoData(kXMP_TIFFFile, &this->xmpObj, this->exifMgr, 0, 0); + if (this->exifMgr->IsLegacyChanged()) { + XMP_Uns8* exifPtr; + XMP_Uns32 exifLen = + this->exifMgr->UpdateMemoryStream((void**)&exifPtr); + RawDataBlock exifData(&exifChunk->data[0], &exifChunk->data[6]); + exifData.insert(exifData.begin() + 6, &exifPtr[0], + &exifPtr[exifLen]); + exifChunk->data = exifData; + exifChunk->size = exifLen + 6; + exifChunk->needsRewrite = true; + } + } + } + + this->packetInfo.charForm = stdCharForm; + this->packetInfo.writeable = true; + this->packetInfo.offset = kXMPFiles_UnknownOffset; + this->packetInfo.length = kXMPFiles_UnknownLength; + + this->xmpObj.SerializeToBuffer(&this->xmpPacket, kXMP_OmitPacketWrapper); + + this->mainChunk->write(this); + this->needsUpdate = false; // do last for safety +} + +void WEBP_MetaHandler::WriteTempFile(XMP_IO* tempRef) +{ + IgnoreParam(tempRef); + XMP_Throw("WEBP_MetaHandler::WriteTempFile: Not supported (must go through " + "UpdateFile)", + kXMPErr_Unavailable); +} diff --git a/XMPFiles/source/FileHandlers/WEBP_Handler.hpp b/XMPFiles/source/FileHandlers/WEBP_Handler.hpp new file mode 100644 index 0000000..c5041ea --- /dev/null +++ b/XMPFiles/source/FileHandlers/WEBP_Handler.hpp @@ -0,0 +1,48 @@ +#ifndef __WEBP_Handler_hpp__ +#define __WEBP_Handler_hpp__ 1 + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_Environment.h" + +#include "XMPFiles/source/FormatSupport/WEBP_Support.hpp" +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" + +#include "source/XIO.hpp" + +// File format handler for WEBP + +extern XMPFileHandler* WEBP_MetaHandlerCTor(XMPFiles* parent); + +extern bool WEBP_CheckFormat(XMP_FileFormat format, XMP_StringPtr filePath, + XMP_IO* fileRef, XMPFiles* parent); + +static const XMP_OptionBits kWEBP_HandlerFlags = + (kXMPFiles_CanInjectXMP | kXMPFiles_CanExpand | kXMPFiles_PrefersInPlace | + kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | + kXMPFiles_CanReconcile); + +class WEBP_MetaHandler + : public XMPFileHandler { +public: + WEBP_MetaHandler(XMPFiles* parent); + ~WEBP_MetaHandler(); + + void CacheFileData(); + void ProcessXMP(); + void UpdateFile(bool doSafeUpdate); + void WriteTempFile(XMP_IO* tempRef); + + WEBP::Container* mainChunk; + WEBP::XMPChunk* xmpChunk; + XMP_Int64 initialFileSize; + TIFF_Manager* exifMgr; + // The PSIR_Manager and IPTC_Manager aren't actually used, but they need + // to be instantiated and passed to the function that reconciles EXIF and + // XMP data. + PSIR_Manager* psirMgr; + IPTC_Manager* iptcMgr; +}; + +#endif /* __WEBP_Handler_hpp__ */ diff --git a/XMPFiles/source/FormatSupport/Makefile.am b/XMPFiles/source/FormatSupport/Makefile.am index c090716..8916332 100644 --- a/XMPFiles/source/FormatSupport/Makefile.am +++ b/XMPFiles/source/FormatSupport/Makefile.am @@ -76,6 +76,7 @@ libformatsupport_la_SOURCES = \ WAVE/CartMetadata.cpp WAVE/Cr8rMetadata.h WAVE/INFOMetadata.cpp WAVE/PrmLMetadata.h WAVE/WAVEReconcile.cpp \ WAVE/iXMLMetadata.h WAVE/iXMLMetadata.cpp \ P2_Support.hpp P2_Support.cpp \ + WEBP_Support.hpp WEBP_Support.cpp \ $(NULL) diff --git a/XMPFiles/source/FormatSupport/WEBP_Support.cpp b/XMPFiles/source/FormatSupport/WEBP_Support.cpp new file mode 100644 index 0000000..2b007de --- /dev/null +++ b/XMPFiles/source/FormatSupport/WEBP_Support.cpp @@ -0,0 +1,273 @@ +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XIO.hpp" +#include "source/XMPFiles_IO.hpp" + +#include "XMPFiles/source/FileHandlers/WEBP_Handler.hpp" +#include "XMPFiles/source/FormatSupport/WEBP_Support.hpp" + +#include <stdexcept> // std::out_of_range +#include <vector> + +using namespace WEBP; + +namespace WEBP { + +// Constructor for reading an existing chunk +Chunk::Chunk(Container* parent, WEBP_MetaHandler* handler) +{ + this->needsRewrite = (parent) ? parent->needsRewrite : false; + this->parent = parent; + + XMP_IO* file = handler->parent->ioRef; + this->pos = file->Offset(); + + this->tag = XIO::ReadUns32_LE(file); + + this->size = XIO::ReadUns32_LE(file); + + // Make sure the size is within expected bounds. + if ((this->pos + this->size + 8) > handler->initialFileSize) { + XMP_Throw("Bad RIFF chunk size", kXMPErr_BadFileFormat); + } + + this->data.reserve((XMP_Int32) this->size); + this->data.assign((XMP_Int32) this->size, '\0'); + file->ReadAll((void*)this->data.data(), (XMP_Int32) this->size); + + // Account for padding + if (this->size & 1) { + file->Seek(1, kXMP_SeekFromCurrent); + } +} + +// Constructor for creating a new chunk +Chunk::Chunk(Container* parent, XMP_Uns32 tag) + : parent(parent), tag(tag) +{ + this->needsRewrite = true; +} + +void Chunk::write(WEBP_MetaHandler* handler) +{ + XMP_IO* file = handler->parent->ioRef; + if (this->needsRewrite) { + this->pos = file->Offset(); + XIO::WriteUns32_LE(file, this->tag); + XIO::WriteUns32_LE(file, (XMP_Uns32) this->size); + file->Write(this->data.data(), (XMP_Int32) this->size); + } + else { + file->Seek(this->pos + this->size + 8, kXMP_SeekFromStart); + } + if (this->size & 1) { + const XMP_Uns8 zero = 0; + file->Write(&zero, 1); + } +} + +Chunk::~Chunk() +{ + // Do nothing +} + +XMPChunk::XMPChunk(Container* parent) + : Chunk(parent, kChunk_XMP_) +{ + this->size = 0; +} + +XMPChunk::XMPChunk(Container* parent, WEBP_MetaHandler* handler) + : Chunk(parent, handler) +{ + handler->packetInfo.offset = this->pos + 8; + handler->packetInfo.length = (XMP_Int32) this->data.size(); + + handler->xmpPacket.reserve(handler->packetInfo.length); + handler->xmpPacket.assign(handler->packetInfo.length, '\0'); + handler->xmpPacket.insert(handler->xmpPacket.begin(), this->data.begin(), + this->data.end()); + + handler->containsXMP = true; // last, after all possible failure + + // pointer for later processing + handler->xmpChunk = this; +} + +void XMPChunk::write(WEBP_MetaHandler* handler) +{ + XMP_IO* file = handler->parent->ioRef; + this->size = handler->xmpPacket.size(); + XIO::WriteUns32_LE(file, this->tag); + XIO::WriteUns32_LE(file, (XMP_Uns32) this->size); + file->Write(handler->xmpPacket.data(), (XMP_Int32) this->size); + if (this->size & 1) { + const XMP_Uns8 zero = 0; + file->Write(&zero, 1); + } +} + +VP8XChunk::VP8XChunk(Container* parent) + : Chunk(parent, kChunk_VP8X) +{ + this->needsRewrite = true; + this->size = 10; + this->data.resize(this->size); + this->data.assign(this->size, 0); + XMP_Uns8* bitstream = + (XMP_Uns8*)parent->chunks[WEBP_CHUNK_IMAGE][0]->data.data(); + XMP_Uns32 width = ((bitstream[7] << 8) | bitstream[6]) & 0x3fff; + XMP_Uns32 height = ((bitstream[9] << 8) | bitstream[8]) & 0x3fff; + this->width(width); + this->height(height); + parent->vp8x = this; +} + +VP8XChunk::VP8XChunk(Container* parent, WEBP_MetaHandler* handler) + : Chunk(parent, handler) +{ + this->size = 10; + this->needsRewrite = true; + parent->vp8x = this; +} + +XMP_Uns32 VP8XChunk::width() +{ + return GetLE24(&this->data[4]) + 1; +} +void VP8XChunk::width(XMP_Uns32 val) +{ + PutLE24(&this->data[4], val - 1); +} +XMP_Uns32 VP8XChunk::height() +{ + return GetLE24(&this->data[7]) + 1; +} +void VP8XChunk::height(XMP_Uns32 val) +{ + PutLE24(&this->data[7], val - 1); +} +bool VP8XChunk::xmp() +{ + XMP_Uns32 flags = GetLE32(&this->data[0]); + return (bool)((flags >> XMP_FLAG_BIT) & 1); +} +void VP8XChunk::xmp(bool hasXMP) +{ + XMP_Uns32 flags = GetLE32(&this->data[0]); + flags ^= (-hasXMP ^ flags) & (1 << XMP_FLAG_BIT); + PutLE32(&this->data[0], flags); +} + +Container::Container(WEBP_MetaHandler* handler) : Chunk(NULL, handler) +{ + this->needsRewrite = false; + + XMP_IO* file = handler->parent->ioRef; + + file->Seek(12, kXMP_SeekFromStart); + + XMP_Int64 size = handler->initialFileSize; + + XMP_Uns32 peek = 0; + + while (file->Offset() < size) { + peek = XIO::PeekUns32_LE(file); + switch (peek) { + case kChunk_XMP_: + this->addChunk(new XMPChunk(this, handler)); + break; + case kChunk_VP8X: + this->addChunk(new VP8XChunk(this, handler)); + break; + default: + this->addChunk(new Chunk(this, handler)); + break; + } + } + + if (this->chunks[WEBP_CHUNK_IMAGE].size() == 0) { + XMP_Throw("File has no image bitstream", kXMPErr_BadFileFormat); + } + + if (this->chunks[WEBP_CHUNK_VP8X].size() == 0) { + // The file is either in Lossy or Lossless Simple File Format. + // For simplicity we will always convert it to the Extended File Format. + this->needsRewrite = true; + this->addChunk(new VP8XChunk(this)); + } + + if (this->chunks[WEBP_CHUNK_XMP].size() == 0) { + XMPChunk* xmpChunk = new XMPChunk(this); + this->addChunk(xmpChunk); + handler->xmpChunk = xmpChunk; + this->vp8x->xmp(true); + } +} + +Chunk* Container::getExifChunk() +{ + if (this->chunks[WEBP::WEBP_CHUNK_EXIF].size() == 0) { + return NULL; + } + return this->chunks[WEBP::WEBP_CHUNK_EXIF][0]; +} + +void Container::addChunk(Chunk* chunk) +{ + ChunkId idx; + + try { + idx = chunkMap.at(chunk->tag); + } + catch (const std::out_of_range& e) { + idx = WEBP_CHUNK_UNKNOWN; + } + this->chunks[idx].push_back(chunk); +} + +void Container::write(WEBP_MetaHandler* handler) +{ + XMP_IO* file = handler->parent->ioRef; + file->Rewind(); + XIO::WriteUns32_LE(file, this->tag); + XIO::WriteUns32_LE(file, (XMP_Uns32) this->size); + XIO::WriteUns32_LE(file, kChunk_WEBP); + + size_t i, j; + std::vector<Chunk*> chunkVect; + for (i = 0; i < WEBP_CHUNK_NIL; i++) { + chunkVect = this->chunks[i]; + for (j = 0; j < chunkVect.size(); j++) { + chunkVect.at(j)->write(handler); + } + } + XMP_Int64 lastOffset = file->Offset(); + this->size = lastOffset - 8; + file->Seek(this->pos + 4, kXMP_SeekFromStart); + XIO::WriteUns32_LE(file, (XMP_Uns32) this->size); + file->Seek(lastOffset, kXMP_SeekFromStart); + if (lastOffset < handler->initialFileSize) { + file->Truncate(lastOffset); + } +} + +Container::~Container() +{ + Chunk* chunk; + size_t i; + std::vector<Chunk*> chunkVect; + for (i = 0; i < WEBP_CHUNK_NIL; i++) { + chunkVect = this->chunks[i]; + while (!chunkVect.empty()) { + chunk = chunkVect.back(); + delete chunk; + chunkVect.pop_back(); + } + } +} +} diff --git a/XMPFiles/source/FormatSupport/WEBP_Support.hpp b/XMPFiles/source/FormatSupport/WEBP_Support.hpp new file mode 100644 index 0000000..aebe781 --- /dev/null +++ b/XMPFiles/source/FormatSupport/WEBP_Support.hpp @@ -0,0 +1,174 @@ +#ifndef __WEBP_Support_hpp__ +#define __WEBP_Support_hpp__ 1 + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/Endian.h" +#include "source/XMPFiles_IO.hpp" + +#include <array> +#include <map> +#include <vector> + +// forward declaration: +class WEBP_MetaHandler; + +namespace WEBP { + +// Read 16, 24 or 32 bits stored in little-endian order. +// +// The inline functions provided in EndianUtils.hpp and XIO.hpp operate on +// pointers to XMP_Uns32, and then flip the bytes if the desired endianness +// differs from the host's. It seems to me that it is much simpler to read +// the data into XMP_Uns8 (i.e. unsigned char) arrays and just use shift +// operators (which operate on values rather than their representation +// in memory) to convert between values and the bytes read from or written to +// a file in a platform-independent way. And besides, WEBP stores dimensions +// in 24 bit LE, and converting that to and from a 32 bit pointer, having to +// account for the platform's endianness, would be a nightmare. +static inline XMP_Uns32 GetLE16(const XMP_Uns8* const data) +{ + return (XMP_Uns32)(data[0] << 0) | (data[1] << 8); +} + +static inline XMP_Uns32 GetLE24(const XMP_Uns8* const data) +{ + return GetLE16(data) | (data[2] << 16); +} + +static inline XMP_Uns32 GetLE32(const XMP_Uns8* const data) +{ + return (XMP_Uns32)GetLE16(data) | (GetLE16(data + 2) << 16); +} + +// Store 16, 24 or 32 bits in little-endian order. +static inline void PutLE16(XMP_Uns8* const data, XMP_Uns32 val) +{ + assert(val < (1 << 16)); + data[0] = (val >> 0); + data[1] = (val >> 8); +} + +static inline void PutLE24(XMP_Uns8* const buf, XMP_Uns32 val) +{ + assert(val < (1 << 24)); + PutLE16(buf, val & 0xffff); + buf[2] = (val >> 16); +} + +static inline void PutLE32(XMP_Uns8* const data, XMP_Uns32 val) +{ + PutLE16(data, (XMP_Uns32)(val & 0xffff)); + PutLE16(data + 2, (XMP_Uns32)(val >> 16)); +} + +#define WEBP_MKFOURCC(a, b, c, d) \ + ((XMP_Uns32)(a) | (b) << 8 | (c) << 16 | (d) << 24) + +// VP8X Feature Flags. +typedef enum FeatureFlagBits { + FRAGMENTS_FLAG_BIT, // Experimental, not enabled by default + ANIMATION_FLAG_BIT, + XMP_FLAG_BIT, + EXIF_FLAG_BIT, + ALPHA_FLAG_BIT, + ICCP_FLAG_BIT +} FeatureFlagBits; + +typedef enum ChunkId { + WEBP_CHUNK_VP8X, // VP8X + WEBP_CHUNK_ICCP, // ICCP + WEBP_CHUNK_ANIM, // ANIM + WEBP_CHUNK_ANMF, // ANMF + WEBP_CHUNK_FRGM, // FRGM + WEBP_CHUNK_ALPHA, // ALPH + WEBP_CHUNK_IMAGE, // VP8/VP8L + WEBP_CHUNK_EXIF, // EXIF + WEBP_CHUNK_XMP, // XMP + WEBP_CHUNK_UNKNOWN, // Other chunks. + WEBP_CHUNK_NIL +} ChunkId; + +const XMP_Uns32 kChunk_RIFF = WEBP_MKFOURCC('R', 'I', 'F', 'F'); +const XMP_Uns32 kChunk_WEBP = WEBP_MKFOURCC('W', 'E', 'B', 'P'); +const XMP_Uns32 kChunk_VP8_ = WEBP_MKFOURCC('V', 'P', '8', ' '); +const XMP_Uns32 kChunk_VP8L = WEBP_MKFOURCC('V', 'P', '8', 'L'); +const XMP_Uns32 kChunk_VP8X = WEBP_MKFOURCC('V', 'P', '8', 'X'); +const XMP_Uns32 kChunk_XMP_ = WEBP_MKFOURCC('X', 'M', 'P', ' '); +const XMP_Uns32 kChunk_ANIM = WEBP_MKFOURCC('A', 'N', 'I', 'M'); +const XMP_Uns32 kChunk_ICCP = WEBP_MKFOURCC('I', 'C', 'C', 'P'); +const XMP_Uns32 kChunk_EXIF = WEBP_MKFOURCC('E', 'X', 'I', 'F'); +const XMP_Uns32 kChunk_ANMF = WEBP_MKFOURCC('A', 'N', 'M', 'F'); +const XMP_Uns32 kChunk_FRGM = WEBP_MKFOURCC('F', 'R', 'G', 'M'); +const XMP_Uns32 kChunk_ALPH = WEBP_MKFOURCC('A', 'L', 'P', 'H'); + +static std::map<XMP_Uns32, ChunkId> chunkMap = { + { kChunk_VP8X, WEBP_CHUNK_VP8X }, { kChunk_ICCP, WEBP_CHUNK_ICCP }, + { kChunk_ANIM, WEBP_CHUNK_ANIM }, { kChunk_ANMF, WEBP_CHUNK_ANMF }, + { kChunk_FRGM, WEBP_CHUNK_FRGM }, { kChunk_ALPH, WEBP_CHUNK_ALPHA }, + { kChunk_VP8_, WEBP_CHUNK_IMAGE }, { kChunk_VP8L, WEBP_CHUNK_IMAGE }, + { kChunk_EXIF, WEBP_CHUNK_EXIF }, { kChunk_XMP_, WEBP_CHUNK_XMP } +}; + +class Container; + +class Chunk { +public: + Chunk(Container* parent, WEBP_MetaHandler* handler); + Chunk(Container* parent, XMP_Uns32 tag); + virtual ~Chunk(); + + virtual void write(WEBP_MetaHandler* handler); + + Container* parent; + XMP_Uns32 tag; + RawDataBlock data; + XMP_Int64 pos; + XMP_Int64 size; + bool needsRewrite; +}; + +class XMPChunk + : public Chunk { +public: + XMPChunk(Container* parent, WEBP_MetaHandler* handler); + XMPChunk(Container* parent); + void write(WEBP_MetaHandler* handler); +}; + +class VP8XChunk + : public Chunk { +public: + VP8XChunk(Container* parent, WEBP_MetaHandler* handler); + VP8XChunk(Container* parent); + bool xmp(); + void xmp(bool); + XMP_Uns32 width(); + XMP_Uns32 height(); + void width(XMP_Uns32); + void height(XMP_Uns32); +}; + +typedef std::array<std::vector<Chunk*>, WEBP_CHUNK_NIL> Chunks; + +class Container + : public Chunk { +public: + Container(WEBP_MetaHandler* handler); + ~Container(); + + void write(WEBP_MetaHandler* handler); + void addChunk(Chunk*); + Chunk* getExifChunk(); + + Chunks chunks; + VP8XChunk* vp8x; +}; + +} // namespace WEBP + +#endif // __WEBP_Support_hpp__ diff --git a/XMPFiles/source/HandlerRegistry.cpp b/XMPFiles/source/HandlerRegistry.cpp index 28eb379..69aed6e 100644 --- a/XMPFiles/source/HandlerRegistry.cpp +++ b/XMPFiles/source/HandlerRegistry.cpp @@ -38,6 +38,7 @@ #include "XMPFiles/source/FileHandlers/SWF_Handler.hpp" #include "XMPFiles/source/FileHandlers/XDCAM_Handler.hpp" #include "XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp" + #include "XMPFiles/source/FileHandlers/WEBP_Handler.hpp" #endif #if EnableMiscHandlers @@ -141,6 +142,7 @@ void HandlerRegistry::initialize() allOK &= this->registerNormalHandler ( kXMP_MP3File, kMP3_HandlerFlags, MP3_CheckFormat, MP3_MetaHandlerCTor ); allOK &= this->registerNormalHandler ( kXMP_WAVFile, kWAVE_HandlerFlags, WAVE_CheckFormat, WAVE_MetaHandlerCTor ); allOK &= this->registerNormalHandler ( kXMP_AVIFile, kRIFF_HandlerFlags, RIFF_CheckFormat, RIFF_MetaHandlerCTor ); + allOK &= this->registerNormalHandler ( kXMP_WEBPFile, kWEBP_HandlerFlags, WEBP_CheckFormat, WEBP_MetaHandlerCTor ); allOK &= this->registerNormalHandler ( kXMP_SWFFile, kSWF_HandlerFlags, SWF_CheckFormat, SWF_MetaHandlerCTor ); allOK &= this->registerNormalHandler ( kXMP_MPEG4File, kMPEG4_HandlerFlags, MPEG4_CheckFormat, MPEG4_MetaHandlerCTor ); allOK &= this->registerNormalHandler ( kXMP_MOVFile, kMPEG4_HandlerFlags, MPEG4_CheckFormat, MPEG4_MetaHandlerCTor ); // ! Yes, MPEG-4 includes MOV. diff --git a/XMPFiles/source/XMPFiles_Impl.cpp b/XMPFiles/source/XMPFiles_Impl.cpp index ea0b037..cf4c768 100644 --- a/XMPFiles/source/XMPFiles_Impl.cpp +++ b/XMPFiles/source/XMPFiles_Impl.cpp @@ -92,6 +92,7 @@ const FileExtMapping kFileExtMap[] = { "avi", kXMP_AVIFile }, { "cin", kXMP_CINFile }, { "wav", kXMP_WAVFile }, + { "webp", kXMP_WEBPFile }, { "mp3", kXMP_MP3File }, { "mp4", kXMP_MPEG4File }, { "m4v", kXMP_MPEG4File }, diff --git a/exempi/tests/Makefile.am b/exempi/tests/Makefile.am index a1de912..92c94e9 100644 --- a/exempi/tests/Makefile.am +++ b/exempi/tests/Makefile.am @@ -42,10 +42,10 @@ TEST_EXTENSIONS = .sh if WITH_UNIT_TEST check_PROGRAMS = testexempicore testserialise testwritenewprop \ testtiffleak testxmpfiles testxmpfileswrite \ - test3 testinit testfdo18635 testfdo83313 testcpp + test3 testinit testfdo18635 testfdo83313 testcpp testwebp TESTS = testcore.sh testinit testexempicore testserialise testwritenewprop \ testtiffleak testxmpfiles testxmpfileswrite \ - test3 testfdo18635 testfdo83313 testcpp + test3 testfdo18635 testfdo83313 testcpp testwebp TESTS_ENVIRONMENT = TEST_DIR=$(srcdir) BOOST_TEST_CATCH_SYSTEM_ERRORS=no VALGRIND="$(VALGRIND)" LOG_COMPILER = $(VALGRIND) endif @@ -104,3 +104,7 @@ testfdo83313_LDFLAGS = -static @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ testcpp_SOURCES = testcpp.cpp utils.cpp testcpp_LDADD = ../libexempi.la @BOOST_UNIT_TEST_FRAMEWORK_LIBS@ testcpp_LDFLAGS = -static @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ + +testwebp_SOURCES = test-webp.cpp utils.cpp +testwebp_LDADD = ../libexempi.la @BOOST_UNIT_TEST_FRAMEWORK_LIBS@ +testwebp_LDFLAGS = -static @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ diff --git a/exempi/tests/test-webp.cpp b/exempi/tests/test-webp.cpp new file mode 100644 index 0000000..47aa7dc --- /dev/null +++ b/exempi/tests/test-webp.cpp @@ -0,0 +1,119 @@ +/* + * exempi - test-webp.cpp + * + * Copyright (C) 2007-2016 Hubert Figuiere + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1 Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2 Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * 3 Neither the name of the Authors, nor the names of its + * contributors may be used to endorse or promote products derived + * from this software wit hout specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> + +#include <string> + +#include <boost/test/minimal.hpp> + +#include "utils.h" +#include "xmp.h" +#include "xmpconsts.h" + +using boost::unit_test::test_suite; + + +int test_main(int argc, char *argv[]) +{ + prepare_test(argc, argv, "../../samples/testfiles/BlueSquare.webp"); + + BOOST_CHECK(xmp_init()); + + BOOST_CHECK(xmp_files_check_file_format(g_testfile.c_str()) == XMP_FT_WEBP); + XmpFilePtr f = xmp_files_open_new(g_testfile.c_str(), XMP_OPEN_READ); + + BOOST_CHECK(f != NULL); + if (f == NULL) { + return 1; + } + + XmpPtr xmp = xmp_files_get_new_xmp(f); + BOOST_CHECK(xmp != NULL); + + BOOST_CHECK(xmp_files_free(f)); + + BOOST_CHECK(copy_file(g_testfile, "test.webp")); + BOOST_CHECK(chmod("test.webp", S_IRUSR | S_IWUSR) == 0); + + BOOST_CHECK(xmp_files_check_file_format("test.webp") == XMP_FT_WEBP); + + f = xmp_files_open_new("test.webp", XMP_OPEN_FORUPDATE); + + BOOST_CHECK(f != NULL); + if (f == NULL) { + return 2; + } + + BOOST_CHECK(xmp_set_property(xmp, NS_PHOTOSHOP, "ICCProfile", "foo", 0)); + + BOOST_CHECK(xmp_files_can_put_xmp(f, xmp)); + BOOST_CHECK(xmp_files_put_xmp(f, xmp)); + + BOOST_CHECK(xmp_free(xmp)); + BOOST_CHECK(xmp_files_close(f, XMP_CLOSE_SAFEUPDATE)); + BOOST_CHECK(xmp_files_free(f)); + + f = xmp_files_open_new("test.webp", XMP_OPEN_READ); + + BOOST_CHECK(f != NULL); + if (f == NULL) { + return 3; + } + xmp = xmp_files_get_new_xmp(f); + BOOST_CHECK(xmp != NULL); + + XmpStringPtr the_prop = xmp_string_new(); + BOOST_CHECK( + xmp_get_property(xmp, NS_PHOTOSHOP, "ICCProfile", the_prop, NULL)); + BOOST_CHECK(strcmp("foo", xmp_string_cstr(the_prop)) == 0); + + xmp_string_free(the_prop); + + BOOST_CHECK(xmp_free(xmp)); + BOOST_CHECK(xmp_files_close(f, XMP_CLOSE_NOOPTION)); + BOOST_CHECK(xmp_files_free(f)); + + xmp_terminate(); + + BOOST_CHECK(!g_lt->check_leaks()); + BOOST_CHECK(!g_lt->check_errors()); + + return 0; +} diff --git a/exempi/xmp.h b/exempi/xmp.h index 502174f..c3cfea9 100644 --- a/exempi/xmp.h +++ b/exempi/xmp.h @@ -105,6 +105,7 @@ typedef enum { XMP_FT_TIFF = 0x54494646UL, /* 'TIFF' */ XMP_FT_GIF = 0x47494620UL, /* 'GIF ' */ XMP_FT_PNG = 0x504E4720UL, /* 'PNG ' */ + XMP_FT_WEBP = 0x57454250UL, /* 'WEBP' */ XMP_FT_SWF = 0x53574620UL, /* 'SWF ' */ XMP_FT_FLA = 0x464C4120UL, /* 'FLA ' */ diff --git a/public/include/XMP_Const.h b/public/include/XMP_Const.h index a29c5fa..4b72404 100644 --- a/public/include/XMP_Const.h +++ b/public/include/XMP_Const.h @@ -889,6 +889,8 @@ enum { kXMP_GIFFile = 0x47494620UL, /// Public file format constant: 'PNG ' kXMP_PNGFile = 0x504E4720UL, + /// Public file format constant: 'WEBP ' + kXMP_WEBPFile = 0x57454250UL, /// Public file format constant: 'SWF ' kXMP_SWFFile = 0x53574620UL, diff --git a/samples/testfiles/BlueSquare.webp b/samples/testfiles/BlueSquare.webp Binary files differnew file mode 100644 index 0000000..d68328e --- /dev/null +++ b/samples/testfiles/BlueSquare.webp diff --git a/samples/testfiles/Makefile.am b/samples/testfiles/Makefile.am index 1aa0f57..791f4be 100644 --- a/samples/testfiles/Makefile.am +++ b/samples/testfiles/Makefile.am @@ -36,4 +36,4 @@ EXTRA_DIST=BlueSquare.ai BlueSquare.eps BlueSquare.gif BlueSquare.jpg BlueSquare.mp3 BlueSquare.png BlueSquare.tif\ -BlueSquare.avi BlueSquare.indd BlueSquare.mov BlueSquare.pdf BlueSquare.psd BlueSquare.wav +BlueSquare.avi BlueSquare.indd BlueSquare.mov BlueSquare.pdf BlueSquare.psd BlueSquare.wav BlueSquare.webp |