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