1 // Copyright 2014 Google Inc. All Rights Reserved.
2 //
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the COPYING file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS. All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 // -----------------------------------------------------------------------------
9 //
10 // AnimEncoder implementation.
11 //
12
13 #include <assert.h>
14 #include <limits.h>
15 #include <math.h> // for pow()
16 #include <stdio.h>
17 #include <stdlib.h> // for abs()
18
19 #include "src/mux/animi.h"
20 #include "src/utils/utils.h"
21 #include "src/webp/decode.h"
22 #include "src/webp/encode.h"
23 #include "src/webp/format_constants.h"
24 #include "src/webp/mux.h"
25 #include "src/webp/types.h"
26
27 #if defined(_MSC_VER) && _MSC_VER < 1900
28 #define snprintf _snprintf
29 #endif
30
31 #define ERROR_STR_MAX_LENGTH 100
32
33 //------------------------------------------------------------------------------
34 // Internal structs.
35
36 // Stores frame rectangle dimensions.
37 typedef struct {
38 int x_offset_, y_offset_, width_, height_;
39 } FrameRectangle;
40
41 // Used to store two candidates of encoded data for an animation frame. One of
42 // the two will be chosen later.
43 typedef struct {
44 WebPMuxFrameInfo sub_frame_; // Encoded frame rectangle.
45 WebPMuxFrameInfo key_frame_; // Encoded frame if it is a key-frame.
46 int is_key_frame_; // True if 'key_frame' has been chosen.
47 } EncodedFrame;
48
49 struct WebPAnimEncoder {
50 const int canvas_width_; // Canvas width.
51 const int canvas_height_; // Canvas height.
52 const WebPAnimEncoderOptions options_; // Global encoding options.
53
54 FrameRectangle prev_rect_; // Previous WebP frame rectangle.
55 WebPConfig last_config_; // Cached in case a re-encode is needed.
56 WebPConfig last_config_reversed_; // If 'last_config_' uses lossless, then
57 // this config uses lossy and vice versa;
58 // only valid if 'options_.allow_mixed'
59 // is true.
60
61 WebPPicture* curr_canvas_; // Only pointer; we don't own memory.
62
63 // Canvas buffers.
64 WebPPicture curr_canvas_copy_; // Possibly modified current canvas.
65 int curr_canvas_copy_modified_; // True if pixels in 'curr_canvas_copy_'
66 // differ from those in 'curr_canvas_'.
67
68 WebPPicture prev_canvas_; // Previous canvas.
69 WebPPicture prev_canvas_disposed_; // Previous canvas disposed to background.
70
71 // Encoded data.
72 EncodedFrame* encoded_frames_; // Array of encoded frames.
73 size_t size_; // Number of allocated frames.
74 size_t start_; // Frame start index.
75 size_t count_; // Number of valid frames.
76 size_t flush_count_; // If >0, 'flush_count' frames starting from
77 // 'start' are ready to be added to mux.
78
79 // key-frame related.
80 int64_t best_delta_; // min(canvas size - frame size) over the frames.
81 // Can be negative in certain cases due to
82 // transparent pixels in a frame.
83 int keyframe_; // Index of selected key-frame relative to 'start_'.
84 int count_since_key_frame_; // Frames seen since the last key-frame.
85
86 int first_timestamp_; // Timestamp of the first frame.
87 int prev_timestamp_; // Timestamp of the last added frame.
88 int prev_candidate_undecided_; // True if it's not yet decided if previous
89 // frame would be a sub-frame or a key-frame.
90
91 // Misc.
92 int is_first_frame_; // True if first frame is yet to be added/being added.
93 int got_null_frame_; // True if WebPAnimEncoderAdd() has already been called
94 // with a NULL frame.
95
96 size_t in_frame_count_; // Number of input frames processed so far.
97 size_t out_frame_count_; // Number of frames added to mux so far. This may be
98 // different from 'in_frame_count_' due to merging.
99
100 WebPMux* mux_; // Muxer to assemble the WebP bitstream.
101 char error_str_[ERROR_STR_MAX_LENGTH]; // Error string. Empty if no error.
102 };
103
104 // -----------------------------------------------------------------------------
105 // Life of WebPAnimEncoder object.
106
107 #define DELTA_INFINITY (1ULL << 32)
108 #define KEYFRAME_NONE (-1)
109
110 // Reset the counters in the WebPAnimEncoder.
ResetCounters(WebPAnimEncoder * const enc)111 static void ResetCounters(WebPAnimEncoder* const enc) {
112 enc->start_ = 0;
113 enc->count_ = 0;
114 enc->flush_count_ = 0;
115 enc->best_delta_ = DELTA_INFINITY;
116 enc->keyframe_ = KEYFRAME_NONE;
117 }
118
DisableKeyframes(WebPAnimEncoderOptions * const enc_options)119 static void DisableKeyframes(WebPAnimEncoderOptions* const enc_options) {
120 enc_options->kmax = INT_MAX;
121 enc_options->kmin = enc_options->kmax - 1;
122 }
123
124 #define MAX_CACHED_FRAMES 30
125
SanitizeEncoderOptions(WebPAnimEncoderOptions * const enc_options)126 static void SanitizeEncoderOptions(WebPAnimEncoderOptions* const enc_options) {
127 int print_warning = enc_options->verbose;
128
129 if (enc_options->minimize_size) {
130 DisableKeyframes(enc_options);
131 }
132
133 if (enc_options->kmax == 1) { // All frames will be key-frames.
134 enc_options->kmin = 0;
135 enc_options->kmax = 0;
136 return;
137 } else if (enc_options->kmax <= 0) {
138 DisableKeyframes(enc_options);
139 print_warning = 0;
140 }
141
142 if (enc_options->kmin >= enc_options->kmax) {
143 enc_options->kmin = enc_options->kmax - 1;
144 if (print_warning) {
145 fprintf(stderr, "WARNING: Setting kmin = %d, so that kmin < kmax.\n",
146 enc_options->kmin);
147 }
148 } else {
149 const int kmin_limit = enc_options->kmax / 2 + 1;
150 if (enc_options->kmin < kmin_limit && kmin_limit < enc_options->kmax) {
151 // This ensures that enc.keyframe + kmin >= kmax is always true. So, we
152 // can flush all the frames in the 'count_since_key_frame == kmax' case.
153 enc_options->kmin = kmin_limit;
154 if (print_warning) {
155 fprintf(stderr,
156 "WARNING: Setting kmin = %d, so that kmin >= kmax / 2 + 1.\n",
157 enc_options->kmin);
158 }
159 }
160 }
161 // Limit the max number of frames that are allocated.
162 if (enc_options->kmax - enc_options->kmin > MAX_CACHED_FRAMES) {
163 enc_options->kmin = enc_options->kmax - MAX_CACHED_FRAMES;
164 if (print_warning) {
165 fprintf(stderr,
166 "WARNING: Setting kmin = %d, so that kmax - kmin <= %d.\n",
167 enc_options->kmin, MAX_CACHED_FRAMES);
168 }
169 }
170 assert(enc_options->kmin < enc_options->kmax);
171 }
172
173 #undef MAX_CACHED_FRAMES
174
DefaultEncoderOptions(WebPAnimEncoderOptions * const enc_options)175 static void DefaultEncoderOptions(WebPAnimEncoderOptions* const enc_options) {
176 enc_options->anim_params.loop_count = 0;
177 enc_options->anim_params.bgcolor = 0xffffffff; // White.
178 enc_options->minimize_size = 0;
179 DisableKeyframes(enc_options);
180 enc_options->allow_mixed = 0;
181 enc_options->verbose = 0;
182 }
183
WebPAnimEncoderOptionsInitInternal(WebPAnimEncoderOptions * enc_options,int abi_version)184 int WebPAnimEncoderOptionsInitInternal(WebPAnimEncoderOptions* enc_options,
185 int abi_version) {
186 if (enc_options == NULL ||
187 WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) {
188 return 0;
189 }
190 DefaultEncoderOptions(enc_options);
191 return 1;
192 }
193
194 // This value is used to match a later call to WebPReplaceTransparentPixels(),
195 // making it a no-op for lossless (see WebPEncode()).
196 #define TRANSPARENT_COLOR 0x00000000
197
ClearRectangle(WebPPicture * const picture,int left,int top,int width,int height)198 static void ClearRectangle(WebPPicture* const picture,
199 int left, int top, int width, int height) {
200 int j;
201 for (j = top; j < top + height; ++j) {
202 uint32_t* const dst = picture->argb + j * picture->argb_stride;
203 int i;
204 for (i = left; i < left + width; ++i) {
205 dst[i] = TRANSPARENT_COLOR;
206 }
207 }
208 }
209
WebPUtilClearPic(WebPPicture * const picture,const FrameRectangle * const rect)210 static void WebPUtilClearPic(WebPPicture* const picture,
211 const FrameRectangle* const rect) {
212 if (rect != NULL) {
213 ClearRectangle(picture, rect->x_offset_, rect->y_offset_,
214 rect->width_, rect->height_);
215 } else {
216 ClearRectangle(picture, 0, 0, picture->width, picture->height);
217 }
218 }
219
MarkNoError(WebPAnimEncoder * const enc)220 static void MarkNoError(WebPAnimEncoder* const enc) {
221 enc->error_str_[0] = '\0'; // Empty string.
222 }
223
MarkError(WebPAnimEncoder * const enc,const char * str)224 static void MarkError(WebPAnimEncoder* const enc, const char* str) {
225 if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s.", str) < 0) {
226 assert(0); // FIX ME!
227 }
228 }
229
MarkError2(WebPAnimEncoder * const enc,const char * str,int error_code)230 static void MarkError2(WebPAnimEncoder* const enc,
231 const char* str, int error_code) {
232 if (snprintf(enc->error_str_, ERROR_STR_MAX_LENGTH, "%s: %d.", str,
233 error_code) < 0) {
234 assert(0); // FIX ME!
235 }
236 }
237
WebPAnimEncoderNewInternal(int width,int height,const WebPAnimEncoderOptions * enc_options,int abi_version)238 WebPAnimEncoder* WebPAnimEncoderNewInternal(
239 int width, int height, const WebPAnimEncoderOptions* enc_options,
240 int abi_version) {
241 WebPAnimEncoder* enc;
242
243 if (WEBP_ABI_IS_INCOMPATIBLE(abi_version, WEBP_MUX_ABI_VERSION)) {
244 return NULL;
245 }
246 if (width <= 0 || height <= 0 ||
247 (width * (uint64_t)height) >= MAX_IMAGE_AREA) {
248 return NULL;
249 }
250
251 enc = (WebPAnimEncoder*)WebPSafeCalloc(1, sizeof(*enc));
252 if (enc == NULL) return NULL;
253 MarkNoError(enc);
254
255 // Dimensions and options.
256 *(int*)&enc->canvas_width_ = width;
257 *(int*)&enc->canvas_height_ = height;
258 if (enc_options != NULL) {
259 *(WebPAnimEncoderOptions*)&enc->options_ = *enc_options;
260 SanitizeEncoderOptions((WebPAnimEncoderOptions*)&enc->options_);
261 } else {
262 DefaultEncoderOptions((WebPAnimEncoderOptions*)&enc->options_);
263 }
264
265 // Canvas buffers.
266 if (!WebPPictureInit(&enc->curr_canvas_copy_) ||
267 !WebPPictureInit(&enc->prev_canvas_) ||
268 !WebPPictureInit(&enc->prev_canvas_disposed_)) {
269 goto Err;
270 }
271 enc->curr_canvas_copy_.width = width;
272 enc->curr_canvas_copy_.height = height;
273 enc->curr_canvas_copy_.use_argb = 1;
274 if (!WebPPictureAlloc(&enc->curr_canvas_copy_) ||
275 !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_) ||
276 !WebPPictureCopy(&enc->curr_canvas_copy_, &enc->prev_canvas_disposed_)) {
277 goto Err;
278 }
279 WebPUtilClearPic(&enc->prev_canvas_, NULL);
280 enc->curr_canvas_copy_modified_ = 1;
281
282 // Encoded frames.
283 ResetCounters(enc);
284 // Note: one extra storage is for the previous frame.
285 enc->size_ = enc->options_.kmax - enc->options_.kmin + 1;
286 // We need space for at least 2 frames. But when kmin, kmax are both zero,
287 // enc->size_ will be 1. So we handle that special case below.
288 if (enc->size_ < 2) enc->size_ = 2;
289 enc->encoded_frames_ =
290 (EncodedFrame*)WebPSafeCalloc(enc->size_, sizeof(*enc->encoded_frames_));
291 if (enc->encoded_frames_ == NULL) goto Err;
292
293 enc->mux_ = WebPMuxNew();
294 if (enc->mux_ == NULL) goto Err;
295
296 enc->count_since_key_frame_ = 0;
297 enc->first_timestamp_ = 0;
298 enc->prev_timestamp_ = 0;
299 enc->prev_candidate_undecided_ = 0;
300 enc->is_first_frame_ = 1;
301 enc->got_null_frame_ = 0;
302
303 return enc; // All OK.
304
305 Err:
306 WebPAnimEncoderDelete(enc);
307 return NULL;
308 }
309
310 // Release the data contained by 'encoded_frame'.
FrameRelease(EncodedFrame * const encoded_frame)311 static void FrameRelease(EncodedFrame* const encoded_frame) {
312 if (encoded_frame != NULL) {
313 WebPDataClear(&encoded_frame->sub_frame_.bitstream);
314 WebPDataClear(&encoded_frame->key_frame_.bitstream);
315 memset(encoded_frame, 0, sizeof(*encoded_frame));
316 }
317 }
318
WebPAnimEncoderDelete(WebPAnimEncoder * enc)319 void WebPAnimEncoderDelete(WebPAnimEncoder* enc) {
320 if (enc != NULL) {
321 WebPPictureFree(&enc->curr_canvas_copy_);
322 WebPPictureFree(&enc->prev_canvas_);
323 WebPPictureFree(&enc->prev_canvas_disposed_);
324 if (enc->encoded_frames_ != NULL) {
325 size_t i;
326 for (i = 0; i < enc->size_; ++i) {
327 FrameRelease(&enc->encoded_frames_[i]);
328 }
329 WebPSafeFree(enc->encoded_frames_);
330 }
331 WebPMuxDelete(enc->mux_);
332 WebPSafeFree(enc);
333 }
334 }
335
336 // -----------------------------------------------------------------------------
337 // Frame addition.
338
339 // Returns cached frame at the given 'position'.
GetFrame(const WebPAnimEncoder * const enc,size_t position)340 static EncodedFrame* GetFrame(const WebPAnimEncoder* const enc,
341 size_t position) {
342 assert(enc->start_ + position < enc->size_);
343 return &enc->encoded_frames_[enc->start_ + position];
344 }
345
346 typedef int (*ComparePixelsFunc)(const uint32_t*, int, const uint32_t*, int,
347 int, int);
348
349 // Returns true if 'length' number of pixels in 'src' and 'dst' are equal,
350 // assuming the given step sizes between pixels.
351 // 'max_allowed_diff' is unused and only there to allow function pointer use.
ComparePixelsLossless(const uint32_t * src,int src_step,const uint32_t * dst,int dst_step,int length,int max_allowed_diff)352 static WEBP_INLINE int ComparePixelsLossless(const uint32_t* src, int src_step,
353 const uint32_t* dst, int dst_step,
354 int length, int max_allowed_diff) {
355 (void)max_allowed_diff;
356 assert(length > 0);
357 while (length-- > 0) {
358 if (*src != *dst) {
359 return 0;
360 }
361 src += src_step;
362 dst += dst_step;
363 }
364 return 1;
365 }
366
367 // Helper to check if each channel in 'src' and 'dst' is at most off by
368 // 'max_allowed_diff'.
PixelsAreSimilar(uint32_t src,uint32_t dst,int max_allowed_diff)369 static WEBP_INLINE int PixelsAreSimilar(uint32_t src, uint32_t dst,
370 int max_allowed_diff) {
371 const int src_a = (src >> 24) & 0xff;
372 const int src_r = (src >> 16) & 0xff;
373 const int src_g = (src >> 8) & 0xff;
374 const int src_b = (src >> 0) & 0xff;
375 const int dst_a = (dst >> 24) & 0xff;
376 const int dst_r = (dst >> 16) & 0xff;
377 const int dst_g = (dst >> 8) & 0xff;
378 const int dst_b = (dst >> 0) & 0xff;
379
380 return (src_a == dst_a) &&
381 (abs(src_r - dst_r) * dst_a <= (max_allowed_diff * 255)) &&
382 (abs(src_g - dst_g) * dst_a <= (max_allowed_diff * 255)) &&
383 (abs(src_b - dst_b) * dst_a <= (max_allowed_diff * 255));
384 }
385
386 // Returns true if 'length' number of pixels in 'src' and 'dst' are within an
387 // error bound, assuming the given step sizes between pixels.
ComparePixelsLossy(const uint32_t * src,int src_step,const uint32_t * dst,int dst_step,int length,int max_allowed_diff)388 static WEBP_INLINE int ComparePixelsLossy(const uint32_t* src, int src_step,
389 const uint32_t* dst, int dst_step,
390 int length, int max_allowed_diff) {
391 assert(length > 0);
392 while (length-- > 0) {
393 if (!PixelsAreSimilar(*src, *dst, max_allowed_diff)) {
394 return 0;
395 }
396 src += src_step;
397 dst += dst_step;
398 }
399 return 1;
400 }
401
IsEmptyRect(const FrameRectangle * const rect)402 static int IsEmptyRect(const FrameRectangle* const rect) {
403 return (rect->width_ == 0) || (rect->height_ == 0);
404 }
405
QualityToMaxDiff(float quality)406 static int QualityToMaxDiff(float quality) {
407 const double val = pow(quality / 100., 0.5);
408 const double max_diff = 31 * (1 - val) + 1 * val;
409 return (int)(max_diff + 0.5);
410 }
411
412 // Assumes that an initial valid guess of change rectangle 'rect' is passed.
MinimizeChangeRectangle(const WebPPicture * const src,const WebPPicture * const dst,FrameRectangle * const rect,int is_lossless,float quality)413 static void MinimizeChangeRectangle(const WebPPicture* const src,
414 const WebPPicture* const dst,
415 FrameRectangle* const rect,
416 int is_lossless, float quality) {
417 int i, j;
418 const ComparePixelsFunc compare_pixels =
419 is_lossless ? ComparePixelsLossless : ComparePixelsLossy;
420 const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
421 const int max_allowed_diff = is_lossless ? 0 : max_allowed_diff_lossy;
422
423 // Assumption/correctness checks.
424 assert(src->width == dst->width && src->height == dst->height);
425 assert(rect->x_offset_ + rect->width_ <= dst->width);
426 assert(rect->y_offset_ + rect->height_ <= dst->height);
427
428 // Left boundary.
429 for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
430 const uint32_t* const src_argb =
431 &src->argb[rect->y_offset_ * src->argb_stride + i];
432 const uint32_t* const dst_argb =
433 &dst->argb[rect->y_offset_ * dst->argb_stride + i];
434 if (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
435 rect->height_, max_allowed_diff)) {
436 --rect->width_; // Redundant column.
437 ++rect->x_offset_;
438 } else {
439 break;
440 }
441 }
442 if (rect->width_ == 0) goto NoChange;
443
444 // Right boundary.
445 for (i = rect->x_offset_ + rect->width_ - 1; i >= rect->x_offset_; --i) {
446 const uint32_t* const src_argb =
447 &src->argb[rect->y_offset_ * src->argb_stride + i];
448 const uint32_t* const dst_argb =
449 &dst->argb[rect->y_offset_ * dst->argb_stride + i];
450 if (compare_pixels(src_argb, src->argb_stride, dst_argb, dst->argb_stride,
451 rect->height_, max_allowed_diff)) {
452 --rect->width_; // Redundant column.
453 } else {
454 break;
455 }
456 }
457 if (rect->width_ == 0) goto NoChange;
458
459 // Top boundary.
460 for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
461 const uint32_t* const src_argb =
462 &src->argb[j * src->argb_stride + rect->x_offset_];
463 const uint32_t* const dst_argb =
464 &dst->argb[j * dst->argb_stride + rect->x_offset_];
465 if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
466 max_allowed_diff)) {
467 --rect->height_; // Redundant row.
468 ++rect->y_offset_;
469 } else {
470 break;
471 }
472 }
473 if (rect->height_ == 0) goto NoChange;
474
475 // Bottom boundary.
476 for (j = rect->y_offset_ + rect->height_ - 1; j >= rect->y_offset_; --j) {
477 const uint32_t* const src_argb =
478 &src->argb[j * src->argb_stride + rect->x_offset_];
479 const uint32_t* const dst_argb =
480 &dst->argb[j * dst->argb_stride + rect->x_offset_];
481 if (compare_pixels(src_argb, 1, dst_argb, 1, rect->width_,
482 max_allowed_diff)) {
483 --rect->height_; // Redundant row.
484 } else {
485 break;
486 }
487 }
488 if (rect->height_ == 0) goto NoChange;
489
490 if (IsEmptyRect(rect)) {
491 NoChange:
492 rect->x_offset_ = 0;
493 rect->y_offset_ = 0;
494 rect->width_ = 0;
495 rect->height_ = 0;
496 }
497 }
498
499 // Snap rectangle to even offsets (and adjust dimensions if needed).
SnapToEvenOffsets(FrameRectangle * const rect)500 static WEBP_INLINE void SnapToEvenOffsets(FrameRectangle* const rect) {
501 rect->width_ += (rect->x_offset_ & 1);
502 rect->height_ += (rect->y_offset_ & 1);
503 rect->x_offset_ &= ~1;
504 rect->y_offset_ &= ~1;
505 }
506
507 typedef struct {
508 int should_try_; // Should try this set of parameters.
509 int empty_rect_allowed_; // Frame with empty rectangle can be skipped.
510 FrameRectangle rect_ll_; // Frame rectangle for lossless compression.
511 WebPPicture sub_frame_ll_; // Sub-frame pic for lossless compression.
512 FrameRectangle rect_lossy_; // Frame rectangle for lossy compression.
513 // Could be smaller than rect_ll_ as pixels
514 // with small diffs can be ignored.
515 WebPPicture sub_frame_lossy_; // Sub-frame pic for lossless compression.
516 } SubFrameParams;
517
SubFrameParamsInit(SubFrameParams * const params,int should_try,int empty_rect_allowed)518 static int SubFrameParamsInit(SubFrameParams* const params,
519 int should_try, int empty_rect_allowed) {
520 params->should_try_ = should_try;
521 params->empty_rect_allowed_ = empty_rect_allowed;
522 if (!WebPPictureInit(¶ms->sub_frame_ll_) ||
523 !WebPPictureInit(¶ms->sub_frame_lossy_)) {
524 return 0;
525 }
526 return 1;
527 }
528
SubFrameParamsFree(SubFrameParams * const params)529 static void SubFrameParamsFree(SubFrameParams* const params) {
530 WebPPictureFree(¶ms->sub_frame_ll_);
531 WebPPictureFree(¶ms->sub_frame_lossy_);
532 }
533
534 // Given previous and current canvas, picks the optimal rectangle for the
535 // current frame based on 'is_lossless' and other parameters. Assumes that the
536 // initial guess 'rect' is valid.
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,FrameRectangle * const rect,WebPPicture * const sub_frame)537 static int GetSubRect(const WebPPicture* const prev_canvas,
538 const WebPPicture* const curr_canvas, int is_key_frame,
539 int is_first_frame, int empty_rect_allowed,
540 int is_lossless, float quality,
541 FrameRectangle* const rect,
542 WebPPicture* const sub_frame) {
543 if (!is_key_frame || is_first_frame) { // Optimize frame rectangle.
544 // Note: This behaves as expected for first frame, as 'prev_canvas' is
545 // initialized to a fully transparent canvas in the beginning.
546 MinimizeChangeRectangle(prev_canvas, curr_canvas, rect,
547 is_lossless, quality);
548 }
549
550 if (IsEmptyRect(rect)) {
551 if (empty_rect_allowed) { // No need to get 'sub_frame'.
552 return 1;
553 } else { // Force a 1x1 rectangle.
554 rect->width_ = 1;
555 rect->height_ = 1;
556 assert(rect->x_offset_ == 0);
557 assert(rect->y_offset_ == 0);
558 }
559 }
560
561 SnapToEvenOffsets(rect);
562 return WebPPictureView(curr_canvas, rect->x_offset_, rect->y_offset_,
563 rect->width_, rect->height_, sub_frame);
564 }
565
566 // Picks optimal frame rectangle for both lossless and lossy compression. The
567 // initial guess for frame rectangles will be the full canvas.
GetSubRects(const WebPPicture * const prev_canvas,const WebPPicture * const curr_canvas,int is_key_frame,int is_first_frame,float quality,SubFrameParams * const params)568 static int GetSubRects(const WebPPicture* const prev_canvas,
569 const WebPPicture* const curr_canvas, int is_key_frame,
570 int is_first_frame, float quality,
571 SubFrameParams* const params) {
572 // Lossless frame rectangle.
573 params->rect_ll_.x_offset_ = 0;
574 params->rect_ll_.y_offset_ = 0;
575 params->rect_ll_.width_ = curr_canvas->width;
576 params->rect_ll_.height_ = curr_canvas->height;
577 if (!GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
578 params->empty_rect_allowed_, 1, quality,
579 ¶ms->rect_ll_, ¶ms->sub_frame_ll_)) {
580 return 0;
581 }
582 // Lossy frame rectangle.
583 params->rect_lossy_ = params->rect_ll_; // seed with lossless rect.
584 return GetSubRect(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
585 params->empty_rect_allowed_, 0, quality,
586 ¶ms->rect_lossy_, ¶ms->sub_frame_lossy_);
587 }
588
clip(int v,int min_v,int max_v)589 static WEBP_INLINE int clip(int v, int min_v, int max_v) {
590 return (v < min_v) ? min_v : (v > max_v) ? max_v : v;
591 }
592
WebPAnimEncoderRefineRect(const WebPPicture * const prev_canvas,const WebPPicture * const curr_canvas,int is_lossless,float quality,int * const x_offset,int * const y_offset,int * const width,int * const height)593 int WebPAnimEncoderRefineRect(
594 const WebPPicture* const prev_canvas, const WebPPicture* const curr_canvas,
595 int is_lossless, float quality, int* const x_offset, int* const y_offset,
596 int* const width, int* const height) {
597 FrameRectangle rect;
598 int right, left, bottom, top;
599 if (prev_canvas == NULL || curr_canvas == NULL ||
600 prev_canvas->width != curr_canvas->width ||
601 prev_canvas->height != curr_canvas->height ||
602 !prev_canvas->use_argb || !curr_canvas->use_argb) {
603 return 0;
604 }
605 right = clip(*x_offset + *width, 0, curr_canvas->width);
606 left = clip(*x_offset, 0, curr_canvas->width - 1);
607 bottom = clip(*y_offset + *height, 0, curr_canvas->height);
608 top = clip(*y_offset, 0, curr_canvas->height - 1);
609 rect.x_offset_ = left;
610 rect.y_offset_ = top;
611 rect.width_ = clip(right - left, 0, curr_canvas->width - rect.x_offset_);
612 rect.height_ = clip(bottom - top, 0, curr_canvas->height - rect.y_offset_);
613 MinimizeChangeRectangle(prev_canvas, curr_canvas, &rect, is_lossless,
614 quality);
615 SnapToEvenOffsets(&rect);
616 *x_offset = rect.x_offset_;
617 *y_offset = rect.y_offset_;
618 *width = rect.width_;
619 *height = rect.height_;
620 return 1;
621 }
622
DisposeFrameRectangle(int dispose_method,const FrameRectangle * const rect,WebPPicture * const curr_canvas)623 static void DisposeFrameRectangle(int dispose_method,
624 const FrameRectangle* const rect,
625 WebPPicture* const curr_canvas) {
626 assert(rect != NULL);
627 if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
628 WebPUtilClearPic(curr_canvas, rect);
629 }
630 }
631
RectArea(const FrameRectangle * const rect)632 static uint32_t RectArea(const FrameRectangle* const rect) {
633 return (uint32_t)rect->width_ * rect->height_;
634 }
635
IsLosslessBlendingPossible(const WebPPicture * const src,const WebPPicture * const dst,const FrameRectangle * const rect)636 static int IsLosslessBlendingPossible(const WebPPicture* const src,
637 const WebPPicture* const dst,
638 const FrameRectangle* const rect) {
639 int i, j;
640 assert(src->width == dst->width && src->height == dst->height);
641 assert(rect->x_offset_ + rect->width_ <= dst->width);
642 assert(rect->y_offset_ + rect->height_ <= dst->height);
643 for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
644 for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
645 const uint32_t src_pixel = src->argb[j * src->argb_stride + i];
646 const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i];
647 const uint32_t dst_alpha = dst_pixel >> 24;
648 if (dst_alpha != 0xff && src_pixel != dst_pixel) {
649 // In this case, if we use blending, we can't attain the desired
650 // 'dst_pixel' value for this pixel. So, blending is not possible.
651 return 0;
652 }
653 }
654 }
655 return 1;
656 }
657
IsLossyBlendingPossible(const WebPPicture * const src,const WebPPicture * const dst,const FrameRectangle * const rect,float quality)658 static int IsLossyBlendingPossible(const WebPPicture* const src,
659 const WebPPicture* const dst,
660 const FrameRectangle* const rect,
661 float quality) {
662 const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
663 int i, j;
664 assert(src->width == dst->width && src->height == dst->height);
665 assert(rect->x_offset_ + rect->width_ <= dst->width);
666 assert(rect->y_offset_ + rect->height_ <= dst->height);
667 for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
668 for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
669 const uint32_t src_pixel = src->argb[j * src->argb_stride + i];
670 const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i];
671 const uint32_t dst_alpha = dst_pixel >> 24;
672 if (dst_alpha != 0xff &&
673 !PixelsAreSimilar(src_pixel, dst_pixel, max_allowed_diff_lossy)) {
674 // In this case, if we use blending, we can't attain the desired
675 // 'dst_pixel' value for this pixel. So, blending is not possible.
676 return 0;
677 }
678 }
679 }
680 return 1;
681 }
682
683 // For pixels in 'rect', replace those pixels in 'dst' that are same as 'src' by
684 // transparent pixels.
685 // Returns true if at least one pixel gets modified.
IncreaseTransparency(const WebPPicture * const src,const FrameRectangle * const rect,WebPPicture * const dst)686 static int IncreaseTransparency(const WebPPicture* const src,
687 const FrameRectangle* const rect,
688 WebPPicture* const dst) {
689 int i, j;
690 int modified = 0;
691 assert(src != NULL && dst != NULL && rect != NULL);
692 assert(src->width == dst->width && src->height == dst->height);
693 for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) {
694 const uint32_t* const psrc = src->argb + j * src->argb_stride;
695 uint32_t* const pdst = dst->argb + j * dst->argb_stride;
696 for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) {
697 if (psrc[i] == pdst[i] && pdst[i] != TRANSPARENT_COLOR) {
698 pdst[i] = TRANSPARENT_COLOR;
699 modified = 1;
700 }
701 }
702 }
703 return modified;
704 }
705
706 #undef TRANSPARENT_COLOR
707
708 // Replace similar blocks of pixels by a 'see-through' transparent block
709 // with uniform average color.
710 // Assumes lossy compression is being used.
711 // Returns true if at least one pixel gets modified.
FlattenSimilarBlocks(const WebPPicture * const src,const FrameRectangle * const rect,WebPPicture * const dst,float quality)712 static int FlattenSimilarBlocks(const WebPPicture* const src,
713 const FrameRectangle* const rect,
714 WebPPicture* const dst, float quality) {
715 const int max_allowed_diff_lossy = QualityToMaxDiff(quality);
716 int i, j;
717 int modified = 0;
718 const int block_size = 8;
719 const int y_start = (rect->y_offset_ + block_size) & ~(block_size - 1);
720 const int y_end = (rect->y_offset_ + rect->height_) & ~(block_size - 1);
721 const int x_start = (rect->x_offset_ + block_size) & ~(block_size - 1);
722 const int x_end = (rect->x_offset_ + rect->width_) & ~(block_size - 1);
723 assert(src != NULL && dst != NULL && rect != NULL);
724 assert(src->width == dst->width && src->height == dst->height);
725 assert((block_size & (block_size - 1)) == 0); // must be a power of 2
726 // Iterate over each block and count similar pixels.
727 for (j = y_start; j < y_end; j += block_size) {
728 for (i = x_start; i < x_end; i += block_size) {
729 int cnt = 0;
730 int avg_r = 0, avg_g = 0, avg_b = 0;
731 int x, y;
732 const uint32_t* const psrc = src->argb + j * src->argb_stride + i;
733 uint32_t* const pdst = dst->argb + j * dst->argb_stride + i;
734 for (y = 0; y < block_size; ++y) {
735 for (x = 0; x < block_size; ++x) {
736 const uint32_t src_pixel = psrc[x + y * src->argb_stride];
737 const int alpha = src_pixel >> 24;
738 if (alpha == 0xff &&
739 PixelsAreSimilar(src_pixel, pdst[x + y * dst->argb_stride],
740 max_allowed_diff_lossy)) {
741 ++cnt;
742 avg_r += (src_pixel >> 16) & 0xff;
743 avg_g += (src_pixel >> 8) & 0xff;
744 avg_b += (src_pixel >> 0) & 0xff;
745 }
746 }
747 }
748 // If we have a fully similar block, we replace it with an
749 // average transparent block. This compresses better in lossy mode.
750 if (cnt == block_size * block_size) {
751 const uint32_t color = (0x00 << 24) |
752 ((avg_r / cnt) << 16) |
753 ((avg_g / cnt) << 8) |
754 ((avg_b / cnt) << 0);
755 for (y = 0; y < block_size; ++y) {
756 for (x = 0; x < block_size; ++x) {
757 pdst[x + y * dst->argb_stride] = color;
758 }
759 }
760 modified = 1;
761 }
762 }
763 }
764 return modified;
765 }
766
EncodeFrame(const WebPConfig * const config,WebPPicture * const pic,WebPMemoryWriter * const memory)767 static int EncodeFrame(const WebPConfig* const config, WebPPicture* const pic,
768 WebPMemoryWriter* const memory) {
769 pic->use_argb = 1;
770 pic->writer = WebPMemoryWrite;
771 pic->custom_ptr = memory;
772 if (!WebPEncode(config, pic)) {
773 return 0;
774 }
775 return 1;
776 }
777
778 // Struct representing a candidate encoded frame including its metadata.
779 typedef struct {
780 WebPMemoryWriter mem_;
781 WebPMuxFrameInfo info_;
782 FrameRectangle rect_;
783 int evaluate_; // True if this candidate should be evaluated.
784 } Candidate;
785
786 // Generates a candidate encoded frame given a picture and metadata.
EncodeCandidate(WebPPicture * const sub_frame,const FrameRectangle * const rect,const WebPConfig * const encoder_config,int use_blending,Candidate * const candidate)787 static WebPEncodingError EncodeCandidate(WebPPicture* const sub_frame,
788 const FrameRectangle* const rect,
789 const WebPConfig* const encoder_config,
790 int use_blending,
791 Candidate* const candidate) {
792 WebPConfig config = *encoder_config;
793 WebPEncodingError error_code = VP8_ENC_OK;
794 assert(candidate != NULL);
795 memset(candidate, 0, sizeof(*candidate));
796
797 // Set frame rect and info.
798 candidate->rect_ = *rect;
799 candidate->info_.id = WEBP_CHUNK_ANMF;
800 candidate->info_.x_offset = rect->x_offset_;
801 candidate->info_.y_offset = rect->y_offset_;
802 candidate->info_.dispose_method = WEBP_MUX_DISPOSE_NONE; // Set later.
803 candidate->info_.blend_method =
804 use_blending ? WEBP_MUX_BLEND : WEBP_MUX_NO_BLEND;
805 candidate->info_.duration = 0; // Set in next call to WebPAnimEncoderAdd().
806
807 // Encode picture.
808 WebPMemoryWriterInit(&candidate->mem_);
809
810 if (!config.lossless && use_blending) {
811 // Disable filtering to avoid blockiness in reconstructed frames at the
812 // time of decoding.
813 config.autofilter = 0;
814 config.filter_strength = 0;
815 }
816 if (!EncodeFrame(&config, sub_frame, &candidate->mem_)) {
817 error_code = sub_frame->error_code;
818 goto Err;
819 }
820
821 candidate->evaluate_ = 1;
822 return error_code;
823
824 Err:
825 WebPMemoryWriterClear(&candidate->mem_);
826 return error_code;
827 }
828
CopyCurrentCanvas(WebPAnimEncoder * const enc)829 static void CopyCurrentCanvas(WebPAnimEncoder* const enc) {
830 if (enc->curr_canvas_copy_modified_) {
831 WebPCopyPixels(enc->curr_canvas_, &enc->curr_canvas_copy_);
832 enc->curr_canvas_copy_.progress_hook = enc->curr_canvas_->progress_hook;
833 enc->curr_canvas_copy_.user_data = enc->curr_canvas_->user_data;
834 enc->curr_canvas_copy_modified_ = 0;
835 }
836 }
837
838 enum {
839 LL_DISP_NONE = 0,
840 LL_DISP_BG,
841 LOSSY_DISP_NONE,
842 LOSSY_DISP_BG,
843 CANDIDATE_COUNT
844 };
845
846 #define MIN_COLORS_LOSSY 31 // Don't try lossy below this threshold.
847 #define MAX_COLORS_LOSSLESS 194 // Don't try lossless above this threshold.
848
849 // Generates candidates for a given dispose method given pre-filled sub-frame
850 // 'params'.
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)851 static WebPEncodingError GenerateCandidates(
852 WebPAnimEncoder* const enc, Candidate candidates[CANDIDATE_COUNT],
853 WebPMuxAnimDispose dispose_method, int is_lossless, int is_key_frame,
854 SubFrameParams* const params,
855 const WebPConfig* const config_ll, const WebPConfig* const config_lossy) {
856 WebPEncodingError error_code = VP8_ENC_OK;
857 const int is_dispose_none = (dispose_method == WEBP_MUX_DISPOSE_NONE);
858 Candidate* const candidate_ll =
859 is_dispose_none ? &candidates[LL_DISP_NONE] : &candidates[LL_DISP_BG];
860 Candidate* const candidate_lossy = is_dispose_none
861 ? &candidates[LOSSY_DISP_NONE]
862 : &candidates[LOSSY_DISP_BG];
863 WebPPicture* const curr_canvas = &enc->curr_canvas_copy_;
864 const WebPPicture* const prev_canvas =
865 is_dispose_none ? &enc->prev_canvas_ : &enc->prev_canvas_disposed_;
866 int use_blending_ll, use_blending_lossy;
867 int evaluate_ll, evaluate_lossy;
868
869 CopyCurrentCanvas(enc);
870 use_blending_ll =
871 !is_key_frame &&
872 IsLosslessBlendingPossible(prev_canvas, curr_canvas, ¶ms->rect_ll_);
873 use_blending_lossy =
874 !is_key_frame &&
875 IsLossyBlendingPossible(prev_canvas, curr_canvas, ¶ms->rect_lossy_,
876 config_lossy->quality);
877
878 // Pick candidates to be tried.
879 if (!enc->options_.allow_mixed) {
880 evaluate_ll = is_lossless;
881 evaluate_lossy = !is_lossless;
882 } else if (enc->options_.minimize_size) {
883 evaluate_ll = 1;
884 evaluate_lossy = 1;
885 } else { // Use a heuristic for trying lossless and/or lossy compression.
886 const int num_colors = WebPGetColorPalette(¶ms->sub_frame_ll_, NULL);
887 evaluate_ll = (num_colors < MAX_COLORS_LOSSLESS);
888 evaluate_lossy = (num_colors >= MIN_COLORS_LOSSY);
889 }
890
891 // Generate candidates.
892 if (evaluate_ll) {
893 CopyCurrentCanvas(enc);
894 if (use_blending_ll) {
895 enc->curr_canvas_copy_modified_ =
896 IncreaseTransparency(prev_canvas, ¶ms->rect_ll_, curr_canvas);
897 }
898 error_code = EncodeCandidate(¶ms->sub_frame_ll_, ¶ms->rect_ll_,
899 config_ll, use_blending_ll, candidate_ll);
900 if (error_code != VP8_ENC_OK) return error_code;
901 }
902 if (evaluate_lossy) {
903 CopyCurrentCanvas(enc);
904 if (use_blending_lossy) {
905 enc->curr_canvas_copy_modified_ =
906 FlattenSimilarBlocks(prev_canvas, ¶ms->rect_lossy_, curr_canvas,
907 config_lossy->quality);
908 }
909 error_code =
910 EncodeCandidate(¶ms->sub_frame_lossy_, ¶ms->rect_lossy_,
911 config_lossy, use_blending_lossy, candidate_lossy);
912 if (error_code != VP8_ENC_OK) return error_code;
913 enc->curr_canvas_copy_modified_ = 1;
914 }
915 return error_code;
916 }
917
918 #undef MIN_COLORS_LOSSY
919 #undef MAX_COLORS_LOSSLESS
920
GetEncodedData(const WebPMemoryWriter * const memory,WebPData * const encoded_data)921 static void GetEncodedData(const WebPMemoryWriter* const memory,
922 WebPData* const encoded_data) {
923 encoded_data->bytes = memory->mem;
924 encoded_data->size = memory->size;
925 }
926
927 // Sets dispose method of the previous frame to be 'dispose_method'.
SetPreviousDisposeMethod(WebPAnimEncoder * const enc,WebPMuxAnimDispose dispose_method)928 static void SetPreviousDisposeMethod(WebPAnimEncoder* const enc,
929 WebPMuxAnimDispose dispose_method) {
930 const size_t position = enc->count_ - 2;
931 EncodedFrame* const prev_enc_frame = GetFrame(enc, position);
932 assert(enc->count_ >= 2); // As current and previous frames are in enc.
933
934 if (enc->prev_candidate_undecided_) {
935 assert(dispose_method == WEBP_MUX_DISPOSE_NONE);
936 prev_enc_frame->sub_frame_.dispose_method = dispose_method;
937 prev_enc_frame->key_frame_.dispose_method = dispose_method;
938 } else {
939 WebPMuxFrameInfo* const prev_info = prev_enc_frame->is_key_frame_
940 ? &prev_enc_frame->key_frame_
941 : &prev_enc_frame->sub_frame_;
942 prev_info->dispose_method = dispose_method;
943 }
944 }
945
IncreasePreviousDuration(WebPAnimEncoder * const enc,int duration)946 static int IncreasePreviousDuration(WebPAnimEncoder* const enc, int duration) {
947 const size_t position = enc->count_ - 1;
948 EncodedFrame* const prev_enc_frame = GetFrame(enc, position);
949 int new_duration;
950
951 assert(enc->count_ >= 1);
952 assert(!prev_enc_frame->is_key_frame_ ||
953 prev_enc_frame->sub_frame_.duration ==
954 prev_enc_frame->key_frame_.duration);
955 assert(prev_enc_frame->sub_frame_.duration ==
956 (prev_enc_frame->sub_frame_.duration & (MAX_DURATION - 1)));
957 assert(duration == (duration & (MAX_DURATION - 1)));
958
959 new_duration = prev_enc_frame->sub_frame_.duration + duration;
960 if (new_duration >= MAX_DURATION) { // Special case.
961 // Separate out previous frame from earlier merged frames to avoid overflow.
962 // We add a 1x1 transparent frame for the previous frame, with blending on.
963 const FrameRectangle rect = { 0, 0, 1, 1 };
964 const uint8_t lossless_1x1_bytes[] = {
965 0x52, 0x49, 0x46, 0x46, 0x14, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
966 0x56, 0x50, 0x38, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00,
967 0x10, 0x88, 0x88, 0x08
968 };
969 const WebPData lossless_1x1 = {
970 lossless_1x1_bytes, sizeof(lossless_1x1_bytes)
971 };
972 const uint8_t lossy_1x1_bytes[] = {
973 0x52, 0x49, 0x46, 0x46, 0x40, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50,
974 0x56, 0x50, 0x38, 0x58, 0x0a, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
975 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x4c, 0x50, 0x48, 0x02, 0x00,
976 0x00, 0x00, 0x00, 0x00, 0x56, 0x50, 0x38, 0x20, 0x18, 0x00, 0x00, 0x00,
977 0x30, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00,
978 0x34, 0x25, 0xa4, 0x00, 0x03, 0x70, 0x00, 0xfe, 0xfb, 0xfd, 0x50, 0x00
979 };
980 const WebPData lossy_1x1 = { lossy_1x1_bytes, sizeof(lossy_1x1_bytes) };
981 const int can_use_lossless =
982 (enc->last_config_.lossless || enc->options_.allow_mixed);
983 EncodedFrame* const curr_enc_frame = GetFrame(enc, enc->count_);
984 curr_enc_frame->is_key_frame_ = 0;
985 curr_enc_frame->sub_frame_.id = WEBP_CHUNK_ANMF;
986 curr_enc_frame->sub_frame_.x_offset = 0;
987 curr_enc_frame->sub_frame_.y_offset = 0;
988 curr_enc_frame->sub_frame_.dispose_method = WEBP_MUX_DISPOSE_NONE;
989 curr_enc_frame->sub_frame_.blend_method = WEBP_MUX_BLEND;
990 curr_enc_frame->sub_frame_.duration = duration;
991 if (!WebPDataCopy(can_use_lossless ? &lossless_1x1 : &lossy_1x1,
992 &curr_enc_frame->sub_frame_.bitstream)) {
993 return 0;
994 }
995 ++enc->count_;
996 ++enc->count_since_key_frame_;
997 enc->flush_count_ = enc->count_ - 1;
998 enc->prev_candidate_undecided_ = 0;
999 enc->prev_rect_ = rect;
1000 } else { // Regular case.
1001 // Increase duration of the previous frame by 'duration'.
1002 prev_enc_frame->sub_frame_.duration = new_duration;
1003 prev_enc_frame->key_frame_.duration = new_duration;
1004 }
1005 return 1;
1006 }
1007
1008 // Pick the candidate encoded frame with smallest size and release other
1009 // candidates.
1010 // TODO(later): Perhaps a rough SSIM/PSNR produced by the encoder should
1011 // also be a criteria, in addition to sizes.
PickBestCandidate(WebPAnimEncoder * const enc,Candidate * const candidates,int is_key_frame,EncodedFrame * const encoded_frame)1012 static void PickBestCandidate(WebPAnimEncoder* const enc,
1013 Candidate* const candidates, int is_key_frame,
1014 EncodedFrame* const encoded_frame) {
1015 int i;
1016 int best_idx = -1;
1017 size_t best_size = ~0;
1018 for (i = 0; i < CANDIDATE_COUNT; ++i) {
1019 if (candidates[i].evaluate_) {
1020 const size_t candidate_size = candidates[i].mem_.size;
1021 if (candidate_size < best_size) {
1022 best_idx = i;
1023 best_size = candidate_size;
1024 }
1025 }
1026 }
1027 assert(best_idx != -1);
1028 for (i = 0; i < CANDIDATE_COUNT; ++i) {
1029 if (candidates[i].evaluate_) {
1030 if (i == best_idx) {
1031 WebPMuxFrameInfo* const dst = is_key_frame
1032 ? &encoded_frame->key_frame_
1033 : &encoded_frame->sub_frame_;
1034 *dst = candidates[i].info_;
1035 GetEncodedData(&candidates[i].mem_, &dst->bitstream);
1036 if (!is_key_frame) {
1037 // Note: Previous dispose method only matters for non-keyframes.
1038 // Also, we don't want to modify previous dispose method that was
1039 // selected when a non key-frame was assumed.
1040 const WebPMuxAnimDispose prev_dispose_method =
1041 (best_idx == LL_DISP_NONE || best_idx == LOSSY_DISP_NONE)
1042 ? WEBP_MUX_DISPOSE_NONE
1043 : WEBP_MUX_DISPOSE_BACKGROUND;
1044 SetPreviousDisposeMethod(enc, prev_dispose_method);
1045 }
1046 enc->prev_rect_ = candidates[i].rect_; // save for next frame.
1047 } else {
1048 WebPMemoryWriterClear(&candidates[i].mem_);
1049 candidates[i].evaluate_ = 0;
1050 }
1051 }
1052 }
1053 }
1054
1055 // Depending on the configuration, tries different compressions
1056 // (lossy/lossless), dispose methods, blending methods etc to encode the current
1057 // frame and outputs the best one in 'encoded_frame'.
1058 // 'frame_skipped' will be set to true if this frame should actually be skipped.
SetFrame(WebPAnimEncoder * const enc,const WebPConfig * const config,int is_key_frame,EncodedFrame * const encoded_frame,int * const frame_skipped)1059 static WebPEncodingError SetFrame(WebPAnimEncoder* const enc,
1060 const WebPConfig* const config,
1061 int is_key_frame,
1062 EncodedFrame* const encoded_frame,
1063 int* const frame_skipped) {
1064 int i;
1065 WebPEncodingError error_code = VP8_ENC_OK;
1066 const WebPPicture* const curr_canvas = &enc->curr_canvas_copy_;
1067 const WebPPicture* const prev_canvas = &enc->prev_canvas_;
1068 Candidate candidates[CANDIDATE_COUNT];
1069 const int is_lossless = config->lossless;
1070 const int consider_lossless = is_lossless || enc->options_.allow_mixed;
1071 const int consider_lossy = !is_lossless || enc->options_.allow_mixed;
1072 const int is_first_frame = enc->is_first_frame_;
1073
1074 // First frame cannot be skipped as there is no 'previous frame' to merge it
1075 // to. So, empty rectangle is not allowed for the first frame.
1076 const int empty_rect_allowed_none = !is_first_frame;
1077
1078 // Even if there is exact pixel match between 'disposed previous canvas' and
1079 // 'current canvas', we can't skip current frame, as there may not be exact
1080 // pixel match between 'previous canvas' and 'current canvas'. So, we don't
1081 // allow empty rectangle in this case.
1082 const int empty_rect_allowed_bg = 0;
1083
1084 // If current frame is a key-frame, dispose method of previous frame doesn't
1085 // matter, so we don't try dispose to background.
1086 // Also, if key-frame insertion is on, and previous frame could be picked as
1087 // either a sub-frame or a key-frame, then we can't be sure about what frame
1088 // rectangle would be disposed. In that case too, we don't try dispose to
1089 // background.
1090 const int dispose_bg_possible =
1091 !is_key_frame && !enc->prev_candidate_undecided_;
1092
1093 SubFrameParams dispose_none_params;
1094 SubFrameParams dispose_bg_params;
1095
1096 WebPConfig config_ll = *config;
1097 WebPConfig config_lossy = *config;
1098 config_ll.lossless = 1;
1099 config_lossy.lossless = 0;
1100 enc->last_config_ = *config;
1101 enc->last_config_reversed_ = config->lossless ? config_lossy : config_ll;
1102 *frame_skipped = 0;
1103
1104 if (!SubFrameParamsInit(&dispose_none_params, 1, empty_rect_allowed_none) ||
1105 !SubFrameParamsInit(&dispose_bg_params, 0, empty_rect_allowed_bg)) {
1106 return VP8_ENC_ERROR_INVALID_CONFIGURATION;
1107 }
1108
1109 memset(candidates, 0, sizeof(candidates));
1110
1111 // Change-rectangle assuming previous frame was DISPOSE_NONE.
1112 if (!GetSubRects(prev_canvas, curr_canvas, is_key_frame, is_first_frame,
1113 config_lossy.quality, &dispose_none_params)) {
1114 error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
1115 goto Err;
1116 }
1117
1118 if ((consider_lossless && IsEmptyRect(&dispose_none_params.rect_ll_)) ||
1119 (consider_lossy && IsEmptyRect(&dispose_none_params.rect_lossy_))) {
1120 // Don't encode the frame at all. Instead, the duration of the previous
1121 // frame will be increased later.
1122 assert(empty_rect_allowed_none);
1123 *frame_skipped = 1;
1124 goto End;
1125 }
1126
1127 if (dispose_bg_possible) {
1128 // Change-rectangle assuming previous frame was DISPOSE_BACKGROUND.
1129 WebPPicture* const prev_canvas_disposed = &enc->prev_canvas_disposed_;
1130 WebPCopyPixels(prev_canvas, prev_canvas_disposed);
1131 DisposeFrameRectangle(WEBP_MUX_DISPOSE_BACKGROUND, &enc->prev_rect_,
1132 prev_canvas_disposed);
1133
1134 if (!GetSubRects(prev_canvas_disposed, curr_canvas, is_key_frame,
1135 is_first_frame, config_lossy.quality,
1136 &dispose_bg_params)) {
1137 error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
1138 goto Err;
1139 }
1140 assert(!IsEmptyRect(&dispose_bg_params.rect_ll_));
1141 assert(!IsEmptyRect(&dispose_bg_params.rect_lossy_));
1142
1143 if (enc->options_.minimize_size) { // Try both dispose methods.
1144 dispose_bg_params.should_try_ = 1;
1145 dispose_none_params.should_try_ = 1;
1146 } else if ((is_lossless &&
1147 RectArea(&dispose_bg_params.rect_ll_) <
1148 RectArea(&dispose_none_params.rect_ll_)) ||
1149 (!is_lossless &&
1150 RectArea(&dispose_bg_params.rect_lossy_) <
1151 RectArea(&dispose_none_params.rect_lossy_))) {
1152 dispose_bg_params.should_try_ = 1; // Pick DISPOSE_BACKGROUND.
1153 dispose_none_params.should_try_ = 0;
1154 }
1155 }
1156
1157 if (dispose_none_params.should_try_) {
1158 error_code = GenerateCandidates(
1159 enc, candidates, WEBP_MUX_DISPOSE_NONE, is_lossless, is_key_frame,
1160 &dispose_none_params, &config_ll, &config_lossy);
1161 if (error_code != VP8_ENC_OK) goto Err;
1162 }
1163
1164 if (dispose_bg_params.should_try_) {
1165 assert(!enc->is_first_frame_);
1166 assert(dispose_bg_possible);
1167 error_code = GenerateCandidates(
1168 enc, candidates, WEBP_MUX_DISPOSE_BACKGROUND, is_lossless, is_key_frame,
1169 &dispose_bg_params, &config_ll, &config_lossy);
1170 if (error_code != VP8_ENC_OK) goto Err;
1171 }
1172
1173 PickBestCandidate(enc, candidates, is_key_frame, encoded_frame);
1174
1175 goto End;
1176
1177 Err:
1178 for (i = 0; i < CANDIDATE_COUNT; ++i) {
1179 if (candidates[i].evaluate_) {
1180 WebPMemoryWriterClear(&candidates[i].mem_);
1181 }
1182 }
1183
1184 End:
1185 SubFrameParamsFree(&dispose_none_params);
1186 SubFrameParamsFree(&dispose_bg_params);
1187 return error_code;
1188 }
1189
1190 // Calculate the penalty incurred if we encode given frame as a key frame
1191 // instead of a sub-frame.
KeyFramePenalty(const EncodedFrame * const encoded_frame)1192 static int64_t KeyFramePenalty(const EncodedFrame* const encoded_frame) {
1193 return ((int64_t)encoded_frame->key_frame_.bitstream.size -
1194 encoded_frame->sub_frame_.bitstream.size);
1195 }
1196
CacheFrame(WebPAnimEncoder * const enc,const WebPConfig * const config)1197 static int CacheFrame(WebPAnimEncoder* const enc,
1198 const WebPConfig* const config) {
1199 int ok = 0;
1200 int frame_skipped = 0;
1201 WebPEncodingError error_code = VP8_ENC_OK;
1202 const size_t position = enc->count_;
1203 EncodedFrame* const encoded_frame = GetFrame(enc, position);
1204
1205 ++enc->count_;
1206
1207 if (enc->is_first_frame_) { // Add this as a key-frame.
1208 error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped);
1209 if (error_code != VP8_ENC_OK) goto End;
1210 assert(frame_skipped == 0); // First frame can't be skipped, even if empty.
1211 assert(position == 0 && enc->count_ == 1);
1212 encoded_frame->is_key_frame_ = 1;
1213 enc->flush_count_ = 0;
1214 enc->count_since_key_frame_ = 0;
1215 enc->prev_candidate_undecided_ = 0;
1216 } else {
1217 ++enc->count_since_key_frame_;
1218 if (enc->count_since_key_frame_ <= enc->options_.kmin) {
1219 // Add this as a frame rectangle.
1220 error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped);
1221 if (error_code != VP8_ENC_OK) goto End;
1222 if (frame_skipped) goto Skip;
1223 encoded_frame->is_key_frame_ = 0;
1224 enc->flush_count_ = enc->count_ - 1;
1225 enc->prev_candidate_undecided_ = 0;
1226 } else {
1227 int64_t curr_delta;
1228 FrameRectangle prev_rect_key, prev_rect_sub;
1229
1230 // Add this as a frame rectangle to enc.
1231 error_code = SetFrame(enc, config, 0, encoded_frame, &frame_skipped);
1232 if (error_code != VP8_ENC_OK) goto End;
1233 if (frame_skipped) goto Skip;
1234 prev_rect_sub = enc->prev_rect_;
1235
1236
1237 // Add this as a key-frame to enc, too.
1238 error_code = SetFrame(enc, config, 1, encoded_frame, &frame_skipped);
1239 if (error_code != VP8_ENC_OK) goto End;
1240 assert(frame_skipped == 0); // Key-frame cannot be an empty rectangle.
1241 prev_rect_key = enc->prev_rect_;
1242
1243 // Analyze size difference of the two variants.
1244 curr_delta = KeyFramePenalty(encoded_frame);
1245 if (curr_delta <= enc->best_delta_) { // Pick this as the key-frame.
1246 if (enc->keyframe_ != KEYFRAME_NONE) {
1247 EncodedFrame* const old_keyframe = GetFrame(enc, enc->keyframe_);
1248 assert(old_keyframe->is_key_frame_);
1249 old_keyframe->is_key_frame_ = 0;
1250 }
1251 encoded_frame->is_key_frame_ = 1;
1252 enc->prev_candidate_undecided_ = 1;
1253 enc->keyframe_ = (int)position;
1254 enc->best_delta_ = curr_delta;
1255 enc->flush_count_ = enc->count_ - 1; // We can flush previous frames.
1256 } else {
1257 encoded_frame->is_key_frame_ = 0;
1258 enc->prev_candidate_undecided_ = 0;
1259 }
1260 // Note: We need '>=' below because when kmin and kmax are both zero,
1261 // count_since_key_frame will always be > kmax.
1262 if (enc->count_since_key_frame_ >= enc->options_.kmax) {
1263 enc->flush_count_ = enc->count_ - 1;
1264 enc->count_since_key_frame_ = 0;
1265 enc->keyframe_ = KEYFRAME_NONE;
1266 enc->best_delta_ = DELTA_INFINITY;
1267 }
1268 if (!enc->prev_candidate_undecided_) {
1269 enc->prev_rect_ =
1270 encoded_frame->is_key_frame_ ? prev_rect_key : prev_rect_sub;
1271 }
1272 }
1273 }
1274
1275 // Update previous to previous and previous canvases for next call.
1276 WebPCopyPixels(enc->curr_canvas_, &enc->prev_canvas_);
1277 enc->is_first_frame_ = 0;
1278
1279 Skip:
1280 ok = 1;
1281 ++enc->in_frame_count_;
1282
1283 End:
1284 if (!ok || frame_skipped) {
1285 FrameRelease(encoded_frame);
1286 // We reset some counters, as the frame addition failed/was skipped.
1287 --enc->count_;
1288 if (!enc->is_first_frame_) --enc->count_since_key_frame_;
1289 if (!ok) {
1290 MarkError2(enc, "ERROR adding frame. WebPEncodingError", error_code);
1291 }
1292 }
1293 enc->curr_canvas_->error_code = error_code; // report error_code
1294 assert(ok || error_code != VP8_ENC_OK);
1295 return ok;
1296 }
1297
FlushFrames(WebPAnimEncoder * const enc)1298 static int FlushFrames(WebPAnimEncoder* const enc) {
1299 while (enc->flush_count_ > 0) {
1300 WebPMuxError err;
1301 EncodedFrame* const curr = GetFrame(enc, 0);
1302 const WebPMuxFrameInfo* const info =
1303 curr->is_key_frame_ ? &curr->key_frame_ : &curr->sub_frame_;
1304 assert(enc->mux_ != NULL);
1305 err = WebPMuxPushFrame(enc->mux_, info, 1);
1306 if (err != WEBP_MUX_OK) {
1307 MarkError2(enc, "ERROR adding frame. WebPMuxError", err);
1308 return 0;
1309 }
1310 if (enc->options_.verbose) {
1311 fprintf(stderr, "INFO: Added frame. offset:%d,%d dispose:%d blend:%d\n",
1312 info->x_offset, info->y_offset, info->dispose_method,
1313 info->blend_method);
1314 }
1315 ++enc->out_frame_count_;
1316 FrameRelease(curr);
1317 ++enc->start_;
1318 --enc->flush_count_;
1319 --enc->count_;
1320 if (enc->keyframe_ != KEYFRAME_NONE) --enc->keyframe_;
1321 }
1322
1323 if (enc->count_ == 1 && enc->start_ != 0) {
1324 // Move enc->start to index 0.
1325 const int enc_start_tmp = (int)enc->start_;
1326 EncodedFrame temp = enc->encoded_frames_[0];
1327 enc->encoded_frames_[0] = enc->encoded_frames_[enc_start_tmp];
1328 enc->encoded_frames_[enc_start_tmp] = temp;
1329 FrameRelease(&enc->encoded_frames_[enc_start_tmp]);
1330 enc->start_ = 0;
1331 }
1332 return 1;
1333 }
1334
1335 #undef DELTA_INFINITY
1336 #undef KEYFRAME_NONE
1337
WebPAnimEncoderAdd(WebPAnimEncoder * enc,WebPPicture * frame,int timestamp,const WebPConfig * encoder_config)1338 int WebPAnimEncoderAdd(WebPAnimEncoder* enc, WebPPicture* frame, int timestamp,
1339 const WebPConfig* encoder_config) {
1340 WebPConfig config;
1341 int ok;
1342
1343 if (enc == NULL) {
1344 return 0;
1345 }
1346 MarkNoError(enc);
1347
1348 if (!enc->is_first_frame_) {
1349 // Make sure timestamps are non-decreasing (integer wrap-around is OK).
1350 const uint32_t prev_frame_duration =
1351 (uint32_t)timestamp - enc->prev_timestamp_;
1352 if (prev_frame_duration >= MAX_DURATION) {
1353 if (frame != NULL) {
1354 frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
1355 }
1356 MarkError(enc, "ERROR adding frame: timestamps must be non-decreasing");
1357 return 0;
1358 }
1359 if (!IncreasePreviousDuration(enc, (int)prev_frame_duration)) {
1360 return 0;
1361 }
1362 // IncreasePreviousDuration() may add a frame to avoid exceeding
1363 // MAX_DURATION which could cause CacheFrame() to over read encoded_frames_
1364 // before the next flush.
1365 if (enc->count_ == enc->size_ && !FlushFrames(enc)) {
1366 return 0;
1367 }
1368 } else {
1369 enc->first_timestamp_ = timestamp;
1370 }
1371
1372 if (frame == NULL) { // Special: last call.
1373 enc->got_null_frame_ = 1;
1374 enc->prev_timestamp_ = timestamp;
1375 return 1;
1376 }
1377
1378 if (frame->width != enc->canvas_width_ ||
1379 frame->height != enc->canvas_height_) {
1380 frame->error_code = VP8_ENC_ERROR_INVALID_CONFIGURATION;
1381 MarkError(enc, "ERROR adding frame: Invalid frame dimensions");
1382 return 0;
1383 }
1384
1385 if (!frame->use_argb) { // Convert frame from YUV(A) to ARGB.
1386 if (enc->options_.verbose) {
1387 fprintf(stderr, "WARNING: Converting frame from YUV(A) to ARGB format; "
1388 "this incurs a small loss.\n");
1389 }
1390 if (!WebPPictureYUVAToARGB(frame)) {
1391 MarkError(enc, "ERROR converting frame from YUV(A) to ARGB");
1392 return 0;
1393 }
1394 }
1395
1396 if (encoder_config != NULL) {
1397 if (!WebPValidateConfig(encoder_config)) {
1398 MarkError(enc, "ERROR adding frame: Invalid WebPConfig");
1399 return 0;
1400 }
1401 config = *encoder_config;
1402 } else {
1403 if (!WebPConfigInit(&config)) {
1404 MarkError(enc, "Cannot Init config");
1405 return 0;
1406 }
1407 config.lossless = 1;
1408 }
1409 assert(enc->curr_canvas_ == NULL);
1410 enc->curr_canvas_ = frame; // Store reference.
1411 assert(enc->curr_canvas_copy_modified_ == 1);
1412 CopyCurrentCanvas(enc);
1413
1414 ok = CacheFrame(enc, &config) && FlushFrames(enc);
1415
1416 enc->curr_canvas_ = NULL;
1417 enc->curr_canvas_copy_modified_ = 1;
1418 if (ok) {
1419 enc->prev_timestamp_ = timestamp;
1420 }
1421 return ok;
1422 }
1423
1424 // -----------------------------------------------------------------------------
1425 // Bitstream assembly.
1426
DecodeFrameOntoCanvas(const WebPMuxFrameInfo * const frame,WebPPicture * const canvas)1427 WEBP_NODISCARD static int DecodeFrameOntoCanvas(
1428 const WebPMuxFrameInfo* const frame, WebPPicture* const canvas) {
1429 const WebPData* const image = &frame->bitstream;
1430 WebPPicture sub_image;
1431 WebPDecoderConfig config;
1432 if (!WebPInitDecoderConfig(&config)) {
1433 return 0;
1434 }
1435 WebPUtilClearPic(canvas, NULL);
1436 if (WebPGetFeatures(image->bytes, image->size, &config.input) !=
1437 VP8_STATUS_OK) {
1438 return 0;
1439 }
1440 if (!WebPPictureView(canvas, frame->x_offset, frame->y_offset,
1441 config.input.width, config.input.height, &sub_image)) {
1442 return 0;
1443 }
1444 config.output.is_external_memory = 1;
1445 config.output.colorspace = MODE_BGRA;
1446 config.output.u.RGBA.rgba = (uint8_t*)sub_image.argb;
1447 config.output.u.RGBA.stride = sub_image.argb_stride * 4;
1448 config.output.u.RGBA.size = config.output.u.RGBA.stride * sub_image.height;
1449
1450 if (WebPDecode(image->bytes, image->size, &config) != VP8_STATUS_OK) {
1451 return 0;
1452 }
1453 return 1;
1454 }
1455
FrameToFullCanvas(WebPAnimEncoder * const enc,const WebPMuxFrameInfo * const frame,WebPData * const full_image)1456 static int FrameToFullCanvas(WebPAnimEncoder* const enc,
1457 const WebPMuxFrameInfo* const frame,
1458 WebPData* const full_image) {
1459 WebPPicture* const canvas_buf = &enc->curr_canvas_copy_;
1460 WebPMemoryWriter mem1, mem2;
1461 WebPMemoryWriterInit(&mem1);
1462 WebPMemoryWriterInit(&mem2);
1463
1464 if (!DecodeFrameOntoCanvas(frame, canvas_buf)) goto Err;
1465 if (!EncodeFrame(&enc->last_config_, canvas_buf, &mem1)) goto Err;
1466 GetEncodedData(&mem1, full_image);
1467
1468 if (enc->options_.allow_mixed) {
1469 if (!EncodeFrame(&enc->last_config_reversed_, canvas_buf, &mem2)) goto Err;
1470 if (mem2.size < mem1.size) {
1471 GetEncodedData(&mem2, full_image);
1472 WebPMemoryWriterClear(&mem1);
1473 } else {
1474 WebPMemoryWriterClear(&mem2);
1475 }
1476 }
1477 return 1;
1478
1479 Err:
1480 WebPMemoryWriterClear(&mem1);
1481 WebPMemoryWriterClear(&mem2);
1482 return 0;
1483 }
1484
1485 // Convert a single-frame animation to a non-animated image if appropriate.
1486 // TODO(urvang): Can we pick one of the two heuristically (based on frame
1487 // rectangle and/or presence of alpha)?
OptimizeSingleFrame(WebPAnimEncoder * const enc,WebPData * const webp_data)1488 static WebPMuxError OptimizeSingleFrame(WebPAnimEncoder* const enc,
1489 WebPData* const webp_data) {
1490 WebPMuxError err = WEBP_MUX_OK;
1491 int canvas_width, canvas_height;
1492 WebPMuxFrameInfo frame;
1493 WebPData full_image;
1494 WebPData webp_data2;
1495 WebPMux* const mux = WebPMuxCreate(webp_data, 0);
1496 if (mux == NULL) return WEBP_MUX_BAD_DATA;
1497 assert(enc->out_frame_count_ == 1);
1498 WebPDataInit(&frame.bitstream);
1499 WebPDataInit(&full_image);
1500 WebPDataInit(&webp_data2);
1501
1502 err = WebPMuxGetFrame(mux, 1, &frame);
1503 if (err != WEBP_MUX_OK) goto End;
1504 if (frame.id != WEBP_CHUNK_ANMF) goto End; // Non-animation: nothing to do.
1505 err = WebPMuxGetCanvasSize(mux, &canvas_width, &canvas_height);
1506 if (err != WEBP_MUX_OK) goto End;
1507 if (!FrameToFullCanvas(enc, &frame, &full_image)) {
1508 err = WEBP_MUX_BAD_DATA;
1509 goto End;
1510 }
1511 err = WebPMuxSetImage(mux, &full_image, 1);
1512 if (err != WEBP_MUX_OK) goto End;
1513 err = WebPMuxAssemble(mux, &webp_data2);
1514 if (err != WEBP_MUX_OK) goto End;
1515
1516 if (webp_data2.size < webp_data->size) { // Pick 'webp_data2' if smaller.
1517 WebPDataClear(webp_data);
1518 *webp_data = webp_data2;
1519 WebPDataInit(&webp_data2);
1520 }
1521
1522 End:
1523 WebPDataClear(&frame.bitstream);
1524 WebPDataClear(&full_image);
1525 WebPMuxDelete(mux);
1526 WebPDataClear(&webp_data2);
1527 return err;
1528 }
1529
WebPAnimEncoderAssemble(WebPAnimEncoder * enc,WebPData * webp_data)1530 int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, WebPData* webp_data) {
1531 WebPMux* mux;
1532 WebPMuxError err;
1533
1534 if (enc == NULL) {
1535 return 0;
1536 }
1537 MarkNoError(enc);
1538
1539 if (webp_data == NULL) {
1540 MarkError(enc, "ERROR assembling: NULL input");
1541 return 0;
1542 }
1543
1544 if (enc->in_frame_count_ == 0) {
1545 MarkError(enc, "ERROR: No frames to assemble");
1546 return 0;
1547 }
1548
1549 if (!enc->got_null_frame_ && enc->in_frame_count_ > 1 && enc->count_ > 0) {
1550 // set duration of the last frame to be avg of durations of previous frames.
1551 const double delta_time =
1552 (uint32_t)enc->prev_timestamp_ - enc->first_timestamp_;
1553 const int average_duration = (int)(delta_time / (enc->in_frame_count_ - 1));
1554 if (!IncreasePreviousDuration(enc, average_duration)) {
1555 return 0;
1556 }
1557 }
1558
1559 // Flush any remaining frames.
1560 enc->flush_count_ = enc->count_;
1561 if (!FlushFrames(enc)) {
1562 return 0;
1563 }
1564
1565 // Set definitive canvas size.
1566 mux = enc->mux_;
1567 err = WebPMuxSetCanvasSize(mux, enc->canvas_width_, enc->canvas_height_);
1568 if (err != WEBP_MUX_OK) goto Err;
1569
1570 err = WebPMuxSetAnimationParams(mux, &enc->options_.anim_params);
1571 if (err != WEBP_MUX_OK) goto Err;
1572
1573 // Assemble into a WebP bitstream.
1574 err = WebPMuxAssemble(mux, webp_data);
1575 if (err != WEBP_MUX_OK) goto Err;
1576
1577 if (enc->out_frame_count_ == 1) {
1578 err = OptimizeSingleFrame(enc, webp_data);
1579 if (err != WEBP_MUX_OK) goto Err;
1580 }
1581 return 1;
1582
1583 Err:
1584 MarkError2(enc, "ERROR assembling WebP", err);
1585 return 0;
1586 }
1587
WebPAnimEncoderGetError(WebPAnimEncoder * enc)1588 const char* WebPAnimEncoderGetError(WebPAnimEncoder* enc) {
1589 if (enc == NULL) return NULL;
1590 return enc->error_str_;
1591 }
1592
WebPAnimEncoderSetChunk(WebPAnimEncoder * enc,const char fourcc[4],const WebPData * chunk_data,int copy_data)1593 WebPMuxError WebPAnimEncoderSetChunk(
1594 WebPAnimEncoder* enc, const char fourcc[4], const WebPData* chunk_data,
1595 int copy_data) {
1596 if (enc == NULL) return WEBP_MUX_INVALID_ARGUMENT;
1597 return WebPMuxSetChunk(enc->mux_, fourcc, chunk_data, copy_data);
1598 }
1599
WebPAnimEncoderGetChunk(const WebPAnimEncoder * enc,const char fourcc[4],WebPData * chunk_data)1600 WebPMuxError WebPAnimEncoderGetChunk(
1601 const WebPAnimEncoder* enc, const char fourcc[4], WebPData* chunk_data) {
1602 if (enc == NULL) return WEBP_MUX_INVALID_ARGUMENT;
1603 return WebPMuxGetChunk(enc->mux_, fourcc, chunk_data);
1604 }
1605
WebPAnimEncoderDeleteChunk(WebPAnimEncoder * enc,const char fourcc[4])1606 WebPMuxError WebPAnimEncoderDeleteChunk(
1607 WebPAnimEncoder* enc, const char fourcc[4]) {
1608 if (enc == NULL) return WEBP_MUX_INVALID_ARGUMENT;
1609 return WebPMuxDeleteChunk(enc->mux_, fourcc);
1610 }
1611
1612 // -----------------------------------------------------------------------------
1613