mirror of
https://github.com/webmproject/libwebp.git
synced 2025-12-23 21:46:26 +01:00
Merge "Set best frame candidate at each encode in animenc" into main
This commit is contained in:
@@ -848,13 +848,76 @@ enum {
|
|||||||
#define MIN_COLORS_LOSSY 31 // Don't try lossy below this threshold.
|
#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_COLORS_LOSSLESS 194 // Don't try lossless above this threshold.
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 candidate,
|
||||||
|
WebPMuxAnimDispose dispose_method,
|
||||||
|
int is_key_frame,
|
||||||
|
Candidate** const best_candidate,
|
||||||
|
EncodedFrame* const encoded_frame) {
|
||||||
|
if (*best_candidate == NULL ||
|
||||||
|
candidate->mem.size < (*best_candidate)->mem.size) {
|
||||||
|
WebPMuxFrameInfo* const dst =
|
||||||
|
is_key_frame ? &encoded_frame->key_frame : &encoded_frame->sub_frame;
|
||||||
|
*dst = candidate->info;
|
||||||
|
GetEncodedData(&candidate->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.
|
||||||
|
SetPreviousDisposeMethod(enc, dispose_method);
|
||||||
|
}
|
||||||
|
enc->prev_rect = candidate->rect; // save for next frame.
|
||||||
|
|
||||||
|
// Release the memory of the previous best candidate if any.
|
||||||
|
if (*best_candidate != NULL) {
|
||||||
|
WebPMemoryWriterClear(&(*best_candidate)->mem);
|
||||||
|
(*best_candidate)->evaluate = 0;
|
||||||
|
}
|
||||||
|
*best_candidate = candidate;
|
||||||
|
} else {
|
||||||
|
// Release the memory of the current candidate which is not the best one.
|
||||||
|
WebPMemoryWriterClear(&candidate->mem);
|
||||||
|
candidate->evaluate = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generates candidates for a given dispose method given pre-filled sub-frame
|
// Generates candidates for a given dispose method given pre-filled sub-frame
|
||||||
// 'params'.
|
// 'params'.
|
||||||
static WebPEncodingError GenerateCandidates(
|
static WebPEncodingError GenerateCandidates(
|
||||||
WebPAnimEncoder* const enc, Candidate candidates[CANDIDATE_COUNT],
|
WebPAnimEncoder* const enc, Candidate candidates[CANDIDATE_COUNT],
|
||||||
WebPMuxAnimDispose dispose_method, int is_lossless, int is_key_frame,
|
WebPMuxAnimDispose dispose_method, int is_lossless, int is_key_frame,
|
||||||
SubFrameParams* const params,
|
SubFrameParams* const params,
|
||||||
const WebPConfig* const config_ll, const WebPConfig* const config_lossy) {
|
const WebPConfig* const config_ll, const WebPConfig* const config_lossy,
|
||||||
|
Candidate** const best_candidate, EncodedFrame* const encoded_frame) {
|
||||||
WebPEncodingError error_code = VP8_ENC_OK;
|
WebPEncodingError error_code = VP8_ENC_OK;
|
||||||
const int is_dispose_none = (dispose_method == WEBP_MUX_DISPOSE_NONE);
|
const int is_dispose_none = (dispose_method == WEBP_MUX_DISPOSE_NONE);
|
||||||
Candidate* const candidate_ll =
|
Candidate* const candidate_ll =
|
||||||
@@ -900,19 +963,22 @@ static WebPEncodingError GenerateCandidates(
|
|||||||
error_code = EncodeCandidate(¶ms->sub_frame_ll, ¶ms->rect_ll,
|
error_code = EncodeCandidate(¶ms->sub_frame_ll, ¶ms->rect_ll,
|
||||||
config_ll, use_blending_ll, candidate_ll);
|
config_ll, use_blending_ll, candidate_ll);
|
||||||
if (error_code != VP8_ENC_OK) return error_code;
|
if (error_code != VP8_ENC_OK) return error_code;
|
||||||
|
PickBestCandidate(enc, candidate_ll, dispose_method, is_key_frame,
|
||||||
|
best_candidate, encoded_frame);
|
||||||
}
|
}
|
||||||
if (evaluate_lossy) {
|
if (evaluate_lossy) {
|
||||||
CopyCurrentCanvas(enc);
|
CopyCurrentCanvas(enc);
|
||||||
if (use_blending_lossy) {
|
if (use_blending_lossy) {
|
||||||
enc->curr_canvas_copy_modified =
|
enc->curr_canvas_copy_modified = FlattenSimilarBlocks(
|
||||||
FlattenSimilarBlocks(prev_canvas, ¶ms->rect_lossy, curr_canvas,
|
prev_canvas, ¶ms->rect_lossy, curr_canvas, config_lossy->quality);
|
||||||
config_lossy->quality);
|
|
||||||
}
|
}
|
||||||
error_code =
|
error_code =
|
||||||
EncodeCandidate(¶ms->sub_frame_lossy, ¶ms->rect_lossy,
|
EncodeCandidate(¶ms->sub_frame_lossy, ¶ms->rect_lossy,
|
||||||
config_lossy, use_blending_lossy, candidate_lossy);
|
config_lossy, use_blending_lossy, candidate_lossy);
|
||||||
if (error_code != VP8_ENC_OK) return error_code;
|
if (error_code != VP8_ENC_OK) return error_code;
|
||||||
enc->curr_canvas_copy_modified = 1;
|
enc->curr_canvas_copy_modified = 1;
|
||||||
|
PickBestCandidate(enc, candidate_lossy, dispose_method, is_key_frame,
|
||||||
|
best_candidate, encoded_frame);
|
||||||
}
|
}
|
||||||
return error_code;
|
return error_code;
|
||||||
}
|
}
|
||||||
@@ -920,31 +986,6 @@ static WebPEncodingError GenerateCandidates(
|
|||||||
#undef MIN_COLORS_LOSSY
|
#undef MIN_COLORS_LOSSY
|
||||||
#undef MAX_COLORS_LOSSLESS
|
#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) {
|
static int IncreasePreviousDuration(WebPAnimEncoder* const enc, int duration) {
|
||||||
const size_t position = enc->count - 1;
|
const size_t position = enc->count - 1;
|
||||||
EncodedFrame* const prev_enc_frame = GetFrame(enc, position);
|
EncodedFrame* const prev_enc_frame = GetFrame(enc, position);
|
||||||
@@ -1007,53 +1048,6 @@ static int IncreasePreviousDuration(WebPAnimEncoder* const enc, int duration) {
|
|||||||
return 1;
|
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
|
// Depending on the configuration, tries different compressions
|
||||||
// (lossy/lossless), dispose methods, blending methods etc to encode the current
|
// (lossy/lossless), dispose methods, blending methods etc to encode the current
|
||||||
// frame and outputs the best one in 'encoded_frame'.
|
// frame and outputs the best one in 'encoded_frame'.
|
||||||
@@ -1068,6 +1062,7 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
|
|||||||
const WebPPicture* const curr_canvas = &enc->curr_canvas_copy;
|
const WebPPicture* const curr_canvas = &enc->curr_canvas_copy;
|
||||||
const WebPPicture* const prev_canvas = &enc->prev_canvas;
|
const WebPPicture* const prev_canvas = &enc->prev_canvas;
|
||||||
Candidate candidates[CANDIDATE_COUNT];
|
Candidate candidates[CANDIDATE_COUNT];
|
||||||
|
Candidate* best_candidate = NULL;
|
||||||
const int is_lossless = config->lossless;
|
const int is_lossless = config->lossless;
|
||||||
const int consider_lossless = is_lossless || enc->options.allow_mixed;
|
const int consider_lossless = is_lossless || enc->options.allow_mixed;
|
||||||
const int consider_lossy = !is_lossless || enc->options.allow_mixed;
|
const int consider_lossy = !is_lossless || enc->options.allow_mixed;
|
||||||
@@ -1157,9 +1152,10 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dispose_none_params.should_try) {
|
if (dispose_none_params.should_try) {
|
||||||
error_code = GenerateCandidates(
|
error_code =
|
||||||
enc, candidates, WEBP_MUX_DISPOSE_NONE, is_lossless, is_key_frame,
|
GenerateCandidates(enc, candidates, WEBP_MUX_DISPOSE_NONE, is_lossless,
|
||||||
&dispose_none_params, &config_ll, &config_lossy);
|
is_key_frame, &dispose_none_params, &config_ll,
|
||||||
|
&config_lossy, &best_candidate, encoded_frame);
|
||||||
if (error_code != VP8_ENC_OK) goto Err;
|
if (error_code != VP8_ENC_OK) goto Err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1168,12 +1164,12 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
|
|||||||
assert(dispose_bg_possible);
|
assert(dispose_bg_possible);
|
||||||
error_code = GenerateCandidates(
|
error_code = GenerateCandidates(
|
||||||
enc, candidates, WEBP_MUX_DISPOSE_BACKGROUND, is_lossless, is_key_frame,
|
enc, candidates, WEBP_MUX_DISPOSE_BACKGROUND, is_lossless, is_key_frame,
|
||||||
&dispose_bg_params, &config_ll, &config_lossy);
|
&dispose_bg_params, &config_ll, &config_lossy, &best_candidate,
|
||||||
|
encoded_frame);
|
||||||
if (error_code != VP8_ENC_OK) goto Err;
|
if (error_code != VP8_ENC_OK) goto Err;
|
||||||
}
|
}
|
||||||
|
|
||||||
PickBestCandidate(enc, candidates, is_key_frame, encoded_frame);
|
assert(best_candidate != NULL);
|
||||||
|
|
||||||
goto End;
|
goto End;
|
||||||
|
|
||||||
Err:
|
Err:
|
||||||
|
|||||||
Reference in New Issue
Block a user