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