Coverage Report

Created: 2026-02-14 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/nclx.cc
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2020 Dirk Farin <dirk.farin@gmail.com>
4
 *
5
 * This file is part of libheif.
6
 *
7
 * libheif is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Lesser General Public License as
9
 * published by the Free Software Foundation, either version 3 of
10
 * the License, or (at your option) any later version.
11
 *
12
 * libheif is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
22
#include "nclx.h"
23
#include "libheif/heif_experimental.h"
24
25
#include <cassert>
26
#include <memory>
27
#include <string>
28
#include <vector>
29
30
31
primaries::primaries(float gx, float gy, float bx, float by, float rx, float ry, float wx, float wy)
32
41
{
33
41
  defined = true;
34
41
  redX = rx;
35
41
  redY = ry;
36
41
  greenX = gx;
37
41
  greenY = gy;
38
41
  blueX = bx;
39
41
  blueY = by;
40
41
  whiteX = wx;
41
41
  whiteY = wy;
42
41
}
43
44
45
primaries get_colour_primaries(uint16_t primaries_idx)
46
43
{
47
43
  switch (primaries_idx) {
48
29
    case 1:
49
29
      return {0.300f, 0.600f, 0.150f, 0.060f, 0.640f, 0.330f, 0.3127f, 0.3290f};
50
1
    case 4:
51
1
      return {0.21f, 0.71f, 0.14f, 0.08f, 0.67f, 0.33f, 0.310f, 0.316f};
52
0
    case 5:
53
0
      return {0.29f, 0.60f, 0.15f, 0.06f, 0.64f, 0.33f, 0.3127f, 0.3290f};
54
0
    case 6:
55
0
    case 7:
56
0
      return {0.310f, 0.595f, 0.155f, 0.070f, 0.630f, 0.340f, 0.3127f, 0.3290f};
57
3
    case 8:
58
3
      return {0.243f, 0.692f, 0.145f, 0.049f, 0.681f, 0.319f, 0.310f, 0.316f};
59
2
    case 9:
60
2
      return {0.170f, 0.797f, 0.131f, 0.046f, 0.708f, 0.292f, 0.3127f, 0.3290f};
61
2
    case 10:
62
2
      return {0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.333333f, 0.33333f};
63
2
    case 11:
64
2
      return {0.265f, 0.690f, 0.150f, 0.060f, 0.680f, 0.320f, 0.314f, 0.351f};
65
2
    case 12:
66
2
      return {0.265f, 0.690f, 0.150f, 0.060f, 0.680f, 0.320f, 0.3127f, 0.3290f};
67
0
    case 22:
68
0
      return {0.295f, 0.605f, 0.155f, 0.077f, 0.630f, 0.340f, 0.3127f, 0.3290f};
69
2
    default:
70
2
      return {};
71
43
  }
72
43
}
73
74
Kr_Kb Kr_Kb::defaults()
75
0
{
76
0
  Kr_Kb kr_kb;
77
  // Rec 601.
78
0
  kr_kb.Kr = 0.2990f;
79
0
  kr_kb.Kb = 0.1140f;
80
0
  return kr_kb;
81
0
}
82
83
84
Kr_Kb get_Kr_Kb(uint16_t matrix_coefficients_idx, uint16_t primaries_idx)
85
2.96k
{
86
2.96k
  Kr_Kb result;
87
88
2.96k
  if (matrix_coefficients_idx == 12 ||
89
2.96k
      matrix_coefficients_idx == 13) {
90
91
0
    primaries p = get_colour_primaries(primaries_idx);
92
0
    float zr = 1 - (p.redX + p.redY);
93
0
    float zg = 1 - (p.greenX + p.greenY);
94
0
    float zb = 1 - (p.blueX + p.blueY);
95
0
    float zw = 1 - (p.whiteX + p.whiteY);
96
97
0
    float denom = p.whiteY * (p.redX * (p.greenY * zb - p.blueY * zg) + p.greenX * (p.blueY * zr - p.redY * zb) +
98
0
                              p.blueX * (p.redY * zg - p.greenY * zr));
99
100
0
    if (denom == 0.0f) {
101
0
      return result;
102
0
    }
103
104
0
    result.Kr = (p.redY * (p.whiteX * (p.greenY * zb - p.blueY * zg) + p.whiteY * (p.blueX * zg - p.greenX * zb) +
105
0
                           zw * (p.greenX * p.blueY - p.blueX * p.greenY))) / denom;
106
0
    result.Kb = (p.blueY * (p.whiteX * (p.redY * zg - p.greenY * zr) + p.whiteY * (p.greenX * zr - p.redX * zg) +
107
0
                            zw * (p.redX * p.greenY - p.greenX * p.redY))) / denom;
108
0
  }
109
2.96k
  else
110
2.96k
    switch (matrix_coefficients_idx) {
111
6
      case 1:
112
6
        result.Kr = 0.2126f;
113
6
        result.Kb = 0.0722f;
114
6
        break;
115
2
      case 4:
116
2
        result.Kr = 0.30f;
117
2
        result.Kb = 0.11f;
118
2
        break;
119
14
      case 5:
120
1.06k
      case 6:
121
1.06k
        result.Kr = 0.299f;
122
1.06k
        result.Kb = 0.114f;
123
1.06k
        break;
124
1
      case 7:
125
1
        result.Kr = 0.212f;
126
1
        result.Kb = 0.087f;
127
1
        break;
128
0
      case 9:
129
1
      case 10:
130
1
        result.Kr = 0.2627f;
131
1
        result.Kb = 0.0593f;
132
1
        break;
133
1.89k
      default:;
134
2.96k
    }
135
136
2.96k
  return result;
137
2.96k
}
138
139
140
YCbCr_to_RGB_coefficients YCbCr_to_RGB_coefficients::defaults()
141
5.63k
{
142
5.63k
  YCbCr_to_RGB_coefficients coeffs;
143
5.63k
  coeffs.defined = true;
144
5.63k
  coeffs.r_cr = 1.402f;
145
5.63k
  coeffs.g_cb = -0.344136f;
146
5.63k
  coeffs.g_cr = -0.714136f;
147
5.63k
  coeffs.b_cb = 1.772f;
148
5.63k
  return coeffs;
149
5.63k
}
150
151
YCbCr_to_RGB_coefficients
152
get_YCbCr_to_RGB_coefficients(uint16_t matrix_coefficients_idx, uint16_t primaries_idx)
153
2.96k
{
154
2.96k
  YCbCr_to_RGB_coefficients coeffs;
155
156
2.96k
  Kr_Kb k = get_Kr_Kb(matrix_coefficients_idx, primaries_idx);
157
158
2.96k
  if (k.Kb != 0 || k.Kr != 0) { // both will be != 0 when valid
159
1.07k
    coeffs.defined = true;
160
1.07k
    coeffs.r_cr = 2 * (-k.Kr + 1);
161
1.07k
    coeffs.g_cb = 2 * k.Kb * (-k.Kb + 1) / (k.Kb + k.Kr - 1);
162
1.07k
    coeffs.g_cr = 2 * k.Kr * (-k.Kr + 1) / (k.Kb + k.Kr - 1);
163
1.07k
    coeffs.b_cb = 2 * (-k.Kb + 1);
164
1.07k
  }
165
1.89k
  else {
166
1.89k
    coeffs = YCbCr_to_RGB_coefficients::defaults();
167
1.89k
  }
168
169
2.96k
  return coeffs;
170
2.96k
}
171
172
173
RGB_to_YCbCr_coefficients
174
get_RGB_to_YCbCr_coefficients(uint16_t matrix_coefficients_idx, uint16_t primaries_idx)
175
0
{
176
0
  RGB_to_YCbCr_coefficients coeffs;
177
178
0
  Kr_Kb k = get_Kr_Kb(matrix_coefficients_idx, primaries_idx);
179
180
0
  if (k.Kb != 0 || k.Kr != 0) { // both will be != 0 when valid
181
0
    coeffs.defined = true;
182
0
    coeffs.c[0][0] = k.Kr;
183
0
    coeffs.c[0][1] = 1 - k.Kr - k.Kb;
184
0
    coeffs.c[0][2] = k.Kb;
185
0
    coeffs.c[1][0] = -k.Kr / (1 - k.Kb) / 2;
186
0
    coeffs.c[1][1] = -(1 - k.Kr - k.Kb) / (1 - k.Kb) / 2;
187
0
    coeffs.c[1][2] = 0.5f;
188
0
    coeffs.c[2][0] = 0.5f;
189
0
    coeffs.c[2][1] = -(1 - k.Kr - k.Kb) / (1 - k.Kr) / 2;
190
0
    coeffs.c[2][2] = -k.Kb / (1 - k.Kr) / 2;
191
0
  }
192
0
  else {
193
0
    coeffs = RGB_to_YCbCr_coefficients::defaults();
194
0
  }
195
196
0
  return coeffs;
197
0
}
198
199
200
RGB_to_YCbCr_coefficients RGB_to_YCbCr_coefficients::defaults()
201
0
{
202
0
  RGB_to_YCbCr_coefficients coeffs;
203
0
  coeffs.defined = true;
204
  // Rec 601 full. Kr=0.2990f Kb=0.1140f.
205
0
  coeffs.c[0][0] = 0.299f;
206
0
  coeffs.c[0][1] = 0.587f;
207
0
  coeffs.c[0][2] = 0.114f;
208
0
  coeffs.c[1][0] = -0.168735f;
209
0
  coeffs.c[1][1] = -0.331264f;
210
0
  coeffs.c[1][2] = 0.5f;
211
0
  coeffs.c[2][0] = 0.5f;
212
0
  coeffs.c[2][1] = -0.418688f;
213
0
  coeffs.c[2][2] = -0.081312f;
214
215
0
  return coeffs;
216
0
}
217
218
219
Error color_profile_nclx::parse(BitstreamRange& range)
220
395
{
221
395
  StreamReader::grow_status status;
222
395
  status = range.wait_for_available_bytes(7);
223
395
  if (status != StreamReader::grow_status::size_reached) {
224
    // TODO: return recoverable error at timeout
225
0
    return Error(heif_error_Invalid_input,
226
0
                 heif_suberror_End_of_data);
227
0
  }
228
229
395
  m_profile.m_colour_primaries = range.read16();
230
395
  m_profile.m_transfer_characteristics = range.read16();
231
395
  m_profile.m_matrix_coefficients = range.read16();
232
395
  m_profile.m_full_range_flag = (range.read8() & 0x80 ? true : false);
233
234
395
  return Error::Ok;
235
395
}
236
237
Error color_profile_nclx::parse_nclc(BitstreamRange& range)
238
6
{
239
6
  StreamReader::grow_status status;
240
6
  status = range.wait_for_available_bytes(6);
241
6
  if (status != StreamReader::grow_status::size_reached) {
242
    // TODO: return recoverable error at timeout
243
0
    return Error(heif_error_Invalid_input,
244
0
                 heif_suberror_End_of_data);
245
0
  }
246
247
6
  m_profile.m_colour_primaries = range.read16();
248
6
  m_profile.m_transfer_characteristics = range.read16();
249
6
  m_profile.m_matrix_coefficients = range.read16();
250
251
  // use full range for RGB, limited range otherwise
252
6
  m_profile.m_full_range_flag = (m_profile.m_matrix_coefficients == 0);
253
254
6
  return Error::Ok;
255
6
}
256
257
Error nclx_profile::get_nclx_color_profile(heif_color_profile_nclx** out_data) const
258
78
{
259
78
  *out_data = nullptr;
260
261
78
  heif_color_profile_nclx* nclx = heif_nclx_color_profile_alloc();
262
263
78
  if (nclx == nullptr) {
264
0
    return Error(heif_error_Memory_allocation_error,
265
0
                 heif_suberror_Unspecified);
266
0
  }
267
268
78
  heif_error err;
269
270
78
  nclx->version = 1;
271
272
78
  err = heif_nclx_color_profile_set_color_primaries(nclx, get_colour_primaries());
273
78
  if (err.code) {
274
21
    heif_nclx_color_profile_free(nclx);
275
21
    return {err.code, err.subcode};
276
21
  }
277
278
57
  err = heif_nclx_color_profile_set_transfer_characteristics(nclx, get_transfer_characteristics());
279
57
  if (err.code) {
280
4
    heif_nclx_color_profile_free(nclx);
281
4
    return {err.code, err.subcode};
282
4
  }
283
284
53
  err = heif_nclx_color_profile_set_matrix_coefficients(nclx, get_matrix_coefficients());
285
53
  if (err.code) {
286
10
    heif_nclx_color_profile_free(nclx);
287
10
    return {err.code, err.subcode};
288
10
  }
289
290
43
  nclx->full_range_flag = get_full_range_flag();
291
292
  // fill color primaries
293
294
43
  auto primaries = ::get_colour_primaries(nclx->color_primaries);
295
296
43
  nclx->color_primary_red_x = primaries.redX;
297
43
  nclx->color_primary_red_y = primaries.redY;
298
43
  nclx->color_primary_green_x = primaries.greenX;
299
43
  nclx->color_primary_green_y = primaries.greenY;
300
43
  nclx->color_primary_blue_x = primaries.blueX;
301
43
  nclx->color_primary_blue_y = primaries.blueY;
302
43
  nclx->color_primary_white_x = primaries.whiteX;
303
43
  nclx->color_primary_white_y = primaries.whiteY;
304
305
43
  *out_data = nclx;
306
307
43
  return Error::Ok;
308
53
}
309
310
311
void nclx_profile::set_sRGB_defaults()
312
4.88k
{
313
  // sRGB defaults
314
4.88k
  m_colour_primaries = 1;
315
4.88k
  m_transfer_characteristics = 13;
316
4.88k
  m_matrix_coefficients = 6;
317
4.88k
  m_full_range_flag = true;
318
4.88k
}
319
320
321
void nclx_profile::set_undefined()
322
0
{
323
0
  m_colour_primaries = 2;
324
0
  m_transfer_characteristics = 2;
325
0
  m_matrix_coefficients = 2;
326
0
  m_full_range_flag = true;
327
0
}
328
329
330
bool nclx_profile::is_undefined() const
331
4.15k
{
332
4.15k
  return *this == nclx_profile::undefined();
333
4.15k
}
334
335
336
void nclx_profile::set_from_heif_color_profile_nclx(const heif_color_profile_nclx* nclx)
337
0
{
338
0
  if (nclx) {
339
0
    m_colour_primaries = nclx->color_primaries;
340
0
    m_transfer_characteristics = nclx->transfer_characteristics;
341
0
    m_matrix_coefficients = nclx->matrix_coefficients;
342
0
    m_full_range_flag = nclx->full_range_flag;
343
0
  }
344
0
}
345
346
347
void nclx_profile::copy_to_heif_color_profile_nclx(heif_color_profile_nclx* nclx)
348
0
{
349
0
  assert(nclx);
350
0
  nclx->color_primaries = (enum heif_color_primaries)m_colour_primaries;
351
0
  nclx->transfer_characteristics = (enum heif_transfer_characteristics)m_transfer_characteristics;
352
0
  nclx->matrix_coefficients = (enum heif_matrix_coefficients)m_matrix_coefficients;
353
0
  nclx->full_range_flag = m_full_range_flag;
354
0
}
355
356
357
void nclx_profile::replace_undefined_values_with_sRGB_defaults()
358
4.04k
{
359
4.04k
  if (m_matrix_coefficients == heif_matrix_coefficients_unspecified) {
360
2.36k
    m_matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_601_6;
361
2.36k
  }
362
363
4.04k
  if (m_colour_primaries == heif_color_primaries_unspecified) {
364
2.70k
    m_colour_primaries = heif_color_primaries_ITU_R_BT_709_5;
365
2.70k
  }
366
367
4.04k
  if (m_transfer_characteristics == heif_transfer_characteristic_unspecified) {
368
2.69k
    m_transfer_characteristics = heif_transfer_characteristic_IEC_61966_2_1;
369
2.69k
  }
370
4.04k
}
371
372
373
bool nclx_profile::equal_except_transfer_curve(const nclx_profile& b) const
374
4.53k
{
375
4.53k
  return (m_matrix_coefficients == b.m_matrix_coefficients &&
376
4.53k
          m_colour_primaries == b.m_colour_primaries &&
377
4.53k
          m_full_range_flag == b.m_full_range_flag);
378
4.53k
}
379
380
381
Error Box_colr::parse(BitstreamRange& range, const heif_security_limits* limits)
382
1.96k
{
383
1.96k
  StreamReader::grow_status status;
384
1.96k
  uint32_t colour_type = range.read32();
385
386
1.96k
  if (colour_type == fourcc("nclx")) {
387
395
    auto color_profile = std::make_shared<color_profile_nclx>();
388
395
    m_color_profile = color_profile;
389
395
    Error err = color_profile->parse(range);
390
395
    if (err) {
391
0
      return err;
392
0
    }
393
395
  }
394
1.56k
  else if (colour_type == fourcc("nclc")) {
395
6
    auto color_profile = std::make_shared<color_profile_nclx>();
396
6
    m_color_profile = color_profile;
397
6
    Error err = color_profile->parse_nclc(range);
398
6
    if (err) {
399
0
      return err;
400
0
    }
401
6
  }
402
1.56k
  else if (colour_type == fourcc("prof") ||
403
1.54k
           colour_type == fourcc("rICC")) {
404
1.54k
    if (!has_fixed_box_size()) {
405
1
      return Error(heif_error_Unsupported_feature, heif_suberror_Unspecified, "colr boxes with undefined box size are not supported");
406
1
    }
407
408
1.54k
    uint64_t profile_size_64 = get_box_size() - get_header_size() - 4;
409
1.54k
    if (limits->max_color_profile_size && profile_size_64 > limits->max_color_profile_size) {
410
0
      return Error(heif_error_Invalid_input, heif_suberror_Security_limit_exceeded, "Color profile exceeds maximum supported size");
411
0
    }
412
413
1.54k
    size_t profile_size = static_cast<size_t>(profile_size_64);
414
415
1.54k
    status = range.wait_for_available_bytes(profile_size);
416
1.54k
    if (status != StreamReader::grow_status::size_reached) {
417
      // TODO: return recoverable error at timeout
418
0
      return Error(heif_error_Invalid_input,
419
0
                   heif_suberror_End_of_data);
420
0
    }
421
422
1.54k
    std::vector<uint8_t> rawData(profile_size);
423
995k
    for (size_t i = 0; i < profile_size; i++) {
424
993k
      rawData[i] = range.read8();
425
993k
    }
426
427
1.54k
    m_color_profile = std::make_shared<color_profile_raw>(colour_type, rawData);
428
1.54k
  }
429
19
  else {
430
19
    return Error(heif_error_Invalid_input,
431
19
                 heif_suberror_Unknown_color_profile_type);
432
19
  }
433
434
1.94k
  return range.get_error();
435
1.96k
}
436
437
438
std::string Box_colr::dump(Indent& indent) const
439
0
{
440
0
  std::ostringstream sstr;
441
0
  sstr << Box::dump(indent);
442
443
0
  if (m_color_profile) {
444
0
    sstr << indent << "colour_type: " << fourcc_to_string(get_color_profile_type()) << "\n";
445
0
    sstr << m_color_profile->dump(indent);
446
0
  }
447
0
  else {
448
0
    sstr << indent << "colour_type: ---\n";
449
0
    sstr << "no color profile\n";
450
0
  }
451
452
0
  return sstr.str();
453
0
}
454
455
456
std::string color_profile_raw::dump(Indent& indent) const
457
0
{
458
0
  std::ostringstream sstr;
459
0
  sstr << indent << "profile size: " << m_data.size() << "\n";
460
0
  return sstr.str();
461
0
}
462
463
464
std::string color_profile_nclx::dump(Indent& indent) const
465
0
{
466
0
  std::ostringstream sstr;
467
0
  sstr << indent << "colour_primaries: " << m_profile.m_colour_primaries << "\n"
468
0
       << indent << "transfer_characteristics: " << m_profile.m_transfer_characteristics << "\n"
469
0
       << indent << "matrix_coefficients: " << m_profile.m_matrix_coefficients << "\n"
470
0
       << indent << "full_range_flag: " << m_profile.m_full_range_flag << "\n";
471
0
  return sstr.str();
472
0
}
473
474
475
Error color_profile_nclx::write(StreamWriter& writer) const
476
674
{
477
674
  writer.write16(m_profile.m_colour_primaries);
478
674
  writer.write16(m_profile.m_transfer_characteristics);
479
674
  writer.write16(m_profile.m_matrix_coefficients);
480
674
  writer.write8(m_profile.m_full_range_flag ? 0x80 : 0x00);
481
482
674
  return Error::Ok;
483
674
}
484
485
Error color_profile_raw::write(StreamWriter& writer) const
486
7.56k
{
487
7.56k
  writer.write(m_data);
488
489
7.56k
  return Error::Ok;
490
7.56k
}
491
492
Error Box_colr::write(StreamWriter& writer) const
493
8.23k
{
494
8.23k
  size_t box_start = reserve_box_header_space(writer);
495
496
8.23k
  assert(m_color_profile);
497
498
8.23k
  writer.write32(m_color_profile->get_type());
499
500
8.23k
  Error err = m_color_profile->write(writer);
501
8.23k
  if (err) {
502
0
    return err;
503
0
  }
504
505
8.23k
  prepend_header(writer, box_start);
506
507
8.23k
  return Error::Ok;
508
8.23k
}
509
510