Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/libwebp/src/mux/anim_encode.c
21080 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
#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(&params->sub_frame_ll) ||
525
!WebPPictureInit(&params->sub_frame_lossy)) {
526
return 0;
527
}
528
return 1;
529
}
530
531
static void SubFrameParamsFree(SubFrameParams* const params) {
532
WebPPictureFree(&params->sub_frame_ll);
533
WebPPictureFree(&params->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
&params->rect_ll, &params->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
&params->rect_lossy, &params->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, &params->rect_ll);
875
use_blending_lossy =
876
!is_key_frame &&
877
IsLossyBlendingPossible(prev_canvas, curr_canvas, &params->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(&params->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, &params->rect_ll, curr_canvas);
899
}
900
error_code = EncodeCandidate(&params->sub_frame_ll, &params->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, &params->rect_lossy, curr_canvas,
909
config_lossy->quality);
910
}
911
error_code =
912
EncodeCandidate(&params->sub_frame_lossy, &params->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
// -----------------------------------------------------------------------------
1615
1616