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