summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHubert Figuière <hub@figuiere.net>2016-07-25 22:37:15 -0400
committerHubert Figuière <hub@figuiere.net>2016-07-25 22:37:15 -0400
commit92d410d2f44ea4fadb9e99f0ff54467b07a9c88d (patch)
treefa4c6f985b753503cd88f5de071dd74b7e92e61d
parent59f14131bd67ab590b22b3205f715a51423f1e7c (diff)
parent33a432bab8cb34f553e6be1da6d66839f7987448 (diff)
Merge branch 'webp-handler'
This bring the webp file handler in. From https://github.com/hfiguiere/exempi/pull/2
-rw-r--r--.gitignore2
-rw-r--r--NEWS1
-rw-r--r--XMPFiles/source/FileHandlers/Makefile.am3
-rw-r--r--XMPFiles/source/FileHandlers/WEBP_Handler.cpp197
-rw-r--r--XMPFiles/source/FileHandlers/WEBP_Handler.hpp48
-rw-r--r--XMPFiles/source/FormatSupport/Makefile.am1
-rw-r--r--XMPFiles/source/FormatSupport/WEBP_Support.cpp273
-rw-r--r--XMPFiles/source/FormatSupport/WEBP_Support.hpp174
-rw-r--r--XMPFiles/source/HandlerRegistry.cpp2
-rw-r--r--XMPFiles/source/XMPFiles_Impl.cpp1
-rw-r--r--exempi/tests/Makefile.am8
-rw-r--r--exempi/tests/test-webp.cpp119
-rw-r--r--exempi/xmp.h1
-rw-r--r--public/include/XMP_Const.h2
-rw-r--r--samples/testfiles/BlueSquare.webpbin0 -> 4974 bytes
-rw-r--r--samples/testfiles/Makefile.am2
16 files changed, 830 insertions, 4 deletions
diff --git a/.gitignore b/.gitignore
index a49cc8b..41ca6de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/NEWS b/NEWS
index 6d29410..9281305 100644
--- a/NEWS
+++ b/NEWS
@@ -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
new file mode 100644
index 0000000..d68328e
--- /dev/null
+++ b/samples/testfiles/BlueSquare.webp
Binary files differ
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