Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 96de8374 authored by Dichen Zhang's avatar Dichen Zhang Committed by Android (Google) Code Review
Browse files

Merge "jpegrecoverymap: Added LUT implementations for various OETF and InvOETF functions"

parents 84256b17 555a06b6
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -189,7 +189,8 @@ Color srgbRgbToYuv(Color e_gamma);
 */
float srgbInvOetf(float e_gamma);
Color srgbInvOetf(Color e_gamma);

float srgbInvOetfLUT(float e_gamma);
Color srgbInvOetfLUT(Color e_gamma);

////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations
@@ -229,6 +230,8 @@ Color bt2100YuvToRgb(Color e_gamma);
 */
float hlgOetf(float e);
Color hlgOetf(Color e);
float hlgOetfLUT(float e);
Color hlgOetfLUT(Color e);

/*
 * Convert from HLG to scene luminance.
@@ -237,6 +240,8 @@ Color hlgOetf(Color e);
 */
float hlgInvOetf(float e_gamma);
Color hlgInvOetf(Color e_gamma);
float hlgInvOetfLUT(float e_gamma);
Color hlgInvOetfLUT(Color e_gamma);

/*
 * Convert from scene luminance to PQ.
@@ -245,6 +250,8 @@ Color hlgInvOetf(Color e_gamma);
 */
float pqOetf(float e);
Color pqOetf(Color e);
float pqOetfLUT(float e);
Color pqOetfLUT(Color e);

/*
 * Convert from PQ to scene luminance in nits.
@@ -253,6 +260,8 @@ Color pqOetf(Color e);
 */
float pqInvOetf(float e_gamma);
Color pqInvOetf(Color e_gamma);
float pqInvOetfLUT(float e_gamma);
Color pqInvOetfLUT(Color e_gamma);


////////////////////////////////////////////////////////////////////////////////
+30 −1
Original line number Diff line number Diff line
@@ -41,6 +41,12 @@ using namespace photos_editing_formats::image_io;

namespace android::recoverymap {

#define USE_SRGB_INVOETF_LUT 1
#define USE_HLG_OETF_LUT 1
#define USE_PQ_OETF_LUT 1
#define USE_HLG_INVOETF_LUT 1
#define USE_PQ_INVOETF_LUT 1

#define JPEGR_CHECK(x)          \
  {                             \
    status_t status = (x);      \
@@ -741,11 +747,19 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4
      hdrInvOetf = identityConversion;
      break;
    case JPEGR_TF_HLG:
#if USE_HLG_INVOETF_LUT
      hdrInvOetf = hlgInvOetfLUT;
#else
      hdrInvOetf = hlgInvOetf;
#endif
      hdr_white_nits = kHlgMaxNits;
      break;
    case JPEGR_TF_PQ:
#if USE_PQ_INVOETF_LUT
      hdrInvOetf = pqInvOetfLUT;
#else
      hdrInvOetf = pqInvOetf;
#endif
      hdr_white_nits = kPqMaxNits;
      break;
    case JPEGR_TF_UNSPECIFIED:
@@ -817,7 +831,11 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4
          Color sdr_yuv_gamma =
              sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
          Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
#if USE_SRGB_INVOETF_LUT
          Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
#else
          Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
#endif
          float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;

          Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
@@ -905,10 +923,18 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_
        hdrOetf = identityConversion;
        break;
      case JPEGR_TF_HLG:
#if USE_HLG_OETF_LUT
        hdrOetf = hlgOetfLUT;
#else
        hdrOetf = hlgOetf;
#endif
        break;
      case JPEGR_TF_PQ:
#if USE_PQ_OETF_LUT
        hdrOetf = pqOetfLUT;
#else
        hdrOetf = pqOetf;
#endif
        break;
      case JPEGR_TF_UNSPECIFIED:
        // Should be impossible to hit after input validation.
@@ -921,8 +947,11 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_
        for (size_t x = 0; x < width; ++x) {
          Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
          Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
#if USE_SRGB_INVOETF_LUT
          Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
#else
          Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);

#endif
          float recovery;
          // TODO: determine map scaling factor based on actual map dims
          size_t map_scale_factor = kMapDimensionScaleFactor;
+131 −1
Original line number Diff line number Diff line
@@ -15,11 +15,76 @@
 */

#include <cmath>

#include <vector>
#include <jpegrecoverymap/recoverymapmath.h>

namespace android::recoverymap {

#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)

constexpr size_t kPqOETFPrecision = 10;
constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;

static const std::vector<float> kPqOETF = [] {
    std::vector<float> result;
    float increment = 1.0 / kPqOETFNumEntries;
    float value = 0.0f;
    for (int idx = 0; idx < kPqOETFNumEntries; idx++, value += increment) {
      result.push_back(pqOetf(value));
    }
    return result;
}();

constexpr size_t kPqInvOETFPrecision = 10;
constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;

static const std::vector<float> kPqInvOETF = [] {
    std::vector<float> result;
    float increment = 1.0 / kPqInvOETFNumEntries;
    float value = 0.0f;
    for (int idx = 0; idx < kPqInvOETFNumEntries; idx++, value += increment) {
      result.push_back(pqInvOetf(value));
    }
    return result;
}();

constexpr size_t kHlgOETFPrecision = 10;
constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;

static const std::vector<float> kHlgOETF = [] {
    std::vector<float> result;
    float increment = 1.0 / kHlgOETFNumEntries;
    float value = 0.0f;
    for (int idx = 0; idx < kHlgOETFNumEntries; idx++, value += increment) {
      result.push_back(hlgOetf(value));
    }
    return result;
}();

constexpr size_t kHlgInvOETFPrecision = 10;
constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;

static const std::vector<float> kHlgInvOETF = [] {
    std::vector<float> result;
    float increment = 1.0 / kHlgInvOETFNumEntries;
    float value = 0.0f;
    for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++, value += increment) {
      result.push_back(hlgInvOetf(value));
    }
    return result;
}();

constexpr size_t kSRGBInvOETFPrecision = 10;
constexpr size_t kSRGBInvOETFNumEntries = 1 << kSRGBInvOETFPrecision;
static const std::vector<float> kSRGBInvOETF = [] {
    std::vector<float> result;
    float increment = 1.0 / kSRGBInvOETFNumEntries;
    float value = 0.0f;
    for (int idx = 0; idx < kSRGBInvOETFNumEntries; idx++, value += increment) {
      result.push_back(srgbInvOetf(value));
    }
    return result;
}();

// Use Shepard's method for inverse distance weighting. For more information:
// en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
@@ -117,6 +182,19 @@ Color srgbInvOetf(Color e_gamma) {
             srgbInvOetf(e_gamma.b) }}};
}

// See IEC 61966-2-1, Equations F.5 and F.6.
float srgbInvOetfLUT(float e_gamma) {
  uint32_t value = static_cast<uint32_t>(e_gamma * kSRGBInvOETFNumEntries);
  //TODO() : Remove once conversion modules have appropriate clamping in place
  value = CLIP3(value, 0, kSRGBInvOETFNumEntries - 1);
  return kSRGBInvOETF[value];
}

Color srgbInvOetfLUT(Color e_gamma) {
  return {{{ srgbInvOetfLUT(e_gamma.r),
             srgbInvOetfLUT(e_gamma.g),
             srgbInvOetfLUT(e_gamma.b) }}};
}

////////////////////////////////////////////////////////////////////////////////
// Display-P3 transformations
@@ -194,6 +272,18 @@ Color hlgOetf(Color e) {
  return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
}

float hlgOetfLUT(float e) {
  uint32_t value = static_cast<uint32_t>(e * kHlgOETFNumEntries);
  //TODO() : Remove once conversion modules have appropriate clamping in place
  value = CLIP3(value, 0, kHlgOETFNumEntries - 1);

  return kHlgOETF[value];
}

Color hlgOetfLUT(Color e) {
  return {{{ hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b) }}};
}

// See ITU-R BT.2100-2, Table 5, HLG Reference EOTF.
float hlgInvOetf(float e_gamma) {
  if (e_gamma <= 0.5f) {
@@ -209,6 +299,20 @@ Color hlgInvOetf(Color e_gamma) {
             hlgInvOetf(e_gamma.b) }}};
}

float hlgInvOetfLUT(float e_gamma) {
  uint32_t value = static_cast<uint32_t>(e_gamma * kHlgInvOETFNumEntries);
  //TODO() : Remove once conversion modules have appropriate clamping in place
  value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1);

  return kHlgInvOETF[value];
}

Color hlgInvOetfLUT(Color e_gamma) {
  return {{{ hlgInvOetfLUT(e_gamma.r),
             hlgInvOetfLUT(e_gamma.g),
             hlgInvOetfLUT(e_gamma.b) }}};
}

// 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,
@@ -224,6 +328,18 @@ Color pqOetf(Color e) {
  return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
}

float pqOetfLUT(float e) {
  uint32_t value = static_cast<uint32_t>(e * kPqOETFNumEntries);
  //TODO() : Remove once conversion modules have appropriate clamping in place
  value = CLIP3(value, 0, kPqOETFNumEntries - 1);

  return kPqOETF[value];
}

Color pqOetfLUT(Color e) {
  return {{{ pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b) }}};
}

// Derived from the inverse of the Reference PQ OETF.
static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f,
                   kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f;
@@ -244,6 +360,20 @@ Color pqInvOetf(Color e_gamma) {
             pqInvOetf(e_gamma.b) }}};
}

float pqInvOetfLUT(float e_gamma) {
  uint32_t value = static_cast<uint32_t>(e_gamma * kPqInvOETFNumEntries);
  //TODO() : Remove once conversion modules have appropriate clamping in place
  value = CLIP3(value, 0, kPqInvOETFNumEntries - 1);

  return kPqInvOETF[value];
}

Color pqInvOetfLUT(Color e_gamma) {
  return {{{ pqInvOetfLUT(e_gamma.r),
             pqInvOetfLUT(e_gamma.g),
             pqInvOetfLUT(e_gamma.b) }}};
}


////////////////////////////////////////////////////////////////////////////////
// Color conversions
+40 −0
Original line number Diff line number Diff line
@@ -517,6 +517,46 @@ TEST_F(RecoveryMapMathTest, PqInvOetf) {
  EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
}

TEST_F(RecoveryMapMathTest, PqInvOetfLUT) {
    float increment = 1.0 / 1024.0;
    float value = 0.0f;
    for (int idx = 0; idx < 1024; idx++, value += increment) {
      EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
    }
}

TEST_F(RecoveryMapMathTest, HlgInvOetfLUT) {
    float increment = 1.0 / 1024.0;
    float value = 0.0f;
    for (int idx = 0; idx < 1024; idx++, value += increment) {
      EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
    }
}

TEST_F(RecoveryMapMathTest, pqOetfLUT) {
    float increment = 1.0 / 1024.0;
    float value = 0.0f;
    for (int idx = 0; idx < 1024; idx++, value += increment) {
      EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
    }
}

TEST_F(RecoveryMapMathTest, hlgOetfLUT) {
    float increment = 1.0 / 1024.0;
    float value = 0.0f;
    for (int idx = 0; idx < 1024; idx++, value += increment) {
      EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
    }
}

TEST_F(RecoveryMapMathTest, srgbInvOetfLUT) {
    float increment = 1.0 / 1024.0;
    float value = 0.0f;
    for (int idx = 0; idx < 1024; idx++, value += increment) {
      EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value));
    }
}

TEST_F(RecoveryMapMathTest, PqTransferFunctionRoundtrip) {
  EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(0.0f)), 0.0f);
  EXPECT_NEAR(pqInvOetf(pqOetf(0.01f)), 0.01f, ComparisonEpsilon());