From 232bd431d9b7e7c0eb44b71c6d7351d91371c471 Mon Sep 17 00:00:00 2001 From: Ram Mohan M Date: Sun, 20 Oct 2024 22:39:40 +0530 Subject: [PATCH] apply ootf and its inverse for hlg transfers Test: ./ultrahdr_unit_test --- lib/include/ultrahdr/gainmapmath.h | 5 +++ lib/src/gainmapmath.cpp | 16 ++++++++- lib/src/gpu/applygainmap_gl.cpp | 19 +++++++++++ lib/src/jpegr.cpp | 52 ++++++++++++++++++++++-------- 4 files changed, 77 insertions(+), 15 deletions(-) diff --git a/lib/include/ultrahdr/gainmapmath.h b/lib/include/ultrahdr/gainmapmath.h index 0ec04061..e7cd8a6a 100644 --- a/lib/include/ultrahdr/gainmapmath.h +++ b/lib/include/ultrahdr/gainmapmath.h @@ -278,8 +278,13 @@ constexpr int32_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision; // hlg ootf (normalized) Color hlgOotf(Color e, LuminanceFn luminance); +Color hlgOotfApprox(Color e, [[maybe_unused]] LuminanceFn luminance); inline Color identityOotf(Color e, [[maybe_unused]] LuminanceFn) { return e; } +// hlg inverse ootf (normalized) +Color hlgInverseOotf(Color e, LuminanceFn luminance); +Color hlgInverseOotfApprox(Color e); + // pq oetf float pqOetf(float e); Color pqOetf(Color e); diff --git a/lib/src/gainmapmath.cpp b/lib/src/gainmapmath.cpp index 13835c01..a54bfc6b 100644 --- a/lib/src/gainmapmath.cpp +++ b/lib/src/gainmapmath.cpp @@ -294,6 +294,20 @@ Color hlgOotf(Color e, LuminanceFn luminance) { return e * std::pow(y, kOotfGamma - 1.0f); } +Color hlgOotfApprox(Color e, [[maybe_unused]] LuminanceFn luminance) { + return {{{std::pow(e.r, kOotfGamma), std::pow(e.g, kOotfGamma), std::pow(e.b, kOotfGamma)}}}; +} + +Color hlgInverseOotf(Color e, LuminanceFn luminance) { + float y = luminance(e); + return e * std::pow(y, (1.0f / kOotfGamma) - 1.0f); +} + +Color hlgInverseOotfApprox(Color e) { + return {{{std::pow(e.r, 1.0f / kOotfGamma), std::pow(e.g, 1.0f / kOotfGamma), + std::pow(e.b, 1.0f / kOotfGamma)}}}; +} + // See ITU-R BT.2100-2, Table 4, Reference PQ OETF. static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f; static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f, @@ -1122,7 +1136,7 @@ SceneToDisplayLuminanceFn getOotfFn(uhdr_color_transfer_t transfer) { case UHDR_CT_LINEAR: return identityOotf; case UHDR_CT_HLG: - return hlgOotf; + return hlgOotfApprox; case UHDR_CT_PQ: return identityOotf; case UHDR_CT_SRGB: diff --git a/lib/src/gpu/applygainmap_gl.cpp b/lib/src/gpu/applygainmap_gl.cpp index fff98d2b..6e3677db 100644 --- a/lib/src/gpu/applygainmap_gl.cpp +++ b/lib/src/gpu/applygainmap_gl.cpp @@ -182,6 +182,21 @@ static const std::string pqOETFShader = R"__SHADER__( } )__SHADER__"; +static const std::string hlgInverseOOTFShader = R"__SHADER__( + float InverseOOTF(const float linear) { + const float kOotfGamma = 1.2f; + return pow(linear, 1.0f / kOotfGamma); + } + + vec3 InverseOOTF(const vec3 linear) { + return vec3(InverseOOTF(linear.r), InverseOOTF(linear.g), InverseOOTF(linear.b)); + } +)__SHADER__"; + +static const std::string IdentityInverseOOTFShader = R"__SHADER__( + vec3 InverseOOTF(const vec3 linear) { return linear; } +)__SHADER__"; + std::string getApplyGainMapFragmentShader(uhdr_img_fmt sdr_fmt, uhdr_img_fmt gm_fmt, uhdr_color_transfer output_ct) { std::string shader_code = R"__SHADER__(#version 300 es @@ -205,10 +220,13 @@ std::string getApplyGainMapFragmentShader(uhdr_img_fmt sdr_fmt, uhdr_img_fmt gm_ : getGainMapSampleMultiChannel); shader_code.append(applyGainMapShader); if (output_ct == UHDR_CT_LINEAR) { + shader_code.append(IdentityInverseOOTFShader); shader_code.append(linearOETFShader); } else if (output_ct == UHDR_CT_HLG) { + shader_code.append(hlgInverseOOTFShader); shader_code.append(hlgOETFShader); } else if (output_ct == UHDR_CT_PQ) { + shader_code.append(IdentityInverseOOTFShader); shader_code.append(pqOETFShader); } @@ -219,6 +237,7 @@ std::string getApplyGainMapFragmentShader(uhdr_img_fmt sdr_fmt, uhdr_img_fmt gm_ vec3 rgb_sdr = sRGBEOTF(rgb_gamma_sdr); vec3 gain = sampleMap(gainMapTexture); vec3 rgb_hdr = applyGain(rgb_sdr, gain); + rgb_hdr = InverseOOTF(rgb_hdr); vec3 rgb_gamma_hdr = OETF(rgb_hdr); FragColor = vec4(rgb_gamma_hdr, 1.0); } diff --git a/lib/src/jpegr.cpp b/lib/src/jpegr.cpp index 58e30c27..888a570f 100644 --- a/lib/src/jpegr.cpp +++ b/lib/src/jpegr.cpp @@ -556,6 +556,26 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ return status; } + LuminanceFn hdrLuminanceFn = getLuminanceFn(hdr_intent->cg); + if (hdrLuminanceFn == nullptr) { + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "No implementation available for calculating luminance for color gamut %d", + hdr_intent->cg); + return status; + } + + SceneToDisplayLuminanceFn hdrOotfFn = getOotfFn(hdr_intent->ct); + if (hdrOotfFn == nullptr) { + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "No implementation available for calculating Ootf for color transfer %d", + hdr_intent->ct); + return status; + } + float hdr_white_nits = getReferenceDisplayPeakLuminanceInNits(hdr_intent->ct); if (hdr_white_nits == -1.0f) { status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; @@ -651,9 +671,9 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ uhdr_raw_image_ext_t* dest = gainmap_img.get(); auto generateGainMapOnePass = [this, sdr_intent, hdr_intent, gainmap_metadata, dest, map_height, - hdrInvOetf, hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, - hdrYuvToRgbFn, sdr_sample_pixel_fn, hdr_sample_pixel_fn, - hdr_white_nits, use_luminance]() -> void { + hdrInvOetf, hdrLuminanceFn, hdrOotfFn, hdrGamutConversionFn, + luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, sdr_sample_pixel_fn, + hdr_sample_pixel_fn, hdr_white_nits, use_luminance]() -> void { gainmap_metadata->max_content_boost = hdr_white_nits / kSdrWhiteNits; gainmap_metadata->min_content_boost = 1.0f; gainmap_metadata->gamma = mGamma; @@ -674,9 +694,10 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ size_t rowStep = threads == 1 ? map_height : jobSizeInRows; JobQueue jobQueue; std::function generateMap = - [this, sdr_intent, hdr_intent, gainmap_metadata, dest, hdrInvOetf, hdrGamutConversionFn, - luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, sdr_sample_pixel_fn, hdr_sample_pixel_fn, - hdr_white_nits, log2MinBoost, log2MaxBoost, use_luminance, &jobQueue]() -> void { + [this, sdr_intent, hdr_intent, gainmap_metadata, dest, hdrInvOetf, hdrLuminanceFn, + hdrOotfFn, hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, + sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, log2MinBoost, log2MaxBoost, + use_luminance, &jobQueue]() -> void { size_t rowStart, rowEnd; const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt); const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt); @@ -708,6 +729,7 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); } Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); + hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn); hdr_rgb = hdrGamutConversionFn(hdr_rgb); if (mUseMultiChannelGainMap) { @@ -760,10 +782,10 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); }; - auto generateGainMapTwoPass = [this, sdr_intent, hdr_intent, gainmap_metadata, dest, map_width, - map_height, hdrInvOetf, hdrGamutConversionFn, luminanceFn, - sdrYuvToRgbFn, hdrYuvToRgbFn, sdr_sample_pixel_fn, - hdr_sample_pixel_fn, hdr_white_nits, use_luminance]() -> void { + auto generateGainMapTwoPass = + [this, sdr_intent, hdr_intent, gainmap_metadata, dest, map_width, map_height, hdrInvOetf, + hdrLuminanceFn, hdrOotfFn, hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, + sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, use_luminance]() -> void { uhdr_memory_block_t gainmap_mem(map_width * map_height * sizeof(float) * (mUseMultiChannelGainMap ? 3 : 1)); float* gainmap_data = reinterpret_cast(gainmap_mem.m_buffer.get()); @@ -776,10 +798,10 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ size_t rowStep = threads == 1 ? map_height : jobSizeInRows; JobQueue jobQueue; std::function generateMap = - [this, sdr_intent, hdr_intent, gainmap_data, map_width, hdrInvOetf, hdrGamutConversionFn, - luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, sdr_sample_pixel_fn, hdr_sample_pixel_fn, - hdr_white_nits, use_luminance, &gainmap_min, &gainmap_max, &gainmap_minmax, - &jobQueue]() -> void { + [this, sdr_intent, hdr_intent, gainmap_data, map_width, hdrInvOetf, hdrLuminanceFn, + hdrOotfFn, hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, + sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, use_luminance, &gainmap_min, + &gainmap_max, &gainmap_minmax, &jobQueue]() -> void { size_t rowStart, rowEnd; const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt); const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt); @@ -814,6 +836,7 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); } Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); + hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn); hdr_rgb = hdrGamutConversionFn(hdr_rgb); if (mUseMultiChannelGainMap) { @@ -1516,6 +1539,7 @@ uhdr_error_info_t JpegR::applyGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ima #else ColorTransformFn hdrOetf = hlgOetf; #endif + rgb_hdr = hlgInverseOotfApprox(rgb_hdr); Color rgb_gamma_hdr = hdrOetf(rgb_hdr); uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr); reinterpret_cast(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] =