blob: 03b94bfeb60a8031ca79f6c8f821414d3e003c58 [file] [log] [blame]
Urvang Joshid538cea2013-09-12 13:41:09 -07001// Copyright 2013 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// Helper structs and methods for gif2webp tool.
11//
12
13#include <assert.h>
14#include <stdio.h>
15
16#include "webp/encode.h"
17#include "./gif2webp_util.h"
18
19#define DELTA_INFINITY 1ULL << 32
20#define KEYFRAME_NONE -1
21
22//------------------------------------------------------------------------------
23// Encoded frame.
24
25// Used to store two candidates of encoded data for an animation frame. One of
26// the two will be chosen later.
27typedef struct {
28 WebPMuxFrameInfo sub_frame; // Encoded frame rectangle.
29 WebPMuxFrameInfo key_frame; // Encoded frame if it was converted to keyframe.
30} EncodedFrame;
31
32// Release the data contained by 'encoded_frame'.
33static void FrameRelease(EncodedFrame* const encoded_frame) {
34 WebPDataClear(&encoded_frame->sub_frame.bitstream);
35 WebPDataClear(&encoded_frame->key_frame.bitstream);
36 memset(encoded_frame, 0, sizeof(*encoded_frame));
37}
38
39//------------------------------------------------------------------------------
40// Frame cache.
41
42// Used to store encoded frames that haven't been output yet.
43struct WebPFrameCache {
44 EncodedFrame* encoded_frames; // Array of encoded frames.
45 size_t size; // Number of allocated data elements.
46 size_t start; // Start index.
47 size_t count; // Number of valid data elements.
48 int flush_count; // If >0, ‘flush_count’ frames starting from
49 // 'start' are ready to be added to mux.
50 int64_t best_delta; // min(canvas size - frame size) over the frames.
51 // Can be negative in certain cases due to
52 // transparent pixels in a frame.
53 int keyframe; // Index of selected keyframe relative to 'start'.
54
55 size_t kmin; // Min distance between key frames.
56 size_t kmax; // Max distance between key frames.
57 size_t count_since_key_frame; // Frames seen since the last key frame.
58};
59
60// Reset the counters in the cache struct. Doesn't touch 'cache->encoded_frames'
61// and 'cache->size'.
62static void CacheReset(WebPFrameCache* const cache) {
63 cache->start = 0;
64 cache->count = 0;
65 cache->flush_count = 0;
66 cache->best_delta = DELTA_INFINITY;
67 cache->keyframe = KEYFRAME_NONE;
68}
69
70WebPFrameCache* WebPFrameCacheNew(size_t kmin, size_t kmax) {
71 WebPFrameCache* cache = (WebPFrameCache*)malloc(sizeof(*cache));
72 if (cache == NULL) return NULL;
73 CacheReset(cache);
74 cache->kmin = kmin;
75 cache->kmax = kmax;
76 cache->count_since_key_frame = 0;
Urvang Joshid78a82c2013-09-16 13:35:13 -070077 assert(kmax > kmin);
Urvang Joshid538cea2013-09-12 13:41:09 -070078 cache->size = kmax - kmin;
79 cache->encoded_frames =
80 (EncodedFrame*)calloc(cache->size, sizeof(*cache->encoded_frames));
Urvang Joshie89c6fc2013-09-16 13:12:33 -070081 if (cache->encoded_frames == NULL) {
82 free(cache);
83 return NULL;
84 }
Urvang Joshid538cea2013-09-12 13:41:09 -070085 return cache;
86}
87
88void WebPFrameCacheDelete(WebPFrameCache* const cache) {
89 if (cache != NULL) {
90 size_t i;
91 for (i = 0; i < cache->size; ++i) {
92 FrameRelease(&cache->encoded_frames[i]);
93 }
94 free(cache->encoded_frames);
95 free(cache);
96 }
97}
98
99static int EncodeFrame(const WebPConfig* const config, WebPPicture* const pic,
100 WebPData* const encoded_data) {
101 WebPMemoryWriter memory;
102 pic->use_argb = 1;
103 pic->writer = WebPMemoryWrite;
104 pic->custom_ptr = &memory;
105 WebPMemoryWriterInit(&memory);
106 if (!WebPEncode(config, pic)) {
107 return 0;
108 }
109 encoded_data->bytes = memory.mem;
110 encoded_data->size = memory.size;
111 return 1;
112}
113
Pascal Massimino2f5e8932013-09-14 02:02:09 -0700114// Returns cached frame at given 'position' index.
Urvang Joshid538cea2013-09-12 13:41:09 -0700115static EncodedFrame* CacheGetFrame(const WebPFrameCache* const cache,
Pascal Massimino2f5e8932013-09-14 02:02:09 -0700116 size_t position) {
117 assert(cache->start + position < cache->size);
118 return &cache->encoded_frames[cache->start + position];
Urvang Joshid538cea2013-09-12 13:41:09 -0700119}
120
121// Calculate the penalty incurred if we encode given frame as a key frame
122// instead of a sub-frame.
123static int64_t KeyFramePenalty(const EncodedFrame* const encoded_frame) {
124 return ((int64_t)encoded_frame->key_frame.bitstream.size -
125 encoded_frame->sub_frame.bitstream.size);
126}
127
128static int SetFrame(const WebPConfig* const config,
129 const WebPMuxFrameInfo* const info, WebPPicture* const pic,
130 WebPMuxFrameInfo* const dst) {
131 *dst = *info;
132 if (!EncodeFrame(config, pic, &dst->bitstream)) {
133 return 0;
134 }
135 return 1;
136}
137
138int WebPFrameCacheAddFrame(WebPFrameCache* const cache,
139 const WebPConfig* const config,
140 const WebPMuxFrameInfo* const sub_frame_info,
141 WebPPicture* const sub_frame_pic,
142 const WebPMuxFrameInfo* const key_frame_info,
143 WebPPicture* const key_frame_pic) {
Pascal Massimino2f5e8932013-09-14 02:02:09 -0700144 const size_t position = cache->count;
145 EncodedFrame* const encoded_frame = CacheGetFrame(cache, position);
146 assert(position < cache->size);
Urvang Joshid538cea2013-09-12 13:41:09 -0700147 assert(sub_frame_pic != NULL || key_frame_pic != NULL);
148 if (sub_frame_pic != NULL && !SetFrame(config, sub_frame_info, sub_frame_pic,
149 &encoded_frame->sub_frame)) {
150 return 0;
151 }
152 if (key_frame_pic != NULL && !SetFrame(config, key_frame_info, key_frame_pic,
153 &encoded_frame->key_frame)) {
154 return 0;
155 }
156
157 ++cache->count;
158
159 if (sub_frame_pic == NULL && key_frame_pic != NULL) { // Keyframe.
Pascal Massimino2f5e8932013-09-14 02:02:09 -0700160 cache->keyframe = position;
Urvang Joshid538cea2013-09-12 13:41:09 -0700161 cache->flush_count = cache->count;
162 cache->count_since_key_frame = 0;
163 } else {
164 ++cache->count_since_key_frame;
165 if (sub_frame_pic != NULL && key_frame_pic == NULL) { // Non-keyframe.
166 assert(cache->count_since_key_frame < cache->kmax);
167 cache->flush_count = cache->count;
168 } else { // Analyze size difference of the two variants.
169 const int64_t curr_delta = KeyFramePenalty(encoded_frame);
170 if (curr_delta <= cache->best_delta) { // Pick this as keyframe.
Pascal Massimino2f5e8932013-09-14 02:02:09 -0700171 cache->keyframe = position;
Urvang Joshid538cea2013-09-12 13:41:09 -0700172 cache->best_delta = curr_delta;
173 cache->flush_count = cache->count - 1; // We can flush previous frames.
174 }
175 if (cache->count_since_key_frame == cache->kmax) {
176 cache->flush_count = cache->count;
177 cache->count_since_key_frame = 0;
178 }
179 }
180 }
181
182 return 1;
183}
184
185WebPMuxError WebPFrameCacheFlush(WebPFrameCache* const cache, int verbose,
186 WebPMux* const mux) {
187 while (cache->flush_count > 0) {
188 WebPMuxFrameInfo* info;
189 WebPMuxError err;
190 EncodedFrame* const curr = CacheGetFrame(cache, 0);
191 // Pick frame or full canvas.
192 if (cache->keyframe == 0) {
193 info = &curr->key_frame;
194 info->blend_method = WEBP_MUX_NO_BLEND;
195 cache->keyframe = KEYFRAME_NONE;
196 cache->best_delta = DELTA_INFINITY;
197 } else {
198 info = &curr->sub_frame;
199 info->blend_method = WEBP_MUX_BLEND;
200 }
201 // Add to mux.
202 err = WebPMuxPushFrame(mux, info, 1);
203 if (err != WEBP_MUX_OK) return err;
204 if (verbose) {
205 printf("Added frame. offset:%d,%d duration:%d dispose:%d blend:%d\n",
206 info->x_offset, info->y_offset, info->duration,
207 info->dispose_method, info->blend_method);
208 }
209 FrameRelease(curr);
210 ++cache->start;
211 --cache->flush_count;
212 --cache->count;
213 if (cache->keyframe != KEYFRAME_NONE) --cache->keyframe;
214 }
215
216 if (cache->count == 0) CacheReset(cache);
217 return WEBP_MUX_OK;
218}
219
220WebPMuxError WebPFrameCacheFlushAll(WebPFrameCache* const cache, int verbose,
221 WebPMux* const mux) {
222 cache->flush_count = cache->count; // Force flushing of all frames.
223 return WebPFrameCacheFlush(cache, verbose, mux);
224}
225
226int WebPFrameCacheShouldTryKeyFrame(const WebPFrameCache* const cache) {
227 return cache->count_since_key_frame >= cache->kmin;
228}
229
230//------------------------------------------------------------------------------
231// Frame rectangle and related utilities.
232
233static void ClearRectangle(WebPPicture* const picture,
234 int left, int top, int width, int height) {
235 int j;
236 for (j = top; j < top + height; ++j) {
237 uint32_t* const dst = picture->argb + j * picture->argb_stride;
238 int i;
239 for (i = left; i < left + width; ++i) {
240 dst[i] = TRANSPARENT_COLOR;
241 }
242 }
243}
244
245// Clear pixels in 'picture' within given 'rect' to transparent color.
246void WebPUtilClearPic(WebPPicture* const picture,
247 const WebPFrameRect* const rect) {
248 if (rect != NULL) {
249 ClearRectangle(picture, rect->x_offset, rect->y_offset,
250 rect->width, rect->height);
251 } else {
252 ClearRectangle(picture, 0, 0, picture->width, picture->height);
253 }
254}
255
256// TODO: Also used in picture.c. Move to a common location?
257// Copy width x height pixels from 'src' to 'dst' honoring the strides.
258static void CopyPlane(const uint8_t* src, int src_stride,
259 uint8_t* dst, int dst_stride, int width, int height) {
260 while (height-- > 0) {
261 memcpy(dst, src, width);
262 src += src_stride;
263 dst += dst_stride;
264 }
265}
266
267void WebPUtilCopyPixels(const WebPPicture* const src, WebPPicture* const dst) {
268 assert(src->width == dst->width && src->height == dst->height);
269 CopyPlane((uint8_t*)src->argb, 4 * src->argb_stride, (uint8_t*)dst->argb,
270 4 * dst->argb_stride, 4 * src->width, src->height);
271}
272
273void WebPUtilBlendPixels(const WebPPicture* const src,
274 const WebPFrameRect* const rect,
275 WebPPicture* const dst) {
276 int j;
277 assert(src->width == dst->width && src->height == dst->height);
278 for (j = rect->y_offset; j < rect->y_offset + rect->height; ++j) {
279 int i;
280 for (i = rect->x_offset; i < rect->x_offset + rect->width; ++i) {
281 const uint32_t src_pixel = src->argb[j * src->argb_stride + i];
282 const int src_alpha = src_pixel >> 24;
283 if (src_alpha != 0) {
284 dst->argb[j * dst->argb_stride + i] = src_pixel;
285 }
286 }
287 }
288}
289
Urvang Joshi606c4302013-09-30 16:48:39 -0700290void WebPUtilReduceTransparency(const WebPPicture* const src,
291 const WebPFrameRect* const rect,
292 WebPPicture* const dst) {
skal00125192013-10-08 15:04:52 +0200293 int i, j;
294 assert(src != NULL && dst != NULL && rect != NULL);
Urvang Joshi606c4302013-09-30 16:48:39 -0700295 assert(src->width == dst->width && src->height == dst->height);
296 for (j = rect->y_offset; j < rect->y_offset + rect->height; ++j) {
Urvang Joshi606c4302013-09-30 16:48:39 -0700297 for (i = rect->x_offset; i < rect->x_offset + rect->width; ++i) {
298 const uint32_t src_pixel = src->argb[j * src->argb_stride + i];
299 const int src_alpha = src_pixel >> 24;
300 const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i];
301 const int dst_alpha = dst_pixel >> 24;
302 if (dst_alpha == 0 && src_alpha == 0xff) {
303 dst->argb[j * dst->argb_stride + i] = src_pixel;
304 }
305 }
306 }
307}
308
skal00125192013-10-08 15:04:52 +0200309void WebPUtilFlattenSimilarBlocks(const WebPPicture* const src,
310 const WebPFrameRect* const rect,
311 WebPPicture* const dst) {
312 int i, j;
313 const int block_size = 8;
314 const int y_start = (rect->y_offset + block_size) & ~(block_size - 1);
315 const int y_end = (rect->y_offset + rect->height) & ~(block_size - 1);
316 const int x_start = (rect->x_offset + block_size) & ~(block_size - 1);
317 const int x_end = (rect->x_offset + rect->width) & ~(block_size - 1);
318 assert(src != NULL && dst != NULL && rect != NULL);
319 assert(src->width == dst->width && src->height == dst->height);
320 assert((block_size & (block_size - 1)) == 0); // must be a power of 2
321 // Iterate over each block and count similar pixels.
322 for (j = y_start; j < y_end; j += block_size) {
323 for (i = x_start; i < x_end; i += block_size) {
324 int cnt = 0;
325 int avg_r = 0, avg_g = 0, avg_b = 0;
326 int x, y;
327 const uint32_t* const psrc = src->argb + j * src->argb_stride + i;
328 uint32_t* const pdst = dst->argb + j * dst->argb_stride + i;
329 for (y = 0; y < block_size; ++y) {
330 for (x = 0; x < block_size; ++x) {
331 const uint32_t src_pixel = psrc[x + y * src->argb_stride];
332 const int alpha = src_pixel >> 24;
333 if (alpha == 0xff &&
334 src_pixel == pdst[x + y * dst->argb_stride]) {
335 ++cnt;
336 avg_r += (src_pixel >> 16) & 0xff;
337 avg_g += (src_pixel >> 8) & 0xff;
338 avg_b += (src_pixel >> 0) & 0xff;
339 }
340 }
341 }
342 // If we have a fully similar block, we replace it with an
343 // average transparent block. This compresses better in lossy mode.
344 if (cnt == block_size * block_size) {
345 const uint32_t color = (0x00 << 24) |
346 ((avg_r / cnt) << 16) |
347 ((avg_g / cnt) << 8) |
348 ((avg_b / cnt) << 0);
349 for (y = 0; y < block_size; ++y) {
350 for (x = 0; x < block_size; ++x) {
351 pdst[x + y * dst->argb_stride] = color;
352 }
353 }
354 }
355 }
356 }
357}
358
Urvang Joshid538cea2013-09-12 13:41:09 -0700359//------------------------------------------------------------------------------
360// Key frame related utilities.
361
362int WebPUtilIsKeyFrame(const WebPPicture* const curr,
363 const WebPFrameRect* const curr_rect,
364 const WebPPicture* const prev) {
365 int i, j;
366 int is_key_frame = 1;
367
368 // If previous canvas (with previous frame disposed) is all transparent,
369 // current frame is a key frame.
370 for (i = 0; i < prev->width; ++i) {
371 for (j = 0; j < prev->height; ++j) {
372 const uint32_t prev_alpha = (prev->argb[j * prev->argb_stride + i]) >> 24;
373 if (prev_alpha != 0) {
374 is_key_frame = 0;
375 break;
376 }
377 }
378 if (!is_key_frame) break;
379 }
380 if (is_key_frame) return 1;
381
382 // If current frame covers the whole canvas and does not contain any
383 // transparent pixels that depend on previous canvas, then current frame is
384 // a key frame.
385 if (curr_rect->width == curr->width && curr_rect->height == curr->height) {
386 assert(curr_rect->x_offset == 0 && curr_rect->y_offset == 0);
387 is_key_frame = 1;
388 for (j = 0; j < prev->height; ++j) {
389 for (i = 0; i < prev->width; ++i) {
390 const uint32_t prev_alpha =
391 (prev->argb[j * prev->argb_stride + i]) >> 24;
392 const uint32_t curr_alpha =
393 (curr->argb[j * curr->argb_stride + i]) >> 24;
394 if (curr_alpha != 0xff && prev_alpha != 0) {
395 is_key_frame = 0;
396 break;
397 }
398 }
399 if (!is_key_frame) break;
400 }
401 if (is_key_frame) return 1;
402 }
403
404 return 0;
405}
406
407void WebPUtilConvertToKeyFrame(const WebPPicture* const prev,
408 WebPFrameRect* const rect,
409 WebPPicture* const curr) {
410 int j;
411 assert(curr->width == prev->width && curr->height == prev->height);
412
413 // Replace transparent pixels of current canvas with those from previous
414 // canvas (with previous frame disposed).
415 for (j = 0; j < curr->height; ++j) {
416 int i;
417 for (i = 0; i < curr->width; ++i) {
418 uint32_t* const curr_pixel = curr->argb + j * curr->argb_stride + i;
419 const int curr_alpha = *curr_pixel >> 24;
420 if (curr_alpha == 0) {
421 *curr_pixel = prev->argb[j * prev->argb_stride + i];
422 }
423 }
424 }
425
426 // Frame rectangle now covers the whole canvas.
427 rect->x_offset = 0;
428 rect->y_offset = 0;
429 rect->width = curr->width;
430 rect->height = curr->height;
431}
432
433//------------------------------------------------------------------------------