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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/Makefile +4 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +5 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/FastPngBitmap.cpp +3 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +28 -8
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +11 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Hub.cpp +5 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Hub.h +11 -7
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Part.h +2 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.cpp +108 -66
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Partitionable.h +5 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +33 -28
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +5 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Position.cpp +2 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Queueable.h +15 -20
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +1 -1
- data/lib/contrek/bitmaps/rgb_color.rb +0 -15
- data/lib/contrek/bitmaps/rgb_cpp_color.rb +10 -0
- data/lib/contrek/finder/concurrent/cluster.rb +2 -2
- data/lib/contrek/finder/concurrent/cursor.rb +32 -15
- data/lib/contrek/finder/concurrent/finder.rb +12 -1
- data/lib/contrek/finder/concurrent/hub.rb +2 -2
- data/lib/contrek/finder/concurrent/part.rb +6 -1
- data/lib/contrek/finder/concurrent/partitionable.rb +61 -33
- data/lib/contrek/finder/concurrent/polyline.rb +44 -2
- data/lib/contrek/finder/concurrent/queueable.rb +12 -20
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +4 -3
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 862b863c602031251fedc2dc18a59c9bd270d91acff25761c24164e34e8952e9
|
|
4
|
+
data.tar.gz: 97f18e804accd2d6ae11f95223c95eebe198eafb07c745261d1110f5edf5eae4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
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.
|
|
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 = -
|
|
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 = -
|
|
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,
|
|
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 << "
|
|
179
|
+
std::cout << "image reading time =" << cpu_timer.stop() << std::endl;
|
|
180
180
|
|
|
181
|
-
int color = png_bitmap.
|
|
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, ¬_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
|
-
|
|
75
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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 (
|
|
166
|
-
|
|
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
|
|
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
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
|
13
|
-
: width_(
|
|
14
|
-
{ size_t total_pixels = static_cast<size_t>(
|
|
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
|
-
|
|
20
|
+
int Hub::spawn_end_point() {
|
|
21
21
|
endpoint_pool_.emplace_back();
|
|
22
|
-
return
|
|
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
|
|
18
|
-
|
|
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
|
-
|
|
20
|
+
const int start_x() const { return start_x_; }
|
|
21
|
+
inline EndPoint* get(int key) {
|
|
21
22
|
if (!is_set(key)) return nullptr;
|
|
22
|
-
|
|
23
|
+
int index = payloads_[key];
|
|
24
|
+
return &endpoint_pool_[index];
|
|
23
25
|
}
|
|
24
|
-
inline
|
|
25
|
-
payloads_[key] =
|
|
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
|
-
|
|
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<
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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::
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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<
|
|
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();
|
|
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
|
|
@@ -3,10 +3,10 @@ module Contrek
|
|
|
3
3
|
class Cluster
|
|
4
4
|
attr_reader :tiles, :hub
|
|
5
5
|
|
|
6
|
-
def initialize(finder:, height:,
|
|
6
|
+
def initialize(finder:, height:, start_x:, end_x:)
|
|
7
7
|
@finder = finder
|
|
8
8
|
@tiles = []
|
|
9
|
-
@hub = Hub.new(
|
|
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
|
-
|
|
60
|
-
next
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -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
|
-
|
|
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
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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}
|
|
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
|
-
@
|
|
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
|
data/lib/contrek/version.rb
CHANGED
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::
|
|
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.
|
|
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::
|
|
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.
|
|
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-
|
|
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
|