contrek 1.2.1 → 1.2.2

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -1
  3. data/Gemfile.lock +1 -1
  4. data/PERFORMANCE.md +177 -0
  5. data/README.md +2 -1
  6. data/contrek.gemspec +5 -1
  7. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +32 -41
  8. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.h +2 -1
  9. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +33 -5
  10. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +3 -2
  11. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/InnerPolyline.cpp +1 -2
  12. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/InnerPolyline.h +1 -3
  13. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +16 -20
  14. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +8 -3
  15. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.cpp +5 -0
  16. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.h +3 -1
  17. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.cpp +2 -2
  18. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.h +1 -1
  19. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +5 -3
  20. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.h +4 -4
  21. data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +2 -3
  22. data/lib/contrek/finder/concurrent/cluster.rb +30 -29
  23. data/lib/contrek/finder/concurrent/cursor.rb +35 -12
  24. data/lib/contrek/finder/concurrent/inner_polyline.rb +2 -3
  25. data/lib/contrek/finder/concurrent/polyline.rb +5 -7
  26. data/lib/contrek/finder/concurrent/shape.rb +7 -3
  27. data/lib/contrek/finder/concurrent/tile.rb +5 -4
  28. data/lib/contrek/version.rb +1 -1
  29. metadata +3 -6
  30. data/ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp +0 -56
  31. data/ext/cpp_polygon_finder/PolygonFinder/images/graphs_1024x1024.png +0 -0
  32. data/ext/cpp_polygon_finder/PolygonFinder/images/labyrinth.png +0 -0
  33. data/ext/cpp_polygon_finder/PolygonFinder/images/sample_10240x10240.png +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e7576fef74116c62a018f66a13d9e29d215f7b505637b40bcb16631250fed0d
4
- data.tar.gz: 7ba2f39e97a44eaddd13361b4dc5a8f8cb1790a93dab327c1794d61e0ffc45a0
3
+ metadata.gz: 0d4e8a9a3c94ae345edb0ecbb6087020141ad20cd2661e2b58578323a721f66e
4
+ data.tar.gz: ec99c9629d41d589e90ff39d8305c7e0355743b3f51caeb1e0a9da4702007fde
5
5
  SHA512:
6
- metadata.gz: 748e4750b607bc2a9e9e916fc0a00a96db530e94658822d1d6eb97508fa9e302e5def958a63e621a2f97be1396bb0acad4971aea936cbca700c1ea93ac5acdaf
7
- data.tar.gz: 804a329359ea6648e4bcbf4babdb8af53a12205851d669b562fa37d5aa69a49f8b6988a143f2783c85b2681432f65ff52b82db8f207718bd29b16c46882f518d
6
+ metadata.gz: 9180029576fc846f3cbc8adcd68e5f68374b49fb734db8352e9ced637a447cfad93beb37cb5206084b411f4cb5cc26de9a1ac075b09f8ce1b39bb6e33fadd018
7
+ data.tar.gz: 163a00611440eb83d538b4dcd3f3c36745350c1d34e657fcdae069dd8c8020f36eec005264bd581e2024fca772e7f53dc0f856d35eca4717b741b0bcbaa91e5a
data/CHANGELOG.md CHANGED
@@ -83,4 +83,12 @@ All notable changes to this project will be documented in this file.
83
83
 
84
84
  ## [1.2.0] - 2026-05-02
85
85
  ### Changed
86
- - Further improvements have been applied to the internal parts joining algorithm using a new structural approach. This update is faster and resolves edge cases where inner parts were mistakenly classified as outer perimeters, ensuring precise contour hierarchy. The simplified logic has led to a significant reduction in codebase complexity and the removal of substantial redundant code.
86
+ - Further improvements have been applied to the internal parts joining algorithm using a new structural approach. This update is faster and resolves edge cases where inner parts were mistakenly classified as outer perimeters, ensuring precise contour hierarchy. The simplified logic has led to a significant reduction in codebase complexity and the removal of substantial redundant code.
87
+
88
+ ## [1.2.1] - 2026-05-09
89
+ ### Changed
90
+ - Some c++ optimizations.
91
+
92
+ ## [1.2.2] - 2026-05-20
93
+ ### Changed
94
+ - The treemap determination algorithm has been heavily optimized. Calls to the geometric routine that checks whether a newly generated inner polyline encloses other already-existing ones have been reduced to the minimum. Polylines adjacent to the shared overlap stripe are now excluded from these checks, as they are already identified during the initial polygon detection phase. The geometric approach remains unavoidable in this context and is still a performance bottleneck. It will certainly be the subject of future optimizations.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contrek (1.2.0)
4
+ contrek (1.2.2)
5
5
  chunky_png (~> 1.4)
6
6
  concurrent-ruby (~> 1.3.5)
7
7
  rice (= 4.5.0)
data/PERFORMANCE.md ADDED
@@ -0,0 +1,177 @@
1
+ # ⚡ Contrek Performance Tuning
2
+
3
+ This document describes optional dependencies and configuration tips to get the best performance out of Contrek on large images.
4
+
5
+ All optimizations are **optional** — Contrek works correctly without any of them. However, on high-resolution images (10k×10k and above), the combined effect is significant.
6
+
7
+ ---
8
+
9
+ ## Benchmark Reference
10
+
11
+ > System: AMD Ryzen 7 3700X 8-Core Processor (BogoMIPS: 7199,99) on Ubuntu distro
12
+ > Image: 20480×20480 pixels — 8 threads / 8 tiles
13
+ >
14
+ > **Note:** Benchmarks were measured inside a VMware virtual machine.
15
+
16
+ | Configuration | Time |
17
+ |---|---|
18
+ | Baseline (no tuning) | 5316 ms |
19
+ | **Fully tuned** | **2938.05 ms** |
20
+
21
+ ---
22
+
23
+ ## 1. zlib-ng — Faster PNG Decoding
24
+
25
+ **Impact: ~57% reduction in PNG decode time**
26
+
27
+ Contrek uses [libspng](https://libspng.org/) for PNG decoding, which internally relies on zlib for decompression. [zlib-ng](https://github.com/zlib-ng/zlib-ng) is a high-performance, drop-in replacement for zlib that uses modern CPU instructions (AVX2, SSE4) to significantly accelerate deflate decompression.
28
+
29
+ If zlib-ng is not installed, standard zlib is used automatically — no errors, just slower PNG decoding.
30
+
31
+ ### Installation
32
+
33
+ **Ubuntu / Debian** — not available in standard repos, build from source:
34
+
35
+ ```bash
36
+ git clone https://github.com/zlib-ng/zlib-ng.git
37
+ cd zlib-ng && mkdir build && cd build
38
+ cmake .. -DZLIB_COMPAT=ON -DCMAKE_BUILD_TYPE=Release
39
+ make -j$(nproc)
40
+ sudo make install
41
+ sudo ldconfig
42
+ ```
43
+
44
+ > ⚠️ The `-DZLIB_COMPAT=ON` flag is mandatory. Without it, zlib-ng uses a different ABI and CMake's `find_package(ZLIB)` won't detect it.
45
+
46
+ **macOS:**
47
+ ```bash
48
+ brew install zlib-ng
49
+ ```
50
+
51
+ **Arch Linux:**
52
+ ```bash
53
+ sudo pacman -S zlib-ng
54
+ ```
55
+
56
+ After installation, rebuild Contrek — CMake will automatically detect zlib-ng in `/usr/local` and use it instead of system zlib.
57
+
58
+ ---
59
+
60
+ ## 2. tcmalloc — Faster Memory Allocation
61
+
62
+ **Impact: significant reduction in allocator contention under multithreaded load**
63
+
64
+ Contrek creates and destroys large numbers of small objects during processing. Under multithreaded workloads, the standard glibc allocator serializes many of these operations, causing thread contention. [tcmalloc](https://github.com/google/tcmalloc) (Thread-Caching Malloc) is Google's high-performance allocator that maintains per-thread caches, dramatically reducing lock contention.
65
+
66
+ ### Installation
67
+
68
+ **Ubuntu / Debian:**
69
+ ```bash
70
+ sudo apt-get install libgoogle-perftools-dev
71
+ ```
72
+
73
+ **macOS:**
74
+ ```bash
75
+ brew install gperftools
76
+ ```
77
+
78
+ CMake will detect tcmalloc automatically. You will see this confirmation during the build:
79
+ ```
80
+ -- Contrek: tcmalloc found in /usr/lib/x86_64-linux-gnu/libtcmalloc.so
81
+ ```
82
+
83
+ ### Tuning tcmalloc cache size
84
+
85
+ For large images with many threads, increasing the per-thread cache size reduces requests to the central allocator. Add this at the very beginning of your `main()`:
86
+
87
+ ```cpp
88
+ #include <gperftools/malloc_extension.h>
89
+
90
+ int main() {
91
+ MallocExtension::instance()->SetNumericProperty(
92
+ "tcmalloc.max_total_thread_cache_bytes",
93
+ 1024 * 1024 * 1024 // 1GB total thread cache
94
+ );
95
+ // ...
96
+ }
97
+ ```
98
+
99
+ The default is 32MB total. On systems with 16GB+ RAM, 1GB is a safe value that virtually eliminates allocator contention.
100
+
101
+ ---
102
+
103
+ ## 3. Thread and Tile Configuration
104
+
105
+ **Impact: up to ~35% reduction in processing time on multi-core systems**
106
+
107
+ Contrek splits the image into vertical tiles processed in parallel. The optimal configuration depends on your hardware.
108
+
109
+ ### General rule
110
+
111
+ Set both `threads` and `tiles` to the number of **physical cores** on your machine.
112
+
113
+ ```cpp
114
+ Contrek::Config cfg;
115
+ cfg.threads = 8; // match your physical core count
116
+ cfg.tiles = 8; // same as threads for best results
117
+ ```
118
+
119
+ ```ruby
120
+ result = Contrek.contour!(
121
+ png_file_path: "image.png",
122
+ options: {
123
+ number_of_threads: 8,
124
+ finder: { number_of_tiles: 8 }
125
+ }
126
+ )
127
+ ```
128
+
129
+ ### Why threads == tiles?
130
+
131
+ - **Fewer tiles than threads**: some cores sit idle waiting for others to finish
132
+ - **More tiles than threads**: merge overhead increases without adding parallelism
133
+ - **threads == tiles**: optimal balance between parallel scan and merge cost
134
+
135
+ Consider this depends your system. Probably is better not to saturate all cores leaving one ot two to the system and the others to Contrek. So on 8 cpu core 6 thread/tiles at maximum.
136
+
137
+ ---
138
+
139
+ ## 4. Combining All Optimizations
140
+
141
+ Install zlib-ng and tcmalloc, then configure:
142
+
143
+ ```ruby
144
+ # Ruby
145
+ result = Contrek.contour!(
146
+ png_file_path: "large_image.png",
147
+ options: {
148
+ number_of_threads: 8, # match your core count (or 1-2 less)
149
+ class: "value_not_matcher",
150
+ color: { r: 255, g: 255, b: 255, a: 255 },
151
+ finder: {
152
+ number_of_tiles: 8, # same as threads
153
+ compress: { uniq: true }
154
+ }
155
+ }
156
+ )
157
+ ```
158
+
159
+ ```cpp
160
+ // C++ standalone
161
+ #include <gperftools/malloc_extension.h>
162
+ #include "ContrekApi.h"
163
+
164
+ int main() {
165
+ MallocExtension::instance()->SetNumericProperty(
166
+ "tcmalloc.max_total_thread_cache_bytes",
167
+ 1024 * 1024 * 1024
168
+ );
169
+
170
+ Contrek::Config cfg;
171
+ cfg.threads = 8;
172
+ cfg.tiles = 8;
173
+
174
+ auto result = Contrek::trace("large_image.png", cfg);
175
+ std::cout << "Time: " << result->total_time << " ms" << std::endl;
176
+ }
177
+ ```
data/README.md CHANGED
@@ -158,7 +158,8 @@ You can process from a raw stream
158
158
  [{:outer=>[{:x=>5, :y=>4}, {:x=>5, :y=>5}, {:x=>8, :y=>5}, {:x=>8, :y=>4}], :inner=>[]}]
159
159
  ```
160
160
 
161
- Multithreaded contour processing is supported. However, on Ruby MRI (the standard Ruby implementation, at least up to 3.x), the Global Interpreter Lock (GIL) prevents more than one thread from executing Ruby code simultaneously. As a consequence, execution remains effectively serialized even on multicore systems, unless the gem is used under JRuby or TruffleRuby (not tested).
161
+ Multithreaded contour processing is supported by both the native C++ and pure Ruby implementations. When using the C++ engine (default), multithreading works as expected and fully utilizes all available cores.
162
+ When running the pure Ruby implementation, however, the Global Interpreter Lock (GIL) in Ruby MRI (the standard Ruby interpreter, up to at least version 3.x) prevents true parallel execution — threads are serialized even on multicore systems. Switching to JRuby or TruffleRuby would bypass this limitation, though these runtimes have not been tested with Contrek.
162
163
 
163
164
  ```ruby
164
165
  result = Contrek.contour!(
data/contrek.gemspec CHANGED
@@ -11,7 +11,11 @@ Gem::Specification.new do |s|
11
11
  s.homepage = "https://github.com/runout77/contrek"
12
12
  s.licenses = ["MIT", "AGPL-3.0-only"]
13
13
  s.files = Dir.chdir(File.expand_path("..", __FILE__)) do
14
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(docs|pkg|spec)/}) }
14
+ `git ls-files -z`.split("\x0").reject do |f|
15
+ f.match(%r{^(docs|pkg|spec)/}) ||
16
+ f.include?("PolygonFinder/images/") ||
17
+ f.include?("PolygonFinder/examples/")
18
+ end
15
19
  end
16
20
  s.metadata = {
17
21
  "homepage_uri" => "https://github.com/runout77/contrek",
@@ -54,7 +54,7 @@ Tile* Cluster::merge_tiles() {
54
54
  double tot_outer = 0;
55
55
  CpuTimer timer;
56
56
 
57
- std::list<Shape*> new_shapes;
57
+ std::vector<Shape*> new_shapes;
58
58
  std::vector<InnerPolyline*> all_new_inner_polylines;
59
59
 
60
60
  timer.start();
@@ -69,7 +69,7 @@ Tile* Cluster::merge_tiles() {
69
69
  tot_outer += timer.stop();
70
70
 
71
71
  for (Tile* tile : tiles_) {
72
- std::list<Shape*>& src = tile->shapes();
72
+ std::vector<Shape*>& src = tile->shapes();
73
73
 
74
74
  for (Shape* shape : src) {
75
75
  if (shape->outer_polyline->is_on(Polyline::TRACKED_OUTER) || shape->outer_polyline->width() == 0) {
@@ -86,7 +86,7 @@ Tile* Cluster::merge_tiles() {
86
86
 
87
87
  timer.start();
88
88
  std::vector<InnerPolyline*> new_inners = shape->inner_polylines;
89
- std::vector<InnerPolyline*> new_inner_polylines = cursor.join_inners(new_outer);
89
+ std::vector<InnerPolyline*> new_inner_polylines = cursor.join_inners(new_outer, treemap);
90
90
  tot_inner += timer.stop();
91
91
 
92
92
  for (InnerPolyline* inner_polyline : new_inner_polylines) {
@@ -94,59 +94,44 @@ Tile* Cluster::merge_tiles() {
94
94
  if (treemap) {
95
95
  inner_polyline->sequence()->compute_vertical_bounds();
96
96
  all_new_inner_polylines.push_back(inner_polyline);
97
- for (const auto orphan_inner : cursor.orphan_inners()) {
98
- if (orphan_inner->recombined()) {
99
- all_new_inner_polylines.push_back(orphan_inner);
100
- }
101
- }
102
97
  }
103
98
  }
104
-
105
99
  for (auto s : cursor.orphan_inners()) {
106
100
  new_inners.push_back(s);
107
101
  }
108
-
109
102
  Polyline* polyline = tile->shapes_pool->acquire_polyline(tile, new_outer->to_vector(), std::nullopt);
110
103
  Shape* inserting_new_shape = tile->shapes_pool->acquire_shape(polyline, new_inners);
111
-
112
104
  new_shapes.push_back(inserting_new_shape);
113
105
  polyline->shape = inserting_new_shape;
114
- inserting_new_shape->set_parent_shape(shape->parent_shape());
115
106
 
116
107
  for (InnerPolyline* inner_polyline : new_inner_polylines) {
117
108
  inner_polyline->sequence()->shape = inserting_new_shape;
118
109
  }
119
-
120
110
  if (treemap) {
121
111
  for (const auto merged_shape : cursor.shapes_sequence()) {
122
112
  merged_shape->merged_to_shape = inserting_new_shape;
123
113
  }
124
- this->assign_ancestry(inserting_new_shape, all_new_inner_polylines);
114
+ InnerPolyline* inside_inner_polyline = shape->outer_polyline->inside_inner_polyline;
115
+ if (inside_inner_polyline) {
116
+ assign_ancestry(inserting_new_shape, inside_inner_polyline);
117
+ }
125
118
  }
126
-
127
119
  } else {
128
- if (treemap && !shape->reassociation_skip && shape->parent_shape() == nullptr) {
129
- this->assign_ancestry(shape, all_new_inner_polylines);
120
+ if (treemap) {
121
+ if (shape->fixed) {
122
+ Shape* ms = shape->parent_shape()->merged_to_shape;
123
+ if (ms) {
124
+ shape->set_parent_shape(ms);
125
+ }
126
+ } else {
127
+ is_children(shape, all_new_inner_polylines);
128
+ }
130
129
  }
131
130
  new_shapes.push_back(shape);
132
131
  }
133
132
  }
134
133
  }
135
134
 
136
- if (treemap) {
137
- for (Tile* tile : tiles_) {
138
- for (Shape* shape : tile->shapes()) {
139
- Shape* parent = shape->parent_shape();
140
- while (parent && parent->merged_to_shape != nullptr) {
141
- parent = parent->merged_to_shape;
142
- }
143
- if (parent != shape->parent_shape()) {
144
- shape->set_parent_shape(parent);
145
- }
146
- }
147
- }
148
- }
149
-
150
135
  double past_tot_outer = tiles_.front()->benchmarks.outer + tiles_.back()->benchmarks.outer;
151
136
  double past_tot_inner = tiles_.front()->benchmarks.inner + tiles_.back()->benchmarks.inner;
152
137
 
@@ -165,16 +150,22 @@ Tile* Cluster::merge_tiles() {
165
150
  return tile;
166
151
  }
167
152
 
168
- void Cluster::assign_ancestry(Shape *shape, std::vector<InnerPolyline*>& inner_polylines)
169
- { for (auto* inner_polyline : inner_polylines) {
170
- if (shape->outer_polyline->vert_bounds_intersect(inner_polyline->vertical_bounds())) {
171
- if (shape->outer_polyline->within(inner_polyline->raw())) {
172
- shape->set_parent_shape(inner_polyline->shape());
173
- shape->parent_inner_polyline = inner_polyline;
174
- for (auto* children_shape : shape->children_shapes) {
175
- children_shape->reassociation_skip = true;
176
- }
177
- }
153
+ void Cluster::assign_ancestry(Shape *shape, InnerPolyline* inner_polyline)
154
+ { shape->set_parent_shape(inner_polyline->sequence()->shape);
155
+ shape->parent_inner_polyline = inner_polyline;
156
+ shape->fixed = true;
157
+ }
158
+
159
+ void Cluster::is_children(Shape* shape, std::vector<InnerPolyline*> inner_polylines) {
160
+ int shape_max_y = shape->outer_polyline->max_y();
161
+ int shape_min_y = shape->outer_polyline->min_y();
162
+ for (InnerPolyline* inner_polyline : inner_polylines) {
163
+ Bounds bounds = inner_polyline->vertical_bounds();
164
+ int min_y = bounds.min;
165
+ int max_y = bounds.max;
166
+ if (shape_max_y < min_y || shape_min_y > max_y ) continue;
167
+ if (shape->outer_polyline->within(inner_polyline->raw())) {
168
+ assign_ancestry(shape, inner_polyline);
178
169
  }
179
170
  }
180
171
  }
@@ -21,7 +21,8 @@ class Cluster {
21
21
  Finder *finder;
22
22
  std::vector<Tile*> tiles_;
23
23
  Hub *hub_ = nullptr;
24
- void assign_ancestry(Shape *shape, std::vector<InnerPolyline*>& inner_polylines);
24
+ void assign_ancestry(Shape *shape, InnerPolyline* inner_polyline);
25
+ void is_children(Shape* shape, std::vector<InnerPolyline*> inner_polylines);
25
26
 
26
27
  public:
27
28
  Cluster(Finder *finder, int height, int start_x, int end_x);
@@ -129,7 +129,7 @@ void Cursor::traverse_outer(Part* act_part,
129
129
  }
130
130
  }
131
131
 
132
- std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq) {
132
+ std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq, bool treemap) {
133
133
  std::vector<InnerPolyline*> return_inner_polylines;
134
134
  std::vector<Shape*> processing_queue = shapes_sequence_;
135
135
  for (size_t i = 0; i < shapes_sequence_.size(); ++i)
@@ -138,11 +138,12 @@ std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq) {
138
138
  for (Part* part : polyline->parts())
139
139
  { if (part->innerable())
140
140
  { std::vector<Part*> all_parts;
141
+ std::vector<EndPoint*> tracked_end_points;
141
142
  Bounds bounds{
142
143
  .min = polyline->max_y(),
143
144
  .max = 0
144
145
  };
145
- traverse_inner(part, all_parts, bounds);
146
+ traverse_inner(part, all_parts, bounds, tracked_end_points);
146
147
  Sequence* retme_sequence = shape->outer_polyline->tile->shapes_pool->acquire_sequence();
147
148
  for (Part* part : all_parts)
148
149
  { part->touch();
@@ -157,7 +158,11 @@ std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq) {
157
158
  });
158
159
  }
159
160
  if (retme_sequence->is_not_vertical()) {
160
- return_inner_polylines.push_back(polyline->tile->shapes_pool->acquire_inner_polyline(retme_sequence));
161
+ InnerPolyline* inner_polyline = polyline->tile->shapes_pool->acquire_inner_polyline(retme_sequence);
162
+ return_inner_polylines.push_back(inner_polyline);
163
+ if (treemap) {
164
+ mark_children(tracked_end_points, polyline, inner_polyline);
165
+ }
161
166
  }
162
167
  }
163
168
  }
@@ -165,7 +170,26 @@ std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq) {
165
170
  return(return_inner_polylines);
166
171
  }
167
172
 
168
- void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bounds& bounds) {
173
+ void Cursor::mark_children(std::vector<EndPoint*>& end_points, const Polyline* outer_polyline, InnerPolyline* inner_polyline) {
174
+ for (size_t i = 0; i + 1 < end_points.size(); i += 2) {
175
+ const auto& a = end_points[i];
176
+ const auto& b = end_points[i + 1];
177
+ auto [y_min, y_max] = std::minmax(a->get_point()->y, b->get_point()->y);
178
+ for (int y = y_min + 1; y < y_max; ++y) {
179
+ EndPoint* end_point = this->cluster.hub()->get(y);
180
+ if (end_point) {
181
+ for (auto part_p : end_point->queues())
182
+ { Part* part = static_cast<Part*>(part_p);
183
+ if (part->polyline() != outer_polyline) {
184
+ part->polyline()->inside_inner_polyline = inner_polyline;
185
+ }
186
+ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bounds& bounds, std::vector<EndPoint*>& tracked_end_points) {
169
193
  PartPool& pool = act_part->polyline()->tile->cluster->parts_pool;
170
194
  while (act_part != nullptr) {
171
195
  if (!all_parts.empty() && act_part == all_parts.front()) return;
@@ -186,13 +210,17 @@ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bound
186
210
  all_parts.push_back(act_part);
187
211
  } else {
188
212
  if (act_part->head)
189
- { for (auto dest_part_p : static_cast<Position*>(act_part->head)->end_point()->queues()) {
213
+ { EndPoint* current_end_point = static_cast<Position*>(act_part->head)->end_point();
214
+ for (auto dest_part_p : current_end_point->queues()) {
190
215
  Part* dest_part = static_cast<Part*>(dest_part_p);
191
216
  if (dest_part->polyline()->tile == act_part->polyline()->tile) {
192
217
  continue;
193
218
  }
194
219
  int dest_part_versus = dest_part->versus();
195
220
  if (dest_part_versus != 0 && dest_part_versus == act_part->versus()) continue;
221
+
222
+ tracked_end_points.push_back(current_end_point);
223
+
196
224
  std::vector<EndPoint*> link_seq = dest_part->continuum_to(*act_part);
197
225
  if (!link_seq.empty()) {
198
226
  Part* ins_part = pool.acquire(Part::ADDED, act_part->polyline());
@@ -21,7 +21,7 @@ class Cursor {
21
21
  public:
22
22
  Cursor(Cluster& cluster, Shape* shape);
23
23
  Sequence* join_outers();
24
- std::vector<InnerPolyline*> join_inners(Sequence* outer_seq);
24
+ std::vector<InnerPolyline*> join_inners(Sequence* outer_seq, bool treemap);
25
25
  std::list<InnerPolyline*> orphan_inners() { return orphan_inners_; }
26
26
  const std::vector<Shape*>& shapes_sequence() const { return shapes_sequence_; }
27
27
 
@@ -35,6 +35,7 @@ class Cursor {
35
35
  std::vector<Part*>& all_parts,
36
36
  std::vector<Shape*>& shapes_sequence,
37
37
  Sequence* outer_joined_polyline);
38
- void traverse_inner(Part* act_part, std::vector<Part*> &all_parts, Bounds& bounds);
38
+ void traverse_inner(Part* act_part, std::vector<Part*> &all_parts, Bounds& bounds, std::vector<EndPoint*>& tracked_end_points);
39
39
  std::vector<Shape*> connect_missings(std::vector<Shape*> shapes_sequence, std::vector<Shape*> missing_shapes);
40
+ void mark_children(std::vector<EndPoint*>& end_points, const Polyline* outer_polyline, InnerPolyline* inner_polyline);
40
41
  };
@@ -11,9 +11,8 @@
11
11
  #include <vector>
12
12
  #include "InnerPolyline.h"
13
13
 
14
- InnerPolyline::InnerPolyline(std::vector<Point*> raw_coordinates, Shape* shape, bool recombined)
14
+ InnerPolyline::InnerPolyline(std::vector<Point*> raw_coordinates, Shape* shape)
15
15
  : raw_coordinates_(std::move(raw_coordinates)),
16
- recombined_(recombined),
17
16
  shape_(shape) {}
18
17
  InnerPolyline::InnerPolyline(Sequence* sequence)
19
18
  : sequence_(sequence) {
@@ -14,16 +14,14 @@
14
14
 
15
15
  class InnerPolyline {
16
16
  public:
17
- explicit InnerPolyline(std::vector<Point*> raw_coordinates, Shape* shape, bool recombined = false);
17
+ explicit InnerPolyline(std::vector<Point*> raw_coordinates, Shape* shape);
18
18
  explicit InnerPolyline(Sequence* sequence);
19
19
  std::vector<Point*>& raw();
20
20
  Sequence* sequence() { return this->sequence_; }
21
21
  Bounds& vertical_bounds();
22
- bool recombined() { return this->recombined_; }
23
22
  Shape* shape();
24
23
  Shape* assigned_shape = nullptr;
25
24
  private:
26
- bool recombined_ = false;
27
25
  std::vector<Point*> raw_coordinates_;
28
26
  Sequence* sequence_ = nullptr;
29
27
  Shape* shape_;
@@ -22,15 +22,12 @@ Polyline::Polyline(Tile* tile, const std::vector<Point*>& polygon, const std::op
22
22
  { if (bounds.has_value()) {
23
23
  min_x = bounds->min_x;
24
24
  max_x = bounds->max_x;
25
- min_y = bounds->min_y;
25
+ min_y_ = bounds->min_y;
26
26
  max_y_ = bounds->max_y;
27
27
  } else {
28
28
  this->find_boundary(); // TODO(ema): optimize when merging the bounds are the sum of the previouses
29
29
  }
30
- }
31
-
32
- bool Polyline::vert_intersect(Polyline& other) {
33
- return( !(this->max_y_ < other.min_y || other.max_y_ < this->min_y));
30
+ this->name = tile->shapes().size();
34
31
  }
35
32
 
36
33
  int Polyline::width() {
@@ -46,7 +43,7 @@ void Polyline::find_boundary() {
46
43
  if (raw_.empty()) return;
47
44
  min_x = std::numeric_limits<int>::max();
48
45
  max_x = -std::numeric_limits<int>::max();
49
- min_y = std::numeric_limits<int>::max();
46
+ min_y_ = std::numeric_limits<int>::max();
50
47
  max_y_ = -std::numeric_limits<int>::max();
51
48
  for (Point* p : raw_) {
52
49
  if (!p) continue;
@@ -54,7 +51,7 @@ void Polyline::find_boundary() {
54
51
  int y = p->y;
55
52
  if (x < min_x) min_x = x;
56
53
  if (x > max_x) max_x = x;
57
- if (y < min_y) min_y = y;
54
+ if (y < min_y_) min_y_ = y;
58
55
  if (y > max_y_) max_y_ = y;
59
56
  }
60
57
  }
@@ -67,20 +64,8 @@ bool Polyline::is_empty() {
67
64
  return raw_.empty();
68
65
  }
69
66
 
70
- std::string Polyline::info() {
71
- Shape* shape = this->shape;
72
- size_t part_index = 0;
73
- auto it = std::find(this->tile->shapes().begin(), this->tile->shapes().end(), shape);
74
- if (it != this->tile->shapes().end()) {
75
- part_index = std::distance(this->tile->shapes().begin(), it);
76
- }
77
- std::stringstream ss;
78
- ss << "b" << this->tile->name() << " S" << part_index;
79
- return ss.str();
80
- }
81
-
82
67
  bool Polyline::vert_bounds_intersect(Bounds& vertical_bounds)
83
- { return !(this->max_y_ < vertical_bounds.min || vertical_bounds.max < this->min_y);
68
+ { return !(this->max_y_ < vertical_bounds.min || vertical_bounds.max < this->min_y_);
84
69
  }
85
70
 
86
71
  bool Polyline::within(std::vector<Point*>& points) {
@@ -99,3 +84,14 @@ bool Polyline::within(std::vector<Point*>& points) {
99
84
  }
100
85
  return inside;
101
86
  }
87
+
88
+ std::string Polyline::named() {
89
+ if (this->named_.empty()) {
90
+ std::stringstream ss;
91
+ ss << "t" << this->tile->name() << "s" << this->name;
92
+ if (this->boundary()) ss << "B";
93
+ return ss.str();
94
+ } else {
95
+ return this->named_;
96
+ }
97
+ }
@@ -23,6 +23,7 @@
23
23
  class Tile;
24
24
  class Shape;
25
25
  class Point;
26
+ class InnerPolyline;
26
27
 
27
28
  class Polyline : public Partitionable {
28
29
  public:
@@ -42,17 +43,21 @@ class Polyline : public Partitionable {
42
43
  std::vector<Point*> raw() const { return raw_; }
43
44
  const std::vector<Part*>& parts() const { return parts_; }
44
45
  const int max_y() const { return max_y_; }
46
+ const int min_y() const { return min_y_; }
45
47
  void clear();
46
48
  bool is_empty();
47
- bool vert_intersect(Polyline& other);
48
49
  bool any_ancients = false;
49
- std::string info();
50
50
  bool vert_bounds_intersect(Bounds& vertical_bounds);
51
51
  bool within(std::vector<Point*>& points);
52
+ InnerPolyline* inside_inner_polyline = nullptr;
53
+ std::string named();
54
+ void set_named(std::string force_named) { this->named_ = force_named; }
52
55
 
53
56
  private:
54
57
  std::vector<Point*> raw_;
55
- int min_x, max_x, min_y, max_y_;
58
+ int min_x, max_x, min_y_, max_y_;
56
59
  void find_boundary();
57
60
  uint32_t flags_ = 0;
61
+ std::string named_;
62
+ int name;
58
63
  };
@@ -9,6 +9,7 @@
9
9
 
10
10
  #include <list>
11
11
  #include <vector>
12
+ #include <string>
12
13
  #include "Shape.h"
13
14
  #include "Polyline.h"
14
15
 
@@ -34,3 +35,7 @@ void Shape::set_parent_shape(Shape* shape) {
34
35
  { shape->children_shapes.push_back(this);
35
36
  }
36
37
  }
38
+
39
+ std::string Shape::name() {
40
+ return(this->outer_polyline->named());
41
+ }
@@ -11,6 +11,7 @@
11
11
  #include <list>
12
12
  #include <iostream>
13
13
  #include <vector>
14
+ #include <string>
14
15
  #include "InnerPolyline.h"
15
16
 
16
17
  class Point;
@@ -24,9 +25,10 @@ class Shape {
24
25
  InnerPolyline* parent_inner_polyline = nullptr;
25
26
  std::vector<Shape*> children_shapes;
26
27
  void clear_inner();
27
- bool reassociation_skip = false;
28
+ bool fixed = false;
28
29
  Shape* parent_shape() { return parent_shape_; }
29
30
  void set_parent_shape(Shape*);
31
+ std::string name();
30
32
  private:
31
33
  Shape* parent_shape_ = nullptr;
32
34
  };
@@ -17,8 +17,8 @@ Shape* ShapePool::acquire_shape(Polyline* outer_polyline, const std::vector<Inne
17
17
  return shape;
18
18
  }
19
19
 
20
- InnerPolyline* ShapePool::acquire_inner_polyline(std::vector<Point*> coords, Shape* shape, bool rec) {
21
- inner_polylines_storage.emplace_back(coords, shape, rec);
20
+ InnerPolyline* ShapePool::acquire_inner_polyline(std::vector<Point*> coords, Shape* shape) {
21
+ inner_polylines_storage.emplace_back(coords, shape);
22
22
  InnerPolyline* ip = &inner_polylines_storage.back();
23
23
  return ip;
24
24
  }
@@ -27,7 +27,7 @@ class ShapePool {
27
27
 
28
28
  public:
29
29
  Shape* acquire_shape(Polyline* outer_polyline, const std::vector<InnerPolyline*>& inner_polylines);
30
- InnerPolyline* acquire_inner_polyline(std::vector<Point*> coords, Shape* s, bool rec);
30
+ InnerPolyline* acquire_inner_polyline(std::vector<Point*> coords, Shape* s);
31
31
  InnerPolyline* acquire_inner_polyline(Sequence* seq);
32
32
  Sequence* acquire_sequence();
33
33
  Polyline* acquire_polyline(Tile* tile, const std::vector<Point*>& polygon, const std::optional<RectBounds>& bounds);
@@ -61,7 +61,7 @@ void Tile::assign_raw_polygons(const std::list<Polygon>& raw_polylines, const st
61
61
  { Polyline* polyline = this->shapes_pool->acquire_polyline(this, raw_polyline.outer, raw_polyline.bounds);
62
62
  std::vector<InnerPolyline*> inner_polylines_list;
63
63
  for (auto& raw_points : raw_polyline.inner) {
64
- inner_polylines_list.push_back(this->shapes_pool->acquire_inner_polyline(raw_points, nullptr, false));
64
+ inner_polylines_list.push_back(this->shapes_pool->acquire_inner_polyline(raw_points, nullptr));
65
65
  }
66
66
  Shape* shape = this->shapes_pool->acquire_shape(polyline, inner_polylines_list);
67
67
  polyline->shape = shape;
@@ -75,6 +75,7 @@ void Tile::assign_raw_polygons(const std::list<Polygon>& raw_polylines, const st
75
75
  if (it != shapes_map.end()) {
76
76
  Shape* parent = it->second;
77
77
  shape->set_parent_shape(parent);
78
+ shape->fixed = true;
78
79
  shape->parent_inner_polyline = parent->inner_polylines[treemap_entry.second];
79
80
  }
80
81
  }
@@ -84,7 +85,7 @@ void Tile::assign_raw_polygons(const std::list<Polygon>& raw_polylines, const st
84
85
  }
85
86
  }
86
87
 
87
- void Tile::assign_shapes(std::list<Shape*>& shapes) {
88
+ void Tile::assign_shapes(std::vector<Shape*>& shapes) {
88
89
  for (Shape *shape : shapes) {
89
90
  shape->outer_polyline->tile = this;
90
91
  }
@@ -135,7 +136,8 @@ std::vector<std::pair<int, int>> Tile::compute_treemap()
135
136
  for (auto* shape : this->shapes_) {
136
137
  if (shape->outer_polyline->is_empty()) continue;
137
138
  if (shape->parent_shape() != nullptr) {
138
- int p_idx = shapes_map[shape->parent_shape()];
139
+ auto p_it = shapes_map.find(shape->parent_shape());
140
+ int p_idx = (p_it != shapes_map.end()) ? p_it->second : -1;
139
141
  const auto& inners = shape->parent_shape()->inner_polylines;
140
142
  auto it = std::find(inners.begin(), inners.end(), shape->parent_inner_polyline);
141
143
  int inner_idx = static_cast<int>(std::distance(inners.begin(), it));
@@ -30,7 +30,7 @@ class Tile {
30
30
  int start_x_;
31
31
  int end_x_;
32
32
  std::string name_;
33
- std::list<Shape*> shapes_;
33
+ std::vector<Shape*> shapes_;
34
34
 
35
35
  public:
36
36
  Tile(Finder *finder, int start_x, int end_x, std::string name, const Benchmarks& b);
@@ -42,15 +42,15 @@ class Tile {
42
42
  int start_x() const { return start_x_; }
43
43
  int end_x() const { return end_x_; }
44
44
  std::string name() const { return name_; }
45
- const std::list<Shape*>& shapes() const { return shapes_; }
46
- std::list<Shape*>& shapes() { return shapes_; }
45
+ const std::vector<Shape*>& shapes() const { return shapes_; }
46
+ std::vector<Shape*>& shapes() { return shapes_; }
47
47
  bool whole();
48
48
  bool left();
49
49
  bool right();
50
50
  void initial_process(ClippedPolygonFinder *finder);
51
51
  void info();
52
52
  bool tg_border(const Point& coord);
53
- void assign_shapes(std::list<Shape*>& shapes);
53
+ void assign_shapes(std::vector<Shape*>& shapes);
54
54
  void assign_raw_polygons(const std::list<Polygon>& raw_polylines, const std::vector<std::pair<int, int>>& treemap);
55
55
  std::list<Polygon> to_raw_polygons();
56
56
  std::vector<std::pair<int, int>> compute_treemap();
@@ -291,9 +291,8 @@ extern "C"
291
291
  void Init_cpp_polygon_finder() {
292
292
  #ifdef HAVE_TCMALLOC
293
293
  MallocExtension::instance()->SetNumericProperty(
294
- "tcmalloc.max_total_thread_cache_bytes",
295
- 1024 * 1024 * 1024
296
- );
294
+ "tcmalloc.max_total_thread_cache_bytes",
295
+ 1024 * 1024 * 1024);
297
296
  #endif
298
297
 
299
298
  Data_Type<Bitmap> rb_cBitmap =
@@ -6,7 +6,7 @@ module Contrek
6
6
  def initialize(finder:, height:, start_x:, end_x:)
7
7
  @finder = finder
8
8
  @tiles = []
9
- @hub = Hub.new(height: )
9
+ @hub = Hub.new(height:)
10
10
  end
11
11
 
12
12
  def add(tile)
@@ -28,6 +28,7 @@ module Contrek
28
28
 
29
29
  new_shapes = []
30
30
  all_new_inner_polylines = []
31
+
31
32
  tot_outer += Benchmark.measure do
32
33
  @tiles.each do |tile|
33
34
  tile.shapes.each do |shape|
@@ -54,13 +55,12 @@ module Contrek
54
55
  new_inners = shape.inner_polylines
55
56
  new_inner_polylines = []
56
57
  tot_inner += Benchmark.measure do
57
- new_inner_polylines = cursor.join_inners!(new_outer)
58
+ new_inner_polylines = cursor.join_inners!(new_outer, treemap)
58
59
  new_inners += new_inner_polylines
59
60
  if treemap
60
- new_inner_polylines.each { |p| p.sequence.compute_vertical_bounds! }
61
- all_new_inner_polylines += new_inner_polylines
62
- cursor.orphan_inners.each do |orphan_inner|
63
- all_new_inner_polylines << orphan_inner if orphan_inner.recombined
61
+ new_inner_polylines.each do |inner_polyline|
62
+ inner_polyline.sequence.compute_vertical_bounds!
63
+ all_new_inner_polylines += new_inner_polylines
64
64
  end
65
65
  end
66
66
  new_inners += cursor.orphan_inners
@@ -70,7 +70,6 @@ module Contrek
70
70
  inserting_new_shape = Shape.init_by(polyline, new_inners)
71
71
  new_shapes << inserting_new_shape
72
72
  polyline.shape = inserting_new_shape
73
- inserting_new_shape.set_parent_shape(shape.parent_shape)
74
73
 
75
74
  new_inner_polylines.each { |inner_polyline| inner_polyline.sequence.shape = inserting_new_shape }
76
75
 
@@ -78,23 +77,20 @@ module Contrek
78
77
  cursor.shapes_sequence.each do |merged_shape|
79
78
  merged_shape.merged_to_shape = inserting_new_shape
80
79
  end
81
- assign_ancestry(inserting_new_shape, all_new_inner_polylines)
80
+ if shape.outer_polyline.inside_inner_polyline
81
+ assign_ancestry(inserting_new_shape, shape.outer_polyline.inside_inner_polyline)
82
+ end
82
83
  end
83
84
  else
84
- if treemap && !shape.reassociation_skip && shape.parent_shape.nil?
85
- assign_ancestry(shape, all_new_inner_polylines)
85
+ if treemap
86
+ if shape.fixed
87
+ shape.set_parent_shape(shape.parent_shape.merged_to_shape) if shape.parent_shape.merged_to_shape
88
+ else
89
+ is_children(shape, all_new_inner_polylines)
90
+ end
86
91
  end
87
- new_shapes << shape
88
- end
89
- end
90
- end
91
92
 
92
- if treemap
93
- @tiles.each do |tile|
94
- tile.shapes.each do |shape|
95
- if (merged_to_shape = shape.parent_shape&.merged_to_shape)
96
- shape.set_parent_shape(merged_to_shape)
97
- end
93
+ new_shapes << shape
98
94
  end
99
95
  end
100
96
  end
@@ -116,16 +112,21 @@ module Contrek
116
112
 
117
113
  private
118
114
 
119
- def assign_ancestry(shape, inner_polylines)
115
+ def assign_ancestry(shape, inner_polyline)
116
+ shape.set_parent_shape(inner_polyline.sequence.shape)
117
+ shape.parent_inner_polyline = inner_polyline
118
+ shape.fixed = true
119
+ end
120
+
121
+ def is_children(shape, inner_polylines)
120
122
  inner_polylines.each do |inner_polyline|
121
- if shape.outer_polyline.vert_bounds_intersect?(inner_polyline.vertical_bounds)
122
- if shape.outer_polyline.within?(inner_polyline.raw)
123
- shape.set_parent_shape(inner_polyline.shape)
124
- shape.parent_inner_polyline = inner_polyline
125
- shape.children_shapes.each do |children_shape|
126
- children_shape.reassociation_skip = true
127
- end
128
- end
123
+ bounds = inner_polyline.vertical_bounds
124
+ min_y = bounds[:min]
125
+ max_y = bounds[:max]
126
+ next if shape.outer_polyline.get_bounds[:max_y] < min_y ||
127
+ shape.outer_polyline.get_bounds[:min_y] > max_y
128
+ if shape.outer_polyline.within?(inner_polyline.raw)
129
+ assign_ancestry(shape, inner_polyline)
129
130
  end
130
131
  end
131
132
  end
@@ -4,7 +4,7 @@ module Contrek
4
4
  attr_reader :orphan_inners, :shapes_sequence
5
5
 
6
6
  def initialize(cluster:, shape:)
7
- @shapes_sequence = Set.new([shape])
7
+ @shapes_sequence = [shape]
8
8
  @cluster = cluster
9
9
  @outer_polyline = shape.outer_polyline
10
10
  @orphan_inners = []
@@ -38,18 +38,19 @@ module Contrek
38
38
  outer_joined_polyline
39
39
  end
40
40
 
41
- def join_inners!(outer_seq)
41
+ def join_inners!(outer_seq, treemap)
42
42
  return_inner_polylines = []
43
+ shape_index = 0
43
44
 
44
- @processing_shapes = @shapes_sequence.to_a
45
-
46
- @processing_shapes.each do |shape|
45
+ while shape_index < @shapes_sequence.size
46
+ shape = @shapes_sequence[shape_index]
47
47
  polyline = shape.outer_polyline
48
48
  polyline.parts.each do |part|
49
49
  if part.innerable?
50
50
  all_parts = []
51
+ tracked_end_points = []
51
52
  bounds = {min: polyline.max_y, max: 0}
52
- traverse_inner(part, all_parts, bounds)
53
+ traverse_inner(part, all_parts, bounds, tracked_end_points)
53
54
  range_of_bounds = (bounds[:min]..bounds[:max])
54
55
 
55
56
  retme_sequence = Sequence.new
@@ -62,16 +63,36 @@ module Contrek
62
63
  end
63
64
  end
64
65
  if retme_sequence.is_not_vertical
65
- return_inner_polylines << InnerPolyline.new(sequence: retme_sequence)
66
+ inner_polyline = InnerPolyline.new(sequence: retme_sequence)
67
+ return_inner_polylines << inner_polyline
68
+ mark_children(tracked_end_points, polyline, inner_polyline) if treemap
66
69
  end
67
70
  end
68
71
  end
72
+ shape_index += 1
69
73
  end
70
74
  return_inner_polylines
71
75
  end
72
76
 
73
77
  private
74
78
 
79
+ # finds each part (and relative polyline) inscribed between two end_points and sets the
80
+ # founded inner_polyline which be later used to define in which parent hole is placed.
81
+ def mark_children(end_points, outer_polyline, inner_polyline)
82
+ end_points.each_slice(2) do |a, b|
83
+ range = [a.position[:y], b.position[:y]].sort
84
+ (range[0] + 1).upto(range[1] - 1) do |y|
85
+ if (end_point = @cluster.hub.payloads[y])
86
+ end_point.queues.each do |part|
87
+ if part.polyline != outer_polyline
88
+ part.polyline.inside_inner_polyline = inner_polyline
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+
75
96
  # rubocop:disable Lint/NonLocalExitFromIterator
76
97
  def traverse_outer(act_part, all_parts, shapes_sequence, outer_joined_polyline)
77
98
  last_part = all_parts.last
@@ -104,7 +125,7 @@ module Contrek
104
125
  cont = false if map.size == 1 && map.first == Part::SEAM
105
126
  end
106
127
  if cont
107
- shapes_sequence.add(part.polyline.shape)
128
+ shapes_sequence << part.polyline.shape if !shapes_sequence.include?(part.polyline.shape)
108
129
  part.next_position(new_position)
109
130
  part.dead_end = true
110
131
  traverse_outer(part, all_parts, shapes_sequence, outer_joined_polyline)
@@ -120,7 +141,7 @@ module Contrek
120
141
  traverse_outer(act_part.circular_next, all_parts, shapes_sequence, outer_joined_polyline)
121
142
  end
122
143
 
123
- def traverse_inner(act_part, all_parts, bounds)
144
+ def traverse_inner(act_part, all_parts, bounds, tracked_end_points)
124
145
  return if act_part == all_parts.first
125
146
 
126
147
  if act_part.size > 0
@@ -140,6 +161,8 @@ module Contrek
140
161
  dest_part_versus = dest_part.versus
141
162
  next if dest_part_versus != 0 && dest_part_versus == act_part.versus
142
163
 
164
+ tracked_end_points << act_part.head.end_point
165
+
143
166
  link_seq = dest_part.continuum_to?(act_part)
144
167
  if link_seq.any?
145
168
  ins_part = Part.new(Part::ADDED, act_part.polyline)
@@ -150,13 +173,13 @@ module Contrek
150
173
  end
151
174
  shape = dest_part.polyline.shape
152
175
  if !dest_part.polyline.on?(Polyline::TRACKED_OUTER)
153
- @processing_shapes << shape
176
+ @shapes_sequence << shape
154
177
  @orphan_inners += shape.inner_polylines
155
178
  end
156
179
  dest_part.polyline.turn_on(Polyline::TRACKED_OUTER)
157
180
  if !dest_part.touched
158
181
  dest_part.touch!
159
- traverse_inner(dest_part.circular_next, all_parts, bounds)
182
+ traverse_inner(dest_part.circular_next, all_parts, bounds, tracked_end_points)
160
183
  return
161
184
  end
162
185
  end
@@ -165,7 +188,7 @@ module Contrek
165
188
  end
166
189
  end
167
190
  elsif act_part.next
168
- traverse_inner(act_part.next, all_parts, bounds)
191
+ traverse_inner(act_part.next, all_parts, bounds, tracked_end_points)
169
192
  end
170
193
  end
171
194
  # rubocop:enable Lint/NonLocalExitFromIterator
@@ -1,12 +1,11 @@
1
1
  module Contrek
2
2
  module Concurrent
3
3
  class InnerPolyline
4
- attr_reader :sequence, :recombined
4
+ attr_reader :sequence
5
5
 
6
- def initialize(shape: nil, raw_coordinates: [], sequence: nil, recombined: false)
6
+ def initialize(shape: nil, raw_coordinates: [], sequence: nil)
7
7
  @raw = raw_coordinates if raw_coordinates
8
8
  @sequence = sequence if sequence
9
- @recombined = recombined
10
9
  @shape = shape
11
10
  end
12
11
 
@@ -6,15 +6,17 @@ module Contrek
6
6
  TRACKED_OUTER = 1 << 0
7
7
 
8
8
  attr_reader :raw, :name, :min_y, :max_y
9
- attr_accessor :shape, :tile, :any_ancients
9
+ attr_accessor :shape, :tile, :any_ancients, :inside_inner_polyline
10
10
 
11
- def initialize(tile:, polygon:, shape: nil, bounds: nil)
11
+ def initialize(tile:, polygon:, shape: nil, bounds: nil, force_named: nil)
12
12
  @tile = tile
13
13
  @name = tile.shapes.count
14
+ @named = force_named
14
15
  @raw = polygon
15
16
  @shape = shape
16
17
  @flags = 0
17
18
  @any_ancients = false
19
+ @inside_inner_polyline = nil
18
20
 
19
21
  if bounds.nil?
20
22
  find_boundary
@@ -31,17 +33,13 @@ module Contrek
31
33
  end
32
34
 
33
35
  def named
34
- "[b#{@tile.name} S#{@name} #{"B" if boundary?}]"
36
+ @named || "t#{@tile.name}s#{@name}#{"B" if boundary?}"
35
37
  end
36
38
 
37
39
  def numpy_raw
38
40
  raw.flat_map { |p| [p[:x], p[:y]] }
39
41
  end
40
42
 
41
- def info
42
- "w#{@tile.name} S#{@name}"
43
- end
44
-
45
43
  def turn_on(flag)
46
44
  @flags |= flag
47
45
  end
@@ -1,8 +1,8 @@
1
1
  module Contrek
2
2
  module Concurrent
3
3
  class Shape
4
- attr_accessor :outer_polyline, :inner_polylines, :merged_to_shape, :parent_inner_polyline,
5
- :reassociation_skip
4
+ attr_accessor :outer_polyline, :inner_polylines, :merged_to_shape,
5
+ :parent_inner_polyline, :fixed
6
6
  attr_reader :parent_shape, :children_shapes
7
7
 
8
8
  def initialize
@@ -10,7 +10,7 @@ module Contrek
10
10
  @merged_to_shape = nil
11
11
  @parent_inner_polyline = nil
12
12
  @children_shapes = []
13
- @reassociation_skip = false
13
+ @fixed = false
14
14
  end
15
15
 
16
16
  def self.init_by(set_outer_polyline, set_inner_polylines)
@@ -24,6 +24,10 @@ module Contrek
24
24
  @inner_polylines.clear
25
25
  end
26
26
 
27
+ def name
28
+ outer_polyline.named
29
+ end
30
+
27
31
  def set_parent_shape(shape)
28
32
  @parent_shape&.children_shapes&.delete(self)
29
33
  @parent_shape = shape
@@ -61,15 +61,15 @@ module Contrek
61
61
  end
62
62
 
63
63
  def compute_treemap
64
- @shapes_map = {}
64
+ shapes_map = {}
65
65
  shape_index = 0
66
66
 
67
- @shapes.map do |shape|
67
+ shapes.map do |shape|
68
68
  next if shape.outer_polyline.empty?
69
- @shapes_map[shape] = shape_index
69
+ shapes_map[shape] = shape_index
70
70
  shape_index += 1
71
71
  if shape.parent_shape
72
- [@shapes_map[shape.parent_shape], shape.parent_shape.inner_polylines.index(shape.parent_inner_polyline)]
72
+ [shapes_map[shape.parent_shape], shape.parent_shape.inner_polylines.index(shape.parent_inner_polyline)]
73
73
  else
74
74
  [-1, -1]
75
75
  end
@@ -104,6 +104,7 @@ module Contrek
104
104
  if treemap_entry != [-1, -1]
105
105
  parent = shapes_map[treemap_entry.first]
106
106
  shape.set_parent_shape(parent)
107
+ shape.fixed = true
107
108
  shape.parent_inner_polyline = parent.inner_polylines[treemap_entry.last]
108
109
  end
109
110
  end
@@ -1,3 +1,3 @@
1
1
  module Contrek
2
- VERSION = "1.2.1"
2
+ VERSION = "1.2.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contrek
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emanuele Cesaroni
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-09 00:00:00.000000000 Z
11
+ date: 2026-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -136,6 +136,7 @@ files:
136
136
  - Gemfile
137
137
  - Gemfile.lock
138
138
  - LICENSE.md
139
+ - PERFORMANCE.md
139
140
  - README.md
140
141
  - Rakefile
141
142
  - contrek.gemspec
@@ -143,10 +144,6 @@ files:
143
144
  - ext/cpp_polygon_finder/PolygonFinder/CMakeLists.txt
144
145
  - ext/cpp_polygon_finder/PolygonFinder/LICENSE_AGPL.txt
145
146
  - ext/cpp_polygon_finder/PolygonFinder/clean.sh
146
- - ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp
147
- - ext/cpp_polygon_finder/PolygonFinder/images/graphs_1024x1024.png
148
- - ext/cpp_polygon_finder/PolygonFinder/images/labyrinth.png
149
- - ext/cpp_polygon_finder/PolygonFinder/images/sample_10240x10240.png
150
147
  - ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h
151
148
  - ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp
152
149
  - ext/cpp_polygon_finder/PolygonFinder/src/Tests.h
@@ -1,56 +0,0 @@
1
- /*
2
- * example.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 <iostream>
11
- #include <gperftools/malloc_extension.h>
12
- #include "ContrekApi.h"
13
- #include "Tests.h"
14
-
15
- void run_test() {
16
- CpuTimer cpu_timer;
17
- Tests test_suite;
18
- cpu_timer.start();
19
-
20
- // test_suite.test_a();
21
- // test_suite.test_b();
22
- // test_suite.test_c();
23
- // test_suite.test_d();
24
- // test_suite.test_e();
25
- // test_suite.test_f();
26
- // test_suite.test_g();
27
- // test_suite.test_h();
28
- // test_suite.test_i();
29
- std::cout << "compute time =" << cpu_timer.stop() << std::endl;
30
- }
31
-
32
- int main() {
33
- MallocExtension::instance()->SetNumericProperty(
34
- "tcmalloc.max_total_thread_cache_bytes",
35
- 1024 * 1024 * 1024
36
- );
37
-
38
- Contrek::Config cfg;
39
- cfg.threads = 4;
40
- cfg.tiles = 4;
41
- cfg.compress_unique = true;
42
- // cfg.treemap = true;
43
- // cfg.connectivity_mode = Contrek::Connectivity::OMNIDIRECTIONAL;
44
-
45
- CpuTimer cpu_timer;
46
- cpu_timer.start();
47
- std::cout << "--- Start Native Benchmark ---" << std::endl;
48
- // auto result = Contrek::trace("../images/graphs_1024x1024.png", cfg);
49
- auto result = Contrek::trace("../images/sample_10240x10240.png", cfg);
50
- result->print_info();
51
- std::cout << "Found polygons: " << result->groups << std::endl;
52
- std::cout << "Time: " << cpu_timer.stop() << " ms" << std::endl;
53
-
54
- // run_test();
55
- return 0;
56
- }