jpegrecoverymap: Added LUT implementations for various OETF and InvOETF functions

Instead of computing these for each pixel, these are computed once
at 10-bit precision (index from 0 to 1023) and are used to speed up
the computations.

Bug: 261877699
Test: push files from tests/data to /sdcard/Documents and then \
 atest libjpegdecoder_test libjpegencoder_test libjpegrecoverymap_test
Change-Id: Ifd98a96a78bc38879a77d86ebab137a4efc8fa20
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
index 2f9799d..f14fafc 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -189,7 +189,8 @@
  */
 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 @@
  */
 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 @@
  */
 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 @@
  */
 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 @@
  */
 float pqInvOetf(float e_gamma);
 Color pqInvOetf(Color e_gamma);
+float pqInvOetfLUT(float e_gamma);
+Color pqInvOetfLUT(Color e_gamma);
 
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 222b5f0..a898f1e 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -41,6 +41,12 @@
 
 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 @@
       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 @@
           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 @@
         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 @@
         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;
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
index 7d29a37..64fa44b 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -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 @@
              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 @@
   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 @@
              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 @@
   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 @@
              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
diff --git a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
index 58c139d..0d0e4fc 100644
--- a/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymapmath_test.cpp
@@ -517,6 +517,46 @@
   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());