mirror of
https://github.com/webmproject/libwebp.git
synced 2024-12-26 13:48:21 +01:00
anim_encoder: drop a frame if it has same pixels as the prev frame.
Earlier, we stored a 1x1 frame for such frames. Now, we drop every such frame and increase the duration of its previous frame instead. Also, modify the anim_diff tool to handle animated images that are equivalent, but have different number of frames. Change-Id: I2688b1771e1f5f9f6a78e48ec81b01c3cd495403
This commit is contained in:
parent
df9f6ec829
commit
27933e2a8e
@ -13,6 +13,7 @@
|
||||
//
|
||||
// example: anim_diff foo.gif bar.webp
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // for 'strtod'.
|
||||
#include <string.h> // for 'strcmp'.
|
||||
@ -24,6 +25,31 @@
|
||||
|
||||
namespace {
|
||||
|
||||
// Return true if 'a + b' will overflow.
|
||||
bool AdditionWillOverflow(int a, int b) {
|
||||
return (b > 0) && (a > INT_MAX - b);
|
||||
}
|
||||
|
||||
// Minimize number of frames by combining successive frames that have exact same
|
||||
// ARGB data into a single longer duration frame.
|
||||
void MinimizeAnimationFrames(AnimatedImage* const img) {
|
||||
for (size_t i = 1; i < img->frames.size(); ++i) {
|
||||
DecodedFrame* const frame1 = &img->frames[i - 1];
|
||||
DecodedFrame* const frame2 = &img->frames[i];
|
||||
// If merging frames will result in integer overflow for 'duration',
|
||||
// skip merging.
|
||||
if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue;
|
||||
const uint8_t* rgba1 = frame1->rgba.data();
|
||||
const uint8_t* rgba2 = frame2->rgba.data();
|
||||
if (!memcmp(rgba1, rgba2, img->canvas_width * 4 * img->canvas_height)) {
|
||||
// Merge 'i+1'th frame into 'i'th frame.
|
||||
frame1->duration += frame2->duration;
|
||||
img->frames.erase(img->frames.begin() + i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool CompareValues(T a, T b, const std::string& output_str) {
|
||||
if (a != b) {
|
||||
@ -163,6 +189,7 @@ int main(int argc, const char* argv[]) {
|
||||
fprintf(stderr, "Error decoding file: %s\n Aborting.\n", files[i]);
|
||||
return -2;
|
||||
}
|
||||
MinimizeAnimationFrames(&images[i]);
|
||||
}
|
||||
|
||||
if (!CompareAnimatedImagePair(images[0], images[1],
|
||||
|
@ -79,7 +79,10 @@ struct WebPAnimEncoder {
|
||||
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 frame_count_; // Number of frames added to mux so far.
|
||||
|
||||
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.
|
||||
};
|
||||
@ -325,6 +328,10 @@ static WEBP_INLINE int ComparePixels(const uint32_t* src, int src_step,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int IsEmptyRect(const FrameRect* const rect) {
|
||||
return (rect->width_ == 0) || (rect->height_ == 0);
|
||||
}
|
||||
|
||||
// Assumes that an initial valid guess of change rectangle 'rect' is passed.
|
||||
static void MinimizeChangeRectangle(const WebPPicture* const src,
|
||||
const WebPPicture* const dst,
|
||||
@ -349,7 +356,7 @@ static void MinimizeChangeRectangle(const WebPPicture* const src,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rect->width_ == 0) goto End;
|
||||
if (rect->width_ == 0) goto NO_CHANGE;
|
||||
|
||||
// Right boundary.
|
||||
for (i = rect->x_offset_ + rect->width_ - 1; i >= rect->x_offset_; --i) {
|
||||
@ -364,7 +371,7 @@ static void MinimizeChangeRectangle(const WebPPicture* const src,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rect->width_ == 0) goto End;
|
||||
if (rect->width_ == 0) goto NO_CHANGE;
|
||||
|
||||
// Top boundary.
|
||||
for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
|
||||
@ -379,7 +386,7 @@ static void MinimizeChangeRectangle(const WebPPicture* const src,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rect->height_ == 0) goto End;
|
||||
if (rect->height_ == 0) goto NO_CHANGE;
|
||||
|
||||
// Bottom boundary.
|
||||
for (j = rect->y_offset_ + rect->height_ - 1; j >= rect->y_offset_; --j) {
|
||||
@ -393,17 +400,14 @@ static void MinimizeChangeRectangle(const WebPPicture* const src,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rect->height_ == 0) goto End;
|
||||
if (rect->height_ == 0) goto NO_CHANGE;
|
||||
|
||||
if (rect->width_ == 0 || rect->height_ == 0) {
|
||||
End:
|
||||
// TODO(later): This rare case can happen for a bad GIF. In such a case, the
|
||||
// frame should not be encoded at all and the duration of prev frame should
|
||||
// be increased instead. For now, we just create a 1x1 frame at zero offset.
|
||||
if (IsEmptyRect(rect)) {
|
||||
NO_CHANGE:
|
||||
rect->x_offset_ = 0;
|
||||
rect->y_offset_ = 0;
|
||||
rect->width_ = 1;
|
||||
rect->height_ = 1;
|
||||
rect->width_ = 0;
|
||||
rect->height_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -419,8 +423,8 @@ static WEBP_INLINE void SnapToEvenOffsets(FrameRect* const rect) {
|
||||
// current frame. The initial guess for 'rect' will be the full canvas.
|
||||
static int GetSubRect(const WebPPicture* const prev_canvas,
|
||||
const WebPPicture* const curr_canvas, int is_key_frame,
|
||||
int is_first_frame, FrameRect* const rect,
|
||||
WebPPicture* const sub_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;
|
||||
@ -430,8 +434,19 @@ static int GetSubRect(const WebPPicture* const prev_canvas,
|
||||
// initialized to a fully transparent canvas in the beginning.
|
||||
MinimizeChangeRectangle(prev_canvas, curr_canvas, rect);
|
||||
}
|
||||
SnapToEvenOffsets(rect);
|
||||
|
||||
if (IsEmptyRect(rect)) {
|
||||
if (empty_rect_allowed) { // No need to get 'sub_frame'.
|
||||
return 1;
|
||||
} else { // Force a 1x1 rectangle.
|
||||
rect->width_ = 1;
|
||||
rect->height_ = 1;
|
||||
assert(rect->x_offset_ == 0);
|
||||
assert(rect->y_offset_ == 0);
|
||||
}
|
||||
}
|
||||
|
||||
SnapToEvenOffsets(rect);
|
||||
return WebPPictureView(curr_canvas, rect->x_offset_, rect->y_offset_,
|
||||
rect->width_, rect->height_, sub_frame);
|
||||
}
|
||||
@ -772,13 +787,65 @@ static void SetPreviousDisposeMethod(WebPAnimEncoder* const enc,
|
||||
}
|
||||
}
|
||||
|
||||
// Sets duration of the previous frame to be 'duration'.
|
||||
static void SetPreviousDuration(WebPAnimEncoder* const enc, int duration) {
|
||||
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);
|
||||
prev_enc_frame->sub_frame_.duration = duration;
|
||||
prev_enc_frame->key_frame_.duration = duration;
|
||||
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
|
||||
@ -831,10 +898,12 @@ static void PickBestCandidate(WebPAnimEncoder* const enc,
|
||||
// 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) {
|
||||
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_;
|
||||
@ -846,6 +915,9 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
|
||||
int try_dispose_none = 1; // Default.
|
||||
FrameRect rect_none;
|
||||
WebPPicture sub_frame_none;
|
||||
// First frame cannot be skipped as there is no 'previous frame' to merge it
|
||||
// to. So, empty rectangle is not allowed for the first frame.
|
||||
const int empty_rect_allowed_none = !is_first_frame;
|
||||
|
||||
// If current frame is a key-frame, dispose method of previous frame doesn't
|
||||
// matter, so we don't try dispose to background.
|
||||
@ -865,6 +937,7 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
|
||||
config_lossy.lossless = 0;
|
||||
enc->last_config_ = *config;
|
||||
enc->last_config2_ = config->lossless ? config_lossy : config_ll;
|
||||
*frame_skipped = 0;
|
||||
|
||||
if (!WebPPictureInit(&sub_frame_none) || !WebPPictureInit(&sub_frame_bg)) {
|
||||
return VP8_ENC_ERROR_INVALID_CONFIGURATION;
|
||||
@ -876,7 +949,15 @@ 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,
|
||||
&rect_none, &sub_frame_none);
|
||||
empty_rect_allowed_none, &rect_none, &sub_frame_none);
|
||||
|
||||
if (IsEmptyRect(&rect_none)) {
|
||||
// Don't encode the frame at all. Instead, the duration of the previous
|
||||
// frame will be increased later.
|
||||
assert(empty_rect_allowed_none);
|
||||
*frame_skipped = 1;
|
||||
goto End;
|
||||
}
|
||||
|
||||
if (dispose_bg_possible) {
|
||||
// Change-rectangle assuming previous frame was DISPOSE_BACKGROUND.
|
||||
@ -884,8 +965,13 @@ static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
|
||||
CopyPixels(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,
|
||||
&rect_bg, &sub_frame_bg);
|
||||
0 /* empty_rect_allowed */, &rect_bg, &sub_frame_bg);
|
||||
assert(!IsEmptyRect(&rect_bg));
|
||||
|
||||
if (enc->options_.minimize_size) { // Try both dispose methods.
|
||||
try_dispose_bg = 1;
|
||||
@ -939,6 +1025,7 @@ static int64_t KeyFramePenalty(const EncodedFrame* const encoded_frame) {
|
||||
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);
|
||||
@ -946,11 +1033,9 @@ static int CacheFrame(WebPAnimEncoder* const enc,
|
||||
++enc->count_;
|
||||
|
||||
if (enc->is_first_frame_) { // Add this as a key-frame.
|
||||
error_code =
|
||||
SetFrame(enc, config, 1, encoded_frame);
|
||||
if (error_code != VP8_ENC_OK) {
|
||||
goto End;
|
||||
}
|
||||
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;
|
||||
@ -960,10 +1045,9 @@ static int CacheFrame(WebPAnimEncoder* const enc,
|
||||
++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);
|
||||
if (error_code != VP8_ENC_OK) {
|
||||
goto End;
|
||||
}
|
||||
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;
|
||||
@ -971,12 +1055,14 @@ static int CacheFrame(WebPAnimEncoder* const enc,
|
||||
int64_t curr_delta;
|
||||
|
||||
// Add this as a frame rectangle to enc.
|
||||
error_code = SetFrame(enc, config, 0, encoded_frame);
|
||||
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);
|
||||
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);
|
||||
@ -1008,13 +1094,18 @@ static int CacheFrame(WebPAnimEncoder* const enc,
|
||||
// Update previous to previous and previous canvases for next call.
|
||||
CopyPixels(enc->curr_canvas_, &enc->prev_canvas_);
|
||||
enc->is_first_frame_ = 0;
|
||||
|
||||
Skip:
|
||||
ok = 1;
|
||||
++enc->in_frame_count_;
|
||||
|
||||
End:
|
||||
if (!ok) {
|
||||
if (!ok || frame_skipped) {
|
||||
FrameRelease(encoded_frame);
|
||||
--enc->count_; // We reset the count, as the frame addition failed.
|
||||
if (enc->options_.verbose) {
|
||||
// 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 && enc->options_.verbose) {
|
||||
fprintf(stderr, "ERROR adding frame. WebPEncodingError: %d.\n",
|
||||
error_code);
|
||||
}
|
||||
@ -1043,7 +1134,7 @@ static int FlushFrames(WebPAnimEncoder* const enc) {
|
||||
info->x_offset, info->y_offset, info->dispose_method,
|
||||
info->blend_method);
|
||||
}
|
||||
++enc->frame_count_;
|
||||
++enc->out_frame_count_;
|
||||
FrameRelease(curr);
|
||||
++enc->start_;
|
||||
--enc->flush_count_;
|
||||
@ -1088,13 +1179,16 @@ int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int timestamp,
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
SetPreviousDuration(enc, (int)prev_frame_duration);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1132,6 +1226,7 @@ int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int timestamp,
|
||||
if (!CacheFrame(enc, &config)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!FlushFrames(enc)) {
|
||||
return 0;
|
||||
}
|
||||
@ -1212,7 +1307,7 @@ static WebPMuxError OptimizeSingleFrame(WebPAnimEncoder* const enc,
|
||||
WebPData webp_data2;
|
||||
WebPMux* const mux = WebPMuxCreate(webp_data, 0);
|
||||
if (mux == NULL) return WEBP_MUX_BAD_DATA;
|
||||
assert(enc->frame_count_ == 1);
|
||||
assert(enc->out_frame_count_ == 1);
|
||||
WebPDataInit(&frame.bitstream);
|
||||
WebPDataInit(&full_image);
|
||||
WebPDataInit(&webp_data2);
|
||||
@ -1248,7 +1343,6 @@ static WebPMuxError OptimizeSingleFrame(WebPAnimEncoder* const enc,
|
||||
int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) {
|
||||
WebPMux* mux;
|
||||
WebPMuxError err;
|
||||
size_t total_frames; // Muxed frames + cached (but not yet muxed) frames.
|
||||
|
||||
if (enc == NULL) {
|
||||
return 0;
|
||||
@ -1260,19 +1354,20 @@ int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
total_frames = enc->frame_count_ + enc->count_;
|
||||
if (total_frames == 0) {
|
||||
if (enc->in_frame_count_ == 0) {
|
||||
if (enc->options_.verbose) {
|
||||
fprintf(stderr, "ERROR: No frames to assemble\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!enc->got_null_frame_ && total_frames > 1 && enc->count_ > 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 / (total_frames - 1));
|
||||
SetPreviousDuration(enc, average_duration);
|
||||
const int average_duration = (int)(delta_time / (enc->in_frame_count_ - 1));
|
||||
if (!IncreasePreviousDuration(enc, average_duration)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush any remaining frames.
|
||||
@ -1293,7 +1388,7 @@ int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) {
|
||||
err = WebPMuxAssemble(mux, webp_data);
|
||||
if (err != WEBP_MUX_OK) goto Err;
|
||||
|
||||
if (enc->frame_count_ == 1) {
|
||||
if (enc->out_frame_count_ == 1) {
|
||||
err = OptimizeSingleFrame(enc, webp_data);
|
||||
if (err != WEBP_MUX_OK) goto Err;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user