|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
|
|
|
|
/*
|
|
|
|
Google's WEBP container spec can be found at the link below:
|
|
|
|
https://developers.google.com/speed/webp/docs/riff_container
|
|
|
|
*/
|
|
|
|
|
|
|
|
// included header files
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include "webpimage.hpp"
|
|
|
|
#include "image_int.hpp"
|
|
|
|
#include "enforce.hpp"
|
|
|
|
#include "futils.hpp"
|
|
|
|
#include "basicio.hpp"
|
|
|
|
#include "tags.hpp"
|
|
|
|
#include "tags_int.hpp"
|
|
|
|
#include "types.hpp"
|
|
|
|
#include "tiffimage.hpp"
|
|
|
|
#include "tiffimage_int.hpp"
|
|
|
|
#include "convert.hpp"
|
|
|
|
#include "safe_op.hpp"
|
|
|
|
|
|
|
|
#include <cmath>
|
|
|
|
#include <iomanip>
|
|
|
|
#include <string>
|
|
|
|
#include <cstring>
|
|
|
|
#include <iostream>
|
|
|
|
#include <sstream>
|
|
|
|
#include <cassert>
|
|
|
|
#include <cstdio>
|
|
|
|
|
|
|
|
#define CHECK_BIT(var,pos) ((var) & (1<<(pos)))
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
[[maybe_unused]] std::string binaryToHex(const uint8_t* data, size_t size)
|
|
|
|
{
|
|
|
|
std::stringstream hexOutput;
|
|
|
|
|
|
|
|
auto tl = size_t(size / 16) * 16;
|
|
|
|
auto tl_offset = size_t(size) - tl;
|
|
|
|
|
|
|
|
for (size_t loop = 0; loop < size; loop++) {
|
|
|
|
if (data[loop] < 16) {
|
|
|
|
hexOutput << "0";
|
|
|
|
}
|
|
|
|
hexOutput << std::hex << static_cast<int>(data[loop]);
|
|
|
|
if ((loop % 8) == 7) {
|
|
|
|
hexOutput << " ";
|
|
|
|
}
|
|
|
|
if ((loop % 16) == 15 || loop == (tl + tl_offset - 1)) {
|
|
|
|
int max = 15;
|
|
|
|
if (loop >= tl) {
|
|
|
|
max = int(tl_offset) - 1;
|
|
|
|
for (int offset = 0; offset < int(16 - tl_offset); offset++) {
|
|
|
|
if ((offset % 8) == 7) {
|
|
|
|
hexOutput << " ";
|
|
|
|
}
|
|
|
|
hexOutput << " ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
hexOutput << " ";
|
|
|
|
for (int offset = max; offset >= 0; offset--) {
|
|
|
|
if (offset == (max - 8)) {
|
|
|
|
hexOutput << " ";
|
|
|
|
}
|
|
|
|
uint8_t c = '.';
|
|
|
|
if (data[loop - offset] >= 0x20 && data[loop - offset] <= 0x7E) {
|
|
|
|
c = data[loop - offset];
|
|
|
|
}
|
|
|
|
hexOutput << static_cast<char>(c);
|
|
|
|
}
|
|
|
|
hexOutput << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hexOutput << std::endl << std::endl << std::endl;
|
|
|
|
|
|
|
|
return hexOutput.str();
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
// *****************************************************************************
|
|
|
|
// class member definitions
|
|
|
|
namespace Exiv2 {
|
|
|
|
using namespace Exiv2::Internal;
|
|
|
|
|
|
|
|
WebPImage::WebPImage(BasicIo::UniquePtr io)
|
|
|
|
: Image(ImageType::webp, mdNone, std::move(io))
|
|
|
|
{
|
|
|
|
} // WebPImage::WebPImage
|
|
|
|
|
|
|
|
std::string WebPImage::mimeType() const
|
|
|
|
{
|
|
|
|
return "image/webp";
|
|
|
|
}
|
|
|
|
|
|
|
|
/* =========================================== */
|
|
|
|
|
|
|
|
/* Misc. */
|
|
|
|
constexpr byte WebPImage::WEBP_PAD_ODD = 0;
|
|
|
|
constexpr int WebPImage::WEBP_TAG_SIZE = 0x4;
|
|
|
|
|
|
|
|
/* VP8X feature flags */
|
|
|
|
constexpr int WebPImage::WEBP_VP8X_ICC_BIT = 0x20;
|
|
|
|
constexpr int WebPImage::WEBP_VP8X_ALPHA_BIT = 0x10;
|
|
|
|
constexpr int WebPImage::WEBP_VP8X_EXIF_BIT = 0x8;
|
|
|
|
constexpr int WebPImage::WEBP_VP8X_XMP_BIT = 0x4;
|
|
|
|
|
|
|
|
/* Chunk header names */
|
|
|
|
constexpr const char* WebPImage::WEBP_CHUNK_HEADER_VP8X = "VP8X";
|
|
|
|
constexpr const char* WebPImage::WEBP_CHUNK_HEADER_VP8L = "VP8L";
|
|
|
|
constexpr const char* WebPImage::WEBP_CHUNK_HEADER_VP8 = "VP8 ";
|
|
|
|
constexpr const char* WebPImage::WEBP_CHUNK_HEADER_ANMF = "ANMF";
|
|
|
|
constexpr const char* WebPImage::WEBP_CHUNK_HEADER_ANIM = "ANIM";
|
|
|
|
constexpr const char* WebPImage::WEBP_CHUNK_HEADER_ICCP = "ICCP";
|
|
|
|
constexpr const char* WebPImage::WEBP_CHUNK_HEADER_EXIF = "EXIF";
|
|
|
|
constexpr const char* WebPImage::WEBP_CHUNK_HEADER_XMP = "XMP ";
|
|
|
|
|
|
|
|
/* =========================================== */
|
|
|
|
|
|
|
|
void WebPImage::setIptcData(const IptcData& /*iptcData*/)
|
|
|
|
{
|
|
|
|
// not supported
|
|
|
|
// just quietly ignore the request
|
|
|
|
// throw(Error(kerInvalidSettingForImage, "IPTC metadata", "WebP"));
|
|
|
|
}
|
|
|
|
|
|
|
|
void WebPImage::setComment(std::string_view /*comment*/)
|
|
|
|
{
|
|
|
|
// not supported
|
|
|
|
throw(Error(kerInvalidSettingForImage, "Image comment", "WebP"));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* =========================================== */
|
|
|
|
|
|
|
|
void WebPImage::writeMetadata()
|
|
|
|
{
|
|
|
|
if (io_->open() != 0) {
|
|
|
|
throw Error(kerDataSourceOpenFailed, io_->path(), strError());
|
|
|
|
}
|
|
|
|
IoCloser closer(*io_);
|
|
|
|
auto tempIo = std::make_unique<MemIo>();
|
|
|
|
|
|
|
|
doWriteMetadata(*tempIo); // may throw
|
|
|
|
io_->close();
|
|
|
|
io_->transfer(*tempIo); // may throw
|
|
|
|
} // WebPImage::writeMetadata
|
|
|
|
|
|
|
|
|
|
|
|
void WebPImage::doWriteMetadata(BasicIo& outIo)
|
|
|
|
{
|
|
|
|
if (!io_->isopen()) throw Error(kerInputDataReadFailed);
|
|
|
|
if (!outIo.isopen()) throw Error(kerImageWriteFailed);
|
|
|
|
|
|
|
|
#ifdef EXIV2_DEBUG_MESSAGES
|
|
|
|
std::cout << "Writing metadata" << std::endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
byte data [WEBP_TAG_SIZE*3];
|
|
|
|
DataBuf chunkId(WEBP_TAG_SIZE+1);
|
|
|
|
chunkId.write_uint8(WEBP_TAG_SIZE, '\0');
|
|
|
|
|
|
|
|
io_->readOrThrow(data, WEBP_TAG_SIZE * 3, Exiv2::kerCorruptedMetadata);
|
|
|
|
uint64_t filesize = Exiv2::getULong(data + WEBP_TAG_SIZE, littleEndian);
|
|
|
|
|
|
|
|
/* Set up header */
|
|
|
|
if (outIo.write(data, WEBP_TAG_SIZE * 3) != WEBP_TAG_SIZE * 3)
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
|
|
|
|
/* Parse Chunks */
|
|
|
|
bool has_size = false;
|
|
|
|
bool has_xmp = false;
|
|
|
|
bool has_exif = false;
|
|
|
|
bool has_vp8x = false;
|
|
|
|
bool has_alpha = false;
|
|
|
|
bool has_icc = iccProfileDefined();
|
|
|
|
|
|
|
|
int width = 0;
|
|
|
|
int height = 0;
|
|
|
|
|
|
|
|
byte size_buff[WEBP_TAG_SIZE];
|
|
|
|
Blob blob;
|
|
|
|
|
|
|
|
if (exifData_.count() > 0) {
|
|
|
|
ExifParser::encode(blob, littleEndian, exifData_);
|
|
|
|
if (!blob.empty()) {
|
|
|
|
has_exif = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xmpData_.count() > 0 && !writeXmpFromPacket()) {
|
|
|
|
XmpParser::encode(xmpPacket_, xmpData_,
|
|
|
|
XmpParser::useCompactFormat |
|
|
|
|
XmpParser::omitAllFormatting);
|
|
|
|
}
|
|
|
|
has_xmp = !xmpPacket_.empty();
|
|
|
|
std::string xmp(xmpPacket_);
|
|
|
|
|
|
|
|
/* Verify for a VP8X Chunk First before writing in
|
|
|
|
case we have any exif or xmp data, also check
|
|
|
|
for any chunks with alpha frame/layer set */
|
|
|
|
while (!io_->eof() && static_cast<uint64_t>(io_->tell()) < filesize) {
|
|
|
|
io_->readOrThrow(chunkId.data(), WEBP_TAG_SIZE, Exiv2::kerCorruptedMetadata);
|
|
|
|
io_->readOrThrow(size_buff, WEBP_TAG_SIZE, Exiv2::kerCorruptedMetadata);
|
|
|
|
const uint32_t size_u32 = Exiv2::getULong(size_buff, littleEndian);
|
|
|
|
|
|
|
|
// Check that `size_u32` is safe to cast to `long`.
|
|
|
|
enforce(size_u32 <= static_cast<size_t>(std::numeric_limits<unsigned int>::max()),
|
|
|
|
Exiv2::kerCorruptedMetadata);
|
|
|
|
const auto size = static_cast<long>(size_u32);
|
|
|
|
DataBuf payload(size);
|
|
|
|
io_->readOrThrow(payload.data(), payload.size(), Exiv2::kerCorruptedMetadata);
|
|
|
|
if ( payload.size() % 2 ) {
|
|
|
|
byte c = 0;
|
|
|
|
io_->readOrThrow(&c, 1, Exiv2::kerCorruptedMetadata);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Chunk with information about features
|
|
|
|
used in the file. */
|
|
|
|
if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8X) && !has_vp8x) {
|
|
|
|
has_vp8x = true;
|
|
|
|
}
|
|
|
|
if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8X) && !has_size) {
|
|
|
|
enforce(size >= 10, Exiv2::kerCorruptedMetadata);
|
|
|
|
has_size = true;
|
|
|
|
byte size_buf[WEBP_TAG_SIZE];
|
|
|
|
|
|
|
|
// Fetch width - stored in 24bits
|
|
|
|
memcpy(&size_buf, payload.c_data(4), 3);
|
|
|
|
size_buf[3] = 0;
|
|
|
|
width = Exiv2::getULong(size_buf, littleEndian) + 1;
|
|
|
|
|
|
|
|
// Fetch height - stored in 24bits
|
|
|
|
memcpy(&size_buf, payload.c_data(7), 3);
|
|
|
|
size_buf[3] = 0;
|
|
|
|
height = Exiv2::getULong(size_buf, littleEndian) + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Chunk with animation control data. */
|
|
|
|
#ifdef __CHECK_FOR_ALPHA__ // Maybe in the future
|
|
|
|
if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ANIM) && !has_alpha) {
|
|
|
|
has_alpha = true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Chunk with lossy image data. */
|
|
|
|
#ifdef __CHECK_FOR_ALPHA__ // Maybe in the future
|
|
|
|
if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8) && !has_alpha) {
|
|
|
|
has_alpha = true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8) && !has_size) {
|
|
|
|
enforce(size >= 10, Exiv2::kerCorruptedMetadata);
|
|
|
|
has_size = true;
|
|
|
|
byte size_buf[2];
|
|
|
|
|
|
|
|
/* Refer to this https://tools.ietf.org/html/rfc6386
|
|
|
|
for height and width reference for VP8 chunks */
|
|
|
|
|
|
|
|
// Fetch width - stored in 16bits
|
|
|
|
memcpy(&size_buf, payload.c_data(6), 2);
|
|
|
|
width = Exiv2::getUShort(size_buf, littleEndian) & 0x3fff;
|
|
|
|
|
|
|
|
// Fetch height - stored in 16bits
|
|
|
|
memcpy(&size_buf, payload.c_data(8), 2);
|
|
|
|
height = Exiv2::getUShort(size_buf, littleEndian) & 0x3fff;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Chunk with lossless image data. */
|
|
|
|
if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8L) && !has_alpha) {
|
|
|
|
enforce(size >= 5, Exiv2::kerCorruptedMetadata);
|
|
|
|
if ((payload.read_uint8(4) & WEBP_VP8X_ALPHA_BIT) == WEBP_VP8X_ALPHA_BIT) {
|
|
|
|
has_alpha = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8L) && !has_size) {
|
|
|
|
enforce(size >= 5, Exiv2::kerCorruptedMetadata);
|
|
|
|
has_size = true;
|
|
|
|
byte size_buf_w[2];
|
|
|
|
byte size_buf_h[3];
|
|
|
|
|
|
|
|
/* For VP8L chunks width & height are stored in 28 bits
|
|
|
|
of a 32 bit field requires bitshifting to get actual
|
|
|
|
sizes. Width and height are split even into 14 bits
|
|
|
|
each. Refer to this https://goo.gl/bpgMJf */
|
|
|
|
|
|
|
|
// Fetch width - 14 bits wide
|
|
|
|
memcpy(&size_buf_w, payload.c_data(1), 2);
|
|
|
|
size_buf_w[1] &= 0x3F;
|
|
|
|
width = Exiv2::getUShort(size_buf_w, littleEndian) + 1;
|
|
|
|
|
|
|
|
// Fetch height - 14 bits wide
|
|
|
|
memcpy(&size_buf_h, payload.c_data(2), 3);
|
|
|
|
size_buf_h[0] =
|
|
|
|
((size_buf_h[0] >> 6) & 0x3) |
|
|
|
|
((size_buf_h[1] & 0x3F) << 0x2);
|
|
|
|
size_buf_h[1] =
|
|
|
|
((size_buf_h[1] >> 6) & 0x3) |
|
|
|
|
((size_buf_h[2] & 0xF) << 0x2);
|
|
|
|
height = Exiv2::getUShort(size_buf_h, littleEndian) + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Chunk with animation frame. */
|
|
|
|
if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ANMF) && !has_alpha) {
|
|
|
|
enforce(size >= 6, Exiv2::kerCorruptedMetadata);
|
|
|
|
if ((payload.read_uint8(5) & 0x2) == 0x2) {
|
|
|
|
has_alpha = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ANMF) && !has_size) {
|
|
|
|
enforce(size >= 12, Exiv2::kerCorruptedMetadata);
|
|
|
|
has_size = true;
|
|
|
|
byte size_buf[WEBP_TAG_SIZE];
|
|
|
|
|
|
|
|
// Fetch width - stored in 24bits
|
|
|
|
memcpy(&size_buf, payload.c_data(6), 3);
|
|
|
|
size_buf[3] = 0;
|
|
|
|
width = Exiv2::getULong(size_buf, littleEndian) + 1;
|
|
|
|
|
|
|
|
// Fetch height - stored in 24bits
|
|
|
|
memcpy(&size_buf, payload.c_data(9), 3);
|
|
|
|
size_buf[3] = 0;
|
|
|
|
height = Exiv2::getULong(size_buf, littleEndian) + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Chunk with alpha data. */
|
|
|
|
if (equalsWebPTag(chunkId, "ALPH") && !has_alpha) {
|
|
|
|
has_alpha = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Inject a VP8X chunk if one isn't available. */
|
|
|
|
if (!has_vp8x) {
|
|
|
|
inject_VP8X(outIo, has_xmp, has_exif, has_alpha,
|
|
|
|
has_icc, width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
io_->seek(12, BasicIo::beg);
|
|
|
|
while (!io_->eof() && static_cast<uint64_t>(io_->tell()) < filesize) {
|
|
|
|
io_->readOrThrow(chunkId.data(), 4, Exiv2::kerCorruptedMetadata);
|
|
|
|
io_->readOrThrow(size_buff, 4, Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
const uint32_t size_u32 = Exiv2::getULong(size_buff, littleEndian);
|
|
|
|
|
|
|
|
// Check that `size_u32` is safe to cast to `long`.
|
|
|
|
enforce(size_u32 <= static_cast<size_t>(std::numeric_limits<unsigned int>::max()),
|
|
|
|
Exiv2::kerCorruptedMetadata);
|
|
|
|
const auto size = static_cast<long>(size_u32);
|
|
|
|
|
|
|
|
DataBuf payload(size);
|
|
|
|
io_->readOrThrow(payload.data(), size, Exiv2::kerCorruptedMetadata);
|
|
|
|
if ( io_->tell() % 2 ) io_->seek(+1,BasicIo::cur); // skip pad
|
|
|
|
|
|
|
|
if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8X)) {
|
|
|
|
enforce(size >= 1, Exiv2::kerCorruptedMetadata);
|
|
|
|
if (has_icc){
|
|
|
|
const uint8_t x = payload.read_uint8(0);
|
|
|
|
payload.write_uint8(0, x | WEBP_VP8X_ICC_BIT);
|
|
|
|
} else {
|
|
|
|
const uint8_t x = payload.read_uint8(0);
|
|
|
|
payload.write_uint8(0, x & ~WEBP_VP8X_ICC_BIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_xmp){
|
|
|
|
const uint8_t x = payload.read_uint8(0);
|
|
|
|
payload.write_uint8(0, x | WEBP_VP8X_XMP_BIT);
|
|
|
|
} else {
|
|
|
|
const uint8_t x = payload.read_uint8(0);
|
|
|
|
payload.write_uint8(0, x & ~WEBP_VP8X_XMP_BIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_exif) {
|
|
|
|
const uint8_t x = payload.read_uint8(0);
|
|
|
|
payload.write_uint8(0, x | WEBP_VP8X_EXIF_BIT);
|
|
|
|
} else {
|
|
|
|
const uint8_t x = payload.read_uint8(0);
|
|
|
|
payload.write_uint8(0, x & ~WEBP_VP8X_EXIF_BIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (outIo.write(chunkId.c_data(), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
if (outIo.write(size_buff, WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
if (outIo.write(payload.c_data(), payload.size()) != payload.size())
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
if (outIo.tell() % 2) {
|
|
|
|
if (outIo.write(&WEBP_PAD_ODD, 1) != 1) throw Error(kerImageWriteFailed);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_icc) {
|
|
|
|
if (outIo.write(reinterpret_cast<const byte*>(WEBP_CHUNK_HEADER_ICCP), WEBP_TAG_SIZE) !=
|
|
|
|
WEBP_TAG_SIZE)
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
ul2Data(data, static_cast<uint32_t>(iccProfile_.size()), littleEndian);
|
|
|
|
if (outIo.write(data, WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
if (outIo.write(iccProfile_.c_data(), iccProfile_.size()) != iccProfile_.size()) {
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
}
|
|
|
|
has_icc = false;
|
|
|
|
}
|
|
|
|
} else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ICCP)) {
|
|
|
|
// Skip it altogether handle it prior to here :)
|
|
|
|
} else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_EXIF)) {
|
|
|
|
// Skip and add new data afterwards
|
|
|
|
} else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_XMP)) {
|
|
|
|
// Skip and add new data afterwards
|
|
|
|
} else {
|
|
|
|
if (outIo.write(chunkId.c_data(), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
if (outIo.write(size_buff, WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
if (outIo.write(payload.c_data(), payload.size()) != payload.size())
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encoder required to pad odd sized data with a null byte
|
|
|
|
if (outIo.tell() % 2) {
|
|
|
|
if (outIo.write(&WEBP_PAD_ODD, 1) != 1) throw Error(kerImageWriteFailed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_exif) {
|
|
|
|
if (outIo.write(reinterpret_cast<const byte*>(WEBP_CHUNK_HEADER_EXIF), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
us2Data(data, static_cast<uint16_t>(blob.size()) + 8, bigEndian);
|
|
|
|
ul2Data(data, static_cast<uint32_t>(blob.size()), littleEndian);
|
|
|
|
if (outIo.write(data, WEBP_TAG_SIZE) != WEBP_TAG_SIZE) throw Error(kerImageWriteFailed);
|
|
|
|
if (outIo.write(&blob[0], blob.size()) != blob.size()) {
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
}
|
|
|
|
if (outIo.tell() % 2) {
|
|
|
|
if (outIo.write(&WEBP_PAD_ODD, 1) != 1) throw Error(kerImageWriteFailed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_xmp) {
|
|
|
|
if (outIo.write(reinterpret_cast<const byte*>(WEBP_CHUNK_HEADER_XMP), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
ul2Data(data, static_cast<uint32_t>(xmpPacket().size()), littleEndian);
|
|
|
|
if (outIo.write(data, WEBP_TAG_SIZE) != WEBP_TAG_SIZE) throw Error(kerImageWriteFailed);
|
|
|
|
if (outIo.write(reinterpret_cast<const byte*>(xmp.data()), xmp.size()) != xmp.size()) {
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
}
|
|
|
|
if (outIo.tell() % 2) {
|
|
|
|
if (outIo.write(&WEBP_PAD_ODD, 1) != 1) throw Error(kerImageWriteFailed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix File Size Payload Data
|
|
|
|
outIo.seek(0, BasicIo::beg);
|
|
|
|
filesize = outIo.size() - 8;
|
|
|
|
outIo.seek(4, BasicIo::beg);
|
|
|
|
ul2Data(data, static_cast<uint32_t>(filesize), littleEndian);
|
|
|
|
if (outIo.write(data, WEBP_TAG_SIZE) != WEBP_TAG_SIZE) throw Error(kerImageWriteFailed);
|
|
|
|
|
|
|
|
} // WebPImage::writeMetadata
|
|
|
|
|
|
|
|
/* =========================================== */
|
|
|
|
|
|
|
|
void WebPImage::printStructure(std::ostream& out, PrintStructureOption option,int depth)
|
|
|
|
{
|
|
|
|
if (io_->open() != 0) {
|
|
|
|
throw Error(kerDataSourceOpenFailed, io_->path(), strError());
|
|
|
|
}
|
|
|
|
// Ensure this is the correct image type
|
|
|
|
if (!isWebPType(*io_, true)) {
|
|
|
|
if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
|
|
|
|
throw Error(kerNotAnImage, "WEBP");
|
|
|
|
}
|
|
|
|
|
|
|
|
bool bPrint = option==kpsBasic || option==kpsRecursive;
|
|
|
|
if ( bPrint || option == kpsXMP || option == kpsIccProfile || option == kpsIptcErase ) {
|
|
|
|
byte data [WEBP_TAG_SIZE * 2];
|
|
|
|
io_->read(data, WEBP_TAG_SIZE * 2);
|
|
|
|
uint64_t filesize = Exiv2::getULong(data + WEBP_TAG_SIZE, littleEndian);
|
|
|
|
DataBuf chunkId(5) ;
|
|
|
|
chunkId.write_uint8(4, '\0');
|
|
|
|
|
|
|
|
if ( bPrint ) {
|
|
|
|
out << Internal::indent(depth)
|
|
|
|
<< "STRUCTURE OF WEBP FILE: "
|
|
|
|
<< io().path()
|
|
|
|
<< std::endl;
|
|
|
|
out << Internal::indent(depth)
|
|
|
|
<< Internal::stringFormat(" Chunk | Length | Offset | Payload")
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
io_->seek(0,BasicIo::beg); // rewind
|
|
|
|
while (!io_->eof() && static_cast<uint64_t>(io_->tell()) < filesize) {
|
|
|
|
auto offset = static_cast<uint64_t>(io_->tell());
|
|
|
|
byte size_buff[WEBP_TAG_SIZE];
|
|
|
|
io_->read(chunkId.data(), WEBP_TAG_SIZE);
|
|
|
|
io_->read(size_buff, WEBP_TAG_SIZE);
|
|
|
|
long size = Exiv2::getULong(size_buff, littleEndian);
|
|
|
|
DataBuf payload(offset?size:WEBP_TAG_SIZE); // header is different from chunks
|
|
|
|
io_->read(payload.data(), payload.size());
|
|
|
|
|
|
|
|
if ( bPrint ) {
|
|
|
|
out << Internal::indent(depth)
|
|
|
|
<< Internal::stringFormat(" %s | %8u | %8u | ", chunkId.c_str(),
|
|
|
|
static_cast<uint32_t>(size), static_cast<uint32_t>(offset))
|
|
|
|
<< Internal::binaryToString(makeSlice(payload, 0, payload.size() > 32 ? 32 : payload.size()))
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_EXIF) && option==kpsRecursive ) {
|
|
|
|
// create memio object with the payload, then print the structure
|
|
|
|
MemIo p (payload.c_data(), payload.size());
|
|
|
|
printTiffStructure(p,out,option,depth);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool bPrintPayload = (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_XMP) && option==kpsXMP)
|
|
|
|
|| (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ICCP) && option==kpsIccProfile)
|
|
|
|
;
|
|
|
|
if ( bPrintPayload ) {
|
|
|
|
out.write(payload.c_str(), payload.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( offset && io_->tell() % 2 ) io_->seek(+1, BasicIo::cur); // skip padding byte on sub-chunks
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* =========================================== */
|
|
|
|
|
|
|
|
void WebPImage::readMetadata()
|
|
|
|
{
|
|
|
|
if (io_->open() != 0) throw Error(kerDataSourceOpenFailed, io_->path(), strError());
|
|
|
|
IoCloser closer(*io_);
|
|
|
|
// Ensure that this is the correct image type
|
|
|
|
if (!isWebPType(*io_, true)) {
|
|
|
|
if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
|
|
|
|
throw Error(kerNotAJpeg);
|
|
|
|
}
|
|
|
|
clearMetadata();
|
|
|
|
|
|
|
|
byte data[12];
|
|
|
|
DataBuf chunkId(5);
|
|
|
|
chunkId.write_uint8(4, '\0');
|
|
|
|
|
|
|
|
io_->readOrThrow(data, WEBP_TAG_SIZE * 3, Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
const uint32_t filesize_u32 = Safe::add(Exiv2::getULong(data + WEBP_TAG_SIZE, littleEndian), 8U);
|
|
|
|
enforce(filesize_u32 <= io_->size(), Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
// Check that `filesize_u32` is safe to cast to `long`.
|
|
|
|
enforce(filesize_u32 <= static_cast<uint32_t>(std::numeric_limits<long>::max()), Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
WebPImage::decodeChunks(static_cast<long>(filesize_u32));
|
|
|
|
|
|
|
|
} // WebPImage::readMetadata
|
|
|
|
|
|
|
|
void WebPImage::decodeChunks(long filesize)
|
|
|
|
{
|
|
|
|
DataBuf chunkId(5);
|
|
|
|
byte size_buff[WEBP_TAG_SIZE];
|
|
|
|
bool has_canvas_data = false;
|
|
|
|
|
|
|
|
#ifdef EXIV2_DEBUG_MESSAGES
|
|
|
|
std::cout << "Reading metadata" << std::endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
chunkId.write_uint8(4, '\0');
|
|
|
|
while (!io_->eof() && io_->tell() < filesize) {
|
|
|
|
io_->readOrThrow(chunkId.data(), WEBP_TAG_SIZE, Exiv2::kerCorruptedMetadata);
|
|
|
|
io_->readOrThrow(size_buff, WEBP_TAG_SIZE, Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
const uint32_t size_u32 = Exiv2::getULong(size_buff, littleEndian);
|
|
|
|
|
|
|
|
// Check that `size_u32` is safe to cast to `long`.
|
|
|
|
enforce(size_u32 <= static_cast<uint32_t>(std::numeric_limits<long>::max()), Exiv2::kerCorruptedMetadata);
|
|
|
|
const auto size = static_cast<long>(size_u32);
|
|
|
|
|
|
|
|
// Check that `size` is within bounds.
|
|
|
|
enforce(io_->tell() <= filesize, Exiv2::kerCorruptedMetadata);
|
|
|
|
enforce(size <= (filesize - io_->tell()), Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
DataBuf payload(size);
|
|
|
|
|
|
|
|
if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8X) && !has_canvas_data) {
|
|
|
|
enforce(size >= 10, Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
has_canvas_data = true;
|
|
|
|
byte size_buf[WEBP_TAG_SIZE];
|
|
|
|
|
|
|
|
io_->readOrThrow(payload.data(), payload.size(), Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
// Fetch width
|
|
|
|
memcpy(&size_buf, payload.c_data(4), 3);
|
|
|
|
size_buf[3] = 0;
|
|
|
|
pixelWidth_ = Exiv2::getULong(size_buf, littleEndian) + 1;
|
|
|
|
|
|
|
|
// Fetch height
|
|
|
|
memcpy(&size_buf, payload.c_data(7), 3);
|
|
|
|
size_buf[3] = 0;
|
|
|
|
pixelHeight_ = Exiv2::getULong(size_buf, littleEndian) + 1;
|
|
|
|
} else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8) && !has_canvas_data) {
|
|
|
|
enforce(size >= 10, Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
has_canvas_data = true;
|
|
|
|
io_->readOrThrow(payload.data(), payload.size(), Exiv2::kerCorruptedMetadata);
|
|
|
|
byte size_buf[WEBP_TAG_SIZE];
|
|
|
|
|
|
|
|
// Fetch width""
|
|
|
|
memcpy(&size_buf, payload.c_data(6), 2);
|
|
|
|
size_buf[2] = 0;
|
|
|
|
size_buf[3] = 0;
|
|
|
|
pixelWidth_ = Exiv2::getULong(size_buf, littleEndian) & 0x3fff;
|
|
|
|
|
|
|
|
// Fetch height
|
|
|
|
memcpy(&size_buf, payload.c_data(8), 2);
|
|
|
|
size_buf[2] = 0;
|
|
|
|
size_buf[3] = 0;
|
|
|
|
pixelHeight_ = Exiv2::getULong(size_buf, littleEndian) & 0x3fff;
|
|
|
|
} else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_VP8L) && !has_canvas_data) {
|
|
|
|
enforce(size >= 5, Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
has_canvas_data = true;
|
|
|
|
byte size_buf_w[2];
|
|
|
|
byte size_buf_h[3];
|
|
|
|
|
|
|
|
io_->readOrThrow(payload.data(), payload.size(), Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
// Fetch width
|
|
|
|
memcpy(&size_buf_w, payload.c_data(1), 2);
|
|
|
|
size_buf_w[1] &= 0x3F;
|
|
|
|
pixelWidth_ = Exiv2::getUShort(size_buf_w, littleEndian) + 1;
|
|
|
|
|
|
|
|
// Fetch height
|
|
|
|
memcpy(&size_buf_h, payload.c_data(2), 3);
|
|
|
|
size_buf_h[0] = ((size_buf_h[0] >> 6) & 0x3) | ((size_buf_h[1] & 0x3F) << 0x2);
|
|
|
|
size_buf_h[1] = ((size_buf_h[1] >> 6) & 0x3) | ((size_buf_h[2] & 0xF) << 0x2);
|
|
|
|
pixelHeight_ = Exiv2::getUShort(size_buf_h, littleEndian) + 1;
|
|
|
|
} else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ANMF) && !has_canvas_data) {
|
|
|
|
enforce(size >= 12, Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
has_canvas_data = true;
|
|
|
|
byte size_buf[WEBP_TAG_SIZE];
|
|
|
|
|
|
|
|
io_->readOrThrow(payload.data(), payload.size(), Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
// Fetch width
|
|
|
|
memcpy(&size_buf, payload.c_data(6), 3);
|
|
|
|
size_buf[3] = 0;
|
|
|
|
pixelWidth_ = Exiv2::getULong(size_buf, littleEndian) + 1;
|
|
|
|
|
|
|
|
// Fetch height
|
|
|
|
memcpy(&size_buf, payload.c_data(9), 3);
|
|
|
|
size_buf[3] = 0;
|
|
|
|
pixelHeight_ = Exiv2::getULong(size_buf, littleEndian) + 1;
|
|
|
|
} else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_ICCP)) {
|
|
|
|
io_->readOrThrow(payload.data(), payload.size(), Exiv2::kerCorruptedMetadata);
|
|
|
|
this->setIccProfile(std::move(payload));
|
|
|
|
} else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_EXIF)) {
|
|
|
|
io_->readOrThrow(payload.data(), payload.size(), Exiv2::kerCorruptedMetadata);
|
|
|
|
|
|
|
|
byte size_buff2[2];
|
|
|
|
// 4 meaningful bytes + 2 padding bytes
|
|
|
|
byte exifLongHeader[] = { 0xFF, 0x01, 0xFF, 0xE1, 0x00, 0x00 };
|
|
|
|
byte exifShortHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 };
|
|
|
|
byte exifTiffLEHeader[] = { 0x49, 0x49, 0x2A }; // "MM*"
|
|
|
|
byte exifTiffBEHeader[] = { 0x4D, 0x4D, 0x00, 0x2A }; // "II\0*"
|
|
|
|
size_t offset = 0;
|
|
|
|
bool s_header = false;
|
|
|
|
bool le_header = false;
|
|
|
|
bool be_header = false;
|
|
|
|
long pos = getHeaderOffset(payload.c_data(), static_cast<long>(payload.size()), reinterpret_cast<byte*>(&exifLongHeader), 4);
|
|
|
|
|
|
|
|
if (pos == -1) {
|
|
|
|
pos = getHeaderOffset(payload.c_data(), static_cast<long>(payload.size()), reinterpret_cast<byte*>(&exifLongHeader), 6);
|
|
|
|
if (pos != -1) {
|
|
|
|
s_header = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pos == -1) {
|
|
|
|
pos = getHeaderOffset(payload.c_data(), static_cast<long>(payload.size()), reinterpret_cast<byte*>(&exifTiffLEHeader), 3);
|
|
|
|
if (pos != -1) {
|
|
|
|
le_header = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pos == -1) {
|
|
|
|
pos = getHeaderOffset(payload.c_data(), static_cast<long>(payload.size()), reinterpret_cast<byte*>(&exifTiffBEHeader), 4);
|
|
|
|
if (pos != -1) {
|
|
|
|
be_header = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s_header) {
|
|
|
|
offset += 6;
|
|
|
|
}
|
|
|
|
if (be_header || le_header) {
|
|
|
|
offset += 12;
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t sizePayload = Safe::add(payload.size(), offset);
|
|
|
|
DataBuf rawExifData(sizePayload);
|
|
|
|
|
|
|
|
if (s_header) {
|
|
|
|
us2Data(size_buff2, static_cast<uint16_t>(sizePayload - 6), bigEndian);
|
|
|
|
rawExifData.copyBytes(0, reinterpret_cast<char*>(&exifLongHeader), 4);
|
|
|
|
rawExifData.copyBytes(4, reinterpret_cast<char*>(&size_buff2), 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (be_header || le_header) {
|
|
|
|
us2Data(size_buff2, static_cast<uint16_t>(sizePayload - 6), bigEndian);
|
|
|
|
rawExifData.copyBytes(0, reinterpret_cast<char*>(&exifLongHeader), 4);
|
|
|
|
rawExifData.copyBytes(4, reinterpret_cast<char*>(&size_buff2), 2);
|
|
|
|
rawExifData.copyBytes(6, reinterpret_cast<char*>(&exifShortHeader), 6);
|
|
|
|
}
|
|
|
|
|
|
|
|
rawExifData.copyBytes(offset, payload.c_data(), payload.size());
|
|
|
|
|
|
|
|
#ifdef EXIV2_DEBUG_MESSAGES
|
|
|
|
std::cout << "Display Hex Dump [size:" << static_cast<unsigned long>(sizePayload) << "]" << std::endl;
|
|
|
|
std::cout << binaryToHex(rawExifData.c_data(), sizePayload);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (pos != -1) {
|
|
|
|
XmpData xmpData;
|
|
|
|
ByteOrder bo = ExifParser::decode(exifData_,
|
|
|
|
payload.c_data(pos),
|
|
|
|
payload.size() - pos);
|
|
|
|
setByteOrder(bo);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
#ifndef SUPPRESS_WARNINGS
|
|
|
|
EXV_WARNING << "Failed to decode Exif metadata." << std::endl;
|
|
|
|
#endif
|
|
|
|
exifData_.clear();
|
|
|
|
}
|
|
|
|
} else if (equalsWebPTag(chunkId, WEBP_CHUNK_HEADER_XMP)) {
|
|
|
|
io_->readOrThrow(payload.data(), payload.size(), Exiv2::kerCorruptedMetadata);
|
|
|
|
xmpPacket_.assign(payload.c_str(), payload.size());
|
|
|
|
if (!xmpPacket_.empty() && XmpParser::decode(xmpData_, xmpPacket_)) {
|
|
|
|
#ifndef SUPPRESS_WARNINGS
|
|
|
|
EXV_WARNING << "Failed to decode XMP metadata." << std::endl;
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
#ifdef EXIV2_DEBUG_MESSAGES
|
|
|
|
std::cout << "Display Hex Dump [size:" << static_cast<unsigned long>(payload.size()) << "]"
|
|
|
|
<< std::endl;
|
|
|
|
std::cout << binaryToHex(payload.c_data(), payload.size());
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
io_->seek(size, BasicIo::cur);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( io_->tell() % 2 ) io_->seek(+1, BasicIo::cur);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* =========================================== */
|
|
|
|
|
|
|
|
Image::UniquePtr newWebPInstance(BasicIo::UniquePtr io, bool /*create*/)
|
|
|
|
{
|
|
|
|
auto image = std::make_unique<WebPImage>(std::move(io));
|
|
|
|
if (!image->good()) {
|
|
|
|
image.reset();
|
|
|
|
}
|
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool isWebPType(BasicIo& iIo, bool /*advance*/)
|
|
|
|
{
|
|
|
|
if (iIo.size() < 12) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const int32_t len = 4;
|
|
|
|
const unsigned char RiffImageId[4] = { 'R', 'I', 'F' ,'F'};
|
|
|
|
const unsigned char WebPImageId[4] = { 'W', 'E', 'B' ,'P'};
|
|
|
|
byte webp[len];
|
|
|
|
byte data[len];
|
|
|
|
byte riff[len];
|
|
|
|
iIo.readOrThrow(riff, len, Exiv2::kerCorruptedMetadata);
|
|
|
|
iIo.readOrThrow(data, len, Exiv2::kerCorruptedMetadata);
|
|
|
|
iIo.readOrThrow(webp, len, Exiv2::kerCorruptedMetadata);
|
|
|
|
bool matched_riff = (memcmp(riff, RiffImageId, len) == 0);
|
|
|
|
bool matched_webp = (memcmp(webp, WebPImageId, len) == 0);
|
|
|
|
iIo.seek(-12, BasicIo::cur);
|
|
|
|
return matched_riff && matched_webp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Function used to check equality of a Tags with a
|
|
|
|
particular string (ignores case while comparing).
|
|
|
|
@param buf Data buffer that will contain Tag to compare
|
|
|
|
@param str char* Pointer to string
|
|
|
|
@return Returns true if the buffer value is equal to string.
|
|
|
|
*/
|
|
|
|
bool WebPImage::equalsWebPTag(Exiv2::DataBuf& buf, const char* str) {
|
|
|
|
for(int i = 0; i < 4; i++ )
|
|
|
|
if(toupper(buf.read_uint8(i)) != str[i])
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
@brief Function used to add missing EXIF & XMP flags
|
|
|
|
to the feature section.
|
|
|
|
@param iIo get BasicIo pointer to inject data
|
|
|
|
@param has_xmp Verify if we have xmp data and set required flag
|
|
|
|
@param has_exif Verify if we have exif data and set required flag
|
|
|
|
@return Returns void
|
|
|
|
*/
|
|
|
|
void WebPImage::inject_VP8X(BasicIo& iIo, bool has_xmp,
|
|
|
|
bool has_exif, bool has_alpha,
|
|
|
|
bool has_icc, int width, int height) {
|
|
|
|
byte size[4] = { 0x0A, 0x00, 0x00, 0x00 };
|
|
|
|
byte data[10] = { 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
|
|
iIo.write(reinterpret_cast<const byte*>(WEBP_CHUNK_HEADER_VP8X), WEBP_TAG_SIZE);
|
|
|
|
iIo.write(size, WEBP_TAG_SIZE);
|
|
|
|
|
|
|
|
if (has_alpha) {
|
|
|
|
data[0] |= WEBP_VP8X_ALPHA_BIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_icc) {
|
|
|
|
data[0] |= WEBP_VP8X_ICC_BIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_xmp) {
|
|
|
|
data[0] |= WEBP_VP8X_XMP_BIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_exif) {
|
|
|
|
data[0] |= WEBP_VP8X_EXIF_BIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set width - stored in 24bits*/
|
|
|
|
int w = width - 1;
|
|
|
|
data[4] = w & 0xFF;
|
|
|
|
data[5] = (w >> 8) & 0xFF;
|
|
|
|
data[6] = (w >> 16) & 0xFF;
|
|
|
|
|
|
|
|
/* set height - stored in 24bits */
|
|
|
|
int h = height - 1;
|
|
|
|
data[7] = h & 0xFF;
|
|
|
|
data[8] = (h >> 8) & 0xFF;
|
|
|
|
data[9] = (h >> 16) & 0xFF;
|
|
|
|
|
|
|
|
iIo.write(data, 10);
|
|
|
|
|
|
|
|
/* Handle inject an icc profile right after VP8X chunk */
|
|
|
|
if (has_icc) {
|
|
|
|
byte size_buff[WEBP_TAG_SIZE];
|
|
|
|
ul2Data(size_buff, static_cast<uint32_t>(iccProfile_.size()), littleEndian);
|
|
|
|
if (iIo.write(reinterpret_cast<const byte*>(WEBP_CHUNK_HEADER_VP8X), WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
if (iIo.write(size_buff, WEBP_TAG_SIZE) != WEBP_TAG_SIZE)
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
if (iIo.write(iccProfile_.c_data(), iccProfile_.size()) != iccProfile_.size())
|
|
|
|
throw Error(kerImageWriteFailed);
|
|
|
|
if (iIo.tell() % 2) {
|
|
|
|
if (iIo.write(&WEBP_PAD_ODD, 1) != 1) throw Error(kerImageWriteFailed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
long WebPImage::getHeaderOffset(const byte* data, long data_size, const byte* header, long header_size)
|
|
|
|
{
|
|
|
|
if (data_size < header_size) { return -1; }
|
|
|
|
long pos = -1;
|
|
|
|
for (long i=0; i < data_size - header_size; i++) {
|
|
|
|
if (memcmp(header, &data[i], header_size) == 0) {
|
|
|
|
pos = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Exiv2
|