diff --git a/lib/include/ultrahdr/gainmapmath.h b/lib/include/ultrahdr/gainmapmath.h index ca6bf6e2..0ec04061 100644 --- a/lib/include/ultrahdr/gainmapmath.h +++ b/lib/include/ultrahdr/gainmapmath.h @@ -73,6 +73,7 @@ struct Color { typedef Color (*ColorTransformFn)(Color); typedef float (*LuminanceFn)(Color); +typedef Color (*SceneToDisplayLuminanceFn)(Color, LuminanceFn); typedef Color (*GetPixelFn)(uhdr_raw_image_t*, size_t, size_t); typedef Color (*SamplePixelFn)(uhdr_raw_image_t*, size_t, size_t, size_t); typedef void (*PutPixelFn)(uhdr_raw_image_t*, size_t, size_t, Color&); @@ -275,6 +276,10 @@ Color hlgInvOetfLUT(Color e_gamma); constexpr int32_t kHlgInvOETFPrecision = 12; constexpr int32_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision; +// hlg ootf (normalized) +Color hlgOotf(Color e, LuminanceFn luminance); +inline Color identityOotf(Color e, [[maybe_unused]] LuminanceFn) { return e; } + // pq oetf float pqOetf(float e); Color pqOetf(Color e); @@ -469,6 +474,7 @@ ColorTransformFn getGamutConversionFn(uhdr_color_gamut_t dst_gamut, uhdr_color_g ColorTransformFn getYuvToRgbFn(uhdr_color_gamut_t gamut); LuminanceFn getLuminanceFn(uhdr_color_gamut_t gamut); ColorTransformFn getInverseOetfFn(uhdr_color_transfer_t transfer); +SceneToDisplayLuminanceFn getOotfFn(uhdr_color_transfer_t transfer); GetPixelFn getPixelFn(uhdr_img_fmt_t format); SamplePixelFn getSamplePixelFn(uhdr_img_fmt_t format); PutPixelFn putPixelFn(uhdr_img_fmt_t format); diff --git a/lib/include/ultrahdr/jpegr.h b/lib/include/ultrahdr/jpegr.h index a899da60..248d4864 100644 --- a/lib/include/ultrahdr/jpegr.h +++ b/lib/include/ultrahdr/jpegr.h @@ -621,8 +621,7 @@ struct GlobalTonemapOutputs { // `rgb_out` is returned in this same range. `headroom` describes the ratio // between the HDR and SDR peak luminances and must be > 1. The `y_sdr` output // is in the range [0.0, 1.0] while `y_hdr` is in the range [0.0, headroom]. -GlobalTonemapOutputs globalTonemap(const std::array& rgb_in, float headroom, - float luminance); +GlobalTonemapOutputs globalTonemap(const std::array& rgb_in, float headroom); } // namespace ultrahdr diff --git a/lib/src/gainmapmath.cpp b/lib/src/gainmapmath.cpp index 25e7ffd5..13835c01 100644 --- a/lib/src/gainmapmath.cpp +++ b/lib/src/gainmapmath.cpp @@ -286,6 +286,14 @@ Color hlgInvOetfLUT(Color e_gamma) { return {{{hlgInvOetfLUT(e_gamma.r), hlgInvOetfLUT(e_gamma.g), hlgInvOetfLUT(e_gamma.b)}}}; } +// 1.2f + 0.42 * log(kHlgMaxNits / 1000) +static const float kOotfGamma = 1.2f; + +Color hlgOotf(Color e, LuminanceFn luminance) { + float y = luminance(e); + return e * std::pow(y, kOotfGamma - 1.0f); +} + // 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, @@ -1109,6 +1117,22 @@ ColorTransformFn getInverseOetfFn(uhdr_color_transfer_t transfer) { return nullptr; } +SceneToDisplayLuminanceFn getOotfFn(uhdr_color_transfer_t transfer) { + switch (transfer) { + case UHDR_CT_LINEAR: + return identityOotf; + case UHDR_CT_HLG: + return hlgOotf; + case UHDR_CT_PQ: + return identityOotf; + case UHDR_CT_SRGB: + return identityOotf; + case UHDR_CT_UNSPECIFIED: + return nullptr; + } + return nullptr; +} + GetPixelFn getPixelFn(uhdr_img_fmt_t format) { switch (format) { case UHDR_IMG_FMT_24bppYCbCr444: diff --git a/lib/src/jpegr.cpp b/lib/src/jpegr.cpp index bf4f3d82..58e30c27 100644 --- a/lib/src/jpegr.cpp +++ b/lib/src/jpegr.cpp @@ -1678,16 +1678,12 @@ static float ReinhardMap(float y_hdr, float headroom) { return out * y_hdr; } -GlobalTonemapOutputs globalTonemap(const std::array& rgb_in, float headroom, float y_in) { - constexpr float kOotfGamma = 1.2f; - - // Apply OOTF and Scale to Headroom to get HDR values that are referenced to - // SDR white. The range [0.0, 1.0] is linearly stretched to [0.0, headroom] - // after the OOTF. - const float y_ootf_div_y_in = std::pow(y_in, kOotfGamma - 1.0f); +GlobalTonemapOutputs globalTonemap(const std::array& rgb_in, float headroom) { + // Scale to Headroom to get HDR values that are referenced to SDR white. The range [0.0, 1.0] is + // linearly stretched to [0.0, headroom]. std::array rgb_hdr; std::transform(rgb_in.begin(), rgb_in.end(), rgb_hdr.begin(), - [&](float x) { return x * headroom * y_ootf_div_y_in; }); + [&](float x) { return x * headroom; }); // Apply a tone mapping to compress the range [0, headroom] to [0, 1] by // keeping the shadows the same and crushing the highlights. @@ -1788,6 +1784,17 @@ uhdr_error_info_t JpegR::toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* return status; } + SceneToDisplayLuminanceFn hdrOotfFn = getOotfFn(hdr_intent->ct); + if (hdrOotfFn == nullptr) { + uhdr_error_info_t status; + 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; + } + ColorTransformFn hdrInvOetf = getInverseOetfFn(hdr_intent->ct); if (hdrInvOetf == nullptr) { uhdr_error_info_t status; @@ -1848,7 +1855,7 @@ uhdr_error_info_t JpegR::toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* std::function toneMapInternal; toneMapInternal = [hdr_intent, sdr_intent, hdrInvOetf, hdrGamutConversionFn, hdrYuvToRgbFn, - hdr_white_nits, get_pixel_fn, put_pixel_fn, hdrLuminanceFn, + hdr_white_nits, get_pixel_fn, put_pixel_fn, hdrLuminanceFn, hdrOotfFn, &jobQueue]() -> void { size_t rowStart, rowEnd; const int hfactor = hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? 2 : 1; @@ -1880,10 +1887,10 @@ uhdr_error_info_t JpegR::toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); } Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); + hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn); GlobalTonemapOutputs tonemap_outputs = - globalTonemap({hdr_rgb.r, hdr_rgb.g, hdr_rgb.b}, hdr_white_nits / kSdrWhiteNits, - hdrLuminanceFn({{{hdr_rgb.r, hdr_rgb.g, hdr_rgb.b}}})); + globalTonemap({hdr_rgb.r, hdr_rgb.g, hdr_rgb.b}, hdr_white_nits / kSdrWhiteNits); Color sdr_rgb_linear_bt2100 = { {{tonemap_outputs.rgb_out[0], tonemap_outputs.rgb_out[1], tonemap_outputs.rgb_out[2]}}};