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());