1 /*
2 * Copyright 2024 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 <algorithm>
18 #include <cmath>
19
20 #include "ultrahdr/gainmapmath.h"
21 #include "ultrahdr/gainmapmetadata.h"
22
23 namespace ultrahdr {
24
streamWriteU8(std::vector<uint8_t> & data,uint8_t value)25 void streamWriteU8(std::vector<uint8_t> &data, uint8_t value) { data.push_back(value); }
26
streamWriteU16(std::vector<uint8_t> & data,uint16_t value)27 void streamWriteU16(std::vector<uint8_t> &data, uint16_t value) {
28 data.push_back((value >> 8) & 0xff);
29 data.push_back(value & 0xff);
30 }
31
streamWriteU32(std::vector<uint8_t> & data,uint32_t value)32 void streamWriteU32(std::vector<uint8_t> &data, uint32_t value) {
33 data.push_back((value >> 24) & 0xff);
34 data.push_back((value >> 16) & 0xff);
35 data.push_back((value >> 8) & 0xff);
36 data.push_back(value & 0xff);
37 }
38
streamWriteS32(std::vector<uint8_t> & data,int32_t value)39 void streamWriteS32(std::vector<uint8_t> &data, int32_t value) {
40 data.push_back((value >> 24) & 0xff);
41 data.push_back((value >> 16) & 0xff);
42 data.push_back((value >> 8) & 0xff);
43 data.push_back(value & 0xff);
44 }
45
streamReadU8(const std::vector<uint8_t> & data,uint8_t & value,size_t & pos)46 uhdr_error_info_t streamReadU8(const std::vector<uint8_t> &data, uint8_t &value, size_t &pos) {
47 if (pos >= data.size()) {
48 uhdr_error_info_t status;
49 status.error_code = UHDR_CODEC_MEM_ERROR;
50 status.has_detail = 1;
51 snprintf(status.detail, sizeof status.detail,
52 "attempting to read byte at position %d when the buffer size is %d", (int)pos,
53 (int)data.size());
54 return status;
55 }
56 value = data[pos++];
57 return g_no_error;
58 }
59
streamReadU16(const std::vector<uint8_t> & data,uint16_t & value,size_t & pos)60 uhdr_error_info_t streamReadU16(const std::vector<uint8_t> &data, uint16_t &value, size_t &pos) {
61 if (pos + 1 >= data.size()) {
62 uhdr_error_info_t status;
63 status.error_code = UHDR_CODEC_MEM_ERROR;
64 status.has_detail = 1;
65 snprintf(status.detail, sizeof status.detail,
66 "attempting to read 2 bytes from position %d when the buffer size is %d", (int)pos,
67 (int)data.size());
68 return status;
69 }
70 value = (data[pos] << 8 | data[pos + 1]);
71 pos += 2;
72 return g_no_error;
73 }
74
streamReadU32(const std::vector<uint8_t> & data,uint32_t & value,size_t & pos)75 uhdr_error_info_t streamReadU32(const std::vector<uint8_t> &data, uint32_t &value, size_t &pos) {
76 if (pos + 3 >= data.size()) {
77 uhdr_error_info_t status;
78 status.error_code = UHDR_CODEC_MEM_ERROR;
79 status.has_detail = 1;
80 snprintf(status.detail, sizeof status.detail,
81 "attempting to read 4 bytes from position %d when the buffer size is %d", (int)pos,
82 (int)data.size());
83 return status;
84 }
85 value = (data[pos] << 24 | data[pos + 1] << 16 | data[pos + 2] << 8 | data[pos + 3]);
86 pos += 4;
87 return g_no_error;
88 }
89
streamReadS32(const std::vector<uint8_t> & data,int32_t & value,size_t & pos)90 uhdr_error_info_t streamReadS32(const std::vector<uint8_t> &data, int32_t &value, size_t &pos) {
91 if (pos + 3 >= data.size()) {
92 uhdr_error_info_t status;
93 status.error_code = UHDR_CODEC_MEM_ERROR;
94 status.has_detail = 1;
95 snprintf(status.detail, sizeof status.detail,
96 "attempting to read 4 bytes from position %d when the buffer size is %d", (int)pos,
97 (int)data.size());
98 return status;
99 }
100 value = (data[pos] << 24 | data[pos + 1] << 16 | data[pos + 2] << 8 | data[pos + 3]);
101 pos += 4;
102 return g_no_error;
103 }
104
allChannelsIdentical() const105 bool uhdr_gainmap_metadata_frac::allChannelsIdentical() const {
106 return gainMapMinN[0] == gainMapMinN[1] && gainMapMinN[0] == gainMapMinN[2] &&
107 gainMapMinD[0] == gainMapMinD[1] && gainMapMinD[0] == gainMapMinD[2] &&
108 gainMapMaxN[0] == gainMapMaxN[1] && gainMapMaxN[0] == gainMapMaxN[2] &&
109 gainMapMaxD[0] == gainMapMaxD[1] && gainMapMaxD[0] == gainMapMaxD[2] &&
110 gainMapGammaN[0] == gainMapGammaN[1] && gainMapGammaN[0] == gainMapGammaN[2] &&
111 gainMapGammaD[0] == gainMapGammaD[1] && gainMapGammaD[0] == gainMapGammaD[2] &&
112 baseOffsetN[0] == baseOffsetN[1] && baseOffsetN[0] == baseOffsetN[2] &&
113 baseOffsetD[0] == baseOffsetD[1] && baseOffsetD[0] == baseOffsetD[2] &&
114 alternateOffsetN[0] == alternateOffsetN[1] && alternateOffsetN[0] == alternateOffsetN[2] &&
115 alternateOffsetD[0] == alternateOffsetD[1] && alternateOffsetD[0] == alternateOffsetD[2];
116 }
117
encodeGainmapMetadata(const uhdr_gainmap_metadata_frac * in_metadata,std::vector<uint8_t> & out_data)118 uhdr_error_info_t uhdr_gainmap_metadata_frac::encodeGainmapMetadata(
119 const uhdr_gainmap_metadata_frac *in_metadata, std::vector<uint8_t> &out_data) {
120 if (in_metadata == nullptr) {
121 uhdr_error_info_t status;
122 status.error_code = UHDR_CODEC_INVALID_PARAM;
123 status.has_detail = 1;
124 snprintf(status.detail, sizeof status.detail,
125 "received nullptr for gain map metadata descriptor");
126 return status;
127 }
128
129 const uint16_t min_version = 0, writer_version = 0;
130 streamWriteU16(out_data, min_version);
131 streamWriteU16(out_data, writer_version);
132
133 uint8_t flags = 0u;
134 // Always write three channels for now for simplicity.
135 // TODO(maryla): the draft says that this specifies the count of channels of the
136 // gain map. But tone mapping is done in RGB space so there are always three
137 // channels, even if the gain map is grayscale. Should this be revised?
138 const uint8_t channelCount = in_metadata->allChannelsIdentical() ? 1u : 3u;
139
140 if (channelCount == 3) {
141 flags |= kIsMultiChannelMask;
142 }
143 if (in_metadata->useBaseColorSpace) {
144 flags |= kUseBaseColorSpaceMask;
145 }
146 if (in_metadata->backwardDirection) {
147 flags |= 4;
148 }
149
150 const uint32_t denom = in_metadata->baseHdrHeadroomD;
151 bool useCommonDenominator = true;
152 if (in_metadata->baseHdrHeadroomD != denom || in_metadata->alternateHdrHeadroomD != denom) {
153 useCommonDenominator = false;
154 }
155 for (int c = 0; c < channelCount; ++c) {
156 if (in_metadata->gainMapMinD[c] != denom || in_metadata->gainMapMaxD[c] != denom ||
157 in_metadata->gainMapGammaD[c] != denom || in_metadata->baseOffsetD[c] != denom ||
158 in_metadata->alternateOffsetD[c] != denom) {
159 useCommonDenominator = false;
160 }
161 }
162 if (useCommonDenominator) {
163 flags |= 8;
164 }
165 streamWriteU8(out_data, flags);
166
167 if (useCommonDenominator) {
168 streamWriteU32(out_data, denom);
169 streamWriteU32(out_data, in_metadata->baseHdrHeadroomN);
170 streamWriteU32(out_data, in_metadata->alternateHdrHeadroomN);
171 for (int c = 0; c < channelCount; ++c) {
172 streamWriteS32(out_data, in_metadata->gainMapMinN[c]);
173 streamWriteS32(out_data, in_metadata->gainMapMaxN[c]);
174 streamWriteU32(out_data, in_metadata->gainMapGammaN[c]);
175 streamWriteS32(out_data, in_metadata->baseOffsetN[c]);
176 streamWriteS32(out_data, in_metadata->alternateOffsetN[c]);
177 }
178 } else {
179 streamWriteU32(out_data, in_metadata->baseHdrHeadroomN);
180 streamWriteU32(out_data, in_metadata->baseHdrHeadroomD);
181 streamWriteU32(out_data, in_metadata->alternateHdrHeadroomN);
182 streamWriteU32(out_data, in_metadata->alternateHdrHeadroomD);
183 for (int c = 0; c < channelCount; ++c) {
184 streamWriteS32(out_data, in_metadata->gainMapMinN[c]);
185 streamWriteU32(out_data, in_metadata->gainMapMinD[c]);
186 streamWriteS32(out_data, in_metadata->gainMapMaxN[c]);
187 streamWriteU32(out_data, in_metadata->gainMapMaxD[c]);
188 streamWriteU32(out_data, in_metadata->gainMapGammaN[c]);
189 streamWriteU32(out_data, in_metadata->gainMapGammaD[c]);
190 streamWriteS32(out_data, in_metadata->baseOffsetN[c]);
191 streamWriteU32(out_data, in_metadata->baseOffsetD[c]);
192 streamWriteS32(out_data, in_metadata->alternateOffsetN[c]);
193 streamWriteU32(out_data, in_metadata->alternateOffsetD[c]);
194 }
195 }
196
197 return g_no_error;
198 }
199
decodeGainmapMetadata(const std::vector<uint8_t> & in_data,uhdr_gainmap_metadata_frac * out_metadata)200 uhdr_error_info_t uhdr_gainmap_metadata_frac::decodeGainmapMetadata(
201 const std::vector<uint8_t> &in_data, uhdr_gainmap_metadata_frac *out_metadata) {
202 if (out_metadata == nullptr) {
203 uhdr_error_info_t status;
204 status.error_code = UHDR_CODEC_INVALID_PARAM;
205 status.has_detail = 1;
206 snprintf(status.detail, sizeof status.detail,
207 "received nullptr for gain map metadata descriptor");
208 return status;
209 }
210
211 size_t pos = 0;
212 uint16_t min_version = 0xffff;
213 uint16_t writer_version = 0xffff;
214 UHDR_ERR_CHECK(streamReadU16(in_data, min_version, pos))
215 if (min_version != 0) {
216 uhdr_error_info_t status;
217 status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
218 status.has_detail = 1;
219 snprintf(status.detail, sizeof status.detail,
220 "received unexpected minimum version %d, expected 0", min_version);
221 return status;
222 }
223 UHDR_ERR_CHECK(streamReadU16(in_data, writer_version, pos))
224
225 uint8_t flags = 0xff;
226 UHDR_ERR_CHECK(streamReadU8(in_data, flags, pos))
227 uint8_t channelCount = ((flags & kIsMultiChannelMask) != 0) * 2 + 1;
228 if (!(channelCount == 1 || channelCount == 3)) {
229 uhdr_error_info_t status;
230 status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
231 status.has_detail = 1;
232 snprintf(status.detail, sizeof status.detail,
233 "received unexpected channel count %d, expects one of {1, 3}", channelCount);
234 return status;
235 }
236 out_metadata->useBaseColorSpace = (flags & kUseBaseColorSpaceMask) != 0;
237 out_metadata->backwardDirection = (flags & 4) != 0;
238 const bool useCommonDenominator = (flags & 8) != 0;
239
240 if (useCommonDenominator) {
241 uint32_t commonDenominator = 1u;
242 UHDR_ERR_CHECK(streamReadU32(in_data, commonDenominator, pos))
243
244 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->baseHdrHeadroomN, pos))
245 out_metadata->baseHdrHeadroomD = commonDenominator;
246 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->alternateHdrHeadroomN, pos))
247 out_metadata->alternateHdrHeadroomD = commonDenominator;
248
249 for (int c = 0; c < channelCount; ++c) {
250 UHDR_ERR_CHECK(streamReadS32(in_data, out_metadata->gainMapMinN[c], pos))
251 out_metadata->gainMapMinD[c] = commonDenominator;
252 UHDR_ERR_CHECK(streamReadS32(in_data, out_metadata->gainMapMaxN[c], pos))
253 out_metadata->gainMapMaxD[c] = commonDenominator;
254 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapGammaN[c], pos))
255 out_metadata->gainMapGammaD[c] = commonDenominator;
256 UHDR_ERR_CHECK(streamReadS32(in_data, out_metadata->baseOffsetN[c], pos))
257 out_metadata->baseOffsetD[c] = commonDenominator;
258 UHDR_ERR_CHECK(streamReadS32(in_data, out_metadata->alternateOffsetN[c], pos))
259 out_metadata->alternateOffsetD[c] = commonDenominator;
260 }
261 } else {
262 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->baseHdrHeadroomN, pos))
263 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->baseHdrHeadroomD, pos))
264 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->alternateHdrHeadroomN, pos))
265 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->alternateHdrHeadroomD, pos))
266 for (int c = 0; c < channelCount; ++c) {
267 UHDR_ERR_CHECK(streamReadS32(in_data, out_metadata->gainMapMinN[c], pos))
268 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapMinD[c], pos))
269 UHDR_ERR_CHECK(streamReadS32(in_data, out_metadata->gainMapMaxN[c], pos))
270 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapMaxD[c], pos))
271 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapGammaN[c], pos))
272 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapGammaD[c], pos))
273 UHDR_ERR_CHECK(streamReadS32(in_data, out_metadata->baseOffsetN[c], pos))
274 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->baseOffsetD[c], pos))
275 UHDR_ERR_CHECK(streamReadS32(in_data, out_metadata->alternateOffsetN[c], pos))
276 UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->alternateOffsetD[c], pos))
277 }
278 }
279
280 // Fill the remaining values by copying those from the first channel.
281 for (int c = channelCount; c < 3; ++c) {
282 out_metadata->gainMapMinN[c] = out_metadata->gainMapMinN[0];
283 out_metadata->gainMapMinD[c] = out_metadata->gainMapMinD[0];
284 out_metadata->gainMapMaxN[c] = out_metadata->gainMapMaxN[0];
285 out_metadata->gainMapMaxD[c] = out_metadata->gainMapMaxD[0];
286 out_metadata->gainMapGammaN[c] = out_metadata->gainMapGammaN[0];
287 out_metadata->gainMapGammaD[c] = out_metadata->gainMapGammaD[0];
288 out_metadata->baseOffsetN[c] = out_metadata->baseOffsetN[0];
289 out_metadata->baseOffsetD[c] = out_metadata->baseOffsetD[0];
290 out_metadata->alternateOffsetN[c] = out_metadata->alternateOffsetN[0];
291 out_metadata->alternateOffsetD[c] = out_metadata->alternateOffsetD[0];
292 }
293
294 return g_no_error;
295 }
296
297 #define UHDR_CHECK_NON_ZERO(x, message) \
298 if (x == 0) { \
299 uhdr_error_info_t status; \
300 status.error_code = UHDR_CODEC_INVALID_PARAM; \
301 status.has_detail = 1; \
302 snprintf(status.detail, sizeof status.detail, "received 0 (bad value) for field %s", message); \
303 return status; \
304 }
305
gainmapMetadataFractionToFloat(const uhdr_gainmap_metadata_frac * from,uhdr_gainmap_metadata_ext_t * to)306 uhdr_error_info_t uhdr_gainmap_metadata_frac::gainmapMetadataFractionToFloat(
307 const uhdr_gainmap_metadata_frac *from, uhdr_gainmap_metadata_ext_t *to) {
308 if (from == nullptr || to == nullptr) {
309 uhdr_error_info_t status;
310 status.error_code = UHDR_CODEC_INVALID_PARAM;
311 status.has_detail = 1;
312 snprintf(status.detail, sizeof status.detail,
313 "received nullptr for gain map metadata descriptor");
314 return status;
315 }
316
317 UHDR_CHECK_NON_ZERO(from->baseHdrHeadroomD, "baseHdrHeadroom denominator");
318 UHDR_CHECK_NON_ZERO(from->alternateHdrHeadroomD, "alternateHdrHeadroom denominator");
319 for (int i = 0; i < 3; ++i) {
320 UHDR_CHECK_NON_ZERO(from->gainMapMaxD[i], "gainMapMax denominator");
321 UHDR_CHECK_NON_ZERO(from->gainMapGammaD[i], "gainMapGamma denominator");
322 UHDR_CHECK_NON_ZERO(from->gainMapMinD[i], "gainMapMin denominator");
323 UHDR_CHECK_NON_ZERO(from->baseOffsetD[i], "baseOffset denominator");
324 UHDR_CHECK_NON_ZERO(from->alternateOffsetD[i], "alternateOffset denominator");
325 }
326
327 // TODO: extend uhdr_gainmap_metadata_ext_t to cover multi-channel
328 if (!from->allChannelsIdentical()) {
329 uhdr_error_info_t status;
330 status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
331 status.has_detail = 1;
332 snprintf(status.detail, sizeof status.detail,
333 "current implementation does not handle images with gainmap metadata different "
334 "across r/g/b channels");
335 return status;
336 }
337
338 // jpeg supports only 8 bits per component, applying gainmap in inverse direction is unexpected
339 if (from->backwardDirection) {
340 uhdr_error_info_t status;
341 status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
342 status.has_detail = 1;
343 snprintf(status.detail, sizeof status.detail, "hdr intent as base rendition is not supported");
344 return status;
345 }
346
347 // TODO: parse gainmap image icc and use it for color conversion during applygainmap
348 if (!from->useBaseColorSpace) {
349 uhdr_error_info_t status;
350 status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
351 status.has_detail = 1;
352 snprintf(status.detail, sizeof status.detail,
353 "current implementation requires gainmap application space to match base color space");
354 return status;
355 }
356
357 to->version = kJpegrVersion;
358 to->max_content_boost = exp2((float)from->gainMapMaxN[0] / from->gainMapMaxD[0]);
359 to->min_content_boost = exp2((float)from->gainMapMinN[0] / from->gainMapMinD[0]);
360
361 to->gamma = (float)from->gainMapGammaN[0] / from->gainMapGammaD[0];
362
363 // BaseRenditionIsHDR is false
364 to->offset_sdr = (float)from->baseOffsetN[0] / from->baseOffsetD[0];
365 to->offset_hdr = (float)from->alternateOffsetN[0] / from->alternateOffsetD[0];
366 to->hdr_capacity_max = exp2((float)from->alternateHdrHeadroomN / from->alternateHdrHeadroomD);
367 to->hdr_capacity_min = exp2((float)from->baseHdrHeadroomN / from->baseHdrHeadroomD);
368
369 return g_no_error;
370 }
371
gainmapMetadataFloatToFraction(const uhdr_gainmap_metadata_ext_t * from,uhdr_gainmap_metadata_frac * to)372 uhdr_error_info_t uhdr_gainmap_metadata_frac::gainmapMetadataFloatToFraction(
373 const uhdr_gainmap_metadata_ext_t *from, uhdr_gainmap_metadata_frac *to) {
374 if (from == nullptr || to == nullptr) {
375 uhdr_error_info_t status;
376 status.error_code = UHDR_CODEC_INVALID_PARAM;
377 status.has_detail = 1;
378 snprintf(status.detail, sizeof status.detail,
379 "received nullptr for gain map metadata descriptor");
380 return status;
381 }
382
383 to->backwardDirection = false;
384 to->useBaseColorSpace = true;
385
386 #define CONVERT_FLT_TO_UNSIGNED_FRACTION(flt, numerator, denominator) \
387 if (!floatToUnsignedFraction(flt, numerator, denominator)) { \
388 uhdr_error_info_t status; \
389 status.error_code = UHDR_CODEC_INVALID_PARAM; \
390 status.has_detail = 1; \
391 snprintf(status.detail, sizeof status.detail, \
392 "encountered error while representing float %f as a rational number (p/q form) ", \
393 flt); \
394 return status; \
395 }
396
397 #define CONVERT_FLT_TO_SIGNED_FRACTION(flt, numerator, denominator) \
398 if (!floatToSignedFraction(flt, numerator, denominator)) { \
399 uhdr_error_info_t status; \
400 status.error_code = UHDR_CODEC_INVALID_PARAM; \
401 status.has_detail = 1; \
402 snprintf(status.detail, sizeof status.detail, \
403 "encountered error while representing float %f as a rational number (p/q form) ", \
404 flt); \
405 return status; \
406 }
407
408 CONVERT_FLT_TO_SIGNED_FRACTION(log2(from->max_content_boost), &to->gainMapMaxN[0],
409 &to->gainMapMaxD[0])
410 to->gainMapMaxN[2] = to->gainMapMaxN[1] = to->gainMapMaxN[0];
411 to->gainMapMaxD[2] = to->gainMapMaxD[1] = to->gainMapMaxD[0];
412
413 CONVERT_FLT_TO_SIGNED_FRACTION(log2(from->min_content_boost), &to->gainMapMinN[0],
414 &to->gainMapMinD[0]);
415 to->gainMapMinN[2] = to->gainMapMinN[1] = to->gainMapMinN[0];
416 to->gainMapMinD[2] = to->gainMapMinD[1] = to->gainMapMinD[0];
417
418 CONVERT_FLT_TO_UNSIGNED_FRACTION(from->gamma, &to->gainMapGammaN[0], &to->gainMapGammaD[0]);
419 to->gainMapGammaN[2] = to->gainMapGammaN[1] = to->gainMapGammaN[0];
420 to->gainMapGammaD[2] = to->gainMapGammaD[1] = to->gainMapGammaD[0];
421
422 CONVERT_FLT_TO_SIGNED_FRACTION(from->offset_sdr, &to->baseOffsetN[0], &to->baseOffsetD[0]);
423 to->baseOffsetN[2] = to->baseOffsetN[1] = to->baseOffsetN[0];
424 to->baseOffsetD[2] = to->baseOffsetD[1] = to->baseOffsetD[0];
425
426 CONVERT_FLT_TO_SIGNED_FRACTION(from->offset_hdr, &to->alternateOffsetN[0],
427 &to->alternateOffsetD[0]);
428 to->alternateOffsetN[2] = to->alternateOffsetN[1] = to->alternateOffsetN[0];
429 to->alternateOffsetD[2] = to->alternateOffsetD[1] = to->alternateOffsetD[0];
430
431 CONVERT_FLT_TO_UNSIGNED_FRACTION(log2(from->hdr_capacity_min), &to->baseHdrHeadroomN,
432 &to->baseHdrHeadroomD);
433
434 CONVERT_FLT_TO_UNSIGNED_FRACTION(log2(from->hdr_capacity_max), &to->alternateHdrHeadroomN,
435 &to->alternateHdrHeadroomD);
436
437 return g_no_error;
438 }
439
440 } // namespace ultrahdr
441