1 // Copyright 2019 The Amber Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <iostream>
16 #include <sstream>
17 #include <vector>
18
19 #include "src/buffer.h"
20 #include "src/format.h"
21 #include "src/type_parser.h"
22
23 #pragma clang diagnostic push
24 #pragma clang diagnostic ignored "-Wweak-vtables"
25 #include "third_party/lodepng/lodepng.h"
26 #pragma clang diagnostic pop
27
28 namespace {
29
30 enum class CompareAlgorithm { kRMSE = 0, kHISTOGRAM_EMD = 1 };
31
32 struct Options {
33 std::vector<std::string> input_filenames;
34 bool show_help = false;
35 float tolerance = 1.0f;
36 CompareAlgorithm compare_algorithm = CompareAlgorithm::kRMSE;
37 };
38
39 const char kUsage[] = R"(Usage: image_diff [options] image1.png image2.png
40
41 Exactly one algorithm (and its parameters) must be specified.
42
43 Algorithms:
44
45 --rmse TOLERANCE
46 Compare using the Root Mean Square Error (RMSE) algorithm with
47 a floating point TOLERANCE value in the range 0..255, where 0
48 indicates identical images and 255 indicates images where every
49 color channel has the maximum difference.
50
51 --histogram_emd TOLERANCE
52 Compare the per-channel color histograms of the images using a
53 variant of the Earth Mover's Distance (EMD) algorithm with a
54 floating point TOLERANCE value in the range 0.0..1.0, where 0.0
55 indicates identical histograms and 1.0 indicates completely
56 different histograms for at least one of the color channels.
57 E.g. an image with red=255 for every pixel vs. an image with
58 red=0 for every pixel.
59
60 Other options:
61
62 -h | --help This help text.
63 )";
64
ParseArgs(const std::vector<std::string> & args,Options * opts)65 bool ParseArgs(const std::vector<std::string>& args, Options* opts) {
66 int num_algorithms = 0;
67 for (size_t i = 1; i < args.size(); ++i) {
68 const std::string& arg = args[i];
69 if (arg == "-h" || arg == "--help") {
70 opts->show_help = true;
71 return true;
72 } else if (arg == "--rmse") {
73 num_algorithms++;
74 opts->compare_algorithm = CompareAlgorithm::kRMSE;
75 ++i;
76 if (i >= args.size()) {
77 std::cerr << "Missing tolerance value for RMSE comparison."
78 << std::endl;
79 return false;
80 }
81 std::stringstream sstream(args[i]);
82 sstream >> opts->tolerance;
83 if (sstream.fail()) {
84 std::cerr << "Invalid tolerance value " << args[i] << std::endl;
85 return false;
86 }
87 if (opts->tolerance < 0 || opts->tolerance > 255) {
88 std::cerr << "Tolerance must be in the range 0..255." << std::endl;
89 return false;
90 }
91
92 } else if (arg == "--histogram_emd") {
93 num_algorithms++;
94 opts->compare_algorithm = CompareAlgorithm::kHISTOGRAM_EMD;
95 ++i;
96 if (i >= args.size()) {
97 std::cerr << "Missing tolerance value for histogram EMD comparison."
98 << std::endl;
99 return false;
100 }
101 std::stringstream sstream(args[i]);
102 sstream >> opts->tolerance;
103 if (sstream.fail()) {
104 std::cerr << "Invalid tolerance value " << args[i] << std::endl;
105 return false;
106 }
107 if (opts->tolerance < 0 || opts->tolerance > 1) {
108 std::cerr << "Tolerance must be in the range 0..1." << std::endl;
109 return false;
110 }
111 } else if (!arg.empty()) {
112 opts->input_filenames.push_back(arg);
113 }
114 }
115 if (num_algorithms == 0) {
116 std::cerr << "No comparison algorithm specified." << std::endl;
117 return false;
118 } else if (num_algorithms > 1) {
119 std::cerr << "Only one comparison algorithm can be specified." << std::endl;
120 return false;
121 }
122
123 return true;
124 }
125
LoadPngToBuffer(const std::string & filename,amber::Buffer * buffer)126 amber::Result LoadPngToBuffer(const std::string& filename,
127 amber::Buffer* buffer) {
128 std::vector<unsigned char> image;
129 uint32_t width;
130 uint32_t height;
131 uint32_t error = lodepng::decode(image, width, height, filename.c_str());
132
133 if (error) {
134 std::string result = "PNG decode error: ";
135 result += lodepng_error_text(error);
136 return amber::Result(result);
137 }
138
139 std::vector<amber::Value> values;
140 values.resize(image.size());
141 for (size_t i = 0; i < image.size(); ++i) {
142 values[i].SetIntValue(image[i]);
143 }
144
145 buffer->SetData(values);
146
147 return {};
148 }
149
150 } // namespace
151
main(int argc,const char ** argv)152 int main(int argc, const char** argv) {
153 std::vector<std::string> args(argv, argv + argc);
154 Options options;
155
156 if (!ParseArgs(args, &options)) {
157 return 1;
158 }
159
160 if (options.show_help) {
161 std::cout << kUsage << std::endl;
162 return 0;
163 }
164
165 if (options.input_filenames.size() != 2) {
166 std::cerr << "Two input file names are required." << std::endl;
167 return 1;
168 }
169
170 amber::TypeParser parser;
171 auto type = parser.Parse("R8G8B8A8_UNORM");
172 amber::Format fmt(type.get());
173
174 amber::Buffer buffers[2];
175 for (size_t i = 0; i < 2; ++i) {
176 buffers[i].SetFormat(&fmt);
177 amber::Result res =
178 LoadPngToBuffer(options.input_filenames[i], &buffers[i]);
179 if (!res.IsSuccess()) {
180 std::cerr << "Error loading " << options.input_filenames[i] << ": "
181 << res.Error() << std::endl;
182 return 1;
183 }
184 }
185
186 amber::Result res;
187 if (options.compare_algorithm == CompareAlgorithm::kRMSE)
188 res = buffers[0].CompareRMSE(&buffers[1], options.tolerance);
189 else if (options.compare_algorithm == CompareAlgorithm::kHISTOGRAM_EMD)
190 res = buffers[0].CompareHistogramEMD(&buffers[1], options.tolerance);
191
192 if (res.IsSuccess())
193 std::cout << "Images similar" << std::endl;
194 else
195 std::cout << "Images differ: " << res.Error() << std::endl;
196
197 return !res.IsSuccess();
198 }
199