| /* |
| * Copyright Samsung Electronics Co.,LTD. |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <algorithm> |
| #include <vector> |
| #include <set> |
| #include <queue> |
| #include <cmath> |
| |
| #include <system/graphics.h> |
| #include <log/log.h> |
| |
| #ifndef HDR_TEST |
| #include <VendorVideoAPI.h> |
| #else |
| #include <VendorVideoAPI_hdrTest.h> |
| #endif |
| |
| #include "hdrCurveData.h" |
| |
| using namespace std; |
| |
| template<typename T> |
| class curveData { |
| public: |
| curveData(T &info, int inputrange, int outputrange, int minx) |
| : info(info), inputRange(inputrange), outputRange(outputrange), minX(minx) {} |
| virtual ~curveData() {} |
| |
| virtual int getY(int __attribute__((unused)) px) { return 0; } |
| |
| T info; |
| int inputRange; |
| int outputRange; |
| int minX; |
| }; |
| |
| //#define NUM_DYNAMIC_TM_X_BITS 27 |
| //#define NUM_DYNAMIC_TM_Y_BITS 20 |
| //#define NUM_DYNAMIC_X_TM (1 << NUM_DYNAMIC_TM_X_BITS) |
| //#define NUM_DYNAMIC_Y_TM (1 << NUM_DYNAMIC_TM_Y_BITS) |
| |
| class OETFCurveData : public curveData<ExynosHdrDynamicInfo_t> { |
| public: |
| OETFCurveData(ExynosHdrDynamicInfo_t &meta, int inputrange, int outputrange, int minx) |
| : curveData(meta, inputrange, outputrange, minx) {} |
| virtual ~OETFCurveData() {} |
| |
| int lookupTonemapGain(int px) |
| { |
| static float powX[META_MAX_PCOEFF_SIZE]; |
| static float powDX[META_MAX_PCOEFF_SIZE]; |
| static const float EBZ_COEFF[META_MAX_PCOEFF_SIZE + 2][META_MAX_PCOEFF_SIZE] = |
| { |
| /*order 0*/{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, |
| /*order 1*/{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, |
| /*order 2*/{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, |
| /*order 3*/{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, |
| /*order 4*/{ 4, 6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, |
| /*order 5*/{ 5, 10, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, |
| /*order 6*/{ 6, 15, 20, 15, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, |
| /*order 7*/{ 7, 21, 35, 35, 21, 7, 0, 0, 0, 0, 0, 0, 0, 0 }, |
| /*order 8*/{ 8, 28, 56, 70, 56, 28, 8, 0, 0, 0, 0, 0, 0, 0 }, |
| /*order 9*/{ 9, 36, 84, 126, 126, 84, 36, 9, 0, 0, 0, 0, 0, 0 }, |
| /*order 10*/{ 10, 45, 120, 210, 252, 210, 120, 45, 10, 0, 0, 0, 0, 0 }, |
| /*order 11*/{ 11, 55, 165, 330, 462, 462, 330, 165, 55, 11, 0, 0, 0, 0 }, |
| /*order 12*/{ 12, 66, 220, 495, 792, 924, 792, 495, 220, 66, 12, 0, 0, 0 }, |
| /*order 13*/{ 13, 78, 286, 715, 1287, 1716, 1716, 1287, 715, 286, 78, 13, 0, 0 }, |
| /*order 14*/{ 14, 91, 364, 1001, 2002, 3003, 3432, 3003, 2002, 1001, 364, 91, 14, 0 }, |
| /*order 15*/{ 15, 105, 455, 1365, 3003, 5005, 6435, 6435, 5005, 3003, 1365, 455, 105, 15 } |
| }; |
| |
| CurveParameters curveParam; |
| curveParam.setValues(info.data.tone_mapping.knee_point_x, |
| info.data.tone_mapping.knee_point_y, |
| info.data.tone_mapping.bezier_curve_anchors, |
| info.data.tone_mapping.num_bezier_curve_anchors + 1, |
| META_JSON_2094_EBZ_KNEE_POINT_MAX, |
| META_JSON_2094_EBZ_PCOEFF_MAX); |
| |
| const float sx = curveParam.KPx; |
| const float sy = curveParam.KPy; |
| const int order = curveParam.order; |
| const int numPCoeff = order - 1; |
| float out; |
| |
| if ((double)px / inputRange < sx) { |
| float k = (sx > 0) ? (sy / sx) : (0.0f); |
| out = std::max(std::min((double)px * k / inputRange, 1.0), 0.0); |
| } |
| else { |
| // Pre compute constants |
| const float x = ((double)px / inputRange - sx) / (1.0f - sx); |
| const float dx = 1.0f - x; |
| |
| // Compute power of x and dx by mul instead of pow() function : faster |
| powX[0] = x; |
| powDX[0] = dx; |
| for (int i = 1; i < numPCoeff; i++) { |
| powX[i] = powX[i - 1] * x; |
| powDX[i] = powDX[i - 1] * dx; |
| } |
| |
| /******** Compute curve output ******** |
| // Original Equation : |
| // y = sum for i=0:order ( NChooseK(order, i) * pow(x,i) * pow((1-x),order-i) * pi |
| // |
| // Where, |
| // Fixed : p0 = 0.0f and pn = 1.0f |
| // Input : p1....pn : specified in range 0~1 |
| // x : Input in range 0~1 |
| // |
| // This is simplified as below (ignore first term as it is always zero, last term is always pow(x, order) |
| // NChooseK() coefficients for each order is saved as LUT : EBZ_COEFF |
| // dx = (1.0f-x) is precomputed |
| // all powers of x and (1-x) are precomuted as DP table powX and powDX |
| *****************************************/ |
| |
| float ebzy = 0.0f; |
| for (int i = 0; i < numPCoeff; i++) |
| ebzy += EBZ_COEFF[order][i] * powX[i] * powDX[numPCoeff - i - 1] * curveParam.pcoeff[i]; |
| |
| ebzy += powX[numPCoeff - 1] * x; |
| |
| out = sy + (1.0f - sy) * ebzy; |
| } |
| |
| if (px == 0) |
| px = 1; |
| return (int)(out * inputRange * outputRange / px ); |
| } |
| |
| int getY(int px) |
| { |
| return lookupTonemapGain(max(px, minX)); |
| } |
| }; |
| |
| // copy Android ToneMapper from frameworks/native/libs/tonemap/tonemap.cpp |
| class ATMCurveData : public curveData<CurveInfo> { |
| public: |
| ATMCurveData(CurveInfo &info, int inputrange, int outputrange, int minx) |
| : curveData(info, inputrange, outputrange, minx) { |
| int transfer = info.inDataspace & HAL_DATASPACE_TRANSFER_MASK; |
| if (transfer != HAL_DATASPACE_TRANSFER_HLG && info.metaIf != nullptr) |
| info.metaIf->configHDR10Tonemap(info.maxInLumi, info.maxOutLumi); |
| } |
| virtual ~ATMCurveData() {} |
| |
| double lookupTonemapGainO(double nit) |
| { |
| //# three control points |
| double x0 = 10.0; |
| double y0 = 17.0; |
| double x1 = info.maxOutLumi * 0.75; |
| double y1 = x1; |
| double x2 = x1 + (info.maxInLumi - x1) / 2.0; |
| double y2 = y1 + (info.maxOutLumi - y1) * 0.75; |
| |
| //#horizontal distances between the last three control points |
| double h12 = x2 - x1; |
| double h23 = info.maxInLumi - x2; |
| |
| //# tangents at the last three control points |
| double m1 = (y2 - y1) / h12; |
| double m3 = (info.maxOutLumi - y2) / h23; |
| double m2 = (m1 + m3) / 2.0; |
| |
| if ((info.inDataspace & HAL_DATASPACE_TRANSFER_MASK) == HAL_DATASPACE_TRANSFER_HLG) { |
| nit *= std::pow(nit, 0.2); |
| } |
| |
| if (info.maxInLumi <= info.maxOutLumi) { |
| return nit; |
| } |
| |
| if (nit < x0) { //# scale [0.0, x0] to [0.0, y0] linearly |
| double slope = y0 / x0; |
| return nit * slope; |
| } else if (nit < x1) { //# scale [x0, x1] to [y0, y1] linearly |
| double slope = (y1 - y0) / (x1 - x0); |
| return y0 + (nit - x0) * slope; |
| } else if (nit < x2) { //# scale [x1, x2] to [y1, y2] using Hermite interp |
| double t = (nit - x1) / h12; |
| return (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) * (1.0 - t) |
| + (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t; |
| } else { //# scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite interp |
| double t = (nit - x2) / h23; |
| return (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) * (1.0 - t) |
| + (info.maxOutLumi * (3.0 - 2.0 * t) + h23 * m3 * (t - 1.0)) * t * t; |
| } |
| } |
| |
| double OETF_ST2084(double nits) { |
| nits = nits / 10000.0; |
| double m1 = (2610.0 / 4096.0) / 4.0; |
| double m2 = (2523.0 / 4096.0) * 128.0; |
| double c1 = (3424.0 / 4096.0); |
| double c2 = (2413.0 / 4096.0) * 32.0; |
| double c3 = (2392.0 / 4096.0) * 32.0; |
| |
| double tmp = std::pow(nits, m1); |
| tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp); |
| return std::pow(tmp, m2); |
| } |
| |
| double lookupTonemapGain13_PQ(double nit) |
| { |
| const double x1 = info.maxOutLumi * 0.65; |
| const double y1 = x1; |
| |
| const double x3 = info.maxInLumi; |
| const double y3 = info.maxOutLumi; |
| |
| const double x2 = x1 + (x3 - x1) * 4.0 / 17.0; |
| const double y2 = info.maxOutLumi * 0.9; |
| |
| const double greyNorm1 = OETF_ST2084(x1); |
| const double greyNorm2 = OETF_ST2084(x2); |
| const double greyNorm3 = OETF_ST2084(x3); |
| |
| const double slope2 = (y2 - y1) / (greyNorm2 - greyNorm1); |
| const double slope3 = (y3 - y2) / (greyNorm3 - greyNorm2); |
| |
| if (nit < x1) { |
| return nit; |
| } |
| |
| if (nit > info.maxInLumi) { |
| return info.maxOutLumi; |
| } |
| |
| const double greyNits = OETF_ST2084(nit); |
| |
| if (greyNits <= greyNorm2) { |
| return (greyNits - greyNorm2) * slope2 + y2; |
| } else if (greyNits <= greyNorm3) { |
| return (greyNits - greyNorm3) * slope3 + y3; |
| } else { |
| return info.maxOutLumi; |
| } |
| } |
| |
| float computeHlgGamma(float currentDisplayBrightnessNits) { |
| /* BT 2100-2's recommendation for taking into account the nominal max |
| * brightness of the display does not work when the current brightness is |
| * very low. For instance, the gamma becomes negative when the current |
| * brightness is between 1 and 2 nits, which would be a bad experience in a |
| * dark environment. Furthermore, BT2100-2 recommends applying |
| * channel^(gamma - 1) as its OOTF, which means that when the current |
| * brightness is lower than 335 nits then channel * channel^(gamma - 1) > |
| * channel, which makes dark scenes very bright. As a workaround for those |
| * problems, lower-bound the brightness to 500 nits. |
| * |
| * remove 500 nit lower-bound to meet BT2100 specification |
| *constexpr float minBrightnessNits = 500.f; |
| *currentDisplayBrightnessNits = std::max(minBrightnessNits, currentDisplayBrightnessNits); |
| */ |
| return 1.2 + 0.42 * std::log10(currentDisplayBrightnessNits / 1000); |
| } |
| |
| double lookupTonemapGain13_HLG(double nit) |
| { |
| const double hlgGamma = computeHlgGamma(info.maxOutLumi); |
| return nit |
| * std::pow(nit / 1000.0, hlgGamma - 1) |
| * info.maxOutLumi / 1000.0; |
| } |
| |
| int getY(int px) |
| { |
| int transfer = info.inDataspace & HAL_DATASPACE_TRANSFER_MASK; |
| double px_norm = (double)max(px, minX) / inputRange; // scale x to [0, 1] |
| double py_out, px_in = px_norm * info.maxInLumi; // scale x to [0, maxInLumi] |
| // x: [0, maxInLumi] / y: [0, maxOutLumi] |
| if (transfer == HAL_DATASPACE_TRANSFER_HLG) |
| py_out = lookupTonemapGain13_HLG(px_in); |
| else { |
| int ret = HDRMETAIF_ERR_MAX; |
| if (info.metaIf != nullptr) |
| ret = info.metaIf->computeHDR10Tonemap(px_in, &py_out); |
| if (ret != HDRMETAIF_ERR_NO) |
| py_out = lookupTonemapGain13_PQ(px_in); |
| } |
| double py_norm = py_out / info.maxOutLumi; // scale y to [0, 1] |
| return (int)(py_norm * outputRange / px_norm); // scale y/x to [0, outputRange] |
| } |
| }; |
| |
| class EOTFCurveData : public curveData<CurveInfo> { |
| public: |
| EOTFCurveData(CurveInfo &info, int inputrange, int outputrange, int minx) |
| : curveData(info, inputrange, outputrange, minx) {} |
| virtual ~EOTFCurveData() {} |
| |
| double get_SMPTE_PQ (double N) |
| { |
| double m1 = 0.1593017578125; |
| double m2 = 78.84375; |
| double c2 = 18.8515625; |
| double c3 = 18.6875; |
| double c1 = c3 - c2 + 1.0; |
| |
| return std::pow((fmax((std::pow(N, (1/m2)))-c1, 0) |
| / (c2 - c3 * (std::pow(N, (1/m2))))) |
| ,(1/m1)); |
| } |
| |
| double computePQ(double px_norm) |
| { |
| return std::min(get_SMPTE_PQ(px_norm) / (info.maxInLumi/10000.0), 1.0); |
| } |
| |
| double computeHLG(double px_norm) |
| { |
| double a = 0.17883277; |
| double b = 0.28466892; |
| double c = 0.55991073; |
| |
| if (px_norm <= 0.5) |
| return px_norm * px_norm / 3.0; |
| else |
| return (std::exp((px_norm - c) / a) + b) / 12.0; |
| } |
| |
| int getY(int px) |
| { |
| int transfer = info.inDataspace & HAL_DATASPACE_TRANSFER_MASK; |
| double py_norm, px_norm = (double)px / inputRange; // scale x [0,inputRange] to [0, 1] |
| // x: [0, 1] / y: [0, 1] |
| if (transfer == HAL_DATASPACE_TRANSFER_HLG) |
| py_norm = computeHLG(px_norm); |
| else |
| py_norm = computePQ(px_norm); |
| return (int)(py_norm * outputRange); // scale y to [0,outputRange] |
| } |
| }; |
| |
| struct _Node { |
| _Node(int lx, int ly, int rx, int ry, int cy, int py, int ny) |
| : leftX(lx), leftY(ly), rightX(rx), rightY(ry), curY(cy), prevY(py), nextY(ny) |
| { |
| curX = (leftX + rightX) >> 1; |
| cost = 0; |
| } |
| virtual ~_Node() {} |
| |
| int leftX; |
| int leftY; |
| int rightX; |
| int rightY; |
| int curX; |
| int curY; |
| int prevY; |
| int nextY; |
| int64_t cost; |
| |
| virtual int64_t getCost() = 0; |
| }; |
| |
| struct Node : _Node { |
| Node(int lx, int ly, int rx, int ry, int cy, int py, int ny) |
| : _Node(lx, ly, rx, ry, cy, py, ny) |
| { |
| cost = getCost(); |
| } |
| virtual ~Node() {} |
| |
| virtual int64_t getCost() { |
| return (int64_t)abs(curY - ((leftY + rightY) >> 1)); |
| } |
| }; |
| |
| struct NodeAdv : _Node { |
| NodeAdv(int lx, int ly, int rx, int ry, int cy, int py, int ny) |
| : _Node(lx, ly, rx, ry, cy, py, ny) |
| { |
| cost = getCost(); |
| } |
| virtual ~NodeAdv() {} |
| |
| virtual int64_t getCost() { // consider "x distance" to spread out |
| return (int64_t)abs(curY - ((leftY + rightY) >> 1)) * (curX - leftX); |
| } |
| }; |
| |
| struct NodeMultiPoints : _Node { |
| NodeMultiPoints(int lx, int ly, int rx, int ry, int cy, int py, int ny) |
| : _Node(lx, ly, rx, ry, cy, py, ny) |
| { |
| cost = getCost(); |
| } |
| virtual ~NodeMultiPoints() {} |
| |
| virtual int64_t getCost() { // sum of 1/4, 2/4, 3/4 points |
| return (int64_t)abs(curY - ((leftY + rightY) >> 1)) + |
| (int64_t)abs(prevY - ((leftY * 3 + rightY) >> 2)) + |
| (int64_t)abs(nextY - ((leftY + rightY * 3) >> 2)); |
| } |
| }; |
| |
| template<typename N> |
| struct selectComp { |
| bool operator()(const N &l, const N &r) const { |
| if (l.curX == r.curX) |
| ALOGD("duplicated points (%d, %d) vs (%d, %d)", l.curX, l.curY, r.curX, r.curY); |
| return l.curX < r.curX; |
| } |
| }; |
| |
| template<typename N> |
| struct candidateComp { |
| bool operator()(const N &l, const N &r) const { |
| if (l.cost == r.cost) |
| return l.curX < r.curX; |
| return l.cost < r.cost; |
| } |
| }; |
| |
| template<typename T, typename N> |
| class kneePointExtractor { |
| public: |
| kneePointExtractor(curveData<T> &data) |
| : curvedata(data) |
| { |
| // Initial start point of select list. |
| int lx = 0; |
| int ly = curvedata.getY(lx); |
| |
| selectList.insert({lx, ly, lx, ly, ly, ly, ly}); |
| |
| // Initial end point of select list. |
| int rx = data.inputRange; |
| int ry = curvedata.getY(rx); |
| |
| selectList.insert({rx, ry, rx, ry, ry, ry, ry}); |
| |
| // Initial start point of searching candidate list |
| int cx = (lx + rx) >> 1; |
| int cy = curvedata.getY(cx); |
| int py = curvedata.getY(cx - ((rx - lx) >> 2)); |
| int ny = curvedata.getY(cx + ((rx - lx) >> 2)); |
| |
| candidateList.push({lx, ly, rx, ry, cy, py, ny}); |
| } |
| |
| void select_from_candidate(int numArray) |
| { |
| for (int i = 0; i < numArray; i++) { |
| N node = candidateList.top(); |
| |
| // Move from candidate to select |
| candidateList.pop(); |
| selectList.insert(node); |
| |
| // Leaf node |
| if (node.rightX - node.leftX <= (curvedata.minX << 1)) |
| continue; |
| |
| candidateList.push({ node.leftX, node.leftY, node.curX, node.curY, |
| curvedata.getY((node.leftX + node.curX) >> 1), |
| curvedata.getY(((node.leftX + node.curX) >> 1) - ((node.curX - node.leftX) >> 2)), |
| curvedata.getY(((node.leftX + node.curX) >> 1) + ((node.curX - node.leftX) >> 2)), |
| }); |
| candidateList.push({ node.curX, node.curY, node.rightX, node.rightY, |
| curvedata.getY((node.curX + node.rightX) >> 1), |
| curvedata.getY(((node.curX + node.rightX) >> 1) - ((node.rightX - node.curX) >> 2)), |
| curvedata.getY(((node.curX + node.rightX) >> 1) + ((node.rightX - node.curX) >> 2)), |
| }); |
| } |
| } |
| |
| void select(int numArray, std::vector<int> &arrX, std::vector<int> &arrY) |
| { |
| // Select node already contains start and end point, so subtract two. |
| select_from_candidate(numArray - 2); |
| |
| // Write select node |
| int i = 0; |
| for (auto &node : selectList) { |
| arrX[i] = node.curX; |
| arrY[i] = node.curY; |
| i++; |
| } |
| |
| // The last coordinate has difference with prior value |
| arrX[i - 1] -= arrX[i - 2]; |
| arrY[i - 1] -= arrY[i - 2]; |
| } |
| |
| private: |
| set<N, selectComp<N>> selectList; |
| priority_queue<N, vector<N>, candidateComp<N>> candidateList; |
| curveData<T> &curvedata; |
| }; |
| |
| void genTMCurve( |
| CurveInfo info, |
| void *data, |
| std::vector<int> &arrX, std::vector<int> &arrY, int numArray, |
| int x_bits, int y_bits, int minx_bits) |
| { |
| ExynosHdrDynamicInfo_t meta = *(ExynosHdrDynamicInfo_t *)data; |
| |
| meta2meta(info.maxOutLumi, info.maxInLumi, meta); |
| |
| int NUM_X = 1 << x_bits; |
| int NUM_Y = 1 << y_bits; |
| int MIN_X = 1 << minx_bits; |
| OETFCurveData curvedata(meta, NUM_X, NUM_Y, MIN_X); |
| kneePointExtractor<ExynosHdrDynamicInfo_t, NodeMultiPoints> points(curvedata); |
| |
| points.select(numArray, arrX, arrY); |
| } |
| |
| void genTMCurve( |
| CurveInfo info, |
| std::vector<int> &arrX, std::vector<int> &arrY, int numArray, |
| int x_bits, int y_bits, int minx_bits) |
| { |
| int NUM_X = 1 << x_bits; |
| int NUM_Y = 1 << y_bits; |
| int MIN_X = 1 << minx_bits; |
| ATMCurveData curvedata(info, NUM_X, NUM_Y, MIN_X); |
| kneePointExtractor<CurveInfo, NodeMultiPoints> points(curvedata); |
| |
| points.select(numArray, arrX, arrY); |
| } |
| |
| void genEOTFCurve( |
| CurveInfo info, |
| std::vector<int> &arrX, std::vector<int> &arrY, int numArray, |
| int x_bits, int y_bits, int minx_bits) |
| { |
| int NUM_X = 1 << x_bits; |
| int NUM_Y = (1 << y_bits) - 1; |
| int MIN_X = 1 << minx_bits; |
| EOTFCurveData curvedata(info, NUM_X, NUM_Y, MIN_X); |
| kneePointExtractor<CurveInfo, NodeMultiPoints> points(curvedata); |
| |
| points.select(numArray, arrX, arrY); |
| } |
| |