summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/libwebp/src/mux/anim_encode.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/libwebp/src/mux/anim_encode.c')
-rw-r--r--src/3rdparty/libwebp/src/mux/anim_encode.c414
1 files changed, 272 insertions, 142 deletions
diff --git a/src/3rdparty/libwebp/src/mux/anim_encode.c b/src/3rdparty/libwebp/src/mux/anim_encode.c
index fa86eaa..53e2906 100644
--- a/src/3rdparty/libwebp/src/mux/anim_encode.c
+++ b/src/3rdparty/libwebp/src/mux/anim_encode.c
@@ -12,7 +12,9 @@
#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"
@@ -49,8 +51,10 @@ struct WebPAnimEncoder {
FrameRect prev_rect_; // Previous WebP frame rectangle.
WebPConfig last_config_; // Cached in case a re-encode is needed.
- WebPConfig last_config2_; // 2nd cached config; only valid if
- // 'options_.allow_mixed' is true.
+ 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.
@@ -173,6 +177,7 @@ static void DefaultEncoderOptions(WebPAnimEncoderOptions* const enc_options) {
enc_options->minimize_size = 0;
DisableKeyframes(enc_options);
enc_options->allow_mixed = 0;
+ enc_options->verbose = 0;
}
int WebPAnimEncoderOptionsInitInternal(WebPAnimEncoderOptions* enc_options,
@@ -185,7 +190,8 @@ int WebPAnimEncoderOptionsInitInternal(WebPAnimEncoderOptions* enc_options,
return 1;
}
-#define TRANSPARENT_COLOR 0x00ffffff
+// 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) {
@@ -338,11 +344,16 @@ static EncodedFrame* GetFrame(const WebPAnimEncoder* const enc,
return &enc->encoded_frames_[enc->start_ + position];
}
-// Returns true if 'length' number of pixels in 'src' and 'dst' are identical,
+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.
-static WEBP_INLINE int ComparePixels(const uint32_t* src, int src_step,
- const uint32_t* dst, int dst_step,
- int length) {
+// '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) {
@@ -354,15 +365,62 @@ static WEBP_INLINE int ComparePixels(const uint32_t* src, int src_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) {
+ 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);
@@ -374,8 +432,8 @@ static void MinimizeChangeRectangle(const WebPPicture* const src,
&src->argb[rect->y_offset_ * src->argb_stride + i];
const uint32_t* const dst_argb =
&dst->argb[rect->y_offset_ * dst->argb_stride + i];
- if (ComparePixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
- rect->height_)) {
+ 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 {
@@ -390,8 +448,8 @@ static void MinimizeChangeRectangle(const WebPPicture* const src,
&src->argb[rect->y_offset_ * src->argb_stride + i];
const uint32_t* const dst_argb =
&dst->argb[rect->y_offset_ * dst->argb_stride + i];
- if (ComparePixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
- rect->height_)) {
+ if (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
+ rect->height_, max_allowed_diff)) {
--rect->width_; // Redundant column.
} else {
break;
@@ -405,7 +463,8 @@ static void MinimizeChangeRectangle(const WebPPicture* const src,
&src->argb[j * src->argb_stride + rect->x_offset_];
const uint32_t* const dst_argb =
&dst->argb[j * dst->argb_stride + rect->x_offset_];
- if (ComparePixels(src_argb, 1, dst_argb, 1, rect->width_)) {
+ if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
+ max_allowed_diff)) {
--rect->height_; // Redundant row.
++rect->y_offset_;
} else {
@@ -420,7 +479,8 @@ static void MinimizeChangeRectangle(const WebPPicture* const src,
&src->argb[j * src->argb_stride + rect->x_offset_];
const uint32_t* const dst_argb =
&dst->argb[j * dst->argb_stride + rect->x_offset_];
- if (ComparePixels(src_argb, 1, dst_argb, 1, rect->width_)) {
+ if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
+ max_allowed_diff)) {
--rect->height_; // Redundant row.
} else {
break;
@@ -445,20 +505,46 @@ static WEBP_INLINE void SnapToEvenOffsets(FrameRect* const rect) {
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. The initial guess for 'rect' will be the full canvas.
+// 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,
- FrameRect* const rect, WebPPicture* const sub_frame) {
- rect->x_offset_ = 0;
- rect->y_offset_ = 0;
- rect->width_ = curr_canvas->width;
- rect->height_ = curr_canvas->height;
+ 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);
+ MinimizeChangeRectangle(prev_canvas, curr_canvas, rect,
+ is_lossless, quality);
}
if (IsEmptyRect(rect)) {
@@ -477,6 +563,29 @@ static int GetSubRect(const WebPPicture* const prev_canvas,
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) {
@@ -490,9 +599,9 @@ static uint32_t RectArea(const FrameRect* const rect) {
return (uint32_t)rect->width_ * rect->height_;
}
-static int IsBlendingPossible(const WebPPicture* const src,
- const WebPPicture* const dst,
- const FrameRect* const rect) {
+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);
@@ -512,88 +621,66 @@ static int IsBlendingPossible(const WebPPicture* const src,
return 1;
}
-#define MIN_COLORS_LOSSY 31 // Don't try lossy below this threshold.
-#define MAX_COLORS_LOSSLESS 194 // Don't try lossless above this threshold.
-#define MAX_COLOR_COUNT 256 // Power of 2 greater than MAX_COLORS_LOSSLESS.
-#define HASH_SIZE (MAX_COLOR_COUNT * 4)
-#define HASH_RIGHT_SHIFT 22 // 32 - log2(HASH_SIZE).
-
-// TODO(urvang): Also used in enc/vp8l.c. Move to utils.
-// If the number of colors in the 'pic' is at least MAX_COLOR_COUNT, return
-// MAX_COLOR_COUNT. Otherwise, return the exact number of colors in the 'pic'.
-static int GetColorCount(const WebPPicture* const pic) {
- int x, y;
- int num_colors = 0;
- uint8_t in_use[HASH_SIZE] = { 0 };
- uint32_t colors[HASH_SIZE];
- static const uint32_t kHashMul = 0x1e35a7bd;
- const uint32_t* argb = pic->argb;
- const int width = pic->width;
- const int height = pic->height;
- uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0]
-
- for (y = 0; y < height; ++y) {
- for (x = 0; x < width; ++x) {
- int key;
- if (argb[x] == last_pix) {
- continue;
- }
- last_pix = argb[x];
- key = (kHashMul * last_pix) >> HASH_RIGHT_SHIFT;
- while (1) {
- if (!in_use[key]) {
- colors[key] = last_pix;
- in_use[key] = 1;
- ++num_colors;
- if (num_colors >= MAX_COLOR_COUNT) {
- return MAX_COLOR_COUNT; // Exact count not needed.
- }
- break;
- } else if (colors[key] == last_pix) {
- break; // The color is already there.
- } else {
- // Some other color sits here, so do linear conflict resolution.
- ++key;
- key &= (HASH_SIZE - 1); // Key mask.
- }
+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;
}
}
- argb += pic->argb_stride;
}
- return num_colors;
+ return 1;
}
-#undef MAX_COLOR_COUNT
-#undef HASH_SIZE
-#undef HASH_RIGHT_SHIFT
-
// For pixels in 'rect', replace those pixels in 'dst' that are same as 'src' by
// transparent pixels.
-static void IncreaseTransparency(const WebPPicture* const src,
- const FrameRect* const rect,
- WebPPicture* const dst) {
+// 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]) {
+ 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.
-static void FlattenSimilarBlocks(const WebPPicture* const src,
- const FrameRect* const rect,
- WebPPicture* const dst) {
+// 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);
@@ -615,11 +702,12 @@ static void FlattenSimilarBlocks(const WebPPicture* const src,
const uint32_t src_pixel = psrc[x + y * src->argb_stride];
const int alpha = src_pixel >> 24;
if (alpha == 0xff &&
- src_pixel == pdst[x + y * dst->argb_stride]) {
- ++cnt;
- avg_r += (src_pixel >> 16) & 0xff;
- avg_g += (src_pixel >> 8) & 0xff;
- avg_b += (src_pixel >> 0) & 0xff;
+ 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;
}
}
}
@@ -635,9 +723,11 @@ static void FlattenSimilarBlocks(const WebPPicture* const src,
pdst[x + y * dst->argb_stride] = color;
}
}
+ modified = 1;
}
}
}
+ return modified;
}
static int EncodeFrame(const WebPConfig* const config, WebPPicture* const pic,
@@ -662,9 +752,10 @@ typedef struct {
// Generates a candidate encoded frame given a picture and metadata.
static WebPEncodingError EncodeCandidate(WebPPicture* const sub_frame,
const FrameRect* const rect,
- const WebPConfig* const config,
+ 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));
@@ -682,7 +773,13 @@ static WebPEncodingError EncodeCandidate(WebPPicture* const sub_frame,
// Encode picture.
WebPMemoryWriterInit(&candidate->mem_);
- if (!EncodeFrame(config, sub_frame, &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;
}
@@ -698,6 +795,8 @@ static WebPEncodingError EncodeCandidate(WebPPicture* const sub_frame,
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;
}
}
@@ -710,12 +809,15 @@ enum {
CANDIDATE_COUNT
};
-// Generates candidates for a given dispose method given pre-filled 'rect'
-// and 'sub_frame'.
+#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,
- const FrameRect* const rect, WebPPicture* sub_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);
@@ -727,16 +829,24 @@ static WebPEncodingError GenerateCandidates(
WebPPicture* const curr_canvas = &enc->curr_canvas_copy_;
const WebPPicture* const prev_canvas =
is_dispose_none ? &enc->prev_canvas_ : &enc->prev_canvas_disposed_;
- const int use_blending =
+ 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 &&
- IsBlendingPossible(prev_canvas, curr_canvas, rect);
+ 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 = GetColorCount(sub_frame);
+ 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);
}
@@ -744,23 +854,26 @@ static WebPEncodingError GenerateCandidates(
// Generate candidates.
if (candidate_ll->evaluate_) {
CopyCurrentCanvas(enc);
- if (use_blending) {
- IncreaseTransparency(prev_canvas, rect, curr_canvas);
- enc->curr_canvas_copy_modified_ = 1;
+ if (use_blending_ll) {
+ enc->curr_canvas_copy_modified_ =
+ IncreaseTransparency(prev_canvas, &params->rect_ll_, curr_canvas);
}
- error_code = EncodeCandidate(sub_frame, rect, config_ll, use_blending,
- candidate_ll);
+ 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) {
- FlattenSimilarBlocks(prev_canvas, rect, curr_canvas);
- enc->curr_canvas_copy_modified_ = 1;
+ if (use_blending_lossy) {
+ enc->curr_canvas_copy_modified_ =
+ FlattenSimilarBlocks(prev_canvas, &params->rect_lossy_, curr_canvas,
+ config_lossy->quality);
}
- error_code = EncodeCandidate(sub_frame, rect, config_lossy, use_blending,
- candidate_lossy);
+ 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;
}
@@ -918,13 +1031,16 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
const int is_lossless = config->lossless;
const int is_first_frame = enc->is_first_frame_;
- int try_dispose_none = 1; // Default.
- FrameRect rect_none;
- WebPPicture sub_frame_none;
// First frame cannot be skipped as there is no 'previous frame' to merge it
// to. So, empty rectangle is not allowed for the first frame.
const int empty_rect_allowed_none = !is_first_frame;
+ // 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
@@ -933,19 +1049,20 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
// background.
const int dispose_bg_possible =
!is_key_frame && !enc->prev_candidate_undecided_;
- int try_dispose_bg = 0; // Default.
- FrameRect rect_bg;
- WebPPicture sub_frame_bg;
+
+ 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_config2_ = config->lossless ? config_lossy : config_ll;
+ enc->last_config_reversed_ = config->lossless ? config_lossy : config_ll;
*frame_skipped = 0;
- if (!WebPPictureInit(&sub_frame_none) || !WebPPictureInit(&sub_frame_bg)) {
+ 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;
}
@@ -954,10 +1071,14 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
}
// Change-rectangle assuming previous frame was DISPOSE_NONE.
- GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
- empty_rect_allowed_none, &rect_none, &sub_frame_none);
+ if (!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 (IsEmptyRect(&rect_none)) {
+ 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);
@@ -971,36 +1092,43 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
WebPCopyPixels(prev_canvas, prev_canvas_disposed);
DisposeFrameRectangle(WEBP_MUX_DISPOSE_BACKGROUND, &enc->prev_rect_,
prev_canvas_disposed);
- // Even if there is exact pixel match between 'disposed previous canvas' and
- // 'current canvas', we can't skip current frame, as there may not be exact
- // pixel match between 'previous canvas' and 'current canvas'. So, we don't
- // allow empty rectangle in this case.
- GetSubRect(prev_canvas_disposed, curr_canvas, is_key_frame, is_first_frame,
- 0 /* empty_rect_allowed */, &rect_bg, &sub_frame_bg);
- assert(!IsEmptyRect(&rect_bg));
+
+ if (!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.
- try_dispose_bg = 1;
- try_dispose_none = 1;
- } else if (RectArea(&rect_bg) < RectArea(&rect_none)) {
- try_dispose_bg = 1; // Pick DISPOSE_BACKGROUND.
- try_dispose_none = 0;
+ 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 (try_dispose_none) {
+ if (dispose_none_params.should_try_) {
error_code = GenerateCandidates(
enc, candidates, WEBP_MUX_DISPOSE_NONE, is_lossless, is_key_frame,
- &rect_none, &sub_frame_none, &config_ll, &config_lossy);
+ &dispose_none_params, &config_ll, &config_lossy);
if (error_code != VP8_ENC_OK) goto Err;
}
- if (try_dispose_bg) {
+ 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,
- &rect_bg, &sub_frame_bg, &config_ll, &config_lossy);
+ &dispose_bg_params, &config_ll, &config_lossy);
if (error_code != VP8_ENC_OK) goto Err;
}
@@ -1016,8 +1144,8 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
}
End:
- WebPPictureFree(&sub_frame_none);
- WebPPictureFree(&sub_frame_bg);
+ SubFrameParamsFree(&dispose_none_params);
+ SubFrameParamsFree(&dispose_bg_params);
return error_code;
}
@@ -1163,6 +1291,7 @@ static int FlushFrames(WebPAnimEncoder* const enc) {
int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int timestamp,
const WebPConfig* encoder_config) {
WebPConfig config;
+ int ok;
if (enc == NULL) {
return 0;
@@ -1212,6 +1341,10 @@ int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int timestamp,
}
if (encoder_config != NULL) {
+ if (!WebPValidateConfig(encoder_config)) {
+ MarkError(enc, "ERROR adding frame: Invalid WebPConfig");
+ return 0;
+ }
config = *encoder_config;
} else {
WebPConfigInit(&config);
@@ -1222,17 +1355,14 @@ int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int timestamp,
assert(enc->curr_canvas_copy_modified_ == 1);
CopyCurrentCanvas(enc);
- if (!CacheFrame(enc, &config)) {
- return 0;
- }
+ ok = CacheFrame(enc, &config) && FlushFrames(enc);
- if (!FlushFrames(enc)) {
- return 0;
- }
enc->curr_canvas_ = NULL;
enc->curr_canvas_copy_modified_ = 1;
- enc->prev_timestamp_ = timestamp;
- return 1;
+ if (ok) {
+ enc->prev_timestamp_ = timestamp;
+ }
+ return ok;
}
// -----------------------------------------------------------------------------
@@ -1278,7 +1408,7 @@ static int FrameToFullCanvas(WebPAnimEncoder* const enc,
GetEncodedData(&mem1, full_image);
if (enc->options_.allow_mixed) {
- if (!EncodeFrame(&enc->last_config_, canvas_buf, &mem2)) goto Err;
+ if (!EncodeFrame(&enc->last_config_reversed_, canvas_buf, &mem2)) goto Err;
if (mem2.size < mem1.size) {
GetEncodedData(&mem2, full_image);
WebPMemoryWriterClear(&mem1);