mirror of
https://github.com/webmproject/libwebp.git
synced 2024-12-27 06:08:21 +01:00
27933e2a8e
Earlier, we stored a 1x1 frame for such frames. Now, we drop every such frame and increase the duration of its previous frame instead. Also, modify the anim_diff tool to handle animated images that are equivalent, but have different number of frames. Change-Id: I2688b1771e1f5f9f6a78e48ec81b01c3cd495403
204 lines
6.4 KiB
C++
204 lines
6.4 KiB
C++
// Copyright 2015 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Checks if given pair of animated GIF/WebP images are identical:
|
|
// That is: their reconstructed canvases match pixel-by-pixel and their other
|
|
// animation properties (loop count etc) also match.
|
|
//
|
|
// example: anim_diff foo.gif bar.webp
|
|
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h> // for 'strtod'.
|
|
#include <string.h> // for 'strcmp'.
|
|
|
|
#include <iostream> // for 'cout'.
|
|
#include <sstream> // for 'ostringstream'.
|
|
|
|
#include "./anim_util.h"
|
|
|
|
namespace {
|
|
|
|
// Return true if 'a + b' will overflow.
|
|
bool 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.
|
|
void MinimizeAnimationFrames(AnimatedImage* const img) {
|
|
for (size_t i = 1; i < img->frames.size(); ++i) {
|
|
DecodedFrame* const frame1 = &img->frames[i - 1];
|
|
DecodedFrame* const frame2 = &img->frames[i];
|
|
// If merging frames will result in integer overflow for 'duration',
|
|
// skip merging.
|
|
if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue;
|
|
const uint8_t* rgba1 = frame1->rgba.data();
|
|
const uint8_t* rgba2 = frame2->rgba.data();
|
|
if (!memcmp(rgba1, rgba2, img->canvas_width * 4 * img->canvas_height)) {
|
|
// Merge 'i+1'th frame into 'i'th frame.
|
|
frame1->duration += frame2->duration;
|
|
img->frames.erase(img->frames.begin() + i);
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
bool CompareValues(T a, T b, const std::string& output_str) {
|
|
if (a != b) {
|
|
std::cout << output_str << ": " << a << " vs " << b << std::endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Note: As long as frame durations and reconstructed frames are identical, it
|
|
// is OK for other aspects like offsets, dispose/blend method to vary.
|
|
bool CompareAnimatedImagePair(const AnimatedImage& img1,
|
|
const AnimatedImage& img2,
|
|
bool premultiply,
|
|
double min_psnr) {
|
|
bool ok = true;
|
|
ok = CompareValues(img1.canvas_width, img2.canvas_width,
|
|
"Canvas width mismatch") && ok;
|
|
ok = CompareValues(img1.canvas_height, img2.canvas_height,
|
|
"Canvas height mismatch") && ok;
|
|
ok = CompareValues(img1.frames.size(), img2.frames.size(),
|
|
"Frame count mismatch") && ok;
|
|
if (!ok) return false; // These are fatal failures, can't proceed.
|
|
|
|
const bool is_multi_frame_image = (img1.frames.size() > 1);
|
|
if (is_multi_frame_image) { // Checks relevant for multi-frame images only.
|
|
ok = CompareValues(img1.loop_count, img2.loop_count,
|
|
"Loop count mismatch") && ok;
|
|
ok = CompareValues(img1.bgcolor, img2.bgcolor,
|
|
"Background color mismatch") && ok;
|
|
}
|
|
|
|
for (size_t i = 0; i < img1.frames.size(); ++i) {
|
|
if (is_multi_frame_image) { // Check relevant for multi-frame images only.
|
|
std::ostringstream error_str;
|
|
error_str << "Frame #" << i << ", duration mismatch";
|
|
ok = CompareValues(img1.frames[i].duration, img2.frames[i].duration,
|
|
error_str.str()) && ok;
|
|
}
|
|
// Pixel-by-pixel comparison.
|
|
const uint8_t* rgba1 = img1.frames[i].rgba.data();
|
|
const uint8_t* rgba2 = img2.frames[i].rgba.data();
|
|
int max_diff;
|
|
double psnr;
|
|
GetDiffAndPSNR(rgba1, rgba2, img1.canvas_width, img1.canvas_height,
|
|
premultiply, &max_diff, &psnr);
|
|
if (min_psnr > 0.) {
|
|
if (psnr < min_psnr) {
|
|
fprintf(stderr, "Frame #%zu, psnr = %.2lf (min_psnr = %f)\n", i,
|
|
psnr, min_psnr);
|
|
ok = false;
|
|
}
|
|
} else {
|
|
if (max_diff != 0) {
|
|
fprintf(stderr, "Frame #%zu, max pixel diff: %d\n", i, max_diff);
|
|
ok = false;
|
|
}
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
void Help() {
|
|
printf("\nUsage: anim_diff <image1> <image2> [-dump_frames <folder>] "
|
|
"[-min_psnr <float>][-raw_comparison]\n");
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, const char* argv[]) {
|
|
bool dump_frames = false;
|
|
const char* dump_folder = NULL;
|
|
double min_psnr = 0.;
|
|
bool got_input1 = false;
|
|
bool got_input2 = false;
|
|
bool premultiply = true;
|
|
const char* files[2];
|
|
|
|
if (argc < 3) {
|
|
Help();
|
|
return -1;
|
|
}
|
|
|
|
for (int c = 1; c < argc; ++c) {
|
|
bool parse_error = false;
|
|
if (!strcmp(argv[c], "-dump_frames")) {
|
|
if (c < argc - 1) {
|
|
dump_frames = true;
|
|
dump_folder = argv[++c];
|
|
} else {
|
|
parse_error = true;
|
|
}
|
|
} else if (!strcmp(argv[c], "-min_psnr")) {
|
|
if (c < argc - 1) {
|
|
const char* const v = argv[++c];
|
|
char* end = NULL;
|
|
const double d = strtod(v, &end);
|
|
if (end == v) {
|
|
parse_error = true;
|
|
fprintf(stderr, "Error! '%s' is not a floating point number.\n", v);
|
|
}
|
|
min_psnr = d;
|
|
} else {
|
|
parse_error = true;
|
|
}
|
|
} else if (!strcmp(argv[c], "-raw_comparison")) {
|
|
premultiply = false;
|
|
} else {
|
|
if (!got_input1) {
|
|
files[0] = argv[c];
|
|
got_input1 = true;
|
|
} else if (!got_input2) {
|
|
files[1] = argv[c];
|
|
got_input2 = true;
|
|
} else {
|
|
parse_error = true;
|
|
}
|
|
}
|
|
if (parse_error) {
|
|
Help();
|
|
return -1;
|
|
}
|
|
}
|
|
if (!got_input2) {
|
|
Help();
|
|
return -1;
|
|
}
|
|
|
|
if (dump_frames) {
|
|
printf("Dumping decoded frames in: %s\n", dump_folder);
|
|
}
|
|
|
|
AnimatedImage images[2];
|
|
for (int i = 0; i < 2; ++i) {
|
|
printf("Decoding file: %s\n", files[i]);
|
|
if (!ReadAnimatedImage(files[i], &images[i], dump_frames, dump_folder)) {
|
|
fprintf(stderr, "Error decoding file: %s\n Aborting.\n", files[i]);
|
|
return -2;
|
|
}
|
|
MinimizeAnimationFrames(&images[i]);
|
|
}
|
|
|
|
if (!CompareAnimatedImagePair(images[0], images[1],
|
|
premultiply, min_psnr)) {
|
|
fprintf(stderr, "\nFiles %s and %s differ.\n", files[0], files[1]);
|
|
return -3;
|
|
}
|
|
|
|
printf("\nFiles %s and %s are identical.\n", files[0], files[1]);
|
|
return 0;
|
|
}
|