xref: /aosp_15_r20/external/libultrahdr/lib/src/gainmapmetadata.cpp (revision 89a0ef05262152531a00a15832a2d3b1e3990773)
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