diff options
Diffstat (limited to 'drivers/webp/mux')
-rw-r--r-- | drivers/webp/mux/demux.c | 902 | ||||
-rw-r--r-- | drivers/webp/mux/muxedit.c | 662 | ||||
-rw-r--r-- | drivers/webp/mux/muxi.h | 161 | ||||
-rw-r--r-- | drivers/webp/mux/muxinternal.c | 323 | ||||
-rw-r--r-- | drivers/webp/mux/muxread.c | 437 |
5 files changed, 1692 insertions, 793 deletions
diff --git a/drivers/webp/mux/demux.c b/drivers/webp/mux/demux.c new file mode 100644 index 0000000000..4519f7d55b --- /dev/null +++ b/drivers/webp/mux/demux.c @@ -0,0 +1,902 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// This code is licensed under the same terms as WebM: +// Software License Agreement: http://www.webmproject.org/license/software/ +// Additional IP Rights Grant: http://www.webmproject.org/license/additional/ +// ----------------------------------------------------------------------------- +// +// WebP container demux. +// + +#include "../webp/mux.h" + +#include <stdlib.h> +#include <string.h> + +#include "../webp/decode.h" // WebPGetInfo +#include "../webp/format_constants.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +#define MKFOURCC(a, b, c, d) ((uint32_t)(a) | (b) << 8 | (c) << 16 | (d) << 24) + +typedef struct { + size_t start_; // start location of the data + size_t end_; // end location + size_t riff_end_; // riff chunk end location, can be > end_. + size_t buf_size_; // size of the buffer + const uint8_t* buf_; +} MemBuffer; + +typedef struct { + size_t offset_; + size_t size_; +} ChunkData; + +typedef struct Frame { + int x_offset_, y_offset_; + int width_, height_; + int duration_; + int is_tile_; // this is an image fragment from a 'TILE'. + int frame_num_; // the referent frame number for use in assembling tiles. + int complete_; // img_components_ contains a full image. + ChunkData img_components_[2]; // 0=VP8{,L} 1=ALPH + struct Frame* next_; +} Frame; + +typedef struct Chunk { + ChunkData data_; + struct Chunk* next_; +} Chunk; + +struct WebPDemuxer { + MemBuffer mem_; + WebPDemuxState state_; + int is_ext_format_; + uint32_t feature_flags_; + int canvas_width_, canvas_height_; + int loop_count_; + int num_frames_; + Frame* frames_; + Chunk* chunks_; // non-image chunks +}; + +typedef enum { + PARSE_OK, + PARSE_NEED_MORE_DATA, + PARSE_ERROR +} ParseStatus; + +typedef struct ChunkParser { + uint8_t id[4]; + ParseStatus (*parse)(WebPDemuxer* const dmux); + int (*valid)(const WebPDemuxer* const dmux); +} ChunkParser; + +static ParseStatus ParseSingleImage(WebPDemuxer* const dmux); +static ParseStatus ParseVP8X(WebPDemuxer* const dmux); +static int IsValidSimpleFormat(const WebPDemuxer* const dmux); +static int IsValidExtendedFormat(const WebPDemuxer* const dmux); + +static const ChunkParser kMasterChunks[] = { + { { 'V', 'P', '8', ' ' }, ParseSingleImage, IsValidSimpleFormat }, + { { 'V', 'P', '8', 'L' }, ParseSingleImage, IsValidSimpleFormat }, + { { 'V', 'P', '8', 'X' }, ParseVP8X, IsValidExtendedFormat }, + { { '0', '0', '0', '0' }, NULL, NULL }, +}; + +// ----------------------------------------------------------------------------- +// MemBuffer + +static int RemapMemBuffer(MemBuffer* const mem, + const uint8_t* data, size_t size) { + if (size < mem->buf_size_) return 0; // can't remap to a shorter buffer! + + mem->buf_ = data; + mem->end_ = mem->buf_size_ = size; + return 1; +} + +static int InitMemBuffer(MemBuffer* const mem, + const uint8_t* data, size_t size) { + memset(mem, 0, sizeof(*mem)); + return RemapMemBuffer(mem, data, size); +} + +// Return the remaining data size available in 'mem'. +static WEBP_INLINE size_t MemDataSize(const MemBuffer* const mem) { + return (mem->end_ - mem->start_); +} + +// Return true if 'size' exceeds the end of the RIFF chunk. +static WEBP_INLINE int SizeIsInvalid(const MemBuffer* const mem, size_t size) { + return (size > mem->riff_end_ - mem->start_); +} + +static WEBP_INLINE void Skip(MemBuffer* const mem, size_t size) { + mem->start_ += size; +} + +static WEBP_INLINE void Rewind(MemBuffer* const mem, size_t size) { + mem->start_ -= size; +} + +static WEBP_INLINE const uint8_t* GetBuffer(MemBuffer* const mem) { + return mem->buf_ + mem->start_; +} + +static WEBP_INLINE uint8_t GetByte(MemBuffer* const mem) { + const uint8_t byte = mem->buf_[mem->start_]; + Skip(mem, 1); + return byte; +} + +// Read 16, 24 or 32 bits stored in little-endian order. +static WEBP_INLINE int ReadLE16s(const uint8_t* const data) { + return (int)(data[0] << 0) | (data[1] << 8); +} + +static WEBP_INLINE int ReadLE24s(const uint8_t* const data) { + return ReadLE16s(data) | (data[2] << 16); +} + +static WEBP_INLINE uint32_t ReadLE32(const uint8_t* const data) { + return (uint32_t)ReadLE24s(data) | (data[3] << 24); +} + +// In addition to reading, skip the read bytes. +static WEBP_INLINE int GetLE16s(MemBuffer* const mem) { + const uint8_t* const data = mem->buf_ + mem->start_; + const int val = ReadLE16s(data); + Skip(mem, 2); + return val; +} + +static WEBP_INLINE int GetLE24s(MemBuffer* const mem) { + const uint8_t* const data = mem->buf_ + mem->start_; + const int val = ReadLE24s(data); + Skip(mem, 3); + return val; +} + +static WEBP_INLINE uint32_t GetLE32(MemBuffer* const mem) { + const uint8_t* const data = mem->buf_ + mem->start_; + const uint32_t val = ReadLE32(data); + Skip(mem, 4); + return val; +} + +// ----------------------------------------------------------------------------- +// Secondary chunk parsing + +static void AddChunk(WebPDemuxer* const dmux, Chunk* const chunk) { + Chunk** c = &dmux->chunks_; + while (*c != NULL) c = &(*c)->next_; + *c = chunk; + chunk->next_ = NULL; +} + +// Add a frame to the end of the list, ensuring the last frame is complete. +// Returns true on success, false otherwise. +static int AddFrame(WebPDemuxer* const dmux, Frame* const frame) { + const Frame* last_frame = NULL; + Frame** f = &dmux->frames_; + while (*f != NULL) { + last_frame = *f; + f = &(*f)->next_; + } + if (last_frame != NULL && !last_frame->complete_) return 0; + *f = frame; + frame->next_ = NULL; + return 1; +} + +// Store image bearing chunks to 'frame'. +static ParseStatus StoreFrame(int frame_num, MemBuffer* const mem, + Frame* const frame) { + int alpha_chunks = 0; + int image_chunks = 0; + int done = (MemDataSize(mem) < CHUNK_HEADER_SIZE); + ParseStatus status = PARSE_OK; + + if (done) return PARSE_NEED_MORE_DATA; + + do { + const size_t chunk_start_offset = mem->start_; + const uint32_t fourcc = GetLE32(mem); + const uint32_t payload_size = GetLE32(mem); + const uint32_t payload_size_padded = payload_size + (payload_size & 1); + const size_t payload_available = (payload_size_padded > MemDataSize(mem)) + ? MemDataSize(mem) : payload_size_padded; + const size_t chunk_size = CHUNK_HEADER_SIZE + payload_available; + + if (payload_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR; + if (SizeIsInvalid(mem, payload_size_padded)) return PARSE_ERROR; + if (payload_size_padded > MemDataSize(mem)) status = PARSE_NEED_MORE_DATA; + + switch (fourcc) { + case MKFOURCC('A', 'L', 'P', 'H'): + if (alpha_chunks == 0) { + ++alpha_chunks; + frame->img_components_[1].offset_ = chunk_start_offset; + frame->img_components_[1].size_ = chunk_size; + frame->frame_num_ = frame_num; + Skip(mem, payload_available); + } else { + goto Done; + } + break; + case MKFOURCC('V', 'P', '8', ' '): + case MKFOURCC('V', 'P', '8', 'L'): + if (image_chunks == 0) { + int width = 0, height = 0; + ++image_chunks; + frame->img_components_[0].offset_ = chunk_start_offset; + frame->img_components_[0].size_ = chunk_size; + // Extract the width and height from the bitstream, tolerating + // failures when the data is incomplete. + if (!WebPGetInfo(mem->buf_ + frame->img_components_[0].offset_, + frame->img_components_[0].size_, &width, &height) && + status != PARSE_NEED_MORE_DATA) { + return PARSE_ERROR; + } + + frame->width_ = width; + frame->height_ = height; + frame->frame_num_ = frame_num; + frame->complete_ = (status == PARSE_OK); + Skip(mem, payload_available); + } else { + goto Done; + } + break; + Done: + default: + // Restore fourcc/size when moving up one level in parsing. + Rewind(mem, CHUNK_HEADER_SIZE); + done = 1; + break; + } + + if (mem->start_ == mem->riff_end_) { + done = 1; + } else if (MemDataSize(mem) < CHUNK_HEADER_SIZE) { + status = PARSE_NEED_MORE_DATA; + } + } while (!done && status == PARSE_OK); + + return status; +} + +// Creates a new Frame if 'actual_size' is within bounds and 'mem' contains +// enough data ('min_size') to parse the payload. +// Returns PARSE_OK on success with *frame pointing to the new Frame. +// Returns PARSE_NEED_MORE_DATA with insufficient data, PARSE_ERROR otherwise. +static ParseStatus NewFrame(const MemBuffer* const mem, + uint32_t min_size, uint32_t expected_size, + uint32_t actual_size, Frame** frame) { + if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR; + if (actual_size < expected_size) return PARSE_ERROR; + if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA; + + *frame = (Frame*)calloc(1, sizeof(**frame)); + return (*frame == NULL) ? PARSE_ERROR : PARSE_OK; +} + +// Parse a 'FRM ' chunk and any image bearing chunks that immediately follow. +// 'frame_chunk_size' is the previously validated, padded chunk size. +static ParseStatus ParseFrame( + WebPDemuxer* const dmux, uint32_t frame_chunk_size) { + const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG); + const uint32_t min_size = frame_chunk_size + CHUNK_HEADER_SIZE; + int added_frame = 0; + MemBuffer* const mem = &dmux->mem_; + Frame* frame; + ParseStatus status = + NewFrame(mem, min_size, FRAME_CHUNK_SIZE, frame_chunk_size, &frame); + if (status != PARSE_OK) return status; + + frame->x_offset_ = 2 * GetLE24s(mem); + frame->y_offset_ = 2 * GetLE24s(mem); + frame->width_ = 1 + GetLE24s(mem); + frame->height_ = 1 + GetLE24s(mem); + frame->duration_ = 1 + GetLE24s(mem); + Skip(mem, frame_chunk_size - FRAME_CHUNK_SIZE); // skip any trailing data. + if (frame->width_ * (uint64_t)frame->height_ >= MAX_IMAGE_AREA) { + return PARSE_ERROR; + } + + // Store a (potentially partial) frame only if the animation flag is set + // and there is some data in 'frame'. + status = StoreFrame(dmux->num_frames_ + 1, mem, frame); + if (status != PARSE_ERROR && has_frames && frame->frame_num_ > 0) { + added_frame = AddFrame(dmux, frame); + if (added_frame) { + ++dmux->num_frames_; + } else { + status = PARSE_ERROR; + } + } + + if (!added_frame) free(frame); + return status; +} + +// Parse a 'TILE' chunk and any image bearing chunks that immediately follow. +// 'tile_chunk_size' is the previously validated, padded chunk size. +static ParseStatus ParseTile(WebPDemuxer* const dmux, + uint32_t tile_chunk_size) { + const int has_tiles = !!(dmux->feature_flags_ & TILE_FLAG); + const uint32_t min_size = tile_chunk_size + CHUNK_HEADER_SIZE; + int added_tile = 0; + MemBuffer* const mem = &dmux->mem_; + Frame* frame; + ParseStatus status = + NewFrame(mem, min_size, TILE_CHUNK_SIZE, tile_chunk_size, &frame); + if (status != PARSE_OK) return status; + + frame->is_tile_ = 1; + frame->x_offset_ = 2 * GetLE24s(mem); + frame->y_offset_ = 2 * GetLE24s(mem); + Skip(mem, tile_chunk_size - TILE_CHUNK_SIZE); // skip any trailing data. + + // Store a (potentially partial) tile only if the tile flag is set + // and the tile contains some data. + status = StoreFrame(dmux->num_frames_, mem, frame); + if (status != PARSE_ERROR && has_tiles && frame->frame_num_ > 0) { + // Note num_frames_ is incremented only when all tiles have been consumed. + added_tile = AddFrame(dmux, frame); + if (!added_tile) status = PARSE_ERROR; + } + + if (!added_tile) free(frame); + return status; +} + +// General chunk storage starting with the header at 'start_offset' allowing +// the user to request the payload via a fourcc string. 'size' includes the +// header and the unpadded payload size. +// Returns true on success, false otherwise. +static int StoreChunk(WebPDemuxer* const dmux, + size_t start_offset, uint32_t size) { + Chunk* const chunk = (Chunk*)calloc(1, sizeof(*chunk)); + if (chunk == NULL) return 0; + + chunk->data_.offset_ = start_offset; + chunk->data_.size_ = size; + AddChunk(dmux, chunk); + return 1; +} + +// ----------------------------------------------------------------------------- +// Primary chunk parsing + +static int ReadHeader(MemBuffer* const mem) { + const size_t min_size = RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE; + uint32_t riff_size; + + // Basic file level validation. + if (MemDataSize(mem) < min_size) return 0; + if (memcmp(GetBuffer(mem), "RIFF", CHUNK_SIZE_BYTES) || + memcmp(GetBuffer(mem) + CHUNK_HEADER_SIZE, "WEBP", CHUNK_SIZE_BYTES)) { + return 0; + } + + riff_size = ReadLE32(GetBuffer(mem) + TAG_SIZE); + if (riff_size < CHUNK_HEADER_SIZE) return 0; + if (riff_size > MAX_CHUNK_PAYLOAD) return 0; + + // There's no point in reading past the end of the RIFF chunk + mem->riff_end_ = riff_size + CHUNK_HEADER_SIZE; + if (mem->buf_size_ > mem->riff_end_) { + mem->buf_size_ = mem->end_ = mem->riff_end_; + } + + Skip(mem, RIFF_HEADER_SIZE); + return 1; +} + +static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) { + const size_t min_size = CHUNK_HEADER_SIZE; + MemBuffer* const mem = &dmux->mem_; + Frame* frame; + ParseStatus status; + + if (dmux->frames_ != NULL) return PARSE_ERROR; + if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR; + if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA; + + frame = (Frame*)calloc(1, sizeof(*frame)); + if (frame == NULL) return PARSE_ERROR; + + status = StoreFrame(1, &dmux->mem_, frame); + if (status != PARSE_ERROR) { + const int has_alpha = !!(dmux->feature_flags_ & ALPHA_FLAG); + // Clear any alpha when the alpha flag is missing. + if (!has_alpha && frame->img_components_[1].size_ > 0) { + frame->img_components_[1].offset_ = 0; + frame->img_components_[1].size_ = 0; + } + + // Use the frame width/height as the canvas values for non-vp8x files. + if (!dmux->is_ext_format_ && frame->width_ > 0 && frame->height_ > 0) { + dmux->state_ = WEBP_DEMUX_PARSED_HEADER; + dmux->canvas_width_ = frame->width_; + dmux->canvas_height_ = frame->height_; + } + AddFrame(dmux, frame); + dmux->num_frames_ = 1; + } else { + free(frame); + } + + return status; +} + +static ParseStatus ParseVP8X(WebPDemuxer* const dmux) { + MemBuffer* const mem = &dmux->mem_; + int loop_chunks = 0; + uint32_t vp8x_size; + ParseStatus status = PARSE_OK; + + if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA; + + dmux->is_ext_format_ = 1; + Skip(mem, TAG_SIZE); // VP8X + vp8x_size = GetLE32(mem); + if (vp8x_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR; + if (vp8x_size < VP8X_CHUNK_SIZE) return PARSE_ERROR; + vp8x_size += vp8x_size & 1; + if (SizeIsInvalid(mem, vp8x_size)) return PARSE_ERROR; + if (MemDataSize(mem) < vp8x_size) return PARSE_NEED_MORE_DATA; + + dmux->feature_flags_ = GetByte(mem); + Skip(mem, 3); // Reserved. + dmux->canvas_width_ = 1 + GetLE24s(mem); + dmux->canvas_height_ = 1 + GetLE24s(mem); + if (dmux->canvas_width_ * (uint64_t)dmux->canvas_height_ >= MAX_IMAGE_AREA) { + return PARSE_ERROR; // image final dimension is too large + } + Skip(mem, vp8x_size - VP8X_CHUNK_SIZE); // skip any trailing data. + dmux->state_ = WEBP_DEMUX_PARSED_HEADER; + + if (SizeIsInvalid(mem, CHUNK_HEADER_SIZE)) return PARSE_ERROR; + if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA; + + do { + int store_chunk = 1; + const size_t chunk_start_offset = mem->start_; + const uint32_t fourcc = GetLE32(mem); + const uint32_t chunk_size = GetLE32(mem); + const uint32_t chunk_size_padded = chunk_size + (chunk_size & 1); + + if (chunk_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR; + if (SizeIsInvalid(mem, chunk_size_padded)) return PARSE_ERROR; + + switch (fourcc) { + case MKFOURCC('V', 'P', '8', 'X'): { + return PARSE_ERROR; + } + case MKFOURCC('A', 'L', 'P', 'H'): + case MKFOURCC('V', 'P', '8', ' '): + case MKFOURCC('V', 'P', '8', 'L'): { + Rewind(mem, CHUNK_HEADER_SIZE); + status = ParseSingleImage(dmux); + break; + } + case MKFOURCC('L', 'O', 'O', 'P'): { + if (chunk_size_padded < LOOP_CHUNK_SIZE) return PARSE_ERROR; + + if (MemDataSize(mem) < chunk_size_padded) { + status = PARSE_NEED_MORE_DATA; + } else if (loop_chunks == 0) { + ++loop_chunks; + dmux->loop_count_ = GetLE16s(mem); + Skip(mem, chunk_size_padded - LOOP_CHUNK_SIZE); + } else { + store_chunk = 0; + goto Skip; + } + break; + } + case MKFOURCC('F', 'R', 'M', ' '): { + status = ParseFrame(dmux, chunk_size_padded); + break; + } + case MKFOURCC('T', 'I', 'L', 'E'): { + if (dmux->num_frames_ == 0) dmux->num_frames_ = 1; + status = ParseTile(dmux, chunk_size_padded); + break; + } + case MKFOURCC('I', 'C', 'C', 'P'): { + store_chunk = !!(dmux->feature_flags_ & ICCP_FLAG); + goto Skip; + } + case MKFOURCC('M', 'E', 'T', 'A'): { + store_chunk = !!(dmux->feature_flags_ & META_FLAG); + goto Skip; + } + Skip: + default: { + if (chunk_size_padded <= MemDataSize(mem)) { + if (store_chunk) { + // Store only the chunk header and unpadded size as only the payload + // will be returned to the user. + if (!StoreChunk(dmux, chunk_start_offset, + CHUNK_HEADER_SIZE + chunk_size)) { + return PARSE_ERROR; + } + } + Skip(mem, chunk_size_padded); + } else { + status = PARSE_NEED_MORE_DATA; + } + } + } + + if (mem->start_ == mem->riff_end_) { + break; + } else if (MemDataSize(mem) < CHUNK_HEADER_SIZE) { + status = PARSE_NEED_MORE_DATA; + } + } while (status == PARSE_OK); + + return status; +} + +// ----------------------------------------------------------------------------- +// Format validation + +static int IsValidSimpleFormat(const WebPDemuxer* const dmux) { + const Frame* const frame = dmux->frames_; + if (dmux->state_ == WEBP_DEMUX_PARSING_HEADER) return 1; + + if (dmux->canvas_width_ <= 0 || dmux->canvas_height_ <= 0) return 0; + if (dmux->state_ == WEBP_DEMUX_DONE && frame == NULL) return 0; + + if (frame->width_ <= 0 || frame->height_ <= 0) return 0; + return 1; +} + +static int IsValidExtendedFormat(const WebPDemuxer* const dmux) { + const int has_tiles = !!(dmux->feature_flags_ & TILE_FLAG); + const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG); + const Frame* f; + + if (dmux->state_ == WEBP_DEMUX_PARSING_HEADER) return 1; + + if (dmux->canvas_width_ <= 0 || dmux->canvas_height_ <= 0) return 0; + if (dmux->loop_count_ < 0) return 0; + if (dmux->state_ == WEBP_DEMUX_DONE && dmux->frames_ == NULL) return 0; + + for (f = dmux->frames_; f != NULL; f = f->next_) { + const int cur_frame_set = f->frame_num_; + int frame_count = 0, tile_count = 0; + + // Check frame properties and if the image is composed of tiles that each + // fragment came from a 'TILE'. + for (; f != NULL && f->frame_num_ == cur_frame_set; f = f->next_) { + const ChunkData* const image = f->img_components_; + const ChunkData* const alpha = f->img_components_ + 1; + + if (!has_tiles && f->is_tile_) return 0; + if (!has_frames && f->frame_num_ > 1) return 0; + if (f->x_offset_ < 0 || f->y_offset_ < 0) return 0; + if (f->complete_) { + if (alpha->size_ == 0 && image->size_ == 0) return 0; + // Ensure alpha precedes image bitstream. + if (alpha->size_ > 0 && alpha->offset_ > image->offset_) { + return 0; + } + + if (f->width_ <= 0 || f->height_ <= 0) return 0; + } else { + // Ensure alpha precedes image bitstream. + if (alpha->size_ > 0 && image->size_ > 0 && + alpha->offset_ > image->offset_) { + return 0; + } + // There shouldn't be any frames after an incomplete one. + if (f->next_ != NULL) return 0; + } + + tile_count += f->is_tile_; + ++frame_count; + } + if (!has_tiles && frame_count > 1) return 0; + if (tile_count > 0 && frame_count != tile_count) return 0; + if (f == NULL) break; + } + return 1; +} + +// ----------------------------------------------------------------------------- +// WebPDemuxer object + +static void InitDemux(WebPDemuxer* const dmux, const MemBuffer* const mem) { + dmux->state_ = WEBP_DEMUX_PARSING_HEADER; + dmux->loop_count_ = 1; + dmux->canvas_width_ = -1; + dmux->canvas_height_ = -1; + dmux->mem_ = *mem; +} + +WebPDemuxer* WebPDemuxInternal(const WebPData* data, int allow_partial, + WebPDemuxState* state, int version) { + const ChunkParser* parser; + int partial; + ParseStatus status = PARSE_ERROR; + MemBuffer mem; + WebPDemuxer* dmux; + + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DEMUX_ABI_VERSION)) return NULL; + if (data == NULL || data->bytes_ == NULL || data->size_ == 0) return NULL; + + if (!InitMemBuffer(&mem, data->bytes_, data->size_)) return NULL; + if (!ReadHeader(&mem)) return NULL; + + partial = (mem.buf_size_ < mem.riff_end_); + if (!allow_partial && partial) return NULL; + + dmux = (WebPDemuxer*)calloc(1, sizeof(*dmux)); + if (dmux == NULL) return NULL; + InitDemux(dmux, &mem); + + for (parser = kMasterChunks; parser->parse != NULL; ++parser) { + if (!memcmp(parser->id, GetBuffer(&dmux->mem_), TAG_SIZE)) { + status = parser->parse(dmux); + if (status == PARSE_OK) dmux->state_ = WEBP_DEMUX_DONE; + if (status != PARSE_ERROR && !parser->valid(dmux)) status = PARSE_ERROR; + break; + } + } + if (state) *state = dmux->state_; + + if (status == PARSE_ERROR) { + WebPDemuxDelete(dmux); + return NULL; + } + return dmux; +} + +void WebPDemuxDelete(WebPDemuxer* dmux) { + Chunk* c; + Frame* f; + if (dmux == NULL) return; + + for (f = dmux->frames_; f != NULL;) { + Frame* const cur_frame = f; + f = f->next_; + free(cur_frame); + } + for (c = dmux->chunks_; c != NULL;) { + Chunk* const cur_chunk = c; + c = c->next_; + free(cur_chunk); + } + free(dmux); +} + +// ----------------------------------------------------------------------------- + +uint32_t WebPDemuxGetI(const WebPDemuxer* dmux, WebPFormatFeature feature) { + if (dmux == NULL) return 0; + + switch (feature) { + case WEBP_FF_FORMAT_FLAGS: return dmux->feature_flags_; + case WEBP_FF_CANVAS_WIDTH: return (uint32_t)dmux->canvas_width_; + case WEBP_FF_CANVAS_HEIGHT: return (uint32_t)dmux->canvas_height_; + case WEBP_FF_LOOP_COUNT: return (uint32_t)dmux->loop_count_; + } + return 0; +} + +// ----------------------------------------------------------------------------- +// Frame iteration + +// Find the first 'frame_num' frame. There may be multiple in a tiled frame. +static const Frame* GetFrame(const WebPDemuxer* const dmux, int frame_num) { + const Frame* f; + for (f = dmux->frames_; f != NULL; f = f->next_) { + if (frame_num == f->frame_num_) break; + } + return f; +} + +// Returns tile 'tile_num' and the total count. +static const Frame* GetTile( + const Frame* const frame_set, int tile_num, int* const count) { + const int this_frame = frame_set->frame_num_; + const Frame* f = frame_set; + const Frame* tile = NULL; + int total; + + for (total = 0; f != NULL && f->frame_num_ == this_frame; f = f->next_) { + if (++total == tile_num) tile = f; + } + *count = total; + return tile; +} + +static const uint8_t* GetFramePayload(const uint8_t* const mem_buf, + const Frame* const frame, + size_t* const data_size) { + *data_size = 0; + if (frame != NULL) { + const ChunkData* const image = frame->img_components_; + const ChunkData* const alpha = frame->img_components_ + 1; + size_t start_offset = image->offset_; + *data_size = image->size_; + + // if alpha exists it precedes image, update the size allowing for + // intervening chunks. + if (alpha->size_ > 0) { + const size_t inter_size = (image->offset_ > 0) + ? image->offset_ - (alpha->offset_ + alpha->size_) + : 0; + start_offset = alpha->offset_; + *data_size += alpha->size_ + inter_size; + } + return mem_buf + start_offset; + } + return NULL; +} + +// Create a whole 'frame' from VP8 (+ alpha) or lossless. +static int SynthesizeFrame(const WebPDemuxer* const dmux, + const Frame* const first_frame, + int tile_num, WebPIterator* const iter) { + const uint8_t* const mem_buf = dmux->mem_.buf_; + int num_tiles; + size_t payload_size = 0; + const Frame* const tile = GetTile(first_frame, tile_num, &num_tiles); + const uint8_t* const payload = GetFramePayload(mem_buf, tile, &payload_size); + if (payload == NULL) return 0; + + iter->frame_num_ = first_frame->frame_num_; + iter->num_frames_ = dmux->num_frames_; + iter->tile_num_ = tile_num; + iter->num_tiles_ = num_tiles; + iter->x_offset_ = tile->x_offset_; + iter->y_offset_ = tile->y_offset_; + iter->width_ = tile->width_; + iter->height_ = tile->height_; + iter->duration_ = tile->duration_; + iter->complete_ = tile->complete_; + iter->tile_.bytes_ = payload; + iter->tile_.size_ = payload_size; + // TODO(jzern): adjust offsets for 'TILE's embedded in 'FRM 's + return 1; +} + +static int SetFrame(int frame_num, WebPIterator* const iter) { + const Frame* frame; + const WebPDemuxer* const dmux = (WebPDemuxer*)iter->private_; + if (dmux == NULL || frame_num < 0) return 0; + if (frame_num > dmux->num_frames_) return 0; + if (frame_num == 0) frame_num = dmux->num_frames_; + + frame = GetFrame(dmux, frame_num); + return SynthesizeFrame(dmux, frame, 1, iter); +} + +int WebPDemuxGetFrame(const WebPDemuxer* dmux, int frame, WebPIterator* iter) { + if (iter == NULL) return 0; + + memset(iter, 0, sizeof(*iter)); + iter->private_ = (void*)dmux; + return SetFrame(frame, iter); +} + +int WebPDemuxNextFrame(WebPIterator* iter) { + if (iter == NULL) return 0; + return SetFrame(iter->frame_num_ + 1, iter); +} + +int WebPDemuxPrevFrame(WebPIterator* iter) { + if (iter == NULL) return 0; + if (iter->frame_num_ <= 1) return 0; + return SetFrame(iter->frame_num_ - 1, iter); +} + +int WebPDemuxSelectTile(WebPIterator* iter, int tile) { + if (iter != NULL && iter->private_ != NULL && tile > 0) { + const WebPDemuxer* const dmux = (WebPDemuxer*)iter->private_; + const Frame* const frame = GetFrame(dmux, iter->frame_num_); + if (frame == NULL) return 0; + + return SynthesizeFrame(dmux, frame, tile, iter); + } + return 0; +} + +void WebPDemuxReleaseIterator(WebPIterator* iter) { + (void)iter; +} + +// ----------------------------------------------------------------------------- +// Chunk iteration + +static int ChunkCount(const WebPDemuxer* const dmux, const char fourcc[4]) { + const uint8_t* const mem_buf = dmux->mem_.buf_; + const Chunk* c; + int count = 0; + for (c = dmux->chunks_; c != NULL; c = c->next_) { + const uint8_t* const header = mem_buf + c->data_.offset_; + if (!memcmp(header, fourcc, TAG_SIZE)) ++count; + } + return count; +} + +static const Chunk* GetChunk(const WebPDemuxer* const dmux, + const char fourcc[4], int chunk_num) { + const uint8_t* const mem_buf = dmux->mem_.buf_; + const Chunk* c; + int count = 0; + for (c = dmux->chunks_; c != NULL; c = c->next_) { + const uint8_t* const header = mem_buf + c->data_.offset_; + if (!memcmp(header, fourcc, TAG_SIZE)) ++count; + if (count == chunk_num) break; + } + return c; +} + +static int SetChunk(const char fourcc[4], int chunk_num, + WebPChunkIterator* const iter) { + const WebPDemuxer* const dmux = (WebPDemuxer*)iter->private_; + int count; + + if (dmux == NULL || fourcc == NULL || chunk_num < 0) return 0; + count = ChunkCount(dmux, fourcc); + if (count == 0) return 0; + if (chunk_num == 0) chunk_num = count; + + if (chunk_num <= count) { + const uint8_t* const mem_buf = dmux->mem_.buf_; + const Chunk* const chunk = GetChunk(dmux, fourcc, chunk_num); + iter->chunk_.bytes_ = mem_buf + chunk->data_.offset_ + CHUNK_HEADER_SIZE; + iter->chunk_.size_ = chunk->data_.size_ - CHUNK_HEADER_SIZE; + iter->num_chunks_ = count; + iter->chunk_num_ = chunk_num; + return 1; + } + return 0; +} + +int WebPDemuxGetChunk(const WebPDemuxer* dmux, + const char fourcc[4], int chunk_num, + WebPChunkIterator* iter) { + if (iter == NULL) return 0; + + memset(iter, 0, sizeof(*iter)); + iter->private_ = (void*)dmux; + return SetChunk(fourcc, chunk_num, iter); +} + +int WebPDemuxNextChunk(WebPChunkIterator* iter) { + if (iter != NULL) { + const char* const fourcc = + (const char*)iter->chunk_.bytes_ - CHUNK_HEADER_SIZE; + return SetChunk(fourcc, iter->chunk_num_ + 1, iter); + } + return 0; +} + +int WebPDemuxPrevChunk(WebPChunkIterator* iter) { + if (iter != NULL && iter->chunk_num_ > 1) { + const char* const fourcc = + (const char*)iter->chunk_.bytes_ - CHUNK_HEADER_SIZE; + return SetChunk(fourcc, iter->chunk_num_ - 1, iter); + } + return 0; +} + +void WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter) { + (void)iter; +} + +#if defined(__cplusplus) || defined(c_plusplus) +} // extern "C" +#endif diff --git a/drivers/webp/mux/muxedit.c b/drivers/webp/mux/muxedit.c index 25770b3546..08629d4ae2 100644 --- a/drivers/webp/mux/muxedit.c +++ b/drivers/webp/mux/muxedit.c @@ -1,10 +1,8 @@ // Copyright 2011 Google Inc. All Rights Reserved. // -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. +// This code is licensed under the same terms as WebM: +// Software License Agreement: http://www.webmproject.org/license/software/ +// Additional IP Rights Grant: http://www.webmproject.org/license/additional/ // ----------------------------------------------------------------------------- // // Set and delete APIs for mux. @@ -14,7 +12,10 @@ #include <assert.h> #include "./muxi.h" -#include "../utils/utils.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif //------------------------------------------------------------------------------ // Life of a mux object. @@ -35,22 +36,20 @@ WebPMux* WebPNewInternal(int version) { } } -// Delete all images in 'wpi_list'. -static void DeleteAllImages(WebPMuxImage** const wpi_list) { - while (*wpi_list != NULL) { - *wpi_list = MuxImageDelete(*wpi_list); +static void DeleteAllChunks(WebPChunk** const chunk_list) { + while (*chunk_list) { + *chunk_list = ChunkDelete(*chunk_list); } } static void MuxRelease(WebPMux* const mux) { if (mux == NULL) return; - DeleteAllImages(&mux->images_); - ChunkListDelete(&mux->vp8x_); - ChunkListDelete(&mux->iccp_); - ChunkListDelete(&mux->anim_); - ChunkListDelete(&mux->exif_); - ChunkListDelete(&mux->xmp_); - ChunkListDelete(&mux->unknown_); + MuxImageDeleteAll(&mux->images_); + DeleteAllChunks(&mux->vp8x_); + DeleteAllChunks(&mux->iccp_); + DeleteAllChunks(&mux->loop_); + DeleteAllChunks(&mux->meta_); + DeleteAllChunks(&mux->unknown_); } void WebPMuxDelete(WebPMux* mux) { @@ -65,60 +64,81 @@ void WebPMuxDelete(WebPMux* mux) { // Handy MACRO, makes MuxSet() very symmetric to MuxGet(). #define SWITCH_ID_LIST(INDEX, LIST) \ if (idx == (INDEX)) { \ - err = ChunkAssignData(&chunk, data, copy_data, tag); \ + err = ChunkAssignData(&chunk, data, copy_data, kChunks[(INDEX)].tag); \ if (err == WEBP_MUX_OK) { \ err = ChunkSetNth(&chunk, (LIST), nth); \ } \ return err; \ } -static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag, uint32_t nth, +static WebPMuxError MuxSet(WebPMux* const mux, CHUNK_INDEX idx, uint32_t nth, const WebPData* const data, int copy_data) { WebPChunk chunk; WebPMuxError err = WEBP_MUX_NOT_FOUND; - const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag); assert(mux != NULL); assert(!IsWPI(kChunks[idx].id)); ChunkInit(&chunk); - SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_); - SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_); - SWITCH_ID_LIST(IDX_ANIM, &mux->anim_); - SWITCH_ID_LIST(IDX_EXIF, &mux->exif_); - SWITCH_ID_LIST(IDX_XMP, &mux->xmp_); - SWITCH_ID_LIST(IDX_UNKNOWN, &mux->unknown_); + SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_); + SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_); + SWITCH_ID_LIST(IDX_LOOP, &mux->loop_); + SWITCH_ID_LIST(IDX_META, &mux->meta_); + if (idx == IDX_UNKNOWN && data->size_ > TAG_SIZE) { + // For raw-data unknown chunk, the first four bytes should be the tag to be + // used for the chunk. + const WebPData tmp = { data->bytes_ + TAG_SIZE, data->size_ - TAG_SIZE }; + err = ChunkAssignData(&chunk, &tmp, copy_data, GetLE32(data->bytes_ + 0)); + if (err == WEBP_MUX_OK) + err = ChunkSetNth(&chunk, &mux->unknown_, nth); + } return err; } #undef SWITCH_ID_LIST -// Create data for frame/fragment given image data, offsets and duration. -static WebPMuxError CreateFrameFragmentData( - int width, int height, const WebPMuxFrameInfo* const info, int is_frame, - WebPData* const frame_frgm) { - uint8_t* frame_frgm_bytes; - const size_t frame_frgm_size = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].size; +static WebPMuxError MuxAddChunk(WebPMux* const mux, uint32_t nth, uint32_t tag, + const uint8_t* data, size_t size, + int copy_data) { + const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag); + const WebPData chunk_data = { data, size }; + assert(mux != NULL); + assert(size <= MAX_CHUNK_PAYLOAD); + assert(idx != IDX_NIL); + return MuxSet(mux, idx, nth, &chunk_data, copy_data); +} - assert(width > 0 && height > 0 && info->duration >= 0); - assert(info->dispose_method == (info->dispose_method & 1)); +// Create data for frame/tile given image data, offsets and duration. +static WebPMuxError CreateFrameTileData(const WebPData* const image, + int x_offset, int y_offset, + int duration, int is_lossless, + int is_frame, + WebPData* const frame_tile) { + int width; + int height; + uint8_t* frame_tile_bytes; + const size_t frame_tile_size = kChunks[is_frame ? IDX_FRAME : IDX_TILE].size; + + const int ok = is_lossless ? + VP8LGetInfo(image->bytes_, image->size_, &width, &height, NULL) : + VP8GetInfo(image->bytes_, image->size_, image->size_, &width, &height); + if (!ok) return WEBP_MUX_INVALID_ARGUMENT; + + assert(width > 0 && height > 0 && duration > 0); // Note: assertion on upper bounds is done in PutLE24(). - frame_frgm_bytes = (uint8_t*)malloc(frame_frgm_size); - if (frame_frgm_bytes == NULL) return WEBP_MUX_MEMORY_ERROR; + frame_tile_bytes = (uint8_t*)malloc(frame_tile_size); + if (frame_tile_bytes == NULL) return WEBP_MUX_MEMORY_ERROR; - PutLE24(frame_frgm_bytes + 0, info->x_offset / 2); - PutLE24(frame_frgm_bytes + 3, info->y_offset / 2); + PutLE24(frame_tile_bytes + 0, x_offset / 2); + PutLE24(frame_tile_bytes + 3, y_offset / 2); if (is_frame) { - PutLE24(frame_frgm_bytes + 6, width - 1); - PutLE24(frame_frgm_bytes + 9, height - 1); - PutLE24(frame_frgm_bytes + 12, info->duration); - frame_frgm_bytes[15] = - (info->blend_method == WEBP_MUX_NO_BLEND ? 2 : 0) | - (info->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? 1 : 0); + PutLE24(frame_tile_bytes + 6, width - 1); + PutLE24(frame_tile_bytes + 9, height - 1); + PutLE24(frame_tile_bytes + 12, duration - 1); } - frame_frgm->bytes = frame_frgm_bytes; - frame_frgm->size = frame_frgm_size; + frame_tile->bytes_ = frame_tile_bytes; + frame_tile->size_ = frame_tile_size; return WEBP_MUX_OK; } @@ -129,8 +149,8 @@ static WebPMuxError GetImageData(const WebPData* const bitstream, WebPData* const image, WebPData* const alpha, int* const is_lossless) { WebPDataInit(alpha); // Default: no alpha. - if (bitstream->size < TAG_SIZE || - memcmp(bitstream->bytes, "RIFF", TAG_SIZE)) { + if (bitstream->size_ < TAG_SIZE || + memcmp(bitstream->bytes_, "RIFF", TAG_SIZE)) { // It is NOT webp file data. Return input data as is. *image = *bitstream; } else { @@ -146,7 +166,7 @@ static WebPMuxError GetImageData(const WebPData* const bitstream, } WebPMuxDelete(mux); } - *is_lossless = VP8LCheckSignature(image->bytes, image->size); + *is_lossless = VP8LCheckSignature(image->bytes_, image->size_); return WEBP_MUX_OK; } @@ -165,168 +185,204 @@ static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) { return err; } -static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) { - const WebPChunkId id = ChunkGetIdFromTag(tag); - assert(mux != NULL); +static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, CHUNK_INDEX idx) { + const WebPChunkId id = kChunks[idx].id; + WebPChunk** chunk_list; + + if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; if (IsWPI(id)) return WEBP_MUX_INVALID_ARGUMENT; - return DeleteChunks(MuxGetChunkListFromId(mux, id), tag); + + chunk_list = MuxGetChunkListFromId(mux, id); + if (chunk_list == NULL) return WEBP_MUX_INVALID_ARGUMENT; + + return DeleteChunks(chunk_list, kChunks[idx].tag); +} + +static WebPMuxError DeleteLoopCount(WebPMux* const mux) { + return MuxDeleteAllNamedData(mux, IDX_LOOP); } //------------------------------------------------------------------------------ // Set API(s). -WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4], - const WebPData* chunk_data, int copy_data) { - uint32_t tag; +WebPMuxError WebPMuxSetImage(WebPMux* mux, + const WebPData* bitstream, int copy_data) { WebPMuxError err; - if (mux == NULL || fourcc == NULL || chunk_data == NULL || - chunk_data->bytes == NULL || chunk_data->size > MAX_CHUNK_PAYLOAD) { + WebPChunk chunk; + WebPMuxImage wpi; + WebPData image; + WebPData alpha; + int is_lossless; + int image_tag; + + if (mux == NULL || bitstream == NULL || bitstream->bytes_ == NULL || + bitstream->size_ > MAX_CHUNK_PAYLOAD) { return WEBP_MUX_INVALID_ARGUMENT; } - tag = ChunkGetTagFromFourCC(fourcc); - // Delete existing chunk(s) with the same 'fourcc'. - err = MuxDeleteAllNamedData(mux, tag); - if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; + // If given data is for a whole webp file, + // extract only the VP8/VP8L data from it. + err = GetImageData(bitstream, &image, &alpha, &is_lossless); + if (err != WEBP_MUX_OK) return err; + image_tag = is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag; - // Add the given chunk. - return MuxSet(mux, tag, 1, chunk_data, copy_data); -} + // Delete the existing images. + MuxImageDeleteAll(&mux->images_); -// Creates a chunk from given 'data' and sets it as 1st chunk in 'chunk_list'. -static WebPMuxError AddDataToChunkList( - const WebPData* const data, int copy_data, uint32_t tag, - WebPChunk** chunk_list) { - WebPChunk chunk; - WebPMuxError err; + MuxImageInit(&wpi); + + if (alpha.bytes_ != NULL) { // Add alpha chunk. + ChunkInit(&chunk); + err = ChunkAssignData(&chunk, &alpha, copy_data, kChunks[IDX_ALPHA].tag); + if (err != WEBP_MUX_OK) goto Err; + err = ChunkSetNth(&chunk, &wpi.alpha_, 1); + if (err != WEBP_MUX_OK) goto Err; + } + + // Add image chunk. ChunkInit(&chunk); - err = ChunkAssignData(&chunk, data, copy_data, tag); + err = ChunkAssignData(&chunk, &image, copy_data, image_tag); if (err != WEBP_MUX_OK) goto Err; - err = ChunkSetNth(&chunk, chunk_list, 1); + err = ChunkSetNth(&chunk, &wpi.img_, 1); if (err != WEBP_MUX_OK) goto Err; + + // Add this image to mux. + err = MuxImagePush(&wpi, &mux->images_); + if (err != WEBP_MUX_OK) goto Err; + + // All OK. return WEBP_MUX_OK; + Err: + // Something bad happened. ChunkRelease(&chunk); + MuxImageRelease(&wpi); return err; } -// Extracts image & alpha data from the given bitstream and then sets wpi.alpha_ -// and wpi.img_ appropriately. -static WebPMuxError SetAlphaAndImageChunks( - const WebPData* const bitstream, int copy_data, WebPMuxImage* const wpi) { - int is_lossless = 0; - WebPData image, alpha; - WebPMuxError err = GetImageData(bitstream, &image, &alpha, &is_lossless); - const int image_tag = - is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag; - if (err != WEBP_MUX_OK) return err; - if (alpha.bytes != NULL) { - err = AddDataToChunkList(&alpha, copy_data, kChunks[IDX_ALPHA].tag, - &wpi->alpha_); - if (err != WEBP_MUX_OK) return err; +WebPMuxError WebPMuxSetMetadata(WebPMux* mux, const WebPData* metadata, + int copy_data) { + WebPMuxError err; + + if (mux == NULL || metadata == NULL || metadata->bytes_ == NULL || + metadata->size_ > MAX_CHUNK_PAYLOAD) { + return WEBP_MUX_INVALID_ARGUMENT; } - err = AddDataToChunkList(&image, copy_data, image_tag, &wpi->img_); - if (err != WEBP_MUX_OK) return err; - return MuxImageFinalize(wpi) ? WEBP_MUX_OK : WEBP_MUX_INVALID_ARGUMENT; + + // Delete the existing metadata chunk(s). + err = WebPMuxDeleteMetadata(mux); + if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; + + // Add the given metadata chunk. + return MuxSet(mux, IDX_META, 1, metadata, copy_data); } -WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream, - int copy_data) { - WebPMuxImage wpi; +WebPMuxError WebPMuxSetColorProfile(WebPMux* mux, const WebPData* color_profile, + int copy_data) { WebPMuxError err; - // Sanity checks. - if (mux == NULL || bitstream == NULL || bitstream->bytes == NULL || - bitstream->size > MAX_CHUNK_PAYLOAD) { + if (mux == NULL || color_profile == NULL || color_profile->bytes_ == NULL || + color_profile->size_ > MAX_CHUNK_PAYLOAD) { return WEBP_MUX_INVALID_ARGUMENT; } - if (mux->images_ != NULL) { - // Only one 'simple image' can be added in mux. So, remove present images. - DeleteAllImages(&mux->images_); - } + // Delete the existing ICCP chunk(s). + err = WebPMuxDeleteColorProfile(mux); + if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; - MuxImageInit(&wpi); - err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi); - if (err != WEBP_MUX_OK) goto Err; + // Add the given ICCP chunk. + return MuxSet(mux, IDX_ICCP, 1, color_profile, copy_data); +} - // Add this WebPMuxImage to mux. - err = MuxImagePush(&wpi, &mux->images_); - if (err != WEBP_MUX_OK) goto Err; +WebPMuxError WebPMuxSetLoopCount(WebPMux* mux, int loop_count) { + WebPMuxError err; + uint8_t* data = NULL; - // All is well. - return WEBP_MUX_OK; + if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; + if (loop_count >= MAX_LOOP_COUNT) return WEBP_MUX_INVALID_ARGUMENT; - Err: // Something bad happened. - MuxImageRelease(&wpi); + // Delete the existing LOOP chunk(s). + err = DeleteLoopCount(mux); + if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; + + // Add the given loop count. + data = (uint8_t*)malloc(kChunks[IDX_LOOP].size); + if (data == NULL) return WEBP_MUX_MEMORY_ERROR; + + PutLE16(data, loop_count); + err = MuxAddChunk(mux, 1, kChunks[IDX_LOOP].tag, data, + kChunks[IDX_LOOP].size, 1); + free(data); return err; } -WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame, - int copy_data) { +static WebPMuxError MuxPushFrameTileInternal( + WebPMux* const mux, const WebPData* const bitstream, int x_offset, + int y_offset, int duration, int copy_data, uint32_t tag) { + WebPChunk chunk; + WebPData image; + WebPData alpha; WebPMuxImage wpi; WebPMuxError err; - int is_frame; - const WebPData* const bitstream = &frame->bitstream; + WebPData frame_tile; + const int is_frame = (tag == kChunks[IDX_FRAME].tag) ? 1 : 0; + int is_lossless; + int image_tag; // Sanity checks. - if (mux == NULL || frame == NULL) return WEBP_MUX_INVALID_ARGUMENT; - - is_frame = (frame->id == WEBP_CHUNK_ANMF); - if (!(is_frame || (frame->id == WEBP_CHUNK_FRGM))) { + if (mux == NULL || bitstream == NULL || bitstream->bytes_ == NULL || + bitstream->size_ > MAX_CHUNK_PAYLOAD) { return WEBP_MUX_INVALID_ARGUMENT; } -#ifndef WEBP_EXPERIMENTAL_FEATURES - if (frame->id == WEBP_CHUNK_FRGM) { // disabled for now. + if (x_offset < 0 || x_offset >= MAX_POSITION_OFFSET || + y_offset < 0 || y_offset >= MAX_POSITION_OFFSET || + duration <= 0 || duration > MAX_DURATION) { return WEBP_MUX_INVALID_ARGUMENT; } -#endif - if (bitstream->bytes == NULL || bitstream->size > MAX_CHUNK_PAYLOAD) { - return WEBP_MUX_INVALID_ARGUMENT; - } + // Snap offsets to even positions. + x_offset &= ~1; + y_offset &= ~1; - if (mux->images_ != NULL) { - const WebPMuxImage* const image = mux->images_; - const uint32_t image_id = (image->header_ != NULL) ? - ChunkGetIdFromTag(image->header_->tag_) : WEBP_CHUNK_IMAGE; - if (image_id != frame->id) { - return WEBP_MUX_INVALID_ARGUMENT; // Conflicting frame types. - } - } + // If given data is for a whole webp file, + // extract only the VP8/VP8L data from it. + err = GetImageData(bitstream, &image, &alpha, &is_lossless); + if (err != WEBP_MUX_OK) return err; + image_tag = is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag; + WebPDataInit(&frame_tile); + ChunkInit(&chunk); MuxImageInit(&wpi); - err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi); - if (err != WEBP_MUX_OK) goto Err; - assert(wpi.img_ != NULL); // As SetAlphaAndImageChunks() was successful. - - { - WebPData frame_frgm; - const uint32_t tag = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].tag; - WebPMuxFrameInfo tmp = *frame; - tmp.x_offset &= ~1; // Snap offsets to even. - tmp.y_offset &= ~1; - if (!is_frame) { // Reset unused values. - tmp.duration = 1; - tmp.dispose_method = WEBP_MUX_DISPOSE_NONE; - tmp.blend_method = WEBP_MUX_BLEND; - } - if (tmp.x_offset < 0 || tmp.x_offset >= MAX_POSITION_OFFSET || - tmp.y_offset < 0 || tmp.y_offset >= MAX_POSITION_OFFSET || - (tmp.duration < 0 || tmp.duration >= MAX_DURATION) || - tmp.dispose_method != (tmp.dispose_method & 1)) { - err = WEBP_MUX_INVALID_ARGUMENT; - goto Err; - } - err = CreateFrameFragmentData(wpi.width_, wpi.height_, &tmp, is_frame, - &frame_frgm); + + if (alpha.bytes_ != NULL) { + // Add alpha chunk. + err = ChunkAssignData(&chunk, &alpha, copy_data, kChunks[IDX_ALPHA].tag); if (err != WEBP_MUX_OK) goto Err; - // Add frame/fragment chunk (with copy_data = 1). - err = AddDataToChunkList(&frame_frgm, 1, tag, &wpi.header_); - WebPDataClear(&frame_frgm); // frame_frgm owned by wpi.header_ now. + err = ChunkSetNth(&chunk, &wpi.alpha_, 1); if (err != WEBP_MUX_OK) goto Err; + ChunkInit(&chunk); // chunk owned by wpi.alpha_ now. } + // Add image chunk. + err = ChunkAssignData(&chunk, &image, copy_data, image_tag); + if (err != WEBP_MUX_OK) goto Err; + err = ChunkSetNth(&chunk, &wpi.img_, 1); + if (err != WEBP_MUX_OK) goto Err; + ChunkInit(&chunk); // chunk owned by wpi.img_ now. + + // Create frame/tile data. + err = CreateFrameTileData(&image, x_offset, y_offset, duration, is_lossless, + is_frame, &frame_tile); + if (err != WEBP_MUX_OK) goto Err; + + // Add frame/tile chunk (with copy_data = 1). + err = ChunkAssignData(&chunk, &frame_tile, 1, tag); + if (err != WEBP_MUX_OK) goto Err; + WebPDataClear(&frame_tile); + err = ChunkSetNth(&chunk, &wpi.header_, 1); + if (err != WEBP_MUX_OK) goto Err; + ChunkInit(&chunk); // chunk owned by wpi.header_ now. + // Add this WebPMuxImage to mux. err = MuxImagePush(&wpi, &mux->images_); if (err != WEBP_MUX_OK) goto Err; @@ -335,82 +391,123 @@ WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame, return WEBP_MUX_OK; Err: // Something bad happened. + WebPDataClear(&frame_tile); + ChunkRelease(&chunk); MuxImageRelease(&wpi); return err; } -WebPMuxError WebPMuxSetAnimationParams(WebPMux* mux, - const WebPMuxAnimParams* params) { +WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPData* bitstream, + int x_offset, int y_offset, + int duration, int copy_data) { + return MuxPushFrameTileInternal(mux, bitstream, x_offset, y_offset, + duration, copy_data, kChunks[IDX_FRAME].tag); +} + +WebPMuxError WebPMuxPushTile(WebPMux* mux, const WebPData* bitstream, + int x_offset, int y_offset, + int copy_data) { + return MuxPushFrameTileInternal(mux, bitstream, x_offset, y_offset, + 1 /* unused duration */, copy_data, + kChunks[IDX_TILE].tag); +} + +//------------------------------------------------------------------------------ +// Delete API(s). + +WebPMuxError WebPMuxDeleteImage(WebPMux* mux) { WebPMuxError err; - uint8_t data[ANIM_CHUNK_SIZE]; - const WebPData anim = { data, ANIM_CHUNK_SIZE }; - if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT; - if (params->loop_count < 0 || params->loop_count >= MAX_LOOP_COUNT) { - return WEBP_MUX_INVALID_ARGUMENT; - } + if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; - // Delete any existing ANIM chunk(s). - err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag); - if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; + err = MuxValidateForImage(mux); + if (err != WEBP_MUX_OK) return err; - // Set the animation parameters. - PutLE32(data, params->bgcolor); - PutLE16(data + 4, params->loop_count); - return MuxSet(mux, kChunks[IDX_ANIM].tag, 1, &anim, 1); + // All well, delete image. + MuxImageDeleteAll(&mux->images_); + return WEBP_MUX_OK; } -//------------------------------------------------------------------------------ -// Delete API(s). +WebPMuxError WebPMuxDeleteMetadata(WebPMux* mux) { + return MuxDeleteAllNamedData(mux, IDX_META); +} -WebPMuxError WebPMuxDeleteChunk(WebPMux* mux, const char fourcc[4]) { - if (mux == NULL || fourcc == NULL) return WEBP_MUX_INVALID_ARGUMENT; - return MuxDeleteAllNamedData(mux, ChunkGetTagFromFourCC(fourcc)); +WebPMuxError WebPMuxDeleteColorProfile(WebPMux* mux) { + return MuxDeleteAllNamedData(mux, IDX_ICCP); } -WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth) { +static WebPMuxError DeleteFrameTileInternal(WebPMux* const mux, uint32_t nth, + CHUNK_INDEX idx) { + const WebPChunkId id = kChunks[idx].id; if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; - return MuxImageDeleteNth(&mux->images_, nth); + + assert(idx == IDX_FRAME || idx == IDX_TILE); + return MuxImageDeleteNth(&mux->images_, nth, id); +} + +WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth) { + return DeleteFrameTileInternal(mux, nth, IDX_FRAME); +} + +WebPMuxError WebPMuxDeleteTile(WebPMux* mux, uint32_t nth) { + return DeleteFrameTileInternal(mux, nth, IDX_TILE); } //------------------------------------------------------------------------------ // Assembly of the WebP RIFF file. -static WebPMuxError GetFrameFragmentInfo( - const WebPChunk* const frame_frgm_chunk, - int* const x_offset, int* const y_offset, int* const duration) { - const uint32_t tag = frame_frgm_chunk->tag_; - const int is_frame = (tag == kChunks[IDX_ANMF].tag); - const WebPData* const data = &frame_frgm_chunk->data_; +static WebPMuxError GetFrameTileInfo(const WebPChunk* const frame_tile_chunk, + int* const x_offset, int* const y_offset, + int* const duration) { + const uint32_t tag = frame_tile_chunk->tag_; + const int is_frame = (tag == kChunks[IDX_FRAME].tag); + const WebPData* const data = &frame_tile_chunk->data_; const size_t expected_data_size = - is_frame ? ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE; - assert(frame_frgm_chunk != NULL); - assert(tag == kChunks[IDX_ANMF].tag || tag == kChunks[IDX_FRGM].tag); - if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT; - - *x_offset = 2 * GetLE24(data->bytes + 0); - *y_offset = 2 * GetLE24(data->bytes + 3); - if (is_frame) *duration = GetLE24(data->bytes + 12); + is_frame ? FRAME_CHUNK_SIZE : TILE_CHUNK_SIZE; + assert(frame_tile_chunk != NULL); + assert(tag == kChunks[IDX_FRAME].tag || tag == kChunks[IDX_TILE].tag); + if (data->size_ != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT; + + *x_offset = 2 * GetLE24(data->bytes_ + 0); + *y_offset = 2 * GetLE24(data->bytes_ + 3); + if (is_frame) *duration = 1 + GetLE24(data->bytes_ + 12); return WEBP_MUX_OK; } +WebPMuxError MuxGetImageWidthHeight(const WebPChunk* const image_chunk, + int* const width, int* const height) { + const uint32_t tag = image_chunk->tag_; + const WebPData* const data = &image_chunk->data_; + int w, h; + int ok; + assert(image_chunk != NULL); + assert(tag == kChunks[IDX_VP8].tag || tag == kChunks[IDX_VP8L].tag); + ok = (tag == kChunks[IDX_VP8].tag) ? + VP8GetInfo(data->bytes_, data->size_, data->size_, &w, &h) : + VP8LGetInfo(data->bytes_, data->size_, &w, &h, NULL); + if (ok) { + *width = w; + *height = h; + return WEBP_MUX_OK; + } else { + return WEBP_MUX_BAD_DATA; + } +} + static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi, int* const x_offset, int* const y_offset, int* const duration, int* const width, int* const height) { - const WebPChunk* const frame_frgm_chunk = wpi->header_; - WebPMuxError err; - assert(wpi != NULL); - assert(frame_frgm_chunk != NULL); + const WebPChunk* const image_chunk = wpi->img_; + const WebPChunk* const frame_tile_chunk = wpi->header_; - // Get offsets and duration from ANMF/FRGM chunk. - err = GetFrameFragmentInfo(frame_frgm_chunk, x_offset, y_offset, duration); + // Get offsets and duration from FRM/TILE chunk. + const WebPMuxError err = + GetFrameTileInfo(frame_tile_chunk, x_offset, y_offset, duration); if (err != WEBP_MUX_OK) return err; // Get width and height from VP8/VP8L chunk. - if (width != NULL) *width = wpi->width_; - if (height != NULL) *height = wpi->height_; - return WEBP_MUX_OK; + return MuxGetImageWidthHeight(image_chunk, width, height); } static WebPMuxError GetImageCanvasWidthHeight( @@ -424,15 +521,13 @@ static WebPMuxError GetImageCanvasWidthHeight( assert(wpi != NULL); assert(wpi->img_ != NULL); - if (wpi->next_ != NULL) { + if (wpi->next_) { int max_x = 0; int max_y = 0; int64_t image_area = 0; - // if we have a chain of wpi's, header_ is necessarily set - assert(wpi->header_ != NULL); - // Aggregate the bounding box for animation frames & fragmented images. + // Aggregate the bounding box for animation frames & tiled images. for (; wpi != NULL; wpi = wpi->next_) { - int x_offset = 0, y_offset = 0, duration = 0, w = 0, h = 0; + int x_offset, y_offset, duration, w, h; const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset, &duration, &w, &h); const int max_x_pos = x_offset + w; @@ -447,19 +542,23 @@ static WebPMuxError GetImageCanvasWidthHeight( } *width = max_x; *height = max_y; - // Crude check to validate that there are no image overlaps/holes for - // fragmented images. Check that the aggregated image area for individual - // fragments exactly matches the image area of the constructed canvas. - // However, the area-match is necessary but not sufficient condition. - if ((flags & FRAGMENTS_FLAG) && (image_area != (max_x * max_y))) { + // Crude check to validate that there are no image overlaps/holes for tile + // images. Check that the aggregated image area for individual tiles exactly + // matches the image area of the constructed canvas. However, the area-match + // is necessary but not sufficient condition. + if ((flags & TILE_FLAG) && (image_area != (max_x * max_y))) { *width = 0; *height = 0; return WEBP_MUX_INVALID_ARGUMENT; } } else { - // For a single image, canvas dimensions are same as image dimensions. - *width = wpi->width_; - *height = wpi->height_; + // For a single image, extract the width & height from VP8/VP8L image-data. + int w, h; + const WebPChunk* const image_chunk = wpi->img_; + const WebPMuxError err = MuxGetImageWidthHeight(image_chunk, &w, &h); + if (err != WEBP_MUX_OK) return err; + *width = w; + *height = h; } return WEBP_MUX_OK; } @@ -475,40 +574,40 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { int width = 0; int height = 0; uint8_t data[VP8X_CHUNK_SIZE]; - const WebPData vp8x = { data, VP8X_CHUNK_SIZE }; + const size_t data_size = VP8X_CHUNK_SIZE; const WebPMuxImage* images = NULL; assert(mux != NULL); images = mux->images_; // First image. if (images == NULL || images->img_ == NULL || - images->img_->data_.bytes == NULL) { + images->img_->data_.bytes_ == NULL) { return WEBP_MUX_INVALID_ARGUMENT; } // If VP8X chunk(s) is(are) already present, remove them (and later add new // VP8X chunk with updated flags). - err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag); + err = MuxDeleteAllNamedData(mux, IDX_VP8X); if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; // Set flags. - if (mux->iccp_ != NULL && mux->iccp_->data_.bytes != NULL) { + if (mux->iccp_ != NULL && mux->iccp_->data_.bytes_ != NULL) { flags |= ICCP_FLAG; } - if (mux->exif_ != NULL && mux->exif_->data_.bytes != NULL) { - flags |= EXIF_FLAG; - } - if (mux->xmp_ != NULL && mux->xmp_->data_.bytes != NULL) { - flags |= XMP_FLAG; + + if (mux->meta_ != NULL && mux->meta_->data_.bytes_ != NULL) { + flags |= META_FLAG; } + if (images->header_ != NULL) { - if (images->header_->tag_ == kChunks[IDX_FRGM].tag) { - // This is a fragmented image. - flags |= FRAGMENTS_FLAG; - } else if (images->header_->tag_ == kChunks[IDX_ANMF].tag) { + if (images->header_->tag_ == kChunks[IDX_TILE].tag) { + // This is a tiled image. + flags |= TILE_FLAG; + } else if (images->header_->tag_ == kChunks[IDX_FRAME].tag) { // This is an image with animation. flags |= ANIMATION_FLAG; } } + if (MuxImageCount(images, WEBP_CHUNK_ALPHA) > 0) { flags |= ALPHA_FLAG; // Some images have an alpha channel. } @@ -528,8 +627,9 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { return WEBP_MUX_INVALID_ARGUMENT; } - if (MuxHasAlpha(images)) { - // This means some frames explicitly/implicitly contain alpha. + if (MuxHasLosslessImages(images)) { + // We have a file with a VP8X chunk having some lossless images. + // As lossless images implicitly contain alpha, force ALPHA_FLAG to be true. // Note: This 'flags' update must NOT be done for a lossless image // without a VP8X chunk! flags |= ALPHA_FLAG; @@ -539,85 +639,43 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { PutLE24(data + 4, width - 1); // canvas width. PutLE24(data + 7, height - 1); // canvas height. - return MuxSet(mux, kChunks[IDX_VP8X].tag, 1, &vp8x, 1); -} - -// Cleans up 'mux' by removing any unnecessary chunks. -static WebPMuxError MuxCleanup(WebPMux* const mux) { - int num_frames; - int num_fragments; - int num_anim_chunks; - - // If we have an image with single fragment or frame, convert it to a - // non-animated non-fragmented image (to avoid writing FRGM/ANMF chunk - // unnecessarily). - WebPMuxError err = WebPMuxNumChunks(mux, kChunks[IDX_ANMF].id, &num_frames); - if (err != WEBP_MUX_OK) return err; - err = WebPMuxNumChunks(mux, kChunks[IDX_FRGM].id, &num_fragments); - if (err != WEBP_MUX_OK) return err; - if (num_frames == 1 || num_fragments == 1) { - WebPMuxImage* frame_frag; - err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, 1, &frame_frag); - assert(err == WEBP_MUX_OK); // We know that one frame/fragment does exist. - if (frame_frag->header_ != NULL) { - assert(frame_frag->header_->tag_ == kChunks[IDX_ANMF].tag || - frame_frag->header_->tag_ == kChunks[IDX_FRGM].tag); - ChunkDelete(frame_frag->header_); // Removes ANMF/FRGM chunk. - frame_frag->header_ = NULL; - } - num_frames = 0; - num_fragments = 0; - } - // Remove ANIM chunk if this is a non-animated image. - err = WebPMuxNumChunks(mux, kChunks[IDX_ANIM].id, &num_anim_chunks); - if (err != WEBP_MUX_OK) return err; - if (num_anim_chunks >= 1 && num_frames == 0) { - err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag); - if (err != WEBP_MUX_OK) return err; - } - return WEBP_MUX_OK; -} - -// Total size of a list of images. -static size_t ImageListDiskSize(const WebPMuxImage* wpi_list) { - size_t size = 0; - while (wpi_list != NULL) { - size += MuxImageDiskSize(wpi_list); - wpi_list = wpi_list->next_; - } - return size; -} - -// Write out the given list of images into 'dst'. -static uint8_t* ImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst) { - while (wpi_list != NULL) { - dst = MuxImageEmit(wpi_list, dst); - wpi_list = wpi_list->next_; - } - return dst; + err = MuxAddChunk(mux, 1, kChunks[IDX_VP8X].tag, data, data_size, 1); + return err; } WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) { size_t size = 0; uint8_t* data = NULL; uint8_t* dst = NULL; + int num_frames; + int num_loop_chunks; WebPMuxError err; if (mux == NULL || assembled_data == NULL) { return WEBP_MUX_INVALID_ARGUMENT; } - // Finalize mux. - err = MuxCleanup(mux); + // Remove LOOP chunk if unnecessary. + err = WebPMuxNumChunks(mux, kChunks[IDX_LOOP].id, &num_loop_chunks); if (err != WEBP_MUX_OK) return err; + if (num_loop_chunks >= 1) { + err = WebPMuxNumChunks(mux, kChunks[IDX_FRAME].id, &num_frames); + if (err != WEBP_MUX_OK) return err; + if (num_frames == 0) { + err = DeleteLoopCount(mux); + if (err != WEBP_MUX_OK) return err; + } + } + + // Create VP8X chunk. err = CreateVP8XChunk(mux); if (err != WEBP_MUX_OK) return err; // Allocate data. - size = ChunkListDiskSize(mux->vp8x_) + ChunkListDiskSize(mux->iccp_) - + ChunkListDiskSize(mux->anim_) + ImageListDiskSize(mux->images_) - + ChunkListDiskSize(mux->exif_) + ChunkListDiskSize(mux->xmp_) - + ChunkListDiskSize(mux->unknown_) + RIFF_HEADER_SIZE; + size = ChunksListDiskSize(mux->vp8x_) + ChunksListDiskSize(mux->iccp_) + + ChunksListDiskSize(mux->loop_) + MuxImageListDiskSize(mux->images_) + + ChunksListDiskSize(mux->meta_) + ChunksListDiskSize(mux->unknown_) + + RIFF_HEADER_SIZE; data = (uint8_t*)malloc(size); if (data == NULL) return WEBP_MUX_MEMORY_ERROR; @@ -626,10 +684,9 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) { dst = MuxEmitRiffHeader(data, size); dst = ChunkListEmit(mux->vp8x_, dst); dst = ChunkListEmit(mux->iccp_, dst); - dst = ChunkListEmit(mux->anim_, dst); - dst = ImageListEmit(mux->images_, dst); - dst = ChunkListEmit(mux->exif_, dst); - dst = ChunkListEmit(mux->xmp_, dst); + dst = ChunkListEmit(mux->loop_, dst); + dst = MuxImageListEmit(mux->images_, dst); + dst = ChunkListEmit(mux->meta_, dst); dst = ChunkListEmit(mux->unknown_, dst); assert(dst == data + size); @@ -641,12 +698,15 @@ WebPMuxError WebPMuxAssemble(WebPMux* mux, WebPData* assembled_data) { size = 0; } - // Finalize data. - assembled_data->bytes = data; - assembled_data->size = size; + // Finalize. + assembled_data->bytes_ = data; + assembled_data->size_ = size; return err; } //------------------------------------------------------------------------------ +#if defined(__cplusplus) || defined(c_plusplus) +} // extern "C" +#endif diff --git a/drivers/webp/mux/muxi.h b/drivers/webp/mux/muxi.h index 277d5fba1b..edd8c368cd 100644 --- a/drivers/webp/mux/muxi.h +++ b/drivers/webp/mux/muxi.h @@ -1,10 +1,8 @@ // Copyright 2011 Google Inc. All Rights Reserved. // -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. +// This code is licensed under the same terms as WebM: +// Software License Agreement: http://www.webmproject.org/license/software/ +// Additional IP Rights Grant: http://www.webmproject.org/license/additional/ // ----------------------------------------------------------------------------- // // Internal header for mux library. @@ -17,41 +15,34 @@ #include <stdlib.h> #include "../dec/vp8i.h" #include "../dec/vp8li.h" +#include "../webp/format_constants.h" #include "../webp/mux.h" -#ifdef __cplusplus +#if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif //------------------------------------------------------------------------------ // Defines and constants. -#define MUX_MAJ_VERSION 0 -#define MUX_MIN_VERSION 2 -#define MUX_REV_VERSION 0 - // Chunk object. typedef struct WebPChunk WebPChunk; struct WebPChunk { uint32_t tag_; int owner_; // True if *data_ memory is owned internally. - // VP8X, ANIM, and other internally created chunks - // like ANMF/FRGM are always owned. + // VP8X, Loop, and other internally created chunks + // like frame/tile are always owned. WebPData data_; WebPChunk* next_; }; -// MuxImage object. Store a full WebP image (including ANMF/FRGM chunk, ALPH +// MuxImage object. Store a full webp image (including frame/tile chunk, alpha // chunk and VP8/VP8L chunk), typedef struct WebPMuxImage WebPMuxImage; struct WebPMuxImage { - WebPChunk* header_; // Corresponds to WEBP_CHUNK_ANMF/WEBP_CHUNK_FRGM. + WebPChunk* header_; // Corresponds to WEBP_CHUNK_FRAME/WEBP_CHUNK_TILE. WebPChunk* alpha_; // Corresponds to WEBP_CHUNK_ALPHA. WebPChunk* img_; // Corresponds to WEBP_CHUNK_IMAGE. - WebPChunk* unknown_; // Corresponds to WEBP_CHUNK_UNKNOWN. - int width_; - int height_; - int has_alpha_; // Through ALPH chunk or as part of VP8L. int is_partial_; // True if only some of the chunks are filled. WebPMuxImage* next_; }; @@ -60,9 +51,8 @@ struct WebPMuxImage { struct WebPMux { WebPMuxImage* images_; WebPChunk* iccp_; - WebPChunk* exif_; - WebPChunk* xmp_; - WebPChunk* anim_; + WebPChunk* meta_; + WebPChunk* loop_; WebPChunk* vp8x_; WebPChunk* unknown_; @@ -75,14 +65,13 @@ struct WebPMux { typedef enum { IDX_VP8X = 0, IDX_ICCP, - IDX_ANIM, - IDX_ANMF, - IDX_FRGM, + IDX_LOOP, + IDX_FRAME, + IDX_TILE, IDX_ALPHA, IDX_VP8, IDX_VP8L, - IDX_EXIF, - IDX_XMP, + IDX_META, IDX_UNKNOWN, IDX_NIL, @@ -91,6 +80,8 @@ typedef enum { #define NIL_TAG 0x00000000u // To signal void chunk. +#define MKFOURCC(a, b, c, d) ((uint32_t)(a) | (b) << 8 | (c) << 16 | (d) << 24) + typedef struct { uint32_t tag; WebPChunkId id; @@ -100,23 +91,55 @@ typedef struct { extern const ChunkInfo kChunks[IDX_LAST_CHUNK]; //------------------------------------------------------------------------------ +// Helper functions. + +// Read 16, 24 or 32 bits stored in little-endian order. +static WEBP_INLINE int GetLE16(const uint8_t* const data) { + return (int)(data[0] << 0) | (data[1] << 8); +} + +static WEBP_INLINE int GetLE24(const uint8_t* const data) { + return GetLE16(data) | (data[2] << 16); +} + +static WEBP_INLINE uint32_t GetLE32(const uint8_t* const data) { + return (uint32_t)GetLE16(data) | (GetLE16(data + 2) << 16); +} + +// Store 16, 24 or 32 bits in little-endian order. +static WEBP_INLINE void PutLE16(uint8_t* const data, int val) { + assert(val < (1 << 16)); + data[0] = (val >> 0); + data[1] = (val >> 8); +} + +static WEBP_INLINE void PutLE24(uint8_t* const data, int val) { + assert(val < (1 << 24)); + PutLE16(data, val & 0xffff); + data[2] = (val >> 16); +} + +static WEBP_INLINE void PutLE32(uint8_t* const data, uint32_t val) { + PutLE16(data, (int)(val & 0xffff)); + PutLE16(data + 2, (int)(val >> 16)); +} + +static WEBP_INLINE size_t SizeWithPadding(size_t chunk_size) { + return CHUNK_HEADER_SIZE + ((chunk_size + 1) & ~1U); +} + +//------------------------------------------------------------------------------ // Chunk object management. // Initialize. void ChunkInit(WebPChunk* const chunk); -// Get chunk index from chunk tag. Returns IDX_UNKNOWN if not found. +// Get chunk index from chunk tag. Returns IDX_NIL if not found. CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag); -// Get chunk id from chunk tag. Returns WEBP_CHUNK_UNKNOWN if not found. +// Get chunk id from chunk tag. Returns WEBP_CHUNK_NIL if not found. WebPChunkId ChunkGetIdFromTag(uint32_t tag); -// Convert a fourcc string to a tag. -uint32_t ChunkGetTagFromFourCC(const char fourcc[4]); - -// Get chunk index from fourcc. Returns IDX_UNKNOWN if given fourcc is unknown. -CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]); - // Search for nth chunk with given 'tag' in the chunk list. // nth = 0 means "last of the list". WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag); @@ -127,8 +150,7 @@ WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data, // Sets 'chunk' at nth position in the 'chunk_list'. // nth = 0 has the special meaning "last of the list". -// On success ownership is transferred from 'chunk' to the 'chunk_list'. -WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list, +WebPMuxError ChunkSetNth(const WebPChunk* chunk, WebPChunk** chunk_list, uint32_t nth); // Releases chunk and returns chunk->next_. @@ -137,27 +159,23 @@ WebPChunk* ChunkRelease(WebPChunk* const chunk); // Deletes given chunk & returns chunk->next_. WebPChunk* ChunkDelete(WebPChunk* const chunk); -// Deletes all chunks in the given chunk list. -void ChunkListDelete(WebPChunk** const chunk_list); - -// Returns size of the chunk including chunk header and padding byte (if any). -static WEBP_INLINE size_t SizeWithPadding(size_t chunk_size) { - return CHUNK_HEADER_SIZE + ((chunk_size + 1) & ~1U); -} - // Size of a chunk including header and padding. static WEBP_INLINE size_t ChunkDiskSize(const WebPChunk* chunk) { - const size_t data_size = chunk->data_.size; + const size_t data_size = chunk->data_.size_; assert(data_size < MAX_CHUNK_PAYLOAD); return SizeWithPadding(data_size); } // Total size of a list of chunks. -size_t ChunkListDiskSize(const WebPChunk* chunk_list); +size_t ChunksListDiskSize(const WebPChunk* chunk_list); // Write out the given list of chunks into 'dst'. uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst); +// Get the width & height of image stored in 'image_chunk'. +WebPMuxError MuxGetImageWidthHeight(const WebPChunk* const image_chunk, + int* const width, int* const height); + //------------------------------------------------------------------------------ // MuxImage object management. @@ -171,59 +189,82 @@ WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi); // 'wpi' can be NULL. WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi); +// Delete all images in 'wpi_list'. +void MuxImageDeleteAll(WebPMuxImage** const wpi_list); + // Count number of images matching the given tag id in the 'wpi_list'. -// If id == WEBP_CHUNK_NIL, all images will be matched. int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id); -// Update width/height/has_alpha info from chunks within wpi. -// Also remove ALPH chunk if not needed. -int MuxImageFinalize(WebPMuxImage* const wpi); - // Check if given ID corresponds to an image related chunk. static WEBP_INLINE int IsWPI(WebPChunkId id) { switch (id) { - case WEBP_CHUNK_ANMF: - case WEBP_CHUNK_FRGM: + case WEBP_CHUNK_FRAME: + case WEBP_CHUNK_TILE: case WEBP_CHUNK_ALPHA: case WEBP_CHUNK_IMAGE: return 1; default: return 0; } } +// Get a reference to appropriate chunk list within an image given chunk tag. +static WEBP_INLINE WebPChunk** MuxImageGetListFromId( + const WebPMuxImage* const wpi, WebPChunkId id) { + assert(wpi != NULL); + switch (id) { + case WEBP_CHUNK_FRAME: + case WEBP_CHUNK_TILE: return (WebPChunk**)&wpi->header_; + case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha_; + case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img_; + default: return NULL; + } +} + // Pushes 'wpi' at the end of 'wpi_list'. WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list); -// Delete nth image in the image list. -WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth); +// Delete nth image in the image list with given tag id. +WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth, + WebPChunkId id); -// Get nth image in the image list. +// Get nth image in the image list with given tag id. WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth, - WebPMuxImage** wpi); + WebPChunkId id, WebPMuxImage** wpi); // Total size of the given image. size_t MuxImageDiskSize(const WebPMuxImage* const wpi); +// Total size of a list of images. +size_t MuxImageListDiskSize(const WebPMuxImage* wpi_list); + // Write out the given image into 'dst'. uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst); +// Write out the given list of images into 'dst'. +uint8_t* MuxImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst); + //------------------------------------------------------------------------------ // Helper methods for mux. -// Checks if the given image list contains at least one image with alpha. -int MuxHasAlpha(const WebPMuxImage* images); +// Checks if the given image list contains at least one lossless image. +int MuxHasLosslessImages(const WebPMuxImage* images); // Write out RIFF header into 'data', given total data size 'size'. uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size); // Returns the list where chunk with given ID is to be inserted in mux. +// Return value is NULL if this chunk should be inserted in mux->images_ list +// or if 'id' is not known. WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id); +// Validates that the given mux has a single image. +WebPMuxError MuxValidateForImage(const WebPMux* const mux); + // Validates the given mux object. WebPMuxError MuxValidate(const WebPMux* const mux); //------------------------------------------------------------------------------ -#ifdef __cplusplus +#if defined(__cplusplus) || defined(c_plusplus) } // extern "C" #endif diff --git a/drivers/webp/mux/muxinternal.c b/drivers/webp/mux/muxinternal.c index 3f992ce130..6c3c4fe60a 100644 --- a/drivers/webp/mux/muxinternal.c +++ b/drivers/webp/mux/muxinternal.c @@ -1,10 +1,8 @@ // Copyright 2011 Google Inc. All Rights Reserved. // -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. +// This code is licensed under the same terms as WebM: +// Software License Agreement: http://www.webmproject.org/license/software/ +// Additional IP Rights Grant: http://www.webmproject.org/license/additional/ // ----------------------------------------------------------------------------- // // Internal objects and utils for mux. @@ -14,33 +12,29 @@ #include <assert.h> #include "./muxi.h" -#include "../utils/utils.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif #define UNDEFINED_CHUNK_SIZE (-1) const ChunkInfo kChunks[] = { { MKFOURCC('V', 'P', '8', 'X'), WEBP_CHUNK_VP8X, VP8X_CHUNK_SIZE }, { MKFOURCC('I', 'C', 'C', 'P'), WEBP_CHUNK_ICCP, UNDEFINED_CHUNK_SIZE }, - { MKFOURCC('A', 'N', 'I', 'M'), WEBP_CHUNK_ANIM, ANIM_CHUNK_SIZE }, - { MKFOURCC('A', 'N', 'M', 'F'), WEBP_CHUNK_ANMF, ANMF_CHUNK_SIZE }, - { MKFOURCC('F', 'R', 'G', 'M'), WEBP_CHUNK_FRGM, FRGM_CHUNK_SIZE }, + { MKFOURCC('L', 'O', 'O', 'P'), WEBP_CHUNK_LOOP, LOOP_CHUNK_SIZE }, + { MKFOURCC('F', 'R', 'M', ' '), WEBP_CHUNK_FRAME, FRAME_CHUNK_SIZE }, + { MKFOURCC('T', 'I', 'L', 'E'), WEBP_CHUNK_TILE, TILE_CHUNK_SIZE }, { MKFOURCC('A', 'L', 'P', 'H'), WEBP_CHUNK_ALPHA, UNDEFINED_CHUNK_SIZE }, { MKFOURCC('V', 'P', '8', ' '), WEBP_CHUNK_IMAGE, UNDEFINED_CHUNK_SIZE }, { MKFOURCC('V', 'P', '8', 'L'), WEBP_CHUNK_IMAGE, UNDEFINED_CHUNK_SIZE }, - { MKFOURCC('E', 'X', 'I', 'F'), WEBP_CHUNK_EXIF, UNDEFINED_CHUNK_SIZE }, - { MKFOURCC('X', 'M', 'P', ' '), WEBP_CHUNK_XMP, UNDEFINED_CHUNK_SIZE }, - { NIL_TAG, WEBP_CHUNK_UNKNOWN, UNDEFINED_CHUNK_SIZE }, + { MKFOURCC('M', 'E', 'T', 'A'), WEBP_CHUNK_META, UNDEFINED_CHUNK_SIZE }, + { MKFOURCC('U', 'N', 'K', 'N'), WEBP_CHUNK_UNKNOWN, UNDEFINED_CHUNK_SIZE }, - { NIL_TAG, WEBP_CHUNK_NIL, UNDEFINED_CHUNK_SIZE } + { NIL_TAG, WEBP_CHUNK_NIL, UNDEFINED_CHUNK_SIZE } }; //------------------------------------------------------------------------------ - -int WebPGetMuxVersion(void) { - return (MUX_MAJ_VERSION << 16) | (MUX_MIN_VERSION << 8) | MUX_REV_VERSION; -} - -//------------------------------------------------------------------------------ // Life of a chunk object. void ChunkInit(WebPChunk* const chunk) { @@ -66,9 +60,9 @@ WebPChunk* ChunkRelease(WebPChunk* const chunk) { CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag) { int i; for (i = 0; kChunks[i].tag != NIL_TAG; ++i) { - if (tag == kChunks[i].tag) return (CHUNK_INDEX)i; + if (tag == kChunks[i].tag) return i; } - return IDX_UNKNOWN; + return IDX_NIL; } WebPChunkId ChunkGetIdFromTag(uint32_t tag) { @@ -76,16 +70,7 @@ WebPChunkId ChunkGetIdFromTag(uint32_t tag) { for (i = 0; kChunks[i].tag != NIL_TAG; ++i) { if (tag == kChunks[i].tag) return kChunks[i].id; } - return WEBP_CHUNK_UNKNOWN; -} - -uint32_t ChunkGetTagFromFourCC(const char fourcc[4]) { - return MKFOURCC(fourcc[0], fourcc[1], fourcc[2], fourcc[3]); -} - -CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]) { - const uint32_t tag = ChunkGetTagFromFourCC(fourcc); - return ChunkGetIndexFromTag(tag); + return WEBP_CHUNK_NIL; } //------------------------------------------------------------------------------ @@ -93,7 +78,7 @@ CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]) { // Returns next chunk in the chunk list with the given tag. static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) { - while (chunk != NULL && chunk->tag_ != tag) { + while (chunk && chunk->tag_ != tag) { chunk = chunk->next_; } return chunk; @@ -102,7 +87,7 @@ static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) { WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) { uint32_t iter = nth; first = ChunkSearchNextInList(first, tag); - if (first == NULL) return NULL; + if (!first) return NULL; while (--iter != 0) { WebPChunk* next_chunk = ChunkSearchNextInList(first->next_, tag); @@ -114,14 +99,14 @@ WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) { // Outputs a pointer to 'prev_chunk->next_', // where 'prev_chunk' is the pointer to the chunk at position (nth - 1). -// Returns true if nth chunk was found. +// Returns 1 if nth chunk was found, 0 otherwise. static int ChunkSearchListToSet(WebPChunk** chunk_list, uint32_t nth, WebPChunk*** const location) { uint32_t count = 0; - assert(chunk_list != NULL); + assert(chunk_list); *location = chunk_list; - while (*chunk_list != NULL) { + while (*chunk_list) { WebPChunk* const cur_chunk = *chunk_list; ++count; if (count == nth) return 1; // Found. @@ -139,25 +124,34 @@ static int ChunkSearchListToSet(WebPChunk** chunk_list, uint32_t nth, WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data, int copy_data, uint32_t tag) { // For internally allocated chunks, always copy data & make it owner of data. - if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].tag) { + if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_LOOP].tag) { copy_data = 1; } ChunkRelease(chunk); if (data != NULL) { - if (copy_data) { // Copy data. - if (!WebPDataCopy(data, &chunk->data_)) return WEBP_MUX_MEMORY_ERROR; - chunk->owner_ = 1; // Chunk is owner of data. - } else { // Don't copy data. + if (copy_data) { + // Copy data. + chunk->data_.bytes_ = (uint8_t*)malloc(data->size_); + if (chunk->data_.bytes_ == NULL) return WEBP_MUX_MEMORY_ERROR; + memcpy((uint8_t*)chunk->data_.bytes_, data->bytes_, data->size_); + chunk->data_.size_ = data->size_; + + // Chunk is owner of data. + chunk->owner_ = 1; + } else { + // Don't copy data. chunk->data_ = *data; } } + chunk->tag_ = tag; + return WEBP_MUX_OK; } -WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list, +WebPMuxError ChunkSetNth(const WebPChunk* chunk, WebPChunk** chunk_list, uint32_t nth) { WebPChunk* new_chunk; @@ -168,7 +162,6 @@ WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list, new_chunk = (WebPChunk*)malloc(sizeof(*new_chunk)); if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR; *new_chunk = *chunk; - chunk->owner_ = 0; new_chunk->next_ = *chunk_list; *chunk_list = new_chunk; return WEBP_MUX_OK; @@ -183,43 +176,66 @@ WebPChunk* ChunkDelete(WebPChunk* const chunk) { return next; } -void ChunkListDelete(WebPChunk** const chunk_list) { - while (*chunk_list != NULL) { - *chunk_list = ChunkDelete(*chunk_list); - } -} - //------------------------------------------------------------------------------ // Chunk serialization methods. +size_t ChunksListDiskSize(const WebPChunk* chunk_list) { + size_t size = 0; + while (chunk_list) { + size += ChunkDiskSize(chunk_list); + chunk_list = chunk_list->next_; + } + return size; +} + static uint8_t* ChunkEmit(const WebPChunk* const chunk, uint8_t* dst) { - const size_t chunk_size = chunk->data_.size; + const size_t chunk_size = chunk->data_.size_; assert(chunk); assert(chunk->tag_ != NIL_TAG); PutLE32(dst + 0, chunk->tag_); PutLE32(dst + TAG_SIZE, (uint32_t)chunk_size); assert(chunk_size == (uint32_t)chunk_size); - memcpy(dst + CHUNK_HEADER_SIZE, chunk->data_.bytes, chunk_size); + memcpy(dst + CHUNK_HEADER_SIZE, chunk->data_.bytes_, chunk_size); if (chunk_size & 1) dst[CHUNK_HEADER_SIZE + chunk_size] = 0; // Add padding. return dst + ChunkDiskSize(chunk); } uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) { - while (chunk_list != NULL) { + while (chunk_list) { dst = ChunkEmit(chunk_list, dst); chunk_list = chunk_list->next_; } return dst; } -size_t ChunkListDiskSize(const WebPChunk* chunk_list) { - size_t size = 0; - while (chunk_list != NULL) { - size += ChunkDiskSize(chunk_list); - chunk_list = chunk_list->next_; +//------------------------------------------------------------------------------ +// Manipulation of a WebPData object. + +void WebPDataInit(WebPData* webp_data) { + if (webp_data != NULL) { + memset(webp_data, 0, sizeof(*webp_data)); } - return size; +} + +void WebPDataClear(WebPData* webp_data) { + if (webp_data != NULL) { + free((void*)webp_data->bytes_); + WebPDataInit(webp_data); + } +} + +int WebPDataCopy(const WebPData* src, WebPData* dst) { + if (src == NULL || dst == NULL) return 0; + + WebPDataInit(dst); + if (src->bytes_ != NULL && src->size_ != 0) { + dst->bytes_ = (uint8_t*)malloc(src->size_); + if (dst->bytes_ == NULL) return 0; + memcpy((void*)dst->bytes_, src->bytes_, src->size_); + dst->size_ = src->size_; + } + return 1; } //------------------------------------------------------------------------------ @@ -236,7 +252,6 @@ WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) { ChunkDelete(wpi->header_); ChunkDelete(wpi->alpha_); ChunkDelete(wpi->img_); - ChunkListDelete(&wpi->unknown_); next = wpi->next_; MuxImageInit(wpi); @@ -246,31 +261,14 @@ WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) { //------------------------------------------------------------------------------ // MuxImage search methods. -// Get a reference to appropriate chunk list within an image given chunk tag. -static WebPChunk** GetChunkListFromId(const WebPMuxImage* const wpi, - WebPChunkId id) { - assert(wpi != NULL); - switch (id) { - case WEBP_CHUNK_ANMF: - case WEBP_CHUNK_FRGM: return (WebPChunk**)&wpi->header_; - case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha_; - case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img_; - default: return NULL; - } -} - int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) { int count = 0; const WebPMuxImage* current; for (current = wpi_list; current != NULL; current = current->next_) { - if (id == WEBP_CHUNK_NIL) { - ++count; // Special case: count all images. - } else { - const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id); - if (wpi_chunk != NULL) { - const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_); - if (wpi_chunk_id == id) ++count; // Count images with a matching 'id'. - } + const WebPChunk* const wpi_chunk = *MuxImageGetListFromId(current, id); + if (wpi_chunk != NULL) { + const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_); + if (wpi_chunk_id == id) ++count; } } return count; @@ -278,22 +276,34 @@ int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) { // Outputs a pointer to 'prev_wpi->next_', // where 'prev_wpi' is the pointer to the image at position (nth - 1). -// Returns true if nth image was found. +// Returns 1 if nth image with given id was found, 0 otherwise. static int SearchImageToGetOrDelete(WebPMuxImage** wpi_list, uint32_t nth, + WebPChunkId id, WebPMuxImage*** const location) { uint32_t count = 0; assert(wpi_list); *location = wpi_list; + // Search makes sense only for the following. + assert(id == WEBP_CHUNK_FRAME || id == WEBP_CHUNK_TILE || + id == WEBP_CHUNK_IMAGE); + assert(id != WEBP_CHUNK_IMAGE || nth == 1); + if (nth == 0) { - nth = MuxImageCount(*wpi_list, WEBP_CHUNK_NIL); + nth = MuxImageCount(*wpi_list, id); if (nth == 0) return 0; // Not found. } - while (*wpi_list != NULL) { + while (*wpi_list) { WebPMuxImage* const cur_wpi = *wpi_list; - ++count; - if (count == nth) return 1; // Found. + const WebPChunk* const wpi_chunk = *MuxImageGetListFromId(cur_wpi, id); + if (wpi_chunk != NULL) { + const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_); + if (wpi_chunk_id == id) { + ++count; + if (count == nth) return 1; // Found. + } + } wpi_list = &cur_wpi->next_; *location = wpi_list; } @@ -335,9 +345,16 @@ WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) { return next; } -WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) { +void MuxImageDeleteAll(WebPMuxImage** const wpi_list) { + while (*wpi_list) { + *wpi_list = MuxImageDelete(*wpi_list); + } +} + +WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth, + WebPChunkId id) { assert(wpi_list); - if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) { + if (!SearchImageToGetOrDelete(wpi_list, nth, id, &wpi_list)) { return WEBP_MUX_NOT_FOUND; } *wpi_list = MuxImageDelete(*wpi_list); @@ -348,10 +365,10 @@ WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) { // MuxImage reader methods. WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth, - WebPMuxImage** wpi) { + WebPChunkId id, WebPMuxImage** wpi) { assert(wpi_list); assert(wpi); - if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth, + if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth, id, (WebPMuxImage***)&wpi_list)) { return WEBP_MUX_NOT_FOUND; } @@ -368,48 +385,47 @@ size_t MuxImageDiskSize(const WebPMuxImage* const wpi) { if (wpi->header_ != NULL) size += ChunkDiskSize(wpi->header_); if (wpi->alpha_ != NULL) size += ChunkDiskSize(wpi->alpha_); if (wpi->img_ != NULL) size += ChunkDiskSize(wpi->img_); - if (wpi->unknown_ != NULL) size += ChunkListDiskSize(wpi->unknown_); return size; } -// Special case as ANMF/FRGM chunk encapsulates other image chunks. -static uint8_t* ChunkEmitSpecial(const WebPChunk* const header, - size_t total_size, uint8_t* dst) { - const size_t header_size = header->data_.size; - const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE; - assert(header->tag_ == kChunks[IDX_ANMF].tag || - header->tag_ == kChunks[IDX_FRGM].tag); - PutLE32(dst + 0, header->tag_); - PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next); - assert(header_size == (uint32_t)header_size); - memcpy(dst + CHUNK_HEADER_SIZE, header->data_.bytes, header_size); - if (header_size & 1) { - dst[CHUNK_HEADER_SIZE + header_size] = 0; // Add padding. +size_t MuxImageListDiskSize(const WebPMuxImage* wpi_list) { + size_t size = 0; + while (wpi_list) { + size += MuxImageDiskSize(wpi_list); + wpi_list = wpi_list->next_; } - return dst + ChunkDiskSize(header); + return size; } uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) { // Ordering of chunks to be emitted is strictly as follows: - // 1. ANMF/FRGM chunk (if present). - // 2. ALPH chunk (if present). + // 1. Frame/Tile chunk (if present). + // 2. Alpha chunk (if present). // 3. VP8/VP8L chunk. assert(wpi); - if (wpi->header_ != NULL) { - dst = ChunkEmitSpecial(wpi->header_, MuxImageDiskSize(wpi), dst); - } + if (wpi->header_ != NULL) dst = ChunkEmit(wpi->header_, dst); if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst); if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst); - if (wpi->unknown_ != NULL) dst = ChunkListEmit(wpi->unknown_, dst); + return dst; +} + +uint8_t* MuxImageListEmit(const WebPMuxImage* wpi_list, uint8_t* dst) { + while (wpi_list) { + dst = MuxImageEmit(wpi_list, dst); + wpi_list = wpi_list->next_; + } return dst; } //------------------------------------------------------------------------------ // Helper methods for mux. -int MuxHasAlpha(const WebPMuxImage* images) { +int MuxHasLosslessImages(const WebPMuxImage* images) { while (images != NULL) { - if (images->has_alpha_) return 1; + assert(images->img_ != NULL); + if (images->img_->tag_ == kChunks[IDX_VP8L].tag) { + return 1; + } images = images->next_; } return 0; @@ -425,13 +441,30 @@ uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) { WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) { assert(mux != NULL); - switch (id) { + switch(id) { case WEBP_CHUNK_VP8X: return (WebPChunk**)&mux->vp8x_; case WEBP_CHUNK_ICCP: return (WebPChunk**)&mux->iccp_; - case WEBP_CHUNK_ANIM: return (WebPChunk**)&mux->anim_; - case WEBP_CHUNK_EXIF: return (WebPChunk**)&mux->exif_; - case WEBP_CHUNK_XMP: return (WebPChunk**)&mux->xmp_; - default: return (WebPChunk**)&mux->unknown_; + case WEBP_CHUNK_LOOP: return (WebPChunk**)&mux->loop_; + case WEBP_CHUNK_META: return (WebPChunk**)&mux->meta_; + case WEBP_CHUNK_UNKNOWN: return (WebPChunk**)&mux->unknown_; + default: return NULL; + } +} + +WebPMuxError MuxValidateForImage(const WebPMux* const mux) { + const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE); + const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_FRAME); + const int num_tiles = MuxImageCount(mux->images_, WEBP_CHUNK_TILE); + + if (num_images == 0) { + // No images in mux. + return WEBP_MUX_NOT_FOUND; + } else if (num_images == 1 && num_frames == 0 && num_tiles == 0) { + // Valid case (single image). + return WEBP_MUX_OK; + } else { + // Frame/Tile case OR an invalid mux. + return WEBP_MUX_INVALID_ARGUMENT; } } @@ -447,7 +480,7 @@ static int IsNotCompatible(int feature, int num_items) { // On success returns WEBP_MUX_OK and stores the chunk count in *num. static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx, WebPFeatureFlags feature, - uint32_t vp8x_flags, + WebPFeatureFlags vp8x_flags, int max, int* num) { const WebPMuxError err = WebPMuxNumChunks(mux, kChunks[idx].id, num); @@ -461,11 +494,10 @@ static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx, WebPMuxError MuxValidate(const WebPMux* const mux) { int num_iccp; - int num_exif; - int num_xmp; - int num_anim; + int num_meta; + int num_loop_chunks; int num_frames; - int num_fragments; + int num_tiles; int num_vp8x; int num_images; int num_alpha; @@ -485,33 +517,29 @@ WebPMuxError MuxValidate(const WebPMux* const mux) { err = ValidateChunk(mux, IDX_ICCP, ICCP_FLAG, flags, 1, &num_iccp); if (err != WEBP_MUX_OK) return err; - // At most one EXIF metadata. - err = ValidateChunk(mux, IDX_EXIF, EXIF_FLAG, flags, 1, &num_exif); - if (err != WEBP_MUX_OK) return err; - // At most one XMP metadata. - err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp); + err = ValidateChunk(mux, IDX_META, META_FLAG, flags, 1, &num_meta); if (err != WEBP_MUX_OK) return err; - // Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent. - // At most one ANIM chunk. - err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim); + // Animation: ANIMATION_FLAG, loop chunk and frame chunk(s) are consistent. + // At most one loop chunk. + err = ValidateChunk(mux, IDX_LOOP, NO_FLAG, flags, 1, &num_loop_chunks); if (err != WEBP_MUX_OK) return err; - err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames); + err = ValidateChunk(mux, IDX_FRAME, NO_FLAG, flags, -1, &num_frames); if (err != WEBP_MUX_OK) return err; { const int has_animation = !!(flags & ANIMATION_FLAG); - if (has_animation && (num_anim == 0 || num_frames == 0)) { + if (has_animation && (num_loop_chunks == 0 || num_frames == 0)) { return WEBP_MUX_INVALID_ARGUMENT; } - if (!has_animation && (num_anim == 1 || num_frames > 0)) { + if (!has_animation && (num_loop_chunks == 1 || num_frames > 0)) { return WEBP_MUX_INVALID_ARGUMENT; } } - // Fragmentation: FRAGMENTS_FLAG and FRGM chunk(s) are consistent. - err = ValidateChunk(mux, IDX_FRGM, FRAGMENTS_FLAG, flags, -1, &num_fragments); + // Tiling: TILE_FLAG and tile chunk(s) are consistent. + err = ValidateChunk(mux, IDX_TILE, TILE_FLAG, flags, -1, &num_tiles); if (err != WEBP_MUX_OK) return err; // Verify either VP8X chunk is present OR there is only one elem in @@ -523,22 +551,16 @@ WebPMuxError MuxValidate(const WebPMux* const mux) { if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT; // ALPHA_FLAG & alpha chunk(s) are consistent. - if (MuxHasAlpha(mux->images_)) { - if (num_vp8x > 0) { - // VP8X chunk is present, so it should contain ALPHA_FLAG. - if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT; - } else { - // VP8X chunk is not present, so ALPH chunks should NOT be present either. - err = WebPMuxNumChunks(mux, WEBP_CHUNK_ALPHA, &num_alpha); - if (err != WEBP_MUX_OK) return err; - if (num_alpha > 0) return WEBP_MUX_INVALID_ARGUMENT; - } - } else { // Mux doesn't need alpha. So, ALPHA_FLAG should NOT be present. - if (flags & ALPHA_FLAG) return WEBP_MUX_INVALID_ARGUMENT; + if (num_vp8x > 0 && MuxHasLosslessImages(mux->images_)) { + // Special case: we have a VP8X chunk as well as some lossless images. + if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT; + } else { + err = ValidateChunk(mux, IDX_ALPHA, ALPHA_FLAG, flags, -1, &num_alpha); + if (err != WEBP_MUX_OK) return err; } - // num_fragments & num_images are consistent. - if (num_fragments > 0 && num_images != num_fragments) { + // num_tiles & num_images are consistent. + if (num_tiles > 0 && num_images != num_tiles) { return WEBP_MUX_INVALID_ARGUMENT; } @@ -549,3 +571,6 @@ WebPMuxError MuxValidate(const WebPMux* const mux) { //------------------------------------------------------------------------------ +#if defined(__cplusplus) || defined(c_plusplus) +} // extern "C" +#endif diff --git a/drivers/webp/mux/muxread.c b/drivers/webp/mux/muxread.c index 6003a25b71..21c3cfbaeb 100644 --- a/drivers/webp/mux/muxread.c +++ b/drivers/webp/mux/muxread.c @@ -1,10 +1,8 @@ // Copyright 2011 Google Inc. All Rights Reserved. // -// Use of this source code is governed by a BSD-style license -// that can be found in the COPYING file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. +// This code is licensed under the same terms as WebM: +// Software License Agreement: http://www.webmproject.org/license/software/ +// Additional IP Rights Grant: http://www.webmproject.org/license/additional/ // ----------------------------------------------------------------------------- // // Read APIs for mux. @@ -14,7 +12,10 @@ #include <assert.h> #include "./muxi.h" -#include "../utils/utils.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif //------------------------------------------------------------------------------ // Helper method(s). @@ -40,9 +41,8 @@ static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx, SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_); SWITCH_ID_LIST(IDX_ICCP, mux->iccp_); - SWITCH_ID_LIST(IDX_ANIM, mux->anim_); - SWITCH_ID_LIST(IDX_EXIF, mux->exif_); - SWITCH_ID_LIST(IDX_XMP, mux->xmp_); + SWITCH_ID_LIST(IDX_LOOP, mux->loop_); + SWITCH_ID_LIST(IDX_META, mux->meta_); SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_); return WEBP_MUX_NOT_FOUND; } @@ -50,9 +50,10 @@ static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx, // Fill the chunk with the given data (includes chunk header bytes), after some // verifications. -static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk, - const uint8_t* data, size_t data_size, - size_t riff_size, int copy_data) { +static WebPMuxError ChunkVerifyAndAssignData(WebPChunk* chunk, + const uint8_t* data, + size_t data_size, size_t riff_size, + int copy_data) { uint32_t chunk_size; WebPData chunk_data; @@ -67,103 +68,11 @@ static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk, } // Data assignment. - chunk_data.bytes = data + CHUNK_HEADER_SIZE; - chunk_data.size = chunk_size; + chunk_data.bytes_ = data + CHUNK_HEADER_SIZE; + chunk_data.size_ = chunk_size; return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0)); } -int MuxImageFinalize(WebPMuxImage* const wpi) { - const WebPChunk* const img = wpi->img_; - const WebPData* const image = &img->data_; - const int is_lossless = (img->tag_ == kChunks[IDX_VP8L].tag); - int w, h; - int vp8l_has_alpha = 0; - const int ok = is_lossless ? - VP8LGetInfo(image->bytes, image->size, &w, &h, &vp8l_has_alpha) : - VP8GetInfo(image->bytes, image->size, image->size, &w, &h); - assert(img != NULL); - if (ok) { - // Ignore ALPH chunk accompanying VP8L. - if (is_lossless && (wpi->alpha_ != NULL)) { - ChunkDelete(wpi->alpha_); - wpi->alpha_ = NULL; - } - wpi->width_ = w; - wpi->height_ = h; - wpi->has_alpha_ = vp8l_has_alpha || (wpi->alpha_ != NULL); - } - return ok; -} - -static int MuxImageParse(const WebPChunk* const chunk, int copy_data, - WebPMuxImage* const wpi) { - const uint8_t* bytes = chunk->data_.bytes; - size_t size = chunk->data_.size; - const uint8_t* const last = bytes + size; - WebPChunk subchunk; - size_t subchunk_size; - ChunkInit(&subchunk); - - assert(chunk->tag_ == kChunks[IDX_ANMF].tag || - chunk->tag_ == kChunks[IDX_FRGM].tag); - assert(!wpi->is_partial_); - - // ANMF/FRGM. - { - const size_t hdr_size = (chunk->tag_ == kChunks[IDX_ANMF].tag) ? - ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE; - const WebPData temp = { bytes, hdr_size }; - // Each of ANMF and FRGM chunk contain a header at the beginning. So, its - // size should at least be 'hdr_size'. - if (size < hdr_size) goto Fail; - ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_); - } - ChunkSetNth(&subchunk, &wpi->header_, 1); - wpi->is_partial_ = 1; // Waiting for ALPH and/or VP8/VP8L chunks. - - // Rest of the chunks. - subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE; - bytes += subchunk_size; - size -= subchunk_size; - - while (bytes != last) { - ChunkInit(&subchunk); - if (ChunkVerifyAndAssign(&subchunk, bytes, size, size, - copy_data) != WEBP_MUX_OK) { - goto Fail; - } - switch (ChunkGetIdFromTag(subchunk.tag_)) { - case WEBP_CHUNK_ALPHA: - if (wpi->alpha_ != NULL) goto Fail; // Consecutive ALPH chunks. - if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail; - wpi->is_partial_ = 1; // Waiting for a VP8 chunk. - break; - case WEBP_CHUNK_IMAGE: - if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail; - if (!MuxImageFinalize(wpi)) goto Fail; - wpi->is_partial_ = 0; // wpi is completely filled. - break; - case WEBP_CHUNK_UNKNOWN: - if (wpi->is_partial_) goto Fail; // Encountered an unknown chunk - // before some image chunks. - if (ChunkSetNth(&subchunk, &wpi->unknown_, 0) != WEBP_MUX_OK) goto Fail; - break; - default: - goto Fail; - break; - } - subchunk_size = ChunkDiskSize(&subchunk); - bytes += subchunk_size; - size -= subchunk_size; - } - if (wpi->is_partial_) goto Fail; - return 1; - - Fail: - ChunkRelease(&subchunk); - return 0; -} - //------------------------------------------------------------------------------ // Create a mux object from WebP-RIFF data. @@ -185,8 +94,8 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, } if (bitstream == NULL) return NULL; - data = bitstream->bytes; - size = bitstream->size; + data = bitstream->bytes_; + size = bitstream->size_; if (data == NULL) return NULL; if (size < RIFF_HEADER_SIZE) return NULL; @@ -226,48 +135,42 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, // Loop over chunks. while (data != end) { - size_t data_size; WebPChunkId id; - WebPChunk** chunk_list; - if (ChunkVerifyAndAssign(&chunk, data, size, riff_size, - copy_data) != WEBP_MUX_OK) { - goto Err; - } - data_size = ChunkDiskSize(&chunk); + WebPMuxError err; + + err = ChunkVerifyAndAssignData(&chunk, data, size, riff_size, copy_data); + if (err != WEBP_MUX_OK) goto Err; + id = ChunkGetIdFromTag(chunk.tag_); - switch (id) { - case WEBP_CHUNK_ALPHA: - if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks. - if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err; - wpi->is_partial_ = 1; // Waiting for a VP8 chunk. - break; - case WEBP_CHUNK_IMAGE: - if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err; - if (!MuxImageFinalize(wpi)) goto Err; + + if (IsWPI(id)) { // An image chunk (frame/tile/alpha/vp8). + WebPChunk** wpi_chunk_ptr = + MuxImageGetListFromId(wpi, id); // Image chunk to set. + assert(wpi_chunk_ptr != NULL); + if (*wpi_chunk_ptr != NULL) goto Err; // Consecutive alpha chunks or + // consecutive frame/tile chunks. + if (ChunkSetNth(&chunk, wpi_chunk_ptr, 1) != WEBP_MUX_OK) goto Err; + if (id == WEBP_CHUNK_IMAGE) { wpi->is_partial_ = 0; // wpi is completely filled. - PushImage: // Add this to mux->images_ list. if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err; MuxImageInit(wpi); // Reset for reading next image. - break; - case WEBP_CHUNK_ANMF: -#ifdef WEBP_EXPERIMENTAL_FEATURES - case WEBP_CHUNK_FRGM: -#endif - if (wpi->is_partial_) goto Err; // Previous wpi is still incomplete. - if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err; - ChunkRelease(&chunk); - goto PushImage; - break; - default: // A non-image chunk. - if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before - // getting all chunks of an image. - chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk. - if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err; - break; + } else { + wpi->is_partial_ = 1; // wpi is only partially filled. + } + } else { // A non-image chunk. + WebPChunk** chunk_list; + if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before + // getting all chunks of an image. + chunk_list = MuxGetChunkListFromId(mux, id); // List to add this chunk. + if (chunk_list == NULL) chunk_list = &mux->unknown_; + if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err; + } + { + const size_t data_size = ChunkDiskSize(&chunk); + data += data_size; + size -= data_size; } - data += data_size; - size -= data_size; ChunkInit(&chunk); } @@ -287,66 +190,29 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, //------------------------------------------------------------------------------ // Get API(s). -// Validates that the given mux has a single image. -static WebPMuxError ValidateForSingleImage(const WebPMux* const mux) { - const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE); - const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF); - const int num_fragments = MuxImageCount(mux->images_, WEBP_CHUNK_FRGM); - - if (num_images == 0) { - // No images in mux. - return WEBP_MUX_NOT_FOUND; - } else if (num_images == 1 && num_frames == 0 && num_fragments == 0) { - // Valid case (single image). - return WEBP_MUX_OK; - } else { - // Frame/Fragment case OR an invalid mux. - return WEBP_MUX_INVALID_ARGUMENT; - } -} - -// Get the canvas width, height and flags after validating that VP8X/VP8/VP8L -// chunk and canvas size are valid. -static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux, - int* width, int* height, uint32_t* flags) { - int w, h; - uint32_t f = 0; +WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) { WebPData data; - assert(mux != NULL); + WebPMuxError err; + + if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT; + *flags = 0; // Check if VP8X chunk is present. - if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) { - if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA; - f = GetLE32(data.bytes + 0); - w = GetLE24(data.bytes + 4) + 1; - h = GetLE24(data.bytes + 7) + 1; - } else { // Single image case. - const WebPMuxImage* const wpi = mux->images_; - WebPMuxError err = ValidateForSingleImage(mux); - if (err != WEBP_MUX_OK) return err; - assert(wpi != NULL); - w = wpi->width_; - h = wpi->height_; - if (wpi->has_alpha_) f |= ALPHA_FLAG; + err = MuxGet(mux, IDX_VP8X, 1, &data); + if (err == WEBP_MUX_NOT_FOUND) { + // Check if VP8/VP8L chunk is present. + err = WebPMuxGetImage(mux, &data); + WebPDataClear(&data); + return err; + } else if (err != WEBP_MUX_OK) { + return err; } - if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA; - if (width != NULL) *width = w; - if (height != NULL) *height = h; - if (flags != NULL) *flags = f; - return WEBP_MUX_OK; -} - -WebPMuxError WebPMuxGetCanvasSize(const WebPMux* mux, int* width, int* height) { - if (mux == NULL || width == NULL || height == NULL) { - return WEBP_MUX_INVALID_ARGUMENT; - } - return MuxGetCanvasInfo(mux, width, height, NULL); -} + if (data.size_ < CHUNK_SIZE_BYTES) return WEBP_MUX_BAD_DATA; -WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) { - if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT; - return MuxGetCanvasInfo(mux, NULL, NULL, flags); + // All OK. Fill up flags. + *flags = GetLE32(data.bytes_); + return WEBP_MUX_OK; } static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width, @@ -364,7 +230,7 @@ static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width, } // Assemble a single image WebP bitstream from 'wpi'. -static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi, +static WebPMuxError SynthesizeBitstream(WebPMuxImage* const wpi, WebPData* const bitstream) { uint8_t* dst; @@ -372,7 +238,7 @@ static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi, const int need_vp8x = (wpi->alpha_ != NULL); const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0; const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha_) : 0; - // Note: No need to output ANMF/FRGM chunk for a single image. + // Note: No need to output FRM/TILE chunk for a single image. const size_t size = RIFF_HEADER_SIZE + vp8x_size + alpha_size + ChunkDiskSize(wpi->img_); uint8_t* const data = (uint8_t*)malloc(size); @@ -382,7 +248,15 @@ static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi, dst = MuxEmitRiffHeader(data, size); if (need_vp8x) { - dst = EmitVP8XChunk(dst, wpi->width_, wpi->height_, ALPHA_FLAG); // VP8X. + int w, h; + WebPMuxError err; + assert(wpi->img_ != NULL); + err = MuxGetImageWidthHeight(wpi->img_, &w, &h); + if (err != WEBP_MUX_OK) { + free(data); + return err; + } + dst = EmitVP8XChunk(dst, w, h, ALPHA_FLAG); // VP8X. dst = ChunkListEmit(wpi->alpha_, dst); // ALPH. } @@ -391,117 +265,107 @@ static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi, assert(dst == data + size); // Output. - bitstream->bytes = data; - bitstream->size = size; + bitstream->bytes_ = data; + bitstream->size_ = size; return WEBP_MUX_OK; } -WebPMuxError WebPMuxGetChunk(const WebPMux* mux, const char fourcc[4], - WebPData* chunk_data) { - CHUNK_INDEX idx; - if (mux == NULL || fourcc == NULL || chunk_data == NULL) { - return WEBP_MUX_INVALID_ARGUMENT; - } - idx = ChunkGetIndexFromFourCC(fourcc); - if (IsWPI(kChunks[idx].id)) { // An image chunk. +WebPMuxError WebPMuxGetImage(const WebPMux* mux, WebPData* bitstream) { + WebPMuxError err; + WebPMuxImage* wpi = NULL; + + if (mux == NULL || bitstream == NULL) { return WEBP_MUX_INVALID_ARGUMENT; - } else if (idx != IDX_UNKNOWN) { // A known chunk type. - return MuxGet(mux, idx, 1, chunk_data); - } else { // An unknown chunk type. - const WebPChunk* const chunk = - ChunkSearchList(mux->unknown_, 1, ChunkGetTagFromFourCC(fourcc)); - if (chunk == NULL) return WEBP_MUX_NOT_FOUND; - *chunk_data = chunk->data_; - return WEBP_MUX_OK; } + + err = MuxValidateForImage(mux); + if (err != WEBP_MUX_OK) return err; + + // All well. Get the image. + err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, 1, WEBP_CHUNK_IMAGE, + &wpi); + assert(err == WEBP_MUX_OK); // Already tested above. + + return SynthesizeBitstream(wpi, bitstream); } -static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi, - WebPMuxFrameInfo* const info) { - // Set some defaults for unrelated fields. - info->x_offset = 0; - info->y_offset = 0; - info->duration = 1; - info->dispose_method = WEBP_MUX_DISPOSE_NONE; - info->blend_method = WEBP_MUX_BLEND; - // Extract data for related fields. - info->id = ChunkGetIdFromTag(wpi->img_->tag_); - return SynthesizeBitstream(wpi, &info->bitstream); +WebPMuxError WebPMuxGetMetadata(const WebPMux* mux, WebPData* metadata) { + if (mux == NULL || metadata == NULL) return WEBP_MUX_INVALID_ARGUMENT; + return MuxGet(mux, IDX_META, 1, metadata); } -static WebPMuxError MuxGetFrameFragmentInternal(const WebPMuxImage* const wpi, - WebPMuxFrameInfo* const frame) { - const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag); - const CHUNK_INDEX idx = is_frame ? IDX_ANMF : IDX_FRGM; - const WebPData* frame_frgm_data; -#ifndef WEBP_EXPERIMENTAL_FEATURES - if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT; -#endif - assert(wpi->header_ != NULL); // Already checked by WebPMuxGetFrame(). - // Get frame/fragment chunk. - frame_frgm_data = &wpi->header_->data_; - if (frame_frgm_data->size < kChunks[idx].size) return WEBP_MUX_BAD_DATA; - // Extract info. - frame->x_offset = 2 * GetLE24(frame_frgm_data->bytes + 0); - frame->y_offset = 2 * GetLE24(frame_frgm_data->bytes + 3); - if (is_frame) { - const uint8_t bits = frame_frgm_data->bytes[15]; - frame->duration = GetLE24(frame_frgm_data->bytes + 12); - frame->dispose_method = - (bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE; - frame->blend_method = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND; - } else { // Defaults for unused values. - frame->duration = 1; - frame->dispose_method = WEBP_MUX_DISPOSE_NONE; - frame->blend_method = WEBP_MUX_BLEND; - } - frame->id = ChunkGetIdFromTag(wpi->header_->tag_); - return SynthesizeBitstream(wpi, &frame->bitstream); +WebPMuxError WebPMuxGetColorProfile(const WebPMux* mux, + WebPData* color_profile) { + if (mux == NULL || color_profile == NULL) return WEBP_MUX_INVALID_ARGUMENT; + return MuxGet(mux, IDX_ICCP, 1, color_profile); } -WebPMuxError WebPMuxGetFrame( - const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame) { +WebPMuxError WebPMuxGetLoopCount(const WebPMux* mux, int* loop_count) { + WebPData image; + WebPMuxError err; + + if (mux == NULL || loop_count == NULL) return WEBP_MUX_INVALID_ARGUMENT; + + err = MuxGet(mux, IDX_LOOP, 1, &image); + if (err != WEBP_MUX_OK) return err; + if (image.size_ < kChunks[WEBP_CHUNK_LOOP].size) return WEBP_MUX_BAD_DATA; + *loop_count = GetLE16(image.bytes_); + + return WEBP_MUX_OK; +} + +static WebPMuxError MuxGetFrameTileInternal( + const WebPMux* const mux, uint32_t nth, WebPData* const bitstream, + int* const x_offset, int* const y_offset, int* const duration, + uint32_t tag) { + const WebPData* frame_tile_data; WebPMuxError err; WebPMuxImage* wpi; - // Sanity checks. - if (mux == NULL || frame == NULL) { + const int is_frame = (tag == kChunks[WEBP_CHUNK_FRAME].tag) ? 1 : 0; + const CHUNK_INDEX idx = is_frame ? IDX_FRAME : IDX_TILE; + const WebPChunkId id = kChunks[idx].id; + + if (mux == NULL || bitstream == NULL || + x_offset == NULL || y_offset == NULL || (is_frame && duration == NULL)) { return WEBP_MUX_INVALID_ARGUMENT; } // Get the nth WebPMuxImage. - err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, &wpi); + err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, id, &wpi); if (err != WEBP_MUX_OK) return err; - // Get frame info. - if (wpi->header_ == NULL) { - return MuxGetImageInternal(wpi, frame); - } else { - return MuxGetFrameFragmentInternal(wpi, frame); - } -} + // Get frame chunk. + assert(wpi->header_ != NULL); // As MuxImageGetNth() already checked header_. + frame_tile_data = &wpi->header_->data_; -WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux, - WebPMuxAnimParams* params) { - WebPData anim; - WebPMuxError err; + if (frame_tile_data->size_ < kChunks[idx].size) return WEBP_MUX_BAD_DATA; + *x_offset = 2 * GetLE24(frame_tile_data->bytes_ + 0); + *y_offset = 2 * GetLE24(frame_tile_data->bytes_ + 3); + if (is_frame) *duration = 1 + GetLE24(frame_tile_data->bytes_ + 12); - if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT; + return SynthesizeBitstream(wpi, bitstream); +} - err = MuxGet(mux, IDX_ANIM, 1, &anim); - if (err != WEBP_MUX_OK) return err; - if (anim.size < kChunks[WEBP_CHUNK_ANIM].size) return WEBP_MUX_BAD_DATA; - params->bgcolor = GetLE32(anim.bytes); - params->loop_count = GetLE16(anim.bytes + 4); +WebPMuxError WebPMuxGetFrame(const WebPMux* mux, uint32_t nth, + WebPData* bitstream, + int* x_offset, int* y_offset, int* duration) { + return MuxGetFrameTileInternal(mux, nth, bitstream, x_offset, y_offset, + duration, kChunks[IDX_FRAME].tag); +} - return WEBP_MUX_OK; +WebPMuxError WebPMuxGetTile(const WebPMux* mux, uint32_t nth, + WebPData* bitstream, + int* x_offset, int* y_offset) { + return MuxGetFrameTileInternal(mux, nth, bitstream, x_offset, y_offset, NULL, + kChunks[IDX_TILE].tag); } // Get chunk index from chunk id. Returns IDX_NIL if not found. static CHUNK_INDEX ChunkGetIndexFromId(WebPChunkId id) { int i; for (i = 0; kChunks[i].id != WEBP_CHUNK_NIL; ++i) { - if (id == kChunks[i].id) return (CHUNK_INDEX)i; + if (id == kChunks[i].id) return i; } return IDX_NIL; } @@ -529,8 +393,12 @@ WebPMuxError WebPMuxNumChunks(const WebPMux* mux, *num_elements = MuxImageCount(mux->images_, id); } else { WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id); - const CHUNK_INDEX idx = ChunkGetIndexFromId(id); - *num_elements = CountChunks(*chunk_list, kChunks[idx].tag); + if (chunk_list == NULL) { + *num_elements = 0; + } else { + const CHUNK_INDEX idx = ChunkGetIndexFromId(id); + *num_elements = CountChunks(*chunk_list, kChunks[idx].tag); + } } return WEBP_MUX_OK; @@ -538,3 +406,6 @@ WebPMuxError WebPMuxNumChunks(const WebPMux* mux, //------------------------------------------------------------------------------ +#if defined(__cplusplus) || defined(c_plusplus) +} // extern "C" +#endif |