Skip to content

Commit

Permalink
Refactor gain map API.
Browse files Browse the repository at this point in the history
Remote avifGainMapMetadata and avifGainMapMetadataDouble which were
not extensible. Expose avifDoubleTo(Un)SignedFraction instead.
  • Loading branch information
maryla-uc committed Sep 26, 2024
1 parent 2fe5b3a commit dd6325e
Show file tree
Hide file tree
Showing 19 changed files with 467 additions and 557 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ The changes are relative to the previous release, unless the baseline is specifi
* Write an empty HandlerBox name field instead of "libavif" (saves 7 bytes).
* Update aom.cmd/LocalAom.cmake: v3.10.0
* Update svt.cmd/svt.sh/LocalSvt.cmake: v2.2.1
* Change experimental gainmap API: remove avifGainMapMetadata and
avifGainMapMetadataDouble structs.
* Add avifDoubleToSignedFraction and avifDoubleToUnsignedFraction functions.

## [1.1.1] - 2024-07-30

Expand Down
2 changes: 1 addition & 1 deletion apps/avifgainmaputil/convert_command.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ avifResult ConvertCommand::Run() {
if (arg_swap_base_) {
int depth = arg_image_read_.depth;
if (depth == 0) {
depth = image->gainMap->metadata.alternateHdrHeadroomN == 0 ? 8 : 10;
depth = image->gainMap->alternateHdrHeadroomN == 0 ? 8 : 10;
}
ImagePtr new_base(avifImageCreateEmpty());
if (new_base == nullptr) {
Expand Down
24 changes: 12 additions & 12 deletions apps/avifgainmaputil/printmetadata_command.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,34 +65,34 @@ avifResult PrintMetadataCommand::Run() {
}
assert(decoder->image->gainMap);

const avifGainMapMetadata& metadata = decoder->image->gainMap->metadata;
const avifGainMap& gainMap = *decoder->image->gainMap;
const int w = 20;
std::cout << " * " << std::left << std::setw(w) << "Base headroom: "
<< FormatFraction(metadata.baseHdrHeadroomN,
metadata.baseHdrHeadroomD)
<< FormatFraction(gainMap.baseHdrHeadroomN,
gainMap.baseHdrHeadroomD)
<< "\n";
std::cout << " * " << std::left << std::setw(w) << "Alternate headroom: "
<< FormatFraction(metadata.alternateHdrHeadroomN,
metadata.alternateHdrHeadroomD)
<< FormatFraction(gainMap.alternateHdrHeadroomN,
gainMap.alternateHdrHeadroomD)
<< "\n";
std::cout << " * " << std::left << std::setw(w) << "Gain Map Min: "
<< FormatFractions(metadata.gainMapMinN, metadata.gainMapMinD)
<< FormatFractions(gainMap.gainMapMinN, gainMap.gainMapMinD)
<< "\n";
std::cout << " * " << std::left << std::setw(w) << "Gain Map Max: "
<< FormatFractions(metadata.gainMapMaxN, metadata.gainMapMaxD)
<< FormatFractions(gainMap.gainMapMaxN, gainMap.gainMapMaxD)
<< "\n";
std::cout << " * " << std::left << std::setw(w) << "Base Offset: "
<< FormatFractions(metadata.baseOffsetN, metadata.baseOffsetD)
<< FormatFractions(gainMap.baseOffsetN, gainMap.baseOffsetD)
<< "\n";
std::cout << " * " << std::left << std::setw(w) << "Alternate Offset: "
<< FormatFractions(metadata.alternateOffsetN,
metadata.alternateOffsetD)
<< FormatFractions(gainMap.alternateOffsetN,
gainMap.alternateOffsetD)
<< "\n";
std::cout << " * " << std::left << std::setw(w) << "Gain Map Gamma: "
<< FormatFractions(metadata.gainMapGammaN, metadata.gainMapGammaD)
<< FormatFractions(gainMap.gainMapGammaN, gainMap.gainMapGammaD)
<< "\n";
std::cout << " * " << std::left << std::setw(w) << "Use Base Color Space: "
<< (metadata.useBaseColorSpace ? "True" : "False") << "\n";
<< (gainMap.useBaseColorSpace ? "True" : "False") << "\n";

return AVIF_RESULT_OK;
}
Expand Down
20 changes: 11 additions & 9 deletions apps/avifgainmaputil/swapbase_command.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ avifResult ChangeBase(const avifImage& image, int depth,
swapped->yuvFormat = yuvFormat;

const float headroom =
static_cast<float>(image.gainMap->metadata.alternateHdrHeadroomN) /
image.gainMap->metadata.alternateHdrHeadroomD;
image.gainMap->alternateHdrHeadroomD == 0
? 0.0f
: static_cast<float>(image.gainMap->alternateHdrHeadroomN) /
image.gainMap->alternateHdrHeadroomD;
const bool tone_mapping_to_sdr = (headroom == 0.0f);

swapped->colorPrimaries = image.gainMap->altColorPrimaries;
Expand Down Expand Up @@ -97,14 +99,14 @@ avifResult ChangeBase(const avifImage& image, int depth,
(image.yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3;
swapped->gainMap->altCLLI = image.clli;

// Swap base and alternate in the gain map metadata.
avifGainMapMetadata& metadata = swapped->gainMap->metadata;
metadata.useBaseColorSpace = !metadata.useBaseColorSpace;
std::swap(metadata.baseHdrHeadroomN, metadata.alternateHdrHeadroomN);
std::swap(metadata.baseHdrHeadroomD, metadata.alternateHdrHeadroomD);
// Swap base and alternate in the gain map
avifGainMap* gainMap = swapped->gainMap;
gainMap->useBaseColorSpace = !gainMap->useBaseColorSpace;
std::swap(gainMap->baseHdrHeadroomN, gainMap->alternateHdrHeadroomN);
std::swap(gainMap->baseHdrHeadroomD, gainMap->alternateHdrHeadroomD);
for (int c = 0; c < 3; ++c) {
std::swap(metadata.baseOffsetN, metadata.alternateOffsetN);
std::swap(metadata.baseOffsetD, metadata.alternateOffsetD);
std::swap(gainMap->baseOffsetN, gainMap->alternateOffsetN);
std::swap(gainMap->baseOffsetD, gainMap->alternateOffsetD);
}

return AVIF_RESULT_OK;
Expand Down
36 changes: 19 additions & 17 deletions apps/avifgainmaputil/tonemap_command.cc
Original file line number Diff line number Diff line change
Expand Up @@ -86,28 +86,30 @@ avifResult TonemapCommand::Run() {
return AVIF_RESULT_INVALID_ARGUMENT;
}

avifGainMapMetadataDouble metadata;
if (!avifGainMapMetadataFractionsToDouble(&metadata,
&image->gainMap->metadata)) {
std::cerr << "Input image " << arg_input_filename_
<< " has invalid gain map metadata\n";
return AVIF_RESULT_INVALID_ARGUMENT;
}

const float base_hdr_hreadroom =
image->gainMap->baseHdrHeadroomD == 0
? 0.0f
: (float)image->gainMap->baseHdrHeadroomN /
image->gainMap->baseHdrHeadroomD;
const float alternate_hdr_hreadroom =
image->gainMap->alternateHdrHeadroomD == 0
? 0.0f
: (float)image->gainMap->alternateHdrHeadroomN /
image->gainMap->alternateHdrHeadroomD;
// We are either tone mapping to the base image (i.e. leaving it as is),
// or tone mapping to the alternate image (i.e. fully applying the gain map),
// or tone mapping in between (partially applying the gain map).
const bool tone_mapping_to_base =
(headroom <= metadata.baseHdrHeadroom &&
metadata.baseHdrHeadroom <= metadata.alternateHdrHeadroom) ||
(headroom >= metadata.baseHdrHeadroom &&
metadata.baseHdrHeadroom >= metadata.alternateHdrHeadroom);
(headroom <= base_hdr_hreadroom &&
base_hdr_hreadroom <= alternate_hdr_hreadroom) ||
(headroom >= base_hdr_hreadroom &&
base_hdr_hreadroom >= alternate_hdr_hreadroom);
const bool tone_mapping_to_alternate =
(headroom <= metadata.alternateHdrHeadroom &&
metadata.alternateHdrHeadroom <= metadata.baseHdrHeadroom) ||
(headroom >= metadata.alternateHdrHeadroom &&
metadata.alternateHdrHeadroom >= metadata.baseHdrHeadroom);
const bool base_is_hdr = (metadata.baseHdrHeadroom != 0.0f);
(headroom <= alternate_hdr_hreadroom &&
alternate_hdr_hreadroom <= base_hdr_hreadroom) ||
(headroom >= alternate_hdr_hreadroom &&
alternate_hdr_hreadroom >= base_hdr_hreadroom);
const bool base_is_hdr = (base_hdr_hreadroom != 0.0f);

// Determine output CICP.
CicpValues cicp;
Expand Down
70 changes: 37 additions & 33 deletions apps/shared/avifjpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -592,75 +592,79 @@ static inline void SwapDoubles(double * x, double * y)
// Parses gain map metadata from XMP.
// See https://helpx.adobe.com/camera-raw/using/gain-map.html
// Returns AVIF_TRUE if the gain map metadata was successfully read.
static avifBool avifJPEGParseGainMapXMPProperties(const xmlNode * rootNode, avifGainMapMetadata * metadata)
static avifBool avifJPEGParseGainMapXMPProperties(const xmlNode * rootNode, avifGainMap * gainMap)
{
const xmlNode * descNode = avifJPEGFindGainMapXMPNode(rootNode);
if (descNode == NULL) {
return AVIF_FALSE;
}

avifGainMapMetadataDouble metadataDouble;
// Set default values from Adobe's spec.
metadataDouble.baseHdrHeadroom = 0.0;
metadataDouble.alternateHdrHeadroom = 1.0;
for (int i = 0; i < 3; ++i) {
metadataDouble.gainMapMin[i] = 0.0;
metadataDouble.gainMapMax[i] = 1.0;
metadataDouble.baseOffset[i] = 1.0 / 64.0;
metadataDouble.alternateOffset[i] = 1.0 / 64.0;
metadataDouble.gainMapGamma[i] = 1.0;
}
// Not in Adobe's spec but both color spaces should be the same so this value doesn't matter.
metadataDouble.useBaseColorSpace = AVIF_TRUE;

AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "HDRCapacityMin", &metadataDouble.baseHdrHeadroom, /*numDoubles=*/1));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "HDRCapacityMax", &metadataDouble.alternateHdrHeadroom, /*numDoubles=*/1));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "OffsetSDR", metadataDouble.baseOffset, /*numDoubles=*/3));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "OffsetHDR", metadataDouble.alternateOffset, /*numDoubles=*/3));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "GainMapMin", metadataDouble.gainMapMin, /*numDoubles=*/3));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "GainMapMax", metadataDouble.gainMapMax, /*numDoubles=*/3));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "Gamma", metadataDouble.gainMapGamma, /*numDoubles=*/3));
double baseHdrHeadroom = 0.0;
double alternateHdrHeadroom = 1.0;
double gainMapMin[3] = { 0.0, 0.0, 0.0 };
double gainMapMax[3] = { 1.0, 1.0, 1.0 };
double gainMapGamma[3] = { 1.0, 1.0, 1.0 };
double baseOffset[3] = { 1.0 / 64.0, 1.0 / 64.0, 1.0 / 64.0 };
double alternateOffset[3] = { 1.0 / 64.0, 1.0 / 64.0, 1.0 / 64.0 };
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "HDRCapacityMin", &baseHdrHeadroom, /*numDoubles=*/1));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "HDRCapacityMax", &alternateHdrHeadroom, /*numDoubles=*/1));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "OffsetSDR", baseOffset, /*numDoubles=*/3));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "OffsetHDR", alternateOffset, /*numDoubles=*/3));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "GainMapMin", gainMapMin, /*numDoubles=*/3));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "GainMapMax", gainMapMax, /*numDoubles=*/3));
AVIF_CHECK(avifJPEGFindGainMapPropertyDoubles(descNode, "Gamma", gainMapGamma, /*numDoubles=*/3));

// See inequality requirements in section 'XMP Representation of Gain Map Metadata' of Adobe's gain map specification
// https://helpx.adobe.com/camera-raw/using/gain-map.html
AVIF_CHECK(metadataDouble.alternateHdrHeadroom > metadataDouble.baseHdrHeadroom);
AVIF_CHECK(metadataDouble.baseHdrHeadroom >= 0);
AVIF_CHECK(alternateHdrHeadroom > baseHdrHeadroom);
AVIF_CHECK(baseHdrHeadroom >= 0);
for (int i = 0; i < 3; ++i) {
AVIF_CHECK(metadataDouble.gainMapMax[i] >= metadataDouble.gainMapMin[i]);
AVIF_CHECK(metadataDouble.baseOffset[i] >= 0.0);
AVIF_CHECK(metadataDouble.alternateOffset[i] >= 0.0);
AVIF_CHECK(metadataDouble.gainMapGamma[i] > 0.0);
AVIF_CHECK(gainMapMax[i] >= gainMapMin[i]);
AVIF_CHECK(baseOffset[i] >= 0.0);
AVIF_CHECK(alternateOffset[i] >= 0.0);
AVIF_CHECK(gainMapGamma[i] > 0.0);
}

uint32_t numValues;
const char * baseRenditionIsHDR;
if (avifJPEGFindGainMapProperty(descNode, "BaseRenditionIsHDR", /*maxValues=*/1, &baseRenditionIsHDR, &numValues)) {
if (!strcmp(baseRenditionIsHDR, "True")) {
SwapDoubles(&metadataDouble.baseHdrHeadroom, &metadataDouble.alternateHdrHeadroom);
SwapDoubles(&baseHdrHeadroom, &alternateHdrHeadroom);
for (int c = 0; c < 3; ++c) {
SwapDoubles(&metadataDouble.baseOffset[c], &metadataDouble.alternateOffset[c]);
SwapDoubles(&baseOffset[c], &alternateOffset[c]);
}
} else if (!strcmp(baseRenditionIsHDR, "False")) {
} else {
return AVIF_FALSE; // Unexpected value.
}
}

AVIF_CHECK(avifGainMapMetadataDoubleToFractions(metadata, &metadataDouble));
for (int i = 0; i < 3; ++i) {
AVIF_CHECK(avifDoubleToSignedFraction(gainMapMin[i], &gainMap->gainMapMinN[i], &gainMap->gainMapMinD[i]));
AVIF_CHECK(avifDoubleToSignedFraction(gainMapMax[i], &gainMap->gainMapMaxN[i], &gainMap->gainMapMaxD[i]));
AVIF_CHECK(avifDoubleToUnsignedFraction(gainMapGamma[i], &gainMap->gainMapGammaN[i], &gainMap->gainMapGammaD[i]));
AVIF_CHECK(avifDoubleToSignedFraction(baseOffset[i], &gainMap->baseOffsetN[i], &gainMap->baseOffsetD[i]));
AVIF_CHECK(avifDoubleToSignedFraction(alternateOffset[i], &gainMap->alternateOffsetN[i], &gainMap->alternateOffsetD[i]));
}
AVIF_CHECK(avifDoubleToUnsignedFraction(baseHdrHeadroom, &gainMap->baseHdrHeadroomN, &gainMap->baseHdrHeadroomD));
AVIF_CHECK(avifDoubleToUnsignedFraction(alternateHdrHeadroom, &gainMap->alternateHdrHeadroomN, &gainMap->alternateHdrHeadroomD));
// Not in Adobe's spec but both color spaces should be the same so this value doesn't matter.
gainMap->useBaseColorSpace = AVIF_TRUE;

return AVIF_TRUE;
}

// Parses gain map metadata from an XMP payload.
// Returns AVIF_TRUE if the gain map metadata was successfully read.
avifBool avifJPEGParseGainMapXMP(const uint8_t * xmpData, size_t xmpSize, avifGainMapMetadata * metadata)
avifBool avifJPEGParseGainMapXMP(const uint8_t * xmpData, size_t xmpSize, avifGainMap * gainMap)
{
xmlDoc * document = xmlReadMemory((const char *)xmpData, (int)xmpSize, NULL, NULL, LIBXML2_XML_PARSING_FLAGS);
if (document == NULL) {
return AVIF_FALSE; // Probably an out of memory error.
}
xmlNode * rootNode = xmlDocGetRootElement(document);
const avifBool res = avifJPEGParseGainMapXMPProperties(rootNode, metadata);
const avifBool res = avifJPEGParseGainMapXMPProperties(rootNode, gainMap);
xmlFreeDoc(document);
return res;
}
Expand Down Expand Up @@ -817,7 +821,7 @@ static avifBool avifJPEGExtractGainMapImage(FILE * f,
avifImageDestroy(image);
return AVIF_FALSE;
}
if (!avifJPEGParseGainMapXMP(image->xmp.data, image->xmp.size, &gainMap->metadata)) {
if (!avifJPEGParseGainMapXMP(image->xmp.data, image->xmp.size, gainMap)) {
fprintf(stderr, "Warning: failed to parse gain map metadata\n");
avifImageDestroy(image);
return AVIF_FALSE;
Expand Down
2 changes: 1 addition & 1 deletion apps/shared/avifjpeg.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ avifBool avifJPEGWrite(const char * outputFilename, const avifImage * avif, int

#if defined(AVIF_ENABLE_EXPERIMENTAL_JPEG_GAIN_MAP_CONVERSION)
// Parses XMP gain map metadata. Visible for testing.
avifBool avifJPEGParseGainMapXMP(const uint8_t * xmpData, size_t xmpSize, avifGainMapMetadata * metadata);
avifBool avifJPEGParseGainMapXMP(const uint8_t * xmpData, size_t xmpSize, avifGainMap * gainMap);
#endif

#ifdef __cplusplus
Expand Down
2 changes: 1 addition & 1 deletion apps/shared/avifutil.c
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ static void avifImageDumpInternal(const avifImage * avif,
avifPixelFormatToString(gainMapImage->yuvFormat),
(gainMapImage->yuvRange == AVIF_RANGE_FULL) ? "Full" : "Limited",
gainMapImage->matrixCoefficients,
(avif->gainMap->metadata.baseHdrHeadroomN == 0) ? "SDR" : "HDR");
(avif->gainMap->baseHdrHeadroomN == 0) ? "SDR" : "HDR");
printf(" * Alternate image:\n");
printf(" * Color Primaries: %u\n", avif->gainMap->altColorPrimaries);
printf(" * Transfer Char. : %u\n", avif->gainMap->altTransferCharacteristics);
Expand Down
Loading

0 comments on commit dd6325e

Please sign in to comment.