diff options
Diffstat (limited to 'drivers/webpold/mux')
-rw-r--r-- | drivers/webpold/mux/demux.c | 902 | ||||
-rw-r--r-- | drivers/webpold/mux/muxedit.c | 712 | ||||
-rw-r--r-- | drivers/webpold/mux/muxi.h | 271 | ||||
-rw-r--r-- | drivers/webpold/mux/muxinternal.c | 576 | ||||
-rw-r--r-- | drivers/webpold/mux/muxread.c | 411 |
5 files changed, 2872 insertions, 0 deletions
diff --git a/drivers/webpold/mux/demux.c b/drivers/webpold/mux/demux.c new file mode 100644 index 0000000000..501d08f41d --- /dev/null +++ b/drivers/webpold/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 "../mux.h" + +#include <stdlib.h> +#include <string.h> + +#include "../decode.h" // WebPGetInfo +#include "../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/webpold/mux/muxedit.c b/drivers/webpold/mux/muxedit.c new file mode 100644 index 0000000000..08629d4ae2 --- /dev/null +++ b/drivers/webpold/mux/muxedit.c @@ -0,0 +1,712 @@ +// Copyright 2011 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/ +// ----------------------------------------------------------------------------- +// +// Set and delete APIs for mux. +// +// Authors: Urvang (urvang@google.com) +// Vikas (vikasa@google.com) + +#include <assert.h> +#include "./muxi.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Life of a mux object. + +static void MuxInit(WebPMux* const mux) { + if (mux == NULL) return; + memset(mux, 0, sizeof(*mux)); +} + +WebPMux* WebPNewInternal(int version) { + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) { + return NULL; + } else { + WebPMux* const mux = (WebPMux*)malloc(sizeof(WebPMux)); + // If mux is NULL MuxInit is a noop. + MuxInit(mux); + return mux; + } +} + +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; + MuxImageDeleteAll(&mux->images_); + DeleteAllChunks(&mux->vp8x_); + DeleteAllChunks(&mux->iccp_); + DeleteAllChunks(&mux->loop_); + DeleteAllChunks(&mux->meta_); + DeleteAllChunks(&mux->unknown_); +} + +void WebPMuxDelete(WebPMux* mux) { + // If mux is NULL MuxRelease is a noop. + MuxRelease(mux); + free(mux); +} + +//------------------------------------------------------------------------------ +// Helper method(s). + +// Handy MACRO, makes MuxSet() very symmetric to MuxGet(). +#define SWITCH_ID_LIST(INDEX, LIST) \ + if (idx == (INDEX)) { \ + 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, CHUNK_INDEX idx, uint32_t nth, + const WebPData* const data, int copy_data) { + WebPChunk chunk; + WebPMuxError err = WEBP_MUX_NOT_FOUND; + 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_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 + +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); +} + +// 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_tile_bytes = (uint8_t*)malloc(frame_tile_size); + if (frame_tile_bytes == NULL) return WEBP_MUX_MEMORY_ERROR; + + PutLE24(frame_tile_bytes + 0, x_offset / 2); + PutLE24(frame_tile_bytes + 3, y_offset / 2); + + if (is_frame) { + PutLE24(frame_tile_bytes + 6, width - 1); + PutLE24(frame_tile_bytes + 9, height - 1); + PutLE24(frame_tile_bytes + 12, duration - 1); + } + + frame_tile->bytes_ = frame_tile_bytes; + frame_tile->size_ = frame_tile_size; + return WEBP_MUX_OK; +} + +// Outputs image data given a bitstream. The bitstream can either be a +// single-image WebP file or raw VP8/VP8L data. +// Also outputs 'is_lossless' to be true if the given bitstream is lossless. +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)) { + // It is NOT webp file data. Return input data as is. + *image = *bitstream; + } else { + // It is webp file data. Extract image data from it. + const WebPMuxImage* wpi; + WebPMux* const mux = WebPMuxCreate(bitstream, 0); + if (mux == NULL) return WEBP_MUX_BAD_DATA; + wpi = mux->images_; + assert(wpi != NULL && wpi->img_ != NULL); + *image = wpi->img_->data_; + if (wpi->alpha_ != NULL) { + *alpha = wpi->alpha_->data_; + } + WebPMuxDelete(mux); + } + *is_lossless = VP8LCheckSignature(image->bytes_, image->size_); + return WEBP_MUX_OK; +} + +static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) { + WebPMuxError err = WEBP_MUX_NOT_FOUND; + assert(chunk_list); + while (*chunk_list) { + WebPChunk* const chunk = *chunk_list; + if (chunk->tag_ == tag) { + *chunk_list = ChunkDelete(chunk); + err = WEBP_MUX_OK; + } else { + chunk_list = &chunk->next_; + } + } + return err; +} + +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; + + 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 WebPMuxSetImage(WebPMux* mux, + const WebPData* bitstream, int copy_data) { + WebPMuxError err; + 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; + } + + // 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; + + // Delete the existing images. + MuxImageDeleteAll(&mux->images_); + + 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, &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; + + // 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; +} + +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; + } + + // 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 WebPMuxSetColorProfile(WebPMux* mux, const WebPData* color_profile, + int copy_data) { + WebPMuxError err; + + if (mux == NULL || color_profile == NULL || color_profile->bytes_ == NULL || + color_profile->size_ > MAX_CHUNK_PAYLOAD) { + return WEBP_MUX_INVALID_ARGUMENT; + } + + // Delete the existing ICCP chunk(s). + err = WebPMuxDeleteColorProfile(mux); + if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; + + // Add the given ICCP chunk. + return MuxSet(mux, IDX_ICCP, 1, color_profile, copy_data); +} + +WebPMuxError WebPMuxSetLoopCount(WebPMux* mux, int loop_count) { + WebPMuxError err; + uint8_t* data = NULL; + + if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; + if (loop_count >= MAX_LOOP_COUNT) return WEBP_MUX_INVALID_ARGUMENT; + + // 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; +} + +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; + 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 || bitstream == NULL || bitstream->bytes_ == NULL || + bitstream->size_ > MAX_CHUNK_PAYLOAD) { + return WEBP_MUX_INVALID_ARGUMENT; + } + 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; + } + + // Snap offsets to even positions. + x_offset &= ~1; + y_offset &= ~1; + + // 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); + + if (alpha.bytes_ != NULL) { + // Add alpha 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; + 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; + + // All is well. + return WEBP_MUX_OK; + + Err: // Something bad happened. + WebPDataClear(&frame_tile); + ChunkRelease(&chunk); + MuxImageRelease(&wpi); + return err; +} + +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; + + if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; + + err = MuxValidateForImage(mux); + if (err != WEBP_MUX_OK) return err; + + // All well, delete image. + MuxImageDeleteAll(&mux->images_); + return WEBP_MUX_OK; +} + +WebPMuxError WebPMuxDeleteMetadata(WebPMux* mux) { + return MuxDeleteAllNamedData(mux, IDX_META); +} + +WebPMuxError WebPMuxDeleteColorProfile(WebPMux* mux) { + return MuxDeleteAllNamedData(mux, IDX_ICCP); +} + +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; + + 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 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 ? 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 image_chunk = wpi->img_; + const WebPChunk* const frame_tile_chunk = wpi->header_; + + // 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. + return MuxGetImageWidthHeight(image_chunk, width, height); +} + +static WebPMuxError GetImageCanvasWidthHeight( + const WebPMux* const mux, uint32_t flags, + int* const width, int* const height) { + WebPMuxImage* wpi = NULL; + assert(mux != NULL); + assert(width != NULL && height != NULL); + + wpi = mux->images_; + assert(wpi != NULL); + assert(wpi->img_ != NULL); + + if (wpi->next_) { + int max_x = 0; + int max_y = 0; + int64_t image_area = 0; + // Aggregate the bounding box for animation frames & tiled images. + for (; wpi != NULL; wpi = wpi->next_) { + 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; + const int max_y_pos = y_offset + h; + if (err != WEBP_MUX_OK) return err; + assert(x_offset < MAX_POSITION_OFFSET); + assert(y_offset < MAX_POSITION_OFFSET); + + if (max_x_pos > max_x) max_x = max_x_pos; + if (max_y_pos > max_y) max_y = max_y_pos; + image_area += w * h; + } + *width = max_x; + *height = 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, 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; +} + +// VP8X format: +// Total Size : 10, +// Flags : 4 bytes, +// Width : 3 bytes, +// Height : 3 bytes. +static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { + WebPMuxError err = WEBP_MUX_OK; + uint32_t flags = 0; + int width = 0; + int height = 0; + uint8_t 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) { + 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, 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) { + flags |= ICCP_FLAG; + } + + if (mux->meta_ != NULL && mux->meta_->data_.bytes_ != NULL) { + flags |= META_FLAG; + } + + if (images->header_ != NULL) { + 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. + } + + if (flags == 0) { + // For Simple Image, VP8X chunk should not be added. + return WEBP_MUX_OK; + } + + err = GetImageCanvasWidthHeight(mux, flags, &width, &height); + if (err != WEBP_MUX_OK) return err; + + if (width <= 0 || height <= 0) { + return WEBP_MUX_INVALID_ARGUMENT; + } + if (width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) { + return WEBP_MUX_INVALID_ARGUMENT; + } + + 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; + } + + PutLE32(data + 0, flags); // VP8X chunk flags. + PutLE24(data + 4, width - 1); // canvas width. + PutLE24(data + 7, height - 1); // canvas height. + + 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; + } + + // 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 = 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; + + // Emit header & chunks. + dst = MuxEmitRiffHeader(data, size); + dst = ChunkListEmit(mux->vp8x_, dst); + dst = ChunkListEmit(mux->iccp_, 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); + + // Validate mux. + err = MuxValidate(mux); + if (err != WEBP_MUX_OK) { + free(data); + data = NULL; + size = 0; + } + + // Finalize. + assembled_data->bytes_ = data; + assembled_data->size_ = size; + + return err; +} + +//------------------------------------------------------------------------------ + +#if defined(__cplusplus) || defined(c_plusplus) +} // extern "C" +#endif diff --git a/drivers/webpold/mux/muxi.h b/drivers/webpold/mux/muxi.h new file mode 100644 index 0000000000..2f06f3ed03 --- /dev/null +++ b/drivers/webpold/mux/muxi.h @@ -0,0 +1,271 @@ +// Copyright 2011 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/ +// ----------------------------------------------------------------------------- +// +// Internal header for mux library. +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_MUX_MUXI_H_ +#define WEBP_MUX_MUXI_H_ + +#include <stdlib.h> +#include "../dec/vp8i.h" +#include "../dec/vp8li.h" +#include "../format_constants.h" +#include "../mux.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Defines and constants. + +// Chunk object. +typedef struct WebPChunk WebPChunk; +struct WebPChunk { + uint32_t tag_; + int owner_; // True if *data_ memory is owned internally. + // 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 frame/tile chunk, alpha +// chunk and VP8/VP8L chunk), +typedef struct WebPMuxImage WebPMuxImage; +struct WebPMuxImage { + WebPChunk* header_; // Corresponds to WEBP_CHUNK_FRAME/WEBP_CHUNK_TILE. + WebPChunk* alpha_; // Corresponds to WEBP_CHUNK_ALPHA. + WebPChunk* img_; // Corresponds to WEBP_CHUNK_IMAGE. + int is_partial_; // True if only some of the chunks are filled. + WebPMuxImage* next_; +}; + +// Main mux object. Stores data chunks. +struct WebPMux { + WebPMuxImage* images_; + WebPChunk* iccp_; + WebPChunk* meta_; + WebPChunk* loop_; + WebPChunk* vp8x_; + + WebPChunk* unknown_; +}; + +// CHUNK_INDEX enum: used for indexing within 'kChunks' (defined below) only. +// Note: the reason for having two enums ('WebPChunkId' and 'CHUNK_INDEX') is to +// allow two different chunks to have the same id (e.g. WebPChunkId +// 'WEBP_CHUNK_IMAGE' can correspond to CHUNK_INDEX 'IDX_VP8' or 'IDX_VP8L'). +typedef enum { + IDX_VP8X = 0, + IDX_ICCP, + IDX_LOOP, + IDX_FRAME, + IDX_TILE, + IDX_ALPHA, + IDX_VP8, + IDX_VP8L, + IDX_META, + IDX_UNKNOWN, + + IDX_NIL, + IDX_LAST_CHUNK +} CHUNK_INDEX; + +#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; + uint32_t size; +} ChunkInfo; + +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_NIL if not found. +CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag); + +// Get chunk id from chunk tag. Returns WEBP_CHUNK_NIL if not found. +WebPChunkId ChunkGetIdFromTag(uint32_t tag); + +// 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); + +// Fill the chunk with the given data. +WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data, + int copy_data, uint32_t tag); + +// Sets 'chunk' at nth position in the 'chunk_list'. +// nth = 0 has the special meaning "last of the list". +WebPMuxError ChunkSetNth(const WebPChunk* chunk, WebPChunk** chunk_list, + uint32_t nth); + +// Releases chunk and returns chunk->next_. +WebPChunk* ChunkRelease(WebPChunk* const chunk); + +// Deletes given chunk & returns chunk->next_. +WebPChunk* ChunkDelete(WebPChunk* const chunk); + +// 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_; + assert(data_size < MAX_CHUNK_PAYLOAD); + return SizeWithPadding(data_size); +} + +// Total size of a list of chunks. +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. + +// Initialize. +void MuxImageInit(WebPMuxImage* const wpi); + +// Releases image 'wpi' and returns wpi->next. +WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi); + +// Delete image 'wpi' and return the next image in the list or NULL. +// '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'. +int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id); + +// Check if given ID corresponds to an image related chunk. +static WEBP_INLINE int IsWPI(WebPChunkId id) { + switch (id) { + 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 with given tag id. +WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth, + WebPChunkId id); + +// Get nth image in the image list with given tag id. +WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth, + 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 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); + +//------------------------------------------------------------------------------ + +#if defined(__cplusplus) || defined(c_plusplus) +} // extern "C" +#endif + +#endif /* WEBP_MUX_MUXI_H_ */ diff --git a/drivers/webpold/mux/muxinternal.c b/drivers/webpold/mux/muxinternal.c new file mode 100644 index 0000000000..6c3c4fe60a --- /dev/null +++ b/drivers/webpold/mux/muxinternal.c @@ -0,0 +1,576 @@ +// Copyright 2011 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/ +// ----------------------------------------------------------------------------- +// +// Internal objects and utils for mux. +// +// Authors: Urvang (urvang@google.com) +// Vikas (vikasa@google.com) + +#include <assert.h> +#include "./muxi.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('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('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 } +}; + +//------------------------------------------------------------------------------ +// Life of a chunk object. + +void ChunkInit(WebPChunk* const chunk) { + assert(chunk); + memset(chunk, 0, sizeof(*chunk)); + chunk->tag_ = NIL_TAG; +} + +WebPChunk* ChunkRelease(WebPChunk* const chunk) { + WebPChunk* next; + if (chunk == NULL) return NULL; + if (chunk->owner_) { + WebPDataClear(&chunk->data_); + } + next = chunk->next_; + ChunkInit(chunk); + return next; +} + +//------------------------------------------------------------------------------ +// Chunk misc methods. + +CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag) { + int i; + for (i = 0; kChunks[i].tag != NIL_TAG; ++i) { + if (tag == kChunks[i].tag) return i; + } + return IDX_NIL; +} + +WebPChunkId ChunkGetIdFromTag(uint32_t tag) { + int i; + for (i = 0; kChunks[i].tag != NIL_TAG; ++i) { + if (tag == kChunks[i].tag) return kChunks[i].id; + } + return WEBP_CHUNK_NIL; +} + +//------------------------------------------------------------------------------ +// Chunk search methods. + +// Returns next chunk in the chunk list with the given tag. +static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) { + while (chunk && chunk->tag_ != tag) { + chunk = chunk->next_; + } + return chunk; +} + +WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) { + uint32_t iter = nth; + first = ChunkSearchNextInList(first, tag); + if (!first) return NULL; + + while (--iter != 0) { + WebPChunk* next_chunk = ChunkSearchNextInList(first->next_, tag); + if (next_chunk == NULL) break; + first = next_chunk; + } + return ((nth > 0) && (iter > 0)) ? NULL : first; +} + +// Outputs a pointer to 'prev_chunk->next_', +// where 'prev_chunk' is the pointer to the chunk at position (nth - 1). +// 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); + *location = chunk_list; + + while (*chunk_list) { + WebPChunk* const cur_chunk = *chunk_list; + ++count; + if (count == nth) return 1; // Found. + chunk_list = &cur_chunk->next_; + *location = chunk_list; + } + + // *chunk_list is ok to be NULL if adding at last location. + return (nth == 0 || (count == nth - 1)) ? 1 : 0; +} + +//------------------------------------------------------------------------------ +// Chunk writer methods. + +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_LOOP].tag) { + copy_data = 1; + } + + ChunkRelease(chunk); + + if (data != NULL) { + 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(const WebPChunk* chunk, WebPChunk** chunk_list, + uint32_t nth) { + WebPChunk* new_chunk; + + if (!ChunkSearchListToSet(chunk_list, nth, &chunk_list)) { + return WEBP_MUX_NOT_FOUND; + } + + new_chunk = (WebPChunk*)malloc(sizeof(*new_chunk)); + if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR; + *new_chunk = *chunk; + new_chunk->next_ = *chunk_list; + *chunk_list = new_chunk; + return WEBP_MUX_OK; +} + +//------------------------------------------------------------------------------ +// Chunk deletion method(s). + +WebPChunk* ChunkDelete(WebPChunk* const chunk) { + WebPChunk* const next = ChunkRelease(chunk); + free(chunk); + return next; +} + +//------------------------------------------------------------------------------ +// 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_; + 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); + 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) { + dst = ChunkEmit(chunk_list, dst); + chunk_list = chunk_list->next_; + } + return dst; +} + +//------------------------------------------------------------------------------ +// Manipulation of a WebPData object. + +void WebPDataInit(WebPData* webp_data) { + if (webp_data != NULL) { + memset(webp_data, 0, sizeof(*webp_data)); + } +} + +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; +} + +//------------------------------------------------------------------------------ +// Life of a MuxImage object. + +void MuxImageInit(WebPMuxImage* const wpi) { + assert(wpi); + memset(wpi, 0, sizeof(*wpi)); +} + +WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) { + WebPMuxImage* next; + if (wpi == NULL) return NULL; + ChunkDelete(wpi->header_); + ChunkDelete(wpi->alpha_); + ChunkDelete(wpi->img_); + + next = wpi->next_; + MuxImageInit(wpi); + return next; +} + +//------------------------------------------------------------------------------ +// MuxImage search methods. + +int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) { + int count = 0; + const WebPMuxImage* current; + for (current = wpi_list; current != NULL; current = current->next_) { + 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; +} + +// Outputs a pointer to 'prev_wpi->next_', +// where 'prev_wpi' is the pointer to the image at position (nth - 1). +// 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, id); + if (nth == 0) return 0; // Not found. + } + + while (*wpi_list) { + WebPMuxImage* const cur_wpi = *wpi_list; + 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; + } + return 0; // Not found. +} + +//------------------------------------------------------------------------------ +// MuxImage writer methods. + +WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) { + WebPMuxImage* new_wpi; + + while (*wpi_list != NULL) { + WebPMuxImage* const cur_wpi = *wpi_list; + if (cur_wpi->next_ == NULL) break; + wpi_list = &cur_wpi->next_; + } + + new_wpi = (WebPMuxImage*)malloc(sizeof(*new_wpi)); + if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR; + *new_wpi = *wpi; + new_wpi->next_ = NULL; + + if (*wpi_list != NULL) { + (*wpi_list)->next_ = new_wpi; + } else { + *wpi_list = new_wpi; + } + return WEBP_MUX_OK; +} + +//------------------------------------------------------------------------------ +// MuxImage deletion methods. + +WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) { + // Delete the components of wpi. If wpi is NULL this is a noop. + WebPMuxImage* const next = MuxImageRelease(wpi); + free(wpi); + return next; +} + +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, id, &wpi_list)) { + return WEBP_MUX_NOT_FOUND; + } + *wpi_list = MuxImageDelete(*wpi_list); + return WEBP_MUX_OK; +} + +//------------------------------------------------------------------------------ +// MuxImage reader methods. + +WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth, + WebPChunkId id, WebPMuxImage** wpi) { + assert(wpi_list); + assert(wpi); + if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth, id, + (WebPMuxImage***)&wpi_list)) { + return WEBP_MUX_NOT_FOUND; + } + *wpi = (WebPMuxImage*)*wpi_list; + return WEBP_MUX_OK; +} + +//------------------------------------------------------------------------------ +// MuxImage serialization methods. + +// Size of an image. +size_t MuxImageDiskSize(const WebPMuxImage* const wpi) { + size_t size = 0; + if (wpi->header_ != NULL) size += ChunkDiskSize(wpi->header_); + if (wpi->alpha_ != NULL) size += ChunkDiskSize(wpi->alpha_); + if (wpi->img_ != NULL) size += ChunkDiskSize(wpi->img_); + return size; +} + +size_t MuxImageListDiskSize(const WebPMuxImage* wpi_list) { + size_t size = 0; + while (wpi_list) { + size += MuxImageDiskSize(wpi_list); + wpi_list = wpi_list->next_; + } + return size; +} + +uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) { + // Ordering of chunks to be emitted is strictly as follows: + // 1. Frame/Tile chunk (if present). + // 2. Alpha chunk (if present). + // 3. VP8/VP8L chunk. + assert(wpi); + 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); + 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 MuxHasLosslessImages(const WebPMuxImage* images) { + while (images != NULL) { + assert(images->img_ != NULL); + if (images->img_->tag_ == kChunks[IDX_VP8L].tag) { + return 1; + } + images = images->next_; + } + return 0; +} + +uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) { + PutLE32(data + 0, MKFOURCC('R', 'I', 'F', 'F')); + PutLE32(data + TAG_SIZE, (uint32_t)size - CHUNK_HEADER_SIZE); + assert(size == (uint32_t)size); + PutLE32(data + TAG_SIZE + CHUNK_SIZE_BYTES, MKFOURCC('W', 'E', 'B', 'P')); + return data + RIFF_HEADER_SIZE; +} + +WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) { + assert(mux != NULL); + switch(id) { + case WEBP_CHUNK_VP8X: return (WebPChunk**)&mux->vp8x_; + case WEBP_CHUNK_ICCP: return (WebPChunk**)&mux->iccp_; + 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; + } +} + +static int IsNotCompatible(int feature, int num_items) { + return (feature != 0) != (num_items > 0); +} + +#define NO_FLAG 0 + +// Test basic constraints: +// retrieval, maximum number of chunks by index (use -1 to skip) +// and feature incompatibility (use NO_FLAG to skip). +// 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, + WebPFeatureFlags vp8x_flags, + int max, int* num) { + const WebPMuxError err = + WebPMuxNumChunks(mux, kChunks[idx].id, num); + if (err != WEBP_MUX_OK) return err; + if (max > -1 && *num > max) return WEBP_MUX_INVALID_ARGUMENT; + if (feature != NO_FLAG && IsNotCompatible(vp8x_flags & feature, *num)) { + return WEBP_MUX_INVALID_ARGUMENT; + } + return WEBP_MUX_OK; +} + +WebPMuxError MuxValidate(const WebPMux* const mux) { + int num_iccp; + int num_meta; + int num_loop_chunks; + int num_frames; + int num_tiles; + int num_vp8x; + int num_images; + int num_alpha; + uint32_t flags; + WebPMuxError err; + + // Verify mux is not NULL. + if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; + + // Verify mux has at least one image. + if (mux->images_ == NULL) return WEBP_MUX_INVALID_ARGUMENT; + + err = WebPMuxGetFeatures(mux, &flags); + if (err != WEBP_MUX_OK) return err; + + // At most one color profile chunk. + err = ValidateChunk(mux, IDX_ICCP, ICCP_FLAG, flags, 1, &num_iccp); + if (err != WEBP_MUX_OK) return err; + + // At most one XMP metadata. + err = ValidateChunk(mux, IDX_META, META_FLAG, flags, 1, &num_meta); + if (err != WEBP_MUX_OK) return err; + + // 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_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_loop_chunks == 0 || num_frames == 0)) { + return WEBP_MUX_INVALID_ARGUMENT; + } + if (!has_animation && (num_loop_chunks == 1 || num_frames > 0)) { + return WEBP_MUX_INVALID_ARGUMENT; + } + } + + // 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 + // mux->images_. + err = ValidateChunk(mux, IDX_VP8X, NO_FLAG, flags, 1, &num_vp8x); + if (err != WEBP_MUX_OK) return err; + err = ValidateChunk(mux, IDX_VP8, NO_FLAG, flags, -1, &num_images); + if (err != WEBP_MUX_OK) return err; + if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT; + + // ALPHA_FLAG & alpha chunk(s) are consistent. + 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_tiles & num_images are consistent. + if (num_tiles > 0 && num_images != num_tiles) { + return WEBP_MUX_INVALID_ARGUMENT; + } + + return WEBP_MUX_OK; +} + +#undef NO_FLAG + +//------------------------------------------------------------------------------ + +#if defined(__cplusplus) || defined(c_plusplus) +} // extern "C" +#endif diff --git a/drivers/webpold/mux/muxread.c b/drivers/webpold/mux/muxread.c new file mode 100644 index 0000000000..21c3cfbaeb --- /dev/null +++ b/drivers/webpold/mux/muxread.c @@ -0,0 +1,411 @@ +// Copyright 2011 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/ +// ----------------------------------------------------------------------------- +// +// Read APIs for mux. +// +// Authors: Urvang (urvang@google.com) +// Vikas (vikasa@google.com) + +#include <assert.h> +#include "./muxi.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Helper method(s). + +// Handy MACRO. +#define SWITCH_ID_LIST(INDEX, LIST) \ + if (idx == (INDEX)) { \ + const WebPChunk* const chunk = ChunkSearchList((LIST), nth, \ + kChunks[(INDEX)].tag); \ + if (chunk) { \ + *data = chunk->data_; \ + return WEBP_MUX_OK; \ + } else { \ + return WEBP_MUX_NOT_FOUND; \ + } \ + } + +static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx, + uint32_t nth, WebPData* const data) { + assert(mux != NULL); + assert(!IsWPI(kChunks[idx].id)); + WebPDataInit(data); + + 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_); + SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_); + return WEBP_MUX_NOT_FOUND; +} +#undef SWITCH_ID_LIST + +// Fill the chunk with the given data (includes chunk header bytes), after some +// verifications. +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; + + // Sanity checks. + if (data_size < TAG_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA; + chunk_size = GetLE32(data + TAG_SIZE); + + { + const size_t chunk_disk_size = SizeWithPadding(chunk_size); + if (chunk_disk_size > riff_size) return WEBP_MUX_BAD_DATA; + if (chunk_disk_size > data_size) return WEBP_MUX_NOT_ENOUGH_DATA; + } + + // Data assignment. + chunk_data.bytes_ = data + CHUNK_HEADER_SIZE; + chunk_data.size_ = chunk_size; + return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0)); +} + +//------------------------------------------------------------------------------ +// Create a mux object from WebP-RIFF data. + +WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, + int version) { + size_t riff_size; + uint32_t tag; + const uint8_t* end; + WebPMux* mux = NULL; + WebPMuxImage* wpi = NULL; + const uint8_t* data; + size_t size; + WebPChunk chunk; + ChunkInit(&chunk); + + // Sanity checks. + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) { + return NULL; // version mismatch + } + if (bitstream == NULL) return NULL; + + data = bitstream->bytes_; + size = bitstream->size_; + + if (data == NULL) return NULL; + if (size < RIFF_HEADER_SIZE) return NULL; + if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') || + GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) { + return NULL; + } + + mux = WebPMuxNew(); + if (mux == NULL) return NULL; + + if (size < RIFF_HEADER_SIZE + TAG_SIZE) goto Err; + + tag = GetLE32(data + RIFF_HEADER_SIZE); + if (tag != kChunks[IDX_VP8].tag && + tag != kChunks[IDX_VP8L].tag && + tag != kChunks[IDX_VP8X].tag) { + goto Err; // First chunk should be VP8, VP8L or VP8X. + } + + riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE)); + if (riff_size > MAX_CHUNK_PAYLOAD || riff_size > size) { + goto Err; + } else { + if (riff_size < size) { // Redundant data after last chunk. + size = riff_size; // To make sure we don't read any data beyond mux_size. + } + } + + end = data + size; + data += RIFF_HEADER_SIZE; + size -= RIFF_HEADER_SIZE; + + wpi = (WebPMuxImage*)malloc(sizeof(*wpi)); + if (wpi == NULL) goto Err; + MuxImageInit(wpi); + + // Loop over chunks. + while (data != end) { + WebPChunkId id; + WebPMuxError err; + + err = ChunkVerifyAndAssignData(&chunk, data, size, riff_size, copy_data); + if (err != WEBP_MUX_OK) goto Err; + + id = ChunkGetIdFromTag(chunk.tag_); + + 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. + // Add this to mux->images_ list. + if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err; + MuxImageInit(wpi); // Reset for reading next image. + } 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; + } + ChunkInit(&chunk); + } + + // Validate mux if complete. + if (MuxValidate(mux) != WEBP_MUX_OK) goto Err; + + MuxImageDelete(wpi); + return mux; // All OK; + + Err: // Something bad happened. + ChunkRelease(&chunk); + MuxImageDelete(wpi); + WebPMuxDelete(mux); + return NULL; +} + +//------------------------------------------------------------------------------ +// Get API(s). + +WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) { + WebPData data; + WebPMuxError err; + + if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT; + *flags = 0; + + // Check if VP8X chunk is present. + 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 (data.size_ < CHUNK_SIZE_BYTES) return WEBP_MUX_BAD_DATA; + + // All OK. Fill up flags. + *flags = GetLE32(data.bytes_); + return WEBP_MUX_OK; +} + +static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width, + int height, uint32_t flags) { + const size_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE; + assert(width >= 1 && height >= 1); + assert(width <= MAX_CANVAS_SIZE && height <= MAX_CANVAS_SIZE); + assert(width * (uint64_t)height < MAX_IMAGE_AREA); + PutLE32(dst, MKFOURCC('V', 'P', '8', 'X')); + PutLE32(dst + TAG_SIZE, VP8X_CHUNK_SIZE); + PutLE32(dst + CHUNK_HEADER_SIZE, flags); + PutLE24(dst + CHUNK_HEADER_SIZE + 4, width - 1); + PutLE24(dst + CHUNK_HEADER_SIZE + 7, height - 1); + return dst + vp8x_size; +} + +// Assemble a single image WebP bitstream from 'wpi'. +static WebPMuxError SynthesizeBitstream(WebPMuxImage* const wpi, + WebPData* const bitstream) { + uint8_t* dst; + + // Allocate data. + 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 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); + if (data == NULL) return WEBP_MUX_MEMORY_ERROR; + + // Main RIFF header. + dst = MuxEmitRiffHeader(data, size); + + if (need_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. + } + + // Bitstream. + dst = ChunkListEmit(wpi->img_, dst); + assert(dst == data + size); + + // Output. + bitstream->bytes_ = data; + bitstream->size_ = size; + return WEBP_MUX_OK; +} + +WebPMuxError WebPMuxGetImage(const WebPMux* mux, WebPData* bitstream) { + WebPMuxError err; + WebPMuxImage* wpi = NULL; + + if (mux == NULL || bitstream == NULL) { + return WEBP_MUX_INVALID_ARGUMENT; + } + + 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); +} + +WebPMuxError WebPMuxGetMetadata(const WebPMux* mux, WebPData* metadata) { + if (mux == NULL || metadata == NULL) return WEBP_MUX_INVALID_ARGUMENT; + return MuxGet(mux, IDX_META, 1, metadata); +} + +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 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; + + 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, id, &wpi); + if (err != WEBP_MUX_OK) return err; + + // Get frame chunk. + assert(wpi->header_ != NULL); // As MuxImageGetNth() already checked header_. + frame_tile_data = &wpi->header_->data_; + + 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); + + return SynthesizeBitstream(wpi, bitstream); +} + +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); +} + +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 i; + } + return IDX_NIL; +} + +// Count number of chunks matching 'tag' in the 'chunk_list'. +// If tag == NIL_TAG, any tag will be matched. +static int CountChunks(const WebPChunk* const chunk_list, uint32_t tag) { + int count = 0; + const WebPChunk* current; + for (current = chunk_list; current != NULL; current = current->next_) { + if (tag == NIL_TAG || current->tag_ == tag) { + count++; // Count chunks whose tags match. + } + } + return count; +} + +WebPMuxError WebPMuxNumChunks(const WebPMux* mux, + WebPChunkId id, int* num_elements) { + if (mux == NULL || num_elements == NULL) { + return WEBP_MUX_INVALID_ARGUMENT; + } + + if (IsWPI(id)) { + *num_elements = MuxImageCount(mux->images_, id); + } else { + WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id); + 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; +} + +//------------------------------------------------------------------------------ + +#if defined(__cplusplus) || defined(c_plusplus) +} // extern "C" +#endif |