summaryrefslogtreecommitdiff
path: root/thirdparty/libwebp/mux
diff options
context:
space:
mode:
authorRémi Verschelde <rverschelde@gmail.com>2016-10-11 23:35:58 +0200
committerRémi Verschelde <rverschelde@gmail.com>2016-10-15 11:50:39 +0200
commitee3cf211c6fd4d1e30617467cdbbe945798a68b3 (patch)
treed770150c48c806df4daca66770cde8d5b665a3ff /thirdparty/libwebp/mux
parentb1e8889d969f5f88539c47c2afac6c9ea2a2dc11 (diff)
webp: Make it a module and unbundle libwebp thirdparty files
Note that there are two Godot-specific changes made to libwebp for the javascript/HTML5 platform. They are documented in the README.md.
Diffstat (limited to 'thirdparty/libwebp/mux')
-rw-r--r--thirdparty/libwebp/mux/anim_encode.c1534
-rw-r--r--thirdparty/libwebp/mux/muxedit.c696
-rw-r--r--thirdparty/libwebp/mux/muxi.h232
-rw-r--r--thirdparty/libwebp/mux/muxinternal.c551
-rw-r--r--thirdparty/libwebp/mux/muxread.c544
5 files changed, 3557 insertions, 0 deletions
diff --git a/thirdparty/libwebp/mux/anim_encode.c b/thirdparty/libwebp/mux/anim_encode.c
new file mode 100644
index 0000000000..53e2906a82
--- /dev/null
+++ b/thirdparty/libwebp/mux/anim_encode.c
@@ -0,0 +1,1534 @@
+// 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 <math.h> // for pow()
+#include <stdio.h>
+#include <stdlib.h> // for abs()
+
+#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_config_reversed_; // If 'last_config_' uses lossless, then
+ // this config uses lossy and vice versa;
+ // 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;
+ enc_options->verbose = 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;
+}
+
+// This starting value is more fit to WebPCleanupTransparentAreaLossless().
+#define TRANSPARENT_COLOR 0x00000000
+
+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];
+}
+
+typedef int (*ComparePixelsFunc)(const uint32_t*, int, const uint32_t*, int,
+ int, int);
+
+// Returns true if 'length' number of pixels in 'src' and 'dst' are equal,
+// assuming the given step sizes between pixels.
+// 'max_allowed_diff' is unused and only there to allow function pointer use.
+static WEBP_INLINE int ComparePixelsLossless(const uint32_t* src, int src_step,
+ const uint32_t* dst, int dst_step,
+ int length, int max_allowed_diff) {
+ (void)max_allowed_diff;
+ assert(length > 0);
+ while (length-- > 0) {
+ if (*src != *dst) {
+ return 0;
+ }
+ src += src_step;
+ dst += dst_step;
+ }
+ return 1;
+}
+
+// Helper to check if each channel in 'src' and 'dst' is at most off by
+// 'max_allowed_diff'.
+static WEBP_INLINE int PixelsAreSimilar(uint32_t src, uint32_t dst,
+ int max_allowed_diff) {
+ const int src_a = (src >> 24) & 0xff;
+ const int src_r = (src >> 16) & 0xff;
+ const int src_g = (src >> 8) & 0xff;
+ const int src_b = (src >> 0) & 0xff;
+ const int dst_a = (dst >> 24) & 0xff;
+ const int dst_r = (dst >> 16) & 0xff;
+ const int dst_g = (dst >> 8) & 0xff;
+ const int dst_b = (dst >> 0) & 0xff;
+
+ return (abs(src_r * src_a - dst_r * dst_a) <= (max_allowed_diff * 255)) &&
+ (abs(src_g * src_a - dst_g * dst_a) <= (max_allowed_diff * 255)) &&
+ (abs(src_b * src_a - dst_b * dst_a) <= (max_allowed_diff * 255)) &&
+ (abs(src_a - dst_a) <= max_allowed_diff);
+}
+
+// Returns true if 'length' number of pixels in 'src' and 'dst' are within an
+// error bound, assuming the given step sizes between pixels.
+static WEBP_INLINE int ComparePixelsLossy(const uint32_t* src, int src_step,
+ const uint32_t* dst, int dst_step,
+ int length, int max_allowed_diff) {
+ assert(length > 0);
+ while (length-- > 0) {
+ if (!PixelsAreSimilar(*src, *dst, max_allowed_diff)) {
+ return 0;
+ }
+ src += src_step;
+ dst += dst_step;
+ }
+ return 1;
+}
+
+static int IsEmptyRect(const FrameRect* const rect) {
+ return (rect->width_ == 0) || (rect->height_ == 0);
+}
+
+static int QualityToMaxDiff(float quality) {
+ const double val = pow(quality / 100., 0.5);
+ const double max_diff = 31 * (1 - val) + 1 * val;
+ return (int)(max_diff + 0.5);
+}
+
+// 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 is_lossless, float quality) {
+ int i, j;
+ const ComparePixelsFunc compare_pixels =
+ is_lossless ? ComparePixelsLossless : ComparePixelsLossy;
+ const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
+ const int max_allowed_diff = is_lossless ? 0 : max_allowed_diff_lossy;
+
+ // 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 (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
+ rect->height_, max_allowed_diff)) {
+ --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 (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
+ rect->height_, max_allowed_diff)) {
+ --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 (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
+ max_allowed_diff)) {
+ --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 (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
+ max_allowed_diff)) {
+ --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;
+}
+
+typedef struct {
+ int should_try_; // Should try this set of parameters.
+ int empty_rect_allowed_; // Frame with empty rectangle can be skipped.
+ FrameRect rect_ll_; // Frame rectangle for lossless compression.
+ WebPPicture sub_frame_ll_; // Sub-frame pic for lossless compression.
+ FrameRect rect_lossy_; // Frame rectangle for lossy compression.
+ // Could be smaller than rect_ll_ as pixels
+ // with small diffs can be ignored.
+ WebPPicture sub_frame_lossy_; // Sub-frame pic for lossless compression.
+} SubFrameParams;
+
+static int SubFrameParamsInit(SubFrameParams* const params,
+ int should_try, int empty_rect_allowed) {
+ params->should_try_ = should_try;
+ params->empty_rect_allowed_ = empty_rect_allowed;
+ if (!WebPPictureInit(&params->sub_frame_ll_) ||
+ !WebPPictureInit(&params->sub_frame_lossy_)) {
+ return 0;
+ }
+ return 1;
+}
+
+static void SubFrameParamsFree(SubFrameParams* const params) {
+ WebPPictureFree(&params->sub_frame_ll_);
+ WebPPictureFree(&params->sub_frame_lossy_);
+}
+
+// Given previous and current canvas, picks the optimal rectangle for the
+// current frame based on 'is_lossless' and other parameters. Assumes that the
+// initial guess 'rect' is valid.
+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,
+ int is_lossless, float quality, FrameRect* const rect,
+ WebPPicture* const sub_frame) {
+ 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,
+ is_lossless, quality);
+ }
+
+ 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);
+}
+
+// Picks optimal frame rectangle for both lossless and lossy compression. The
+// initial guess for frame rectangles will be the full canvas.
+static int GetSubRects(const WebPPicture* const prev_canvas,
+ const WebPPicture* const curr_canvas, int is_key_frame,
+ int is_first_frame, float quality,
+ SubFrameParams* const params) {
+ // Lossless frame rectangle.
+ params->rect_ll_.x_offset_ = 0;
+ params->rect_ll_.y_offset_ = 0;
+ params->rect_ll_.width_ = curr_canvas->width;
+ params->rect_ll_.height_ = curr_canvas->height;
+ if (!GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
+ params->empty_rect_allowed_, 1, quality,
+ &params->rect_ll_, &params->sub_frame_ll_)) {
+ return 0;
+ }
+ // Lossy frame rectangle.
+ params->rect_lossy_ = params->rect_ll_; // seed with lossless rect.
+ return GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
+ params->empty_rect_allowed_, 0, quality,
+ &params->rect_lossy_, &params->sub_frame_lossy_);
+}
+
+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 IsLosslessBlendingPossible(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;
+}
+
+static int IsLossyBlendingPossible(const WebPPicture* const src,
+ const WebPPicture* const dst,
+ const FrameRect* const rect,
+ float quality) {
+ const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
+ 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 &&
+ !PixelsAreSimilar(src_pixel, dst_pixel, max_allowed_diff_lossy)) {
+ // 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;
+}
+
+// For pixels in 'rect', replace those pixels in 'dst' that are same as 'src' by
+// transparent pixels.
+// Returns true if at least one pixel gets modified.
+static int IncreaseTransparency(const WebPPicture* const src,
+ const FrameRect* const rect,
+ WebPPicture* const dst) {
+ int i, j;
+ int modified = 0;
+ 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) {
+ pdst[i] = TRANSPARENT_COLOR;
+ modified = 1;
+ }
+ }
+ }
+ return modified;
+}
+
+#undef TRANSPARENT_COLOR
+
+// Replace similar blocks of pixels by a 'see-through' transparent block
+// with uniform average color.
+// Assumes lossy compression is being used.
+// Returns true if at least one pixel gets modified.
+static int FlattenSimilarBlocks(const WebPPicture* const src,
+ const FrameRect* const rect,
+ WebPPicture* const dst, float quality) {
+ const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
+ int i, j;
+ int modified = 0;
+ 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 &&
+ PixelsAreSimilar(src_pixel, pdst[x + y * dst->argb_stride],
+ max_allowed_diff_lossy)) {
+ ++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;
+ }
+ }
+ modified = 1;
+ }
+ }
+ }
+ return modified;
+}
+
+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 encoder_config,
+ int use_blending,
+ Candidate* const candidate) {
+ WebPConfig config = *encoder_config;
+ 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 (!config.lossless && use_blending) {
+ // Disable filtering to avoid blockiness in reconstructed frames at the
+ // time of decoding.
+ config.autofilter = 0;
+ config.filter_strength = 0;
+ }
+ 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_.progress_hook = enc->curr_canvas_->progress_hook;
+ enc->curr_canvas_copy_.user_data = enc->curr_canvas_->user_data;
+ enc->curr_canvas_copy_modified_ = 0;
+ }
+}
+
+enum {
+ LL_DISP_NONE = 0,
+ LL_DISP_BG,
+ LOSSY_DISP_NONE,
+ LOSSY_DISP_BG,
+ CANDIDATE_COUNT
+};
+
+#define MIN_COLORS_LOSSY 31 // Don't try lossy below this threshold.
+#define MAX_COLORS_LOSSLESS 194 // Don't try lossless above this threshold.
+
+// Generates candidates for a given dispose method given pre-filled sub-frame
+// 'params'.
+static WebPEncodingError GenerateCandidates(
+ WebPAnimEncoder* const enc, Candidate candidates[CANDIDATE_COUNT],
+ WebPMuxAnimDispose dispose_method, int is_lossless, int is_key_frame,
+ SubFrameParams* const params,
+ 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_;
+ int use_blending_ll;
+ int use_blending_lossy;
+
+ CopyCurrentCanvas(enc);
+ use_blending_ll =
+ !is_key_frame &&
+ IsLosslessBlendingPossible(prev_canvas, curr_canvas, &params->rect_ll_);
+ use_blending_lossy =
+ !is_key_frame &&
+ IsLossyBlendingPossible(prev_canvas, curr_canvas, &params->rect_lossy_,
+ config_lossy->quality);
+
+ // 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 = WebPGetColorPalette(&params->sub_frame_ll_, NULL);
+ 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_ll) {
+ enc->curr_canvas_copy_modified_ =
+ IncreaseTransparency(prev_canvas, &params->rect_ll_, curr_canvas);
+ }
+ error_code = EncodeCandidate(&params->sub_frame_ll_, &params->rect_ll_,
+ config_ll, use_blending_ll, candidate_ll);
+ if (error_code != VP8_ENC_OK) return error_code;
+ }
+ if (candidate_lossy->evaluate_) {
+ CopyCurrentCanvas(enc);
+ if (use_blending_lossy) {
+ enc->curr_canvas_copy_modified_ =
+ FlattenSimilarBlocks(prev_canvas, &params->rect_lossy_, curr_canvas,
+ config_lossy->quality);
+ }
+ error_code =
+ EncodeCandidate(&params->sub_frame_lossy_, &params->rect_lossy_,
+ config_lossy, use_blending_lossy, candidate_lossy);
+ if (error_code != VP8_ENC_OK) return error_code;
+ enc->curr_canvas_copy_modified_ = 1;
+ }
+ 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_;
+
+ // 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;
+
+ // 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.
+ const int empty_rect_allowed_bg = 0;
+
+ // 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_;
+
+ SubFrameParams dispose_none_params;
+ SubFrameParams dispose_bg_params;
+
+ WebPConfig config_ll = *config;
+ WebPConfig config_lossy = *config;
+ config_ll.lossless = 1;
+ config_lossy.lossless = 0;
+ enc->last_config_ = *config;
+ enc->last_config_reversed_ = config->lossless ? config_lossy : config_ll;
+ *frame_skipped = 0;
+
+ if (!SubFrameParamsInit(&dispose_none_params, 1, empty_rect_allowed_none) ||
+ !SubFrameParamsInit(&dispose_bg_params, 0, empty_rect_allowed_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.
+ if (!GetSubRects(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
+ config_lossy.quality, &dispose_none_params)) {
+ error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
+ goto Err;
+ }
+
+ if ((is_lossless && IsEmptyRect(&dispose_none_params.rect_ll_)) ||
+ (!is_lossless && IsEmptyRect(&dispose_none_params.rect_lossy_))) {
+ // 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);
+
+ if (!GetSubRects(prev_canvas_disposed, curr_canvas, is_key_frame,
+ is_first_frame, config_lossy.quality,
+ &dispose_bg_params)) {
+ error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
+ goto Err;
+ }
+ assert(!IsEmptyRect(&dispose_bg_params.rect_ll_));
+ assert(!IsEmptyRect(&dispose_bg_params.rect_lossy_));
+
+ if (enc->options_.minimize_size) { // Try both dispose methods.
+ dispose_bg_params.should_try_ = 1;
+ dispose_none_params.should_try_ = 1;
+ } else if ((is_lossless &&
+ RectArea(&dispose_bg_params.rect_ll_) <
+ RectArea(&dispose_none_params.rect_ll_)) ||
+ (!is_lossless &&
+ RectArea(&dispose_bg_params.rect_lossy_) <
+ RectArea(&dispose_none_params.rect_lossy_))) {
+ dispose_bg_params.should_try_ = 1; // Pick DISPOSE_BACKGROUND.
+ dispose_none_params.should_try_ = 0;
+ }
+ }
+
+ if (dispose_none_params.should_try_) {
+ error_code = GenerateCandidates(
+ enc, candidates, WEBP_MUX_DISPOSE_NONE, is_lossless, is_key_frame,
+ &dispose_none_params, &config_ll, &config_lossy);
+ if (error_code != VP8_ENC_OK) goto Err;
+ }
+
+ if (dispose_bg_params.should_try_) {
+ assert(!enc->is_first_frame_);
+ assert(dispose_bg_possible);
+ error_code = GenerateCandidates(
+ enc, candidates, WEBP_MUX_DISPOSE_BACKGROUND, is_lossless, is_key_frame,
+ &dispose_bg_params, &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:
+ SubFrameParamsFree(&dispose_none_params);
+ SubFrameParamsFree(&dispose_bg_params);
+ 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;
+ int ok;
+
+ 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) {
+ if (!WebPValidateConfig(encoder_config)) {
+ MarkError(enc, "ERROR adding frame: Invalid WebPConfig");
+ return 0;
+ }
+ 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);
+
+ ok = CacheFrame(enc, &config) && FlushFrames(enc);
+
+ enc->curr_canvas_ = NULL;
+ enc->curr_canvas_copy_modified_ = 1;
+ if (ok) {
+ enc->prev_timestamp_ = timestamp;
+ }
+ return ok;
+}
+
+// -----------------------------------------------------------------------------
+// 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_reversed_, 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/thirdparty/libwebp/mux/muxedit.c b/thirdparty/libwebp/mux/muxedit.c
new file mode 100644
index 0000000000..9bbed42b1a
--- /dev/null
+++ b/thirdparty/libwebp/mux/muxedit.c
@@ -0,0 +1,696 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Set and delete APIs for mux.
+//
+// Authors: Urvang (urvang@google.com)
+// Vikas (vikasa@google.com)
+
+#include <assert.h>
+#include "./muxi.h"
+#include "../utils/utils.h"
+
+//------------------------------------------------------------------------------
+// Life of a mux object.
+
+static void MuxInit(WebPMux* const mux) {
+ 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*)WebPSafeMalloc(1ULL, sizeof(WebPMux));
+ if (mux != NULL) MuxInit(mux);
+ return mux;
+ }
+}
+
+// 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) {
+ 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 != NULL) {
+ MuxRelease(mux);
+ WebPSafeFree(mux);
+ }
+}
+
+//------------------------------------------------------------------------------
+// Helper method(s).
+
+// Handy MACRO, makes MuxSet() very symmetric to MuxGet().
+#define SWITCH_ID_LIST(INDEX, LIST) \
+ if (idx == (INDEX)) { \
+ err = ChunkAssignData(&chunk, data, copy_data, tag); \
+ if (err == WEBP_MUX_OK) { \
+ err = ChunkSetNth(&chunk, (LIST), nth); \
+ } \
+ return err; \
+ }
+
+static WebPMuxError MuxSet(WebPMux* const mux, uint32_t tag, uint32_t nth,
+ const WebPData* const data, int copy_data) {
+ WebPChunk chunk;
+ WebPMuxError err = WEBP_MUX_NOT_FOUND;
+ const CHUNK_INDEX idx = ChunkGetIndexFromTag(tag);
+ assert(mux != NULL);
+ assert(!IsWPI(kChunks[idx].id));
+
+ ChunkInit(&chunk);
+ SWITCH_ID_LIST(IDX_VP8X, &mux->vp8x_);
+ SWITCH_ID_LIST(IDX_ICCP, &mux->iccp_);
+ SWITCH_ID_LIST(IDX_ANIM, &mux->anim_);
+ SWITCH_ID_LIST(IDX_EXIF, &mux->exif_);
+ SWITCH_ID_LIST(IDX_XMP, &mux->xmp_);
+ SWITCH_ID_LIST(IDX_UNKNOWN, &mux->unknown_);
+ return err;
+}
+#undef SWITCH_ID_LIST
+
+// Create data for frame/fragment given image data, offsets and duration.
+static WebPMuxError CreateFrameFragmentData(
+ int width, int height, const WebPMuxFrameInfo* const info, int is_frame,
+ WebPData* const frame_frgm) {
+ uint8_t* frame_frgm_bytes;
+ const size_t frame_frgm_size = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].size;
+
+ 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_frgm_bytes = (uint8_t*)WebPSafeMalloc(1ULL, frame_frgm_size);
+ if (frame_frgm_bytes == NULL) return WEBP_MUX_MEMORY_ERROR;
+
+ PutLE24(frame_frgm_bytes + 0, info->x_offset / 2);
+ PutLE24(frame_frgm_bytes + 3, info->y_offset / 2);
+
+ if (is_frame) {
+ PutLE24(frame_frgm_bytes + 6, width - 1);
+ PutLE24(frame_frgm_bytes + 9, height - 1);
+ PutLE24(frame_frgm_bytes + 12, info->duration);
+ frame_frgm_bytes[15] =
+ (info->blend_method == WEBP_MUX_NO_BLEND ? 2 : 0) |
+ (info->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? 1 : 0);
+ }
+
+ frame_frgm->bytes = frame_frgm_bytes;
+ frame_frgm->size = frame_frgm_size;
+ return WEBP_MUX_OK;
+}
+
+// Outputs image data given a bitstream. The bitstream can either be a
+// single-image WebP file or raw VP8/VP8L data.
+// Also outputs 'is_lossless' to be true if the given bitstream is lossless.
+static WebPMuxError GetImageData(const WebPData* const bitstream,
+ WebPData* const image, WebPData* const alpha,
+ int* const is_lossless) {
+ WebPDataInit(alpha); // Default: no alpha.
+ if (bitstream->size < TAG_SIZE ||
+ memcmp(bitstream->bytes, "RIFF", TAG_SIZE)) {
+ // It is NOT webp file data. Return input data as is.
+ *image = *bitstream;
+ } else {
+ // It is webp file data. Extract image data from it.
+ const WebPMuxImage* wpi;
+ WebPMux* const mux = WebPMuxCreate(bitstream, 0);
+ if (mux == NULL) return WEBP_MUX_BAD_DATA;
+ wpi = mux->images_;
+ assert(wpi != NULL && wpi->img_ != NULL);
+ *image = wpi->img_->data_;
+ if (wpi->alpha_ != NULL) {
+ *alpha = wpi->alpha_->data_;
+ }
+ WebPMuxDelete(mux);
+ }
+ *is_lossless = VP8LCheckSignature(image->bytes, image->size);
+ return WEBP_MUX_OK;
+}
+
+static WebPMuxError DeleteChunks(WebPChunk** chunk_list, uint32_t tag) {
+ WebPMuxError err = WEBP_MUX_NOT_FOUND;
+ assert(chunk_list);
+ while (*chunk_list) {
+ WebPChunk* const chunk = *chunk_list;
+ if (chunk->tag_ == tag) {
+ *chunk_list = ChunkDelete(chunk);
+ err = WEBP_MUX_OK;
+ } else {
+ chunk_list = &chunk->next_;
+ }
+ }
+ return err;
+}
+
+static WebPMuxError MuxDeleteAllNamedData(WebPMux* const mux, uint32_t tag) {
+ const WebPChunkId id = ChunkGetIdFromTag(tag);
+ assert(mux != NULL);
+ if (IsWPI(id)) return WEBP_MUX_INVALID_ARGUMENT;
+ return DeleteChunks(MuxGetChunkListFromId(mux, id), tag);
+}
+
+//------------------------------------------------------------------------------
+// Set API(s).
+
+WebPMuxError WebPMuxSetChunk(WebPMux* mux, const char fourcc[4],
+ const WebPData* chunk_data, int copy_data) {
+ uint32_t tag;
+ WebPMuxError err;
+ 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);
+
+ // Delete existing chunk(s) with the same 'fourcc'.
+ err = MuxDeleteAllNamedData(mux, tag);
+ if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
+
+ // Add the given chunk.
+ return MuxSet(mux, tag, 1, chunk_data, copy_data);
+}
+
+// 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, data, copy_data, tag);
+ if (err != WEBP_MUX_OK) goto Err;
+ err = ChunkSetNth(&chunk, chunk_list, 1);
+ if (err != WEBP_MUX_OK) goto Err;
+ return WEBP_MUX_OK;
+ Err:
+ ChunkRelease(&chunk);
+ return err;
+}
+
+// Extracts image & alpha data from the given bitstream and then sets wpi.alpha_
+// and wpi.img_ appropriately.
+static WebPMuxError SetAlphaAndImageChunks(
+ const WebPData* const bitstream, int copy_data, WebPMuxImage* const wpi) {
+ int is_lossless = 0;
+ WebPData image, alpha;
+ WebPMuxError err = GetImageData(bitstream, &image, &alpha, &is_lossless);
+ const int image_tag =
+ is_lossless ? kChunks[IDX_VP8L].tag : kChunks[IDX_VP8].tag;
+ if (err != WEBP_MUX_OK) return err;
+ if (alpha.bytes != NULL) {
+ err = AddDataToChunkList(&alpha, copy_data, kChunks[IDX_ALPHA].tag,
+ &wpi->alpha_);
+ if (err != WEBP_MUX_OK) return err;
+ }
+ 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 WebPMuxSetImage(WebPMux* mux, const WebPData* bitstream,
+ int copy_data) {
+ WebPMuxImage wpi;
+ WebPMuxError err;
+
+ // Sanity checks.
+ if (mux == NULL || bitstream == NULL || bitstream->bytes == NULL ||
+ bitstream->size > MAX_CHUNK_PAYLOAD) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+
+ if (mux->images_ != NULL) {
+ // Only one 'simple image' can be added in mux. So, remove present images.
+ DeleteAllImages(&mux->images_);
+ }
+
+ MuxImageInit(&wpi);
+ err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);
+ if (err != WEBP_MUX_OK) goto Err;
+
+ // Add this WebPMuxImage to mux.
+ err = MuxImagePush(&wpi, &mux->images_);
+ if (err != WEBP_MUX_OK) goto Err;
+
+ // All is well.
+ return WEBP_MUX_OK;
+
+ Err: // Something bad happened.
+ MuxImageRelease(&wpi);
+ return err;
+}
+
+WebPMuxError WebPMuxPushFrame(WebPMux* mux, const WebPMuxFrameInfo* frame,
+ int copy_data) {
+ WebPMuxImage wpi;
+ WebPMuxError err;
+ int is_frame;
+ const WebPData* const bitstream = &frame->bitstream;
+
+ // Sanity checks.
+ if (mux == NULL || frame == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+ is_frame = (frame->id == WEBP_CHUNK_ANMF);
+ if (!(is_frame || (frame->id == WEBP_CHUNK_FRGM))) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+ if (frame->id == WEBP_CHUNK_FRGM) { // Dead experiment.
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+
+ if (bitstream->bytes == NULL || bitstream->size > MAX_CHUNK_PAYLOAD) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+
+ 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.
+ }
+ }
+
+ MuxImageInit(&wpi);
+ err = SetAlphaAndImageChunks(bitstream, copy_data, &wpi);
+ if (err != WEBP_MUX_OK) goto Err;
+ assert(wpi.img_ != NULL); // As SetAlphaAndImageChunks() was successful.
+
+ {
+ WebPData frame_frgm;
+ const uint32_t tag = kChunks[is_frame ? IDX_ANMF : IDX_FRGM].tag;
+ WebPMuxFrameInfo tmp = *frame;
+ tmp.x_offset &= ~1; // Snap offsets to even.
+ tmp.y_offset &= ~1;
+ if (!is_frame) { // Reset unused values.
+ tmp.duration = 1;
+ tmp.dispose_method = WEBP_MUX_DISPOSE_NONE;
+ tmp.blend_method = WEBP_MUX_BLEND;
+ }
+ if (tmp.x_offset < 0 || tmp.x_offset >= MAX_POSITION_OFFSET ||
+ tmp.y_offset < 0 || tmp.y_offset >= MAX_POSITION_OFFSET ||
+ (tmp.duration < 0 || tmp.duration >= MAX_DURATION) ||
+ tmp.dispose_method != (tmp.dispose_method & 1)) {
+ err = WEBP_MUX_INVALID_ARGUMENT;
+ goto Err;
+ }
+ err = CreateFrameFragmentData(wpi.width_, wpi.height_, &tmp, is_frame,
+ &frame_frgm);
+ if (err != WEBP_MUX_OK) goto Err;
+ // Add frame/fragment chunk (with copy_data = 1).
+ err = AddDataToChunkList(&frame_frgm, 1, tag, &wpi.header_);
+ WebPDataClear(&frame_frgm); // frame_frgm owned by wpi.header_ now.
+ if (err != WEBP_MUX_OK) goto Err;
+ }
+
+ // Add this WebPMuxImage to mux.
+ err = MuxImagePush(&wpi, &mux->images_);
+ if (err != WEBP_MUX_OK) goto Err;
+
+ // All is well.
+ return WEBP_MUX_OK;
+
+ Err: // Something bad happened.
+ MuxImageRelease(&wpi);
+ return err;
+}
+
+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 || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+ if (params->loop_count < 0 || params->loop_count >= MAX_LOOP_COUNT) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+
+ // Delete any existing ANIM chunk(s).
+ err = MuxDeleteAllNamedData(mux, kChunks[IDX_ANIM].tag);
+ if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
+
+ // 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 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;
+
+ mux->canvas_width_ = width;
+ mux->canvas_height_ = height;
+ return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// Delete API(s).
+
+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) {
+ if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+ return MuxImageDeleteNth(&mux->images_, nth);
+}
+
+//------------------------------------------------------------------------------
+// Assembly of the WebP RIFF file.
+
+static WebPMuxError GetFrameFragmentInfo(
+ const WebPChunk* const frame_frgm_chunk,
+ int* const x_offset, int* const y_offset, int* const duration) {
+ const uint32_t tag = frame_frgm_chunk->tag_;
+ const int is_frame = (tag == kChunks[IDX_ANMF].tag);
+ const WebPData* const data = &frame_frgm_chunk->data_;
+ const size_t expected_data_size =
+ is_frame ? ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE;
+ assert(frame_frgm_chunk != NULL);
+ assert(tag == kChunks[IDX_ANMF].tag || tag == kChunks[IDX_FRGM].tag);
+ if (data->size != expected_data_size) return WEBP_MUX_INVALID_ARGUMENT;
+
+ *x_offset = 2 * GetLE24(data->bytes + 0);
+ *y_offset = 2 * GetLE24(data->bytes + 3);
+ if (is_frame) *duration = GetLE24(data->bytes + 12);
+ return WEBP_MUX_OK;
+}
+
+static WebPMuxError GetImageInfo(const WebPMuxImage* const wpi,
+ int* const x_offset, int* const y_offset,
+ int* const duration,
+ int* const width, int* const height) {
+ const WebPChunk* const frame_frgm_chunk = wpi->header_;
+ WebPMuxError err;
+ assert(wpi != NULL);
+ assert(frame_frgm_chunk != NULL);
+
+ // 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.
+ if (width != NULL) *width = wpi->width_;
+ if (height != NULL) *height = wpi->height_;
+ return WEBP_MUX_OK;
+}
+
+// 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);
+
+ wpi = mux->images_;
+ assert(wpi != NULL);
+ assert(wpi->img_ != NULL);
+
+ if (wpi->next_ != NULL) {
+ int max_x = 0;
+ int max_y = 0;
+ int64_t image_area = 0;
+ // if we have a chain of wpi's, header_ is necessarily set
+ assert(wpi->header_ != NULL);
+ // Aggregate the bounding box for animation frames & fragmented images.
+ for (; wpi != NULL; wpi = wpi->next_) {
+ 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;
+ const int max_y_pos = y_offset + h;
+ if (err != WEBP_MUX_OK) return err;
+ assert(x_offset < MAX_POSITION_OFFSET);
+ assert(y_offset < MAX_POSITION_OFFSET);
+
+ if (max_x_pos > max_x) max_x = max_x_pos;
+ if (max_y_pos > max_y) max_y = max_y_pos;
+ image_area += w * h;
+ }
+ *width = max_x;
+ *height = max_y;
+ // Crude check to validate that there are no image overlaps/holes for
+ // 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, canvas dimensions are same as image dimensions.
+ *width = wpi->width_;
+ *height = wpi->height_;
+ }
+ return WEBP_MUX_OK;
+}
+
+// VP8X format:
+// Total Size : 10,
+// Flags : 4 bytes,
+// Width : 3 bytes,
+// Height : 3 bytes.
+static WebPMuxError CreateVP8XChunk(WebPMux* const mux) {
+ WebPMuxError err = WEBP_MUX_OK;
+ uint32_t flags = 0;
+ int width = 0;
+ int height = 0;
+ uint8_t data[VP8X_CHUNK_SIZE];
+ const 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) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+
+ // If VP8X chunk(s) is(are) already present, remove them (and later add new
+ // VP8X chunk with updated flags).
+ err = MuxDeleteAllNamedData(mux, kChunks[IDX_VP8X].tag);
+ if (err != WEBP_MUX_OK && err != WEBP_MUX_NOT_FOUND) return err;
+
+ // Set flags.
+ if (mux->iccp_ != NULL && mux->iccp_->data_.bytes != NULL) {
+ flags |= ICCP_FLAG;
+ }
+ if (mux->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_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.
+ }
+
+ err = GetAdjustedCanvasSize(mux, flags, &width, &height);
+ if (err != WEBP_MUX_OK) return err;
+
+ if (width <= 0 || height <= 0) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+ if (width > MAX_CANVAS_SIZE || height > MAX_CANVAS_SIZE) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+
+ if (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 && mux->unknown_ == NULL) {
+ // For simple file format, 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;
+ }
+
+ PutLE32(data + 0, flags); // VP8X chunk flags.
+ PutLE24(data + 4, width - 1); // canvas width.
+ PutLE24(data + 7, height - 1); // canvas height.
+
+ return MuxSet(mux, kChunks[IDX_VP8X].tag, 1, &vp8x, 1);
+}
+
+// Cleans up 'mux' by removing any unnecessary chunks.
+static WebPMuxError MuxCleanup(WebPMux* const mux) {
+ int num_frames;
+ int num_fragments;
+ int num_anim_chunks;
+
+ // If we have an image with 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;
+ WebPMuxError err;
+
+ 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));
+
+ if (mux == NULL) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+
+ // 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 = 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*)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->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) {
+ WebPSafeFree(data);
+ data = NULL;
+ size = 0;
+ }
+
+ // Finalize data.
+ assembled_data->bytes = data;
+ assembled_data->size = size;
+
+ return err;
+}
+
+//------------------------------------------------------------------------------
diff --git a/thirdparty/libwebp/mux/muxi.h b/thirdparty/libwebp/mux/muxi.h
new file mode 100644
index 0000000000..d4d5cbad91
--- /dev/null
+++ b/thirdparty/libwebp/mux/muxi.h
@@ -0,0 +1,232 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Internal header for mux library.
+//
+// Author: Urvang (urvang@google.com)
+
+#ifndef WEBP_MUX_MUXI_H_
+#define WEBP_MUX_MUXI_H_
+
+#include <stdlib.h>
+#include "../dec/vp8i.h"
+#include "../dec/vp8li.h"
+#include "../webp/mux.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//------------------------------------------------------------------------------
+// Defines and constants.
+
+#define MUX_MAJ_VERSION 0
+#define MUX_MIN_VERSION 3
+#define MUX_REV_VERSION 1
+
+// Chunk object.
+typedef struct WebPChunk WebPChunk;
+struct WebPChunk {
+ uint32_t tag_;
+ int owner_; // True if *data_ memory is owned internally.
+ // VP8X, ANIM, and other internally created chunks
+ // like ANMF/FRGM are always owned.
+ WebPData data_;
+ WebPChunk* next_;
+};
+
+// 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_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_;
+};
+
+// Main mux object. Stores data chunks.
+struct WebPMux {
+ WebPMuxImage* images_;
+ WebPChunk* iccp_;
+ WebPChunk* exif_;
+ WebPChunk* xmp_;
+ WebPChunk* anim_;
+ WebPChunk* vp8x_;
+
+ WebPChunk* unknown_;
+ int canvas_width_;
+ int canvas_height_;
+};
+
+// CHUNK_INDEX enum: used for indexing within 'kChunks' (defined below) only.
+// Note: the reason for having two enums ('WebPChunkId' and 'CHUNK_INDEX') is to
+// allow two different chunks to have the same id (e.g. WebPChunkId
+// 'WEBP_CHUNK_IMAGE' can correspond to CHUNK_INDEX 'IDX_VP8' or 'IDX_VP8L').
+typedef enum {
+ IDX_VP8X = 0,
+ IDX_ICCP,
+ IDX_ANIM,
+ IDX_ANMF,
+ IDX_FRGM,
+ IDX_ALPHA,
+ IDX_VP8,
+ IDX_VP8L,
+ IDX_EXIF,
+ IDX_XMP,
+ IDX_UNKNOWN,
+
+ IDX_NIL,
+ IDX_LAST_CHUNK
+} CHUNK_INDEX;
+
+#define NIL_TAG 0x00000000u // To signal void chunk.
+
+typedef struct {
+ uint32_t tag;
+ WebPChunkId id;
+ uint32_t size;
+} ChunkInfo;
+
+extern const ChunkInfo kChunks[IDX_LAST_CHUNK];
+
+//------------------------------------------------------------------------------
+// Chunk object management.
+
+// Initialize.
+void ChunkInit(WebPChunk* const chunk);
+
+// 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_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);
+
+// Fill the chunk with the given data.
+WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,
+ int copy_data, uint32_t tag);
+
+// Sets 'chunk' at nth position in the 'chunk_list'.
+// nth = 0 has the special meaning "last of the list".
+// 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_.
+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;
+ assert(data_size < MAX_CHUNK_PAYLOAD);
+ return SizeWithPadding(data_size);
+}
+
+// Total size of a list of chunks.
+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);
+
+//------------------------------------------------------------------------------
+// MuxImage object management.
+
+// Initialize.
+void MuxImageInit(WebPMuxImage* const wpi);
+
+// Releases image 'wpi' and returns wpi->next.
+WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi);
+
+// Delete image 'wpi' and return the next image in the list or NULL.
+// 'wpi' can be NULL.
+WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi);
+
+// Count number of images matching the given tag id in the 'wpi_list'.
+// If id == WEBP_CHUNK_NIL, all images will be matched.
+int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id);
+
+// Update width/height/has_alpha info from chunks within wpi.
+// Also remove ALPH chunk if not needed.
+int MuxImageFinalize(WebPMuxImage* const wpi);
+
+// Check if given ID corresponds to an image related chunk.
+static WEBP_INLINE int IsWPI(WebPChunkId id) {
+ switch (id) {
+ case WEBP_CHUNK_ANMF:
+ case WEBP_CHUNK_FRGM:
+ case WEBP_CHUNK_ALPHA:
+ case WEBP_CHUNK_IMAGE: return 1;
+ default: return 0;
+ }
+}
+
+// Pushes 'wpi' at the end of 'wpi_list'.
+WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list);
+
+// Delete nth image in the image list.
+WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth);
+
+// Get nth image in the image list.
+WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth,
+ WebPMuxImage** wpi);
+
+// Total size of the given image.
+size_t MuxImageDiskSize(const WebPMuxImage* const wpi);
+
+// Write out the given image into 'dst'.
+uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst);
+
+//------------------------------------------------------------------------------
+// Helper methods for mux.
+
+// Checks if the given image list contains at least one image with alpha.
+int MuxHasAlpha(const WebPMuxImage* images);
+
+// 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.
+WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id);
+
+// Validates the given mux object.
+WebPMuxError MuxValidate(const WebPMux* const mux);
+
+//------------------------------------------------------------------------------
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif /* WEBP_MUX_MUXI_H_ */
diff --git a/thirdparty/libwebp/mux/muxinternal.c b/thirdparty/libwebp/mux/muxinternal.c
new file mode 100644
index 0000000000..4babbe82fc
--- /dev/null
+++ b/thirdparty/libwebp/mux/muxinternal.c
@@ -0,0 +1,551 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Internal objects and utils for mux.
+//
+// Authors: Urvang (urvang@google.com)
+// Vikas (vikasa@google.com)
+
+#include <assert.h>
+#include "./muxi.h"
+#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('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('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 }
+};
+
+//------------------------------------------------------------------------------
+
+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) {
+ assert(chunk);
+ memset(chunk, 0, sizeof(*chunk));
+ chunk->tag_ = NIL_TAG;
+}
+
+WebPChunk* ChunkRelease(WebPChunk* const chunk) {
+ WebPChunk* next;
+ if (chunk == NULL) return NULL;
+ if (chunk->owner_) {
+ WebPDataClear(&chunk->data_);
+ }
+ next = chunk->next_;
+ ChunkInit(chunk);
+ return next;
+}
+
+//------------------------------------------------------------------------------
+// Chunk misc methods.
+
+CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag) {
+ int i;
+ for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {
+ if (tag == kChunks[i].tag) return (CHUNK_INDEX)i;
+ }
+ return IDX_UNKNOWN;
+}
+
+WebPChunkId ChunkGetIdFromTag(uint32_t tag) {
+ int i;
+ for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {
+ if (tag == kChunks[i].tag) return kChunks[i].id;
+ }
+ return WEBP_CHUNK_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);
+}
+
+//------------------------------------------------------------------------------
+// Chunk search methods.
+
+// Returns next chunk in the chunk list with the given tag.
+static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) {
+ while (chunk != NULL && chunk->tag_ != tag) {
+ chunk = chunk->next_;
+ }
+ return chunk;
+}
+
+WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) {
+ uint32_t iter = nth;
+ first = ChunkSearchNextInList(first, tag);
+ if (first == NULL) return NULL;
+
+ while (--iter != 0) {
+ WebPChunk* next_chunk = ChunkSearchNextInList(first->next_, tag);
+ if (next_chunk == NULL) break;
+ first = next_chunk;
+ }
+ return ((nth > 0) && (iter > 0)) ? NULL : first;
+}
+
+// Outputs a pointer to 'prev_chunk->next_',
+// where 'prev_chunk' is the pointer to the chunk at position (nth - 1).
+// Returns 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 != NULL);
+ *location = chunk_list;
+
+ while (*chunk_list != NULL) {
+ WebPChunk* const cur_chunk = *chunk_list;
+ ++count;
+ if (count == nth) return 1; // Found.
+ chunk_list = &cur_chunk->next_;
+ *location = chunk_list;
+ }
+
+ // *chunk_list is ok to be NULL if adding at last location.
+ return (nth == 0 || (count == nth - 1)) ? 1 : 0;
+}
+
+//------------------------------------------------------------------------------
+// Chunk writer methods.
+
+WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,
+ int copy_data, uint32_t tag) {
+ // For internally allocated chunks, always copy data & make it owner of data.
+ if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].tag) {
+ copy_data = 1;
+ }
+
+ ChunkRelease(chunk);
+
+ if (data != NULL) {
+ if (copy_data) { // Copy data.
+ if (!WebPDataCopy(data, &chunk->data_)) return WEBP_MUX_MEMORY_ERROR;
+ chunk->owner_ = 1; // Chunk is owner of data.
+ } else { // Don't copy data.
+ chunk->data_ = *data;
+ }
+ }
+ chunk->tag_ = tag;
+ return WEBP_MUX_OK;
+}
+
+WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list,
+ uint32_t nth) {
+ WebPChunk* new_chunk;
+
+ if (!ChunkSearchListToSet(chunk_list, nth, &chunk_list)) {
+ return WEBP_MUX_NOT_FOUND;
+ }
+
+ new_chunk = (WebPChunk*)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;
+}
+
+//------------------------------------------------------------------------------
+// Chunk deletion method(s).
+
+WebPChunk* ChunkDelete(WebPChunk* const chunk) {
+ WebPChunk* const next = ChunkRelease(chunk);
+ WebPSafeFree(chunk);
+ return next;
+}
+
+void ChunkListDelete(WebPChunk** const chunk_list) {
+ while (*chunk_list != NULL) {
+ *chunk_list = ChunkDelete(*chunk_list);
+ }
+}
+
+//------------------------------------------------------------------------------
+// Chunk serialization methods.
+
+static uint8_t* ChunkEmit(const WebPChunk* const chunk, uint8_t* dst) {
+ const size_t chunk_size = chunk->data_.size;
+ assert(chunk);
+ assert(chunk->tag_ != NIL_TAG);
+ PutLE32(dst + 0, chunk->tag_);
+ PutLE32(dst + TAG_SIZE, (uint32_t)chunk_size);
+ assert(chunk_size == (uint32_t)chunk_size);
+ memcpy(dst + CHUNK_HEADER_SIZE, chunk->data_.bytes, chunk_size);
+ if (chunk_size & 1)
+ dst[CHUNK_HEADER_SIZE + chunk_size] = 0; // Add padding.
+ return dst + ChunkDiskSize(chunk);
+}
+
+uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) {
+ while (chunk_list != NULL) {
+ dst = ChunkEmit(chunk_list, dst);
+ chunk_list = chunk_list->next_;
+ }
+ return dst;
+}
+
+size_t ChunkListDiskSize(const WebPChunk* chunk_list) {
+ size_t size = 0;
+ while (chunk_list != NULL) {
+ size += ChunkDiskSize(chunk_list);
+ chunk_list = chunk_list->next_;
+ }
+ return size;
+}
+
+//------------------------------------------------------------------------------
+// Life of a MuxImage object.
+
+void MuxImageInit(WebPMuxImage* const wpi) {
+ assert(wpi);
+ memset(wpi, 0, sizeof(*wpi));
+}
+
+WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) {
+ WebPMuxImage* next;
+ if (wpi == NULL) return NULL;
+ ChunkDelete(wpi->header_);
+ ChunkDelete(wpi->alpha_);
+ ChunkDelete(wpi->img_);
+ ChunkListDelete(&wpi->unknown_);
+
+ next = wpi->next_;
+ MuxImageInit(wpi);
+ return next;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage search methods.
+
+// Get a reference to appropriate chunk list within an image given chunk tag.
+static WebPChunk** GetChunkListFromId(const WebPMuxImage* const wpi,
+ WebPChunkId id) {
+ assert(wpi != NULL);
+ switch (id) {
+ case WEBP_CHUNK_ANMF:
+ case WEBP_CHUNK_FRGM: return (WebPChunk**)&wpi->header_;
+ case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha_;
+ case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img_;
+ default: return NULL;
+ }
+}
+
+int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) {
+ int count = 0;
+ const WebPMuxImage* current;
+ for (current = wpi_list; current != NULL; current = current->next_) {
+ if (id == WEBP_CHUNK_NIL) {
+ ++count; // Special case: count all images.
+ } else {
+ const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id);
+ if (wpi_chunk != NULL) {
+ const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_);
+ if (wpi_chunk_id == id) ++count; // Count images with a matching 'id'.
+ }
+ }
+ }
+ return count;
+}
+
+// Outputs a pointer to 'prev_wpi->next_',
+// where 'prev_wpi' is the pointer to the image at position (nth - 1).
+// Returns true if nth image was found.
+static int SearchImageToGetOrDelete(WebPMuxImage** wpi_list, uint32_t nth,
+ WebPMuxImage*** const location) {
+ uint32_t count = 0;
+ assert(wpi_list);
+ *location = wpi_list;
+
+ if (nth == 0) {
+ nth = MuxImageCount(*wpi_list, WEBP_CHUNK_NIL);
+ if (nth == 0) return 0; // Not found.
+ }
+
+ while (*wpi_list != NULL) {
+ WebPMuxImage* const cur_wpi = *wpi_list;
+ ++count;
+ if (count == nth) return 1; // Found.
+ wpi_list = &cur_wpi->next_;
+ *location = wpi_list;
+ }
+ return 0; // Not found.
+}
+
+//------------------------------------------------------------------------------
+// MuxImage writer methods.
+
+WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) {
+ WebPMuxImage* new_wpi;
+
+ while (*wpi_list != NULL) {
+ WebPMuxImage* const cur_wpi = *wpi_list;
+ if (cur_wpi->next_ == NULL) break;
+ wpi_list = &cur_wpi->next_;
+ }
+
+ new_wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*new_wpi));
+ if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR;
+ *new_wpi = *wpi;
+ new_wpi->next_ = NULL;
+
+ if (*wpi_list != NULL) {
+ (*wpi_list)->next_ = new_wpi;
+ } else {
+ *wpi_list = new_wpi;
+ }
+ return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage deletion methods.
+
+WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) {
+ // Delete the components of wpi. If wpi is NULL this is a noop.
+ WebPMuxImage* const next = MuxImageRelease(wpi);
+ WebPSafeFree(wpi);
+ return next;
+}
+
+WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) {
+ assert(wpi_list);
+ if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) {
+ return WEBP_MUX_NOT_FOUND;
+ }
+ *wpi_list = MuxImageDelete(*wpi_list);
+ return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage reader methods.
+
+WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth,
+ WebPMuxImage** wpi) {
+ assert(wpi_list);
+ assert(wpi);
+ if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth,
+ (WebPMuxImage***)&wpi_list)) {
+ return WEBP_MUX_NOT_FOUND;
+ }
+ *wpi = (WebPMuxImage*)*wpi_list;
+ return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------
+// MuxImage serialization methods.
+
+// Size of an image.
+size_t MuxImageDiskSize(const WebPMuxImage* const wpi) {
+ size_t size = 0;
+ if (wpi->header_ != NULL) size += ChunkDiskSize(wpi->header_);
+ if (wpi->alpha_ != NULL) size += ChunkDiskSize(wpi->alpha_);
+ if (wpi->img_ != NULL) size += ChunkDiskSize(wpi->img_);
+ if (wpi->unknown_ != NULL) size += ChunkListDiskSize(wpi->unknown_);
+ return size;
+}
+
+// Special case as ANMF/FRGM chunk encapsulates other image chunks.
+static uint8_t* ChunkEmitSpecial(const WebPChunk* const header,
+ size_t total_size, uint8_t* dst) {
+ const size_t header_size = header->data_.size;
+ const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE;
+ assert(header->tag_ == kChunks[IDX_ANMF].tag ||
+ header->tag_ == kChunks[IDX_FRGM].tag);
+ PutLE32(dst + 0, header->tag_);
+ PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next);
+ assert(header_size == (uint32_t)header_size);
+ memcpy(dst + CHUNK_HEADER_SIZE, header->data_.bytes, header_size);
+ if (header_size & 1) {
+ dst[CHUNK_HEADER_SIZE + header_size] = 0; // Add padding.
+ }
+ 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. ANMF/FRGM chunk (if present).
+ // 2. ALPH chunk (if present).
+ // 3. VP8/VP8L chunk.
+ assert(wpi);
+ 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);
+ if (wpi->unknown_ != NULL) dst = ChunkListEmit(wpi->unknown_, dst);
+ return dst;
+}
+
+//------------------------------------------------------------------------------
+// Helper methods for mux.
+
+int MuxHasAlpha(const WebPMuxImage* images) {
+ while (images != NULL) {
+ if (images->has_alpha_) return 1;
+ images = images->next_;
+ }
+ return 0;
+}
+
+uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) {
+ PutLE32(data + 0, MKFOURCC('R', 'I', 'F', 'F'));
+ PutLE32(data + TAG_SIZE, (uint32_t)size - CHUNK_HEADER_SIZE);
+ assert(size == (uint32_t)size);
+ PutLE32(data + TAG_SIZE + CHUNK_SIZE_BYTES, MKFOURCC('W', 'E', 'B', 'P'));
+ return data + RIFF_HEADER_SIZE;
+}
+
+WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) {
+ assert(mux != NULL);
+ switch (id) {
+ case WEBP_CHUNK_VP8X: return (WebPChunk**)&mux->vp8x_;
+ case WEBP_CHUNK_ICCP: return (WebPChunk**)&mux->iccp_;
+ case WEBP_CHUNK_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_;
+ }
+}
+
+static int IsNotCompatible(int feature, int num_items) {
+ return (feature != 0) != (num_items > 0);
+}
+
+#define NO_FLAG 0
+
+// Test basic constraints:
+// retrieval, maximum number of chunks by index (use -1 to skip)
+// and feature incompatibility (use NO_FLAG to skip).
+// On success returns WEBP_MUX_OK and stores the chunk count in *num.
+static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx,
+ WebPFeatureFlags feature,
+ uint32_t vp8x_flags,
+ int max, int* num) {
+ const WebPMuxError err =
+ WebPMuxNumChunks(mux, kChunks[idx].id, num);
+ if (err != WEBP_MUX_OK) return err;
+ if (max > -1 && *num > max) return WEBP_MUX_INVALID_ARGUMENT;
+ if (feature != NO_FLAG && IsNotCompatible(vp8x_flags & feature, *num)) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+ return WEBP_MUX_OK;
+}
+
+WebPMuxError MuxValidate(const WebPMux* const mux) {
+ int num_iccp;
+ int num_exif;
+ int num_xmp;
+ int num_anim;
+ int num_frames;
+ int num_fragments;
+ int num_vp8x;
+ int num_images;
+ int num_alpha;
+ uint32_t flags;
+ WebPMuxError err;
+
+ // Verify mux is not NULL.
+ if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+ // Verify mux has at least one image.
+ if (mux->images_ == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+ err = WebPMuxGetFeatures(mux, &flags);
+ if (err != WEBP_MUX_OK) return err;
+
+ // At most one color profile chunk.
+ err = ValidateChunk(mux, IDX_ICCP, ICCP_FLAG, flags, 1, &num_iccp);
+ if (err != WEBP_MUX_OK) return err;
+
+ // At most one EXIF metadata.
+ err = ValidateChunk(mux, IDX_EXIF, EXIF_FLAG, flags, 1, &num_exif);
+ if (err != WEBP_MUX_OK) return err;
+
+ // At most one XMP metadata.
+ err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp);
+ if (err != WEBP_MUX_OK) return err;
+
+ // Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent.
+ // At most one ANIM chunk.
+ err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim);
+ if (err != WEBP_MUX_OK) return err;
+ 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_anim == 0 || num_frames == 0)) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+ if (!has_animation && (num_anim == 1 || num_frames > 0)) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+ }
+
+ // Fragmentation: FRAGMENTS_FLAG and FRGM chunk(s) are consistent.
+ err = ValidateChunk(mux, IDX_FRGM, FRAGMENTS_FLAG, flags, -1, &num_fragments);
+ if (err != WEBP_MUX_OK) return err;
+
+ // Verify either VP8X chunk is present OR there is only one elem in
+ // mux->images_.
+ err = ValidateChunk(mux, IDX_VP8X, NO_FLAG, flags, 1, &num_vp8x);
+ if (err != WEBP_MUX_OK) return err;
+ err = ValidateChunk(mux, IDX_VP8, NO_FLAG, flags, -1, &num_images);
+ if (err != WEBP_MUX_OK) return err;
+ if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT;
+
+ // ALPHA_FLAG & alpha chunk(s) are consistent.
+ if (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_fragments & num_images are consistent.
+ if (num_fragments > 0 && num_images != num_fragments) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+
+ return WEBP_MUX_OK;
+}
+
+#undef NO_FLAG
+
+//------------------------------------------------------------------------------
+
diff --git a/thirdparty/libwebp/mux/muxread.c b/thirdparty/libwebp/mux/muxread.c
new file mode 100644
index 0000000000..8957a1e46e
--- /dev/null
+++ b/thirdparty/libwebp/mux/muxread.c
@@ -0,0 +1,544 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Read APIs for mux.
+//
+// Authors: Urvang (urvang@google.com)
+// Vikas (vikasa@google.com)
+
+#include <assert.h>
+#include "./muxi.h"
+#include "../utils/utils.h"
+
+//------------------------------------------------------------------------------
+// Helper method(s).
+
+// Handy MACRO.
+#define SWITCH_ID_LIST(INDEX, LIST) \
+ if (idx == (INDEX)) { \
+ const WebPChunk* const chunk = ChunkSearchList((LIST), nth, \
+ kChunks[(INDEX)].tag); \
+ if (chunk) { \
+ *data = chunk->data_; \
+ return WEBP_MUX_OK; \
+ } else { \
+ return WEBP_MUX_NOT_FOUND; \
+ } \
+ }
+
+static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,
+ uint32_t nth, WebPData* const data) {
+ assert(mux != NULL);
+ assert(!IsWPI(kChunks[idx].id));
+ WebPDataInit(data);
+
+ SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_);
+ SWITCH_ID_LIST(IDX_ICCP, mux->iccp_);
+ SWITCH_ID_LIST(IDX_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;
+}
+#undef SWITCH_ID_LIST
+
+// Fill the chunk with the given data (includes chunk header bytes), after some
+// verifications.
+static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,
+ const uint8_t* data, size_t data_size,
+ size_t riff_size, int copy_data) {
+ uint32_t chunk_size;
+ WebPData chunk_data;
+
+ // Sanity checks.
+ if (data_size < CHUNK_HEADER_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA;
+ chunk_size = GetLE32(data + TAG_SIZE);
+
+ {
+ const size_t chunk_disk_size = SizeWithPadding(chunk_size);
+ if (chunk_disk_size > riff_size) return WEBP_MUX_BAD_DATA;
+ if (chunk_disk_size > data_size) return WEBP_MUX_NOT_ENOUGH_DATA;
+ }
+
+ // Data assignment.
+ chunk_data.bytes = data + CHUNK_HEADER_SIZE;
+ chunk_data.size = chunk_size;
+ return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
+}
+
+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.
+
+WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
+ int version) {
+ size_t riff_size;
+ uint32_t tag;
+ const uint8_t* end;
+ WebPMux* mux = NULL;
+ WebPMuxImage* wpi = NULL;
+ const uint8_t* data;
+ size_t size;
+ WebPChunk chunk;
+ ChunkInit(&chunk);
+
+ // Sanity checks.
+ if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {
+ return NULL; // version mismatch
+ }
+ if (bitstream == NULL) return NULL;
+
+ data = bitstream->bytes;
+ size = bitstream->size;
+
+ if (data == NULL) return NULL;
+ if (size < RIFF_HEADER_SIZE) return NULL;
+ if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') ||
+ GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) {
+ return NULL;
+ }
+
+ mux = WebPMuxNew();
+ if (mux == NULL) return NULL;
+
+ if (size < RIFF_HEADER_SIZE + TAG_SIZE) goto Err;
+
+ tag = GetLE32(data + RIFF_HEADER_SIZE);
+ if (tag != kChunks[IDX_VP8].tag &&
+ tag != kChunks[IDX_VP8L].tag &&
+ tag != kChunks[IDX_VP8X].tag) {
+ goto Err; // First chunk should be VP8, VP8L or VP8X.
+ }
+
+ riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE));
+ if (riff_size > MAX_CHUNK_PAYLOAD || riff_size > size) {
+ goto Err;
+ } else {
+ if (riff_size < size) { // Redundant data after last chunk.
+ size = riff_size; // To make sure we don't read any data beyond mux_size.
+ }
+ }
+
+ end = data + size;
+ data += RIFF_HEADER_SIZE;
+ size -= RIFF_HEADER_SIZE;
+
+ wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*wpi));
+ if (wpi == NULL) goto Err;
+ MuxImageInit(wpi);
+
+ // Loop over chunks.
+ while (data != end) {
+ size_t data_size;
+ WebPChunkId id;
+ WebPChunk** chunk_list;
+ if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
+ copy_data) != WEBP_MUX_OK) {
+ goto Err;
+ }
+ data_size = ChunkDiskSize(&chunk);
+ id = ChunkGetIdFromTag(chunk.tag_);
+ switch (id) {
+ case WEBP_CHUNK_ALPHA:
+ if (wpi->alpha_ != NULL) goto Err; // Consecutive ALPH chunks.
+ if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;
+ wpi->is_partial_ = 1; // Waiting for a VP8 chunk.
+ break;
+ case WEBP_CHUNK_IMAGE:
+ if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;
+ if (!MuxImageFinalize(wpi)) goto Err;
+ wpi->is_partial_ = 0; // wpi is completely filled.
+ PushImage:
+ // Add this to mux->images_ list.
+ if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
+ MuxImageInit(wpi); // Reset for reading next image.
+ break;
+ case WEBP_CHUNK_ANMF:
+ 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);
+ }
+
+ // Validate mux if complete.
+ if (MuxValidate(mux) != WEBP_MUX_OK) goto Err;
+
+ MuxImageDelete(wpi);
+ return mux; // All OK;
+
+ Err: // Something bad happened.
+ ChunkRelease(&chunk);
+ MuxImageDelete(wpi);
+ WebPMuxDelete(mux);
+ return NULL;
+}
+
+//------------------------------------------------------------------------------
+// Get API(s).
+
+// Validates that the given mux has a single image.
+static WebPMuxError ValidateForSingleImage(const WebPMux* const mux) {
+ const int num_images = MuxImageCount(mux->images_, WEBP_CHUNK_IMAGE);
+ const int num_frames = MuxImageCount(mux->images_, WEBP_CHUNK_ANMF);
+ const int num_fragments = MuxImageCount(mux->images_, WEBP_CHUNK_FRGM);
+
+ if (num_images == 0) {
+ // No images in mux.
+ return WEBP_MUX_NOT_FOUND;
+ } else if (num_images == 1 && num_frames == 0 && num_fragments == 0) {
+ // Valid case (single image).
+ return WEBP_MUX_OK;
+ } else {
+ // Frame/Fragment case OR an invalid mux.
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+}
+
+// Get the canvas width, height and flags after validating that VP8X/VP8/VP8L
+// chunk and canvas size are valid.
+static WebPMuxError MuxGetCanvasInfo(const WebPMux* const mux,
+ int* width, int* height, uint32_t* flags) {
+ int w, h;
+ uint32_t f = 0;
+ WebPData data;
+ assert(mux != NULL);
+
+ // Check if VP8X chunk is present.
+ if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) {
+ if (data.size < VP8X_CHUNK_SIZE) return WEBP_MUX_BAD_DATA;
+ f = GetLE32(data.bytes + 0);
+ w = GetLE24(data.bytes + 4) + 1;
+ h = GetLE24(data.bytes + 7) + 1;
+ } else {
+ 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 (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;
+ assert(width >= 1 && height >= 1);
+ assert(width <= MAX_CANVAS_SIZE && height <= MAX_CANVAS_SIZE);
+ assert(width * (uint64_t)height < MAX_IMAGE_AREA);
+ PutLE32(dst, MKFOURCC('V', 'P', '8', 'X'));
+ PutLE32(dst + TAG_SIZE, VP8X_CHUNK_SIZE);
+ PutLE32(dst + CHUNK_HEADER_SIZE, flags);
+ PutLE24(dst + CHUNK_HEADER_SIZE + 4, width - 1);
+ PutLE24(dst + CHUNK_HEADER_SIZE + 7, height - 1);
+ return dst + vp8x_size;
+}
+
+// Assemble a single image WebP bitstream from 'wpi'.
+static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi,
+ WebPData* const bitstream) {
+ uint8_t* dst;
+
+ // Allocate data.
+ const int need_vp8x = (wpi->alpha_ != NULL);
+ const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0;
+ const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha_) : 0;
+ // Note: No need to output 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*)WebPSafeMalloc(1ULL, size);
+ if (data == NULL) return WEBP_MUX_MEMORY_ERROR;
+
+ // Main RIFF header.
+ dst = MuxEmitRiffHeader(data, size);
+
+ if (need_vp8x) {
+ dst = EmitVP8XChunk(dst, wpi->width_, wpi->height_, ALPHA_FLAG); // VP8X.
+ dst = ChunkListEmit(wpi->alpha_, dst); // ALPH.
+ }
+
+ // Bitstream.
+ dst = ChunkListEmit(wpi->img_, dst);
+ assert(dst == data + size);
+
+ // Output.
+ bitstream->bytes = data;
+ bitstream->size = size;
+ return WEBP_MUX_OK;
+}
+
+WebPMuxError WebPMuxGetChunk(const WebPMux* mux, const char fourcc[4],
+ WebPData* chunk_data) {
+ CHUNK_INDEX idx;
+ if (mux == NULL || fourcc == NULL || chunk_data == NULL) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+ idx = ChunkGetIndexFromFourCC(fourcc);
+ if (IsWPI(kChunks[idx].id)) { // An image chunk.
+ 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;
+ }
+}
+
+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);
+}
+
+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);
+}
+
+WebPMuxError WebPMuxGetFrame(
+ const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame) {
+ WebPMuxError err;
+ WebPMuxImage* wpi;
+
+ // Sanity checks.
+ if (mux == NULL || frame == NULL) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+
+ // Get the nth WebPMuxImage.
+ err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, &wpi);
+ if (err != WEBP_MUX_OK) return err;
+
+ // Get frame info.
+ if (wpi->header_ == NULL) {
+ return MuxGetImageInternal(wpi, frame);
+ } else {
+ return MuxGetFrameFragmentInternal(wpi, frame);
+ }
+}
+
+WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux,
+ WebPMuxAnimParams* params) {
+ WebPData anim;
+ WebPMuxError err;
+
+ if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
+
+ 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);
+
+ 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 (CHUNK_INDEX)i;
+ }
+ return IDX_NIL;
+}
+
+// Count number of chunks matching 'tag' in the 'chunk_list'.
+// If tag == NIL_TAG, any tag will be matched.
+static int CountChunks(const WebPChunk* const chunk_list, uint32_t tag) {
+ int count = 0;
+ const WebPChunk* current;
+ for (current = chunk_list; current != NULL; current = current->next_) {
+ if (tag == NIL_TAG || current->tag_ == tag) {
+ count++; // Count chunks whose tags match.
+ }
+ }
+ return count;
+}
+
+WebPMuxError WebPMuxNumChunks(const WebPMux* mux,
+ WebPChunkId id, int* num_elements) {
+ if (mux == NULL || num_elements == NULL) {
+ return WEBP_MUX_INVALID_ARGUMENT;
+ }
+
+ if (IsWPI(id)) {
+ *num_elements = MuxImageCount(mux->images_, id);
+ } else {
+ WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id);
+ const CHUNK_INDEX idx = ChunkGetIndexFromId(id);
+ *num_elements = CountChunks(*chunk_list, kChunks[idx].tag);
+ }
+
+ return WEBP_MUX_OK;
+}
+
+//------------------------------------------------------------------------------