xref: /aosp_15_r20/external/libultrahdr/lib/src/gainmapmath.cpp (revision 89a0ef05262152531a00a15832a2d3b1e3990773)
1 /*
2  * Copyright 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <cmath>
18 
19 #include "ultrahdr/gainmapmath.h"
20 
21 namespace ultrahdr {
22 
23 ////////////////////////////////////////////////////////////////////////////////
24 // Framework
25 
getReferenceDisplayPeakLuminanceInNits(uhdr_color_transfer_t transfer)26 float getReferenceDisplayPeakLuminanceInNits(uhdr_color_transfer_t transfer) {
27   switch (transfer) {
28     case UHDR_CT_LINEAR:
29       return kPqMaxNits;
30     case UHDR_CT_HLG:
31       return kHlgMaxNits;
32     case UHDR_CT_PQ:
33       return kPqMaxNits;
34     case UHDR_CT_SRGB:
35       return kSdrWhiteNits;
36     case UHDR_CT_UNSPECIFIED:
37       return -1.0f;
38   }
39   return -1.0f;
40 }
41 
42 ////////////////////////////////////////////////////////////////////////////////
43 // Use Shepard's method for inverse distance weighting.
44 
euclideanDistance(float x1,float x2,float y1,float y2)45 float ShepardsIDW::euclideanDistance(float x1, float x2, float y1, float y2) {
46   return sqrt(((y2 - y1) * (y2 - y1)) + (x2 - x1) * (x2 - x1));
47 }
48 
fillShepardsIDW(float * weights,int incR,int incB)49 void ShepardsIDW::fillShepardsIDW(float* weights, int incR, int incB) {
50   for (int y = 0; y < mMapScaleFactor; y++) {
51     for (int x = 0; x < mMapScaleFactor; x++) {
52       float pos_x = ((float)x) / mMapScaleFactor;
53       float pos_y = ((float)y) / mMapScaleFactor;
54       int curr_x = floor(pos_x);
55       int curr_y = floor(pos_y);
56       int next_x = curr_x + incR;
57       int next_y = curr_y + incB;
58       float e1_distance = euclideanDistance(pos_x, curr_x, pos_y, curr_y);
59       int index = y * mMapScaleFactor * 4 + x * 4;
60       if (e1_distance == 0) {
61         weights[index++] = 1.f;
62         weights[index++] = 0.f;
63         weights[index++] = 0.f;
64         weights[index++] = 0.f;
65       } else {
66         float e1_weight = 1.f / e1_distance;
67 
68         float e2_distance = euclideanDistance(pos_x, curr_x, pos_y, next_y);
69         float e2_weight = 1.f / e2_distance;
70 
71         float e3_distance = euclideanDistance(pos_x, next_x, pos_y, curr_y);
72         float e3_weight = 1.f / e3_distance;
73 
74         float e4_distance = euclideanDistance(pos_x, next_x, pos_y, next_y);
75         float e4_weight = 1.f / e4_distance;
76 
77         float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
78 
79         weights[index++] = e1_weight / total_weight;
80         weights[index++] = e2_weight / total_weight;
81         weights[index++] = e3_weight / total_weight;
82         weights[index++] = e4_weight / total_weight;
83       }
84     }
85   }
86 }
87 
88 ////////////////////////////////////////////////////////////////////////////////
89 // sRGB transformations
90 
91 // See IEC 61966-2-1/Amd 1:2003, Equation F.7.
92 static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f;
93 
srgbLuminance(Color e)94 float srgbLuminance(Color e) { return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b; }
95 
96 // See ITU-R BT.709-6, Section 3.
97 // Uses the same coefficients for deriving luma signal as
98 // IEC 61966-2-1/Amd 1:2003 states for luminance, so we reuse the luminance
99 // function above.
100 static const float kSrgbCb = 1.8556f, kSrgbCr = 1.5748f;
101 
srgbRgbToYuv(Color e_gamma)102 Color srgbRgbToYuv(Color e_gamma) {
103   float y_gamma = srgbLuminance(e_gamma);
104   return {{{y_gamma, (e_gamma.b - y_gamma) / kSrgbCb, (e_gamma.r - y_gamma) / kSrgbCr}}};
105 }
106 
107 // See ITU-R BT.709-6, Section 3.
108 // Same derivation to BT.2100's YUV->RGB, below. Similar to srgbRgbToYuv, we
109 // can reuse the luminance coefficients since they are the same.
110 static const float kSrgbGCb = kSrgbB * kSrgbCb / kSrgbG;
111 static const float kSrgbGCr = kSrgbR * kSrgbCr / kSrgbG;
112 
srgbYuvToRgb(Color e_gamma)113 Color srgbYuvToRgb(Color e_gamma) {
114   return {{{clampPixelFloat(e_gamma.y + kSrgbCr * e_gamma.v),
115             clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v),
116             clampPixelFloat(e_gamma.y + kSrgbCb * e_gamma.u)}}};
117 }
118 
119 // See IEC 61966-2-1/Amd 1:2003, Equations F.5 and F.6.
srgbInvOetf(float e_gamma)120 float srgbInvOetf(float e_gamma) {
121   if (e_gamma <= 0.04045f) {
122     return e_gamma / 12.92f;
123   } else {
124     return pow((e_gamma + 0.055f) / 1.055f, 2.4);
125   }
126 }
127 
srgbInvOetf(Color e_gamma)128 Color srgbInvOetf(Color e_gamma) {
129   return {{{srgbInvOetf(e_gamma.r), srgbInvOetf(e_gamma.g), srgbInvOetf(e_gamma.b)}}};
130 }
131 
132 // See IEC 61966-2-1, Equations F.5 and F.6.
srgbInvOetfLUT(float e_gamma)133 float srgbInvOetfLUT(float e_gamma) {
134   int32_t value = static_cast<int32_t>(e_gamma * (kSrgbInvOETFNumEntries - 1) + 0.5);
135   // TODO() : Remove once conversion modules have appropriate clamping in place
136   value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1);
137   static LookUpTable kSrgbLut(kSrgbInvOETFNumEntries, static_cast<float (*)(float)>(srgbInvOetf));
138   return kSrgbLut.getTable()[value];
139 }
140 
srgbInvOetfLUT(Color e_gamma)141 Color srgbInvOetfLUT(Color e_gamma) {
142   return {{{srgbInvOetfLUT(e_gamma.r), srgbInvOetfLUT(e_gamma.g), srgbInvOetfLUT(e_gamma.b)}}};
143 }
144 
srgbOetf(float e)145 float srgbOetf(float e) {
146   constexpr float kThreshold = 0.0031308;
147   constexpr float kLowSlope = 12.92;
148   constexpr float kHighOffset = 0.055;
149   constexpr float kPowerExponent = 1.0 / 2.4;
150   if (e <= kThreshold) {
151     return kLowSlope * e;
152   }
153   return (1.0 + kHighOffset) * std::pow(e, kPowerExponent) - kHighOffset;
154 }
155 
srgbOetf(Color e)156 Color srgbOetf(Color e) { return {{{srgbOetf(e.r), srgbOetf(e.g), srgbOetf(e.b)}}}; }
157 
158 ////////////////////////////////////////////////////////////////////////////////
159 // Display-P3 transformations
160 
161 // See SMPTE EG 432-1, Equation 7-8.
162 static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f;
163 
p3Luminance(Color e)164 float p3Luminance(Color e) { return kP3R * e.r + kP3G * e.g + kP3B * e.b; }
165 
166 // See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
167 // Unfortunately, calculation of luma signal differs from calculation of
168 // luminance for Display-P3, so we can't reuse p3Luminance here.
169 static const float kP3YR = 0.299f, kP3YG = 0.587f, kP3YB = 0.114f;
170 static const float kP3Cb = 1.772f, kP3Cr = 1.402f;
171 
p3RgbToYuv(Color e_gamma)172 Color p3RgbToYuv(Color e_gamma) {
173   float y_gamma = kP3YR * e_gamma.r + kP3YG * e_gamma.g + kP3YB * e_gamma.b;
174   return {{{y_gamma, (e_gamma.b - y_gamma) / kP3Cb, (e_gamma.r - y_gamma) / kP3Cr}}};
175 }
176 
177 // See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
178 // Same derivation to BT.2100's YUV->RGB, below. Similar to p3RgbToYuv, we must
179 // use luma signal coefficients rather than the luminance coefficients.
180 static const float kP3GCb = kP3YB * kP3Cb / kP3YG;
181 static const float kP3GCr = kP3YR * kP3Cr / kP3YG;
182 
p3YuvToRgb(Color e_gamma)183 Color p3YuvToRgb(Color e_gamma) {
184   return {{{clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v),
185             clampPixelFloat(e_gamma.y - kP3GCb * e_gamma.u - kP3GCr * e_gamma.v),
186             clampPixelFloat(e_gamma.y + kP3Cb * e_gamma.u)}}};
187 }
188 
189 ////////////////////////////////////////////////////////////////////////////////
190 // BT.2100 transformations - according to ITU-R BT.2100-2
191 
192 // See ITU-R BT.2100-2, Table 5, HLG Reference OOTF
193 static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f;
194 
bt2100Luminance(Color e)195 float bt2100Luminance(Color e) { return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b; }
196 
197 // See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
198 // BT.2100 uses the same coefficients for calculating luma signal and luminance,
199 // so we reuse the luminance function here.
200 static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;
201 
bt2100RgbToYuv(Color e_gamma)202 Color bt2100RgbToYuv(Color e_gamma) {
203   float y_gamma = bt2100Luminance(e_gamma);
204   return {{{y_gamma, (e_gamma.b - y_gamma) / kBt2100Cb, (e_gamma.r - y_gamma) / kBt2100Cr}}};
205 }
206 
207 // See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
208 //
209 // Similar to bt2100RgbToYuv above, we can reuse the luminance coefficients.
210 //
211 // Derived by inversing bt2100RgbToYuv. The derivation for R and B are  pretty
212 // straight forward; we just invert the formulas for U and V above. But deriving
213 // the formula for G is a bit more complicated:
214 //
215 // Start with equation for luminance:
216 //   Y = kBt2100R * R + kBt2100G * G + kBt2100B * B
217 // Solve for G:
218 //   G = (Y - kBt2100R * R - kBt2100B * B) / kBt2100B
219 // Substitute equations for R and B in terms YUV:
220 //   G = (Y - kBt2100R * (Y + kBt2100Cr * V) - kBt2100B * (Y + kBt2100Cb * U)) / kBt2100B
221 // Simplify:
222 //   G = Y * ((1 - kBt2100R - kBt2100B) / kBt2100G)
223 //     + U * (kBt2100B * kBt2100Cb / kBt2100G)
224 //     + V * (kBt2100R * kBt2100Cr / kBt2100G)
225 //
226 // We then get the following coeficients for calculating G from YUV:
227 //
228 // Coef for Y = (1 - kBt2100R - kBt2100B) / kBt2100G = 1
229 // Coef for U = kBt2100B * kBt2100Cb / kBt2100G = kBt2100GCb = ~0.1645
230 // Coef for V = kBt2100R * kBt2100Cr / kBt2100G = kBt2100GCr = ~0.5713
231 
232 static const float kBt2100GCb = kBt2100B * kBt2100Cb / kBt2100G;
233 static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G;
234 
bt2100YuvToRgb(Color e_gamma)235 Color bt2100YuvToRgb(Color e_gamma) {
236   return {{{clampPixelFloat(e_gamma.y + kBt2100Cr * e_gamma.v),
237             clampPixelFloat(e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v),
238             clampPixelFloat(e_gamma.y + kBt2100Cb * e_gamma.u)}}};
239 }
240 
241 // See ITU-R BT.2100-2, Table 5, HLG Reference OETF.
242 static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073;
243 
hlgOetf(float e)244 float hlgOetf(float e) {
245   if (e <= 1.0f / 12.0f) {
246     return sqrt(3.0f * e);
247   } else {
248     return kHlgA * log(12.0f * e - kHlgB) + kHlgC;
249   }
250 }
251 
hlgOetf(Color e)252 Color hlgOetf(Color e) { return {{{hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b)}}}; }
253 
hlgOetfLUT(float e)254 float hlgOetfLUT(float e) {
255   int32_t value = static_cast<int32_t>(e * (kHlgOETFNumEntries - 1) + 0.5);
256   // TODO() : Remove once conversion modules have appropriate clamping in place
257   value = CLIP3(value, 0, kHlgOETFNumEntries - 1);
258   static LookUpTable kHlgLut(kHlgOETFNumEntries, static_cast<float (*)(float)>(hlgOetf));
259   return kHlgLut.getTable()[value];
260 }
261 
hlgOetfLUT(Color e)262 Color hlgOetfLUT(Color e) { return {{{hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b)}}}; }
263 
264 // See ITU-R BT.2100-2, Table 5, HLG Reference EOTF.
hlgInvOetf(float e_gamma)265 float hlgInvOetf(float e_gamma) {
266   if (e_gamma <= 0.5f) {
267     return pow(e_gamma, 2.0f) / 3.0f;
268   } else {
269     return (exp((e_gamma - kHlgC) / kHlgA) + kHlgB) / 12.0f;
270   }
271 }
272 
hlgInvOetf(Color e_gamma)273 Color hlgInvOetf(Color e_gamma) {
274   return {{{hlgInvOetf(e_gamma.r), hlgInvOetf(e_gamma.g), hlgInvOetf(e_gamma.b)}}};
275 }
276 
hlgInvOetfLUT(float e_gamma)277 float hlgInvOetfLUT(float e_gamma) {
278   int32_t value = static_cast<int32_t>(e_gamma * (kHlgInvOETFNumEntries - 1) + 0.5);
279   // TODO() : Remove once conversion modules have appropriate clamping in place
280   value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1);
281   static LookUpTable kHlgInvLut(kHlgInvOETFNumEntries, static_cast<float (*)(float)>(hlgInvOetf));
282   return kHlgInvLut.getTable()[value];
283 }
284 
hlgInvOetfLUT(Color e_gamma)285 Color hlgInvOetfLUT(Color e_gamma) {
286   return {{{hlgInvOetfLUT(e_gamma.r), hlgInvOetfLUT(e_gamma.g), hlgInvOetfLUT(e_gamma.b)}}};
287 }
288 
289 // 1.2f + 0.42 * log(kHlgMaxNits / 1000)
290 static const float kOotfGamma = 1.2f;
291 
hlgOotf(Color e,LuminanceFn luminance)292 Color hlgOotf(Color e, LuminanceFn luminance) {
293   float y = luminance(e);
294   return e * std::pow(y, kOotfGamma - 1.0f);
295 }
296 
hlgOotfApprox(Color e,LuminanceFn luminance)297 Color hlgOotfApprox(Color e, [[maybe_unused]] LuminanceFn luminance) {
298   return {{{std::pow(e.r, kOotfGamma), std::pow(e.g, kOotfGamma), std::pow(e.b, kOotfGamma)}}};
299 }
300 
hlgInverseOotf(Color e,LuminanceFn luminance)301 Color hlgInverseOotf(Color e, LuminanceFn luminance) {
302   float y = luminance(e);
303   return e * std::pow(y, (1.0f / kOotfGamma) - 1.0f);
304 }
305 
hlgInverseOotfApprox(Color e)306 Color hlgInverseOotfApprox(Color e) {
307   return {{{std::pow(e.r, 1.0f / kOotfGamma), std::pow(e.g, 1.0f / kOotfGamma),
308             std::pow(e.b, 1.0f / kOotfGamma)}}};
309 }
310 
311 // See ITU-R BT.2100-2, Table 4, Reference PQ OETF.
312 static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f;
313 static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f,
314                    kPqC3 = 2392.0f / 4096.0f * 32.0f;
315 
pqOetf(float e)316 float pqOetf(float e) {
317   if (e <= 0.0f) return 0.0f;
318   return pow((kPqC1 + kPqC2 * pow(e, kPqM1)) / (1 + kPqC3 * pow(e, kPqM1)), kPqM2);
319 }
320 
pqOetf(Color e)321 Color pqOetf(Color e) { return {{{pqOetf(e.r), pqOetf(e.g), pqOetf(e.b)}}}; }
322 
pqOetfLUT(float e)323 float pqOetfLUT(float e) {
324   int32_t value = static_cast<int32_t>(e * (kPqOETFNumEntries - 1) + 0.5);
325   // TODO() : Remove once conversion modules have appropriate clamping in place
326   value = CLIP3(value, 0, kPqOETFNumEntries - 1);
327   static LookUpTable kPqLut(kPqOETFNumEntries, static_cast<float (*)(float)>(pqOetf));
328   return kPqLut.getTable()[value];
329 }
330 
pqOetfLUT(Color e)331 Color pqOetfLUT(Color e) { return {{{pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b)}}}; }
332 
pqInvOetf(float e_gamma)333 float pqInvOetf(float e_gamma) {
334   float val = pow(e_gamma, (1 / kPqM2));
335   return pow((((std::max)(val - kPqC1, 0.0f)) / (kPqC2 - kPqC3 * val)), 1 / kPqM1);
336 }
337 
pqInvOetf(Color e_gamma)338 Color pqInvOetf(Color e_gamma) {
339   return {{{pqInvOetf(e_gamma.r), pqInvOetf(e_gamma.g), pqInvOetf(e_gamma.b)}}};
340 }
341 
pqInvOetfLUT(float e_gamma)342 float pqInvOetfLUT(float e_gamma) {
343   int32_t value = static_cast<int32_t>(e_gamma * (kPqInvOETFNumEntries - 1) + 0.5);
344   // TODO() : Remove once conversion modules have appropriate clamping in place
345   value = CLIP3(value, 0, kPqInvOETFNumEntries - 1);
346   static LookUpTable kPqInvLut(kPqInvOETFNumEntries, static_cast<float (*)(float)>(pqInvOetf));
347   return kPqInvLut.getTable()[value];
348 }
349 
pqInvOetfLUT(Color e_gamma)350 Color pqInvOetfLUT(Color e_gamma) {
351   return {{{pqInvOetfLUT(e_gamma.r), pqInvOetfLUT(e_gamma.g), pqInvOetfLUT(e_gamma.b)}}};
352 }
353 
354 ////////////////////////////////////////////////////////////////////////////////
355 // Color access functions
356 
getYuv4abPixel(uhdr_raw_image_t * image,size_t x,size_t y,int h_factor,int v_factor)357 Color getYuv4abPixel(uhdr_raw_image_t* image, size_t x, size_t y, int h_factor, int v_factor) {
358   uint8_t* luma_data = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_Y]);
359   size_t luma_stride = image->stride[UHDR_PLANE_Y];
360   uint8_t* cb_data = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_U]);
361   size_t cb_stride = image->stride[UHDR_PLANE_U];
362   uint8_t* cr_data = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_V]);
363   size_t cr_stride = image->stride[UHDR_PLANE_V];
364 
365   size_t pixel_y_idx = x + y * luma_stride;
366   size_t pixel_cb_idx = x / h_factor + (y / v_factor) * cb_stride;
367   size_t pixel_cr_idx = x / h_factor + (y / v_factor) * cr_stride;
368 
369   uint8_t y_uint = luma_data[pixel_y_idx];
370   uint8_t u_uint = cb_data[pixel_cb_idx];
371   uint8_t v_uint = cr_data[pixel_cr_idx];
372 
373   // 128 bias for UV given we are using jpeglib; see:
374   // https://github.com/kornelski/libjpeg/blob/master/structure.doc
375   return {
376       {{static_cast<float>(y_uint) * (1 / 255.0f), static_cast<float>(u_uint - 128) * (1 / 255.0f),
377         static_cast<float>(v_uint - 128) * (1 / 255.0f)}}};
378 }
379 
getYuv444Pixel(uhdr_raw_image_t * image,size_t x,size_t y)380 Color getYuv444Pixel(uhdr_raw_image_t* image, size_t x, size_t y) {
381   return getYuv4abPixel(image, x, y, 1, 1);
382 }
383 
getYuv422Pixel(uhdr_raw_image_t * image,size_t x,size_t y)384 Color getYuv422Pixel(uhdr_raw_image_t* image, size_t x, size_t y) {
385   return getYuv4abPixel(image, x, y, 2, 1);
386 }
387 
getYuv420Pixel(uhdr_raw_image_t * image,size_t x,size_t y)388 Color getYuv420Pixel(uhdr_raw_image_t* image, size_t x, size_t y) {
389   return getYuv4abPixel(image, x, y, 2, 2);
390 }
391 
getYuv400Pixel(uhdr_raw_image_t * image,size_t x,size_t y)392 Color getYuv400Pixel(uhdr_raw_image_t* image, size_t x, size_t y) {
393   uint8_t* luma_data = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_Y]);
394   size_t luma_stride = image->stride[UHDR_PLANE_Y];
395   size_t pixel_y_idx = x + y * luma_stride;
396   uint8_t y_uint = luma_data[pixel_y_idx];
397 
398   return {{{static_cast<float>(y_uint) * (1 / 255.0f), 0.f, 0.f}}};
399 }
400 
getYuv444Pixel10bit(uhdr_raw_image_t * image,size_t x,size_t y)401 Color getYuv444Pixel10bit(uhdr_raw_image_t* image, size_t x, size_t y) {
402   uint16_t* luma_data = reinterpret_cast<uint16_t*>(image->planes[UHDR_PLANE_Y]);
403   size_t luma_stride = image->stride[UHDR_PLANE_Y];
404   uint16_t* cb_data = reinterpret_cast<uint16_t*>(image->planes[UHDR_PLANE_U]);
405   size_t cb_stride = image->stride[UHDR_PLANE_U];
406   uint16_t* cr_data = reinterpret_cast<uint16_t*>(image->planes[UHDR_PLANE_V]);
407   size_t cr_stride = image->stride[UHDR_PLANE_V];
408 
409   size_t pixel_y_idx = y * luma_stride + x;
410   size_t pixel_u_idx = y * cb_stride + x;
411   size_t pixel_v_idx = y * cr_stride + x;
412 
413   uint16_t y_uint = luma_data[pixel_y_idx];
414   uint16_t u_uint = cb_data[pixel_u_idx];
415   uint16_t v_uint = cr_data[pixel_v_idx];
416 
417   if (image->range == UHDR_CR_FULL_RANGE) {
418     return {{{static_cast<float>(y_uint) / 1023.0f, static_cast<float>(u_uint) / 1023.0f - 0.5f,
419               static_cast<float>(v_uint) / 1023.0f - 0.5f}}};
420   }
421 
422   // Conversions include taking narrow-range into account.
423   return {{{static_cast<float>(y_uint - 64) * (1 / 876.0f),
424             static_cast<float>(u_uint - 64) * (1 / 896.0f) - 0.5f,
425             static_cast<float>(v_uint - 64) * (1 / 896.0f) - 0.5f}}};
426 }
427 
getP010Pixel(uhdr_raw_image_t * image,size_t x,size_t y)428 Color getP010Pixel(uhdr_raw_image_t* image, size_t x, size_t y) {
429   uint16_t* luma_data = reinterpret_cast<uint16_t*>(image->planes[UHDR_PLANE_Y]);
430   size_t luma_stride = image->stride[UHDR_PLANE_Y];
431   uint16_t* chroma_data = reinterpret_cast<uint16_t*>(image->planes[UHDR_PLANE_UV]);
432   size_t chroma_stride = image->stride[UHDR_PLANE_UV];
433 
434   size_t pixel_y_idx = y * luma_stride + x;
435   size_t pixel_u_idx = (y >> 1) * chroma_stride + (x & ~0x1);
436   size_t pixel_v_idx = pixel_u_idx + 1;
437 
438   uint16_t y_uint = luma_data[pixel_y_idx] >> 6;
439   uint16_t u_uint = chroma_data[pixel_u_idx] >> 6;
440   uint16_t v_uint = chroma_data[pixel_v_idx] >> 6;
441 
442   if (image->range == UHDR_CR_FULL_RANGE) {
443     return {{{static_cast<float>(y_uint) / 1023.0f, static_cast<float>(u_uint) / 1023.0f - 0.5f,
444               static_cast<float>(v_uint) / 1023.0f - 0.5f}}};
445   }
446 
447   // Conversions include taking narrow-range into account.
448   return {{{static_cast<float>(y_uint - 64) * (1 / 876.0f),
449             static_cast<float>(u_uint - 64) * (1 / 896.0f) - 0.5f,
450             static_cast<float>(v_uint - 64) * (1 / 896.0f) - 0.5f}}};
451 }
452 
getRgb888Pixel(uhdr_raw_image_t * image,size_t x,size_t y)453 Color getRgb888Pixel(uhdr_raw_image_t* image, size_t x, size_t y) {
454   uint8_t* rgbData = static_cast<uint8_t*>(image->planes[UHDR_PLANE_PACKED]);
455   unsigned int srcStride = image->stride[UHDR_PLANE_PACKED];
456   size_t offset = x * 3 + y * srcStride * 3;
457   Color pixel;
458   pixel.r = float(rgbData[offset]);
459   pixel.g = float(rgbData[offset + 1]);
460   pixel.b = float(rgbData[offset + 2]);
461   return pixel / 255.0f;
462 }
463 
getRgba8888Pixel(uhdr_raw_image_t * image,size_t x,size_t y)464 Color getRgba8888Pixel(uhdr_raw_image_t* image, size_t x, size_t y) {
465   uint32_t* rgbData = static_cast<uint32_t*>(image->planes[UHDR_PLANE_PACKED]);
466   unsigned int srcStride = image->stride[UHDR_PLANE_PACKED];
467 
468   Color pixel;
469   pixel.r = float(rgbData[x + y * srcStride] & 0xff);
470   pixel.g = float((rgbData[x + y * srcStride] >> 8) & 0xff);
471   pixel.b = float((rgbData[x + y * srcStride] >> 16) & 0xff);
472   return pixel / 255.0f;
473 }
474 
getRgba1010102Pixel(uhdr_raw_image_t * image,size_t x,size_t y)475 Color getRgba1010102Pixel(uhdr_raw_image_t* image, size_t x, size_t y) {
476   uint32_t* rgbData = static_cast<uint32_t*>(image->planes[UHDR_PLANE_PACKED]);
477   unsigned int srcStride = image->stride[UHDR_PLANE_PACKED];
478 
479   Color pixel;
480   pixel.r = float(rgbData[x + y * srcStride] & 0x3ff);
481   pixel.g = float((rgbData[x + y * srcStride] >> 10) & 0x3ff);
482   pixel.b = float((rgbData[x + y * srcStride] >> 20) & 0x3ff);
483   return pixel / 1023.0f;
484 }
485 
getRgbaF16Pixel(uhdr_raw_image_t * image,size_t x,size_t y)486 Color getRgbaF16Pixel(uhdr_raw_image_t* image, size_t x, size_t y) {
487   uint64_t* rgbData = static_cast<uint64_t*>(image->planes[UHDR_PLANE_PACKED]);
488   unsigned int srcStride = image->stride[UHDR_PLANE_PACKED];
489 
490   Color pixel;
491   pixel.r = halfToFloat(rgbData[x + y * srcStride] & 0xffff);
492   pixel.g = halfToFloat((rgbData[x + y * srcStride] >> 16) & 0xffff);
493   pixel.b = halfToFloat((rgbData[x + y * srcStride] >> 32) & 0xffff);
494   return sanitizePixel(pixel);
495 }
496 
samplePixels(uhdr_raw_image_t * image,size_t map_scale_factor,size_t x,size_t y,GetPixelFn get_pixel_fn)497 static Color samplePixels(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y,
498                           GetPixelFn get_pixel_fn) {
499   Color e = {{{0.0f, 0.0f, 0.0f}}};
500   for (size_t dy = 0; dy < map_scale_factor; ++dy) {
501     for (size_t dx = 0; dx < map_scale_factor; ++dx) {
502       e += get_pixel_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy);
503     }
504   }
505 
506   return e / static_cast<float>(map_scale_factor * map_scale_factor);
507 }
508 
sampleYuv444(uhdr_raw_image_t * image,size_t map_scale_factor,size_t x,size_t y)509 Color sampleYuv444(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y) {
510   return samplePixels(image, map_scale_factor, x, y, getYuv444Pixel);
511 }
512 
sampleYuv422(uhdr_raw_image_t * image,size_t map_scale_factor,size_t x,size_t y)513 Color sampleYuv422(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y) {
514   return samplePixels(image, map_scale_factor, x, y, getYuv422Pixel);
515 }
516 
sampleYuv420(uhdr_raw_image_t * image,size_t map_scale_factor,size_t x,size_t y)517 Color sampleYuv420(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y) {
518   return samplePixels(image, map_scale_factor, x, y, getYuv420Pixel);
519 }
520 
sampleP010(uhdr_raw_image_t * image,size_t map_scale_factor,size_t x,size_t y)521 Color sampleP010(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y) {
522   return samplePixels(image, map_scale_factor, x, y, getP010Pixel);
523 }
524 
sampleYuv44410bit(uhdr_raw_image_t * image,size_t map_scale_factor,size_t x,size_t y)525 Color sampleYuv44410bit(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y) {
526   return samplePixels(image, map_scale_factor, x, y, getYuv444Pixel10bit);
527 }
528 
sampleRgba8888(uhdr_raw_image_t * image,size_t map_scale_factor,size_t x,size_t y)529 Color sampleRgba8888(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y) {
530   return samplePixels(image, map_scale_factor, x, y, getRgba8888Pixel);
531 }
532 
sampleRgba1010102(uhdr_raw_image_t * image,size_t map_scale_factor,size_t x,size_t y)533 Color sampleRgba1010102(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y) {
534   return samplePixels(image, map_scale_factor, x, y, getRgba1010102Pixel);
535 }
536 
sampleRgbaF16(uhdr_raw_image_t * image,size_t map_scale_factor,size_t x,size_t y)537 Color sampleRgbaF16(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y) {
538   return samplePixels(image, map_scale_factor, x, y, getRgbaF16Pixel);
539 }
540 
putRgba8888Pixel(uhdr_raw_image_t * image,size_t x,size_t y,Color & pixel)541 void putRgba8888Pixel(uhdr_raw_image_t* image, size_t x, size_t y, Color& pixel) {
542   uint32_t* rgbData = static_cast<uint32_t*>(image->planes[UHDR_PLANE_PACKED]);
543   unsigned int srcStride = image->stride[UHDR_PLANE_PACKED];
544 
545   pixel *= 255.0f;
546   pixel += 0.5f;
547   pixel.r = CLIP3(pixel.r, 0.0f, 255.0f);
548   pixel.g = CLIP3(pixel.g, 0.0f, 255.0f);
549   pixel.b = CLIP3(pixel.b, 0.0f, 255.0f);
550 
551   int32_t r0 = int32_t(pixel.r);
552   int32_t g0 = int32_t(pixel.g);
553   int32_t b0 = int32_t(pixel.b);
554   rgbData[x + y * srcStride] = r0 | (g0 << 8) | (b0 << 16) | (255 << 24);  // Set alpha to 1.0
555 }
556 
putRgb888Pixel(uhdr_raw_image_t * image,size_t x,size_t y,Color & pixel)557 void putRgb888Pixel(uhdr_raw_image_t* image, size_t x, size_t y, Color& pixel) {
558   uint8_t* rgbData = static_cast<uint8_t*>(image->planes[UHDR_PLANE_PACKED]);
559   unsigned int srcStride = image->stride[UHDR_PLANE_PACKED];
560   size_t offset = x * 3 + y * srcStride * 3;
561   pixel *= 255.0f;
562   pixel += 0.5f;
563   pixel.r = CLIP3(pixel.r, 0.0f, 255.0f);
564   pixel.g = CLIP3(pixel.g, 0.0f, 255.0f);
565   pixel.b = CLIP3(pixel.b, 0.0f, 255.0f);
566   rgbData[offset] = uint8_t(pixel.r);
567   rgbData[offset + 1] = uint8_t(pixel.r);
568   rgbData[offset + 2] = uint8_t(pixel.b);
569 }
570 
putYuv400Pixel(uhdr_raw_image_t * image,size_t x,size_t y,Color & pixel)571 void putYuv400Pixel(uhdr_raw_image_t* image, size_t x, size_t y, Color& pixel) {
572   uint8_t* luma_data = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_Y]);
573   size_t luma_stride = image->stride[UHDR_PLANE_Y];
574 
575   pixel *= 255.0f;
576   pixel += 0.5f;
577   pixel.y = CLIP3(pixel.y, 0.0f, 255.0f);
578 
579   luma_data[x + y * luma_stride] = uint8_t(pixel.y);
580 }
581 
putYuv444Pixel(uhdr_raw_image_t * image,size_t x,size_t y,Color & pixel)582 void putYuv444Pixel(uhdr_raw_image_t* image, size_t x, size_t y, Color& pixel) {
583   uint8_t* luma_data = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_Y]);
584   uint8_t* cb_data = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_U]);
585   uint8_t* cr_data = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_V]);
586   size_t luma_stride = image->stride[UHDR_PLANE_Y];
587   size_t cb_stride = image->stride[UHDR_PLANE_U];
588   size_t cr_stride = image->stride[UHDR_PLANE_V];
589 
590   pixel *= 255.0f;
591   pixel += 0.5f;
592   pixel.y = CLIP3(pixel.y, 0.0f, 255.0f);
593   pixel.u = CLIP3(pixel.u, 0.0f, 255.0f);
594   pixel.v = CLIP3(pixel.v, 0.0f, 255.0f);
595 
596   luma_data[x + y * luma_stride] = uint8_t(pixel.y);
597   cb_data[x + y * cb_stride] = uint8_t(pixel.u);
598   cr_data[x + y * cr_stride] = uint8_t(pixel.v);
599 }
600 
601 ////////////////////////////////////////////////////////////////////////////////
602 // Color space conversions
603 
bt709ToP3(Color e)604 Color bt709ToP3(Color e) {
605   return {{{0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b,
606             0.03312f * e.r + 0.96684f * e.g + -0.00001f * e.b,
607             0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b}}};
608 }
609 
bt709ToBt2100(Color e)610 Color bt709ToBt2100(Color e) {
611   return {{{0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b,
612             0.06904f * e.r + 0.91958f * e.g + 0.01138f * e.b,
613             0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b}}};
614 }
615 
p3ToBt709(Color e)616 Color p3ToBt709(Color e) {
617   return {{{1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b,
618             -0.04196f * e.r + 1.04199f * e.g + 0.00001f * e.b,
619             -0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b}}};
620 }
621 
p3ToBt2100(Color e)622 Color p3ToBt2100(Color e) {
623   return {{{0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b,
624             0.04576f * e.r + 0.94177f * e.g + 0.01250f * e.b,
625             -0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b}}};
626 }
627 
bt2100ToBt709(Color e)628 Color bt2100ToBt709(Color e) {
629   return {{{1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b,
630             -0.12445f * e.r + 1.13282f * e.g + -0.00837f * e.b,
631             -0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b}}};
632 }
633 
bt2100ToP3(Color e)634 Color bt2100ToP3(Color e) {
635   return {{{1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b,
636             -0.06533f * e.r + 1.07580f * e.g + -0.01051f * e.b,
637             0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b}}};
638 }
639 
640 // All of these conversions are derived from the respective input YUV->RGB conversion followed by
641 // the RGB->YUV for the receiving encoding. They are consistent with the RGB<->YUV functions in
642 // gainmapmath.cpp, given that we use BT.709 encoding for sRGB and BT.601 encoding for Display-P3,
643 // to match DataSpace.
644 
645 // Yuv Bt709 -> Yuv Bt601
646 // Y' = (1.0 * Y) + ( 0.101579 * U) + ( 0.196076 * V)
647 // U' = (0.0 * Y) + ( 0.989854 * U) + (-0.110653 * V)
648 // V' = (0.0 * Y) + (-0.072453 * U) + ( 0.983398 * V)
649 const std::array<float, 9> kYuvBt709ToBt601 = {
650     1.0f, 0.101579f, 0.196076f, 0.0f, 0.989854f, -0.110653f, 0.0f, -0.072453f, 0.983398f};
651 
652 // Yuv Bt709 -> Yuv Bt2100
653 // Y' = (1.0 * Y) + (-0.016969 * U) + ( 0.096312 * V)
654 // U' = (0.0 * Y) + ( 0.995306 * U) + (-0.051192 * V)
655 // V' = (0.0 * Y) + ( 0.011507 * U) + ( 1.002637 * V)
656 const std::array<float, 9> kYuvBt709ToBt2100 = {
657     1.0f, -0.016969f, 0.096312f, 0.0f, 0.995306f, -0.051192f, 0.0f, 0.011507f, 1.002637f};
658 
659 // Yuv Bt601 -> Yuv Bt709
660 // Y' = (1.0 * Y) + (-0.118188 * U) + (-0.212685 * V)
661 // U' = (0.0 * Y) + ( 1.018640 * U) + ( 0.114618 * V)
662 // V' = (0.0 * Y) + ( 0.075049 * U) + ( 1.025327 * V)
663 const std::array<float, 9> kYuvBt601ToBt709 = {
664     1.0f, -0.118188f, -0.212685f, 0.0f, 1.018640f, 0.114618f, 0.0f, 0.075049f, 1.025327f};
665 
666 // Yuv Bt601 -> Yuv Bt2100
667 // Y' = (1.0 * Y) + (-0.128245 * U) + (-0.115879 * V)
668 // U' = (0.0 * Y) + ( 1.010016 * U) + ( 0.061592 * V)
669 // V' = (0.0 * Y) + ( 0.086969 * U) + ( 1.029350 * V)
670 const std::array<float, 9> kYuvBt601ToBt2100 = {
671     1.0f, -0.128245f, -0.115879, 0.0f, 1.010016f, 0.061592f, 0.0f, 0.086969f, 1.029350f};
672 
673 // Yuv Bt2100 -> Yuv Bt709
674 // Y' = (1.0 * Y) + ( 0.018149 * U) + (-0.095132 * V)
675 // U' = (0.0 * Y) + ( 1.004123 * U) + ( 0.051267 * V)
676 // V' = (0.0 * Y) + (-0.011524 * U) + ( 0.996782 * V)
677 const std::array<float, 9> kYuvBt2100ToBt709 = {
678     1.0f, 0.018149f, -0.095132f, 0.0f, 1.004123f, 0.051267f, 0.0f, -0.011524f, 0.996782f};
679 
680 // Yuv Bt2100 -> Yuv Bt601
681 // Y' = (1.0 * Y) + ( 0.117887 * U) + ( 0.105521 * V)
682 // U' = (0.0 * Y) + ( 0.995211 * U) + (-0.059549 * V)
683 // V' = (0.0 * Y) + (-0.084085 * U) + ( 0.976518 * V)
684 const std::array<float, 9> kYuvBt2100ToBt601 = {
685     1.0f, 0.117887f, 0.105521f, 0.0f, 0.995211f, -0.059549f, 0.0f, -0.084085f, 0.976518f};
686 
yuvColorGamutConversion(Color e_gamma,const std::array<float,9> & coeffs)687 Color yuvColorGamutConversion(Color e_gamma, const std::array<float, 9>& coeffs) {
688   const float y = e_gamma.y * std::get<0>(coeffs) + e_gamma.u * std::get<1>(coeffs) +
689                   e_gamma.v * std::get<2>(coeffs);
690   const float u = e_gamma.y * std::get<3>(coeffs) + e_gamma.u * std::get<4>(coeffs) +
691                   e_gamma.v * std::get<5>(coeffs);
692   const float v = e_gamma.y * std::get<6>(coeffs) + e_gamma.u * std::get<7>(coeffs) +
693                   e_gamma.v * std::get<8>(coeffs);
694   return {{{y, u, v}}};
695 }
696 
transformYuv420(uhdr_raw_image_t * image,const std::array<float,9> & coeffs)697 void transformYuv420(uhdr_raw_image_t* image, const std::array<float, 9>& coeffs) {
698   for (size_t y = 0; y < image->h / 2; ++y) {
699     for (size_t x = 0; x < image->w / 2; ++x) {
700       Color yuv1 = getYuv420Pixel(image, x * 2, y * 2);
701       Color yuv2 = getYuv420Pixel(image, x * 2 + 1, y * 2);
702       Color yuv3 = getYuv420Pixel(image, x * 2, y * 2 + 1);
703       Color yuv4 = getYuv420Pixel(image, x * 2 + 1, y * 2 + 1);
704 
705       yuv1 = yuvColorGamutConversion(yuv1, coeffs);
706       yuv2 = yuvColorGamutConversion(yuv2, coeffs);
707       yuv3 = yuvColorGamutConversion(yuv3, coeffs);
708       yuv4 = yuvColorGamutConversion(yuv4, coeffs);
709 
710       Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f;
711 
712       size_t pixel_y1_idx = x * 2 + y * 2 * image->stride[UHDR_PLANE_Y];
713       size_t pixel_y2_idx = (x * 2 + 1) + y * 2 * image->stride[UHDR_PLANE_Y];
714       size_t pixel_y3_idx = x * 2 + (y * 2 + 1) * image->stride[UHDR_PLANE_Y];
715       size_t pixel_y4_idx = (x * 2 + 1) + (y * 2 + 1) * image->stride[UHDR_PLANE_Y];
716 
717       uint8_t& y1_uint = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_Y])[pixel_y1_idx];
718       uint8_t& y2_uint = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_Y])[pixel_y2_idx];
719       uint8_t& y3_uint = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_Y])[pixel_y3_idx];
720       uint8_t& y4_uint = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_Y])[pixel_y4_idx];
721 
722       size_t pixel_u_idx = x + y * image->stride[UHDR_PLANE_U];
723       uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_U])[pixel_u_idx];
724 
725       size_t pixel_v_idx = x + y * image->stride[UHDR_PLANE_V];
726       uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_V])[pixel_v_idx];
727 
728       y1_uint = static_cast<uint8_t>(CLIP3((yuv1.y * 255.0f + 0.5f), 0, 255));
729       y2_uint = static_cast<uint8_t>(CLIP3((yuv2.y * 255.0f + 0.5f), 0, 255));
730       y3_uint = static_cast<uint8_t>(CLIP3((yuv3.y * 255.0f + 0.5f), 0, 255));
731       y4_uint = static_cast<uint8_t>(CLIP3((yuv4.y * 255.0f + 0.5f), 0, 255));
732 
733       u_uint = static_cast<uint8_t>(CLIP3((new_uv.u * 255.0f + 128.0f + 0.5f), 0, 255));
734       v_uint = static_cast<uint8_t>(CLIP3((new_uv.v * 255.0f + 128.0f + 0.5f), 0, 255));
735     }
736   }
737 }
738 
transformYuv444(uhdr_raw_image_t * image,const std::array<float,9> & coeffs)739 void transformYuv444(uhdr_raw_image_t* image, const std::array<float, 9>& coeffs) {
740   for (size_t y = 0; y < image->h; ++y) {
741     for (size_t x = 0; x < image->w; ++x) {
742       Color yuv = getYuv444Pixel(image, x, y);
743       yuv = yuvColorGamutConversion(yuv, coeffs);
744 
745       size_t pixel_y_idx = x + y * image->stride[UHDR_PLANE_Y];
746       uint8_t& y1_uint = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_Y])[pixel_y_idx];
747 
748       size_t pixel_u_idx = x + y * image->stride[UHDR_PLANE_U];
749       uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_U])[pixel_u_idx];
750 
751       size_t pixel_v_idx = x + y * image->stride[UHDR_PLANE_V];
752       uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->planes[UHDR_PLANE_V])[pixel_v_idx];
753 
754       y1_uint = static_cast<uint8_t>(CLIP3((yuv.y * 255.0f + 0.5f), 0, 255));
755       u_uint = static_cast<uint8_t>(CLIP3((yuv.u * 255.0f + 128.0f + 0.5f), 0, 255));
756       v_uint = static_cast<uint8_t>(CLIP3((yuv.v * 255.0f + 128.0f + 0.5f), 0, 255));
757     }
758   }
759 }
760 
761 ////////////////////////////////////////////////////////////////////////////////
762 // Gain map calculations
763 
encodeGain(float y_sdr,float y_hdr,uhdr_gainmap_metadata_ext_t * metadata)764 uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata) {
765   return encodeGain(y_sdr, y_hdr, metadata, log2(metadata->min_content_boost),
766                     log2(metadata->max_content_boost));
767 }
768 
encodeGain(float y_sdr,float y_hdr,uhdr_gainmap_metadata_ext_t * metadata,float log2MinContentBoost,float log2MaxContentBoost)769 uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata,
770                    float log2MinContentBoost, float log2MaxContentBoost) {
771   float gain = 1.0f;
772   if (y_sdr > 0.0f) {
773     gain = y_hdr / y_sdr;
774   }
775 
776   if (gain < metadata->min_content_boost) gain = metadata->min_content_boost;
777   if (gain > metadata->max_content_boost) gain = metadata->max_content_boost;
778   float gain_normalized =
779       (log2(gain) - log2MinContentBoost) / (log2MaxContentBoost - log2MinContentBoost);
780   float gain_normalized_gamma = powf(gain_normalized, metadata->gamma);
781   return static_cast<uint8_t>(gain_normalized_gamma * 255.0f);
782 }
783 
computeGain(float sdr,float hdr)784 float computeGain(float sdr, float hdr) {
785   if (sdr == 0.0f) return 0.0f;  // for sdr black return no gain
786   if (hdr == 0.0f) {  // for hdr black, return a gain large enough to attenuate the sdr pel
787     float offset = (1.0f / 64);
788     return log2(offset / (offset + sdr));
789   }
790   return log2(hdr / sdr);
791 }
792 
affineMapGain(float gainlog2,float mingainlog2,float maxgainlog2,float gamma)793 uint8_t affineMapGain(float gainlog2, float mingainlog2, float maxgainlog2, float gamma) {
794   float mappedVal = (gainlog2 - mingainlog2) / (maxgainlog2 - mingainlog2);
795   if (gamma != 1.0f) mappedVal = pow(mappedVal, gamma);
796   mappedVal *= 255;
797   return CLIP3(mappedVal + 0.5f, 0, 255);
798 }
799 
applyGain(Color e,float gain,uhdr_gainmap_metadata_ext_t * metadata)800 Color applyGain(Color e, float gain, uhdr_gainmap_metadata_ext_t* metadata) {
801   if (metadata->gamma != 1.0f) gain = pow(gain, 1.0f / metadata->gamma);
802   float logBoost =
803       log2(metadata->min_content_boost) * (1.0f - gain) + log2(metadata->max_content_boost) * gain;
804   float gainFactor = exp2(logBoost);
805   return ((e + metadata->offset_sdr) * gainFactor) - metadata->offset_hdr;
806 }
807 
applyGain(Color e,float gain,uhdr_gainmap_metadata_ext_t * metadata,float gainmapWeight)808 Color applyGain(Color e, float gain, uhdr_gainmap_metadata_ext_t* metadata, float gainmapWeight) {
809   if (metadata->gamma != 1.0f) gain = pow(gain, 1.0f / metadata->gamma);
810   float logBoost =
811       log2(metadata->min_content_boost) * (1.0f - gain) + log2(metadata->max_content_boost) * gain;
812   float gainFactor = exp2(logBoost * gainmapWeight);
813   return ((e + metadata->offset_sdr) * gainFactor) - metadata->offset_hdr;
814 }
815 
applyGainLUT(Color e,float gain,GainLUT & gainLUT,uhdr_gainmap_metadata_ext_t * metadata)816 Color applyGainLUT(Color e, float gain, GainLUT& gainLUT, uhdr_gainmap_metadata_ext_t* metadata) {
817   float gainFactor = gainLUT.getGainFactor(gain);
818   return ((e + metadata->offset_sdr) * gainFactor) - metadata->offset_hdr;
819 }
820 
applyGain(Color e,Color gain,uhdr_gainmap_metadata_ext_t * metadata)821 Color applyGain(Color e, Color gain, uhdr_gainmap_metadata_ext_t* metadata) {
822   if (metadata->gamma != 1.0f) {
823     gain.r = pow(gain.r, 1.0f / metadata->gamma);
824     gain.g = pow(gain.g, 1.0f / metadata->gamma);
825     gain.b = pow(gain.b, 1.0f / metadata->gamma);
826   }
827   float logBoostR = log2(metadata->min_content_boost) * (1.0f - gain.r) +
828                     log2(metadata->max_content_boost) * gain.r;
829   float logBoostG = log2(metadata->min_content_boost) * (1.0f - gain.g) +
830                     log2(metadata->max_content_boost) * gain.g;
831   float logBoostB = log2(metadata->min_content_boost) * (1.0f - gain.b) +
832                     log2(metadata->max_content_boost) * gain.b;
833   float gainFactorR = exp2(logBoostR);
834   float gainFactorG = exp2(logBoostG);
835   float gainFactorB = exp2(logBoostB);
836   return {{{((e.r + metadata->offset_sdr) * gainFactorR) - metadata->offset_hdr,
837             ((e.g + metadata->offset_sdr) * gainFactorG) - metadata->offset_hdr,
838             ((e.b + metadata->offset_sdr) * gainFactorB) - metadata->offset_hdr}}};
839 }
840 
applyGain(Color e,Color gain,uhdr_gainmap_metadata_ext_t * metadata,float gainmapWeight)841 Color applyGain(Color e, Color gain, uhdr_gainmap_metadata_ext_t* metadata, float gainmapWeight) {
842   if (metadata->gamma != 1.0f) {
843     gain.r = pow(gain.r, 1.0f / metadata->gamma);
844     gain.g = pow(gain.g, 1.0f / metadata->gamma);
845     gain.b = pow(gain.b, 1.0f / metadata->gamma);
846   }
847   float logBoostR = log2(metadata->min_content_boost) * (1.0f - gain.r) +
848                     log2(metadata->max_content_boost) * gain.r;
849   float logBoostG = log2(metadata->min_content_boost) * (1.0f - gain.g) +
850                     log2(metadata->max_content_boost) * gain.g;
851   float logBoostB = log2(metadata->min_content_boost) * (1.0f - gain.b) +
852                     log2(metadata->max_content_boost) * gain.b;
853   float gainFactorR = exp2(logBoostR * gainmapWeight);
854   float gainFactorG = exp2(logBoostG * gainmapWeight);
855   float gainFactorB = exp2(logBoostB * gainmapWeight);
856   return {{{((e.r + metadata->offset_sdr) * gainFactorR) - metadata->offset_hdr,
857             ((e.g + metadata->offset_sdr) * gainFactorG) - metadata->offset_hdr,
858             ((e.b + metadata->offset_sdr) * gainFactorB) - metadata->offset_hdr}}};
859 }
860 
applyGainLUT(Color e,Color gain,GainLUT & gainLUT,uhdr_gainmap_metadata_ext_t * metadata)861 Color applyGainLUT(Color e, Color gain, GainLUT& gainLUT, uhdr_gainmap_metadata_ext_t* metadata) {
862   float gainFactorR = gainLUT.getGainFactor(gain.r);
863   float gainFactorG = gainLUT.getGainFactor(gain.g);
864   float gainFactorB = gainLUT.getGainFactor(gain.b);
865   return {{{((e.r + metadata->offset_sdr) * gainFactorR) - metadata->offset_hdr,
866             ((e.g + metadata->offset_sdr) * gainFactorG) - metadata->offset_hdr,
867             ((e.b + metadata->offset_sdr) * gainFactorB) - metadata->offset_hdr}}};
868 }
869 
870 // TODO: do we need something more clever for filtering either the map or images
871 // to generate the map?
872 
clamp(const size_t & val,const size_t & low,const size_t & high)873 static size_t clamp(const size_t& val, const size_t& low, const size_t& high) {
874   return val < low ? low : (high < val ? high : val);
875 }
876 
mapUintToFloat(uint8_t map_uint)877 static float mapUintToFloat(uint8_t map_uint) { return static_cast<float>(map_uint) / 255.0f; }
878 
pythDistance(float x_diff,float y_diff)879 static float pythDistance(float x_diff, float y_diff) {
880   return sqrt(pow(x_diff, 2.0f) + pow(y_diff, 2.0f));
881 }
882 
883 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
sampleMap(uhdr_raw_image_t * map,float map_scale_factor,size_t x,size_t y)884 float sampleMap(uhdr_raw_image_t* map, float map_scale_factor, size_t x, size_t y) {
885   float x_map = static_cast<float>(x) / map_scale_factor;
886   float y_map = static_cast<float>(y) / map_scale_factor;
887 
888   size_t x_lower = static_cast<size_t>(floor(x_map));
889   size_t x_upper = x_lower + 1;
890   size_t y_lower = static_cast<size_t>(floor(y_map));
891   size_t y_upper = y_lower + 1;
892 
893   x_lower = clamp(x_lower, 0, map->w - 1);
894   x_upper = clamp(x_upper, 0, map->w - 1);
895   y_lower = clamp(y_lower, 0, map->h - 1);
896   y_upper = clamp(y_upper, 0, map->h - 1);
897 
898   // Use Shepard's method for inverse distance weighting. For more information:
899   // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
900   uint8_t* data = reinterpret_cast<uint8_t*>(map->planes[UHDR_PLANE_Y]);
901   size_t stride = map->stride[UHDR_PLANE_Y];
902 
903   float e1 = mapUintToFloat(data[x_lower + y_lower * stride]);
904   float e1_dist =
905       pythDistance(x_map - static_cast<float>(x_lower), y_map - static_cast<float>(y_lower));
906   if (e1_dist == 0.0f) return e1;
907 
908   float e2 = mapUintToFloat(data[x_lower + y_upper * stride]);
909   float e2_dist =
910       pythDistance(x_map - static_cast<float>(x_lower), y_map - static_cast<float>(y_upper));
911   if (e2_dist == 0.0f) return e2;
912 
913   float e3 = mapUintToFloat(data[x_upper + y_lower * stride]);
914   float e3_dist =
915       pythDistance(x_map - static_cast<float>(x_upper), y_map - static_cast<float>(y_lower));
916   if (e3_dist == 0.0f) return e3;
917 
918   float e4 = mapUintToFloat(data[x_upper + y_upper * stride]);
919   float e4_dist =
920       pythDistance(x_map - static_cast<float>(x_upper), y_map - static_cast<float>(y_upper));
921   if (e4_dist == 0.0f) return e2;
922 
923   float e1_weight = 1.0f / e1_dist;
924   float e2_weight = 1.0f / e2_dist;
925   float e3_weight = 1.0f / e3_dist;
926   float e4_weight = 1.0f / e4_dist;
927   float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
928 
929   return e1 * (e1_weight / total_weight) + e2 * (e2_weight / total_weight) +
930          e3 * (e3_weight / total_weight) + e4 * (e4_weight / total_weight);
931 }
932 
sampleMap(uhdr_raw_image_t * map,size_t map_scale_factor,size_t x,size_t y,ShepardsIDW & weightTables)933 float sampleMap(uhdr_raw_image_t* map, size_t map_scale_factor, size_t x, size_t y,
934                 ShepardsIDW& weightTables) {
935   // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
936   // following by computing log2(map_scale_factor) once and then using >> log2(map_scale_factor)
937   size_t x_lower = x / map_scale_factor;
938   size_t x_upper = x_lower + 1;
939   size_t y_lower = y / map_scale_factor;
940   size_t y_upper = y_lower + 1;
941 
942   x_lower = std::min(x_lower, (size_t)map->w - 1);
943   x_upper = std::min(x_upper, (size_t)map->w - 1);
944   y_lower = std::min(y_lower, (size_t)map->h - 1);
945   y_upper = std::min(y_upper, (size_t)map->h - 1);
946 
947   uint8_t* data = reinterpret_cast<uint8_t*>(map->planes[UHDR_PLANE_Y]);
948   size_t stride = map->stride[UHDR_PLANE_Y];
949   float e1 = mapUintToFloat(data[x_lower + y_lower * stride]);
950   float e2 = mapUintToFloat(data[x_lower + y_upper * stride]);
951   float e3 = mapUintToFloat(data[x_upper + y_lower * stride]);
952   float e4 = mapUintToFloat(data[x_upper + y_upper * stride]);
953 
954   // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
955   // following by using & (map_scale_factor - 1)
956   size_t offset_x = x % map_scale_factor;
957   size_t offset_y = y % map_scale_factor;
958 
959   float* weights = weightTables.mWeights;
960   if (x_lower == x_upper && y_lower == y_upper)
961     weights = weightTables.mWeightsC;
962   else if (x_lower == x_upper)
963     weights = weightTables.mWeightsNR;
964   else if (y_lower == y_upper)
965     weights = weightTables.mWeightsNB;
966   weights += offset_y * map_scale_factor * 4 + offset_x * 4;
967 
968   return e1 * weights[0] + e2 * weights[1] + e3 * weights[2] + e4 * weights[3];
969 }
970 
sampleMap3Channel(uhdr_raw_image_t * map,float map_scale_factor,size_t x,size_t y,bool has_alpha)971 Color sampleMap3Channel(uhdr_raw_image_t* map, float map_scale_factor, size_t x, size_t y,
972                         bool has_alpha) {
973   float x_map = static_cast<float>(x) / map_scale_factor;
974   float y_map = static_cast<float>(y) / map_scale_factor;
975 
976   size_t x_lower = static_cast<size_t>(floor(x_map));
977   size_t x_upper = x_lower + 1;
978   size_t y_lower = static_cast<size_t>(floor(y_map));
979   size_t y_upper = y_lower + 1;
980 
981   x_lower = std::min(x_lower, (size_t)map->w - 1);
982   x_upper = std::min(x_upper, (size_t)map->w - 1);
983   y_lower = std::min(y_lower, (size_t)map->h - 1);
984   y_upper = std::min(y_upper, (size_t)map->h - 1);
985 
986   int factor = has_alpha ? 4 : 3;
987 
988   uint8_t* data = reinterpret_cast<uint8_t*>(map->planes[UHDR_PLANE_PACKED]);
989   size_t stride = map->stride[UHDR_PLANE_PACKED];
990 
991   float r1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor]);
992   float r2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor]);
993   float r3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor]);
994   float r4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor]);
995 
996   float g1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor + 1]);
997   float g2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor + 1]);
998   float g3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor + 1]);
999   float g4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor + 1]);
1000 
1001   float b1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor + 2]);
1002   float b2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor + 2]);
1003   float b3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor + 2]);
1004   float b4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor + 2]);
1005 
1006   Color rgb1 = {{{r1, g1, b1}}};
1007   Color rgb2 = {{{r2, g2, b2}}};
1008   Color rgb3 = {{{r3, g3, b3}}};
1009   Color rgb4 = {{{r4, g4, b4}}};
1010 
1011   // Use Shepard's method for inverse distance weighting. For more information:
1012   // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
1013   float e1_dist =
1014       pythDistance(x_map - static_cast<float>(x_lower), y_map - static_cast<float>(y_lower));
1015   if (e1_dist == 0.0f) return rgb1;
1016 
1017   float e2_dist =
1018       pythDistance(x_map - static_cast<float>(x_lower), y_map - static_cast<float>(y_upper));
1019   if (e2_dist == 0.0f) return rgb2;
1020 
1021   float e3_dist =
1022       pythDistance(x_map - static_cast<float>(x_upper), y_map - static_cast<float>(y_lower));
1023   if (e3_dist == 0.0f) return rgb3;
1024 
1025   float e4_dist =
1026       pythDistance(x_map - static_cast<float>(x_upper), y_map - static_cast<float>(y_upper));
1027   if (e4_dist == 0.0f) return rgb4;
1028 
1029   float e1_weight = 1.0f / e1_dist;
1030   float e2_weight = 1.0f / e2_dist;
1031   float e3_weight = 1.0f / e3_dist;
1032   float e4_weight = 1.0f / e4_dist;
1033   float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
1034 
1035   return rgb1 * (e1_weight / total_weight) + rgb2 * (e2_weight / total_weight) +
1036          rgb3 * (e3_weight / total_weight) + rgb4 * (e4_weight / total_weight);
1037 }
1038 
sampleMap3Channel(uhdr_raw_image_t * map,size_t map_scale_factor,size_t x,size_t y,ShepardsIDW & weightTables,bool has_alpha)1039 Color sampleMap3Channel(uhdr_raw_image_t* map, size_t map_scale_factor, size_t x, size_t y,
1040                         ShepardsIDW& weightTables, bool has_alpha) {
1041   // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
1042   // following by computing log2(map_scale_factor) once and then using >> log2(map_scale_factor)
1043   size_t x_lower = x / map_scale_factor;
1044   size_t x_upper = x_lower + 1;
1045   size_t y_lower = y / map_scale_factor;
1046   size_t y_upper = y_lower + 1;
1047 
1048   x_lower = std::min(x_lower, (size_t)map->w - 1);
1049   x_upper = std::min(x_upper, (size_t)map->w - 1);
1050   y_lower = std::min(y_lower, (size_t)map->h - 1);
1051   y_upper = std::min(y_upper, (size_t)map->h - 1);
1052 
1053   int factor = has_alpha ? 4 : 3;
1054 
1055   uint8_t* data = reinterpret_cast<uint8_t*>(map->planes[UHDR_PLANE_PACKED]);
1056   size_t stride = map->stride[UHDR_PLANE_PACKED];
1057 
1058   float r1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor]);
1059   float r2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor]);
1060   float r3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor]);
1061   float r4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor]);
1062 
1063   float g1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor + 1]);
1064   float g2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor + 1]);
1065   float g3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor + 1]);
1066   float g4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor + 1]);
1067 
1068   float b1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor + 2]);
1069   float b2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor + 2]);
1070   float b3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor + 2]);
1071   float b4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor + 2]);
1072 
1073   Color rgb1 = {{{r1, g1, b1}}};
1074   Color rgb2 = {{{r2, g2, b2}}};
1075   Color rgb3 = {{{r3, g3, b3}}};
1076   Color rgb4 = {{{r4, g4, b4}}};
1077 
1078   // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
1079   // following by using & (map_scale_factor - 1)
1080   size_t offset_x = x % map_scale_factor;
1081   size_t offset_y = y % map_scale_factor;
1082 
1083   float* weights = weightTables.mWeights;
1084   if (x_lower == x_upper && y_lower == y_upper)
1085     weights = weightTables.mWeightsC;
1086   else if (x_lower == x_upper)
1087     weights = weightTables.mWeightsNR;
1088   else if (y_lower == y_upper)
1089     weights = weightTables.mWeightsNB;
1090   weights += offset_y * map_scale_factor * 4 + offset_x * 4;
1091 
1092   return rgb1 * weights[0] + rgb2 * weights[1] + rgb3 * weights[2] + rgb4 * weights[3];
1093 }
1094 
1095 ////////////////////////////////////////////////////////////////////////////////
1096 // function selectors
1097 
1098 // TODO: confirm we always want to convert like this before calculating
1099 // luminance.
getGamutConversionFn(uhdr_color_gamut_t dst_gamut,uhdr_color_gamut_t src_gamut)1100 ColorTransformFn getGamutConversionFn(uhdr_color_gamut_t dst_gamut, uhdr_color_gamut_t src_gamut) {
1101   switch (dst_gamut) {
1102     case UHDR_CG_BT_709:
1103       switch (src_gamut) {
1104         case UHDR_CG_BT_709:
1105           return identityConversion;
1106         case UHDR_CG_DISPLAY_P3:
1107           return p3ToBt709;
1108         case UHDR_CG_BT_2100:
1109           return bt2100ToBt709;
1110         case UHDR_CG_UNSPECIFIED:
1111           return nullptr;
1112       }
1113       break;
1114     case UHDR_CG_DISPLAY_P3:
1115       switch (src_gamut) {
1116         case UHDR_CG_BT_709:
1117           return bt709ToP3;
1118         case UHDR_CG_DISPLAY_P3:
1119           return identityConversion;
1120         case UHDR_CG_BT_2100:
1121           return bt2100ToP3;
1122         case UHDR_CG_UNSPECIFIED:
1123           return nullptr;
1124       }
1125       break;
1126     case UHDR_CG_BT_2100:
1127       switch (src_gamut) {
1128         case UHDR_CG_BT_709:
1129           return bt709ToBt2100;
1130         case UHDR_CG_DISPLAY_P3:
1131           return p3ToBt2100;
1132         case UHDR_CG_BT_2100:
1133           return identityConversion;
1134         case UHDR_CG_UNSPECIFIED:
1135           return nullptr;
1136       }
1137       break;
1138     case UHDR_CG_UNSPECIFIED:
1139       return nullptr;
1140   }
1141   return nullptr;
1142 }
1143 
getYuvToRgbFn(uhdr_color_gamut_t gamut)1144 ColorTransformFn getYuvToRgbFn(uhdr_color_gamut_t gamut) {
1145   switch (gamut) {
1146     case UHDR_CG_BT_709:
1147       return srgbYuvToRgb;
1148     case UHDR_CG_DISPLAY_P3:
1149       return p3YuvToRgb;
1150     case UHDR_CG_BT_2100:
1151       return bt2100YuvToRgb;
1152     case UHDR_CG_UNSPECIFIED:
1153       return nullptr;
1154   }
1155   return nullptr;
1156 }
1157 
getLuminanceFn(uhdr_color_gamut_t gamut)1158 LuminanceFn getLuminanceFn(uhdr_color_gamut_t gamut) {
1159   switch (gamut) {
1160     case UHDR_CG_BT_709:
1161       return srgbLuminance;
1162     case UHDR_CG_DISPLAY_P3:
1163       return p3Luminance;
1164     case UHDR_CG_BT_2100:
1165       return bt2100Luminance;
1166     case UHDR_CG_UNSPECIFIED:
1167       return nullptr;
1168   }
1169   return nullptr;
1170 }
1171 
getInverseOetfFn(uhdr_color_transfer_t transfer)1172 ColorTransformFn getInverseOetfFn(uhdr_color_transfer_t transfer) {
1173   switch (transfer) {
1174     case UHDR_CT_LINEAR:
1175       return identityConversion;
1176     case UHDR_CT_HLG:
1177 #if USE_HLG_INVOETF_LUT
1178       return hlgInvOetfLUT;
1179 #else
1180       return hlgInvOetf;
1181 #endif
1182     case UHDR_CT_PQ:
1183 #if USE_PQ_INVOETF_LUT
1184       return pqInvOetfLUT;
1185 #else
1186       return pqInvOetf;
1187 #endif
1188     case UHDR_CT_SRGB:
1189 #if USE_SRGB_INVOETF_LUT
1190       return srgbInvOetfLUT;
1191 #else
1192       return srgbInvOetf;
1193 #endif
1194     case UHDR_CT_UNSPECIFIED:
1195       return nullptr;
1196   }
1197   return nullptr;
1198 }
1199 
getOotfFn(uhdr_color_transfer_t transfer)1200 SceneToDisplayLuminanceFn getOotfFn(uhdr_color_transfer_t transfer) {
1201   switch (transfer) {
1202     case UHDR_CT_LINEAR:
1203       return identityOotf;
1204     case UHDR_CT_HLG:
1205       return hlgOotfApprox;
1206     case UHDR_CT_PQ:
1207       return identityOotf;
1208     case UHDR_CT_SRGB:
1209       return identityOotf;
1210     case UHDR_CT_UNSPECIFIED:
1211       return nullptr;
1212   }
1213   return nullptr;
1214 }
1215 
getPixelFn(uhdr_img_fmt_t format)1216 GetPixelFn getPixelFn(uhdr_img_fmt_t format) {
1217   switch (format) {
1218     case UHDR_IMG_FMT_24bppYCbCr444:
1219       return getYuv444Pixel;
1220     case UHDR_IMG_FMT_16bppYCbCr422:
1221       return getYuv422Pixel;
1222     case UHDR_IMG_FMT_12bppYCbCr420:
1223       return getYuv420Pixel;
1224     case UHDR_IMG_FMT_24bppYCbCrP010:
1225       return getP010Pixel;
1226     case UHDR_IMG_FMT_30bppYCbCr444:
1227       return getYuv444Pixel10bit;
1228     case UHDR_IMG_FMT_32bppRGBA8888:
1229       return getRgba8888Pixel;
1230     case UHDR_IMG_FMT_32bppRGBA1010102:
1231       return getRgba1010102Pixel;
1232     case UHDR_IMG_FMT_64bppRGBAHalfFloat:
1233       return getRgbaF16Pixel;
1234     case UHDR_IMG_FMT_8bppYCbCr400:
1235       return getYuv400Pixel;
1236     case UHDR_IMG_FMT_24bppRGB888:
1237       return getRgb888Pixel;
1238     default:
1239       return nullptr;
1240   }
1241   return nullptr;
1242 }
1243 
putPixelFn(uhdr_img_fmt_t format)1244 PutPixelFn putPixelFn(uhdr_img_fmt_t format) {
1245   switch (format) {
1246     case UHDR_IMG_FMT_24bppYCbCr444:
1247       return putYuv444Pixel;
1248     case UHDR_IMG_FMT_32bppRGBA8888:
1249       return putRgba8888Pixel;
1250     case UHDR_IMG_FMT_8bppYCbCr400:
1251       return putYuv400Pixel;
1252     case UHDR_IMG_FMT_24bppRGB888:
1253       return putRgb888Pixel;
1254     default:
1255       return nullptr;
1256   }
1257   return nullptr;
1258 }
1259 
getSamplePixelFn(uhdr_img_fmt_t format)1260 SamplePixelFn getSamplePixelFn(uhdr_img_fmt_t format) {
1261   switch (format) {
1262     case UHDR_IMG_FMT_24bppYCbCr444:
1263       return sampleYuv444;
1264     case UHDR_IMG_FMT_16bppYCbCr422:
1265       return sampleYuv422;
1266     case UHDR_IMG_FMT_12bppYCbCr420:
1267       return sampleYuv420;
1268     case UHDR_IMG_FMT_24bppYCbCrP010:
1269       return sampleP010;
1270     case UHDR_IMG_FMT_30bppYCbCr444:
1271       return sampleYuv44410bit;
1272     case UHDR_IMG_FMT_32bppRGBA8888:
1273       return sampleRgba8888;
1274     case UHDR_IMG_FMT_32bppRGBA1010102:
1275       return sampleRgba1010102;
1276     case UHDR_IMG_FMT_64bppRGBAHalfFloat:
1277       return sampleRgbaF16;
1278     default:
1279       return nullptr;
1280   }
1281   return nullptr;
1282 }
1283 
1284 ////////////////////////////////////////////////////////////////////////////////
1285 // common utils
1286 
isPixelFormatRgb(uhdr_img_fmt_t format)1287 bool isPixelFormatRgb(uhdr_img_fmt_t format) {
1288   return format == UHDR_IMG_FMT_64bppRGBAHalfFloat || format == UHDR_IMG_FMT_32bppRGBA8888 ||
1289          format == UHDR_IMG_FMT_32bppRGBA1010102;
1290 }
1291 
colorToRgba1010102(Color e_gamma)1292 uint32_t colorToRgba1010102(Color e_gamma) {
1293   uint32_t r = CLIP3((e_gamma.r * 1023 + 0.5f), 0.0f, 1023.0f);
1294   uint32_t g = CLIP3((e_gamma.g * 1023 + 0.5f), 0.0f, 1023.0f);
1295   uint32_t b = CLIP3((e_gamma.b * 1023 + 0.5f), 0.0f, 1023.0f);
1296   return (r | (g << 10) | (b << 20) | (0x3 << 30));  // Set alpha to 1.0
1297 }
1298 
colorToRgbaF16(Color e_gamma)1299 uint64_t colorToRgbaF16(Color e_gamma) {
1300   return (uint64_t)floatToHalf(e_gamma.r) | (((uint64_t)floatToHalf(e_gamma.g)) << 16) |
1301          (((uint64_t)floatToHalf(e_gamma.b)) << 32) | (((uint64_t)floatToHalf(1.0f)) << 48);
1302 }
1303 
convert_raw_input_to_ycbcr(uhdr_raw_image_t * src,bool chroma_sampling_enabled)1304 std::unique_ptr<uhdr_raw_image_ext_t> convert_raw_input_to_ycbcr(uhdr_raw_image_t* src,
1305                                                                  bool chroma_sampling_enabled) {
1306   std::unique_ptr<uhdr_raw_image_ext_t> dst = nullptr;
1307   Color (*rgbToyuv)(Color) = nullptr;
1308 
1309   if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888) {
1310     if (src->cg == UHDR_CG_BT_709) {
1311       rgbToyuv = srgbRgbToYuv;
1312     } else if (src->cg == UHDR_CG_BT_2100) {
1313       rgbToyuv = bt2100RgbToYuv;
1314     } else if (src->cg == UHDR_CG_DISPLAY_P3) {
1315       rgbToyuv = p3RgbToYuv;
1316     } else {
1317       return dst;
1318     }
1319   }
1320 
1321   if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 && chroma_sampling_enabled) {
1322     dst = std::make_unique<uhdr_raw_image_ext_t>(UHDR_IMG_FMT_24bppYCbCrP010, src->cg, src->ct,
1323                                                  UHDR_CR_FULL_RANGE, src->w, src->h, 64);
1324 
1325     uint32_t* rgbData = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
1326     unsigned int srcStride = src->stride[UHDR_PLANE_PACKED];
1327 
1328     uint16_t* yData = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_Y]);
1329     uint16_t* uData = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_UV]);
1330     uint16_t* vData = uData + 1;
1331 
1332     for (size_t i = 0; i < dst->h; i += 2) {
1333       for (size_t j = 0; j < dst->w; j += 2) {
1334         Color pixel[4];
1335 
1336         pixel[0].r = float(rgbData[srcStride * i + j] & 0x3ff);
1337         pixel[0].g = float((rgbData[srcStride * i + j] >> 10) & 0x3ff);
1338         pixel[0].b = float((rgbData[srcStride * i + j] >> 20) & 0x3ff);
1339 
1340         pixel[1].r = float(rgbData[srcStride * i + j + 1] & 0x3ff);
1341         pixel[1].g = float((rgbData[srcStride * i + j + 1] >> 10) & 0x3ff);
1342         pixel[1].b = float((rgbData[srcStride * i + j + 1] >> 20) & 0x3ff);
1343 
1344         pixel[2].r = float(rgbData[srcStride * (i + 1) + j] & 0x3ff);
1345         pixel[2].g = float((rgbData[srcStride * (i + 1) + j] >> 10) & 0x3ff);
1346         pixel[2].b = float((rgbData[srcStride * (i + 1) + j] >> 20) & 0x3ff);
1347 
1348         pixel[3].r = float(rgbData[srcStride * (i + 1) + j + 1] & 0x3ff);
1349         pixel[3].g = float((rgbData[srcStride * (i + 1) + j + 1] >> 10) & 0x3ff);
1350         pixel[3].b = float((rgbData[srcStride * (i + 1) + j + 1] >> 20) & 0x3ff);
1351 
1352         for (int k = 0; k < 4; k++) {
1353           // Now we only support the RGB input being full range
1354           pixel[k] /= 1023.0f;
1355           pixel[k] = (*rgbToyuv)(pixel[k]);
1356 
1357           pixel[k].y = (pixel[k].y * 1023.0f) + 0.5f;
1358           pixel[k].y = CLIP3(pixel[k].y, 0.0f, 1023.0f);
1359         }
1360 
1361         yData[dst->stride[UHDR_PLANE_Y] * i + j] = uint16_t(pixel[0].y) << 6;
1362         yData[dst->stride[UHDR_PLANE_Y] * i + j + 1] = uint16_t(pixel[1].y) << 6;
1363         yData[dst->stride[UHDR_PLANE_Y] * (i + 1) + j] = uint16_t(pixel[2].y) << 6;
1364         yData[dst->stride[UHDR_PLANE_Y] * (i + 1) + j + 1] = uint16_t(pixel[3].y) << 6;
1365 
1366         pixel[0].u = (pixel[0].u + pixel[1].u + pixel[2].u + pixel[3].u) / 4;
1367         pixel[0].v = (pixel[0].v + pixel[1].v + pixel[2].v + pixel[3].v) / 4;
1368 
1369         pixel[0].u = (pixel[0].u * 1023.0f) + 512.0f + 0.5f;
1370         pixel[0].v = (pixel[0].v * 1023.0f) + 512.0f + 0.5f;
1371 
1372         pixel[0].u = CLIP3(pixel[0].u, 0.0f, 1023.0f);
1373         pixel[0].v = CLIP3(pixel[0].v, 0.0f, 1023.0f);
1374 
1375         uData[dst->stride[UHDR_PLANE_UV] * (i / 2) + j] = uint16_t(pixel[0].u) << 6;
1376         vData[dst->stride[UHDR_PLANE_UV] * (i / 2) + j] = uint16_t(pixel[0].v) << 6;
1377       }
1378     }
1379   } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
1380     dst = std::make_unique<uhdr_raw_image_ext_t>(UHDR_IMG_FMT_30bppYCbCr444, src->cg, src->ct,
1381                                                  UHDR_CR_FULL_RANGE, src->w, src->h, 64);
1382 
1383     uint32_t* rgbData = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
1384     unsigned int srcStride = src->stride[UHDR_PLANE_PACKED];
1385 
1386     uint16_t* yData = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_Y]);
1387     uint16_t* uData = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_U]);
1388     uint16_t* vData = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_V]);
1389 
1390     for (size_t i = 0; i < dst->h; i++) {
1391       for (size_t j = 0; j < dst->w; j++) {
1392         Color pixel;
1393 
1394         pixel.r = float(rgbData[srcStride * i + j] & 0x3ff);
1395         pixel.g = float((rgbData[srcStride * i + j] >> 10) & 0x3ff);
1396         pixel.b = float((rgbData[srcStride * i + j] >> 20) & 0x3ff);
1397 
1398         // Now we only support the RGB input being full range
1399         pixel /= 1023.0f;
1400         pixel = (*rgbToyuv)(pixel);
1401 
1402         pixel.y = (pixel.y * 1023.0f) + 0.5f;
1403         pixel.y = CLIP3(pixel.y, 0.0f, 1023.0f);
1404 
1405         yData[dst->stride[UHDR_PLANE_Y] * i + j] = uint16_t(pixel.y);
1406 
1407         pixel.u = (pixel.u * 1023.0f) + 512.0f + 0.5f;
1408         pixel.v = (pixel.v * 1023.0f) + 512.0f + 0.5f;
1409 
1410         pixel.u = CLIP3(pixel.u, 0.0f, 1023.0f);
1411         pixel.v = CLIP3(pixel.v, 0.0f, 1023.0f);
1412 
1413         uData[dst->stride[UHDR_PLANE_U] * i + j] = uint16_t(pixel.u);
1414         vData[dst->stride[UHDR_PLANE_V] * i + j] = uint16_t(pixel.v);
1415       }
1416     }
1417   } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA8888 && chroma_sampling_enabled) {
1418     dst = std::make_unique<uhdr_raw_image_ext_t>(UHDR_IMG_FMT_12bppYCbCr420, src->cg, src->ct,
1419                                                  UHDR_CR_FULL_RANGE, src->w, src->h, 64);
1420     uint32_t* rgbData = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
1421     unsigned int srcStride = src->stride[UHDR_PLANE_PACKED];
1422 
1423     uint8_t* yData = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_Y]);
1424     uint8_t* uData = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_U]);
1425     uint8_t* vData = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_V]);
1426     for (size_t i = 0; i < dst->h; i += 2) {
1427       for (size_t j = 0; j < dst->w; j += 2) {
1428         Color pixel[4];
1429 
1430         pixel[0].r = float(rgbData[srcStride * i + j] & 0xff);
1431         pixel[0].g = float((rgbData[srcStride * i + j] >> 8) & 0xff);
1432         pixel[0].b = float((rgbData[srcStride * i + j] >> 16) & 0xff);
1433 
1434         pixel[1].r = float(rgbData[srcStride * i + (j + 1)] & 0xff);
1435         pixel[1].g = float((rgbData[srcStride * i + (j + 1)] >> 8) & 0xff);
1436         pixel[1].b = float((rgbData[srcStride * i + (j + 1)] >> 16) & 0xff);
1437 
1438         pixel[2].r = float(rgbData[srcStride * (i + 1) + j] & 0xff);
1439         pixel[2].g = float((rgbData[srcStride * (i + 1) + j] >> 8) & 0xff);
1440         pixel[2].b = float((rgbData[srcStride * (i + 1) + j] >> 16) & 0xff);
1441 
1442         pixel[3].r = float(rgbData[srcStride * (i + 1) + (j + 1)] & 0xff);
1443         pixel[3].g = float((rgbData[srcStride * (i + 1) + (j + 1)] >> 8) & 0xff);
1444         pixel[3].b = float((rgbData[srcStride * (i + 1) + (j + 1)] >> 16) & 0xff);
1445 
1446         for (int k = 0; k < 4; k++) {
1447           // Now we only support the RGB input being full range
1448           pixel[k] /= 255.0f;
1449           pixel[k] = (*rgbToyuv)(pixel[k]);
1450 
1451           pixel[k].y = pixel[k].y * 255.0f + 0.5f;
1452           pixel[k].y = CLIP3(pixel[k].y, 0.0f, 255.0f);
1453         }
1454         yData[dst->stride[UHDR_PLANE_Y] * i + j] = uint8_t(pixel[0].y);
1455         yData[dst->stride[UHDR_PLANE_Y] * i + j + 1] = uint8_t(pixel[1].y);
1456         yData[dst->stride[UHDR_PLANE_Y] * (i + 1) + j] = uint8_t(pixel[2].y);
1457         yData[dst->stride[UHDR_PLANE_Y] * (i + 1) + j + 1] = uint8_t(pixel[3].y);
1458 
1459         pixel[0].u = (pixel[0].u + pixel[1].u + pixel[2].u + pixel[3].u) / 4;
1460         pixel[0].v = (pixel[0].v + pixel[1].v + pixel[2].v + pixel[3].v) / 4;
1461 
1462         pixel[0].u = pixel[0].u * 255.0f + 0.5f + 128.0f;
1463         pixel[0].v = pixel[0].v * 255.0f + 0.5f + 128.0f;
1464 
1465         pixel[0].u = CLIP3(pixel[0].u, 0.0f, 255.0f);
1466         pixel[0].v = CLIP3(pixel[0].v, 0.0f, 255.0f);
1467 
1468         uData[dst->stride[UHDR_PLANE_U] * (i / 2) + (j / 2)] = uint8_t(pixel[0].u);
1469         vData[dst->stride[UHDR_PLANE_V] * (i / 2) + (j / 2)] = uint8_t(pixel[0].v);
1470       }
1471     }
1472   } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA8888) {
1473     dst = std::make_unique<uhdr_raw_image_ext_t>(UHDR_IMG_FMT_24bppYCbCr444, src->cg, src->ct,
1474                                                  UHDR_CR_FULL_RANGE, src->w, src->h, 64);
1475     uint32_t* rgbData = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
1476     unsigned int srcStride = src->stride[UHDR_PLANE_PACKED];
1477 
1478     uint8_t* yData = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_Y]);
1479     uint8_t* uData = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_U]);
1480     uint8_t* vData = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_V]);
1481     for (size_t i = 0; i < dst->h; i++) {
1482       for (size_t j = 0; j < dst->w; j++) {
1483         Color pixel;
1484 
1485         pixel.r = float(rgbData[srcStride * i + j] & 0xff);
1486         pixel.g = float((rgbData[srcStride * i + j] >> 8) & 0xff);
1487         pixel.b = float((rgbData[srcStride * i + j] >> 16) & 0xff);
1488 
1489         // Now we only support the RGB input being full range
1490         pixel /= 255.0f;
1491         pixel = (*rgbToyuv)(pixel);
1492 
1493         pixel.y = pixel.y * 255.0f + 0.5f;
1494         pixel.y = CLIP3(pixel.y, 0.0f, 255.0f);
1495         yData[dst->stride[UHDR_PLANE_Y] * i + j] = uint8_t(pixel.y);
1496 
1497         pixel.u = pixel.u * 255.0f + 0.5f + 128.0f;
1498         pixel.v = pixel.v * 255.0f + 0.5f + 128.0f;
1499 
1500         pixel.u = CLIP3(pixel.u, 0.0f, 255.0f);
1501         pixel.v = CLIP3(pixel.v, 0.0f, 255.0f);
1502 
1503         uData[dst->stride[UHDR_PLANE_U] * i + j] = uint8_t(pixel.u);
1504         vData[dst->stride[UHDR_PLANE_V] * i + j] = uint8_t(pixel.v);
1505       }
1506     }
1507   } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420 || src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
1508     dst = std::make_unique<ultrahdr::uhdr_raw_image_ext_t>(src->fmt, src->cg, src->ct, src->range,
1509                                                            src->w, src->h, 64);
1510     auto status = copy_raw_image(src, dst.get());
1511     if (status.error_code != UHDR_CODEC_OK) return nullptr;
1512   }
1513   return dst;
1514 }
1515 
copy_raw_image(uhdr_raw_image_t * src)1516 std::unique_ptr<uhdr_raw_image_ext_t> copy_raw_image(uhdr_raw_image_t* src) {
1517   std::unique_ptr<uhdr_raw_image_ext_t> dst = std::make_unique<ultrahdr::uhdr_raw_image_ext_t>(
1518       src->fmt, src->cg, src->ct, src->range, src->w, src->h, 64);
1519   auto status = copy_raw_image(src, dst.get());
1520   if (status.error_code != UHDR_CODEC_OK) return nullptr;
1521   return dst;
1522 }
1523 
copy_raw_image(uhdr_raw_image_t * src,uhdr_raw_image_t * dst)1524 uhdr_error_info_t copy_raw_image(uhdr_raw_image_t* src, uhdr_raw_image_t* dst) {
1525   if (dst->w != src->w || dst->h != src->h) {
1526     uhdr_error_info_t status;
1527     status.error_code = UHDR_CODEC_MEM_ERROR;
1528     status.has_detail = 1;
1529     snprintf(status.detail, sizeof status.detail,
1530              "destination image dimensions %dx%d and source image dimensions %dx%d are not "
1531              "identical for copy_raw_image",
1532              dst->w, dst->h, src->w, src->h);
1533     return status;
1534   }
1535 
1536   dst->cg = src->cg;
1537   dst->ct = src->ct;
1538   dst->range = src->range;
1539   if (dst->fmt == src->fmt) {
1540     if (src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
1541       size_t bpp = 2;
1542       uint8_t* y_dst = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_Y]);
1543       uint8_t* y_src = static_cast<uint8_t*>(src->planes[UHDR_PLANE_Y]);
1544       uint8_t* uv_dst = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_UV]);
1545       uint8_t* uv_src = static_cast<uint8_t*>(src->planes[UHDR_PLANE_UV]);
1546 
1547       // copy y
1548       for (size_t i = 0; i < src->h; i++) {
1549         memcpy(y_dst, y_src, src->w * bpp);
1550         y_dst += (dst->stride[UHDR_PLANE_Y] * bpp);
1551         y_src += (src->stride[UHDR_PLANE_Y] * bpp);
1552       }
1553       // copy cbcr
1554       for (size_t i = 0; i < src->h / 2; i++) {
1555         memcpy(uv_dst, uv_src, src->w * bpp);
1556         uv_dst += (dst->stride[UHDR_PLANE_UV] * bpp);
1557         uv_src += (src->stride[UHDR_PLANE_UV] * bpp);
1558       }
1559       return g_no_error;
1560     } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
1561       uint8_t* y_dst = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_Y]);
1562       uint8_t* y_src = static_cast<uint8_t*>(src->planes[UHDR_PLANE_Y]);
1563       uint8_t* u_dst = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_U]);
1564       uint8_t* u_src = static_cast<uint8_t*>(src->planes[UHDR_PLANE_U]);
1565       uint8_t* v_dst = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_V]);
1566       uint8_t* v_src = static_cast<uint8_t*>(src->planes[UHDR_PLANE_V]);
1567 
1568       // copy y
1569       for (size_t i = 0; i < src->h; i++) {
1570         memcpy(y_dst, y_src, src->w);
1571         y_dst += dst->stride[UHDR_PLANE_Y];
1572         y_src += src->stride[UHDR_PLANE_Y];
1573       }
1574       // copy cb & cr
1575       for (size_t i = 0; i < src->h / 2; i++) {
1576         memcpy(u_dst, u_src, src->w / 2);
1577         memcpy(v_dst, v_src, src->w / 2);
1578         u_dst += dst->stride[UHDR_PLANE_U];
1579         v_dst += dst->stride[UHDR_PLANE_V];
1580         u_src += src->stride[UHDR_PLANE_U];
1581         v_src += src->stride[UHDR_PLANE_V];
1582       }
1583       return g_no_error;
1584     } else if (src->fmt == UHDR_IMG_FMT_8bppYCbCr400 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
1585                src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ||
1586                src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_24bppRGB888) {
1587       uint8_t* plane_dst = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_PACKED]);
1588       uint8_t* plane_src = static_cast<uint8_t*>(src->planes[UHDR_PLANE_PACKED]);
1589       size_t bpp = 1;
1590 
1591       if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888)
1592         bpp = 4;
1593       else if (src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat)
1594         bpp = 8;
1595       else if (src->fmt == UHDR_IMG_FMT_24bppRGB888)
1596         bpp = 3;
1597       for (size_t i = 0; i < src->h; i++) {
1598         memcpy(plane_dst, plane_src, src->w * bpp);
1599         plane_dst += (bpp * dst->stride[UHDR_PLANE_PACKED]);
1600         plane_src += (bpp * src->stride[UHDR_PLANE_PACKED]);
1601       }
1602       return g_no_error;
1603     }
1604   } else {
1605     if (src->fmt == UHDR_IMG_FMT_24bppRGB888 && dst->fmt == UHDR_IMG_FMT_32bppRGBA8888) {
1606       uint32_t* plane_dst = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_PACKED]);
1607       uint8_t* plane_src = static_cast<uint8_t*>(src->planes[UHDR_PLANE_PACKED]);
1608       for (size_t i = 0; i < src->h; i++) {
1609         uint32_t* pixel_dst = plane_dst;
1610         uint8_t* pixel_src = plane_src;
1611         for (size_t j = 0; j < src->w; j++) {
1612           *pixel_dst = pixel_src[0] | (pixel_src[1] << 8) | (pixel_src[2] << 16) | (0xff << 24);
1613           pixel_src += 3;
1614           pixel_dst += 1;
1615         }
1616         plane_dst += dst->stride[UHDR_PLANE_PACKED];
1617         plane_src += (size_t)3 * src->stride[UHDR_PLANE_PACKED];
1618       }
1619       return g_no_error;
1620     }
1621   }
1622   uhdr_error_info_t status;
1623   status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
1624   status.has_detail = 1;
1625   snprintf(
1626       status.detail, sizeof status.detail,
1627       "unsupported source / destinations color formats in copy_raw_image, src fmt %d, dst fmt %d",
1628       src->fmt, dst->fmt);
1629   return status;
1630 }
1631 
1632 // Use double type for intermediate results for better precision.
floatToUnsignedFractionImpl(float v,uint32_t maxNumerator,uint32_t * numerator,uint32_t * denominator)1633 static bool floatToUnsignedFractionImpl(float v, uint32_t maxNumerator, uint32_t* numerator,
1634                                         uint32_t* denominator) {
1635   if (std::isnan(v) || v < 0 || v > maxNumerator) {
1636     return false;
1637   }
1638 
1639   // Maximum denominator: makes sure that the numerator is <= maxNumerator and the denominator
1640   // is <= UINT32_MAX.
1641   const uint64_t maxD = (v <= 1) ? UINT32_MAX : (uint64_t)floor(maxNumerator / v);
1642 
1643   // Find the best approximation of v as a fraction using continued fractions, see
1644   // https://en.wikipedia.org/wiki/Continued_fraction
1645   *denominator = 1;
1646   uint32_t previousD = 0;
1647   double currentV = (double)v - floor(v);
1648   int iter = 0;
1649   // Set a maximum number of iterations to be safe. Most numbers should
1650   // converge in less than ~20 iterations.
1651   // The golden ratio is the worst case and takes 39 iterations.
1652   const int maxIter = 39;
1653   while (iter < maxIter) {
1654     const double numeratorDouble = (double)(*denominator) * v;
1655     if (numeratorDouble > maxNumerator) {
1656       return false;
1657     }
1658     *numerator = (uint32_t)round(numeratorDouble);
1659     if (fabs(numeratorDouble - (*numerator)) == 0.0) {
1660       return true;
1661     }
1662     currentV = 1.0 / currentV;
1663     const double newD = previousD + floor(currentV) * (*denominator);
1664     if (newD > maxD) {
1665       // This is the best we can do with a denominator <= max_d.
1666       return true;
1667     }
1668     previousD = *denominator;
1669     if (newD > (double)UINT32_MAX) {
1670       return false;
1671     }
1672     *denominator = (uint32_t)newD;
1673     currentV -= floor(currentV);
1674     ++iter;
1675   }
1676   // Maximum number of iterations reached, return what we've found.
1677   // For max_iter >= 39 we shouldn't get here. max_iter can be set
1678   // to a lower value to speed up the algorithm if needed.
1679   *numerator = (uint32_t)round((double)(*denominator) * v);
1680   return true;
1681 }
1682 
floatToSignedFraction(float v,int32_t * numerator,uint32_t * denominator)1683 bool floatToSignedFraction(float v, int32_t* numerator, uint32_t* denominator) {
1684   uint32_t positive_numerator;
1685   if (!floatToUnsignedFractionImpl(fabs(v), INT32_MAX, &positive_numerator, denominator)) {
1686     return false;
1687   }
1688   *numerator = (int32_t)positive_numerator;
1689   if (v < 0) {
1690     *numerator *= -1;
1691   }
1692   return true;
1693 }
1694 
floatToUnsignedFraction(float v,uint32_t * numerator,uint32_t * denominator)1695 bool floatToUnsignedFraction(float v, uint32_t* numerator, uint32_t* denominator) {
1696   return floatToUnsignedFractionImpl(v, UINT32_MAX, numerator, denominator);
1697 }
1698 
1699 }  // namespace ultrahdr
1700