• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&params->sub_frame_ll_) ||
523       !WebPPictureInit(&params->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(&params->sub_frame_ll_);
531   WebPPictureFree(&params->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                   &params->rect_ll_, &params->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                     &params->rect_lossy_, &params->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, &params->rect_ll_);
873   use_blending_lossy =
874       !is_key_frame &&
875       IsLossyBlendingPossible(prev_canvas, curr_canvas, &params->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(&params->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, &params->rect_ll_, curr_canvas);
897     }
898     error_code = EncodeCandidate(&params->sub_frame_ll_, &params->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, &params->rect_lossy_, curr_canvas,
907                                config_lossy->quality);
908     }
909     error_code =
910         EncodeCandidate(&params->sub_frame_lossy_, &params->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