anim_encode.c 57 KB

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