Coverage Report

Created: 2025-11-14 07:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/region.cc
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2023 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
#include "region.h"
22
#include "error.h"
23
#include "file.h"
24
#include "box.h"
25
#include "libheif/heif_regions.h"
26
#include <algorithm>
27
#include <utility>
28
29
30
Error RegionItem::parse(const std::vector<uint8_t>& data)
31
506
{
32
506
  if (data.size() < 8) {
33
25
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
34
25
                 "Less than 8 bytes of data");
35
25
  }
36
37
481
  uint8_t version = data[0];
38
481
  (void) version; // version is unused
39
40
481
  uint8_t flags = data[1];
41
481
  int field_size = ((flags & 1) ? 32 : 16);
42
43
481
  unsigned int dataOffset;
44
481
  if (field_size == 32) {
45
186
    if (data.size() < 12) {
46
2
      return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
47
2
                   "Region data incomplete");
48
2
    }
49
184
    reference_width =
50
184
        ((data[2] << 24) | (data[3] << 16) | (data[4] << 8) | (data[5]));
51
52
184
    reference_height =
53
184
        ((data[6] << 24) | (data[7] << 16) | (data[8] << 8) | (data[9]));
54
184
    dataOffset = 10;
55
184
  }
56
295
  else {
57
295
    reference_width = ((data[2] << 8) | (data[3]));
58
295
    reference_height = ((data[4] << 8) | (data[5]));
59
295
    dataOffset = 6;
60
295
  }
61
62
479
  uint8_t region_count = data[dataOffset];
63
479
  dataOffset += 1;
64
29.2k
  for (int i = 0; i < region_count; i++) {
65
29.0k
    if (data.size() <= dataOffset) {
66
56
      return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
67
56
                   "Region data incomplete");
68
56
    }
69
70
29.0k
    uint8_t geometry_type = data[dataOffset];
71
29.0k
    dataOffset += 1;
72
73
29.0k
    std::shared_ptr<RegionGeometry> region;
74
75
29.0k
    if (geometry_type == heif_region_type_point) {
76
6.43k
      region = std::make_shared<RegionGeometry_Point>();
77
6.43k
    }
78
22.5k
    else if (geometry_type == heif_region_type_rectangle) {
79
913
      region = std::make_shared<RegionGeometry_Rectangle>();
80
913
    }
81
21.6k
    else if (geometry_type == heif_region_type_ellipse) {
82
692
      region = std::make_shared<RegionGeometry_Ellipse>();
83
692
    }
84
20.9k
    else if (geometry_type == heif_region_type_polygon) {
85
271
      auto polygon = std::make_shared<RegionGeometry_Polygon>();
86
271
      polygon->closed = true;
87
271
      region = polygon;
88
271
    }
89
20.7k
    else if (geometry_type == heif_region_type_referenced_mask) {
90
595
      region = std::make_shared<RegionGeometry_ReferencedMask>();
91
595
    }
92
20.1k
    else if (geometry_type == heif_region_type_inline_mask) {
93
170
      region = std::make_shared<RegionGeometry_InlineMask>();
94
170
    }
95
19.9k
    else if (geometry_type == heif_region_type_polyline) {
96
181
      auto polygon = std::make_shared<RegionGeometry_Polygon>();
97
181
      polygon->closed = false;
98
181
      region = polygon;
99
181
    }
100
19.7k
    else {
101
      //     // TODO: this isn't going to work - we can only exit here.
102
      //   std::cout << "ignoring unsupported region geometry type: "
103
      //             << (int)geometry_type << std::endl;
104
105
19.7k
      continue;
106
19.7k
    }
107
108
9.26k
    Error error = region->parse(data, field_size, &dataOffset);
109
9.26k
    if (error) {
110
290
      return error;
111
290
    }
112
113
8.97k
    mRegions.push_back(region);
114
8.97k
  }
115
133
  return Error::Ok;
116
479
}
117
118
Error RegionItem::encode(std::vector<uint8_t>& result) const
119
0
{
120
0
  StreamWriter writer;
121
122
0
  writer.write8(0);
123
124
  // --- compute required field size
125
126
0
  int field_size_bytes = 2;
127
128
0
  if (reference_width <= 0xFFFF &&
129
0
      reference_height <= 0xFFFF) {
130
0
    field_size_bytes = 4;
131
0
  }
132
133
0
  if (field_size_bytes != 4) {
134
0
    for (auto& region : mRegions) {
135
0
      if (region->encode_needs_32bit()) {
136
0
        field_size_bytes = 4;
137
0
        break;
138
0
      }
139
0
    }
140
0
  }
141
142
  // --- write flags
143
144
0
  uint8_t flags = 0;
145
146
0
  if (field_size_bytes == 4) {
147
0
    flags |= 1;
148
0
  }
149
150
0
  writer.write8(flags);
151
152
  // --- write reference size
153
154
0
  writer.write(field_size_bytes, reference_width);
155
0
  writer.write(field_size_bytes, reference_height);
156
157
  // --- write regions
158
159
0
  if (mRegions.size() >= 256) {
160
0
    return Error(heif_error_Encoding_error, heif_suberror_Too_many_regions);
161
0
  }
162
163
0
  writer.write8((uint8_t) mRegions.size());
164
165
0
  for (auto& region : mRegions) {
166
0
    region->encode(writer, field_size_bytes);
167
0
  }
168
169
0
  result = writer.get_data();
170
171
0
  return Error::Ok;
172
0
}
173
174
175
uint32_t RegionGeometry::parse_unsigned(const std::vector<uint8_t>& data,
176
                                        int field_size,
177
                                        unsigned int* dataOffset)
178
33.6k
{
179
33.6k
  uint32_t x;
180
33.6k
  if (field_size == 32) {
181
10.3k
    x = ((data[*dataOffset] << 24) | (data[*dataOffset + 1] << 16) |
182
10.3k
         (data[*dataOffset + 2] << 8) | (data[*dataOffset + 3]));
183
10.3k
    *dataOffset = *dataOffset + 4;
184
10.3k
  }
185
23.2k
  else {
186
23.2k
    x = ((data[*dataOffset] << 8) | (data[*dataOffset + 1]));
187
23.2k
    *dataOffset = *dataOffset + 2;
188
23.2k
  }
189
33.6k
  return x;
190
33.6k
}
191
192
int32_t RegionGeometry::parse_signed(const std::vector<uint8_t>& data,
193
                                     int field_size,
194
                                     unsigned int* dataOffset)
195
28.5k
{
196
28.5k
  if (field_size == 32) {
197
8.50k
    return (int32_t)parse_unsigned(data, field_size, dataOffset);
198
20.0k
  } else {
199
20.0k
    return (int16_t)parse_unsigned(data, field_size, dataOffset);
200
20.0k
  }
201
28.5k
}
202
203
Error RegionGeometry_Point::parse(const std::vector<uint8_t>& data,
204
                                  int field_size,
205
                                  unsigned int* dataOffset)
206
6.43k
{
207
6.43k
  unsigned int bytesRequired = (field_size / 8) * 2;
208
6.43k
  if (data.size() - *dataOffset < bytesRequired) {
209
71
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
210
71
                 "Insufficient data remaining for point region");
211
71
  }
212
6.36k
  x = parse_signed(data, field_size, dataOffset);
213
6.36k
  y = parse_signed(data, field_size, dataOffset);
214
215
6.36k
  return Error::Ok;
216
6.43k
}
217
218
219
static bool exceeds_s16(int32_t v)
220
0
{
221
0
  return (v > 32767 || v < -32768);
222
0
}
223
224
static bool exceeds_u16(uint32_t v)
225
0
{
226
0
  return v > 0xFFFF;
227
0
}
228
229
230
bool RegionGeometry_Point::encode_needs_32bit() const
231
0
{
232
0
  return exceeds_s16(x) || exceeds_s16(y);
233
0
}
234
235
236
void RegionGeometry_Point::encode(StreamWriter& writer, int field_size_bytes) const
237
0
{
238
0
  writer.write8(heif_region_type_point);
239
0
  writer.write(field_size_bytes, x);
240
0
  writer.write(field_size_bytes, y);
241
0
}
242
243
244
Error RegionGeometry_Rectangle::parse(const std::vector<uint8_t>& data,
245
                                      int field_size,
246
                                      unsigned int* dataOffset)
247
913
{
248
913
  unsigned int bytesRequired = (field_size / 8) * 4;
249
913
  if (data.size() - *dataOffset < bytesRequired) {
250
23
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
251
23
                 "Insufficient data remaining for rectangle region");
252
23
  }
253
890
  x = parse_signed(data, field_size, dataOffset);
254
890
  y = parse_signed(data, field_size, dataOffset);
255
890
  width = parse_unsigned(data, field_size, dataOffset);
256
890
  height = parse_unsigned(data, field_size, dataOffset);
257
890
  return Error::Ok;
258
913
}
259
260
261
bool RegionGeometry_Rectangle::encode_needs_32bit() const
262
0
{
263
0
  return exceeds_s16(x) || exceeds_s16(y) || exceeds_u16(width) || exceeds_u16(height);
264
0
}
265
266
267
void RegionGeometry_Rectangle::encode(StreamWriter& writer, int field_size_bytes) const
268
0
{
269
0
  writer.write8(heif_region_type_rectangle);
270
0
  writer.write(field_size_bytes, x);
271
0
  writer.write(field_size_bytes, y);
272
0
  writer.write(field_size_bytes, width);
273
0
  writer.write(field_size_bytes, height);
274
0
}
275
276
Error RegionGeometry_Ellipse::parse(const std::vector<uint8_t>& data,
277
                                    int field_size,
278
                                    unsigned int* dataOffset)
279
692
{
280
692
  unsigned int bytesRequired = (field_size / 8) * 4;
281
692
  if (data.size() - *dataOffset < bytesRequired) {
282
23
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
283
23
                 "Insufficient data remaining for ellipse region");
284
23
  }
285
669
  x = parse_signed(data, field_size, dataOffset);
286
669
  y = parse_signed(data, field_size, dataOffset);
287
669
  radius_x = parse_unsigned(data, field_size, dataOffset);
288
669
  radius_y = parse_unsigned(data, field_size, dataOffset);
289
669
  return Error::Ok;
290
692
}
291
292
bool RegionGeometry_Ellipse::encode_needs_32bit() const
293
0
{
294
0
  return exceeds_s16(x) || exceeds_s16(y) || exceeds_u16(radius_x) || exceeds_u16(radius_y);
295
0
}
296
297
298
void RegionGeometry_Ellipse::encode(StreamWriter& writer, int field_size_bytes) const
299
0
{
300
0
  writer.write8(heif_region_type_ellipse);
301
0
  writer.write(field_size_bytes, x);
302
0
  writer.write(field_size_bytes, y);
303
0
  writer.write(field_size_bytes, radius_x);
304
0
  writer.write(field_size_bytes, radius_y);
305
0
}
306
307
308
309
Error RegionGeometry_Polygon::parse(const std::vector<uint8_t>& data,
310
                                    int field_size,
311
                                    unsigned int* dataOffset)
312
452
{
313
452
  uint32_t bytesRequired1 = (field_size / 8) * 1;
314
452
  if (data.size() - *dataOffset < bytesRequired1) {
315
13
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
316
13
                 "Insufficient data remaining for polygon");
317
13
  }
318
319
  // Note: we need to do the calculation in uint64_t because numPoints may be any 32-bit number
320
  // and it is multiplied by (at most) 8.
321
322
439
  uint32_t numPoints = parse_unsigned(data, field_size, dataOffset);
323
439
  uint64_t bytesRequired2 = (field_size / 8) * uint64_t(numPoints) * 2;
324
439
  if (data.size() - *dataOffset < bytesRequired2) {
325
83
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
326
83
                 "Insufficient data remaining for polygon");
327
83
  }
328
329
5.97k
  for (uint32_t i = 0; i < numPoints; i++) {
330
5.61k
    Point p;
331
5.61k
    p.x = parse_signed(data, field_size, dataOffset);
332
5.61k
    p.y = parse_signed(data, field_size, dataOffset);
333
5.61k
    points.push_back(p);
334
5.61k
  }
335
336
356
  return Error::Ok;
337
439
}
338
339
340
Error RegionGeometry_ReferencedMask::parse(const std::vector<uint8_t>& data,
341
                                          int field_size,
342
                                          unsigned int* dataOffset)
343
595
{
344
595
  unsigned int bytesRequired = (field_size / 8) * 4;
345
595
  if (data.size() - *dataOffset < bytesRequired) {
346
17
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
347
17
                 "Insufficient data remaining for referenced mask region");
348
17
  }
349
578
  x = parse_signed(data, field_size, dataOffset);
350
578
  y = parse_signed(data, field_size, dataOffset);
351
578
  width = parse_unsigned(data, field_size, dataOffset);
352
578
  height = parse_unsigned(data, field_size, dataOffset);
353
578
  return Error::Ok;
354
595
}
355
356
357
void RegionGeometry_ReferencedMask::encode(StreamWriter& writer, int field_size_bytes) const
358
0
{
359
0
  writer.write8(heif_region_type_referenced_mask);
360
0
  writer.write(field_size_bytes, x);
361
0
  writer.write(field_size_bytes, y);
362
0
  writer.write(field_size_bytes, width);
363
0
  writer.write(field_size_bytes, height);
364
0
}
365
366
bool RegionGeometry_Polygon::encode_needs_32bit() const
367
0
{
368
0
  if (exceeds_u16((uint32_t)points.size())) {
369
0
    return true;
370
0
  }
371
372
0
  for (auto& p : points) {
373
0
    if (exceeds_s16(p.x) || exceeds_s16(p.y)) {
374
0
      return true;
375
0
    }
376
0
  }
377
378
0
  return false;
379
0
}
380
381
382
void RegionGeometry_Polygon::encode(StreamWriter& writer, int field_size_bytes) const
383
0
{
384
0
  writer.write8(closed ? heif_region_type_polygon : heif_region_type_polyline);
385
386
0
  writer.write(field_size_bytes, points.size());
387
388
0
  for (auto& p : points) {
389
0
    writer.write(field_size_bytes, p.x);
390
0
    writer.write(field_size_bytes, p.y);
391
0
  }
392
0
}
393
394
395
Error RegionGeometry_InlineMask::parse(const std::vector<uint8_t>& data,
396
                                       int field_size,
397
                                       unsigned int* dataOffset)
398
170
{
399
170
  unsigned int bytesRequired = (field_size / 8) * 4 + 1;
400
170
  if (data.size() - *dataOffset < bytesRequired) {
401
5
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
402
5
                 "Insufficient data remaining for inline mask region");
403
5
  }
404
165
  x = parse_signed(data, field_size, dataOffset);
405
165
  y = parse_signed(data, field_size, dataOffset);
406
165
  width = parse_unsigned(data, field_size, dataOffset);
407
165
  height = parse_unsigned(data, field_size, dataOffset);
408
165
  uint8_t mask_coding_method = data[*dataOffset];
409
165
  *dataOffset = *dataOffset + 1;
410
165
  if (mask_coding_method != 0) {
411
21
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
412
21
                 "Deflate compressed inline mask is not yet supported");
413
21
  }
414
144
  unsigned int additionalBytesRequired = width * height / 8;
415
144
  if (data.size() - *dataOffset < additionalBytesRequired) {
416
34
        return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
417
34
                 "Insufficient data remaining for inline mask region data[]");
418
34
  }
419
110
  mask_data.resize(additionalBytesRequired);
420
110
  std::copy(data.begin() + *dataOffset, data.begin() + *dataOffset + additionalBytesRequired, mask_data.begin());
421
110
  return Error::Ok;
422
144
}
423
424
425
void RegionGeometry_InlineMask::encode(StreamWriter& writer, int field_size_bytes) const
426
0
{
427
0
  writer.write8(heif_region_type_inline_mask);
428
0
  writer.write(field_size_bytes, x);
429
0
  writer.write(field_size_bytes, y);
430
0
  writer.write(field_size_bytes, width);
431
0
  writer.write(field_size_bytes, height);
432
0
  writer.write8(0); // coding method
433
  // if using some other coding method, there are parameters to write out here.
434
0
  writer.write(mask_data);
435
0
}
436
437
438
RegionCoordinateTransform RegionCoordinateTransform::create(std::shared_ptr<HeifFile> file,
439
                                                            heif_item_id item_id,
440
                                                            int reference_width, int reference_height)
441
0
{
442
0
  std::vector<std::shared_ptr<Box>> properties;
443
444
0
  Error err = file->get_properties(item_id, properties);
445
0
  if (err) {
446
0
    return {};
447
0
  }
448
449
0
  uint32_t image_width = 0, image_height = 0;
450
451
0
  for (auto& property : properties) {
452
0
    if (auto ispe = std::dynamic_pointer_cast<Box_ispe>(property)) {
453
0
      image_width = ispe->get_width();
454
0
      image_height = ispe->get_height();
455
0
      break;
456
0
    }
457
0
  }
458
459
0
  if (image_width == 0 || image_height == 0) {
460
0
    return {};
461
0
  }
462
463
0
  RegionCoordinateTransform transform;
464
0
  transform.a = image_width / (double) reference_width;
465
0
  transform.d = image_height / (double) reference_height;
466
467
0
  for (auto& property : properties) {
468
0
    switch (property->get_short_type()) {
469
0
      case fourcc("imir"): {
470
0
        auto imir = std::dynamic_pointer_cast<Box_imir>(property);
471
0
        if (imir->get_mirror_direction() == heif_transform_mirror_direction_horizontal) {
472
0
          transform.a = -transform.a;
473
0
          transform.b = -transform.b;
474
0
          transform.tx = image_width - 1 - transform.tx;
475
0
        }
476
0
        else {
477
0
          transform.c = -transform.c;
478
0
          transform.d = -transform.d;
479
0
          transform.ty = image_height - 1 - transform.ty;
480
0
        }
481
0
        break;
482
0
      }
483
0
      case fourcc("irot"): {
484
0
        auto irot = std::dynamic_pointer_cast<Box_irot>(property);
485
0
        RegionCoordinateTransform tmp;
486
0
        switch (irot->get_rotation_ccw()) {
487
0
          case 90:
488
0
            tmp.a = transform.c;
489
0
            tmp.b = transform.d;
490
0
            tmp.c = -transform.a;
491
0
            tmp.d = -transform.b;
492
0
            tmp.tx = transform.ty;
493
0
            tmp.ty = -transform.tx + image_width - 1;
494
0
            transform = tmp;
495
0
            std::swap(image_width, image_height);
496
0
            break;
497
0
          case 180:
498
0
            transform.a = -transform.a;
499
0
            transform.b = -transform.b;
500
0
            transform.tx = image_width - 1 - transform.tx;
501
0
            transform.c = -transform.c;
502
0
            transform.d = -transform.d;
503
0
            transform.ty = image_height - 1 - transform.ty;
504
0
            break;
505
0
          case 270:
506
0
            tmp.a = -transform.c;
507
0
            tmp.b = -transform.d;
508
0
            tmp.c = transform.a;
509
0
            tmp.d = transform.b;
510
0
            tmp.tx = -transform.ty + image_height - 1;
511
0
            tmp.ty = transform.tx;
512
0
            transform = tmp;
513
0
            std::swap(image_width, image_height);
514
0
            break;
515
0
          default:
516
0
            break;
517
0
        }
518
0
        break;
519
0
      }
520
0
      case fourcc("clap"): {
521
0
        auto clap = std::dynamic_pointer_cast<Box_clap>(property);
522
0
        int left = clap->left_rounded(image_width);
523
0
        int top = clap->top_rounded(image_height);
524
0
        transform.tx -= left;
525
0
        transform.ty -= top;
526
0
        image_width = clap->get_width_rounded();
527
0
        image_height = clap->get_height_rounded();
528
0
        break;
529
0
      }
530
0
      default:
531
0
        break;
532
0
    }
533
0
  }
534
535
0
  return transform;
536
0
}
537
538
539
RegionCoordinateTransform::Point RegionCoordinateTransform::transform_point(Point p)
540
0
{
541
0
  Point newp;
542
0
  newp.x = p.x * a + p.x * b + tx;
543
0
  newp.y = p.x * c + p.y * d + ty;
544
0
  return newp;
545
0
}
546
547
548
RegionCoordinateTransform::Extent RegionCoordinateTransform::transform_extent(Extent e)
549
0
{
550
0
  Extent newe;
551
0
  newe.x = e.x * a + e.y * b;
552
0
  newe.y = e.x * c + e.y * d;
553
0
  return newe;
554
0
}
555
556
557