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 +4 -4
- data/CHANGELOG.md +3 -0
- data/Gemfile.lock +1 -1
- data/README.md +11 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +13 -18
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp +3 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.cpp +10 -33
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.h +4 -9
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/GeoLocalization.h +32 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/PngSource.cpp +6 -11
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/RasterStreamer.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/TiffSource.cpp +301 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/TiffSource.h +51 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/GeoJsonStreamingMerger.h +51 -6
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +39 -2
- data/ext/cpp_polygon_finder/extconf.rb +9 -0
- data/lib/contrek/bitmaps/streaming/raster_streamer.rb +1 -1
- data/lib/contrek/bitmaps/streaming/sources/tiff.rb +349 -0
- data/lib/contrek/finder/concurrent/geo_json_streaming_merger.rb +19 -2
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +1 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 802afd86666e1705cc50240c6f7d79abfd9c7e93c315b1222199e79836fdd9e2
|
|
4
|
+
data.tar.gz: e9f593c3ef9a661720f2587b44d65406839b25c2bc1690b8c4bfae477f967de6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
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", (
|
|
345
|
+
{"processing_height", static_cast<int>(buffer_rows)},
|
|
346
346
|
{"versus", Identifier{"a"}},
|
|
347
347
|
};
|
|
348
348
|
PolygonFinder polygon_finder(&bitmap, ¬_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,¬_matcher,nullptr,
|
|
422
|
+
[&](Bitmap& bitmap, uint32_t buffer_rows, std::size_t, std::size_t) {
|
|
423
|
+
PolygonFinder finder(&bitmap, ¬_matcher, nullptr,
|
|
426
424
|
{
|
|
427
|
-
{"processing_height", (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|