blob: f6805d54328bc1afaf23a1e1893897b92cc676a4 [file]
// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
//
// Compares two files and returns whether they are the same once decoded.
//
// Usage:
// are_images_equal file1 file2 ignore_alpha_flag [psnr_threshold]
// [ignore_gain_map_flag]
// If psnr_threshold is not provided or is 0, the images are compared for strict
// equality, including all metadata. If psnr_threshold is greater than 0, the
// images are compared for similarity using PSNR. Most metadata is NOT checked.
// If a gain map is present and ignore_gain_map_flag is absent or 0, the
// tone-mapped images are also compared using PSNR. ignore_alpha_flag and
// ignore_gain_map_flag are booleans (0 or 1).
#include <cstdint>
#include <cstring>
#include <iostream>
#include <limits>
#include <string>
#include "aviftest_helpers.h"
#include "avifutil.h"
using avif::ImagePtr;
using avif::RGBImageCleanup;
namespace {
ImagePtr ToneMapToAlternate(const ImagePtr& image) {
avif::testutil::AvifRgbImage toneMappedRgb(image.get(), image->depth,
AVIF_RGB_FORMAT_RGB);
RGBImageCleanup rgbCleanup(&toneMappedRgb);
avif::ImagePtr toneMapped(
avifImageCreate(toneMappedRgb.width, toneMappedRgb.height,
toneMappedRgb.depth, AVIF_PIXEL_FORMAT_YUV444));
toneMapped->colorPrimaries = image->gainMap->altColorPrimaries;
toneMapped->transferCharacteristics =
image->gainMap->altTransferCharacteristics;
toneMapped->matrixCoefficients = image->gainMap->altMatrixCoefficients;
toneMapped->yuvRange = image->yuvRange;
float altHeadroom = 0.0f;
if (image->gainMap->alternateHdrHeadroom.d != 0) {
altHeadroom = (float)image->gainMap->alternateHdrHeadroom.n /
image->gainMap->alternateHdrHeadroom.d;
}
avifDiagnostics diag;
avifResult res = avifImageApplyGainMap(
image.get(), image->gainMap, altHeadroom, toneMapped->colorPrimaries,
toneMapped->transferCharacteristics, &toneMappedRgb, &toneMapped->clli,
&diag);
if (res != AVIF_RESULT_OK) {
std::cerr << "Failed to apply gain map: " << diag.error << std::endl;
return nullptr;
}
res = avifImageRGBToYUV(toneMapped.get(), &toneMappedRgb);
if (res != AVIF_RESULT_OK) {
return nullptr;
}
return toneMapped;
}
} // namespace
int main(int argc, char** argv) {
if (argc < 4 || argc > 6) {
std::cerr << "Wrong argument: " << argv[0]
<< " file1 file2 ignore_alpha_flag [psnr_threshold] "
"[ignore_gain_map_flag]"
<< std::endl;
return 2;
}
const bool ignore_gain_map = argc > 5 && std::stoi(argv[5]) != 0;
const double psnr_threshold = argc > 4 ? std::stod(argv[4]) : 0;
ImagePtr decoded[2] = {ImagePtr(avifImageCreateEmpty()),
ImagePtr(avifImageCreateEmpty())};
if (!decoded[0] || !decoded[1]) {
std::cerr << "Cannot create AVIF images." << std::endl;
return 2;
}
uint32_t depth[2];
// Request the bit depth closest to the bit depth of the input file.
constexpr int kRequestedDepth = 0;
constexpr avifPixelFormat requestedFormat = AVIF_PIXEL_FORMAT_NONE;
for (int i : {0, 1}) {
// Make sure no color conversion happens.
decoded[i]->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
if (avifReadImage(
argv[i + 1], AVIF_APP_FILE_FORMAT_UNKNOWN /* guess format */,
requestedFormat, kRequestedDepth,
AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
/*ignoreColorProfile==*/AVIF_FALSE, /*ignoreExif=*/AVIF_FALSE,
/*ignoreXMP=*/AVIF_FALSE, ignore_gain_map,
/*imageSizeLimit=*/std::numeric_limits<uint32_t>::max(),
decoded[i].get(), &depth[i], nullptr,
nullptr) == AVIF_APP_FILE_FORMAT_UNKNOWN) {
std::cerr << "Image " << argv[i + 1] << " cannot be read." << std::endl;
return 2;
}
}
if (depth[0] != depth[1]) {
std::cerr << "Images " << argv[1] << " and " << argv[2]
<< " have different depths." << std::endl;
return 1;
}
if (!ignore_gain_map &&
((decoded[0]->gainMap != nullptr) != (decoded[1]->gainMap != nullptr))) {
std::cerr << "Images " << argv[1] << " and " << argv[2]
<< " have different gainmap presence" << std::endl;
return 1;
}
bool ignore_alpha = std::stoi(argv[3]) != 0;
if (psnr_threshold <= 0) { // Check for strict equality.
if (!avif::testutil::AreImagesEqual(*decoded[0], *decoded[1],
ignore_alpha)) {
auto psnr =
avif::testutil::GetPsnr(*decoded[0], *decoded[1], ignore_alpha);
std::cerr << "Images " << argv[1] << " and " << argv[2]
<< " are different (base image PSNR: " << psnr << ")."
<< std::endl;
return 1;
}
std::cout << "Images " << argv[1] << " and " << argv[2] << " are identical."
<< std::endl;
} else {
// If there is a gain map, this is the PSNR of the base images only.
auto psnr = avif::testutil::GetPsnr(*decoded[0], *decoded[1], ignore_alpha);
if (psnr < psnr_threshold) {
std::cerr << "PSNR: " << psnr << ", images " << argv[1] << " and "
<< argv[2] << " are not similar enough (threshold: " << argv[4]
<< ")." << std::endl;
return 1;
}
std::cout << "PSNR: " << psnr << ", images " << argv[1] << " and "
<< argv[2] << " are similar." << std::endl;
// If there is a gain map, tone map both images to their alternate headroom
// and check the PSNR of the tone mapped images.
if (decoded[0]->gainMap && decoded[1]->gainMap &&
decoded[0]->gainMap->image && decoded[1]->gainMap->image) {
// We don't support ICC profiles when tonemapping, so clear them after
// checking for equality.
if (decoded[0]->icc.size != decoded[1]->icc.size ||
(decoded[0]->icc.size > 0 &&
std::memcmp(decoded[0]->icc.data, decoded[1]->icc.data,
decoded[0]->icc.size) != 0)) {
std::cerr << "Gainmap PSNR: Images " << argv[1] << " and " << argv[2]
<< " have different ICC profiles." << std::endl;
return 1;
}
if (decoded[0]->gainMap->altICC.size !=
decoded[1]->gainMap->altICC.size ||
(decoded[0]->gainMap->altICC.size > 0 &&
std::memcmp(decoded[0]->gainMap->altICC.data,
decoded[1]->gainMap->altICC.data,
decoded[0]->gainMap->altICC.size) != 0)) {
std::cerr << "Gainmap PSNR: Images " << argv[1] << " and " << argv[2]
<< " have different ICC profiles." << std::endl;
return 1;
}
avifRWDataFree(&decoded[0]->icc);
avifRWDataFree(&decoded[1]->icc);
avifRWDataFree(&decoded[0]->gainMap->altICC);
avifRWDataFree(&decoded[1]->gainMap->altICC);
avif::ImagePtr toneMapped0 = ToneMapToAlternate(decoded[0]);
avif::ImagePtr toneMapped1 = ToneMapToAlternate(decoded[1]);
if (!toneMapped0 || !toneMapped1) {
std::cerr << "Failed to tone map images." << std::endl;
return 1;
}
psnr = avif::testutil::GetPsnr(*toneMapped0, *toneMapped1);
if (psnr < psnr_threshold) {
std::cerr << "Tone-mapped PSNR: " << psnr << ", images " << argv[1]
<< " and " << argv[2]
<< " are not similar enough (threshold: " << argv[4] << ")."
<< std::endl;
return 1;
}
std::cout << "Tone-mapped PSNR: " << psnr << ", images " << argv[1]
<< " and " << argv[2] << " are similar." << std::endl;
}
}
return 0;
}