You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
5.9 KiB
199 lines
5.9 KiB
// Copyright 2019 The Amber Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <vector>
|
|
|
|
#include "src/buffer.h"
|
|
#include "src/format.h"
|
|
#include "src/type_parser.h"
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wweak-vtables"
|
|
#include "third_party/lodepng/lodepng.h"
|
|
#pragma clang diagnostic pop
|
|
|
|
namespace {
|
|
|
|
enum class CompareAlgorithm { kRMSE = 0, kHISTOGRAM_EMD = 1 };
|
|
|
|
struct Options {
|
|
std::vector<std::string> input_filenames;
|
|
bool show_help = false;
|
|
float tolerance = 1.0f;
|
|
CompareAlgorithm compare_algorithm = CompareAlgorithm::kRMSE;
|
|
};
|
|
|
|
const char kUsage[] = R"(Usage: image_diff [options] image1.png image2.png
|
|
|
|
Exactly one algorithm (and its parameters) must be specified.
|
|
|
|
Algorithms:
|
|
|
|
--rmse TOLERANCE
|
|
Compare using the Root Mean Square Error (RMSE) algorithm with
|
|
a floating point TOLERANCE value in the range 0..255, where 0
|
|
indicates identical images and 255 indicates images where every
|
|
color channel has the maximum difference.
|
|
|
|
--histogram_emd TOLERANCE
|
|
Compare the per-channel color histograms of the images using a
|
|
variant of the Earth Mover's Distance (EMD) algorithm with a
|
|
floating point TOLERANCE value in the range 0.0..1.0, where 0.0
|
|
indicates identical histograms and 1.0 indicates completely
|
|
different histograms for at least one of the color channels.
|
|
E.g. an image with red=255 for every pixel vs. an image with
|
|
red=0 for every pixel.
|
|
|
|
Other options:
|
|
|
|
-h | --help This help text.
|
|
)";
|
|
|
|
bool ParseArgs(const std::vector<std::string>& args, Options* opts) {
|
|
int num_algorithms = 0;
|
|
for (size_t i = 1; i < args.size(); ++i) {
|
|
const std::string& arg = args[i];
|
|
if (arg == "-h" || arg == "--help") {
|
|
opts->show_help = true;
|
|
return true;
|
|
} else if (arg == "--rmse") {
|
|
num_algorithms++;
|
|
opts->compare_algorithm = CompareAlgorithm::kRMSE;
|
|
++i;
|
|
if (i >= args.size()) {
|
|
std::cerr << "Missing tolerance value for RMSE comparison."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
std::stringstream sstream(args[i]);
|
|
sstream >> opts->tolerance;
|
|
if (sstream.fail()) {
|
|
std::cerr << "Invalid tolerance value " << args[i] << std::endl;
|
|
return false;
|
|
}
|
|
if (opts->tolerance < 0 || opts->tolerance > 255) {
|
|
std::cerr << "Tolerance must be in the range 0..255." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
} else if (arg == "--histogram_emd") {
|
|
num_algorithms++;
|
|
opts->compare_algorithm = CompareAlgorithm::kHISTOGRAM_EMD;
|
|
++i;
|
|
if (i >= args.size()) {
|
|
std::cerr << "Missing tolerance value for histogram EMD comparison."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
std::stringstream sstream(args[i]);
|
|
sstream >> opts->tolerance;
|
|
if (sstream.fail()) {
|
|
std::cerr << "Invalid tolerance value " << args[i] << std::endl;
|
|
return false;
|
|
}
|
|
if (opts->tolerance < 0 || opts->tolerance > 1) {
|
|
std::cerr << "Tolerance must be in the range 0..1." << std::endl;
|
|
return false;
|
|
}
|
|
} else if (!arg.empty()) {
|
|
opts->input_filenames.push_back(arg);
|
|
}
|
|
}
|
|
if (num_algorithms == 0) {
|
|
std::cerr << "No comparison algorithm specified." << std::endl;
|
|
return false;
|
|
} else if (num_algorithms > 1) {
|
|
std::cerr << "Only one comparison algorithm can be specified." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
amber::Result LoadPngToBuffer(const std::string& filename,
|
|
amber::Buffer* buffer) {
|
|
std::vector<unsigned char> image;
|
|
uint32_t width;
|
|
uint32_t height;
|
|
uint32_t error = lodepng::decode(image, width, height, filename.c_str());
|
|
|
|
if (error) {
|
|
std::string result = "PNG decode error: ";
|
|
result += lodepng_error_text(error);
|
|
return amber::Result(result);
|
|
}
|
|
|
|
std::vector<amber::Value> values;
|
|
values.resize(image.size());
|
|
for (size_t i = 0; i < image.size(); ++i) {
|
|
values[i].SetIntValue(image[i]);
|
|
}
|
|
|
|
buffer->SetData(values);
|
|
|
|
return {};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, const char** argv) {
|
|
std::vector<std::string> args(argv, argv + argc);
|
|
Options options;
|
|
|
|
if (!ParseArgs(args, &options)) {
|
|
return 1;
|
|
}
|
|
|
|
if (options.show_help) {
|
|
std::cout << kUsage << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
if (options.input_filenames.size() != 2) {
|
|
std::cerr << "Two input file names are required." << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
amber::TypeParser parser;
|
|
auto type = parser.Parse("R8G8B8A8_UNORM");
|
|
amber::Format fmt(type.get());
|
|
|
|
amber::Buffer buffers[2];
|
|
for (size_t i = 0; i < 2; ++i) {
|
|
buffers[i].SetFormat(&fmt);
|
|
amber::Result res =
|
|
LoadPngToBuffer(options.input_filenames[i], &buffers[i]);
|
|
if (!res.IsSuccess()) {
|
|
std::cerr << "Error loading " << options.input_filenames[i] << ": "
|
|
<< res.Error() << std::endl;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
amber::Result res;
|
|
if (options.compare_algorithm == CompareAlgorithm::kRMSE)
|
|
res = buffers[0].CompareRMSE(&buffers[1], options.tolerance);
|
|
else if (options.compare_algorithm == CompareAlgorithm::kHISTOGRAM_EMD)
|
|
res = buffers[0].CompareHistogramEMD(&buffers[1], options.tolerance);
|
|
|
|
if (res.IsSuccess())
|
|
std::cout << "Images similar" << std::endl;
|
|
else
|
|
std::cout << "Images differ: " << res.Error() << std::endl;
|
|
|
|
return !res.IsSuccess();
|
|
}
|