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