Coverage Report

Created: 2026-06-16 07:20

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
#include <limits>
29
30
31
Error RegionItem::parse(const std::vector<uint8_t>& data,
32
                        const heif_security_limits* limits)
33
1.76k
{
34
1.76k
  if (data.size() < 8) {
35
83
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
36
83
                 "Less than 8 bytes of data");
37
83
  }
38
39
1.67k
  uint8_t version = data[0];
40
1.67k
  (void) version; // version is unused
41
42
1.67k
  uint8_t flags = data[1];
43
1.67k
  int field_size = ((flags & 1) ? 32 : 16);
44
45
1.67k
  unsigned int dataOffset;
46
1.67k
  if (field_size == 32) {
47
608
    if (data.size() < 12) {
48
12
      return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
49
12
                   "Region data incomplete");
50
12
    }
51
596
    reference_width = four_bytes_to_uint32(data[2], data[3], data[4], data[5]);
52
596
    reference_height = four_bytes_to_uint32(data[6], data[7], data[8], data[9]);
53
596
    dataOffset = 10;
54
596
  }
55
1.07k
  else {
56
1.07k
    reference_width = four_bytes_to_uint32(0, 0, data[2], data[3]);
57
1.07k
    reference_height = four_bytes_to_uint32(0, 0, data[4], data[5]);
58
1.07k
    dataOffset = 6;
59
1.07k
  }
60
61
1.66k
  uint8_t region_count = data[dataOffset];
62
1.66k
  dataOffset += 1;
63
94.8k
  for (int i = 0; i < region_count; i++) {
64
94.2k
    if (data.size() <= dataOffset) {
65
98
      return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
66
98
                   "Region data incomplete");
67
98
    }
68
69
94.1k
    uint8_t geometry_type = data[dataOffset];
70
94.1k
    dataOffset += 1;
71
72
94.1k
    std::shared_ptr<RegionGeometry> region;
73
74
94.1k
    if (geometry_type == heif_region_type_point) {
75
16.7k
      region = std::make_shared<RegionGeometry_Point>();
76
16.7k
    }
77
77.4k
    else if (geometry_type == heif_region_type_rectangle) {
78
2.12k
      region = std::make_shared<RegionGeometry_Rectangle>();
79
2.12k
    }
80
75.3k
    else if (geometry_type == heif_region_type_ellipse) {
81
1.75k
      region = std::make_shared<RegionGeometry_Ellipse>();
82
1.75k
    }
83
73.5k
    else if (geometry_type == heif_region_type_polygon) {
84
535
      auto polygon = std::make_shared<RegionGeometry_Polygon>();
85
535
      polygon->closed = true;
86
535
      region = polygon;
87
535
    }
88
73.0k
    else if (geometry_type == heif_region_type_referenced_mask) {
89
3.34k
      region = std::make_shared<RegionGeometry_ReferencedMask>();
90
3.34k
    }
91
69.6k
    else if (geometry_type == heif_region_type_inline_mask) {
92
383
      region = std::make_shared<RegionGeometry_InlineMask>();
93
383
    }
94
69.2k
    else if (geometry_type == heif_region_type_polyline) {
95
376
      auto polygon = std::make_shared<RegionGeometry_Polygon>();
96
376
      polygon->closed = false;
97
376
      region = polygon;
98
376
    }
99
68.9k
    else {
100
      //     // TODO: this isn't going to work - we can only exit here.
101
      //   std::cout << "ignoring unsupported region geometry type: "
102
      //             << (int)geometry_type << std::endl;
103
104
68.9k
      continue;
105
68.9k
    }
106
107
25.2k
    Error error = region->parse(data, field_size, &dataOffset, limits);
108
25.2k
    if (error) {
109
956
      return error;
110
956
    }
111
112
24.2k
    mRegions.push_back(region);
113
24.2k
  }
114
613
  return Error::Ok;
115
1.66k
}
116
117
Error RegionItem::encode(std::vector<uint8_t>& result) const
118
0
{
119
0
  StreamWriter writer;
120
121
0
  writer.write8(0);
122
123
  // --- compute required field size
124
125
0
  int field_size_bytes = 2;
126
127
0
  if (reference_width <= 0xFFFF &&
128
0
      reference_height <= 0xFFFF) {
129
0
    field_size_bytes = 4;
130
0
  }
131
132
0
  if (field_size_bytes != 4) {
133
0
    for (auto& region : mRegions) {
134
0
      if (region->encode_needs_32bit()) {
135
0
        field_size_bytes = 4;
136
0
        break;
137
0
      }
138
0
    }
139
0
  }
140
141
  // --- write flags
142
143
0
  uint8_t flags = 0;
144
145
0
  if (field_size_bytes == 4) {
146
0
    flags |= 1;
147
0
  }
148
149
0
  writer.write8(flags);
150
151
  // --- write reference size
152
153
0
  writer.write(field_size_bytes, reference_width);
154
0
  writer.write(field_size_bytes, reference_height);
155
156
  // --- write regions
157
158
0
  if (mRegions.size() >= 256) {
159
0
    return Error(heif_error_Encoding_error, heif_suberror_Too_many_regions);
160
0
  }
161
162
0
  writer.write8((uint8_t) mRegions.size());
163
164
0
  for (auto& region : mRegions) {
165
0
    region->encode(writer, field_size_bytes);
166
0
  }
167
168
0
  result = writer.get_data();
169
170
0
  return Error::Ok;
171
0
}
172
173
174
uint32_t RegionGeometry::parse_unsigned(const std::vector<uint8_t>& data,
175
                                        int field_size,
176
                                        unsigned int* dataOffset)
177
535k
{
178
535k
  uint32_t x;
179
535k
  if (field_size == 32) {
180
25.3k
    x = four_bytes_to_uint32(data[*dataOffset + 0],
181
25.3k
                             data[*dataOffset + 1],
182
25.3k
                             data[*dataOffset + 2],
183
25.3k
                             data[*dataOffset + 3]);
184
25.3k
    *dataOffset = *dataOffset + 4;
185
25.3k
  }
186
510k
  else {
187
510k
    x = four_bytes_to_uint32(0, 0, data[*dataOffset], data[*dataOffset + 1]);
188
510k
    *dataOffset = *dataOffset + 2;
189
510k
  }
190
535k
  return x;
191
535k
}
192
193
int32_t RegionGeometry::parse_signed(const std::vector<uint8_t>& data,
194
                                     int field_size,
195
                                     unsigned int* dataOffset)
196
519k
{
197
519k
  if (field_size == 32) {
198
20.5k
    return (int32_t)parse_unsigned(data, field_size, dataOffset);
199
499k
  } else {
200
499k
    return (int16_t)parse_unsigned(data, field_size, dataOffset);
201
499k
  }
202
519k
}
203
204
Error RegionGeometry_Point::parse(const std::vector<uint8_t>& data,
205
                                  int field_size,
206
                                  unsigned int* dataOffset,
207
                                  const heif_security_limits* limits)
208
16.7k
{
209
16.7k
  unsigned int bytesRequired = (field_size / 8) * 2;
210
16.7k
  if (data.size() - *dataOffset < bytesRequired) {
211
106
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
212
106
                 "Insufficient data remaining for point region");
213
106
  }
214
16.6k
  x = parse_signed(data, field_size, dataOffset);
215
16.6k
  y = parse_signed(data, field_size, dataOffset);
216
217
16.6k
  return Error::Ok;
218
16.7k
}
219
220
221
static bool exceeds_s16(int32_t v)
222
0
{
223
0
  return (v > 32767 || v < -32768);
224
0
}
225
226
static bool exceeds_u16(uint32_t v)
227
0
{
228
0
  return v > 0xFFFF;
229
0
}
230
231
232
bool RegionGeometry_Point::encode_needs_32bit() const
233
0
{
234
0
  return exceeds_s16(x) || exceeds_s16(y);
235
0
}
236
237
238
void RegionGeometry_Point::encode(StreamWriter& writer, int field_size_bytes) const
239
0
{
240
0
  writer.write8(heif_region_type_point);
241
0
  writer.write(field_size_bytes, x);
242
0
  writer.write(field_size_bytes, y);
243
0
}
244
245
246
Error RegionGeometry_Rectangle::parse(const std::vector<uint8_t>& data,
247
                                      int field_size,
248
                                      unsigned int* dataOffset,
249
                                      const heif_security_limits* limits)
250
2.12k
{
251
2.12k
  unsigned int bytesRequired = (field_size / 8) * 4;
252
2.12k
  if (data.size() - *dataOffset < bytesRequired) {
253
24
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
254
24
                 "Insufficient data remaining for rectangle region");
255
24
  }
256
2.10k
  x = parse_signed(data, field_size, dataOffset);
257
2.10k
  y = parse_signed(data, field_size, dataOffset);
258
2.10k
  width = parse_unsigned(data, field_size, dataOffset);
259
2.10k
  height = parse_unsigned(data, field_size, dataOffset);
260
2.10k
  return Error::Ok;
261
2.12k
}
262
263
264
bool RegionGeometry_Rectangle::encode_needs_32bit() const
265
0
{
266
0
  return exceeds_s16(x) || exceeds_s16(y) || exceeds_u16(width) || exceeds_u16(height);
267
0
}
268
269
270
void RegionGeometry_Rectangle::encode(StreamWriter& writer, int field_size_bytes) const
271
0
{
272
0
  writer.write8(heif_region_type_rectangle);
273
0
  writer.write(field_size_bytes, x);
274
0
  writer.write(field_size_bytes, y);
275
0
  writer.write(field_size_bytes, width);
276
0
  writer.write(field_size_bytes, height);
277
0
}
278
279
Error RegionGeometry_Ellipse::parse(const std::vector<uint8_t>& data,
280
                                    int field_size,
281
                                    unsigned int* dataOffset,
282
                                    const heif_security_limits* limits)
283
1.75k
{
284
1.75k
  unsigned int bytesRequired = (field_size / 8) * 4;
285
1.75k
  if (data.size() - *dataOffset < bytesRequired) {
286
42
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
287
42
                 "Insufficient data remaining for ellipse region");
288
42
  }
289
1.71k
  x = parse_signed(data, field_size, dataOffset);
290
1.71k
  y = parse_signed(data, field_size, dataOffset);
291
1.71k
  radius_x = parse_unsigned(data, field_size, dataOffset);
292
1.71k
  radius_y = parse_unsigned(data, field_size, dataOffset);
293
1.71k
  return Error::Ok;
294
1.75k
}
295
296
bool RegionGeometry_Ellipse::encode_needs_32bit() const
297
0
{
298
0
  return exceeds_s16(x) || exceeds_s16(y) || exceeds_u16(radius_x) || exceeds_u16(radius_y);
299
0
}
300
301
302
void RegionGeometry_Ellipse::encode(StreamWriter& writer, int field_size_bytes) const
303
0
{
304
0
  writer.write8(heif_region_type_ellipse);
305
0
  writer.write(field_size_bytes, x);
306
0
  writer.write(field_size_bytes, y);
307
0
  writer.write(field_size_bytes, radius_x);
308
0
  writer.write(field_size_bytes, radius_y);
309
0
}
310
311
312
313
Error RegionGeometry_Polygon::parse(const std::vector<uint8_t>& data,
314
                                    int field_size,
315
                                    unsigned int* dataOffset,
316
                                    const heif_security_limits* limits)
317
911
{
318
911
  uint32_t bytesRequired1 = (field_size / 8) * 1;
319
911
  if (data.size() - *dataOffset < bytesRequired1) {
320
12
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
321
12
                 "Insufficient data remaining for polygon");
322
12
  }
323
324
  // Note: we need to do the calculation in uint64_t because numPoints may be any 32-bit number
325
  // and it is multiplied by (at most) 8.
326
327
899
  uint32_t numPoints = parse_unsigned(data, field_size, dataOffset);
328
899
  uint64_t bytesRequired2 = (field_size / 8) * uint64_t(numPoints) * 2;
329
899
  if (data.size() - *dataOffset < bytesRequired2) {
330
376
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
331
376
                 "Insufficient data remaining for polygon");
332
376
  }
333
334
523
  if (closed) {
335
292
    if (numPoints < 3) {
336
88
      return {
337
88
        heif_error_Invalid_input,
338
88
        heif_suberror_Unspecified,
339
88
        "Region polygon with less than 3 points."
340
88
      };
341
88
    }
342
292
  }
343
231
  else {
344
231
    if (numPoints < 2) {
345
55
      return {
346
55
        heif_error_Invalid_input,
347
55
        heif_suberror_Unspecified,
348
55
        "Region polyline with less than 2 points."
349
55
      };
350
55
    }
351
231
  }
352
353
380
  if (UINT32_MAX / numPoints < sizeof(Point)) {
354
0
    return {
355
0
      heif_error_Memory_allocation_error,
356
0
      heif_suberror_Unspecified,
357
0
      "Region polygon size exceeds integer range."
358
0
    };
359
0
  }
360
361
380
  if (auto err = m_memory_handle.alloc(numPoints, sizeof(Point), limits, "region polygon")) {
362
0
    return err;
363
0
  }
364
365
236k
  for (uint32_t i = 0; i < numPoints; i++) {
366
235k
    Point p;
367
235k
    p.x = parse_signed(data, field_size, dataOffset);
368
235k
    p.y = parse_signed(data, field_size, dataOffset);
369
235k
    points.push_back(p);
370
235k
  }
371
372
380
  return Error::Ok;
373
380
}
374
375
376
Error RegionGeometry_ReferencedMask::parse(const std::vector<uint8_t>& data,
377
                                          int field_size,
378
                                          unsigned int* dataOffset,
379
                                          const heif_security_limits* limits)
380
3.34k
{
381
3.34k
  unsigned int bytesRequired = (field_size / 8) * 4;
382
3.34k
  if (data.size() - *dataOffset < bytesRequired) {
383
13
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
384
13
                 "Insufficient data remaining for referenced mask region");
385
13
  }
386
3.33k
  x = parse_signed(data, field_size, dataOffset);
387
3.33k
  y = parse_signed(data, field_size, dataOffset);
388
3.33k
  width = parse_unsigned(data, field_size, dataOffset);
389
3.33k
  height = parse_unsigned(data, field_size, dataOffset);
390
3.33k
  return Error::Ok;
391
3.34k
}
392
393
394
void RegionGeometry_ReferencedMask::encode(StreamWriter& writer, int field_size_bytes) const
395
0
{
396
0
  writer.write8(heif_region_type_referenced_mask);
397
0
  writer.write(field_size_bytes, x);
398
0
  writer.write(field_size_bytes, y);
399
0
  writer.write(field_size_bytes, width);
400
0
  writer.write(field_size_bytes, height);
401
0
}
402
403
bool RegionGeometry_Polygon::encode_needs_32bit() const
404
0
{
405
0
  if (exceeds_u16((uint32_t)points.size())) {
406
0
    return true;
407
0
  }
408
409
0
  for (auto& p : points) {
410
0
    if (exceeds_s16(p.x) || exceeds_s16(p.y)) {
411
0
      return true;
412
0
    }
413
0
  }
414
415
0
  return false;
416
0
}
417
418
419
void RegionGeometry_Polygon::encode(StreamWriter& writer, int field_size_bytes) const
420
0
{
421
0
  writer.write8(closed ? heif_region_type_polygon : heif_region_type_polyline);
422
423
0
  writer.write(field_size_bytes, points.size());
424
425
0
  for (auto& p : points) {
426
0
    writer.write(field_size_bytes, p.x);
427
0
    writer.write(field_size_bytes, p.y);
428
0
  }
429
0
}
430
431
432
Error RegionGeometry_InlineMask::parse(const std::vector<uint8_t>& data,
433
                                       int field_size,
434
                                       unsigned int* dataOffset,
435
                                       const heif_security_limits* limits)
436
383
{
437
383
  unsigned int bytesRequired = (field_size / 8) * 4 + 1;
438
383
  if (data.size() - *dataOffset < bytesRequired) {
439
10
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
440
10
                 "Insufficient data remaining for inline mask region");
441
10
  }
442
373
  x = parse_signed(data, field_size, dataOffset);
443
373
  y = parse_signed(data, field_size, dataOffset);
444
373
  width = parse_unsigned(data, field_size, dataOffset);
445
373
  height = parse_unsigned(data, field_size, dataOffset);
446
373
  uint8_t mask_coding_method = data[*dataOffset];
447
373
  *dataOffset = *dataOffset + 1;
448
449
373
  if (mask_coding_method != 0) {
450
133
    return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
451
133
                 "Deflate compressed inline mask is not yet supported");
452
133
  }
453
454
240
  if (width==0 || height==0) {
455
44
    return {
456
44
      heif_error_Invalid_input,
457
44
      heif_suberror_Unspecified,
458
44
      "Zero size mask image."
459
44
    };
460
44
  }
461
462
  // Mask is stored with one bit per pixel, no padding exists at the end of the line.
463
  // Only the very last byte is padded.
464
465
  // error if:
466
  // (width * height + 7) / 8 > UINT32_MAX
467
  // more strict:
468
  // (width * height + height) / 8 > UINT32_MAX
469
  // width/8 * height + 1 > UINT32_MAX
470
471
196
  uint64_t bytes_for_mask = (static_cast<uint64_t>(width) * height + 7) / 8;
472
196
  if (bytes_for_mask > std::numeric_limits<ptrdiff_t>::max()) {
473
0
    return {
474
0
      heif_error_Memory_allocation_error,
475
0
      heif_suberror_Unspecified,
476
0
      "Mask image size exceeds maximum integer range."
477
0
    };
478
0
  }
479
480
196
  if (limits->max_image_size_pixels / width < height) {
481
39
    return {
482
39
      heif_error_Memory_allocation_error,
483
39
      heif_suberror_Security_limit_exceeded,
484
39
      "Inline mask image exceeds maximum image size."
485
39
    };
486
39
  }
487
488
157
  if (data.size() - *dataOffset < bytes_for_mask) {
489
14
        return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data,
490
14
                 "Insufficient data remaining for inline mask region data[]");
491
14
  }
492
493
143
  if (auto err = m_memory_handle.alloc(bytes_for_mask, limits, "region mask")) {
494
0
    return err;
495
0
  }
496
497
143
  mask_data.resize(bytes_for_mask);
498
143
  std::copy(data.begin() + *dataOffset, data.begin() + *dataOffset + static_cast<ptrdiff_t>(bytes_for_mask), mask_data.begin());
499
143
  *dataOffset += static_cast<unsigned int>(bytes_for_mask);
500
143
  return Error::Ok;
501
143
}
502
503
504
void RegionGeometry_InlineMask::encode(StreamWriter& writer, int field_size_bytes) const
505
0
{
506
0
  writer.write8(heif_region_type_inline_mask);
507
0
  writer.write(field_size_bytes, x);
508
0
  writer.write(field_size_bytes, y);
509
0
  writer.write(field_size_bytes, width);
510
0
  writer.write(field_size_bytes, height);
511
0
  writer.write8(0); // coding method
512
  // if using some other coding method, there are parameters to write out here.
513
0
  writer.write(mask_data);
514
0
}
515
516
517
RegionCoordinateTransform RegionCoordinateTransform::create(std::shared_ptr<HeifFile> file,
518
                                                            heif_item_id item_id,
519
                                                            int reference_width, int reference_height)
520
0
{
521
0
  std::vector<std::shared_ptr<Box>> properties;
522
523
0
  Error err = file->get_properties(item_id, properties);
524
0
  if (err) {
525
0
    return {};
526
0
  }
527
528
0
  uint32_t image_width = 0, image_height = 0;
529
530
0
  for (auto& property : properties) {
531
0
    if (auto ispe = std::dynamic_pointer_cast<Box_ispe>(property)) {
532
0
      image_width = ispe->get_width();
533
0
      image_height = ispe->get_height();
534
0
      break;
535
0
    }
536
0
  }
537
538
0
  if (image_width == 0 || image_height == 0) {
539
0
    return {};
540
0
  }
541
542
0
  RegionCoordinateTransform transform;
543
0
  transform.a = image_width / (double) reference_width;
544
0
  transform.d = image_height / (double) reference_height;
545
546
0
  for (auto& property : properties) {
547
0
    switch (property->get_short_type()) {
548
0
      case fourcc("imir"): {
549
0
        auto imir = std::dynamic_pointer_cast<Box_imir>(property);
550
0
        if (imir->get_mirror_direction() == heif_transform_mirror_direction_horizontal) {
551
0
          transform.a = -transform.a;
552
0
          transform.b = -transform.b;
553
0
          transform.tx = image_width - 1 - transform.tx;
554
0
        }
555
0
        else {
556
0
          transform.c = -transform.c;
557
0
          transform.d = -transform.d;
558
0
          transform.ty = image_height - 1 - transform.ty;
559
0
        }
560
0
        break;
561
0
      }
562
0
      case fourcc("irot"): {
563
0
        auto irot = std::dynamic_pointer_cast<Box_irot>(property);
564
0
        RegionCoordinateTransform tmp;
565
0
        switch (irot->get_rotation_ccw()) {
566
0
          case 90:
567
0
            tmp.a = transform.c;
568
0
            tmp.b = transform.d;
569
0
            tmp.c = -transform.a;
570
0
            tmp.d = -transform.b;
571
0
            tmp.tx = transform.ty;
572
0
            tmp.ty = -transform.tx + image_width - 1;
573
0
            transform = tmp;
574
0
            std::swap(image_width, image_height);
575
0
            break;
576
0
          case 180:
577
0
            transform.a = -transform.a;
578
0
            transform.b = -transform.b;
579
0
            transform.tx = image_width - 1 - transform.tx;
580
0
            transform.c = -transform.c;
581
0
            transform.d = -transform.d;
582
0
            transform.ty = image_height - 1 - transform.ty;
583
0
            break;
584
0
          case 270:
585
0
            tmp.a = -transform.c;
586
0
            tmp.b = -transform.d;
587
0
            tmp.c = transform.a;
588
0
            tmp.d = transform.b;
589
0
            tmp.tx = -transform.ty + image_height - 1;
590
0
            tmp.ty = transform.tx;
591
0
            transform = tmp;
592
0
            std::swap(image_width, image_height);
593
0
            break;
594
0
          default:
595
0
            break;
596
0
        }
597
0
        break;
598
0
      }
599
0
      case fourcc("clap"): {
600
0
        auto clap = std::dynamic_pointer_cast<Box_clap>(property);
601
0
        int left = clap->left_rounded(image_width);
602
0
        int top = clap->top_rounded(image_height);
603
0
        transform.tx -= left;
604
0
        transform.ty -= top;
605
0
        image_width = clap->get_width_rounded();
606
0
        image_height = clap->get_height_rounded();
607
0
        break;
608
0
      }
609
0
      default:
610
0
        break;
611
0
    }
612
0
  }
613
614
0
  return transform;
615
0
}
616
617
618
RegionCoordinateTransform::Point RegionCoordinateTransform::transform_point(Point p)
619
0
{
620
0
  Point newp;
621
0
  newp.x = p.x * a + p.x * b + tx;
622
0
  newp.y = p.x * c + p.y * d + ty;
623
0
  return newp;
624
0
}
625
626
627
RegionCoordinateTransform::Extent RegionCoordinateTransform::transform_extent(Extent e)
628
0
{
629
0
  Extent newe;
630
0
  newe.x = e.x * a + e.y * b;
631
0
  newe.y = e.x * c + e.y * d;
632
0
  return newe;
633
0
}
634
635
636