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