blob: 752f892ff718943c95da26aac84e3de6dcce3e44 [file]
// Copyright 2023 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
#include "swapbase_command.h"
#include "avif/avif_cxx.h"
#include "imageio.h"
namespace avif {
SwapBaseCommand::SwapBaseCommand()
: ProgramCommand(
"swapbase",
"Swaps the base and alternate images (e.g. if the base image is SDR "
"and the alternate is HDR, makes the base HDR). The alternate image "
"is the result ot fully applying the gain map.") {
argparse_.add_argument(arg_input_filename_, "input_filename");
argparse_.add_argument(arg_output_filename_, "output_filename");
arg_image_read_.Init(argparse_);
arg_image_encode_.Init(argparse_, /*can_have_alpha=*/true);
argparse_.add_argument(arg_gain_map_quality_, "--qgain-map")
.help("Quality for the gain map (0-100, where 100 is lossless)")
.default_value("60");
}
avifResult SwapBaseCommand::Run() {
DecoderPtr decoder(avifDecoderCreate());
if (decoder == NULL) {
return AVIF_RESULT_OUT_OF_MEMORY;
}
decoder->enableParsingGainMapMetadata = true;
decoder->enableDecodingGainMap = true;
avifResult result = ReadAvif(decoder.get(), arg_input_filename_,
arg_image_read_.ignore_profile);
if (result != AVIF_RESULT_OK) {
return result;
}
if (decoder->image->gainMap.image == nullptr) {
std::cerr << "Input image " << arg_output_filename_
<< " does not contain a gain map\n";
return AVIF_RESULT_INVALID_ARGUMENT;
}
int depth = arg_image_read_.depth;
const float headroom =
static_cast<double>(
decoder->image->gainMap.metadata.alternateHdrHeadroomN) /
decoder->image->gainMap.metadata.alternateHdrHeadroomD;
const bool tone_mapping_to_sdr = (headroom == 0.0f);
if (depth == 0) {
depth = tone_mapping_to_sdr
? 8
: std::max(decoder->image->depth,
decoder->image->gainMap.image->depth);
}
ImagePtr new_base(
avifImageCreate(decoder->image->width, decoder->image->height, depth,
(avifPixelFormat)arg_image_read_.pixel_format.value()));
// The gain map's cicp values are those of the 'tmap' item and describe the
// image obtained by fully applying the gain map. See ISO/IEC JTC 1/SC 29/WG 3
// m64379v1 4.1.1: A tmap derived item shall be associated with a colr item
// property. This property describes the colour properties of the
// reconstructed image if the gain map input item is fully applied according
// to ISO/AWI 214961.
if (decoder->image->gainMap.image->transferCharacteristics !=
AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED ||
decoder->image->gainMap.image->colorPrimaries !=
AVIF_COLOR_PRIMARIES_UNSPECIFIED ||
decoder->image->gainMap.image->matrixCoefficients !=
AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED) {
new_base->colorPrimaries = decoder->image->gainMap.image->colorPrimaries;
new_base->transferCharacteristics =
decoder->image->gainMap.image->transferCharacteristics;
new_base->matrixCoefficients =
decoder->image->gainMap.image->matrixCoefficients;
} else {
// If there is no cicp on the gain map, use the same values as the old base
// image, except for the transfer characteristics.
new_base->colorPrimaries = decoder->image->colorPrimaries;
const avifTransferCharacteristics transfer_characteristics =
tone_mapping_to_sdr ? AVIF_TRANSFER_CHARACTERISTICS_SRGB
: AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084;
new_base->transferCharacteristics = transfer_characteristics;
new_base->matrixCoefficients = decoder->image->matrixCoefficients;
}
avifRGBImage new_base_rgb;
avifRGBImageSetDefaults(&new_base_rgb, new_base.get());
avifContentLightLevelInformationBox clli =
decoder->image->gainMap.image->clli;
const bool compute_clli =
!tone_mapping_to_sdr && clli.maxCLL == 0 && clli.maxPALL == 0;
avifDiagnostics diag;
result =
avifImageApplyGainMap(decoder->image, &decoder->image->gainMap, headroom,
new_base->transferCharacteristics, &new_base_rgb,
(compute_clli ? &clli : nullptr), &diag);
if (result != AVIF_RESULT_OK) {
std::cout << "Failed to tone map image: " << avifResultToString(result)
<< " (" << diag.error << ")\n";
return result;
}
result = avifImageRGBToYUV(new_base.get(), &new_base_rgb);
if (result != AVIF_RESULT_OK) {
std::cerr << "Failed to convert to YUV: " << avifResultToString(result)
<< "\n";
return result;
}
new_base->clli = clli;
new_base->gainMap = decoder->image->gainMap;
// new_base has taken ownership of the gain map image, so remove ownership
// from the old image to prevent a double free.
decoder->image->gainMap.image = nullptr;
// Set the gain map's cicp values and clli to the old base image's.
new_base->gainMap.image->clli = decoder->image->clli;
new_base->gainMap.image->colorPrimaries = decoder->image->colorPrimaries;
new_base->gainMap.image->transferCharacteristics =
decoder->image->transferCharacteristics;
new_base->gainMap.image->matrixCoefficients =
decoder->image->matrixCoefficients;
// Swap base and alternate in the gain map metadata.
avifGainMapMetadata& metadata = new_base->gainMap.metadata;
metadata.backwardDirection = !metadata.backwardDirection;
metadata.useBaseColorSpace = !metadata.useBaseColorSpace;
std::swap(metadata.baseHdrHeadroomN, metadata.alternateHdrHeadroomN);
std::swap(metadata.baseHdrHeadroomD, metadata.alternateHdrHeadroomD);
for (int c = 0; c < 3; ++c) {
std::swap(metadata.baseOffsetN, metadata.alternateOffsetN);
std::swap(metadata.baseOffsetD, metadata.alternateOffsetD);
}
// Steal metadata.
std::swap(new_base->xmp, decoder->image->xmp);
std::swap(new_base->exif, decoder->image->exif);
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(new_base.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