Skip to content

Commit

Permalink
improve gainmap computation logic for dark pixels
Browse files Browse the repository at this point in the history
while computing gain for near blacks or pixels with channel values zero,
sdr_offset is used. This value is extremely small and can result large
gainmap coefficient. During encode-decode process, if this sdr pixel
flips to zero, after applying gainmap the hdr pixel will blow out. It is
best if the gainmap computation logic is desensitized for dark pixels.

also, conversion from wider to narrower gamut space can cause overrange
values. clip only negative excursions so that domain of computeGain
remains positive.

Test: ./ultrahdr_app
  • Loading branch information
ram-mohan committed Nov 28, 2024
1 parent cff53d3 commit 546d9c4
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 21 deletions.
8 changes: 8 additions & 0 deletions lib/include/ultrahdr/gainmapmath.h
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,14 @@ PutPixelFn putPixelFn(uhdr_img_fmt_t format);

////////////////////////////////////////////////////////////////////////////////
// common utils
static const float kHdrOffset = 1e-7f;
static const float kSdrOffset = 1e-7f;

static inline float clipNegatives(float value) { return (value < 0.0f) ? 0.0f : value; }

static inline Color clipNegatives(Color e) {
return {{{clipNegatives(e.r), clipNegatives(e.g), clipNegatives(e.b)}}};
}

// maximum limit of normalized pixel value in float representation
static const float kMaxPixelFloat = 1.0f;
Expand Down
12 changes: 7 additions & 5 deletions lib/src/gainmapmath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -782,12 +782,14 @@ uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metada
}

float computeGain(float sdr, float hdr) {
if (sdr == 0.0f) return 0.0f; // for sdr black return no gain
if (hdr == 0.0f) { // for hdr black, return a gain large enough to attenuate the sdr pel
float offset = (1.0f / 64);
return log2(offset / (offset + sdr));
float gain = log2((hdr + kHdrOffset) / (sdr + kSdrOffset));
if (sdr < 2.f / 255.0f) {
// If sdr is zero and hdr is non zero, it can result in very large gain values. In compression -
// decompression process, if the same sdr pixel increases to 1, the hdr recovered pixel will
// blow out. Dont allow dark pixels to signal large gains.
gain = (std::min)(gain, 2.3f);
}
return log2(hdr / sdr);
return gain;
}

uint8_t affineMapGain(float gainlog2, float mingainlog2, float maxgainlog2, float gamma) {
Expand Down
23 changes: 9 additions & 14 deletions lib/src/jpegr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -697,9 +697,6 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt);
const float hdrSampleToNitsFactor =
hdr_intent->ct == UHDR_CT_LINEAR ? kSdrWhiteNits : hdr_white_nits;
ColorTransformFn clampPixel = hdr_intent->ct == UHDR_CT_LINEAR
? static_cast<ColorTransformFn>(clampPixelFloatLinear)
: static_cast<ColorTransformFn>(clampPixelFloat);
while (jobQueue.dequeueJob(rowStart, rowEnd)) {
for (size_t y = rowStart; y < rowEnd; ++y) {
for (size_t x = 0; x < dest->w; ++x) {
Expand Down Expand Up @@ -730,7 +727,7 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn);
hdr_rgb = hdrGamutConversionFn(hdr_rgb);
hdr_rgb = clampPixel(hdr_rgb);
hdr_rgb = clipNegatives(hdr_rgb);

if (mUseMultiChannelGainMap) {
Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits;
Expand Down Expand Up @@ -807,9 +804,6 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt);
const float hdrSampleToNitsFactor =
hdr_intent->ct == UHDR_CT_LINEAR ? kSdrWhiteNits : hdr_white_nits;
ColorTransformFn clampPixel = hdr_intent->ct == UHDR_CT_LINEAR
? static_cast<ColorTransformFn>(clampPixelFloatLinear)
: static_cast<ColorTransformFn>(clampPixelFloat);
float gainmap_min_th[3] = {127.0f, 127.0f, 127.0f};
float gainmap_max_th[3] = {-128.0f, -128.0f, -128.0f};

Expand Down Expand Up @@ -843,7 +837,7 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn);
hdr_rgb = hdrGamutConversionFn(hdr_rgb);
hdr_rgb = clampPixel(hdr_rgb);
hdr_rgb = clipNegatives(hdr_rgb);

if (mUseMultiChannelGainMap) {
Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits;
Expand Down Expand Up @@ -907,10 +901,11 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
min_content_boost_log2 = (std::min)(gainmap_min[index], min_content_boost_log2);
max_content_boost_log2 = (std::max)(gainmap_max[index], max_content_boost_log2);
}
// -13.0 emphirically is a small enough gain factor that is capable of representing hdr
// black from any sdr luminance. Allowing further excursion might not offer any benefit and on
// the downside can cause bigger error during affine map and inverse map.
min_content_boost_log2 = (std::max)(-13.0f, min_content_boost_log2);
// gain coefficient range [-15.3, 15.6] is capable of representing hdr pels from sdr pels.
// Allowing further excursion might not offer any benefit and on the downside can cause bigger
// error during affine map and inverse affine map.
min_content_boost_log2 = (std::clamp)(min_content_boost_log2, -15.3f, 15.6f);
max_content_boost_log2 = (std::clamp)(max_content_boost_log2, -15.3f, 15.6f);
if (this->mMaxContentBoost != FLT_MAX) {
float suggestion = log2(this->mMaxContentBoost);
max_content_boost_log2 = (std::min)(max_content_boost_log2, suggestion);
Expand Down Expand Up @@ -969,8 +964,8 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
gainmap_metadata->max_content_boost = exp2(max_content_boost_log2);
gainmap_metadata->min_content_boost = exp2(min_content_boost_log2);
gainmap_metadata->gamma = this->mGamma;
gainmap_metadata->offset_sdr = 0.0f;
gainmap_metadata->offset_hdr = 0.0f;
gainmap_metadata->offset_sdr = kSdrOffset;
gainmap_metadata->offset_hdr = kHdrOffset;
gainmap_metadata->hdr_capacity_min = 1.0f;
if (this->mTargetDispPeakBrightness != -1.0f) {
gainmap_metadata->hdr_capacity_max = this->mTargetDispPeakBrightness / kSdrWhiteNits;
Expand Down
4 changes: 2 additions & 2 deletions tests/gainmapmath_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1276,7 +1276,7 @@ TEST_F(GainMapMathTest, EncodeGain) {
float max_boost = log2(4.0f);
float gamma = 1.0f;

EXPECT_EQ(affineMapGain(computeGain(0.0f, 1.0f), min_boost, max_boost, 1.0f), 128);
EXPECT_EQ(affineMapGain(computeGain(0.0f, 1.0f), min_boost, max_boost, 1.0f), 255);
EXPECT_EQ(affineMapGain(computeGain(1.0f, 0.0f), min_boost, max_boost, 1.0f), 0);
EXPECT_EQ(affineMapGain(computeGain(0.5f, 0.0f), min_boost, max_boost, 1.0f), 0);
EXPECT_EQ(affineMapGain(computeGain(1.0f, 1.0), min_boost, max_boost, 1.0f), 128);
Expand Down Expand Up @@ -1322,7 +1322,7 @@ TEST_F(GainMapMathTest, EncodeGain) {
EXPECT_EQ(affineMapGain(computeGain(1.0f, 1.0f), min_boost, max_boost, 1.0f), 64);
EXPECT_EQ(affineMapGain(computeGain(1.0f, 8.0f), min_boost, max_boost, 1.0f), 255);
EXPECT_EQ(affineMapGain(computeGain(1.0f, 4.0f), min_boost, max_boost, 1.0f), 191);
EXPECT_EQ(affineMapGain(computeGain(1.0f, 2.0f), min_boost, max_boost, 1.0f), 128);
EXPECT_EQ(affineMapGain(computeGain(1.0f, 2.0f), min_boost, max_boost, 1.0f), 127);
EXPECT_EQ(affineMapGain(computeGain(1.0f, 0.7071f), min_boost, max_boost, 1.0f), 32);
EXPECT_EQ(affineMapGain(computeGain(1.0f, 0.5f), min_boost, max_boost, 1.0f), 0);
}
Expand Down

0 comments on commit 546d9c4

Please sign in to comment.