contrek 1.2.8 → 1.3.0
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/.gitignore +2 -1
- data/.rubocop.yml +11 -0
- data/CHANGELOG.md +7 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/Rakefile +2 -0
- data/contrek.gemspec +2 -0
- data/ext/cpp_polygon_finder/PolygonFinder/CMakeLists.txt +2 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +118 -19
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.h +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +5 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +4 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +14 -15
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +2 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Polygon.h +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +13 -13
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +79 -354
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +6 -6
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.h +3 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +8 -8
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/EndPoint.h +4 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.h +3 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/InnerPolyline.cpp +6 -6
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/InnerPolyline.h +3 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp +4 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.cpp +29 -9
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +3 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +45 -30
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +18 -19
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +6 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Position.cpp +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Position.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queueable.h +4 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.cpp +7 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Sequence.h +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.cpp +8 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.h +4 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.cpp +19 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.h +6 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/StreamingMerger.cpp +117 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/StreamingMerger.h +41 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +9 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.h +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.cpp +4 -13
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/VerticalMerger.h +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/LinearReducer.cpp +8 -8
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/LinearReducer.h +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/Reducer.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/Reducer.h +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/UniqReducer.cpp +3 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/UniqReducer.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/VisvalingamReducer.cpp +3 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/reducers/VisvalingamReducer.h +10 -10
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +66 -16
- data/ext/cpp_polygon_finder/extconf.rb +2 -0
- data/lib/contrek/bitmaps/bitmap.rb +2 -0
- data/lib/contrek/bitmaps/chunky_bitmap.rb +13 -0
- data/lib/contrek/bitmaps/painting.rb +2 -0
- data/lib/contrek/bitmaps/png_bitmap.rb +2 -0
- data/lib/contrek/bitmaps/raw_bitmap.rb +2 -0
- data/lib/contrek/bitmaps/rgb_color.rb +2 -0
- data/lib/contrek/bitmaps/rgb_cpp_color.rb +2 -0
- data/lib/contrek/bitmaps/sample_generator.rb +2 -0
- data/lib/contrek/cpp/cpp_concurrent_finder.rb +2 -0
- data/lib/contrek/cpp/cpp_concurrent_horizontal_merger.rb +2 -0
- data/lib/contrek/cpp/cpp_concurrent_merger.rb +2 -0
- data/lib/contrek/cpp/cpp_concurrent_streaming_merger.rb +11 -0
- data/lib/contrek/cpp/cpp_concurrent_vertical_merger.rb +2 -0
- data/lib/contrek/cpp/cpp_result.rb +2 -0
- data/lib/contrek/cpp/cpp_tempfile.rb +28 -0
- data/lib/contrek/finder/bounds.rb +2 -0
- data/lib/contrek/finder/concurrent/clipped_polygon_finder.rb +2 -0
- data/lib/contrek/finder/concurrent/cluster.rb +3 -1
- data/lib/contrek/finder/concurrent/cursor.rb +2 -0
- data/lib/contrek/finder/concurrent/end_point.rb +2 -0
- data/lib/contrek/finder/concurrent/fake_cluster.rb +2 -0
- data/lib/contrek/finder/concurrent/finder.rb +6 -0
- data/lib/contrek/finder/concurrent/horizontal_merger.rb +2 -0
- data/lib/contrek/finder/concurrent/hub.rb +2 -0
- data/lib/contrek/finder/concurrent/inner_polyline.rb +2 -0
- data/lib/contrek/finder/concurrent/listable.rb +2 -0
- data/lib/contrek/finder/concurrent/merger.rb +4 -0
- data/lib/contrek/finder/concurrent/part.rb +13 -0
- data/lib/contrek/finder/concurrent/partitionable.rb +33 -17
- data/lib/contrek/finder/concurrent/polyline.rb +2 -0
- data/lib/contrek/finder/concurrent/poolable.rb +2 -0
- data/lib/contrek/finder/concurrent/position.rb +2 -0
- data/lib/contrek/finder/concurrent/queueable.rb +2 -0
- data/lib/contrek/finder/concurrent/sequence.rb +2 -0
- data/lib/contrek/finder/concurrent/shape.rb +2 -0
- data/lib/contrek/finder/concurrent/streaming_merger.rb +89 -0
- data/lib/contrek/finder/concurrent/tile.rb +2 -0
- data/lib/contrek/finder/concurrent/vertical_merger.rb +8 -2
- data/lib/contrek/finder/list.rb +2 -0
- data/lib/contrek/finder/list_entry.rb +2 -0
- data/lib/contrek/finder/listable.rb +2 -0
- data/lib/contrek/finder/lists.rb +2 -0
- data/lib/contrek/finder/node.rb +2 -0
- data/lib/contrek/finder/node_cluster.rb +3 -1
- data/lib/contrek/finder/polygon_finder.rb +4 -1
- data/lib/contrek/finder/result.rb +2 -0
- data/lib/contrek/map/mercator_projection.rb +2 -0
- data/lib/contrek/matchers/matcher.rb +2 -0
- data/lib/contrek/matchers/matcher_hsb.rb +2 -0
- data/lib/contrek/matchers/value_not_matcher.rb +2 -0
- data/lib/contrek/reducers/linear_reducer.rb +2 -0
- data/lib/contrek/reducers/reducer.rb +2 -0
- data/lib/contrek/reducers/uniq_reducer.rb +2 -0
- data/lib/contrek/reducers/visvalingam_reducer.rb +2 -0
- data/lib/contrek/shared/result.rb +2 -0
- data/lib/contrek/version.rb +3 -1
- data/lib/contrek.rb +5 -0
- metadata +11 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 58d88411970256b3edc37da05dd9304aaa4593b320e7d73db5966b69d403c4cf
|
|
4
|
+
data.tar.gz: 6a05687f7ffba4b22eb7ebf02abf2a622931bc3250968fa785fb13720036118e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7150e06efce7580fdefd55725db0de52e2805b909e800679a0f99481c3e52333209d1b5ceb750c1fc63a28fa6aaaad7f7662d3baf3ebf377b29cfe4dacf6d19d
|
|
7
|
+
data.tar.gz: 988e85fd99234df2b3cd0e8bec0d8f68ba0f631111a3fda62e114e185dd1619589223266b5e3db71a2a5c478d2871f862209489df997a13a9da866b84718c609
|
data/.gitignore
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
.ruby-version
|
|
4
4
|
# rspec failure tracking
|
|
5
5
|
.rspec_status
|
|
6
|
+
/spec/files/performance_history.json
|
|
6
7
|
|
|
7
8
|
/ext/cpp_polygon_finder/PolygonFinder/build/
|
|
8
9
|
/ext/cpp_polygon_finder/PolygonFinder/contrek
|
|
@@ -12,4 +13,4 @@
|
|
|
12
13
|
/ext/cpp_polygon_finder/cpp_polygon_finder.o
|
|
13
14
|
/ext/cpp_polygon_finder/cpp_polygon_finder.so
|
|
14
15
|
/ext/cpp_polygon_finder/mkmf.log
|
|
15
|
-
/lib/cpp_polygon_finder.so
|
|
16
|
+
/lib/cpp_polygon_finder.so
|
data/.rubocop.yml
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -115,4 +115,10 @@ All notable changes to this project will be documented in this file.
|
|
|
115
115
|
|
|
116
116
|
## [1.2.7] - 2026-06-02
|
|
117
117
|
### Changed
|
|
118
|
-
- **Refactored `bounds` option:** Starting from this release, precalculated bounds for each polygon can now be requested in concurrent mode as well, in addition to single-threaded mode.
|
|
118
|
+
- **Refactored `bounds` option:** Starting from this release, precalculated bounds for each polygon can now be requested in concurrent mode as well, in addition to single-threaded mode.
|
|
119
|
+
|
|
120
|
+
## [1.2.8] - 2026-06-07
|
|
121
|
+
- **Optimize main pixel scanning loop:** Implemented 4-way loop unrolling to maximize L1 cache hits and eliminate redundant RAM lookups via direct register bit-casting.
|
|
122
|
+
|
|
123
|
+
## [1.2.9] - 2026-06-13
|
|
124
|
+
- **Streaming merger:** The streaming merger class extends VerticalMerger and adds a useful feature: the progressive extraction of contours into a disk buffer (SVG file). In this way, all extracted polygons that are no longer within the junction zone of the next stripe are removed from the system and streamed directly to disk. This incredibly reduces memory consumption, allowing the processing of very large files on machines with low memory availability, at the expense of increased processing times. An example of this technique is available in both C++ and Ruby in the repository.
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Contrek
|
|
2
|
-
Contrek is a Ruby gem powered by a <u>standalone C++17 core library</u> for fast contour tracing and edge detection in PNG images and raw memory streams. It employs a **topological approach** to extract polygonal contours, representing shapes as a connected graph of shared endpoints. This ensures perfect adjacency and structural integrity for shape analysis and raster-to-vector workflows, such as PNG to SVG conversion, managed via libspng (0.7.4) with multithreading support.
|
|
2
|
+
Contrek is a Ruby gem powered by a <u>[standalone C++17 core library](#-c-standalone-library-usage)</u> for fast contour tracing and edge detection in PNG images and raw memory streams. It employs a **topological approach** to extract polygonal contours, representing shapes as a connected graph of shared endpoints. This ensures perfect adjacency and structural integrity for shape analysis and raster-to-vector workflows, such as PNG to SVG conversion, managed via libspng (0.7.4) with multithreading support.
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
## About Contrek library
|
data/Rakefile
CHANGED
data/contrek.gemspec
CHANGED
|
@@ -11,10 +11,8 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
|
|
11
11
|
list(FILTER CMAKE_CXX_FLAGS EXCLUDE REGEX "-DNDEBUG")
|
|
12
12
|
list(FILTER CMAKE_C_FLAGS EXCLUDE REGEX "-DNDEBUG")
|
|
13
13
|
else()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pthread -march=native -DNDEBUG -Ofast -flto -fopenmp-simd -ftree-vectorize")
|
|
17
|
-
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pthread -march=native -DNDEBUG -Ofast -flto -fopenmp-simd -ftree-vectorize")
|
|
14
|
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pthread -march=native -fPIC -DNDEBUG -Ofast -flto -ftree-vectorize")
|
|
15
|
+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pthread -march=native -fPIC -DNDEBUG -Ofast -flto -ftree-vectorize")
|
|
18
16
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread -flto=auto -Wl,--no-as-needed")
|
|
19
17
|
find_library(TCMALLOC_LIB tcmalloc)
|
|
20
18
|
if(TCMALLOC_LIB)
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
#include "polygon/finder/concurrent/Finder.h"
|
|
35
35
|
#include "polygon/finder/concurrent/HorizontalMerger.h"
|
|
36
36
|
#include "polygon/finder/concurrent/VerticalMerger.h"
|
|
37
|
+
#include "polygon/finder/concurrent/StreamingMerger.h"
|
|
37
38
|
#include "polygon/finder/concurrent/Sequence.h"
|
|
38
39
|
#include "polygon/finder/concurrent/Position.h"
|
|
39
40
|
#include "polygon/finder/Polygon.h"
|
|
@@ -59,16 +60,16 @@ void Tests::test_a()
|
|
|
59
60
|
std::vector<int> array_compare;
|
|
60
61
|
|
|
61
62
|
for (const auto& x : o->polygons)
|
|
62
|
-
{ for (const Point
|
|
63
|
-
array_compare.push_back(p
|
|
64
|
-
array_compare.push_back(p
|
|
63
|
+
{ for (const Point& p : x.outer) {
|
|
64
|
+
array_compare.push_back(p.x);
|
|
65
|
+
array_compare.push_back(p.y);
|
|
65
66
|
}
|
|
66
67
|
if (outer_array != array_compare) throw std::runtime_error("Wrong OUTER results!");
|
|
67
68
|
array_compare.clear();
|
|
68
69
|
for (const auto& z : x.inner)
|
|
69
|
-
{ for (const Point
|
|
70
|
-
{ array_compare.push_back(y
|
|
71
|
-
array_compare.push_back(y
|
|
70
|
+
{ for (const Point& y : z)
|
|
71
|
+
{ array_compare.push_back(y.x);
|
|
72
|
+
array_compare.push_back(y.y);
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
if (inner_array != array_compare) throw std::runtime_error("Wrong INNER results!");
|
|
@@ -96,9 +97,9 @@ void Tests::test_b()
|
|
|
96
97
|
|
|
97
98
|
void Tests::test_c()
|
|
98
99
|
{ Sequence sequence;
|
|
99
|
-
Point
|
|
100
|
-
Point
|
|
101
|
-
Point
|
|
100
|
+
Point p1{1, 1};
|
|
101
|
+
Point p2{2, 2};
|
|
102
|
+
Point p3{3, 3};
|
|
102
103
|
|
|
103
104
|
Hub* hub = new Hub(4);
|
|
104
105
|
|
|
@@ -113,7 +114,7 @@ void Tests::test_c()
|
|
|
113
114
|
if (sequence.size != 3) throw std::runtime_error("Wrong sequence size");
|
|
114
115
|
|
|
115
116
|
// iterator() initially gives head
|
|
116
|
-
Point
|
|
117
|
+
const Point& head = sequence.head->payload;
|
|
117
118
|
if (head != p1) throw std::runtime_error("Wrong head");
|
|
118
119
|
if (sequence.iterator()->payload != p1) throw std::runtime_error("Wrong iterator to head");
|
|
119
120
|
if (sequence.iterator() != pos1) throw std::runtime_error("Wrong iterator to head");
|
|
@@ -134,10 +135,6 @@ void Tests::test_c()
|
|
|
134
135
|
sequence.rewind();
|
|
135
136
|
if (sequence.iterator()->payload != p1) throw std::runtime_error("Wrong iterator to head");
|
|
136
137
|
|
|
137
|
-
delete p1;
|
|
138
|
-
delete p2;
|
|
139
|
-
delete p3;
|
|
140
|
-
|
|
141
138
|
delete hub;
|
|
142
139
|
|
|
143
140
|
delete pos1;
|
|
@@ -347,10 +344,8 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height, bool
|
|
|
347
344
|
ProcessResult *result = polygon_finder.process_info();
|
|
348
345
|
if (result) {
|
|
349
346
|
std::cout << "stripe " << stripe_count << ": found polygons " << result->groups << std::endl;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
vmerger.add_tile(*safe_result);
|
|
353
|
-
delete result;
|
|
347
|
+
result_clones.push_back(result);
|
|
348
|
+
vmerger.add_tile(*result);
|
|
354
349
|
}
|
|
355
350
|
stripe_count++;
|
|
356
351
|
}
|
|
@@ -385,6 +380,110 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height, bool
|
|
|
385
380
|
}
|
|
386
381
|
|
|
387
382
|
void Tests::test_i() {
|
|
388
|
-
stream_png_image("../images/graphs_1024x1024.png", 300);
|
|
383
|
+
stream_png_image("../images/graphs_1024x1024.png", 300, true);
|
|
384
|
+
std::cout << "Memory usage peak: " << get_peak_rss() << " MB" << std::endl;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_height) {
|
|
388
|
+
std::vector<ProcessResult*> result_clones;
|
|
389
|
+
std::vector<std::string> varguments = {"--bounds"};
|
|
390
|
+
// opens image to stream
|
|
391
|
+
FILE* fp = fopen(filepath.c_str(), "rb");
|
|
392
|
+
if (!fp) {
|
|
393
|
+
std::cerr << "Unable open file: " << filepath << std::endl;
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// exams image
|
|
398
|
+
spng_ctx *ctx = spng_ctx_new(0);
|
|
399
|
+
spng_set_png_file(ctx, fp);
|
|
400
|
+
struct spng_ihdr ihdr;
|
|
401
|
+
if (spng_get_ihdr(ctx, &ihdr)) {
|
|
402
|
+
fclose(fp);
|
|
403
|
+
spng_ctx_free(ctx);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
uint32_t total_width = ihdr.width;
|
|
407
|
+
uint32_t total_height = ihdr.height;
|
|
408
|
+
|
|
409
|
+
// allocates stripe buffer
|
|
410
|
+
RawBitmap stripe_bitmap;
|
|
411
|
+
stripe_bitmap.define(total_width, stripe_height, 4, true);
|
|
412
|
+
RGBNotMatcher not_matcher(-1);
|
|
413
|
+
if (spng_decode_image(ctx, NULL, 0, SPNG_FMT_RGBA8, SPNG_DECODE_PROGRESSIVE)) {
|
|
414
|
+
fclose(fp);
|
|
415
|
+
spng_ctx_free(ctx);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// allocates streaming svg buffer
|
|
420
|
+
std::string output_path = "streaming_buffer.svg";
|
|
421
|
+
std::ofstream shared_stream(output_path, std::ios::out | std::ios::binary);
|
|
422
|
+
if (!shared_stream) {
|
|
423
|
+
std::cerr << "Error: Unable creating output streaming file!" << std::endl;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
StreamingMerger vmerger(0, &varguments, &shared_stream, total_width, total_height);
|
|
427
|
+
try {
|
|
428
|
+
size_t row_size = static_cast<size_t>(total_width) * 4;
|
|
429
|
+
int stripe_count = 0;
|
|
430
|
+
// main stripes loop
|
|
431
|
+
for (uint32_t current_y_offset = 0; current_y_offset < total_height; current_y_offset += stripe_height) {
|
|
432
|
+
int uncovered_height = total_height - current_y_offset;
|
|
433
|
+
|
|
434
|
+
// copy previous last line to the next new one (each contigue stripe must share one pixel scanline)
|
|
435
|
+
if (current_y_offset > 0) {
|
|
436
|
+
unsigned char* last_row_prev = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(stripe_height - 1));
|
|
437
|
+
unsigned char* first_row_curr = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(0));
|
|
438
|
+
std::memcpy(first_row_curr, last_row_prev, row_size);
|
|
439
|
+
}
|
|
440
|
+
// clears the part of the stripe wont be overwritten by png data
|
|
441
|
+
if (uncovered_height < stripe_height)
|
|
442
|
+
{ stripe_bitmap.draw_filled_rectangle(0, 1, total_width, stripe_height - 1, 255, 255, 255);
|
|
443
|
+
}
|
|
444
|
+
// decoding data directly in the stripe buffer
|
|
445
|
+
uint32_t lines_to_read = std::min(stripe_height, total_height - current_y_offset);
|
|
446
|
+
for (uint32_t y = (current_y_offset == 0 ? 0 : 1); y < lines_to_read; y++) {
|
|
447
|
+
unsigned char* row_ptr = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(y));
|
|
448
|
+
int ret = spng_decode_row(ctx, row_ptr, row_size);
|
|
449
|
+
if (ret != 0 && ret != SPNG_EOI) break;
|
|
450
|
+
}
|
|
451
|
+
// stripe contour tracing
|
|
452
|
+
std::vector<std::string> finder_arguments = {
|
|
453
|
+
"--versus=a",
|
|
454
|
+
"--bounds",
|
|
455
|
+
"--strict_bounds", // <- this option is strictly needed when working with vertical merger
|
|
456
|
+
"--compress_uniq",
|
|
457
|
+
"--compress_linear"
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
PolygonFinder polygon_finder(&stripe_bitmap, ¬_matcher, nullptr, &finder_arguments);
|
|
461
|
+
ProcessResult *result = polygon_finder.process_info();
|
|
462
|
+
if (result) {
|
|
463
|
+
std::cout << "stripe " << stripe_count << ": found polygons " << result->groups << std::endl;
|
|
464
|
+
result_clones.push_back(result);
|
|
465
|
+
vmerger.add_tile(*result, !(current_y_offset + stripe_height < total_height));
|
|
466
|
+
}
|
|
467
|
+
stripe_count++;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
ProcessResult *merged_result = vmerger.process_info();
|
|
471
|
+
std::cout << "total found polygons " << merged_result->groups << std::endl;
|
|
472
|
+
delete merged_result;
|
|
473
|
+
|
|
474
|
+
// frees memory
|
|
475
|
+
for (auto c : result_clones) {
|
|
476
|
+
delete c;
|
|
477
|
+
}
|
|
478
|
+
} catch (const std::exception& e) {
|
|
479
|
+
std::cerr << "\n[ERROR] Processing exception: " << e.what() << std::endl;
|
|
480
|
+
if (shared_stream.is_open()) shared_stream.close();
|
|
481
|
+
}
|
|
482
|
+
spng_ctx_free(ctx);
|
|
483
|
+
fclose(fp);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
void Tests::test_l() {
|
|
487
|
+
stream_progressive_png_image("../images/mixed_shapes_1024x1024.png", 300);
|
|
389
488
|
std::cout << "Memory usage peak: " << get_peak_rss() << " MB" << std::endl;
|
|
390
489
|
}
|
|
@@ -100,33 +100,31 @@ Node* Node::my_next_outer(Node *last, int versus) {
|
|
|
100
100
|
return get_tangent_node_by_virtual_index(this->tangs_sequence[last_node_index]);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
Point
|
|
103
|
+
Point Node::coords_entering_to(Node *enter_to, int mode, int tracking) {
|
|
104
104
|
int enter_to_index;
|
|
105
105
|
if (enter_to->y < this->y) enter_to_index = enter_to->abs_x_index + this->up_indexer;
|
|
106
106
|
else enter_to_index = this->down_indexer - enter_to->abs_x_index;
|
|
107
107
|
|
|
108
108
|
int tg_index = this->tangs_sequence[enter_to_index];
|
|
109
|
-
Point* point;
|
|
110
109
|
if (tg_index < 0) {
|
|
111
110
|
Node& node_up = cluster->vert_nodes[y + T_UP][-(tg_index + 1)];
|
|
112
111
|
if (mode == Node::A) {
|
|
113
112
|
enter_to->track |= TURNER[tracking][OMAX - 1];
|
|
114
|
-
|
|
113
|
+
return node_up.end_point;
|
|
115
114
|
} else {
|
|
116
115
|
enter_to->track |= TURNER[tracking][OMIN - 1];
|
|
117
|
-
|
|
116
|
+
return node_up.start_point;
|
|
118
117
|
}
|
|
119
118
|
} else {
|
|
120
119
|
Node& node_down = cluster->vert_nodes[y + T_DOWN][tg_index];
|
|
121
120
|
if (mode == Node::A) {
|
|
122
121
|
enter_to->track |= TURNER[tracking][OMIN - 1];
|
|
123
|
-
|
|
122
|
+
return node_down.start_point;
|
|
124
123
|
} else {
|
|
125
124
|
enter_to->track |= TURNER[tracking][OMAX - 1];
|
|
126
|
-
|
|
125
|
+
return node_down.end_point;
|
|
127
126
|
}
|
|
128
127
|
}
|
|
129
|
-
return point;
|
|
130
128
|
}
|
|
131
129
|
|
|
132
130
|
Node* Node::get_tangent_node_by_virtual_index(int virtual_index) {
|
|
@@ -61,6 +61,9 @@ struct Point {
|
|
|
61
61
|
bool operator==(const Point& other) const {
|
|
62
62
|
return x == other.x && y == other.y;
|
|
63
63
|
}
|
|
64
|
+
bool operator!=(const Point& other) const {
|
|
65
|
+
return !(*this == other);
|
|
66
|
+
}
|
|
64
67
|
Point(int x_, int y_) : x(x_), y(y_) {}
|
|
65
68
|
Point() : x(0), y(0) {}
|
|
66
69
|
};
|
|
@@ -96,7 +99,7 @@ class Node : public Listable {
|
|
|
96
99
|
NodeCluster* cluster;
|
|
97
100
|
void add_intersection(Node& other_node, int other_node_index);
|
|
98
101
|
SmallVec tangs_sequence;
|
|
99
|
-
Point
|
|
102
|
+
Point coords_entering_to(Node *enter_to, int mode, int tracking);
|
|
100
103
|
Node* my_next_outer(Node *last, int versus);
|
|
101
104
|
Node* my_next_inner(Node *last, int versus);
|
|
102
105
|
Node* get_tangent_node_by_virtual_index(int vitual_index);
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
#include "NodeCluster.h"
|
|
19
19
|
#include "Node.h"
|
|
20
20
|
#include "RectBounds.h"
|
|
21
|
-
#include "PointPool.h"
|
|
22
21
|
#include "../reducers/UniqReducer.h"
|
|
23
22
|
#include "../reducers/LinearReducer.h"
|
|
24
23
|
#include "../reducers/VisvalingamReducer.h"
|
|
@@ -44,7 +43,7 @@ NodeCluster::~NodeCluster() {
|
|
|
44
43
|
void NodeCluster::compress_coords(std::list<Polygon>& polygons, pf_Options options) {
|
|
45
44
|
if (!(options.compress_linear || options.compress_uniq || options.compress_visvalingam)) return;
|
|
46
45
|
|
|
47
|
-
auto compress_sequence = [&](std::vector<Point
|
|
46
|
+
auto compress_sequence = [&](std::vector<Point>& points_vec) {
|
|
48
47
|
if (points_vec.empty()) return;
|
|
49
48
|
|
|
50
49
|
if (options.compress_uniq) {
|
|
@@ -122,9 +121,9 @@ void NodeCluster::plot(int versus) {
|
|
|
122
121
|
next_node = root_node->get_tangent_node_by_virtual_index(root_node->tangs_sequence.front());
|
|
123
122
|
|
|
124
123
|
if (next_node != nullptr)
|
|
125
|
-
{ Point
|
|
124
|
+
{ Point p = next_node->coords_entering_to(root_node, versus_inverter[versus], Node::OUTER);
|
|
125
|
+
poly.bounds.expand(p.x, p.y);
|
|
126
126
|
poly.outer.push_back(p);
|
|
127
|
-
poly.bounds.expand(p->x, p->y);
|
|
128
127
|
}
|
|
129
128
|
if ((this->nodes > 0) && (next_node != nullptr))
|
|
130
129
|
{ plot_node(poly.outer, next_node, root_node, versus, poly.bounds);
|
|
@@ -137,7 +136,7 @@ void NodeCluster::plot(int versus) {
|
|
|
137
136
|
int index_inner = 0;
|
|
138
137
|
while (inner_plot->size() > 0)
|
|
139
138
|
{ this->plot_sequence.clear();
|
|
140
|
-
std::vector<Point
|
|
139
|
+
std::vector<Point> inner_sequence;
|
|
141
140
|
std::list<Node*>::iterator first_i;
|
|
142
141
|
Node *first = nullptr;
|
|
143
142
|
|
|
@@ -243,7 +242,7 @@ std::pair<int, int> NodeCluster::test_in_hole_o(Node* node)
|
|
|
243
242
|
return {-1, -1};
|
|
244
243
|
}
|
|
245
244
|
|
|
246
|
-
void NodeCluster::plot_inner_node(std::vector<Point
|
|
245
|
+
void NodeCluster::plot_inner_node(std::vector<Point>& sequence_coords, Node *node, int versus, Node *stop_at, Node *start_node) {
|
|
247
246
|
Node *current_node = node;
|
|
248
247
|
bool strict_bounds = this->options->strict_bounds;
|
|
249
248
|
while (current_node != nullptr) {
|
|
@@ -279,8 +278,8 @@ void NodeCluster::plot_inner_node(std::vector<Point*>& sequence_coords, Node *no
|
|
|
279
278
|
}
|
|
280
279
|
}
|
|
281
280
|
} else if (strict_bounds) {
|
|
282
|
-
sequence_coords.push_back(
|
|
283
|
-
sequence_coords.push_back(
|
|
281
|
+
sequence_coords.push_back(Point{(first_is_max ? last_node->max_x : last_node->min_x), current_node->y});
|
|
282
|
+
sequence_coords.push_back(Point{(first_is_max ? next_node->min_x : next_node->max_x), current_node->y});
|
|
284
283
|
}
|
|
285
284
|
|
|
286
285
|
if (current_node->track_uncomplete()) {
|
|
@@ -293,7 +292,7 @@ void NodeCluster::plot_inner_node(std::vector<Point*>& sequence_coords, Node *no
|
|
|
293
292
|
}
|
|
294
293
|
}
|
|
295
294
|
|
|
296
|
-
void NodeCluster::plot_node(std::vector<Point
|
|
295
|
+
void NodeCluster::plot_node(std::vector<Point>& sequence_coords, Node *node, Node *start_node, int versus, RectBounds& bounds) {
|
|
297
296
|
Node *current_node = node;
|
|
298
297
|
bool strict_bounds = this->options->strict_bounds;
|
|
299
298
|
|
|
@@ -315,23 +314,23 @@ void NodeCluster::plot_node(std::vector<Point*>& sequence_coords, Node *node, No
|
|
|
315
314
|
plot = (n == next_node);
|
|
316
315
|
}
|
|
317
316
|
if (plot) {
|
|
318
|
-
Point
|
|
317
|
+
Point p = last_node->coords_entering_to(current_node, versus, Node::OUTER);
|
|
318
|
+
bounds.expand(p.x, p.y);
|
|
319
319
|
sequence_coords.push_back(p);
|
|
320
|
-
bounds.expand(p->x, p->y);
|
|
321
320
|
if (current_node != start_node) {
|
|
322
321
|
inner_plot->contains(current_node) ? inner_plot->remove(current_node) : inner_plot->push_back(current_node);
|
|
323
322
|
if (last_node->y == next_node->y) {
|
|
324
|
-
Point
|
|
323
|
+
Point p1 = next_node->coords_entering_to(current_node, versus_inverter[versus], Node::OUTER);
|
|
324
|
+
bounds.expand(p1.x, p1.y);
|
|
325
325
|
sequence_coords.push_back(p1);
|
|
326
|
-
bounds.expand(p1->x, p1->y);
|
|
327
326
|
inner_plot->contains(current_node) ? inner_plot->remove(current_node) : inner_plot->push_back(current_node);
|
|
328
327
|
}
|
|
329
328
|
}
|
|
330
329
|
} else if (strict_bounds) {
|
|
331
330
|
bool is_down = current_node->y > last_node->y;
|
|
332
331
|
bool is_a = (versus == Node::A);
|
|
333
|
-
sequence_coords.push_back(
|
|
334
|
-
sequence_coords.push_back(
|
|
332
|
+
sequence_coords.push_back(Point{(is_down == is_a ? last_node->min_x : last_node->max_x), current_node->y});
|
|
333
|
+
sequence_coords.push_back(Point{(is_down == is_a ? next_node->max_x : next_node->min_x), current_node->y});
|
|
335
334
|
}
|
|
336
335
|
|
|
337
336
|
if (current_node == start_node) {
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
#include "Lists.h"
|
|
19
19
|
#include "RectBounds.h"
|
|
20
20
|
#include "Polygon.h"
|
|
21
|
-
#include "PointPool.h"
|
|
22
21
|
|
|
23
22
|
class Node;
|
|
24
23
|
struct Point;
|
|
@@ -26,8 +25,8 @@ struct pf_Options;
|
|
|
26
25
|
|
|
27
26
|
class NodeCluster {
|
|
28
27
|
private:
|
|
29
|
-
void plot_node(std::vector<Point
|
|
30
|
-
void plot_inner_node(std::vector<Point
|
|
28
|
+
void plot_node(std::vector<Point>& sequence_coords, Node *node, Node *start_node, int versus, RectBounds& bounds);
|
|
29
|
+
void plot_inner_node(std::vector<Point>& sequence_coords, Node *node, int versus, Node *stop_at, Node *start_node);
|
|
31
30
|
std::vector<Node*> plot_sequence;
|
|
32
31
|
List *inner_plot;
|
|
33
32
|
List *inner_new;
|
|
@@ -35,7 +34,6 @@ class NodeCluster {
|
|
|
35
34
|
int count = 0;
|
|
36
35
|
int nodes;
|
|
37
36
|
int width;
|
|
38
|
-
PointPool points_pool;
|
|
39
37
|
|
|
40
38
|
public:
|
|
41
39
|
pf_Options *options;
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
#include "RectBounds.h"
|
|
15
15
|
|
|
16
16
|
struct Polygon {
|
|
17
|
-
std::vector<Point
|
|
18
|
-
std::list<std::vector<Point
|
|
17
|
+
std::vector<Point> outer;
|
|
18
|
+
std::list<std::vector<Point>> inner;
|
|
19
19
|
RectBounds bounds;
|
|
20
20
|
Polygon() : bounds(RectBounds::empty()) {}
|
|
21
21
|
};
|
|
@@ -46,7 +46,6 @@ PolygonFinder::PolygonFinder(Bitmap *bitmap,
|
|
|
46
46
|
cpu_timer.start();
|
|
47
47
|
scan();
|
|
48
48
|
reports["scan"] = cpu_timer.stop();
|
|
49
|
-
//std::cout << "scan " << reports["scan"] << std::endl;
|
|
50
49
|
//=====================//
|
|
51
50
|
|
|
52
51
|
//= BUILD_TANGS_SEQUENCE ===//
|
|
@@ -109,6 +108,7 @@ ProcessResult* PolygonFinder::process_info() {
|
|
|
109
108
|
pr->width = this->source_bitmap->w();
|
|
110
109
|
pr->height = this->source_bitmap->h();
|
|
111
110
|
pr->has_bounds = this->node_cluster->options->bounds;
|
|
111
|
+
pr->versus = this->options.versus;
|
|
112
112
|
|
|
113
113
|
if (this->node_cluster->options->named_sequences && typeid(*this->source_bitmap) == typeid(Bitmap))
|
|
114
114
|
{ std::string sequence;
|
|
@@ -134,25 +134,25 @@ void ProcessResult::draw_on_bitmap(RawBitmap& bitmap) const {
|
|
|
134
134
|
// --- OUTER ---
|
|
135
135
|
if (!poly.outer.empty()) {
|
|
136
136
|
for (size_t i = 0; i < poly.outer.size() - 1; ++i) {
|
|
137
|
-
Point
|
|
138
|
-
Point
|
|
139
|
-
bitmap.draw_line(p1
|
|
137
|
+
const Point& p1 = poly.outer[i];
|
|
138
|
+
const Point& p2 = poly.outer[i+1];
|
|
139
|
+
bitmap.draw_line(p1.x, p1.y, p2.x, p2.y, 255, 0, 0, 255);
|
|
140
140
|
}
|
|
141
|
-
Point
|
|
142
|
-
Point
|
|
143
|
-
bitmap.draw_line(last
|
|
141
|
+
const Point& last = poly.outer.back();
|
|
142
|
+
const Point& first = poly.outer.front();
|
|
143
|
+
bitmap.draw_line(last.x, last.y, first.x, first.y, 255, 0, 0, 255);
|
|
144
144
|
}
|
|
145
145
|
// --- INNER ---
|
|
146
146
|
for (const auto& sequence : poly.inner) {
|
|
147
147
|
if (sequence.empty()) continue;
|
|
148
148
|
for (size_t i = 0; i < sequence.size() - 1; ++i) {
|
|
149
|
-
Point
|
|
150
|
-
Point
|
|
151
|
-
bitmap.draw_line(p1
|
|
149
|
+
const Point& p1 = sequence[i];
|
|
150
|
+
const Point& p2 = sequence[i+1];
|
|
151
|
+
bitmap.draw_line(p1.x, p1.y, p2.x, p2.y, 0, 128, 0, 255);
|
|
152
152
|
}
|
|
153
|
-
Point
|
|
154
|
-
Point
|
|
155
|
-
bitmap.draw_line(last
|
|
153
|
+
const Point& last = sequence.back();
|
|
154
|
+
const Point& first = sequence.front();
|
|
155
|
+
bitmap.draw_line(last.x, last.y, first.x, first.y, 0, 128, 0, 255);
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
}
|