contrek 1.4.2 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 330f21ed351ac9ec5b507c77c49c0595264d174f5403d00449cd6abf25be9e98
4
- data.tar.gz: 33813776ce0b886f1807d08e6e3245fb4a2276cb6da7ae0fdc571886101160ae
3
+ metadata.gz: 802afd86666e1705cc50240c6f7d79abfd9c7e93c315b1222199e79836fdd9e2
4
+ data.tar.gz: e9f593c3ef9a661720f2587b44d65406839b25c2bc1690b8c4bfae477f967de6
5
5
  SHA512:
6
- metadata.gz: d9ee2d8f093dad25a2c6913247a3c719fbf63d6c818ca3b451b27c8ec8984eeaa1760688e4bb5baf799afd3ea6b712c1db3940eb46fea859a6b3336815ad5e1f
7
- data.tar.gz: b542aec71f43fa199e300d745838ac5ff25e3c3b34e988ea6e1ca58051ac9f93723d2caa5d0ba2a4102fd0c30daf7b1ad3ced65f4ee3d077d1fff85a824c2960
6
+ metadata.gz: 8e4d8940a0f6cb93fc0ee685808c8889110bedbfd604fb8de3e3aca6b0fb9caa69d57bec518cc50ce15314a6ffa45e525fc947b60d52d71403510b6c77b7302f
7
+ data.tar.gz: e12d7f30b9dec7ee474ab5c400f86f8331cc8eaa8e1571e6c8b895ddea4a4f0633806d2ccb0b4fdd12380d77caa714c4b6c85b9f347e3bf6ff70f39b0a1cb9d9
data/CHANGELOG.md CHANGED
@@ -159,3 +159,6 @@ All notable changes to this project will be documented in this file.
159
159
 
160
160
  ## [1.4.2] - 2026-09-06
161
161
  - Contrek takes care of the streaming process, providing an API that greatly simplifies the implementation of streaming raster processing.
162
+
163
+ ## [1.4.3] - 2026-09-09
164
+ - Added TIFF/GeoTIFF streaming support with geolocation metadata and direct WGS84 GeoJSON output.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contrek (1.4.2)
4
+ contrek (1.4.3)
5
5
  chunky_png (~> 1.4)
6
6
  concurrent-ruby (~> 1.3.5)
7
7
  ffi (~> 1.17)
data/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  **Contrek** is a standalone **C++17 contour tracing and polygonization library** for raster images.
4
4
 
5
- It was originally developed to solve a practical problem: extracting polygons from very large raster images without loading the entire image into memory. Over time the implementation evolved into a reusable library that can process PNG images as well as raw memory buffers, while preserving polygon topology during the entire tracing process.
5
+ It was originally developed to solve a practical problem: extracting polygons from very large raster images without loading the entire image into memory. Over time the implementation evolved into a reusable library that can process PNG and TIFF/GeoTIFF images as well as raw memory buffers, while preserving polygon topology during the entire tracing process.
6
6
 
7
- The engine is based on programmable pixel matchers, allowing the caller to decide which pixels belong to the regions being traced. Polygon coordinates can be streamed as they are produced, making the library suitable for datasets that would otherwise require a large amount of RAM.
7
+ The engine is based on programmable pixel matchers, allowing the caller to decide which pixels belong to the regions being traced. Polygon coordinates can be streamed as they are produced, making the library suitable for datasets that would otherwise require a large amount of RAM. GeoTIFF geolocation can also be preserved when streaming directly to GeoJSON (EPSG:4326).
8
8
 
9
9
  Although the tracing engine is written in C++, Contrek is also distributed as a Ruby gem exposing almost the complete native API through an idiomatic Ruby interface.
10
10
 
@@ -305,6 +305,15 @@ During installation the native C++ extension is compiled automatically.
305
305
 
306
306
  Once installed, the C++ implementation is used by default. A pure Ruby implementation is also included and can be selected explicitly whenever native extensions are not desired.
307
307
 
308
+ ### TIFF / GeoTIFF support
309
+
310
+ TIFF and GeoTIFF streaming support requires the system `libtiff` and `libgeotiff` libraries.
311
+ Ubuntu / Debian:
312
+
313
+ ```bash
314
+ sudo apt install libtiff-dev libgeotiff-dev
315
+ ```
316
+
308
317
  # Usage
309
318
 
310
319
  The simplest way to use Contrek is through the `Contrek.contour!` helper.
@@ -102,7 +102,7 @@ void Tests::test_b()
102
102
 
103
103
  Options options = {
104
104
  {"versus", Identifier{"o"}},
105
- {"number_of_tiles",2},
105
+ {"number_of_tiles", 2},
106
106
  {"compress", Options{
107
107
  {"uniq", true},
108
108
  {"linear", true},
@@ -204,7 +204,7 @@ void Tests::test_e()
204
204
 
205
205
  Options options = {
206
206
  {"versus", Identifier{"a"}},
207
- {"number_of_tiles",2},
207
+ {"number_of_tiles", 2},
208
208
  {"compress", Options{
209
209
  {"uniq", true},
210
210
  }},
@@ -245,7 +245,7 @@ void Tests::test_g()
245
245
 
246
246
  Options options = {
247
247
  {"versus", Identifier{"a"}},
248
- {"number_of_tiles",2},
248
+ {"number_of_tiles", 2},
249
249
  {"compress", Options{
250
250
  {"uniq", true},
251
251
  }},
@@ -339,10 +339,10 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height, bool
339
339
  int stripe_count = 0;
340
340
 
341
341
  // main stripes loop
342
- streamer.each(stripe_bitmap, [&](Bitmap& bitmap, uint32_t buffer_rows, std::size_t) {
342
+ streamer.each(stripe_bitmap, [&](Bitmap& bitmap, uint32_t buffer_rows, std::size_t, std::size_t) {
343
343
  // stripe contour tracing
344
344
  Options options = {
345
- {"processing_height", (int) buffer_rows},
345
+ {"processing_height", static_cast<int>(buffer_rows)},
346
346
  {"versus", Identifier{"a"}},
347
347
  };
348
348
  PolygonFinder polygon_finder(&bitmap, &not_matcher, nullptr, options);
@@ -399,7 +399,7 @@ double get_current_rss_mb() {
399
399
  return (resident * page_size) / (1024.0 * 1024.0);
400
400
  }
401
401
 
402
- void stream_progressive_png_image(const std::string& filepath,uint32_t stripe_height) {
402
+ void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_height) {
403
403
  PngSource source(filepath);
404
404
  RasterStreamer streamer(source, stripe_height);
405
405
  RawBitmap bitmap(source.width(), stripe_height);
@@ -412,38 +412,33 @@ void stream_progressive_png_image(const std::string& filepath,uint32_t stripe_he
412
412
  {{"bounds", true}},
413
413
  &stream,
414
414
  source.width(),
415
- source.height()
416
- );
415
+ source.height());
417
416
 
418
417
  uint32_t processed_rows = 0;
419
418
  bool first = true;
420
419
  int stripe_count = 0;
421
420
  streamer.each(
422
421
  bitmap,
423
- [&](Bitmap& bitmap, uint32_t buffer_rows, std::size_t) {
424
-
425
- PolygonFinder finder(&bitmap,&not_matcher,nullptr,
422
+ [&](Bitmap& bitmap, uint32_t buffer_rows, std::size_t, std::size_t) {
423
+ PolygonFinder finder(&bitmap, &not_matcher, nullptr,
426
424
  {
427
- {"processing_height", (int) buffer_rows},
425
+ {"processing_height", static_cast<int>(buffer_rows)},
428
426
  {"versus", Identifier{"a"}},
429
427
  {"bounds", true},
430
428
  {"compress", Options{
431
429
  {"uniq", true},
432
430
  {"linear", true},
433
431
  }},
434
- }
435
- );
432
+ });
436
433
  ProcessResult* result = finder.process_info();
437
434
  if (result) {
438
435
  std::cout << "stripe " << stripe_count++ << ": found polygons " << result->groups << std::endl;
439
436
  processed_rows += buffer_rows - (first ? 0 : RasterStreamer::OVERLAP);
440
- merger.add_tile(*result,processed_rows == source.height());
437
+ merger.add_tile(*result, processed_rows == source.height());
441
438
  delete result;
442
439
  }
443
440
  first = false;
444
-
445
- }
446
- );
441
+ });
447
442
 
448
443
  delete merger.process_info();
449
444
  }
@@ -7,14 +7,14 @@
7
7
  * See the LICENSE file in this directory for the full license text.
8
8
  */
9
9
 
10
+ #include "RawBitmap.h"
11
+ #include "spng.h"
10
12
  #include <memory>
11
13
  #include <cstdio>
12
14
  #include <iostream>
13
15
  #include <cstring>
14
16
  #include <cerrno>
15
17
  #include <string>
16
- #include "spng.h"
17
- #include "RawBitmap.h"
18
18
 
19
19
  RawBitmap::RawBitmap() : Bitmap("", 0),
20
20
  width(0),
@@ -42,11 +42,7 @@ char RawBitmap::value_at(int, int) {
42
42
  }
43
43
 
44
44
  const unsigned char* RawBitmap::get_row_ptr(int y) const {
45
- return image.get() + (
46
- static_cast<std::size_t>(y) *
47
- static_cast<std::size_t>(width) *
48
- static_cast<std::size_t>(bpp)
49
- );
45
+ return image.get() + (static_cast<std::size_t>(y) * static_cast<std::size_t>(width) * static_cast<std::size_t>(bpp));
50
46
  }
51
47
 
52
48
  int RawBitmap::get_bytes_per_pixel() const {
@@ -17,53 +17,30 @@ AsciiSource::AsciiSource(Bitmap& bitmap)
17
17
  }
18
18
 
19
19
  uint32_t AsciiSource::width() const {
20
- return static_cast<uint32_t>(
21
- bitmap_.w()
22
- );
20
+ return static_cast<uint32_t>(bitmap_.w());
23
21
  }
24
22
 
25
23
  uint32_t AsciiSource::height() const {
26
- return static_cast<uint32_t>(
27
- bitmap_.h()
28
- );
24
+ return static_cast<uint32_t>(bitmap_.h());
29
25
  }
30
26
 
31
27
  uint32_t AsciiSource::get_bytes_per_pixel() const {
32
- return static_cast<uint32_t>(
33
- bitmap_.get_bytes_per_pixel()
34
- );
28
+ return static_cast<uint32_t>(bitmap_.get_bytes_per_pixel());
35
29
  }
36
30
 
37
- bool AsciiSource::read_next_row(
38
- unsigned char* destination,
39
- std::size_t row_size
40
- ) {
41
- if (current_row_ >= height()) {
31
+ bool AsciiSource::read_next_row(unsigned char* destination, std::size_t row_size)
32
+ { if (current_row_ >= height()) {
42
33
  return false;
43
34
  }
44
35
 
45
- const unsigned char* row =
46
- bitmap_.get_row_ptr(current_row_);
47
-
48
- const std::size_t expected_row_size =
49
- static_cast<std::size_t>(width()) *
50
- static_cast<std::size_t>(
51
- get_bytes_per_pixel()
52
- );
36
+ const unsigned char* row = bitmap_.get_row_ptr(current_row_);
37
+ const std::size_t expected_row_size = static_cast<std::size_t>(width()) * static_cast<std::size_t>(get_bytes_per_pixel());
53
38
 
54
39
  if (expected_row_size != row_size) {
55
- throw std::runtime_error(
56
- "Invalid row size"
57
- );
40
+ throw std::runtime_error("Invalid row size");
58
41
  }
59
-
60
- std::memcpy(
61
- destination,
62
- row,
63
- row_size
64
- );
65
-
42
+ std::memcpy(destination, row, row_size);
66
43
  ++current_row_;
67
44
 
68
45
  return true;
69
- }
46
+ }
@@ -16,19 +16,14 @@
16
16
  #include "../Bitmap.h"
17
17
 
18
18
  class AsciiSource : public RasterSource {
19
- public:
19
+ public:
20
20
  explicit AsciiSource(Bitmap& bitmap);
21
-
22
21
  uint32_t width() const override;
23
22
  uint32_t height() const override;
24
23
  uint32_t get_bytes_per_pixel() const override;
24
+ bool read_next_row(unsigned char* destination, std::size_t row_size) override;
25
25
 
26
- bool read_next_row(
27
- unsigned char* destination,
28
- std::size_t row_size
29
- ) override;
30
-
31
- private:
26
+ private:
32
27
  Bitmap& bitmap_;
33
28
  uint32_t current_row_;
34
- };
29
+ };
@@ -0,0 +1,32 @@
1
+ /*
2
+ * GeoLocalization.h
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #pragma once
11
+
12
+ #include <string>
13
+
14
+ struct GeoTransform {
15
+ double x_origin = 0.0;
16
+ double y_origin = 0.0;
17
+ double x_pixel_size = 1.0;
18
+ double y_pixel_size = 1.0;
19
+ double x_row_offset = 0.0;
20
+ double y_column_offset = 0.0;
21
+ };
22
+
23
+ struct GeoCrs {
24
+ std::string authority;
25
+ int code = 0;
26
+ };
27
+
28
+ struct GeoLocalization {
29
+ bool valid = false;
30
+ GeoTransform transform;
31
+ GeoCrs crs;
32
+ };
@@ -11,6 +11,7 @@
11
11
 
12
12
  #include <stdexcept>
13
13
  #include <string>
14
+ #include <cstdio>
14
15
 
15
16
  PngSource::PngSource(const std::string& filepath)
16
17
  : fp_(nullptr),
@@ -18,7 +19,7 @@ PngSource::PngSource(const std::string& filepath)
18
19
  width_(0),
19
20
  height_(0),
20
21
  current_row_(0) {
21
- fp_ = std::fopen(filepath.c_str(),"rb");
22
+ fp_ = std::fopen(filepath.c_str(), "rb");
22
23
  if (!fp_) {
23
24
  throw std::runtime_error("Unable to open PNG file");
24
25
  }
@@ -30,7 +31,7 @@ PngSource::PngSource(const std::string& filepath)
30
31
  throw std::runtime_error("Unable to create spng context");
31
32
  }
32
33
 
33
- int ret = spng_set_png_file(ctx_,fp_);
34
+ int ret = spng_set_png_file(ctx_, fp_);
34
35
  if (ret != 0) {
35
36
  spng_ctx_free(ctx_);
36
37
  ctx_ = nullptr;
@@ -52,13 +53,7 @@ PngSource::PngSource(const std::string& filepath)
52
53
 
53
54
  width_ = static_cast<uint32_t>(ihdr.width);
54
55
  height_ = static_cast<uint32_t>(ihdr.height);
55
- ret = spng_decode_image(
56
- ctx_,
57
- nullptr,
58
- 0,
59
- SPNG_FMT_RGBA8,
60
- SPNG_DECODE_PROGRESSIVE
61
- );
56
+ ret = spng_decode_image(ctx_, nullptr, 0, SPNG_FMT_RGBA8, SPNG_DECODE_PROGRESSIVE);
62
57
 
63
58
  if (ret != 0) {
64
59
  spng_ctx_free(ctx_);
@@ -98,11 +93,11 @@ bool PngSource::read_next_row(unsigned char* destination, std::size_t row_size)
98
93
  if (row_size != expected_row_size) {
99
94
  throw std::runtime_error("Invalid row size");
100
95
  }
101
- const int ret = spng_decode_row(ctx_,destination,row_size);
96
+ const int ret = spng_decode_row(ctx_, destination, row_size);
102
97
 
103
98
  if (ret != 0 && ret != SPNG_EOI) {
104
99
  throw std::runtime_error(std::string("spng_decode_row failed: ") + spng_strerror(ret));
105
100
  }
106
101
  ++current_row_;
107
102
  return true;
108
- }
103
+ }
@@ -80,7 +80,7 @@ class RasterStreamer {
80
80
  }
81
81
  const std::size_t buffer_size = static_cast<std::size_t>(buffer_rows) * row_size;
82
82
 
83
- std::forward<Callback>(callback)(buffer, buffer_rows, buffer_size);
83
+ std::forward<Callback>(callback)(buffer, buffer_rows, buffer_size, rows_read);
84
84
  previous_buffer_rows = buffer_rows;
85
85
  first_stripe = false;
86
86
  }
@@ -0,0 +1,301 @@
1
+ /*
2
+ * TiffSource.cpp
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #include "TiffSource.h"
11
+
12
+ #include <geotiff/geotiff.h>
13
+ #include <geotiff/geotiffio.h>
14
+ #include <geotiff/geokeys.h>
15
+ #include <geotiff/xtiffio.h>
16
+ #include <tiffio.h>
17
+ #include <stdexcept>
18
+ #include <string>
19
+
20
+ static const uint32_t TIFFTAG_MODELPIXELSCALE = 33550;
21
+ static const uint32_t TIFFTAG_MODELTIEPOINT = 33922;
22
+ static const uint32_t TIFFTAG_MODELTRANSFORMATION = 34264;
23
+
24
+ TiffSource::TiffSource(const std::string& filepath, bool suppress_warnings)
25
+ : tiff_(nullptr),
26
+ width_(0),
27
+ height_(0),
28
+ bits_per_sample_(0),
29
+ samples_per_pixel_(0),
30
+ photometric_(0),
31
+ planar_config_(0),
32
+ sample_format_(0),
33
+ current_row_(0) {
34
+ if (suppress_warnings) {
35
+ TIFFSetWarningHandler(nullptr);
36
+ }
37
+ tiff_ = XTIFFOpen(filepath.c_str(), "r");
38
+
39
+ if (!tiff_) {
40
+ throw std::runtime_error("Unable to open TIFF: " + filepath);
41
+ }
42
+
43
+ try {
44
+ read_image_info();
45
+ read_geo_localization();
46
+
47
+ const tmsize_t scanline_size = TIFFScanlineSize(tiff_);
48
+ if (scanline_size <= 0) throw std::runtime_error("Invalid TIFF scanline size");
49
+
50
+ source_row_.resize(static_cast<std::size_t>(scanline_size));
51
+ }
52
+ catch (...) {
53
+ XTIFFClose(tiff_);
54
+ tiff_ = nullptr;
55
+ throw;
56
+ }
57
+ }
58
+
59
+ TiffSource::~TiffSource() {
60
+ if (tiff_) {
61
+ XTIFFClose(tiff_);
62
+ tiff_ = nullptr;
63
+ }
64
+ }
65
+
66
+ uint32_t TiffSource::width() const {
67
+ return width_;
68
+ }
69
+
70
+ uint32_t TiffSource::height() const {
71
+ return height_;
72
+ }
73
+
74
+ uint32_t TiffSource::get_bytes_per_pixel() const {
75
+ return 4;
76
+ }
77
+
78
+ const GeoLocalization& TiffSource::geo_localization() const {
79
+ return geo_localization_;
80
+ }
81
+
82
+ void TiffSource::read_image_info() {
83
+ if (!TIFFGetField(tiff_, TIFFTAG_IMAGEWIDTH, &width_)) throw std::runtime_error("Unable to read TIFF width");
84
+ if (!TIFFGetField(tiff_, TIFFTAG_IMAGELENGTH, &height_)) throw std::runtime_error("Unable to read TIFF height");
85
+
86
+ TIFFGetFieldDefaulted(tiff_, TIFFTAG_BITSPERSAMPLE, &bits_per_sample_);
87
+ TIFFGetFieldDefaulted(tiff_, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel_);
88
+
89
+ if (!TIFFGetField(tiff_, TIFFTAG_PHOTOMETRIC, &photometric_)) throw std::runtime_error("Unable to read TIFF photometric");
90
+
91
+ TIFFGetFieldDefaulted(tiff_, TIFFTAG_PLANARCONFIG, &planar_config_);
92
+ TIFFGetFieldDefaulted(tiff_, TIFFTAG_SAMPLEFORMAT, &sample_format_);
93
+
94
+ if (TIFFIsTiled(tiff_)) throw std::runtime_error("Tiled TIFF is not supported yet");
95
+ if (planar_config_ != PLANARCONFIG_CONTIG) throw std::runtime_error("Unsupported TIFF planar configuration");
96
+ if (bits_per_sample_ != 8 && bits_per_sample_ != 16) throw std::runtime_error("Unsupported TIFF bits per sample");
97
+ if (samples_per_pixel_ != 1 && samples_per_pixel_ != 3 && samples_per_pixel_ != 4) throw std::runtime_error("Unsupported TIFF samples per pixel");
98
+ if (sample_format_ != SAMPLEFORMAT_UINT) throw std::runtime_error("Unsupported TIFF sample format");
99
+
100
+ if (photometric_ != PHOTOMETRIC_MINISWHITE &&
101
+ photometric_ != PHOTOMETRIC_MINISBLACK &&
102
+ photometric_ != PHOTOMETRIC_RGB) {
103
+ throw std::runtime_error("Unsupported TIFF photometric");
104
+ }
105
+ }
106
+
107
+ void TiffSource::read_geo_localization() {
108
+ double* matrix = nullptr;
109
+ uint16_t matrix_count = 0;
110
+
111
+ if (TIFFGetField(tiff_, TIFFTAG_MODELTRANSFORMATION, &matrix_count, &matrix) && matrix && matrix_count >= 16) {
112
+ read_model_transformation(matrix, matrix_count);
113
+ } else {
114
+ read_pixel_scale_and_tiepoint();
115
+ }
116
+
117
+ if (geo_localization_.valid) read_crs();
118
+ }
119
+
120
+ void TiffSource::read_model_transformation(double* matrix, uint16_t count) {
121
+ if (!matrix || count < 16) return;
122
+
123
+ GeoTransform& transform = geo_localization_.transform;
124
+
125
+ transform.x_pixel_size = matrix[0];
126
+ transform.x_row_offset = matrix[1];
127
+ transform.x_origin = matrix[3];
128
+
129
+ transform.y_column_offset = matrix[4];
130
+ transform.y_pixel_size = matrix[5];
131
+ transform.y_origin = matrix[7];
132
+
133
+ geo_localization_.valid = true;
134
+ }
135
+
136
+ void TiffSource::read_pixel_scale_and_tiepoint() {
137
+ double* scale = nullptr;
138
+ double* tiepoint = nullptr;
139
+
140
+ uint16_t scale_count = 0;
141
+ uint16_t tiepoint_count = 0;
142
+
143
+ if (!TIFFGetField(tiff_, TIFFTAG_MODELPIXELSCALE, &scale_count, &scale)) return;
144
+ if (!TIFFGetField(tiff_, TIFFTAG_MODELTIEPOINT, &tiepoint_count, &tiepoint)) return;
145
+ if (!scale || !tiepoint || scale_count < 2 || tiepoint_count < 6) return;
146
+
147
+ const double pixel_x = tiepoint[0];
148
+ const double pixel_y = tiepoint[1];
149
+
150
+ const double model_x = tiepoint[3];
151
+ const double model_y = tiepoint[4];
152
+
153
+ const double x_pixel_size = scale[0];
154
+ const double y_pixel_size = scale[1];
155
+
156
+ GeoTransform& transform = geo_localization_.transform;
157
+
158
+ transform.x_origin = model_x - pixel_x * x_pixel_size;
159
+ transform.y_origin = model_y + pixel_y * y_pixel_size;
160
+
161
+ transform.x_pixel_size = x_pixel_size;
162
+ transform.y_pixel_size = -y_pixel_size;
163
+
164
+ transform.x_row_offset = 0.0;
165
+ transform.y_column_offset = 0.0;
166
+
167
+ geo_localization_.valid = true;
168
+ }
169
+
170
+ void TiffSource::read_crs() {
171
+ GTIF* gtif = GTIFNew(tiff_);
172
+ if (!gtif) return;
173
+
174
+ uint16_t model_type = 0;
175
+ uint16_t code = 0;
176
+
177
+ if (GTIFKeyGet(gtif, GTModelTypeGeoKey, &model_type, 0, 1) == 1) {
178
+ if (model_type == ModelTypeProjected) {
179
+ if (GTIFKeyGet(gtif, ProjectedCSTypeGeoKey, &code, 0, 1) == 1) {
180
+ geo_localization_.crs.authority = "EPSG";
181
+ geo_localization_.crs.code = static_cast<int>(code);
182
+ }
183
+ } else if (model_type == ModelTypeGeographic) {
184
+ if (GTIFKeyGet(gtif, GeographicTypeGeoKey, &code, 0, 1) == 1) {
185
+ geo_localization_.crs.authority = "EPSG";
186
+ geo_localization_.crs.code = static_cast<int>(code);
187
+ }
188
+ }
189
+ }
190
+
191
+ GTIFFree(gtif);
192
+ }
193
+
194
+ bool TiffSource::read_next_row(unsigned char* destination, std::size_t row_size) {
195
+ if (current_row_ >= height_) return false;
196
+
197
+ const std::size_t expected_row_size = static_cast<std::size_t>(width_) * get_bytes_per_pixel();
198
+
199
+ if (row_size != expected_row_size) {
200
+ throw std::runtime_error("Invalid TIFF destination row size");
201
+ }
202
+
203
+ if (TIFFReadScanline(tiff_, source_row_.data(), current_row_, 0) < 0) {
204
+ throw std::runtime_error("TIFFReadScanline failed at row " + std::to_string(current_row_));
205
+ }
206
+
207
+ convert_row(destination);
208
+
209
+ ++current_row_;
210
+ return true;
211
+ }
212
+
213
+ void TiffSource::convert_row(unsigned char* destination) {
214
+ if (samples_per_pixel_ == 4 && bits_per_sample_ == 8) {
215
+ for (uint32_t x = 0; x < width_; ++x) {
216
+ const std::size_t source_offset = static_cast<std::size_t>(x) * 4;
217
+ const std::size_t destination_offset = static_cast<std::size_t>(x) * 4;
218
+
219
+ destination[destination_offset] = source_row_[source_offset];
220
+ destination[destination_offset + 1] = source_row_[source_offset + 1];
221
+ destination[destination_offset + 2] = source_row_[source_offset + 2];
222
+ destination[destination_offset + 3] = source_row_[source_offset + 3];
223
+ }
224
+ return;
225
+ }
226
+
227
+ if (samples_per_pixel_ == 3 && bits_per_sample_ == 8) {
228
+ for (uint32_t x = 0; x < width_; ++x) {
229
+ const std::size_t source_offset = static_cast<std::size_t>(x) * 3;
230
+ const std::size_t destination_offset = static_cast<std::size_t>(x) * 4;
231
+
232
+ destination[destination_offset] = source_row_[source_offset];
233
+ destination[destination_offset + 1] = source_row_[source_offset + 1];
234
+ destination[destination_offset + 2] = source_row_[source_offset + 2];
235
+ destination[destination_offset + 3] = 255;
236
+ }
237
+ return;
238
+ }
239
+
240
+ if (samples_per_pixel_ == 1 && bits_per_sample_ == 8) {
241
+ for (uint32_t x = 0; x < width_; ++x) {
242
+ unsigned char value = source_row_[x];
243
+
244
+ if (photometric_ == PHOTOMETRIC_MINISWHITE) value = static_cast<unsigned char>(255 - value);
245
+
246
+ const std::size_t destination_offset = static_cast<std::size_t>(x) * 4;
247
+
248
+ destination[destination_offset] = value;
249
+ destination[destination_offset + 1] = value;
250
+ destination[destination_offset + 2] = value;
251
+ destination[destination_offset + 3] = 255;
252
+ }
253
+ return;
254
+ }
255
+
256
+ const uint16_t* source16 = reinterpret_cast<const uint16_t*>(source_row_.data());
257
+
258
+ if (samples_per_pixel_ == 4 && bits_per_sample_ == 16) {
259
+ for (uint32_t x = 0; x < width_; ++x) {
260
+ const std::size_t source_offset = static_cast<std::size_t>(x) * 4;
261
+ const std::size_t destination_offset = static_cast<std::size_t>(x) * 4;
262
+
263
+ destination[destination_offset] = static_cast<unsigned char>(source16[source_offset] >> 8);
264
+ destination[destination_offset + 1] = static_cast<unsigned char>(source16[source_offset + 1] >> 8);
265
+ destination[destination_offset + 2] = static_cast<unsigned char>(source16[source_offset + 2] >> 8);
266
+ destination[destination_offset + 3] = static_cast<unsigned char>(source16[source_offset + 3] >> 8);
267
+ }
268
+ return;
269
+ }
270
+
271
+ if (samples_per_pixel_ == 3 && bits_per_sample_ == 16) {
272
+ for (uint32_t x = 0; x < width_; ++x) {
273
+ const std::size_t source_offset = static_cast<std::size_t>(x) * 3;
274
+ const std::size_t destination_offset = static_cast<std::size_t>(x) * 4;
275
+
276
+ destination[destination_offset] = static_cast<unsigned char>(source16[source_offset] >> 8);
277
+ destination[destination_offset + 1] = static_cast<unsigned char>(source16[source_offset + 1] >> 8);
278
+ destination[destination_offset + 2] = static_cast<unsigned char>(source16[source_offset + 2] >> 8);
279
+ destination[destination_offset + 3] = 255;
280
+ }
281
+ return;
282
+ }
283
+
284
+ if (samples_per_pixel_ == 1 && bits_per_sample_ == 16) {
285
+ for (uint32_t x = 0; x < width_; ++x) {
286
+ unsigned char value = static_cast<unsigned char>(source16[x] >> 8);
287
+
288
+ if (photometric_ == PHOTOMETRIC_MINISWHITE) value = static_cast<unsigned char>(255 - value);
289
+
290
+ const std::size_t destination_offset = static_cast<std::size_t>(x) * 4;
291
+
292
+ destination[destination_offset] = value;
293
+ destination[destination_offset + 1] = value;
294
+ destination[destination_offset + 2] = value;
295
+ destination[destination_offset + 3] = 255;
296
+ }
297
+ return;
298
+ }
299
+
300
+ throw std::runtime_error("Unsupported TIFF pixel format");
301
+ }