blob: dab9c6b6a2ac536edfdd2533c8ec2f52207a243d [file] [log] [blame]
Pascal Massimino96201e52015-11-06 07:47:03 +01001// Copyright 2015 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// Checks if given pair of animated GIF/WebP images are identical:
11// That is: their reconstructed canvases match pixel-by-pixel and their other
12// animation properties (loop count etc) also match.
13//
14// example: anim_diff foo.gif bar.webp
15
16#include <limits.h>
17#include <stdio.h>
18#include <stdlib.h> // for 'strtod'.
19#include <string.h> // for 'strcmp'.
20
21#include "./anim_util.h"
22
23#if defined(_MSC_VER) && _MSC_VER < 1900
24#define snprintf _snprintf
25#endif
26
27// Returns true if 'a + b' will overflow.
28static int AdditionWillOverflow(int a, int b) {
29 return (b > 0) && (a > INT_MAX - b);
30}
31
32// Minimize number of frames by combining successive frames that have exact same
33// ARGB data into a single longer duration frame.
34static void MinimizeAnimationFrames(AnimatedImage* const img) {
35 uint32_t i;
36 for (i = 1; i < img->num_frames; ++i) {
37 DecodedFrame* const frame1 = &img->frames[i - 1];
38 DecodedFrame* const frame2 = &img->frames[i];
39 const uint8_t* const rgba1 = frame1->rgba;
40 const uint8_t* const rgba2 = frame2->rgba;
41 // If merging frames will result in integer overflow for 'duration',
42 // skip merging.
43 if (AdditionWillOverflow(frame1->duration, frame2->duration)) continue;
44 if (!memcmp(rgba1, rgba2, img->canvas_width * 4 * img->canvas_height)) {
45 // Merge 'i+1'th frame into 'i'th frame.
46 frame1->duration += frame2->duration;
47 if (i + 1 < img->num_frames) {
48 memmove(&img->frames[i], &img->frames[i + 1],
49 (img->num_frames - i - 1) * sizeof(*img->frames));
50 }
51 --img->num_frames;
52 --i;
53 }
54 }
55}
56
57static int CompareValues(uint32_t a, uint32_t b, const char* output_str) {
58 if (a != b) {
59 fprintf(stderr, "%s: %d vs %d\n", output_str, a, b);
60 return 0;
61 }
62 return 1;
63}
64
65// Note: As long as frame durations and reconstructed frames are identical, it
66// is OK for other aspects like offsets, dispose/blend method to vary.
67static int CompareAnimatedImagePair(const AnimatedImage* const img1,
68 const AnimatedImage* const img2,
69 int premultiply,
70 double min_psnr) {
71 int ok = 1;
72 const int is_multi_frame_image = (img1->num_frames > 1);
73 uint32_t i;
74
75 ok = CompareValues(img1->canvas_width, img2->canvas_width,
76 "Canvas width mismatch") && ok;
77 ok = CompareValues(img1->canvas_height, img2->canvas_height,
78 "Canvas height mismatch") && ok;
79 ok = CompareValues(img1->num_frames, img2->num_frames,
80 "Frame count mismatch") && ok;
81 if (!ok) return 0; // These are fatal failures, can't proceed.
82
83 if (is_multi_frame_image) { // Checks relevant for multi-frame images only.
84 ok = CompareValues(img1->loop_count, img2->loop_count,
85 "Loop count mismatch") && ok;
86 ok = CompareValues(img1->bgcolor, img2->bgcolor,
87 "Background color mismatch") && ok;
88 }
89
90 for (i = 0; i < img1->num_frames; ++i) {
91 // Pixel-by-pixel comparison.
92 const uint8_t* const rgba1 = img1->frames[i].rgba;
93 const uint8_t* const rgba2 = img2->frames[i].rgba;
94 int max_diff;
95 double psnr;
96 if (is_multi_frame_image) { // Check relevant for multi-frame images only.
97 const char format[] = "Frame #%d, duration mismatch";
98 char tmp[sizeof(format) + 8];
99 ok = ok && (snprintf(tmp, sizeof(tmp), format, i) >= 0);
100 ok = ok && CompareValues(img1->frames[i].duration,
101 img2->frames[i].duration, tmp);
102 }
103 GetDiffAndPSNR(rgba1, rgba2, img1->canvas_width, img1->canvas_height,
104 premultiply, &max_diff, &psnr);
105 if (min_psnr > 0.) {
106 if (psnr < min_psnr) {
107 fprintf(stderr, "Frame #%d, psnr = %.2lf (min_psnr = %f)\n", i,
108 psnr, min_psnr);
109 ok = 0;
110 }
111 } else {
112 if (max_diff != 0) {
113 fprintf(stderr, "Frame #%d, max pixel diff: %d\n", i, max_diff);
114 ok = 0;
115 }
116 }
117 }
118 return ok;
119}
120
121static void Help(void) {
122 printf("\nUsage: anim_diff <image1> <image2> [-dump_frames <folder>] "
123 "[-min_psnr <float>][-raw_comparison]\n");
124}
125
126int main(int argc, const char* argv[]) {
127 int return_code = -1;
128 int dump_frames = 0;
129 const char* dump_folder = NULL;
130 double min_psnr = 0.;
131 int got_input1 = 0;
132 int got_input2 = 0;
133 int premultiply = 1;
134 int i, c;
135 const char* files[2] = { NULL, NULL };
136 AnimatedImage images[2];
137
138 if (argc < 3) {
139 Help();
140 return -1;
141 }
142
143 for (c = 1; c < argc; ++c) {
144 int parse_error = 0;
145 if (!strcmp(argv[c], "-dump_frames")) {
146 if (c < argc - 1) {
147 dump_frames = 1;
148 dump_folder = argv[++c];
149 } else {
150 parse_error = 1;
151 }
152 } else if (!strcmp(argv[c], "-min_psnr")) {
153 if (c < argc - 1) {
154 const char* const v = argv[++c];
155 char* end = NULL;
156 const double d = strtod(v, &end);
157 if (end == v) {
158 parse_error = 1;
159 fprintf(stderr, "Error! '%s' is not a floating point number.\n", v);
160 }
161 min_psnr = d;
162 } else {
163 parse_error = 1;
164 }
165 } else if (!strcmp(argv[c], "-raw_comparison")) {
166 premultiply = 0;
167 } else {
168 if (!got_input1) {
169 files[0] = argv[c];
170 got_input1 = 1;
171 } else if (!got_input2) {
172 files[1] = argv[c];
173 got_input2 = 1;
174 } else {
175 parse_error = 1;
176 }
177 }
178 if (parse_error) {
179 Help();
180 return -1;
181 }
182 }
183 if (!got_input2) {
184 Help();
185 return -1;
186 }
187
188 if (dump_frames) {
189 printf("Dumping decoded frames in: %s\n", dump_folder);
190 }
191
192 memset(images, 0, sizeof(images));
193 for (i = 0; i < 2; ++i) {
194 printf("Decoding file: %s\n", files[i]);
195 if (!ReadAnimatedImage(files[i], &images[i], dump_frames, dump_folder)) {
196 fprintf(stderr, "Error decoding file: %s\n Aborting.\n", files[i]);
197 return_code = -2;
198 goto End;
199 } else {
200 MinimizeAnimationFrames(&images[i]);
201 }
202 }
203
204 if (!CompareAnimatedImagePair(&images[0], &images[1],
205 premultiply, min_psnr)) {
206 fprintf(stderr, "\nFiles %s and %s differ.\n", files[0], files[1]);
207 return_code = -3;
208 } else {
209 printf("\nFiles %s and %s are identical.\n", files[0], files[1]);
210 return_code = 0;
211 }
212 End:
213 ClearAnimatedImage(&images[0]);
214 ClearAnimatedImage(&images[1]);
215 return return_code;
216}