diff options
Diffstat (limited to 'drivers/webp/mux/anim_encode.c')
-rw-r--r-- | drivers/webp/mux/anim_encode.c | 414 |
1 files changed, 272 insertions, 142 deletions
diff --git a/drivers/webp/mux/anim_encode.c b/drivers/webp/mux/anim_encode.c index bb7c0f50b9..2226febf13 100644 --- a/drivers/webp/mux/anim_encode.c +++ b/drivers/webp/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(¶ms->sub_frame_ll_) || + !WebPPictureInit(¶ms->sub_frame_lossy_)) { + return 0; + } + return 1; +} + +static void SubFrameParamsFree(SubFrameParams* const params) { + WebPPictureFree(¶ms->sub_frame_ll_); + WebPPictureFree(¶ms->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, + ¶ms->rect_ll_, ¶ms->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, + ¶ms->rect_lossy_, ¶ms->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, ¶ms->rect_ll_); + use_blending_lossy = !is_key_frame && - IsBlendingPossible(prev_canvas, curr_canvas, rect); + IsLossyBlendingPossible(prev_canvas, curr_canvas, ¶ms->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(¶ms->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, ¶ms->rect_ll_, curr_canvas); } - error_code = EncodeCandidate(sub_frame, rect, config_ll, use_blending, - candidate_ll); + error_code = EncodeCandidate(¶ms->sub_frame_ll_, ¶ms->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, ¶ms->rect_lossy_, curr_canvas, + config_lossy->quality); } - error_code = EncodeCandidate(sub_frame, rect, config_lossy, use_blending, - candidate_lossy); + error_code = + EncodeCandidate(¶ms->sub_frame_lossy_, ¶ms->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); |