Coverage Report

Created: 2026-09-28 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/codecs/jpeg2000_boxes.cc
Line
Count
Source
1
/*
2
 * HEIF JPEG 2000 codec.
3
 * Copyright (c) 2023 Brad Hards <bradh@frogmouth.net>
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
#include "jpeg2000_boxes.h"
22
#include "api_structs.h"
23
#include <cstdint>
24
#include <iostream>
25
#include <cstdio>
26
27
static const uint16_t JPEG2000_CAP_MARKER = 0xFF50;
28
static const uint16_t JPEG2000_SIZ_MARKER = 0xFF51;
29
static const uint16_t JPEG2000_SOC_MARKER = 0xFF4F;
30
31
32
Error Box_cdef::parse(BitstreamRange& range, const heif_security_limits* limits)
33
81
{
34
81
  uint16_t channel_count = range.read16();
35
36
81
  if (limits->max_components && channel_count > limits->max_components) {
37
4
    std::stringstream sstr;
38
4
    sstr << "cdef box wants to define " << channel_count << " JPEG-2000 channels, but the security limit is set to "
39
4
         << limits->max_components << " components";
40
4
    return {heif_error_Invalid_input,
41
4
            heif_suberror_Security_limit_exceeded,
42
4
            sstr.str()};
43
4
  }
44
45
77
  if (channel_count > range.get_remaining_bytes() / 6) {
46
8
    std::stringstream sstr;
47
8
    sstr << "cdef box wants to define " << channel_count << " JPEG-2000 channels, but file only contains "
48
8
         << range.get_remaining_bytes() / 6 << " components";
49
8
    return {heif_error_Invalid_input,
50
8
            heif_suberror_End_of_data,
51
8
            sstr.str()};
52
8
  }
53
54
69
  m_channels.resize(channel_count);
55
56
1.76k
  for (uint16_t i = 0; i < channel_count && !range.error() && !range.eof(); i++) {
57
1.69k
    Channel channel;
58
1.69k
    channel.channel_index = range.read16();
59
1.69k
    channel.channel_type = range.read16();
60
1.69k
    channel.channel_association = range.read16();
61
1.69k
    m_channels[i] = channel;
62
1.69k
  }
63
64
69
  return range.get_error();
65
77
}
66
67
std::string Box_cdef::dump(Indent& indent) const
68
0
{
69
0
  std::ostringstream sstr;
70
0
  sstr << Box::dump(indent);
71
72
0
  for (const auto& channel : m_channels) {
73
0
    sstr << indent << "channel_index: " << channel.channel_index
74
0
         << ", channel_type: " << channel.channel_type
75
0
         << ", channel_association: " << channel.channel_association << "\n";
76
0
  }
77
78
0
  return sstr.str();
79
0
}
80
81
82
Error Box_cdef::write(StreamWriter& writer) const
83
0
{
84
0
  size_t box_start = reserve_box_header_space(writer);
85
86
0
  writer.write16((uint16_t) m_channels.size());
87
0
  for (const auto& channel : m_channels) {
88
0
    writer.write16(channel.channel_index);
89
0
    writer.write16(channel.channel_type);
90
0
    writer.write16(channel.channel_association);
91
0
  }
92
93
0
  prepend_header(writer, box_start);
94
95
0
  return Error::Ok;
96
0
}
97
98
99
void Box_cdef::set_channels(heif_colorspace colorspace)
100
0
{
101
  // TODO - Check for the presence of a cmap box which specifies channel indices.
102
103
0
  const uint16_t TYPE_COLOR = 0;
104
0
  const uint16_t ASOC_GREY = 1;
105
0
  const uint16_t ASOC_RED = 1;
106
0
  const uint16_t ASOC_GREEN = 2;
107
0
  const uint16_t ASOC_BLUE = 3;
108
0
  const uint16_t ASOC_Y = 1;
109
0
  const uint16_t ASOC_Cb = 2;
110
0
  const uint16_t ASOC_Cr = 3;
111
112
0
  switch (colorspace) {
113
0
    case heif_colorspace_RGB:
114
0
      m_channels.push_back({0, TYPE_COLOR, ASOC_RED});
115
0
      m_channels.push_back({1, TYPE_COLOR, ASOC_GREEN});
116
0
      m_channels.push_back({2, TYPE_COLOR, ASOC_BLUE});
117
0
      break;
118
119
0
    case heif_colorspace_YCbCr:
120
0
      m_channels.push_back({0, TYPE_COLOR, ASOC_Y});
121
0
      m_channels.push_back({1, TYPE_COLOR, ASOC_Cb});
122
0
      m_channels.push_back({2, TYPE_COLOR, ASOC_Cr});
123
0
      break;
124
125
0
    case heif_colorspace_monochrome:
126
0
      m_channels.push_back({0, TYPE_COLOR, ASOC_GREY});
127
0
      break;
128
129
0
    default:
130
      //TODO - Handle remaining cases.
131
0
      break;
132
0
  }
133
0
}
134
135
Error Box_cmap::parse(BitstreamRange& range, const heif_security_limits* limits)
136
77
{
137
619
  while (!range.eof() && !range.error()) {
138
542
    Component component;
139
542
    component.component_index = range.read16();
140
542
    component.mapping_type = range.read8();
141
542
    component.palette_colour = range.read8();
142
542
    m_components.push_back(component);
143
542
  }
144
145
77
  return range.get_error();
146
77
}
147
148
149
std::string Box_cmap::dump(Indent& indent) const
150
0
{
151
0
  std::ostringstream sstr;
152
0
  sstr << Box::dump(indent);
153
154
0
  for (const auto& component : m_components) {
155
0
    sstr << indent << "component_index: " << component.component_index
156
0
         << ", mapping_type: " << (int) (component.mapping_type)
157
0
         << ", palette_colour: " << (int) (component.palette_colour) << "\n";
158
0
  }
159
160
0
  return sstr.str();
161
0
}
162
163
164
Error Box_cmap::write(StreamWriter& writer) const
165
0
{
166
0
  size_t box_start = reserve_box_header_space(writer);
167
168
0
  for (const auto& component : m_components) {
169
0
    writer.write16(component.component_index);
170
0
    writer.write8(component.mapping_type);
171
0
    writer.write8(component.palette_colour);
172
0
  }
173
174
0
  prepend_header(writer, box_start);
175
176
0
  return Error::Ok;
177
0
}
178
179
180
Error Box_pclr::parse(BitstreamRange& range, const heif_security_limits* limits)
181
93
{
182
93
  uint16_t num_entries = range.read16();
183
93
  uint8_t num_palette_columns = range.read8();
184
185
  // ISO/IEC 15444-1 (Table I.12) requires NPC to be in the range 1 to 255.
186
  // A palette without columns carries no entry data, so the per-entry byte
187
  // count below would be zero and could not bound num_entries. The entry loop
188
  // then allocated up to 65535 empty PaletteEntry vectors from an 11-byte box,
189
  // and nested 'j2kH' containers could repeat this hundreds of times without
190
  // any of it being charged to the memory limits (GHSA-9c75-9g8r-4728).
191
93
  if (num_palette_columns == 0) {
192
5
    return Error(heif_error_Invalid_input,
193
5
                 heif_suberror_Invalid_J2K_codestream,
194
5
                 "pclr box declares zero palette columns");
195
5
  }
196
197
1.05k
  for (uint8_t i = 0; i < num_palette_columns; i++) {
198
    // B_i (Table I.13): the high bit marks signed values, the low 7 bits hold
199
    // the column precision minus one (0..37 for 1..38 bits).
200
974
    uint8_t b = range.read8();
201
974
    if (b & 0x80) {
202
6
      return Error(heif_error_Unsupported_feature,
203
6
                   heif_suberror_Unsupported_data_version,
204
6
                   "pclr with signed data is not supported");
205
6
    }
206
968
    uint8_t bit_depth = static_cast<uint8_t>((b & 0x7F) + 1);
207
968
    if (bit_depth > 16) {
208
3
      return Error(heif_error_Unsupported_feature,
209
3
                   heif_suberror_Unsupported_data_version,
210
3
                   "pclr more than 16 bits per channel is not supported");
211
3
    }
212
965
    m_bitDepths.push_back(bit_depth);
213
965
  }
214
  // Number of bytes each palette entry occupies in the box: each C_ji value is
215
  // padded to a whole number of bytes (I.5.3.4). Used to bound num_entries by
216
  // the data actually present, so a small header cannot force a large
217
  // allocation (analogous to the 'cdef'/'j2kL' checks). The precision is at
218
  // least 1 bit, so every entry occupies at least one byte and the bound is
219
  // never vacuous. num_entries is a uint16_t and bytes_per_entry is at most
220
  // 255 * 2, so the product cannot overflow.
221
79
  size_t bytes_per_entry = 0;
222
932
  for (uint8_t bd : m_bitDepths) {
223
932
    bytes_per_entry += (bd + 7) / 8;
224
932
  }
225
226
79
  if (static_cast<size_t>(num_entries) * bytes_per_entry > range.get_remaining_bytes()) {
227
12
    return Error(heif_error_Invalid_input,
228
12
                 heif_suberror_End_of_data,
229
12
                 "pclr box declares more entries than the box contains");
230
12
  }
231
232
  // Each entry is stored as its own vector, so the palette costs noticeably
233
  // more memory than the bytes it occupies in the file. Charge that storage
234
  // to the memory limits before allocating it.
235
67
  size_t bytes_per_stored_entry = sizeof(PaletteEntry) + num_palette_columns * sizeof(uint16_t);
236
67
  if (auto err = m_memory_handle.alloc(num_entries, bytes_per_stored_entry,
237
67
                                       limits, "the 'pclr' palette")) {
238
0
    return err;
239
0
  }
240
241
67
  m_entries.reserve(num_entries);
242
243
110
  for (uint16_t j = 0; j < num_entries; j++) {
244
43
    PaletteEntry entry;
245
91
    for (size_t i = 0; i < m_bitDepths.size(); i++) {
246
48
      if (m_bitDepths[i] <= 8) {
247
42
        entry.columns.push_back(range.read8());
248
42
      }
249
6
      else {
250
6
        entry.columns.push_back(range.read16());
251
6
      }
252
48
    }
253
43
    m_entries.push_back(std::move(entry));
254
43
  }
255
256
67
  return range.get_error();
257
67
}
258
259
260
std::string Box_pclr::dump(Indent& indent) const
261
0
{
262
0
  std::ostringstream sstr;
263
0
  sstr << Box::dump(indent);
264
265
0
  sstr << indent << "NE: " << m_entries.size();
266
0
  sstr << ", NPC: " << (int) get_num_columns();
267
0
  sstr << ", B: ";
268
0
  for (uint8_t b : m_bitDepths) {
269
0
    sstr << (int) b << ", ";
270
0
  }
271
  // TODO: maybe dump entries too?
272
0
  sstr << "\n";
273
274
0
  return sstr.str();
275
0
}
276
277
278
Error Box_pclr::write(StreamWriter& writer) const
279
0
{
280
0
  if (get_num_columns() == 0) {
281
    // skip
282
0
    return Error::Ok;
283
0
  }
284
285
0
  size_t box_start = reserve_box_header_space(writer);
286
287
0
  writer.write16(get_num_entries());
288
0
  writer.write8(get_num_columns());
289
0
  for (uint8_t bd : m_bitDepths) {
290
0
    writer.write8(static_cast<uint8_t>(bd - 1));  // B_i stores the precision minus one
291
0
  }
292
0
  for (PaletteEntry entry : m_entries) {
293
0
    for (unsigned long int i = 0; i < entry.columns.size(); i++) {
294
0
      if (m_bitDepths[i] <= 8) {
295
0
        writer.write8((uint8_t) (entry.columns[i]));
296
0
      }
297
0
      else {
298
0
        writer.write16(entry.columns[i]);
299
0
      }
300
0
    }
301
0
  }
302
303
0
  prepend_header(writer, box_start);
304
305
0
  return Error::Ok;
306
0
}
307
308
void Box_pclr::set_columns(uint8_t num_columns, uint8_t bit_depth)
309
0
{
310
0
  m_bitDepths.clear();
311
0
  m_entries.clear();
312
0
  for (int i = 0; i < num_columns; i++) {
313
0
    m_bitDepths.push_back(bit_depth);
314
0
  }
315
0
}
316
317
Error Box_j2kL::parse(BitstreamRange& range, const heif_security_limits* limits)
318
56
{
319
56
  uint16_t layer_count = range.read16();
320
321
56
  if (layer_count > range.get_remaining_bytes() / (2+1+2)) {
322
7
    std::stringstream sstr;
323
7
    sstr << "j2kL box wants to define " << layer_count << "JPEG-2000 layers, but the box only contains "
324
7
         << range.get_remaining_bytes() / (2 + 1 + 2) << " layers entries";
325
7
    return {heif_error_Invalid_input,
326
7
            heif_suberror_End_of_data,
327
7
            sstr.str()};
328
7
  }
329
330
49
  m_layers.resize(layer_count);
331
332
165
  for (int i = 0; i < layer_count && !range.error() && !range.eof(); i++) {
333
116
    Layer layer;
334
116
    layer.layer_id = range.read16();
335
116
    layer.discard_levels = range.read8();
336
116
    layer.decode_layers = range.read16();
337
116
    m_layers[i] = layer;
338
116
  }
339
340
49
  if (range.get_error()) {
341
2
    m_layers.clear();
342
2
  }
343
344
49
  return range.get_error();
345
56
}
346
347
std::string Box_j2kL::dump(Indent& indent) const
348
0
{
349
0
  std::ostringstream sstr;
350
0
  sstr << Box::dump(indent);
351
352
0
  for (const auto& layer : m_layers) {
353
0
    sstr << indent << "layer_id: " << layer.layer_id
354
0
         << ", discard_levels: " << (int) (layer.discard_levels)
355
0
         << ", decode_layers: " << layer.decode_layers << "\n";
356
0
  }
357
358
0
  return sstr.str();
359
0
}
360
361
362
Error Box_j2kL::write(StreamWriter& writer) const
363
0
{
364
0
  size_t box_start = reserve_box_header_space(writer);
365
366
0
  writer.write16((uint16_t) m_layers.size());
367
0
  for (const auto& layer : m_layers) {
368
0
    writer.write16(layer.layer_id);
369
0
    writer.write8(layer.discard_levels);
370
0
    writer.write16(layer.decode_layers);
371
0
  }
372
373
0
  prepend_header(writer, box_start);
374
375
0
  return Error::Ok;
376
0
}
377
378
379
Error Box_j2kH::parse(BitstreamRange& range, const heif_security_limits* limits)
380
86
{
381
86
  return read_children(range, READ_CHILDREN_ALL, limits);
382
86
}
383
384
std::string Box_j2kH::dump(Indent& indent) const
385
0
{
386
0
  std::ostringstream sstr;
387
0
  sstr << Box::dump(indent);
388
389
0
  sstr << dump_children(indent);
390
391
0
  return sstr.str();
392
0
}
393
394
395
Error JPEG2000MainHeader::parseHeader(const std::vector<uint8_t>& compressedImageData)
396
0
{
397
  // TODO: it is very inefficient to store the whole image data when we only need the header
398
399
0
  headerData = compressedImageData;
400
0
  return doParse();
401
0
}
402
403
Error JPEG2000MainHeader::doParse()
404
0
{
405
0
  cursor = 0;
406
0
  Error err = parse_SOC_segment();
407
0
  if (err) {
408
0
    return err;
409
0
  }
410
0
  err = parse_SIZ_segment();
411
0
  if (err) {
412
0
    return err;
413
0
  }
414
0
  if (cursor < headerData.size() - MARKER_LEN) {
415
0
    uint16_t marker = read16();
416
0
    if (marker == JPEG2000_CAP_MARKER) {
417
0
      return parse_CAP_segment_body();
418
0
    }
419
0
    return Error::Ok;
420
0
  }
421
  // we should have at least COD and QCD, so this is probably broken.
422
0
  return Error(heif_error_Invalid_input,
423
0
               heif_suberror_Invalid_J2K_codestream,
424
0
               std::string("Missing required header marker(s)"));
425
0
}
426
427
Error JPEG2000MainHeader::parse_SOC_segment()
428
0
{
429
0
  const size_t REQUIRED_BYTES = MARKER_LEN;
430
0
  if ((headerData.size() < REQUIRED_BYTES) || (cursor > (headerData.size() - REQUIRED_BYTES))) {
431
0
    return Error(heif_error_Invalid_input,
432
0
                 heif_suberror_Invalid_J2K_codestream);
433
0
  }
434
0
  uint16_t marker = read16();
435
0
  if (marker == JPEG2000_SOC_MARKER) {
436
0
    return Error::Ok;
437
0
  }
438
0
  return Error(heif_error_Invalid_input,
439
0
               heif_suberror_Invalid_J2K_codestream,
440
0
               std::string("Missing required SOC Marker"));
441
0
}
442
443
Error JPEG2000MainHeader::parse_SIZ_segment()
444
0
{
445
0
  size_t REQUIRED_BYTES = MARKER_LEN + 38 + 3 * 1;
446
0
  if ((headerData.size() < REQUIRED_BYTES) || (cursor > (headerData.size() - REQUIRED_BYTES))) {
447
0
    return Error(heif_error_Invalid_input,
448
0
                 heif_suberror_Invalid_J2K_codestream);
449
0
  }
450
451
0
  uint16_t marker = read16();
452
0
  if (marker != JPEG2000_SIZ_MARKER) {
453
0
    return Error(heif_error_Invalid_input,
454
0
                 heif_suberror_Invalid_J2K_codestream,
455
0
                 std::string("Missing required SIZ Marker"));
456
0
  }
457
0
  uint16_t lsiz = read16();
458
0
  if ((lsiz < 41) || (lsiz > 49190)) {
459
0
    return Error(heif_error_Invalid_input,
460
0
                 heif_suberror_Invalid_J2K_codestream,
461
0
                 std::string("Out of range Lsiz value"));
462
0
  }
463
0
  siz.decoder_capabilities = read16();
464
0
  siz.reference_grid_width = read32();
465
0
  siz.reference_grid_height = read32();
466
0
  siz.image_horizontal_offset = read32();
467
0
  siz.image_vertical_offset = read32();
468
0
  siz.tile_width = read32();
469
0
  siz.tile_height = read32();
470
0
  siz.tile_offset_x = read32();
471
0
  siz.tile_offset_y = read32();
472
0
  uint16_t csiz = read16();
473
0
  if ((csiz < 1) || (csiz > 16384)) {
474
0
    return Error(heif_error_Invalid_input,
475
0
                 heif_suberror_Invalid_J2K_codestream,
476
0
                 std::string("Out of range Csiz value"));
477
0
  }
478
0
  if (3 * static_cast<size_t>(csiz) > headerData.size() - cursor) {
479
0
    return Error(heif_error_Invalid_input,
480
0
                 heif_suberror_Invalid_J2K_codestream);
481
0
  }
482
  // TODO: consider checking for Lsiz consistent with Csiz
483
0
  for (uint16_t c = 0; c < csiz; c++) {
484
0
    JPEG2000_SIZ_segment::component comp;
485
0
    uint8_t ssiz = read8();
486
0
    comp.is_signed = (ssiz & 0x80);
487
0
    comp.precision = uint8_t((ssiz & 0x7F) + 1);
488
0
    comp.h_separation = read8();
489
0
    comp.v_separation = read8();
490
0
    siz.components.push_back(comp);
491
0
  }
492
0
  return Error::Ok;
493
0
}
494
495
Error JPEG2000MainHeader::parse_CAP_segment_body()
496
0
{
497
  // Need at least Lcap (2) + Pcap (4) = 6 bytes.
498
0
  if (headerData.size() - cursor < 6) {
499
0
    return Error(heif_error_Invalid_input,
500
0
                 heif_suberror_Invalid_J2K_codestream);
501
0
  }
502
0
  uint16_t lcap = read16();
503
0
  if ((lcap < 8) || (lcap > 70)) {
504
0
    return Error(heif_error_Invalid_input,
505
0
                 heif_suberror_Invalid_J2K_codestream,
506
0
                 std::string("Out of range Lcap value"));
507
0
  }
508
  // Lcap counts the Lcap field itself, so Lcap-2 bytes must follow it.
509
0
  if (headerData.size() - cursor < static_cast<size_t>(lcap) - 2) {
510
0
    return Error(heif_error_Invalid_input,
511
0
                 heif_suberror_Invalid_J2K_codestream);
512
0
  }
513
0
  uint32_t pcap = read32();
514
0
  size_t segment_end = cursor + (static_cast<size_t>(lcap) - 6);
515
0
  for (uint8_t i = 2; i <= 32; i++) {
516
0
    if (pcap & (1u << (32 - i))) {
517
0
      if (segment_end - cursor < 2) {
518
0
        return Error(heif_error_Invalid_input,
519
0
                     heif_suberror_Invalid_J2K_codestream,
520
0
                     std::string("CAP segment Pcap inconsistent with Lcap"));
521
0
      }
522
0
      switch (i) {
523
0
        case JPEG2000_Extension_Capability_HT::IDENT:
524
0
          parse_Ccap15();
525
0
          break;
526
0
        default:
527
0
          read16();
528
0
      }
529
0
    }
530
0
  }
531
0
  return Error::Ok;
532
0
}
533
534
void JPEG2000MainHeader::parse_Ccap15()
535
0
{
536
0
  uint16_t val = read16();
537
0
  JPEG2000_Extension_Capability_HT ccap;
538
  // We could parse more here, but we don't need that yet.
539
0
  ccap.setValue(val);
540
0
  cap.push_back(ccap);
541
0
}
542
543
heif_chroma JPEG2000MainHeader::get_chroma_format() const
544
0
{
545
  // Y-plane must be full resolution
546
0
  if (siz.components[0].h_separation != 1 || siz.components[0].v_separation != 1) {
547
0
    return heif_chroma_undefined;
548
0
  }
549
550
0
  if (siz.components.size() == 1) {
551
0
    return heif_chroma_monochrome;
552
0
  }
553
0
  else if (siz.components.size() == 3) {
554
    // TODO: we should map channels through `cdef` ?
555
556
    // both chroma components must have the same sampling
557
0
    if (siz.components[1].h_separation != siz.components[2].h_separation ||
558
0
        siz.components[1].v_separation != siz.components[2].v_separation) {
559
0
      return heif_chroma_undefined;
560
0
    }
561
562
0
    if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==2) {
563
0
      return heif_chroma_420;
564
0
    }
565
0
    if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==1) {
566
0
      return heif_chroma_422;
567
0
    }
568
0
    if (siz.components[1].h_separation == 1 && siz.components[1].v_separation==1) {
569
0
      return heif_chroma_444;
570
0
    }
571
0
  }
572
573
0
  return heif_chroma_undefined;
574
0
}