From 31f2b8d8e19d105fedc03f7cf3f47cd4bbe7977c Mon Sep 17 00:00:00 2001 From: Urvang Joshi Date: Mon, 4 Apr 2016 20:27:52 +0000 Subject: [PATCH 1/2] WebPAnimEncoder: FlattenSimilarPixels(): look for similar not exactly same. Based on lossy WebP quality setting, ignore minor differences when flattening similar blocks. For 6k set, at default quality with '-min_size' option, improves compression by 0.3% Change-Id: Ifcb64219f941e869eb2643e231220b278aad4cd4 --- src/mux/anim_encode.c | 55 +++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/src/mux/anim_encode.c b/src/mux/anim_encode.c index 9f46d3f3..2920a244 100644 --- a/src/mux/anim_encode.c +++ b/src/mux/anim_encode.c @@ -596,9 +596,9 @@ static uint32_t RectArea(const FrameRect* const rect) { return (uint32_t)rect->width_ * rect->height_; } -static int IsBlendingPossible(const WebPPicture* const src, - const WebPPicture* const dst, - const FrameRect* const rect) { +static int IsLosslessBlendingPossible(const WebPPicture* const src, + const WebPPicture* const dst, + const FrameRect* const rect) { int i, j; assert(src->width == dst->width && src->height == dst->height); assert(rect->x_offset_ + rect->width_ <= dst->width); @@ -618,6 +618,31 @@ static int IsBlendingPossible(const WebPPicture* const src, return 1; } +static int IsLossyBlendingPossible(const WebPPicture* const src, + const WebPPicture* const dst, + const FrameRect* const rect, + float quality) { + const int max_allowed_diff_lossy = QualityToMaxDiff(quality); + int i, j; + assert(src->width == dst->width && src->height == dst->height); + assert(rect->x_offset_ + rect->width_ <= dst->width); + assert(rect->y_offset_ + rect->height_ <= dst->height); + for (j = rect->y_offset_; j < rect->y_offset_ + rect->height_; ++j) { + for (i = rect->x_offset_; i < rect->x_offset_ + rect->width_; ++i) { + const uint32_t src_pixel = src->argb[j * src->argb_stride + i]; + const uint32_t dst_pixel = dst->argb[j * dst->argb_stride + i]; + const uint32_t dst_alpha = dst_pixel >> 24; + if (dst_alpha != 0xff && + !PixelsAreSimilar(src_pixel, dst_pixel, max_allowed_diff_lossy)) { + // In this case, if we use blending, we can't attain the desired + // 'dst_pixel' value for this pixel. So, blending is not possible. + return 0; + } + } + } + return 1; +} + #define MIN_COLORS_LOSSY 31 // Don't try lossy below this threshold. #define MAX_COLORS_LOSSLESS 194 // Don't try lossless above this threshold. #define MAX_COLOR_COUNT 256 // Power of 2 greater than MAX_COLORS_LOSSLESS. @@ -696,9 +721,12 @@ static void IncreaseTransparency(const WebPPicture* const src, // Replace similar blocks of pixels by a 'see-through' transparent block // with uniform average color. +// Assumes lossy compression is being used. static void FlattenSimilarBlocks(const WebPPicture* const src, const FrameRect* const rect, - WebPPicture* const dst) { + WebPPicture* const dst, + float quality) { + const int max_allowed_diff_lossy = QualityToMaxDiff(quality); int i, j; const int block_size = 8; const int y_start = (rect->y_offset_ + block_size) & ~(block_size - 1); @@ -721,11 +749,12 @@ static void FlattenSimilarBlocks(const WebPPicture* const src, const uint32_t src_pixel = psrc[x + y * src->argb_stride]; const int alpha = src_pixel >> 24; if (alpha == 0xff && - src_pixel == pdst[x + y * dst->argb_stride]) { - ++cnt; - avg_r += (src_pixel >> 16) & 0xff; - avg_g += (src_pixel >> 8) & 0xff; - avg_b += (src_pixel >> 0) & 0xff; + PixelsAreSimilar(src_pixel, pdst[x + y * dst->argb_stride], + max_allowed_diff_lossy)) { + ++cnt; + avg_r += (src_pixel >> 16) & 0xff; + avg_g += (src_pixel >> 8) & 0xff; + avg_b += (src_pixel >> 0) & 0xff; } } } @@ -844,10 +873,11 @@ static WebPEncodingError GenerateCandidates( is_dispose_none ? &enc->prev_canvas_ : &enc->prev_canvas_disposed_; const int use_blending_ll = !is_key_frame && - IsBlendingPossible(prev_canvas, curr_canvas, ¶ms->rect_ll_); + IsLosslessBlendingPossible(prev_canvas, curr_canvas, ¶ms->rect_ll_); const int use_blending_lossy = !is_key_frame && - IsBlendingPossible(prev_canvas, curr_canvas, ¶ms->rect_lossy_); + IsLossyBlendingPossible(prev_canvas, curr_canvas, ¶ms->rect_lossy_, + config_lossy->quality); // Pick candidates to be tried. if (!enc->options_.allow_mixed) { @@ -873,7 +903,8 @@ static WebPEncodingError GenerateCandidates( if (candidate_lossy->evaluate_) { CopyCurrentCanvas(enc); if (use_blending_lossy) { - FlattenSimilarBlocks(prev_canvas, ¶ms->rect_lossy_, curr_canvas); + FlattenSimilarBlocks(prev_canvas, ¶ms->rect_lossy_, curr_canvas, + config_lossy->quality); enc->curr_canvas_copy_modified_ = 1; } error_code = From 47dd07080f4c090d67ffe7223f148c8ba9bef155 Mon Sep 17 00:00:00 2001 From: Urvang Joshi Date: Mon, 4 Apr 2016 18:43:58 +0000 Subject: [PATCH 2/2] anim_diff: Add an experimental option for max inter-frame diff. After the introduction of lossy frame rectangles we need equivalent option in anim_diff for merging similar frames. Change-Id: I1d03acace396ec4cb0212586c6e8b8ec5b0b0bfc --- examples/Makefile.am | 2 +- examples/anim_diff.c | 81 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/examples/Makefile.am b/examples/Makefile.am index 81970ce1..3b9e5b20 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -21,7 +21,7 @@ if BUILD_ANIMDIFF endif anim_diff_SOURCES = anim_diff.c anim_util.c anim_util.h -anim_diff_CPPFLAGS = $(AM_CPPFLAGS) $(GIF_INCLUDES) +anim_diff_CPPFLAGS = $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE) $(GIF_INCLUDES) anim_diff_LDADD = ../src/demux/libwebpdemux.la anim_diff_LDADD += libexampleutil.la anim_diff_LDADD += $(GIF_LIBS) -lm diff --git a/examples/anim_diff.c b/examples/anim_diff.c index df24a1a9..a91c8b34 100644 --- a/examples/anim_diff.c +++ b/examples/anim_diff.c @@ -13,6 +13,7 @@ // // example: anim_diff foo.gif bar.webp +#include #include #include #include // for 'strtod'. @@ -29,20 +30,67 @@ static int AdditionWillOverflow(int a, int b) { return (b > 0) && (a > INT_MAX - b); } -// Minimize number of frames by combining successive frames that have exact same -// ARGB data into a single longer duration frame. -static void MinimizeAnimationFrames(AnimatedImage* const img) { +static int FramesAreEqual(const uint8_t* const rgba1, + const uint8_t* const rgba2, int width, int height) { + const int stride = width * 4; // Always true for 'DecodedFrame.rgba'. + return !memcmp(rgba1, rgba2, stride * height); +} + +static WEBP_INLINE int PixelsAreSimilar(uint32_t src, uint32_t dst, + int max_allowed_diff) { + const int src_a = (src >> 24) & 0xff; + const int src_r = (src >> 16) & 0xff; + const int src_g = (src >> 8) & 0xff; + const int src_b = (src >> 0) & 0xff; + const int dst_a = (dst >> 24) & 0xff; + const int dst_r = (dst >> 16) & 0xff; + const int dst_g = (dst >> 8) & 0xff; + const int dst_b = (dst >> 0) & 0xff; + + return (abs(src_r * src_a - dst_r * dst_a) <= (max_allowed_diff * 255)) && + (abs(src_g * src_a - dst_g * dst_a) <= (max_allowed_diff * 255)) && + (abs(src_b * src_a - dst_b * dst_a) <= (max_allowed_diff * 255)) && + (abs(src_a - dst_a) <= max_allowed_diff); +} + +static int FramesAreSimilar(const uint8_t* const rgba1, + const uint8_t* const rgba2, + int width, int height, int max_allowed_diff) { + int i, j; + assert(max_allowed_diff > 0); + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + const int stride = width * 4; + const size_t offset = j * stride + i; + if (!PixelsAreSimilar(rgba1[offset], rgba2[offset], max_allowed_diff)) { + return 0; + } + } + } + return 1; +} + +// Minimize number of frames by combining successive frames that have at max +// 'max_diff' difference per channel between corresponding pixels. +static void MinimizeAnimationFrames(AnimatedImage* const img, int max_diff) { uint32_t i; for (i = 1; i < img->num_frames; ++i) { DecodedFrame* const frame1 = &img->frames[i - 1]; DecodedFrame* const frame2 = &img->frames[i]; const uint8_t* const rgba1 = frame1->rgba; const uint8_t* const rgba2 = frame2->rgba; + int should_merge_frames = 0; // If merging frames will result in integer overflow for 'duration', // skip merging. if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue; - if (!memcmp(rgba1, rgba2, img->canvas_width * 4 * img->canvas_height)) { - // Merge 'i+1'th frame into 'i'th frame. + if (max_diff > 0) { + should_merge_frames = FramesAreSimilar(rgba1, rgba2, img->canvas_width, + img->canvas_height, max_diff); + } else { + should_merge_frames = + FramesAreEqual(rgba1, rgba2, img->canvas_width, img->canvas_height); + } + if (should_merge_frames) { // Merge 'i+1'th frame into 'i'th frame. frame1->duration += frame2->duration; if (i + 1 < img->num_frames) { memmove(&img->frames[i], &img->frames[i + 1], @@ -125,6 +173,11 @@ static void Help(void) { printf(" -min_psnr ... minimum per-frame PSNR\n"); printf(" -raw_comparison ..... if this flag is not used, RGB is\n"); printf(" premultiplied before comparison\n"); +#ifdef WEBP_EXPERIMENTAL_FEATURES + printf(" -max_diff ..... maximum allowed difference per channel " + " between corresponding pixels in subsequent" + " frames\n"); +#endif } int main(int argc, const char* argv[]) { @@ -135,6 +188,7 @@ int main(int argc, const char* argv[]) { int got_input1 = 0; int got_input2 = 0; int premultiply = 1; + int max_diff = 0; int i, c; const char* files[2] = { NULL, NULL }; AnimatedImage images[2]; @@ -168,6 +222,21 @@ int main(int argc, const char* argv[]) { } } else if (!strcmp(argv[c], "-raw_comparison")) { premultiply = 0; +#ifdef WEBP_EXPERIMENTAL_FEATURES + } else if (!strcmp(argv[c], "-max_diff")) { + if (c < argc - 1) { + const char* const v = argv[++c]; + char* end = NULL; + const int n = (int)strtol(v, &end, 10); + if (end == v) { + parse_error = 1; + fprintf(stderr, "Error! '%s' is not an integer.\n", v); + } + max_diff = n; + } else { + parse_error = 1; + } +#endif } else { if (!got_input1) { files[0] = argv[c]; @@ -201,7 +270,7 @@ int main(int argc, const char* argv[]) { return_code = -2; goto End; } else { - MinimizeAnimationFrames(&images[i]); + MinimizeAnimationFrames(&images[i], max_diff); } }