diff --git a/examples/gif2webp.c b/examples/gif2webp.c index 31e16379..e9b5806b 100644 --- a/examples/gif2webp.c +++ b/examples/gif2webp.c @@ -46,18 +46,7 @@ //------------------------------------------------------------------------------ -static int transparent_index; // Index of transparent color in the map. - -static void ResetFrameInfo(WebPMuxFrameInfo* const info) { - WebPDataInit(&info->bitstream); - info->x_offset = 0; - info->y_offset = 0; - info->duration = 0; - info->id = WEBP_CHUNK_ANMF; - info->dispose_method = WEBP_MUX_DISPOSE_NONE; - info->blend_method = WEBP_MUX_BLEND; - transparent_index = -1; // Opaque frame by default. -} +static int transparent_index = -1; // Opaque frame by default. static void SanitizeKeyFrameIntervals(size_t* const kmin_ptr, size_t* const kmax_ptr) { @@ -270,7 +259,8 @@ int main(int argc, const char *argv[]) { GifFileType* gif = NULL; WebPConfig config; WebPPicture frame; - WebPMuxFrameInfo info; + int duration = 0; + FrameDisposeMethod orig_dispose = FRAME_DISPOSE_NONE; WebPMuxAnimParams anim = { WHITE_COLOR, 0 }; WebPFrameCache* cache = NULL; @@ -290,8 +280,6 @@ int main(int argc, const char *argv[]) { size_t kmax = 0; int allow_mixed = 0; // If true, each frame can be lossy or lossless. - ResetFrameInfo(&info); - if (!WebPConfigInit(&config) || !WebPPictureInit(&frame)) { fprintf(stderr, "Error! Version mismatch!\n"); return -1; @@ -499,7 +487,8 @@ int main(int argc, const char *argv[]) { goto End; } - if (!WebPFrameCacheAddFrame(cache, &config, &gif_rect, &frame, &info)) { + if (!WebPFrameCacheAddFrame(cache, &config, &gif_rect, orig_dispose, + duration, &frame)) { fprintf(stderr, "Error! Cannot encode frame as WebP\n"); fprintf(stderr, "Error code: %d\n", frame.error_code); } @@ -515,7 +504,9 @@ int main(int argc, const char *argv[]) { // In GIF, graphic control extensions are optional for a frame, so we // may not get one before reading the next frame. To handle this case, // we reset frame properties to reasonable defaults for the next frame. - ResetFrameInfo(&info); + orig_dispose = FRAME_DISPOSE_NONE; + duration = 0; + transparent_index = -1; // Opaque frame by default. break; } case EXTENSION_RECORD_TYPE: { @@ -533,20 +524,19 @@ int main(int argc, const char *argv[]) { const int dispose = (flags >> GIF_DISPOSE_SHIFT) & GIF_DISPOSE_MASK; const int delay = data[2] | (data[3] << 8); // In 10 ms units. if (data[0] != 4) goto End; - info.duration = delay * 10; // Duration is in 1 ms units for WebP. - if (dispose == 3) { - static int warning_printed = 0; - if (!warning_printed) { - fprintf(stderr, "WARNING: GIF_DISPOSE_RESTORE unsupported.\n"); - warning_printed = 1; - } - // failsafe. TODO(urvang): emulate the correct behaviour by - // recoding the whole frame. - info.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND; - } else { - info.dispose_method = - (dispose == 2) ? WEBP_MUX_DISPOSE_BACKGROUND - : WEBP_MUX_DISPOSE_NONE; + duration = delay * 10; // Duration is in 1 ms units for WebP. + switch (dispose) { + case 3: + orig_dispose = FRAME_DISPOSE_RESTORE_PREVIOUS; + break; + case 2: + orig_dispose = FRAME_DISPOSE_BACKGROUND; + break; + case 1: + case 0: + default: + orig_dispose = FRAME_DISPOSE_NONE; + break; } transparent_index = (flags & GIF_TRANSPARENT_MASK) ? data[4] : -1; break; diff --git a/examples/gif2webp_util.c b/examples/gif2webp_util.c index 0cb4d0bb..ed048e4d 100644 --- a/examples/gif2webp_util.c +++ b/examples/gif2webp_util.c @@ -13,6 +13,7 @@ #include #include +#include "utils/utils.h" #include "webp/encode.h" #include "./gif2webp_util.h" @@ -293,14 +294,17 @@ struct WebPFrameCache { WebPFrameRect prev_orig_rect; // Previous input (e.g. GIF) frame rectangle. WebPFrameRect prev_webp_rect; // Previous WebP frame rectangle. - WebPMuxAnimDispose prev_orig_dispose; // Previous input dispose method. + FrameDisposeMethod prev_orig_dispose; // Previous input dispose method. int prev_candidate_undecided; // True if sub-frame vs keyframe decision // hasn't been made for the previous frame yet. - WebPPicture prev_canvas; // Previous canvas (NOT disposed). - WebPPicture curr_canvas; // Current canvas (NOT disposed). + WebPPicture curr_canvas; // Current canvas (NOT disposed). + WebPPicture curr_canvas_tmp; // Temporary storage for current canvas. + WebPPicture prev_canvas; // Previous canvas (NOT disposed). WebPPicture prev_canvas_disposed; // Previous canvas disposed to background. - WebPPicture curr_canvas_tmp; // Temporary storage for current canvas. + WebPPicture prev_to_prev_canvas_disposed; // Previous to previous canvas + // (disposed as per its original + // dispose method). int is_first_frame; // True if no frames have been added to the cache // since WebPFrameCacheNew(). }; @@ -317,7 +321,7 @@ static void CacheReset(WebPFrameCache* const cache) { WebPFrameCache* WebPFrameCacheNew(int width, int height, size_t kmin, size_t kmax, int allow_mixed) { - WebPFrameCache* cache = (WebPFrameCache*)malloc(sizeof(*cache)); + WebPFrameCache* cache = (WebPFrameCache*)WebPSafeCalloc(1, sizeof(*cache)); if (cache == NULL) return NULL; CacheReset(cache); // sanity init, so we can call WebPFrameCacheDelete(): @@ -327,22 +331,27 @@ WebPFrameCache* WebPFrameCacheNew(int width, int height, cache->is_first_frame = 1; // Picture buffers. - if (!WebPPictureInit(&cache->prev_canvas) || - !WebPPictureInit(&cache->curr_canvas) || + if (!WebPPictureInit(&cache->curr_canvas) || + !WebPPictureInit(&cache->curr_canvas_tmp) || + !WebPPictureInit(&cache->prev_canvas) || !WebPPictureInit(&cache->prev_canvas_disposed) || - !WebPPictureInit(&cache->curr_canvas_tmp)) { + !WebPPictureInit(&cache->prev_to_prev_canvas_disposed)) { return NULL; } - cache->prev_canvas.width = width; - cache->prev_canvas.height = height; - cache->prev_canvas.use_argb = 1; - if (!WebPPictureAlloc(&cache->prev_canvas) || - !WebPPictureCopy(&cache->prev_canvas, &cache->curr_canvas) || - !WebPPictureCopy(&cache->prev_canvas, &cache->prev_canvas_disposed) || - !WebPPictureCopy(&cache->prev_canvas, &cache->curr_canvas_tmp)) { + cache->curr_canvas.width = width; + cache->curr_canvas.height = height; + cache->curr_canvas.use_argb = 1; + if (!WebPPictureAlloc(&cache->curr_canvas) || + !WebPPictureCopy(&cache->curr_canvas, &cache->curr_canvas_tmp) || + !WebPPictureCopy(&cache->curr_canvas, &cache->prev_canvas) || + !WebPPictureCopy(&cache->curr_canvas, &cache->prev_canvas_disposed) || + !WebPPictureCopy(&cache->curr_canvas, + &cache->prev_to_prev_canvas_disposed)) { goto Err; } WebPUtilClearPic(&cache->prev_canvas, NULL); + WebPUtilClearPic(&cache->prev_canvas_disposed, NULL); + WebPUtilClearPic(&cache->prev_to_prev_canvas_disposed, NULL); // Cache data. cache->allow_mixed = allow_mixed; @@ -351,8 +360,8 @@ WebPFrameCache* WebPFrameCacheNew(int width, int height, cache->count_since_key_frame = 0; assert(kmax > kmin); cache->size = kmax - kmin + 1; // One extra storage for previous frame. - cache->encoded_frames = - (EncodedFrame*)calloc(cache->size, sizeof(*cache->encoded_frames)); + cache->encoded_frames = (EncodedFrame*)WebPSafeCalloc( + cache->size, sizeof(*cache->encoded_frames)); if (cache->encoded_frames == NULL) goto Err; return cache; // All OK. @@ -369,13 +378,14 @@ void WebPFrameCacheDelete(WebPFrameCache* const cache) { for (i = 0; i < cache->size; ++i) { FrameRelease(&cache->encoded_frames[i]); } - free(cache->encoded_frames); + WebPSafeFree(cache->encoded_frames); } - WebPPictureFree(&cache->prev_canvas); WebPPictureFree(&cache->curr_canvas); - WebPPictureFree(&cache->prev_canvas_disposed); WebPPictureFree(&cache->curr_canvas_tmp); - free(cache); + WebPPictureFree(&cache->prev_canvas); + WebPPictureFree(&cache->prev_canvas_disposed); + WebPPictureFree(&cache->prev_to_prev_canvas_disposed); + WebPSafeFree(cache); } } @@ -451,18 +461,23 @@ static int GetColorCount(const WebPPicture* const pic) { #undef HASH_SIZE #undef HASH_RIGHT_SHIFT -static void DisposeFullFrame(WebPMuxAnimDispose dispose_method, - WebPPicture* const frame) { - if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { - WebPUtilClearPic(frame, NULL); - } -} - -static void DisposeFrameRectangle(WebPMuxAnimDispose dispose_method, - const WebPFrameRect* const gif_rect, - WebPPicture* const frame) { - if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) { - WebPUtilClearPic(frame, gif_rect); +static void DisposeFrameRectangle(int dispose_method, + const WebPFrameRect* const rect, + const WebPPicture* const prev_canvas, + WebPPicture* const curr_canvas) { + assert(rect != NULL); + if (dispose_method == FRAME_DISPOSE_BACKGROUND) { + WebPUtilClearPic(curr_canvas, rect); + } else if (dispose_method == FRAME_DISPOSE_RESTORE_PREVIOUS) { + const int src_stride = prev_canvas->argb_stride; + const uint32_t* const src = + prev_canvas->argb + rect->x_offset + rect->y_offset * src_stride; + const int dst_stride = curr_canvas->argb_stride; + uint32_t* const dst = + curr_canvas->argb + rect->x_offset + rect->y_offset * dst_stride; + assert(prev_canvas != NULL); + CopyPlane((uint8_t*)src, 4 * src_stride, (uint8_t*)dst, 4 * dst_stride, + 4 * rect->width, rect->height); } } @@ -536,9 +551,8 @@ typedef struct { // Generates a candidate encoded frame given a picture and metadata. static WebPEncodingError EncodeCandidate(WebPPicture* const sub_frame, const WebPFrameRect* const rect, - const WebPMuxFrameInfo* const info, const WebPConfig* const config, - int use_blending, + int use_blending, int duration, Candidate* const candidate) { WebPEncodingError error_code = VP8_ENC_OK; assert(candidate != NULL); @@ -546,11 +560,13 @@ static WebPEncodingError EncodeCandidate(WebPPicture* const sub_frame, // Set frame rect and info. candidate->rect = *rect; - candidate->info = *info; + candidate->info.id = WEBP_CHUNK_ANMF; candidate->info.x_offset = rect->x_offset; candidate->info.y_offset = rect->y_offset; + candidate->info.dispose_method = WEBP_MUX_DISPOSE_NONE; // Set later. candidate->info.blend_method = use_blending ? WEBP_MUX_BLEND : WEBP_MUX_NO_BLEND; + candidate->info.duration = duration; // Encode picture. WebPMemoryWriterInit(&candidate->mem); @@ -612,8 +628,7 @@ enum { static WebPEncodingError GenerateCandidates( WebPFrameCache* const cache, Candidate candidates[CANDIDATE_COUNT], WebPMuxAnimDispose dispose_method, int is_lossless, int is_key_frame, - const WebPFrameRect* const rect, WebPPicture* sub_frame, - const WebPMuxFrameInfo* const info, + const WebPFrameRect* const rect, WebPPicture* sub_frame, int duration, const WebPConfig* const config_ll, const WebPConfig* const config_lossy) { WebPEncodingError error_code = VP8_ENC_OK; const int is_dispose_none = (dispose_method == WEBP_MUX_DISPOSE_NONE); @@ -622,10 +637,10 @@ static WebPEncodingError GenerateCandidates( Candidate* const candidate_lossy = is_dispose_none ? &candidates[LOSSY_DISP_NONE] : &candidates[LOSSY_DISP_BG]; - const WebPPicture* const prev_canvas = - is_dispose_none ? &cache->prev_canvas : &cache->prev_canvas_disposed; WebPPicture* const curr_canvas = &cache->curr_canvas; WebPPicture* const curr_canvas_tmp = &cache->curr_canvas_tmp; + const WebPPicture* const prev_canvas = + is_dispose_none ? &cache->prev_canvas : &cache->prev_canvas_disposed; const int use_blending = !is_key_frame && IsBlendingPossible(prev_canvas, curr_canvas, rect); @@ -648,8 +663,8 @@ static WebPEncodingError GenerateCandidates( curr_canvas_saved = 1; IncreaseTransparency(prev_canvas, rect, curr_canvas); } - error_code = EncodeCandidate(sub_frame, rect, info, config_ll, use_blending, - candidate_ll); + error_code = EncodeCandidate(sub_frame, rect, config_ll, use_blending, + duration, candidate_ll); if (error_code != VP8_ENC_OK) return error_code; if (use_blending) { CopyPixels(curr_canvas_tmp, curr_canvas); // restore @@ -666,8 +681,8 @@ static WebPEncodingError GenerateCandidates( } FlattenSimilarBlocks(prev_canvas, rect, curr_canvas); } - error_code = EncodeCandidate(sub_frame, rect, info, config_lossy, - use_blending, candidate_lossy); + error_code = EncodeCandidate(sub_frame, rect, config_lossy, use_blending, + duration, candidate_lossy); if (error_code != VP8_ENC_OK) return error_code; if (!is_key_frame) { CopyPixels(curr_canvas_tmp, curr_canvas); // restore @@ -732,16 +747,15 @@ static void PickBestCandidate(WebPFrameCache* const cache, // (lossy/lossless), dispose methods, blending methods etc to encode the current // frame and outputs the best one in 'encoded_frame'. static WebPEncodingError SetFrame(WebPFrameCache* const cache, - const WebPConfig* const config, - const WebPMuxFrameInfo* const info, + const WebPConfig* const config, int duration, const WebPFrameRect* const orig_rect, int is_key_frame, EncodedFrame* const encoded_frame) { int i; WebPEncodingError error_code = VP8_ENC_OK; + WebPPicture* const curr_canvas = &cache->curr_canvas; const WebPPicture* const prev_canvas = &cache->prev_canvas; WebPPicture* const prev_canvas_disposed = &cache->prev_canvas_disposed; - WebPPicture* const curr_canvas = &cache->curr_canvas; Candidate candidates[CANDIDATE_COUNT]; const int is_lossless = config->lossless; @@ -782,7 +796,7 @@ static WebPEncodingError SetFrame(WebPFrameCache* const cache, // Change-rectangle assuming previous frame was DISPOSE_BACKGROUND. CopyPixels(prev_canvas, prev_canvas_disposed); DisposeFrameRectangle(WEBP_MUX_DISPOSE_BACKGROUND, &cache->prev_webp_rect, - prev_canvas_disposed); + NULL, prev_canvas_disposed); GetSubRect(prev_canvas_disposed, curr_canvas, orig_rect, is_key_frame, &rect_bg, &sub_frame_bg); @@ -795,16 +809,17 @@ static WebPEncodingError SetFrame(WebPFrameCache* const cache, if (try_dispose_none) { error_code = GenerateCandidates( cache, candidates, WEBP_MUX_DISPOSE_NONE, is_lossless, is_key_frame, - &rect_none, &sub_frame_none, info, &config_ll, &config_lossy); + &rect_none, &sub_frame_none, duration, &config_ll, &config_lossy); if (error_code != VP8_ENC_OK) goto Err; } if (try_dispose_bg) { assert(!cache->is_first_frame); assert(dispose_bg_possible); - error_code = GenerateCandidates( - cache, candidates, WEBP_MUX_DISPOSE_BACKGROUND, is_lossless, - is_key_frame, &rect_bg, &sub_frame_bg, info, &config_ll, &config_lossy); + error_code = + GenerateCandidates(cache, candidates, WEBP_MUX_DISPOSE_BACKGROUND, + is_lossless, is_key_frame, &rect_bg, &sub_frame_bg, + duration, &config_ll, &config_lossy); if (error_code != VP8_ENC_OK) goto Err; } @@ -843,19 +858,22 @@ static int64_t KeyFramePenalty(const EncodedFrame* const encoded_frame) { int WebPFrameCacheAddFrame(WebPFrameCache* const cache, const WebPConfig* const config, const WebPFrameRect* const orig_rect_ptr, - WebPPicture* const frame, - WebPMuxFrameInfo* const info) { + FrameDisposeMethod orig_dispose_method, + int duration, WebPPicture* const frame) { // Initialize. int ok = 0; WebPEncodingError error_code = VP8_ENC_OK; - WebPPicture* const prev_canvas = &cache->prev_canvas; WebPPicture* const curr_canvas = &cache->curr_canvas; + WebPPicture* const prev_canvas = &cache->prev_canvas; + WebPPicture* const prev_to_prev_canvas_disposed = + &cache->prev_to_prev_canvas_disposed; + WebPPicture* const prev_canvas_disposed = &cache->prev_canvas_disposed; const size_t position = cache->count; EncodedFrame* const encoded_frame = CacheGetFrame(cache, position); WebPFrameRect orig_rect; assert(position < cache->size); - if (frame == NULL || info == NULL) { + if (frame == NULL) { return 0; } @@ -882,7 +900,8 @@ int WebPFrameCacheAddFrame(WebPFrameCache* const cache, CopyPixels(frame, curr_canvas); // Add this as a key frame. // Note: we use original rectangle as-is for the first frame. - error_code = SetFrame(cache, config, info, &orig_rect, 1, encoded_frame); + error_code = + SetFrame(cache, config, duration, &orig_rect, 1, encoded_frame); if (error_code != VP8_ENC_OK) { goto End; } @@ -892,19 +911,21 @@ int WebPFrameCacheAddFrame(WebPFrameCache* const cache, cache->count_since_key_frame = 0; cache->prev_candidate_undecided = 0; } else { - // Store previous canvas. + // Store previous to previous and previous canvases. + CopyPixels(prev_canvas_disposed, prev_to_prev_canvas_disposed); CopyPixels(curr_canvas, prev_canvas); // Create curr_canvas: // * Start with disposed previous canvas. // * Then blend 'frame' onto it. DisposeFrameRectangle(cache->prev_orig_dispose, &cache->prev_orig_rect, - curr_canvas); + prev_to_prev_canvas_disposed, curr_canvas); + CopyPixels(curr_canvas, prev_canvas_disposed); BlendPixels(frame, &orig_rect, curr_canvas); ++cache->count_since_key_frame; if (cache->count_since_key_frame <= cache->kmin) { // Add this as a frame rectangle. - error_code = SetFrame(cache, config, info, NULL, 0, encoded_frame); + error_code = SetFrame(cache, config, duration, NULL, 0, encoded_frame); if (error_code != VP8_ENC_OK) { goto End; } @@ -912,21 +933,16 @@ int WebPFrameCacheAddFrame(WebPFrameCache* const cache, cache->flush_count = cache->count - 1; cache->prev_candidate_undecided = 0; } else { - WebPMuxFrameInfo full_image_info; int64_t curr_delta; // Add frame rectangle to cache. - error_code = SetFrame(cache, config, info, NULL, 0, encoded_frame); + error_code = SetFrame(cache, config, duration, NULL, 0, encoded_frame); if (error_code != VP8_ENC_OK) { goto End; } // Add key frame to cache, too. - full_image_info = *info; - full_image_info.x_offset = 0; - full_image_info.y_offset = 0; - error_code = - SetFrame(cache, config, &full_image_info, NULL, 1, encoded_frame); + error_code = SetFrame(cache, config, duration, NULL, 1, encoded_frame); if (error_code != VP8_ENC_OK) goto End; // Analyze size difference of the two variants. @@ -951,12 +967,18 @@ int WebPFrameCacheAddFrame(WebPFrameCache* const cache, } cache->prev_candidate_undecided = 1; } + // Recalculate prev_canvas_disposed (as it might have been modified). + CopyPixels(prev_canvas, prev_canvas_disposed); + DisposeFrameRectangle(cache->prev_orig_dispose, &cache->prev_orig_rect, + prev_to_prev_canvas_disposed, prev_canvas_disposed); } - DisposeFullFrame(info->dispose_method, frame); + // Dispose the 'frame'. + DisposeFrameRectangle(orig_dispose_method, &orig_rect, prev_canvas_disposed, + frame); cache->is_first_frame = 0; - cache->prev_orig_dispose = info->dispose_method; + cache->prev_orig_dispose = orig_dispose_method; cache->prev_orig_rect = orig_rect; ok = 1; diff --git a/examples/gif2webp_util.h b/examples/gif2webp_util.h index 94f12d5e..522a3fe7 100644 --- a/examples/gif2webp_util.h +++ b/examples/gif2webp_util.h @@ -29,6 +29,13 @@ extern "C" { struct WebPPicture; +// Includes all disposal methods, even the ones not supported by WebP bitstream. +typedef enum FrameDisposeMethod { + FRAME_DISPOSE_NONE, + FRAME_DISPOSE_BACKGROUND, + FRAME_DISPOSE_RESTORE_PREVIOUS +} FrameDisposeMethod; + typedef struct { int x_offset, y_offset, width, height; } WebPFrameRect; @@ -53,15 +60,15 @@ WebPFrameCache* WebPFrameCacheNew(int width, int height, // Release all the frame data from 'cache' and free 'cache'. void WebPFrameCacheDelete(WebPFrameCache* const cache); -// Given an image described by 'frame', 'info' and 'orig_rect', optimize it for -// WebP, encode it and add it to 'cache'. 'orig_rect' can be NULL. -// This takes care of frame disposal too, according to 'info->dispose_method'. +// Given an image described by 'frame', 'rect', 'dispose_method' and 'duration', +// optimize it for WebP, encode it and add it to 'cache'. 'rect' can be NULL. +// This takes care of frame disposal too, according to 'dispose_method'. // Returns false in case of error (and sets frame->error_code accordingly). int WebPFrameCacheAddFrame(WebPFrameCache* const cache, const WebPConfig* const config, - const WebPFrameRect* const orig_rect, - WebPPicture* const frame, - WebPMuxFrameInfo* const info); + const WebPFrameRect* const rect, + FrameDisposeMethod dispose_method, int duration, + WebPPicture* const frame); // Flush the *ready* frames from cache and add them to 'mux'. If 'verbose' is // true, prints the information about these frames.