• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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