From 25d0d1a8ea5dadcb1beff78319437f7bbbf4150c Mon Sep 17 00:00:00 2001 From: Ram Mohan M Date: Mon, 25 Nov 2024 01:45:31 +0530 Subject: [PATCH] improve gainmap computation logic for dark pixels 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 Change-Id: Id98d95d020a01db6e85e08115a4405eac07d42c1 --- lib/include/ultrahdr/gainmapmath.h | 8 ++++++++ lib/src/gainmapmath.cpp | 12 +++++++----- lib/src/jpegr.cpp | 23 +++++++++-------------- tests/gainmapmath_test.cpp | 4 ++-- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/lib/include/ultrahdr/gainmapmath.h b/lib/include/ultrahdr/gainmapmath.h index d604ad2b..29713a9a 100644 --- a/lib/include/ultrahdr/gainmapmath.h +++ b/lib/include/ultrahdr/gainmapmath.h @@ -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; diff --git a/lib/src/gainmapmath.cpp b/lib/src/gainmapmath.cpp index fa56c3e8..d69e085a 100644 --- a/lib/src/gainmapmath.cpp +++ b/lib/src/gainmapmath.cpp @@ -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) { diff --git a/lib/src/jpegr.cpp b/lib/src/jpegr.cpp index 1f83b34d..630c9f75 100644 --- a/lib/src/jpegr.cpp +++ b/lib/src/jpegr.cpp @@ -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(clampPixelFloatLinear) - : static_cast(clampPixelFloat); while (jobQueue.dequeueJob(rowStart, rowEnd)) { for (size_t y = rowStart; y < rowEnd; ++y) { for (size_t x = 0; x < dest->w; ++x) { @@ -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; @@ -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(clampPixelFloatLinear) - : static_cast(clampPixelFloat); float gainmap_min_th[3] = {127.0f, 127.0f, 127.0f}; float gainmap_max_th[3] = {-128.0f, -128.0f, -128.0f}; @@ -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; @@ -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 [-14.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, -14.3f, 15.6f); + max_content_boost_log2 = (std::clamp)(max_content_boost_log2, -14.3f, 15.6f); if (this->mMaxContentBoost != FLT_MAX) { float suggestion = log2(this->mMaxContentBoost); max_content_boost_log2 = (std::min)(max_content_boost_log2, suggestion); @@ -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; diff --git a/tests/gainmapmath_test.cpp b/tests/gainmapmath_test.cpp index 91d942a8..832b01a6 100644 --- a/tests/gainmapmath_test.cpp +++ b/tests/gainmapmath_test.cpp @@ -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); @@ -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); }