blob: db28ebed4807d1fd57c429af0bf82e3983979d88 [file] [log] [blame]
// Copyright 2023 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include "combine_command.h"
#include <cmath>
#include "avif/avif_cxx.h"
#include "imageio.h"
namespace avif {
CombineCommand::CombineCommand()
: ProgramCommand("combine",
"Creates an avif image with a gain map from a base image "
"and an alternate image.") {
argparse_.add_argument(arg_base_filename_, "base_image")
.help(
"The base image, that will be shown by viewers that don't support "
"gain maps");
argparse_.add_argument(arg_alternate_filename_, "alternate_image")
.help("The alternate image, the result of fully applying the gain map");
argparse_.add_argument(arg_output_filename_, "output_image.avif");
argparse_.add_argument(arg_downscaling_, "--downscaling")
.help("Downscaling factor for the gain map")
.default_value("1");
argparse_.add_argument(arg_gain_map_quality_, "--qgain-map")
.help("Quality for the gain map (0-100, where 100 is lossless)")
.default_value("60");
argparse_.add_argument(arg_gain_map_depth_, "--depth-gain-map")
.choices({"8", "10", "12"})
.help("Output depth for the gain map")
.default_value("8");
argparse_
.add_argument<int, PixelFormatConverter>(arg_gain_map_pixel_format_,
"--yuv-gain-map")
.choices({"444", "422", "420", "400"})
.help("Output format for the gain map")
.default_value("444");
argparse_
.add_argument<CicpValues, CicpConverter>(arg_base_cicp_, "--cicp-base")
.help(
"Set or override the cicp values for the base image, expressed as "
"P/T/M where P = color primaries, T = transfer characteristics, "
"M = matrix coefficients.");
argparse_
.add_argument<CicpValues, CicpConverter>(arg_alternate_cicp_,
"--cicp-alternate")
.help(
"Set or override the cicp values for the alternate image, expressed "
"as P/T/M where P = color primaries, T = transfer characteristics, "
"M = matrix coefficients.");
arg_image_encode_.Init(argparse_, /*can_have_alpha=*/true);
arg_image_read_.Init(argparse_);
}
avifResult CombineCommand::Run() {
const avifPixelFormat pixel_format =
static_cast<avifPixelFormat>(arg_image_read_.pixel_format.value());
const avifPixelFormat gain_map_pixel_format =
static_cast<avifPixelFormat>(arg_gain_map_pixel_format_.value());
ImagePtr base_image(avifImageCreateEmpty());
ImagePtr alternate_image(avifImageCreateEmpty());
if (base_image == nullptr || alternate_image == nullptr) {
return AVIF_RESULT_OUT_OF_MEMORY;
}
avifResult result =
ReadImage(base_image.get(), arg_base_filename_, pixel_format,
arg_image_read_.depth, arg_image_read_.ignore_profile);
if (result != AVIF_RESULT_OK) {
std::cout << "Failed to read base image: " << avifResultToString(result)
<< "\n";
return result;
}
if (arg_base_cicp_.provenance() == argparse::Provenance::SPECIFIED) {
base_image->colorPrimaries = arg_base_cicp_.value().color_primaries;
base_image->transferCharacteristics =
arg_base_cicp_.value().transfer_characteristics;
base_image->matrixCoefficients = arg_base_cicp_.value().matrix_coefficients;
}
result =
ReadImage(alternate_image.get(), arg_alternate_filename_, pixel_format,
arg_image_read_.depth, arg_image_read_.ignore_profile);
if (result != AVIF_RESULT_OK) {
std::cout << "Failed to read alternate image: "
<< avifResultToString(result) << "\n";
return result;
}
if (arg_alternate_cicp_.provenance() == argparse::Provenance::SPECIFIED) {
alternate_image->colorPrimaries =
arg_alternate_cicp_.value().color_primaries;
alternate_image->transferCharacteristics =
arg_alternate_cicp_.value().transfer_characteristics;
alternate_image->matrixCoefficients =
arg_alternate_cicp_.value().matrix_coefficients;
}
const int downscaling = std::max<int>(1, arg_downscaling_);
const uint32_t gain_map_width = std::max<uint32_t>(
std::round((float)base_image->width / downscaling), 1u);
const uint32_t gain_map_height = std::max<uint32_t>(
std::round((float)base_image->height / downscaling), 1u);
std::cout << "Creating a gain map of size " << gain_map_width << " x "
<< gain_map_height << "\n";
base_image->gainMap = avifGainMapCreate();
base_image->gainMap->image =
avifImageCreate(gain_map_width, gain_map_height, arg_gain_map_depth_,
gain_map_pixel_format);
if (base_image->gainMap->image == nullptr) {
return AVIF_RESULT_OUT_OF_MEMORY;
}
avifDiagnostics diag;
result = avifImageComputeGainMap(base_image.get(), alternate_image.get(),
base_image->gainMap, &diag);
if (result != AVIF_RESULT_OK) {
std::cout << "Failed to compute gain map: " << avifResultToString(result)
<< " (" << diag.error << ")\n";
return result;
}
EncoderPtr encoder(avifEncoderCreate());
if (encoder == nullptr) {
return AVIF_RESULT_OUT_OF_MEMORY;
}
encoder->quality = arg_image_encode_.quality;
encoder->qualityAlpha = arg_image_encode_.quality_alpha;
encoder->qualityGainMap = arg_gain_map_quality_;
encoder->speed = arg_image_encode_.speed;
result = WriteAvif(base_image.get(), encoder.get(), arg_output_filename_);
if (result != AVIF_RESULT_OK) {
std::cout << "Failed to encode image: " << avifResultToString(result)
<< " (" << encoder->diag.error << ")\n";
return result;
}
return AVIF_RESULT_OK;
}
} // namespace avif