contrek 1.0.7 → 1.0.8

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +1 -1
  5. data/ext/cpp_polygon_finder/PolygonFinder/Makefile +4 -4
  6. data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +5 -4
  7. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp +3 -5
  8. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +1 -1
  9. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +2 -2
  10. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.h +1 -1
  11. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +28 -8
  12. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +1 -0
  13. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +11 -1
  14. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Hub.cpp +5 -5
  15. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Hub.h +11 -7
  16. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +2 -0
  17. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +108 -66
  18. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +5 -3
  19. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +33 -28
  20. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +5 -1
  21. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Position.cpp +2 -3
  22. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queueable.h +15 -20
  23. data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +1 -1
  24. data/lib/contrek/bitmaps/rgb_color.rb +0 -15
  25. data/lib/contrek/bitmaps/rgb_cpp_color.rb +10 -0
  26. data/lib/contrek/finder/concurrent/cluster.rb +2 -2
  27. data/lib/contrek/finder/concurrent/cursor.rb +32 -15
  28. data/lib/contrek/finder/concurrent/finder.rb +12 -1
  29. data/lib/contrek/finder/concurrent/hub.rb +2 -2
  30. data/lib/contrek/finder/concurrent/part.rb +6 -1
  31. data/lib/contrek/finder/concurrent/partitionable.rb +61 -33
  32. data/lib/contrek/finder/concurrent/polyline.rb +44 -2
  33. data/lib/contrek/finder/concurrent/queueable.rb +12 -20
  34. data/lib/contrek/version.rb +1 -1
  35. data/lib/contrek.rb +4 -3
  36. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0d1a364ff39a11f632f5f57d99970448173727e8f5a42e84e1b334521c1e1d1
4
- data.tar.gz: 24a52785c8735e9fce60931a1ffc1bd974aa5a50a6fb755229cd25dd5263cab3
3
+ metadata.gz: 862b863c602031251fedc2dc18a59c9bd270d91acff25761c24164e34e8952e9
4
+ data.tar.gz: 97f18e804accd2d6ae11f95223c95eebe198eafb07c745261d1110f5edf5eae4
5
5
  SHA512:
6
- metadata.gz: e321b716a5c16d6c75f24bc6bcdebc302a6ea9785009bad93905321ebc7a97ef29048fb1c6e4e0557611ec9d096dee874bd1ff37f66ad2763c4dacd7176b0ce4
7
- data.tar.gz: 38608a89ed8ee0c43a985bf8057d07b860acd524d2e8c0022651e5ec3fdcc8f13e3d515e309d436ad261203e00a58b901dd6cf141bfbeeb93520f7937ccdb4b8
6
+ metadata.gz: d3d9ce7c0b5ae1a3c62f91480d63856312d89334f181899ccc2ff304c3fef983578838484da18f865ce1d50ea3cb3c291ce49fef1b01dcfaf397ab688bed9e37
7
+ data.tar.gz: c8f7aade05577e132829e7ffcc6af5e637aa879bb6972f4ba8566c8db7d11b641862e32a170c39db4b8ab57666996a4639ffd3f516ba85712266b7ffd82ac276
data/CHANGELOG.md CHANGED
@@ -23,3 +23,9 @@ All notable changes to this project will be documented in this file.
23
23
  ## [1.0.7] - 2026-01-10
24
24
  ### Added
25
25
  - Optimized C++/Ruby data transfer: Implemented NumPy-compatible binary serialization for coordinate outputs. This reduces serialization overhead and significantly increases data throughput between the C++ core and the Ruby interface.
26
+
27
+ ## [1.0.8] - 2026-01-25
28
+ ### Changed
29
+ - Fixed ARGB/RGBA format discrepancies between Ruby and C++.
30
+ - Updated the multithreading-side algorithm for rejoining mono-connected orphan polygons to other orphan polygons. The algorithm can now identify them and defer their processing within the pipeline.
31
+ - Improved, on the multithreading side, the polygon intersection detection mechanism; the geometric approach has been dropped in favor of a purely topological one.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contrek (1.0.7)
4
+ contrek (1.0.8)
5
5
  chunky_png (~> 1.4)
6
6
  concurrent-ruby (~> 1.3.5)
7
7
  rice (= 4.5.0)
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Contrek
2
- Contrek is a Ruby gem with a C++ core for fast contour tracing and edge detection in PNG images. It extracts polygonal contours from bitmap shapes, enabling image processing, shape analysis, and raster-to-vector workflows such as PNG to SVG conversion. Manages png images usign libspng (version 0.7.4) library. May work multithreading.
2
+ Contrek is a Ruby gem with a C++ core for fast contour tracing and edge detection in PNG images. Unlike standard geometric tracers, 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
  ## About Contrek library
5
5
  Contrek (**con**tour **trek**king) simply scans your png bitmap and returns shape contour as close polygonal lines, both for the external and internal sides. It can compute the nesting level of the polygons found with a tree structure. It supports various levels and modes of compression and approximation of the found coordinates. It is capable of multithreaded processing, splitting the image into vertical strips and recombining the coordinates in pairs.
@@ -2,15 +2,15 @@ CXX = g++
2
2
  CXXFLAGS = -Wall -Wextra -std=c++17 -Isrc -MMD -MP -pthread -g \
3
3
  -fno-omit-frame-pointer -fsanitize=address,undefined \
4
4
  -O0
5
- LDFLAGS = -lpng -pthread -lz -fsanitize=address,undefined
5
+ LDFLAGS = -pthread -lz -fsanitize=address,undefined
6
6
 
7
7
  # valgrinf or perf setup
8
- #CXXFLAGS = -Wall -Wextra -std=c++17 -Isrc -MMD -MP -pthread -g -fno-omit-frame-pointer -march=native -DNDEBUG -O3
9
- #LDFLAGS = -lpng -pthread -lz
8
+ # CXXFLAGS = -Wall -Wextra -std=c++17 -Isrc -MMD -MP -pthread -g -fno-omit-frame-pointer -march=native -DNDEBUG -O3
9
+ # LDFLAGS = -lpng -pthread -lz
10
10
 
11
11
  # best performances
12
12
  CXXFLAGS = -Wall -Wextra -std=c++17 -Isrc -MMD -MP -pthread -g -march=native -DNDEBUG -Ofast -flto
13
- LDFLAGS = -lpng -pthread -lz -flto=auto
13
+ LDFLAGS = -pthread -lz -flto=auto
14
14
 
15
15
  SRC = $(shell find src -name '*.cpp')
16
16
  C_SRC = $(shell find src -name 'spng.c')
@@ -107,7 +107,7 @@ void Tests::test_c()
107
107
  Point* p2 = new Point({2, 2});
108
108
  Point* p3 = new Point({3, 3});
109
109
 
110
- Hub* hub = new Hub(4, 4); // coordinate 0 -> 3 sia x che y, matrice di 4 * 4
110
+ Hub* hub = new Hub(4, 0, 3);
111
111
 
112
112
  Position* pos1 = new Position(hub, p1);
113
113
  Position* pos2 = new Position(hub, p2);
@@ -176,15 +176,16 @@ void Tests::test_e()
176
176
  cpu_timer.start();
177
177
  FastPngBitmap png_bitmap("images/sample_10240x10240.png");
178
178
  // FastPngBitmap png_bitmap("images/sample_1024x1024.png");
179
- std::cout << "immagine =" << cpu_timer.stop() << std::endl;
179
+ std::cout << "image reading time =" << cpu_timer.stop() << std::endl;
180
180
 
181
- int color = png_bitmap.value_at(0, 0);
182
- std::cout << "color =" << color << std::endl;
181
+ int color = png_bitmap.rgb_value_at(0, 0);
182
+ std::cout << "color = " << color << std::endl;
183
183
  RGBNotMatcher not_matcher(color);
184
184
 
185
185
  std::vector<std::string> arguments = {"--versus=a", "--compress_uniq", "--number_of_tiles=2"};
186
186
  Finder pl(2, &png_bitmap, &not_matcher, &arguments);
187
187
  ProcessResult *o = pl.process_info();
188
188
  o->print_info();
189
+ std::cout << "polygons =" << o->groups << std::endl;
189
190
  delete o;
190
191
  }
@@ -70,12 +70,10 @@ char FastPngBitmap::value_at(int x, int y) {
70
70
  return(0);
71
71
  }
72
72
 
73
+ // source image format RGBA => returning uint ABGR
73
74
  unsigned int FastPngBitmap::rgb_value_at(int x, int y) {
74
- int32_t index = ((y * width) + x) * 4;
75
- unsigned int color;
76
- unsigned char *red = &image[index];
77
- std::memcpy(&color, red, 3);
78
- return(color);
75
+ uint32_t index = (uint32_t(y) * width + x) * 4;
76
+ return *reinterpret_cast<const uint32_t*>(&image[index]);
79
77
  }
80
78
 
81
79
  const unsigned char* FastPngBitmap::get_row_ptr(int y) const {
@@ -89,7 +89,7 @@ void PolygonFinder::scan() {
89
89
  if (rgb_m) run_loop(rgb_m, fetcher);
90
90
  else run_loop(this->matcher, fetcher);
91
91
  } else {
92
- auto fetcher = [](const unsigned char* p) { return p[0] | (p[1] << 8) | (p[2] << 16); };
92
+ auto fetcher = [](const unsigned char* p) { return *reinterpret_cast<const uint32_t*>(p); };
93
93
  if (rgb_m) run_loop(rgb_m, fetcher);
94
94
  else run_loop(this->matcher, fetcher);
95
95
  }
@@ -16,10 +16,10 @@
16
16
  #include "Cursor.h"
17
17
  #include "../../CpuTimer.h"
18
18
 
19
- Cluster::Cluster(Finder *finder, int height, int width)
19
+ Cluster::Cluster(Finder *finder, int height, int start_x, int end_x)
20
20
  : finder(finder)
21
21
  { tiles_.reserve(2); // only two (left|right)
22
- this->hub_ = new Hub(height, width);
22
+ this->hub_ = new Hub(height, start_x, end_x);
23
23
  }
24
24
 
25
25
  Cluster::~Cluster() {
@@ -20,7 +20,7 @@ class Cluster {
20
20
  std::vector<Tile*> tiles_;
21
21
  Hub *hub_ = nullptr;
22
22
  public:
23
- Cluster(Finder *finder, int height, int width);
23
+ Cluster(Finder *finder, int height, int start_x, int end_x);
24
24
  virtual ~Cluster();
25
25
  void add(Tile* tile);
26
26
  Tile* merge_tiles();
@@ -159,13 +159,36 @@ std::vector<Sequence*> Cursor::join_inners(Sequence* outer_seq) {
159
159
  }
160
160
  }
161
161
 
162
+ std::vector<Shape*> to_delay;
163
+ to_delay = connect_missings(missing_shapes);
164
+ while (!to_delay.empty()) {
165
+ to_delay = connect_missings(to_delay);
166
+ }
167
+
168
+ retme = collect_inner_sequences(outer_seq);
169
+ for (Polyline* polyline : polylines_sequence) {
170
+ polyline->turn_on(Polyline::TRACKED_INNER);
171
+ }
172
+ return(retme);
173
+ }
174
+
175
+ std::vector<Shape*> Cursor::connect_missings(std::vector<Shape*> missing_shapes) {
176
+ std::vector<Shape*> delay_shapes;
177
+
162
178
  for (Polyline *polyline : polylines_sequence)
163
179
  { for (Shape *missing_shape : missing_shapes)
164
180
  { Polyline* outer_polyline = missing_shape->outer_polyline;
165
- if (!polyline->vert_intersect(*outer_polyline)) continue;
166
- std::vector<Point*> intersection = polyline->intersection(outer_polyline);
181
+ if ( outer_polyline->is_on(Polyline::TRACKED_OUTER) ||
182
+ !polyline->vert_intersect(*outer_polyline)) continue;
183
+ std::vector<std::pair<int, int>> intersection = polyline->intersection(outer_polyline);
167
184
  if (intersection.size() > 0)
168
- { auto [inject_sequences_left, inject_sequences_right] = polyline->sew(intersection, outer_polyline);
185
+ { auto result = polyline->sew(intersection, outer_polyline);
186
+ if (!result) {
187
+ delay_shapes.push_back(missing_shape);
188
+ continue;
189
+ }
190
+ auto& inject_sequences_left = result->first;
191
+ auto& inject_sequences_right = result->second;
169
192
  auto combined = combine(inject_sequences_right, inject_sequences_left);
170
193
  for (auto& sewn_sequence : combined) {
171
194
  std::vector<Point*> unique;
@@ -205,11 +228,8 @@ std::vector<Sequence*> Cursor::join_inners(Sequence* outer_seq) {
205
228
  }
206
229
  }
207
230
  }
208
- retme = collect_inner_sequences(outer_seq);
209
- for (Polyline* polyline : polylines_sequence) {
210
- polyline->turn_on(Polyline::TRACKED_INNER);
211
- }
212
- return(retme);
231
+
232
+ return(delay_shapes);
213
233
  }
214
234
 
215
235
  std::vector<Sequence*> Cursor::collect_inner_sequences(Sequence* outer_seq) {
@@ -43,4 +43,5 @@ class Cursor {
43
43
  void traverse_inner(Part* act_part, std::vector<Part*> &all_parts, Bounds& bounds);
44
44
  std::vector<Point*> duplicates_intersection(const Part& part_a, const Part& part_b);
45
45
  std::vector<std::vector<Point*>> combine(std::vector<std::vector<Point*>>& seqa, std::vector<std::vector<Point*>>& seqb);
46
+ std::vector<Shape*> connect_missings(std::vector<Shape*> missing_shapes);
46
47
  };
@@ -86,7 +86,17 @@ void Finder::process_tiles() {
86
86
 
87
87
  if (it != arriving_tiles.end()) {
88
88
  Tile* twin_tile = *it;
89
- Cluster *cluster = new Cluster(this, this->bitmap->h(), this->bitmap->w());
89
+ int start_x, end_x;
90
+ if (twin_tile->start_x() == (tile->end_x() - 1)) {
91
+ start_x = tile->start_x();
92
+ end_x = twin_tile->end_x();
93
+ } else {
94
+ start_x = twin_tile->start_x();
95
+ end_x = tile->end_x();
96
+ }
97
+
98
+ Cluster *cluster = new Cluster(this, this->bitmap->h(), start_x, end_x);
99
+
90
100
  if (twin_tile->start_x() == (tile->end_x() - 1)) {
91
101
  cluster->add(tile);
92
102
  cluster->add(twin_tile);
@@ -9,15 +9,15 @@
9
9
  #include "Hub.h"
10
10
  #include <cstring>
11
11
 
12
- Hub::Hub(int height, int width)
13
- : width_(width), height_(height)
14
- { size_t total_pixels = static_cast<size_t>(width) * height;
12
+ Hub::Hub(int height, int start_x, int end_x)
13
+ : width_(end_x - start_x), height_(height), start_x_(start_x)
14
+ { size_t total_pixels = static_cast<size_t>(width_) * height;
15
15
  payloads_.resize(total_pixels);
16
16
  bitset_.resize((total_pixels + 63) / 64, 0);
17
17
  std::memset(bitset_.data(), 0, bitset_.size() * sizeof(uint64_t));
18
18
  }
19
19
 
20
- EndPoint* Hub::spawn_end_point() {
20
+ int Hub::spawn_end_point() {
21
21
  endpoint_pool_.emplace_back();
22
- return &endpoint_pool_.back();
22
+ return static_cast<int>(endpoint_pool_.size() - 1);
23
23
  }
@@ -14,16 +14,19 @@
14
14
  class EndPoint;
15
15
  class Hub {
16
16
  public:
17
- explicit Hub(int height, int width);
18
- EndPoint* spawn_end_point();
17
+ explicit Hub(int height, int start_x, int end_x);
18
+ int spawn_end_point();
19
19
  const int width() const { return width_; }
20
- inline EndPoint* get(int key) const {
20
+ const int start_x() const { return start_x_; }
21
+ inline EndPoint* get(int key) {
21
22
  if (!is_set(key)) return nullptr;
22
- return payloads_[key];
23
+ int index = payloads_[key];
24
+ return &endpoint_pool_[index];
23
25
  }
24
- inline void put(int key, EndPoint* payload) {
25
- payloads_[key] = payload;
26
+ inline EndPoint* put(int key, int index) {
27
+ payloads_[key] = index;
26
28
  bitset_[key >> 6] |= (1ULL << (key & 63));
29
+ return &endpoint_pool_[index];
27
30
  }
28
31
  inline bool is_set(int key) const {
29
32
  return (bitset_[key >> 6] & (1ULL << (key & 63)));
@@ -32,7 +35,8 @@ class Hub {
32
35
  private:
33
36
  int width_;
34
37
  int height_;
35
- std::vector<EndPoint*> payloads_;
38
+ int start_x_;
39
+ std::vector<int> payloads_;
36
40
  std::deque<EndPoint> endpoint_pool_;
37
41
  Hub(const Hub&) = delete;
38
42
  Hub& operator=(const Hub&) = delete;
@@ -27,6 +27,7 @@ class Part : public Queueable<Point> {
27
27
  bool is(Types type);
28
28
  bool inverts = false;
29
29
  bool trasmuted = false;
30
+ bool delayed = false;
30
31
  Part* next = nullptr;
31
32
  Part* prev = nullptr;
32
33
  Part* circular_next = nullptr;
@@ -40,6 +41,7 @@ class Part : public Queueable<Point> {
40
41
  const bool touched() const { return touched_; }
41
42
  void touch();
42
43
  bool intersect_part(Part* other_part);
44
+ void set_polyline(Polyline* polyline) { this->polyline_ = polyline; }
43
45
 
44
46
  private:
45
47
  bool touched_ = false;
@@ -9,6 +9,7 @@
9
9
  #include <vector>
10
10
  #include <utility>
11
11
  #include <iostream>
12
+ #include <algorithm>
12
13
  #include "Partitionable.h"
13
14
  #include "Polyline.h"
14
15
  #include "Sequence.h"
@@ -31,6 +32,17 @@ void Partitionable::add_part(Part* new_part)
31
32
  new_part->circular_next = this->parts_.front();
32
33
  }
33
34
 
35
+ void Partitionable::insert_after(Part* part, Part* new_part) {
36
+ auto it = std::find(parts_.begin(), parts_.end(), part);
37
+ if (it != parts_.end()) parts_.insert(it + 1, new_part);
38
+ new_part->prev = part;
39
+ new_part->next = part->next;
40
+ new_part->circular_next = part->next;
41
+ if (part->next) part->next->prev = new_part;
42
+ part->next = new_part;
43
+ part->circular_next = new_part;
44
+ }
45
+
34
46
  void Partitionable::partition()
35
47
  { this->parts_.clear();
36
48
  Polyline *polyline = dynamic_cast<Polyline*>(this);
@@ -73,85 +85,115 @@ Part* Partitionable::find_first_part_by_position(Position* position) {
73
85
  return(nullptr);
74
86
  }
75
87
 
76
- std::pair<
77
- std::vector<std::vector<Point*>>,
78
- std::vector<std::vector<Point*>>
79
- >
80
- Partitionable::sew(std::vector<Point*> intersection, Polyline* other)
81
- { std::vector<int> matching_part_indexes;
82
- for (int i = 0; i < static_cast<int>(parts_.size()); ++i) {
83
- Part* part = parts_[i];
84
- if (!part || part->trasmuted) continue;
85
- if (part->intersection_with_array(intersection)) {
86
- matching_part_indexes.push_back(i);
87
- }
88
- }
88
+ std::optional<SewReturnData> Partitionable::sew(std::vector<std::pair<int, int>> intersection, Polyline* other)
89
+ { std::vector<int> matching_part_indexes;
89
90
  std::vector<int> other_matching_part_indexes;
90
- for (int i = 0; i < static_cast<int>(other->parts_.size()); ++i) {
91
- Part* part = other->parts_[i];
92
- if (!part || part->trasmuted) continue;
93
- if (part->intersection_with_array(intersection)) {
94
- other_matching_part_indexes.push_back(i);
95
- }
91
+ matching_part_indexes.reserve(intersection.size());
92
+ other_matching_part_indexes.reserve(intersection.size());
93
+
94
+ // traspose
95
+ for (const auto& pair : intersection) {
96
+ matching_part_indexes.push_back(pair.first);
97
+ other_matching_part_indexes.push_back(pair.second);
96
98
  }
97
- std::vector<Part*> before_parts;
98
- for (int i = other_matching_part_indexes.back() + 1;
99
- i < static_cast<int>(other->parts_.size()); ++i)
100
- { before_parts.push_back(other->parts_[i]);
99
+ std::sort(matching_part_indexes.begin(), matching_part_indexes.end());
100
+ std::sort(other_matching_part_indexes.begin(), other_matching_part_indexes.end());
101
+
102
+ auto start_it_before = other->parts_.begin() + other_matching_part_indexes.back() + 1;
103
+ auto end_it_before = other->parts_.end();
104
+ std::vector<Part*> before_parts(start_it_before, end_it_before);
105
+ auto start_it_after = other->parts_.begin();
106
+ auto end_it_after = other->parts_.begin() + other_matching_part_indexes.front();
107
+ std::vector<Part*> after_parts(start_it_after, end_it_after);
108
+
109
+ int start_idx = matching_part_indexes.front();
110
+ int end_idx = matching_part_indexes.back();
111
+ Part* part_start = parts_[start_idx];
112
+ Part* part_end = parts_[end_idx];
113
+
114
+ auto collect_sequences = [&](const std::vector<int>& indices, std::vector<Part*>& p_list)
115
+ -> std::vector<std::vector<Point*>> {
116
+ std::vector<std::vector<Point*>> result;
117
+ int last_n = -1;
118
+ if (indices.size() < 2) return result;
119
+ for (int n = indices.front() + 1; n < indices.back(); ++n) {
120
+ if (std::find(indices.begin(), indices.end(), n) == indices.end()) {
121
+ Part* part = p_list[n];
122
+ if (part->is(Part::SEAM) && part->size > 0 && !part->delayed) {
123
+ part->delayed = true;
124
+ result.clear();
125
+ result.push_back({nullptr});
126
+ return result;
127
+ }
128
+ if (last_n == (n - 1) && !result.empty()) {
129
+ std::vector<Point*> pts = part->to_vector();
130
+ result.back().insert(result.back().end(), pts.begin(), pts.end());
131
+ } else {
132
+ result.push_back(part->to_vector());
133
+ }
134
+ last_n = n;
135
+ }
136
+ }
137
+ return result;
138
+ };
139
+ auto left = collect_sequences(matching_part_indexes, this->parts_);
140
+ if (!left.empty() && !left[0].empty() && left[0][0] == nullptr) {
141
+ return std::nullopt;
101
142
  }
102
- std::vector<Part*> after_parts;
103
- for (int i = 0; i < other_matching_part_indexes.front(); ++i) {
104
- after_parts.push_back(other->parts_[i]);
143
+ auto right = collect_sequences(other_matching_part_indexes, other->parts_);
144
+ if (!right.empty() && !right[0].empty() && right[0][0] == nullptr) {
145
+ return std::nullopt;
105
146
  }
106
- Part* part_start = parts_[matching_part_indexes.front()];
107
- Part* part_end = parts_[matching_part_indexes.back()];
108
- // They are inverted since they traverse in opposite directions
109
- Sequence sequence;
110
- sequence.add(part_start->head);
111
- for (Part* p : before_parts) sequence.append(*p);
112
- for (Part* p : after_parts) sequence.append(*p);
113
- if (part_end->tail) sequence.add(part_end->tail);
114
- part_start->replace(sequence);
115
- part_start->type = Part::EXCLUSIVE;
116
- if (part_start != part_end) part_end->reset();
117
-
118
- std::vector<std::vector<Point*>> left;
119
-
120
- for (int n = matching_part_indexes.front() + 1;
121
- n <= matching_part_indexes.back() - 1;
122
- ++n)
123
- { if (std::find(matching_part_indexes.begin(),
124
- matching_part_indexes.end(),
125
- n) == matching_part_indexes.end())
126
- { auto pts = parts_[n]->to_vector();
127
- left.push_back(pts);
147
+
148
+ if (part_start != part_end) {
149
+ for (int n = end_idx - 1; n > start_idx; --n) {
150
+ Part* delete_part = parts_[n];
151
+ // Topological detachment of pointers
152
+ if (delete_part->prev) delete_part->prev->next = delete_part->next;
153
+ if (delete_part->next) delete_part->next->prev = delete_part->prev;
154
+ parts_.erase(parts_.begin() + n);
128
155
  }
129
156
  }
130
157
 
131
- // delete parts in reverse order
132
- for (int n = matching_part_indexes.back() - 1;
133
- n >= matching_part_indexes.front() + 1;
134
- --n)
135
- { Part* delete_part = parts_[n];
136
- if (delete_part->prev) delete_part->prev->next = delete_part->next;
137
- if (delete_part->next) delete_part->next->prev = delete_part->prev;
138
- parts_.erase(parts_.begin() + n);
158
+ std::vector<Part*> all_parts;
159
+ Polyline* polyline = dynamic_cast<Polyline*>(this);
160
+ all_parts.reserve(before_parts.size() + after_parts.size());
161
+ all_parts.insert(all_parts.end(), before_parts.begin(), before_parts.end());
162
+ all_parts.insert(all_parts.end(), after_parts.begin(), after_parts.end());
163
+ Part* will_be_last = all_parts.empty() ? nullptr : all_parts.back();
164
+ for (auto it = all_parts.rbegin(); it != all_parts.rend(); ++it) {
165
+ Part* p = *it;
166
+ this->insert_after(part_start, p);
167
+ auto& op = other->parts_;
168
+ op.erase(std::remove(op.begin(), op.end(), p), op.end());
169
+ p->set_polyline(polyline);
139
170
  }
140
171
 
141
- std::vector<std::vector<Point*>> right;
142
- for (int n = other_matching_part_indexes.front() + 1;
143
- n <= other_matching_part_indexes.back() - 1;
144
- ++n)
145
- { if (std::find(other_matching_part_indexes.begin(),
146
- other_matching_part_indexes.end(),
147
- n) == other_matching_part_indexes.end())
148
- { auto pts = other->parts_[n]->to_vector();
149
- right.push_back(pts);
172
+ part_start->type = Part::EXCLUSIVE;
173
+ PartPool& pool = polyline->tile->cluster->parts_pool;
174
+ Part* new_end_part = pool.acquire(Part::EXCLUSIVE, polyline);
175
+ new_end_part->add(part_end->tail);
176
+ part_start->singleton();
177
+
178
+ // deletes part_end
179
+ if (part_start != part_end) {
180
+ if (part_end->prev) part_end->prev->next = part_end->next;
181
+ if (part_end->next) part_end->next->prev = part_end->prev;
182
+ auto it = std::find(parts_.begin(), parts_.end(), part_end);
183
+ if (it != parts_.end()) {
184
+ parts_.erase(it);
150
185
  }
151
186
  }
152
- return { left, right };
187
+
188
+ Part* reference_part = (will_be_last != nullptr) ? will_be_last : part_start;
189
+ this->insert_after(reference_part, new_end_part);
190
+
191
+ polyline->reset_tracked_endpoints();
192
+
193
+ return std::make_pair(left, right);
153
194
  }
154
195
 
196
+
155
197
  void Partitionable::trasmute_parts()
156
198
  { std::vector<Part*> insides;
157
199
  for (Part* p : parts_) {
@@ -9,22 +9,24 @@
9
9
  #pragma once
10
10
  #include <vector>
11
11
  #include <utility>
12
+ #include <optional>
12
13
  #include "Part.h"
13
14
 
15
+ using SewReturnData = std::pair<std::vector<std::vector<Point*>>, std::vector<std::vector<Point*>>>;
16
+
14
17
  class Partitionable {
15
18
  public:
16
19
  explicit Partitionable() {}
17
20
  virtual ~Partitionable() = default;
18
21
  void partition();
19
22
  Part* find_first_part_by_position(Position* position);
20
- std::pair<
21
- std::vector<std::vector<Point*>>,
22
- std::vector<std::vector<Point*>>> sew(std::vector<Point*> intersection, Polyline* other);
23
+ std::optional<SewReturnData> sew(std::vector<std::pair<int, int>> intersection, Polyline* other);
23
24
 
24
25
  protected:
25
26
  std::vector<Part*> parts_;
26
27
 
27
28
  private:
28
29
  void add_part(Part* new_part);
30
+ void insert_after(Part* part, Part* new_part);
29
31
  void trasmute_parts();
30
32
  };
@@ -48,6 +48,10 @@ bool Polyline::boundary() {
48
48
  return( tile->tg_border(Point{min_x, 0}) || tile->tg_border(Point{max_x, 0}));
49
49
  }
50
50
 
51
+ void Polyline::reset_tracked_endpoints() {
52
+ tracked_endpoints.clear();
53
+ }
54
+
51
55
  void Polyline::find_boundary() {
52
56
  if (raw_.empty()) return;
53
57
  min_x = std::numeric_limits<int>::max();
@@ -65,40 +69,41 @@ void Polyline::find_boundary() {
65
69
  }
66
70
  }
67
71
 
68
- struct PointHash {
69
- size_t operator()(const Point* p) const {
70
- if (!p) return 0;
71
- std::hash<double> hasher;
72
- size_t h1 = hasher(p->x);
73
- size_t h2 = hasher(p->y);
74
- return h1 ^ (h2 << 1);
75
- }
76
- };
77
-
78
- struct PointEqual {
79
- bool operator()(const Point* p1, const Point* p2) const {
80
- if (!p1 || !p2) return p1 == p2;
81
- return *p1 == *p2;
72
+ std::vector<std::pair<int, int>> Polyline::intersection(const Polyline* other) const {
73
+ if (this->tracked_endpoints.empty()) {
74
+ for (int i = 0; i < parts_.size(); ++i) {
75
+ auto& part = parts_[i];
76
+ if (!part->is(Part::SEAM) && part->trasmuted) continue;
77
+ part->each([&](QNode<Point>* pos) -> bool {
78
+ Position *position = dynamic_cast<Position*>(pos);
79
+ this->tracked_endpoints[position->end_point()] = i;
80
+ return true;
81
+ });
82
+ }
82
83
  }
83
- };
84
84
 
85
- std::vector<Point*> Polyline::intersection(const Polyline* other) const {
86
- std::vector<Point*> result;
87
- if (!other) return result;
88
- std::unordered_set<Point*, PointHash, PointEqual> other_set;
89
- other_set.reserve(other->raw().size());
90
- for (Point* p : other->raw()) {
91
- if (p) other_set.insert(p);
92
- }
93
- for (Point* p : raw_) {
94
- if (!p) continue;
95
- if (other_set.count(p)) {
96
- result.push_back(p);
85
+ std::vector<std::pair<int, int>> matching_parts;
86
+ for (int j = 0; j < other->parts_.size(); ++j) {
87
+ auto& other_part = other->parts_[j];
88
+ if (!other_part->is(Part::SEAM) && other_part->trasmuted) {
89
+ continue;
97
90
  }
91
+ other_part->each([&](QNode<Point>* pos) -> bool {
92
+ Position *position = dynamic_cast<Position*>(pos);
93
+ auto it = this->tracked_endpoints.find(position->end_point());
94
+ if (it != this->tracked_endpoints.end()) {
95
+ int self_index = it->second;
96
+ matching_parts.push_back({self_index, j});
97
+ return false;
98
+ }
99
+ return true;
100
+ });
98
101
  }
99
- return result;
102
+
103
+ return matching_parts;
100
104
  }
101
105
 
106
+
102
107
  void Polyline::clear() {
103
108
  this->raw_.clear();
104
109
  }
@@ -10,7 +10,9 @@
10
10
  #include <list>
11
11
  #include <cstdint>
12
12
  #include <optional>
13
+ #include <utility>
13
14
  #include <vector>
15
+ #include <unordered_map>
14
16
  #include "Partitionable.h"
15
17
  #include "../RectBounds.h"
16
18
 
@@ -37,11 +39,12 @@ class Polyline : public Partitionable {
37
39
  std::vector<Point*> raw() const { return raw_; }
38
40
  const std::list<Shape*>& next_tile_eligible_shapes() const { return next_tile_eligible_shapes_; }
39
41
  const std::vector<Part*>& parts() const { return parts_; }
40
- std::vector<Point*> intersection(const Polyline* other) const;
42
+ std::vector<std::pair<int, int>> intersection(const Polyline* other) const;
41
43
  const int max_y() const { return max_y_; }
42
44
  void clear();
43
45
  bool is_empty();
44
46
  bool vert_intersect(Polyline& other);
47
+ void reset_tracked_endpoints();
45
48
 
46
49
  private:
47
50
  std::vector<Point*> raw_;
@@ -49,4 +52,5 @@ class Polyline : public Partitionable {
49
52
  void find_boundary();
50
53
  uint32_t flags_ = 0;
51
54
  std::list<Shape*> next_tile_eligible_shapes_;
55
+ mutable std::unordered_map<const EndPoint*, int> tracked_endpoints;
52
56
  };
@@ -11,11 +11,10 @@
11
11
 
12
12
  Position::Position(Hub* hub, Point* point)
13
13
  : QNode<Point>(point)
14
- { int key = point->y * hub->width() + point->x;
14
+ { int key = point->y * hub->width() + (point->x - hub->start_x());
15
15
  EndPoint* existing_ep = hub->get(key);
16
16
  if (existing_ep == nullptr)
17
- { end_point_ = hub->spawn_end_point();
18
- hub->put(key, end_point_);
17
+ { end_point_ = hub->put(key, hub->spawn_end_point());
19
18
  } else {
20
19
  end_point_ = existing_ep;
21
20
  }
@@ -79,9 +79,21 @@ class Queueable {
79
79
  head = nullptr;
80
80
  tail = nullptr;
81
81
  _iterator = nullptr;
82
+ _started = false;
82
83
  size = 0;
83
84
  }
84
85
 
86
+ void singleton()
87
+ { if (head && head->next) {
88
+ head->next->prev = nullptr;
89
+ head->next = nullptr;
90
+ }
91
+ tail = nullptr;
92
+ size = 1;
93
+ _iterator = nullptr;
94
+ _started = false;
95
+ }
96
+
85
97
  void replace(Queueable<T>& q) {
86
98
  reset();
87
99
  append(q);
@@ -130,8 +142,8 @@ class Queueable {
130
142
  }
131
143
 
132
144
  void next_of(QNode<T>* node) {
133
- if (!node) throw std::runtime_error("nil node");
134
- if (node->owner != this) throw std::runtime_error("wrong node");
145
+ // if (!node) throw std::runtime_error("nil node");
146
+ // if (node->owner != this) throw std::runtime_error("wrong node");
135
147
  _iterator = node->next;
136
148
  _started = true;
137
149
  }
@@ -181,23 +193,6 @@ class Queueable {
181
193
  return out;
182
194
  }
183
195
 
184
- bool intersection_with_array(const std::vector<T*>& array) {
185
- if (array.empty() || size == 0) return false;
186
-
187
- QNode<T>* current = head;
188
- while (current) {
189
- if (current->payload) {
190
- for (T* item : array) {
191
- if (item && *item == *(current->payload)) {
192
- return true;
193
- }
194
- }
195
- }
196
- current = current->next;
197
- }
198
- return false;
199
- }
200
-
201
196
  QNode<T>* pop() {
202
197
  if (!tail) return nullptr;
203
198
  return rem(tail);
@@ -220,7 +215,7 @@ class Queueable {
220
215
 
221
216
  for (T* current : source) {
222
217
  if (!result.empty() && *result.back() == *current) {
223
- result.pop_back(); // "Annulla" la coppia invece di ricominciare
218
+ result.pop_back();
224
219
  } else {
225
220
  result.push_back(current);
226
221
  }
@@ -267,7 +267,7 @@ void Init_cpp_polygon_finder() {
267
267
 
268
268
  Data_Type<RGBNotMatcher> rb_cRGBNotMatcher =
269
269
  define_class<RGBNotMatcher, Matcher>("CPPRGBNotMatcher")
270
- .define_constructor(Constructor<RGBNotMatcher, int>())
270
+ .define_constructor(Constructor<RGBNotMatcher, unsigned int>())
271
271
  .define_method("match", &RGBNotMatcher::match);
272
272
 
273
273
  Data_Type<PolygonFinder> rb_cPolygonFinder =
@@ -3,23 +3,8 @@ module Contrek
3
3
  class RgbColor
4
4
  attr_reader :raw
5
5
  def initialize(r:, g:, b:, a: 255)
6
- @r = r
7
- @g = g
8
- @b = b
9
- @a = a
10
6
  @raw = (r << 24) + (g << 16) + (b << 8) + a
11
7
  end
12
-
13
- def to_rgb_raw
14
- @raw >> 8
15
- end
16
-
17
- def self.reverse_raw(raw)
18
- [:a, :b, :g, :r].each_with_object({}) do |c, h|
19
- h[c] = raw & 0xFF
20
- raw >>= 8
21
- end
22
- end
23
8
  end
24
9
  end
25
10
  end
@@ -0,0 +1,10 @@
1
+ module Contrek
2
+ module Bitmaps
3
+ class RgbCppColor
4
+ attr_reader :raw
5
+ def initialize(r:, g:, b:, a: 255)
6
+ @raw = (a << 24) | (b << 16) | (g << 8) | r
7
+ end
8
+ end
9
+ end
10
+ end
@@ -3,10 +3,10 @@ module Contrek
3
3
  class Cluster
4
4
  attr_reader :tiles, :hub
5
5
 
6
- def initialize(finder:, height:, width:)
6
+ def initialize(finder:, height:, start_x:, end_x:)
7
7
  @finder = finder
8
8
  @tiles = []
9
- @hub = Hub.new(height:, width:)
9
+ @hub = Hub.new(start_x:, end_x:)
10
10
  end
11
11
 
12
12
  def add(tile)
@@ -54,34 +54,51 @@ module Contrek
54
54
  missing_shapes << shape
55
55
  end
56
56
  end
57
+ to_delay = connect_missings(missing_shapes)
58
+ while to_delay.any?
59
+ to_delay = connect_missings(to_delay)
60
+ end
61
+
62
+ retme = collect_inner_sequences(outer_seq)
63
+
64
+ @polylines_sequence.each do |polyline|
65
+ polyline.turn_on(Polyline::TRACKED_INNER)
66
+ end
67
+ retme
68
+ end
69
+
70
+ private
71
+
72
+ def connect_missings(missing_shapes)
73
+ delay_shapes = []
74
+
57
75
  @polylines_sequence.each do |polyline|
58
76
  missing_shapes.each do |missing_shape|
59
- outer_polyline = missing_shape.outer_polyline
60
- next unless polyline.vert_intersect?(outer_polyline)
61
- if (intersection = polyline.intersection(outer_polyline)).any?
62
- inject_sequences_left, inject_sequences_right = polyline.sew!(intersection, outer_polyline)
77
+ missing_outer_polyline = missing_shape.outer_polyline
78
+ next if missing_outer_polyline.on?(Polyline::TRACKED_OUTER) ||
79
+ !polyline.vert_intersect?(missing_outer_polyline)
80
+
81
+ if (intersection = polyline.intersection(missing_outer_polyline)).any?
82
+ inject_sequences_left, inject_sequences_right = polyline.sew!(intersection, missing_outer_polyline)
83
+ if inject_sequences_left.nil?
84
+ delay_shapes << missing_shape
85
+ next
86
+ end
63
87
  combine!(inject_sequences_right, inject_sequences_left).each do |sewn_sequence|
64
88
  sewn_sequence.uniq!
65
89
  @orphan_inners << sewn_sequence if sewn_sequence.size > 1 && sewn_sequence.map { |c| c[:x] }.uniq.size > 1 # segmenti non sono ammessi, solo aree
66
90
  end
67
- outer_polyline.clear!
68
- outer_polyline.turn_on(Polyline::TRACKED_OUTER)
69
- outer_polyline.turn_on(Polyline::TRACKED_INNER)
91
+ missing_outer_polyline.clear!
92
+ missing_outer_polyline.turn_on(Polyline::TRACKED_OUTER)
93
+ missing_outer_polyline.turn_on(Polyline::TRACKED_INNER)
70
94
  @orphan_inners += missing_shape.inner_polylines
71
95
  end
72
96
  end
73
97
  end
74
98
 
75
- retme = collect_inner_sequences(outer_seq)
76
-
77
- @polylines_sequence.each do |polyline|
78
- polyline.turn_on(Polyline::TRACKED_INNER)
79
- end
80
- retme
99
+ delay_shapes
81
100
  end
82
101
 
83
- private
84
-
85
102
  # rubocop:disable Lint/NonLocalExitFromIterator
86
103
  def traverse_outer(act_part, all_parts, polylines, shapes, outer_joined_polyline)
87
104
  all_parts << act_part if all_parts.last != act_part
@@ -98,7 +98,18 @@ module Contrek
98
98
  return
99
99
  end
100
100
  if (twin_tile = arriving_tiles.find { |b| (b.start_x == (tile.end_x - 1)) || ((b.end_x - 1) == tile.start_x) })
101
- cluster = Cluster.new(finder: self, height: bitmap.h, width: bitmap.w)
101
+
102
+ if twin_tile.start_x == (tile.end_x - 1)
103
+ start_x = tile.start_x
104
+ end_x = twin_tile.end_x
105
+ else
106
+ start_x = twin_tile.start_x
107
+ end_x = tile.end_x
108
+ end
109
+
110
+ # puts "start = #{start_x} end = #{end_x}"
111
+
112
+ cluster = Cluster.new(finder: self, height: bitmap.h, start_x:, end_x:)
102
113
  if twin_tile.start_x == (tile.end_x - 1)
103
114
  cluster.add(tile)
104
115
  cluster.add(twin_tile)
@@ -2,8 +2,8 @@ module Contrek
2
2
  module Concurrent
3
3
  class Hub
4
4
  attr_reader :payloads, :width
5
- def initialize(height:, width:)
6
- @width = width
5
+ def initialize(start_x:, end_x:)
6
+ @width = end_x - start_x
7
7
  # @payloads = Array.new(width * height)
8
8
  @payloads = {}
9
9
  end
@@ -8,7 +8,7 @@ module Contrek
8
8
  ADDED = 2
9
9
 
10
10
  attr_reader :polyline, :index, :touched
11
- attr_accessor :next, :circular_next, :prev, :type, :passes, :inverts, :trasmuted
11
+ attr_accessor :next, :circular_next, :prev, :type, :passes, :inverts, :trasmuted, :delayed
12
12
  def initialize(type, polyline)
13
13
  @type = type
14
14
  @polyline = polyline
@@ -19,12 +19,17 @@ module Contrek
19
19
  @touched = false
20
20
  @inverts = false
21
21
  @trasmuted = false
22
+ @delayed = false
22
23
  end
23
24
 
24
25
  def is?(type)
25
26
  @type == type
26
27
  end
27
28
 
29
+ def set_polyline(polyline)
30
+ @polyline = polyline
31
+ end
32
+
28
33
  def add_position(position)
29
34
  add(Position.new(position: position, hub: polyline.tile.cluster.hub))
30
35
  end
@@ -16,6 +16,15 @@ module Contrek
16
16
  new_part.prev = last
17
17
  end
18
18
 
19
+ def insert_after(part, new_part)
20
+ part_index = @parts.index(part)
21
+ @parts.insert(part_index + 1, new_part)
22
+ new_part.prev = part
23
+ new_part.next = new_part.circular_next = part.next
24
+ part.next.prev = new_part if part.next
25
+ part.next = part.circular_next = new_part
26
+ end
27
+
19
28
  def find_first_part_by_position(position)
20
29
  @parts.find do |part|
21
30
  part.is?(Part::SEAM) &&
@@ -57,48 +66,67 @@ module Contrek
57
66
  end
58
67
 
59
68
  def sew!(intersection, other)
60
- matching_part_indexes = []
61
- parts.each_with_index do |part, index|
62
- next if part.trasmuted
63
- matching_part_indexes << index if part.intersection_with_array?(intersection)
64
- end
65
- other_matching_part_indexes = []
66
- other.parts.each_with_index do |part, index|
67
- next if part.trasmuted
68
- other_matching_part_indexes << index if part.intersection_with_array?(intersection)
69
- end
70
- # other_matching_part_indexes and matching_part_indexes always contain at least one element
69
+ matching_part_indexes, other_matching_part_indexes = intersection.transpose.map(&:sort)
70
+ # other_matching_part_indexes and matching_part_indexes always must contain at least one element
71
71
  before_parts = other.parts[other_matching_part_indexes.last + 1..]
72
72
  after_parts = other_matching_part_indexes.first.zero? ? [] : other.parts[0..other_matching_part_indexes.first - 1]
73
73
  part_start = parts[matching_part_indexes.first]
74
74
  part_end = parts[matching_part_indexes.last]
75
75
 
76
- # They are inverted since they traverse in opposite directions
77
- sequence = Sequence.new
78
- sequence.add part_start.head
79
- before_parts.each { |part| sequence.append(part) }
80
- after_parts.each { |part| sequence.append(part) }
81
- sequence.add part_end.tail if part_end.tail # nil when part_start == part_end
82
-
83
- part_start.replace!(sequence)
84
- part_start.type = Part::EXCLUSIVE
85
- part_end.reset! if part_start != part_end
76
+ # left and right side reduces will be combined and later converted into orphan inners sequences
77
+ returning_data = [[matching_part_indexes, parts], [other_matching_part_indexes, other.parts]].map do |matching_part_indexes, parts|
78
+ lastn = 0
79
+ result = []
80
+ (matching_part_indexes.first + 1).upto(matching_part_indexes.last - 1) do |n|
81
+ if matching_part_indexes.index(n).nil?
82
+ part = parts[n]
83
+ if part.is?(Part::SEAM) && part.size > 0 && !part.delayed # fallback, delays the shape
84
+ part.delayed = true
85
+ return nil
86
+ end
87
+ if (lastn == (n - 1)) && result.any?
88
+ result.last.concat part.to_a
89
+ else
90
+ result << part.to_a
91
+ end
92
+ lastn = n
93
+ end
94
+ end
95
+ result
96
+ end
86
97
 
87
- left = []
88
- (matching_part_indexes.first + 1).upto(matching_part_indexes.last - 1) do |n|
89
- left << parts[n].to_a if matching_part_indexes.index(n).nil?
98
+ if part_start != part_end
99
+ (matching_part_indexes.last - 1).downto(matching_part_indexes.first + 1) do |n|
100
+ delete_part = parts[n]
101
+ delete_part.prev.next = delete_part.next if delete_part.prev
102
+ delete_part.next.prev = delete_part.prev if delete_part.next
103
+ parts.delete_at(n)
104
+ end
90
105
  end
91
- (matching_part_indexes.last - 1).downto(matching_part_indexes.first + 1) do |n|
92
- delete_part = parts[n]
93
- delete_part.prev.next = delete_part.next if delete_part.prev
94
- delete_part.next.prev = delete_part.prev if delete_part.next
95
- parts.delete_at(n)
106
+
107
+ all_parts = before_parts + after_parts
108
+ will_be_last = all_parts.last
109
+ all_parts.reverse_each do |part|
110
+ insert_after(part_start, part)
111
+ other.parts.delete(part)
112
+ part.set_polyline(self)
96
113
  end
97
- right = []
98
- (other_matching_part_indexes.first + 1).upto(other_matching_part_indexes.last - 1) do |n|
99
- right << other.parts[n].to_a if other_matching_part_indexes.index(n).nil?
114
+
115
+ part_start.type = Part::EXCLUSIVE
116
+ new_end_part = Part.new(Part::EXCLUSIVE, self)
117
+ new_end_part.add(part_end.tail)
118
+ part_start.singleton! # reduce part to its head only
119
+
120
+ if part_start != part_end
121
+ part_end.prev.next = part_end.next if part_end.prev
122
+ part_end.next.prev = part_end.prev if part_end.next
123
+ parts.delete(part_end)
100
124
  end
101
- [left, right]
125
+ insert_after(will_be_last, new_end_part)
126
+
127
+ reset_tracked_endpoints!
128
+
129
+ returning_data
102
130
  end
103
131
 
104
132
  private
@@ -27,7 +27,15 @@ module Contrek
27
27
  end
28
28
 
29
29
  def inspect
30
- "#{self.class}[b#{@tile.name} S#{@name} #{"B" if boundary?}] (#{raw.count} => #{raw.inspect})"
30
+ "#{self.class}#{named} (#{raw.count} => #{raw.inspect})"
31
+ end
32
+
33
+ def named
34
+ "[b#{@tile.name} S#{@name} #{"B" if boundary?}]"
35
+ end
36
+
37
+ def numpy_raw
38
+ raw.flat_map { |p| [p[:x], p[:y]] }
31
39
  end
32
40
 
33
41
  def info
@@ -46,8 +54,35 @@ module Contrek
46
54
  (@flags & flag) != 0
47
55
  end
48
56
 
57
+ def reset_tracked_endpoints!
58
+ @tracked_endpoints = nil
59
+ end
60
+
61
+ # returns for every position of intersection an array composed by the indexes of parts (self,other) involved
62
+ # es [[1,3],[2,6],...]. The first time the sequence for self is computed is stored.
49
63
  def intersection(other)
50
- @raw & other.raw
64
+ if @tracked_endpoints.nil?
65
+ @tracked_endpoints = {} # memoize found sequence
66
+ parts.each_with_index do |part, part_index|
67
+ next if !part.is?(Part::SEAM) && part.trasmuted
68
+ part.each do |pos|
69
+ @tracked_endpoints[pos.end_point.object_id] = part_index
70
+ end
71
+ end
72
+ end
73
+ matching_parts = []
74
+ other.parts.each_with_index do |part, part_index|
75
+ next if !part.is?(Part::SEAM) && part.trasmuted
76
+ part.each do |pos|
77
+ if (self_index = @tracked_endpoints[pos.end_point.object_id])
78
+ matching_parts << [self_index, part_index]
79
+ false
80
+ else
81
+ true
82
+ end
83
+ end
84
+ end
85
+ matching_parts
51
86
  end
52
87
 
53
88
  def empty?
@@ -82,6 +117,13 @@ module Contrek
82
117
  !(@max_y < other.min_y || other.max_y < @min_y)
83
118
  end
84
119
 
120
+ def get_bounds
121
+ {min_x: @min_x,
122
+ max_x: @max_x,
123
+ min_y: @min_y,
124
+ max_y: @max_y}
125
+ end
126
+
85
127
  private
86
128
 
87
129
  def find_boundary
@@ -10,6 +10,16 @@ module Contrek
10
10
  @size = 0
11
11
  end
12
12
 
13
+ def singleton!
14
+ if @head&.next
15
+ @head.next.prev = nil
16
+ @head.next = nil
17
+ end
18
+ @tail = nil
19
+ @size = 1
20
+ @iterator = 0
21
+ end
22
+
13
23
  def rem(node)
14
24
  Raise "Not my node" if node.owner != self
15
25
 
@@ -84,12 +94,13 @@ module Contrek
84
94
  @iterator = 0
85
95
  end
86
96
 
97
+ # from yield: false => stop, true => continue
87
98
  def each(&block)
88
99
  last = nil
89
100
  unless @head.nil?
90
101
  pointer = @head
91
102
  loop do
92
- yield(pointer)
103
+ break unless yield(pointer)
93
104
  last = pointer
94
105
  break unless (pointer = pointer.next)
95
106
  end
@@ -142,25 +153,6 @@ module Contrek
142
153
  rem(@tail)
143
154
  end
144
155
 
145
- def intersection_with(queueable)
146
- int = []
147
- each do |node|
148
- int += queueable.map do |e|
149
- break [e.payload] if e.payload == node.payload
150
- end.compact
151
- end
152
- int
153
- end
154
-
155
- def intersection_with_array?(array)
156
- each { |node| return true if array.index(node.payload) }
157
- false
158
- end
159
-
160
- def intersect_with?(queueable)
161
- intersection_with(queueable).any?
162
- end
163
-
164
156
  def remove_adjacent_pairs(array = nil)
165
157
  array = to_a if array.nil?
166
158
  n = array.size
@@ -1,3 +1,3 @@
1
1
  module Contrek
2
- VERSION = "1.0.7"
2
+ VERSION = "1.0.8"
3
3
  end
data/lib/contrek.rb CHANGED
@@ -5,6 +5,7 @@ require "contrek/bitmaps/chunky_bitmap"
5
5
  require "contrek/bitmaps/png_bitmap"
6
6
  require "contrek/bitmaps/custom_bitmap"
7
7
  require "contrek/bitmaps/rgb_color"
8
+ require "contrek/bitmaps/rgb_cpp_color"
8
9
  require "contrek/finder/bounds"
9
10
  require "contrek/bitmaps/sample_generator"
10
11
  require "contrek/finder/list"
@@ -54,10 +55,10 @@ module Contrek
54
55
  private
55
56
 
56
57
  def compute_cpp(png_file_path, options)
57
- color = Bitmaps::RgbColor.new(**options[:color])
58
+ color = Bitmaps::RgbCppColor.new(**options[:color])
58
59
  png_bitmap = CPPPngBitMap.new(png_file_path)
59
60
  rgb_matcher_klass = (options[:class] == "value_not_matcher") ? CPPRGBNotMatcher : CPPRGBMatcher
60
- rgb_matcher = rgb_matcher_klass.new(color.to_rgb_raw)
61
+ rgb_matcher = rgb_matcher_klass.new(color.raw)
61
62
  if options.key?(:number_of_threads) || options[:finder]&.key?(:number_of_tiles)
62
63
  Contrek::Cpp::CPPConcurrentFinder.new(
63
64
  number_of_threads: options.dig(:number_of_threads) || 0,
@@ -74,7 +75,7 @@ module Contrek
74
75
  end
75
76
 
76
77
  def compute_ruby_pure(png_file_path, options)
77
- color = Bitmaps::RgbColor.new(**options[:color])
78
+ color = Bitmaps::RgbCppColor.new(**options[:color])
78
79
  png_bitmap = Bitmaps::PngBitmap.new(png_file_path)
79
80
  rgb_matcher = const_get("Contrek::Matchers::" + camelize("value_not_matcher")).new(color.raw)
80
81
  if options.key?(:number_of_threads) || options[:finder]&.key?(:number_of_tiles)
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.0.7
4
+ version: 1.0.8
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-01-10 00:00:00.000000000 Z
11
+ date: 2026-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -229,6 +229,7 @@ files:
229
229
  - lib/contrek/bitmaps/painting.rb
230
230
  - lib/contrek/bitmaps/png_bitmap.rb
231
231
  - lib/contrek/bitmaps/rgb_color.rb
232
+ - lib/contrek/bitmaps/rgb_cpp_color.rb
232
233
  - lib/contrek/bitmaps/sample_generator.rb
233
234
  - lib/contrek/cpp/cpp_concurrent_finder.rb
234
235
  - lib/contrek/finder/bounds.rb