diff options
Diffstat (limited to 'drivers/webp/mux')
-rw-r--r-- | drivers/webp/mux/anim_encode.c | 1404 | ||||
-rw-r--r-- | drivers/webp/mux/demux.c | 902 | ||||
-rw-r--r-- | drivers/webp/mux/muxedit.c | 740 | ||||
-rw-r--r-- | drivers/webp/mux/muxi.h | 167 | ||||
-rw-r--r-- | drivers/webp/mux/muxinternal.c | 331 | ||||
-rw-r--r-- | drivers/webp/mux/muxread.c | 449 |
6 files changed, 2274 insertions, 1719 deletions
diff --git a/drivers/webp/mux/anim_encode.c b/drivers/webp/mux/anim_encode.c new file mode 100644 index 0000000000..bb7c0f50b9 --- /dev/null +++ b/drivers/webp/mux/anim_encode.c @@ -0,0 +1,1404 @@ +// Copyright 2014 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. +// ----------------------------------------------------------------------------- +// +// AnimEncoder implementation. +// + +#include <assert.h> +#include <limits.h> +#include <stdio.h> + +#include "../utils/utils.h" +#include "webp/decode.h" +#include "webp/encode.h" +#include "webp/format_constants.h" +#include "webp/mux.h" + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define snprintf _snprintf +#endif + +#define ERROR_STR_MAX_LENGTH 100 + +//------------------------------------------------------------------------------ +// Internal structs. + +// Stores frame rectangle dimensions. +typedef struct { + int x_offset_, y_offset_, width_, height_; +} FrameRect; + +// Used to store two candidates of encoded data for an animation frame. One of +// the two will be chosen later. +typedef struct { + WebPMuxFrameInfo sub_frame_; // Encoded frame rectangle. + WebPMuxFrameInfo key_frame_; // Encoded frame if it is a key-frame. + int is_key_frame_; // True if 'key_frame' has been chosen. +} EncodedFrame; + +struct WebPAnimEncoder { + const int canvas_width_; // Canvas width. + const int canvas_height_; // Canvas height. + const WebPAnimEncoderOptions options_; // Global encoding options. + + FrameRect prev_rect_; // Previous WebP frame rectangle. + WebPConfig last_config_; // Cached in case a re-encode is needed. + WebPConfig last_config2_; // 2nd cached config; only valid if + // 'options_.allow_mixed' is true. + + WebPPicture* curr_canvas_; // Only pointer; we don't own memory. + + // Canvas buffers. + WebPPicture curr_canvas_copy_; // Possibly modified current canvas. + int curr_canvas_copy_modified_; // True if pixels in 'curr_canvas_copy_' + // differ from those in 'curr_canvas_'. + + WebPPicture prev_canvas_; // Previous canvas. + WebPPicture prev_canvas_disposed_; // Previous canvas disposed to background. + + // Encoded data. + EncodedFrame* encoded_frames_; // Array of encoded frames. + size_t size_; // Number of allocated frames. + size_t start_; // Frame start index. + size_t count_; // Number of valid frames. + size_t flush_count_; // If >0, 'flush_count' frames starting from + // 'start' are ready to be added to mux. + + // key-frame related. + int64_t best_delta_; // min(canvas size - frame size) over the frames. + // Can be negative in certain cases due to + // transparent pixels in a frame. + int keyframe_; // Index of selected key-frame relative to 'start_'. + int count_since_key_frame_; // Frames seen since the last key-frame. + + int first_timestamp_; // Timestamp of the first frame. + int prev_timestamp_; // Timestamp of the last added frame. + int prev_candidate_undecided_; // True if it's not yet decided if previous + // frame would be a sub-frame or a key-frame. + + // Misc. + int is_first_frame_; // True if first frame is yet to be added/being added. + int got_null_frame_; // True if WebPAnimEncoderAdd() has already been called + // with a NULL frame. + + size_t in_frame_count_; // Number of input frames processed so far. + size_t out_frame_count_; // Number of frames added to mux so far. This may be + // different from 'in_frame_count_' due to merging. + + WebPMux* mux_; // Muxer to assemble the WebP bitstream. + char error_str_[ERROR_STR_MAX_LENGTH]; // Error string. Empty if no error. +}; + +// ----------------------------------------------------------------------------- +// Life of WebPAnimEncoder object. + +#define DELTA_INFINITY (1ULL << 32) +#define KEYFRAME_NONE (-1) + +// Reset the counters in the WebPAnimEncoder. +static void ResetCounters(WebPAnimEncoder* const enc) { + enc->start_ = 0; + enc->count_ = 0; + enc->flush_count_ = 0; + enc->best_delta_ = DELTA_INFINITY; + enc->keyframe_ = KEYFRAME_NONE; +} + +static void DisableKeyframes(WebPAnimEncoderOptions* const enc_options) { + enc_options->kmax = INT_MAX; + enc_options->kmin = enc_options->kmax - 1; +} + +#define MAX_CACHED_FRAMES 30 + +static void SanitizeEncoderOptions(WebPAnimEncoderOptions* const enc_options) { + int print_warning = enc_options->verbose; + + if (enc_options->minimize_size) { + DisableKeyframes(enc_options); + } + + if (enc_options->kmin <= 0) { + DisableKeyframes(enc_options); + print_warning = 0; + } + if (enc_options->kmax <= 0) { // All frames will be key-frames. + enc_options->kmin = 0; + enc_options->kmax = 0; + return; + } + + if (enc_options->kmin >= enc_options->kmax) { + enc_options->kmin = enc_options->kmax - 1; + if (print_warning) { + fprintf(stderr, "WARNING: Setting kmin = %d, so that kmin < kmax.\n", + enc_options->kmin); + } + } else { + const int kmin_limit = enc_options->kmax / 2 + 1; + if (enc_options->kmin < kmin_limit && kmin_limit < enc_options->kmax) { + // This ensures that enc.keyframe + kmin >= kmax is always true. So, we + // can flush all the frames in the 'count_since_key_frame == kmax' case. + enc_options->kmin = kmin_limit; + if (print_warning) { + fprintf(stderr, + "WARNING: Setting kmin = %d, so that kmin >= kmax / 2 + 1.\n", + enc_options->kmin); + } + } + } + // Limit the max number of frames that are allocated. + if (enc_options->kmax - enc_options->kmin > MAX_CACHED_FRAMES) { + enc_options->kmin = enc_options->kmax - MAX_CACHED_FRAMES; + if (print_warning) { + fprintf(stderr, + "WARNING: Setting kmin = %d, so that kmax - kmin <= %d.\n", + enc_options->kmin, MAX_CACHED_FRAMES); + } + } + assert(enc_options->kmin < enc_options->kmax); +} + +#undef MAX_CACHED_FRAMES + +static void DefaultEncoderOptions(WebPAnimEncoderOptions* const enc_options) { + enc_options->anim_params.loop_count = 0; + enc_options->anim_params.bgcolor = 0xffffffff; // White. + enc_options->minimize_size = 0; + DisableKeyframes(enc_options); + enc_options->allow_mixed = 0; +} + +int WebPAnimEncoderOptionsInitInternal(WebPAnimEncoderOptions* enc_options, + int abi_version) { + if (enc_options == NULL || + WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) { + return 0; + } + DefaultEncoderOptions(enc_options); + return 1; +} + +#define TRANSPARENT_COLOR 0x00ffffff + +static void ClearRectangle(WebPPicture* const picture, + int left, int top, int width, int height) { + int j; + for (j = top; j < top + height; ++j) { + uint32_t* const dst = picture->argb + j * picture->argb_stride; + int i; + for (i = left; i < left + width; ++i) { + dst[i] = TRANSPARENT_COLOR; + } + } +} + +static void WebPUtilClearPic(WebPPicture* const picture, + const FrameRect* const rect) { + if (rect != NULL) { + ClearRectangle(picture, rect->x_offset_, rect->y_offset_, + rect->width_, rect->height_); + } else { + ClearRectangle(picture, 0, 0, picture->width, picture->height); + } +} + +static void MarkNoError(WebPAnimEncoder* const enc) { + enc->error_str_[0] = '\0'; // Empty string. +} + +static void MarkError(WebPAnimEncoder* const enc, const char* str) { + if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s.", str) < 0) { + assert(0); // FIX ME! + } +} + +static void MarkError2(WebPAnimEncoder* const enc, + const char* str, int error_code) { + if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s: %d.", str, + error_code) < 0) { + assert(0); // FIX ME! + } +} + +WebPAnimEncoder* WebPAnimEncoderNewInternal( + int width, int height, const WebPAnimEncoderOptions* enc_options, + int abi_version) { + WebPAnimEncoder* enc; + + if (WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) { + return NULL; + } + if (width <= 0 || height <= 0 || + (width * (uint64_t)height) >= MAX_IMAGE_AREA) { + return NULL; + } + + enc = (WebPAnimEncoder*)WebPSafeCalloc(1, sizeof(*enc)); + if (enc == NULL) return NULL; + // sanity inits, so we can call WebPAnimEncoderDelete(): + enc->encoded_frames_ = NULL; + enc->mux_ = NULL; + MarkNoError(enc); + + // Dimensions and options. + *(int*)&enc->canvas_width_ = width; + *(int*)&enc->canvas_height_ = height; + if (enc_options != NULL) { + *(WebPAnimEncoderOptions*)&enc->options_ = *enc_options; + SanitizeEncoderOptions((WebPAnimEncoderOptions*)&enc->options_); + } else { + DefaultEncoderOptions((WebPAnimEncoderOptions*)&enc->options_); + } + + // Canvas buffers. + if (!WebPPictureInit(&enc->curr_canvas_copy_) || + !WebPPictureInit(&enc->prev_canvas_) || + !WebPPictureInit(&enc->prev_canvas_disposed_)) { + goto Err; + } + enc->curr_canvas_copy_.width = width; + enc->curr_canvas_copy_.height = height; + enc->curr_canvas_copy_.use_argb = 1; + if (!WebPPictureAlloc(&enc->curr_canvas_copy_) || + !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_) || + !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_disposed_)) { + goto Err; + } + WebPUtilClearPic(&enc->prev_canvas_, NULL); + enc->curr_canvas_copy_modified_ = 1; + + // Encoded frames. + ResetCounters(enc); + // Note: one extra storage is for the previous frame. + enc->size_ = enc->options_.kmax - enc->options_.kmin + 1; + // We need space for at least 2 frames. But when kmin, kmax are both zero, + // enc->size_ will be 1. So we handle that special case below. + if (enc->size_ < 2) enc->size_ = 2; + enc->encoded_frames_ = + (EncodedFrame*)WebPSafeCalloc(enc->size_, sizeof(*enc->encoded_frames_)); + if (enc->encoded_frames_ == NULL) goto Err; + + enc->mux_ = WebPMuxNew(); + if (enc->mux_ == NULL) goto Err; + + enc->count_since_key_frame_ = 0; + enc->first_timestamp_ = 0; + enc->prev_timestamp_ = 0; + enc->prev_candidate_undecided_ = 0; + enc->is_first_frame_ = 1; + enc->got_null_frame_ = 0; + + return enc; // All OK. + + Err: + WebPAnimEncoderDelete(enc); + return NULL; +} + +// Release the data contained by 'encoded_frame'. +static void FrameRelease(EncodedFrame* const encoded_frame) { + if (encoded_frame != NULL) { + WebPDataClear(&encoded_frame->sub_frame_.bitstream); + WebPDataClear(&encoded_frame->key_frame_.bitstream); + memset(encoded_frame, 0, sizeof(*encoded_frame)); + } +} + +void WebPAnimEncoderDelete(WebPAnimEncoder* enc) { + if (enc != NULL) { + WebPPictureFree(&enc->curr_canvas_copy_); + WebPPictureFree(&enc->prev_canvas_); + WebPPictureFree(&enc->prev_canvas_disposed_); + if (enc->encoded_frames_ != NULL) { + size_t i; + for (i = 0; i < enc->size_; ++i) { + FrameRelease(&enc->encoded_frames_[i]); + } + WebPSafeFree(enc->encoded_frames_); + } + WebPMuxDelete(enc->mux_); + WebPSafeFree(enc); + } +} + +// ----------------------------------------------------------------------------- +// Frame addition. + +// Returns cached frame at the given 'position'. +static EncodedFrame* GetFrame(const WebPAnimEncoder* const enc, + size_t position) { + assert(enc->start_ + position < enc->size_); + return &enc->encoded_frames_[enc->start_ + position]; +} + +// Returns true if 'length' number of pixels in 'src' and 'dst' are identical, +// assuming the given step sizes between pixels. +static WEBP_INLINE int ComparePixels(const uint32_t* src, int src_step, + const uint32_t* dst, int dst_step, + int length) { + assert(length > 0); + while (length-- > 0) { + if (*src != *dst) { + return 0; + } + src += src_step; + dst += dst_step; + } + return 1; +} + +static int IsEmptyRect(const FrameRect* const rect) { + return (rect->width_ == 0) || (rect->height_ == 0); +} + +// Assumes that an initial valid guess of change rectangle 'rect' is passed. +static void MinimizeChangeRectangle(const WebPPicture* const src, + const WebPPicture* const dst, + FrameRect* const rect) { + int i, j; + // Sanity checks. + assert(src->width == dst->width && src->height == dst->height); + assert(rect->x_offset_ + rect->width_ <= dst->width); + assert(rect->y_offset_ + rect->height_ <= dst->height); + + // Left boundary. + for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) { + const uint32_t* const src_argb = + &src->argb[rect->y_offset_ * src->argb_stride + i]; + const uint32_t* const dst_argb = + &dst->argb[rect->y_offset_ * dst->argb_stride + i]; + if (ComparePixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride, + rect->height_)) { + --rect->width_; // Redundant column. + ++rect->x_offset_; + } else { + break; + } + } + if (rect->width_ == 0) goto NoChange; + + // Right boundary. + for (i = rect->x_offset_ + rect->width_ - 1; i >= rect->x_offset_; --i) { + const uint32_t* const src_argb = + &src->argb[rect->y_offset_ * src->argb_stride + i]; + const uint32_t* const dst_argb = + &dst->argb[rect->y_offset_ * dst->argb_stride + i]; + if (ComparePixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride, + rect->height_)) { + --rect->width_; // Redundant column. + } else { + break; + } + } + if (rect->width_ == 0) goto NoChange; + + // Top boundary. + for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) { + const uint32_t* const src_argb = + &src->argb[j * src->argb_stride + rect->x_offset_]; + const uint32_t* const dst_argb = + &dst->argb[j * dst->argb_stride + rect->x_offset_]; + if (ComparePixels(src_argb, 1, dst_argb, 1, rect->width_)) { + --rect->height_; // Redundant row. + ++rect->y_offset_; + } else { + break; + } + } + if (rect->height_ == 0) goto NoChange; + + // Bottom boundary. + for (j = rect->y_offset_ + rect->height_ - 1; j >= rect->y_offset_; --j) { + const uint32_t* const src_argb = + &src->argb[j * src->argb_stride + rect->x_offset_]; + const uint32_t* const dst_argb = + &dst->argb[j * dst->argb_stride + rect->x_offset_]; + if (ComparePixels(src_argb, 1, dst_argb, 1, rect->width_)) { + --rect->height_; // Redundant row. + } else { + break; + } + } + if (rect->height_ == 0) goto NoChange; + + if (IsEmptyRect(rect)) { + NoChange: + rect->x_offset_ = 0; + rect->y_offset_ = 0; + rect->width_ = 0; + rect->height_ = 0; + } +} + +// Snap rectangle to even offsets (and adjust dimensions if needed). +static WEBP_INLINE void SnapToEvenOffsets(FrameRect* const rect) { + rect->width_ += (rect->x_offset_ & 1); + rect->height_ += (rect->y_offset_ & 1); + rect->x_offset_ &= ~1; + rect->y_offset_ &= ~1; +} + +// Given previous and current canvas, picks the optimal rectangle for the +// current frame. The initial guess for 'rect' will be the full canvas. +static int GetSubRect(const WebPPicture* const prev_canvas, + const WebPPicture* const curr_canvas, int is_key_frame, + int is_first_frame, int empty_rect_allowed, + FrameRect* const rect, WebPPicture* const sub_frame) { + rect->x_offset_ = 0; + rect->y_offset_ = 0; + rect->width_ = curr_canvas->width; + rect->height_ = curr_canvas->height; + if (!is_key_frame || is_first_frame) { // Optimize frame rectangle. + // Note: This behaves as expected for first frame, as 'prev_canvas' is + // initialized to a fully transparent canvas in the beginning. + MinimizeChangeRectangle(prev_canvas, curr_canvas, rect); + } + + if (IsEmptyRect(rect)) { + if (empty_rect_allowed) { // No need to get 'sub_frame'. + return 1; + } else { // Force a 1x1 rectangle. + rect->width_ = 1; + rect->height_ = 1; + assert(rect->x_offset_ == 0); + assert(rect->y_offset_ == 0); + } + } + + SnapToEvenOffsets(rect); + return WebPPictureView(curr_canvas, rect->x_offset_, rect->y_offset_, + rect->width_, rect->height_, sub_frame); +} + +static void DisposeFrameRectangle(int dispose_method, + const FrameRect* const rect, + WebPPicture* const curr_canvas) { + assert(rect != NULL); + if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { + WebPUtilClearPic(curr_canvas, rect); + } +} + +static uint32_t RectArea(const FrameRect* const rect) { + return (uint32_t)rect->width_ * rect->height_; +} + +static int IsBlendingPossible(const WebPPicture* const src, + const WebPPicture* const dst, + const FrameRect* const rect) { + int i, j; + assert(src->width == dst->width && src->height == dst->height); + assert(rect->x_offset_ + rect->width_ <= dst->width); + assert(rect->y_offset_ + rect->height_ <= dst->height); + for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) { + for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) { + const uint32_t src_pixel = src->argb[j * src->argb_stride + i]; + const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i]; + const uint32_t dst_alpha = dst_pixel >> 24; + if (dst_alpha != 0xff && src_pixel != dst_pixel) { + // In this case, if we use blending, we can't attain the desired + // 'dst_pixel' value for this pixel. So, blending is not possible. + return 0; + } + } + } + return 1; +} + +#define MIN_COLORS_LOSSY 31 // Don't try lossy below this threshold. +#define MAX_COLORS_LOSSLESS 194 // Don't try lossless above this threshold. +#define MAX_COLOR_COUNT 256 // Power of 2 greater than MAX_COLORS_LOSSLESS. +#define HASH_SIZE (MAX_COLOR_COUNT * 4) +#define HASH_RIGHT_SHIFT 22 // 32 - log2(HASH_SIZE). + +// TODO(urvang): Also used in enc/vp8l.c. Move to utils. +// If the number of colors in the 'pic' is at least MAX_COLOR_COUNT, return +// MAX_COLOR_COUNT. Otherwise, return the exact number of colors in the 'pic'. +static int GetColorCount(const WebPPicture* const pic) { + int x, y; + int num_colors = 0; + uint8_t in_use[HASH_SIZE] = { 0 }; + uint32_t colors[HASH_SIZE]; + static const uint32_t kHashMul = 0x1e35a7bd; + const uint32_t* argb = pic->argb; + const int width = pic->width; + const int height = pic->height; + uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0] + + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { + int key; + if (argb[x] == last_pix) { + continue; + } + last_pix = argb[x]; + key = (kHashMul * last_pix) >> HASH_RIGHT_SHIFT; + while (1) { + if (!in_use[key]) { + colors[key] = last_pix; + in_use[key] = 1; + ++num_colors; + if (num_colors >= MAX_COLOR_COUNT) { + return MAX_COLOR_COUNT; // Exact count not needed. + } + break; + } else if (colors[key] == last_pix) { + break; // The color is already there. + } else { + // Some other color sits here, so do linear conflict resolution. + ++key; + key &= (HASH_SIZE - 1); // Key mask. + } + } + } + argb += pic->argb_stride; + } + return num_colors; +} + +#undef MAX_COLOR_COUNT +#undef HASH_SIZE +#undef HASH_RIGHT_SHIFT + +// For pixels in 'rect', replace those pixels in 'dst' that are same as 'src' by +// transparent pixels. +static void IncreaseTransparency(const WebPPicture* const src, + const FrameRect* const rect, + WebPPicture* const dst) { + int i, j; + assert(src != NULL && dst != NULL && rect != NULL); + assert(src->width == dst->width && src->height == dst->height); + for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) { + const uint32_t* const psrc = src->argb + j * src->argb_stride; + uint32_t* const pdst = dst->argb + j * dst->argb_stride; + for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) { + if (psrc[i] == pdst[i]) { + pdst[i] = TRANSPARENT_COLOR; + } + } + } +} + +#undef TRANSPARENT_COLOR + +// Replace similar blocks of pixels by a 'see-through' transparent block +// with uniform average color. +static void FlattenSimilarBlocks(const WebPPicture* const src, + const FrameRect* const rect, + WebPPicture* const dst) { + int i, j; + const int block_size = 8; + const int y_start = (rect->y_offset_ + block_size) & ~(block_size - 1); + const int y_end = (rect->y_offset_ + rect->height_) & ~(block_size - 1); + const int x_start = (rect->x_offset_ + block_size) & ~(block_size - 1); + const int x_end = (rect->x_offset_ + rect->width_) & ~(block_size - 1); + assert(src != NULL && dst != NULL && rect != NULL); + assert(src->width == dst->width && src->height == dst->height); + assert((block_size & (block_size - 1)) == 0); // must be a power of 2 + // Iterate over each block and count similar pixels. + for (j = y_start; j < y_end; j += block_size) { + for (i = x_start; i < x_end; i += block_size) { + int cnt = 0; + int avg_r = 0, avg_g = 0, avg_b = 0; + int x, y; + const uint32_t* const psrc = src->argb + j * src->argb_stride + i; + uint32_t* const pdst = dst->argb + j * dst->argb_stride + i; + for (y = 0; y < block_size; ++y) { + for (x = 0; x < block_size; ++x) { + const uint32_t src_pixel = psrc[x + y * src->argb_stride]; + const int alpha = src_pixel >> 24; + if (alpha == 0xff && + src_pixel == pdst[x + y * dst->argb_stride]) { + ++cnt; + avg_r += (src_pixel >> 16) & 0xff; + avg_g += (src_pixel >> 8) & 0xff; + avg_b += (src_pixel >> 0) & 0xff; + } + } + } + // If we have a fully similar block, we replace it with an + // average transparent block. This compresses better in lossy mode. + if (cnt == block_size * block_size) { + const uint32_t color = (0x00 << 24) | + ((avg_r / cnt) << 16) | + ((avg_g / cnt) << 8) | + ((avg_b / cnt) << 0); + for (y = 0; y < block_size; ++y) { + for (x = 0; x < block_size; ++x) { + pdst[x + y * dst->argb_stride] = color; + } + } + } + } + } +} + +static int EncodeFrame(const WebPConfig* const config, WebPPicture* const pic, + WebPMemoryWriter* const memory) { + pic->use_argb = 1; + pic->writer = WebPMemoryWrite; + pic->custom_ptr = memory; + if (!WebPEncode(config, pic)) { + return 0; + } + return 1; +} + +// Struct representing a candidate encoded frame including its metadata. +typedef struct { + WebPMemoryWriter mem_; + WebPMuxFrameInfo info_; + FrameRect rect_; + int evaluate_; // True if this candidate should be evaluated. +} Candidate; + +// Generates a candidate encoded frame given a picture and metadata. +static WebPEncodingError EncodeCandidate(WebPPicture* const sub_frame, + const FrameRect* const rect, + const WebPConfig* const config, + int use_blending, + Candidate* const candidate) { + WebPEncodingError error_code = VP8_ENC_OK; + assert(candidate != NULL); + memset(candidate, 0, sizeof(*candidate)); + + // Set frame rect and info. + candidate->rect_ = *rect; + candidate->info_.id = WEBP_CHUNK_ANMF; + candidate->info_.x_offset = rect->x_offset_; + candidate->info_.y_offset = rect->y_offset_; + candidate->info_.dispose_method = WEBP_MUX_DISPOSE_NONE; // Set later. + candidate->info_.blend_method = + use_blending ? WEBP_MUX_BLEND : WEBP_MUX_NO_BLEND; + candidate->info_.duration = 0; // Set in next call to WebPAnimEncoderAdd(). + + // Encode picture. + WebPMemoryWriterInit(&candidate->mem_); + + if (!EncodeFrame(config, sub_frame, &candidate->mem_)) { + error_code = sub_frame->error_code; + goto Err; + } + + candidate->evaluate_ = 1; + return error_code; + + Err: + WebPMemoryWriterClear(&candidate->mem_); + return error_code; +} + +static void CopyCurrentCanvas(WebPAnimEncoder* const enc) { + if (enc->curr_canvas_copy_modified_) { + WebPCopyPixels(enc->curr_canvas_, &enc->curr_canvas_copy_); + enc->curr_canvas_copy_modified_ = 0; + } +} + +enum { + LL_DISP_NONE = 0, + LL_DISP_BG, + LOSSY_DISP_NONE, + LOSSY_DISP_BG, + CANDIDATE_COUNT +}; + +// Generates candidates for a given dispose method given pre-filled 'rect' +// and 'sub_frame'. +static WebPEncodingError GenerateCandidates( + WebPAnimEncoder* const enc, Candidate candidates[CANDIDATE_COUNT], + WebPMuxAnimDispose dispose_method, int is_lossless, int is_key_frame, + const FrameRect* const rect, WebPPicture* sub_frame, + const WebPConfig* const config_ll, const WebPConfig* const config_lossy) { + WebPEncodingError error_code = VP8_ENC_OK; + const int is_dispose_none = (dispose_method == WEBP_MUX_DISPOSE_NONE); + Candidate* const candidate_ll = + is_dispose_none ? &candidates[LL_DISP_NONE] : &candidates[LL_DISP_BG]; + Candidate* const candidate_lossy = is_dispose_none + ? &candidates[LOSSY_DISP_NONE] + : &candidates[LOSSY_DISP_BG]; + WebPPicture* const curr_canvas = &enc->curr_canvas_copy_; + const WebPPicture* const prev_canvas = + is_dispose_none ? &enc->prev_canvas_ : &enc->prev_canvas_disposed_; + const int use_blending = + !is_key_frame && + IsBlendingPossible(prev_canvas, curr_canvas, rect); + + // Pick candidates to be tried. + if (!enc->options_.allow_mixed) { + candidate_ll->evaluate_ = is_lossless; + candidate_lossy->evaluate_ = !is_lossless; + } else { // Use a heuristic for trying lossless and/or lossy compression. + const int num_colors = GetColorCount(sub_frame); + candidate_ll->evaluate_ = (num_colors < MAX_COLORS_LOSSLESS); + candidate_lossy->evaluate_ = (num_colors >= MIN_COLORS_LOSSY); + } + + // Generate candidates. + if (candidate_ll->evaluate_) { + CopyCurrentCanvas(enc); + if (use_blending) { + IncreaseTransparency(prev_canvas, rect, curr_canvas); + enc->curr_canvas_copy_modified_ = 1; + } + error_code = EncodeCandidate(sub_frame, rect, config_ll, use_blending, + candidate_ll); + if (error_code != VP8_ENC_OK) return error_code; + } + if (candidate_lossy->evaluate_) { + CopyCurrentCanvas(enc); + if (use_blending) { + FlattenSimilarBlocks(prev_canvas, rect, curr_canvas); + enc->curr_canvas_copy_modified_ = 1; + } + error_code = EncodeCandidate(sub_frame, rect, config_lossy, use_blending, + candidate_lossy); + if (error_code != VP8_ENC_OK) return error_code; + } + return error_code; +} + +#undef MIN_COLORS_LOSSY +#undef MAX_COLORS_LOSSLESS + +static void GetEncodedData(const WebPMemoryWriter* const memory, + WebPData* const encoded_data) { + encoded_data->bytes = memory->mem; + encoded_data->size = memory->size; +} + +// Sets dispose method of the previous frame to be 'dispose_method'. +static void SetPreviousDisposeMethod(WebPAnimEncoder* const enc, + WebPMuxAnimDispose dispose_method) { + const size_t position = enc->count_ - 2; + EncodedFrame* const prev_enc_frame = GetFrame(enc, position); + assert(enc->count_ >= 2); // As current and previous frames are in enc. + + if (enc->prev_candidate_undecided_) { + assert(dispose_method == WEBP_MUX_DISPOSE_NONE); + prev_enc_frame->sub_frame_.dispose_method = dispose_method; + prev_enc_frame->key_frame_.dispose_method = dispose_method; + } else { + WebPMuxFrameInfo* const prev_info = prev_enc_frame->is_key_frame_ + ? &prev_enc_frame->key_frame_ + : &prev_enc_frame->sub_frame_; + prev_info->dispose_method = dispose_method; + } +} + +static int IncreasePreviousDuration(WebPAnimEncoder* const enc, int duration) { + const size_t position = enc->count_ - 1; + EncodedFrame* const prev_enc_frame = GetFrame(enc, position); + int new_duration; + + assert(enc->count_ >= 1); + assert(prev_enc_frame->sub_frame_.duration == + prev_enc_frame->key_frame_.duration); + assert(prev_enc_frame->sub_frame_.duration == + (prev_enc_frame->sub_frame_.duration & (MAX_DURATION - 1))); + assert(duration == (duration & (MAX_DURATION - 1))); + + new_duration = prev_enc_frame->sub_frame_.duration + duration; + if (new_duration >= MAX_DURATION) { // Special case. + // Separate out previous frame from earlier merged frames to avoid overflow. + // We add a 1x1 transparent frame for the previous frame, with blending on. + const FrameRect rect = { 0, 0, 1, 1 }; + const uint8_t lossless_1x1_bytes[] = { + 0x52, 0x49, 0x46, 0x46, 0x14, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, + 0x56, 0x50, 0x38, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, + 0x10, 0x88, 0x88, 0x08 + }; + const WebPData lossless_1x1 = { + lossless_1x1_bytes, sizeof(lossless_1x1_bytes) + }; + const uint8_t lossy_1x1_bytes[] = { + 0x52, 0x49, 0x46, 0x46, 0x40, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, + 0x56, 0x50, 0x38, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x4c, 0x50, 0x48, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x50, 0x38, 0x20, 0x18, 0x00, 0x00, 0x00, + 0x30, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x34, 0x25, 0xa4, 0x00, 0x03, 0x70, 0x00, 0xfe, 0xfb, 0xfd, 0x50, 0x00 + }; + const WebPData lossy_1x1 = { lossy_1x1_bytes, sizeof(lossy_1x1_bytes) }; + const int can_use_lossless = + (enc->last_config_.lossless || enc->options_.allow_mixed); + EncodedFrame* const curr_enc_frame = GetFrame(enc, enc->count_); + curr_enc_frame->is_key_frame_ = 0; + curr_enc_frame->sub_frame_.id = WEBP_CHUNK_ANMF; + curr_enc_frame->sub_frame_.x_offset = 0; + curr_enc_frame->sub_frame_.y_offset = 0; + curr_enc_frame->sub_frame_.dispose_method = WEBP_MUX_DISPOSE_NONE; + curr_enc_frame->sub_frame_.blend_method = WEBP_MUX_BLEND; + curr_enc_frame->sub_frame_.duration = duration; + if (!WebPDataCopy(can_use_lossless ? &lossless_1x1 : &lossy_1x1, + &curr_enc_frame->sub_frame_.bitstream)) { + return 0; + } + ++enc->count_; + ++enc->count_since_key_frame_; + enc->flush_count_ = enc->count_ - 1; + enc->prev_candidate_undecided_ = 0; + enc->prev_rect_ = rect; + } else { // Regular case. + // Increase duration of the previous frame by 'duration'. + prev_enc_frame->sub_frame_.duration = new_duration; + prev_enc_frame->key_frame_.duration = new_duration; + } + return 1; +} + +// Pick the candidate encoded frame with smallest size and release other +// candidates. +// TODO(later): Perhaps a rough SSIM/PSNR produced by the encoder should +// also be a criteria, in addition to sizes. +static void PickBestCandidate(WebPAnimEncoder* const enc, + Candidate* const candidates, int is_key_frame, + EncodedFrame* const encoded_frame) { + int i; + int best_idx = -1; + size_t best_size = ~0; + for (i = 0; i < CANDIDATE_COUNT; ++i) { + if (candidates[i].evaluate_) { + const size_t candidate_size = candidates[i].mem_.size; + if (candidate_size < best_size) { + best_idx = i; + best_size = candidate_size; + } + } + } + assert(best_idx != -1); + for (i = 0; i < CANDIDATE_COUNT; ++i) { + if (candidates[i].evaluate_) { + if (i == best_idx) { + WebPMuxFrameInfo* const dst = is_key_frame + ? &encoded_frame->key_frame_ + : &encoded_frame->sub_frame_; + *dst = candidates[i].info_; + GetEncodedData(&candidates[i].mem_, &dst->bitstream); + if (!is_key_frame) { + // Note: Previous dispose method only matters for non-keyframes. + // Also, we don't want to modify previous dispose method that was + // selected when a non key-frame was assumed. + const WebPMuxAnimDispose prev_dispose_method = + (best_idx == LL_DISP_NONE || best_idx == LOSSY_DISP_NONE) + ? WEBP_MUX_DISPOSE_NONE + : WEBP_MUX_DISPOSE_BACKGROUND; + SetPreviousDisposeMethod(enc, prev_dispose_method); + } + enc->prev_rect_ = candidates[i].rect_; // save for next frame. + } else { + WebPMemoryWriterClear(&candidates[i].mem_); + candidates[i].evaluate_ = 0; + } + } + } +} + +// Depending on the configuration, tries different compressions +// (lossy/lossless), dispose methods, blending methods etc to encode the current +// frame and outputs the best one in 'encoded_frame'. +// 'frame_skipped' will be set to true if this frame should actually be skipped. +static WebPEncodingError SetFrame(WebPAnimEncoder* const enc, + const WebPConfig* const config, + int is_key_frame, + EncodedFrame* const encoded_frame, + int* const frame_skipped) { + int i; + WebPEncodingError error_code = VP8_ENC_OK; + const WebPPicture* const curr_canvas = &enc->curr_canvas_copy_; + const WebPPicture* const prev_canvas = &enc->prev_canvas_; + Candidate candidates[CANDIDATE_COUNT]; + const int is_lossless = config->lossless; + const int is_first_frame = enc->is_first_frame_; + + int try_dispose_none = 1; // Default. + FrameRect rect_none; + WebPPicture sub_frame_none; + // First frame cannot be skipped as there is no 'previous frame' to merge it + // to. So, empty rectangle is not allowed for the first frame. + const int empty_rect_allowed_none = !is_first_frame; + + // If current frame is a key-frame, dispose method of previous frame doesn't + // matter, so we don't try dispose to background. + // Also, if key-frame insertion is on, and previous frame could be picked as + // either a sub-frame or a key-frame, then we can't be sure about what frame + // rectangle would be disposed. In that case too, we don't try dispose to + // background. + const int dispose_bg_possible = + !is_key_frame && !enc->prev_candidate_undecided_; + int try_dispose_bg = 0; // Default. + FrameRect rect_bg; + WebPPicture sub_frame_bg; + + WebPConfig config_ll = *config; + WebPConfig config_lossy = *config; + config_ll.lossless = 1; + config_lossy.lossless = 0; + enc->last_config_ = *config; + enc->last_config2_ = config->lossless ? config_lossy : config_ll; + *frame_skipped = 0; + + if (!WebPPictureInit(&sub_frame_none) || !WebPPictureInit(&sub_frame_bg)) { + return VP8_ENC_ERROR_INVALID_CONFIGURATION; + } + + for (i = 0; i < CANDIDATE_COUNT; ++i) { + candidates[i].evaluate_ = 0; + } + + // Change-rectangle assuming previous frame was DISPOSE_NONE. + GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame, + empty_rect_allowed_none, &rect_none, &sub_frame_none); + + if (IsEmptyRect(&rect_none)) { + // Don't encode the frame at all. Instead, the duration of the previous + // frame will be increased later. + assert(empty_rect_allowed_none); + *frame_skipped = 1; + goto End; + } + + if (dispose_bg_possible) { + // Change-rectangle assuming previous frame was DISPOSE_BACKGROUND. + WebPPicture* const prev_canvas_disposed = &enc->prev_canvas_disposed_; + WebPCopyPixels(prev_canvas, prev_canvas_disposed); + DisposeFrameRectangle(WEBP_MUX_DISPOSE_BACKGROUND, &enc->prev_rect_, + prev_canvas_disposed); + // Even if there is exact pixel match between 'disposed previous canvas' and + // 'current canvas', we can't skip current frame, as there may not be exact + // pixel match between 'previous canvas' and 'current canvas'. So, we don't + // allow empty rectangle in this case. + GetSubRect(prev_canvas_disposed, curr_canvas, is_key_frame, is_first_frame, + 0 /* empty_rect_allowed */, &rect_bg, &sub_frame_bg); + assert(!IsEmptyRect(&rect_bg)); + + if (enc->options_.minimize_size) { // Try both dispose methods. + try_dispose_bg = 1; + try_dispose_none = 1; + } else if (RectArea(&rect_bg) < RectArea(&rect_none)) { + try_dispose_bg = 1; // Pick DISPOSE_BACKGROUND. + try_dispose_none = 0; + } + } + + if (try_dispose_none) { + error_code = GenerateCandidates( + enc, candidates, WEBP_MUX_DISPOSE_NONE, is_lossless, is_key_frame, + &rect_none, &sub_frame_none, &config_ll, &config_lossy); + if (error_code != VP8_ENC_OK) goto Err; + } + + if (try_dispose_bg) { + assert(!enc->is_first_frame_); + assert(dispose_bg_possible); + error_code = GenerateCandidates( + enc, candidates, WEBP_MUX_DISPOSE_BACKGROUND, is_lossless, is_key_frame, + &rect_bg, &sub_frame_bg, &config_ll, &config_lossy); + if (error_code != VP8_ENC_OK) goto Err; + } + + PickBestCandidate(enc, candidates, is_key_frame, encoded_frame); + + goto End; + + Err: + for (i = 0; i < CANDIDATE_COUNT; ++i) { + if (candidates[i].evaluate_) { + WebPMemoryWriterClear(&candidates[i].mem_); + } + } + + End: + WebPPictureFree(&sub_frame_none); + WebPPictureFree(&sub_frame_bg); + return error_code; +} + +// Calculate the penalty incurred if we encode given frame as a key frame +// instead of a sub-frame. +static int64_t KeyFramePenalty(const EncodedFrame* const encoded_frame) { + return ((int64_t)encoded_frame->key_frame_.bitstream.size - + encoded_frame->sub_frame_.bitstream.size); +} + +static int CacheFrame(WebPAnimEncoder* const enc, + const WebPConfig* const config) { + int ok = 0; + int frame_skipped = 0; + WebPEncodingError error_code = VP8_ENC_OK; + const size_t position = enc->count_; + EncodedFrame* const encoded_frame = GetFrame(enc, position); + + ++enc->count_; + + if (enc->is_first_frame_) { // Add this as a key-frame. + error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped); + if (error_code != VP8_ENC_OK) goto End; + assert(frame_skipped == 0); // First frame can't be skipped, even if empty. + assert(position == 0 && enc->count_ == 1); + encoded_frame->is_key_frame_ = 1; + enc->flush_count_ = 0; + enc->count_since_key_frame_ = 0; + enc->prev_candidate_undecided_ = 0; + } else { + ++enc->count_since_key_frame_; + if (enc->count_since_key_frame_ <= enc->options_.kmin) { + // Add this as a frame rectangle. + error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped); + if (error_code != VP8_ENC_OK) goto End; + if (frame_skipped) goto Skip; + encoded_frame->is_key_frame_ = 0; + enc->flush_count_ = enc->count_ - 1; + enc->prev_candidate_undecided_ = 0; + } else { + int64_t curr_delta; + + // Add this as a frame rectangle to enc. + error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped); + if (error_code != VP8_ENC_OK) goto End; + if (frame_skipped) goto Skip; + + // Add this as a key-frame to enc, too. + error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped); + if (error_code != VP8_ENC_OK) goto End; + assert(frame_skipped == 0); // Key-frame cannot be an empty rectangle. + + // Analyze size difference of the two variants. + curr_delta = KeyFramePenalty(encoded_frame); + if (curr_delta <= enc->best_delta_) { // Pick this as the key-frame. + if (enc->keyframe_ != KEYFRAME_NONE) { + EncodedFrame* const old_keyframe = GetFrame(enc, enc->keyframe_); + assert(old_keyframe->is_key_frame_); + old_keyframe->is_key_frame_ = 0; + } + encoded_frame->is_key_frame_ = 1; + enc->keyframe_ = (int)position; + enc->best_delta_ = curr_delta; + enc->flush_count_ = enc->count_ - 1; // We can flush previous frames. + } else { + encoded_frame->is_key_frame_ = 0; + } + // Note: We need '>=' below because when kmin and kmax are both zero, + // count_since_key_frame will always be > kmax. + if (enc->count_since_key_frame_ >= enc->options_.kmax) { + enc->flush_count_ = enc->count_ - 1; + enc->count_since_key_frame_ = 0; + enc->keyframe_ = KEYFRAME_NONE; + enc->best_delta_ = DELTA_INFINITY; + } + enc->prev_candidate_undecided_ = 1; + } + } + + // Update previous to previous and previous canvases for next call. + WebPCopyPixels(enc->curr_canvas_, &enc->prev_canvas_); + enc->is_first_frame_ = 0; + + Skip: + ok = 1; + ++enc->in_frame_count_; + + End: + if (!ok || frame_skipped) { + FrameRelease(encoded_frame); + // We reset some counters, as the frame addition failed/was skipped. + --enc->count_; + if (!enc->is_first_frame_) --enc->count_since_key_frame_; + if (!ok) { + MarkError2(enc, "ERROR adding frame. WebPEncodingError", error_code); + } + } + enc->curr_canvas_->error_code = error_code; // report error_code + assert(ok || error_code != VP8_ENC_OK); + return ok; +} + +static int FlushFrames(WebPAnimEncoder* const enc) { + while (enc->flush_count_ > 0) { + WebPMuxError err; + EncodedFrame* const curr = GetFrame(enc, 0); + const WebPMuxFrameInfo* const info = + curr->is_key_frame_ ? &curr->key_frame_ : &curr->sub_frame_; + assert(enc->mux_ != NULL); + err = WebPMuxPushFrame(enc->mux_, info, 1); + if (err != WEBP_MUX_OK) { + MarkError2(enc, "ERROR adding frame. WebPMuxError", err); + return 0; + } + if (enc->options_.verbose) { + fprintf(stderr, "INFO: Added frame. offset:%d,%d dispose:%d blend:%d\n", + info->x_offset, info->y_offset, info->dispose_method, + info->blend_method); + } + ++enc->out_frame_count_; + FrameRelease(curr); + ++enc->start_; + --enc->flush_count_; + --enc->count_; + if (enc->keyframe_ != KEYFRAME_NONE) --enc->keyframe_; + } + + if (enc->count_ == 1 && enc->start_ != 0) { + // Move enc->start to index 0. + const int enc_start_tmp = (int)enc->start_; + EncodedFrame temp = enc->encoded_frames_[0]; + enc->encoded_frames_[0] = enc->encoded_frames_[enc_start_tmp]; + enc->encoded_frames_[enc_start_tmp] = temp; + FrameRelease(&enc->encoded_frames_[enc_start_tmp]); + enc->start_ = 0; + } + return 1; +} + +#undef DELTA_INFINITY +#undef KEYFRAME_NONE + +int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int timestamp, + const WebPConfig* encoder_config) { + WebPConfig config; + + if (enc == NULL) { + return 0; + } + MarkNoError(enc); + + if (!enc->is_first_frame_) { + // Make sure timestamps are non-decreasing (integer wrap-around is OK). + const uint32_t prev_frame_duration = + (uint32_t)timestamp - enc->prev_timestamp_; + if (prev_frame_duration >= MAX_DURATION) { + if (frame != NULL) { + frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION; + } + MarkError(enc, "ERROR adding frame: timestamps must be non-decreasing"); + return 0; + } + if (!IncreasePreviousDuration(enc, (int)prev_frame_duration)) { + return 0; + } + } else { + enc->first_timestamp_ = timestamp; + } + + if (frame == NULL) { // Special: last call. + enc->got_null_frame_ = 1; + enc->prev_timestamp_ = timestamp; + return 1; + } + + if (frame->width != enc->canvas_width_ || + frame->height != enc->canvas_height_) { + frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION; + MarkError(enc, "ERROR adding frame: Invalid frame dimensions"); + return 0; + } + + if (!frame->use_argb) { // Convert frame from YUV(A) to ARGB. + if (enc->options_.verbose) { + fprintf(stderr, "WARNING: Converting frame from YUV(A) to ARGB format; " + "this incurs a small loss.\n"); + } + if (!WebPPictureYUVAToARGB(frame)) { + MarkError(enc, "ERROR converting frame from YUV(A) to ARGB"); + return 0; + } + } + + if (encoder_config != NULL) { + config = *encoder_config; + } else { + WebPConfigInit(&config); + config.lossless = 1; + } + assert(enc->curr_canvas_ == NULL); + enc->curr_canvas_ = frame; // Store reference. + assert(enc->curr_canvas_copy_modified_ == 1); + CopyCurrentCanvas(enc); + + if (!CacheFrame(enc, &config)) { + return 0; + } + + if (!FlushFrames(enc)) { + return 0; + } + enc->curr_canvas_ = NULL; + enc->curr_canvas_copy_modified_ = 1; + enc->prev_timestamp_ = timestamp; + return 1; +} + +// ----------------------------------------------------------------------------- +// Bitstream assembly. + +static int DecodeFrameOntoCanvas(const WebPMuxFrameInfo* const frame, + WebPPicture* const canvas) { + const WebPData* const image = &frame->bitstream; + WebPPicture sub_image; + WebPDecoderConfig config; + WebPInitDecoderConfig(&config); + WebPUtilClearPic(canvas, NULL); + if (WebPGetFeatures(image->bytes, image->size, &config.input) != + VP8_STATUS_OK) { + return 0; + } + if (!WebPPictureView(canvas, frame->x_offset, frame->y_offset, + config.input.width, config.input.height, &sub_image)) { + return 0; + } + config.output.is_external_memory = 1; + config.output.colorspace = MODE_BGRA; + config.output.u.RGBA.rgba = (uint8_t*)sub_image.argb; + config.output.u.RGBA.stride = sub_image.argb_stride * 4; + config.output.u.RGBA.size = config.output.u.RGBA.stride * sub_image.height; + + if (WebPDecode(image->bytes, image->size, &config) != VP8_STATUS_OK) { + return 0; + } + return 1; +} + +static int FrameToFullCanvas(WebPAnimEncoder* const enc, + const WebPMuxFrameInfo* const frame, + WebPData* const full_image) { + WebPPicture* const canvas_buf = &enc->curr_canvas_copy_; + WebPMemoryWriter mem1, mem2; + WebPMemoryWriterInit(&mem1); + WebPMemoryWriterInit(&mem2); + + if (!DecodeFrameOntoCanvas(frame, canvas_buf)) goto Err; + if (!EncodeFrame(&enc->last_config_, canvas_buf, &mem1)) goto Err; + GetEncodedData(&mem1, full_image); + + if (enc->options_.allow_mixed) { + if (!EncodeFrame(&enc->last_config_, canvas_buf, &mem2)) goto Err; + if (mem2.size < mem1.size) { + GetEncodedData(&mem2, full_image); + WebPMemoryWriterClear(&mem1); + } else { + WebPMemoryWriterClear(&mem2); + } + } + return 1; + + Err: + WebPMemoryWriterClear(&mem1); + WebPMemoryWriterClear(&mem2); + return 0; +} + +// Convert a single-frame animation to a non-animated image if appropriate. +// TODO(urvang): Can we pick one of the two heuristically (based on frame +// rectangle and/or presence of alpha)? +static WebPMuxError OptimizeSingleFrame(WebPAnimEncoder* const enc, + WebPData* const webp_data) { + WebPMuxError err = WEBP_MUX_OK; + int canvas_width, canvas_height; + WebPMuxFrameInfo frame; + WebPData full_image; + WebPData webp_data2; + WebPMux* const mux = WebPMuxCreate(webp_data, 0); + if (mux == NULL) return WEBP_MUX_BAD_DATA; + assert(enc->out_frame_count_ == 1); + WebPDataInit(&frame.bitstream); + WebPDataInit(&full_image); + WebPDataInit(&webp_data2); + + err = WebPMuxGetFrame(mux, 1, &frame); + if (err != WEBP_MUX_OK) goto End; + if (frame.id != WEBP_CHUNK_ANMF) goto End; // Non-animation: nothing to do. + err = WebPMuxGetCanvasSize(mux, &canvas_width, &canvas_height); + if (err != WEBP_MUX_OK) goto End; + if (!FrameToFullCanvas(enc, &frame, &full_image)) { + err = WEBP_MUX_BAD_DATA; + goto End; + } + err = WebPMuxSetImage(mux, &full_image, 1); + if (err != WEBP_MUX_OK) goto End; + err = WebPMuxAssemble(mux, &webp_data2); + if (err != WEBP_MUX_OK) goto End; + + if (webp_data2.size < webp_data->size) { // Pick 'webp_data2' if smaller. + WebPDataClear(webp_data); + *webp_data = webp_data2; + WebPDataInit(&webp_data2); + } + + End: + WebPDataClear(&frame.bitstream); + WebPDataClear(&full_image); + WebPMuxDelete(mux); + WebPDataClear(&webp_data2); + return err; +} + +int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) { + WebPMux* mux; + WebPMuxError err; + + if (enc == NULL) { + return 0; + } + MarkNoError(enc); + + if (webp_data == NULL) { + MarkError(enc, "ERROR assembling: NULL input"); + return 0; + } + + if (enc->in_frame_count_ == 0) { + MarkError(enc, "ERROR: No frames to assemble"); + return 0; + } + + if (!enc->got_null_frame_ && enc->in_frame_count_ > 1 && enc->count_ > 0) { + // set duration of the last frame to be avg of durations of previous frames. + const double delta_time = enc->prev_timestamp_ - enc->first_timestamp_; + const int average_duration = (int)(delta_time / (enc->in_frame_count_ - 1)); + if (!IncreasePreviousDuration(enc, average_duration)) { + return 0; + } + } + + // Flush any remaining frames. + enc->flush_count_ = enc->count_; + if (!FlushFrames(enc)) { + return 0; + } + + // Set definitive canvas size. + mux = enc->mux_; + err = WebPMuxSetCanvasSize(mux, enc->canvas_width_, enc->canvas_height_); + if (err != WEBP_MUX_OK) goto Err; + + err = WebPMuxSetAnimationParams(mux, &enc->options_.anim_params); + if (err != WEBP_MUX_OK) goto Err; + + // Assemble into a WebP bitstream. + err = WebPMuxAssemble(mux, webp_data); + if (err != WEBP_MUX_OK) goto Err; + + if (enc->out_frame_count_ == 1) { + err = OptimizeSingleFrame(enc, webp_data); + if (err != WEBP_MUX_OK) goto Err; + } + return 1; + + Err: + MarkError2(enc, "ERROR assembling WebP", err); + return 0; +} + +const char* WebPAnimEncoderGetError(WebPAnimEncoder* enc) { + if (enc == NULL) return NULL; + return enc->error_str_; +} + +// ----------------------------------------------------------------------------- diff --git a/drivers/webp/mux/demux.c b/drivers/webp/mux/demux.c deleted file mode 100644 index 501d08f41d..0000000000 --- a/drivers/webp/mux/demux.c +++ /dev/null @@ -1,902 +0,0 @@ -// 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/webp/mux/muxedit.c b/drivers/webp/mux/muxedit.c index 08629d4ae2..b27663f87a 100644 --- a/drivers/webp/mux/muxedit.c +++ b/drivers/webp/mux/muxedit.c @@ -1,8 +1,10 @@ // 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/ +// 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. // ----------------------------------------------------------------------------- // // Set and delete APIs for mux. @@ -12,50 +14,51 @@ #include <assert.h> #include "./muxi.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "../utils/utils.h" //------------------------------------------------------------------------------ // Life of a mux object. static void MuxInit(WebPMux* const mux) { - if (mux == NULL) return; + assert(mux != NULL); memset(mux, 0, sizeof(*mux)); + mux->canvas_width_ = 0; // just to be explicit + mux->canvas_height_ = 0; } 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); + WebPMux* const mux = (WebPMux*)WebPSafeMalloc(1ULL, sizeof(WebPMux)); + if (mux != NULL) MuxInit(mux); return mux; } } -static void DeleteAllChunks(WebPChunk** const chunk_list) { - while (*chunk_list) { - *chunk_list = ChunkDelete(*chunk_list); +// Delete all images in 'wpi_list'. +static void DeleteAllImages(WebPMuxImage** const wpi_list) { + while (*wpi_list != NULL) { + *wpi_list = MuxImageDelete(*wpi_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_); + assert(mux != NULL); + DeleteAllImages(&mux->images_); + ChunkListDelete(&mux->vp8x_); + ChunkListDelete(&mux->iccp_); + ChunkListDelete(&mux->anim_); + ChunkListDelete(&mux->exif_); + ChunkListDelete(&mux->xmp_); + ChunkListDelete(&mux->unknown_); } void WebPMuxDelete(WebPMux* mux) { - // If mux is NULL MuxRelease is a noop. - MuxRelease(mux); - free(mux); + if (mux != NULL) { + MuxRelease(mux); + WebPSafeFree(mux); + } } //------------------------------------------------------------------------------ @@ -64,81 +67,60 @@ 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, kChunks[(INDEX)].tag); \ + err = ChunkAssignData(&chunk, data, copy_data, 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, +static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag, 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_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); - } + 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_); 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/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; -// 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); + assert(width > 0 && height > 0 && info->duration >= 0); + assert(info->dispose_method == (info->dispose_method & 1)); // 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; + frame_frgm_bytes = (uint8_t*)WebPSafeMalloc(1ULL, frame_frgm_size); + if (frame_frgm_bytes == NULL) return WEBP_MUX_MEMORY_ERROR; - PutLE24(frame_tile_bytes + 0, x_offset / 2); - PutLE24(frame_tile_bytes + 3, y_offset / 2); + PutLE24(frame_frgm_bytes + 0, info->x_offset / 2); + PutLE24(frame_frgm_bytes + 3, info->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); + 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); } - frame_tile->bytes_ = frame_tile_bytes; - frame_tile->size_ = frame_tile_size; + frame_frgm->bytes = frame_frgm_bytes; + frame_frgm->size = frame_frgm_size; return WEBP_MUX_OK; } @@ -149,8 +131,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 { @@ -166,7 +148,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; } @@ -185,204 +167,166 @@ static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) { 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; +static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) { + const WebPChunkId id = ChunkGetIdFromTag(tag); + assert(mux != NULL); 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); + return DeleteChunks(MuxGetChunkListFromId(mux, id), tag); } //------------------------------------------------------------------------------ // Set API(s). -WebPMuxError WebPMuxSetImage(WebPMux* mux, - const WebPData* bitstream, int copy_data) { +WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4], + const WebPData* chunk_data, int copy_data) { + uint32_t tag; 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) { + if (mux == NULL || fourcc == NULL || chunk_data == NULL || + chunk_data->bytes == NULL || chunk_data->size > MAX_CHUNK_PAYLOAD) { return WEBP_MUX_INVALID_ARGUMENT; } + tag = ChunkGetTagFromFourCC(fourcc); - // 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); + // 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 (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 the given chunk. + return MuxSet(mux, tag, 1, chunk_data, copy_data); +} - // Add image chunk. +// 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; ChunkInit(&chunk); - err = ChunkAssignData(&chunk, &image, copy_data, image_tag); + err = ChunkAssignData(&chunk, data, copy_data, 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_); + err = ChunkSetNth(&chunk, chunk_list, 1); 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; +// 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; } - - // 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); + 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; } -WebPMuxError WebPMuxSetColorProfile(WebPMux* mux, const WebPData* color_profile, - int copy_data) { +WebPMuxError WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream, + int copy_data) { + WebPMuxImage wpi; WebPMuxError err; - if (mux == NULL || color_profile == NULL || color_profile->bytes_ == NULL || - color_profile->size_ > MAX_CHUNK_PAYLOAD) { + // Sanity checks. + if (mux == NULL || bitstream == NULL || bitstream->bytes == NULL || + bitstream->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->images_ != NULL) { + // Only one 'simple image' can be added in mux. So, remove present images. + DeleteAllImages(&mux->images_); + } - if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; - if (loop_count >= MAX_LOOP_COUNT) return WEBP_MUX_INVALID_ARGUMENT; + MuxImageInit(&wpi); + err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi); + if (err != WEBP_MUX_OK) goto Err; - // Delete the existing LOOP chunk(s). - err = DeleteLoopCount(mux); - if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; + // Add this WebPMuxImage to mux. + err = MuxImagePush(&wpi, &mux->images_); + if (err != WEBP_MUX_OK) goto Err; - // Add the given loop count. - data = (uint8_t*)malloc(kChunks[IDX_LOOP].size); - if (data == NULL) return WEBP_MUX_MEMORY_ERROR; + // All is well. + return WEBP_MUX_OK; - PutLE16(data, loop_count); - err = MuxAddChunk(mux, 1, kChunks[IDX_LOOP].tag, data, - kChunks[IDX_LOOP].size, 1); - free(data); + Err: // Something bad happened. + MuxImageRelease(&wpi); 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; +WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame, + int copy_data) { WebPMuxImage wpi; WebPMuxError err; - WebPData frame_tile; - const int is_frame = (tag == kChunks[IDX_FRAME].tag) ? 1 : 0; - int is_lossless; - int image_tag; + int is_frame; + const WebPData* const bitstream = &frame->bitstream; // Sanity checks. - if (mux == NULL || bitstream == NULL || bitstream->bytes_ == NULL || - bitstream->size_ > MAX_CHUNK_PAYLOAD) { + 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))) { 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) { + if (frame->id == WEBP_CHUNK_FRGM) { // Dead experiment. return WEBP_MUX_INVALID_ARGUMENT; } - // Snap offsets to even positions. - x_offset &= ~1; - y_offset &= ~1; + if (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; + 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. + } + } - WebPDataInit(&frame_tile); - ChunkInit(&chunk); MuxImageInit(&wpi); - - if (alpha.bytes_ != NULL) { - // Add alpha chunk. - err = ChunkAssignData(&chunk, &alpha, copy_data, kChunks[IDX_ALPHA].tag); + 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 (err != WEBP_MUX_OK) goto Err; - err = ChunkSetNth(&chunk, &wpi.alpha_, 1); + // 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. 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; @@ -391,128 +335,114 @@ static WebPMuxError MuxPushFrameTileInternal( 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 WebPMuxSetAnimationParams(WebPMux* mux, + const WebPMuxAnimParams* params) { WebPMuxError err; + uint8_t data[ANIM_CHUNK_SIZE]; + const WebPData anim = { data, ANIM_CHUNK_SIZE }; - if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; + 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; + } - err = MuxValidateForImage(mux); - if (err != WEBP_MUX_OK) return err; + // 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; - // All well, delete image. - MuxImageDeleteAll(&mux->images_); - return WEBP_MUX_OK; + // Set the animation parameters. + PutLE32(data, params->bgcolor); + PutLE16(data + 4, params->loop_count); + return MuxSet(mux, kChunks[IDX_ANIM].tag, 1, &anim, 1); } -WebPMuxError WebPMuxDeleteMetadata(WebPMux* mux) { - return MuxDeleteAllNamedData(mux, IDX_META); -} +WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux, + int width, int height) { + WebPMuxError err; + if (mux == NULL) { + return WEBP_MUX_INVALID_ARGUMENT; + } + if (width < 0 || height < 0 || + width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) { + return WEBP_MUX_INVALID_ARGUMENT; + } + if (width * (uint64_t)height >= MAX_IMAGE_AREA) { + return WEBP_MUX_INVALID_ARGUMENT; + } + if ((width * height) == 0 && (width | height) != 0) { + // one of width / height is zero, but not both -> invalid! + return WEBP_MUX_INVALID_ARGUMENT; + } + // If we already assembled a VP8X chunk, invalidate it. + err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag); + if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err; -WebPMuxError WebPMuxDeleteColorProfile(WebPMux* mux) { - return MuxDeleteAllNamedData(mux, IDX_ICCP); + mux->canvas_width_ = width; + mux->canvas_height_ = height; + return WEBP_MUX_OK; } -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; +//------------------------------------------------------------------------------ +// Delete API(s). - assert(idx == IDX_FRAME || idx == IDX_TILE); - return MuxImageDeleteNth(&mux->images_, nth, id); +WebPMuxError WebPMuxDeleteChunk(WebPMux* mux, const char fourcc[4]) { + if (mux == NULL || fourcc == NULL) return WEBP_MUX_INVALID_ARGUMENT; + return MuxDeleteAllNamedData(mux, ChunkGetTagFromFourCC(fourcc)); } 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); + if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; + return MuxImageDeleteNth(&mux->images_, nth); } //------------------------------------------------------------------------------ // 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_; +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_; 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); + 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); 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_; + const WebPChunk* const frame_frgm_chunk = wpi->header_; + WebPMuxError err; + assert(wpi != NULL); + assert(frame_frgm_chunk != NULL); - // Get offsets and duration from FRM/TILE chunk. - const WebPMuxError err = - GetFrameTileInfo(frame_tile_chunk, x_offset, y_offset, duration); + // Get offsets and duration from ANMF/FRGM chunk. + err = GetFrameFragmentInfo(frame_frgm_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); + if (width != NULL) *width = wpi->width_; + if (height != NULL) *height = wpi->height_; + return WEBP_MUX_OK; } -static WebPMuxError GetImageCanvasWidthHeight( - const WebPMux* const mux, uint32_t flags, - int* const width, int* const height) { +// Returns the tightest dimension for the canvas considering the image list. +static WebPMuxError GetAdjustedCanvasSize(const WebPMux* const mux, + uint32_t flags, + int* const width, int* const height) { WebPMuxImage* wpi = NULL; assert(mux != NULL); assert(width != NULL && height != NULL); @@ -521,13 +451,15 @@ static WebPMuxError GetImageCanvasWidthHeight( assert(wpi != NULL); assert(wpi->img_ != NULL); - if (wpi->next_) { + if (wpi->next_ != NULL) { int max_x = 0; int max_y = 0; int64_t image_area = 0; - // Aggregate the bounding box for animation frames & tiled images. + // 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. for (; wpi != NULL; wpi = wpi->next_) { - int x_offset, y_offset, duration, w, h; + int x_offset = 0, y_offset = 0, duration = 0, w = 0, h = 0; const WebPMuxError err = GetImageInfo(wpi, &x_offset, &y_offset, &duration, &w, &h); const int max_x_pos = x_offset + w; @@ -542,23 +474,19 @@ static WebPMuxError GetImageCanvasWidthHeight( } *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))) { + // 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))) { *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; + // For a single image, canvas dimensions are same as image dimensions. + *width = wpi->width_; + *height = wpi->height_; } return WEBP_MUX_OK; } @@ -574,50 +502,45 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { int width = 0; int height = 0; uint8_t data[VP8X_CHUNK_SIZE]; - const size_t data_size = VP8X_CHUNK_SIZE; + const WebPData vp8x = { data, 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, IDX_VP8X); + err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag); 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->meta_ != NULL && mux->meta_->data_.bytes_ != NULL) { - flags |= META_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 (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) { + 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) { // 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); + err = GetAdjustedCanvasSize(mux, flags, &width, &height); if (err != WEBP_MUX_OK) return err; if (width <= 0 || height <= 0) { @@ -627,9 +550,21 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { 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. + if (mux->canvas_width_ != 0 || mux->canvas_height_ != 0) { + if (width > mux->canvas_width_ || height > mux->canvas_height_) { + return WEBP_MUX_INVALID_ARGUMENT; + } + width = mux->canvas_width_; + height = mux->canvas_height_; + } + + if (flags == 0) { + // For Simple Image, VP8X chunk should not be added. + return WEBP_MUX_OK; + } + + if (MuxHasAlpha(images)) { + // This means some frames explicitly/implicitly contain alpha. // Note: This 'flags' update must NOT be done for a lossless image // without a VP8X chunk! flags |= ALPHA_FLAG; @@ -639,74 +574,123 @@ static WebPMuxError CreateVP8XChunk(WebPMux* const mux) { 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; + 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 a single fragment or frame, and its rectangle + // covers the whole canvas, 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. + assert(frame_frag != NULL); + if (frame_frag->header_ != NULL && + ((mux->canvas_width_ == 0 && mux->canvas_height_ == 0) || + (frame_frag->width_ == mux->canvas_width_ && + frame_frag->height_ == mux->canvas_height_))) { + 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; } 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) { + if (assembled_data == NULL) { return WEBP_MUX_INVALID_ARGUMENT; } + // Clean up returned data, in case something goes wrong. + memset(assembled_data, 0, sizeof(*assembled_data)); - // 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; - } + if (mux == NULL) { + return WEBP_MUX_INVALID_ARGUMENT; } - // Create VP8X chunk. + // Finalize mux. + err = MuxCleanup(mux); + if (err != WEBP_MUX_OK) return err; 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; + 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; - data = (uint8_t*)malloc(size); + data = (uint8_t*)WebPSafeMalloc(1ULL, 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->anim_, dst); + dst = ImageListEmit(mux->images_, dst); + dst = ChunkListEmit(mux->exif_, dst); + dst = ChunkListEmit(mux->xmp_, dst); dst = ChunkListEmit(mux->unknown_, dst); assert(dst == data + size); // Validate mux. err = MuxValidate(mux); if (err != WEBP_MUX_OK) { - free(data); + WebPSafeFree(data); data = NULL; size = 0; } - // Finalize. - assembled_data->bytes_ = data; - assembled_data->size_ = size; + // Finalize data. + 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 2f06f3ed03..8bd5291661 100644 --- a/drivers/webp/mux/muxi.h +++ b/drivers/webp/mux/muxi.h @@ -1,8 +1,10 @@ // 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/ +// 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. // ----------------------------------------------------------------------------- // // Internal header for mux library. @@ -15,34 +17,41 @@ #include <stdlib.h> #include "../dec/vp8i.h" #include "../dec/vp8li.h" -#include "../format_constants.h" -#include "../mux.h" +#include "webp/mux.h" -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif //------------------------------------------------------------------------------ // Defines and constants. +#define MUX_MAJ_VERSION 0 +#define MUX_MIN_VERSION 2 +#define MUX_REV_VERSION 2 + // 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. + // VP8X, ANIM, and other internally created chunks + // like ANMF/FRGM are always owned. WebPData data_; WebPChunk* next_; }; -// MuxImage object. Store a full webp image (including frame/tile chunk, alpha +// MuxImage object. Store a full WebP image (including ANMF/FRGM chunk, ALPH // chunk and VP8/VP8L chunk), typedef struct WebPMuxImage WebPMuxImage; struct WebPMuxImage { - WebPChunk* header_; // Corresponds to WEBP_CHUNK_FRAME/WEBP_CHUNK_TILE. + WebPChunk* header_; // Corresponds to WEBP_CHUNK_ANMF/WEBP_CHUNK_FRGM. 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_; }; @@ -51,11 +60,14 @@ struct WebPMuxImage { struct WebPMux { WebPMuxImage* images_; WebPChunk* iccp_; - WebPChunk* meta_; - WebPChunk* loop_; + WebPChunk* exif_; + WebPChunk* xmp_; + WebPChunk* anim_; WebPChunk* vp8x_; - WebPChunk* unknown_; + WebPChunk* unknown_; + int canvas_width_; + int canvas_height_; }; // CHUNK_INDEX enum: used for indexing within 'kChunks' (defined below) only. @@ -65,13 +77,14 @@ struct WebPMux { typedef enum { IDX_VP8X = 0, IDX_ICCP, - IDX_LOOP, - IDX_FRAME, - IDX_TILE, + IDX_ANIM, + IDX_ANMF, + IDX_FRGM, IDX_ALPHA, IDX_VP8, IDX_VP8L, - IDX_META, + IDX_EXIF, + IDX_XMP, IDX_UNKNOWN, IDX_NIL, @@ -80,8 +93,6 @@ 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; @@ -91,55 +102,23 @@ 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_NIL if not found. +// Get chunk index from chunk tag. Returns IDX_UNKNOWN if not found. CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag); -// Get chunk id from chunk tag. Returns WEBP_CHUNK_NIL if not found. +// Get chunk id from chunk tag. Returns WEBP_CHUNK_UNKNOWN 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); @@ -150,7 +129,8 @@ 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". -WebPMuxError ChunkSetNth(const WebPChunk* chunk, WebPChunk** chunk_list, +// On success ownership is transferred from 'chunk' to the 'chunk_list'. +WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list, uint32_t nth); // Releases chunk and returns chunk->next_. @@ -159,23 +139,27 @@ 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 ChunksListDiskSize(const WebPChunk* chunk_list); +size_t ChunkListDiskSize(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. @@ -189,82 +173,59 @@ 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_FRAME: - case WEBP_CHUNK_TILE: + case WEBP_CHUNK_ANMF: + case WEBP_CHUNK_FRGM: 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); +// Delete nth image in the image list. +WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth); -// Get nth image in the image list with given tag id. +// Get nth image in the image list. WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth, - WebPChunkId id, WebPMuxImage** wpi); + 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); +// Checks if the given image list contains at least one image with alpha. +int MuxHasAlpha(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) +#ifdef __cplusplus } // extern "C" #endif diff --git a/drivers/webp/mux/muxinternal.c b/drivers/webp/mux/muxinternal.c index 6c3c4fe60a..4babbe82fc 100644 --- a/drivers/webp/mux/muxinternal.c +++ b/drivers/webp/mux/muxinternal.c @@ -1,8 +1,10 @@ // 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/ +// 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. // ----------------------------------------------------------------------------- // // Internal objects and utils for mux. @@ -12,29 +14,33 @@ #include <assert.h> #include "./muxi.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "../utils/utils.h" #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', '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('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 }, + { 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 }, - { 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) { @@ -60,9 +66,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 i; + if (tag == kChunks[i].tag) return (CHUNK_INDEX)i; } - return IDX_NIL; + return IDX_UNKNOWN; } WebPChunkId ChunkGetIdFromTag(uint32_t tag) { @@ -70,7 +76,16 @@ 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_NIL; + 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); } //------------------------------------------------------------------------------ @@ -78,7 +93,7 @@ WebPChunkId ChunkGetIdFromTag(uint32_t tag) { // Returns next chunk in the chunk list with the given tag. static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) { - while (chunk && chunk->tag_ != tag) { + while (chunk != NULL && chunk->tag_ != tag) { chunk = chunk->next_; } return chunk; @@ -87,7 +102,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) return NULL; + if (first == NULL) return NULL; while (--iter != 0) { WebPChunk* next_chunk = ChunkSearchNextInList(first->next_, tag); @@ -99,14 +114,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 1 if nth chunk was found, 0 otherwise. +// Returns true if nth chunk was found. static int ChunkSearchListToSet(WebPChunk** chunk_list, uint32_t nth, WebPChunk*** const location) { uint32_t count = 0; - assert(chunk_list); + assert(chunk_list != NULL); *location = chunk_list; - while (*chunk_list) { + while (*chunk_list != NULL) { WebPChunk* const cur_chunk = *chunk_list; ++count; if (count == nth) return 1; // Found. @@ -124,34 +139,25 @@ 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_LOOP].tag) { + if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].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. + 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. chunk->data_ = *data; } } - chunk->tag_ = tag; - return WEBP_MUX_OK; } -WebPMuxError ChunkSetNth(const WebPChunk* chunk, WebPChunk** chunk_list, +WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list, uint32_t nth) { WebPChunk* new_chunk; @@ -159,9 +165,10 @@ WebPMuxError ChunkSetNth(const WebPChunk* chunk, WebPChunk** chunk_list, return WEBP_MUX_NOT_FOUND; } - new_chunk = (WebPChunk*)malloc(sizeof(*new_chunk)); + new_chunk = (WebPChunk*)WebPSafeMalloc(1ULL, 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; @@ -172,70 +179,47 @@ WebPMuxError ChunkSetNth(const WebPChunk* chunk, WebPChunk** chunk_list, WebPChunk* ChunkDelete(WebPChunk* const chunk) { WebPChunk* const next = ChunkRelease(chunk); - free(chunk); + WebPSafeFree(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_; +void ChunkListDelete(WebPChunk** const chunk_list) { + while (*chunk_list != NULL) { + *chunk_list = ChunkDelete(*chunk_list); } - return size; } +//------------------------------------------------------------------------------ +// Chunk serialization methods. + 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) { + while (chunk_list != NULL) { 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_; +size_t ChunkListDiskSize(const WebPChunk* chunk_list) { + size_t size = 0; + while (chunk_list != NULL) { + size += ChunkDiskSize(chunk_list); + chunk_list = chunk_list->next_; } - return 1; + return size; } //------------------------------------------------------------------------------ @@ -252,6 +236,7 @@ WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) { ChunkDelete(wpi->header_); ChunkDelete(wpi->alpha_); ChunkDelete(wpi->img_); + ChunkListDelete(&wpi->unknown_); next = wpi->next_; MuxImageInit(wpi); @@ -261,14 +246,31 @@ 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_) { - 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; + 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'. + } } } return count; @@ -276,34 +278,22 @@ 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 1 if nth image with given id was found, 0 otherwise. +// Returns true if nth image was found. 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); + nth = MuxImageCount(*wpi_list, WEBP_CHUNK_NIL); if (nth == 0) return 0; // Not found. } - while (*wpi_list) { + while (*wpi_list != NULL) { 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. - } - } + ++count; + if (count == nth) return 1; // Found. wpi_list = &cur_wpi->next_; *location = wpi_list; } @@ -322,7 +312,7 @@ WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) { wpi_list = &cur_wpi->next_; } - new_wpi = (WebPMuxImage*)malloc(sizeof(*new_wpi)); + new_wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*new_wpi)); if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR; *new_wpi = *wpi; new_wpi->next_ = NULL; @@ -341,20 +331,13 @@ WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) { 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); + WebPSafeFree(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) { +WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) { assert(wpi_list); - if (!SearchImageToGetOrDelete(wpi_list, nth, id, &wpi_list)) { + if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) { return WEBP_MUX_NOT_FOUND; } *wpi_list = MuxImageDelete(*wpi_list); @@ -365,10 +348,10 @@ WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth, // MuxImage reader methods. WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth, - WebPChunkId id, WebPMuxImage** wpi) { + WebPMuxImage** wpi) { assert(wpi_list); assert(wpi); - if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth, id, + if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth, (WebPMuxImage***)&wpi_list)) { return WEBP_MUX_NOT_FOUND; } @@ -385,47 +368,48 @@ 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; } -size_t MuxImageListDiskSize(const WebPMuxImage* wpi_list) { - size_t size = 0; - while (wpi_list) { - size += MuxImageDiskSize(wpi_list); - wpi_list = wpi_list->next_; +// 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. } - return size; + return dst + ChunkDiskSize(header); } 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). + // 1. ANMF/FRGM chunk (if present). + // 2. ALPH chunk (if present). // 3. VP8/VP8L chunk. assert(wpi); - if (wpi->header_ != NULL) dst = ChunkEmit(wpi->header_, dst); + if (wpi->header_ != NULL) { + dst = ChunkEmitSpecial(wpi->header_, MuxImageDiskSize(wpi), 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_; - } + if (wpi->unknown_ != NULL) dst = ChunkListEmit(wpi->unknown_, dst); return dst; } //------------------------------------------------------------------------------ // Helper methods for mux. -int MuxHasLosslessImages(const WebPMuxImage* images) { +int MuxHasAlpha(const WebPMuxImage* images) { while (images != NULL) { - assert(images->img_ != NULL); - if (images->img_->tag_ == kChunks[IDX_VP8L].tag) { - return 1; - } + if (images->has_alpha_) return 1; images = images->next_; } return 0; @@ -441,30 +425,13 @@ 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_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; + 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_; } } @@ -480,7 +447,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, - WebPFeatureFlags vp8x_flags, + uint32_t vp8x_flags, int max, int* num) { const WebPMuxError err = WebPMuxNumChunks(mux, kChunks[idx].id, num); @@ -494,10 +461,11 @@ static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx, WebPMuxError MuxValidate(const WebPMux* const mux) { int num_iccp; - int num_meta; - int num_loop_chunks; + int num_exif; + int num_xmp; + int num_anim; int num_frames; - int num_tiles; + int num_fragments; int num_vp8x; int num_images; int num_alpha; @@ -517,29 +485,33 @@ 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_META, META_FLAG, flags, 1, &num_meta); + err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp); 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); + // 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); if (err != WEBP_MUX_OK) return err; - err = ValidateChunk(mux, IDX_FRAME, NO_FLAG, flags, -1, &num_frames); + err = ValidateChunk(mux, IDX_ANMF, 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)) { + if (has_animation && (num_anim == 0 || num_frames == 0)) { return WEBP_MUX_INVALID_ARGUMENT; } - if (!has_animation && (num_loop_chunks == 1 || num_frames > 0)) { + if (!has_animation && (num_anim == 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); + // Fragmentation: FRAGMENTS_FLAG and FRGM chunk(s) are consistent. + err = ValidateChunk(mux, IDX_FRGM, FRAGMENTS_FLAG, flags, -1, &num_fragments); if (err != WEBP_MUX_OK) return err; // Verify either VP8X chunk is present OR there is only one elem in @@ -551,16 +523,22 @@ 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 (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; + 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; } - // num_tiles & num_images are consistent. - if (num_tiles > 0 && num_images != num_tiles) { + // num_fragments & num_images are consistent. + if (num_fragments > 0 && num_images != num_fragments) { return WEBP_MUX_INVALID_ARGUMENT; } @@ -571,6 +549,3 @@ 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 21c3cfbaeb..8957a1e46e 100644 --- a/drivers/webp/mux/muxread.c +++ b/drivers/webp/mux/muxread.c @@ -1,8 +1,10 @@ // 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/ +// 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. // ----------------------------------------------------------------------------- // // Read APIs for mux. @@ -12,10 +14,7 @@ #include <assert.h> #include "./muxi.h" - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif +#include "../utils/utils.h" //------------------------------------------------------------------------------ // Helper method(s). @@ -41,8 +40,9 @@ 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_LOOP, mux->loop_); - SWITCH_ID_LIST(IDX_META, mux->meta_); + 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_); return WEBP_MUX_NOT_FOUND; } @@ -50,15 +50,14 @@ 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 ChunkVerifyAndAssignData(WebPChunk* chunk, - const uint8_t* data, - size_t data_size, size_t riff_size, - int copy_data) { +static WebPMuxError ChunkVerifyAndAssign(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; + if (data_size < CHUNK_HEADER_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA; chunk_size = GetLE32(data + TAG_SIZE); { @@ -68,11 +67,103 @@ static WebPMuxError ChunkVerifyAndAssignData(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. @@ -94,8 +185,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; @@ -129,48 +220,55 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, data += RIFF_HEADER_SIZE; size -= RIFF_HEADER_SIZE; - wpi = (WebPMuxImage*)malloc(sizeof(*wpi)); + wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*wpi)); if (wpi == NULL) goto Err; MuxImageInit(wpi); // Loop over chunks. while (data != end) { + size_t data_size; WebPChunkId id; - WebPMuxError err; - - err = ChunkVerifyAndAssignData(&chunk, data, size, riff_size, copy_data); - if (err != WEBP_MUX_OK) goto Err; - + WebPChunk** chunk_list; + if (ChunkVerifyAndAssign(&chunk, data, size, riff_size, + copy_data) != WEBP_MUX_OK) { + goto Err; + } + data_size = ChunkDiskSize(&chunk); 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) { + 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; 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. - } 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; + break; + case WEBP_CHUNK_ANMF: + 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; + if (id == WEBP_CHUNK_VP8X) { // grab global specs + mux->canvas_width_ = GetLE24(data + 12) + 1; + mux->canvas_height_ = GetLE24(data + 15) + 1; + } + break; } + data += data_size; + size -= data_size; ChunkInit(&chunk); } @@ -190,31 +288,74 @@ WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data, //------------------------------------------------------------------------------ // Get API(s). -WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) { - WebPData data; - WebPMuxError err; +// 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; + } +} - if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT; - *flags = 0; +// 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; + WebPData data; + assert(mux != NULL); // 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 (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 { + const WebPMuxImage* const wpi = mux->images_; + // Grab user-forced canvas size as default. + w = mux->canvas_width_; + h = mux->canvas_height_; + if (w == 0 && h == 0 && ValidateForSingleImage(mux) == WEBP_MUX_OK) { + // single image and not forced canvas size => use dimension of first frame + assert(wpi != NULL); + w = wpi->width_; + h = wpi->height_; + } + if (wpi != NULL) { + if (wpi->has_alpha_) f |= ALPHA_FLAG; + } } + if (w * (uint64_t)h >= MAX_IMAGE_AREA) return WEBP_MUX_BAD_DATA; - if (data.size_ < CHUNK_SIZE_BYTES) return WEBP_MUX_BAD_DATA; - - // All OK. Fill up flags. - *flags = GetLE32(data.bytes_); + 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); +} + +WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) { + if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT; + return MuxGetCanvasInfo(mux, NULL, NULL, flags); +} + 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; @@ -230,7 +371,7 @@ static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width, } // Assemble a single image WebP bitstream from 'wpi'. -static WebPMuxError SynthesizeBitstream(WebPMuxImage* const wpi, +static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi, WebPData* const bitstream) { uint8_t* dst; @@ -238,25 +379,17 @@ static WebPMuxError SynthesizeBitstream(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 FRM/TILE chunk for a single image. + // Note: No need to output ANMF/FRGM 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); + uint8_t* const data = (uint8_t*)WebPSafeMalloc(1ULL, 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 = EmitVP8XChunk(dst, wpi->width_, wpi->height_, ALPHA_FLAG); // VP8X. dst = ChunkListEmit(wpi->alpha_, dst); // ALPH. } @@ -265,107 +398,115 @@ static WebPMuxError SynthesizeBitstream(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 WebPMuxGetImage(const WebPMux* mux, WebPData* bitstream) { - WebPMuxError err; - WebPMuxImage* wpi = NULL; - - if (mux == NULL || bitstream == NULL) { +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; } - - 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); + idx = ChunkGetIndexFromFourCC(fourcc); + if (IsWPI(kChunks[idx].id)) { // An image chunk. + 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; + } } -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); +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 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 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; + if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT; + 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); } -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 WebPMuxGetFrame( + const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame) { 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)) { + // Sanity checks. + if (mux == NULL || frame == NULL) { return WEBP_MUX_INVALID_ARGUMENT; } // Get the nth WebPMuxImage. - err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, id, &wpi); + err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, &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_; + // Get frame info. + if (wpi->header_ == NULL) { + return MuxGetImageInternal(wpi, frame); + } else { + return MuxGetFrameFragmentInternal(wpi, frame); + } +} - 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); +WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux, + WebPMuxAnimParams* params) { + WebPData anim; + WebPMuxError err; - return SynthesizeBitstream(wpi, bitstream); -} + if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT; -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); -} + 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 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); + return WEBP_MUX_OK; } // 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; + if (id == kChunks[i].id) return (CHUNK_INDEX)i; } return IDX_NIL; } @@ -393,19 +534,11 @@ WebPMuxError WebPMuxNumChunks(const WebPMux* mux, *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); - } + 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 |