mirror of
https://github.com/webmproject/libwebp.git
synced 2024-11-19 20:08:28 +01:00
Merge changes I1d03acac,Ifcb64219
* changes: anim_diff: Add an experimental option for max inter-frame diff. WebPAnimEncoder: FlattenSimilarPixels(): look for similar
This commit is contained in:
commit
602f344a36
@ -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
|
||||
|
@ -13,6 +13,7 @@
|
||||
//
|
||||
// example: anim_diff foo.gif bar.webp
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // 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 <float> ... 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 <int> ..... 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -596,7 +596,7 @@ static uint32_t RectArea(const FrameRect* const rect) {
|
||||
return (uint32_t)rect->width_ * rect->height_;
|
||||
}
|
||||
|
||||
static int IsBlendingPossible(const WebPPicture* const src,
|
||||
static int IsLosslessBlendingPossible(const WebPPicture* const src,
|
||||
const WebPPicture* const dst,
|
||||
const FrameRect* const rect) {
|
||||
int i, j;
|
||||
@ -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,7 +749,8 @@ 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]) {
|
||||
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;
|
||||
@ -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 =
|
||||
|
Loading…
Reference in New Issue
Block a user